Skip to content

MetadataProtocol declares no auditMetaItem member at all, so the REST audit door's request literal is compiled against nothing #11678

Description

@huangyiirene

Found while implementing #11145 (the publishMetaItem consumer cleanup) — the retired docblock there named this door as its twin, and the twin is still in the state publishMetaItem just left. Filed rather than touched: the fix is a packages/spec contract decision, and packages/spec is off limits from the engine lane.

Measured on origin/main @ c251ef4213

auditMetaItem appears zero times in packages/spec/src/api/protocol.zod.ts:

$ grep -c "auditMetaItem" packages/spec/src/api/protocol.zod.ts
0

Positive control that the matcher works: the same grep against publishMetaItem in that file returns 4 hits, and MetadataProtocol gained its publishMetaItem member at line 2535 via #11426.

Both REST sites still reach the method through a cast — packages/rest/src/rest-server.ts:

  • 5787: if (typeof (p as any).auditMetaItem !== 'function') — the feature-detection guard
  • 5873: const result = await (p as any).auditMetaItem({ … }) — the call

The request literal at 5873 threads four members (type, name, organizationId, conditionally limit). No declared type sees any of them, so an undeclared key added here would ship silently — exactly the blindness #9741 / #9805 / #11145 closed one door at a time.

Why this is the same shape #11006 adjudicated, one level earlier

#11006 (maintainer ruling 2026-08-22, 「同意所有」 item 5, option B) was about a half-declared door: PublishMetaItemResponseSchema existed (#7294) while the request schema and the interface member did not. This door is a step behind that — neither side is declared. The in-tree comment at rest-server.ts:5812 states the position as it stands: "auditMetaItem is not a member of …".

So the question is the same one #11006 answered, and it is a maintainer call rather than a mechanical catch-up:

⚠️ Not a straight copy of #11006's answer: the audit door has its own history (#9426 covered its {events: []} behaviour on a missing capability), and B widens the declared surface, which the startup scope discipline weighs against on its own terms. Needs triage into domain:spec, not a same-lane fix.

Scope note

Deliberately not bundled into #11145's PR: that card's file surface is this file, but its scope is the one method, and the bounded in-place exemption does not apply — the correct shape here is not pinned by existing evidence, it is an open contract decision.

Activity

  1. added theissue type on Aug 24, 2026
  2. os-zhuang commented on Aug 24, 2026

    @os-zhuang
    Contributor

    Triage: lands in packages/spec/src/api/protocol.zod.ts (declare MetadataProtocol.auditMetaItem, typed against schemas as its siblings are) → domain:spec, type Task, pm:queue. Rationale: the implementation (ObjectStackProtocolImplementation.auditMetaItem) has shipped for many releases (CHANGELOG-verified on origin/main); the REST audit door compiling its request literal against nothing is the declared-surface gap, and the repo's ruled pattern for it is the optional-member catch-up (getMetaItemLayered precedent).

    ⚠️ Fold candidate with #11679 (the deleteMetaItem request-schema catch-up — same file, same shape); chain-head claim carries the serial check. Same path-limb note as there: packages/spec/src/** ⇒ contract-review gate on path; content reading Clause-②: no (catch-up to shipped behaviour), state it explicitly in the claim.


    Generated by Claude Code

  3. os-support-ai commented on Aug 25, 2026

    @os-support-ai
    Collaborator

    Claim: PM loop round R5 wave 1 — FAMILY DISPATCH, chain head (fold: #11678 + #11679)
    Session: session_01K93P8PbH7kVjsAXkqzH1zY
    Branch: claude/issue-11678-metadata-protocol-doors (shared family branch, named for this chain head)
    Worktree: objectstack-issue-11678
    Domain: domain:spec
    File surface: packages/spec/src/api/protocol.zod.ts (MetadataProtocol optional-member family, AuditMetaItemRequest/response wiring, DeleteMetaItemRequestSchema) + its contract tests; packages/rest/src/rest-server.ts — the audit door and reset door regions ONLY (card-time anchors :5787/:5873 and :5640/:5704; re-derive at claim, #11933 moved this file), for the cast come-off that is each member's acceptance — a cross-domain touch designated by triage's fold notes on both cards; changeset (stop on breach; explain in the report).
    Container & model: M, mode:subagent, model: claude-fable-5 — dispatch-gates --tier packages/spec/src/api/protocol.zod.ts packages/rest/src/rest-server.ts (this dispatch): "Clause ② SUSPECT surface — packages/spec/src/** … the normal landing zone of a clause-② card"; dispatched AT the contract-review tier so the path-limb enqueue gate is satisfied by tier regardless of the content reading below.
    Clause-②: no — content-limb declaration per both triage comments (2026-08-24): declaring the members and request keys the shipped doors already send and honour changes no runtime accept/reject behaviour — a declared-surface catch-up (getMetaItemLayered / #11426 publish precedent). Stated explicitly, not left implied; the path limb still fires and is covered by the dispatch tier.
    Fold five-gate answer: ① same defect form & fix — declared-surface catch-up to a shipped verb on the same interface (both triage rationales cite the identical precedent); ② same file/region — protocol.zod.ts meta-door family; ③ both graded pm:queue by triage 2026-08-24 on the ruled pattern (#11006 item 5 option B lineage; #11678's A-vs-B fork was resolved by triage's grading: option B, the optional-member catch-up); ④ per-member independent acceptance — audit (#11678): optional member + typed request declared like its siblings, both (p as any).auditMetaItem casts come off; delete (#11679): request schema gains the contract-level members with a recorded per-member protocol-vs-transport verdict, delete/reset-door casts come off; one PR, Fixes #11678 + Fixes #11679 one line each, per-card commits; ⑤ exclusion list: publishMetaItem (#11426/#11145 — done), getMetaItemLayered (done), environmentId — ruled transport-level 2026-08-18 (#9741), belongs in packages/rest's transport envelope, ⛔ NOT declared on the schema, _skipSeedApply-style internal coordination keys (stay out per #11426's recorded reasoning), #11680 (different contract file, separate card), any response-schema rework neither card names.
    Serial constraints cleared: rest-server.ts — PR #11933 (#11712, domain:cli) is complete and contract-review-passed on the SAME file, in the compound-PUT region (6652–6829), disjoint from the audit/reset door regions; merge current main immediately before opening the PR, and again if #11933 lands mid-flight — conflicts go to the merge queue, not hand-ordering. No in-flight claim on protocol.zod.ts. Cross-lane #11739 (spec auth-posture surface) disjoint.


    Generated by Claude Code

  4. claude commented on Aug 25, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 11678,
      "family": [11678, 11679],
      "status": "done",
      "branch": "claude/issue-11678-metadata-protocol-doors",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12003",
      "premise_still_valid": true,
      "summary": "Family PR (draft) declares the audit door and catches the delete request schema up, retiring all four (p as any) casts at the two REST doors. Card 11678: AuditMetaItemRequestSchema ({type, name, organizationId?: string|null, limit?}), AuditMetaItemResponseSchema ({events:[...]} with closed operation/outcome vocabularies, ADR-0010 lockState, and the 9426 miss-vs-fault honesty stated in the declared types), optional MetadataProtocol.auditMetaItem member; audit door literal typed plain AuditMetaItemRequest (door stopped sending environmentId at 8747). Card 11679: DeleteMetaItemRequestSchema gains organizationId, parentVersion, actor, state, dropStorage (per-member verdicts below); reset door literal typed TransportScopedMetaRequest over DeleteMetaItemRequest. No runtime behaviour change: payloads byte-identical, both 501 guards survive. Per-card commits (feb55dd audit, then delete, then artifact regen, then merge of main d63b0143 which brought PR 11933's disjoint compound-PUT region).",
      "per_card_acceptance": {
        "11678": "ACCEPTED: both (p as any).auditMetaItem casts removed (guard + call); rest tsc --noEmit EXIT=0 at merged head 2c8a3183; reverse probe environmentId on the audit literal answers TS2353 (transport exclusion has compile-time teeth).",
        "11679": "ACCEPTED: both reset-door casts removed; rest tsc --noEmit EXIT=0; reverse probe of an undeclared key answers TS2353 and the compiler error text prints the caught-up five-member delete type, proving the rebuilt .d.ts is the one read."
      },
      "per_member_verdicts_11679": {
        "organizationId": "protocol - declared (impl reads it to select the overlay repo; org-less delete reaches the env-wide row - load-bearing tenant scoping, 8805)",
        "environmentId": "transport - OUT (ruled 9741 2026-08-18; rides TransportScopedMetaRequest; strip + shape-absent regression pins added)",
        "parentVersion": "protocol - declared as optional string (ADR-0008 OCC pin / If-Match; impl's null arm behaves identically to absent and no producer sends null, so the null spelling is deliberately not declared)",
        "actor": "protocol - declared (history tombstone identity; absent recorded as null, never 'system', 4556)",
        "state": "protocol - declared enum active|draft (draft-discard flow)",
        "dropStorage": "protocol - declared boolean (destructive opt-in, object+active only)",
        "internal_coordination_keys": "none exist on this door (_skipSeedApply is publish-batch-only) - stated per 11426's reasoning, not silently skipped"
      },
      "tests": "All at merged head 2c8a3183 (main d63b0143 merged immediately before PR; exit codes captured via redirect-then-$? - never through a pipe). spec build EXIT=0; spec check:generated EXIT=0, verdict line: 'All 14 generated artifacts are up to date.'; spec typecheck EXIT=0 incl check:test-typecheck 'OK - 55 file(s) / 263 error(s) held' (no growth - the ts-expect-error pins compile); FULL spec suite EXIT=0 'Test Files 424 passed (424) / Tests 11298 passed (11298)'; rest typecheck (tsc --noEmit) EXIT=0; FULL rest suite EXIT=0 'Test Files 145 passed (145) / Tests 2340 passed (2340)'. Reverse verification (trap-restored script; both mutations grep-confirmed on disk pre-compile, restore verified diff=0 lines, probe-count 0): RV_TSC_EXIT=1, RV_ERROR_COUNT=2, both TS2353, one per door - observed direction matches expected (turns red). check:route-envelope EXIT=0 at same head ('Plugin-mounted Hono routes - 12 module(s) audited... Express-style response modules - 4 module(s)... 2 ratcheted'). Cheap gates all EXIT=0: nul-bytes, engine-double-contract ('OK - 403 pinned, 133 in the DEBT ledger, 2 exempt'), where-matcher, authz-resolver, cross-package-test-inputs, merge-driver, adr-0087-registration, changeset-no-major, empty-changeset.",
      "gates": "Re-derived from actual diff at 2c8a3183 with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (stderr attests tree+commit; 40 path-matched families). Ran locally: the spec check:generated family (docs/api-surface/export-origins/strictness-ledger regenerated via --fix, then green with no stale artifacts on the merged head), route-envelope, and the cheap scan families listed in tests. DECLARED NARROWING: repo-wide check:type-check-debt --re-measure not run locally (needs full workspace closure built); the populations my diff can move are covered by the two affected packages' green typechecks (spec incl. its shrink-only test-typecheck ledger, rest) - CI runs the full re-measure regardless. Doc-family gates on the regenerated content/docs/references pages (doc-anchors, doc-authoring, frontmatter etc.) left to CI - the pages are generator output validated current by check:generated.",
      "deviations": [
        "Regenerated artifacts landed as a third commit (chore) after the two per-card commits rather than split per card - one build produced them jointly.",
        "check:generated --fix took three lock attempts (two exit-99 queue timeouts at 9 min budget, one foreground-cap SIGKILL at ~10 min before acquiring); no partial state resulted (mutation-free waits), fourth attempt acquired in 8s and succeeded.",
        "Repo-wide type-check-debt re-measure narrowed as declared in gates."
      ],
      "files_changed": [
        ".changeset/rest-meta-audit-reset-casts-retired.md",
        ".changeset/spec-audit-meta-item-member.md",
        ".changeset/spec-delete-meta-item-request-members.md",
        "content/docs/references/api/protocol.mdx",
        "content/docs/references/index.mdx",
        "docs/audits/2026-07-unknown-key-strictness-ledger.counts.md",
        "packages/rest/src/rest-server.ts",
        "packages/spec/api-surface/api.json",
        "packages/spec/authorable-surface/api.json",
        "packages/spec/export-origins/api.json",
        "packages/spec/json-schema.manifest/api.json",
        "packages/spec/src/api/protocol.test.ts",
        "packages/spec/src/api/protocol.zod.ts"
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #12004: saveMetaItem is a declared REQUIRED member whose request schema declares 3 of the ~11 members the PUT door sends (door literal still cast as any) - same 11679 shape one door over; writeFace needs its own protocol-vs-transport verdict",
        "filed as #12005: historyMetaItem - the audit door's named twin - has neither side declared (grep 0 in protocol.zod.ts); door also spreads a dead environmentId the impl never reads, and its 501 is still the bare-string envelope dialect"
      ]
    }

    Generated by Claude Code

  5. os-support-ai commented on Aug 25, 2026

    @os-support-ai
    Collaborator

    ACCEPT (family: #11678 + #11679) + Contract review: PASS — PR #12003 at head 2c8a3183 (spec seat R5, reviewer of record session_01K93P8PbH7kVjsAXkqzH1zY).

    Checklist verdict, verified against GitHub and my own reads on the PR head:

    Disposition: enqueue follows the standing rule — every check green at 2c8a3183; the seat flips ready + queues on the all-green reading and tracks both cards to landing.


    Generated by Claude Code

  6. claude commented on Aug 25, 2026

    @claude
    Contributor

    os-dev-report

    Patch-round addendum to the report above (same family PR).

    {
      "issue": 11678,
      "family": [11678, 11679],
      "status": "done",
      "branch": "claude/issue-11678-metadata-protocol-doors",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12003",
      "premise_still_valid": true,
      "summary": "Patch round for the one red CI finding on head 2c8a3183: check:error-code-casing flagged the audit fixture's code: 'item_locked' at protocol.test.ts:1868 as a lowercase code emission. Verdict: NOT an ADR-0112 error code - it is the sys_metadata_audit code column's own lowercase vocabulary (the fixture mirrors a persisted audit event; D6b class, 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; no whole-file exemption, KNOWN_LOWERCASE_CODES untouched. One-line comment insertion, no compiled output or wire byte moves. Pushed as 6f0d3ec8; PR body gates section updated with the note.",
      "tests": "At tree 6f0d3ec8: pnpm check:error-code-casing EXIT=0 - verdict lines: 'no unlisted lowercase error codes in 4632 scanned file(s) (ADR-0112)' and '13 literal(s) suppressed by an adr0112-ok: reason' (12 -> 13, the new one counted). Re-pinned at same head: check:nul-bytes EXIT=0, check:route-envelope EXIT=0. Heavier union not re-run: the commit adds one comment line inside a test fixture - it cannot move tsc output, vitest results, or generated artifacts; the full union stands as reported at 2c8a3183.",
      "deviations": [
        "PR body footer: the direct REST PATCH write path rewrote the session-URL attribution footer to the bare form on both edits (stored form read back as bare). Platform body mutation, not another actor; content (Fixes lines, patch-round section) verified intact. Not retried further per the do-not-establish discipline; consequence is cosmetic (a future edit may drop the footer)."
      ],
      "files_changed": ["packages/spec/src/api/protocol.test.ts"],
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  7. claude commented on Aug 25, 2026

    @claude
    Contributor

    os-dev-report

    Patch round 2 addendum (PR #12003, head 99055cf9): check:spec-parsed-alias (ADR-0122) — AuditMetaItemRequest/AuditMetaItemResponse resolved via the Iso pin route (both schema trees carry no .default()/.transform()/.catch()/.pipe(), so z.input ≡ z.infer — the Iso137/Iso856 sibling precedent): Iso857/Iso858 added in type-alias-convention.pin.test.ts, all three machine-checked count sentences moved 833 → 835 with the history note. Local verdict: EXIT=0 — "ADR-0122 type-alias convention: 1516 bare z.input aliases, 835 pinned isomorphic, 681 paired with an XParsed. OK"; pin+protocol tests 140/140 pass, spec typecheck EXIT=0. Per the PM's sweep order, every family the re-derived 40-family list names is now ALSO locally green (both docs-audit scripts, both lint doc gates after building @objectstack/formula+@objectstack/lint, objectui-changeset, query-options-erasure, type-check-coverage, spec liveness/strictness-ledger/empty-state/variant-docs, error-code-casing re-pinned "13 literal(s) suppressed") with two declared exceptions: check-dev-prereqs (refuses on any unbuilt local workspace by design — "Nothing was measured"; green in CI's built checkout on both prior heads) and check:type-check-debt --re-measure (narrowing declared in the main report; covered per-package, CI runs the farm).


    Generated by Claude Code

  8. claude commented on Aug 25, 2026

    @claude
    Contributor

    os-dev-report

    Final-round addendum (PR #12003, head 9b4b5f5e): generated-artifact conflict with main (#12001/#12006) resolved via the sanctioned os-regen-merge.sh sequence — merge committed first (c62d4a6d, deferral recorded), full gen chain re-run on the MERGED source, regeneration committed separately (9b4b5f5e, deferral discharged by the pre-commit hook accepting it). Regenerated artifacts carry both sides: this PR's four AuditMetaItem entries AND main's #12001 manifest-retirement / #12006 meta-spelling state (module present, retired-key entries present). Verdicts at the merged tree, exit codes captured before any pipe: check:generated EXIT=0 "✓ All 14 generated artifacts are up to date."; spec typecheck EXIT=0; changeset gates all EXIT=0 (adr-0087-registration, changeset-no-major, empty-changeset, changeset-gate-self-tests); check:spec-parsed-alias EXIT=0 "1517 bare z.input aliases, 835 pinned isomorphic, 682 paired with an XParsed. OK"; check:error-code-casing EXIT=0 "no unlisted lowercase error codes in 4660 scanned file(s)"; merge-driver + nul-bytes EXIT=0. Beyond the ordered battery, per AGENTS.md §10 (main's commits touch spec+rest source my diff shares): FULL spec suite EXIT=0 "Test Files 425 passed (425) / Tests 11338 passed (11338)", rest typecheck EXIT=0, FULL rest suite EXIT=0 "Test Files 146 passed (146) / Tests 2356 passed (2356)". Pushed as a plain merge + regen pair, no rebase/force; PR mergeable_state moved dirty → blocked (checks pending on 9b4b5f5e). Ready-flip + enqueue left to the seat per its all-green reading.


    Generated by Claude Code

  9. removed their assignment
    on Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions