Skip to content

feat(spec): declare MetadataProtocol.historyMetaItem and de-cast the REST history door - #13521

Merged
os-project-manager merged 5 commits into
mainfrom
claude/issue-12005-history-meta-item-declared
Aug 31, 2026
Merged

feat(spec): declare MetadataProtocol.historyMetaItem and de-cast the REST history door#13521
os-project-manager merged 5 commits into
mainfrom
claude/issue-12005-history-meta-item-declared

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes #12005

Declares the historyMetaItem member on MetadataProtocol and 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.ts

  • HistoryMetaItemRequestSchema / HistoryMetaItemRequest{ type, name, organizationId?, sinceSeq?, limit? }, mirroring ObjectStackProtocolImplementation.historyMetaItem's parameter type (packages/metadata-protocol/src/protocol.ts) member for member.
  • HistoryMetaItemResponseSchema / HistoryMetaItemResponse{ events: MetadataEvent[] }, transcribing MetadataEventSchema from @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 its auditMetaItem / deleteMetaItem / getMetaItemLayered siblings.

packages/rest/src/rest-server.ts (GET /meta/:type/:name/history)

  • Guard if (!(p as any).historyMetaItem)if (!p.historyMetaItem) — identical truthiness semantics, minus the cast.
  • Call (p as any).historyMetaItem({...}) → a named const typed TransportScopedMetaRequest<HistoryMetaItemRequest> — the reset-door spelling, not the audit door's plain request type, because this door still spreads the transport-level environmentId (see member map). Same keys, same conditional spreads, byte-identical wire payload.

Tests: three capture/pin suites in packages/spec/src/api/protocol.test.ts mirroring the #11678 blocks (request mirror + value preservation, response body incl. honest-empty and closed op vocabulary, type-level member pins with @ts-expect-error on undeclared keys). Two new isomorphism pins (Iso863/Iso864) in type-alias-convention.pin.test.ts (833 → 835, ledger paragraph appended).

Changesets: @objectstack/spec minor + @objectstack/rest patch — 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)

Member Verdict Why / precedent
type, name declared, required z.string() door sends, implementation requires — same as auditMetaItem (#12003)
organizationId declared, z.string().optional() — NOT nullable #12003's rule is "mirror the implementation's parameter type member for member". Audit's implementation declares string | null and its door always sends ctx?.tenantId ?? null, hence .nullable() there; history's implementation declares plain organizationId?: string and 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.
sinceSeq declared, z.number().optional() door sends (Number.isFinite-guarded), implementation reads; exclusive lower bound (HistoryOptions.sinceSeq). No audit equivalent; treated by the same rule as limit.
limit declared, z.number().optional(), no bounds door sends, implementation reads. Unlike audit's [1, 500] clamp, history forwards unclamped with no default — declaring .min()/.max() would refuse values the shipped verb accepts (the same no-accept/reject-drift rule #12003 recorded for audit's clamp).
environmentId NOT declared; wire spread kept transport-level by the #9741 ruling; rides TransportScopedMetaRequest exactly 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.
response events MetadataEvent[] transcription, op enum closed per the implementation's return type; closed vocabularies mirror #12003's treatment of operation/outcome. ref.type is a plain string, not the registry enum — plugin runtime-create types flow through this door (the #12038 1C anti-freezing reasoning); ref.name carries no regex, same rationale.

Deliberately NOT in this PR (per card fences)

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) then pnpm --filter @objectstack/rest test — exit 0; vitest: Test Files 161 passed (161) · Tests 2703 passed (2703) (includes rest-server-query-multiplicity.test.ts, which pins the history door's { limit, sinceSeq } forwarding — behaviour unchanged).
  • pnpm --filter @objectstack/spec check:generated — exit 0 at 4bcd09f0 (earlier run proved 4 artifacts stale and --fix regenerated exactly those; the committed diff carries them).
  • Derived families re-run at 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).
  • The remaining repo-wide families run in CI as usual.

Ablation (de-cast proof)

Both legs rebuilt @objectstack/spec and proved the tree state on disk (full log in the report comment's transcript):

  • Mutation: protocol.zod.ts + the four generated spec artifacts reverted to the pre-declaration base 602d4a0a; proven by grep (historyMetaItem src count 3 → 0, manifest 0) and by ablation-dist-preflight --absent after 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.
  • Predicted red, observed red: pnpm --filter @objectstack/rest typecheck exit 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.)
  • Restore: all 5 blobs hash-equal to HEAD, rebuild green, ablation-dist-preflight finds the marker back in dist/api/index.d.ts/d.mts, whole-tree git status clean, 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

claude added 2 commits August 30, 2026 15:04
…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
@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/rest, @objectstack/spec, touching 10 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.

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/kernel/contracts/metadata-service.mdx (via getHistory (sdk))
  • content/docs/kernel/services-checklist.mdx (via MetadataProtocol (symbol))
  • content/docs/plugins/adding-a-metadata-type.mdx (via /:type/:name/history (route), /api/v1/meta/:type/:name/history (route))

2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v15.mdx (via MetadataProtocol (symbol))
  • content/docs/releases/v17.mdx (via MetadataProtocol (symbol))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

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 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: 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 ff3757697600591804e2135a1e58b7afaebe0a48packageMentionDocs.

Which tree this was computed on

This run read content/docs from 1d98ed391a9e9821a18dd85874631b70766b77ed — the merge of head c8a5d8b48ccdebd1db52b7e91f174c0e1ddbdf3c into base ff3757697600591804e2135a1e58b7afaebe0a48, 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 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

⚠️ 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 ff3757697600591804e2135a1e58b7afaebe0a48 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

claude added 3 commits August 30, 2026 16:31
…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

Copy link
Copy Markdown
Collaborator Author

Contract review (fable seat, maintainer-directed): PASS.

The request/response schemas mirror the implementation member-for-member, with the two divergences argued correctly: organizationId plain optional per THIS implementation's declared type (not the audit twin's nullable), and limit unbounded because declared bounds would refuse values the shipped verb accepts. environmentId stays transport-level per #9741 via the TransportScopedMetaRequest spelling at the door; the two anti-freezing widenings (ref.type plain string, no ref.name regex) follow the #12038 1C reasoning. The de-cast door's wire payload is byte-identical and the 501 refusal stays untouched.

The overnight both-sides-re-anchored merge conflict was resolved by re-deriving the system-context.mdx anchors on the merged tree (check-system-context-census: OK — 109 sites, 145 anchors) and regenerating protocol.mdx with the repo tooling, discharging the os-regen deferral.

Releasing per the maintainer's authorization (2026-08-31): marking ready and arming auto-merge; needs:contract-review comes off both carriers.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants