docs: identity account-lifecycle contract (S2 contract 4) #1433

Merged
fred merged 4 commits from contract/identity-lifecycle into next 2026-08-27 00:13:50 +00:00
Collaborator

Contract 4 of the 9 S2 prerequisites (webui-audit REPORT.md), first on the critical path per the T10 ruling (Q1 = O1, better-auth owns identity per D10).

Covers: system of record, registration gating (registration_mode open/invite/closed), the bootstrap/first-admin invariant folded in from #1430 (all-writer coverage or DB enforcement, pool safety, D4 re-runnability, atomic outcome — each constraint traces to PR #1431's review blockers), OIDC JIT provisioning, claim mapping, account-linking trust, deactivation propagation, and verification requirements (real-Postgres concurrency test for the invariant).

One embedded ruling: registration defaults to closed after bootstrap (§2.2).

Refs #1430. Base: next.

Contract 4 of the 9 S2 prerequisites (webui-audit REPORT.md), first on the critical path per the T10 ruling (Q1 = O1, better-auth owns identity per D10). Covers: system of record, registration gating (registration_mode open/invite/closed), the bootstrap/first-admin invariant folded in from #1430 (all-writer coverage or DB enforcement, pool safety, D4 re-runnability, atomic outcome — each constraint traces to PR #1431's review blockers), OIDC JIT provisioning, claim mapping, account-linking trust, deactivation propagation, and verification requirements (real-Postgres concurrency test for the invariant). One embedded ruling: registration defaults to closed after bootstrap (§2.2). Refs #1430. Base: next.
fred added 1 commit 2026-08-26 22:49:35 +00:00
fred added 1 commit 2026-08-26 23:05:35 +00:00
- §3: durable fail-closed epoch + writer-level refusal + atomic transition
  (adapter must be transaction-bound or bootstrap writes users itself);
  first-admin SSO defined as the bootstrap writer, not JIT
- §5: canonical account email split from per-principal provider claims;
  explicit email-change workflow only
- §6: external principal keyed by issuer+subject, DB-unique, immutable
  provider->issuer binding; explicit link requires step-up reauth
- §2/§4: JIT per-provider default off always, wizard-explicit enable;
  effective closed during epoch; allowlist requires verified email
- §7.1: measurable deactivation (atomic-or-fail-closed ban+revocation,
  all transports, 30s/next-message socket bound, admin tokens)
- §7.3: deletion gated here; hard-delete endpoint fails closed until a
  chartered deletion/retention contract ratifies (contract 7 is D14 custody)
- §1.4: account creates no workspace membership/authority (SOT REQ-TEN-001)
- §8: expanded verification matrix with bounded observables
- factual fixes: 21 FK constraints across 19 tables; ci-postgres is the
  postgres service of the test step
fred added 1 commit 2026-08-26 23:21:26 +00:00
docs: identity lifecycle contract revision 3
ci/woodpecker/pr/ci Pipeline is pending
e595855adc
Addresses the revision-2 re-review residuals and new findings:
- emailVerified joins the canonical set; defined reset rule on email change
- external-principal uniqueness moved to (issuer, subject)
- 'usable login method' defined for unlink refusal
- shipped delete affordances (web admin, mosaic CLI) must be removed or
  disabled with a defined visible state; deactivation exposed instead
- admin-creation switch dropped in favor of plain admin authorization
- verification matrix extended: IdP removal, forward-auth non-use,
  first-admin SSO, wizard-recorded JIT choice, step-up positive/expiry,
  issuer-aliasing witness, UI/CLI delete assertions, docs observable
fred added 1 commit 2026-08-26 23:21:43 +00:00
style: prettier formatting in identity-lifecycle.md
ci/woodpecker/pr/ci Pipeline was successful
339e147f77
rev-code-01 approved these changes 2026-08-26 23:34:41 +00:00
rev-code-01 left a comment
Member

