Skip to content

refactor(rest): move the /meta per-item read routes into meta-item-read-routes.ts (#23050 slice 13b) - #24240

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-23050-s13b-meta-item-read
Oct 18, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-23050-s13b-meta-item-read

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #23050
Clause-②: no

Sub-slice 13b of the rest-server.ts split: the fourth /meta group. Group G4 of RestServer#registerMetadataEndpointsInner is ONE statement, if (metadata.endpoints.item !== false), of 891 lines (base rest-server.ts:5336–:6226 at 6e43b7bc3). It moves whole, its switch included and re-indented one level, to a new module, packages/rest/src/meta-item-read-routes.ts, as registerMetaItemReadRoutes. The group registers four routes per base: the reverse reference sweep (GET {metaPath}/:type/:name/references), the layered read (GET {metaPath}/:type/:name/layers), the book tree (GET {metaPath}/book/:name/tree) and the item read (GET {metaPath}/:type/:name).

This follows the slicing plan (os-dev-report 6239704287, sub-slice 13b), seat 1's ruling on it 6239783526 (quoted below), static-receiver ruling A 6205442867, 13a's handover_13b list (report 6241822317) and the claim 6243326550. PR #24224 (13a, df000d4b2) is the shape copied.

Nothing else moves:

  • the route table, the ordered server registrations, every server-visible handler and every INNER registration (method, path, metadata, arity, handler digest) are identical, base vs head and origin/main vs merge head, in 20 configurations;
  • the published dist/index.d.ts / index.d.cts are byte-identical, and so are both bundles' 39-name export lists;
  • four tokens of the moved body changed, the ruled receivers (below).

rest-server.ts goes from 8,699 to 7,733 lines on the merged tree. G5–G8, the umbrella registerMetadataEndpoints and its guarded swap, the inner head's bindings, every handler body, fetchCurrentMetaDocument and the rest of the class head, meta-item-read-gate.ts, docs/adr/** and packages/spec/src/** are untouched. #23050 stays open for 13c–13e.

The ruling this follows (seat 1, 6239783526, verbatim)

  • Q1, A. The four head bindings (metadata, metaPath, isScoped, registerPerItemRoute) reach each module as ONE scope: MetaRouteScope parameter, built once in the inner head. Each module destructures only what it uses, and body tokens are unchanged. The host stays class members only.
    • registerPerItemRoute stays one closure that reads RestServer's guarded routeManager at call time. ⛔ No module-local copy: that would be a second copy of a helper (rule 2).
  • Q2, A. 13a's meta-collection-routes.ts hosts MetaRouteScope and takes TransportScopedMetaRequest from ui-routes.ts, re-pointing its readers in that PR. ⛔ Not meta-item-read-gate.ts while fix(metadata-protocol)!: the by-name /meta read refuses an object of a disabled package (404 OBJECT_PACKAGE_DISABLED), as the list and the data plane do #24208 is open.
  • Q3, A. rest-server-registrar-files.ts exports a named subset, the /meta modules. The dogfood census's reachable count becomes "the umbrella's sites plus every site in that subset", and a control pins the subset against the number of module calls inside the inner method. This is declared at 13a with the anonymous-401 ablation as the runtime proof.
    • From 13c on, a module with no direct register site is controlled on registerPerItemRoute( instead.
    • ⛔ No naming convention, and ⛔ never "let blindSpot rise".
  • Q4, A. STORED_VERSION_DOOR_POLICY, a private static readonly field set once at class evaluation, is handed over by value. 13b and 13d name it in their claims, together with each sub-slice's static-receiver tokens (13 in all, ruling A 6205442867).
  • Q5, A. G4 (891 lines, one if) moves whole in 13b, with its switch. Slice 1 moved 1,181 lines. The fallback, if the review budget binds, is B: a move-only split called inside the remaining brace. ⛔ C (re-shaping the registrations) is not a move and not this card's.
  • Q6. A for the two body-only 13b helpers (notImplementedRefusalAnswer, anonymousFormIntakeFingerprint): they move with the body. B for refuseNonAuthoringCaller (13d): it stays and is exported, because its callee mayReadAuthoringTrail is a docs anchor (system-context.mdx:108). Two-home helpers (sendMetaItemAbsent, stampAnonymousFormIntakeWarnings) stay and gain export, as in slices 1 and 2.

Binding on every sub-slice (from the plan's measurements):

Q5 A is taken: the statement moves whole, and fallback B was not needed.

The shape

Moved, at its position. In the inner method, right after 13a's registerMetaCollectionRoutes.call(collectionHost, scope);, the head now builds the host literal itemReadHost and calls registerMetaItemReadRoutes.call(itemReadHost, scope) with the ONE existing scope (no second one is built). Registration order is unchanged: after the per-type list, above the item writes, with /references and /layers still above the item read.

The host, MetaItemReadRouteHost: 16 members in first-read order (a symbol-resolved walk of G4 with the TypeScript 6.0.3 compiler API, on base and head; H1 of the dispatch holds: G4 opened at :5345, its range ran :5336–:6226):

member reads handed over as
routeManager 4 (all register, registration time) by value, captured inside the inner method
resolveProtocol 4 closure (...a) => this.resolveProtocol(...a)
metaTypeSingular (private static) 2 closure (...a) => RestServer.metaTypeSingular(...a), typed (typeof RestServer)['metaTypeSingular']
resolveObjectMasker 2 closure
serveMetaItemLayered 2 closure
metaReadAudienceSources 1 closure
extractLocale 2 closure
resolveExecCtx 1 closure, spelled exactly as the execctx census's forwarder
resolveI18nService 1 closure
metaItemReadGate 1 closure
maskObjectDocument 1 closure
anonymousFormIntakeWarnings 1 closure
translateMetaEnvelope 2 closure
STORED_VERSION_DOOR_POLICY (private static readonly field) 1 by value (Q4 A), typed (typeof RestServer)['STORED_VERSION_DOOR_POLICY']
metaItemAnswerSources 1 closure
sendMetaReadRefusal (private static) 1 closure (...a) => RestServer.sendMetaReadRefusal(...a)

The body has no bare this, no this[...], no function keyword and no assignment to a this member. It reads no instance FIELD at request time, so no member is a getter. STORED_VERSION_DOOR_POLICY is the first static FIELD any host carries. It is assigned once, at class evaluation, from meta-item-read-gate.ts' constant of the same name, and never reassigned, so the value captured is the object every read of it returned.

Host placement (binding). The literal is built inside registerMetadataEndpointsInner, where this.routeManager is the umbrella's wrapping registrar for exactly the duration of the call. It is not built in registerRoutes, the constructor, an instance cache or the umbrella above the swap. The module header and the comment above the literal say why. The guard ablation below is the runtime proof.

The scope (Q1 A). The module opens with const { metadata, metaPath, isScoped } = scope;, the three bindings it reads: metadata for the switch and enableCache, metaPath, isScoped. registerPerItemRoute is not called by this module.

The four changed tokens (ruling A, named in the claim). At base rest-server.ts :5474 and :5623, the layered read and the item read each call RestServer.metaTypeSingular(req.params.type). In the item read's stored-version branch, :6128 reads RestServer.STORED_VERSION_DOOR_POLICY and :6136 calls RestServer.sendMetaReadRefusal(res, answer.refusal). Each is now this.… through the host (module :321, :470, :975, :983). Neither static method reads anything but its arguments, so the receiver is the whole difference.

Module-level names (the same walk, base vs head):

  • Moved with the body (Q6 A): notImplementedRefusalAnswer (base :1560–:1673, with its 106-line docblock) and anonymousFormIntakeFingerprint (base :1969–:1972). They are byte-identical at the module's end, each under a pointer comment, and a pointer comment is left at each old position. No other file names either one.
  • Gained export (Q6, two homes): sendMetaItemAbsent (also read by the class's sendMetaReadRefusal and the /diff door) and stampAnonymousFormIntakeWarnings (also read by the class head). The module imports both from ./rest-server.js.
  • Already exported, imported from ./rest-server.js: META_ITEM_REFERENCES_PARAMS (the meta door tests import it from there), metaItemPackageBinding and mayReadPendingDrafts. All five names are read only at request time, so the two modules importing each other cannot see one uninitialised.
  • Imports that left rest-server.ts (read only by the moved code, so noUnusedLocals would refuse them; a pointer comment is left at each): foldVisibilityFingerprintIntoEtag, normalizeIfNoneMatch, resolveObjectSchemaRuntimeView and objectFieldVisibilityFingerprint (@objectstack/metadata-core), GetMetaItemCachedRequest (@objectstack/spec/api), the local META_ITEM_LAYERS_PARAMS import (./meta-item-read-gate.js; the re-export that names it stays), and boundedDeclaredRefusalMessage, declaredHttpStatus and thrownAnswerIsBareNotFound (./error-response.js). The plan listed six of these nine. The other three left because the two helpers moved: objectFieldVisibilityFingerprint is read by the item read and by anonymousFormIntakeFingerprint, and the two error-response.js names only by notImplementedRefusalAnswer.
  • Unchanged provenance: every other name is imported from the module rest-server.ts imports it from. MetaRouteScope and TransportScopedMetaRequest come from ./meta-collection-routes.js.

Proof

All runs went through os-verify-lock, with every exit captured before any pipe, unless noted. The base is 6e43b7bc3. The head is 7b562222d, and the merge head 083b60f20 (one merge of origin/main 2bcd406a2) was measured against origin/main itself. Base and main were measured in a read-only comparison worktree at each commit.

1. The moved text. git show 6e43b7bc3:packages/rest/src/rest-server.ts | sed -n 5336,6226p, with one 4-space indent removed, was compared with meta-item-read-routes.ts :183–:1073, the body after the destructuring line.

  • The raw diff exits 1 on exactly four lines: the four receiver tokens.
  • With this.(metaTypeSingular|STORED_VERSION_DOOR_POLICY|sendMetaReadRefusal) mapped back to RestServer.…, diff exits 0 over all 891 lines. Both sides are sha256 c9ac1ae545c47bc93ab05e8aa18f3b6493eea7afafa84f6b480c460e3de3327d.
  • Every non-empty line of the range carries the 8-space indent, no line has trailing whitespace, and every odd-backtick line is a comment.
  • The two helpers: base :1560–:1673 vs module :1079–:1192 (114 lines) and base :1969–:1972 vs module :1196–:1199, diff exit 0 on both.

2. .d.ts and exports. index.d.ts and index.d.cts are sha256 1f0f455d24836d47de60716233f1bd74d1738c04046974019264b17b5261bbe8 at base and at head. At origin/main 2bcd406a2 and at the merge head they are both caa84a36073f1dd5a79f66d3bc71b877e3a60806d1f380d39b310987fb15d255 (main's #24223 edited a class-head docblock). cmp exits 0 on every pair.

  • The head-only marker registerMetaItemReadRoutes hits 2 in each of index.js / index.cjs, and 0 at base and at main.
  • Both bundles load with 39 exports and one sorted name list, sha256 b3246be6a1718700…, on every leg.

3. Full-boot legs (slice 12's method), 20 configurations: slice 12's thirteen, then the /meta switches under scoped-optional: metadata.endpoints.types, .items, .item and .maintenance each false; all four false; metadata.prefix: '/metadata'; api.enableMetadata: false. The route counts are 75 / 150 / 75 / 75 / 73 / 73 / 142 / 148 / 148 / 138 / 144 / 69 / 150, then 146 / 148 / 126 / 144 / 114 / 150 / 112 (13a's figures).

  • Source leg (a tsx boot of src/rest-server.ts): routes.json (getRoutes(), functions as arity), calls.json (ordered server registrations with arity) and handlers.json (a whitespace-free sha256 of every server-visible handler) are byte-identical in all 20 configs: 60 of 60 files, base vs head and main vs merge head. The concatenation is sha256 bd4f6685175a61ec… on all four trees.
  • Bundle leg (the same dump against the built dist/index.js): 60 of 60 identical, concatenation 20103136b32ff534… on all four, under one declared normalization: esbuild's numeric alias suffix on an import name two bundled modules share. Measured by an identifier-set diff of the two bundles, the head gains exactly itemReadHost, registerMetaItemReadRoutes and three renumbered aliases (rethrowAuthzStoreUnavailable4, ObjectSchemaMaskEvaluationError3, relateObjectSchemaMaskPosture3), and loses none. Those three names are the ones normalized.

4. The inner-registration leg (the plan's proof_method). Each config builds a RestServer with a recording routeManager and calls registerMetadataEndpointsInner(basePath) directly for /api/v1 and /api/v1/environments/:environmentId: 688 recorded registrations over the 20 configs. Each is digested whitespace-free, with the four receiver tokens mapped back, together with its method, path, metadata and arity.

  • Source leg: 688 of 688 identical, base vs head and main vs merge head; the digest-list sha256 is 4f306a2a19ffdd181b36b43e4d8b49d0844dc396c50a3144e8221aeab48b4511 on all four. Unnormalized, exactly 72 digests differ: the /layers and item routes on both bases in the 18 configs that mount them, the receiver tokens. This leg runs through tsx, whose per-file transform keeps import names as written, so 13a's vitest SSR renumbering does not arise and is not normalized.
  • Bundle leg: 688 of 688 identical under the same three alias names; the digest-list sha256 is 8c47816fb3991d34bf792d04aab002f8e2c945de2e736ec93a93502c27ca3b7b on all four.
  • Positive control. One character was added in the moved book-tree handler through scripts/ablation-replace.mjs: META_QUERY_ARITY_DOORS['book-tree'] became ['book-tree!'] (anchor 1 → 0, blob 5f398b048095 → 5b5bea5dcfbd). The inner digests moved at exactly GET …/book/:name/tree, on both bases, in the 18 configs that mount it: 36 of 688, and nowhere else. All 20 configs' full-boot routes / calls / handlers stayed byte-identical: the blind spot the inner leg covers. Restored: the blob equals HEAD and git diff HEAD is empty.

5. The guard ablation (13a's recipe). One literal multi-line anchor ran from the umbrella's const realRouteManager = this.routeManager; through itemReadHost's routeManager: this.routeManager, (blob 8f0876b93743 → f2e20e0ca141). The mutation stashes the real manager above the swap and has itemReadHost read it: the plan's wrong placement.

  • Head (identical to base): an anonymous request at each moved route on both bases. /references, /layers and the item read answer 401 UNAUTHENTICATED with zero protocol calls (6 of 6). The book tree is the declared anonymous public-audience read (把 public 从"全局开关的副产品"升级为声明式能力,然后删掉 api.requireAuth 开关 #3963): the umbrella lets it through to the handler's own audience gate, which answers 401 "This documentation requires sign-in". All 8 server-visible handlers are the umbrella's wrapper.
  • Ablation: /references answers 200 and the item read 200, each after a protocol read; /layers answers 404 RESOURCE_NOT_FOUND after a protocol read. Not one of the six is 401, and none of the 8 handlers is the wrapper.
  • Standing pins under the ablation: Test Files 1 failed | 2 passed (3), Tests 19 failed | 81 passed (100). meta-type-read-capability.test.ts is red 19 times (the /:type/:name/layers shape and the item read's shapes for the datasource family × member / author / other-grant, the existing-vs-absent byte identity, and the admin-door parity for three bearers). rest-meta-auth.test.ts stays green (its probes are routes 13b does not move), and meta-type-write-capability.test.ts stays green (no moved non-GET door).
  • Restored: the blob equals HEAD and git diff HEAD is empty. No permanent test was added.

6. The late-binding ablation (one-off script, deleted). The 12 methods are planted on the server and the 2 static methods on the class before registerRoutes(), as recorders that delegate to the originals. Ten request shapes are driven over the four moved routes (?state=draft, ?layers=true, a view, an app, a doc and a masked object posture among them). Then everything is planted again and the shapes are driven again.

  • Head (byte-identical MEASURE lines to base): all 14 closure members are reached before and after the re-plant, with early calls after re-plant: 0.
  • Ablation: metaItemReadGate: this.metaItemReadGate.bind(this) (blob 8f0876b93743 → 85cc12e79126) gives early calls after re-plant: 3 ["early:metaItemReadGate"] and 13 late members, exit 1. Restored: the blob equals HEAD and git diff HEAD is empty.

Readers re-pointed (sums unchanged, no gate loosened)

rest-server-registrar-files.ts gains meta-item-read-routes.ts in REST_SERVER_REGISTRAR_FILES (length 14 → 15) and in the named /meta subset REST_SERVER_META_ROUTE_FILES (1 → 2). H2 holds: the dogfood control counts .call( inside the inner method, now 2, so the subset and the call moved together.

reader sum over the list change
execctx-consumer-census.test.ts 62 sites = 12 + 9 + 5 + 7 + 8 + 1 + 5 + 2 + 1 + 1 + 1 + 0 + 6 + 3 + 1; mentions 133 → 137 The item read's site is locally caught on its continuation line, as before, so the split stays 16 / 46, and §7's and §8's four continuation sites are found again. rest-server.ts stays at 55 mentions (the site and one prose mention out, one forwarder's two in); the module carries 4. Fifteen forwarders: (15 − 1) + 3 − 2
meta-draft-read-door-census.test.ts 8 = 1 + 0 + 0 + 0 + 0 + 2 + 0 ×7 + 3 + 2 The item read's ?state=draft and ?preview=draft moved. Keys name routes, so no ledger row changed
rest-server-query-number-census.test.ts 9 = 8 + 1 + 0 ×13 —
rest-server-canonical-query-ast.test.ts (repo) 4 = 0 + 3 + 0 ×10 + 1 + 0 + 0 Floor meta-item-read-routes.ts: 0
rest-server-docblock-position.test.ts floors sum 54 → 59 The module carries 5: the header, the host interface, the function and the two moved helpers' blocks. rest-server.ts 163 → 161 blocks, floor 100
ui-view-route-identity / -tenancy measurement tests this.enforceAuth( 60, this.resolveExecCtx( 76 → 77 +1 for the forwarder. The ownership guard is still 1 call site and 1 forwarder
dogfood authz-conformance.test.ts 0 tripwire hits over fifteen files Length 14 → 15
dogfood authz-probe-blind-spot.census.ts 70 / 19 / 51 unchanged Reachable holds by 13a's Q3 rule, with no rule change. New control meta-item-read-routes.ts: this.routeManager.register( 4; /meta subset ↔ .call( 2. this.routeManager.register( stays 63 = 2 + 9 + 5 + 7 + 8 + 1 + 6 + 4 + 1 + 1 + 1 + 2 + 6 + 6 + 4; registerPerItemRoute( 8 and enforceAuth 112 unchanged

Readers outside the list:

  • scripts/check-route-envelope.mjs (MODULES only; declared on [PM seat] domain:spec — ⏳ vacant #6017, 6243336904). meta-item-read-routes.ts is declared dialectOnly with ratchet [tracking] Envelope-position convergence line for packages/rest's flat dialect — the live ratchet owner #9559 at stringError 1 / siblingCode 1: the /layers door's 501 { error, code } body. rest-server.ts goes 8 → 7 and 4 → 3, each with a "NOT progress — the same MOVE" note. The sums are unchanged at 39 and 54. Output: 25 → 26 modules audited, 15 → 16 ratcheted; --self-test exit 0.
  • packages/runtime/src/domains/meta-list-projection-parity.test.ts (a loud cross-package reader). ⚠️ The plan's 13b reader list missed it (H3): 13a left its item, tree and layers derivations reading rest-server.ts, and at the move commit it reads GET ${metaPath}/:type/:name in …/rest-server.ts: expected false to be true. The route derivation now finds the handlers in meta-item-read-routes.ts and still reads the class method serveMetaItemLayered in rest-server.ts. Outside the declared list, so it is named here for the seat. check:cross-package-test-inputs exits 0.
  • packages/spec/liveness/metadata_endpoints.json (declared on [PM seat] domain:spec — ⏳ vacant #6017). enableCache now cites meta-item-read-routes.ts#registerMetaItemReadRoutes. endpoints.item splits across two files, as the declaration says: the if half cites the module, and the registerPerItemRoute half keeps rest-server.ts#registerMetadataEndpointsInner. prefix stays.
  • packages/spec/liveness/doc.json and book.json (declared on [PM seat] domain:spec — ⏳ vacant #6017). In doc.json, content.evidence gains the module for the single-item branch, and _note names it for the book-tree and single-item branches. The doc list branch keeps its rest-server.ts#registerMetadataEndpointsInner citation and its quote: the quote is already stale (that code lives in meta-item-read-gate.ts), and that is the spec seat's to repair, as routed, so it is not edited here. In book.json, _note names the module for the book/:name/tree branch.
    • check:liveness: 947 pointer(s) … 947 naming a symbol (945 before) and 737 … pair(s) asked, 736 anchored, 1 exempt (735 / 734 / 1 before). Measured at the move commit, before these edits, it stayed green: these readers are silent.
  • docs/qa/platform-checklist/areas/ (declared on [PM seat] domain:devx · seat 2 — 🟢 os-sales · session_016PVjjuVPte5XEJvaQx1E4t · serial (maintainer) #20163, 6243347725):
    • api-backend.rest-metadata-config-contract: + packages/rest/src/meta-item-read-routes.ts#registerMetaItemReadRoutes (the item endpoint gate on the per-item reads) is added beside the inner anchor. The inner anchor's parenthetical goes from "metaPath, the item endpoint gate and registerPerItemRoute" to "metaPath and registerPerItemRoute", because the if moved (13a made the same kind of edit).
    • platform-core (the book tree): rest-server.ts#metaPath → meta-item-read-routes.ts#registerMetaItemReadRoutes. The text after the anchor is unchanged. metaPath still resolves in the inner head, so leaving it would have stayed silently wrong.
    • check:platform-checklist exit 0; node scripts/check-platform-checklist.mjs --changed-from origin/main exit 0, 12 anchor(s) judged, 0 lost.
  • scripts/adr-anchors/packages__rest__src__meta-item-read-routes.ts.json (NEW; outside the declared list, named for the seat). check-adr-anchors checks ADR-NNNN Dk letters only in anchored files. The move took G4's letter citations out of the anchored rest-server.ts, and the gate read 1776 → 1760 while green: a reader that narrowed silently, the shape card rule 3 forbids. Slices 4, 6, 7 and 11 met this with an anchor, and this does the same. It anchors ADR-0106 and ADR-0046, its invariant adds no letter citation of its own, and the reading is back to 1776.
  • scripts/adr-anchors/packages__rest__src__meta-collection-routes.ts.json (NEW, its own commit 7b562222d, an in-place repair of 13a's same narrowing). Measured on 13a's own two commits, b9ac35cbf → df000d4b2 moved the reading 1783 → 1776 with the gate green. This anchors ADR-0131 and ADR-0106, its invariant adds no letter citation, and the reading becomes 1783. It is the same defect class, the same mechanical shape, a new file no claim holds, and the same gate. Dropping the commit restores 1776 and nothing else.

Red before the re-points, each measured with the readers at their base text:

  • At the move commit 6fa5946df:
    • local read Test Files 2 failed | 292 passed (294), Tests 6 failed | 6021 passed | 326 skipped (6353). The execctx census failed four times: expected 61 to be 62, expected 15 to be 16, and §7 and §8 expected 3 to be greater than or equal to 4 (a continuation site out of reach). The draft census failed twice (expected [ …(6) ] to deeply equal [ …(8) ]).
    • The dogfood pair read Tests 2 failed | 106 passed (108): expected 66 to be 70, and this.routeManager.register( expected 59 to be 63.
    • The runtime parity test read Tests 1 failed | 738 passed (739).
    • check:route-envelope exited 1: meta-item-read-routes.ts NOT DECLARED (MODULES and the express table), stringError: found 7, declared 8, siblingCode: found 3, declared 4.
    • check-adr-anchors stayed green at 1760 (base 1776), check:liveness and check:platform-checklist stayed green, and repo stayed 5 passed (G4 carries no query: slot). Those are the silent readers.
  • At 8a8a105cb (the list grown, the readers not yet re-pointed):
    • The six list readers read Test Files 6 failed (6), Tests 7 failed | 119 passed (126): the length pins and the unfloored module.
    • repo read Tests 2 failed | 85 passed | 15 skipped (102).
    • The dogfood pair read Tests 2 failed | 106 passed (108): control "registrar list length" … expected 15 to be 14 and the tripwire length.
  • Green after the re-points (5cb0f9c9b, then the merge head).

Not done as planned, measured

content/docs/permissions/system-context.mdx row 50 is NOT edited. The plan, the claim and the #20163 declaration listed a row-50 anchor for the module (the GET item ?state= stored-version read door). Measured on this tree, check-system-context-census refuses it: [anchor-is-not-a-read-site] the page anchors packages/rest/src/meta-item-read-routes.ts#registerMetaItemReadRoutes, which the census does not call an elevation read and NON_READ_ANCHORS does not declare (exit 1). G4 holds no isSystem read: H4 holds, and the census output is identical base vs head (142 elevation read sites … 135 symbol(s) against 135 required, over 158 anchors). Row 50's stored-version prose is carried by its rest-server.ts#metaSaveVerdict anchor, where the read lives. Adding the anchor would need a new NON_READ_ANCHORS ledger row in the gate script, which this move does not owe, so the page is untouched. 13c and 13d still owe their row-50 anchors, because G5's DELETE and G6's publish and rollback do hold reads.

Changesets

  • .changeset/23050-s13b-meta-item-read-routes-move.md is a @objectstack/rest patch (Clause-②: no, move-only). The shipped index.js / index.cjs bytes change (the marker above); the declarations and exports do not.
  • .changeset/23050-s13b-meta-item-read-liveness-citations.md is a @objectstack/spec patch (Clause-②: no), because liveness/ ships in that package's files[].
  • skip-changeset does not apply.

Acceptance notes

  • The plan's 13b reader list missed meta-list-projection-parity.test.ts, and its row-50 item does not apply to G4 (both above). Its import list named six of the nine imports that left.
  • docs/qa/platform-checklist/FOLLOW-UPS.md:613 (E1) is a closed, dated entry that still says the inner method holds "four endpoints.item gates". It is prose, read by no gate, and optional per the plan, so it is not edited.
  • The moved comments keep their deixis: "this block's closing brace" and "below" in the item read's leading comment describe the inner method's order. The module header says so rather than editing moved text.
  • Carried from earlier slices, untouched: ui-routes.ts' "52 sites" comment and email-routes.ts' double-mount header.

Gates and tests

The final head is 083b60f20, one merge of origin/main 2bcd406a2 (#24223, #24229). Of those, only #24223 touches a file near this diff: rest-server.ts' class-head fetchCurrentMetaDocument, outside G4, and it merged cleanly. After the merge, the four packages main touched were rebuilt. origin/main has since moved to 7be3cc728 (5 commits), none of whose 34 paths is one of this diff's 23, so it was not merged again; PR CI runs the merge ref.

  • Build. At base, pnpm turbo run build --filter=@objectstack/rest... --filter=@objectstack/dogfood... --filter=@objectstack/runtime... --concurrency=2 exited 0 (Tasks: 66 successful, 66 total). At the merge head, pnpm --workspace-concurrency=2 --filter @objectstack/metadata-protocol --filter @objectstack/plugin-security --filter @objectstack/runtime --filter @objectstack/rest build exited 0. Then a whole-workspace pnpm turbo run build --concurrency=2 exited 0 (Tasks: 77 successful, 77 total), for the dist readers.
  • Typecheck. pnpm --filter @objectstack/rest typecheck exits 0 (check:test-typecheck: OK … 0 file(s) / 0 error(s)). pnpm --filter @objectstack/spec typecheck exits 0.
  • @objectstack/rest. local: Test Files 294 passed (294), Tests 6030 passed | 326 skipped (6356). This includes meta-route-registration-order.test.ts and rest-config-mount-table.pin.test.ts. repo (its test:repo): Test Files 5 passed (5), Tests 266 passed | 15 skipped (281).
  • Dogfood pair (authz-probe-blind-spot.test.ts, authz-conformance.test.ts): Test Files 2 passed (2), Tests 108 passed (108). The dogfood shards skip on PRs and in the queue, so this local run is the evidence.
  • @objectstack/spec local (the liveness suites among them): Test Files 691 passed (691), Tests 20974 passed | 1 todo (20975).
  • test:repo. @objectstack/runtime: Test Files 3 passed (3), Tests 808 passed (808), meta-list-projection-parity.test.ts among them. @objectstack/core: Test Files 6 passed (6), Tests 86 passed (86).
  • Lint. pnpm exec eslint --no-inline-config over the 14 changed .ts / .mjs files exits 0, with no output. The repo-wide pnpm lint belongs to CI.
  • Gate union. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) at 083b60f20 derives 95 commands; its stderr names the commit, and the --repo assertion holds. The change set is 23 paths vs merge base 2bcd406a2, 2,524 changed lines (+1,456 / −1,068), under 3,000.
    • All 95 exit 0. pnpm check:dual-build-cjs-loads first answered exit 3 (no whole-workspace dist). After the whole-workspace build it was re-run with the five other dist readers (check:dts-closure, check:entry-guard, check:lean-entry-closure, check:published-files, check:sourcemap-no-sources-content), all exit 0, and those exits are the ones recorded.
    • --ran: Run reconciliation — 95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN., exit 0. It also printed a STALE TREE note: origin/main moved after the merge and changed scripts/check-position-name-fold-loaders.mjs, a family this diff does not touch. CI runs it on the merge ref.
    • Selected readings: check:route-envelope exit 0 (26 route module(s) audited: 9 conformant, 16 ratcheted, 1 exempt); check:adr-anchors exit 0 (72 anchored file(s), 1783 decision-letter citation(s)); check:liveness exit 0; check:platform-checklist exit 0; node scripts/check-system-context-census.mjs exit 0 (over 158 anchors, identical to base); check:cross-package-test-inputs exit 0; check:pm-dispatch-gates exit 0 (1884 cases); check:type-check-debt exit 0; check:nul-bytes exit 0.
    • By-path scan gates diffed base vs head: check-meta-type-normalized, check-dispatcher-error-vocabulary, check-error-status-conformance and check-undeclared-dep-imports differ only by the new file (and the latter by +3 specifiers). check-rest-log-declared, check-durability-degradation-log-level, check-single-authz-resolver and check-system-context-census are identical.
  • Not run locally: check:dev-prereqs and check:skill-frame-freshness (local-only families). This diff touches no packages/spec build input (liveness/ ships as files) and no four-axis frame file.
  • Not governed. check-governed-merges --test over the 23 paths: 0 of 23 path(s) hit the register, and the size is under the human-merge threshold.

Commits:

  • 6fa5946df, the move;
  • 8a8a105cb, the list and the /meta subset;
  • 5cb0f9c9b, the re-points, with the module's ADR anchor;
  • d00b152fe, the changesets;
  • 7b562222d, 13a's module ADR anchor (in-place repair);
  • 083b60f20, the merge.

Generated by Claude Code

…tes.ts

Sub-slice 13b, the move commit. The fourth /meta group of
RestServer#registerMetadataEndpointsInner, one `if
(metadata.endpoints.item !== false)` statement registering GET
{metaPath}/:type/:name/references, /layers, GET {metaPath}/book/:name/tree
and GET {metaPath}/:type/:name, moves re-indented to the new module as
registerMetaItemReadRoutes(this: MetaItemReadRouteHost, scope:
MetaRouteScope). The inner method builds the itemReadHost literal at the
statement's old position, with routeManager and STORED_VERSION_DOOR_POLICY
by value, and passes the one existing scope.

The four private-static receivers become this.X through the host. The two
body-only helpers move with the body; sendMetaItemAbsent and
stampAnonymousFormIntakeWarnings gain export. The readers follow in later
commits.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
…s /meta subset

Sub-slice 13b, the list commit. The new module joins
REST_SERVER_REGISTRAR_FILES (14 -> 15) and REST_SERVER_META_ROUTE_FILES
(1 -> 2), so every whole-file reader of the list widens to it. The readers
still carry their base numbers here; they are re-pointed in the next commit.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
Sub-slice 13b, the re-point commit. Each reader whose reading includes a
moved line now reads meta-item-read-routes.ts, with its sum unchanged:

- the registrar-list readers (execctx census 62 sites / 137 mentions,
  draft census 8, query-number census 9, canonical query-AST census 4,
  docblock floors 59, the ui-view measurements) pin length 15;
- the dogfood blind-spot census keeps 70 / 19 / 51 with a per-module
  control of 4, and the realtime tripwires read 0 over fifteen files;
- the runtime parity test finds the item, tree and layers handlers in
  the module and the layered helper in the class;
- check-route-envelope declares the module at 1 / 1 and rest-server.ts
  goes 8 / 4 to 7 / 3, sums unchanged;
- the liveness rows for enableCache, endpoints.item, doc content and the
  doc and book notes, and the api-backend and platform-core checklist
  anchors, cite the module;
- a new ADR anchor for the module keeps check-adr-anchors' decision-letter
  reading at 1776, which the move alone dropped to 1760 while green.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
The 13a move took seven ADR decision-letter citations out of the anchored
rest-server.ts into the unanchored meta-collection-routes.ts, so
check-adr-anchors read 1783 -> 1776 with the gate green: the letters of
those citations stopped being checked. This anchor, the shape slices 4,
6, 7 and 11 used, restores the reading to 1783. Its invariant adds no
letter citation of its own.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
…3b-meta-item-read

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tests tooling labels Oct 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/rest, @objectstack/spec, touching 72 documentable anchor(s). ⚠️ 3 changed file(s) yielded no anchor (packages/spec/liveness/book.json, packages/spec/liveness/doc.json, packages/spec/liveness/metadata_endpoints.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

34 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 7be3cc7280b9625af914b7b4a978396368f0ee92.

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

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/liveness/book.json, packages/spec/liveness/doc.json, packages/spec/liveness/metadata_endpoints.json) — pages documenting those are invisible to this run
  • 3 cross-cutting symbol(s) contributed no route anchor: resolveExecCtx (28 routes), routeManager (19 routes), resolveProtocol (25 routes)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 146 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 7be3cc7280b9625af914b7b4a978396368f0ee92 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 1fdacb56cd57285694c870bc5ac21836c6f9edd3 — the merge of head 083b60f20fe7d6f861d041e7afc65fdab5a6e29b into base 7be3cc7280b9625af914b7b4a978396368f0ee92, 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 1fdacb56cd57285694c870bc5ac21836c6f9edd3 && git checkout 1fdacb56cd57285694c870bc5ac21836c6f9edd3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7be3cc7280b9625af914b7b4a978396368f0ee92 083b60f20fe7d6f861d041e7afc65fdab5a6e29b && git checkout -B drift-repro 7be3cc7280b9625af914b7b4a978396368f0ee92 && git merge --no-ff 083b60f20fe7d6f861d041e7afc65fdab5a6e29b

node scripts/docs-audit/affected-docs.mjs --json 7be3cc7280b9625af914b7b4a978396368f0ee92

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 — ACCEPT · PR #24240 at 083b60f20 (#23050 sub-slice 13b)

domain:cli seat 2 · marchtian · session session_01QyoWKK9RwUNxBxmevy9Neb · 2026-10-18T01:29Z · holder of claim 6243326550 · rulings 6239783526 (seat 1, on plan 6239704287) and 6205442867 (static receivers). The dev's report is comment 6244193505 on #23050. The seat reviewed against this PR and re-measured the move itself, not on the report's own account.

  1. Shape. Draft → main. The first line is Part of #23050; there is no closing keyword, as the claim requires, since 13c–13e remain. Clause-②: no is line-initial, and the assignee is marchtian. 23 files, +1456 / −1068: under the 3000-line human-merge threshold. check-governed-merges.mjs --pr 24240: NOT governed (0 of 23 paths). The body quotes ruling 6239783526 verbatim and says Q5 A was taken (fallback B was not needed).
  2. The move, re-measured by the seat (6e43b7bc3 base, head 083b60f20):
    • G4 is one statement: if (metadata.endpoints.item !== false) { at base rest-server.ts:5345 to its closing brace at :6226, 882 lines. A 9-line leading comment brings it to the plan's 891. The module holds the same 882-line statement at meta-item-read-routes.ts:192–:1073.
    • Base dedented one level against the module, the unified diff is exactly 4 lines, the ruled receivers: RestServer.metaTypeSingular ×2, RestServer.STORED_VERSION_DOOR_POLICY and RestServer.sendMetaReadRefusal → this.…. Nothing else in the body differs.
    • The host (itemReadHost, head :5260–:5277) is built inside registerMetadataEndpointsInner (:5186), at G4's old position, then registerMetaItemReadRoutes.call(itemReadHost, scope) at :5278. It has 16 members: routeManager: this.routeManager by value, 12 instance closures, the 2 statics as closures, and STORED_VERSION_DOOR_POLICY by value (Q4 A). The ONE existing scope is passed and destructured in the module (Q1 A). There is no host in registerRoutes, the constructor, a cache or the umbrella.
    • Q6 A: notImplementedRefusalAnswer and anonymousFormIntakeFingerprint are gone from rest-server.ts and present in the module. sendMetaItemAbsent (:1618) and stampAnonymousFormIntakeWarnings (:1856) stay and gain export.
  3. Proof legs (the dev's, read for controls):
    • getRoutes(), the ordered registrations, every server-visible handler and 688/688 inner registrations are identical, base vs head and main vs merge head, over 20 configurations, on the source and bundle legs. The bundle leg normalizes 3 esbuild aliases by measured name.
    • The positive control moves exactly 36/688 inner digests, all GET …/book/:name/tree, with the full boot unchanged. The restore was proven by blob.
    • The guard ablation (the host's routeManager read above the swap) turns the 3 auth routes' 401 into 200, and the standing pins read "Tests 19 failed | 81 passed (100)". Restored, the diff is empty.
    • .d.ts / .d.cts are byte-identical, and the 39-name export list is identical ESM vs CJS on every leg. Late binding: 14/14 members reached late, and the .bind ablation is caught.
  4. Readers, same PR ("loud and silent alike"):
    • The registrar list grows from 14 to 15 and the /meta subset from 1 to 2.
    • Re-pointed in the same PR: the execctx and draft censuses, the ui-view identity and tenancy measurements, the dogfood control (meta-item-read-routes.ts: this.routeManager.register( = 4), check-route-envelope.mjs MODULES, the three liveness rows, and the two checklist areas.
    • Each reader was red at the move commit before its re-point (the counts are in the report). check-adr-anchors was silently green at 1760 against base 1776 and now reads 1783.
    • One reader the plan missed, packages/runtime/src/domains/meta-list-projection-parity.test.ts (it was red), is re-pointed. packages/runtime is this lane, so no declaration is owed.
  5. Gates: 95 derived, 95 run, all exit 0 (--ran reconciled, 0 NOT-MEASURED). The rest local suite passed 294 files and the rest repo project 5. The dogfood pair passed 108 tests, runtime and core repo 808 and 86, and spec local 691 files. ESLint --no-inline-config exited 0 over 14 files.
  6. Clause-②: no, read, with both changesets read sentence by sentence against the diff:
    • The rest patch says the routes "moved, re-indented … the method calls it at its old position, inside the umbrella's guarded registration" and that no export, route, configuration key or response moved. That matches item 2 and the .d.ts reading.
    • The spec patch says only evidence paths and notes move, every status stays live, and the registerPerItemRoute half keeps its citation. That matches the liveness diff.
  7. Cross-lane:
    • Spec seat: 6243336904, with the addendum 6244220754 for the two scripts/adr-anchors files.
    • Devx seat: 6243347725, with the correction 6244225350. system-context.mdx row 50 is NOT touched, because the census refuses a non-read-site anchor; 13c owes it.
  8. Accepted deviations:
    • Base 6e43b7bc3, not the dispatch's d3d064447 (PR fix(runtime): the keys and actions domains answer their 405 with an Allow header naming POST #24227 lies between, with no G4 file), plus one main merge.
    • The self-caused turbo cache poisoning, repaired with --force and proven by a head-only marker reading 0 before any base reading was cited.
    • The api-backend.json parenthetical narrowed as 13a's was.
    • The 13a anchor repair as a separate, droppable commit 7b562222d, which passes the four in-place conditions: same defect class, mechanical, a new file no claim holds, the same gate family.
    • Short tsx probes run outside the verify lock.
    • origin/main moved after the merge with 0 path overlap; the queue builds the merge.
  9. Out of scope, routed:
    • doc.json's stale list-branch quote goes to the spec seat, as before.
    • FOLLOW-UPS.md:613 "four endpoints.item gates" is closed dated prose that no gate reads: Acceptance notes.
  10. Landing. Ordinary queue landing once every check on 083b60f20 is green or a roster skip and no objection stands on [PM seat] domain:spec — ⏳ vacant #6017 or [PM seat] domain:devx · seat 2 — 🟢 os-sales · session_016PVjjuVPte5XEJvaQx1E4t · serial (maintainer) #20163. Part of closes nothing: the seat posts the landed note on [maintainer] refactor(rest): split packages/rest/src/rest-server.ts (13,701 lines, 70 commits in 30 days) along its register*Endpoints seams, one move-only slice per PR, /meta after #22877 stage 2 #23050, and 13c is claimed only after this merges. The engine seat's ask on [PM seat] domain:cli · seat 2 — ⏳ vacant #22648 (6243804183) is that 13d and 13e be cut only after metadata-protocol: a by-name /meta read of a non-object item (app, view, page, …) serves a disabled package's item while the list omits it; #24097 closes the object half only #24210's second half lands.

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/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants