Skip to content

refactor(rest): move registerDataActionEndpoints into data-action-routes.ts (#23050 slice 1) - #23116

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-23050-s1-data-action-endpoints
Oct 13, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-23050-s1-data-action-endpoints

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #23050
Clause-②: no

Slice 1 of the rest-server.ts split: the body of RestServer#registerDataActionEndpoints (record clone, the sync and async import doors, the import-job reads, undo and cancel, the streaming export) moves, unedited, to the new module packages/rest/src/data-action-routes.ts. It is move-only. Routes, the route ledger and the published dist/*.d.ts are byte-identical, and every reader that read the moved lines by path now reads them where they are. The card stays open for the later slices.

The pattern later slices repeat

Members the moved body reads (base 36b2889cdd line numbers): fields config :1959, routeManager :1960, cancelledImportJobs :2064 (readonly). Methods resolveProtocol :2338, enforceAuth :2390, enforceApiAccess :2572, enforceExportPermission :2675, enforceImportTemplateGates :2736, resolveExecCtx :2785, resolveMessageTranslator :3890, translateMetaItem :3912, answerImportTemplate :10161, resolveSecurityService :10267. That is 13 members, found by an AST walk of the body.

Three shapes, measured on both criteria:

shape published .d.ts tsc --noEmit repeatable
A. The class keeps only the call at registerRoutes and the method is deleted changes: tsup emits private members with their JSDoc, so base dist/index.d.ts lines 3153-3163 (the docblock plus private registerDataActionEndpoints;) would go n/a yes
C. The class passes itself: registerDataActionEndpoints.call(this as unknown as DataActionRouteHost, basePath) identical exit 2: TS6133 x5, because noUnusedLocals stops seeing reads made through the cast (cancelledImportJobs, enforceExportPermission, enforceImportTemplateGates, resolveMessageTranslator, answerImportTemplate). The tsup DTS build fails the same way only if the class also reads every member some other way
B. The class builds a host literal (chosen) identical exit 0 yes, as below

How B works:

  • The class keeps private registerDataActionEndpoints(basePath) with the same name, docblock and position. Its body is now one DataActionRouteHost literal and registerDataActionEndpoints.call(host, basePath).
  • DataActionRouteHost lists every member the body reads, and only those. Each one is typed as the class member itself (RestServer['enforceAuth'], an indexed access), so a renamed or respelled member is a compile error. There is no cast, and no member of RestServer becomes public or protected.
  • Methods are passed as late-bound closures, (...a) => this.m(...a). A spy or test double set on the instance or prototype after registration is still the one called, as before.
  • Fields are passed by value. That equals a live read here only because cancelledImportJobs is readonly and config / routeManager are read only while registration runs. A later slice whose body reads a reassignable field at request time passes a getter instead, checked field by field.
  • The moved body keeps saying this. (it is a this: DataActionRouteHost function), so the moved lines are unedited.
  • Module-level helpers stay in rest-server.ts and are exported for the module (12 functions and constants). The package entry does not re-export them.
  • One exception, measured: exporting the ServerScopedDataRequest type alias from rest-server.ts changed dist/index.d.ts by one blank line, because rollup-plugin-dts emits the alias and then tree-shakes it. A second copy is ruled out by the card. So the alias moved verbatim, with its docblock, into the new module, and rest-server.ts imports it type-only. The CRUD, form and batch doors in rest-server.ts now import it from there. A later slice can rehome it.

Proof of move-only

  • .d.ts: packages/rest/dist/index.d.ts and index.d.cts are byte-identical to base. sha256 is 991a8fc9cae955b56745db0b3b978a60d0d3a8f918a3a05e39ee8176601e5998 for both, before and after (cmp silent), rebuilt at the final head. The ESM bundle's export list is unchanged (34 names). The bundle JS bytes differ, because code moved within it.
  • Routes: a booted RestServer (source, recording mock server) was dumped under three configs (default, scoped-optional, scoped-required). Both getRoutes() and the ordered server registration calls are byte-identical, 6 dumps of 6:
    • default routes.sha256=1e1859c0ff16…babd2, calls bc7196b91265…3fef8e (75 routes, 9 data-action)
    • scoped-optional d79275a35e12…4d74b09 / 2fa050c0d6e0…bd3650 (150 routes, 18 data-action)
    • scoped-required 066d85bfcdef…aa5fc6 / 7595d734a014…08775e7 (75 routes, 9 data-action)
  • Route ledger: rest-route-ledger.ts and runtime/src/route-ledger.ts are untouched. check:route-ledger-census output is identical base vs head.
  • Moved block (git diff --color-moved=plain --color-moved-ws=allow-indentation-change 36b2889cdd HEAD over the two files): 1098 deleted and 1115 added lines are detected as moved. Without allow-indentation-change, the one-level re-indent hides most of the move. The non-moved hunks:
    • rest-server.ts: the stub body (the host literal, 20 lines); one import line (plus its one-line comment); the three-line pointer comment where the alias was; export added to 12 module-level declarations; imports the move left unused removed (ListImportJobsRequestSchema, IMPORT_JOB_MAX_ROWS with its comment, referenceFieldNames, formatRowForJson, cellFontColor, readTemplateMode, runImport, prepareImportRequest); the #20061 schema-import comment corrected from "three doors" to two plus a pointer.
    • data-action-routes.ts: the module docblock, the imports, export on the moved alias line, the DataActionRouteHost interface, and the function docblock and signature.

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

  • scripts/check-route-envelope.mjs: the new module is declared dialectOnly (the guarantee these bodies had in rest-server.ts). rest-server's stringError goes 39 to 26, and the new module carries 13. siblingCode goes 54 to 39, and the new module carries 15. Both new counts are measured by the gate, and both sums are unchanged.
  • packages/rest/src/execctx-consumer-census.test.ts:
    • Sites are keyed file:line over both files: 62 = 53 + 9.
    • The stack attribution steps over the host literal's one forwarder frame. The forwarder's line is pinned.
    • Mentions go from 77 to 81. The four new mentions are the seam itself (the forwarder and the interface member), not consumers.
  • rest-server-query-number-census.test.ts: reads both files. The ledger is unchanged, because keys are routes, not files.
  • rest-server-canonical-query-ast.test.ts: the query-slot floor of 4 becomes 1 + 3 (the new module is censused under the same door rule), and the two by-name literals are asserted where they now live.
  • ui-view-route-identity.measurement.test.ts and ui-view-route-tenancy.measurement.test.ts: the whole-file spelling reverse checks count over both files.
  • rest-server-docblock-position.test.ts: the module is read under the same no-stacked-docblock rule, floor 4 (its measured count), because the alias docblock moved there.
  • packages/qa/dogfood/test/authz-probe-blind-spot.census.ts:
    • The rest-server row's registration population reads the module too, so 70 / 19 / 51 is unchanged. The nine moved routes are still RestServer's and still in the blind spot.
    • A new control covers the module's own share (9).
    • enforceAuth goes from 57 to 61, re-anchored: the four new occurrences are the seam, and no call site was added.
  • docs/qa/platform-checklist/areas/records-forms.json: the clone item's source anchor now names data-action-routes.ts#registerDataActionEndpoints.

Readers examined and not re-pointed, because the moved lines contribute zero to them (measured on the module):

  • The dogfood realtime TRIPWIREs on rest-server.ts: 0 mechanics hits and 0 route hits.
  • objectql-slot-consumer-census: 0 provider reads. A future read would have to add a member to the host literal in rest-server.ts, where this census reads.
  • meta-draft-read-door-census: 0 draft switches.
  • auth-gate-allowlist-fault-window, execctx-authz-input-seam-reachability, runtime meta-list-projection-parity: they slice other registrars.
  • These gates' outputs are identical base vs head, apart from scanned-file counts going up by one: check-durability-degradation-log-level, check-system-context-census, check-single-authz-resolver, check-undeclared-dep-imports, check-dispatcher-error-vocabulary, check-meta-type-normalized.
  • The ADR anchor packages__rest__src__rest-server.ts.json (ADR-0045, filterAppForUser) is unaffected.

Acceptance notes

  • Two packages/spec/liveness evidence strings name rest-server.ts#registerDataActionEndpoints (crud_endpoints.json dataPrefix, route_generation.json overrides). They still resolve, to the private method that registers these routes by calling the moved body, and check:liveness is green. Their prose about dataPath and body.overrides now describes code in data-action-routes.ts. They are not edited here, because the claim keeps packages/spec out of this slice. Named in the report.
  • check:platform-checklist is red on the base tree with the same 8 findings, none in records-forms.json. The output is identical base vs head.
  • Lines of rest-server.ts after the moved block shift by about 1,100. No line citation in the repo points into the moved range, base 9107-10137 or 311-372.

Gates

All at 5a05b4bde6 (the slice plus a merge of origin/main 965a1bfc72; no overlap with this diff).

  • pnpm --filter @objectstack/rest test: Test Files 275 passed (275) before (36b2889cdd) and after; Tests 5759 passed | 326 skipped (6085) before, 5761 passed | 326 skipped (6087) after (+2 = the docblock pin's new block for the moved module). VERDICT command-exit 0.
  • pnpm --filter @objectstack/rest test:repo: Test Files 5 passed (5) before and after; Tests 209 passed | 1 skipped (210) before, 213 passed | 2 skipped (215) after (+5 = the canonical-query-ast census's new file entry: 4 cases + 1 skipIf). VERDICT command-exit 0.
  • pnpm --filter @objectstack/rest typecheck: exit 0 (tsc --noEmit and check:test-typecheck: 0 files / 0 errors in the debt ledger).
  • pnpm --filter @objectstack/dogfood exec vitest run test/authz-probe-blind-spot.test.ts test/authz-conformance.test.ts: 2 files, 92 passed. VERDICT command-exit 0.
  • Lint, proven narrowing: eslint --no-inline-config --format json over the 10 touched lintable files gives 10 results, 0 errors, 0 warnings. ESLint's own isPathIgnored is false for each one, and calculateConfigForFile gives 2 to 6 rules each. The config has no parserOptions.project (no type-aware lint), and its only file inputs (eslint.config.mjs, scripts/slot-lookup-baseline.json, scripts/query-options-erasure-baseline.json) are untouched, so no untouched file's verdict can move.
  • dispatch-gates derivation with no paths: 79 commands. --ran reconciliation: 79 derived, 78 run, 1 NOT-MEASURED, 0 UNRUN.
    • 77 of 79 exit 0.
    • pnpm check:platform-checklist exits 1 with the same 8 findings as the base tree (none in records-forms.json).
    • NOT MEASURED: check:dual-build-cjs-loads, reason: PREREQUISITE NOT MET (exit 3, 19 workspace packages have no dist/ in this container). As a narrow stand-in, packages/rest/dist/index.cjs and index.js both load with 34 exports each. CI runs the gate over a full build.

Per command (exit code):

  • node scripts/check-ci-filter-parity.mjs exit 0
  • node scripts/check-closing-keyword-parity.mjs exit 0
  • node scripts/check-closing-keyword-parity.mjs --self-test exit 0
  • node scripts/check-comment-mask-adoption.mjs exit 0
  • node scripts/check-comment-mask-adoption.mjs --self-test exit 0
  • node scripts/check-comment-mask-corpus.mjs exit 0
  • node scripts/check-declaration-mirrors.mjs exit 0
  • node scripts/check-declaration-mirrors.mjs --self-test exit 0
  • node scripts/check-dts-emitted.mjs --self-test exit 0
  • node scripts/check-issue-citations.mjs exit 0
  • node scripts/check-keyed-text-bounds.mjs exit 0
  • node scripts/check-keyed-text-bounds.mjs --self-test exit 0
  • node scripts/check-platform-object-tenancy-census.mjs exit 0
  • node scripts/check-platform-object-tenancy-census.mjs --self-test exit 0
  • node scripts/check-plugin-teardown-shape.mjs exit 0
  • node scripts/check-plugin-teardown-shape.mjs --self-test exit 0
  • node scripts/check-registry-log-declared.mjs exit 0
  • node scripts/check-registry-log-declared.mjs --self-test exit 0
  • node scripts/check-rest-log-spy-declared.mjs exit 0
  • node scripts/check-rest-log-spy-declared.mjs --self-test exit 0
  • node scripts/check-scripts-symbol-anchors.mjs exit 0
  • node scripts/check-scripts-symbol-anchors.mjs --self-test exit 0
  • node scripts/check-self-test-wired.mjs exit 0
  • node scripts/check-self-test-wired.mjs --self-test exit 0
  • node scripts/check-self-test-workflow-commands.mjs exit 0
  • node scripts/check-self-test-workflow-commands.mjs --self-test exit 0
  • node scripts/check-system-context-census.mjs exit 0
  • node scripts/check-system-context-census.mjs --self-test exit 0
  • node scripts/check-undeclared-dep-imports.mjs exit 0
  • node scripts/check-undeclared-dep-imports.mjs --self-test exit 0
  • node scripts/check-whole-set-label-write.mjs exit 0
  • node scripts/check-whole-set-label-write.mjs --self-test exit 0
  • node scripts/docs-audit/check-affected-docs.mjs exit 0
  • node scripts/docs-audit/check-drift-comment.mjs exit 0
  • node scripts/pm/bare-root-worklist.mjs --self-test exit 0
  • pnpm --filter @objectstack/lint run check:doc-formula-expressions exit 0
  • pnpm --filter @objectstack/spec run check:duration-unit-keys exit 0
  • pnpm --filter @objectstack/spec run check:empty-state exit 0
  • pnpm --filter @objectstack/spec run check:liveness exit 0
  • pnpm --filter @objectstack/spec run check:strictness-ledger exit 0
  • pnpm --filter @objectstack/spec run check:variant-docs exit 0
  • pnpm check:agent-test-spelling exit 0
  • pnpm check:authz-resolver exit 0
  • pnpm check:bash32-floor exit 0
  • pnpm check:cli-command-ids exit 0
  • pnpm check:cross-package-test-inputs exit 0
  • pnpm check:dispatcher-error-vocabulary exit 0
  • pnpm check:doc-authoring exit 0
  • pnpm check:driver-memory-census exit 0
  • pnpm check:dts-closure exit 0
  • pnpm check:dual-build-cjs-loads exit 3
  • pnpm check:engine-double-contract exit 0
  • pnpm check:entry-guard exit 0
  • pnpm check:error-status-conformance exit 0
  • pnpm check:gitlink-declared exit 0
  • pnpm check:issue-citations exit 0
  • pnpm check:lean-entry-closure exit 0
  • pnpm check:logger-receiver-detach exit 0
  • pnpm check:nul-bytes exit 0
  • pnpm check:objectql-double-limit exit 0
  • pnpm check:org-identifier exit 0
  • pnpm check:page-declaration-shape exit 0
  • pnpm check:parse-guard exit 0
  • pnpm check:platform-checklist exit 1
  • pnpm check:pm-dispatch-gates exit 0
  • pnpm check:pnpm-filter-targets exit 0
  • pnpm check:published-files exit 0
  • pnpm check:query-options-erasure exit 0
  • pnpm check:ratchet-remedy-authority exit 0
  • pnpm check:refd-timer-probe exit 0
  • pnpm check:route-envelope exit 0
  • pnpm check:slot-lookup exit 0
  • pnpm check:sourcemap-no-sources-content exit 0
  • pnpm check:test-source-alias exit 0
  • pnpm check:tier-file-adoption exit 0
  • pnpm check:type-check-coverage exit 0
  • pnpm check:type-check-debt exit 0
  • pnpm check:watch-hint-literal exit 0
  • pnpm check:where-matcher exit 0

Generated by Claude Code

…-routes.ts (#23050 slice 1)

Move-only. The body of RestServer#registerDataActionEndpoints moves,
re-indented one level and otherwise unedited, to a new module
packages/rest/src/data-action-routes.ts as
registerDataActionEndpoints(this: DataActionRouteHost, basePath).

The class keeps the private method (name, docblock and position are in
the published dist/*.d.ts), now building a DataActionRouteHost literal
that hands over every member the body reads, and calling the moved body
with it. Module-level helpers the body uses stay in rest-server.ts and
are exported for the new module only; the package entry is untouched.
Imports the move left unused in rest-server.ts are removed.

Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6
Co-authored-by: Claude <noreply@anthropic.com>
…action body (#23050 slice 1)

Exporting the alias from rest-server.ts for the new module changed the
published dist/index.d.ts by one blank line (rollup-plugin-dts emits an
exported type alias and then tree-shakes it). Exporting functions and
constants does not. The alias moves verbatim to data-action-routes.ts,
and rest-server.ts imports it type-only. dist/index.d.ts and
dist/index.d.cts are now byte-identical to the base.

Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6
Co-authored-by: Claude <noreply@anthropic.com>
…23050 slice 1)

Every reader that read the moved lines through rest-server.ts now reads
data-action-routes.ts too, at the same rule and with sums unchanged:

- check-route-envelope: the new module is declared dialectOnly at the
  counts that left rest-server.ts (stringError 39 -> 26 + 13, siblingCode
  54 -> 39 + 15).
- execctx-consumer-census: sites are file:line over both files (62 =
  53 + 9), the host literal's forwarder frame is stepped over and pinned;
  mentions 77 -> 81 are the seam's four, none a consumer.
- rest-server-query-number-census: coercions over both files; ledger
  unchanged (keys are routes, not files).
- rest-server-canonical-query-ast: query-slot floors 4 -> 1 + 3, and the
  two by-name literals asserted where they now live.
- ui-view-route-{identity,tenancy}: the whole-file spelling reverse checks
  count over both files.

Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6
Co-authored-by: Claude <noreply@anthropic.com>
…23050 slice 1)

- dogfood authz-probe-blind-spot census: the rest-server.ts row's
  registration population reads data-action-routes.ts too, so 70 / 19 / 51
  stays unchanged (the nine moved routes are still RestServer's, all in the
  blind spot). New control for the module's own share (9); enforceAuth
  57 -> 61 re-anchored: the four new occurrences are the host literal's
  forwarder and the host interface's member, no call site.
- rest-server-docblock-position: the module is read under the same rule
  (it took the ServerScopedDataRequest docblock), floor 4 = its measured
  count.
- platform-checklist records-forms: the clone item's source anchor names
  data-action-routes.ts#registerDataActionEndpoints, where the body is.

Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest, touching 84 documentable anchor(s).

48 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 a3d47c2c59dce84c1cb1a64abb0f07b16675ce0b.

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

What this run could not see
  • 6 cross-cutting symbol(s) contributed no route anchor: enforceApiAccess (9 routes), enforceAuth (23 routes), resolveExecCtx (28 routes), resolveProtocol (25 routes), routeManager (23 routes), readDeclaredQueryNumber (4 routes)
  • 1 anchor(s) matched too much of the corpus to be a work list: created_at (literal, 38 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 — 17 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 a3d47c2c59dce84c1cb1a64abb0f07b16675ce0b → packageMentionDocs.

Which tree this was computed on

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

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

⚠️ 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 a3d47c2c59dce84c1cb1a64abb0f07b16675ce0b → 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

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 5a05b4bde69fb06eb4ec5a5bceb79f4cbf7f0674
Local-runs: none

Rendered 2026-10-13T01:01Z by an isolated review inside the adopting seat's session, on PR #23116 at the head above (branch claude/issue-23050-s1-data-action-endpoints, merge base 965a1bfc72). Inputs: card #23050's body and its four comments (ruling and triage 6126748916, claim 6134225153, os-dev-report 6135442258, and the seat's ACCEPT 6135541164, read as a claim under test), the PR body and file list, the net diff against the merge base (11 files, +1501 / −1184), and the check-runs on the head. Owed because the PR edits a gate in another lane (scripts/check-route-envelope.mjs, domain:spec, declared after dispatch as 6135513000 on #6017); the whole PR is judged, since "move-only" is the claim that makes that edit honest.

Check-runs on the head, read 2026-10-13T00:57Z: 41 names, latest run of each: 32 success, 9 skipped (label- or path-gated: Auto Label, Build Docs, Check Changeset, Check PR Size, Console Pin Gate, the dogfood shards, Dogfood Verify CLI, the packed-tarball smoke, Temporal Conformance), 0 failed, 0 still running. Test Core (3/6) and (4/6) were in progress at the seat's ACCEPT and have since concluded success. The gates this review leans on all concluded success: Lint · docs & code guards (hosts check:route-envelope, lint.yml), Build Core (hosts check:dual-build-cjs-loads, ci.yml), Spec property liveness (check:liveness), Type Check · workspace, Dogfood Regression Gate, and all six Test Core shards.

① Derived judgments

Move-only — RIGHT, measured.

  • The method body: base rest-server.ts :9108–:10136 (1029 lines) against data-action-routes.ts :190–:1218 (1029 lines). diff is empty once one 4-space indent is removed from the base; whitespace-stripped, both sides hash to sha256 bbe45e60…8314. Not one token of the body changed: no route path, method, handler, status, envelope, or enforceAuth / enforceApiAccess / enforceExportPermission / enforceImportTemplateGates / resolveExecCtx call moved.
  • The alias block: base :311–:372 equals the module's :96–:157 except export on the alias line.
  • Everything else in rest-server.ts (14 hunks): the 20-line stub (host literal plus .call), one import line with a one-line comment replacing the runImport / prepareImportRequest imports, the 3-line pointer where the alias stood, the #20061 comment ("three doors" → "two of the three", with a pointer), export on exactly 12 module-level declarations (a diff of the file's export declaration names shows 12 added, 0 removed: UNDECLARED_WHOLE_NUMBER_PARAM, readDeclaredQueryNumber, IMPORT_JOB_OBJECT, IMPORT_JOB_UNDO_MAX_ROWS, newImportJobId, capImportResults, parseUndoLog, importJobUndoable, importJobToProgress, importJobToSummary, rowsToCsv, createXlsxStream), and the removal of eight imports the move left unused. answerImportTemplate and resolveSecurityService are untouched (the one class hunk ends at the stub's closing brace). No line citation anywhere in the head tree points into base :9107–:10137 or :311–:372.
  • The this. reads in the moved body are exactly 13 distinct members, and the host literal supplies exactly those 13; the interface names no other.

The published surface — RIGHT, nothing new is published. packages/rest/package.json has one exports entry (. → dist/index.js / index.cjs and index.d.ts / index.d.cts); src/index.ts is named-exports only, unchanged base→head, and never names data-action-routes. So registerDataActionEndpoints, DataActionRouteHost, ServerScopedDataRequest and the 12 newly exported helpers are reachable from no published entry, and the root entry exports no new name. The private method and its JSDoc keep their position in the class. The byte-identical dist/*.d.ts (sha256 991a8fc9…) is the dev's measurement, not re-run here; it is the only outcome consistent with an unchanged entry file and a declaration bundler that emits reachable names, and Build Core plus the four Type Check jobs concluded success on the head. The RestServer['member'] indexed accesses on private members compile (TypeScript permits them) and make a renamed member a build error.

The pattern — RIGHT, no stale value is reachable. config is assigned once, in the constructor (:2037), and read once, at the function's top level during registration. routeManager is assigned in the constructor (:2038) and swapped at :5534 / :5538 inside registerMetadataEndpoints under try / finally; registerRoutes calls registerMetadataEndpoints(bp) at :4601 before registerDataActionEndpoints(bp) at :4641, so the literal captures the real manager, and all nine this.routeManager.register( reads sit at the function's top level, synchronous, during registration. cancelledImportJobs is private readonly (:1997), a Set that is mutated and never replaced, so the six request-time reads see the live object. Methods are late-bound arrow closures on the server, so a spy or double set after registration is still what the body reaches. this inside the body is only the host: the function declares this: DataActionRouteHost and is reached only through .call(host, basePath). The dev's rule for later slices (a reassignable field read at request time takes a getter) is the right one; it is stated in the module header and the report.

The gate edit, scripts/check-route-envelope.mjs — RIGHT, a split with each sum unchanged and no loosening.

  • auditDialectOnly refuses got !== want in both directions ("a NEW non-conforming body" above, "fewer than pinned — bank it" below). Lint · docs & code guards concluded success on the head, so the gate itself proved rest-server.ts stringError = 26 and siblingCode = 39, and data-action-routes.ts stringError = 13 and siblingCode = 15. 26 + 13 = 39 and 39 + 15 = 54, the base declarations.
  • dialectOnly is the same key rest-server.ts carries, with the ratchet it requires (#9559) and none of the write-site keys it refuses to sit beside (responses / ok / err / privateOk). The body was held to the two dialect facts inside rest-server.ts; it is held to exactly those in its own file. A responses pin would be a guarantee these bodies never carried, added by a PR whose contract is to move nothing. Noted, not a finding: the module inherits a waiver whose stated reason (208 write sites, edited daily) describes the hot file, not a 1,219-line module; converting it onto sendOk / sendError is #9559's business, not this slice's.
  • Discovery makes the entry required, not optional: discover() walks packages/ for every *-routes.ts plus OFF_CONVENTION_MODULES; a discovered file absent from MODULES is "NOT DECLARED" and reds, and a declared file not found ("moved or deleted?") reds too. Without the new entry the gate would have failed on this head.

Every other re-pointed reader — RIGHT, each a re-point with population and sum unchanged, none a widened allowance or a lowered floor.

  • execctx-consumer-census.test.ts: sites keyed file:line over both files; 62 sites unchanged (53 + 9), 16 caught / 46 bare unchanged; mentions 77 → 81 with the four new ones being the forwarder (two) and the interface member (two); siteOfStack steps over exactly one frame, the forwarder's, whose line is pinned above 0; the §6 wrapper assertion keys rest-server.ts.
  • rest-server-query-number-census.test.ts: both files form one population; the ledger is keyed by route or function and did not change a row; the unclassified message names the file.
  • rest-server-canonical-query-ast.test.ts: minQuerySlots 4 → 1 + 3 under the same door rule (noDoor: false on both), each floor its file's measured count; the two by-name literals are asserted where they now live.
  • rest-server-docblock-position.test.ts: the module is read under the same no-stacked-docblock rule with floor 4 (header, alias, interface, function); the original file's pin is untouched.
  • ui-view-route-identity and ui-view-route-tenancy measurements: the reverse-check floors (above 40, and exactly 1 for enforceEnvironmentOwnership) are unchanged thresholds now counted over the same code in two files; the sliced per-registrar readings still read rest-server.ts alone.
  • authz-probe-blind-spot.census.ts: the rest-server row's population reads both files, so 70 / 19 / 51 is unchanged; this.routeManager.register( stays 63 (54 + 9) with a new control of 9 on the module alone, so a reading that stopped appending the module cannot hide; enforceAuth 57 → 61 is re-anchored, the four new occurrences being the forwarder and the interface member (two each), with no call site added.
  • docs/qa/platform-checklist/areas/records-forms.json: the clone item's source anchor now names data-action-routes.ts#registerDataActionEndpoints, where the clone handler is.
  • The ADR anchor packages__rest__src__rest-server.ts.json pins filterAppForUser (ADR-0045), outside the moved range.

Readers that still name the old home, in prose — noted, not a finding. Two the dev listed: packages/spec/liveness/crud_endpoints.json:59 (evidence anchor rest-server.ts#registerDataActionEndpoints, which still resolves to the registrar; Spec property liveness success) and route_generation.json:27 (a note, not scanned). Two the dev did not list, found here: packages/spec/src/api/protocol.zod.ts:2348–:2349 ("ServerScopedDataRequest in packages/rest/src/rest-server.ts") and packages/core/src/utils/import-runner.ts:138–:139 ("rest-server.ts declares a structurally identical local alias"). Both are docblock prose; the substance of each still holds (rest-server.ts imports the alias and its delete-door literal is still typed against it); no gate or resolvable anchor reads them. Under Prime Directive #10 these belong in the acceptance notes, and two of the four were not noted. See ③.

② Semver level

skip-changeset and Clause-②: no match what the diff publishes, which is nothing: the exports map and src/index.ts are unchanged, no .changeset/*.md is added, routes and both route ledgers are untouched, and the 12 new export keywords are module-internal. Check Changeset is skipped by the label, as designed. No accept set, no public name and no spec key moves.

③ Boundary flags

Every deviation in os-dev-report 6135442258, answered:

  1. The stub kept — the claim's "the class keeps only the call" and its "the published .d.ts does not change" conflict, and the dev measured that shape A changes the declaration (the docblock and private registerDataActionEndpoints; would go). The stub is the only shape that honours the stronger order. Answered.
  2. The narrower range — answerImportTemplate (base :10139–:10253, pinned in the .d.ts, spied on in tests) and resolveSecurityService (:10255–:10281, four other callers) stay and are reached through the host. The diff confirms neither is touched. Answered.
  3. The alias moved — forced by a measured one-line .d.ts change and the card's ban on a second copy. Answered; it leaves the two unlisted prose pointers above and a value-level import cycle between rest-server.ts and data-action-routes.ts that no module-level evaluation reads across (the bundler folds both into one file; Build Core success).
  4. Readers touched outside the claim's literal surface — all are re-points the order asks for; the two cross-lane files were declared after the fact (6135513000 on [PM seat] domain:spec — ⏳ vacant #6017, 6135521429 on [PM seat] domain:devx @ objectstack — 🟢 os-elon-musk · 由 os-elon-musk 账户的项目负责 #6023), which is the runbook's shape and why this review exists. Answered.
  5. One merge of origin/main (965a1bfc72) — the net diff reviewed is against that merge base and is disjoint from the merged commits. Answered.
  6. check:dual-build-cjs-loads NOT MEASURED locally — Build Core hosts the gate (ci.yml) and concluded success on this head. Answered by CI.
  7. The -cmp worktree — removed; no trace in the diff. Answered.

The two open_questions, judged against the seat's rulings in 6135541164:

  1. The packages/spec/liveness evidence strings — the seat ruled A (leave). No defect hides behind it: the anchor names the registrar, the registrar still registers those routes through the moved body, and the liveness gate resolves path#symbol by symbol existence, which holds. ESCALATED AS A NOTE, not a card: the same one-hop prose drift exists at protocol.zod.ts:2348 and import-runner.ts:138, neither in the report. The slice that rehomes the alias (question 2) should re-point all four in its surface; the seat's ledger should carry that line now so it is not rediscovered.
  2. The alias's final home — the seat ruled A (the CRUD slice moves it beside its doors or into one small shared module). No defect hides behind it; the interim home is a type-only concern for rest-server.ts.

Out of scope, confirmed: check:platform-checklist is red with the same 8 findings on base and head, none in records-forms.json; it is out of per-PR CI by decision and reads on #6023.

Implemented-by: claude/issue-23050-s1-data-action-endpoints
Reviewed-by: session_01VoSxBQujKLZKPwK2u5ehQ6

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 13, 2026 01:03
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 13, 2026 01:03
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 13, 2026
Merged via the queue into main with commit bcebd84 Oct 13, 2026
53 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-23050-s1-data-action-endpoints branch October 13, 2026 01:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants