# Parallel production preview deployment pattern

Use this reference when a preview must run beside an unchanged production fallback on an existing private machine.

## Release shape

1. **Commit the regeneration contract first.** Any queue worker, timer, or post-write refresh must preserve the same named profile used by manual generation. Define a small allowlist (`default`, one explicit preview profile); reject unknown, combined, whitespace-mutated, or contradictory values before touching the queue or database.
2. **Prove profile symmetry with synthetic data.** Test disabled output, enabled assets/markup, queue→worker→regeneration, and invalid-profile fail-closed behavior. The queue test must use a private database copy and a synthetic action, never production.
3. **Publish the preflight commit before runtime work.** Verify remote SHA and 0/0. This makes every installed artifact traceable to a remote commit and keeps runtime rollback independent from uncommitted source.
4. **Take a metadata-only inventory.** Capture hashes, modes, ownership, sizes, service state/restart counters, queue count/modes, available disk, and database integrity. Resolve effective runtime configuration from both unit `Environment=` values and referenced environment files without printing either source; do not assume values found in one source are complete. Never print environment contents, record data, document names, paths stored in records, or tokens.
5. **Prepare rollback before mutation.** Use SQLite's backup API for an active database when the release changes the database; do not copy the live database file directly. For a read-only/UI-only release, record `database_changed=false` and verify integrity instead of creating a needless database mutation path. Store previous HTML, installed scripts/assets, units as applicable, and a metadata manifest in an owner-only directory. The manifest must record targets that did not previously exist so rollback removes newly introduced files rather than leaving mixed-version residue. Write the exact restore/reload sequence before migration or installation.
6. **Migrate narrowly.** Run only the versioned migration commands, then their check command and a fresh SQLite integrity check. Confirm ownership/mode remain acceptable. On failure, stop and execute the prepared rollback rather than improvising another schema change.
7. **Install atomically.** Stage files under the destination filesystem, set intended owner/mode, verify staged hashes, then rename into place. Install only files needed by the pinned remote commit. Validate user units before daemon-reload and restart only affected services. After restart, compare every installed release file to the pushed source hash; never leave a runtime-only patch or deploy from a dirty worktree.
8. **Preserve the fallback invariant.** Hash the fallback HTML before and after; do not regenerate or replace it. Keep existing bind address, private access mechanism, host allowlist, CSP, CSRF, and session policy unchanged.
9. **Smoke in layers against the effective private bind.** Do not assume loopback merely because the unit defaults to it: a private environment file may bind the service to an existing private interface. Derive the effective endpoint without printing secret environment values. `active/running` is not readiness; wait for the listener and then verify HTTP 200 for fallback and preview, local asset loads, enabled profile markers, exactly one browser-session bootstrap, no external requests, core search/view/navigation, browser history, zero `console.error`/`pageerror` including CSP violations, and mobile overflow.
10. **Audit browser secrecy semantically, not with one broad keyword regex.** A SameSite/CSRF workflow can legitimately place a CSRF field in generated HTML. Reject bearer/API credentials, authorization headers, filesystem locations, sensitive URL parameters, and persisted storage secrets; do not misclassify the word `csrf` itself as a credential leak. Confirm URL and storage separately from HTML.
11. **Navigate through the product router during live smoke.** A hand-written query such as `?view=explorer` may not be a supported route even when the Explorer is healthy. Open parent tabs through their actual controls, then exercise child searches and actions. Treat unsupported guessed deep links as a smoke-script error, not a product regression.
12. **Separate production-safe and synthetic smokes.** Exercise queue→worker regeneration against a private database copy before restarting production. Do not enqueue a synthetic production action; leave the real check-in to the user.
13. **Commit deployment evidence after success.** Evidence should contain technical metadata only: commit SHAs, hash comparison, migration/integrity status, service states, smoke counts, private route label, rollback readiness, and repository sync. Mark automated checks separately from reviews.

## Secure authenticated publication

- Prefer the operator's canonical owner-only token file over ambient tokens whose repository scope is unknown. Verify mode without printing content.
- For HTTPS Git, use a temporary `0700` `GIT_ASKPASS` helper that reads the token file at invocation time; never embed the token in the script, URL, command line, evidence, or Git config.
- Explicitly override any inherited/stale `GIT_ASKPASS` for **both** push and the follow-up fetch/`ls-remote`. Do not delete the helper between push and remote verification. Remove it only after local SHA, remote SHA, Ahead/Behind `0/0`, and clean-tree checks complete.
- Treat HTTPS `403` and SSH `publickey denied` as authorization-path failures, not evidence that the commit was rejected. If another approved canonical credential exists, switch to that credential source; otherwise stop and request operator action.

## Authenticated private smoke contracts

- A sensitive preview may intentionally return `401` without a principal and `200` with authenticated Basic/Bearer access. Record both statuses. For Playwright, configure `httpCredentials` on the browser context; do not put credentials in the URL.
- Derive the installed runtime file set from the pushed base-to-HEAD diff, map only runtime-relevant paths, back up those targets plus the generated preview, regenerate the exact enabled profile, install atomically, restart, and require source-hash equality for every installed source.
- Live smoke scripts must follow the actual payload and DOM contracts. Confirm field names and selectors from source before treating a zero count or missing locator as product failure. A `supported_no_data` response or a wrong aggregate key is not coverage proof.
- When dates are unknown, scan a bounded allowed interval and stop at the first documented day, reporting only aggregate counts/completeness—not names, values, snippets, or raw payloads. Verify document/card and coverage-label counts, nutrition item/missing-mapping counts, CSP/no-store, zero failed requests, zero console errors, and the protected fallback hash.
- If the first smoke uses a stale selector or wrong response field, correct the probe and rerun; preserve product code. This is a smoke-harness correction, not a product repair attempt.

## Approval-gated service operations

Commands such as user-service inspection, daemon reload, restart, and deployment writes may require an execution approval even when the user authorized the overall deployment in prose. Probe this boundary early, before irreversible work. Keep approval-gated service calls separate from parallel inventory calls so a blocked confirmation does not obscure successful read-only results. If the tool reports that confirmation timed out, do not retry or route around it: report the exact blocked command class and request explicit continuation. A timeout is not consent.

## Evidence rules

- Hashes are safe metadata; file contents and private environment values are not.
- Record queue counts and permission modes, not action payloads.
- Record FTS row/chunk counts only if explicitly allowed; never snippets or document metadata.
- A rollback backup path should be described generically in committed evidence; reveal the exact private path only when the operator needs it.
- The final route may be given as the existing private route or redacted private base plus route; never publish credentials or sensitive network coordinates.
