Repository navigation
refactor(rest): move registerCrudEndpoints into crud-routes.ts (#23050 slice 12) - #24205
Conversation
…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>
…ch (#23050 slice 12) Claude-Session: https://claude.ai/code/session_01BVm1pAXVXQ3PjC42j1kPFf Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 45 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 146 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 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
|
席位核对 — ACCEPT ·
|
Part of #23050
Clause-②: no
Slice 12 of the
rest-server.tssplit: CRUD. It follows slice 1's pattern and slices 2–11's shape, with slice 11 (PR #24054) as the direct precedent. The body ofRestServer#registerCrudEndpointsmoves, 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:
dist/*.d.tsis byte-identical, and so are both bundles' export lists;rest-server.tsgoes from 9,512 to 9,086 lines (basecb8756b00). #23050 stays open for the last slice,/meta(registerMetadataEndpointsInner), which this PR does not touch.packages/spec/liveness/crud_endpoints.jsongets six evidence-path substitutions. The consumer text, quoted code, producers, verdicts and statuses are unchanged. This is the onepackages/specexception declared to the spec seat on #6017 as6235976157, under slice 11's precedent (ruling A6225503839).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).config:8324, the body's first statement):1997, privaterouteManagerregistercalls:1998, privateresolveProtocol:2376, private asyncresolveExecCtx:2863, private asyncenforceAuth:2444, privateenforceApiAccess:2631, private asyncThe body has no bare
this, no element access onthis, noRestServer.static access and nofunctionkeyword.Control walk (the same walker on the same base): the
registerUiEndpointsstub readsrouteManager,resolveRequestEnvironmentId,resolveExecCtx,enforceAuth,enforceEnvironmentOwnershipandresolveProtocolonce each. That is slice 10's host literal, member for member.Module-level names the body reads (the same walk):
rest-server.ts' import block for each:FindDataRequest,GetDataRequest,CreateDataRequest,UpdateDataRequestandDeleteDataRequest, with their [finding]p.deleteData({...} as any)/p.updateData({...} as any)erase the compile-time check of the assembled request againstDeleteDataRequest/UpdateDataRequest#15866 note;assertFilterParamSuppliedOnce, with its finding: a repeated?filter=onGET /data/:objectcannot be told from a filter AST, so it is diagnosed as a malformed filter (and, rarely, succeeds) #7390 note;logUnexpectedRouteErrorandapplyDroppedFieldsHeader.mapDataError,refuseUnknownQueryParams,refuseRepeatedQueryParams,zodIssuesToFieldsandvalidationFailure.ServerScopedDataRequestis imported fromdata-action-routes.ts, where slice 1 moved it. Its last reader inrest-server.tswas this body, sorest-server.tsno longer imports it. The slice-1 note there now says so.rest-server.tsitself, 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 forAPPROVAL_REQUEST_LIST_PARAMS.versionTokenRefusal. It gainsexportfor this module only, the shape slices 1 and 2 used for their helpers. It does not move, because the runtime dispatcher's/datatwin (packages/runtime/src/domains/data.ts:79) namespackages/rest/src/rest-server.tsas the home of its full argument, andpackages/runtime/**is outside this slice. Moving it would make that pointer false..d.tsis 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).
CrudRouteHosthas 6 members, in the order the body first reads them, each typedRestServer['m'].:8320–:8323, head:8316–:8319) diffs to 0.registerCrudEndpoints.call(host, basePath). Methods go as late-bound closures,(...a) => this.m(...a).registerRoutes'registerForBaseis unchanged:if (this.config.api.enableCrud) { this.registerCrudEndpoints(bp); }, afterregisterDataActionEndpoints.registerRoutes(75 lines) diffs to 0 between base and head.Fields, checked one by one:
configconst { crud } = this.config;is the body's first statement, and the handlers close overcrud,operations,dataPathandisScoped:2138at head)routeManagerregistercalls):5166/:5170at head) wrapsregisterMetadataEndpointsInner, not this registrarNo 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 fivecrud.operations(listgates two mounts) andcrud.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.
RestServergets the four methods planted BEFOREregisterRoutes(), and all six doors are driven. Then four new methods are planted AFTER registration, and the six doors are driven again.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 islate:proto,late:ctx,late:auth,late:api×6, withearly calls after re-plant 0.Tests 1 passed (1).enforceApiAccessclosure was replaced bythis.enforceApiAccess.bind(this)throughscripts/ablation-replace.mjs(anchor 1 → 0, blobbc2c21614369→d8d11e7ef378).early:apithen 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: blobbc2c21614369equalsHEAD, andgit diff HEADis empty.rest-server.ts(blob5a0f948c2e8a) was put in the tree under a trap. It printed the same twoMEASURElines byte for byte,Tests 1 passed (1). Restored: blob equalsHEAD,git diff HEADempty.Proof of move-only
Moved body. Base
rest-server.tslines 8324–8758, with one 4-space indent removed, againstcrud-routes.tslines 107–541:diffexits 0 over all 435 lines, so no token changed..d.ts.packages/rest/dist/index.d.tsandindex.d.ctswere built at basecb8756b00before any edit and at45d976972. Both legs are sha2561f0f455d24836d47de60716233f1bd74d1738c04046974019264b17b5261bbe8, andcmpexits 0 on each pair.registerCrudEndpoints.callhits 1 in each ofindex.jsandindex.cjs, and 0 at base. So the declaration was rebuilt from the moved source.versionTokenRefusalfromrest-server.tsmoved 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 sourceRestServerwas booted on a recording mock server, with a protocol answering every member. Each dump holds:getRoutes()(functions as arity);It was dumped under thirteen configs:
enableDiscovery: false;enableOpenApi: false; both off under scoped-optional;enableSearch: falseunder scoped-optional;enableUi: falseunder scoped-optional;enableCrud: falseunder scoped-optional;crud.operations{ list: false, delete: false }under scoped-optional; all five operations false;crud.dataPrefix: '/objects'under scoped-optional.Results:
routes.json,calls.jsonandhandlers.jsonare byte-identical between base and head in all thirteen configs. The sha256 over the 39 dump files is8e9544b5b00f03081398465aa73416f51aee7a4aeef956ed550283937017daa8on both legs, and the default config'sroutes.jsonis6931d1a5d1827fea…on both.diff -rexit 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, blobd25749c75351→251ede3ffa43).PATCH …/:object/:iddoor 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.d25749c75351equalsHEAD,git diff HEADempty.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 againstgetRoutes()pass in thelocalrun below.Readers re-pointed (sums unchanged, no gate loosened)
packages/rest/src/rest-server-registrar-files.tsgainscrud-routes.ts, with a[#23050 slice 12]comment (length 12 → 13). Every reader that pins its length re-reads its number:execctx-consumer-census.test.tsrest-server.tsgoes 60 → 56. Thirteen forwarders = (13 − 1) + 3 − 2. Length 12 → 13meta-draft-read-door-census.test.tsrest-server-query-number-census.test.tsrest-server-canonical-query-ast.test.ts(repoproject)query:slotsquery: req.querymoved, so the floors arerest-server.ts1 → 0 andcrud-routes.ts: 1. Length 12 → 13rest-server-docblock-position.test.tscrud-routes.ts: 3: the module header, the host interface's block and the function's. The CRUD body held no TSDoc block, sorest-server.tskeeps its 163. Length 12 → 13ui-view-route-identity.measurement.test.tsthis.enforceAuth(59 → 60,this.resolveExecCtx(74 → 75ui-view-route-tenancy.measurement.test.tsauthz-conformance.test.ts(realtime tripwires)authz-probe-blind-spot.census.ts,rest-server.tsrowthis.routeManager.register(63 = 12 + 9 + 5 + 7 + 8 + 1 + 6 + 4 + 1 + 1 + 1 + 2 + 6;private register*Endpoints(16;enforceAuth108 → 112crud-routes.ts: this.routeManager.register(6;registrar list length13;populationRulenames the new module.enforceAuthis RE-ANCHORED, not relaxed: the six call sites moved and are counted in both readings, and the four new occurrences are the seamReaders outside the list:
scripts/check-route-envelope.mjs(a gate's target table,domain:spec).crud-routes.tsis declareddialectOnlywith 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 arestringError 3andsiblingCode 3: the three request gates' 400VALIDATION_FAILEDbodies ('Invalid create request','Invalid query request','Invalid update request').rest-server.tsdrops by exactly those bodies:stringError11 → 8 andsiblingCode7 → 4, each recorded as "NOT progress — the same MOVE".rest-server.tsrow and the new row (22 → 23 modules audited, 13 → 14 ratcheted).packages/spec/liveness/crud_endpoints.json. Six evidence pathspackages/rest/src/rest-server.ts#registerCrudEndpoints→packages/rest/src/crud-routes.ts#registerCrudEndpoints: the fiveoperations.*rows (if (operations.X)) and the first citation of thedataPrefixrow (const dataPath = …crud.dataPrefix…). Every quoted fragment now lives incrud-routes.ts. See the next section.docs/qa/platform-checklist/areas/api-backend.json(domain:devx, slice 7/11's precedent). Three sources namecrud-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.check-adr-anchorsreads 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.jsonanchors ADR-0045'sfilterAppForUser, which did not move.content/docs/**anchor. Measured: no page undercontent/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:
c8f1643c8(the list not yet grown):localreadTest 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, thenexpected 40 to be 46twice. The six CRUD sites had left the reach of a reader that did not yet know the module.reporeadTests 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-envelopereadcrud-routes.ts NOT DECLARED,stringError: found 8, declared 11andsiblingCode: found 4, declared 7.3e58c49cb(the list grown, the readers not yet re-pointed):local(the six list readers) readTest 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.reporeadTests 3 failed | 76 passed | 13 skipped (92): the length pin and both floors.Tests 2 failed | 106 passed (108): the tripwire's length pin, andcontrol "registrar list length" moved in packages/rest/src/rest-server.ts: expected 13 to be 12.45d976972, then the merge head).The
packages/specreader, re-pointed (the declared exception)crud_endpoints.jsoncitedrest-server.ts#registerCrudEndpointsfor the code that gates and prefixes the CRUD mounts, and that code now lives incrud-routes.ts.check:livenessstays green either way:registerCrudEndpointsinrest-server.ts;rest-server.tsstill names every key (operations,dataPrefix) innormalizeConfig.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:livenessat the head exits 0. It readssymbol anchors: 945 pointer(s) written path#symbol, 945 naming a symbol the cited file contains.andkey-mention anchoring: 735 (entry, cited file) pair(s) asked, 734 anchored, 1 exempt.(734 / 733 / 1 at base). ThedataPrefixrow now cites two files,crud-routes.tsfor the CRUD mount andrest-server.tsfor its four sibling consumers, so it asks one more pair, and that pair anchors.No
rest_api.jsonrow cites the moved code. That was measured over every liveness file by extracting each backtick-quoted fragment from the rows that citerest-server.ts. Only the sixcrud_endpoints.jsonrows quote code found in the CRUD body and nowhere else inrest-server.ts.rest_api.json'senableCrudrow citesregisterRoutes'if (this.config.api.enableCrud), which did not move.Changesets:
.changeset/23050-s12-crud-liveness-citations.mdis a@objectstack/specpatch (Clause-②: no), becauseliveness/ships in that package'sfiles[]..changeset/23050-s12-crud-routes-move.mdis a@objectstack/restpatch (Clause-②: no, move-only), because the shippeddist/index.jsandindex.cjsbytes change: the head-only marker above. The declarations and exports do not change.skip-changesetdoes 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, atregisterCrudEndpoints' read-record handler");ui-view-route-identity.measurement.test.ts(byRegistrar.get('registerCrudEndpoints'), read off the mounted registrar at runtime);note;docs/qa/platform-checklist/FOLLOW-UPS.mdE3;packages/spec/src/api/rest-server.zod.tsand the18.api__CrudEndpointsConfig__patternsretired-key entry ("the server'sregisterCrudEndpoints");content/docs/references/api/rest-server.mdx.crud_endpoints.json'spatternsnote 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 thedataPrefixrow'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/restregisterCrudEndpoints(). It is Tier H and not edited, and the name still resolves.The runtime
/datatwin's pointer (packages/runtime/src/domains/data.ts:79, "versionTokenRefusalinpackages/rest/src/rest-server.ts") stays true, because the function stays there.Repo tests outside
packages/restthat readpackages/rest/src:core'sauthz-store-unavailable.test.tsclassifiesrest-server.tsasguardedfor a seam the CRUD body never calls;runtime'smeta-list-projection-parity.test.tsreads only theGET {metaPath}/:typehandler;spec's five listpackages/resttest FILES by name.None reads the moved code, and all three
test:reporuns are green below.Gates that walk
packages/rest/srcreach the moved code in its new file. Their scan outputs were compared between a worktree atorigin/main2bcaefba9and the final headc333dafda. They differ only by the one scanned file or by this diff's own re-points:check-meta-type-normalized40 → 41 files;check-undeclared-dep-imports+1 file, +2 specifiers;check-dispatcher-error-vocabularyandcheck-error-status-conformance+1 file, classified sites unchanged;check-affected-docsroute sources 30 → 31 and registration call sites 25 → 26, by the*-routes.tsconvention;check-scripts-symbol-anchors3910 → 3914 file-level anchors (the route-envelope gate's new comments);check:platform-checklistcited sources 400 → 401;check:livenessone more key-mention pair, anchored;check-route-envelopethe 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-parityandcheck-adr-anchors(70 anchored files, 1,782 decision-letter citations). At the move commit, against basecb8756b00, 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 incrud-routes.ts, in the sameRestServerdoors. This is a prose pointer, not edited by a move (no carrier).dataPrefixrow's four sibling citations and thepatternsnote citerest-server.ts#register…names whose bodies slices 1, 2, 4, 7 and this one moved. Each still resolves by name to its stub, andcheck:livenessanchors them by file. Re-pointing them is outside this slice's declaredpackages/specsubstitution. Carrier: thedomain:specseat, 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.ui-routes.ts' "52 sites" comment andemail-routes.ts' double-mount header (no non-move edit of either text here).Gates and tests
Final head
c333dafda. It is one merge oforigin/main2bcaefba9(#24170, #24177, #24180, #24178, #24181). None of those touches a file in this diff.rest-server.ts' blob is5a0f948c2e8aatcb8756b00and at2bcaefba9. main's #24178 changeddata-action-routes.ts' export handler, so the merge-head proofs are taken againstorigin/mainitself:.d.ts. A base leg putorigin/main'srest-server.tsinto the merged tree under a trap and built@objectstack/rest. It gives1f0f455d…with the marker at 0. Restored (blob andgit diff HEADproof) and rebuilt, the head gives1f0f455d…with the marker at 1. The export lists are 39 /b3246be6…on both bundles.origin/main'srest-server.tsequal the merge head's (diff -rexit 0, sha2561d7630283baf…on both legs). They differ fromcb8756b00's only in theGET …/data/:object/exporthandler 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. Atc333dafda:pnpm --workspace-concurrency=2 --filter '@objectstack/rest...' --filter '@objectstack/verify...' --filter '@objectstack/dogfood^...' buildexit 0. Thenpnpm turbo run build --concurrency=2(whole workspace) exit 0:Tasks: 77 successful, 77 total.pnpm --filter @objectstack/spec check:generatedexit 0:All 14 generated artifacts are up to date. main movedpackages/spec.pnpm --filter @objectstack/rest typecheckexit 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 itstest:repo:Test Files 5 passed (5),Tests 258 passed | 13 skipped (271).authz-probe-blind-spot.test.ts,authz-conformance.test.ts):Test Files 2 passed (2),Tests 108 passed (108).packages/speclocal,scripts/liveness/):Test Files 10 passed (10),Tests 269 passed (269).test:repoof every package whose repo tests readpackages/rest:@objectstack/core:Tests 84 passed (84);@objectstack/runtime:Tests 808 passed (808);@objectstack/spec:Test Files 67 passed (67),Tests 1115 passed (1115).pnpm exec eslint --no-inline-configover the 13 changed.ts/.mjsfiles: exit 0, 0 errors, 0 warnings. The repo-widepnpm lintbelongs to CI (Lint & Repo Gates).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths) atc333dafdaderives 94 commands. Its stderr names the commit, and the--repoassertion holds. The change set is 17 paths vs merge base2bcaefba9, 1,197 changed lines (+714 / −483).pnpm check:dual-build-cjs-loadsfirst answered exit 3,PREREQUISITE NOT MET(no whole-workspacedist). It was re-run after the whole-workspace build and exits 0, and that measured exit is the one in the--ranfile. 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-envelopeexit 0:23 route module(s) audited: 8 conformant, 14 ratcheted, 1 exempt.check:livenessexit 0 (above).check:pm-dispatch-gatesexit 0.check:type-check-debtexit 0.node scripts/pm/check-governed-merges.mjs --testover the 17 paths,--additions 714 --deletions 483, reads0 of 17 path(s) hit the registerand NOT governed, under the 3,000-line threshold.Not run locally:
check:dev-prereqsandcheck:skill-frame-freshness(local-only families). This diff touches nopackages/specbuild input, sinceliveness/ships as files and is never built, and no four-axis frame file.check-dev-prereqs --self-testis in the union.Generated by Claude Code