Repository navigation
fix(service-storage)!: the family gate and the route judge the same credential - #24388
Conversation
…edential The storage family's ADR-0069 gate now judges every credential a route of the family can serve, so a session the policy holds is refused on every route of the family whatever else the request carries. A held key is still refused with its owner's posture through the shared key-admission seam. Claude-Session: https://claude.ai/code/session_01Nk17Z26X5DB59iQztDBADH Co-authored-by: Claude <noreply@anthropic.com>
…ntial, and the changeset Pins that a session the ADR-0069 gate holds is refused 403 with its posture on the download routes (an unclaimed file and a parent-governed file) and the upload start route, whatever else the request carries. Controls: an un-held session is served, and a held owner's key alone is refused as today. Clause-②: no Claude-Session: https://claude.ai/code/session_01Nk17Z26X5DB59iQztDBADH Co-authored-by: Claude <noreply@anthropic.com>
… unknown operators loudly The in-memory matcher now throws on an unsupported combinator or operator rather than silently excluding the row, as check:where-matcher requires. Claude-Session: https://claude.ai/code/session_01Nk17Z26X5DB59iQztDBADH Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 9 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 9bd2728ae99b278add98efecb047357f87ab4624 && git checkout 9bd2728ae99b278add98efecb047357f87ab4624
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 584b4225e1fcbaf5e8bed14d9c280d6347a897ff 8ab3f986c351d5bb184c344bb9119731492396ce && git checkout -B drift-repro 584b4225e1fcbaf5e8bed14d9c280d6347a897ff && git merge --no-ff 8ab3f986c351d5bb184c344bb9119731492396ce
node scripts/docs-audit/affected-docs.mjs --json 584b4225e1fcbaf5e8bed14d9c280d6347a897ff |
REWORK · patch round 1 ·
|
…act; docs: storage refuses a gated session The new pin's fake engine opens findOne with assertEngineFindOnePredicate and update with assertEngineUpdateDispatch, as check:engine-double-contract requires, and the pinned ledger records it. The authentication docs now say that the storage routes refuse a gated session whatever else the request carries. Claude-Session: https://claude.ai/code/session_01Nk17Z26X5DB59iQztDBADH Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Read: the card and its five comments, the PR body and file list, the net diff against the merge-base with ① Derived judgments
② Semver levelWrong. The changeset grades
Remedy, four prose-face edits and no code change: ③ Boundary flags
Implemented-by: VERDICT: FAIL Generated by Claude Code |
REWORK · patch round 2 ·
|
…g, and correct the lifecycle-arm comment The changeset now grades @objectstack/service-storage minor, carries the narrowing arm, the BREAKING banner and the ADR-0087 disposition, in the form of this package's own precedent for the same ADR-0069 act. The lifecycle-arm comment in the gate reader now says what is true on each route. No code change. Claude-Session: https://claude.ai/code/session_01Nk17Z26X5DB59iQztDBADH Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Second record on this PR, after the FAIL at ① Derived judgments
② Semver levelRight on this head. Judged fresh against AGENTS.md (line 1088),
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对ACCEPT ·
Generated by Claude Code |
Fixes #24366
Clause-②: no (narrowing)
What changed
The storage family's ADR-0069 authentication-policy gate and the route now judge the same credential. The family door fronts routes that serve different credentials — the upload routes and the unclaimed-file download identify their caller by the session, the parent-governed download serves the one request-to-ExecutionContext composition's principal — so the door judges every credential a route of the family can serve. A session the policy HOLDS (an expired password, enforced MFA not yet enrolled) is refused
403with the gate's posture code on every route of the family, whatever else the request carries, before any of the family's own logic runs. A held key's owner is still refused through the shared key-admission seam with the owner's posture.The fix is one narrowing in
buildAuthGateSessionReader(packages/services/service-storage/src/storage-service-plugin.ts): the gate the door asks now always covers the credential the route will serve.Why it is a narrowing
No request a storage route refuses today becomes accepted: this only adds refusals (a held session that was served is now refused). No export, schema or accepted value changes.
Clause-②: no (narrowing).Scope
packages/services/service-storageonly.packages/core/packages/runtime(the one compositionresolveRequestExecutionContext) are untouched — this is aservice-storage-local decision.The gate reader stays a hand-written door fact beside the one composition (it is the input 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's item 3 waits on; see below), implemented here as a parity pin rather than a gate-only mode of the composition.
Docs (patch round 1, rework
6253965841):content/docs/permissions/authentication.mdxnow says that the storage routes refuse a gated session whatever else the request carries. This PR made the earlier sentence false there.scripts/engine-double-contract.pinned.jsonrecords the new pin's fake engine, written by the gate's own--write.Tests
storage-routes.family-gate-credential.pin.test.ts: on the download routes (an unclaimed file and a parent-governed file) and the upload start route, a held session is refused403with its posture whatever else the request carries, storage never touched. Controls: an un-held session is served; a held owner's key alone is refused as today.storage-routes.api-key-account-posture.pin.test.ts,storage-routes.request-context-door.pin.test.ts) to the rule.pnpm --filter @objectstack/service-storage test(1051 pass) andtypecheckgreen locally; the derived gate families run in CI.Bearing on #23217
This card's design is #23217 item 3's input: the storage family's gate reader stays a hand-written door fact (a parity pin), not a gate-only mode of the one composition.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Nk17Z26X5DB59iQztDBADH