Repository navigation
Commit a515abd
Part of #23050
Clause-②: no
Slice 5 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–4's shape (PR #23670, PR #23705): the body of
`RestServer#registerAnalyticsEndpoints` (one route, `POST
{basePath}/analytics/dataset/query`, the semantic-layer dataset
preview/query door) moves, unedited, to ONE new module,
`packages/rest/src/analytics-routes.ts`. It is move-only: routes, the
ordered registrations, every handler and the published `dist/*.d.ts` are
byte-identical, and every reader whose reading included a moved line now
reads it where it is, with its sum unchanged.
`rest-server.ts` goes from 11,495 to 11,042 lines (base `8c85141c5`).
The card stays open for the later slices.
## The pattern, applied
**Members the moved body reads**, from an AST walk of the method
(TypeScript compiler API, base `8c85141c5`: JSDoc `:10249`, method
`:10262`-`:10733`, body `:10263`-`:10732`):
| kind | member (reads) | class declaration |
|---|---|---|
| field | `routeManager` (1, its one `register` call) | `:2010`, private
|
| field | `kernelManager` (2, inside `resolveService`) | `:2019`,
private, not `readonly` |
| field | `analyticsServiceProvider` (2, inside `resolveService`) |
`:2084`, private, not `readonly` |
| method | `resolveRequestEnvironmentId` (1), `resolveExecCtx` (1),
`enforceAuth` (1), `resolveProtocol` (1), `translateMetaItem` (1) |
private |
No bare `this`, no element access on `this`, and no `this`-rebinding
function inside the body. Its module-level reads:
- **Imports still used elsewhere in `rest-server.ts`** (AST count
outside the method): `GetMetaItemsRequest` (type),
`classifiedRefusalAnswer`, `sandboxFaultAnswer`, `logError`,
`refuseRepeatedQueryParams`. They stay there; the module imports the
same names from the same modules.
- **Imports ONLY this body read** (zero uses outside the method):
`INTERNAL_ERROR_MESSAGE`, `declaresServerFault`,
`looksLikeInternalErrorLeak` (`@objectstack/types`),
`stripReadDecorations` (`@objectstack/spec/kernel`),
`sandboxBusinessMessage`, `boundedDeclaredUserMessage`,
`declaredServerFaultAnswer` (`./error-response.js`) and
`datasetSelectionRefusal` (`./analytics-selection-door.js`, with its
`[#17551]` comment). They moved with the body, as slice 3 moved
`strandedDecisionDetails`; each left a pointer comment. Leaving them
would fail `noUnusedLocals`.
- **One helper declared in `rest-server.ts`**: `mayReadPendingDrafts`
(`:1825`, 4 other uses there). It is now `export`ed from where it lives
and imported by the module: one declaration, never a copy.
`src/index.ts` does not re-export it, so the published `.d.ts` is
unchanged (measured below). `meta-draft-read-door-census.test.ts`'s
bare-delegation pin still finds exactly one `FunctionDeclaration` with
the same body.
**Host shape (slice 1's shape B):** the class keeps the private method
with its name, docblock and position. Its body is now one host literal,
`AnalyticsRouteHost` (each member typed `RestServer['m']`), plus
`registerAnalyticsEndpoints.call(host, basePath)`. Methods go as
late-bound closures, `(...a) => this.m(...a)`. The call site in
`registerRoutes` is unchanged.
**Fields, checked one by one:**
| field | read when | reassigned after registration? | handed over as |
|---|---|---|---|
| `routeManager` | registration only (the one `register` call) | its one
swap wraps `registerMetadataEndpointsInner`, not this registrar | value
|
| `kernelManager` | **request time** (`resolveService`) | not by the
class (constructor only), but not `readonly` | **getter** |
| `analyticsServiceProvider` | **request time** (`resolveService`) | not
by the class (constructor only), but not `readonly` | **getter** |
The getters are measured, not argued. A one-off vitest file (copied in,
run, deleted) boots a source `RestServer`, drives the door, assigns the
field on the instance AFTER `registerRoutes()`, and drives it again:
- this head: `analyticsServiceProvider` reads `501 NOT_IMPLEMENTED`,
then `200 {"rows":[{"region":"NA","revenue":1}],"fields":[]}`.
`kernelManager` (scoped route, `environmentId: env_1`) reads `501`, then
`200 {"rows":[{"env":"env_1"}],"fields":[]}`.
- by-value ablation (`scripts/ablation-replace.mjs`, each getter in turn
replaced by `X: this.X,`; anchor 1 → 0, replacement 0 → 1, blob
`d5ddbc9d00` → `26f5c5f250` / `c19048589c`): the ablated door reads
`501` then `501` (frozen at registration) while the other door still
reads `200` (`Tests 1 failed | 1 passed (2)`, each run). Restored by the
tool and a trap: blob `d5ddbc9d00` equals `HEAD`, `git diff HEAD` empty,
the one-off file deleted. The path is source (relative import), so no
`dist` leg applies.
**Mechanism hypotheses from the dispatch, measured:** (a) the body
resolves the analytics service at request time through `resolveService`,
which reads TWO fields: the environment kernel's `analytics` service via
`kernelManager` first, then `analyticsServiceProvider` as the fallback.
Both go as getters. (b) The request `ExecutionContext` is threaded
through `resolveExecCtx`: one more forwarder, as slices 3–4 had. (c)
**Falsified:** the body carries no `ADR-NNNN Dk` citation (`ADR-0021
D-C` sits in the method's JSDoc, which stays, and does not match the
gate's digit pattern). `check-adr-anchors` reads 1761 decision-letter
citations at base, at the move-only commit and at head, so no new anchor
is needed.
## The registrar list
`packages/rest/src/rest-server-registrar-files.ts` gains
`analytics-routes.ts` with a `[#23050 slice 5]` comment (length 5 → 6).
Every reader that pins its length re-reads its number, and every sum is
unchanged:
| reader | sum over the list | change |
|---|---|---|
| `rest-server-query-number-census.test.ts` | 9 = 8 + 1 + 0 + 0 + 0 + 0
| length 5 → 6 |
| `rest-server-canonical-query-ast.test.ts` | 4 = 1 + 3 + 0 + 0 + 0 + 0
`query:` slots | floor `analytics-routes.ts: 0`, length 5 → 6 |
| `meta-draft-read-door-census.test.ts` | 8 = 6 + 0 + 0 + 0 + 0 + 2 |
the door's two switches (`body.previewDrafts`, `req.query?.preview ===
'draft'`) moved; keys name the route, so no ledger row changed; length 5
→ 6 |
| `rest-server-docblock-position.test.ts` | floors 4 + 3 + 3 + 8 + 3 =
21 | floor `analytics-routes.ts: 3` (header, host interface, function);
`rest-server.ts` keeps 167 blocks (the body held none); length 5 → 6 |
| `execctx-consumer-census.test.ts` | 62 sites = 32 + 9 + 5 + 7 + 8 + 1
| mentions 97 → 101 (the seam: forwarder two, interface member two);
forwarders 6 = (6 - 1) + `EXTRA_MOVED_REGISTRARS` 1, formula unchanged |
| `ui-view-route-identity.measurement.test.ts` /
`ui-view-route-tenancy.measurement.test.ts` | `this.enforceAuth(` 53,
`this.resolveExecCtx(` 68 over the list (52 / 67 before, +1 each for the
forwarder); ownership guard 1 | length 5 → 6 |
| dogfood `authz-conformance.test.ts` (realtime tripwires) | 0 hits over
the six files | length 5 → 6 |
| dogfood `authz-probe-blind-spot.census.ts`, `rest-server.ts` row | 70
/ 19 / 51 unchanged; `this.routeManager.register(` 63 = 33 + 9 + 5 + 7 +
8 + 1 | new control `analytics-routes.ts: this.routeManager.register(`
1; `registrar list length` 6; `enforceAuth` 80 → 84 re-anchored (the
seam: forwarder two, interface member two; no call site added) |
Each number above was also counted statically on the tree before the
tests were re-pointed (`grep -oF` per file).
## Proof of move-only
- **`.d.ts`:** `packages/rest/dist/index.d.ts` and `index.d.cts` built
at base `8c85141c5` (this worktree, before any edit) and at the merge
head `4ede7ba6c5` are byte-identical: sha256
`7489de84bc6feccb7cb1f28bcbe4a97775324959c61c9bfc35e8a8c45a6f3bc9` for
all four files, `cmp` silent. The head bundles carry a head-only marker
(`readAnalyticsServiceProvider`, 2 hits in each of `index.js` and
`index.cjs`), so the declaration was rebuilt from the moved source, not
replayed. ESM and CJS both load with 39 exports and one sorted
export-name list (sha256 `84008c9643d55420…`, the reading slice 4
recorded). `AnalyticsRouteHost`, `analytics-routes` and
`mayReadPendingDrafts` appear 0 times in the `.d.ts` and in neither
bundle's export list; `src/index.ts` is untouched.
- **Routes:** a source `RestServer` on a recording mock server, with a
protocol that answers every member, dumped under four configs at base
and at head: `getRoutes()` (functions as arity), the ordered server
registration calls, and a whitespace-free sha256 of every handler's
source. **12 dumps of 12 are byte-identical.** default: routes
`6931d1a5d182…`, calls `ccb3bd12f10b…`, handlers `6d4926ef1465…` (75
routes, 1 analytics); scoped-optional: `275c72f46c30…` / `339e8c3abc05…`
/ `92790e91927d…` (150, 2); scoped-required: `a2aa8183e213…` /
`734f33133150…` / `f6e3fd853aea…` (75, 1); every-provider-set equals
default. A second base run was identical to the first.
- **The handler digest can fail:** a positive control changed one
character of the moved handler (`Analytics dataset query error:` → `!`,
through `ablation-replace.mjs`): routes and calls unchanged, the handler
list differed in exactly `POST /api/v1/analytics/dataset/query` (and its
scoped twin in scoped-optional). Restored, blob `79b94357c4` equals
`HEAD`.
- **Moved body:** base `rest-server.ts` lines 10263-10732, one 4-space
indent removed, against `analytics-routes.ts` lines 88-557: `diff` exits
0 (470 lines). No moved line holds a multi-line string or template
literal (both odd-backtick lines are comments), so re-indenting changes
no runtime string.
- **Moved-line accounting:** `git diff --color-moved=plain
--color-moved-ws=allow-indentation-change` base → move commit over the
two files: 477 deleted and 483 added lines detected as moved.
`rest-server.ts` is +28 / -481: the stub body (host literal plus
`.call`), the slice-5 import and its comment, three import-pointer
comments, and `export` on `mayReadPendingDrafts`.
## Readers re-pointed (sums unchanged, no gate loosened)
- `scripts/check-route-envelope.mjs` (a gate's target paths,
`domain:spec`): `analytics-routes.ts` is discovered by the `*-routes.ts`
convention and declared `dialectOnly` with ratchet #9559, as the four
earlier modules are, at its measured `stringError 0`, `siblingCode 1`
(the undeclared-fault `{ code: 'ANALYTICS_QUERY_FAILED', error: outward
}` arm, a computed `error`). `rest-server.ts` `siblingCode` 18 → 17;
`stringError` stays 17. Sums 17 + 13 + 8 + 1 + 0 + 0 = 39 and 17 + 15 +
8 + 9 + 4 + 1 = 54, unchanged. Output differs from base only in those
lines (15 → 16 modules, 6 → 7 ratcheted). `--self-test` green.
- The nine registrar-list readers above.
- Prose pointers that named `rest-server.ts` as the analytics door's
home, in this lane: `error-response.ts` (the `classifiedRefusalAnswer`
docblock; not in the published `.d.ts`),
`analytics-dataset-refusal-envelope.test.ts` (the 1000-character
`detail` cap) and `analytics-fault-user-message.test.ts` (arm ①).
**Red before the re-points**, each measured with the move in place
(`60fb2f6d99`): the six `local` list readers read `Test Files 6 failed
(6)`, `Tests 7 failed | 101 passed (108)` (five length pins, plus the
docblock pin's length pin and its new module with no floor);
`rest-server-canonical-query-ast.test.ts` (`repo`) `Tests 2 failed | 49
passed | 6 skipped (57)`; the dogfood pair `Tests 2 failed | 106 passed
(108)` (list length; the census row's `registrar list length` control).
`check:route-envelope`: `analytics-routes.ts NOT DECLARED` (MODULES and
EXPRESS_RESPONSE_MODULES) and `rest-server.ts` siblingCode 17 against
declared 18.
## Readers examined and not re-pointed
- `check-adr-anchors`: 1761 decision-letter citations at base, move and
head (hypothesis c above); `packages__rest__src__rest-server.ts.json`
(ADR-0045, `filterAppForUser`) is unaffected.
- `objectql-slot-consumer-census`, `auth-gate-allowlist-fault-window`,
`execctx-authz-input-seam-reachability`, runtime
`meta-list-projection-parity`, core `authz-store-unavailable`
(transports found by `resolveAuthzContext`): they read members or blocks
of `rest-server.ts` the moved body does not touch.
- Every `rest-server.ts#symbol` anchor in the tree whose symbol occurs
in the moved range (`NOT_IMPLEMENTED`, `data`, `route`, `enforceAuth`,
`translateMetaItem`) still has declarations outside it;
`check-scripts-symbol-anchors`, `check:platform-checklist` and
`check-affected-docs` are green. No `rest-server.ts:NNNN` line citation
falls in the moved range; `docs/qa/platform-checklist` has no anchor on
the analytics door.
- Gate outputs, base `8c85141c5` vs head, identical:
`check-system-context-census`, `check-single-authz-resolver`,
`check-rest-log-declared`, `check:test-source-alias`,
`check:rest-log-spy-declared`, `check:platform-checklist`,
`check-filter-alias-parity`, `check:liveness`. Identical except the
scanned-file count (+1, the new module): `check-meta-type-normalized`,
`check-dispatcher-error-vocabulary` (57 sites, 57 classified),
`check:error-status-conformance` (463 producer sites),
`check:doc-authoring`; `check-undeclared-dep-imports` +3 specifiers (the
module's three package imports). `check-scripts-symbol-anchors` +5
file-level anchors (the new comments in `check-route-envelope.mjs`);
`check-affected-docs` 23 → 24 route sources. The other differences come
from `main`'s two merged commits
(`check-durability-degradation-log-level`: one `objectql/src/plugin.ts`
line shift; `check:cross-package-test-inputs`: +1 test file;
`check:nul-bytes` and `check-adr-anchors`: new files).
## Acceptance notes
- **`packages/spec` readers:** none reads a moved line by path.
`packages/spec/liveness/translation.json` names
`rest-server.ts#translateMetaItem`, a method that stays. The
`packages/spec` pointers slices 1–4 carried to the CRUD-doors slice are
unchanged by this slice.
- Other-lane prose that names `rest-server.ts` for the analytics door's
code, read by nothing: `packages/services/service-analytics`
(`analytics-service.ts` about :139, `read-scope-sql.ts` about :179,
`strategies/filter-normalizer.ts` about :205, `date-range-array-arm.ts`
about :65, and eleven test files under `src/__tests__/`, most reading
"exactly as `rest-server.ts`'s catch reads them"),
`packages/runtime/src/domains/packages.ts` (about :81) and
`docs/design/predicate-compilation-convergence.md` (row F11, an
already-dated `rest-server.ts:5872`). Not edited.
- `error-response-structured-arm-door-parity.test.ts` (about :1003) says
the pin avoided booting the route because "`rest-server.ts` is held by
another branch": a dated reason, not a location pointer. Not edited.
- `rest-route-ledger.ts` (about :113) and
`rest-5xx-status-passthrough.test.ts` (header) carry dated measurements
of `rest-server.ts`; unchanged in substance by this move. Not edited.
- `docs/adr/0076-…`'s status line and D11 (slice 3's note) are still
carried to the maintainer by the seat; this slice adds nothing to them.
## Gates and tests
Measured at this branch: base `8c85141c5`, the move `60fb2f6d99`, the
re-points `ba8dab2289`, and one merge of `origin/main` `53e3638ea`
(`4ede7ba6c5`, two commits in `objectql` / `runtime` and the console
pin, 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.
- `pnpm turbo run build --filter=@objectstack/rest...
--filter=@objectstack/verify...` at `4ede7ba6c5`: exit 0 (49 tasks, 18
cached).
- `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 285 passed (285), Tests 5862 passed / 326
skipped (6188).
- `pnpm --filter @objectstack/rest exec vitest run --project repo
--maxWorkers=2`: Test Files 5 passed (5), Tests 230 passed / 6 skipped
(236).
- `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).
- `dispatch-gates --commands --repo objectstack-ai/objectstack`, no
paths, at the final head `4ede7ba6c5` (stderr names that commit; the
`--repo` assertion holds): 78 commands, all run at `4ede7ba6c5`. 77
exited 0 on the first pass; `pnpm check:dual-build-cjs-loads` exited 3
(PREREQUISITE NOT MET: 19 workspace packages had no `dist/`), and after
a full `turbo run build` (75 tasks, 74 cached) it exited 0 at the same
head. `--ran`: `Run reconciliation — 78 derived, 78 run, 0 NOT-MEASURED,
0 UNRUN.`
- Among them `pnpm check:route-envelope` (its `--self-test`, then the
scan), `pnpm check:durability-log-level`, `pnpm
check:dispatcher-error-vocabulary`, `pnpm check:dts-closure` and `pnpm
check:pm-dispatch-gates`. No test file executes
`check-route-envelope.mjs` (twelve test files name it, all in prose), so
its own suite is its `--self-test`.
- Lint, the proven narrowing: `eslint --no-inline-config --format json`
over `packages/rest`, the two dogfood files and
`scripts/check-route-envelope.mjs`: 328 files, 0 errors, 0 warnings, and
every one of this branch's 16 changed files is in the result set at 0 /
0. The published `.d.ts` is byte-identical, so no consumer outside
`packages/rest` can lint differently. Repo-wide `pnpm lint` is CI's.
- `check:dev-prereqs` and `check:skill-frame-freshness` (local-only)
were not run: no `packages/spec` build input and no four-axis frame file
is touched.
- No changeset: nothing any released package publishes changes (`.d.ts`,
export list and routes byte-identical above). `skip-changeset` is the
seat's to apply on its own check.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9e0945c commit a515abd
16 files changed
Lines changed: 690 additions & 504 deletions
File tree
- packages
- qa/dogfood/test
- rest/src
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1626 | 1626 | | |
1627 | 1627 | | |
1628 | 1628 | | |
1629 | | - | |
| 1629 | + | |
| 1630 | + | |
1630 | 1631 | | |
1631 | 1632 | | |
1632 | 1633 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
519 | 519 | | |
520 | 520 | | |
521 | 521 | | |
522 | | - | |
523 | | - | |
524 | | - | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
525 | 526 | | |
526 | 527 | | |
527 | 528 | | |
| |||
691 | 692 | | |
692 | 693 | | |
693 | 694 | | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
694 | 706 | | |
695 | 707 | | |
696 | 708 | | |
697 | | - | |
| 709 | + | |
698 | 710 | | |
699 | 711 | | |
700 | 712 | | |
701 | 713 | | |
| 714 | + | |
702 | 715 | | |
703 | 716 | | |
704 | 717 | | |
705 | 718 | | |
706 | 719 | | |
707 | 720 | | |
708 | 721 | | |
709 | | - | |
| 722 | + | |
710 | 723 | | |
711 | 724 | | |
712 | 725 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
268 | 268 | | |
269 | 269 | | |
270 | 270 | | |
271 | | - | |
| 271 | + | |
| 272 | + | |
272 | 273 | | |
273 | 274 | | |
274 | 275 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | | - | |
| 18 | + | |
| 19 | + | |
19 | 20 | | |
20 | 21 | | |
21 | 22 | | |
| |||
0 commit comments