Uh oh!
There was an error while loading. Please reload this page.
refactor(rest): source package-routes getMetaItems types from the spec - #9959
Conversation
The `protocol.getMetaItems` option in `PackageRoutesOptions` re-declared the
request/response as a hand-rolled structural type (`{ type: string }` /
`{ items: any[] }`), so the spec was free to move underneath the seam while
this module kept compiling green.
Source both from `@objectstack/spec/api` instead, keeping the member OPTIONAL
and both call sites' `typeof … === 'function'` feature-detection:
`MetadataProtocol` declares this verb REQUIRED, and adopting it whole would
change what the seam tolerates.
Adds a compile-time coupling pin (exact type equality, in compiled source
rather than a test file this package's tsc program excludes).
Part of #9846📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 13 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 e859cd6214e6138808da341ec8b4485ab6cca198 && git checkout e859cd6214e6138808da341ec8b4485ab6cca198
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5989b0de9018a951a62a5cbd8d0f762832124a76 832d973419a3adc75b6c17de03ec6357a75e5e18 && git checkout -B drift-repro 5989b0de9018a951a62a5cbd8d0f762832124a76 && git merge --no-ff 832d973419a3adc75b6c17de03ec6357a75e5e18
node scripts/docs-audit/affected-docs.mjs --json 5989b0de9018a951a62a5cbd8d0f762832124a76
|
huangyiirene
commented
Aug 19, 2026
PM review — ACCEPTReport received GitHub-first as instructed (#9846 comment
Boundary — held exactly
The premise, re-measured and strengthenedI told you Two things I suspected and measured away
⭐ The sharpest thing in this run: where the pin livesYou put the type-level assertion in compiled source, not in a So a type-level assertion written in a test file for this package would be compiled by nothing — a check that evaluates never, passes always, and stays green when deleted. Most implementations of "add a type-level pin" ship exactly that phantom. Catching it required reading the package's own tsconfig instead of following the habit, and it is the difference between a pin and a decoration. The The ablation is the standard I want other cards held to
⭐ You falsified a standing instruction of mine — record itMy brief carried the warning as if universal: "Known gap, three confirmed instances (#9721 / #9700): dispatch-gates names neither You measured that GatesEleven green, read as counts.
⏳ Landing
Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Part of #9846
PackageRoutesOptions.protocoldeclared its meta-read verb as a hand-rolledstructural type instead of naming the shapes
packages/specalready declares:Nothing was broken by it — both call sites send exactly
{ type: 'package' },a valid request, and both read
result?.itemsdefensively. The defect is thata locally re-stated request type lets the spec move underneath this module —
a narrowed
typevocabulary, a newly required member, a renamed key — whilethe file keeps compiling green against a shape the protocol no longer has.
Both are now sourced from
@objectstack/spec/api:This is the pattern landed in
rest-server.ts(45862a53d8): declared typesnamed, optional call and
typeof … === 'function'guards and defensive resultreads deliberately preserved.
The optionality is deliberately kept
MetadataProtocoldeclaresgetMetaItemsas a required member, while thisoption is optional and feature-detected at both call sites. Adopting
MetadataProtocolwhole would change what the seam tolerates — a behaviourquestion this change does not answer.
What naming the declared response surfaced
The spec types
itemsas an array ofunknown, because it says nothing aboutwhat a metadata item contains; the local shape had claimed an array of
any.The compiler reported three errors at the first call site, which read
registry-specific keys (
manifest.id) that are not spec-declared.The element read therefore stays runtime-shaped on purpose — the same
disposition the sibling meta-read doors take via
metaItemsArray. The seam istyped; the coercion is confined to the read and changes no behaviour. Neither
tscnor ESLint objected to the defensiveresult?.itemschain.The pin, and what it would actually catch
A compile-time exact type-equality assertion that the option's
request/response types are still the spec's. A test that only drove today's
call sites would not notice spec drift; this fails both when someone
re-hand-rolls the local shape and when the spec's shape changes without this
seam being re-read. A third pin holds the member optional, so a later edit
cannot quietly adopt the required-member form.
Mutual assignability would not do: the old local shape and
GetMetaItemsRequestare assignable in both directions, so an assignability check would pass on
exactly the shape this change removes.
It lives in compiled source, not a test file. This package's
tsconfig.jsonexcludes its test files and no sibling gate type-checks them,so a type-level assertion written there would be compiled by nothing — a
phantom check that evaluates never and stays green when deleted.
Ablation (reverting only the seam to the hand-rolled shape) reports 2
errors — the request pin and the response pin, both
Type 'false' does not satisfy the constraint 'true'. The optionality pincorrectly stays silent, since the ablated shape kept the member optional.
Restoring reports 0. Both legs read from reported counts, never a piped
exit code. No rebuild is involved:
tsc --noEmitreads this package's sourcesdirectly, so no
dist/staleness can flatter either leg.deletePackageis deliberately untouchedThe card body says "Both verbs are already declared in
packages/spec". Thatis half false, re-measured here on
301bf26be1:getMetaItemsis declared,but a search for
deletePackage,DeletePackageRequestandDeletePackageResponseacross all ofpackages/spec/src/returns no typedeclaration — only prose in two semantic-migration entries. There is no
declared shape to read, and minting one is a contract act for the spec seat,
not a typing cleanup. Its local structural type is left exactly as it was.
Verification
Run against
832d973419, the tree this PR pushes:pnpm --filter @objectstack/rest typecheck— cleanpnpm --filter @objectstack/rest test— 129 files, 2106 tests passedpnpm lint— cleanpnpm check:route-envelope— 10 route modules audited: 7 conformant, 2ratcheted, 1 exempt. The two ratcheted are
rest-server.tsanderror-response.ts, both pre-existing; this file is conformant and noratchet moved.
pnpm check:cross-package-test-inputs,check:dispatcher-error-vocabulary,check:slot-lookup(107 unswept sites, none new),check:changeset-gate-self-tests,check:objectui-changeset,check-adr-0087-registration.mjs,check-changeset-no-major.mjs,check-empty-changeset.mjs,check-cross-package-test-inputs.mjs,docs-audit/check-affected-docs.mjs,check-nul-bytes.mjs— all greenGates were re-derived from the actually-changed paths after the final commit.
No
package.jsonedit was needed:@objectstack/specis already aworkspace:*dependency of this package.Internal typing only —
PackageRoutesOptionsis not exported from thepackage's entrypoint, so no public surface changes and no route changes what it
accepts or rejects.
Generated by Claude Code