Skip to content

refactor(rest): move registerCrudEndpoints into crud-routes.ts (#23050 slice 12) - #24205

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-23050-s12-crud-endpoints
Oct 17, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-23050-s12-crud-endpoints

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #23050
Clause-②: no

Slice 12 of the rest-server.ts split: CRUD. It follows slice 1's pattern and slices 2–11's shape, with slice 11 (PR #24054) as the direct precedent. The body of RestServer#registerCrudEndpoints moves, re-indented one level and otherwise unedited, to one new module, packages/rest/src/crud-routes.ts. That body is the six record doors under {basePath}{crud.dataPrefix}: list, read, create, the QueryAST query, update and delete.

Nothing else moves:

  • the route table, the ordered registrations and every handler's source (whitespace-free digest) are byte-identical;
  • the published dist/*.d.ts is byte-identical, and so are both bundles' export lists;
  • no token of the body changed.

rest-server.ts goes from 9,512 to 9,086 lines (base cb8756b00). #23050 stays open for the last slice, /meta (registerMetadataEndpointsInner), which this PR does not touch.

packages/spec/liveness/crud_endpoints.json gets six evidence-path substitutions. The consumer text, quoted code, producers, verdicts and statuses are unchanged. This is the one packages/spec exception declared to the spec seat on #6017 as 6235976157, under slice 11's precedent (ruling A 6225503839).

The pattern, applied

Members the moved body reads. Measured by an AST walk of the method (TypeScript compiler API, base cb8756b00). The method spans :8323–:8759, and the body inside it spans :8324–:8758 (435 lines).

kind member reads class declaration
field config 1 (:8324, the body's first statement) :1997, private
field routeManager 6, the six register calls :1998, private
method resolveProtocol 6 :2376, private async
method resolveExecCtx 6 :2863, private async
method enforceAuth 6 :2444, private
method enforceApiAccess 6 :2631, private async

The body has no bare this, no element access on this, no RestServer. static access and no function keyword.

Control walk (the same walker on the same base): the registerUiEndpoints stub reads routeManager, resolveRequestEnvironmentId, resolveExecCtx, enforceAuth, enforceEnvironmentOwnership and resolveProtocol once each. That is slice 10's host literal, member for member.

Module-level names the body reads (the same walk):

  • Read only by this body, so the import moves with it. A pointer comment is left in rest-server.ts' import block for each:
  • Still read elsewhere, so the import stays. The module imports the same names from the same modules: mapDataError, refuseUnknownQueryParams, refuseRepeatedQueryParams, zodIssuesToFields and validationFailure.
  • ServerScopedDataRequest is imported from data-action-routes.ts, where slice 1 moved it. Its last reader in rest-server.ts was this body, so rest-server.ts no longer imports it. The slice-1 note there now says so.
  • Two names are declared in rest-server.ts itself, stay there and are imported from it:
    • DATA_RECORD_READ_PARAMS. It was already exported, and its pin test (rest-server-closed-query-params.test.ts) imports it from there. This is slice 3's shape for APPROVAL_REQUEST_LIST_PARAMS.
    • versionTokenRefusal. It gains export for this module only, the shape slices 1 and 2 used for their helpers. It does not move, because the runtime dispatcher's /data twin (packages/runtime/src/domains/data.ts:79) names packages/rest/src/rest-server.ts as the home of its full argument, and packages/runtime/** is outside this slice. Moving it would make that pointer false.
    • The package entry re-exports neither name, and the .d.ts is byte-identical (below). Both are read only at request time, so the two modules importing each other cannot see them uninitialised.

Host shape (slice 1's shape B). CrudRouteHost has 6 members, in the order the body first reads them, each typed RestServer['m'].

  • The class keeps the private method's name, docblock and position. Its JSDoc plus signature (base :8320–:8323, head :8316–:8319) diffs to 0.
  • Its body is the host literal plus registerCrudEndpoints.call(host, basePath). Methods go as late-bound closures, (...a) => this.m(...a).
  • The call site in registerRoutes' registerForBase is unchanged: if (this.config.api.enableCrud) { this.registerCrudEndpoints(bp); }, after registerDataActionEndpoints. registerRoutes (75 lines) diffs to 0 between base and head.

Fields, checked one by one:

field read when reassigned after registration? handed over as
config registration only: const { crud } = this.config; is the body's first statement, and the handlers close over crud, operations, dataPath and isScoped no, assigned only in the constructor (:2138 at head) value
routeManager registration only (the six register calls) its one swap (:5166/:5170 at head) wraps registerMetadataEndpointsInner, not this registrar value

No provider field is read. The protocol, the caller's context, the anonymous-deny gate and the per-object API exposure gate are each resolved per request by a METHOD, so no getter is owed.

Route switches. The registrar has its own switches: api.enableCrud, the five crud.operations (list gates two mounts) and crud.dataPrefix. So slice 10/11's nine configurations are extended with four CRUD ones (below).

Late binding, measured. A one-off vitest file was copied in, run under the verify lock and deleted; it is not committed.

  • Setup. A source RestServer gets the four methods planted BEFORE registerRoutes(), and all six doors are driven. Then four new methods are planted AFTER registration, and the six doors are driven again.
  • Head: MEASURE before: GET 200 {"records":[{"id":"early"}]} | GET 200 {"id":"early"} | POST 201 {"id":"early"} | POST 200 {"records":[{"id":"early"}]} | PATCH 200 {"id":"early"} | DELETE 200 {"id":"early","success":true}. After the re-plant, every door answers "late". The order is late:proto,late:ctx,late:auth,late:api ×6, with early calls after re-plant 0. Tests 1 passed (1).
  • Ablation: the host literal's enforceApiAccess closure was replaced by this.enforceApiAccess.bind(this) through scripts/ablation-replace.mjs (anchor 1 → 0, blob bc2c21614369 → d8d11e7ef378). early:api then runs on all six doors after the re-plant: AssertionError: expected [ 'early:api', 'early:api', …(4) ] to deeply equal [], Tests 1 failed (1). The tool restored the file: blob bc2c21614369 equals HEAD, and git diff HEAD is empty.
  • Base leg: the base rest-server.ts (blob 5a0f948c2e8a) was put in the tree under a trap. It printed the same two MEASURE lines byte for byte, Tests 1 passed (1). Restored: blob equals HEAD, git diff HEAD empty.

Proof of move-only

  • Moved body. Base rest-server.ts lines 8324–8758, with one 4-space indent removed, against crud-routes.ts lines 107–541: diff exits 0 over all 435 lines, so no token changed.

    • Every non-empty line of the range carries the method indent.
    • No line has trailing whitespace.
    • No line has an odd backtick count, so no template literal spans the cut.
  • .d.ts. packages/rest/dist/index.d.ts and index.d.cts were built at base cb8756b00 before any edit and at 45d976972. Both legs are sha256 1f0f455d24836d47de60716233f1bd74d1738c04046974019264b17b5261bbe8, and cmp exits 0 on each pair.

    • The head bundles carry a head-only marker: registerCrudEndpoints.call hits 1 in each of index.js and index.cjs, and 0 at base. So the declaration was rebuilt from the moved source.
    • Exporting versionTokenRefusal from rest-server.ts moved no declaration. That matches slice 1's measurement: exporting a function or a constant does not change the bundle's .d.ts.
  • Export lists. The base and head ESM and CJS bundles each load with 39 exports and one sorted export-name list (sha256 b3246be6a1718700… on all four).

  • Routes (RestServer.getRoutes()). A source RestServer was booted on a recording mock server, with a protocol answering every member. Each dump holds:

    • getRoutes() (functions as arity);
    • the ordered server registration calls;
    • a whitespace-free sha256 of every handler's source, plus that source.

    It was dumped under thirteen configs:

    • slice 10/11's nine: default; scoped-optional; scoped-required; every provider set; enableDiscovery: false; enableOpenApi: false; both off under scoped-optional; enableSearch: false under scoped-optional; enableUi: false under scoped-optional;
    • this slice's four, for the registrar's own switches: enableCrud: false under scoped-optional; crud.operations { list: false, delete: false } under scoped-optional; all five operations false; crud.dataPrefix: '/objects' under scoped-optional.

    Results:

    • Route counts are 75 / 150 / 75 / 75 / 73 / 73 / 142 / 148 / 148 / 138 / 144 / 69 / 150, with 6 / 12 / 6 / 6 / 6 / 6 / 12 / 12 / 12 / 0 / 6 / 0 / 12 CRUD-tagged routes. The first nine are slice 11's figures.
    • routes.json, calls.json and handlers.json are byte-identical between base and head in all thirteen configs. The sha256 over the 39 dump files is 8e9544b5b00f03081398465aa73416f51aee7a4aeef956ed550283937017daa8 on both legs, and the default config's routes.json is 6931d1a5d1827fea… on both.
    • A second base run was identical to the first (diff -r exit 0). Every handler digest is identical, because no token changed.
  • The handler digest can fail. A positive control added one character to the moved PATCH handler through ablation-replace.mjs: 'Invalid update request' became 'Invalid update request!' (anchor 1 → 0, blob d25749c75351 → 251ede3ffa43).

    • Routes and calls were unchanged in all thirteen configs.
    • The handler digests moved in exactly the PATCH …/:object/:id door of each mounted base, and nowhere else. That is 1 or 2 per config, and none in the two configs where that door is not mounted.
    • Restored: blob d25749c75351 equals HEAD, git diff HEAD empty.
  • The route ledger the gates read (packages/rest/src/rest-route-ledger.ts, packages/runtime/src/route-ledger.ts) is not edited. Its conformance tests against getRoutes() pass in the local run below.

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

packages/rest/src/rest-server-registrar-files.ts gains crud-routes.ts, with a [#23050 slice 12] comment (length 12 → 13). Every reader that pins its length re-reads its number:

reader sum over the list change
execctx-consumer-census.test.ts 62 sites = 16 + 9 + 5 + 7 + 8 + 1 + 5 + 2 + 1 + 1 + 1 + 0 + 6; mentions 125 → 129 The six CRUD sites (bare, each guarded on its next line) moved. The four new mentions are the seam: the forwarder, two, and the host interface's member, two. rest-server.ts goes 60 → 56. Thirteen forwarders = (13 − 1) + 3 − 2. Length 12 → 13
meta-draft-read-door-census.test.ts 8 = 6 + 0 + 0 + 0 + 0 + 2 + 0 ×7 The CRUD doors read no draft. Length 12 → 13
rest-server-query-number-census.test.ts 9 = 8 + 1 + 0 ×11 The CRUD doors coerce no number. Length 12 → 13
rest-server-canonical-query-ast.test.ts (repo project) 4 = 0 + 3 + 0 ×10 + 1 query: slots The list door's caller-supplied query: req.query moved, so the floors are rest-server.ts 1 → 0 and crud-routes.ts: 1. Length 12 → 13
rest-server-docblock-position.test.ts floors 4 + 3 + 3 + 8 + 3 + 8 + 5 + 3 + 3 + 4 + 3 + 3 = 50 The floor is crud-routes.ts: 3: the module header, the host interface's block and the function's. The CRUD body held no TSDoc block, so rest-server.ts keeps its 163. Length 12 → 13
ui-view-route-identity.measurement.test.ts this.enforceAuth( 59 → 60, this.resolveExecCtx( 74 → 75 +1 each for the host literal's forwarder; the six call sites are counted in both readings. Length 12 → 13
ui-view-route-tenancy.measurement.test.ts as above; the ownership guard is still 1 call site + 1 forwarder Length 12 → 13
dogfood authz-conformance.test.ts (realtime tripwires) 0 hits over the thirteen files Length 12 → 13
dogfood authz-probe-blind-spot.census.ts, rest-server.ts row 70 / 19 / 51 unchanged; this.routeManager.register( 63 = 12 + 9 + 5 + 7 + 8 + 1 + 6 + 4 + 1 + 1 + 1 + 2 + 6; private register*Endpoints( 16; enforceAuth 108 → 112 New control crud-routes.ts: this.routeManager.register( 6; registrar list length 13; populationRule names the new module. enforceAuth is RE-ANCHORED, not relaxed: the six call sites moved and are counted in both readings, and the four new occurrences are the seam

Readers outside the list:

  • scripts/check-route-envelope.mjs (a gate's target table, domain:spec).
    • crud-routes.ts is declared dialectOnly with ratchet [tracking] Envelope-position convergence line for packages/rest's flat dialect — the live ratchet owner #9559, as the eleven earlier modules are. Its measured counts are stringError 3 and siblingCode 3: the three request gates' 400 VALIDATION_FAILED bodies ('Invalid create request', 'Invalid query request', 'Invalid update request').
    • rest-server.ts drops by exactly those bodies: stringError 11 → 8 and siblingCode 7 → 4, each recorded as "NOT progress — the same MOVE".
    • The sums are unchanged: 8 + 13 + 8 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 1 + 4 + 3 = 39, and 4 + 15 + 8 + 9 + 4 + 1 + 0 + 0 + 3 + 0 + 1 + 6 + 3 = 54.
    • Its output differs from base only in the rest-server.ts row and the new row (22 → 23 modules audited, 13 → 14 ratcheted).
  • packages/spec/liveness/crud_endpoints.json. Six evidence paths packages/rest/src/rest-server.ts#registerCrudEndpoints → packages/rest/src/crud-routes.ts#registerCrudEndpoints: the five operations.* rows (if (operations.X)) and the first citation of the dataPrefix row (const dataPath = …crud.dataPrefix…). Every quoted fragment now lives in crud-routes.ts. See the next section.
  • docs/qa/platform-checklist/areas/api-backend.json (domain:devx, slice 7/11's precedent). Three sources name crud-routes.ts; the text after each anchor is unchanged, and no history entry is added:
    • api-backend.query-contract-matrix: rest-server.ts (POST /data/:object/query answers a FindDataRequestSchema failure …) → crud-routes.ts (…).
    • api-backend.aggregate-contract-matrix: rest-server.ts#data (POST /data/:object/query …) → crud-routes.ts#data (…).
    • api-backend.rest-crud-config-contract: rest-server.ts#registerCrudEndpoints (the six mounts and their five gates), (normalizeConfig …), (parseDeclaredSubConfig …), (getRoutes …) → crud-routes.ts#registerCrudEndpoints (the six mounts and their five gates) + packages/rest/src/rest-server.ts (normalizeConfig …), (parseDeclaredSubConfig …), (getRoutes …). The three helpers did not move, and slice 7 split the same source with + in the same way.
    • check:platform-checklist: 853 of 863 anchors resolve on both trees; cited sources go 400 → 401.
  • No ADR anchor. The CRUD body cites no ADR. check-adr-anchors reads 70 anchored files and 1,782 decision-letter citations at base, at the move commit and at the head. scripts/adr-anchors/packages__rest__src__rest-server.ts.json anchors ADR-0045's filterAppForUser, which did not move.
  • No content/docs/** anchor. Measured: no page under content/docs (references and releases excluded) cites the CRUD code by path, symbol or line.

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

  • At the move commit c8f1643c8 (the list not yet grown):
    • local read Test Files 1 failed | 290 passed (291), Tests 3 failed | 6004 passed | 326 skipped (6333). The failures are the execctx census: expected 56 to be 62, then expected 40 to be 46 twice. The six CRUD sites had left the reach of a reader that did not yet know the module.
    • repo read Tests 2 failed | 252 passed | 12 skipped (266). These are the canonical-query-AST floor (expected 0 to be greater than or equal to 1) and its slot sum (expected 3 to be 4).
    • check:route-envelope read crud-routes.ts NOT DECLARED, stringError: found 8, declared 11 and siblingCode: found 4, declared 7.
  • At 3e58c49cb (the list grown, the readers not yet re-pointed):
    • local (the six list readers) read Test Files 6 failed (6), Tests 7 failed | 115 passed (122). These are the six length pins (expected [ 'rest-server.ts', …(12) ] to have a length of 12 but got 13) and the docblock pin's unfloored new module.
    • repo read Tests 3 failed | 76 passed | 13 skipped (92): the length pin and both floors.
    • The dogfood pair read Tests 2 failed | 106 passed (108): the tripwire's length pin, and control "registrar list length" moved in packages/rest/src/rest-server.ts: expected 13 to be 12.
  • Each is green after the re-point (45d976972, then the merge head).

The packages/spec reader, re-pointed (the declared exception)

crud_endpoints.json cited rest-server.ts#registerCrudEndpoints for the code that gates and prefixes the CRUD mounts, and that code now lives in crud-routes.ts. check:liveness stays green either way:

  • the stub keeps the symbol registerCrudEndpoints in rest-server.ts;
  • rest-server.ts still names every key (operations, dataPrefix) in normalizeConfig.

So without the substitution the six rows would have read as anchored while pointing at a file that no longer holds the quoted code. That is the dead-pin shape the card warns about, and no gate would have caught it. The substitution keeps them pointing at the consumer.

check:liveness at the head exits 0. It reads symbol anchors: 945 pointer(s) written path#symbol, 945 naming a symbol the cited file contains. and key-mention anchoring: 735 (entry, cited file) pair(s) asked, 734 anchored, 1 exempt. (734 / 733 / 1 at base). The dataPrefix row now cites two files, crud-routes.ts for the CRUD mount and rest-server.ts for its four sibling consumers, so it asks one more pair, and that pair anchors.

No rest_api.json row cites the moved code. That was measured over every liveness file by extracting each backtick-quoted fragment from the rows that cite rest-server.ts. Only the six crud_endpoints.json rows quote code found in the CRUD body and nowhere else in rest-server.ts. rest_api.json's enableCrud row cites registerRoutes' if (this.config.api.enableCrud), which did not move.

Changesets:

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

Readers examined and not re-pointed

  • Prose that names the registrar without a file or line, and is still true:

    • registerRoutes' comment ("the greedy GET /data/:object/:id matcher in registerCrudEndpoints");
    • DATA_RECORD_READ_PARAMS' docblock ("Measured, at registerCrudEndpoints' read-record handler");
    • ui-view-route-identity.measurement.test.ts (byRegistrar.get('registerCrudEndpoints'), read off the mounted registrar at runtime);
    • the dogfood census note;
    • docs/qa/platform-checklist/FOLLOW-UPS.md E3;
    • packages/spec/src/api/rest-server.zod.ts and the 18.api__CrudEndpointsConfig__patterns retired-key entry ("the server's registerCrudEndpoints");
    • the generated content/docs/references/api/rest-server.mdx.
  • crud_endpoints.json's patterns note says "every CRUD route is mounted from the fixed method/path pairs in rest-server.ts#registerCrudEndpoints". It is a dead row's retirement record, dated 2026-09-03, and it still resolves by name to the stub. It was left as written, together with the dataPrefix row's four sibling citations (#registerDataActionEndpoints, #registerSharingEndpoints, #registerBatchEndpoints, #registerDiscoveryEndpoints), which cite bodies earlier slices moved. That is outside this slice's declared substitution (see Acceptance notes).

  • docs/adr/0036-… cites @objectstack/rest registerCrudEndpoints(). It is Tier H and not edited, and the name still resolves.

  • The runtime /data twin's pointer (packages/runtime/src/domains/data.ts:79, "versionTokenRefusal in packages/rest/src/rest-server.ts") stays true, because the function stays there.

  • Repo tests outside packages/rest that read packages/rest/src:

    • core's authz-store-unavailable.test.ts classifies rest-server.ts as guarded for a seam the CRUD body never calls;
    • runtime's meta-list-projection-parity.test.ts reads only the GET {metaPath}/:type handler;
    • spec's five list packages/rest test FILES by name.

    None reads the moved code, and all three test:repo runs are green below.

  • Gates that walk packages/rest/src reach the moved code in its new file. Their scan outputs were compared between a worktree at origin/main 2bcaefba9 and the final head c333dafda. They differ only by the one scanned file or by this diff's own re-points:

    • check-meta-type-normalized 40 → 41 files;
    • check-undeclared-dep-imports +1 file, +2 specifiers;
    • check-dispatcher-error-vocabulary and check-error-status-conformance +1 file, classified sites unchanged;
    • check-affected-docs route sources 30 → 31 and registration call sites 25 → 26, by the *-routes.ts convention;
    • check-scripts-symbol-anchors 3910 → 3914 file-level anchors (the route-envelope gate's new comments);
    • check:platform-checklist cited sources 400 → 401;
    • check:liveness one more key-mention pair, anchored;
    • check-route-envelope the two rows above.

    These are identical: check-system-context-census, check-single-authz-resolver, check-rest-log-declared, check-durability-degradation-log-level, check-filter-alias-parity and check-adr-anchors (70 anchored files, 1,782 decision-letter citations). At the move commit, against base cb8756b00, the four walkers already read the one extra file, before any re-point.

Acceptance notes

  • versionTokenRefusal's docblock still says "this file THROWS it into the route's catch". The throw now sits in crud-routes.ts, in the same RestServer doors. This is a prose pointer, not edited by a move (no carrier).
  • The dataPrefix row's four sibling citations and the patterns note cite rest-server.ts#register… names whose bodies slices 1, 2, 4, 7 and this one moved. Each still resolves by name to its stub, and check:liveness anchors them by file. Re-pointing them is outside this slice's declared packages/spec substitution. Carrier: the domain:spec seat, through the seat's [PM seat] domain:spec — 🟢 os-warren #6017 declaration; nothing filed.
  • check:liveness' key-mention axis is file-level. A row that cites a stub's name in a file that still names the key elsewhere reads as anchored after its quoted code moves. This slice re-points by hand. No new gate: card rule 5.
  • Carried from earlier slices, untouched: ui-routes.ts' "52 sites" comment and email-routes.ts' double-mount header (no non-move edit of either text here).

Gates and tests

Final head c333dafda. It is one merge of origin/main 2bcaefba9 (#24170, #24177, #24180, #24178, #24181). None of those touches a file in this diff. rest-server.ts' blob is 5a0f948c2e8a at cb8756b00 and at 2bcaefba9. main's #24178 changed data-action-routes.ts' export handler, so the merge-head proofs are taken against origin/main itself:

  • .d.ts. A base leg put origin/main's rest-server.ts into the merged tree under a trap and built @objectstack/rest. It gives 1f0f455d… with the marker at 0. Restored (blob and git diff HEAD proof) and rebuilt, the head gives 1f0f455d… with the marker at 1. The export lists are 39 / b3246be6… on both bundles.
  • Routes. The thirteen-config dumps with origin/main's rest-server.ts equal the merge head's (diff -r exit 0, sha256 1d7630283baf… on both legs). They differ from cb8756b00's only in the GET …/data/:object/export handler digest, which is fix(rest)!: a schema-derived export skips the fields declared hidden #24178.

Commits:

  • c8f1643c8, the move;
  • 3e58c49cb, the list;
  • 45d976972, the re-points;
  • b588a78b6, the changesets;
  • c333dafda, the merge.

All builds and suites ran through os-verify-lock, and every exit was written to a file before any pipe. At c333dafda:

  • Build. pnpm --workspace-concurrency=2 --filter '@objectstack/rest...' --filter '@objectstack/verify...' --filter '@objectstack/dogfood^...' build exit 0. Then pnpm turbo run build --concurrency=2 (whole workspace) exit 0: Tasks: 77 successful, 77 total.
  • Generated artifacts. pnpm --filter @objectstack/spec check:generated exit 0: All 14 generated artifacts are up to date. main moved packages/spec.
  • Typecheck. pnpm --filter @objectstack/rest typecheck exit 0: check:test-typecheck: OK … 0 file(s) / 0 error(s).
  • @objectstack/rest. local: Test Files 292 passed (292), Tests 6015 passed | 326 skipped (6341). repo, which is its test:repo: Test Files 5 passed (5), Tests 258 passed | 13 skipped (271).
  • Dogfood pair (authz-probe-blind-spot.test.ts, authz-conformance.test.ts): Test Files 2 passed (2), Tests 108 passed (108).
  • Spec liveness suites (packages/spec local, scripts/liveness/): Test Files 10 passed (10), Tests 269 passed (269).
  • test:repo of every package whose repo tests read packages/rest:
    • @objectstack/core: Tests 84 passed (84);
    • @objectstack/runtime: Tests 808 passed (808);
    • @objectstack/spec: Test Files 67 passed (67), Tests 1115 passed (1115).
  • Lint. pnpm exec eslint --no-inline-config over the 13 changed .ts / .mjs files: exit 0, 0 errors, 0 warnings. The repo-wide pnpm lint belongs to CI (Lint & Repo Gates).
  • Gate union. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) at c333dafda derives 94 commands. Its stderr names the commit, and the --repo assertion holds. The change set is 17 paths vs merge base 2bcaefba9, 1,197 changed lines (+714 / −483).
    • All 94 ran. pnpm check:dual-build-cjs-loads first answered exit 3, PREREQUISITE NOT MET (no whole-workspace dist). It was re-run after the whole-workspace build and exits 0, and that measured exit is the one in the --ran file. The other dist readers (check:dts-closure, check:lean-entry-closure, check:sourcemap-no-sources-content, check:published-files, check:entry-guard) were re-run then too, all exit 0.
    • --ran: Run reconciliation — 94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN. The exit is 0.
    • check:route-envelope exit 0: 23 route module(s) audited: 8 conformant, 14 ratcheted, 1 exempt.
    • check:liveness exit 0 (above).
    • check:pm-dispatch-gates exit 0.
    • check:type-check-debt exit 0.
  • Not governed. node scripts/pm/check-governed-merges.mjs --test over the 17 paths, --additions 714 --deletions 483, reads 0 of 17 path(s) hit the register and NOT governed, under the 3,000-line threshold.

Not run locally: check:dev-prereqs and check:skill-frame-freshness (local-only families). This diff touches no packages/spec build input, since liveness/ ships as files and is never built, and no four-axis frame file. check-dev-prereqs --self-test is in the union.


Generated by Claude Code

…23050 slice 12)

The body of RestServer#registerCrudEndpoints (the six record doors under
{basePath}{crud.dataPrefix}) moves, re-indented one level and otherwise
unedited, to packages/rest/src/crud-routes.ts as
registerCrudEndpoints(this: CrudRouteHost, basePath). The class keeps the
private method, its docblock and its position; its body is a host literal
(config and routeManager by value, four methods as late-bound closures)
plus registerCrudEndpoints.call(host, basePath).

Body-only imports move with the body. versionTokenRefusal stays in
rest-server.ts (the runtime /data twin names that file) and is exported
for the module; DATA_RECORD_READ_PARAMS was already exported.

Claude-Session: https://claude.ai/code/session_01BVm1pAXVXQ3PjC42j1kPFf
Co-authored-by: Claude <noreply@anthropic.com>
Every whole-file reader of RestServer's registrars reads the list, so the
moved CRUD body is back in their reach; each reader's length pin re-reads
its number in the next commit.

Claude-Session: https://claude.ai/code/session_01BVm1pAXVXQ3PjC42j1kPFf
Co-authored-by: Claude <noreply@anthropic.com>
 slice 12)

The registrar-list readers re-read their numbers over thirteen files
(sums unchanged); check-route-envelope declares crud-routes.ts at its
measured dialect facts (stringError 3, siblingCode 3; rest-server.ts
11 -> 8 and 7 -> 4, NOT progress, the same MOVE); the liveness ledger's
six CRUD evidence paths and three platform-checklist sources name
crud-routes.ts, where the cited code now lives. Consumer text, verdicts
and statuses unchanged.

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

45 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 b1845ed637fe469f079ef1cdfc96f8a47b5ca9e0.

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

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/liveness/crud_endpoints.json) — pages documenting those are invisible to this run
  • 5 cross-cutting symbol(s) contributed no route anchor: enforceAuth (23 routes), resolveExecCtx (28 routes), routeManager (19 routes), enforceApiAccess (9 routes), resolveProtocol (25 routes)
  • 1 anchor(s) matched too much of the corpus to be a work list: /data/:object (route, 66 pages)
  • 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 b1845ed637fe469f079ef1cdfc96f8a47b5ca9e0 → packageMentionDocs.

Which tree this was computed on

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

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

⚠️ 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 b1845ed637fe469f079ef1cdfc96f8a47b5ca9e0 → 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 · domain:cli seat 1 · os-sales · session session_01BVm1pAXVXQ3PjC42j1kPFf · 2026-10-17T17:57Z

Head c333dafda. This is the seat's review of record for #23050 slice 12 (claim 6235962355). None of the four contract faces is touched:

  • Clause-②: no, with no widening or narrowing;
  • no packages/spec/src/** path;
  • no governed path (check-governed-merges: 0 of 17).

So, by contract-review.md, CI plus the seat's own read decide it, and no second agent runs.

  • Form:

    • Draft → main, titled refactor(rest): ….
    • The body's first line is Part of #23050, with no closing keyword (scanned): the /meta slice remains. Clause-②: no is line-initial.
    • Two patch changesets. The @objectstack/rest one is owed because dist/index.{js,cjs} bytes change while the .d.ts and the export list do not. The @objectstack/spec one is owed because liveness/ ships.
  • The maintainer's five rules, executed:

    1. Move-only. The seat diffed it independently: base rest-server.ts:8324-8758, dedented one level, is identical to crud-routes.ts:107-541, all 435 lines.
    2. One named host. CrudRouteHost hands over exactly the six members an AST walk found: config and routeManager by value, the four methods as late-bound closures. Late binding was measured with an ablation and a base leg. The stub keeps the method's name, docblock and position, so the ADR cites that resolve by name (0036, 0076, 0111, 0138, all Tier H) still resolve.
    3. Byte-identical. Route table, ordered registrations and handler digests are identical under 13 configs. A one-character positive control moved exactly the PATCH door's digest. .d.ts/.d.cts sha256 is 1f0f455d… on base, move head and merge head, and both bundles keep 39 exports.
    4. /meta is untouched.
    5. No new gate.
  • Every by-path reader moved in the same PR, and none went silently empty:

    • the 9 registrar-list readers (sums unchanged);
    • the dogfood census, re-anchored with a new crud-routes.ts control;
    • check:route-envelope (8 / 4 in rest-server.ts, 3 / 3 in crud-routes.ts, sums 39 / 54 unchanged);
    • check:liveness (735 / 734 / 1);
    • the platform checklist (853/863 anchors on both trees).

    The red-before-re-point readings (two stages) show each reader really reads the moved code.

  • The dev's deviations, accepted:

    • versionTokenRefusal stays in rest-server.ts and gains export (the shape of slices 1 and 2), because a held-off packages/runtime path names that file as its home. The .d.ts is unchanged.
    • DATA_RECORD_READ_PARAMS is imported, not handed over: it is a module constant, slice 3's shape.
    • The claim's expected rest_api.json rows do not exist, as measured.
  • Cross-lane declarations:

  • Out-of-scope notes, routed:

    • The stale stub citations in crud_endpoints.json, route_generation.json and rest_api.json go to the spec seat with the 6238125655 amendment, not filed: they resolve by name and the gate stays green.
    • The versionTokenRefusal docblock and check-nul-bytes.mjs' stale line pointer: Acceptance notes, carrier: none.
    • ADR-0036 joins the maintainer's Tier H name-resolution carry.
  • Next slice: /meta. [finding] email_template: a Studio save of the same template in a second locale replaces the first locale's overlay at the next boot (sys_metadata keeps one overlay row per type and name) #22877 stage 2 has landed (038d4c9588), so the card's order gate is met. The dev's report names its prerequisites:

    • its own slicing plan, since about 3,000 lines sit inside the guarded routeManager swap;
    • TransportScopedMetaRequest's re-home;
    • the readers of metadata_endpoints.json, doc.json, book.json and system-context.mdx:184;
    • the census spelling private registerMetadataEndpoints(.
  • Landing: not governed (0 of 17 paths; 1197 changed lines ≤ 3000). The PR goes ready with auto-merge armed once every check on this head is green.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 17, 2026 18:24
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 17, 2026 18:24
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 17, 2026
Merged via the queue into main with commit bb7ccde Oct 17, 2026
42 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-23050-s12-crud-endpoints branch October 17, 2026 18:57
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