Repository navigation
refactor(rest): move registerSearchEndpoints into search-routes.ts (#23050 slice 9) - #23959
Conversation
Slice 9 of the rest-server.ts split, by slice 1's pattern: the body of
RestServer#registerSearchEndpoints (GET {basePath}/search) moves,
re-indented one level and otherwise unedited, to search-routes.ts. The
class keeps the private method and its docblock as a SearchRouteHost
literal plus .call. routeManager goes by value; resolveProtocol,
resolveExecCtx and enforceAuth go as late-bound closures. The registrar
list gains search-routes.ts (9 -> 10). Readers are re-pointed in the
next commit.
Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT
Co-authored-by: Claude <noreply@anthropic.com>
The module header's prose named the three closures, which every census that counts those spellings over the registrar list would have read as three more mentions. It now describes them without naming them. Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT Co-authored-by: Claude <noreply@anthropic.com>
Every reader whose reading included a moved line now reads it in search-routes.ts, each with its sum unchanged: - the nine registrar-list readers pin length 10 (execctx census 62 sites, 117 -> 121 mentions, 11 forwarders; ui-view pair 58 / 73; docblock floors 37 -> 40; query-number 9, canonical query 4, meta-draft 8, realtime tripwires 0); - the dogfood blind-spot row: 70 / 19 / 51 unchanged, this.routeManager.register( 63 over ten files, a new search-routes.ts control (1), enforceAuth 100 -> 104 re-anchored; - check-route-envelope declares search-routes.ts at stringError 0 / siblingCode 0, and rest-server.ts keeps 16 / 14 (sums 39 and 54). Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT Co-authored-by: Claude <noreply@anthropic.com>
…-search-endpoints
📓 Docs Drift CheckThis PR changes 1 package(s): 14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 8 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 18 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin fba8edf3a7ab684d4d3cf3ca7be9355122d56fd5 && git checkout fba8edf3a7ab684d4d3cf3ca7be9355122d56fd5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7277c07f6883fbb5e8893cbe5ad91dd482f83723 06bd4fcbc9c8c9fd0c4d5c7377f570644a8fad1f && git checkout -B drift-repro 7277c07f6883fbb5e8893cbe5ad91dd482f83723 && git merge --no-ff 06bd4fcbc9c8c9fd0c4d5c7377f570644a8fad1f
node scripts/docs-audit/affected-docs.mjs --json 7277c07f6883fbb5e8893cbe5ad91dd482f83723
|
Contract reviewServed-tier: Face: the declaration face. This is a move-only slice of
No Inputs:
Method: read-only. Every count below was read off the git objects of the merge base and the head with ① Derived judgmentsNo accept set changes and no public surface changes. Each change the diff makes is judged below.
② Semver level
③ Boundary flagsEvery dev flag is answered or escalated. The flags come from two places: the PR body's Acceptance notes, and the report's (
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对ACCEPT · #23050 slice 9 (search) ·
Carried, not edited here (a new head would need a new review for comment-only text):
Landing: Generated by Claude Code |
Part of #23050
Clause-②: no
Slice 9 of the
rest-server.tssplit, by slice 1's pattern (landed note6136102437, steps 1–6), slice 2's getter refinement (6189011700) and slices 3–8's shape (PR #23670, PR #23705, PR #23719, PR #23729, PR #23763, PR #23882): the body ofRestServer#registerSearchEndpoints(GET {basePath}/search, the cross-object search door onto the protocol'ssearchAll) moves to ONE new module,packages/rest/src/search-routes.ts. Routes, the ordered registrations, every handler's source digest, the publisheddist/*.d.tsand both bundles' export lists are byte-identical, and 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(AST walk below), so thestaticexception (seat ruling6205442867) is not used. It reads no provider field either, so the seat's provider ruling (6215084478) does not apply: nothing in the host is a getter.rest-server.tsgoes from 10,047 to 9,995 lines (based5666be876). 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, symbol-resolved, base
d5666be876):registerSearchEndpoints(JSDoc:8993, method:8997-:9063, body:8998-:9062, 65 lines).routeManagerregistercall (:8999):2024, privateresolveProtocol:9005):2402, private asyncresolveExecCtx:9006):2870, private asyncenforceAuth:9007):2470, privateNo bare
this, no element access onthis, nothis-rebindingfunction, and noRestServer.NAMEaccess (static reads: none). The seat's text count (the same four members, one read each) is confirmed.Control walk (the same walker, same base, on the neighbouring methods):
resolveSecurityService(:8976, just above) readskernelManager×2,resolveRequestEnvironmentId×1 andsecurityServiceProvider×2, which is what its source says; theregisterEmailEndpointsstub (:9084, just below) readsemailServiceProvider,enforceAuth,resolveExecCtxandrouteManageronce each, slice 8's host literal.Module-level reads (uses outside the method counted by the same walk):
GLOBAL_SEARCH_PARAMS(:661, 0 other uses),UNDECLARED_WHOLE_NUMBER_PARAM(:767, 2) andreadDeclaredQueryNumber(:832, 5): declared inrest-server.tsand already exported there; the module imports them from./rest-server.js. One declaration each, never a copy. Noexportwas added anywhere.refuseUnknownQueryParams(./query-allowlist.js, 11 other uses),refuseRepeatedQueryParams(./query-multiplicity.js, 14),mapDataErrorandlogUnexpectedRouteError(./error-response.js, 8 and 6): still used inrest-server.ts, so every import stays; the module imports the same names from the same modules. No import moved.Host shape (slice 1's shape B):
SearchRouteHost(4 members) lists exactly the members the body reads, each typedRestServer['m']. The class keeps the private method with its name, docblock and position; its body is now a host literal plusregisterSearchEndpoints.call(host, basePath). Methods go as late-bound closures,(...a) => this.m(...a). The call site inregisterRoutes'registerForBase(this.registerSearchEndpoints(bp);,:4806, underapi.enableSearch) is unchanged.Fields, checked one by one:
routeManagerregistercall, a top-level statement of the body):5177/:5181at base) wrapsregisterMetadataEndpointsInner, not this registrarNo other field is read. The protocol the door searches is resolved per request by
resolveProtocol, a method, so no getter is owed.Late binding, measured (a one-off vitest file, copied in, run under the verify lock, deleted; not committed): a source
RestServerregisters its routes, the door is driven once, thenresolveProtocol,resolveExecCtxandenforceAuthare replaced ON THE INSTANCE afterregisterRoutes()and the door is driven again.MEASURE before: 200 {"hits":["early"]} | after: 200 {"hits":["late"]} | seen early:early-user,late:late-user | late enforceAuth calls 1,Tests 1 passed (1).scripts/ablation-replace.mjs: the search host literal'sresolveProtocol: (...a) => this.resolveProtocol(...a),replaced byresolveProtocol: this.resolveProtocol.bind(this),(a three-line anchor naming the literal; anchor 1 → 0, blob1c7f7e45a921→507220e525a3):afterstays{"hits":["early"]},Tests 1 failed (1). Restored by the tool: blob1c7f7e45a921equalsHEAD,git diff HEADempty.rest-server.ts, blob2ba4159180, restored into the tree under a trap, thengit checkout HEADwith blob and diff proof): the same answers as the head,Tests 1 passed (1).Proof of move-only
.d.ts:packages/rest/dist/index.d.tsandindex.d.ctsbuilt at based5666be876(this worktree, before any edit), at the move commit6a57870feand at the merge head06bd4fcbcare byte-identical: sha2567489de84bc6feccb7cb1f28bcbe4a97775324959c61c9bfc35e8a8c45a6f3bc9for all,cmpsilent. The head bundle carries a head-only marker (registerSearchEndpoints.call: 2 hits inindex.js, 0 at base), so the declaration was rebuilt from the moved source, not replayed.SearchRouteHostandsearch-routesappear 0 times in the.d.ts;private registerSearchEndpoints;is still there.src/index.tsis untouched.b3246be6a1718700…on all four). No search-module name is among them.RestServeron a recording mock server, with a protocol that answers every member, dumped under slice 7's seven configs (default; scoped-optional; scoped-required; every provider set;enableDiscovery: false;enableOpenApi: false; both off under scoped-optional) plus an eighth,enableSearch: falseunder scoped-optional. Each dump holdsgetRoutes()(functions as arity), the ordered server registration calls, a whitespace-free sha256 of every handler's source, and the source itself.d5666be876vs merge head06bd4fcbc(diff -rexits 0). default 75 routes (1 search door), scoped-optional 150 (2), scoped-required 75 (1), every-provider-set equals default, discovery-off 73 (1), openapi-off 73 (1), both-off-scoped-optional 142 (2), search-off-scoped-optional 148 (0). A second base run was identical to the first.'Search not supported by this protocol'→…protocol!', throughablation-replace.mjs; anchor 1 → 0, blob7161079aedd6→d2a5eb7f70dd). Routes and calls were unchanged in all eight configs, and the handler digests differed in exactly the search door of each mounted base (GET /api/v1/search, andGET /api/v1/environments/:environmentId/searchwhere scoped), nothing else, and nothing underenableSearch: false. Restored, blob7161079aedd6equalsHEAD.rest-server.tslines 8998-9062, one 4-space indent removed, againstsearch-routes.tslines 74-138:diffexits 0 (65 lines). The body has no empty line, no trailing whitespace, and its one template literal (${basePath}/search) sits on one line; every other backtick in the range is in a line comment.git diff --color-moved=plain --color-moved-ws=allow-indentation-changebase →6a57870feover the two files: all 65 deleted lines are detected as moved.rest-server.tsis +13 / −65 in the whole PR: the stub body (host literal plus.call, 11 lines in place of 65) and the module's import with its comment (+2).Readers re-pointed (sums unchanged, no gate loosened)
packages/rest/src/rest-server-registrar-files.tsgainssearch-routes.tswith a[#23050 slice 9]comment (length 9 → 10). Every reader that pins its length re-reads its number:rest-server-query-number-census.test.tsreadDeclaredQueryNumber, never coerces one; length 9 → 10rest-server-canonical-query-ast.test.ts(repoproject)query:slotssearch-routes.ts: 0; length 9 → 10meta-draft-read-door-census.test.tsrest-server-docblock-position.test.tssearch-routes.ts: 3(header, host interface, function; the body held no TSDoc block);rest-server.tskeeps 164 blocks, floor 100; length 9 → 10execctx-consumer-census.test.tsrest-server.ts, interface member ×2 in the module);rest-server.ts58 → 59. Forwarders 11 = (10 − 1) + 3 − 1; neither map gains an entryui-view-route-identity.measurement.test.ts/ui-view-route-tenancy.measurement.test.tsthis.enforceAuth(58,this.resolveExecCtx(73 over the list (57 / 72 before, +1 each for the search literal's forwarders); ownership guard 1authz-conformance.test.ts(realtime tripwires)authz-probe-blind-spot.census.ts,rest-server.tsrowthis.routeManager.register(63 = 21 + 9 + 5 + 7 + 8 + 1 + 6 + 4 + 1 + 1;private register*Endpoints(16search-routes.ts: this.routeManager.register(1;registrar list length10;enforceAuth100 → 104 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);populationRulenames the new moduleThe module header names none of the three closures in prose, so no census that counts those spellings reads a comment as a seam (a second commit,
6a57870fe, reworded the first draft's header for exactly that reason).Readers outside the list:
scripts/check-route-envelope.mjs(a gate's target paths,domain:spec):search-routes.tsis discovered by the*-routes.tsconvention and declareddialectOnlywith ratchet [tracking] Envelope-position convergence line for packages/rest's flat dialect — the live ratchet owner #9559, as the eight earlier modules are, at its measuredstringError 0/siblingCode 0. Its one hand-built refusal, the 501{ code: 'NOT_IMPLEMENTED', message }, has noerrorkey, and its catch sendsmapDataError's computed body, so neither rule counts it.rest-server.tsstays 16 / 14, so the sums 16 + 13 + 8 + 1 + 0 + 0 + 0 + 0 + 1 + 0 = 39 and 14 + 15 + 8 + 9 + 4 + 1 + 0 + 0 + 3 + 0 = 54 are unchanged (the gate compares both counts exactly, in both directions). Its output differs fromorigin/mainonly by the new row (19 → 20 modules, 10 → 11 ratcheted).--self-testgreen.ADR-NNNN Dk, andcheck-adr-anchorsoutput is identical atorigin/mainand head (1770 decision-letter citations).check:platform-checklistoutput is identical atorigin/mainand head.Red before the re-points, each measured with the move in place (
6a57870fe, the readers at their base text):@objectstack/restlocalreadTest Files 6 failed | 280 passed (286),Tests 7 failed | 5887 passed | 326 skipped (6220)(six length pins and the docblock pin's unfloored new module);rest-server-canonical-query-ast.test.ts(repo) readTests 2 failed | 244 passed | 10 skipped (256); the dogfood pair readTests 2 failed | 106 passed (108)(the realtime tripwire's length pin, andcontrol "registrar list length" moved … expected 10 to be 9).tsc --noEmitwas clean, butcheck:test-typecheckfound 2 type errors, the two typed floor tables without the new module.check:route-envelope(ate8386a523) readsearch-routes.ts NOT DECLARED(MODULES and EXPRESS_RESPONSE_MODULES). Each is green after the re-point.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.jsoncitesrest-server.ts#registerRoutesfor theif (this.config.api.enableSearch)gate, the call site that did not move.packages/spec/src/api/rest-server.zod.ts(about:154) andpackages/spec/src/system/core-services.zod.ts(about:153) name the method without a file, and the method still registers the door.check:livenessoutput is identical atorigin/mainand head.packages/metadata-protocol/src/protocol.ts(about:8304, another lane's comment) says "registerSearchEndpoints(packages/rest/src/rest-server.ts) 501s exactly whentypeof protocol.searchAll !== 'function'". The 501 line now lives insearch-routes.ts; the method of that name still registers the door. No gate reads the comment. Not edited.rest-server.ts'UNDECLARED_WHOLE_NUMBER_PARAMdocblock listsGET /searchamong the doors "in this file"; the export (slice 1) and approvals (slice 3) doors it names left earlier. A module-level const the package entry does not re-export, so not in the.d.ts; pre-existing deixis. Not edited.GLOBAL_SEARCH_PARAMSstays declared inrest-server.ts, where the ruling keeps helpers; it now has no use in that file (its readers are the module andrest-server-closed-query-params.test.ts, both importing it from./rest-server.js).discovery-routes.ts(about:392),discovery-search-capability-agreement.test.ts,query-multiplicity.ts,rest-route-ledger.ts(by route).docs/audits/2026-06-record-search-liveness.mdcitesrest-server.ts:3309, a dated audit line outside the moved range. ReleasedCHANGELOG.mdtext: not edited.packages/rest/srcreach the moved code in its new file: their outputs atorigin/mainand head differ only by the one scanned file (check-meta-type-normalized37 → 38,check-dispatcher-error-vocabulary,check-error-status-conformance,check-undeclared-dep-imports),check-affected-docs(route sources 27 → 28, registration call sites 22 → 23, by the*-routes.tsconvention), andcheck-scripts-symbol-anchors(+4 file-level anchors, the gate's new comments). Identical:check-single-authz-resolver,check-system-context-census,check-rest-log-declared,check:rest-log-spy-declared,check-durability-degradation-log-level,check-filter-alias-parity,check-adr-anchors,check-adr-symbol-anchors,check:platform-checklist,check:liveness.rest-server.ts:NNNNline citation in a gated document falls in the moved range (:8993-:9063).Acceptance notes
objectql-slot-consumer-census.test.ts(slice 8's extra reader) is not touched: the search body reads no provider slot, and it stays green.[#7606]comment's pointer ("same order and same envelope as the export route — seequery-allowlist.ts") still reads true; the export route lives indata-action-routes.ts.Gates and tests
Measured at this branch: base
d5666be876, the movee8386a523plus its header wording6a57870fe, the re-points0ba4e5617, and one merge oforigin/mainb9dbc3d89(06bd4fcbc: six commits in cli, runtime, plugin-security, spec, lint tests and scripts; no file in this diff). All builds and tests went throughos-verify-lock. Every exit code was captured to a file before any pipe. Final head:06bd4fcbc.pnpm install --frozen-lockfileexit 0;pnpm --workspace-concurrency=2 --filter '@objectstack/rest...' --filter '@objectstack/verify...' buildexit 0 at base and at the merge head; thenpnpm turbo run build --concurrency=2, the whole workspace, exit 0 (Tasks: 76 successful, 76 total), after which the@objectstack/rest.d.ts/.d.ctsstill equal base.pnpm --filter @objectstack/rest typecheck: exit 0 (tsc --noEmit, thencheck:test-typecheck: 0 file(s) / 0 error(s)).pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2: Test Files 286 passed (286), Tests 5894 passed / 326 skipped (6220).pnpm --filter @objectstack/rest exec vitest run --project repo --maxWorkers=2: Test Files 5 passed (5), Tests 246 passed / 10 skipped (256).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).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, at06bd4fcbc(stderr names that commit; the--repoassertion holds; 13 paths against merge baseb9dbc3d89, 350 changed lines): 78 commands, all run at06bd4fcbcafter the whole-workspace build, every one exit 0.--ran:Run reconciliation — 78 derived, 78 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-bytesandpnpm check:pm-dispatch-gates.check-adr-anchorsandcheck-platform-checklist(not derived) also ran: exit 0, output identical toorigin/main.origin/mainmoved on after the merge (to7277c07f6, 13 commits): none touches a file in this diff; its onepackages/restaddition is a new test that drives the/metasave door throughRestServerand reads no source, and its checklist additions add no anchor intorest-server.ts. It also adds a gate (check:pnpm-flag-spellings) the derivation above could not name; CI'sLint & Repo Gatesruns it on the merge ref. Not merged again.Per command (exit code), 78 lines:
pnpm exec eslint --no-inline-config --format jsonover the 13 changed files: exit 0, 13 results, 0 errors, 0 warnings. The published.d.tsis byte-identical, so no consumer outsidepackages/restcan lint differently. Repo-widepnpm lintis CI's (Lint & Repo Gates).check:dev-prereqsandcheck:skill-frame-freshness(local-only) were not run: this diff touches nopackages/specbuild input and no four-axis frame file..d.ts, export lists and routes above).skip-changesetis the seat's to apply on its own check.Cross-lane files in this diff:
scripts/check-route-envelope.mjs(domain:spec, a gate's target paths: one new MODULES entry and two history comments on therest-server.tsrow, counts and 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–8.check-governed-merges --testover the 13 paths: NOT governed; 350 changed lines.Generated by Claude Code