Skip to content

Commit a515abd

Browse files
refactor(rest): move registerAnalyticsEndpoints into analytics-routes.ts (#23050 slice 5) (#23719)
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

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1626,7 +1626,8 @@ describe('#9084 — the rest-server.ts transport tripwire keys off mechanics', (
16261626
// re-read. The control is that both probes really READ the list — their
16271627
// text is every file on it, in order — so the zero is not a list ignored.
16281628
// [#23050 slice 4] Length 4 → 5 (`sharing-routes.ts`), still 0 over it.
1629-
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(5);
1629+
// [#23050 slice 5] Length 5 → 6 (`analytics-routes.ts`), still 0 over it.
1630+
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(6);
16301631
expect(REST_SERVER_REGISTRAR_PATHS[0]).toBe(REST_SERVER);
16311632
const listed = REST_SERVER_REGISTRAR_PATHS.map((file) => readFileSync(join(REPO_ROOT, file), 'utf8'));
16321633
for (const probe of [MECHANICS, ROUTES]) {

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -519,9 +519,10 @@ export const PROBE_FILE_CENSUS: readonly ProbeFileReading[] = [
519519
'indentation and the subtrahend is pinned at 1 by the `forwarder slice:` control, never inferred from a ' +
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`) — ' +
522-
'`data-action-routes.ts`, `batch-routes.ts`, `approvals-routes.ts` and `sharing-routes.ts`, the modules the ' +
523-
'bodies of `registerDataActionEndpoints`, `registerBatchEndpoints`, `registerApprovalsEndpoints`, ' +
524-
'`registerSharingEndpoints` and `registerSharingRuleEndpoints` moved to; ' +
522+
'`data-action-routes.ts`, `batch-routes.ts`, `approvals-routes.ts`, `sharing-routes.ts` and ' +
523+
'`analytics-routes.ts`, the modules the bodies of `registerDataActionEndpoints`, `registerBatchEndpoints`, ' +
524+
'`registerApprovalsEndpoints`, `registerSharingEndpoints`, `registerSharingRuleEndpoints` and ' +
525+
'`registerAnalyticsEndpoints` moved to; ' +
525526
'reachable = those inside registerMetadataEndpoints',
526527
// [#15542 / #15854] ⭐ THE POPULATION RULE LEARNED A SECOND SPELLING, and
527528
// the numbers it produces did NOT move: 80 / 19 / 61, exactly as before.
@@ -691,22 +692,34 @@ export const PROBE_FILE_CENSUS: readonly ProbeFileReading[] = [
691692
// slice's seam, the same two spellings TWICE, because the module carries
692693
// two registrars (two host literals' forwarders, four; two host
693694
// interfaces' members, four). No call site was added.
695+
// [#23050 slice 5] 70 / 19 / 51 UNCHANGED, the same measurement again:
696+
// `registerAnalyticsEndpoints`' body (one registration site, outside
697+
// `registerMetadataEndpoints`, so in the blind spot) moved to
698+
// `analytics-routes.ts`, which joins the list (length 5 -> 6) with its own
699+
// control. `this.routeManager.register(` stays 63 over the six files
700+
// (33 here + 9 + 5 + 7 + 8 + 1), `private register*Endpoints(` stays 16
701+
// (the registrar is still declared here), and `enforceAuth` 80 -> 84 is
702+
// RE-ANCHORED, not relaxed: the one call site moved with its route and is
703+
// counted in both readings; the four new occurrences are this slice's
704+
// seam, the same two spellings once (the host literal's forwarder, two;
705+
// the host interface's member, two). No call site was added.
694706
controls: {
695707
'private register*Endpoints(': 16,
696708
'this.routeManager.register(': 63,
697-
'registrar list length': 5,
709+
'registrar list length': 6,
698710
'data-action-routes.ts: this.routeManager.register(': 9,
699711
'batch-routes.ts: this.routeManager.register(': 5,
700712
'approvals-routes.ts: this.routeManager.register(': 7,
701713
'sharing-routes.ts: this.routeManager.register(': 8,
714+
'analytics-routes.ts: this.routeManager.register(': 1,
702715
// Both halves of the new rule carry their own control, so neither can go
703716
// silently to zero: a helper deleted and its routes inlined back would
704717
// still read population 80, and only these two controls would notice the
705718
// shape moved and force this provenance to be re-read.
706719
'registerPerItemRoute(': 8,
707720
'const registerPerItemRoute =': 1,
708721
'forwarder slice: this.routeManager.register(': 1,
709-
enforceAuth: 80,
722+
enforceAuth: 84,
710723
},
711724
note:
712725
'The single non-tripwire probe names ONE registrar of 16. The other 15 can never mint a key: ' +

‎packages/rest/src/analytics-dataset-refusal-envelope.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,8 @@ describe('[#5367] a dataset refusal answers 400 DATASET_INVALID from its own env
268268
*
269269
* The route replaces every parse failure's `message` with a generic
270270
* "Invalid dataset definition." and puts the real one in `detail`, capped at
271-
* 1000 characters (`rest-server.ts`). The prescription's zod-wrapped message
271+
* 1000 characters (`analytics-routes.ts` since #23050 slice 5,
272+
* `rest-server.ts` before). The prescription's zod-wrapped message
272273
* is 1076 chars for `array_agg` and 1304 for `string_agg`, so the two clauses
273274
* an author must act on — "was removed" (~index 192) and the imperative
274275
* "Delete the aggregation" (690 / 918) — arrive, and the closing

‎packages/rest/src/analytics-fault-user-message.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
*
1616
* ```text
1717
* throw { code: 'INVALID_FILTER', status: 400, userMessage: 'Check the filter…' }
18-
* ① analytics (rest-server.ts, the `{ code, message }` it builds by hand)
18+
* ① analytics (analytics-routes.ts since #23050 slice 5, rest-server.ts
19+
* before; the `{ code, message }` it builds by hand)
1920
* → 400 {"code":"INVALID_FILTER","message":"…"} ⛔ no mark
2021
* /data door → 400 {"error":"…","code":"INVALID_FILTER",
2122
* "userMessage":"Check the filter…"}

0 commit comments

Comments
 (0)