# Deployment Mode and Conversion Contract (D3)

Status: DRAFT — awaiting ratification (webui-audit S2, contract 6 of 9).
Authority: PRD D3 (Part I §3) — two modes chosen at install time,
Standalone and Enterprise, with the mode table (brains, user-data
isolation, secrets, conversion); Standalone → Enterprise conversion is
**one-way** and Enterprise is a **terminal state**. PRD D14 (Part I §7)
— the per-user brain split is optional in Standalone and keeping it is
the recommended default because it preserves forward-compatibility with
the one-way conversion. PRD D11 (Part I §9) — v1 ships the Standalone
flow only; Enterprise conversion is explicitly deferred. PRD D3
federation clause — federation is intentionally not fully designed,
deferred, and nothing in v1 may foreclose it.

Revision 2 (luna review F1–F7): the identity precondition restated in
identity-contract terms with a conversion-local acknowledgment record
this contract owns (F1); a durable, keyed preparation state with a
Standalone-safe representation rule, an in-transaction re-check fence,
and an exact flip boundary (F2); the §5.4 unknown-value rule stated
directly without the contradictory non-exhaustiveness clause (F3); the
D14 boundary bound here with a stable column-allowlist witness instead
of delegated to an unratified layout (F4); the conversion witness
matrix extended to every §4.2/§4.4 condition (F5); the mode-record
writer coverage imported concretely from contract 1 §6.3 with a named
schema, closed writer set, crafted-write probe, and mode-resolution
assertion (F6); the mode read command flagged as a §12.1 drafting
addition rather than a D8 mandate (F7). Ownership language aligned
with contract 3 revision 2: mode is recorded at bootstrap and read by
the wizard as input.

This contract binds the mode as a canonical platform property (§2), the
per-mode obligations and which contract owns each (§3), the conversion
transition (§4), the v1 non-foreclosure obligations (§5), and their
witnesses (§6). Domain semantics stay with their owning contracts:
wizard branching (contract 3 §2), identity/SSO
(`identity-lifecycle.md`), custody and per-user brain mechanics
(contract 7, `custody-schema.md`), tool mapping
(`tool-gateway-mapping.md`).

## 1. Definitions

1. **Mode**: the platform-wide deployment mode, exactly one of
   `standalone` or `enterprise`. The vocabulary is closed in v1;
   extension (e.g. a federation mode) is by amendment to this contract,
   never ad hoc.
2. **Conversion**: the one-way transition `standalone → enterprise`.
   No other mode transition exists.
3. **Conversion preconditions**: the verifiable conditions of §4.2 that
   must all hold before the mode record may change.
4. **Preparation unit**: one re-runnable piece of pre-conversion work —
   the migration of one secret to the Vault backend, or the partition
   of one user's brain content (§4.3).

## 2. Mode is a canonical recorded property

1. Mode is recorded canonically in the platform database at bootstrap
   as the operator's install-time choice (D3: modes are "chosen at
   install time"). The record is a single-row keyed record
   (`platform_mode`: mode value, recorded-at timestamp, bootstrap epoch
   reference); this contract owns it, the bootstrap writer performs the
   one v1 write (§6.2), and the wizard reads it as input (contract 3
   §2.3). Mode is never derived from feature state (presence of Vault,
   count of brains, count of users), and no component may infer a
   different mode than the record states.
2. The record is readable by any authenticated user through a Gateway
   command with CLI exposure. This read command is a **drafting
   addition** ratified with this contract (PRD §12.1), not a D8
   mandate: D8 binds only that any surface exposing the value goes
   through official tooling. When a webUI surface consumes the read, a
   mapping row is added to `tool-gateway-mapping.md` by amendment —
   the same route §4.4 already binds for the conversion command.
   Components branch on the read value only.
3. The record is immutable except by the §4 conversion transition.
   Editing it by direct database access, config file, environment
   variable, or wizard re-run is non-conformant (contract 3 §2.3:
   changing mode later is conversion, not a wizard re-run).

## 3. Per-mode obligations (owner map)

The PRD mode table binds four rows; this contract assigns each an
owning contract so no obligation is unowned and none is bound twice:

| Obligation          | Standalone                             | Enterprise                                         | Owner                                         |
| ------------------- | -------------------------------------- | -------------------------------------------------- | --------------------------------------------- |
| Brains              | one mosaic-brain (system + user files) | system brain for config + one brain per user       | contract 7 (custody/brain mechanics)          |
| User-data isolation | single user                            | no user-data leakage between users; sharing opt-in | contract 7 (enforced by architecture, D14)    |
| Secrets             | OpenBao/Vault or flat files            | OpenBao/Vault REQUIRED                             | this contract (§4.2 gate; steady-state check) |
| Conversion          | may convert to Enterprise, one-way     | terminal state                                     | this contract (§4)                            |

