Skip to content

Commit 18b3f32

Browse files
committed
docs(adr-0069): the held key's use is still stamped — correct "nothing is written"
On the held path resolveApiKeyAdmission stamps the key (last_used_at, and a no-expiry key's grace end) before it returns, so the status note's "nothing is written" was untrue. The note now says what holds: a refusal, not a revocation, no state of its own, the use stamped so the inactivity window does not run out during the hold. Claude-Session: https://claude.ai/code/session_01XgS6gBdsbPNicv9ELY6ne5 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 683acf2 commit 18b3f32

1 file changed

Lines changed: 1 addition & 1 deletion

File tree

‎docs/adr/0069-enterprise-authentication-hardening.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ Each row in D1-D6 names exactly one of these seams. No setting is introduced wit
193193
**Status (2026-10-14): ruling C′ is enforced.** D3's amended note holds on `main` through PR [#23440](https://github.com/objectstack-ai/objectstack/pull/23440) (`0f059078ec`) and PR [#23508](https://github.com/objectstack-ai/objectstack/pull/23508) (`daafae6d6d`):
194194

195195
- **The posture vocabulary is closed.** `AuthGateCodeSchema` (`@objectstack/spec/kernel`) is the closed set of codes an `authGate` may carry: `PASSWORD_EXPIRED`, `PASSWORD_RESET_REQUIRED` and `MFA_REQUIRED`. `AUTH_GATE_SCOPES`, typed total over it, declares each code's scope once: `MFA_REQUIRED` holds the **account** (every credential of the user); `PASSWORD_EXPIRED` and `PASSWORD_RESET_REQUIRED` hold the **credential** (password sign-in alone).
196-
- **An API key answers as the session does** (seam 6). `resolveApiKeyAdmission` asks `userAuthGates` for the key's owner and returns the `held` outcome on an `account`-scope posture; the door refuses the key with the posture's code. Nothing is written: the same key is admitted again once the posture clears. A `credential`-scope posture leaves the key admitted.
196+
- **An API key answers as the session does** (seam 6). `resolveApiKeyAdmission` asks `userAuthGates` for the key's owner and returns the `held` outcome on an `account`-scope posture; the door refuses the key with the posture's code. 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. A `credential`-scope posture leaves the key admitted.
197197
- **Every seam that admits a key asks.** PR #23508 made `userAuthGates` a required input of `resolveAuthzContext`, `resolveApiKeyAdmission` and `resolveApiKeyPrincipal`, which closed the two seams that still admitted a held owner's key: the `@objectstack/cloud-connection` install-local resolver and the `@objectstack/mcp` stdio face. The runtime dispatcher, the REST data API, the settings, storage-download, datasource-admin and share-link routes pass the source as well. `resolveApiKeyPrincipal` answers a held owner's key with no principal.
198198
- **The forced reset revokes, on by default.** An administrator's forced password reset (D7) carries `revokeSessionsAndKeys`: it ends every session of the user (the D4 mechanism) and revokes every API key they own. It is on unless the administrator passes a literal `false`. Unlike the hold, this is a revocation: a revoked key stays revoked after the password change.
199199

0 commit comments

Comments
 (0)