Skip to content

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

Open

Description

@objectstack-fleet

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.

What is measured (#23128's dev, on dc93781c19)

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)

  1. One function in @objectstack/core, exported from its . entry beside classifyAdmissionTenancyPosture (Clause-②: yes (widening), minor on @objectstack/core; the home amended by the ruling on [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, batch [WIP] Update action step configuration based on job run #358 item 1 — the runtime home C′ named is unreachable from four of the five doors; later slices Clause-②: no) that takes the pieces a door holds (the request's headers or the Hono context, the auth service, the ObjectQL engine) and returns the ExecutionContext, with the two choices made once and documented: the anonymous entry (fail-closed assembleExecutionContext unless the door's product semantics say guest) and the ADR-0069 auth gate.
  2. 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).
  3. ⛔ 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).

Related: #23128 (ruling C′, dev report 6138940364) · #15747 · #15351 · #15352 · #22258 · ADR-0096 · ADR-0069.

Activity

  1. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Blocked-by: #23982

    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.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

  3. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    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/main 7be3cc728; line numbers move; stop on a breach and explain it in the report):


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  5. 28 remaining items

  6. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    Release: domain:cli seat 1 · zhuangjianguo · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-18T18:03Z · cause: slice 4 landed (Part of #23217) · destination: pm:queue, unassigned, for slice 5 (plugin-hono-server), the last door

    Landed: PR #24364 → 8d3dd6f80 (#23217 slice 4, Part of #23217)

    Reading Result
    PR #24364 object merged: true, merge commit 8d3dd6f80, merged 2026-10-18T18:02Z through the merge queue (enqueued 2026-10-18T17:31Z)
    origin/main 8d3dd6f80 is an ancestor of origin/main, with a single parent (squash). The 10 files and +700 / −88 match what was reviewed at e2891bfbc
    queue branch no gh-readonly-queue/main/*pr-24364* head remains

    Generated by Claude Code

  7. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    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/main 584b4225e; line numbers move; stop on a breach and explain it in the report):


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  9. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    Release: domain:cli seat 1 · zhuangjianguo · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-18T21:05Z · cause: slice 5's premise falsified (report 6255071471: 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/main 584b4225e,未改代码、未开 PR):

    • 用一次性探针在 18 种接线下对比手写门和 resolveRequestExecutionContext(用 hono 的参数)。15 种完全一致。
    • 不一致的 3 种,全是这一个形状:auth 服务的会话读给出 session.userId,但没有 user.id。
    • 现有参数里没有一个能承载这条更窄的规则。dev 逐个翻转参数验证过,每个参数都会在某种接线下显出差异。

    Governing text:

    协议声明、是否改协议:

    • AuthSessionApi 把 session.userId 声明为会话读的基础字段,EstablishedRequestSession 也要求它。
    • A、B、C 都不改协议。D 改了,而且要单独立卡。

    前提(各带 re-check):

    1. 门这边丢弃无 user.id 的读:sed -n '476,479p' packages/plugins/plugin-hono-server/src/current-user-endpoints.ts,读到 return session?.user?.id ? session : null;。
    2. 统一函数接纳它:grep -n "sessionData?.user?.id ?? sessionData?.session?.userId" packages/core/src/security/resolve-authz-context.ts,读到 :549。
    3. 这条规则不像是有意设计的。它最早是 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。
    4. 本仓没有任何生产者会给出这个形状。better-auth 1.7.3 的 findSession 在会话行没有对应用户时返回 null,user.id 就是该行主键;plugin-auth 的 customSession 原样透传。cloud 的生产者没有实测(本会话读不了 cloud)。

    选项:

    选项 做什么 客户可感知后果 工作量
    A · 收敛(推荐) 这扇门直接调统一函数,用现有参数,采用函数的读法 只有「会话只给 session.userId」这种读的回答会变:从未登录变为已登录,与已改的四道门一致。实测没有生产者给出这种读,所以没有实测部署的回答会变 S · domain:cli · 1 PR,按第 5 片原样(先例:第 4 片 PR #24364,+700/−88)
    B · 加命名参数 给 core 函数的会话参数加一个新值,意为「只接纳带 user.id 的会话」;门传这个值 对外行为一字不变。core 的导出入参类型变宽(Clause-②: yes (widening),core minor) S–M · core 一处跨车道 + 本片 · 新参数值无先例
    C · 这扇门走 letter B 保留手写组合,用一个对拍测试钉住它与函数一致,只豁免这个形状 对外不变。这扇门的副本永久留着,卡的验收行对这个文件永远到不了 0 S
    D · 以严格读法为准 core 不再从 session.userId 认人,门再原样转换 已改的四道门及 sharing、MCP、cloud-connection 等所有调用者反向变化,并且和 AuthSessionApi 的契约相冲突 M · 波及面广 · 不推荐

    业务含义直译:

    • A:五扇门对「这个会话是谁」只有一种答案,像银行所有柜台认同一张身份证。
    • B:给第五个柜台单独加一条它自己的规矩,而这条规矩今天没人用得上。
    • C:第五个柜台保留一套手抄的规矩,另加一个人天天去核对它和总规矩是否一致。
    • D:改总规矩,所有柜台跟着变。

    四轴(业务立场):

    • ① 项目长远合理性: A 让五道门只有一种身份读法,这正是本卡和 letter A 要的终态。B 把一个门的方言写进 core 的公共参数,C 留下副本,都在增加特例。两年后的平台应该是一条 request→context 组合,主流平台(Salesforce、Supabase 这类)对一个会话只有一种「当前用户」判定。
    • ② 实际业务拉动: 没有。实测没有生产者会给出这个形状,所以 A 不改变任何实测回答,B 和 C 是为没人遇到的情况花代价。
    • ③ 防 AI 犯错: A 只剩一个组合、一种读法,AI 写新门时不会选错。B 多了一个必须选对的参数值,选错时会静默把用户当成未登录。C 留两份逻辑,靠对拍发现漂移。
    • ④ 创业阶段不扩散: A 不新增任何东西。B 新增一个永久的公共参数值,C 新增一个对拍测试。

    推荐 A,回退 B。

    • 置信缺口:cloud 的生产者没有实测。如果 cloud 有自己的 auth 服务会给出「只有 session.userId」的读,A 会让这类请求在 /auth/me 上从未登录变为已登录(与其余四门一致)。
    • 只看①选 A;②③④ 是否翻转:否(三轴同向)。
    • 需要你裁的只有一点:卡面第 3 条「不改任何一扇门的对外行为」,是否覆盖一个实测没有生产者能到达的形状。按字面守 ⇒ B(这是裁决第 3 条已经给出的机制,无需再进决策箱)。

    四棱
    os-decision-facets

    裁后执行:

    相关: #23982(letter A 裁决);第 3、4 片 PR #24297、#24364;#24366;#24185(f792290df)。


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

    Generated by Claude Code

  11. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    需裁增量 · 第 3 项的输入已落地 · domain:cli 席 1 · session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-18T21:39Z

    接上一条需裁评论,只补一处变化。services 席的说明 6255326912 写到:#24366 已经落地(d8e11b58e7,PR #24388,契约复核 PASS 6254884596)。

    • 第 3 项现状: storage 的 gate reader(buildAuthGateSessionReader)仍是手写的一份,和 resolveRequestExecutionContext 之间用对拍测试钉住,没有并入统一函数。
    • 它和第 5 片是同一类问题。 要并入,就得给 core 函数加一个新的 authGate 模式("无论哪种凭据接纳请求,都判会话自身的状态")。现有的 'resolved' 和 'reread-when-active' 两种模式都不是它。这是 domain:engine 的设计问题。

    请在裁第 5 片时一并裁第 3 项:

    无论第 5 片选 A 还是 B,(i) 和 (ii) 都可以单独定。回复格式:「第 5 片 X,第 3 项 i/ii」。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions