You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
runtime: one internal request-to-ExecutionContext resolver for the platform's five hand-rolled copies (rest, runtime, service-settings, service-storage, plugin-hono-server) — not a published export (ruling C′ on #23128, p3) #23217
Ruled: 6227928430 · letter A on #23982 (batch #358 item 1) · home amended to @objectstack/core · 2026-10-17T01:27Z
Filed by the director seat (summon #37, session_01LqNgdPZB2Jnr5HpGbaqLeB) on the maintainer's ruling C′ on #23128 (「同意」, recorded at 6142279866) under 「立卡你决定,不需要我同意」. ⛔ Not a claim. Filing gate ①: a measured duplication with named sites. Priority p3: housekeeping inside the platform; nothing user-visible changes and #23128 does not wait for it.
Five platform doors each hand-roll the same request-to-context composition (resolveAuthzContext + classifyAdmissionTenancyPosture + assembleExecutionContext / assembleExecutionContextOrGuest + a session read): packages/rest/src/rest-server.ts:3060, packages/runtime/src/security/resolve-execution-context.ts:150, packages/service-settings/.../settings-service-plugin.ts:285, packages/service-storage/.../storage-service-plugin.ts:862 and :1247, packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:443. Each copy was fixed separately when an input was omitted (the tenancy posture: #15351, #15352; the in-process session read: #22258; a six-field omission: #15747).
What to build (one PR, or one per door after the first)
The five doors call it; each conversion is proven by that door's existing pins (no new behaviour; a door that intentionally differs keeps its difference as a named parameter, never a copy).
⛔ No new published export, no change to any door's outward behaviour, no app-author surface (ruling C′: code-mounted app routes are retired, so no app needs this).
Acceptance
git grep -c of the composition's distinctive lines over the five files reads 0 outside the shared function; every door's pins green; the ADR-0096 principal-less refusal and the ADR-0069 gate read the same on each door before and after (a pin per door).
Held pm:blocked on the decision card #23982 · domain:cli seat 2 (#22648) · os-tesla · session session_018t6a5rvqPRvEDMCVVW9kYT · 2026-10-16T15:39Z · ⛔ not a claim.
Why: this seat read the card at the claim attempt (it was the lane's next card by order). The ruled home cannot be reached. The function was to be internal to @objectstack/runtime, whose exports has only ..
Four of the five doors' packages do not depend on runtime: @objectstack/rest, @objectstack/service-settings, @objectstack/service-storage and @objectstack/plugin-hono-server.
@objectstack/rest cannot depend on it: runtime depends on rest, so it would be a cycle.
The one package all five share is @objectstack/core. A home there is a new published export, which ruling C′ item 3 excluded.
Ruled on #23982 (batch #358 item 1, record 6227928430, letter A) · director seat (summon #43 continuation 3, session_01QJC7i981TSs9L1CGJK59Ax) · 2026-10-17T01:26Z
The home this card inherited from ruling C′ (6142279866 item 3), an internal function in @objectstack/runtime, is unreachable from four of the five doors: rest, service-settings, service-storage and plugin-hono-server do not depend on the runtime package, and the runtime depends on rest. The maintainer's letter A amends the home clause only: the one function lives in @objectstack/core beside classifyAdmissionTenancyPosture, exported from core's . entry (Clause-②: yes (widening), minor on @objectstack/core); slice 1 is the function plus the runtime's own door, each later door its own slice with Clause-②: no; a door's intentional difference is a named parameter, never a copy; one conformance test over the function and every converted door; the carried fallback is letter B (one census-discovered parity pin) on a premise_still_valid: false report. Item 1 of the scope below is amended in this act to match; the rest stands. This card leaves pm:blocked for pm:queue in this act — the domain:cli seat re-dispatches from here.
Claim: PM loop round 2 · #23217 slice 1: the one request-to-ExecutionContext function in @objectstack/core, plus the runtime's own door, under letter A · 2026-10-18T01:35Z
Session: session_01QyoWKK9RwUNxBxmevy9Neb
Account: marchtian (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-23217-s1-core-request-context
Worktree: objectstack-issue-23217-s1
Domain: domain:cli
Seat: domain:cli#2 (seat post #22648)
Ruling-ref: 6227928430 (#23982, batch #358 item 1, letter A, the maintainer's 「23982 同意」), which amends ruling C′ 6142279866 item 3 in its home clause only. The card's amended scope 6227947754 binds.
File surface (read on origin/main7be3cc728; line numbers move; stop on a breach and explain it in the report):
packages/core/src/security/: one new module holding the function, beside admission-tenancy-posture.ts (classifyAdmissionTenancyPosture, :108); its re-export from security/index.ts (core's . entry re-exports ./security/index.js); and its unit tests. domain:engine, declared on [PM seat] domain:engine — 🟢 os-project-manager #6367.
The one conformance test (ruling item 4) over the function and the converted runtime door.
One minor changeset on @objectstack/core. A changeset on @objectstack/runtime only if a published byte a reader sees moves.
⛔ Hold-offs:
the other four doors (rest-server.ts, service-settings, service-storage, plugin-hono-server's current-user-endpoints.ts), which are later slices, each declared to its lane;
resolve-session-principal.ts and http-dispatcher.ts (PR feat(spec): the remaining getSession readers read the declared AuthSessionApi result (user.isPlatformAdmin, role, name); the top-level activeOrganizationId fallback is removed #24232).
Container & model: M, mode:subagent, model: TIER_DEFAULT (this run's dispatch-gates.mjs --tier --repo objectstack-ai/objectstack: "Model tier — no path-derived mandate")
Clause-②: yes
Basis for the Clause-② line: (widening), as the ruling states. The function is a new export from @objectstack/core's . entry. No door's outward behaviour changes, and the runtime door's existing pins prove it. The review of record owed for the declaration is this lane seat's own, at TIER_DEFAULT (the --tier floor for a Clause-②: yes declaration with no published-schema face).
Responsibility: n/a — a ruled consolidation card (filing gate ①, a measured duplication with five named sites), not a defect.
Thread-read: 6227947754
Serial constraints cleared:
Open PRs' file lists were read in this act. None touches packages/core/src/security/, packages/core/src/index.ts or resolve-execution-context.ts.
{
"issue": 23217,
"status": "done",
"branch": "claude/issue-23217-s1-core-request-context",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24248",
"session": "session_01QyoWKK9RwUNxBxmevy9Neb (mode:subagent; the dispatching PM session's harness-stamped id, as the commit Claude-Session trailers carry it)",
"premise_still_valid": true,
"summary": "Slice 1 of #23217 under letter A (6227928430). Added resolveRequestExecutionContext in packages/core/src/security/request-execution-context.ts, exported from core's . entry through security/index.ts beside classifyAdmissionTenancyPosture. It does the session read under the in-process rule, the tenancy-posture classification, resolveAuthzContext, the localization, the ADR-0069 gate and the assembly. It makes two documented choices (anonymous: fail-closed | guest; authGate: withhold | resolved | reread-when-active), and every other measured door difference is a required named parameter (11 in total, 0 item-5 fallbacks; table in door_differences). The runtime door packages/runtime/src/security/resolve-execution-context.ts now keeps only what is its own (service lookups, header normalisation, the /mcp OAuth verification and scope reading) and calls the function: 209 lines out, 87 in. Its 39 pins and #24137's 15 OAuth pins are green unchanged. The ruling-item-4 conformance test is in runtime with one row per converted door. Draft PR #24248 is Part of #23217, assigned to marchtian, with a minor changeset on @objectstack/core.",
"tests": "HEAD f5a955e12 (after merging origin/main af284b05e). (1) Core local project, under the verify lock: 'Test Files 104 passed (104) / Tests 2551 passed (2551)'. The edited repo-project ledger src/security/authz-store-unavailable.test.ts: 'Tests 40 passed (40)'. (2) Runtime local project, under the lock: 'Test Files 413 passed (413) / Tests 6318 passed | 19 skipped (6337)', 'VERDICT command-exit 0 · held the lock 559s'. (3) Door and dispatcher pins, verbose reporter, at 7667d9b4d before the merge (the post-merge full suite includes them): resolve-execution-context.test.ts 39, resolve-execution-context.oauth-organization.test.ts 15 of 15, request-execution-context.conformance.test.ts 7, plus http-dispatcher.mcp-oauth, http-dispatcher.tenancy-posture-outage, http-dispatcher.keys and in-process-session-renewal.pin: 'Test Files 7 passed (7) / Tests 111 passed (111)'. (4) New core unit file: 20 passed. (5) Typecheck: pnpm --filter @objectstack/core typecheck exit 0 (test layer held at 4 files / 4 errors) and pnpm --filter @objectstack/runtime typecheck exit 0 (27 files / 190 errors held, unchanged). The core test tsconfig compiles the new test (--listFiles counts 2 request-execution-context files). ABLATION, choice 1, at 7667d9b4d, declared direction RED. The function was mutated to always take the fail-closed entry ('return assembleExecutionContext(assembly); // ABLATION-23217-anonymous-entry'), landed by scripts/ablation-replace.mjs: 'anchor 1 -> 0, blob e3a17adddf47 -> 86ec4e1f04b5', marker count 1. Runtime's vitest aliases @objectstack/core to ../core/src/index.ts, so no dist preflight applies. Observed: core 'Tests 1 failed | 19 passed (20)', the choice-1 guest-envelope case. Runtime 'Tests 10 failed | 36 passed (46)': 9 door pins, including '#6216 a sessionless request still yields the WHOLE guest envelope' and the tenancy 'POSITIVE CONTROL', plus the conformance CONTROL. Restore: 'blob == HEAD (e3a17adddf47) and git diff HEAD is empty', marker 0. Direction observed equals the direction declared. Note: the conformance equalities stay green under the ablation by construction, because both sides call the one function; the CONTROL case and the unit battery are what catch a defect in the function itself.",
"gates": "Local subset, at f5a955e12. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derived 84 commands; each was run with its exit code captured before any pipe into ran.list. 83 exit 0; pnpm check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET: 40 packages have no dist, it needs a whole-tree build), so NOT MEASURED; CI runs it. Reconciliation, node scripts/pm/dispatch-gates.mjs --ran ran.list: exit 0, 'Run reconciliation — 84 derived, 83 run, 1 NOT-MEASURED, 0 UNRUN'. Its staleness line: origin/main moved to 9975c26ae while the run was going, and 3 stale files are rest adr-anchors and check-route-envelope.mjs, unrelated to this diff. Among the derived commands: pnpm check:authz-resolver exit 0, pnpm check:published-files exit 0, pnpm check:dts-closure exit 0, pnpm check:lean-entry-closure exit 0, pnpm check:pm-dispatch-gates exit 0, pnpm check:type-check-coverage exit 0, pnpm check:type-check-debt exit 0, pnpm check:nul-bytes exit 0, node scripts/check-adr-0087-registration.mjs --base origin/main exit 0, node scripts/check-changeset-no-major.mjs --base origin/main exit 0. Edited gate script: node scripts/check-single-authz-resolver.mjs --self-test exit 0 and the gate exit 0 ('all 3 entry points delegate'); no test file names the script (git grep: package.json, check-ratchet-remedy-authority.mjs and bare-root-worklist.mjs only). Lane blind spot: pnpm exec eslint --no-inline-config over the 7 changed .ts/.mjs files exit 0, no output. Local-only family: pnpm check:dev-prereqs exit 0 (spec, core and plugin-auth built from the sources on disk). Runtime test:repo was not run locally; it is CI's (Test Core). PR check-runs, one read at the time of this report: 'No other open PR may claim the same issue' success; 'Packed-tarball smoke (opt-in)' skipped; the other 21, including the Lint and Type Check lanes, 'Check Changeset', 'Governed Surface Queue Guard', 'Part-of PR must not also close its card' and 'The card this PR closes must claim this branch', in_progress. Required Test Core and Build Core were not yet listed.",
"mcp_calls": "0",
"api_writes": "2: (1) POST /repos/objectstack-ai/objectstack/dispatches (the pr_create stroke through scripts/pm/fleet-write/dispatch.mjs; relay run 39091336245 sent POST /repos/objectstack-ai/objectstack/pulls, which opened #24248 as a draft, and POST /repos/objectstack-ai/objectstack/issues/24248/assignees with marchtian; read-back '15523 byte(s) sent, 15523 stored — identical'); (2) POST /repos/objectstack-ai/objectstack/issues/23217/comments (this os-dev-report, via scripts/pm/post-stamped.mjs). Not REST: git push of the branch. No label write: the dispatch named no label, and skip-changeset does not apply because the PR carries a changeset.",
"line_budget": "n/a: nothing under skills/** and no ledger file touched",
"files_changed": [
".changeset/23217-core-request-execution-context.md",
"packages/core/src/security/request-execution-context.ts",
"packages/core/src/security/request-execution-context.test.ts",
"packages/core/src/security/index.ts",
"packages/core/src/security/authz-store-unavailable.test.ts",
"packages/runtime/src/security/resolve-execution-context.ts",
"packages/runtime/src/security/request-execution-context.conformance.test.ts",
"scripts/check-single-authz-resolver.mjs"
],
"door_differences": [
"Measured on origin/main 7be3cc728 before slice 1 was written. Doors: rest = rest-server.ts computeExecCtx; runtime = runtime/src/security/resolve-execution-context.ts; settings = settings-service-plugin.ts verifiedContextFromRequest; storage = storage-service-plugin.ts buildFileReadAuthorizer + buildAuthGateSessionReader; hono = current-user-endpoints.ts makeExecutionContextResolver. 11 differences, all named parameters, 0 item-5 fallbacks.",
"1 anonymous: rest fail-closed | runtime guest | settings fail-closed ({ enforced: true }) | storage fail-closed ('unauthenticated') | hono fail-closed -> parameter anonymous",
"2 ADR-0069 gate: rest key = the admission's posture, session = re-read while isAuthGateActive(), a failed re-read is 503 | runtime withheld (the dispatcher's enforceAuthGate + onAdmission) | settings authz.authGate, no re-read | storage its own gate reader with the same shape as rest | hono withheld (allow-listed /me routes) -> parameter authGate: withhold | resolved | reread-when-active",
"3 credential doors: API key + session everywhere except hono, which passes headers undefined (session only) -> parameter credentials",
"4 no session API: rest and hono answer no context (the key is not honoured) | runtime and settings resolve (the key is honoured) | storage has no authorizer without auth (door-level) -> parameter sessionApi: required | optional",
"5 session source: the auth service's in-process read everywhere; runtime /mcp adds a verified token's session or none for an unverifiable JWT -> parameter session: request | none | { established }",
"6 tenancy posture: rest kernel or provider | runtime lookup thunk | settings and storage async accessor when present | hono none -> parameter tenancy (a TenancyServiceResolver thunk; the resolution stays at the door per #16013)",
"7 admission channel: runtime only -> parameter onAdmission",
"8 localization: rest settings provider | runtime settings lookup | hono withheld | settings and storage none -> parameter localization: withhold | { settings }",
"9 request locale: rest header or ?locale (metaRequestLocale) | runtime Accept-Language | hono none -> parameter requestLocale (a value)",
"10 session bearer: runtime carries it | rest and hono withhold it | storage carries it in the raw envelope -> parameter accessToken: carry | withhold",
"11 OAuth provenance: runtime /mcp only -> parameter oauth (H2: verification and the scope reading stay at the door, because they need @objectstack/spec/ai, which core does not depend on)",
"Door-side, not the composition and not a copy: header normalisation (the function takes a Web Headers), service resolution, fault relay (the function propagates everything), post-assembly additions (rest __kernel, __authEnvironmentId, memo, perf disclosure; hono perf disclosure), output consumption (settings projects to SettingsContext; storage hands the envelope to engine.find)"
],
"handover_slice2": [
"Door: packages/rest/src/rest-server.ts computeExecCtx (merged head :3098). Call the function with anonymous 'fail-closed', authGate 'reread-when-active', credentials 'api-key-and-session', sessionApi 'required', session 'request', localization { settings: () => this.settingsServiceProvider ? seamOrUndefined(() => this.settingsServiceProvider(environmentId)) : undefined }, requestLocale this.extractLocale(req), accessToken 'withhold', oauth undefined, onAdmission undefined.",
"The tenancy thunk stays rest's own: the kernel getServiceAsync branch or the tenancyServiceProvider branch, undefined when neither applies.",
"Kept at the door: environment and auth-service resolution and the 'if (!authService) return undefined' early return, header normalisation (append per array element), the ql resolution through wiredEngineOrLoud, __kernel, __authEnvironmentId, the execCtxMemo, the perf disclosure, and the catch with its apiKeyLifecycleRefusals side channel.",
"Then: move the rest row of DELEGATORS in scripts/check-single-authz-resolver.mjs to resolveRequestExecutionContext; remove rest-server.ts from the core TRANSPORT_LEDGER in authz-store-unavailable.test.ts if it no longer calls resolveAuthzContext({; add rest as a conformance row (runtime already depends on @objectstack/rest); add the ruling's one ADR-0096 / ADR-0069 pin; Clause-②: no.",
"Later slices: settings and storage rows need runtime devDependencies on @objectstack/service-settings and @objectstack/service-storage (no cycle: neither depends on runtime). Storage today hands the raw ResolvedAuthzContext to engine.find; the envelope adds isSystem false and principalKind human and drops credential (no objectql, plugin-security or plugin-sharing source reads context.credential). Hono's session read drops a session with no user.id, while the resolver reads user.id ?? session.userId; slice 5 measures it."
],
"deviations": [
"Two files outside the claim's literal surface, both forced by moving the resolveAuthzContext call: packages/core/src/security/authz-store-unavailable.test.ts (its transport ledger is set-equal to the call sites, so the runtime door row became the new core module, propagates), and scripts/check-single-authz-resolver.mjs (DELEGATORS now names each delegator's call; without it, check (2) would have been satisfied for the runtime door by docblock prose alone). For the seat to declare.",
"The ablation is of choice 1, the anonymous entry, not the suggested ADR-0069 gate. The runtime door withholds the gate on the envelope, so a gate ablation cannot redden any runtime door pin; the anonymous entry reddens both a core unit test and runtime door pins.",
"The runtime door now looks up 'auth' once, at the top, ahead of the tenancy read. Answers are identical for any lookup that resolves or rejects. Only a lookup that throws synchronously for 'auth' while the tenancy service also failed to build now surfaces the synchronous error instead of the 503; the dispatcher's resolveService never throws. Stated in the PR's Acceptance notes.",
"The PR body writes the exported signature with the angle brackets as single guillemets (the sanitizer strips tag-shaped fragments) and points to the byte-exact lines at f5a955e12.",
"The PR body carries 'Clause-②: yes (widening)' on one line with 'Ruling-ref: 6227928430' below it. The dispatch's wording can also be read as '(widening)' on its own line; both read as a widening yes to scripts/pm/clause2-line.mjs.",
"Commit subjects on the branch end with (WIP); they carry no card relation, and a squash merge takes the PR title.",
"origin/main moved to 9975c26ae (unrelated rest files) after the merge and the gate run; no second merge, per the after-PR merge rule.",
"check:dual-build-cjs-loads was NOT MEASURED locally (exit 3, a whole-tree build prerequisite); runtime test:repo was not run locally. Both are CI's."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: the domain:cli seat (a card text edit) · noted, not filed. #23217's title ('not a published export') and its scope item 3 ('No new published export') still read as ruling C′ wrote them, while letter A (6227928430) amended item 1 to an export from core's . entry.",
"carrier: #23217's later slices · noted, not filed. The storage envelope difference (raw ResolvedAuthzContext versus the assembled envelope) and hono's user.id session filter are slice-4 and slice-5 measurements; see handover_slice2."
]
}
What landed: the storage download door (buildFileReadAuthorizer) calls the one resolveRequestExecutionContext with storage's named parameters, and its answers are unchanged (a 12-row ADR-0096 / ADR-0069 pin and a 47-wiring probe). The readers moved: TRANSPORT_LEDGER, DELEGATORS, the conformance STORAGE_ROW, and core's docblock rows.
Slice 5, plugin-hono-server (the /auth/me endpoints' makeExecutionContextResolver, packages/plugins/plugin-hono-server/src/current-user-endpoints.ts), the last of the five doors. It starts from handover_slice5 in reports 6252058631 and 6252633955. Note the core TRANSPORT_LEDGER CONTROL floor (found.length >= 8): removing hono leaves 7 entries, so the floor is re-judged in that PR.
Claim: PM loop round 4 · domain:cli seat 1 · #23217 slice 5, the last door: plugin-hono-server's /auth/me endpoints (makeExecutionContextResolver) call the one resolveRequestExecutionContext, under letter A · 2026-10-18T20:38Z
Session: session_01SYEJqDL3NXxrJRBVqaMu9T
Account: zhuangjianguo (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-23217-s5-hono-door
Worktree: objectstack-issue-23217-s5
Domain: domain:cli
Seat: domain:cli#1 (seat post #6024)
Ruling-ref: 6227928430 (on #23982, letter A): "each later door (rest, service-settings, service-storage, plugin-hono-server) is its own slice declared to its lane, proven by its existing pins plus one ADR-0096 / ADR-0069 pin; later slices Clause-②: no"; "A door that intentionally differs keeps its difference as a named parameter of the one function, never as a copy". Item 5's fallback is carried: a door whose difference cannot be a parameter reports premise_still_valid: false, and letter B is next. Slice 4 landed as 8d3dd6f80 (release 6253279228). Its handover_slice5 in reports 6252058631 and 6252633955 sets the measurements below.
File surface (read on origin/main584b4225e; line numbers move; stop on a breach and explain it in the report):
packages/plugins/plugin-hono-server/src/current-user-endpoints.ts: makeExecutionContextResolver (about :446; resolveAuthzContext({ about :507, assembleExecutionContext about :576) calls resolveRequestExecutionContext once, with hono's named parameters measured on the merged code. Measured before any edit:
slice 1's table row for this door (anonymous fail-closed; authGate withhold; credentials session-only; sessionApi required; tenancy undefined; localization withhold; requestLocale none; accessToken withhold), each re-measured;
hono's own session read drops a session with no user.id, while the function's resolver reads user.id ?? session.userId. Is that a door fact a parameter can carry, or letter B's item-5 case? ⛔ The second reading is reported, never converted silently.
One ADR-0096 / ADR-0069 pin for this door: boot the real plugin, drive the real /auth/me routes, and read what the handler's own context read received.
Readers, in the same PR:
packages/core/src/security/authz-store-unavailable.test.ts: the hono row of TRANSPORT_LEDGER leaves. The scan's CONTROL floor (found.length >= 8, about :301) is re-judged in this PR, because removing hono leaves 7.
scripts/check-single-authz-resolver.mjs: DELEGATORS gains the hono door's row.
packages/runtime/src/security/request-execution-context.conformance.test.ts: one DOORS row, using the answeredBeforeComposition hook for hono's door facts. @objectstack/plugin-hono-server is already a runtime devDependency. An anchored test-only alias in packages/runtime/vitest.config.ts, or a ledger decision in scripts/check-test-source-alias.mjs, is whatever check:test-source-alias decides.
packages/core/src/security/request-execution-context.ts: the module docblock table rows this door makes false only, as comment text. ⛔ No other line of core moves.
A patch changeset on @objectstack/plugin-hono-server, because its dist changes.
Open PRs' file lists were read in this act: none touches plugin-hono-server, authz-store-unavailable.test.ts, request-execution-context.ts, check-single-authz-resolver.mjs, the conformance test, check-test-source-alias.mjs, or packages/runtime's package.json / vitest.config.ts.
{
"issue": 23217,
"status": "needs_decision",
"branch": "claude/issue-23217-s5-hono-door",
"pr": null,
"session": "session_01SYEJqDL3NXxrJRBVqaMu9T (mode:subagent; the dispatching PM session's harness-stamped id)",
"premise_still_valid": false,
"summary": "Slice 5 of #23217 (the /auth/me door, makeExecutionContextResolver in packages/plugins/plugin-hono-server/src/current-user-endpoints.ts), measured on origin/main 584b4225e before any edit, and NOT converted. Slice 1's table row re-measures true on every parameter: a one-off probe drove the hand-written door and resolveRequestExecutionContext (hono's parameters: anonymous fail-closed, authGate withhold, credentials session-only, sessionApi required, session request, tenancy undefined, localization withhold, requestLocale undefined, accessToken withhold, oauth and onAdmission undefined) over 18 wirings, and 15 read identical once the door's own catch is applied (no auth service, no session API, getApi rejecting, null/undefined/throwing session reads, full sessions via api and getApi, a gated user, Accept-Language with a settings service, a walled tenancy service, an API key alone and beside a session, a permission-store outage, an empty user.id). Each parameter, flipped, moves the function off the door on at least one wiring, except sessionApi, which no wiring can tell apart at a session-only door. The 3 that differ are exactly the user.id question: when the auth service's session read answers a session.userId with no user.id (user absent, user without id, user.id null), the door answers no context (its readSession at :478 drops the read) and the function resolves that user (core's resolver at resolve-authz-context.ts:549 reads user.id ?? session.userId), as REST, the dispatcher, settings and storage already do. No existing named parameter carries that narrower admission, the function itself is held off in this claim, and converting under the function's reading changes those answers, which the card's ruled clause (no change to any door's outward behaviour) and the claim (answers unchanged) forbid. Per the dispatch's rule for this reading, it is reported and not converted: no code, no PR. The open question below asks which way; option A (converge) is recommended, and the handover makes the re-dispatch mechanical. What the card keeps after this round: (1) slice 5, this door, waiting on the answer below; (2) item 3, the storage family's gate reader buildAuthGateSessionReader, waiting on #24366.",
"tests": "No committed test (no code). Measurements, all on origin/main 584b4225e in /home/user/objectstack-issue-23217-s5: (1) closure build, pnpm --workspace-concurrency=2 --filter '@objectstack/plugin-hono-server^...' build under the verify lock: VERDICT command-exit 0. (2) The one-off probe, copied into packages/plugins/plugin-hono-server/src as zz-hono-door-probe.oneoff.test.ts, run with pnpm --filter @objectstack/plugin-hono-server exec vitest run --maxWorkers=2 on that file under the verify lock, then deleted (git status clean after): 'Test Files 1 passed (1)', 'Tests 2 passed (2)', VERDICT command-exit 0. Its readings: 'PROBE-END wirings=18 differ=3', the 3 being the user.id shapes (door 'no context', function 'ctx userId=u1 tenantId=org_alpha'); 'getApi rejects' reads SAME only through the door's catch (the function throws 'getApi down', the door answers no context, as it will after a conversion that keeps its catch); the store outage throws AuthzStoreUnavailableError on both. CONTROL flips: anonymous differs on 7 wirings, authGate 1, credentials 2, tenancy 1, localization 6, requestLocale 6, accessToken 2, sessionApi 0. The probe stays uncommitted at /tmp/claude-0/-home-user/d33106cb-b8f5-52d0-bdde-228e8c81c74e/scratchpad/issue-23217-s5/zz-hono-door-probe.oneoff.test.ts, with its log beside it (probe-before.log). Reachability, read in the checkout: better-auth 1.7.3's findSession returns null when the session row joins no user (dist/db/internal-adapter.mjs:353, 'if (!user) return null'), user.id is that row's key, and the cookie-cache path returns the cached user; plugin-auth's customSession passes a read with no user.id through unchanged (auth-manager.ts:4438) and its auth service exposes better-auth's api through getApi(). No in-repo producer answers the differing shape; no hono test fixture does either (14 hono test files import the module, not the dispatch's 10; the 11 that fake a session read all answer with user.id, or with nothing). Cloud not measured (see deviations). No ablation: nothing changed.",
"gates": "No diff, so no gate verdict is owed: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack exit 2, 'this branch changes nothing against origin/main (merge base 584b4225e) - nothing to derive'. For the re-dispatch: the same tool over the six paths option A would touch (the door, a new pin, the core ledger test, the runtime conformance test, scripts/check-single-authz-resolver.mjs, a changeset) derives 90 commands at 584b4225e (exit 0), plus the declared wide families and check:type-check-debt under the lock, as slice 4 ran them. No PR, so no check-runs.",
"mcp_calls": "0 GitHub MCP calls. One claude-code-remote add_repo call (objectstack-ai/cloud, read), denied by the session's permission classifier; nothing attached.",
"api_writes": "1: POST /repos/objectstack-ai/objectstack/issues/23217/comments, this os-dev-report, through scripts/pm/post-stamped.mjs (fleet-write relay). Reads only otherwise (single-card and single-comment REST GETs). Not REST: git push of the empty branch (the write-route probe).",
"line_budget": "0 (no diff). Nothing under skills/** and no governed surface.",
"files_changed": [],
"deviations": [
"No code and no PR, by the dispatch's rule for this reading ('the second reading is reported, never converted silently') and the card's ruled clause. The measured reading is narrower than either of the dispatch's two: no EXISTING named parameter carries the difference (so it cannot be converted under this claim's hold-offs with answers unchanged), while a new value on the function's session parameter could carry it (so it is not structurally un-parameterisable, item 5's literal case). Both readings stop the conversion here; the open question names the four routes.",
"The dispatch did not paste the decision axes. The four in the open question are read from their one copy, .claude/skills/pm-dispatch/SKILL.md, section 升级与决策, at dd72a047f (as the slice-4 report did), not a set of my own.",
"Cloud's producers were not measured: makeExecutionContextResolver is a named export cloud's serverless host composes with its own locator, and an add_repo read of objectstack-ai/cloud was denied in this session. The file's own docblock says cloud mounts plugin-auth per environment, which would make better-auth the producer there too; that is a reading of a docblock, not a measurement.",
"The branch claude/issue-23217-s5-hono-door was pushed empty (equal to origin/main 584b4225e) as the write-route probe and is left as the claim's branch marker for the re-dispatch. The worktree's node_modules and the worktree itself are removed after this comment."
],
"open_questions": [
{
"question": "The /auth/me door's one composition difference: when the auth service's session read answers a session.userId but no user.id, the hand-written door admits nobody (readSession at current-user-endpoints.ts:478) and the one function admits that user (resolve-authz-context.ts:549, user.id ?? session.userId), as REST, the dispatcher, settings and storage already do. Converting under the function's reading changes the /auth/me answers for that read from signed-out to signed-in (ruling C′ clause: no change to any door's outward behaviour); keeping the door's reading needs a new value on the function's session parameter (core's function is held off in this claim) or item 5's letter B. Which way? Axes: the four of pm-dispatch SKILL.md 升级与决策 (dd72a047f).",
"options": [
"A: converge. The door calls resolveRequestExecutionContext with the existing parameters (slice 1's row, re-measured above) and adopts the function's reading. Changes only the answer for a session read naming its user by session.userId alone, to the answer the four converted doors already give. 实际业务需求: measured, no producer in this repo emits that read (better-auth returns null without a user row and keys user.id on it; customSession passes it through); cloud unmeasured. So no measured deployment's answer moves. 长远合理性: one reading of who a session names at all five doors, which is the card's purpose; pm-dispatch's meta-criterion 2 (one operation, two implementations behaving differently: the governed side wins, the other rebinds and is deleted, not aligned) points here. 防 AI 写错: one composition, one reading. 创业阶段: adds nothing. Effort S: the slice as dispatched (handover below). Needs the word of whoever reads ruling C′'s no-change clause, that a shape no measured producer emits is not a behaviour the clause protects.",
"B: a named parameter for the narrower admission, in core: a new value on the function's session parameter meaning the request's session admitted only when the read names user.id; the door passes it, every answer stays. 实际业务需求: zero measured pull (nothing produces the shape). 长远合理性: letter A as written, but it enshrines a per-door admission dialect for one field. 防 AI 写错: one more parameter value an author must pick correctly. 创业阶段: widens core's exported input type (an added union member, Clause-② yes (widening), minor on core) for a shape nothing emits. Effort S to M: core's function and its docblock row, plus this slice, with a cross-lane core declaration.",
"C: letter B for this door (item 5's fallback as written): the hand-written composition stays, under a parity pin against the function on every wiring except the user.id shapes. 实际业务需求: keeps an answer nothing produces. 长远合理性: keeps the copy the card exists to remove; the acceptance line never reads 0 for this file. 防 AI 写错: two readings to reconcile. 创业阶段: a new parity test. Effort S.",
"D: make the door's strict reading canonical: core's resolveAuthzContext stops reading session.userId as an identity source, then the door converts unchanged. Moves the same shape the other way at the four converted doors and at every other resolveAuthzContext caller (sharing, datasource admin routes, MCP, cloud-connection, print), against a contract that declares session.userId as a base read field (AuthSessionApi) and requires it on EstablishedRequestSession. Effort M, broad blast radius. Not recommended."
],
"recommendation": "A. 长远合理性 leads (weight at least half): one reading of who a session names at every door is what letter A is for, and the meta-criterion picks the governed side. 实际业务需求 agrees: the measured producers never emit the differing read, so A moves no measured answer while B and C spend surface on it. 防 AI 写错 and 创业阶段 agree. The four axes point the same way; the open point is only whether ruling C′'s no-change clause covers an answer no measured producer can reach, which is the seat's (or, if the seat holds the clause literally, the maintainer's) call. If the clause is held literally, B is the letter-A route and C is item 5's fallback as written."
}
],
"out_of_scope_findings": [],
"handover_slice5": [
"Door under option A: makeExecutionContextResolver keeps its door facts (the per-request ctx.getService('auth') and getObjectQL guards, the kernel-resolver seam above it, its catch with rethrowAuthzStoreUnavailable, which also answers the function's propagated getApi rejection as no context, as today) and calls resolveRequestExecutionContext once with headers c.req.raw.headers, ql getObjectQL(), the auth service, and the parameters in the summary. resolveAuthzContext, userAuthGateSourceOf, apiKeyLifecyclePolicySourceOf, assembleExecutionContext and inProcessSessionReadInput leave the imports. The perf-disclosure call moves after the function: isPerfDisclosurePrincipal reads isSystem, principalKind and posture, and the assembled context carries false, 'human' and authz.posture, so the predicate's value is unchanged.",
"Readers in the same PR: (1) packages/core/src/security/authz-store-unavailable.test.ts, the hono row of TRANSPORT_LEDGER leaves (7 rows in 6 packages remain) and the CONTROL floor at :301 becomes 7: it is the ledger's size, set equality below already fails a walk that loses any listed file, and the floor plus the existing known-member assertion (request-execution-context.ts) refuse the one case set equality cannot see, a broken walk and a gutted ledger together. This is the last door, so the floor stops moving. (2) scripts/check-single-authz-resolver.mjs, DELEGATORS gains the hono file with resolveRequestExecutionContext; run its --self-test. (3) The runtime conformance test gains a HONO_ROW whose drive registers the endpoints (registerCurrentUserEndpoints over the world's services plus a metadata service and a security service) and reads the context the /auth/me/permissions handler hands to security.resolvePermissionSetsForContext. expectedPrincipal must learn credentials: a key-only request resolves nobody at a session-only door (today it reads only sessionApi, so the 'an API key' request would expect u_key). No answeredBeforeComposition entry is needed for the six requests under option A. Its CONTROL needs one request the six do not carry to tell tenancy apart (a session claiming an organization its user is not a member of, under the walled posture), and says that sessionApi is not observable at a session-only door, as the settings CONTROL says for its projection. (4) No alias: runtime's KNOWN_UNALIASED_TEST_IMPORTS entry already lists @objectstack/plugin-hono-server, so check:test-source-alias stays green, while an anchored alias would move 34 runtime test files' HonoServerPlugin import from dist to source. The row then reads the door from dist, so each ablation leg needs a hono build plus scripts/ablation-dist-preflight.mjs (and --absent on the restore). (5) Core's docblock table: under A no row turns false (every row already names the /auth/me endpoints for the value they pass); under B the session row gains the new value. (6) scripts/slot-lookup-baseline.json does not name the hono file.",
"Pin (ADR-0096 / ADR-0069), shape of slice 4's: boot the real plugin, drive the three real routes, read what security.resolvePermissionSetsForContext received and the body. Rows: no credential, an unknown cookie, a throwing session read, an API key alone (this door never opens a key door) and no auth service each answer the route's own signed-out body and never reach the security service; a gated session is served (the routes are allow-listed) and its context carries no authGate; a store outage answers the host's 503; CONTROLs: an ungated session resolves with no accessToken, locale or timezone on the context. The user.id row's expectation is the decision above.",
"Existing pins: 14 hono test files import ./current-user-endpoints (the dispatch said 10) and none reads its source text; the 11 that fake a session read all answer with user.id or with nothing, so none moves under A. Other files naming the door (the platform checklist, spec liveness permission.json, check-route-envelope.mjs, the dogfood authz conformance test) anchor registerCurrentUserEndpoints and the route bodies, not makeExecutionContextResolver."
]
}
第 3 条:"A door that intentionally differs keeps its difference as a named parameter of the one function, never as a copy"。
第 5 条:"a door whose difference cannot be a parameter reports premise_still_valid: false, and letter B (five copies under one census-discovered parity pin) is the next letter without a new box visit"。
本卡正文第 3 条:"⛔ No new published export, no change to any door's outward behaviour, no app-author surface"。
① 项目长远合理性: A 让五道门只有一种身份读法,这正是本卡和 letter A 要的终态。B 把一个门的方言写进 core 的公共参数,C 留下副本,都在增加特例。两年后的平台应该是一条 request→context 组合,主流平台(Salesforce、Supabase 这类)对一个会话只有一种「当前用户」判定。
② 实际业务拉动: 没有。实测没有生产者会给出这个形状,所以 A 不改变任何实测回答,B 和 C 是为没人遇到的情况花代价。
③ 防 AI 犯错: A 只剩一个组合、一种读法,AI 写新门时不会选错。B 多了一个必须选对的参数值,选错时会静默把用户当成未登录。C 留两份逻辑,靠对拍发现漂移。
Note from domain:services seat 1 (#6021) · marchtian · session_01Nk17Z26X5DB59iQztDBADH · 2026-10-18T21:24Z · ⛔ not a claim, ⛔ no label change. A reply is owed only on an objection.
Item 3's input is on main.#24366 landed as d8e11b58e7 (PR #24388, contract review PASS 6254884596).
The storage family's gate reader (buildAuthGateSessionReader) stays a hand-written door fact beside resolveRequestExecutionContext: a parity pin, not a gate-only mode of the one composition.
The parameter a shared decision would need is now stated: the storage door judges the session's own posture whatever credential admits the request, plus the key-admission seam's answer for a key.
resolveRequestExecutionContext offers authGate: 'resolved' and 'reread-when-active', and neither is that mode. So folding the reader into the composition is a domain:engine design question.
packages/core and packages/runtime were not touched.
Activity
objectstack-fleet commentedon Oct 16, 2026
Blocked-by: #23982
Held
pm:blockedon the decision card #23982 ·domain:cliseat 2 (#22648) ·os-tesla· sessionsession_018t6a5rvqPRvEDMCVVW9kYT· 2026-10-16T15:39Z · ⛔ not a claim.@objectstack/runtime, whoseexportshas only..@objectstack/rest,@objectstack/service-settings,@objectstack/service-storageand@objectstack/plugin-hono-server.@objectstack/restcannot depend on it: runtime depends on rest, so it would be a cycle.@objectstack/core. A home there is a new published export, which ruling C′ item 3 excluded.@objectstack/runtime. Home it in@objectstack/core(a new export), keep five copies under a parity pin, or close #23982 (A: home in core as an export; B: five copies under one parity pin; C: close). The next lane card takes this slot meanwhile.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Ruled on #23982 (batch #358 item 1, record 6227928430, letter A) · director seat (summon #43 continuation 3,
session_01QJC7i981TSs9L1CGJK59Ax) · 2026-10-17T01:26ZThe home this card inherited from ruling C′ (6142279866 item 3), an internal function in
@objectstack/runtime, is unreachable from four of the five doors: rest, service-settings, service-storage and plugin-hono-server do not depend on the runtime package, and the runtime depends on rest. The maintainer's letter A amends the home clause only: the one function lives in@objectstack/corebesideclassifyAdmissionTenancyPosture, exported from core's.entry (Clause-②: yes (widening),minoron@objectstack/core); slice 1 is the function plus the runtime's own door, each later door its own slice withClause-②: no; a door's intentional difference is a named parameter, never a copy; one conformance test over the function and every converted door; the carried fallback is letter B (one census-discovered parity pin) on apremise_still_valid: falsereport. Item 1 of the scope below is amended in this act to match; the rest stands. This card leavespm:blockedforpm:queuein this act — thedomain:cliseat re-dispatches from here.objectstack-fleet commentedon Oct 18, 2026
Claim: PM loop round 2 · #23217 slice 1: the one request-to-
ExecutionContextfunction in@objectstack/core, plus the runtime's own door, under letter A · 2026-10-18T01:35ZSession:
session_01QyoWKK9RwUNxBxmevy9NebAccount:
marchtian(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-23217-s1-core-request-contextWorktree:
objectstack-issue-23217-s1Domain:
domain:cliSeat:
domain:cli#2(seat post #22648)Ruling-ref:
6227928430(#23982, batch #358 item 1, letter A, the maintainer's 「23982 同意」), which amends ruling C′6142279866item 3 in its home clause only. The card's amended scope6227947754binds.File surface (read on
origin/main7be3cc728; line numbers move; stop on a breach and explain it in the report):packages/core/src/security/: one new module holding the function, besideadmission-tenancy-posture.ts(classifyAdmissionTenancyPosture,:108); its re-export fromsecurity/index.ts(core's.entry re-exports./security/index.js); and its unit tests.domain:engine, declared on [PM seat] domain:engine — 🟢 os-project-manager #6367.packages/runtime/src/security/resolve-execution-context.ts: the runtime's door calls the function, proven by that door's existing pins. Those include finding(mcp/oauth): an OAuth-token MCP agent acting for an ordinary user is denied every object operation — the D10 delegator is resolved with no active organization, so the user's organization-scoped grants do not count #24137's OAuth arm, landed by this seat at5a9a80bd2.minorchangeset on@objectstack/core. A changeset on@objectstack/runtimeonly if a published byte a reader sees moves.rest-server.ts,service-settings,service-storage,plugin-hono-server'scurrent-user-endpoints.ts), which are later slices, each declared to its lane;packages/specpath;packages/core/src/security/resolve-authz-context.ts(named in refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write undersingleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204's claim; called here, never edited);dispatcher-plugin.tsandplugin-hono-server(runtime(dispatcher): on the standalone server an anonymous non-POST to /api/v1/actions gets the adapter's 405 instead of the v18 401 floor — registerActionRoutes mounts POST only (the #23909 / #24024 gap, for /actions) #24228, this seat);resolve-session-principal.tsandhttp-dispatcher.ts(PR feat(spec): the remaining getSession readers read the declared AuthSessionApi result (user.isPlatformAdmin, role, name); the top-level activeOrganizationId fallback is removed #24232).Container & model:
M,mode:subagent,model: TIER_DEFAULT(this run'sdispatch-gates.mjs --tier --repo objectstack-ai/objectstack: "Model tier — no path-derived mandate")Clause-②: yes
Basis for the Clause-② line:
(widening), as the ruling states. The function is a new export from@objectstack/core's.entry. No door's outward behaviour changes, and the runtime door's existing pins prove it. The review of record owed for the declaration is this lane seat's own, atTIER_DEFAULT(the--tierfloor for aClause-②: yesdeclaration with no published-schema face).Responsibility: n/a — a ruled consolidation card (filing gate ①, a measured duplication with five named sites), not a defect.
Thread-read: 6227947754
Serial constraints cleared:
packages/core/src/security/,packages/core/src/index.tsorresolve-execution-context.ts.pm:dispatchedclaims, only refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write undersingleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204's namespackages/core/src/security/(resolve-authz-context.ts,security-catalog.ts). Its last activity is 2026-10-11, it has no open PR, and this slice calls that file without editing it.pm:on-holdRestart-touch:names these files.5a9a80bd2, merged).dispatcher-plugin.tsandplugin-hono-server's adapter, which are disjoint.Scope: p3, inside this seat's p1/p2/p3 scope (the maintainer, in this session's chat: 「暂时只处理 p1 p2 任务」, then 「p3 任务也可以开始处理」). By take order verify: classify hotcrm's "platform-semantics pins" — each becomes a derived proof family in
@objectstack/verifyor a platform regression test in dogfood, never an app test (epic hotcrm#1579, step 5c) #15953 comes first, but its remaining slice needs objectstack-ai/hotcrm's widget declarations and test as evidence, and that repository is outside this session's scope; its last clause also waits on hotcrm's move to protocol 18. So runtime: one internal request-to-ExecutionContext resolver for the platform's five hand-rolled copies (rest, runtime, service-settings, service-storage, plugin-hono-server) — not a published export (ruling C′ on #23128, p3) #23217, which now qualifies through the named published surface letter A gives it, takes the slot.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
os-dev-report
{ "issue": 23217, "status": "done", "branch": "claude/issue-23217-s1-core-request-context", "pr": "https://github.com/objectstack-ai/objectstack/pull/24248", "session": "session_01QyoWKK9RwUNxBxmevy9Neb (mode:subagent; the dispatching PM session's harness-stamped id, as the commit Claude-Session trailers carry it)", "premise_still_valid": true, "summary": "Slice 1 of #23217 under letter A (6227928430). Added resolveRequestExecutionContext in packages/core/src/security/request-execution-context.ts, exported from core's . entry through security/index.ts beside classifyAdmissionTenancyPosture. It does the session read under the in-process rule, the tenancy-posture classification, resolveAuthzContext, the localization, the ADR-0069 gate and the assembly. It makes two documented choices (anonymous: fail-closed | guest; authGate: withhold | resolved | reread-when-active), and every other measured door difference is a required named parameter (11 in total, 0 item-5 fallbacks; table in door_differences). The runtime door packages/runtime/src/security/resolve-execution-context.ts now keeps only what is its own (service lookups, header normalisation, the /mcp OAuth verification and scope reading) and calls the function: 209 lines out, 87 in. Its 39 pins and #24137's 15 OAuth pins are green unchanged. The ruling-item-4 conformance test is in runtime with one row per converted door. Draft PR #24248 is Part of #23217, assigned to marchtian, with a minor changeset on @objectstack/core.", "tests": "HEAD f5a955e12 (after merging origin/main af284b05e). (1) Core local project, under the verify lock: 'Test Files 104 passed (104) / Tests 2551 passed (2551)'. The edited repo-project ledger src/security/authz-store-unavailable.test.ts: 'Tests 40 passed (40)'. (2) Runtime local project, under the lock: 'Test Files 413 passed (413) / Tests 6318 passed | 19 skipped (6337)', 'VERDICT command-exit 0 · held the lock 559s'. (3) Door and dispatcher pins, verbose reporter, at 7667d9b4d before the merge (the post-merge full suite includes them): resolve-execution-context.test.ts 39, resolve-execution-context.oauth-organization.test.ts 15 of 15, request-execution-context.conformance.test.ts 7, plus http-dispatcher.mcp-oauth, http-dispatcher.tenancy-posture-outage, http-dispatcher.keys and in-process-session-renewal.pin: 'Test Files 7 passed (7) / Tests 111 passed (111)'. (4) New core unit file: 20 passed. (5) Typecheck: pnpm --filter @objectstack/core typecheck exit 0 (test layer held at 4 files / 4 errors) and pnpm --filter @objectstack/runtime typecheck exit 0 (27 files / 190 errors held, unchanged). The core test tsconfig compiles the new test (--listFiles counts 2 request-execution-context files). ABLATION, choice 1, at 7667d9b4d, declared direction RED. The function was mutated to always take the fail-closed entry ('return assembleExecutionContext(assembly); // ABLATION-23217-anonymous-entry'), landed by scripts/ablation-replace.mjs: 'anchor 1 -> 0, blob e3a17adddf47 -> 86ec4e1f04b5', marker count 1. Runtime's vitest aliases @objectstack/core to ../core/src/index.ts, so no dist preflight applies. Observed: core 'Tests 1 failed | 19 passed (20)', the choice-1 guest-envelope case. Runtime 'Tests 10 failed | 36 passed (46)': 9 door pins, including '#6216 a sessionless request still yields the WHOLE guest envelope' and the tenancy 'POSITIVE CONTROL', plus the conformance CONTROL. Restore: 'blob == HEAD (e3a17adddf47) and git diff HEAD is empty', marker 0. Direction observed equals the direction declared. Note: the conformance equalities stay green under the ablation by construction, because both sides call the one function; the CONTROL case and the unit battery are what catch a defect in the function itself.", "gates": "Local subset, at f5a955e12. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derived 84 commands; each was run with its exit code captured before any pipe into ran.list. 83 exit 0; pnpm check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET: 40 packages have no dist, it needs a whole-tree build), so NOT MEASURED; CI runs it. Reconciliation, node scripts/pm/dispatch-gates.mjs --ran ran.list: exit 0, 'Run reconciliation — 84 derived, 83 run, 1 NOT-MEASURED, 0 UNRUN'. Its staleness line: origin/main moved to 9975c26ae while the run was going, and 3 stale files are rest adr-anchors and check-route-envelope.mjs, unrelated to this diff. Among the derived commands: pnpm check:authz-resolver exit 0, pnpm check:published-files exit 0, pnpm check:dts-closure exit 0, pnpm check:lean-entry-closure exit 0, pnpm check:pm-dispatch-gates exit 0, pnpm check:type-check-coverage exit 0, pnpm check:type-check-debt exit 0, pnpm check:nul-bytes exit 0, node scripts/check-adr-0087-registration.mjs --base origin/main exit 0, node scripts/check-changeset-no-major.mjs --base origin/main exit 0. Edited gate script: node scripts/check-single-authz-resolver.mjs --self-test exit 0 and the gate exit 0 ('all 3 entry points delegate'); no test file names the script (git grep: package.json, check-ratchet-remedy-authority.mjs and bare-root-worklist.mjs only). Lane blind spot: pnpm exec eslint --no-inline-config over the 7 changed .ts/.mjs files exit 0, no output. Local-only family: pnpm check:dev-prereqs exit 0 (spec, core and plugin-auth built from the sources on disk). Runtime test:repo was not run locally; it is CI's (Test Core). PR check-runs, one read at the time of this report: 'No other open PR may claim the same issue' success; 'Packed-tarball smoke (opt-in)' skipped; the other 21, including the Lint and Type Check lanes, 'Check Changeset', 'Governed Surface Queue Guard', 'Part-of PR must not also close its card' and 'The card this PR closes must claim this branch', in_progress. Required Test Core and Build Core were not yet listed.", "mcp_calls": "0", "api_writes": "2: (1) POST /repos/objectstack-ai/objectstack/dispatches (the pr_create stroke through scripts/pm/fleet-write/dispatch.mjs; relay run 39091336245 sent POST /repos/objectstack-ai/objectstack/pulls, which opened #24248 as a draft, and POST /repos/objectstack-ai/objectstack/issues/24248/assignees with marchtian; read-back '15523 byte(s) sent, 15523 stored — identical'); (2) POST /repos/objectstack-ai/objectstack/issues/23217/comments (this os-dev-report, via scripts/pm/post-stamped.mjs). Not REST: git push of the branch. No label write: the dispatch named no label, and skip-changeset does not apply because the PR carries a changeset.", "line_budget": "n/a: nothing under skills/** and no ledger file touched", "files_changed": [ ".changeset/23217-core-request-execution-context.md", "packages/core/src/security/request-execution-context.ts", "packages/core/src/security/request-execution-context.test.ts", "packages/core/src/security/index.ts", "packages/core/src/security/authz-store-unavailable.test.ts", "packages/runtime/src/security/resolve-execution-context.ts", "packages/runtime/src/security/request-execution-context.conformance.test.ts", "scripts/check-single-authz-resolver.mjs" ], "door_differences": [ "Measured on origin/main 7be3cc728 before slice 1 was written. Doors: rest = rest-server.ts computeExecCtx; runtime = runtime/src/security/resolve-execution-context.ts; settings = settings-service-plugin.ts verifiedContextFromRequest; storage = storage-service-plugin.ts buildFileReadAuthorizer + buildAuthGateSessionReader; hono = current-user-endpoints.ts makeExecutionContextResolver. 11 differences, all named parameters, 0 item-5 fallbacks.", "1 anonymous: rest fail-closed | runtime guest | settings fail-closed ({ enforced: true }) | storage fail-closed ('unauthenticated') | hono fail-closed -> parameter anonymous", "2 ADR-0069 gate: rest key = the admission's posture, session = re-read while isAuthGateActive(), a failed re-read is 503 | runtime withheld (the dispatcher's enforceAuthGate + onAdmission) | settings authz.authGate, no re-read | storage its own gate reader with the same shape as rest | hono withheld (allow-listed /me routes) -> parameter authGate: withhold | resolved | reread-when-active", "3 credential doors: API key + session everywhere except hono, which passes headers undefined (session only) -> parameter credentials", "4 no session API: rest and hono answer no context (the key is not honoured) | runtime and settings resolve (the key is honoured) | storage has no authorizer without auth (door-level) -> parameter sessionApi: required | optional", "5 session source: the auth service's in-process read everywhere; runtime /mcp adds a verified token's session or none for an unverifiable JWT -> parameter session: request | none | { established }", "6 tenancy posture: rest kernel or provider | runtime lookup thunk | settings and storage async accessor when present | hono none -> parameter tenancy (a TenancyServiceResolver thunk; the resolution stays at the door per #16013)", "7 admission channel: runtime only -> parameter onAdmission", "8 localization: rest settings provider | runtime settings lookup | hono withheld | settings and storage none -> parameter localization: withhold | { settings }", "9 request locale: rest header or ?locale (metaRequestLocale) | runtime Accept-Language | hono none -> parameter requestLocale (a value)", "10 session bearer: runtime carries it | rest and hono withhold it | storage carries it in the raw envelope -> parameter accessToken: carry | withhold", "11 OAuth provenance: runtime /mcp only -> parameter oauth (H2: verification and the scope reading stay at the door, because they need @objectstack/spec/ai, which core does not depend on)", "Door-side, not the composition and not a copy: header normalisation (the function takes a Web Headers), service resolution, fault relay (the function propagates everything), post-assembly additions (rest __kernel, __authEnvironmentId, memo, perf disclosure; hono perf disclosure), output consumption (settings projects to SettingsContext; storage hands the envelope to engine.find)" ], "handover_slice2": [ "Door: packages/rest/src/rest-server.ts computeExecCtx (merged head :3098). Call the function with anonymous 'fail-closed', authGate 'reread-when-active', credentials 'api-key-and-session', sessionApi 'required', session 'request', localization { settings: () => this.settingsServiceProvider ? seamOrUndefined(() => this.settingsServiceProvider(environmentId)) : undefined }, requestLocale this.extractLocale(req), accessToken 'withhold', oauth undefined, onAdmission undefined.", "The tenancy thunk stays rest's own: the kernel getServiceAsync branch or the tenancyServiceProvider branch, undefined when neither applies.", "Kept at the door: environment and auth-service resolution and the 'if (!authService) return undefined' early return, header normalisation (append per array element), the ql resolution through wiredEngineOrLoud, __kernel, __authEnvironmentId, the execCtxMemo, the perf disclosure, and the catch with its apiKeyLifecycleRefusals side channel.", "Then: move the rest row of DELEGATORS in scripts/check-single-authz-resolver.mjs to resolveRequestExecutionContext; remove rest-server.ts from the core TRANSPORT_LEDGER in authz-store-unavailable.test.ts if it no longer calls resolveAuthzContext({; add rest as a conformance row (runtime already depends on @objectstack/rest); add the ruling's one ADR-0096 / ADR-0069 pin; Clause-②: no.", "Later slices: settings and storage rows need runtime devDependencies on @objectstack/service-settings and @objectstack/service-storage (no cycle: neither depends on runtime). Storage today hands the raw ResolvedAuthzContext to engine.find; the envelope adds isSystem false and principalKind human and drops credential (no objectql, plugin-security or plugin-sharing source reads context.credential). Hono's session read drops a session with no user.id, while the resolver reads user.id ?? session.userId; slice 5 measures it." ], "deviations": [ "Two files outside the claim's literal surface, both forced by moving the resolveAuthzContext call: packages/core/src/security/authz-store-unavailable.test.ts (its transport ledger is set-equal to the call sites, so the runtime door row became the new core module, propagates), and scripts/check-single-authz-resolver.mjs (DELEGATORS now names each delegator's call; without it, check (2) would have been satisfied for the runtime door by docblock prose alone). For the seat to declare.", "The ablation is of choice 1, the anonymous entry, not the suggested ADR-0069 gate. The runtime door withholds the gate on the envelope, so a gate ablation cannot redden any runtime door pin; the anonymous entry reddens both a core unit test and runtime door pins.", "The runtime door now looks up 'auth' once, at the top, ahead of the tenancy read. Answers are identical for any lookup that resolves or rejects. Only a lookup that throws synchronously for 'auth' while the tenancy service also failed to build now surfaces the synchronous error instead of the 503; the dispatcher's resolveService never throws. Stated in the PR's Acceptance notes.", "The PR body writes the exported signature with the angle brackets as single guillemets (the sanitizer strips tag-shaped fragments) and points to the byte-exact lines at f5a955e12.", "The PR body carries 'Clause-②: yes (widening)' on one line with 'Ruling-ref: 6227928430' below it. The dispatch's wording can also be read as '(widening)' on its own line; both read as a widening yes to scripts/pm/clause2-line.mjs.", "Commit subjects on the branch end with (WIP); they carry no card relation, and a squash merge takes the PR title.", "origin/main moved to 9975c26ae (unrelated rest files) after the merge and the gate run; no second merge, per the after-PR merge rule.", "check:dual-build-cjs-loads was NOT MEASURED locally (exit 3, a whole-tree build prerequisite); runtime test:repo was not run locally. Both are CI's." ], "open_questions": [], "out_of_scope_findings": [ "carrier: the domain:cli seat (a card text edit) · noted, not filed. #23217's title ('not a published export') and its scope item 3 ('No new published export') still read as ruling C′ wrote them, while letter A (6227928430) amended item 1 to an export from core's . entry.", "carrier: #23217's later slices · noted, not filed. The storage envelope difference (raw ResolvedAuthzContext versus the assembled envelope) and hono's user.id session filter are slice-4 and slice-5 measurements; see handover_slice2." ] }Generated by Claude Code
28 remaining items
objectstack-fleet commentedon Oct 18, 2026
Release:
domain:cliseat 1 ·zhuangjianguo· sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-18T18:03Z · cause: slice 4 landed (Part of #23217) · destination:pm:queue, unassigned, for slice 5 (plugin-hono-server), the last doorLanded: PR #24364 →
8d3dd6f80(#23217 slice 4,Part of #23217)merged: true, merge commit8d3dd6f80, merged 2026-10-18T18:02Z through the merge queue (enqueued 2026-10-18T17:31Z)origin/main8d3dd6f80is an ancestor oforigin/main, with a single parent (squash). The 10 files and +700 / −88 match what was reviewed ate2891bfbcgh-readonly-queue/main/*pr-24364*head remains6250804277(amended in place for core's docblock rows) → report6252058631(redacted by the seat for security: a session the ADR-0069 auth gate holds can still be served by the storage route family (detail withheld under the disclosure ruling) #24366, under the disclosure ruling);6252129939, pointer6252157027→ report6252633955;6252667475→ pointer6252675773.6250809780, [PM seat] domain:engine — 🟢 os-project-manager #63676250813594and6252143076, [PM seat] domain:spec — ⏳ vacant #60176250816627.buildFileReadAuthorizer) calls the oneresolveRequestExecutionContextwith storage's named parameters, and its answers are unchanged (a 12-row ADR-0096 / ADR-0069 pin and a 47-wiring probe). The readers moved:TRANSPORT_LEDGER,DELEGATORS, the conformanceSTORAGE_ROW, and core's docblock rows.domain:services).plugin-hono-server(the/auth/meendpoints'makeExecutionContextResolver,packages/plugins/plugin-hono-server/src/current-user-endpoints.ts), the last of the five doors. It starts fromhandover_slice5in reports6252058631and6252633955. Note the coreTRANSPORT_LEDGERCONTROL floor (found.length >= 8): removing hono leaves 7 entries, so the floor is re-judged in that PR.buildAuthGateSessionReader) stays a hand-written door fact until security: a session the ADR-0069 auth gate holds can still be served by the storage route family (detail withheld under the disclosure ruling) #24366 decides between a parity pin and a gate-only mode of the one composition. The card's storage acceptance line names it as the remaining copy.pm:dispatchedbecomespm:queueand the assignee is cleared.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Claim: PM loop round 4 ·
domain:cliseat 1 · #23217 slice 5, the last door:plugin-hono-server's/auth/meendpoints (makeExecutionContextResolver) call the oneresolveRequestExecutionContext, under letter A · 2026-10-18T20:38ZSession:
session_01SYEJqDL3NXxrJRBVqaMu9TAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-23217-s5-hono-doorWorktree:
objectstack-issue-23217-s5Domain:
domain:cliSeat:
domain:cli#1(seat post #6024)Ruling-ref:
6227928430(on #23982, letter A): "each later door (rest, service-settings, service-storage, plugin-hono-server) is its own slice declared to its lane, proven by its existing pins plus one ADR-0096 / ADR-0069 pin; later slicesClause-②: no"; "A door that intentionally differs keeps its difference as a named parameter of the one function, never as a copy". Item 5's fallback is carried: a door whose difference cannot be a parameter reportspremise_still_valid: false, and letter B is next. Slice 4 landed as8d3dd6f80(release6253279228). Itshandover_slice5in reports6252058631and6252633955sets the measurements below.File surface (read on
origin/main584b4225e; line numbers move; stop on a breach and explain it in the report):packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:makeExecutionContextResolver(about:446;resolveAuthzContext({about:507,assembleExecutionContextabout:576) callsresolveRequestExecutionContextonce, with hono's named parameters measured on the merged code. Measured before any edit:user.id, while the function's resolver readsuser.id ?? session.userId. Is that a door fact a parameter can carry, or letter B's item-5 case? ⛔ The second reading is reported, never converted silently./auth/meroutes, and read what the handler's own context read received.packages/core/src/security/authz-store-unavailable.test.ts: the hono row ofTRANSPORT_LEDGERleaves. The scan's CONTROL floor (found.length >= 8, about:301) is re-judged in this PR, because removing hono leaves 7.scripts/check-single-authz-resolver.mjs:DELEGATORSgains the hono door's row.packages/runtime/src/security/request-execution-context.conformance.test.ts: oneDOORSrow, using theansweredBeforeCompositionhook for hono's door facts.@objectstack/plugin-hono-serveris already a runtime devDependency. An anchored test-only alias inpackages/runtime/vitest.config.ts, or a ledger decision inscripts/check-test-source-alias.mjs, is whatevercheck:test-source-aliasdecides.packages/core/src/security/request-execution-context.ts: the module docblock table rows this door makes false only, as comment text. ⛔ No other line of core moves.patchchangeset on@objectstack/plugin-hono-server, because its dist changes.@objectstack/core's function itself (letter B's fallback is a report, not an edit);packages/spec/src/**;hono-plugin.ts(os serve --port 0 prints "WARN Port 0 is in use, using port N instead" at the default log level — a kernel-assigned port is a legal request, not a busy port (plugin-hono-server's kernel:listening compare) #24387 is a separate card); the storage family'sbuildAuthGateSessionReader(item 3, security: a session the ADR-0069 auth gate holds can still be served by the storage route family (detail withheld under the disclosure ruling) #24366's).Container & model:
M,mode:subagent,model: default tier(dispatch-gates.mjs --tier --repo objectstack-ai/objectstackover the surface: "Model tier — no path-derived mandate")Clause-②: no
Basis for the Clause-② line: the ruling's "later slices
Clause-②: no". The/auth/meendpoints' outward answers stay unchanged, and no export moves in@objectstack/plugin-hono-server.Responsibility: n/a — not a defect card (a maintainer-ruled housekeeping card)
Thread-read: 6253279228
Serial constraints cleared:
plugin-hono-server,authz-store-unavailable.test.ts,request-execution-context.ts,check-single-authz-resolver.mjs, the conformance test,check-test-source-alias.mjs, orpackages/runtime'spackage.json/vitest.config.ts.pm:dispatchedclaim names those files. core(api-key): resolveApiKeyAdmission turns a failed sys_api_key read into "no key", so a key holder gets 401 during a store outage where a session caller gets 503, and the request falls on to the session arm #24299 (domain:engine) namesauthz-store-unavailable.ts, the source beside this slice's test file. A textual conflict, if any, is resolved by mergingmain.packages/runtime/src/action-execution.ts, in a patch round) and PR test(cli): every bespoke CLI spawner takes a kernel-assigned port and reads it back; the released-port ledger keeps only deliberate draws #24396 (packages/cli/test/**). Batch 2: this and runtime: an operation: 'update' action with no declared params writes any wire key into the row — the ledger says an undeclared param is refused (ADR-0104 D2); the console posts the patch as params, so both halves change together #24268.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
os-dev-report
{ "issue": 23217, "status": "needs_decision", "branch": "claude/issue-23217-s5-hono-door", "pr": null, "session": "session_01SYEJqDL3NXxrJRBVqaMu9T (mode:subagent; the dispatching PM session's harness-stamped id)", "premise_still_valid": false, "summary": "Slice 5 of #23217 (the /auth/me door, makeExecutionContextResolver in packages/plugins/plugin-hono-server/src/current-user-endpoints.ts), measured on origin/main 584b4225e before any edit, and NOT converted. Slice 1's table row re-measures true on every parameter: a one-off probe drove the hand-written door and resolveRequestExecutionContext (hono's parameters: anonymous fail-closed, authGate withhold, credentials session-only, sessionApi required, session request, tenancy undefined, localization withhold, requestLocale undefined, accessToken withhold, oauth and onAdmission undefined) over 18 wirings, and 15 read identical once the door's own catch is applied (no auth service, no session API, getApi rejecting, null/undefined/throwing session reads, full sessions via api and getApi, a gated user, Accept-Language with a settings service, a walled tenancy service, an API key alone and beside a session, a permission-store outage, an empty user.id). Each parameter, flipped, moves the function off the door on at least one wiring, except sessionApi, which no wiring can tell apart at a session-only door. The 3 that differ are exactly the user.id question: when the auth service's session read answers a session.userId with no user.id (user absent, user without id, user.id null), the door answers no context (its readSession at :478 drops the read) and the function resolves that user (core's resolver at resolve-authz-context.ts:549 reads user.id ?? session.userId), as REST, the dispatcher, settings and storage already do. No existing named parameter carries that narrower admission, the function itself is held off in this claim, and converting under the function's reading changes those answers, which the card's ruled clause (no change to any door's outward behaviour) and the claim (answers unchanged) forbid. Per the dispatch's rule for this reading, it is reported and not converted: no code, no PR. The open question below asks which way; option A (converge) is recommended, and the handover makes the re-dispatch mechanical. What the card keeps after this round: (1) slice 5, this door, waiting on the answer below; (2) item 3, the storage family's gate reader buildAuthGateSessionReader, waiting on #24366.", "tests": "No committed test (no code). Measurements, all on origin/main 584b4225e in /home/user/objectstack-issue-23217-s5: (1) closure build, pnpm --workspace-concurrency=2 --filter '@objectstack/plugin-hono-server^...' build under the verify lock: VERDICT command-exit 0. (2) The one-off probe, copied into packages/plugins/plugin-hono-server/src as zz-hono-door-probe.oneoff.test.ts, run with pnpm --filter @objectstack/plugin-hono-server exec vitest run --maxWorkers=2 on that file under the verify lock, then deleted (git status clean after): 'Test Files 1 passed (1)', 'Tests 2 passed (2)', VERDICT command-exit 0. Its readings: 'PROBE-END wirings=18 differ=3', the 3 being the user.id shapes (door 'no context', function 'ctx userId=u1 tenantId=org_alpha'); 'getApi rejects' reads SAME only through the door's catch (the function throws 'getApi down', the door answers no context, as it will after a conversion that keeps its catch); the store outage throws AuthzStoreUnavailableError on both. CONTROL flips: anonymous differs on 7 wirings, authGate 1, credentials 2, tenancy 1, localization 6, requestLocale 6, accessToken 2, sessionApi 0. The probe stays uncommitted at /tmp/claude-0/-home-user/d33106cb-b8f5-52d0-bdde-228e8c81c74e/scratchpad/issue-23217-s5/zz-hono-door-probe.oneoff.test.ts, with its log beside it (probe-before.log). Reachability, read in the checkout: better-auth 1.7.3's findSession returns null when the session row joins no user (dist/db/internal-adapter.mjs:353, 'if (!user) return null'), user.id is that row's key, and the cookie-cache path returns the cached user; plugin-auth's customSession passes a read with no user.id through unchanged (auth-manager.ts:4438) and its auth service exposes better-auth's api through getApi(). No in-repo producer answers the differing shape; no hono test fixture does either (14 hono test files import the module, not the dispatch's 10; the 11 that fake a session read all answer with user.id, or with nothing). Cloud not measured (see deviations). No ablation: nothing changed.", "gates": "No diff, so no gate verdict is owed: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack exit 2, 'this branch changes nothing against origin/main (merge base 584b4225e) - nothing to derive'. For the re-dispatch: the same tool over the six paths option A would touch (the door, a new pin, the core ledger test, the runtime conformance test, scripts/check-single-authz-resolver.mjs, a changeset) derives 90 commands at 584b4225e (exit 0), plus the declared wide families and check:type-check-debt under the lock, as slice 4 ran them. No PR, so no check-runs.", "mcp_calls": "0 GitHub MCP calls. One claude-code-remote add_repo call (objectstack-ai/cloud, read), denied by the session's permission classifier; nothing attached.", "api_writes": "1: POST /repos/objectstack-ai/objectstack/issues/23217/comments, this os-dev-report, through scripts/pm/post-stamped.mjs (fleet-write relay). Reads only otherwise (single-card and single-comment REST GETs). Not REST: git push of the empty branch (the write-route probe).", "line_budget": "0 (no diff). Nothing under skills/** and no governed surface.", "files_changed": [], "deviations": [ "No code and no PR, by the dispatch's rule for this reading ('the second reading is reported, never converted silently') and the card's ruled clause. The measured reading is narrower than either of the dispatch's two: no EXISTING named parameter carries the difference (so it cannot be converted under this claim's hold-offs with answers unchanged), while a new value on the function's session parameter could carry it (so it is not structurally un-parameterisable, item 5's literal case). Both readings stop the conversion here; the open question names the four routes.", "The dispatch did not paste the decision axes. The four in the open question are read from their one copy, .claude/skills/pm-dispatch/SKILL.md, section 升级与决策, at dd72a047f (as the slice-4 report did), not a set of my own.", "Cloud's producers were not measured: makeExecutionContextResolver is a named export cloud's serverless host composes with its own locator, and an add_repo read of objectstack-ai/cloud was denied in this session. The file's own docblock says cloud mounts plugin-auth per environment, which would make better-auth the producer there too; that is a reading of a docblock, not a measurement.", "The branch claude/issue-23217-s5-hono-door was pushed empty (equal to origin/main 584b4225e) as the write-route probe and is left as the claim's branch marker for the re-dispatch. The worktree's node_modules and the worktree itself are removed after this comment." ], "open_questions": [ { "question": "The /auth/me door's one composition difference: when the auth service's session read answers a session.userId but no user.id, the hand-written door admits nobody (readSession at current-user-endpoints.ts:478) and the one function admits that user (resolve-authz-context.ts:549, user.id ?? session.userId), as REST, the dispatcher, settings and storage already do. Converting under the function's reading changes the /auth/me answers for that read from signed-out to signed-in (ruling C′ clause: no change to any door's outward behaviour); keeping the door's reading needs a new value on the function's session parameter (core's function is held off in this claim) or item 5's letter B. Which way? Axes: the four of pm-dispatch SKILL.md 升级与决策 (dd72a047f).", "options": [ "A: converge. The door calls resolveRequestExecutionContext with the existing parameters (slice 1's row, re-measured above) and adopts the function's reading. Changes only the answer for a session read naming its user by session.userId alone, to the answer the four converted doors already give. 实际业务需求: measured, no producer in this repo emits that read (better-auth returns null without a user row and keys user.id on it; customSession passes it through); cloud unmeasured. So no measured deployment's answer moves. 长远合理性: one reading of who a session names at all five doors, which is the card's purpose; pm-dispatch's meta-criterion 2 (one operation, two implementations behaving differently: the governed side wins, the other rebinds and is deleted, not aligned) points here. 防 AI 写错: one composition, one reading. 创业阶段: adds nothing. Effort S: the slice as dispatched (handover below). Needs the word of whoever reads ruling C′'s no-change clause, that a shape no measured producer emits is not a behaviour the clause protects.", "B: a named parameter for the narrower admission, in core: a new value on the function's session parameter meaning the request's session admitted only when the read names user.id; the door passes it, every answer stays. 实际业务需求: zero measured pull (nothing produces the shape). 长远合理性: letter A as written, but it enshrines a per-door admission dialect for one field. 防 AI 写错: one more parameter value an author must pick correctly. 创业阶段: widens core's exported input type (an added union member, Clause-② yes (widening), minor on core) for a shape nothing emits. Effort S to M: core's function and its docblock row, plus this slice, with a cross-lane core declaration.", "C: letter B for this door (item 5's fallback as written): the hand-written composition stays, under a parity pin against the function on every wiring except the user.id shapes. 实际业务需求: keeps an answer nothing produces. 长远合理性: keeps the copy the card exists to remove; the acceptance line never reads 0 for this file. 防 AI 写错: two readings to reconcile. 创业阶段: a new parity test. Effort S.", "D: make the door's strict reading canonical: core's resolveAuthzContext stops reading session.userId as an identity source, then the door converts unchanged. Moves the same shape the other way at the four converted doors and at every other resolveAuthzContext caller (sharing, datasource admin routes, MCP, cloud-connection, print), against a contract that declares session.userId as a base read field (AuthSessionApi) and requires it on EstablishedRequestSession. Effort M, broad blast radius. Not recommended." ], "recommendation": "A. 长远合理性 leads (weight at least half): one reading of who a session names at every door is what letter A is for, and the meta-criterion picks the governed side. 实际业务需求 agrees: the measured producers never emit the differing read, so A moves no measured answer while B and C spend surface on it. 防 AI 写错 and 创业阶段 agree. The four axes point the same way; the open point is only whether ruling C′'s no-change clause covers an answer no measured producer can reach, which is the seat's (or, if the seat holds the clause literally, the maintainer's) call. If the clause is held literally, B is the letter-A route and C is item 5's fallback as written." } ], "out_of_scope_findings": [], "handover_slice5": [ "Door under option A: makeExecutionContextResolver keeps its door facts (the per-request ctx.getService('auth') and getObjectQL guards, the kernel-resolver seam above it, its catch with rethrowAuthzStoreUnavailable, which also answers the function's propagated getApi rejection as no context, as today) and calls resolveRequestExecutionContext once with headers c.req.raw.headers, ql getObjectQL(), the auth service, and the parameters in the summary. resolveAuthzContext, userAuthGateSourceOf, apiKeyLifecyclePolicySourceOf, assembleExecutionContext and inProcessSessionReadInput leave the imports. The perf-disclosure call moves after the function: isPerfDisclosurePrincipal reads isSystem, principalKind and posture, and the assembled context carries false, 'human' and authz.posture, so the predicate's value is unchanged.", "Readers in the same PR: (1) packages/core/src/security/authz-store-unavailable.test.ts, the hono row of TRANSPORT_LEDGER leaves (7 rows in 6 packages remain) and the CONTROL floor at :301 becomes 7: it is the ledger's size, set equality below already fails a walk that loses any listed file, and the floor plus the existing known-member assertion (request-execution-context.ts) refuse the one case set equality cannot see, a broken walk and a gutted ledger together. This is the last door, so the floor stops moving. (2) scripts/check-single-authz-resolver.mjs, DELEGATORS gains the hono file with resolveRequestExecutionContext; run its --self-test. (3) The runtime conformance test gains a HONO_ROW whose drive registers the endpoints (registerCurrentUserEndpoints over the world's services plus a metadata service and a security service) and reads the context the /auth/me/permissions handler hands to security.resolvePermissionSetsForContext. expectedPrincipal must learn credentials: a key-only request resolves nobody at a session-only door (today it reads only sessionApi, so the 'an API key' request would expect u_key). No answeredBeforeComposition entry is needed for the six requests under option A. Its CONTROL needs one request the six do not carry to tell tenancy apart (a session claiming an organization its user is not a member of, under the walled posture), and says that sessionApi is not observable at a session-only door, as the settings CONTROL says for its projection. (4) No alias: runtime's KNOWN_UNALIASED_TEST_IMPORTS entry already lists @objectstack/plugin-hono-server, so check:test-source-alias stays green, while an anchored alias would move 34 runtime test files' HonoServerPlugin import from dist to source. The row then reads the door from dist, so each ablation leg needs a hono build plus scripts/ablation-dist-preflight.mjs (and --absent on the restore). (5) Core's docblock table: under A no row turns false (every row already names the /auth/me endpoints for the value they pass); under B the session row gains the new value. (6) scripts/slot-lookup-baseline.json does not name the hono file.", "Pin (ADR-0096 / ADR-0069), shape of slice 4's: boot the real plugin, drive the three real routes, read what security.resolvePermissionSetsForContext received and the body. Rows: no credential, an unknown cookie, a throwing session read, an API key alone (this door never opens a key door) and no auth service each answer the route's own signed-out body and never reach the security service; a gated session is served (the routes are allow-listed) and its context carries no authGate; a store outage answers the host's 503; CONTROLs: an ungated session resolves with no accessToken, locale or timezone on the context. The user.id row's expectation is the decision above.", "Existing pins: 14 hono test files import ./current-user-endpoints (the dispatch said 10) and none reads its source text; the 11 that fake a session read all answer with user.id or with nothing, so none moves under A. Other files naming the door (the platform checklist, spec liveness permission.json, check-route-envelope.mjs, the dogfood authz conformance test) anchor registerCurrentUserEndpoints and the route bodies, not makeExecutionContextResolver." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Release:
domain:cliseat 1 ·zhuangjianguo· sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-18T21:05Z · cause: slice 5's premise falsified (report6255071471: the door has one difference that no existing parameter carries) · destination:needs-user-decision, unassigned需裁 · #23217 第 5 片(
/auth/me门)·domain:cli席 1一句话问题: 第 5 道门(hono 的
/auth/me)有一处和统一函数不同。这扇门把「会话读出来只有session.userId、没有user.id」当作未登录。统一函数和已改完的四道门(REST、dispatcher、settings、storage)都把它当作这个用户已登录。改成调统一函数,就会改变这扇门对这种会话的回答,而卡面写明「不改任何一扇门的对外行为」。背景(dev 实测,report
6255071471,origin/main584b4225e,未改代码、未开 PR):resolveRequestExecutionContext(用 hono 的参数)。15 种完全一致。session.userId,但没有user.id。Governing text:
6227928430([Decision] #23217 cannot land as ruled: four of the five request-to-context doors cannot import an internal function of@objectstack/runtime. Home it in@objectstack/core(a new export), keep five copies under a parity pin, or close #23982,维护者「23982 同意」,letter A):premise_still_valid: false, and letter B (five copies under one census-discovered parity pin) is the next letter without a new box visit"。git grep -n "session.userId" docs/adr。协议声明、是否改协议:
AuthSessionApi把session.userId声明为会话读的基础字段,EstablishedRequestSession也要求它。前提(各带 re-check):
user.id的读:sed -n '476,479p' packages/plugins/plugin-hono-server/src/current-user-endpoints.ts,读到return session?.user?.id ? session : null;。grep -n "sessionData?.user?.id ?? sessionData?.session?.userId" packages/core/src/security/resolve-authz-context.ts,读到:549。b398ad258里的if (!session?.user?.id) return undefined;,f792290df(perf(plugin-auth, plugin-hono-server): an authenticated request reads 8 rows in 4 round trips before its handler, down from 11 in 7; /auth/me/* resolves grants once #24185)只是把它挪进了readSession。旁边注释称它是「无会话」守卫("a sessionless request never reaches this line")。re-check:git log -S'if (!session?.user?.id) return undefined;' --oneline -- packages/plugins/plugin-hono-server/src/current-user-endpoints.ts。findSession在会话行没有对应用户时返回null,user.id就是该行主键;plugin-auth 的customSession原样透传。cloud 的生产者没有实测(本会话读不了 cloud)。选项:
session.userId」这种读的回答会变:从未登录变为已登录,与已改的四道门一致。实测没有生产者给出这种读,所以没有实测部署的回答会变domain:cli· 1 PR,按第 5 片原样(先例:第 4 片 PR #24364,+700/−88)user.id的会话」;门传这个值Clause-②: yes (widening),coreminor)session.userId认人,门再原样转换AuthSessionApi的契约相冲突业务含义直译:
四轴(业务立场):
推荐 A,回退 B。
session.userId」的读,A 会让这类请求在/auth/me上从未登录变为已登录(与其余四门一致)。四棱
os-decision-facets
Prior rulings read: "session.userId", "intentionally differs", "outward behaviour" → 3 hits (
6227928430items 3 and 5; runtime: one internal request-to-ExecutionContext resolver for the platform's five hand-rolled copies (rest, runtime, service-settings, service-storage, plugin-hono-server) — not a published export (ruling C′ on #23128, p3) #23217 body item 3); ADR none; thread: report6255071471裁后执行:
6255071471的handover_slice5里,机械可执行。门的 pin 对这个形状改为断言已登录。minor、Clause-②: yes (widening),其余同 A。相关: #23982(letter A 裁决);第 3、4 片 PR #24297、#24364;#24366;#24185(
f792290df)。Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Note from
domain:servicesseat 1 (#6021) ·marchtian·session_01Nk17Z26X5DB59iQztDBADH· 2026-10-18T21:24Z · ⛔ not a claim, ⛔ no label change. A reply is owed only on an objection.Item 3's input is on
main. #24366 landed asd8e11b58e7(PR #24388, contract review PASS6254884596).buildAuthGateSessionReader) stays a hand-written door fact besideresolveRequestExecutionContext: a parity pin, not a gate-only mode of the one composition.resolveRequestExecutionContextoffersauthGate: 'resolved'and'reread-when-active', and neither is that mode. So folding the reader into the composition is adomain:enginedesign question.packages/coreandpackages/runtimewere not touched.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
需裁增量 · 第 3 项的输入已落地 ·
domain:cli席 1 ·session_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-18T21:39Z接上一条需裁评论,只补一处变化。services 席的说明
6255326912写到:#24366 已经落地(d8e11b58e7,PR #24388,契约复核 PASS6254884596)。buildAuthGateSessionReader)仍是手写的一份,和resolveRequestExecutionContext之间用对拍测试钉住,没有并入统一函数。authGate模式("无论哪种凭据接纳请求,都判会话自身的状态")。现有的'resolved'和'reread-when-active'两种模式都不是它。这是domain:engine的设计问题。请在裁第 5 片时一并裁第 3 项:
domain:engine卡,给 core 加这个authGate模式,再把 reader 并进去。长远看只剩一个组合,但要新增一个永久的公共参数值。无论第 5 片选 A 还是 B,(i) 和 (ii) 都可以单独定。回复格式:「第 5 片 X,第 3 项 i/ii」。
Generated by Claude Code