Repository navigation
refactor(rest): computeExecCtx calls the one request-to-ExecutionContext composition, resolveRequestExecutionContext - #24260
Conversation
…ate on the data route (WIP) The one ADR-0096 / ADR-0069 pin ruling record 6227928430 item 2 asks of each later door, written against the door's current composition first so the same table reads before and after the conversion. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
…ext composition (WIP) REST's door keeps what is its own (environment and auth-service resolution, header normalisation, the engine through wiredEngineOrLoud, the tenancy wiring as a thunk, __kernel, __authEnvironmentId, the memo, the perf disclosure and the fail-closed catch) and hands the rest to resolveRequestExecutionContext with its differences as named parameters. The readers move with the call: the rest DELEGATORS row, the transport ledger, the seam pins, and a REST conformance row. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
… the one composition (WIP) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
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 5c85c3d934118bf784bdb1b52146624129df26d0 && git checkout 5c85c3d934118bf784bdb1b52146624129df26d0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin fb6fb24e2ffb2fee0cd94d7acaf7030a780e179d 5acdd53a9f53730d4c73aef10a8b288a8a719350 && git checkout -B drift-repro fb6fb24e2ffb2fee0cd94d7acaf7030a780e179d && git merge --no-ff 5acdd53a9f53730d4c73aef10a8b288a8a719350
node scripts/docs-audit/affected-docs.mjs --json fb6fb24e2ffb2fee0cd94d7acaf7030a780e179d
|
席位核对 — ACCEPT · PR #24260 at
|
One conflict, in packages/rest/src/rest-server.ts's import block: main (#23050 slice 13c) moved the PUT /meta/:type/:name door out and dropped the metaSaveRequestOptions import, this branch dropped AuthSessionApi from the contracts type import. Resolved to main's two comment lines and `import type { ISecurityService } from '@objectstack/spec/contracts';`. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
席位核对 — ACCEPT carried to
|
Part of #23217
Clause-②: no
Ruling-ref: 6227928430
Slice 2 of #23217 under ruling record 6227928430 (letter A): REST's door,
RestServer#computeExecCtxinpackages/rest/src/rest-server.ts, calls the oneresolveRequestExecutionContextthat slice 1 landed in@objectstack/core(f5ef054a7). #23217 remains open for three doors (service-settings,service-storage,plugin-hono-server), each a later slice. No export moves and there is no app-author surface.What changed
computeExecCtxkeeps what is this door's own and calls the function once. 192 lines out, 90 in. What stays at the door, in its old order: the environment and auth-service resolution with theif (!authService) return undefinedearly return, the header normalisation (append per array element), the engine throughwiredEngineOrLoud(the kernel branch still absorbs), the tenancy wiring (now a thunk: the kernel'sgetServiceAsync('tenancy'), elsetenancyServiceProvider, else none), then__kernel,__authEnvironmentId, theServer-Timingdisclosure, the memo inresolveExecCtx, and the fail-closedcatchwith itsapiKeyLifecycleRefusalsside channel.f5ef054a7before it was replaced (slice 1's handover, confirmed row by row):anonymous'fail-closed'if (!authz.userId) return undefinedandassembleExecutionContextauthGate'reread-when-active'authz.authGate. A session: re-read only whileisAuthGateActive(); a throwing probe reads as inactive; a failed re-read isAuthzStoreUnavailableError('auth_gate')credentials'api-key-and-session'headersandgetSessionboth handed toresolveAuthzContextsessionApi'required'if (!api?.getSession) return undefined, the API-key door includedsession'request'api.getSession(inProcessSessionReadInput(headers)), a throw read as no sessiontenancyclassifyAdmissionTenancyPostureon the same two wiringslocalization{ settings: () => provider ? seamOrUndefined(provider(env)) : undefined }resolveLocalizationContextrequestLocalethis.extractLocale(req)accessToken'withhold'accessToken: undefinedoauth/onAdmissionundefined/undefinedscripts/check-single-authz-resolver.mjs: the rest row ofDELEGATORSnow namesresolveRequestExecutionContext. Left onresolveAuthzContext, check (2) would pass on the five prose mentions of it that remain in the file.packages/core/src/security/authz-store-unavailable.test.ts:rest-server.tsno longer callsresolveAuthzContext({, so its ledger row goes (the set-equality audit requires it). The scanner's positive control now names the composition's file.packages/rest/src/execctx-authz-input-seam-reachability.test.ts§0 andauth-gate-allowlist-fault-window.measurement.test.ts's structural pin read source text insidecomputeExecCtx. They are re-aimed in place, with the superseded pins quoted, as those files ask. Each still holds its subject: both tenancy wirings reach the classification, the tenancy region holds nocatch, the door declares'reread-when-active'and keeps no second copy of the gate decision, and the call hands over no path. The decision's own window is pinned in core'srequest-execution-context.test.ts(choice 2). The behavioural drives in both files are unchanged and green.packages/runtime/src/security/request-execution-context.conformance.test.ts(ruling item 4) gains REST as a row. It drivesRestServer.resolvePackageRouteExecutionContexton the single-kernel wiring and sets aside__kerneland__authEnvironmentIdby name (both asserted absent on this wiring). It has a new request that tells'resolved'from'reread-when-active'(the gated session while no gate is active) and a REST CONTROL case. Who a request resolves to is now derived from the row's declaredsessionApi: REST authenticates nobody on a host with no auth service, while the runtime door admits the key there.packages/rest/src/rest-exec-ctx-principal-kind.test.ts. It is a six-row table driven through the realGET /api/v1/data/:objectroute and read at the protocol:401with exactlyANONYMOUS_DENY_BODY, andfindDatais never called;403with its code and message, and the engine is never asked;member1, with noauthGateon the envelope;503 SERVICE_UNAVAILABLE, and the engine is never asked;patchon@objectstack/rest(.changeset/23217-s2-rest-door.md). The publishedrest-servercode changes, and the one ordering note below is a fact a reader of the package can meet.Evidence (head
5acdd53a9)mainatfb6fb24e2(merge commit5acdd53a9; no rebase, amend or force-push). One conflicted hunk: the import block ofrest-server.ts(about :220). Main ([maintainer] refactor(rest): splitpackages/rest/src/rest-server.ts(13,701 lines, 70 commits in 30 days) along itsregister*Endpointsseams, one move-only slice per PR,/metaafter #22877 stage 2 #23050 slice 13c) dropped themetaSaveRequestOptionsimport, and this branch droppedAuthSessionApifrom the contracts type import. It was resolved to main's two comment lines andimport type { ISecurityService } from '@objectstack/spec/contracts';. Neither symbol is used as code in the merged file; the only mention left is that comment. The merged file differs from main by exactly this slice's 284 changed lines, and the PR still touches the same 8 files. Census counts inrest-server.ts, main / merge:resolveExecCtx55 / 55,enforceAuth40 / 40,seamOrUndefined11 / 11,wiredEngineOrLoud11 / 11. Re-measured on the merge, after rebuilding the closure:@objectstack/resttypecheck exit 0 (test layer 0 / 0);--no-inline-configonrest-server.tsexit 0;Test Files 295 passed (295),Tests 6042 passed | 326 skipped (6368), which includes this slice's pins, the censuses and themeta-disabled-package-doors.test.tsfile main brought;Tests 9 passed (9)and the core ledgerTests 39 passed (39);check:dual-build-cjs-loadsNOT MEASURED (exit 3), reconciled0 UNRUN.The readings below were taken on the pre-merge head
96f05115f.865e7d135):Tests 20 passed (20). On the converted door it is green again, inside the full suite.Test Files 294 passed (294),Tests 6036 passed | 326 skipped (6362),VERDICT command-exit 0.e6e916a00(same code):Test Files 21 passed (21),Tests 330 passed (330).Tests 9 passed (9)(two rows, six requests, two CONTROLs). Core repo-project ledger:Tests 39 passed (39)(40 before, minus the rest row).@objectstack/restexit 0 (test layer 0 / 0),@objectstack/coreexit 0 (4 files / 4 errors held),@objectstack/runtimeexit 0 (27 files / 190 errors held). All three debt ledgers are unchanged.authGate: 'reread-when-active'became'withhold'viascripts/ablation-replace.mjs(anchor 1 to 0, blob4aec26e3a6c4to8f9ccb644aee, marker count 1). REST gate pins:Tests 21 failed | 61 passed (82)over 5 of 6 files. The red pins include the new pin's 403 and 503 rows,#7280's end-to-end 403, both direct-mount HELD cases and the OUTAGE case, seam §4's POSITIVE CONTROL and its 503, and the key-posture pins.rest-auth-gate.test.tsstays green; it hand-builds its contexts. Conformance:Tests 2 failed | 7 passed (9)(the session row and the REST CONTROL). Restore:blob == HEAD (4aec26e3a6c4) and git diff HEAD is empty, marker 0. Observed direction = declared direction.resolveExecCtxstays at 55 andenforceAuthat 40 inrest-server.ts. These are the bare-term countsexecctx-consumer-census.test.tsand the dogfoodauthz-probe-blind-spot.census.tspin; the dogfood one does not run on PRs.seamOrUndefinedstays at 11 andwiredEngineOrLoudat 10.rest-server.ts, base to head:resolveAuthzContext({1 to 0,classifyAdmissionTenancyPosture(2 to 0,assembleExecutionContext(1 to 0,resolveLocalizationContext(1 to 0,inProcessSessionReadInput(2 to 0,normalizeAuthGate(1 to 0.isAuthGateActive(goes 3 to 1, and the one left is a line of the gate table in a comment.--no-inline-configover the 7 changed.ts/.mjsfiles: exit 0, no output.Acceptance notes
resolvePackageRouteExecutionContexton both trees. Five of seven readings are identical. The two that differ are double faults on the single-kernel wiring: the configured engine provider fails AND the auth service exposes no session read (or itsgetApi()rejects). Those answered401(no context) and now answer503 SERVICE_UNAVAILABLE, the engine outage. With a working auth service the engine outage was already503, so the new answer no longer depends on the auth service's state. The kernel branch absorbs an engine failure, so it is unaffected. Production compositions build the auth service from plugin-auth, whosegetApi()always yields a session read. Keeping the old order byte for byte needs either a second copy of the session-API check at the door, which letter A item 3 forbids, or a lazyqlinput on the function, which is a change to slice 1's export and outside this slice. The changeset states the window.extractLocale(req)is now evaluated at the call, before the reads, instead of at assembly. It is a pure parse of the headers and?localethat cannot throw for a request that passed the header check, so no answer moves.packages/rest/src/execctx-authz-input-seam-reachability.test.tsandpackages/rest/src/auth-gate-allowlist-fault-window.measurement.test.ts. Both are REST source-text pins oncomputeExecCtx's body, re-aimed in place.check:dual-build-cjs-loadsexited 3 (PREREQUISITE NOT MET: it needs a whole-tree build), so it is NOT MEASURED locally and CI runs it.test:repois CI's.Generated by Claude Code