Repository navigation
Commit d54e239
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)
Part of #23287
Clause-②: no
This PR writes ruling C′ item 5 into ADR-0069 D3. The ruling is on
#23287 (comment
[`6159450584`](#23287 (comment)),
the maintainer's 「同意」). The sentence also records the default for the
forced-reset revocation, which the maintainer set as letter A (record
[`6165848580`](#23287 (comment))).
The card's code is PR #23440. This PR contains no code.
**Tier H** (`docs/adr/**`). The PR stays a draft until the maintainer
approves it. Before that approval, no seat marks it ready, queues it or
arms auto-merge.
`Clause-②: no`: this diff adds nothing to a published payload and
publishes no package. The claim's `Clause-②: yes` line belongs to the
code PR #23440. Changeset: none. `docs/adr/**` publishes nothing, so the
PR carries the `skip-changeset` label.
## The change: one file, +12 / −1
`docs/adr/0069-enterprise-authentication-hardening.md` changes in two
places. Nothing else in the record changes:
1. **D3's note.** The note under the D3 table ends with "Gate at the
session-validation seam so a half-authenticated (password-only) session
cannot read data." It now has one more paragraph: a bold provenance
label and **one sentence**.
2. **The status line** gets one `· **Amended**` entry that points to
that note.
### 1. D3: the new paragraph (exact)
```text
> **What the gate holds (amended 2026-10-14: ruling C′ on [#23287](#23287),
> [`6159450584`](#23287 (comment)), maintainer 「同意」;
> the revocation's default is the maintainer's letter A,
> [`6165848580`](#23287 (comment)
> The gate holds the account's standing and an API key answers as the session does (a held
> user's key is refused with the session's code and remediation, and admitted again once the
> standing clears: a refusal, not a revocation); password expiry (D1) holds password sign-in
> alone, so the user's keys keep working; and an administrator's forced password reset (D7)
> carries an explicit revocation of the user's sessions and API keys, on by default and off only
> as the administrator's explicit act.
```
### 2. Status line: the appended entry (exact)
```text
· **Amended** (2026-10-14, ruling C′ on [#23287](#23287), [`6159450584`](#23287 (comment)), maintainer 「同意」) — D3 states what the gate holds for an API key, for password expiry and for a forced reset; see the note under D3.
```
## How each clause maps to the ruling
| Clause in the sentence | Ruling text it carries |
|---|---|
| "The gate holds the account's standing and an API key answers as the
session does" | Item 5: "the gate holds the account's standing and a key
answers as the session does" |
| "(a held user's key is refused with the session's code and
remediation, and admitted again once the standing clears: a refusal, not
a revocation)" | Item 4: "The refusal is not a revocation: the key
resumes the moment the user enrols (or the account posture clears); the
403 body carries the posture code and the remediation pointer the
session gets." |
| "password expiry (D1) holds password sign-in alone, so the user's keys
keep working" | Item 5: "password expiry holds the password alone"; item
1: "it holds password sign-in alone; the user's keys keep working" |
| "an administrator's forced password reset (D7) carries an explicit
revocation of the user's sessions and API keys, on by default and off
only as the administrator's explicit act" | Item 5: "a forced reset
carries an explicit revocation"; letter A (`6165848580`):
`revokeSessionsAndKeys` stays ON by default for every forced reset, and
turning it off is the administrator's explicit act |
## Why it is placed and worded this way (measured on `main` at
`1685cd5ca7`)
- **Placement.** D3 states what the gate holds in two places: its
`mfa_required` row and the note under the table. The note names the seam
and why it is there, so the sentence continues the note rather than
widening a table cell. The sentence cites D1's `password_expiry_days`
row and D7's "force password reset" action by section name. Neither one
is edited.
- **Provenance convention.** This record already writes its one earlier
settled question in two places: the status line ("Session-of-record
question settled 2026-08-04 (#4785)") and bold-led notes inside D2 and
D4. Sibling amendments on `main` use a `· **Amended** (date, ruling …)`
entry on the status line plus the text in place: ADR-0138 D3 item 6
(`34d144ca4d`), ADR-0005 and ADR-0043. This PR follows both conventions.
The status entry is a pointer and does not repeat the decision, so the
two texts cannot drift apart.
- **True as a decision record, whichever PR lands first.** The sentence
names no symbol that exists only in PR #23440. On `main`,
`PASSWORD_RESET_REQUIRED` has 0 hits under `packages/`, and the sentence
does not name `AUTH_GATE_SCOPES` or `resolveUserAuthGates`. "The
account's standing" and "password sign-in alone" are the ruling's scope
words (item 1), written as prose rather than as code identifiers.
- **Prime Directive 13 grep.** Before this PR, ADR-0069 mentioned API
keys 0 times. Across `docs/adr/`, a case-insensitive grep for
`authGate|un-stepped-up|MFA_REQUIRED|PASSWORD_EXPIRED` hits ADR-0069 and
two other places: a field list in ADR-0092 and an allowlist predicate in
ADR-0138. Neither rules on a held user's key. ADR-0101 (stdio key
admission) runs a key "under posture rules (ADR-0099)", and this
sentence does not contradict that.
## Gates
On head `38a672e9b6`, `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` was run with no paths. It derived the
change set from the merge base `1685cd5ca` (1 path, +12 / −1) and listed
**19 commands**. All 19 ran, each exit code was captured before any
pipe, and **all 19 exited 0**. One of them, `pnpm --filter
@objectstack/lint run check:doc-formula-expressions`, first exited 3
with `PREREQUISITE NOT MET` because `@objectstack/formula` and
`@objectstack/lint` were not built. That run measured nothing. After a
build of those two packages under `os-verify-lock` (`VERDICT
command-exit 0`), the gate re-ran and exited 0. The `--ran`
reconciliation with the exit codes recorded printed: `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 gates' own verdict lines:
- `pnpm check:adr-anchors`: `OK (68 anchored file(s), every governing
ADR still referenced; 136 decision number(s), …; 46287 citation(s)
across 6051 file(s) resolve; …)`
- `node scripts/check-adr-links.mjs`: `761 relative link destination(s)
under docs/adr/ resolve`
- `node scripts/check-adr-symbol-anchors.mjs`: `2347 anchors across 142
records resolve`
- `pnpm check:doc-authoring`: clean on all three axes
- `pnpm check:nul-bytes`: `no raw ASCII control bytes`
- `node scripts/pm/check-governed-merges.mjs --branch
claude/issue-23287-adr-0069-d3`: exit 3, which is this tool's governed
verdict and not a failure: `landing tier: H(人合)`, `docs/adr/** ×1`, `13
changed line(s) (+12 / -1) ≤ 3000`
These run in CI, not locally: the lint workflow's full set of checks,
link checking, and the PR-body guards (Part-of / closing keyword,
closing-target claim, duplicate fix). The derivation itself names 15
families that apply only once a changeset exists. This PR has none: it
carries the `skip-changeset` label.
## Acceptance notes
- **Left unedited on purpose** (one sentence, no other ADR edit): the P1
row of the implementation-status table ("✅ implemented", 2026-07-04) and
item 4 of the enforcement-seam summary. On `main`, the shared
key-admission seam does not ask about the key owner's standing, and a
forced reset still answers `PASSWORD_EXPIRED` with no revocation switch.
PR #23440 changes both. Whichever of the two PRs lands first, the
amended sentence reads as a decision and stays true as one. The status
table does not claim that the new sentence is enforced. If the
maintainer wants that table row to name the amended D3 rule and its
enforcement, that is a second sentence and would need this same
approval.
- **Landing order** with PR #23440 does not affect whether the text is
true. The seat already holds that PR's landing behind
objectstack-ai/objectui#12365 and the `.objectui-sha` bump
(`6165848580`, part 2). This PR has no such dependency.
## 维护者速读(草稿)
- **改了什么:** ADR-0069 D3 加了一句话,状态行加了一条「Amended」指针,只改这一个文件(+12 /
−1),不改代码。这句话写的是:这道闸扣的是**账号的状态**,API key 和会话得到同一个答案。被扣用户的 key
收到和会话相同的拒绝码与补救指引;账号恢复合规后 key 自动恢复,这是拒绝,不是吊销。**密码过期只扣密码登录**,key
照常可用。**管理员强制改密时附带一次明示的吊销**,吊销该用户的会话和 API key。这个吊销默认开,只有管理员明确关掉时才不吊销。
- **为什么改:** 这是裁决 C′ 第 5 项的要求。ADR-0069 原文只讲「会话」,对 API key
只字未提,结果同一个请求在不同接口得到两种答案(#23287)。记录不补这一句,下一个人照原文读,会认为闸只管会话,把 PR #23440
的修复当成越权改回去。
- **风险与代价(含回滚):** 纯文档,不进任何发布包,也不改任何运行时行为。合入顺序有两种情况。本 PR
先合:记录里已写着这条规则,运行时要到 PR #23440
合入后才兑现。这句话是决定,不是「已实现」的声明;状态表也没有改成「已实现」。#23440
先合:代码先于记录生效。两种顺序下文字都成立。回滚只需 revert 这一个提交。
- **席位意见:**
- **你要做的:** 读 D3 下面新增的那一句话。同意的话,在本 PR 上批准(Tier H,要您亲手批)。要改措辞,直接在 PR
上说,dev 按您的原话改。
Drafted by the `os-dev` agent that `domain:spec` seat 4 dispatched, on
the claim `6165524186` (branch `claude/issue-23287-adr-0069-d3`).
---
_Generated by [Claude
Code](https://claude.ai/code/session_0121ooBYRmS8ziFXm9ai5YHm)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 0f05907 commit d54e239
1 file changed
Lines changed: 12 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
85 | 85 | | |
86 | 86 | | |
87 | 87 | | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
88 | 99 | | |
89 | 100 | | |
90 | 101 | | |
| |||
0 commit comments