From 84fe8b6ef130875fbd10a7567928006c3bc949ee Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 26 Aug 2026 21:43:57 -0500 Subject: [PATCH] docs: onboarding-wizard contract revision 7 (sol r6 F6 residual + NEW-8) --- docs/requirements/onboarding-wizard.md | 108 +++++++++++++++++++++---- 1 file changed, 93 insertions(+), 15 deletions(-) diff --git a/docs/requirements/onboarding-wizard.md b/docs/requirements/onboarding-wizard.md index a8dd87cb..11d422fa 100644 --- a/docs/requirements/onboarding-wizard.md +++ b/docs/requirements/onboarding-wizard.md @@ -104,6 +104,24 @@ under two different operations executes both independently authorization refusal records no fence row so an authorized retry executes afresh (F7). +Revision 7 (sol re-review 5: residual F6, new N8): the fence gains a +**replay mode** — each keyed submission declares `actor-bound` +(default) or `shared`, recorded in the fence row; a shared row replays +for any freshly authorized actor whose scope and payload digest match, +while an actor-bound row keeps the revision-6 actor-equality rule, and +a declared-mode mismatch is a collision. Every §3.4 seed submission +declares shared, reconciling actor-bound replay with re-runnability: +the seed set is a per-epoch singleton, so a fresh-authorized second +admin's no-change re-run replays the first run's recorded outcomes +instead of colliding (N8). §6.7's different-actor witness splits into +the actor-bound refusal and the shared replay, plus a mode-mismatch +collision and a shared-row lost-eligibility refusal; §6.3 adds the +second-admin re-run witness (N8). §6.3's changed-name case now +continues through step 4 with the bookkeeping cache deleted — original +digests asserted, every seed outcome replayed, zero new nodes, run +completed — proving the dirty form value never enters derivation and +the renamed row resolves cache-independently (F6). + 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); @@ -325,7 +343,10 @@ the named authority: byte-stable across every re-run and resume: the same keys carry the same payload digests, so already-committed mutations replay (recorded outcomes) rather than collide, regardless of any - hierarchy rename performed since. The §4.3 fence, not the + hierarchy rename performed since — and, because every seed + submission declares §4.3's `shared` replay mode, they replay for + whichever currently eligible admin performs the re-run, not only + the actor the fence rows record. The §4.3 fence, not the parameter's presence or absence, is what prevents re-seeding; the parameter is provenance the derivation reads, never a value any later step may change. @@ -375,18 +396,32 @@ collects no sensitive category, so v1 ships no custody surface. as the mutation and its audit event: the key, the operation identifier, the acting principal, the mutation's authorization scope (the target company, or the platform scope for top-level - operations), a digest of the canonicalized request payload, and + operations), a digest of the canonicalized request payload, the + submission's declared **replay mode** — `actor-bound` (the + default) or `shared` (§7.4) — and a reference to the committed outcome. Fence uniqueness is the pair (operation identifier, key). - **Replay.** A submission whose (operation, key) pair is recorded is first authorized exactly as a fresh submission would be — an actor who is not authorized receives the authorization refusal, - never the recorded outcome. If authorization passes and the - recorded actor, scope, and payload digest all equal the - submission's, the command executes nothing and returns the - recorded outcome. + never the recorded outcome. If authorization passes, the + submission's declared replay mode equals the recorded row's, and + the recorded scope and payload digest equal the submission's, + the row's replay mode decides the actor rule: an `actor-bound` + row additionally requires the recorded actor to equal the + submitter; a `shared` row replays for **any** authorized + submitter. On replay the command executes nothing and returns + the recorded outcome. A shared replay adds no audit event and + leaves the fence row untouched: audit attributes each mutation + to the actor who performed it, the recorded acting principal is + immutable, and the recorded outcome discloses nothing beyond + what the replaying actor's just-verified live authorization + already grants — the same mutation that actor could have + performed afresh had no fence row existed. - **Collision.** A submission whose (operation, key) pair is - recorded but whose actor, scope, or payload digest differs is + recorded but whose scope, payload digest, or declared replay + mode differs — or, against an `actor-bound` row, whose actor + differs — is refused with a single bounded conflict error (contract 5 §4.2); it executes nothing and discloses nothing of the recorded outcome. @@ -405,7 +440,15 @@ collects no sensitive category, so v1 ships no custody surface. 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 + seed mutation, and every seed submission declares the `shared` + replay mode: the seed set is a per-epoch singleton (§4.4), derived + from immutable provenance (§3.4), so whichever currently eligible + admin performs a re-run or resume must replay the recorded + outcomes rather than collide on actor identity — re-runnability is + not restricted to the original acting admin (witness §6.3). All + other submissions default to `actor-bound`; nothing in this + contract declares `shared` outside the seed sequence. Correlation + ids (contract 5 §4.3) remain pure trace linkage and carry no idempotency semantics. Wizard bookkeeping of seed node ids remains a cache (§1.3): losing it cannot cause duplication, because the fence is @@ -519,17 +562,36 @@ Binding on the implementing PRs: rename through the hierarchy command), then re-run — the wizard renders the current canonical value, not a stored answer. **Seed-provenance witnesses (§3.1, §3.4):** rename the seed - company through the hierarchy command, then re-run to completion — + company through the hierarchy command, delete the wizard's + seed-node bookkeeping cache, then re-run to completion — the re-derived seed sequence carries the same keys and payload digests as the first run (asserted digest equality), every seed submission replays with the recorded outcome, zero new nodes are - created, and the company keeps its renamed name; a re-run + created, the run completes, and the company keeps its renamed + name — the deleted cache proving the renamed row was resolved + from canonical state alone (§1.3), not from a stored id. A re-run submitting a different company name renames the company row and - commits no settings write to the seed key; a direct post-epoch + commits no settings write to the seed key, **and that same run**, + with the bookkeeping cache likewise deleted beforehand, continues + through step 4 deriving the seed sequence from the immutable seed + parameter — asserted to carry the first run's keys and payload + digests, not digests of the newly submitted name — replays every + seed outcome, creates zero new nodes, zero new users, and zero + new grants, and completes; a direct post-epoch settings write to `bootstrap.seed-company-name`, by any actor, is refused; and a control run that force-mutates a seed payload digest receives the §4.3 collision refusal, proving the fence still discriminates. + **Second-admin re-run witness (§4.3 shared replay):** after admin + A's completed first run, create a second account B and grant it + platform-admin and the hierarchy authority the seed commands + require (B is freshly authorized, and no text restricts re-runs + to A); delete the bookkeeping cache; B re-runs the wizard to + completion making no changes — every seed submission replays with + A's recorded outcome (asserted by mutation-count and + audit-event-count equality across the run, the fence rows still + recording A as acting principal), zero new hierarchy nodes, + users, grants, or example content exist, and the run completes. **Second company:** a re-run creating a second company succeeds with the running user as actor and names the creator as `owner` in the same audited operation — asserted on the single audit event naming @@ -586,13 +648,21 @@ Binding on the implementing PRs: mutation-count and audit-event-count equality); the same key with a changed payload digest is refused with the single bounded conflict and executes nothing; the same key and payload submitted by a - different authorized actor is refused with the same single bounded - conflict (actor mismatch) and executes nothing; the same key, + different authorized actor against an **actor-bound** fence row (a + control row recorded outside the seed sequence) is refused with + the same single bounded conflict (actor mismatch) and executes + nothing, while against a **shared** row (every §3.4 seed fence) it + replays with the recorded outcome, executing nothing and adding no + audit event, the fence row still recording the original actor; a + submission whose declared replay mode differs from the recorded + row's is refused with the single bounded conflict and executes + nothing; the same key, actor, and payload submitted against a different authorization scope is likewise refused (scope mismatch) and executes nothing; a replay by an actor who has since lost eligibility (identity §7.1 ban) receives the authorization refusal, - not the recorded outcome; a submission that failed before commit + not the recorded outcome — asserted against a shared seed fence, + proving shared replay never bypasses fresh authorization; a submission that failed before commit leaves no fence row and its retry executes; 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 @@ -665,7 +735,15 @@ contracts and are not additions: fence, with the §4.3 replay, collision, no-error-replay, and concurrency rules — an addition to contract 5 §4's command envelope, proposed and ratified here, severable from the rest of - this contract. + this contract. Including the **replay mode**: each keyed + submission declares `actor-bound` (default) or `shared`, the + declaration is recorded in the fence row, a shared row replays + for any freshly authorized actor whose scope and payload digest + match while an actor-bound row also requires recorded-actor + equality, and a declared-mode mismatch is a collision. This + contract declares `shared` for exactly the §3.4 seed sequence — + deterministic, epoch-scoped singletons any eligible admin must be + able to re-run (§4.1) — and for nothing else. 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),