Docs updated.

This commit is contained in:
Scott Duensing 2026-05-17 17:38:33 -05:00
parent f5cd8d88eb
commit 60409b9014

View file

@ -81,6 +81,88 @@ or `starter`.
Admin UI: `/admin/dev-deploy/` (logged in as an admin). Machine API:
`/dev-deploy/api/{journal,ingest,file/:uuid,health}` (HMAC-signed peer requests).
### Pairing two instances
Do this once per pair (e.g. MAIN ↔ TEST). Each side needs the other's
`env_id` (a UUID shown on its `/admin/dev-deploy/` dashboard) and base URL.
1. **On instance A** — go to `/admin/dev-deploy/peers`. Under "Add peer", fill
in B's `env_id`, a label, B's base URL, and leave "Existing secret" blank.
Click **Pair**. A's response shows a 64-hex shared secret **once** — copy it.
2. **On instance B**`/admin/dev-deploy/peers` → "Add peer". Fill in A's
`env_id` + base URL, paste the secret from step 1 into "Existing secret",
click **Pair**.
Both sides now have a peer row with a sealed copy of the shared secret. The
plain secret is never persisted on either side — only the AES-256-GCM
ciphertext (KEK derived from `SALTCORN_SESSION_SECRET`). Use **Rotate** on
either side to roll the secret (the peer page surfaces the new one once;
paste it on the other side via Rotate).
### Promoting changes (source → target)
1. Make metadata edits on the source (e.g. create a table on MAIN).
2. Go to `/admin/dev-deploy/plan`, pick the target peer, click **Show plan**.
Lists the ops that would be sent (everything since the last outbound
anchor for that peer).
3. Click **Promote N ops**. The target applies them, advances the outbound
anchor, and returns per-op results (applied / error / conflict counts in
the redirect flash).
Plugin-list mismatches between source and target are surfaced as warnings in
the same flash message (e.g. "peer missing plugin X", "plugin version mismatch
on Y"). They don't block the promote — they're advisory.
### Pulling changes (target ← source)
On the target, `/admin/dev-deploy/peers`**Pull** on the source's peer row.
Fetches everything since the last inbound anchor and applies it. If any op
conflicts with a local op on the same entity since the last sync, the incoming
op is journaled with `status='conflict'` and **not** applied; the flash
redirects to `/admin/dev-deploy/conflicts`.
### Resolving conflicts
A conflict means both sides edited the same entity since the last sync. On
the target side at `/admin/dev-deploy/conflicts`, each pending conflict shows
the incoming op (theirs) next to the local op (mine):
- **Use theirs** — apply the incoming op now, overwriting the local change.
The local op stays in the journal but its effect is gone.
- **Use mine** — mark the incoming op `rejected`. Local state stands. The
peer keeps trying to ship the op on future pulls; we skip it by op_id.
- **Merge per field** (only for `update_X` vs `update_X` conflicts) — opens
a form with each diverging field. Pick **keep current**, **take incoming**,
or type a custom value per field. Submitting marks the conflict `merged`.
### Per-table data mode
By default, table **rows** are local-only (`user` mode) and dev-deploy only
migrates the table's schema. Change this per-table at `/admin/dev-deploy/tables`:
- **user** — rows belong to each environment; never synced. The Saltcorn
`users` table is hard-locked to this mode.
- **starter** — on the first promote, ships current rows once. The target
then owns them; future row changes on the source don't propagate.
- **managed** — rows always sync from source. Source is canonical; target's
row edits get overwritten or surface as row conflicts.
Switching to `managed` or `starter` adds a hidden `_dd_row_uuid` column to
the table via raw `ALTER` (not registered in `_sc_fields`, so Saltcorn's
table builder doesn't show it) and ships current rows in the next promote.
Switching back to `user` drops the column (best-effort; older SQLite without
DROP COLUMN support will error). FKs to user-mode tables are NULL'd on the
target with a warning attached to the op.
### Reverting an op
The journal at `/admin/dev-deploy/ops` shows every op with a **Revert** button.
Revert appends a compensating op (`create_X` → drop; `drop_X` → recreate from
the captured before-snapshot; `update_X` → re-apply before-snapshot as a
patch) rather than rewriting history. The inverse op promotes to peers like
any other op on the next push. Reverting a `drop` produces a new entity with
a fresh UUID — content is restored, identity is not.
## Tests
```bash