Skip to content

refactor(rest): move the /meta per-item write routes into meta-item-write-routes.ts (#23050 slice 13c) - #24257

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

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #23050
Clause-②: no

Sub-slice 13c of the rest-server.ts split: the fifth /meta group. Group G5 of RestServer#registerMetadataEndpointsInner is two statements, the per-item writes, of 405 lines (base rest-server.ts:5284–:5688 at 9975c26ae): registerPerItemRoute({ method: 'PUT', … }) at :5288 (the item save, PUT {metaPath}/:type/:name) and registerPerItemRoute({ method: 'DELETE', … }) at :5528 (the reset to the artifact default, DELETE {metaPath}/:type/:name). They move, re-indented one level, to a new module, packages/rest/src/meta-item-write-routes.ts, as registerMetaItemWriteRoutes. It is the first registerPerItemRoute pass-through: the module registers nothing on the route manager itself.

This follows the slicing plan (os-dev-report 6239704287, sub-slice 13c), seat 1's ruling on it 6239783526 (quoted below), static-receiver ruling A 6205442867, 13b's handover_13c list (report 6244193505) and the claim 6244810707. PR #24240 (13b, 646303eae) 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, in 20 configurations;
  • the published dist/index.d.ts / index.d.cts are byte-identical, and so are both bundles' 39-name export lists;
  • one token of the moved body changed, the ruled receiver (below).

rest-server.ts goes from 7,746 to 7,363 lines. G4 and G6–G8, the umbrella registerMetadataEndpoints and its guarded swap, the inner head's bindings and registerPerItemRoute itself, every handler body, the class head, meta-item-read-gate.ts, docs/adr/** and packages/spec/src/** are untouched. #23050 stays open for 13d–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):

The shape

Moved, at its position. In the inner method, right after 13b's registerMetaItemReadRoutes.call(itemReadHost, scope);, the head now builds the host literal itemWriteHost and calls registerMetaItemWriteRoutes.call(itemWriteHost, scope) with the ONE existing scope (no second one is built). Registration order is unchanged: after the per-item reads, above the history family.

The host, MetaItemWriteRouteHost: 5 members in first-read order (a symbol-resolved walk of G5 with the TypeScript 6.0.3 compiler API, 13b's method; H1 of the dispatch holds: PUT at :5288–:5523, DELETE at :5528–:5688, the history family's first statement at :5696, the one static receiver at :5340):

member reads handed over as
resolveExecCtx 2 closure (...a) => this.resolveExecCtx(...a), spelled exactly as the execctx census's forwarder
metaSaveVerdict (private static) 1 closure (...a) => RestServer.metaSaveVerdict(...a), typed (typeof RestServer)['metaSaveVerdict']
walledCatalogRefusal 2 closure
resolveProtocol 2 closure
resolveMetaWriteActor 2 closure

No routeManager member (the plan's 5, H1). The body reads the route manager nowhere: both statements register through registerPerItemRoute, which reaches the module in scope as the inner head's ONE closure (Q1 A, ⛔ no module-local copy). That closure reads the server's this.routeManager at call time; every call runs during the inner call, inside the umbrella's swap, so both routes still mount behind the wrapping registrar and still answer to metadata.endpoints.item, which the same closure reads. The module header and the comment above the literal say why. The walk found no bare this, no this[...], no function keyword and no assignment to a this member, and the body reads no instance FIELD at request time, so no member is a getter.

Host placement (binding). The literal is built inside registerMetadataEndpointsInner, at G5's old position. It is not built in registerRoutes, the constructor, an instance cache or the umbrella above the swap. For a pass-through module the swap is read through registerPerItemRoute's call-time this.routeManager, so the guard ablation below plants the wrong shape this module could take: a module-local registrar reading a manager captured above the swap.

The scope (Q1 A). The module opens with const { metaPath, isScoped, registerPerItemRoute } = scope;, the three bindings it reads. It does not read metadata: the per-item switch is read inside registerPerItemRoute, where it always was.

The one changed token (ruling A, named in the claim). At base rest-server.ts:5340 the save door calls RestServer.metaSaveVerdict(ctx). It is now this.metaSaveVerdict(ctx) through the host (module :187). The static reads nothing but its argument, so the receiver is the whole difference.

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

  • Imported from ./rest-server.js: META_ITEM_RESET_PARAMS (stays: rest-server-meta-member-selector.test.ts imports it from there) and metaItemPackageBinding (on the package entry). Both 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): the local META_ITEM_SAVE_PARAMS import (./meta-item-read-gate.js; the re-export that names it stays), metaSaveRequestOptions (./meta-save-request.js), and SaveMetaItemRequest and DeleteMetaItemRequest (@objectstack/spec/api). Exactly the plan's four. META_ITEM_RESET_PARAMS stays, as the plan says.
  • Unchanged provenance: every other name (rethrowAuthzStoreUnavailable, metaWriteCapabilityVerdict, handleRouteError, META_ITEM_MEMBER_PARAM, metaMemberSelector, refuseUnknownQueryParams, refuseRepeatedQueryParams) is imported from the module rest-server.ts imports it from, and is still read there too. 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 9975c26ae, measured in a read-only comparison worktree. The head is 3750ddecc; the merge head 39912f9e1 adds one merge of origin/main cbfa5e433 (#24244), which touches packages/runtime only, outside @objectstack/rest's dependency closure (core, metadata, metadata-core, observability, platform-objects, service-package, spec, types), so main vs merge head reads as base vs head; the final commit 892a86538 is a comment in a gate script.

1. The moved text. git show 9975c26ae:packages/rest/src/rest-server.ts | sed -n 5284,5688p, with one 4-space indent removed, was compared with meta-item-write-routes.ts :131–:535, the body after the destructuring line.

  • The raw diff exits 1 on exactly one line: the receiver token (RestServer.metaSaveVerdict(ctx) → this.metaSaveVerdict(ctx)).
  • With this.metaSaveVerdict mapped back to RestServer.metaSaveVerdict, diff exits 0 over all 405 lines. Both sides are sha256 a8935ad228caf0c790c7c1314782c6eb4405ca639cd95d8f5b453982f8069bb6.
  • Every non-empty line of the range carried the 8-space indent, and no line has trailing whitespace.

2. .d.ts and exports. index.d.ts and index.d.cts are sha256 02f1c167ccbea3298a15ea77ac634b50500346fc489efec21828eb5a7ab5928a at base, at head and at the merge head.

  • The head-only marker registerMetaItemWriteRoutes hits 2 in each of index.js / index.cjs at head and at the merge head, and 0 at base.
  • Both bundles load with 39 exports and one sorted name list, sha256 b3246be6a1718700…, ESM == CJS, base and head.

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.

  • Source leg (a tsx boot of src/rest-server.ts): routes.json, calls.json and handlers.json are byte-identical in all 20 configs: 60 of 60 files, concatenation sha256 bd4f6685175a61ec… on both trees.
  • Bundle leg (the same dump against the built dist/index.js): 60 of 60 identical, concatenation 20103136b32ff534…, 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 ESM head gains exactly itemWriteHost, registerMetaItemWriteRoutes and two renumbered aliases (rethrowAuthzStoreUnavailable5, metaWriteCapabilityVerdict2), and loses none; those two names are the ones normalized. (The CJS bundle gains itemWriteHost, registerMetaItemWriteRoutes, import_core38 and import_metadata_core17.)
  • Both concatenation hashes equal 13b's own readings: the full boot is unchanged across both moves.

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, digested whitespace-free with the ruled receiver tokens mapped back (13b's three plus metaSaveVerdict), together with method, path, metadata and arity.

  • Source leg: 688 of 688 identical, same order; the digest-list sha256 is 4f306a2a19ffdd181b36b43e4d8b49d0844dc396c50a3144e8221aeab48b4511 on both trees (13b's reading too). Unnormalized, exactly 36 digests differ: PUT …/:type/:name on both bases in the 18 configs that mount it, the receiver token. The DELETE digests are identical raw.
  • Bundle leg: 688 of 688 identical under the two alias names; digest-list sha256 535abfd10375066c385a596bf97dd8039252b8fe3f38ebb7fc78e073cf8f98c6 on both trees.
  • Positive controls (one character each, through scripts/ablation-replace.mjs, anchor 1 → 0): the PUT door's 501 message gained a ! (blob 395d5e6ec2c9 → 82dd302865c5), then, separately, the DELETE door's (→ 7b4819a6f8d5). Each moved the inner digests of exactly its own route, on both bases, in the 18 configs that mount it: 36 of 688 each, and nowhere else. All 20 configs' full-boot routes / calls / handlers stayed byte-identical (60 of 60): the blind spot the inner leg covers. Restored each time: the blob equals HEAD and git diff HEAD is empty.

5. H2: the handed-over closure is the inner method's own. An anonymous request at each moved route on both bases (one-off tsx probe, scoped-optional), base and head identical:

  • endpoints.item on: PUT and DELETE answer 401 UNAUTHENTICATED with zero protocol calls (4 of 4), and all four server-visible handlers are the umbrella's wrapper.
  • endpoints.item: false: both routes are gone on both bases (150 → 126 routes, 0 PUT / DELETE item mounts), on both trees.

6. The guard ablation (the plan's shape for 13c, 13d and 13e-published). Two literal anchors, nested: the umbrella stashes its realRouteManager (captured above the swap) and itemWriteHost carries it as a routeManager member (rest-server.ts blob 2816281785c6 → 1528afc7f2dd, a 281-line anchor from the umbrella's const realRouteManager = this.routeManager; through itemWriteHost's opening line), and the module replaces the scope's registerPerItemRoute with a module-local registrar that honours the same switch but registers on that host member (meta-item-write-routes.ts blob 395d5e6ec2c9 → 6fdf0913c66a).

  • Ablation: an anonymous PUT and DELETE on both bases answer 403 FORBIDDEN from the door's own capability gate, not 401, and none of the four handlers is the wrapper. Under endpoints.item: false the routes are still gone, so the switch alone cannot tell the two shapes apart; the gate can.
  • Standing pins under the ablation: Test Files 1 failed | 4 passed (5), Tests 9 failed | 121 passed (130): meta-type-write-capability.test.ts is red 9 times ([security] A datasource write path admits callers below the capability the datasource admin door requires for create/update — detail withheld pending maintainer #21124: the datasource and datasources write shapes for member, other-grant and author, the existing-vs-absent byte identity, and the admin-door parity for the author bearer). rest-meta-auth.test.ts, meta-type-read-capability.test.ts, rest-config-mount-table.pin.test.ts and meta-route-registration-order.test.ts stay green.
  • Restored, both files: the blob equals HEAD and git diff HEAD is empty. No permanent test was added.

7. The late-binding ablation (one-off script). The 4 instance methods are planted on the server and the static on the class before registerRoutes(), as recorders that delegate to the originals. Six request shapes are driven over the two moved routes (a plain save, ?mode=draft, ?force=true with If-Match, a plain reset, ?state=draft, ?dropStorage=true with If-Match), each answering 200 after the expected protocol call. Then everything is planted again and the shapes are driven again.

  • Head (byte-identical MEASURE lines to base): all 5 closure members are reached before and after the re-plant, with early calls after re-plant: 0.
  • Ablation: walledCatalogRefusal: this.walledCatalogRefusal.bind(this) (blob 2816281785c6 → fde53002af3c) gives early calls after re-plant: 6 ["early:walledCatalogRefusal"] and 4 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-write-routes.ts in REST_SERVER_REGISTRAR_FILES (length 15 → 16) and in the named /meta subset REST_SERVER_META_ROUTE_FILES (2 → 3). The dogfood control counts .call( inside the inner method, now 3, so the subset and the call moved together.

reader sum over the list change
execctx-consumer-census.test.ts 62 sites = 10 + 9 + 5 + 7 + 8 + 1 + 5 + 2 + 1 + 1 + 1 + 0 + 6 + 3 + 1 + 2; mentions 137 → 141 The two sites are locally caught on their invocation lines, as before, so the split stays 16 / 46 (12 on the invocation line). rest-server.ts stays at 55 mentions (two sites out, one forwarder's two in); the module carries 4. Sixteen forwarders: (16 − 1) + 3 − 2
meta-draft-read-door-census.test.ts 8 = 0 + 0 + 0 + 0 + 0 + 2 + 0 ×7 + 3 + 2 + 1 The reset's ?state=draft (a write row) moved; keys name routes, so no ledger row changed
rest-server-query-number-census.test.ts 9 = 8 + 1 + 0 ×14 —
rest-server-canonical-query-ast.test.ts (repo) 4 = 0 + 3 + 0 ×10 + 1 + 0 + 0 + 0 Floor meta-item-write-routes.ts: 0 (the save mapping's one query key sits mid-line)
rest-server-docblock-position.test.ts floors sum 59 → 62 The module carries 3: the header, the host interface and the function. rest-server.ts keeps its 161 blocks
ui-view-route-identity / -tenancy measurement tests this.enforceAuth( 60, this.resolveExecCtx( 77 → 78 +1 for the forwarder. The ownership guard is still 1 call site and 1 forwarder
dogfood authz-conformance.test.ts 0 tripwire hits over sixteen files Length 15 → 16
dogfood authz-probe-blind-spot.census.ts 70 / 19 / 51 unchanged Reachable holds by 13a's Q3 rule, with no rule change. Two controls re-keyed, as ruled (Q3: "From 13c on, a module with no direct register site is controlled on registerPerItemRoute( instead"): the module's own control is meta-item-write-routes.ts: registerPerItemRoute( 2 (its this.routeManager.register( count is 0, which this census reads as instrument failure); the derivation now emits one control per spelling a module spells, so a module that stops spelling its recorded one makes that control vanish, loudly. The whole-population registerPerItemRoute( control is re-anchored over the population rule's own haystack (this file plus every module on the list), where it stays 8 = 6 + 2; over rest-server.ts alone it would read 6. /meta subset ↔ .call( 3; this.routeManager.register( 63, private register*Endpoints( 16, const registerPerItemRoute = 1 and enforceAuth 112 unchanged

Readers outside the list:

  • scripts/check-route-envelope.mjs (MODULES only; declared on [PM seat] domain:spec — ⏳ vacant #6017, 6244817361). meta-item-write-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 0 / siblingCode 0: both doors' 403, 400 and 501 bodies are the nested { error: { code, message } }. rest-server.ts keeps 7 / 3. Output: 26 → 27 modules audited, 16 → 17 ratcheted; --self-test exit 0.
  • content/docs/permissions/system-context.mdx row 50 (declared on [PM seat] domain:devx · seat 2 — 🟢 os-sales · session_016PVjjuVPte5XEJvaQx1E4t · serial (maintainer) #20163, 6244820755; H4 holds). The reset's isSystem read (metaWriteCapabilityVerdict({ isSystem: ctx?.isSystem === true, … })) moved with it, so at the move commit check-system-context-census exited 1: [file-without-a-row] packages/rest/src/meta-item-write-routes.ts holds 1 elevation read site(s) in 1 symbol(s) (registerMetaItemWriteRoutes). Row 50 gains packages/rest/src/meta-item-write-routes.ts#registerMetaItemWriteRoutes beside the inner anchor, which keeps resolving (publish and rollback still read there). Made green by the anchor, ⛔ not by a NON_READ_ANCHORS row: 142 elevation read sites … across 65 files, living in 123 symbol(s); the page cites 136 symbol(s) against 136 required, over 159 anchors (base: 64 files, 122 symbols, 135 / 135, 158 anchors).
  • scripts/adr-anchors/packages__rest__src__meta-item-write-routes.ts.json (NEW; declared as possible on [PM seat] domain:spec — ⏳ vacant #6017). check-adr-anchors checks ADR-NNNN Dk letters only in anchored files. The move took G5's eight letter citations (ADR-0066 D1 ×2, ADR-0131 D3 ×2, ADR-0131 D6 ×4) out of the anchored rest-server.ts, and the gate read 1783 → 1775 while green: a reader that narrowed silently. This anchors ADR-0066, ADR-0131 and ADR-0106; its invariant adds no letter citation; the reading is back to 1783 (73 anchored file(s)).
  • Examined, unaffected:
    • packages/runtime/src/domains/meta-list-projection-parity.test.ts derives GET handlers only (the item, tree, layers and list reads); G5 holds none, and the test was green at the move commit (Tests 739 passed (739)).
    • packages/spec/liveness/metadata_endpoints.json (the plan: "No liveness edit"): the endpoints.item row's second citation, rest-server.ts#registerMetadataEndpointsInner (registerPerItemRoute gates the per-item PUT / DELETE and the history family), stays true, because the switch is read in that closure, which stays in the inner head. check:liveness was green at the move commit and at head.
    • docs/qa/platform-checklist/areas/api-backend.json' inner anchor ("metaPath and registerPerItemRoute") stays true; check:platform-checklist was green at the move commit. No other checklist or docs anchor names a G5 symbol.

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

  • At the move commit 344cb18d8:
    • Six list readers read Test Files 2 failed | 4 passed (6), Tests 5 failed | 121 passed (126). The execctx census failed three times (expected 60 to be 62, the caught split expected 10 to be 12, and §7's control expected 10 to be greater than or equal to 12); the draft census twice (expected [ …(7) ] to deeply equal [ …(8) ], to have a length of 8 but got 7).
    • The dogfood pair read Tests 2 failed | 106 passed (108): population expected 68 to be 70, and /meta subset ↔ .call( expected 3 to be 2.
    • check:route-envelope exited 1: meta-item-write-routes.ts NOT DECLARED. check-system-context-census exited 1 (above).
    • check-adr-anchors stayed green at 1775 (base 1783); check:liveness, check:platform-checklist, the runtime parity test and repo (Tests 266 passed | 15 skipped (281)) stayed green. Nine standing behaviour pins of the moved doors read Tests 205 passed (205).
  • At 9ce268e26 (the list grown, the readers not yet re-pointed): seven list readers read Test Files 6 failed | 1 passed (7), Tests 7 failed | 127 passed (134) (the length pins and the unfloored module); repo read Tests 2 failed | 268 passed | 16 skipped (286); the dogfood pair read Tests 2 failed | 106 passed (108) (registrar list length and the tripwire length).
  • Green after the re-points (cbd7d7a4c, then the merge head).

Changeset

.changeset/23050-s13c-meta-item-write-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. skip-changeset does not apply.

Acceptance notes

  • The plan's 13c list held: the host's 5 members, the one receiver, the four leaving imports, the 16 / 3 list lengths, the census's two control changes, MODULES at 0 / 0 and the row-50 anchor. Beyond it, the ADR anchor (the handover's conditional item) was owed: the letter count dropped by 8. No reader outside the plan's list and 13b's handover read anything G5 held.
  • The moved comments keep their deixis: "the PUT twins", "next door" and "a few hundred lines up" describe the inner method's order as it was, in which POST {metaPath}/_migrate-stored (now in meta-collection-routes.ts) sat above these doors. The module header says so rather than editing moved text.
  • The inner method's registerPerItemRoute docblock still says the first four members of the per-item face "are inside the if block further down" (13b moved that block to meta-item-read-routes.ts). It is the inner head's text, a hold-off for this slice, and is left for the slice that re-reads that head.
  • Carried from earlier slices, untouched: FOLLOW-UPS.md:613 (E1) prose, ui-routes.ts' "52 sites" comment and email-routes.ts' double-mount header.

Gates and tests

The final head is 892a86538: one merge of origin/main cbfa5e433 (#24244, packages/runtime only), then a comment-only fix to the new MODULES note in scripts/check-route-envelope.mjs. The tests below ran at the merge head 39912f9e1; that last commit changes no file any test reads. The gate union ran at 892a86538.

  • Build. At base, pnpm turbo run build --filter=@objectstack/rest... --concurrency=2 exited 0 (Tasks: 25 successful, 25 total). At the move commit, 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, a whole-workspace pnpm turbo run build --concurrency=2 exited 0 (Tasks: 77 successful, 77 total), for the dist readers. No package source was edited while turbo built it; the base dist reads 0 head-only markers.
  • Typecheck. pnpm --filter @objectstack/rest typecheck exits 0 (check:test-typecheck: OK … 0 file(s) / 0 error(s)). pnpm --filter @objectstack/dogfood typecheck exits 0.
  • @objectstack/rest. local: Test Files 294 passed (294), Tests 6032 passed | 326 skipped (6358), including meta-route-registration-order.test.ts, rest-config-mount-table.pin.test.ts and the moved doors' pins (meta-type-write-capability, rest-server-meta-member-selector, rest-server-meta-door-closed-sets, meta-501-envelope, meta-write-actor-identity, meta-write-door-capability-enumeration). repo (its test:repo, run locally because this diff edits rest-server-canonical-query-ast.test.ts): Test Files 5 passed (5), Tests 270 passed | 16 skipped (286).
  • 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.
  • 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) (its security tests read rest-server.ts from disk).
  • Lint. pnpm exec eslint --no-inline-config over the 13 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 892a86538 derives 117 commands; its stderr names the commit, and the --repo assertion holds. The change set is 16 paths vs merge base cbfa5e433, 1,152 changed lines (+722 / −430), under 3,000.
    • All 117 exit 0, each captured before any pipe.
    • --ran: Run reconciliation — 117 derived, 117 run, 0 NOT-MEASURED, 0 UNRUN., exit 0. It also printed a STALE TREE note: origin/main moved to f5ef054a7 (feat(core): resolveRequestExecutionContext, the one request-to-ExecutionContext composition; the runtime dispatcher's door calls it #24248) after the merge, and changed scripts/check-single-authz-resolver.mjs, whose new DELEGATORS row keys rest-server.ts on resolveAuthzContext (the class head, untouched here). No path of that commit is one of this diff's 16, so it was not merged again; PR CI runs the merge ref.
    • Selected readings: check:route-envelope (27 route module(s) audited: 9 conformant, 17 ratcheted, 1 exempt); check:adr-anchors (73 anchored file(s), 1783 decision-letter citation(s)); node scripts/check-system-context-census.mjs (… across 65 files, living in 123 symbol(s); the page cites 136 symbol(s) against 136 required, over 159 anchors); check:liveness (947 pointer(s) … 947 naming a symbol, 737 … asked, 736 anchored, 1 exempt, as at base); check:undeclared-dep-imports; check:cross-package-test-inputs; check:authz-resolver; check:durability-log-level; check:dispatcher-error-vocabulary; check:error-status-conformance; check:dual-build-cjs-loads (111 published require entry point(s) across 69 package(s) load); check:pm-dispatch-gates (1884 cases pass); check:type-check-coverage; check:type-check-debt; check:nul-bytes. node scripts/check-platform-checklist.mjs --changed-from origin/main: 10 anchor(s) judged, 0 lost.
  • Not run locally: check:dev-prereqs and check:skill-frame-freshness (local-only families). This diff touches no packages/spec build input and no four-axis frame file.
  • Not governed. check-governed-merges --branch HEAD: 0 of 16 path(s) hit the register, and the size is under the human-merge threshold.

Commits:

  • 344cb18d8, the move;
  • 9ce268e26, the list and the /meta subset;
  • cbd7d7a4c, the re-points, with the module's ADR anchor and the row-50 anchor;
  • 3750ddecc, the changeset;
  • 39912f9e1, the merge;
  • 892a86538, a comment-only fix to the new MODULES note (which door builds the 400).

Generated by Claude Code

…outes.ts

Sub-slice 13c, the move commit. The fifth /meta group of
RestServer#registerMetadataEndpointsInner, the two registerPerItemRoute
statements registering PUT and DELETE {metaPath}/:type/:name (the item
save and the reset), moves re-indented to the new module as
registerMetaItemWriteRoutes(this: MetaItemWriteRouteHost, scope:
MetaRouteScope). The inner method builds the itemWriteHost literal at the
statements' old position and passes the one existing scope, through which
the inner head's own per-item registrar reaches the module. The host
carries no routeManager: the module registers only through that closure,
which reads the server's route manager at call time.

The one private-static receiver, RestServer.metaSaveVerdict, becomes
this.metaSaveVerdict through the host. The four imports only the moved
body read (the save's closed set, its request mapping and the two declared
request types) leave rest-server.ts. The readers follow in later commits.

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

Sub-slice 13c, the list commit. REST_SERVER_REGISTRAR_FILES grows 15 to 16
and its named /meta subset REST_SERVER_META_ROUTE_FILES 2 to 3, with the
inner method's module calls (3) beside it. The list's readers are
re-pointed in the next commit, so this one measures them red at their
base text.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
…able and the row-50 anchor at meta-item-write-routes.ts

Sub-slice 13c, the readers commit. Every reader of the registrar list
follows it to 16 files with its sum unchanged: the execctx census (62
sites, 141 mentions, sixteen forwarders), the draft census (8, the
reset's state=draft now in the module), the query-number census (9), the
canonical-query-ast floors (4), the docblock floors (62 over fifteen
modules), the ui-view identity and tenancy measurements (60 / 78) and the
dogfood tripwire (0 over sixteen).

The dogfood blind-spot census stays 70 / 19 / 51 by 13a's rule. Its
per-module control counts the helper's call spelling for a module with no
direct site (meta-item-write-routes.ts: 2), and the whole-population
registerPerItemRoute( control is re-anchored over the population rule's
own haystack, where it stays 8.

check-route-envelope declares the module at 0 / 0 (rest-server.ts keeps
7 / 3). system-context.mdx row 50 gains the module's anchor, since the
reset's isSystem read moved with it. A new ADR anchor restores
check-adr-anchors' decision-letter reading, which the move had narrowed
from 1783 to 1775 with the gate green.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
Sub-slice 13c. The published index.js / index.cjs bytes change (the new
module is bundled); the declarations and the export list do not.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
Brings in cbfa5e4 (#24244, packages/runtime only); no path overlaps this
branch.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
…outes MODULES note

Only the item save builds a 400 body of its own; the reset's 400s come
from the shared query guards. Comment only.

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 1 package(s): @objectstack/rest, touching 31 documentable anchor(s).

33 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 f5ef054a72e743723101d7ff15120b35c7a19b68.

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

What this run could not see
  • 2 cross-cutting symbol(s) contributed no route anchor: resolveExecCtx (28 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 — 18 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 f5ef054a72e743723101d7ff15120b35c7a19b68 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json f5ef054a72e743723101d7ff15120b35c7a19b68

⚠️ 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 f5ef054a72e743723101d7ff15120b35c7a19b68 → 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 #24257 at 892a86538 (#23050 sub-slice 13c)

domain:cli seat 2 · marchtian · session session_01QyoWKK9RwUNxBxmevy9Neb · 2026-10-18T04:15Z · holder of claim 6244810707 · rulings 6239783526 (seat 1, on plan 6239704287) and 6205442867 (static receivers). The dev's report is comment 6245538958 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, since 13d–13e remain. Clause-②: no is line-initial, and the assignee is marchtian. 16 files, +722 / −430: under the human-merge threshold. check-governed-merges.mjs --pr 24257: NOT governed. The body quotes ruling 6239783526 verbatim.
  2. The move, re-measured by the seat (base 9975c26ae, head 892a86538):
    • G5 is rest-server.ts:5284–:5688 at base: the two registerPerItemRoute({ … }) statements, PUT and DELETE. Its 405 lines map onto meta-item-write-routes.ts:131–:535. Base dedented one level against the module, the unified diff is exactly 1 line: the ruled receiver RestServer.metaSaveVerdict(ctx) → this.metaSaveVerdict(ctx).
    • The host (itemWriteHost, head :5298) is built inside registerMetadataEndpointsInner at G5's old position, then registerMetaItemWriteRoutes.call(itemWriteHost, scope) at :5305. It has 5 members and NO routeManager: resolveExecCtx, walledCatalogRefusal, resolveProtocol and resolveMetaWriteActor as closures, plus the static metaSaveVerdict as a closure, which is the plan's host.
    • Q1 A: the module destructures const { metaPath, isScoped, registerPerItemRoute } = scope; and has no definition of its own (0 hits for a local function or const). The inner head's one closure registers both routes.
    • The plan's four imports left rest-server.ts; META_ITEM_RESET_PARAMS stays.
  3. Proof legs (the dev's, read for controls):
    • Full boot and ordered registrations identical over 20 configurations on the source and bundle legs. The bundle leg normalizes 2 esbuild aliases, found by an identifier-set diff.
    • Inner registrations 688/688 identical. The positive controls (one character in the PUT, then the DELETE, 501 message) each moved exactly its own route's 36 digests.
    • .d.ts / .d.cts byte-identical, and the 39-name export list identical ESM vs CJS.
    • H2 (the plan's risk): with endpoints.item on, anonymous PUT / DELETE answer 401 on both bases at base and head; with it off, both routes are gone (150 → 126).
    • The guard ablation for a pass-through module turns anonymous answers to the door's own 403 and reddens 9 meta-type-write-capability pins; restored, the diff is empty.
    • Late binding: 5/5 members late, and the .bind ablation is caught.
  4. Readers, same PR:
    • The registrar list goes to 16 and the /meta subset to 3.
    • The execctx and draft censuses and the ui-view identity / tenancy measurements are re-pointed.
    • The dogfood pair is re-keyed per Q3: a pass-through module gets a registerPerItemRoute( control, and the population control stays 8.
    • check-route-envelope MODULES is declared 0 / 0.
    • system-context.mdx row 50 gets the module anchor. H4 is confirmed: the census reds at the move ("file-without-a-row") and is green with the anchor.
    • A new ADR anchor JSON restores the letter count from 1775 to 1783.
    • Every reader was red at the move commit before its re-point (the counts are in the report). meta-list-projection-parity.test.ts was examined and not edited, because G5 holds no GET.
  5. Gates: 117 derived, 117 run, all exit 0 (after a whole-workspace build), --ran reconciled. The rest local suite passed 294 files and 6032 tests. The rest repo project passed 270, the dogfood pair 108, and runtime and core repo 808 and 86. ESLint --no-inline-config exited 0 over 13 files.
  6. Clause-②: no, read, with the rest changeset read sentence by sentence: "keeps its name, docblock, place and head, its per-item registrar included", "moved, re-indented … calls them at their old position", "still register through that per-item registrar, so the metadata.endpoints.item switch gates the same mounts", and "no export … no route … no response moved". These match items 2 and 3.
  7. Cross-lane: every file is inside the declared list: spec seat 6244817361 (check-route-envelope.mjs, the adr-anchors JSON) and devx seat 6244820755 (system-context.mdx row 50). No objection at this reading.
  8. Accepted deviations:
  9. Out of scope, noted:
    • The inner head's registerPerItemRoute docblock still says the first four per-item members are "inside the if block further down" (13b moved them). That is inner-head text, a hold-off here, and is carried to 13d.
    • The moved comments' deixis ("a few hundred lines up") is kept as moved text; the module header says so.
  10. Landing. Ordinary queue landing once every check on 892a86538 is green or a roster skip. Part of closes nothing. Under the maintainer's serial rule, 13d / 13e are not claimed in parallel: 13d waits for both 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 (PR fix(rest,runtime,metadata-protocol)!: /published, REST /state, _drafts and the event-door absence body answer a disabled package's item as the by-name read does #24247) and its own turn in the one-card-at-a-time queue. handover_13d notes that system-context.mdx row 50's inner anchor must be dropped by whichever slice moves publish and rollback.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 18, 2026 04:37
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 18, 2026 04:37
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 18, 2026
Merged via the queue into main with commit e9bc60b Oct 18, 2026
42 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-23050-s13c-meta-item-write branch October 18, 2026 05:06
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