Uh oh!
There was an error while loading. Please reload this page.
test(spec): restore the compile-time half of the ISecurityService contract test (#7831) - #8139
Conversation
…tract test (#7831) `security-service.test.ts` advertised itself as the exhaustive gate on `ISecurityService` while omitting three REQUIRED members — `hasWriteBypass`, `resolveWriteScope` and `describeDelegableScope` — from both `makeService()`'s literal and the assertion list. The mismatch did not go red because the file held one entry in `test-typecheck-debt.json` (TS2322 at line 21), so the gate was itself suppressed: any future required member could be added with no test noticing. - Complete the stub with each method's own documented FAIL-CLOSED answer (`false`, `'own'`, and the empty `DelegableScope` plugin-security returns when no delegated-admin gate is wired) rather than a cast. - Make the member list exhaustive BY THE COMPILER in both directions: `satisfies readonly RequiredMember[]` rejects an optional member listed as required, and `UnlistedRequiredMember` rejects a required member left out. - Delete the file's `test-typecheck-debt.json` entry — ratchet 1 -> 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0123k4cam2jEAkPmbJeoaY3r
…urity-service-contract-test
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also reference the affected code. These are read-only:
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#7831
packages/spec/src/contracts/security-service.test.tsadvertised itself as the exhaustive gate onISecurityServicewhile omitting three required members —hasWriteBypass,resolveWriteScopeanddescribeDelegableScope— from bothmakeService()'s literal and the assertion list. It did not go red because the file held one entry intest-typecheck-debt.json(TS2322 at line 21), so the gate's compile-time half was asserted past a suppressed error. A gate that is itself type-suppressed does not gate.What changed (two files, exactly the card's scope)
1.
security-service.test.ts— completed, and made exhaustive by the compilerEach stub returns the method's own documented fail-closed answer, so the file stays a contract-honest minimal implementation rather than a cast. All three match what
plugin-security(the reference implementation) returns for the corresponding state:hasWriteBypassfalsemodifyAllRecordsbit only; a stub resolving no permission set holds no such bit — the samefalsethe implementation returns for a principal-less context, an on-behalf-of context, and any resolution failure.resolveWriteScope'own''org': there'org'means either a genuine Modify-All holder or the fail-OPEN "no permission set mentions this object" default, so it is the one value the contract says a caller may not trust on its own.describeDelegableScopeisTenantAdmin: false+ three empty listssecurity-plugin.tsreturns when no delegated-admin gate is wired.The member list is now exhaustive in both directions, by the compiler, using the idiom the sibling
sharing-service.test.tsalready carries (itsRequiredKeysmapped type) rather than a new framework:as const satisfies readonly RequiredMember[]rejects an optional member listed as required — the cheap way to make a red go away without implementing anything.UnlistedRequiredMember(anExcludeof the list from the required-key union) rejects a required member left out — the drift that produced this card.2.
test-typecheck-debt.json— ratchet 1 to 0The file's entry is deleted, not lowered. Measured:
56 file(s) / 264 error(s)before,55 file(s) / 263 error(s)after; the gate reports OK on both sides of the change.Verification, including the reverse direction
Predicted before running, then observed: injecting a new required member into
ISecurityServicemust red two places, and a new optional member must red none.That is the card's claim proven end to end: a future required member goes red, and no suppression is available, because the ledger no longer covers the file. The interface file was restored by
git checkoutfrom the committed branch and confirmed byte-identical (git hash-objectequals the committed blob) — it is not part of this diff.Gates run locally on the merged tree:
pnpm --filter @objectstack/spec typecheck— green (tsc,check:scripts-typecheck,check:test-typecheckat 55/263).pnpm --filter @objectstack/spec test— 385 files / 10195 tests passed (the whole spec suite).node scripts/check-nul-bytes.mjs— OK, 7406 files, no raw control bytes; plus a direct control-byte scan of both changed files.node scripts/check-empty-changeset.mjs --self-test— 118 assertions hold.mainwas merged before pushing (3670cf9f2to37b82ed5b); the merge touchedpackages/specnot at all, so the two sides are disjoint and no generated artifact can have moved.Changeset: none,
skip-changesetinsteadThis PR releases nothing.
packages/spec's build config excludes**/*.test.tsand its publishedfileslist carries neither the test nortest-typecheck-debt.json, which is a repo-internal CI ratchet — so there is no package version to bump and nothing to attach a CHANGELOG entry to. The changeset gate has no path-based exemption (it demands a changeset on every PR unless the label is present), andscripts/check-empty-changeset.mjsis explicit that the label, not an empty changeset, is the route for exactly this case: an empty changeset is a real input tochangesets/actionand an all-empty set stalls a release silently and greenly. Hence theskip-changesetlabel.Generated by Claude Code