Uh oh!
There was an error while loading. Please reload this page.
feat(spec): declare the audit door and catch the delete request schema up — both meta-door casts retired - #12003
Conversation
…REST audit door's casts
The audit door (GET /meta/:type/:name/audit) had neither request nor response
declared; the door reached the verb through (p as any) twice. Declares
AuditMetaItemRequest/Response mirroring the implementation member for member,
adds the optional interface member, and compiles the door's request literal
against the contract. environmentId stays out per the transport-level ruling;
the honest {events: []} miss shape is recorded in the declared types.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K93P8PbH7kVjsAXkqzH1zY… the reset door sends; reset-door cast retired The member was declared all along but the request schema carried 2 of the 8 members DELETE /meta/:type/:name sends. Declares organizationId (tenant scope selecting WHICH row a reset destroys), parentVersion (OCC pin), actor, state and dropStorage, mirroring the implementation; environmentId stays out per the transport-level ruling and rides TransportScopedMetaRequest at the door. Both rest changeset and reset-door literal typing land here. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K93P8PbH7kVjsAXkqzH1zY
check:generated proved 4 stale (api-surface, export-origins, docs references, strictness-ledger counts); --fix regenerated exactly those. The authorable-surface and json-schema.manifest shard updates are the spec build's own gen:schema product for the same declarations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K93P8PbH7kVjsAXkqzH1zY
…tadata-protocol-doors
📓 Docs Drift CheckThis PR changes 2 package(s): 16 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 3 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 128 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f1144a87f784e84c2a6babc94c9225b8940d7bc6 && git checkout f1144a87f784e84c2a6babc94c9225b8940d7bc6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8450eeacb64a0d831b4d569d9bd43dca4c2abc4a 9b4b5f5e472ce4dcd8be07ef5b80c4c3f0c5775f && git checkout -B drift-repro 8450eeacb64a0d831b4d569d9bd43dca4c2abc4a && git merge --no-ff 9b4b5f5e472ce4dcd8be07ef5b80c4c3f0c5775f
node scripts/docs-audit/affected-docs.mjs --json 8450eeacb64a0d831b4d569d9bd43dca4c2abc4a
|
…rsisted audit column vocabulary, not an error code check:error-code-casing read the fixture's code: 'item_locked' as an emission; it is the sys_metadata_audit code column's own lowercase vocabulary (the D6b class, precedent at meta-audit-capability-gap.test.ts:162), suppressed with the corpus's exact reason line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K93P8PbH7kVjsAXkqzH1zY
…sm (ADR-0122) check:spec-parsed-alias flagged AuditMetaItemRequest/Response as author-state aliases with nothing naming their parsed state. Both trees carry no .default(), .transform(), .catch() or .pipe(), so input and infer coincide - the Iso pin route (the Iso137/Iso856 sibling precedent), not an XParsed pair. All three machine-checked count sentences move 833 -> 835 with the history note. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K93P8PbH7kVjsAXkqzH1zY
…tadata-protocol-doors
…os-regen deferral) os-regen-merge.sh sequence: merge committed first, then the full gen chain on the merged source. check:generated --fix regenerated the 4 provably stale artifacts; the audit-door entries (this PR) and the #12001/#12006 sibling entries coexist in the regenerated set. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K93P8PbH7kVjsAXkqzH1zY
Uh oh!
There was an error while loading. Please reload this page.
Fixes#11678
Fixes#11679
Family dispatch (chain head #11678): the two metadata-protocol door catch-ups triage folded — same file, same ruled pattern (#11006 maintainer ruling 2026-08-22 「同意所有」 item 5, option B lineage; #11426 is the publish landing both cards mirror). Per-card commits; acceptance per card = the door's
(p as any)casts come off inpackages/rest/src/rest-server.ts.Card 1 — #11678: declare
auditMetaItemonMetadataProtocolNeither side of the audit door was declared (
grep -c auditMetaItem packages/spec/src/api/protocol.zod.ts→ 0 at claim, re-verified on current main). Landed:AuditMetaItemRequestSchema/AuditMetaItemRequest—{ type, name, organizationId?: string | null, limit? }, mirroringObjectStackProtocolImplementation.auditMetaItem's parameter type member for member.organizationIdis nullable because the REST door always sendsctx?.tenantId ?? null(auditMetaItem'sorganizationIdis dead on both ends — the audit read returns every org's rows for a (type, name), while its comment describes a scope filter that is not in the query #8747 fail-closed scoping).limitdeclares no bounds: the implementation clamps to [1, 500] rather than refusing, and declaring.max(500)would refuse what the shipped verb accepts.AuditMetaItemResponseSchema/AuditMetaItemResponse— the{ events: [...] }body with closedoperation/outcomevocabularies and ADR-0010 §3.3lockState. The The REST audit route answers a MISSINGauditMetaItemcapability with{events: []}— a compliance surface reporting "this item has no audit trail" #9426 miss-vs-fault honesty is stated in the declared types:{ events: [] }is the honest miss shape (clean trail / find-less host engine / unprovisioned table), never the missing-capability answer (501 before the call) and never a failed read (propagated,auditMetaItem's unqualifiedcatchreports ANY failed audit read as{events: []}— the compliance trail says "no entries" when the read broke #9638).MetadataProtocol.auditMetaItem?— optional like itsdeleteMetaItem/getMetaItemLayeredsiblings.typeof p.auditMetaItem !== 'function', request hoisted to a named const typed plainAuditMetaItemRequest— deliberately notTransportScopedMetaRequest: this door stopped sendingenvironmentIdwhen auditMetaItem'sorganizationIdis dead on both ends — the audit read returns every org's rows for a (type, name), while its comment describes a scope filter that is not in the query #8747 scoped the read, so there is no transport member left to layer on. The in-door docblock that recorded "auditMetaItem is not a member of RestProtocol… not declared in packages/spec at all" is rewritten — a rationale for a cast that no longer exists.Card 2 — #11679:
DeleteMetaItemRequestSchemacatches up with the reset doorThe member was declared all along; the request schema declared 2 of the 8 members
DELETE /meta/:type/:namesends. Removing the cast surfaced the predictedTS2353on the undeclared keys (measured, below) — the request-shape half, opposite of the publish door'sTS2339.Per-member protocol-vs-transport verdicts (the eight the door sends)
type,nameorganizationIdrequest.organizationId ?? nullselects the overlay repo); load-bearing for tenant scoping — the call-site comment records that an org-less delete reaches the env-wide row and blanks the item for every tenant (#8805). Same verdict as publish (#11426).environmentIdresolveProtocol(environmentId)selects the kernel before the call and the implementation's parameter type does not carry it. RidesTransportScopedMetaRequestat the door; regression-pinned in the schema tests (strip + shape-absent).parentVersionstring, optional)If-Matchon REST). The implementation's own type isstring | nullwithnullbehaving identically to absent (both fall tocurrent.hash, i.e. unpinned); no producer sendsnull, so the contract declares only thestringspelling — declaring anullarm whose behaviour is indistinguishable from absent would be surface without pull.actoractor ?? nullonto the history tombstone; #4556 records the deliberate null-not-"system"). Same verdict as publish's declaredactor.state'active' | 'draft')state === 'draft'selects the draft row); drives the draft-discard flow the REST?state=draftquery serves.dropStorageboolean)object+activeonly). The one member whose absence from the contract hid a destructive capability._skipSeedApplyis publish-batch-only; the delete implementation's parameter type carries no underscore-prefixed member. Stated per #11426's recorded reasoning rather than silently skipped.if (!p.deleteMetaItem), request hoisted to a named const typed by theTransportScopedMetaRequestwrapper overDeleteMetaItemRequest(generic brackets spelled out in words here on purpose — the body sanitizer mutates angle-bracket fragments).No runtime behaviour change
Declaring what the shipped doors already send and honour changes no accept/reject behaviour (clause-② content limb: no, per both triage gradings). The outgoing payloads are byte-identical — same keys, same conditional spreads; the edits hoist each literal into a named const and delete type-level casts. Both 501 feature-detection guards survive (members are optional; the guards also narrow). Evidence: reverse verification below shows the type-level teeth; the full
@objectstack/restsuite (which drives both doors over real sockets) passes unchanged.Reverse verification (measured, from committed state, trap-restored)
One trap-guarded mutation run (both legs confirmed on disk by grep-count before compiling; restore verified
git diff HEAD= 0 lines and probe-count 0 after): injected an undeclared probe key into each door literal, thentsc --noEmitinpackages/restagainst the rebuilt specdist. Result:RV_TSC_EXIT=1, exactly 2 errors, bothTS2353, one per door — the misspelt probe key refused at the reset-door literal (the compiler's error text prints the caught-up five-member delete type, proving the rebuilt.d.tsis the one being read), andenvironmentIdrefused at the audit-door literal (the transport exclusion has compile-time teeth). The@ts-expect-errorpins inpackages/spec/src/api/protocol.test.tscompile in the spec test-typecheck program (check:test-typecheckgreen; debt ledger unchanged at 55 files / 263 errors — no growth).Verification union — run at the merged head
2c8a3183(post-merge of maind63b0143, which brought #11933's compound-PUT region into this same file; merged clean, disjoint from both door regions)All exit codes captured before any pipe (redirect-to-file, then
echo EXIT=$?):pnpm --filter @objectstack/spec buildEXIT=0;check:generatedEXIT=0 — its own verdict line: "✓ All 14 generated artifacts are up to date."pnpm --filter @objectstack/spec typecheckEXIT=0 (incl.check:test-typecheck: "OK — … 55 file(s) / 263 error(s) held … shrink-only").@objectstack/specsuite: EXIT=0 — "Test Files 424 passed (424) / Tests 11298 passed (11298)".pnpm --filter @objectstack/rest typecheck(tsc --noEmit) EXIT=0 — both doors compile with the casts off.@objectstack/restsuite: EXIT=0 — "Test Files 145 passed (145) / Tests 2340 passed (2340)" (drives both doors over real sockets; includes the merged fix(rest): thread?mode=drafton the compound-name metadata write door #11933 parity file).check:route-envelopeEXIT=0 at the same head — "✓ Plugin-mounted Hono routes — 12 module(s) audited … ✓ Express-style response modules — 4 module(s) … 2 ratcheted" (both 501 guard bodies unchanged).check:nul-bytes0,check:engine-double-contract0 ("OK — 403 pinned, 133 in the DEBT ledger, 2 exempt"),check:where-matcher0,check:authz-resolver0,check:cross-package-test-inputs0,check:merge-driver0,check-adr-0087-registration0 (all three changesets are minor/patch, none declared-breaking),check-changeset-no-major0,check-empty-changeset0.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(40 path-matched families; stderr attests the tree and commit). Declared narrowing: the repo-widecheck:type-check-debt --re-measure(needs the full workspace closure built) was NOT run locally — the populations my diff can move are covered by the two affected packages' own green typechecks (spec incl. its test-typecheck ledger, rest), and CI runs the full re-measure regardless.Patch round —
check:error-code-casing(head6f0d3ec8)CI's Lint & Repo Gates flagged the audit-fixture literal
code: 'item_locked'atprotocol.test.ts:1868as a lowercase code emission. It is not an ADR-0112 error code: it is thesys_metadata_auditcodecolumn's own lowercase vocabulary (the fixture mirrors a persisted audit event; the D6b class, with the identical value already suppressed atpackages/rest/src/meta-audit-capability-gap.test.ts:162). Fixed with the corpus's exact line-above opt-out (adr0112-ok: D6b — persisted audit column, its own lowercase vocabulary) — never a whole-file exemption. Local re-run at this tree: EXIT=0 — "✓ no unlisted lowercase error codes in 4632 scanned file(s) (ADR-0112)", "13 literal(s) suppressed by an adr0112-ok: reason" (12 → 13, mine counted).check:nul-bytesandcheck:route-envelopere-pinned EXIT=0 at the same head; the commit is a one-line comment insertion in a test fixture, no compiled output or wire byte moves.Serial constraints
rest-server.tsis shared with PR #11933 (compound-PUT region, disjoint from both door regions). main merged immediately before opening this PR.Generated by Claude Code
Generated by Claude Code