The Standalone brains row states the default layout, not the only
valid one: the D14 per-user split is a MAY in Standalone with keeping
it the recommended default (PRD §7, contract 7 §6), and Vault-backed
secrets are equally valid Standalone configuration. Both prepared
states are therefore themselves valid Standalone states — the fact
§4.3 relies on.

In Enterprise steady state, a flat-file secrets backend is
non-conformant; the platform refuses to start Enterprise-mode
components against a flat-file secrets configuration (fail-closed, not
warn-and-run).

## 4. Conversion transition

1. **Direction and terminality.** The only transition is
   `standalone → enterprise`. `enterprise → standalone` does not exist:
   there is no command, no admin override, and no support path. An
   attempt is refused with the precondition/state error class of the
   command envelope (`tool-gateway-mapping.md` §4.2).
2. **Preconditions (all verified before the record changes):**
   - Secrets: OpenBao/Vault is configured and reachable, and every
     required secret is served from the Vault backend — none from a
     flat-file backend. Secret migration completes before conversion;
     this contract does not define the migration tooling, only the
     gate.
   - Brains: the per-user brain split required by the Enterprise row of
     §3 is established for **every** existing user (or the deployment
     already kept the split, the D14 recommended default). Brain
     partitioning mechanics are contract 7; this contract binds only
     that the split is complete before the mode flips.
   - Identity: at least one platform administrator account exists that
     is active in identity-contract terms — authenticated capability,
     not banned, not deactivated (identity §2, §5). And the conversion
     request carries a **configuration acknowledgment**: the current
     canonical values of registration mode and per-provider JIT
     enablement (identity §2.2, §4.1), echoed back in the request. A
     mismatch between the echoed values and the canonical values at
     verification refuses the conversion. This acknowledgment record
     is conversion-local, owned by this contract, and stored with the
     §4.4 audit event as the precondition evidence; it adds no
     identity-contract obligation and no mode-specific identity
     default — identity's own defaults remain valid states.
3. **Preparation state and the flip boundary.** Preparatory work is
   tracked durably: each preparation unit (§1.4) records its
   completion in a preparation table keyed by (bootstrap epoch, unit
   identity — the secret's path, the user's id), written in the same
   transaction as the unit's own effect where the unit's backend
   allows it, and reconciled from the backend's actual state where it
   does not (a secret already served by Vault, a brain already split,
   is complete regardless of the table). Units are at-most-once per
   key and re-runnable across attempts. **Standalone-safe
   representation:** every preparation unit moves the deployment into
   a state that is itself valid Standalone configuration (§3 note), so
   an interrupted preparation leaves a fully operational Standalone
   deployment reading its state through the ordinary contracts — no
   rollback, fencing, or special Standalone read path is needed, and
   no component behavior may key on "preparation in progress".
   **The flip:** one transaction that (a) locks the mode record, (b)
   re-verifies every §4.2 precondition after acquiring the lock, and
   (c) writes the mode record and the §4.4 audit event. Any re-check
   failure aborts with no write. External state that changes after the
   re-check but before commit is bounded by the transaction window;
   an external backend (Vault) failing after conversion is an
   Enterprise runtime fault handled by §3's fail-closed steady-state
   rule, not a conversion defect. An interrupted or failed conversion
   leaves the record `standalone` and the platform fully operational;
   there is no intermediate mode and no half-converted state
   observable through the record.
4. **Authority and audit.** Conversion is a platform-administrator
   command carrying an explicit irreversibility acknowledgment in its
   request (distinct from the §4.2 configuration acknowledgment). It
   is an official Gateway/CLI command (D8): when built, it is added to
   the tool↔Gateway mapping by amendment (`tool-gateway-mapping.md`
   §3.3). The transition emits an audit event (actor, prior mode, new
   mode, precondition evidence reference including the configuration
   acknowledgment) in the same transaction as the record change; the
   event survives indefinitely. A refused attempt emits a refusal
   event naming the failed precondition class and actor, with no
   mode-change event.

## 5. v1 obligations (non-foreclosure)

v1 ships Standalone only (D11); the conversion command is deferred
work. v1 still MUST:

1. Record the mode per §2 at bootstrap, with `enterprise` a reserved,
   refused value for bootstrap — v1 bootstrap accepts `standalone`
   only. The wizard reads the record (contract 3 §2.3); nothing in v1
   writes it after bootstrap.
2. Keep the §2.3 immutability rule: no v1 surface mutates the mode
   record.
3. Not foreclose conversion: the v1 platform database holds no
   sensitive user content — sensitive categories live in the owning
   user's brain, and postgres holds structure, consent records, and
   pointers only (the D14 boundary, PRD §7). Custody mechanics are
   contract 7's; this contract binds the boundary itself here so v1
   cannot ship a layout that makes the §4.2 brain precondition
   unsatisfiable, and §6.3 gives it a stable witness that does not
   depend on contract 7's internals. Conversion implementation
   additionally requires contract 7 ratified.
