From eb48d72f67b3f6989d2ea88bd14a2f139a9065c1 Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 26 Aug 2026 19:55:45 -0500 Subject: [PATCH] =?UTF-8?q?docs(wizard):=20revision=203=20=E2=80=94=20clie?= =?UTF-8?q?nt-side=20composition,=20collect-first=20settings,=20idempotenc?= =?UTF-8?q?y=20fence,=20bound=20name=20sources=20(sol=20r2=20residuals=20+?= =?UTF-8?q?=20N1/N2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/requirements/onboarding-wizard.md | 331 +++++++++++++++++-------- 1 file changed, 231 insertions(+), 100 deletions(-) diff --git a/docs/requirements/onboarding-wizard.md b/docs/requirements/onboarding-wizard.md index 7af5e097..689ced06 100644 --- a/docs/requirements/onboarding-wizard.md +++ b/docs/requirements/onboarding-wizard.md @@ -26,6 +26,28 @@ reconciliation with per-mutation failure injection (F7). The custody note reduced to a cross-reference (F8). Creation eligibility defined in identity terms with an actor matrix (F9). +Revision 3 (sol re-review: residual F2/F3/F5/F6/F7/F9, new N1/N2): the +wizard is now explicitly a client-side composition with no server-side +orchestrator, spanning six named command families, every operation an +ordinary outbound Gateway call inside contract 5 §6.1's inventory scope +(F5). First runs collect steps 1–2 without writing; the collected +settings are applied immediately after the epoch transition under the +first admin's platform authority, so no pre-admin write authority +exists or is invented (N1). Pre-epoch mode branching reads the mode +value from the unauthenticated bootstrap-status response, a disclosed +one-field addition; post-epoch runs use contract 6's authenticated read +(F2). The SSO display-name source is bound to identity §5's mapped name +claim, with a canonical-name witness (F3). At-most-once seeding is +fenced by a caller-supplied idempotency key recorded under a unique +constraint in the same transaction as the mutation; correlation ids +revert to pure trace linkage (F7). Re-run witnesses now cover every +mutable family, assert the second-company grant is same-operation, and +extend tab inheritance to re-run and explicit-choice machinery (F6). +Eligibility is restated in identity §7.1's terms — deactivation is the +better-auth ban, with no separate state (F9). Example-data seeding is +mandatory; the skippability policy is removed (PRD §6.4) (N2). All +drafting additions are consolidated in §7. + Scope: the Gateway-backed product onboarding wizard. Out of scope: the host-local install wizard (`mosaic wizard`, which drives host install and gateway bootstrap and is not this artifact — audit REPORT.md layer 3); @@ -41,10 +63,19 @@ through the extensibility rule §2.4). composes Gateway commands. It is subject to the D8 hard rule like every other webUI surface: no wizard operation reaches the database or filesystem directly, and no wizard-only privileged write path - exists. Concretely, the wizard is contract 5's rank-6 family, - composing the rank-1 hierarchy commands and the rank-4 enrollment - command (contract 5 §3.1): those families are built first, and every - wizard operation is a row in contract 5's mapping. + exists. Concretely, the wizard is contract 5's rank-6 family, and it + is a **client-side composition with no server-side wizard + orchestrator**: every wizard operation is an ordinary outbound + Gateway call issued from the wizard's web modules (or the equivalent + scripted sequence), so contract 5 §6.1's outbound-call inventory, + scoped to those modules, sees the wizard's complete operation set. + The composed operations span six existing families, each already + within contract 5's mapping: the rank-1 hierarchy commands and the + rank-4 enrollment command (contract 5 §3.1 — built first), the + settings command family (steps 1–2), the identity bootstrap and + registration surface (step 3), the mode reads (contract 6 §2.2 + post-epoch; the §2.3 bootstrap-status field pre-epoch), and the + ordinary content commands used for example seeding (step 4). 2. **The wizard introduces no new mutation surface.** Every state change it performs is an existing command with its own contract: user and epoch writes under the identity contract §3, hierarchy writes under @@ -56,10 +87,13 @@ through the extensibility rule §2.4). 3. **Wizard state derives from canonical state.** Each step renders the system's current configuration (read through the same commands) and applies deltas; the wizard does not keep an answer file whose contents - can drift from reality. The wizard MAY persist run bookkeeping + can drift from reality. Values a first run has collected but not yet + applied (the §3.3 application rule) are transient run state, not a + persisted answer file. The wizard MAY persist run bookkeeping (started/completed timestamps); bookkeeping is a cache, never the - source of truth for any decision (§4.3 defines the reconciliation - rule that makes this concrete for seeding). + source of truth for any decision. The §4.3 idempotency fence is + command-layer canonical state, not wizard bookkeeping — losing + bookkeeping never affects it. 4. If a wizard-completion marker is stored, it is presentational only (which entry screen to show). No authorization or gating decision may read it: gating state lives where its owning contract puts it @@ -79,12 +113,20 @@ through the extensibility rule §2.4). deployment mode is recorded canonically at bootstrap and owned by contract 6 (§2 there): the wizard reads the recorded mode and branches on it; it never writes mode and never derives it from - feature state. Changing mode later is conversion (contract 6), not a - wizard re-run. In v1 a recorded mode of `enterprise` is refused at - bootstrap (contract 6 §5), so the wizard's Enterprise branch is - unreachable in v1; a wizard invoked against an unsupported or - unreadable mode record produces a single bounded refusal (contract - 5 §4.2 precondition class), never a partial flow. + feature state. During the bootstrap epoch no authenticated account + exists, so contract 6 §2.2's authenticated read surface is + unreachable there; the pre-epoch flow instead branches on the mode + value reported by the **unauthenticated bootstrap-status response** + of the bootstrap surface (identity §3), which evaluates the mode + record server-side and discloses exactly one field beyond epoch + state: the recorded mode value (drafting addition, §7). Post-epoch + wizard runs read mode through contract 6 §2.2 unchanged. Changing + mode later is conversion (contract 6), not a wizard re-run. In v1 a + recorded mode of `enterprise` is refused at bootstrap (contract 6 + §5), so the wizard's Enterprise branch is unreachable in v1; a + wizard invoked against an unsupported or unreadable mode record + produces a single bounded refusal (contract 5 §4.2 precondition + class), never a partial flow. 4. **Extensibility:** new wizards attach as tabs (D4). Attaching a wizard tab is a registration of additional steps against the same skeleton, inheriting this contract's rules (§1 architecture, §3 step contract, @@ -96,29 +138,44 @@ through the extensibility rule §2.4). The v1 wizard consists of exactly these steps, in order, each backed by the named authority: -1. **System and company name.** System name is a settings write. Company - name feeds step 4's company creation. +1. **System and company name.** System name is a settings value; company + name feeds step 4's company creation. On a first run this step + collects and validates only — application follows step 3's + application rule below. On a re-run (an authenticated admin exists) + it applies directly through the settings commands. 2. **Component choices.** Mosaic Comms/Matrix vs external; Mosaic SSO/Authentik vs external; Mosaic DB/PostgreSQL vs external; vector - DB (D4). v1 records the choices as settings and configures what is - installable in-product; component provisioning beyond that is host - tooling, out of scope here. + DB (D4). v1 records the choices as settings (first run: collected, + applied per step 3's application rule; re-run: applied directly) and + configures what is installable in-product; component provisioning + beyond that is host tooling, out of scope here. 3. **Initial user.** Email, password, and display name, or SSO (PRD §6 step list: "email/password/name/SSO"), per identity §3: the first-admin transition is atomic, one per bootstrap epoch, and the SSO variant runs inside the bootstrap transaction (identity §3.6). - The submitted display name lands on the canonical account record - (identity §2); no field of the PRD's step list is dropped. The - wizard also presents the applicable mandatory choices — registration - mode (identity §2.2) and, for each configured SSO provider, that - provider's JIT enablement with its allowlist warning (identity §4.1, - §4.3). The wizard MUST obtain an explicit submission for each - applicable mandatory choice and MUST refuse step completion while - one is unsubmitted. This is a presentation-and-submission obligation - on wizard runs only: identity's own defaults (registration `closed`, - JIT off) continue to govern everything a wizard run never touches, - and this contract creates no new stored-record obligation beyond - those identity itself defines. + The display-name source is bound per path: the password path uses + the submitted display name; the SSO variant uses the name claim + mapped under identity §5's claim mapping. Canonical `users.name` + MUST equal that source value (witness §6.5); no field of the PRD's + step list is dropped. The wizard also presents the applicable + mandatory choices — registration mode (identity §2.2) and, for each + configured SSO provider, that provider's JIT enablement with its + allowlist warning (identity §4.1, §4.3). The wizard MUST obtain an + explicit submission for each applicable mandatory choice and MUST + refuse step completion while one is unsubmitted. This is a + presentation-and-submission obligation on wizard runs only: + identity's own defaults (registration `closed`, JIT off) continue to + govern everything a wizard run never touches, and this contract + creates no new stored-record obligation beyond those identity itself + defines. **Application rule (first runs):** immediately after the + epoch transition, authenticated as the newly created admin — the + first-admin transition (identity §3) yields the account holding the + RBAC §1.1 platform authority that system and component settings + writes require — the run applies the values collected in steps 1–2 + through the ordinary settings commands. Before the transition the + run holds collected values only: no canonical settings write occurs + pre-epoch, and no pre-admin write authority exists or is invented + (disclosed sequencing addition, §7; witness §6.11). 4. **Initial hierarchy and seeding.** Runs strictly after step 3's epoch transition, authenticated as the newly created admin. The bootstrap writer (identity §3) creates only the first account and closes the @@ -131,13 +188,15 @@ the named authority: commands (contract 1 §5.1) under the admin's `owner` authority (contract 2 §4.3). Seeded examples are ordinary workspace content created by ordinary commands, attributable in audit to the acting - admin with the wizard run's correlation ids (contract 5 §4.3). + admin, carrying the run's trace correlation ids (contract 5 §4.3) + and the §4.3 idempotency keys. 5. **Minimal agent enrollment.** One harness, API-key login, agent name and persona (D11). Enrollment specifics belong to the rank-4 agent-enrollment command family; this contract binds only that the step exists, uses that family, and is skippable. Skippability is a drafting addition under PRD §12.1 (the PRD step list does not mark - the step optional); it is ratified with this contract. + the step optional); it is disclosed in §7 and ratified with this + contract. Steps deferred beyond v1 (user onboarding profile, connectors, comms, voice-matching) appear in `docs/ROADMAP.md` per D11. The deferred @@ -155,33 +214,44 @@ collects no sensitive category, so v1 ships no custody surface. "setup already completed" is a stable state, and factory reset — a future, explicitly destructive operation — is the only path to a new epoch. -3. **Idempotent seeding, reconciled from canonical state.** Step 4 on +3. **Idempotent seeding, fenced at the command layer.** Step 4 on re-run MUST NOT duplicate: the initial company/estate/project/ workspace and the example data are created at most once per bootstrap - epoch. The at-most-once key is a deterministic per-mutation - correlation id derived from the bootstrap epoch and the seed role - (e.g. epoch id + "seed-company"), submitted as the command's - correlation id (contract 5 §4.3) and therefore recorded in the - mutation's audit event in the same transaction as the mutation - itself (contract 1 §5.2). Before applying any seed mutation, the - wizard reconciles against the audit/command log for its epoch's seed - correlation ids and skips every mutation whose event exists. Wizard - bookkeeping of seed node ids is a cache of this record, never the - source of truth: losing it cannot cause duplication. A re-run offers - to create _additional_ hierarchy nodes (PRD: users can create N - companies, N estates, N projects) but never re-creates or resets the - originals, and never touches user content added since. Example-data - seeding is likewise at-most-once per epoch and is skippable. + epoch. The at-most-once mechanism is an **idempotency key**, a + disclosed drafting addition to contract 5's §4 command envelope + ratified with this contract (§7): a mutating command MAY carry a + caller-supplied idempotency key; the command layer records the key + in a uniqueness-constrained fence table **in the same transaction** + as the mutation and its audit event, and a submission whose key is + already recorded executes nothing and returns the recorded outcome. + The wizard submits deterministic keys derived from the bootstrap + epoch and the seed role (e.g. epoch id + "seed-company") for every + seed mutation. Correlation ids (contract 5 §4.3) remain pure trace + linkage and carry no idempotency semantics. Two concurrent resumed + runs serialize on the fence's unique constraint: exactly one + execution per key; the other submission receives the recorded + outcome or a single bounded conflict refusal, never a second + mutation. Wizard bookkeeping of seed node ids remains a cache + (§1.3): losing it cannot cause duplication, because the fence is + canonical command-layer state. A re-run offers to create + _additional_ hierarchy nodes (PRD: users can create N companies, N + estates, N projects) but never re-creates or resets the originals, + and never touches user content added since. Example-data seeding is + likewise at-most-once per epoch; the example set is fixed and its + seeding is not skippable (PRD §6.4: a completed first run contains + the fixed example set). 4. **Interrupted runs strand nothing.** The wizard is resumable at the granularity of a single command: each mutation either commits through - its command with its same-transaction audit event, or leaves nothing - (identity §3.5 for the bootstrap step). Step 4 is a sequence of - individually atomic commands, not one transaction: an interruption - between them leaves a prefix of committed seed nodes, and the §4.3 - reconciliation rule makes the resumed run complete exactly the - remaining suffix, without duplication and without compensating - rollback of completed commands. There is no wizard-level transaction - spanning steps. + its command with its same-transaction audit event and fence row, or + leaves nothing (identity §3.5 for the bootstrap step). Step 4 is a + sequence of individually atomic commands, not one transaction: an + interruption between them leaves a prefix of committed seed nodes, + and the §4.3 fence makes the resumed run complete exactly the + remaining suffix — the run re-submits the full ordered seed sequence + with the same deterministic keys, already-committed mutations return + their recorded outcomes, and only the remainder executes, without + duplication and without compensating rollback of completed commands. + There is no wizard-level transaction spanning steps. ## 5. Seeding authority (resolves contract 2 review NEW-1) @@ -198,10 +268,11 @@ collects no sensitive category, so v1 ships no custody surface. create a top-level company and MUST name an initial `owner` grant in the same audited operation (contract 2 §4.3); the creator naming themselves is the default. Eligible means, in identity-contract - terms: an authenticated account (identity §2) that is not banned and - not deactivated (identity §5). No further role or grant is required. - Until that ruling, deny-by-default holds (contract 2 §3.1): no - implicit creation authority exists. + terms: an authenticated account (identity §2) that is not banned + (identity §7.1 — deactivation on this platform IS the better-auth + ban; no separate deactivated state exists). No further role or grant + is required. Until that ruling, deny-by-default holds (contract 2 + §3.1): no implicit creation authority exists. 3. Child-node creation inside the wizard (estate, project, workspace under the seeded company) follows contract 2 §4.3 unchanged: parent `owner` authority, no automatic grant needed. @@ -211,8 +282,13 @@ collects no sensitive category, so v1 ships no custody surface. Binding on the implementing PRs: 1. **D8 mapping witness:** every Gateway call the wizard makes resolves - to a contract 5 mapping row (asserted by contract 5 §6.1's inventory - scoped to the wizard surface); wizard modules appear in no + to a contract 5 mapping row, asserted by contract 5 §6.1's + outbound-call inventory scoped to the wizard modules — an inventory + that is complete for the wizard because §1.1 forbids a server-side + wizard orchestrator, and the witness statically asserts that + prohibition (no server-side module composes wizard operations). The + inventoried operation set is asserted equal, in both directions, to + the §1.1 declared family list. Wizard modules appear in no class-table writer allowlist (contract 1 §6.3b) and hold no direct database or filesystem access (static assertion, plus a runtime probe that a wizard-context filesystem/database access attempt is @@ -220,9 +296,13 @@ Binding on the implementing PRs: operation, the same actor invoking the underlying command directly receives the same authorization outcome as through the wizard — probed for at least one allowed and one refused actor per operation. -2. **Mode witnesses:** the branch point exists (the flow resolves its - step set from the canonical mode record, read through contract 6's - read surface); v1 with mode `standalone` yields the §3 step set; a +2. **Mode witnesses:** the branch point exists — a pre-epoch flow + resolves its step set from the bootstrap-status mode field (§2.3), + a post-epoch run from contract 6 §2.2's authenticated read; the + bootstrap-status response discloses exactly one field beyond epoch + state (closed-field assertion on the response schema); post-epoch, + an unauthenticated mode read is refused (contract 6 §2.2 holds + unchanged); v1 with mode `standalone` yields the §3 step set; a simulated unsupported or unreadable mode record yields one bounded precondition refusal (§2.3) and no partial flow; wizard sources contain no mode write and no feature-state mode derivation. @@ -230,31 +310,43 @@ Binding on the implementing PRs: completion making no changes — assert zero new hierarchy nodes, zero new users, zero new grants, zero duplicated example content, and the bootstrap epoch still completed (identity §8's epoch witnesses cover - the transition itself). **Reconfigurability:** a re-run changes a - mutable value (registration mode) and the canonical setting reflects - it. **Canonical-state drift:** mutate state outside the wizard (a + the transition itself). **Reconfigurability:** a re-run changes one + value in each mutable family — the system name (settings), a + component choice (settings), registration mode, and a configured + provider's JIT enablement — and each canonical value reflects the + change; a run that skipped enrollment enrolls an agent on re-run + through the rank-4 family and the enrollment exists canonically. + **Canonical-state drift:** mutate state outside the wizard (a rename through the hierarchy command), then re-run — the wizard renders the current canonical value, not a stored answer. **Second company:** a re-run creating a second company succeeds with - the running user as actor, names the creator as `owner` by default - (grant row asserted), and leaves the first company's exact node and - content set untouched (row-set and content-digest assertions). + the running user as actor and names the creator as `owner` in the + same audited operation — asserted on the single audit event naming + both the creation and the grant (contract 2 §4.3–§4.4), not merely + on the grant row's existence — and leaves the first company's exact + node and content set untouched (row-set and content-digest + assertions). 4. **Seed-set witness:** after a first completed run, the created seed set is exactly the declared set — one company, one estate, one project, one workspace, the example content (fixed digest), the one §4.3 `owner` grant, and no other node or grant attributable to the - run's correlation ids. Every seeded node, grant, and example item - has its command audit event (contract 1 §5.2, contract 2 §4.4) - carrying the deterministic seed correlation id (§4.3) and the acting - admin; the first-company event names the initial owner grant in the - same audited operation. + run's idempotency keys or trace correlation ids. Every seeded node, + grant, and example item has its command audit event (contract 1 + §5.2, contract 2 §4.4) committed in the same transaction as its + mutation and §4.3 fence row, attributed to the acting admin; the + first-company event names the initial owner grant in the same + audited operation. 5. **Explicit-choice witness:** a run submitting explicit values completes, and each canonical value equals the value the run submitted (asserted against the captured submissions, not by reading - the canonical store twice); a run with an applicable mandatory - choice unsubmitted is refused at step 3 (§3.3); identity's defaults - are asserted for a provider the run never touched (registration - `closed`, JIT off — identity §2.2, §4.1). + the canonical store twice); canonical `users.name` equals the path's + declared source — the submitted display name for the password path, + the identity §5 mapped name claim for the SSO variant (asserted + against the captured submission or captured assertion); a run with + an applicable mandatory choice unsubmitted is refused at step 3 + (§3.3); identity's defaults are asserted for a provider the run + never touched (registration `closed`, JIT off — identity §2.2, + §4.1). 6. **Gating-independence witness:** with any wizard-completion marker deleted (§1.4), authorization and registration gating behave identically — proving no gate reads wizard state. @@ -263,36 +355,75 @@ Binding on the implementing PRs: individual seed command (company, estate, project, workspace, each example item), and after the bookkeeping cache write — and for each injection point assert: no stranded partial outcome (every committed - command has its same-transaction audit event; nothing else exists), - and a resumed run completes exactly the remaining mutations with no - duplication (identity §8 covers the bootstrap step; §4.3–§4.4 define - the reconciliation the witness exercises). Deleting the bookkeeping - cache between failure and resume MUST NOT change the outcome. + command has its same-transaction audit event and fence row; nothing + else exists), and a resumed run completes exactly the remaining + mutations with no duplication (identity §8 covers the bootstrap + step; §4.3–§4.4 define the fence the witness exercises). Deleting + the bookkeeping cache between failure and resume MUST NOT change the + outcome. **Idempotency-fence witnesses:** re-submitting a committed + seed command with its same key executes nothing and returns the + recorded outcome (asserted by mutation-count and audit-event-count + equality); two concurrent resumed runs executing the seed sequence + yield exactly one seed set — per key, exactly one mutation and one + audit event exist, and the losing submission received the recorded + outcome or the single bounded conflict refusal. 8. **Actor-matrix witness (§5.2):** post-bootstrap top-level company creation succeeds for an ordinary authenticated non-admin user - (positive), and is refused for an unauthenticated caller, a banned - account, and a deactivated account (negatives), with contract 2's - error classes. + (positive), and is refused for an unauthenticated caller and for a + banned account (identity §7.1's deactivation-as-ban) (negatives), + with contract 2's error classes. 9. **Tab-inheritance witness:** a registered test tab's steps are subject to the same assertions — its modules fail the §6.1 static - assertion if they access the database directly, and its mutations - appear in the §6.1 mapping inventory — proving §2.4 is enforced by - machinery, not convention. + assertion if they access the database directly, its mutations + appear in the §6.1 mapping inventory, a tab re-run making no + changes produces zero new mutations (§6.3 style), and a tab + declaring a mandatory choice is refused while it is unsubmitted + (§6.5 style) — proving §2.4 is enforced by machinery, not + convention. 10. **Enrollment witness:** the enrollment step exists, invokes only the rank-4 family, and a run that skips it completes with zero enrollment-family mutations. +11. **Pre-epoch authority witness (§3.3 application rule):** during a + first run, before the epoch transition, canonical settings are + untouched (settings-state comparison against the pre-run capture), + and an injected settings-write attempt from the unauthenticated + pre-epoch flow is refused with contract 5 §4.2's authorization + class; after step 3, the collected values are applied and each + application is attributable in audit to the new admin. + +## 7. Drafting additions (PRD §12.1 disclosure) + +Proposed drafting additions, visible here for ratification, each +severable; the step list, mode branching, re-runnability, and seeding +obligations themselves are traced to PRD D4/D11 and the named sibling +contracts and are not additions: + +1. Enrollment-step skippability (§3.5) — the PRD step list does not + mark the step optional. +2. The unauthenticated bootstrap-status mode disclosure — exactly one + field (the recorded mode value), during the bootstrap epoch only + (§2.3). +3. The collect-first, post-epoch application sequencing for steps 1–2 + (§3.3 application rule). +4. The idempotency-key field and its same-transaction uniqueness fence + (§4.3) — an addition to contract 5 §4's command envelope, proposed + and ratified here, severable from the rest of this contract. +5. The presentation-and-submission obligation for applicable mandatory + choices on wizard runs (§3.3). +6. The no-server-side-orchestrator architectural constraint (§1.1). +7. The wizard-completion-marker presentational-only rule (§1.4). ## Ruling request -Ratify sections 1–6 as written, with one decision embedded: +Ratify sections 1–7 as written, with one decision embedded: - Decision (§5.2): post-bootstrap, any eligible platform user - (authenticated, not banned, not deactivated — identity §2/§5) may - create a top-level company, naming an initial `owner` grant (default: - self) in the same audited operation. Basis: PRD Part I §4 "Users can - create N companies, N estates, N projects" read as end-user - capability, not admin-only. Alternative if rejected: top-level - creation stays deny-by-default and becomes a platform-admin-granted - capability in a later contract — nothing in this contract or contract - 2 breaks either way, because deny-by-default is the resting state - (contract 2 §3.1). + (authenticated, not banned — identity §2/§7.1) may create a top-level + company, naming an initial `owner` grant (default: self) in the same + audited operation. Basis: PRD Part I §4 "Users can create N + companies, N estates, N projects" read as end-user capability, not + admin-only. Alternative if rejected: top-level creation stays + deny-by-default and becomes a platform-admin-granted capability in a + later contract — nothing in this contract or contract 2 breaks + either way, because deny-by-default is the resting state (contract 2 + §3.1).