Repository navigation
docs(adr): ADR-0069 records ruling C′'s enforcement once every key-admitting seam asks the owner's posture — a dated status note, and the shared key-admission seam as a sixth enforcement-seam entry (Tier H, the maintainer's hand) #23483
Description
Filing gate ①: a named location whose sentence the landed work makes untrue. This is a follow-up owed by landed work (PR #23440 and PR #23472, both merged 2026-10-14 for #23287). reach: is the record itself. docs/adr/0069-enterprise-authentication-hardening.md line 173 on origin/main d54e2393bf says "Each row in D1-D6 names exactly one of these seams". The five seams listed above it (lines 167–171) do not include the API-key admission seam, which D3's amended note (lines 89–97) now names.
Blocked-by: #23455
Blocked-by: #23456
Filed by domain:spec seat 4 (#18917) · sunhaolin · session session_0121ooBYRmS8ziFXm9ai5YHm, from PR #23472's at-tier contract review (6167905954, recorded on #23287 as 6167957717), checked on origin/main d54e2393bf. ⛔ Not a claim. Lane, type and grade are triage's.
What the record lacks
- A dated status note naming the enforcement. The "Implementation status (2026-07-04)" table (line 183 on) claims only that date's scope. The convention is the dated rows and the "Correction (bug(plugin-auth):
[auth] no cache service registered在 CacheServicePlugin 注册前 21ms 就喊了 —— 误报,且把人引向「你需要 Redis」 #4772)" entry. It invites one note: ruling C′ is enforced by PR feat(spec,core,plugin-auth)!: an API key answers as its owner's session does when the ADR-0069 policy holds the account (#23287) #23440 (0f059078ec). The note covers:- the closed
AuthGateCodeSchema; AUTH_GATE_SCOPES(MFA_REQUIRED= account,PASSWORD_EXPIRED/PASSWORD_RESET_REQUIRED= credential);- the
heldoutcome ofresolveApiKeyAdmission; - the forced-reset revocation, on by default.
- the closed
- The shared key-admission seam as a sixth entry in "Enforcement seam summary" (lines 165–173):
resolveApiKeyAdmissioninpackages/core/src/security/api-key.ts, fed byuserAuthGates. That keeps line 173's "exactly one of these seams" true for D3.
Why it waits on #23455 and #23456
A note saying ruling C′ is enforced must be true at every seam that admits a key. On main today two seams still admit a held owner's key without asking the posture:
@objectstack/cloud-connection's install-local resolver (security(cloud-connection): the marketplace install-local resolver admits an API key without asking its owner's account posture, where ruling C′ refuses it at every seam #23455);@objectstack/mcp's stdio face (security(mcp): the stdio face resolves OS_MCP_STDIO_API_KEY with no owner posture, so a held owner's key starts and serves stdio MCP where ruling C′ refuses it at every seam #23456).
#23455 also carries resolveApiKeyPrincipal and makes userAuthGates a required input. The record is written once, after that family lands, so it never states an enforcement the code does not yet hold.
Reader who acts
domain:spec seat 4, the claim seat of #23287 and of the #23455 family, dispatches it once both blockers close. It is one Tier H record edit to docs/adr/ alone, with a PR of its own: a governed surface, landed only on the maintainer's approval (precedent PR #23472). If triage grades it outside priority:p1/p2, it waits behind that seat's maintainer scope ("后续只处理 p1 p2 的任务") and is a candidate for any seat whose scope covers it.
Dedupe
MCP search_issues on this repo, closed included: ADR-0069 status note enforcement seam summary API key admission held posture amendment → 106 hits. The top ten were read:
- auth-gate parity: a request carrying an API key beside a session cookie held by ADR-0069 is refused at REST but admitted by the five raw-host route families — one cross-seam answer is owed (session versus user) #23287 (closed) is the parent ruling's card.
- docs(adr),plugin-security: retire the dead audience-anchor binding gate and restate the ADR text that names the C3-retired security modules (ADR-0138, 0094, 0126, 0131, 0103) #23403, ADR-0138 E2: the closed list of doors recorded in the conformance matrix, denial by domain, and D2b's publish refusal (D2, D2b) #23225, docs(adr): ADR-0087's artifact window states two disjuncts and "within-line retirements" — since 58f5c90999 (#23001) a first-release floor replays the major step (Tier H, the maintainer's hand) #23038, docs(adr): ADR-0005 still states the overlay uniqueness key as four parts — since 5677add93f (#22877 stage 1) it carries bundle_member (Tier H, the maintainer's hand) #23006, docs(adr): ADR-0128 §4 gets a dated note — the keyed-digest break (#21263, PR #21292) did not carry D1, and why #21327 and docs(adr): ADR-0005 appendix (c): the persisted document is the parsed body once every round-trip key is declared (#20051 stage iii, ruling 甲) #20457 are other ADRs' record edits.
- plugin-security: under the walled posture, deployment-level audience-binding acceptance is host configuration (ADR-0131 D5) — the sync stops warning once declared; the refusal names that path, not "create the Default Organization" (cloud#2872 A′) #23385, Permission Model v2 — named follow-up ADRs (post-ADR-0090 parking lot) #2776 and docs: the released text and four source comments still say an absent security service admits analytics reads; the maintainer ruled deny as measured (objectstack-ai/objectstack#22235, ruling B) #22279 are other subjects.
None is this.
Dedupe words: ADR-0069 status note key admission; enforcement seam summary sixth seam; ruling C′ record enforcement.
Activity
objectstack-fleet commentedon Oct 14, 2026
Triage: first grade ·
documentation·priority:p3·domain:spec·area:access·pm:blockedon #23455 and #23456 · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-14T12:58ZRead fresh. The body's two
Blocked-by:lines stand: a note saying ruling C′ is enforced must be true at every key-admitting seam, and two seams (#23455, #23456) still admit a held owner's key.priority:p3: an ADR record follow-up, Tier H (the maintainer's hand). Nothing ships wrong meanwhile: the seams themselves are carded.domain:spec(auth-gate parity: a request carrying an API key beside a session cookie held by ADR-0069 is refused at REST but admitted by the five raw-host route families — one cross-seam answer is owed (session versus user) #23287's family). The unlock scan returns it when both blockers close.objectstack-fleet commentedon Oct 14, 2026
Unlock scan:
pm:blocked→pm:queue·domain:specseat 4 ·session_0121ooBYRmS8ziFXm9ai5YHm· 2026-10-14T16:32ZBoth blockers are closed
completed:daafae6d6d, landing record6172865997;Every key-admitting seam on
mainnow asks the owner's posture, so the record edit this card asks for states only enforcement the code holds. The card returns to the queue at its own grade (priority:p3).It is one Tier H edit to
docs/adr/0069-enterprise-authentication-hardening.md, landed only on the maintainer's approval. TheBlocked-by:lines stay in the body as the record of what held it.objectstack-fleet commentedon Oct 16, 2026
Claim: PM loop round 10 · 2026-10-16T10:46Z · one Tier H record edit, one PR,
Fixes #23483.docs/adr/0069-enterprise-authentication-hardening.mdgains a dated status note naming ruling C′'s enforcement (PR #23440,0f059078ec, and the seams PR #23508 closed), and the shared key-admission seam (resolveApiKeyAdmission,packages/core/src/security/api-key.ts) as a sixth "Enforcement seam summary" entry, so "Each row in D1-D6 names exactly one of these seams" stays true.Session:
session_01HjR8RUwTPQ3TgbfKihmxp9(the dispatching seat; the dev runs as a cloud session the seat post names)Account:
os-elon-musk(the seat's linked user asGET /useranswers it; this card's assignee)Branch:
claude/issue-23483-adr-0069-enforcement-noteWorktree:
objectstack-issue-23483(in the cloud session's container)Domain:
domain:specSeat:
domain:spec#1(seat post #6017)File surface (at
origin/main6422da58e8or later; stop on a breach and explain it in the report):docs/adr/0069-enterprise-authentication-hardening.mdonly: one dated status note beside the "Implementation status (2026-07-04)" table, in the ADR's own dated-row / "Correction (bug(plugin-auth):[auth] no cache service registered在 CacheServicePlugin 注册前 21ms 就喊了 —— 误报,且把人引向「你需要 Redis」 #4772)" convention, and one sixth entry in "Enforcement seam summary".skip-changeseton a measured no-publish reading.main, not against the card.Container & model:
S,mode:cloud(the maintainer's word in this session's chat: 「batch 调到 5, 可以开云卡」),model: default tier(dispatch-gates --tier: no path-derived mandate).docs/adr/**is a governed Tier H surface: the maintainer lands it, and the seat runs aCONTRACT_REVIEW_TIERreview of the governed text first.Clause-②: no
Responsibility: n/a — a record follow-up owed by landed work (PR feat(spec,core,plugin-auth)!: an API key answers as its owner's session does when the ADR-0069 policy holds the account (#23287) #23440, PR docs(adr-0069): D3 states what the gate holds — the account's standing, so an API key answers as the session does (ruling C′ item 5) #23472), not a defect
Thread-read: 6173234540
Ruling-ref: none (ruling C′ is the parent card auth-gate parity: a request carrying an API key beside a session cookie held by ADR-0069 is refused at REST but admitted by the five raw-host route families — one cross-seam answer is owed (session versus user) #23287's; this card records its enforcement and re-rules nothing)
Serial constraints cleared:
Claim:of all 28 openpm:dispatchedcards were read in this act. None touchesdocs/adr/0069-*.domain:specseat 4 as the reader and makes it "a candidate for any seat whose scope covers it" outside p1/p2. This seat's scope is every priority (/pm-dispatch spec 1, no scope words).completed(security(cloud-connection): the marketplace install-local resolver admits an API key without asking its owner's account posture, where ruling C′ refuses it at every seam #23455 and security(mcp): the stdio face resolves OS_MCP_STDIO_API_KEY with no owner posture, so a held owner's key starts and serves stdio MCP where ruling C′ refuses it at every seam #23456, PR fix(core,cloud-connection,mcp)!: every API-key admission names its owner-posture source, so a held owner's key is refused at install-local and on every MCP stdio call (#23455, #23456) #23508daafae6d6d; unlock6173234540).objectstack-fleet commentedon Oct 16, 2026
os-dev-report
{ "issue": 23483, "status": "done", "branch": "claude/issue-23483-adr-0069-enforcement-note", "pr": "https://github.com/objectstack-ai/objectstack/pull/23875", "session": "session_01XgS6gBdsbPNicv9ELY6ne5", "premise_still_valid": true, "summary": "ADR-0069 gains (1) a sixth 'Enforcement seam summary' entry, API-key admission (resolveApiKeyAdmission in packages/core/src/security/api-key.ts, fed by userAuthGates), and (2) a dated note 'Status (2026-10-14): ruling C′ is enforced.' under the 2026-07-04 status table. The note names PR #23440 (0f059078ec) and PR #23508 (daafae6d6d) and covers the closed AuthGateCodeSchema and AUTH_GATE_SCOPES, the held outcome, every key-admitting seam asking the owner's posture, and the forced-reset revocation, on by default. All four card facts hold on main at 34b740f2d1. The seam sweep found no open seam: 10 non-test call sites (runtime dispatcher, REST, settings, storage x2, datasource admin, share-link, install-local, MCP stdio x2) all pass userAuthGateSourceOf(authService), and the only sys_api_key hash lookup is inside resolveApiKeyAdmission. The card assignee was already os-elon-musk; the PR carries the same assignee.", "tests": "No code or tests changed (an ADR-only diff), so no test is owed and no ablation applies. Premise checks: merge-base --is-ancestor for 0f059078ec and for daafae6d6d against origin/main each exit 0; the control leg origin/main~5 exits 0.", "gates": "Derived at 683acf2c (dispatch-gates --commands --repo objectstack-ai/objectstack): 19 families. Each exit was captured before any pipe and is 0: check-adr-links (+ --self-test), check-adr-symbol-anchors (+ --self-test), check-ci-filter-parity, check-closing-keyword-parity (+ --self-test), check-comment-mask-corpus, check:doc-formula-expressions, check:adr-anchors, check:cross-package-test-inputs, check:doc-authoring, check:driver-memory-census, check:gitlink-declared, check:nul-bytes, check:pm-governed-merges, check:pm-prior-rulings, check:refd-timer-probe, check:watch-hint-literal. check:doc-formula-expressions first exited 3 (PREREQUISITE NOT MET: formula/lint not built); after a build under os-verify-lock ('VERDICT command-exit 0') it exited 0. Outside the derivation: pnpm check:pm-governed-prose exited 0. --ran: '19 derived famil(ies) accounted for — 19 run, 0 NOT-MEASURED (a DERIVED zero)'. CI check-runs on PR #23875: in_progress at report time, not awaited by contract.", "mcp_calls": "2 — mcp__github__issue_read (get #23483), mcp__github__issue_read (get_comments #23483); reads only, no write tool", "api_writes": "3 — relay pr_create = POST /repos/objectstack-ai/objectstack/pulls + POST /repos/objectstack-ai/objectstack/issues/23875/assignees (run 38890610741, body read back byte-identical, 5427 bytes); relay comment = POST /repos/objectstack-ai/objectstack/issues/23483/comments (this report). git push (x2) is not a REST write. label-write skip-changeset: attempted once, refused by the session's auto-mode classifier before any request was sent, so 0 writes.", "line_budget": 8, "files_changed": [ "docs/adr/0069-enterprise-authentication-hardening.md" ], "deviations": [ "skip-changeset was NOT applied to PR #23875. The auto-mode classifier refused 'node scripts/pm/label-write.mjs --repo objectstack-ai/objectstack --issue 23875 --add skip-changeset' (reason: External System Writes). By os-dev rule it was not re-routed (no curl, MCP or relay). The seat applies the label. No-publish reading: the diff is docs/adr/** alone, the os-dev fast-path no-publish surface.", "The worktree is ../objectstack-issue-23483 as dispatched. The harness had checked the branch out in the primary checkout, so the primary was detached at origin/main (34b740f) to free the branch for the worktree. The branch base is 34b740f2d1, newer than the claim's 6422da58e8 floor." ], "open_questions": [ { "question": "D3's amended note says 'an API key answers as the session does' but does not name the symbol resolveApiKeyAdmission. Hypothesis 4 asked for 'the same seam in the same words'.", "options": [ "A: leave the D3 note as ruled; the sixth entry points back to it ('the key half of D3's amended note')", "B: also name the seam inside D3's note (outside this card's file surface, and a touch on ruled text)" ], "recommendation": "A. The claim's file surface names only the summary entry and the status note. The D3 note is the maintainer's ruled text, and the summary sentence 'each row in D1-D6 names exactly one of these seams' is about table rows, which stay true. Four axes: no business need for B; long-term, ruled text stays unedited; for AI-proofing, one place names the symbol; for startup focus, no scope growth." }, { "question": "Status note date: dated 2026-10-14 (the day both PRs landed), not 2026-10-16 (the day it was written).", "options": [ "A: 2026-10-14, the enforcement date, as the ADR's other dated rows record when the fact became true", "B: 2026-10-16" ], "recommendation": "A, because the ADR's dated entries (the 2026-08-04 settlement, the 2026-07-04 status) date the fact, not the edit." } ], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
Patch round → PR #23875 ·
domain:specseat 1 · sessionsession_01HjR8RUwTPQ3TgbfKihmxp9· 2026-10-16T11:33Z · on contract review FAIL6214537670(head683acf2c27), same claim6213793261, the dev session reused.heldoutcome, "Nothing is written: the same key is admitted again once the posture clears." The second half holds; the first does not.resolveApiKeyAdmissioncallsstampAdmittedKeyon the held path before it returns (packages/core/src/security/api-key.ts:832-:839). That writessys_api_key.last_used_at(at most hourly) and, for a key signed with no expiry,expires_at(:410-:429). The write is deliberate and pinned (api-key-lifecycle.test.ts:220-:226, "a held key is stamped too").Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
os-dev-report
{ "issue": 23483, "round": "patch round on contract review 6214537670", "status": "done", "branch": "claude/issue-23483-adr-0069-enforcement-note", "head": "18b3f3215b5bc3d0a7572a2dc6fe808cb6357481", "pr": "https://github.com/objectstack-ai/objectstack/pull/23875", "session": "session_01XgS6gBdsbPNicv9ELY6ne5", "premise_still_valid": true, "summary": "The finding is confirmed on main (497518c3): on the held path resolveApiKeyAdmission calls stampAdmittedKey before it returns (api-key.ts:832-839), writing last_used_at (at most hourly, API_KEY_LAST_USED_STAMP_INTERVAL_MS) and, where the lifecycle policy anchors one (legacyGraceEndMs needs policy.startedAt), a no-expiry key's grace end as expires_at. api-key-lifecycle.test.ts:220-226 pins it. The clause 'Nothing is written: the same key is admitted again once the posture clears.' now reads: 'The hold is a refusal, not a revocation: it keeps no state of its own, and the same key is admitted again once the posture clears. The key's use is still stamped while it is held (`last_used_at`, at most hourly, and, where the lifecycle policy anchors one, a no-expiry key's grace end as `expires_at`), so its inactivity window does not run out during the hold.' This is tighter than the review's offer: expires_at is stamped only where the policy anchors a grace. last_used_at anchors the idle window (the idle-window anchor docblock beside legacyGraceEndMs). No other sentence or file moved. Every other sentence of the note was re-read: git diff 34b740f2d1..origin/main (497518c3) over every cited file is empty, and the call-site grep count is unchanged, so the reviewer's 'Right' list stands. The seat's calls on both open questions (A, A) are taken; nothing is left open.", "tests": "No code or tests changed (an ADR-only diff); no ablation applies.", "gates": "Re-derived at 18b3f321: the same 19 families as round 1. All 19 exit 0, captured before any pipe: check-adr-links (+ --self-test), check-adr-symbol-anchors (+ --self-test), check-ci-filter-parity, check-closing-keyword-parity (+ --self-test), check-comment-mask-corpus, check:doc-formula-expressions (after the formula/lint build under os-verify-lock, 'VERDICT command-exit 0'), check:adr-anchors, check:cross-package-test-inputs, check:doc-authoring, check:driver-memory-census, check:gitlink-declared, check:nul-bytes, check:pm-governed-merges, check:pm-prior-rulings, check:refd-timer-probe, check:watch-hint-literal. Outside the derivation: check:pm-governed-prose exit 0. --ran: '19 derived famil(ies) accounted for — 19 run, 0 NOT-MEASURED (a DERIVED zero — all 19 recorded an exit code and none of them is 3)'. The pre-push hook check:commit-card-trailers passed. CI on the new head: not awaited by contract. A fresh contract-review record on 18b3f321 is owed before the maintainer lands it.", "mcp_calls": "1 this round — mcp__github__issue_read (get_comments #23875, to read review 6214537670); reads only, no write tool", "api_writes": "1 this round — relay comment = POST /repos/objectstack-ai/objectstack/issues/23483/comments (this report). git push (1) is not a REST write.", "line_budget": 8, "files_changed": [ "docs/adr/0069-enterprise-authentication-hardening.md" ], "deviations": [ "The PR body was NOT edited, though the patch order asked for it. os-dev says the dev writes the PR body once, at pr_create, and never PATCHes it; changes are named in the report for the seat to write. This round's write budget (a push plus one comment) also has no body PATCH. Replacement text for the seat to write is in pr_body_patch. The round-1 body's 'nothing is written for the hold' is also untrue (the stamp is a write), so both lines need the replacement.", "The worktree ../objectstack-issue-23483 was re-created from origin/claude/issue-23483-adr-0069-enforcement-note at 683acf2c; round 1 had removed it. The branch was not merged with main: no conflict and no main-caused red, and no cited file moved." ], "pr_body_patch": [ { "section": "## Every sentence measured on `main`, the api-key.ts bullet", "replace": "The function returns `{ outcome: 'held', principal, gate }`, and nothing is written for the hold.", "with": "The function returns `{ outcome: 'held', principal, gate }`. Before returning, `stampAdmittedKey` (`:832`-`:839`) stamps the held key's use — `last_used_at`, at most hourly, and, where the lifecycle policy anchors one, a no-expiry key's grace end as `expires_at` — so its inactivity window does not run out during the hold (pinned by `api-key-lifecycle.test.ts:220`-`:226`). Nothing is revoked, and the hold keeps no state of its own." }, { "section": "## 维护者速读(草稿), 改了什么", "replace": "MFA 未达标用户的 API key 被「挂起」(`held`)而不是吊销", "with": "MFA 未达标用户的 API key 被「挂起」(`held`)而不是吊销——挂起不另存状态,姿态解除即恢复;挂起期间照常记录使用时间(`last_used_at`),以免闲置期限在挂起中耗尽" } ], "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
ACCEPT (Tier H) → PR #23875 comment 6214858472 ·
domain:specseat 1 · sessionsession_01HjR8RUwTPQ3TgbfKihmxp9· 2026-10-16T11:50Z · contract review PASS6214838689on18b3f3215b, after one patch round on FAIL6214537670. Review is requested from both authorized approvers, andneeds-user-decisionis on the PR.ADR-0069 gains the sixth enforcement seam (API-key admission,
resolveApiKeyAdmission) and a dated note: ruling C′ is enforced by PR #23440 and PR #23508. The held-key sentence now says the hold is a refusal, not a revocation, and that the key's use is still stamped. The cloud dev session is archived at this ACCEPT.objectstack-fleet commentedon Oct 16, 2026
Landed · PR #23875 →
570f01d270·domain:specseat 1 ·os-elon-musk· sessionsession_01HjR8RUwTPQ3TgbfKihmxp9· 2026-10-16T12:05Zos-zhuangafter the APPROVED review 5547388274 on18b3f3215b); merge commit570f01d270origin/maingit merge-base --is-ancestor 570f01d270 origin/main→ exit 0570f01d270equals the reviewed head18b3f3215b(ACCEPT6214858472, contract review PASS6214838689, same head)gh-readonly-queue/main/pr-23875-*ref remainsFixes #23483; this card closedcompletedState:
pm:dispatchedis removed in this act;documentation,priority:p3,domain:specandarea:accessstay. The cloud dev session was archived at the seat's ACCEPT.resolveApiKeyAdmission) as its sixth seam. A dated note records that ruling C′ is enforced by PR feat(spec,core,plugin-auth)!: an API key answers as its owner's session does when the ADR-0069 policy holds the account (#23287) #23440 and PR fix(core,cloud-connection,mcp)!: every API-key admission names its owner-posture source, so a held owner's key is refused at install-local and on every MCP stdio call (#23455, #23456) #23508, at every seam that admits a key. The held-key sentence says what the code does: a refusal, not a revocation, with the key's use still stamped.Generated by Claude Code