Skip to content

Commit d132140

Browse files
refactor(rest): move registerUiEndpoints into ui-routes.ts (#23050 slice 10) (#23990)
Part of #23050 Clause-②: no Slice 10 of the `rest-server.ts` split, by slice 1's pattern (landed note `6136102437`, steps 1–6), slice 2's getter refinement (`6189011700`) and slices 3–9's shape (slices 8 and 9: PR #23882, PR #23959). The body of `RestServer#registerUiEndpoints` (`GET {basePath}/ui/view/:object/:type`, the identity- and ownership-gated door onto the protocol's `getUiView`) moves to ONE new module, `packages/rest/src/ui-routes.ts`. The route table, the ordered registrations, every handler's source and digest, the published `dist/*.d.ts` and both bundles' export lists are byte-identical. Every reader whose reading included a moved line now reads it where it is, with its sum unchanged. **No token of the body changed.** The body calls no class `static`, so the `static` exception (`6205442867`) is not used. It reads no provider field, so the provider ruling (`6215084478`) does not apply: nothing in the host is a getter. The ruled order of its guards is unchanged, byte for byte and at runtime (below). **One module-level type moved with the body, by pattern step 5.** The body types its request through `TransportScopedMetaRequest`, a type alias declared in `rest-server.ts` and not exported. The claim says to export helpers from where they are declared. For a type alias, that changes the published `.d.ts` (measured below, twice), and slice 1's step 5 already rules for exactly this case: "A type alias moves with the body, because exporting one changes the `.d.ts`." So the alias block (its docblock and declaration, 40 lines) moved verbatim to `ui-routes.ts`, gaining only `export`. `rest-server.ts` imports it back for its 12 other typed uses. This mirrors slice 1's `ServerScopedDataRequest`. The alias's final home is asked as an open question in the report, as slice 1's was. `rest-server.ts` goes from 9,995 to 9,915 lines (base `68ffa5dc1a`). The card stays open for the later slices; #23050 remains open after this PR. ## The pattern, applied **Members the moved body reads**, from an AST walk of the method (TypeScript compiler API with a type checker, symbol-resolved, base `68ffa5dc1a`): `registerUiEndpoints` (JSDoc `:8284`, method `:8313`–`:8374`, body `:8314`–`:8373`, 60 lines). | kind | member | reads | class declaration | |---|---|---|---| | field | `routeManager` | 1, the `register` call (`:8318`) | `:2026`, private | | method | `resolveRequestEnvironmentId` | 1 (`:8329`) | `:2322`, private async | | method | `resolveExecCtx` | 1 (`:8342`) | `:2872`, private async | | method | `enforceAuth` | 1 (`:8343`) | `:2472`, private | | method | `enforceEnvironmentOwnership` | 1 (`:8344`) | `:2597`, private | | method | `resolveProtocol` | 1 (`:8345`) | `:2404`, private async | The body has no bare `this`, no element access on `this`, no `function` keyword and no `RestServer.NAME` access. The seat's text count (the same six members, one read each) is confirmed. **Control walk** (the same walker, same base): the `registerSearchEndpoints` stub (`:8999`) reads `routeManager`, `resolveProtocol`, `resolveExecCtx` and `enforceAuth` once each (slice 9's host literal). `enforceEnvironmentOwnership` itself (`:2597`) reads `extractProjectIdHeader` once, plus `ANONYMOUS_DENY_STATUS` and `ANONYMOUS_DENY_BODY` from `@objectstack/core`. Both match what their source says. **Module-level reads** (the same walk): - `handleRouteError` (`./error-response.js`): still used across `rest-server.ts`, so its import stays; the module imports the same name from the same module. - `GetUiViewRequest` (type, `@objectstack/spec/api`): read ONLY by this body (2 hits in `rest-server.ts` at base: the import and this use). Its import name moved with the body, leaving a two-line pointer comment in the import block, as slice 6 did for `ErrorCode`. - `TransportScopedMetaRequest` (declared `:299`): see above and the measurement below. **Host shape (slice 1's shape B):** `UiRouteHost` (6 members) lists exactly the members the body reads, each typed `RestServer['m']`, in the body's read order. The class keeps the private method with its name, docblock and position. Its body is now a host literal plus `registerUiEndpoints.call(host, basePath)`. Methods go as late-bound closures, `(...a) => this.m(...a)`. The call site in `registerRoutes`' `registerForBase` (`this.registerUiEndpoints(bp);`, base `:4805`, head `:4772`, under `api.enableUi`) is unchanged: `registerRoutes` (75 lines) diffs to 0 between base and head. **Fields, checked one by one:** | field | read when | reassigned after registration? | handed over as | |---|---|---|---| | `routeManager` | registration only (one `register` call, a top-level statement of the body) | its one swap wraps `registerMetadataEndpointsInner`, not this registrar | value | No other field is read. The environment, the caller, the ownership verdict and the protocol are each resolved per request by a method, so no getter is owed. **Late binding and the guard order, measured** (a one-off vitest file, copied in, run under the verify lock, deleted; not committed). A source `RestServer` gets five instrumented members planted on the instance BEFORE `registerRoutes()`, and the door is driven. Then five new ones are planted AFTER registration, and the door is driven again. Each member records its call. - **Head:** `MEASURE before: 200 {"answeredBy":"early",…} | order early:env,early:ctx,early:auth,early:own,early:proto` and `MEASURE after: 200 {"answeredBy":"late","env":"env_late",…} | order late:env,late:ctx,late:auth,late:own,late:proto | early calls after re-plant 5`. Result: `Tests 1 passed (1)`. The members reached are the late ones, in the ruled order (resolve the environment once, resolve identity in it, refuse anonymity, compare ownership, then resolve the protocol). - **Ablation** through `scripts/ablation-replace.mjs`: the host literal's `enforceEnvironmentOwnership: (...a) => this.enforceEnvironmentOwnership(...a),` was replaced by `enforceEnvironmentOwnership: this.enforceEnvironmentOwnership.bind(this),`. Anchor 1 → 0; blob `b429a90b19d3` → `97583add2404`. - Result: `order late:env,late:ctx,late:auth,late:proto`, with the early guard called a sixth time, and `Tests 1 failed (1)`. - Restored by the tool: blob `b429a90b19d3` equals `HEAD`, and `git diff HEAD` is empty. - **Base leg:** the base `rest-server.ts` (blob `1c7f7e45a921`) was restored into the tree under a trap, then `git checkout HEAD` with blob and diff proof. The answers are the same as the head's, and `Tests 1 passed (1)`. ## Proof of move-only - **Moved body:** base `rest-server.ts` lines 8314–8373, with one 4-space indent removed, against `ui-routes.ts` lines 118–177. `diff` exits 0 over the 60 lines. - Every non-empty line of the range carries the method indent. The range has one empty line and no trailing whitespace. - Every line with a backtick holds an even number of them, so no template literal spans the cut. - **Moved alias block:** base `rest-server.ts` lines 260–299 against `ui-routes.ts` lines 54–93. `diff` exits 0 once the `export ` keyword on the last line is set aside. A `//` note above it says why it moved, and that the block's "here" and "this file" still mean `rest-server.ts`, where the doors it describes live. - **The private method's docblock and signature** (base `:8284`–`:8313`, head `:8251`–`:8280`) diff to 0. - **`.d.ts`:** `packages/rest/dist/index.d.ts` and `index.d.cts` were built at base `68ffa5dc1a` (before any edit), at the merge head `5da02e4b9`, and again after the whole-workspace build. - All are byte-identical: sha256 `7489de84bc6feccb7cb1f28bcbe4a97775324959c61c9bfc35e8a8c45a6f3bc9`. - The head bundles carry a head-only marker (`registerUiEndpoints.call`: 1 hit in each of `index.js` and `index.cjs`, 0 at base), so the declaration was rebuilt from the moved source, not replayed. - `src/index.ts` is untouched. - **Why the alias could not be exported in place** (both measured, each through `ablation-replace.mjs`, restored by blob and diff proof, and rebuilt back to `7489de84…`): - `export type TransportScopedMetaRequest…` at its declaration gives `dist/index.d.ts` sha256 `a82fe845e80c…`: line 1625, a blank line, disappears. - a separate `export type { TransportScopedMetaRequest };` statement gives the same `a82fe845e80c…` and the same one-line change. - Slice 1 measured this for `ServerScopedDataRequest`. Moving the alias, by contrast, leaves the `.d.ts` byte-identical (above). - The first attempt at the first counterfactual was a no-op: its anchor was a substring of its replacement, the tool refused it, and nothing ran. It was re-anchored and re-run. - **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). No UI-module name is among them. - **Routes:** a source `RestServer` runs on a recording mock server, with a protocol that answers every member. It is dumped under slice 9's eight configs plus a ninth, `enableUi: false` under scoped-optional, the door's own switch. The eight are: default; scoped-optional; scoped-required; every provider set; `enableDiscovery: false`; `enableOpenApi: false`; both off under scoped-optional; `enableSearch: false` under scoped-optional. - Each dump holds four files: `getRoutes()` (functions as arity); the ordered server registration calls; a whitespace-free sha256 of every handler's source; and that source. - **36 of 36 dump files are byte-identical**, base `68ffa5dc1a` vs merge head `5da02e4b9` (`diff -r` exits 0). A second base run was identical to the first. - Route counts: default 75 (1 UI door); scoped-optional 150 (2); scoped-required 75 (1); every-provider-set equal to default; discovery-off 73 (1); openapi-off 73 (1); both-off-scoped-optional 142 (2); search-off-scoped-optional 148 (2); ui-off-scoped-optional 148 (0). - The default config's routes and calls digests (`6931d1a5d182…` / `ccb3bd12f10b…`) are the ones slices 5–9 reported for it. - Because no token changed, every handler digest is identical with no mapping. - **The handler digest can fail:** a positive control added one character to the moved handler (`'…protocol implementation'` → `'…protocol implementation!'`, through `ablation-replace.mjs`; anchor 1 → 0, blob `2eab547e5bba` → `1d0809727b0b`). - Routes and calls were unchanged in all nine configs. - The handler digests differed in exactly the UI door of each mounted base: `GET /api/v1/ui/view/:object/:type`, and `GET /api/v1/environments/:environmentId/ui/view/:object/:type` where scoped. Nothing else differed, and nothing differed under `enableUi: false`. - Restored: blob `2eab547e5bba` equals `HEAD`. ## Readers re-pointed (sums unchanged, no gate loosened) `packages/rest/src/rest-server-registrar-files.ts` gains `ui-routes.ts` with a `[#23050 slice 10]` comment (length 10 → 11). Every reader that pins its length re-reads its number: | reader | sum over the list | change | |---|---|---| | `rest-server-query-number-census.test.ts` | 9 = 8 + 1 + 0 ×9 | the UI body reads two path parameters as strings and coerces nothing; length 10 → 11 | | `rest-server-canonical-query-ast.test.ts` (`repo` project) | 4 = 1 + 3 + 0 ×9 `query:` slots | floor `ui-routes.ts: 0`; length 10 → 11 | | `meta-draft-read-door-census.test.ts` | 8 = 6 + 0 + 0 + 0 + 0 + 2 + 0 ×5 | the UI body reads no draft; length 10 → 11 | | `rest-server-docblock-position.test.ts` | floors 4 + 3 + 3 + 8 + 3 + 8 + 5 + 3 + 3 + 4 = 44 | floor `ui-routes.ts: 4` (the module header, the moved alias's block, the host interface's and the function's). `rest-server.ts` gives up only the alias's block: 164 → 163, floor 100. Length 10 → 11 | | `execctx-consumer-census.test.ts` | 62 sites = 22 + 9 + 5 + 7 + 8 + 1 + 5 + 2 + 1 + 1 + 1 | the UI site is BARE and guarded on its next line, as before (46 bare / 16 caught unchanged). Mentions 121 → 125, the seam once (forwarder ×2 in `rest-server.ts`, interface member ×2 in the module); `rest-server.ts` 59 → 60. Forwarders 12 = (11 − 1) + 3 − 1; neither map gains an entry | | `ui-view-route-identity.measurement.test.ts` | `this.enforceAuth(` 59, `this.resolveExecCtx(` 74 over the list (58 / 73 before, +1 each for the UI literal's forwarders; floors 40 hold) | the sliced reading is re-pointed (below); length 10 → 11 | | `ui-view-route-tenancy.measurement.test.ts` | as above; the ownership guard's call sites: 1 | the sliced reading and the ownership pin are re-pointed (below); length 10 → 11 | | dogfood `authz-conformance.test.ts` (realtime tripwires) | 0 hits over the eleven files | length 10 → 11 | | dogfood `authz-probe-blind-spot.census.ts`, `rest-server.ts` row | 70 / 19 / 51 unchanged; `this.routeManager.register(` 63 = 20 + 9 + 5 + 7 + 8 + 1 + 6 + 4 + 1 + 1 + 1; `private register*Endpoints(` 16 | new control `ui-routes.ts: this.routeManager.register(` 1; `registrar list length` 11; `enforceAuth` 104 → 108 re-anchored (the one call site moved and is counted in both readings; the four new occurrences are the seam once; no call site added); `populationRule` names the new module | **The two ui-view measurement tests read this body directly, so they are re-pointed with care.** - **The sliced reading.** Both tests sliced `rest-server.ts` from `private registerUiEndpoints(` to `private registerCrudEndpoints(`. That span is now the host literal, whose forwarders spell `this.enforceAuth(`, `this.resolveExecCtx(`, `this.enforceEnvironmentOwnership(` and `this.resolveProtocol(` in the very order the tenancy test asserts, while calling none of them in a route. A slice there would pass the order assertions vacuously. - Both tests now slice `ui-routes.ts`, from `export function registerUiEndpoints(` to the function's own closing brace (the first column-0 brace after it), so neither the module header nor a later declaration can satisfy it. - Every `toContain` and every order assertion is unchanged. That the private method still mounts this one route is held, as before, by the identity test's §6 runtime attribution (`byRegistrar.get('registerUiEndpoints')` equals the one UI route). - **The ownership pin.** `this.enforceEnvironmentOwnership(` over the list was pinned at exactly 1, "a single new call site plus its definition". Over the eleven files the spelling now reads 2: the one call site, which moved to `ui-routes.ts`, plus this slice's host-literal forwarder. - The pin now asserts both halves exactly: forwarders (`(...a) => this.enforceEnvironmentOwnership(...a)`) `toBe(1)`, and call sites (spellings minus forwarders) `toBe(1)`. - The call-site count is unchanged at 1, and a second forwarder reds as loudly as a second call site. Nothing is loosened. Readers outside the list: - `scripts/check-route-envelope.mjs` (a gate's target paths, `domain:spec`): `ui-routes.ts` is declared `dialectOnly` with ratchet #9559, as the nine earlier modules are, at its measured `stringError 1` / `siblingCode 1`. Its one hand-built refusal is the 501 `{ error: 'UI View resolution not supported by protocol implementation', code: 'NOT_IMPLEMENTED' }`. - `rest-server.ts` drops by exactly that body: `stringError` 16 → 15 and `siblingCode` 14 → 13, each recorded as "NOT progress — the same MOVE". - The sums are unchanged: 15 + 13 + 8 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 1 = 39, and 13 + 15 + 8 + 9 + 4 + 1 + 0 + 0 + 3 + 0 + 1 = 54. - Its output differs from `origin/main` only in the `rest-server.ts` row and the new row (20 → 21 modules, 11 → 12 ratcheted). `--self-test` is green. - No new ADR anchor: neither moved range cites an `ADR-NNNN Dk`. `check-adr-anchors` output is identical at `origin/main` and head (1770 decision-letter citations). - No platform-checklist anchor names the UI registrar or the alias; `check:platform-checklist` output is identical at `origin/main` and head. **Red before the re-points**, each measured with the move in place (`9c34e1a99`, the readers at their base text): - `@objectstack/rest` `local` read `Test Files 6 failed | 281 passed (287)` and `Tests 7 failed | 5895 passed | 326 skipped (6228)`. The failures were the four length pins (execctx, meta-draft, query-number, docblock), the docblock pin's unfloored new module, and both ui-view sliced readings: `expected 'private registerUiEndpoints(basePath:…' to contain 'p.getUiView('`, and the same for `const isScoped = …`. - `rest-server-canonical-query-ast.test.ts` (`repo`) read `Tests 2 failed | 248 passed | 11 skipped (261)`. - The dogfood pair read `Tests 2 failed | 106 passed (108)`: the realtime tripwire's length pin, and `control "registrar list length" moved … expected 11 to be 10`. - `check:route-envelope` read `ui-routes.ts NOT DECLARED`, plus `rest-server.ts` `stringError: found 15, declared 16` and `siblingCode: found 13, declared 14`. - Each is green after the re-point. The tenancy test stopped at its first failing `toContain`, so its exact ownership pin was not observed red in that run. It was counted directly instead: 1 spelling at base, 2 at the move commit (1 call site plus 1 forwarder). ## Readers examined and not re-pointed - **`packages/spec` (stopped at, per the dispatch, not edited):** no reader there includes a moved line. `packages/spec/liveness/rest_api.json` cites `rest-server.ts#registerRoutes` for the `if (this.config.api.enableUi)` gate, the call site that did not move. Its second citation is `registerDiscoveryEndpoints`, slice 7's. `packages/spec` names `TransportScopedMetaRequest` in comments as living in `packages/rest` (e.g. `protocol.zod.ts`), never by file, and it still does. `check:liveness` output is identical at `origin/main` and head. - **Prose naming the method without a file**, still true: the dogfood census note and comments, `rest-route-ledger.ts`, `packages/runtime/src/route-ledger.ts` and `execctx-consumer-census.test.ts` (dated history). The same holds for `data-action-routes.ts`' `{@link TransportScopedMetaRequest}`, which resolves by name exactly as it did before. - `docs/audits/2026-06-handwritten-docs-accuracy-followups.md` cites `rest-server.ts:2603-2612`, a dated audit line outside both moved ranges (`:260`–`:299`, `:8284`–`:8374`). Released `CHANGELOG.md` text: not edited. - **Gates that walk `packages/rest/src` reach the moved code in its new file.** Their outputs at `origin/main` and head differ only by the one scanned file: - `check-meta-type-normalized` 38 → 39; - `check-dispatcher-error-vocabulary` and `check-error-status-conformance` +1 file (57 classified sites unchanged); - `check-undeclared-dep-imports` +1 file, +1 specifier; - `check-affected-docs`: route sources 28 → 29 and registration call sites 23 → 24, by the `*-routes.ts` convention; - `check-scripts-symbol-anchors`: +4 file-level anchors, the gate's new comments. - **Identical at `origin/main` and head:** `check-adr-anchors`, `check-system-context-census`, `check-single-authz-resolver`, `check-rest-log-declared`, `check-durability-degradation-log-level`, `check-filter-alias-parity` and `check:platform-checklist`. - No `rest-server.ts:NNNN` line citation in a gated document falls in a moved range. ## Acceptance notes - `objectql-slot-consumer-census.test.ts` (slice 8's extra reader) is not touched: the UI body reads no provider slot, and it stays green. - The moved `[commit cc837db]` comments in the body still read true in the module: they describe the handler they sit in, and name `execctx-consumer-census.test.ts` by file, which still counts this site. - The moved alias block's prose ("the doors here", "call sites in this file") describes `rest-server.ts`, where those doors live; the note above it in `ui-routes.ts` says so rather than editing the moved text. ## Gates and tests Measured at this branch: base `68ffa5dc1a`; the move `9c34e1a99`; the re-points `c9b5a6440`; and one merge of `origin/main` `0b6956490` (`5da02e4b9`: #23881 plugin-audit, cli and two checklist areas; #23937 spec tests; #23966 create-objectstack; no file in this diff). All builds and tests went through `os-verify-lock`. Every exit code was captured to a file before any pipe. **Final head: `5da02e4b9`.** - `pnpm --workspace-concurrency=2 --filter '@objectstack/rest...' --filter '@objectstack/verify...' build` exited 0 at the merge head. Then `pnpm turbo run build --concurrency=2`, the whole workspace, exited 0 (`Tasks: 76 successful, 76 total`), after which the `@objectstack/rest` `.d.ts` / `.d.cts` still equal base. - `pnpm --filter @objectstack/rest typecheck`: exit 0 (`tsc --noEmit`, then `check:test-typecheck`: 0 file(s) / 0 error(s)). - `pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2`: Test Files 287 passed (287), Tests 5902 passed / 326 skipped (6228). - `pnpm --filter @objectstack/rest exec vitest run --project repo --maxWorkers=2`: Test Files 5 passed (5), Tests 250 passed / 11 skipped (261). - `pnpm --filter @objectstack/dogfood exec vitest run test/authz-probe-blind-spot.test.ts test/authz-conformance.test.ts`: Test Files 2 passed (2), Tests 108 passed (108). - Lint: `pnpm exec eslint --no-inline-config --format json` over the 13 changed files: exit 0, 13 results, 0 errors, 0 warnings. The published `.d.ts` is byte-identical, so no consumer outside `packages/rest` can lint differently. Repo-wide `pnpm lint` is CI's (`Lint & Repo Gates`). - `check:dev-prereqs` and `check:skill-frame-freshness` (local-only) were not run: this diff touches no `packages/spec` build input and no four-axis frame file. - No changeset: nothing any released package publishes changes (the `.d.ts`, export lists and routes above). `skip-changeset` is the seat's to apply on its own check. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, with no paths, at `5da02e4b9`. Its stderr names that commit, the `--repo` assertion holds, and it reads 13 paths against merge base `0b6956490`, 477 changed lines. - It derived 79 commands, all run at `5da02e4b9` after the whole-workspace build, and every one exited 0. `--ran` reads: `Run reconciliation — 79 derived, 79 run, 0 NOT-MEASURED, 0 UNRUN.` - Among them: `pnpm check:route-envelope`, `pnpm --filter @objectstack/spec run check:liveness`, `pnpm check:durability-log-level`, `pnpm check:dispatcher-error-vocabulary`, `pnpm check:dts-closure`, `pnpm check:dual-build-cjs-loads`, `pnpm check:published-files`, `pnpm check:cross-package-test-inputs`, `pnpm check:nul-bytes` and `pnpm check:pm-dispatch-gates` (the battery took 299.7s on a shared box). - `check-adr-anchors` and `check-platform-checklist` are not derived; both also ran, exited 0, and printed output identical to `origin/main`. - `origin/main` moved on after the one merge, to `2e1562091` (five commits: #23970, #23936, #23976, #23973, #23979). None touches a file in this diff, `packages/rest`, the gate or the dogfood authz files. - The reconciliation's STALE TREE note names three derivation inputs main changed: `scripts/check-cli-test-child-env.mjs`, `scripts/ci/spec-shape-retirements.json` and `scripts/sdui-manifest.record.json`. All belong to families this diff does not touch. - CI runs the merge ref. Not merged again. **Per command (exit code), 79 lines:** ```text 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/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 0 pnpm check:durability-log-level :: exit 0 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:pm-dispatch-gates :: exit 0 pnpm check:pnpm-filter-targets :: exit 0 pnpm check:pnpm-flag-spellings :: 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 ``` **Cross-lane files in this diff:** `scripts/check-route-envelope.mjs` (`domain:spec`, a gate's target paths: one new `MODULES` entry and the `rest-server.ts` row's two counts, each with a history comment; sums unchanged). The dogfood files (`packages/qa/dogfood/test/authz-conformance.test.ts`, `authz-probe-blind-spot.census.ts`) are re-pointed as in slices 3–9. No checklist file and no ADR anchor is touched. `check-governed-merges --test` over the 13 paths: NOT governed (0 of 13 paths hit the register); 477 changed lines. --- _Generated by [Claude Code](https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5fb8d15 commit d132140

13 files changed

Lines changed: 347 additions & 130 deletions

‎packages/qa/dogfood/test/authz-conformance.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1631,7 +1631,8 @@ describe('#9084 — the rest-server.ts transport tripwire keys off mechanics', (
16311631
// [#23050 slice 7] Length 7 → 8 (`discovery-routes.ts`), still 0 over it.
16321632
// [#23050 slice 8] Length 8 → 9 (`email-routes.ts`), still 0 over it.
16331633
// [#23050 slice 9] Length 9 → 10 (`search-routes.ts`), still 0 over it.
1634-
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(10);
1634+
// [#23050 slice 10] Length 10 → 11 (`ui-routes.ts`), still 0 over it.
1635+
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(11);
16351636
expect(REST_SERVER_REGISTRAR_PATHS[0]).toBe(REST_SERVER);
16361637
const listed = REST_SERVER_REGISTRAR_PATHS.map((file) => readFileSync(join(REPO_ROOT, file), 'utf8'));
16371638
for (const probe of [MECHANICS, ROUTES]) {

‎packages/qa/dogfood/test/authz-probe-blind-spot.census.ts‎

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -520,12 +520,12 @@ export const PROBE_FILE_CENSUS: readonly ProbeFileReading[] = [
520520
'terminator spelling), PLUS `registerPerItemRoute(` call sites; counted over this file AND every ' +
521521
'module on the registrar list (`packages/rest/src/rest-server-registrar-files.ts`) — ' +
522522
'`data-action-routes.ts`, `batch-routes.ts`, `approvals-routes.ts`, `sharing-routes.ts`, ' +
523-
'`analytics-routes.ts`, `security-routes.ts`, `discovery-routes.ts`, `email-routes.ts` and ' +
524-
'`search-routes.ts`, the modules the bodies of `registerDataActionEndpoints`, `registerBatchEndpoints`, ' +
525-
'`registerApprovalsEndpoints`, `registerSharingEndpoints`, `registerSharingRuleEndpoints`, ' +
526-
'`registerAnalyticsEndpoints`, `registerSecurityExplainEndpoints`, `registerSecurityEndpoints`, ' +
527-
'`registerDiscoveryEndpoints`, `registerOpenApiEndpoints`, `registerEmailEndpoints` and ' +
528-
'`registerSearchEndpoints` moved to; ' +
523+
'`analytics-routes.ts`, `security-routes.ts`, `discovery-routes.ts`, `email-routes.ts`, ' +
524+
'`search-routes.ts` and `ui-routes.ts`, the modules the bodies of `registerDataActionEndpoints`, ' +
525+
'`registerBatchEndpoints`, `registerApprovalsEndpoints`, `registerSharingEndpoints`, ' +
526+
'`registerSharingRuleEndpoints`, `registerAnalyticsEndpoints`, `registerSecurityExplainEndpoints`, ' +
527+
'`registerSecurityEndpoints`, `registerDiscoveryEndpoints`, `registerOpenApiEndpoints`, ' +
528+
'`registerEmailEndpoints`, `registerSearchEndpoints` and `registerUiEndpoints` moved to; ' +
529529
'reachable = those inside registerMetadataEndpoints',
530530
// [#15542 / #15854] ⭐ THE POPULATION RULE LEARNED A SECOND SPELLING, and
531531
// the numbers it produces did NOT move: 80 / 19 / 61, exactly as before.
@@ -758,10 +758,22 @@ export const PROBE_FILE_CENSUS: readonly ProbeFileReading[] = [
758758
// new occurrences are this slice's seam, the same two spellings once (the
759759
// host literal's forwarder, two; the host interface's member, two). No call
760760
// site was added.
761+
// [#23050 slice 10] 70 / 19 / 51 UNCHANGED, the same measurement again:
762+
// `registerUiEndpoints`' body (one registration site, `GET
763+
// {base}/ui/view/:object/:type`, outside `registerMetadataEndpoints`, so in
764+
// the blind spot) moved to `ui-routes.ts`, which joins the list (length
765+
// 10 -> 11) with its own control. `this.routeManager.register(` stays 63
766+
// over the eleven files (20 here + 9 + 5 + 7 + 8 + 1 + 6 + 4 + 1 + 1 + 1),
767+
// `private register*Endpoints(` stays 16 (the registrar is still declared
768+
// here), and `enforceAuth` 104 -> 108 is RE-ANCHORED, not relaxed: the one
769+
// call site moved with its route and is counted in both readings; the four
770+
// new occurrences are this slice's seam, the same two spellings once (the
771+
// host literal's forwarder, two; the host interface's member, two). No call
772+
// site was added.
761773
controls: {
762774
'private register*Endpoints(': 16,
763775
'this.routeManager.register(': 63,
764-
'registrar list length': 10,
776+
'registrar list length': 11,
765777
'data-action-routes.ts: this.routeManager.register(': 9,
766778
'batch-routes.ts: this.routeManager.register(': 5,
767779
'approvals-routes.ts: this.routeManager.register(': 7,
@@ -771,14 +783,15 @@ export const PROBE_FILE_CENSUS: readonly ProbeFileReading[] = [
771783
'discovery-routes.ts: this.routeManager.register(': 4,
772784
'email-routes.ts: this.routeManager.register(': 1,
773785
'search-routes.ts: this.routeManager.register(': 1,
786+
'ui-routes.ts: this.routeManager.register(': 1,
774787
// Both halves of the new rule carry their own control, so neither can go
775788
// silently to zero: a helper deleted and its routes inlined back would
776789
// still read population 80, and only these two controls would notice the
777790
// shape moved and force this provenance to be re-read.
778791
'registerPerItemRoute(': 8,
779792
'const registerPerItemRoute =': 1,
780793
'forwarder slice: this.routeManager.register(': 1,
781-
enforceAuth: 104,
794+
enforceAuth: 108,
782795
},
783796
note:
784797
'The single non-tripwire probe names ONE registrar of 16. The other 15 can never mint a key: ' +

‎packages/rest/src/execctx-consumer-census.test.ts‎

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ const SOURCE = readFileSync(resolve(HERE, 'rest-server.ts'), 'utf8');
109109
*
110110
* [#23050 slice 9] …and `registerSearchEndpoints`' body to `search-routes.ts`,
111111
* with its one.
112+
*
113+
* [#23050 slice 10] …and `registerUiEndpoints`' body to `ui-routes.ts`, with
114+
* its one.
112115
*/
113116
const SITE_FILES = REST_SERVER_REGISTRAR_FILES;
114117
const SOURCES = new Map<string, string>(SITE_FILES.map((file) => [
@@ -123,8 +126,9 @@ const siteKey = (file: string, line: number) => `${file}:${line}`;
123126
* `registerSharingRuleEndpoints`, `registerAnalyticsEndpoints`,
124127
* `registerSecurityExplainEndpoints`, `registerSecurityEndpoints`,
125128
* `registerOpenApiEndpoints`, `registerEmailEndpoints`,
126-
* `registerSearchEndpoints`) hands its moved body forwards each member to the
127-
* server with `(...a) => this.<member>(...a)`, so a moved site's stack shows
129+
* `registerSearchEndpoints`, `registerUiEndpoints`) hands its moved body
130+
* forwards each member to the server with `(...a) => this.<member>(...a)`, so
131+
* a moved site's stack shows
128132
* that forwarder above the site itself. The site is the first censused frame
129133
* that is not one. Found by their spelling, and §2 pins ONE per moved
130134
* REGISTRAR whose body reads the seam — one per moved module, plus
@@ -680,9 +684,20 @@ describe('[#13160] §2 the consumer surface, counted from the tree', () => {
680684
// mentions in). Eleven forwarders now: (10 - 1) + 3 - 1, the module
681685
// carrying one registrar that reads the seam, so neither map gains an
682686
// entry.
683-
expect(SITE_FILES).toHaveLength(10);
687+
//
688+
// [#23050 slice 10] 62 sites UNCHANGED / 121 → 125 mentions — the same MOVE.
689+
// `registerUiEndpoints`' body left for `ui-routes.ts` with its one BARE
690+
// site (guarded on its next line, as before):
691+
// 22 + 9 + 5 + 7 + 8 + 1 + 5 + 2 + 1 + 1 + 1 = 62 sites. The four new
692+
// mentions are this slice's seam, the two spellings once (its host
693+
// literal's forwarder, two; its host interface's member, two):
694+
// `rest-server.ts` goes 59 → 60 (one site out, one forwarder's two
695+
// mentions in). Twelve forwarders now: (11 - 1) + 3 - 1, the module
696+
// carrying one registrar that reads the seam, so neither map gains an
697+
// entry.
698+
expect(SITE_FILES).toHaveLength(11);
684699
expect(SITES.length).toBe(62);
685-
expect([...SOURCES.values()].reduce((n, text) => n + text.split('resolveExecCtx').length - 1, 0)).toBe(121);
700+
expect([...SOURCES.values()].reduce((n, text) => n + text.split('resolveExecCtx').length - 1, 0)).toBe(125);
686701
const extraMovedRegistrars = Object.values(EXTRA_MOVED_REGISTRARS).reduce((n, k) => n + (k ?? 0), 0);
687702
expect(extraMovedRegistrars).toBe(3);
688703
const registrarsReadingNoExecCtx = Object.values(MOVED_REGISTRARS_READING_NO_EXECCTX).reduce((n, k) => n + (k ?? 0), 0);

‎packages/rest/src/meta-draft-read-door-census.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,9 @@ describe('[#20338] census: every draft switch in rest-server.ts is ledgered', ()
254254
// 8 = 6 + 0 + 0 + 0 + 0 + 2 + 0 + 0 + 0, length 8 → 9. [#23050 slice 9]
255255
// Nor does the global search body (`search-routes.ts`):
256256
// 8 = 6 + 0 + 0 + 0 + 0 + 2 + 0 + 0 + 0 + 0, length 9 → 10.
257-
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(10);
257+
// [#23050 slice 10] Nor does the UI view body (`ui-routes.ts`):
258+
// 8 = 6 + 0 + 0 + 0 + 0 + 2 + 0 + 0 + 0 + 0 + 0, length 10 → 11.
259+
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(11);
258260
expect(SOURCE_FILES.map((file) => file.fileName)).toEqual([...REST_SERVER_REGISTRAR_FILES]);
259261
expect(SITES).toHaveLength(8);
260262
});

‎packages/rest/src/rest-server-canonical-query-ast.test.ts‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ interface CensusEntry {
154154
* body (`search-routes.ts`) builds no `query:` literal either — it hands
155155
* `searchAll` a `q` / `objects` / `limit` / `perObject` request read off
156156
* `req.query`, never a `query:` slot.
157+
*
158+
* [#23050 slice 10] 4 = 1 + 3 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0: the UI view
159+
* body (`ui-routes.ts`) builds no `query:` literal either — it hands
160+
* `getUiView` an `{ object, type }` request read off the path, never a
161+
* `query:` slot.
157162
*/
158163
const REGISTRAR_QUERY_SLOT_FLOORS: Readonly<Record<RestServerRegistrarFile, number>> = {
159164
'rest-server.ts': 1,
@@ -166,6 +171,7 @@ const REGISTRAR_QUERY_SLOT_FLOORS: Readonly<Record<RestServerRegistrarFile, numb
166171
'discovery-routes.ts': 0,
167172
'email-routes.ts': 0,
168173
'search-routes.ts': 0,
174+
'ui-routes.ts': 0,
169175
};
170176

171177
const CENSUS: CensusEntry[] = [
@@ -359,7 +365,8 @@ describe('[#16638] §1 CONTROLS on the census instrument itself', () => {
359365
// [#23050 slice 7] Length 7 → 8, the sum unchanged.
360366
// [#23050 slice 8] Length 8 → 9, the sum unchanged.
361367
// [#23050 slice 9] Length 9 → 10, the sum unchanged.
362-
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(10);
368+
// [#23050 slice 10] Length 10 → 11, the sum unchanged.
369+
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(11);
363370
expect(Object.keys(REGISTRAR_QUERY_SLOT_FLOORS).sort()).toEqual([...REST_SERVER_REGISTRAR_FILES].sort());
364371
expect(CENSUS.filter(({ file }) => REGISTRAR_SOURCES.has(file)).map(({ file }) => file))
365372
.toEqual([...REST_SERVER_REGISTRAR_FILES]);

‎packages/rest/src/rest-server-docblock-position.test.ts‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ describe('rest-server.ts docblock position', () => {
175175
* the module header, the host interface's block and the function's. The global
176176
* search body held no TSDoc block of its own, so `rest-server.ts` keeps every
177177
* block it had (164).
178+
*
179+
* [#23050 slice 10] `ui-routes.ts` carries four, as `data-action-routes.ts`
180+
* does: the module header, the moved alias's block (`TransportScopedMetaRequest`
181+
* went with the UI view body, as `ServerScopedDataRequest` went with slice 1's),
182+
* the host interface's block and the function's. The UI view body held no TSDoc
183+
* block of its own, so `rest-server.ts` gives up only the alias's block
184+
* (164 → 163 there, still far above its floor).
178185
*/
179186
const MOVED_FILE_FLOORS: Readonly<Record<Exclude<RestServerRegistrarFile, typeof FILE>, number>> = {
180187
'data-action-routes.ts': 4,
@@ -186,6 +193,7 @@ const MOVED_FILE_FLOORS: Readonly<Record<Exclude<RestServerRegistrarFile, typeof
186193
'discovery-routes.ts': 5,
187194
'email-routes.ts': 3,
188195
'search-routes.ts': 3,
196+
'ui-routes.ts': 4,
189197
};
190198
const MOVED_FILES: ReadonlyArray<{ file: string; minBlocks: number }> = REST_SERVER_REGISTRAR_FILES
191199
.filter((file): file is Exclude<RestServerRegistrarFile, typeof FILE> => file !== FILE)
@@ -219,9 +227,10 @@ describe('[#23050 slice 3] the registrar list this pin reads', () => {
219227
// [#23050 slice 7] 4 + 3 + 3 + 8 + 3 + 8 + 5 = 34 over seven, length 7 → 8.
220228
// [#23050 slice 8] 4 + 3 + 3 + 8 + 3 + 8 + 5 + 3 = 37 over eight, length 8 → 9.
221229
// [#23050 slice 9] 4 + 3 + 3 + 8 + 3 + 8 + 5 + 3 + 3 = 40 over nine, length 9 → 10.
222-
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(10);
230+
// [#23050 slice 10] 4 + 3 + 3 + 8 + 3 + 8 + 5 + 3 + 3 + 4 = 44 over ten, length 10 → 11.
231+
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(11);
223232
expect(Object.keys(MOVED_FILE_FLOORS).sort())
224233
.toEqual(REST_SERVER_REGISTRAR_FILES.filter((file) => file !== FILE).sort());
225-
expect(MOVED_FILES.reduce((n, { minBlocks }) => n + minBlocks, 0)).toBe(40);
234+
expect(MOVED_FILES.reduce((n, { minBlocks }) => n + minBlocks, 0)).toBe(44);
226235
});
227236
});

‎packages/rest/src/rest-server-query-number-census.test.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,10 @@ describe('#20139 census — every numeric coercion in rest-server.ts is classifi
330330
// Nor does the global search body (`search-routes.ts`): it READS its two
331331
// caps through `readDeclaredQueryNumber`, never coerces one:
332332
// 9 = 8 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0, length 9 → 10.
333-
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(10);
333+
// [#23050 slice 10] Nor does the UI view body (`ui-routes.ts`): it reads
334+
// only the two path parameters, as strings:
335+
// 9 = 8 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0, length 10 → 11.
336+
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(11);
334337
expect(SOURCES.map(([file]) => file)).toEqual([...REST_SERVER_REGISTRAR_FILES]);
335338
expect(sites).toHaveLength(9);
336339
});

‎packages/rest/src/rest-server-registrar-files.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export const REST_SERVER_REGISTRAR_FILES = [
4747
'email-routes.ts',
4848
// [#23050 slice 9] `registerSearchEndpoints`' body.
4949
'search-routes.ts',
50+
// [#23050 slice 10] `registerUiEndpoints`' body.
51+
'ui-routes.ts',
5052
] as const;
5153

5254
/** One file on {@link REST_SERVER_REGISTRAR_FILES}. */

0 commit comments

Comments
 (0)