Skip to content

feat(spec): declare the audit door and catch the delete request schema up — both meta-door casts retired - #12003

Merged
os-support-ai merged 8 commits into
mainfrom
claude/issue-11678-metadata-protocol-doors
Aug 25, 2026
Merged

feat(spec): declare the audit door and catch the delete request schema up — both meta-door casts retired#12003
os-support-ai merged 8 commits into
mainfrom
claude/issue-11678-metadata-protocol-doors

Conversation

@os-support-ai

@os-support-aios-support-ai commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

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 in packages/rest/src/rest-server.ts.

Card 1 — #11678: declare auditMetaItem on MetadataProtocol

Neither 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:

Card 2 — #11679: DeleteMetaItemRequestSchema catches up with the reset door

The member was declared all along; the request schema declared 2 of the 8 members DELETE /meta/:type/:name sends. Removing the cast surfaced the predicted TS2353 on the undeclared keys (measured, below) — the request-shape half, opposite of the publish door's TS2339.

Per-member protocol-vs-transport verdicts (the eight the door sends)

MemberVerdictEvidence
type, nameprotocol (already declared)
organizationIdprotocol — declaredRead by the implementation (request.organizationId ?? null selects 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).
environmentIdtransport — OUTRuled 2026-08-18 (#9741): multi-kernel routing key; resolveProtocol(environmentId) selects the kernel before the call and the implementation's parameter type does not carry it. Rides TransportScopedMetaRequest at the door; regression-pinned in the schema tests (strip + shape-absent).
parentVersionprotocol — declared (string, optional)Read by the implementation as the ADR-0008 OCC pin (If-Match on REST). The implementation's own type is string | null with null behaving identically to absent (both fall to current.hash, i.e. unpinned); no producer sends null, so the contract declares only the string spelling — declaring a null arm whose behaviour is indistinguishable from absent would be surface without pull.
actorprotocol — declaredRead by the implementation (actor ?? null onto the history tombstone; #4556 records the deliberate null-not-"system"). Same verdict as publish's declared actor.
stateprotocol — declared ('active' | 'draft')Read by the implementation (state === 'draft' selects the draft row); drives the draft-discard flow the REST ?state=draft query serves.
dropStorageprotocol — declared (boolean)Read by the implementation; destructive opt-in with real behaviour semantics (drops the object's physical table, object + active only). The one member whose absence from the contract hid a destructive capability.
internal coordination keysn/a — none exist on this door_skipSeedApply is publish-batch-only; the delete implementation's parameter type carries no underscore-prefixed member. Stated per #11426's recorded reasoning rather than silently skipped.
  • REST reset door: guard if (!p.deleteMetaItem), request hoisted to a named const typed by the TransportScopedMetaRequest wrapper over DeleteMetaItemRequest (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/rest suite (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, then tsc --noEmit in packages/rest against the rebuilt spec dist. Result: RV_TSC_EXIT=1, exactly 2 errors, both TS2353, 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.ts is the one being read), and environmentId refused at the audit-door literal (the transport exclusion has compile-time teeth). The @ts-expect-error pins in packages/spec/src/api/protocol.test.ts compile in the spec test-typecheck program (check:test-typecheck green; debt ledger unchanged at 55 files / 263 errors — no growth).

Verification union — run at the merged head 2c8a3183 (post-merge of main d63b0143, 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 build EXIT=0; check:generated EXIT=0 — its own verdict line: "✓ All 14 generated artifacts are up to date."
  • pnpm --filter @objectstack/spec typecheck EXIT=0 (incl. check:test-typecheck: "OK — … 55 file(s) / 263 error(s) held … shrink-only").
  • Full @objectstack/spec suite: 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.
  • Full @objectstack/rest suite: EXIT=0 — "Test Files 145 passed (145) / Tests 2340 passed (2340)" (drives both doors over real sockets; includes the merged fix(rest): thread ?mode=draft on the compound-name metadata write door #11933 parity file).
  • check:route-envelope EXIT=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).
  • Cheap scan gates at this head or pre-merge (re-run where the merge could move them): check:nul-bytes 0, check:engine-double-contract 0 ("OK — 403 pinned, 133 in the DEBT ledger, 2 exempt"), check:where-matcher 0, check:authz-resolver 0, check:cross-package-test-inputs 0, check:merge-driver 0, check-adr-0087-registration 0 (all three changesets are minor/patch, none declared-breaking), check-changeset-no-major 0, check-empty-changeset 0.
  • Gate families re-derived from the actual diff at this head with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (40 path-matched families; stderr attests the tree and commit). Declared narrowing: the repo-wide check: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 (head 6f0d3ec8)

CI's Lint & Repo Gates flagged the audit-fixture literal code: 'item_locked' at protocol.test.ts:1868 as a lowercase code emission. It is not an ADR-0112 error code: it is the sys_metadata_auditcode column's own lowercase vocabulary (the fixture mirrors a persisted audit event; the D6b class, with the identical value already suppressed at packages/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-bytes and check:route-envelope re-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.ts is 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

…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
@github-actions

github-actionsBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/rest, @objectstack/spec, touching 18 documentable anchor(s). ⚠️5 changed file(s) yielded no anchor (packages/rest/src/rest-server.ts, packages/spec/api-surface/api.json, packages/spec/authorable-surface/api.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

16 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 8450eeacb64a0d831b4d569d9bd43dca4c2abc4a.

3 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 5 changed file(s) yielded no anchor (packages/rest/src/rest-server.ts, packages/spec/api-surface/api.json, packages/spec/authorable-surface/api.json, …) — pages documenting those are invisible to this run
  • the SDK route bridge reached 45 of 222 client-bound route-ledger rows — the other 177 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 128 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8450eeacb64a0d831b4d569d9bd43dca4c2abc4apackageMentionDocs.

Which tree this was computed on

This run read content/docs from f1144a87f784e84c2a6babc94c9225b8940d7bc6 — the merge of head 9b4b5f5e472ce4dcd8be07ef5b80c4c3f0c5775f into base 8450eeacb64a0d831b4d569d9bd43dca4c2abc4a, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 8450eeacb64a0d831b4d569d9bd43dca4c2abc4a → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…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
…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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

2 participants

@os-support-ai@claude