Repository navigation
refactor(rest): move registerSecurityExplainEndpoints and registerSecurityEndpoints into security-routes.ts (#23050 slice 6) - #23729
Conversation
…utes.ts (#23050 slice 6) The bodies of RestServer#registerSecurityExplainEndpoints (GET/POST {basePath}/security/explain, GET {basePath}/security/my-delegable-scope) and RestServer#registerSecurityEndpoints (GET {basePath}/security/ suggested-bindings and its /:id/confirm and /:id/dismiss) move, unedited and re-indented one level, to ONE new module. The class keeps both private methods and their docblocks as host literals plus .call; routeManager goes by value, kernelManager and securityServiceProvider as getters. The imports only these bodies read move with them, each leaving a pointer comment. The registrar list gains security-routes.ts (6 -> 7). Move-only commit: the readers are re-pointed in the next commit. Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU Co-authored-by: Claude <noreply@anthropic.com>
…23050 slice 6) Each reader whose reading included a moved line now reads it in security-routes.ts, with its sum unchanged: - the nine registrar-list readers re-read their numbers at length 7 (query-number 9, canonical query slots 4, meta-draft switches 8, docblock floors 29, execctx 62 sites / 109 mentions with eight forwarders = (7 - 1) + 2, ui-view pair 55 / 70, the dogfood tripwire 0, the blind-spot row 70 / 19 / 51 with register 63 and enforceAuth 92); - check-route-envelope declares security-routes.ts dialectOnly at stringError 0 / siblingCode 0 (rest-server.ts unchanged at 17 / 17); - a new ADR anchor for security-routes.ts (ADR-0090, ADR-0105, ADR-0112) restores check-adr-anchors' decision-letter reading 1751 -> 1761; - error-response.ts' nestedRefusalEnvelope docblock names the three families' current homes. Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU Co-authored-by: Claude <noreply@anthropic.com>
…-security-endpoints
📓 Docs Drift CheckThis PR changes 1 package(s): 45 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. 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 fd8cd453e82d6ad29fabb36a694278f87ba9ff9f && git checkout fd8cd453e82d6ad29fabb36a694278f87ba9ff9f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9bb93706fbdf57582329745b64c2f40c6a084572 a12cdcf4e4a5f2477dd537b451bcd7ceae37c1ff && git checkout -B drift-repro 9bb93706fbdf57582329745b64c2f40c6a084572 && git merge --no-ff a12cdcf4e4a5f2477dd537b451bcd7ceae37c1ff
node scripts/docs-audit/affected-docs.mjs --json 9bb93706fbdf57582329745b64c2f40c6a084572
|
Contract reviewServed-tier: Face: the declaration face. This is a move-only refactor of 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 ① Derived judgmentsNo accept set 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 the PR's acceptance notes and from the report's (
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对✅ ACCEPT: PR #23729 at
Checklist, read against GitHub and
Cross-lane, declared before review:
Accepted deviations:
The review's one non-blocking item, carried rather than fixed here: the new Out of scope, 「承接者:无」 (named, not edited): other-lane prose naming Landing: Generated by Claude Code |
Part of #23050
Clause-②: no
Slice 6 of the
rest-server.tssplit, by slice 1's pattern (landed note6136102437, steps 1–6), slice 2's getter refinement (6189011700) and slices 3–5's shape (PR #23670, PR #23705's two registrars in one module, PR #23719): the bodies ofRestServer#registerSecurityExplainEndpoints(GET/POST {basePath}/security/explain,GET {basePath}/security/my-delegable-scope) andRestServer#registerSecurityEndpoints(GET {basePath}/security/suggested-bindingsand its/:id/confirmand/:id/dismiss) move, unedited, to ONE new module,packages/rest/src/security-routes.ts. It is move-only: routes, the ordered registrations, every handler, 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.rest-server.tsgoes from 11,042 to 10,636 lines (basea515abdd8c). The card stays open for the later slices.The pattern, applied
Members the moved bodies read, from an AST walk of each method (TypeScript compiler API, base
a515abdd8c):registerSecurityExplainEndpoints(JSDoc:10282, method:10308-:10601, body:10309-:10600) andregisterSecurityEndpoints(JSDoc:10662, method:10694-:10838, body:10695-:10837).routeManagerregistercalls:2010, privatekernelManagerresolveService:2019, private, notreadonlysecurityServiceProviderresolveService:2089, private, notreadonlyresolveRequestEnvironmentId:2306, privateresolveExecCtx:2856, privateenforceAuth:2456, privateNeither body has a bare
this, an element access onthis, or athis-rebindingfunction. Their module-level reads:rest-server.ts(AST count outside both methods):logError(12),sendError as sendEnvelopeError(4) andrefuseRepeatedQueryParams(15). They stay there; the module imports the same names from the same modules. ItssendErrorimport carries a comment pointing at the dialect note onrest-server.ts's import, which the explain family's emitter docblock names.ErrorCode(type,@objectstack/spec/api, with its[#8073]comment),standardErrorCodeForHttpStatus(@objectstack/spec/api,[#11683]),classifiedRefusalAnswer,nestedRefusalEnvelope,sandboxFaultAnswer(./error-response.js), andisAudienceBindingSuggestionStatuswithunknownAudienceBindingSuggestionStatusMessage(@objectstack/core,[#7678]). They moved with the bodies, as slices 3 and 5 moved theirs, and each left a pointer comment. Leaving them would failnoUnusedLocals. The[#8073]and[#7678]comments moved verbatim. The[#11683]comment pointed atregisterSharingEndpointsas its use, so in the module it names the explain family's classified-refusal arm, the use it has there.rest-server.tsis read, so noexportwas added anywhere. The explain body's dynamicimport('@objectstack/spec/security')moved inside it.Host shape (slice 1's shape B, twice, as slice 4): one host interface per registrar,
SecurityExplainRouteHostandSecurityRouteHost. Each lists exactly the members its body reads, typedRestServer['m']. The class keeps both private methods with their names, docblocks and positions. Each body is now a host literal plus.call(host, basePath). Methods go as late-bound closures,(...a) => this.m(...a). The call sites inregisterRoutes(:4812-:4813at base) are unchanged.Fields, checked one by one:
routeManagerregistercalls, top-level statements of the two bodies):5719/:5723) wrapsregisterMetadataEndpointsInner, not these registrarskernelManagerresolveService)readonlysecurityServiceProviderresolveService)readonlyThe 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 AFTERregisterRoutes(), and drives it again:96504dab66):securityServiceProvideron the explain family (GET /api/v1/security/my-delegable-scope) reads501 NOT_IMPLEMENTED, then200 {"businessUnits":["bu_1"],"positions":[]}. On the suggested-binding family (GET /api/v1/security/suggested-bindings) it reads501 NOT_IMPLEMENTED, then200 {"data":[{"id":"sug_1"}]}.kernelManageron the scoped explain door (environmentId: env_1) reads501, then200 {"env":"env_1"}.Tests 3 passed (3).scripts/ablation-replace.mjs, each getter in turn replaced byX: this.X,with a two-line anchor that names its host literal. Each landed: anchor 1 → 0, replacement 0 → 1, blob4867a14302→5c7ebad6df/48f2660f00/f3c9b53365. In each run the ablated door reads501then501(frozen at registration) while the other two still read200(Tests 1 failed | 2 passed (3), each run). Restored by the tool and a trap: blob4867a14302equalsHEAD,git diff HEADempty, the one-off file deleted. The path is source (relative import), so nodistleg applies.Mechanism hypotheses from the dispatch, measured:
resolveService. That reads TWO fields: the environment kernel'ssecurityservice viakernelManagerfirst, thensecurityServiceProvideras the fallback. Naming a principal other than the caller is the service's question (explainAccessForCaller); the door hands it the parsed request and the caller's context and reads no further provider field. Both fields go as getters. The suggested-binding family readssecurityServiceProvideronly, also a getter, measured separately.registerSecurityEndpointsbuilds its paths fromconst dataPath = basePath;, so the suggested-binding doors answer at{basePath}/security/suggested-bindings, not under/data(slice 4's share family appendedcrud.dataPrefix; this one does not). Every path expression moved unedited, and the route dumps below are identical.[#7678]import comment that moved with them, carry 10ADR-NNNN Dkcitations: ADR-0090 D5 ×2, D6 ×2 and D12 ×2, ADR-0105 D8 ×2, and ADR-0112 D5 ×2. They left the anchoredrest-server.ts, socheck-adr-anchorsread 1761 decision-letter citations at base and 1751 at the move-only commitd6fc91a97d, green both times. That is the silent narrowing the card forbids. A new anchor (below) restores 1761 at head.The registrar list
packages/rest/src/rest-server-registrar-files.tsgainssecurity-routes.tswith a[#23050 slice 6]comment (length 6 → 7). Every reader that pins its length re-reads its number, and every sum is unchanged:rest-server-query-number-census.test.tsrest-server-canonical-query-ast.test.tsquery:slotssecurity-routes.ts: 0, length 6 → 7meta-draft-read-door-census.test.tsrest-server-docblock-position.test.tssecurity-routes.ts: 8: header, two host interfaces, two functions, and the three blocks that moved with the bodies (the explain family'srespondErroranddelegableHandlerblocks, the suggested-binding family'srespondErrorblock).rest-server.ts167 → 164 blocks, floor 100; length 6 → 7execctx-consumer-census.test.tsEXTRA_MOVED_REGISTRARS2, the map gainingsecurity-routes.ts: 1besidesharing-routes.ts: 1, as slice 4 counted two registrars in one moduleui-view-route-identity.measurement.test.ts/ui-view-route-tenancy.measurement.test.tsthis.enforceAuth(55,this.resolveExecCtx(70 over the list (53 / 68 before, +1 each per new forwarder); ownership guard 1authz-conformance.test.ts(realtime tripwires)authz-probe-blind-spot.census.ts,rest-server.tsrowthis.routeManager.register(63 = 27 + 9 + 5 + 7 + 8 + 1 + 6;private register*Endpoints(16security-routes.ts: this.routeManager.register(6;registrar list length7;enforceAuth84 → 92 re-anchored (five call sites moved and are counted in both readings; the eight new occurrences are the seam twice; no call site added);populationRulenames the new moduleEach number above was also counted statically on the tree (
grep -oFper file).Proof of move-only
.d.ts:packages/rest/dist/index.d.tsandindex.d.ctsbuilt at basea515abdd8c(this worktree, before any edit), at96504dab66and at the merge heada12cdcf4e4are byte-identical: sha2567489de84bc6feccb7cb1f28bcbe4a97775324959c61c9bfc35e8a8c45a6f3bc9for all six files,cmpsilent. The head bundles carry a head-only marker (readSecurityServiceProvider, 4 hits in each ofindex.jsandindex.cjs), so the declaration was rebuilt from the moved source, not replayed.SecurityExplainRouteHost,SecurityRouteHostandsecurity-routesappear 0 times in the.d.ts;src/index.tsis untouched.b3246be6a171…on all four), and none of the two functions or two host types is among them.RestServeron 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, at96504dab66and again at the merge head. default: routes6931d1a5d182…, callsccb3bd12f10b…, handlers6d4926ef1465…(75 routes, 6 security); scoped-optional:275c72f46c30…/339e8c3abc05…/92790e91927d…(150, 12); scoped-required:a2aa8183e213…/734f33133150…/f6e3fd853aea…(75, 6); every-provider-set equals default. A second base run was identical to the first.Delegable scope error:→!, throughablation-replace.mjs; anchor 1 → 0, blob01fe3b195d→0c2c62e946). Routes and calls were unchanged, and the handler list differed in exactlyGET /api/v1/security/my-delegable-scope(and its scoped twin). Restored, blob01fe3b195dequalsHEAD.rest-server.tslines 10309-10600 and 10695-10837, one 4-space indent removed, againstsecurity-routes.tslines 110-401 and 411-553:diffexits 0 both times (292 + 143 lines). No moved line has an odd number of backticks, so no template literal spans the cut and re-indenting changes no runtime string.git diff --color-moved=plain --color-moved-ws=allow-indentation-changebase → move commit over the two files: 442 deleted and 470 added lines detected as moved.rest-server.tsis +43 / −449: the two stub bodies (host literal plus.call), the slice-6 import and its comment, and four import-pointer comments.Readers re-pointed (sums unchanged, no gate loosened)
scripts/check-route-envelope.mjs(a gate's target paths,domain:spec):security-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 five earlier modules are, at its measuredstringError 0,siblingCode 0. Every arm of both families emits the nested{ error: { code, message } }through its family's one emitter (the emitters [finding]registerSecurityEndpointsanswers THREE different error-envelope shapes on the same three routes (ADR-0112) #7981 and [finding] the two registrars ADJACENT to #7981 still answer the retired flat / bare-string error dialects (ADR-0112) #8073 introduced). Sorest-server.tsstays atstringError 17,siblingCode 17, and the sums 17 + 13 + 8 + 1 + 0 + 0 + 0 = 39 and 17 + 15 + 8 + 9 + 4 + 1 + 0 = 54 are unchanged. Output differs from base only in the new row (16 → 17 modules, 7 → 8 ratcheted).--self-testgreen.scripts/adr-anchors/packages__rest__src__security-routes.ts.json(new, ADR-0090 + ADR-0105 + ADR-0112):check-adr-anchorsdecision-letter citations on the anchored surface 1761 (base) → 1751 (move) → 1761 (head); anchored files 67 → 68. Every listed id appears in the module, and every cited letter is a decision of its record (the gate's own check, green). The invariant writes "its D6", never "ADR-0090 D6", so it adds no citation of its own (measured with the gate's regex: 0).error-response.ts, thenestedRefusalEnvelopedocblock (rest lane, not in the published.d.ts): it namedrest-server.tsas the home of three families, the suggested-binding one among them. Since slices 3, 4 and this one moved all three, the sentence now names each family's current home.Red before the re-points, each measured with the move in place (
d6fc91a97d): the sixlocallist readers readTest Files 6 failed (6),Tests 7 failed | 103 passed (110)(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) readTests 2 failed | 53 passed | 7 skipped (62). The dogfood pair readTests 2 failed | 106 passed (108)(list length; the census row'sregistrar list lengthcontrol).tsc --noEmitwas clean, butcheck:test-typecheckfound 2 type errors, the two typed floor tables without the new module.check:route-envelopereadsecurity-routes.ts NOT DECLARED(MODULES and EXPRESS_RESPONSE_MODULES).check-adr-anchorswas green at 1751.Readers examined and not re-pointed
packages/specreaders: none reads a moved line by path. No liveness anchor names either registrar or a symbol of the moved bodies.packages/spec/liveness/doc.json's note namesthis.registerSecurityEndpoints(bp), a call site that stays, as dated history. Thepackages/specpointers slices 1–4 carried to the CRUD-doors slice are unchanged by this slice.objectql-slot-consumer-census,auth-gate-allowlist-fault-window,execctx-authz-input-seam-reachability, runtimemeta-list-projection-parity, coreauthz-store-unavailableandcheck-single-authz-resolver'sDELEGATORSrow: they read members or blocks ofrest-server.tsthe moved bodies do not touch.rest-server.ts#symbolanchor in the tree names either registrar or a symbol only the moved ranges hold. Norest-server.ts:NNNNline citation falls in or after the moved ranges (the highest is:5872).docs/qa/platform-checklisthas no anchor on these doors.a515abdd8cvs merge heada12cdcf4e4, 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,check:cross-package-test-inputs. Identical except the scanned-file count:check-meta-type-normalized(+1, the new module),check-dispatcher-error-vocabulary(+1; 57 sites, 57 classified) andcheck:error-status-conformance(+2, the new module and one file frommain; 463 producer sites).check-undeclared-dep-imports+2 specifiers and 1 file (the module's package imports).check-scripts-symbol-anchors+4 file-level anchors (the gate's new comments).check-affected-docs24 → 25 route sources. The other differences come frommain's two merged commits (check-durability-degradation-log-level: oneobjectql/src/plugin.tsline shift;check:nul-bytesandcheck:doc-authoring: new files).Acceptance notes
rest-server.ts'sresolveSecurityServicedocblock (about:9506) says it "Mirrors the resolver in registerSecurityExplainEndpoints". That docblock ships in the published.d.ts, so editing it would break the byte-identity this slice must keep. It is not edited, and the name still resolves to the function of that name insecurity-routes.ts, which holds the resolver.rest-server.tsfor the suggested-binding route, read by nothing:packages/core/src/security/audience-binding-suggestion-status.ts(about:10, and about:18, which saysrest-server.tsimports the vocabulary; the importer is nowsecurity-routes.ts) andpackages/runtime/src/domains/security.ts(about:144). Not edited (承接者:无).docs/adr/0112-…'s Consumers line namesrest-server.tsfor@objectstack/restas a whole, not as these doors' home. It is a governed surface and is not edited.scripts/check-route-envelope.mjs's count-history comments forrest-server.ts(the [finding]registerSecurityEndpointsanswers THREE different error-envelope shapes on the same three routes (ADR-0112) #7981 and [finding] the two registrars ADJACENT to #7981 still answer the retired flat / bare-string error dialects (ADR-0112) #8073 entries) say the two registrars sat in that file when those counts moved. They are dated history, unchanged.docs/adr/0076-…(status line and D11) anddocs/adr/0111-…(Consumers line) are still carried to the maintainer by the seat; this slice adds nothing to them.Gates and tests
Measured at this branch: base
a515abdd8c, the moved6fc91a97d, the re-points96504dab66, and one merge oforigin/mainf40d877f81(a12cdcf4e4, two commits inobjectql,plugins/organizationsand one dogfood test, no file in this diff). All builds and tests went throughos-verify-lock. Every exit code was captured to a file before any pipe.a12cdcf4e4:pnpm install --frozen-lockfileexit 0.pnpm turbo run build --concurrency=2, the whole workspace, exit 0 (76 tasks, 33 cached).pnpm --filter @objectstack/rest typecheck: exit 0 (tsc --noEmit, thencheck:test-typecheck: 0 file(s) / 0 error(s)), at96504dab66and ata12cdcf4e4.pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2: Test Files 285 passed (285), Tests 5870 passed / 326 skipped (6196), at both heads.pnpm --filter @objectstack/rest exec vitest run --project repo --maxWorkers=2: Test Files 5 passed (5), Tests 234 passed / 7 skipped (241), at both heads.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), at both heads.dispatch-gates --commands --repo objectstack-ai/objectstack, no paths, at the final heada12cdcf4e4(stderr names that commit; the--repoassertion holds): 79 commands, all run ata12cdcf4e4, all exit 0.--ran:Run reconciliation — 79 derived, 79 run, 0 NOT-MEASURED, 0 UNRUN.Among them arepnpm check:route-envelope(its--self-test, then the scan),pnpm check:adr-anchors,pnpm check:durability-log-level,pnpm check:dispatcher-error-vocabulary,pnpm check:dts-closure,pnpm check:dual-build-cjs-loadsandpnpm check:pm-dispatch-gates.eslint --no-inline-config --format jsonoverpackages/rest, the two dogfood files,scripts/check-route-envelope.mjsand the new anchor JSON: 330 files, 0 errors, 0 lint warnings. Its one warning isFile ignored because no matching configuration was suppliedfor the JSON, whicheslint .never reads. All 15 changed files are in the result set at 0 / 0. The published.d.tsis byte-identical, so no consumer outsidepackages/restcan lint differently. Repo-widepnpm lintis CI's.check:dev-prereqsandcheck:skill-frame-freshness(local-only) were not run: nopackages/specbuild input and no four-axis frame file is touched..d.ts, export lists and routes byte-identical 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),scripts/adr-anchors/packages__rest__src__security-routes.ts.json(a new entry incheck-adr-anchors' registry,domain:devx), and the dogfood pair (packages/qa/dogfood/test/authz-conformance.test.ts,authz-probe-blind-spot.census.ts). No platform-checklist file is edited.Generated by Claude Code