APPROVED — rev-code-01, pinned to head 339e147f775efb0e2f57905ce45db66dbad3f761 (re-verified unmoved immediately before posting; matches fred's corrected sha).

Scope: doc-only, single file docs/requirements/identity-lifecycle.md (+328 lines, four commits). Verified independently of the three model reviews; every codebase-factual claim in the contract was measured by me against the tree at this head:

  1. §2 current state: emailAndPassword: { enabled: true } with no gating confirmed (packages/auth/src/auth.ts:42); default role member confirmed (additionalFields + admin plugin defaultRole: 'member').
  2. §1.2: generic-OAuth wiring confirmed — sso.ts builds provider configs, createAuth passes them to genericOAuth() (auth.ts:14-27).
  3. §6.1: the (provider_id, account_id) index is non-unique today (packages/db/src/schema.ts:87, migration 0003); accounts.issuer exists since migration 0017 (nullable), so the (issuer, subject) unique-constraint requirement is implementable without schema invention.
  4. §7.3: the hard-delete endpoint is live (@Delete(':id') in admin.controller.ts:131db.delete(usersTable)), and both shipped delete affordances exist exactly as named (web admin/page.tsx:138 handleDelete; CLI packages/mosaic/src/commands/auth.ts:245 'delete <id>').
  5. §7.3 FK fan-out: counted myself — exactly 21 FK constraints to users.id across 19 referencing tables in packages/db/src/schema.ts. Claim exact.
  6. §7.1: admin.guard.ts contains no banned check (claim exact); ban route is a user-row update only (requireUpdated(..., { banned: true, ... }), no session revocation).
  7. §8.1: the named test pattern exists (connector-lease.postgres.integration.test.ts) and ci-postgres serves the test CI step (.woodpecker/ci.yml:215,239); the one-pooled-handle caveat is consistent with createDb (single postgres() handle, DB_POOL_MAX, packages/db/src/client.ts:13-18).
  8. §1.4: REQ-TEN-001 / REQ-ID-001 exist in the native-kanban SOT corpus (KBN-010). §5.1: users.emailVerified exists (schema.ts:28).

Gates run by me in a fresh worktree at this head: format:check rc=0 with discriminating control (parent e595855a red under pinned prettier, head green — the style commit is necessary and effective); web-deps build rc=0; typecheck rc=0 (45/45 incl. preflight); lint rc=0 (25/25).

Document quality: internal cross-references (§5.1↔§6.4, §6.5↔§8.5, §2.3↔§3) are consistent; the rev-2/rev-3 revision notes map to the lane review files; every MUST has a bounded observable in §8. One decision is explicitly carved out for ratification (§2.2 default closed) — correctly surfaced, not silently embedded.

Non-blocking notes:

  • [S1] §8.1's parenthetical on the connector-lease pattern is accurate but terse; when implementing, the two-connection harness should state whether it uses two createDb handles or two raw postgres() clients (pool semantics differ).

CI note: pipelines 2807/2812 killed, 2815 canceled, 2816 running on this head at review time (queue congested). Merge must wait for 2816 terminal green.

**APPROVED — rev-code-01, pinned to head `339e147f775efb0e2f57905ce45db66dbad3f761`** (re-verified unmoved immediately before posting; matches fred's corrected sha). Scope: doc-only, single file `docs/requirements/identity-lifecycle.md` (+328 lines, four commits). Verified independently of the three model reviews; every codebase-factual claim in the contract was measured by me against the tree at this head: 1. **§2 current state:** `emailAndPassword: { enabled: true }` with no gating confirmed (`packages/auth/src/auth.ts:42`); default role `member` confirmed (additionalFields + admin plugin `defaultRole: 'member'`). 2. **§1.2:** generic-OAuth wiring confirmed — `sso.ts` builds provider configs, `createAuth` passes them to `genericOAuth()` (`auth.ts:14-27`). 3. **§6.1:** the `(provider_id, account_id)` index is non-unique today (`packages/db/src/schema.ts:87`, migration 0003); `accounts.issuer` exists since migration 0017 (nullable), so the (issuer, subject) unique-constraint requirement is implementable without schema invention. 4. **§7.3:** the hard-delete endpoint is live (`@Delete(':id')` in `admin.controller.ts:131` → `db.delete(usersTable)`), and both shipped delete affordances exist exactly as named (web `admin/page.tsx:138 handleDelete`; CLI `packages/mosaic/src/commands/auth.ts:245 'delete <id>'`). 5. **§7.3 FK fan-out:** counted myself — exactly **21** FK constraints to `users.id` across **19** referencing tables in `packages/db/src/schema.ts`. Claim exact. 6. **§7.1:** `admin.guard.ts` contains no `banned` check (claim exact); ban route is a user-row update only (`requireUpdated(..., { banned: true, ... })`, no session revocation). 7. **§8.1:** the named test pattern exists (`connector-lease.postgres.integration.test.ts`) and `ci-postgres` serves the `test` CI step (`.woodpecker/ci.yml:215,239`); the one-pooled-handle caveat is consistent with `createDb` (single `postgres()` handle, `DB_POOL_MAX`, `packages/db/src/client.ts:13-18`). 8. **§1.4:** REQ-TEN-001 / REQ-ID-001 exist in the native-kanban SOT corpus (KBN-010). **§5.1:** `users.emailVerified` exists (`schema.ts:28`). Gates run by me in a fresh worktree at this head: `format:check` rc=0 with discriminating control (parent `e595855a` red under pinned prettier, head green — the style commit is necessary and effective); web-deps build rc=0; `typecheck` rc=0 (45/45 incl. preflight); `lint` rc=0 (25/25). Document quality: internal cross-references (§5.1↔§6.4, §6.5↔§8.5, §2.3↔§3) are consistent; the rev-2/rev-3 revision notes map to the lane review files; every MUST has a bounded observable in §8. One decision is explicitly carved out for ratification (§2.2 default `closed`) — correctly surfaced, not silently embedded. Non-blocking notes: - [S1] §8.1's parenthetical on the connector-lease pattern is accurate but terse; when implementing, the two-connection harness should state whether it uses two `createDb` handles or two raw `postgres()` clients (pool semantics differ). CI note: pipelines 2807/2812 killed, 2815 canceled, **2816 running on this head at review time** (queue congested). Merge must wait for 2816 terminal green.
fred merged commit 2a30c68b84 into next 2026-08-27 00:13:50 +00:00
Sign in to join this conversation.