4. Not foreclose federation: v1 components accept exactly the two §1.1
   values wherever a mode value is parsed and refuse any other value
   **before side effects** — a refused configuration, not undefined
   behavior and not a crash mid-operation. Forward compatibility lives
   in storage and architecture, not in parser speculation: the mode
   record's storage is not structurally locked to two values (no
   database-level two-value enum), and any future value (e.g. a
   federation mode) is defined by a versioned amendment to this
   contract before any component accepts it. The PRD defers
   federation's shape entirely; this contract does not presume it
   arrives as a third mode value.

## 6. Verification requirements

Binding on the implementing PRs:

1. **Mode-record witness (v1):** after bootstrap the mode is readable
   via the Gateway command and CLI and equals the bootstrap-recorded
   choice; bootstrap with mode `enterprise` is refused; bootstrap with
   any unknown mode value is refused before side effects (§5.4).
2. **Writer-coverage witness (v1):** the mode record's writer set is
   closed by the same three-prong static assertion contract 1 §6.3(b)
   defines — symbol, class-table literal, and raw-execution prongs
   with its allowlist composition rules — scoped to the
   `platform_mode` table, with a writer allowlist containing exactly
   the bootstrap writer in v1 (and exactly plus the conversion command
   at the conversion milestone). Companions: a crafted direct write
   attempted in a test fails and leaves the record unchanged; a
   mode-resolution assertion that no shipped component derives mode
   from feature state (mode reads occur only through the §2.2 read
   surface — static assertion over Gateway, CLI, bootstrap, and
   repository sources).
3. **D14-boundary witness (v1):** a column-allowlist assertion in the
   style of contract 1 §6.2 that the platform database schema contains
   no sensitive-content column — the §5.3 boundary — stable regardless
   of contract 7's internals (contract 7 §7 carries the full custody
   witnesses).
4. **No-downgrade witness (conversion milestone):** with mode
   `enterprise`, a conversion request to `standalone` (and any crafted
   mode-write) is refused with the precondition/state error class and
   no record change.
5. **Precondition witnesses (conversion milestone),** each refused
   with no record change and no partial mode effect, parameterized
   over both OpenBao and Vault where secrets are involved:
   (a) secrets backend unreachable; (b) one required secret still
   flat-file backed (migration incomplete); (c) one unpartitioned user
   brain in a **multi-user** deployment where every other user is
   partitioned; (d) no active platform administrator (the only admin
   banned or deactivated); (e) configuration acknowledgment missing or
   mismatching the canonical registration/JIT values; (f) actor not a
   platform administrator (authorization refusal); (g) irreversibility
   acknowledgment absent. And the steady-state rule: an
   Enterprise-mode component started against a flat-file secrets
   configuration refuses to start (§3).
6. **Interruption and fence witnesses (conversion milestone):** fault
   injection aborting conversion after each preparation unit and
   between preparation and flip leaves the record `standalone` and the
   platform operational in Standalone semantics (§4.3
   Standalone-safety), and a re-attempt completes without duplicating
   prepared state (at-most-once keys); a precondition invalidated
   after preparation but before the flip (a secret reverted to
   flat-file) is caught by the in-transaction re-check and refused.
7. **Audit witnesses (conversion milestone):** a completed conversion
   has exactly one mode-change audit event, same-transaction with the
   record change (transaction linkage asserted), carrying actor, prior
   mode, new mode, and the precondition evidence reference including
   the configuration acknowledgment; a failed attempt has a refusal
   event naming the failed precondition class and no mode-change
   event; the mode-change event remains queryable after subsequent
   unrelated audit activity (retention probe).
8. **Mapping witness (conversion milestone):** the conversion command
   and the mode read command each have their
   `tool-gateway-mapping.md` row (added by amendment per §2.2/§4.4)
   before the commands ship.

## Ruling request

Ratify sections 1–6 as written, with one decision embedded:

- Decision (§5): v1 implements the **mode record and its immutability
  only** — bootstrap records `standalone`, the `enterprise` value is
  reserved and refused, and the conversion command itself is deferred
  to the Enterprise milestone, consistent with D11's deferred list.
  v1 carries three obligations beyond the record: the closed writer
  assertion, the D14 column boundary, and the unknown-value refusal
  (§6.1–§6.3) — these are the non-foreclosure floor, not hidden
  conversion work. Alternative if rejected: build the conversion
  command inside v1 — rejected because D11 scopes v1 to the Standalone
  slice and conversion depends on contract 7 custody mechanics that
  are themselves not in the v1 slice.
