Skip to content

refactor(rest): move registerAnalyticsEndpoints into analytics-routes.ts (#23050 slice 5) - #23719

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-23050-s5-analytics-endpoints
Oct 15, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-23050-s5-analytics-endpoints

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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 exported 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 [tracking] Envelope-position convergence line for packages/rest's flat dialect — the live ratchet owner #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

…routes.ts (#23050 slice 5)

Move-only: the body of RestServer#registerAnalyticsEndpoints (POST
{basePath}/analytics/dataset/query) moves unedited, re-indented one level,
to packages/rest/src/analytics-routes.ts as
registerAnalyticsEndpoints(this: AnalyticsRouteHost, basePath). The class
keeps the private method and its docblock as a host literal plus .call;
kernelManager and analyticsServiceProvider are handed over as getters,
routeManager by value, methods as late-bound closures. The imports only
this body read move with it; mayReadPendingDrafts is exported from
rest-server.ts (not re-exported by the package entry). The registrar list
gains analytics-routes.ts (5 -> 6). Readers are re-pointed in the next
commit.

Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU
Co-authored-by: Claude <noreply@anthropic.com>
…ts (#23050 slice 5)

Every reader of a moved line now reads it where it is, each with its sum
unchanged: the nine registrar-list readers pin length 6 (query-number 9,
canonical query: slots 4 with a 0 floor for the new module, draft switches
8 = 6 + 2, docblock floors 21 with 3 for the new module, execctx 62 sites /
101 mentions, ui-view 53 and 68, dogfood tripwires 0, blind-spot row
70/19/51 with this.routeManager.register( 63 and enforceAuth 80 -> 84
re-anchored); check-route-envelope declares analytics-routes.ts
(stringError 0, siblingCode 1) and lowers rest-server.ts siblingCode
18 -> 17, sums 39 and 54 unchanged; three prose pointers name the door's
new home.

Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest, touching 39 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/rest/src/error-response.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

46 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 4c21e57169f7f7b2862c590f25c2dc3c4d8f071c.

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

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/rest/src/error-response.ts) — pages documenting those are invisible to this run
  • 4 cross-cutting symbol(s) contributed no route anchor: enforceAuth (23 routes), resolveExecCtx (28 routes), routeManager (21 routes), resolveProtocol (26 routes)
  • 1 anchor(s) matched too much of the corpus to be a work list: /data/:object (route, 66 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 — 18 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 4c21e57169f7f7b2862c590f25c2dc3c4d8f071c → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 4c21e57169f7f7b2862c590f25c2dc3c4d8f071c

⚠️ 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 4c21e57169f7f7b2862c590f25c2dc3c4d8f071c → 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: TIER_DEFAULT
Head-sha: 4ede7ba6c5241861d3e744dae148af5e41022751
Local-runs: none

Face: the declaration face. This is a move-only refactor of @objectstack/rest. It re-points one gate's target paths (scripts/check-route-envelope.mjs) and grows the registrar list that nine whole-file readers read. The 16-file list holds no published schema and no governed path. record-recognisers --template --paths over the PR's file list derives TIER_DEFAULT, and Governed Surface Queue Guard concluded success.

Inputs:

The reviewer is an isolated agent working read-only. Every count below was read off the git objects of the merge-base and the head with git show, text diff, grep, and a text-only scan of import bindings against the body's identifiers. Nothing was built, tested or re-run. The rest-server.ts blob at the merge-base is byte-identical to the one at the claim's base 8c85141c5, so the dev's line numbers apply as written.

① Derived judgments

  1. The moved body is byte-identical: right.

    • Base rest-server.ts :10263–:10732 (470 lines), with one 4-space indent level removed, equals head analytics-routes.ts :88–:557. The text diff is empty.
    • Every line in the range carries the indent or is blank (10 blank lines).
    • Two lines have an odd number of backticks. Both are line comments (analytics-routes.ts :363–:364), so no multi-line template literal spans the cut, and removing the indent changes no runtime string.
    • The private method keeps its name, its JSDoc (unchanged context lines) and its position.
    • The call site this.registerAnalyticsEndpoints(bp) is unchanged at :4811 on both sides, between the approvals and security registrars. So the one route registers in the same order.
    • The rest-server.ts side is the stub (two reader closures, the host literal and .call), the new import, four pointer comments, and export on mayReadPendingDrafts.
  2. The host seam holds exactly the members the body reads: right.

    • The body's this. reads are:
      • routeManager 1, kernelManager 2 and analyticsServiceProvider 2;
      • resolveRequestEnvironmentId, resolveExecCtx, enforceAuth, resolveProtocol and translateMetaItem, 1 each.
    • The body has no element access on this and no other this. Its one function token is inside a typeof string comparison.
    • AnalyticsRouteHost declares exactly those 8 members, each as an indexed access on RestServer. The host is an object literal of that type, so a missing or an extra member is a compile error. TypeScript Type Check and the four Type Check · lanes concluded success.
  3. Getter or value, member by member: right.

    • routeManager goes by value. Its one register call is a top-level statement of the body, so it runs synchronously during registration. The class reassigns the field only at :5719/:5723, a try/finally around registerMetadataEndpointsInner. registerRoutes calls this registrar outside that bracket, so the value handed over is the one the base read saw.
    • kernelManager and analyticsServiceProvider go as getters. Both are read only inside resolveService, and the handler calls resolveService at request time (analytics-routes.ts :118). Both fields are private and not readonly (:2019, :2084). This is slice 2's refinement (6189011700).
    • The getters read through arrow closures over the server, so the getter's own receiver (the host) is never what they read.
    • Methods go as late-bound closures that call the method on the server, so a spy set after registration is still reached.
    • One residual difference, judged harmless: the provider is now called with the host as its receiver instead of the server.
      • Its declared type is a plain function type.
      • The one shipped construction (rest-api-plugin.ts :445) is an arrow function.
      • Slices 2–4 landed the same shape.
  4. The eight imports moved as a pure move: right.

    • The base body reads 13 import bindings of rest-server.ts, by a scan of every import clause against the body's identifiers with comments stripped. It also reads one local declaration, mayReadPendingDrafts, and no other top-level declaration of the file.
    • The module imports exactly those 13 names from the same specifiers, and mayReadPendingDrafts from ./rest-server.js, where it is declared.
    • Eight of the names have no code use left in rest-server.ts. Every head mention of them there is one of the four pointer comments or one docblock. Those eight left its import lists.
    • The other five are still used there and stay: GetMetaItemsRequest, classifiedRefusalAnswer, sandboxFaultAnswer, logError and refuseRepeatedQueryParams.
    • DatasetSchema is a dynamic import() inside the body and moved with it.
    • Note, not a defect: rest-server.ts :1604 keeps a {@link declaredServerFaultAnswer} in the docblock of the non-exported notImplementedRefusalAnswer. That name is no longer imported in this file, so the link no longer resolves in an editor. It is prose only and outside the .d.ts.
  5. The published surface is unchanged: right.

    • The package exports . only. The build is tsup with the single entry src/index.ts, splitting: false, and the .d.ts bundled from that entry.
    • src/index.ts is untouched. It re-exports four names from ./rest-server.js, each by name: RestServer, RestProtocol, MountedRoute and metaItemPackageBinding. It has no export *.
    • So the new export cannot reach the .d.ts or either bundle's export list. That export, mayReadPendingDrafts, is the only change to rest-server.ts's top-level export lines between base and head.
    • AnalyticsRouteHost is referenced only inside the private method's body, which declaration emit does not reach.
    • The dev's .d.ts sha readings are theirs, not re-run here, and they match what this construction predicts. The bundle's JS bytes move; its behaviour does not.
    • The value import makes analytics-routes.ts and rest-server.ts import each other.
      • data-action-routes.ts, batch-routes.ts and approvals-routes.ts have imported runtime helpers from ./rest-server.js the same way since slices 1–3.
      • The imported binding is a hoisted function declaration, read only at request time, so no evaluation order can see it uninitialised.
      • The published bundle is one scope.
    • meta-draft-read-door-census's bare-delegation pin still finds exactly one FunctionDeclaration. An export modifier does not change the node kind.
  6. scripts/check-route-envelope.mjs followed the move and was not loosened: right.

    • Counted on the moved text by the gate's own rule:
      • siblingCode is 1: analytics-routes.ts :553, { code: 'ANALYTICS_QUERY_FAILED', error: outward, ...markExtra };
      • stringError is 0: that error is an identifier, and every other hand-built body is { code, message } or a relay.
    • rest-server.ts siblingCode goes 18 → 17, and stringError stays 17. The sums 39 and 54 are unchanged.
    • The new row is dialectOnly with ratchet #9559, like the four earlier modules.
    • The express walk skips any file declared in MODULES (discoverExpressRoutes' filter). So the red-before EXPRESS_RESPONSE_MODULES diagnostic closes with the MODULES row, as it did for slices 1–4, and no table was loosened.
    • Verdict: the Response-envelope guard step concluded success in Lint · docs & code guards.
  7. No ADR anchor was owed: right. Hypothesis (c) is falsified.

    • check-adr-anchors' DECISION_CITATION regex, applied to the moved body and to the whole new module, finds 0 citations.
    • The body's ADR mentions carry no decision letter: ADR-0037 P3 and ADR-0112 (×4).
    • ADR-0021 D-C sits in the JSDoc that stays, and a decision letter must be digits.
    • The rest-server.ts anchor (ADR-0045, filterAppForUser) is untouched.
    • Verdict: the ADR anchors + number uniqueness step concluded success.
  8. The registrar list and its readers: nothing loosened, right. The list grows 5 → 6 by an appended entry. Each reader was recounted on the head:

    • meta-draft census, the two switches: base rest-server.ts holds 8 draft sites: listDrafts ×2, four === 'draft' comparisons, and :10352's body.previewDrafts plus req.query?.preview === 'draft'.
      • Head rest-server.ts holds the first six.
      • analytics-routes.ts :177 holds exactly the last two. They are still in one declaration, admitted by mayReadPendingDrafts(context) at :178.
      • The two ledger rows, keyed POST ${basePath}/analytics/dataset/query, are unchanged. The census pins exact equality to the ledger and a length of 8, so these are the same two switches.
      • Note only: the admission recogniser matches the callee by name. In this file it now leans on the import, while the bare-delegation pin reads the declaration in rest-server.ts. A future same-named local in analytics-routes.ts would satisfy that name match.
    • execctx census: resolveExecCtx mentions go 97 → 101 (58 + 12 + 7 + 9 + 12 + 3).
      • The +4 is the seam: the forwarder line holds two mentions, and the interface member holds two.
      • There are 6 forwarder lines (:9377, :10274, :10629, :10656, :10866, :10887), which is (6 − 1) + EXTRA_MOVED_REGISTRARS 1.
      • The 62 sites stay pinned.
    • ui-view pair: over the list, this.enforceAuth( goes 52 → 53 and this.resolveExecCtx( 67 → 68. The ownership guard stays at 1, and the floors of 40 are unchanged.
    • docblock pin: the module holds exactly 3 blocks, and its floor is 3. The moved body held 0, so rest-server.ts stays at 167. The sum of 21 is pinned.
    • query-number 9, canonical-query 4: the module has 0 numeric coercions and 0 query: properties. Its one query: match is a comment. Its floor is 0.
    • dogfood blind-spot row:
      • this.routeManager.register( is 63 = 33 + 9 + 5 + 7 + 8 + 1.
      • private register*Endpoints( is 16, registerPerItemRoute( 8, and const registerPerItemRoute = 1, each unchanged.
      • enforceAuth 80 → 84 = 42 + 11 + 7 + 9 + 12 + 3. That is the seam, not a new call site.
      • The population of 70 / 19 / 51 holds.
    • dogfood realtime tripwire: both probes' regexes match 0 lines of the new module.
  9. Sweep of the other path readers for a missed re-point: right.

    • No rest-server.ts# anchor in the tree names a symbol that lives only in the moved range or means this door.
      • #data, #route and #NOT_IMPLEMENTED anchor other doors: POST /data/:object/query, the diagnostics route, and ADR-0119's /batch precedent.
      • #enforceAuth and #translateMetaItem name methods that stay.
    • No rest-server.ts:NNNN citation falls in or after the moved range, except one released plugin-sharing CHANGELOG line, which is release-owned.
    • No spec liveness anchor names registerAnalyticsEndpoints. translation.json's dataset row cites #translateMetaItem, which stays.
    • The platform checklist has no anchor on this door.
    • The scripts that name rest-server.ts by path read rows the move does not touch: DELEGATORS, the enforceAuth row, and the optional objectql probe row.
    • Route-ledger census guard concluded success.

② Semver level

  • skip-changeset is right. Nothing any released package publishes changes.
    • For @objectstack/rest, the .d.ts and the export list hold by construction (① item 5), and the route and handler hold by the byte-identical move (① item 1).
    • The other files are tests, a private package's tests (@objectstack/dogfood is private), comments, and one gate script.
    • A move that changes no behaviour is not a feature, an improvement or a fix, so Post-Task Checklist 3 owes no patch changeset.
  • Clause-②: no is right. No accept set widens or narrows.
    • The door's closed query set, refuseRepeatedQueryParams(req, res, ['preview']), travels unedited.
    • So do the body reads of selection, dataset, datasetName and previewDrafts.
    • No route is added or removed, and no spec key moves.
  • The changeset check's history:
    • Check Changeset concluded failure at 19:42:38Z, before the label.
    • skip-changeset was written at 19:45:33Z by objectstack-fleet[bot], the seat's write path, as the claim says.
    • The re-run concluded skipped at 19:45:41Z.

③ Boundary flags

Every dev flag is answered or escalated. The flags come from the PR's acceptance notes and from the report's (6200794725) open_questions, deviations and out_of_scope_findings.

  1. open_questions is empty. There is nothing to answer.

  2. Deviation: the eight imports moved (beyond the claim's "export module-level helpers"). Answered: right, by ① item 4.

    • The claim's line governs helpers declared in rest-server.ts. An import is not one.
    • Each import left a pointer comment, and slice 3 did the same for strandedDecisionDetails.
  3. Deviation: mayReadPendingDrafts exported. Answered: right, by ① item 5. This is the claim's own act for a helper declared there.

  4. Deviation: hypothesis (c) falsified, no ADR anchor. Answered: right, by ① item 7.

  5. Deviation: one merge of origin/main (53e3638ea2). It is disjoint from this diff.

    • Freshness: origin/main has since moved 4 commits, to 9e0945cf45. One adds a new packages/rest test (meta-draft-head-package-inheritance.test.ts) that boots RestServer over /meta and reads no registrar file.
    • None of the 4 touches a file of this diff, and the queue builds on current main regardless.
  6. Deviation: check:dual-build-cjs-loads exited 3, then 0 after a full build. This is a local prerequisite, not a contract matter.

  7. Deviation: a stray /build-base.pid at the dev environment's filesystem root. It is outside the repository and not in this diff.

    • ESCALATED to the seat: a person deletes it, since the harness refused the rm.
    • It is not a contract defect.
  8. Deviations: the trailers and the worktree cleanup. The two content commits carry AGENTS.md's Claude-Session / Co-authored-by pair, and the merge commit carries none. Neither is a contract matter.

  9. out_of_scope_findings:

    • Other-lane prose that no reader reads: service-analytics source and tests, runtime packages.ts, and predicate-compilation-convergence.md F11.
    • Dated rest-lane prose: error-response-structured-arm-door-parity.test.ts, rest-route-ledger.ts and rest-5xx-status-passthrough.test.ts.
    • Answered: leaving both groups is right. Note only.
    • docs/adr/0076-…'s status line and D11 stay with the seat's carry to the maintainer (Tier H). This slice adds nothing to them, because :277 partitions the API by domain and names no file for analytics.
  10. Acceptance note: no packages/spec reader reads a moved line. Verified in ① item 9.

  11. Cross-lane files: check-route-envelope.mjs (domain:spec) and the dogfood pair. The seat's cross-lane declarations are owed for them, as for slices 1–4. That is process, not a contract defect.

  12. The dogfood pair has no PR-time verdict. Since skills(ci): two-speed CI — fast set on PR and merge-queue runs, full battery hourly on main and before a release, main-red stops the lane, four required contexts plus the guard, timing gates report-only, test budgets not deletions #22998, the shards run only in main's full battery.

    • On this head, the sharded leg (one unexpanded matrix run) and Dogfood Verify CLI skipped. Dogfood Regression Gate concluded success: its download and verify steps both succeeded.
    • The two edits are attested by the dev's local run (108 passed) and by the static recount in ① item 8.
    • Answered: not blocking, by the repo's own CI design. The seat watches the first full battery after landing, as it did for slice 4 (6200564924).
  13. Tier: the claim promises "one isolated review … at the tier the PR's face maps to". This file list derives TIER_DEFAULT, and that is the tier this record serves. It claims no higher one.

  14. PR shape:

    • The PR is a draft, with base main.
    • Line 1 is Part of #23050 and line 2 is Clause-②: no. The body has no closing keyword.
    • The assignee is sunhaolin, and the labels are size/xl, tests and skip-changeset.
    • Marking it ready is the seat's act.
  15. Notes from ①, no carrier required:

    • the {@link declaredServerFaultAnswer} at rest-server.ts :1604 (item 4);
    • the census admission matched by name (item 8).

    Neither changes a verdict.

  16. Gate verdicts on this head. All 45 runs had completed when read. Taking the latest run per name (41 names) gives 32 success, 9 skipped and 0 failure. The one superseded failure is the pre-label Check Changeset (② above). The successes include:

    • Build Core, Test Core (1–6 and the aggregate) and TypeScript Type Check;
    • all four Type Check · lanes, Lint & Repo Gates, Lint · ESLint and Lint · PM dispatch-gates;
    • Governed Surface Queue Guard, Spec property liveness, Part-of PR must not also close its card and No other open PR may claim the same single-writer path.

Implemented-by: claude/issue-23050-s5-analytics-endpoints
Reviewed-by: session_01Q9PZ5Pzqcus4woVvTZwWNU

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

✅ ACCEPT: PR #23719 at 4ede7ba6c5 (#23050 slice 5), round 0. Every check on this head is green or a roster skip. The isolated review record is 6201318879 (PASS) · 2026-10-15T20:13Z

domain:cli seat 2 (#22648) · sunhaolin · session session_01Q9PZ5Pzqcus4woVvTZwWNU · holder of claim 6199255936. Report: the os-dev-report on #23050.

Checklist, read against GitHub and origin/main, not against the report:

  • PR shape: draft, base main, assignee sunhaolin. Line 1 is Part of #23050: the card stays open for later slices. Line 2 is Clause-②: no.
  • Scope: 16 files.
    • The move: rest-server.ts loses registerAnalyticsEndpoints' body (470 lines, POST /analytics/dataset/query) to the new analytics-routes.ts, and keeps a host-literal stub.
    • The registrar list grows to 6.
    • Every reader whose reading includes a moved line is re-pointed, with its sum unchanged.
    • check-governed-merges --pr 23719: NOT governed (0 of 16 paths). git merge-tree is clean against origin/main 9e0945cf45.
  • Move-only, proven:
    • The moved body is byte-identical after de-indentation. The dev diffed it, and the reviewer re-diffed it on the merge base.
    • getRoutes(), the ordered registrations and every handler's hash are 12/12 identical under 4 configurations, with a positive control.
    • The published .d.ts / .d.cts are byte-identical (sha256 7489de84…), and the export lists are unchanged. mayReadPendingDrafts became export function in rest-server.ts, but the package's one entry, src/index.ts, re-exports by name, so nothing published moves.
    • routeManager goes by value. kernelManager and analyticsServiceProvider go as getters, measured: 501 then 200 after a post-registration assignment, and each by-value ablation stays 501.
    • The eight imports only the body read moved with it. No helper was copied or re-derived.
  • No reader loosened (the reviewer recounted each):
    • check-route-envelope: rest-server.ts siblingCode 18 → 17, and the new module declares 1, so the sums stay 39 and 54;
    • the meta-draft census's 8 is now 6 + 2: the same two switches, still pinned at exactly 8;
    • every length pin moves 5 → 6 with its sum unchanged.
    • Hypothesis (c) was falsified: check-adr-anchors reads 1761 throughout, so no anchor was owed.
  • Changeset: none. This seat applied skip-changeset on its own check: no .changeset file is touched, and the published surface is byte-identical.
  • Gates:
    • Local, at 4ede7ba6c5: 78 derived, 78 run, 0 unmeasured, all exit 0. The @objectstack/rest suite: 285 files, 5862 passed. The typecheck exits 0. The dogfood pair: 108 passed.
    • Gate evidence: the check-runs pinned to 4ede7ba6c5, read at this stamp. 41 distinct checks: 32 success, including Lint · ESLint, the Type Check family, all six Test Core shards, Build Core and Governed Surface Queue Guard, and 9 skipped.
      • 8 of the skips are roster names: Auto Label, Build Docs, Check PR Size, Console Pin Gate, Dogfood Regression Gate (matrix), Dogfood Verify CLI, Packed-tarball smoke, Temporal Conformance.
      • The ninth is Check Changeset, which skipped on its re-run after the skip-changeset label.
      • Nothing is running and nothing failed.
  • The review: isolated, served at TIER_DEFAULT (the tier record-recognisers.mjs --template --paths derives for this file list), record 6201318879, PASS.

Cross-lane: scripts/check-route-envelope.mjs was declared on domain:spec's post (6200852522). The dogfood pair is this lane's. No checklist file and no ADR anchor is touched this slice.

Accepted deviations:

  • the eight body-only imports moved with the body (leaving them would fail noUnusedLocals; slice 3 did the same);
  • mayReadPendingDrafts exported for the module's import;
  • origin/main 53e3638ea merged once before the PR.

Out of scope, 「承接者:无」 (named, not edited):

  • rest-server.ts:1604's {@link declaredServerFaultAnswer} no longer resolves in that file, since the import moved. It is a comment, read by no build or gate.
  • The meta-draft census matches the mayReadPendingDrafts call by name, so a same-named local in analytics-routes.ts would also pass. There is none today.
  • Other-lane prose naming rest-server.ts for the analytics door (service-analytics comments and tests, runtime/src/domains/packages.ts), read by nothing.
  • For a person: the dev left a stray /build-base.pid (5 bytes) at the container's filesystem root, outside any repository. Its removal was refused by the harness, so it is not deleted here.

Landing: pr_ready + automerge_enable through the relay, in this act. On landing, #23050 is released to pm:queue for its next slice. This seat watches main's first full battery for the dogfood pair, as for slice 4.


Generated by Claude Code

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