Repository navigation
runtime: HttpDispatcher.enforceAuthGate fails open when an active ADR-0069 gate cannot read the session, where REST answers 503 under #13906's decision 2 #23245
Description
Filing gate ①, class (b): the maintainer ruled how an active ADR-0069 gate answers a failed session read, and only one of the two gated seams keeps that ruling. Measured at the dispatcher's door.
reach: measured once by #23190's dev, through HttpDispatcher.dispatch() on a gated route (os-dev-report 6144103571, model_seams). The setup was an active gate (a policy feature on) and a session read that throws. The dispatcher answered 200, serving the request as if no gate applied, while REST's computeExecCtx seam answers 503 SERVICE_UNAVAILABLE in the same window. #23190's contract-review record 6144443224 (on PR #23242) escalated it to be filed. Function level only, as #23190's disclosure keeps it.
Filed by domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6. ⛔ Not a claim. Lane and grade are triage's.
Reader who acts: triage grades it. The landing is packages/runtime/src/http-dispatcher.ts (enforceAuthGate), which is domain:cli by the lane table, and this seat claims it next if triage queues it here.
What happens
HttpDispatcher.enforceAuthGate (packages/runtime/src/http-dispatcher.ts, about :1362-:1405) reads the session with .catch(() => undefined) and wraps the whole decision in a catch { return null; // fail-open }. Its header says "Fails open on any lookup error." So while the auth service cannot answer, a session the policy holds (expired password, or enforced MFA not enrolled) passes the gate on every dispatcher-served route.
Governing text
#13906, the maintainer's ruling: decision 2 is B (5504837153, confirmed as standing in 5535555324, and the record that governs per 5536149069): "an active auth gate whose session re-read fails answers 503". PR #15020 landed it at REST's computeExecCtx seam. #23190's new sites follow it too: the declared-endpoint fallback's read throws AuthzStoreUnavailableError('auth_gate', …) and answers the branded 503. The dispatcher's own seam is now the one gated seam that fails open.
Seam: spec:ExecutionContext.authGate → runtime: HttpDispatcher.enforceAuthGate (consumer: every dispatcher-served route).
Acceptance
- With an active gate and a failing session read,
enforceAuthGateanswers the branded 503, as REST does:AuthzStoreUnavailableError('auth_gate', …)and the dispatcher's existing rendering of it. An inactive gate stays untouched, with no read, and a successful read carrying no gate still admits. Both are pinned, as [finding] two morecomputeExecCtxseams read "failed" and "not wired" as one value, and both feed authorization inputs — tenancy posture and the ADR-0069 auth gate #13906 pinned them. - One pin drives
dispatch()through each of the three cases. Its negative control: restore thecatch { return null }, and the outage case reds while the two controls stay green. - Grade on facts: an outage now refuses what it served, which narrows an accept set. By this lane's precedent (PR fix(rest)!: PUT and the five by-name /meta sub-doors declare closed query sets, so ?member= is refused, never dropped (#23072, RestServer half) #23133's record
6138381862) that is@objectstack/runtimeminor,fix(runtime)!:,Clause-②: no (narrowing), one ADR-0087 marker and the launch-window BREAKING line.
Dedupe
MCP search_issues on objectstack-ai/objectstack, closed included:
HttpDispatcher enforceAuthGate fails open session read throws auth gate 503 dispatcher ADR-0069: 9 hits. They are services: route families mounted straight on the HTTP server never consult the ADR-0069 authentication-policy gate, so a session held by enforced MFA or password expiry may still reach them #23178 and cli: REST's direct-mount registrars, the runtime dispatcher plugin's declared-endpoint fallback and cloud-connection's raw-app plugins never consult the ADR-0069 authentication-policy gate #23190 (both open, about doors that never consult the gate, not this seam's outage answer), and finding: REST'senforceAuthpassesreq.pathtoisAuthGateAllowlistedunguarded — a request with no path silently disables the ADR-0069 gate, the exact trap the sibling seam documents #7432, [gate] the v18 development line is not open — ADR-0131 execution cards are blocked on this card #15193,isAuthGateAllowlistedexempts by UNANCHORED substring/suffix — an object namedauthor a record with idhealthbypasses the ADR-0069 auth-policy gate #16839,HttpDispatcher.dispatchclaims every path whose first segment merely STARTS WITHauth—/authx,/authentication/fooall answer200 {}instead ofROUTE_NOT_FOUND#16026, owd-save-gate: the ADR-0090 D11 authoring gate never runs on any host-config deployment (keyed on environmentId, not authoringChannel) #7674 and finding: anonymous-deny ordering inverted on /security too — the 503 "Security service not available" answers unauthenticated callers ahead of the gate #7911 (all closed). None is this.dispatcher auth gate fail-open authz store unavailable auth_gate session re-read failure: 7 hits. They are cli: REST's direct-mount registrars, the runtime dispatcher plugin's declared-endpoint fallback and cloud-connection's raw-app plugins never consult the ADR-0069 authentication-policy gate #23190 and services: route families mounted straight on the HTTP server never consult the ADR-0069 authentication-policy gate, so a session held by enforced MFA or password expiry may still reach them #23178, plus auth: plugin-auth doors that re-dispatch get-session or call a better-auth endpoint in-process still renew a cookie session without re-issuing its cookie (split session, the residue of #22258) #22398, access-security.packaged-flow-write-door-parity clauses 2 and 3 fail on main — detail withheld pending maintainer #20679, [gate] the v18 development line is not open — ADR-0131 execution cards are blocked on this card #15193, [finding]authz-store-unavailable.test.tswalks and reads ALL ofpackages/twice under the default 5s timeout — it times out as the repo grows, reddening PRs that have nothing to do with authz #13645 and finding:context.authGateis read by REST'senforceAuthbut is not declared onExecutionContextSchema— an undeclared field outside every closure gate #7280 (all closed). None is this.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 13, 2026
Triage: first grade,
bug·security·priority:p1·domain:cli·area:access, intopm:queue(findingremoved). The dispatcher's gate keeps #13906's ruling: an active gate that cannot read the session answers 503Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-13T11:52Z. ⛔ Not a claim, ⛔ not a dispatch.security: while the auth service cannot answer, a session the ADR-0069 policy holds passes the gate on every dispatcher-served route. This is a fail-open of a security gate, in the family of services: route families mounted straight on the HTTP server never consult the ADR-0069 authentication-policy gate, so a session held by enforced MFA or password expiry may still reach them #23178 and cli: REST's direct-mount registrars, the runtime dispatcher plugin's declared-endpoint fallback and cloud-connection's raw-app plugins never consult the ADR-0069 authentication-policy gate #23190 (both p1security). The maintainer's ruling on [finding] two morecomputeExecCtxseams read "failed" and "not wired" as one value, and both feed authorization inputs — tenancy posture and the ADR-0069 auth gate #13906 (decision 2 = B,5504837153) already decides the answer, so this is execution, ⛔ not a decision.packages/runtime/src/http-dispatcher.tsisdomain:cli.AuthzStoreUnavailableError('auth_gate', …), through the dispatcher's existing rendering), as REST'scomputeExecCtxdoes.dispatch()through the three cases. Its negative control restorescatch { return null }: the outage case reds, and the two controls stay green.Clause-②: no (narrowing). It stays in this lane, and owes a contract-tier review before the queue (execution-duties.md).@objectstack/runtimeminor,fix(runtime)!:, with the ADR-0087 marker and the launch-window BREAKING line, per the lane's precedent.packages/runtime/src/http-dispatcher.ts(enforceAuthGate) (domain:cli); rationale: one outage answer on every gated seam.objectstack-fleet commentedon Oct 14, 2026
Claim: PM loop round 1 (#23245: an active ADR-0069 gate that cannot read the session answers the branded 503 at the dispatcher's seam, as #13906's decision 2 rules) · 2026-10-14T00:19Z
Session:
session_015sSBzUJfLxTo3ZZ68LLfCGAccount:
huangyiirene(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-23245-dispatcher-auth-gate-outageWorktree:
objectstack-issue-23245Domain:
domain:cliSeat:
domain:cli#1(seat post #6024)File surface (read on
origin/main6d96721f; stop on a breach and explain it in the report):packages/runtime/src/http-dispatcher.ts,enforceAuthGateonly. Onmainit reads the session with.catch(() => undefined)and wraps the decision incatch { return null; // fail-open … }(read in this act). With an active gate and a failing session read, it answersAuthzStoreUnavailableError('auth_gate', …)(packages/core/src/security/authz-store-unavailable.ts, imported, not edited) through the dispatcher's existing rendering of that error. An inactive gate makes no read, and a successful read carrying no gate still admits.packages/runtime/src/that drivesdispatch()through the three cases, with the card's negative control: restore thecatch { return null }, and the outage case reds while the two controls stay green..changeset/23245-*.md:@objectstack/runtimeminor,fix(runtime)!:,Clause-②: no (narrowing), one ADR-0087 marker and the launch-window BREAKING line, per triage6144928416.Seam:line, both ends: the spec end,ExecutionContext.authGate, is read only and not touched. The runtime end,HttpDispatcher.enforceAuthGate, is the fix.isAuthGateAllowlistedandevaluateAuthGate(packages/core/src/security/auth-gate.ts, which auth-gate parity: the remediation allow-list exempts the whole auth namespace, and the platform-admin mounts under it judge the caller without the ADR-0069 posture, so a held platform admin may reach user administration (read from source, not yet measured) #23335 holds), REST'scomputeExecCtxseam, and any other dispatcher seam. Public text stays at function level.Ruling: #13906 decision 2 = B (
5504837153, confirmed standing in5535555324, governing per5536149069): "an active auth gate whose session re-read fails answers 503". Triage6144928416: execution, ⛔ not a decision.Container & model:
M, judged (one seam's outage answer and a three-case pin),mode:subagent,model: default tier(dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"). It narrows an accept set (an outage now refuses what it served), so oneCONTRACT_REVIEW_TIERreview is owed before the queue.check-governed-merges --testover the claimed paths: not governed.Clause-②: no (narrowing: an active gate's outage now refuses what it served)
Responsibility:
HttpDispatcher.enforceAuthGateinpackages/runtime(this lane) | REST'scomputeExecCtxseam answers 503 on REST's own routes, and nothing covers the dispatcher-served routes | every deployment with an ADR-0069 policy on (password expiry or enforced MFA), whenever its auth service cannot answer a session read, on every dispatcher-served routeThread-read: 6144928416
Serial constraints cleared:
http-dispatcher.ts(the file lists of all 10 open PRs, read in this act). PR fix(types): one caught-fault writer, sendServerFault; four route families' catch-all 5xx withhold the caught error's text #23346 touchespackages/runtime/src/dispatcher-error-vocabulary.ts, a different file. The dev reads that diff if the 503's rendering goes through it, and the later lander mergesmain.domain:spec#3, in flight) is onauth-gate.ts's held-session decision.enforceAuthGatecalls it but does not edit it, so the files are disjoint and the coupling is semantic. The later lander mergesmainand re-runs the pin.http-dispatcher.ts.Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
os-dev-report
{ "issue": 23245, "status": "done", "branch": "claude/issue-23245-dispatcher-auth-gate-outage", "pr": "https://github.com/objectstack-ai/objectstack/pull/23363", "session": "session_015sSBzUJfLxTo3ZZ68LLfCG (subagent: the parent's, as stamped on every commit's Claude-Session trailer)", "premise_still_valid": true, "summary": "HttpDispatcher.enforceAuthGate now throws AuthzStoreUnavailableError('auth_gate', err) when the gate is ACTIVE and the session read fails (the read rejects, the session API cannot be obtained, or the header bag cannot be normalised). Its outer net re-raises that brand alone (rethrowAuthzStoreUnavailable) and keeps failing open on everything else, so an inactive gate (including a probe that throws) still makes no read, and a read that resolves with no posture still admits. The call sits ABOVE dispatch()'s own try, so the branded error leaves dispatch() and the transport's existing exit (dispatcher-plugin errorResponseBase) answers 503 SERVICE_UNAVAILABLE. That is the exit the identity-step and membership-gate outages already use; no second rendering was added. A new pin drives the three cases through dispatch() and, on the wire, through the real dispatcher plugin; its negative control reds exactly the two outage cases. The changeset is graded per triage: runtime minor, fix(runtime)!, Clause-② no (narrowing), one ADR-0087 not-required marker, and the launch-window BREAKING line. Hypotheses: H1 confirmed (6d96721f :1362-:1402, caller :2746). H2 PARTLY FALSIFIED: no catch inside dispatch() renders it. The gate runs before dispatch()'s try, whose catch rethrows non-denials anyway; the rendering is the transport's errorResponseBase, measured on the wire. H3 PARTLY FALSIFIED: header normalisation and getApi run AFTER isAuthGateActive() answered true, not before. Only resolveService (which absorbs every rejection, so it never throws) and the probe precede it. The one arm where REST and this seam differ is in open_questions. H4: no pin asserted this seam's outage fail-open. The fail-open wording appears in prose only (13 runtime test files, case-insensitive), and the full runtime suite is green with the fix.", "tests": "All at HEAD 061c8f78e (after merging origin/main 228357ab6): (1) pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2 answered 'Test Files 372 passed (372) / Tests 5385 passed | 19 skipped (5404)', and --project repo answered 'Test Files 3 passed (3) / Tests 808 passed (808)'; os-verify-lock 'VERDICT command-exit 0' (the same counts held pre-merge at 5a4240465). (2) pnpm --filter @objectstack/runtime typecheck exited 0, with 'check:test-typecheck: OK … 27 file(s) / 190 error(s) / 68 pinned signature(s) held' (the new file adds no debt). (3) The new pin file has 9 tests, all passing. (4) NEGATIVE CONTROL at ebf5848f6, with the fix committed first: node scripts/ablation-replace.mjs replaced the outer net with 'catch { return null }' (anchor hit once, x1 to x0, blob 5b42c4c85e3b to db96181d7c71) and ran the pin: 'CASE 1 — OUTAGE … dispatch() RESOLVED — the active gate failed open on the outage: expected undefined to be defined', 'OUTAGE on the wire … expected 401 to be 503', 'Tests 2 failed | 7 passed (9)'; restore 'ok restored: blob == HEAD (5b42c4c85e3b) and git diff HEAD is empty'. The predicted direction was red, and red was observed. The subject is resolved through a relative src import, so there is no dist leg. (5) Lint, narrowed proof at 061c8f78e. Population per eslint's own config: the two .ts files are linted, and the changeset answers 'File ignored because no matching configuration was supplied'. --format json: 2 files, 0 errors, 0 warnings. Invariance: eslint.config.mjs has no parserOptions.project (no type-aware linting), and its only disk reads are scripts/slot-lookup-baseline.json and scripts/query-options-erasure-baseline.json, which the diff does not touch. The full pnpm lint is CI's. (6) Gates: dispatch-gates --commands with no paths derived 62 at 061c8f78e, the same set as at 5a4240465. All 62 exited 0, and --ran answered '62 derived famil(ies) accounted for — 62 run, 0 NOT-MEASURED'. NOT MEASURED: the 5 workflow-valued families dispatch-gates itself lists as such (check-issue-citations --census and four check-shard-attestation/check-test-completeness CI-only invocations), reason: their argv carries a CI-only variable.", "mcp_calls": "0", "api_writes": "3 relay-executed REST writes in 2 relay strokes, plus the 2 repository_dispatch sends the strokes travel as (POST /repos/objectstack-ai/objectstack/dispatches x2, as the session). Executed as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/pulls (draft PR #23363); POST /repos/objectstack-ai/objectstack/issues/23363/assignees (huangyiirene, the pr_create leg); POST /repos/objectstack-ai/objectstack/issues/23245/comments (this os-dev-report). Zero label writes: the dispatch named no label, and skip-changeset does not apply because a changeset publishes. git push x5 (not REST).", "open_questions": [ { "question": "One arm differs between REST and this seam. With an ACTIVE gate, when the auth service cannot hand back its session API (getApi throws) or the header bag cannot be normalised, REST answers 'anonymous'. Its getApi and normalisation run in the identity half of computeExecCtx, before the gate block, and its outer net turns the fault into no context, so the caller gets 401 on a protected route. The dispatcher's gate runs after identity is decided, so 'anonymous' is not an answer it can give: it either admits or refuses. This PR refuses (503), as the in-package sibling readGatedSessionUser does (#23190, reviewed in 6144443224).", "options": [ "A: keep 503 for these steps on an active gate (as shipped). It matches the declared-endpoint fallback, and the ruling's 'an active gate whose session re-read fails answers 503' reads getApi failure as a failed read.", "B: narrow the 503 to the getSession rejection alone, and admit (fail open) when getApi throws or header normalisation fails. That is closer to the card's literal wording, but on this seam it leaves an active gate admitting when it could not read." ], "recommendation": "A. It is the reading of the ruling the sibling seam already shipped under contract-tier review. B would keep a fail-open arm on an active gate, which is the failure shape the ruling closes. Header normalisation cannot fail on a request an HTTP parser accepted, so the arm is practically getApi alone. The PR body names this arm so the contract review can rule on it." } ], "out_of_scope_findings": [ "carrier: the next PR touching packages/runtime/src/http-dispatcher.root-auth-gate.test.ts (otherwise 'carrier: none'). Comment drift, not a defect: its header (near line 44) and its positive-control comment (near line 110) say enforceAuthGate 'fails open … any thrown error'. After this PR that is false for an active gate's failed session read. Prose only, no assertion depends on it, and the file is outside this claim's surface, so it is recorded in the PR's Acceptance notes and not filed." ], "gates": [ "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0", "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0", "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0", "node scripts/check-changeset-no-major.mjs --self-test :: exit 0", "node scripts/check-ci-filter-parity.mjs :: exit 0", "node scripts/check-closing-keyword-parity.mjs :: exit 0", "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0", "node scripts/check-comment-mask-adoption.mjs :: exit 0", "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0", "node scripts/check-comment-mask-corpus.mjs :: exit 0", "node scripts/check-dts-emitted.mjs --self-test :: exit 0", "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0", "node scripts/check-empty-changeset.mjs --self-test :: exit 0", "node scripts/check-issue-citations.mjs :: exit 0", "node scripts/check-keyed-text-bounds.mjs :: exit 0", "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0", "node scripts/check-platform-object-tenancy-census.mjs :: exit 0", "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0", "node scripts/check-plugin-teardown-shape.mjs :: exit 0", "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0", "node scripts/check-registry-log-declared.mjs :: exit 0", "node scripts/check-registry-log-declared.mjs --self-test :: exit 0", "node scripts/check-rest-log-spy-declared.mjs :: exit 0", "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0", "node scripts/check-system-context-census.mjs :: exit 0", "node scripts/check-system-context-census.mjs --self-test :: exit 0", "node scripts/check-undeclared-dep-imports.mjs :: exit 0", "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0", "node scripts/docs-audit/check-affected-docs.mjs :: exit 0", "node scripts/docs-audit/check-drift-comment.mjs :: exit 0", "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0", "node scripts/release-pending-publish.mjs --self-test :: exit 0", "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0", "pnpm check:changeset-gate-self-tests :: exit 0", "pnpm check:cross-package-test-inputs :: exit 0", "pnpm check:dispatcher-error-vocabulary :: exit 0", "pnpm check:doc-authoring :: exit 0", "pnpm check:driver-memory-census :: exit 0", "pnpm check:dts-closure :: exit 0", "pnpm check:dual-build-cjs-loads :: exit 0", "pnpm check:engine-double-contract :: exit 0", "pnpm check:gitlink-declared :: exit 0", "pnpm check:issue-citations :: exit 0", "pnpm check:lean-entry-closure :: exit 0", "pnpm check:logger-receiver-detach :: exit 0", "pnpm check:nul-bytes :: exit 0", "pnpm check:objectql-double-limit :: exit 0", "pnpm check:objectui-changeset :: exit 0", "pnpm check:org-identifier :: exit 0", "pnpm check:page-declaration-shape :: exit 0", "pnpm check:pm-changeset-deadline-census :: exit 0", "pnpm check:published-files :: exit 0", "pnpm check:query-options-erasure :: exit 0", "pnpm check:refd-timer-probe :: exit 0", "pnpm check:slot-lookup :: exit 0", "pnpm check:sourcemap-no-sources-content :: exit 0", "pnpm check:test-source-alias :: exit 0", "pnpm check:tier-file-adoption :: exit 0", "pnpm check:type-check-coverage :: exit 0", "pnpm check:type-check-debt :: exit 0", "pnpm check:watch-hint-literal :: exit 0", "pnpm check:where-matcher :: exit 0", "pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2 :: exit 0", "pnpm --filter @objectstack/runtime exec vitest run --project repo --maxWorkers=2 :: exit 0", "pnpm --filter @objectstack/runtime typecheck :: exit 0", "node --stack-size=4000 node_modules/eslint/bin/eslint.js --no-inline-config --format json (2 touched .ts files + the changeset) :: exit 0", "node scripts/pm/dispatch-gates.mjs --ran ran.list --repo objectstack-ai/objectstack :: exit 0", "pnpm turbo run build --concurrency=2 --filter=!@objectstack/docs :: exit 0 (75 tasks)" ], "line_budget": { ".changeset/23245-dispatcher-auth-gate-outage.md": "+18 / -0", "packages/runtime/src/http-dispatcher.auth-gate-outage.pin.test.ts": "+277 / -0", "packages/runtime/src/http-dispatcher.ts": "+62 / -22", "total": "+357 / -22 (379 changed lines, under the 3000 human-merge threshold)" }, "deviations": [ "My first detached full-suite run was spelled 'pnpm --filter @objectstack/runtime test -- --maxWorkers=2'. That is the bare-'--' trap: vitest would discard the flag and run with default workers. I killed my own process group (recorded pgid 12874) within about 12 s, took no reading from it, and re-ran as 'exec vitest run --project local --maxWorkers=2'.", "check-plugin-teardown-shape --self-test refused (exit 3) on the shallow clone and prescribed 'git fetch --unshallow origin main'. I fetched the one pinned fixture commit instead ('git fetch --depth=1 origin 621a487607881c66b2899b7e3477115229a156b4', which adds objects to the shared store only), and the self-test then passed.", "check:dual-build-cjs-loads refused (exit 3) because packages were unbuilt. I ran a full locked build (turbo build, filter !@objectstack/docs), then re-ran it and the other dist-reading gates green. Part of the first gate battery overlapped that build; the final battery at 061c8f78e ran with no build of mine in flight.", "Before opening the PR I merged origin/main (228357ab6, which carries #23346) into the branch (AGENTS.md §10), so the PR head 061c8f78e is a merge commit. Build, the full runtime suites, typecheck, gates and lint were all re-run on it.", "PR body footer: I used AGENTS.md's session-URL footer form rather than the harness reminder's emoji footer, because AGENTS.md takes precedence by the reminder's own terms.", "Stayed on the claimed surface: enforceAuthGate body and header only in http-dispatcher.ts (the call-site comment in dispatch() was not touched), one new pin file, and one changeset. Nothing under packages/spec or auth-gate.ts was touched, and no label was written." ], "files_changed": [ ".changeset/23245-dispatcher-auth-gate-outage.md", "packages/runtime/src/http-dispatcher.auth-gate-outage.pin.test.ts", "packages/runtime/src/http-dispatcher.ts" ], "cleanup": "After this comment is posted: rm -rf the worktree's node_modules, then git worktree remove (no --force). The scratchpad issue-23245/ holds the logs only." }Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
✅ ACCEPT: PR #23363 at
061c8f78e(#23245). It queues after oneCONTRACT_REVIEW_TIERPASS on this head and a green headdomain:cliseat 1 (#6024) ·huangyiirene· sessionsession_015sSBzUJfLxTo3ZZ68LLfCG· 2026-10-14T01:58Z · holder of claim6158013575. Report:os-dev-report6159157870. Thread-read: 6159157870. This note stays at function level.Checklist, read against GitHub:
main, line 1Fixes #23245, line 2Clause-②: no (narrowing: an active gate's outage now refuses what it served)(verbatim from the claim), titlefix(runtime)!: …, assigneehuangyiirene. The body has no other closing keyword.http-dispatcher.ts(enforceAuthGate's body and header only), the new pinhttp-dispatcher.auth-gate-outage.pin.test.ts, and.changeset/23245-dispatcher-auth-gate-outage.md.check-governed-merges --pr 23363, run from currentmain's script: not governed, 379 lines.isAuthGateActive()has answeredtrue, a fault in getting the session API, normalising the headers or reading the session throwsAuthzStoreUnavailableError('auth_gate', err). The outer net re-raises that brand alone throughrethrowAuthzStoreUnavailable(already imported,:12), and every other fault still fails open. A probe that throws, an inactive gate and a read that resolves with no posture all answer as before.dispatch()and on the wire through the real dispatcher plugin. Ablating the net back tocatch { return null }reds exactly the two outage cases (2 failed | 7 passed (9)), and the restore was proven againstHEAD. The refusal asserts the envelope (codeSERVICE_UNAVAILABLE,status503).@objectstack/runtimeminor,fix(runtime)!:,Clause-②: no (narrowing), one ADR-0087not-required (no-migration-prescription)marker and the launch-window BREAKING line, as triage graded it. "Refused now" names the session read and the session API, which matches thetryblock. "Unchanged" names the inactive gate, the probe that throws and the resolved read, which match the early return and the outer net. "the route's own handler does not run" holds because the call sits abovedispatch()'stry, and the pin asserts it.061c8f78e(the PR head, after mergingmain228357ab6): runtime--project local372 files / 5385 tests, and--project repo3 / 808; runtime typecheck exits 0. 62 derived gates ran, and all 62 exit 0, reconciled with--ran. Lint is the narrowed proof with all three parts.061c8f78eat this stamp, latest run per name: 18 success, 6 skipped and 13 in progress, with no failure. The two required jobs are still running. Their conclusions are read again before the ready flip.dispatch()renders the brand; the transport's existingerrorResponseBasedoes, on the wire, which is the exit the identity and membership outages already take. H3 was partly falsified: header normalisation andgetApirun after the probe, not before. The order's premises were wrong on both points, and the dev's readings stand.Open question, read by the seat: an active gate whose session API cannot be obtained now answers 503 (option A), where REST answers that window as anonymous. #13906's ruling says "an active auth gate whose session re-read fails answers 503". A session read that could not be made is a failed read, and the in-package sibling
readGatedSessionUser(#23190, contract-tier PASS6144443224) already answers it 503. So the seat reads A as inside the ruling, ⛔ not a new decision. The contract review rules on the arm, since the PR body names it.Out-of-scope findings, one line each:
http-dispatcher.root-auth-gate.test.ts's comment drift (it says the gate fails open on any error): Acceptance notes. It is prose that no assertion reads, and it rides the next edit of that file, recorded on the seat post.Writes:
mcp_calls0.api_writes:pr_createwith its assignee, and the report.Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
Landed: PR #23363 →
5d3018de5(#23245). The card is closedcompleteddomain:cliseat 1 (#6024) ·huangyiirene· sessionsession_015sSBzUJfLxTo3ZZ68LLfCG· 2026-10-14T02:40Z · holder of claim6158013575, released by this landing. This note stays at function level.5d3018de5, a squash with one parent and an ancestor oforigin/main(read in this act).503 SERVICE_UNAVAILABLEon every route the runtime dispatcher serves, as REST and the declared-endpoint fallback already answer. An inactive gate, a probe that throws, and a read that answers all behave as before.6158013575; ACCEPT6159189302; theCONTRACT_REVIEW_TIERrecord PASS on061c8f78e(6159276172on PR fix(runtime)!: an active ADR-0069 gate that cannot read the session answers 503 at the dispatcher's seam #23363), whose served model was checked against the reviewer's transcript (every turn at the tier). Every check on the head was success or a roster skip (check-expected-skips: OK, 11 skips).@objectstack/honohost, thecreateHonoAppcatch-all renders an escaped error fromstatusCodealone. By reading, this outage's503would answer500there; it is not measured. That seam is hono adapter:createHonoApp's catch-all renders an error thrown out ofdispatch()fromstatusCodealone, so a declaredstatus/code(a missing record's404 RECORD_NOT_FOUND, an outage's503) answers500 INTERNAL_ERROR#23365's, filed in this round.http-dispatcher.root-auth-gate.test.ts's comment drift rides the next edit of that file.packages/runtime/src/http-dispatcher.ts(enforceAuthGate), the new pinhttp-dispatcher.auth-gate-outage.pin.test.ts, and the changeset.Generated by Claude Code