feat(spec): declare MetadataProtocol.historyMetaItem and de-cast the REST history door - #13521
Conversation
…REST history door The history door schemas mirror the implementation's parameter and return types member for member (the #11006 pattern, carried one door over exactly as #11678 carried it to the audit twin). The REST door literal now compiles against the declared contract through TransportScopedMetaRequest; wire payload byte-identical. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KX8wnyjStaZcuMyAMNsy3N
check:doc-authoring measured them in the customer-facing describe population; the reasoning stays in the TSDoc comments, which are internal. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KX8wnyjStaZcuMyAMNsy3N
📓 Docs Drift CheckThis PR changes 2 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
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 1d98ed391a9e9821a18dd85874631b70766b77ed && git checkout 1d98ed391a9e9821a18dd85874631b70766b77ed
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ff3757697600591804e2135a1e58b7afaebe0a48 c8a5d8b48ccdebd1db52b7e91f174c0e1ddbdf3c && git checkout -B drift-repro ff3757697600591804e2135a1e58b7afaebe0a48 && git merge --no-ff c8a5d8b48ccdebd1db52b7e91f174c0e1ddbdf3c
node scripts/docs-audit/affected-docs.mjs --json ff3757697600591804e2135a1e58b7afaebe0a48
|
…ons the history-door diff shifted check-system-context-census (CI 'Lint & Repo Gates') caught pure line rot: the new import line shifted every site below it by +1 and the de-cast door block shifted the two sites below it by +19. The gate's --fix refused (its population sanity compares page anchors against census reads without folding in the two NON_READ_ANCHORS-excused seams), so the eight anchors are rewritten by hand to the lines the census and ledger already resolve to. Census now: 109 sites all anchored, 145 anchors resolve, 27 declared non-read. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KX8wnyjStaZcuMyAMNsy3N
Re-anchor the eight rest-server.ts isSystem citations on the merged tree: both sides had re-anchored for their own shifts, so the merged file needed line numbers re-derived by the census (check-system-context-census: OK). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KX8wnyjStaZcuMyAMNsy3N
Discharges the regeneration the merge commit deferred (os-regen). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KX8wnyjStaZcuMyAMNsy3N
|
Contract review (fable seat, maintainer-directed): PASS. The request/response schemas mirror the implementation member-for-member, with the two divergences argued correctly: The overnight both-sides-re-anchored merge conflict was resolved by re-deriving the Releasing per the maintainer's authorization (2026-08-31): marking ready and arming auto-merge; Generated by Claude Code |
Fixes #12005
Declares the
historyMetaItemmember onMetadataProtocoland de-casts the REST history door — the audit door's named twin, following the #11678/PR #12003 pattern member for member (sibling-inheritance grading per triage; the #11006 maintainer-ruled pattern, 2026-08-22 option B).What changed
packages/spec/src/api/protocol.zod.tsHistoryMetaItemRequestSchema/HistoryMetaItemRequest—{ type, name, organizationId?, sinceSeq?, limit? }, mirroringObjectStackProtocolImplementation.historyMetaItem's parameter type (packages/metadata-protocol/src/protocol.ts) member for member.HistoryMetaItemResponseSchema/HistoryMetaItemResponse—{ events: MetadataEvent[] }, transcribingMetadataEventSchemafrom@objectstack/metadata-core(ADR-0008 §2.4; spec cannot import that package — same transcription approach as the audit twin's event rows).MetadataProtocol.historyMetaItem?(...)— optional, like itsauditMetaItem/deleteMetaItem/getMetaItemLayeredsiblings.packages/rest/src/rest-server.ts(GET /meta/:type/:name/history)if (!(p as any).historyMetaItem)→if (!p.historyMetaItem)— identical truthiness semantics, minus the cast.(p as any).historyMetaItem({...})→ a named const typedTransportScopedMetaRequest<HistoryMetaItemRequest>— the reset-door spelling, not the audit door's plain request type, because this door still spreads the transport-levelenvironmentId(see member map). Same keys, same conditional spreads, byte-identical wire payload.Tests: three capture/pin suites in
packages/spec/src/api/protocol.test.tsmirroring the #11678 blocks (request mirror + value preservation, response body incl. honest-empty and closedopvocabulary, type-level member pins with@ts-expect-erroron undeclared keys). Two new isomorphism pins (Iso863/Iso864) intype-alias-convention.pin.test.ts(833 → 835, ledger paragraph appended).Changesets:
@objectstack/specminor +@objectstack/restpatch — the same class as PR #12003's pair (spec-audit-meta-item-member/rest-meta-audit-reset-casts-retired).Per-member map (inheriting PR #12003's verdicts)
type,namez.string()auditMetaItem(#12003)organizationIdz.string().optional()— NOT nullablestring | nulland its door always sendsctx?.tenantId ?? null, hence.nullable()there; history's implementation declares plainorganizationId?: stringand this door sends no organization at all. Whether the door SHOULD send one is the #8747-family measurement the card fences to a future issue — deliberately unanswered here.sinceSeqz.number().optional()HistoryOptions.sinceSeq). No audit equivalent; treated by the same rule aslimit.limitz.number().optional(), no bounds.min()/.max()would refuse values the shipped verb accepts (the same no-accept/reject-drift rule #12003 recorded for audit's clamp).environmentIdTransportScopedMetaRequestexactly like the reset door in #12003's second half. #12003 did not remove the audit equivalent (that was #8747, earlier), so removing this door's dead spread is not inherited — recorded below instead.eventsMetadataEvent[]transcription,openum closedoperation/outcome.ref.typeis a plain string, not the registry enum — plugin runtime-create types flow through this door (the #12038 1C anti-freezing reasoning);ref.namecarries no regex, same rationale.Deliberately NOT in this PR (per card fences)
error: 'History query not supported...', nocode) — finding:rest-server.ts里三个相邻/metahandler 的错误信封是三种不同形状,其中两种不符合 ADR-0112 #7035-family ratcheted debt (check:route-envelopecarries it); converging it is a behaviour change this declaration must not smuggle. Untouched, byte-identical. finding:rest-server.ts里三个相邻/metahandler 的错误信封是三种不同形状,其中两种不符合 ADR-0112 #7035 remains open.organizationIdtenant-scoping question for history rows (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 family) — a measurement for a future card; 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 is not addressed here.environmentIdspread the door sends and the implementation never reads — feat(spec): declare the audit door and catch the delete request schema up — both meta-door casts retired #12003 did not remove its audit equivalent (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 had already done so), so there is no feat(spec): declare the audit door and catch the delete request schema up — both meta-door casts retired #12003 verdict to inherit; recorded here, untouched.saveMetaItem— twin cardsaveMetaItemis a declared REQUIRED member whose request schema declares 3 of the ~11 members the REST PUT door sends, so the call-site literal is castas any#12004 remains open (serialized behind this one), out of scope here.meta.getHistorykeeps its inline return type) and the route-ledger row (responseSchemaunset for the history route) — both are the 17 client-SDK methods have no published response contract to bind to: the wholemeta.*history/diagnostics family (9) plus 8packages.*routes whose handlers call(protocol as any).<method>#12038 rebinding class, not the feat(spec): declare the audit door and catch the delete request schema up — both meta-door casts retired #12003 declaration class; recorded for a follow-up.Verification
All at final head
4bcd09f0(the union re-ran after the last commit):pnpm --filter @objectstack/spec typecheck && pnpm --filter @objectstack/spec test— exit 0 via the shared verify lock; vitest:Test Files 442 passed | 1 skipped (443)·Tests 11819 passed | 1 skipped (11820).pnpm exec turbo run typecheck --filter=@objectstack/rest(Tasks: 25 successful, 25 total) thenpnpm --filter @objectstack/rest test— exit 0; vitest:Test Files 161 passed (161)·Tests 2703 passed (2703)(includesrest-server-query-multiplicity.test.ts, which pins the history door's{ limit, sinceSeq }forwarding — behaviour unchanged).pnpm --filter @objectstack/spec check:generated— exit 0 at4bcd09f0(earlier run proved 4 artifacts stale and--fixregenerated exactly those; the committed diff carries them).4bcd09f0:check:spec-parsed-alias,check:route-envelope(ratchet counts untouched — the 501 literal is byte-identical),check:nul-bytes— all exit 0. Earlier at the pre-docs-fix head, also green:check:authz-resolver,check:dispatcher-error-vocabulary,check:test-source-alias,check:quick-reference-counts,check:doc-anchors,check:docs-single-h1,check:llms-txt,check:doc-authoring(this one first went RED on two internal issue ids in the new.describe()strings — a real catch; fixed in the second commit and green since).Ablation (de-cast proof)
Both legs rebuilt
@objectstack/specand proved the tree state on disk (full log in the report comment's transcript):protocol.zod.ts+ the four generated spec artifacts reverted to the pre-declaration base602d4a0a; proven by grep (historyMetaItemsrc count 3 → 0, manifest 0) and byablation-dist-preflight --absentafter the rebuild (marker gone from all 215 dist files). First attempt reverted only the source and the spec build itself refused (manifest still listed the new schemas) — a null run, redone as a five-path mutation.pnpm --filter @objectstack/rest typecheckexit 2 —TS2724 '"@objectstack/spec/api"' has no exported member named 'HistoryMetaItemRequest'+TS2339 Property 'historyMetaItem' does not exist on type 'RestProtocol'at both the guard (:6006) and the call (:6040). (Predicted TS2305 for the import; tsc emits the near-name variant TS2724 — same missing-export class.)ablation-dist-preflightfinds the marker back indist/api/index.d.ts/d.mts, whole-treegit statusclean, rest typecheck exit 0.The script carried an EXIT/INT/TERM trap with absolute paths; the hash comparison, not the trap, is the restore proof.
Generated by Claude Code