Skip to content

Test-Environment Mirror

Test-Environment Provisioning & prod→test Data Mirror (#451)

Section titled “Test-Environment Provisioning & prod→test Data Mirror (#451)”

R6 of the promotion pipeline (#445). Provisions a test environment that mirrors prod — CF-first — and syncs prod data into it with mandatory redaction. Engine: ~/.claude/scripts/deploy/test-env.js.

Config lives in the project’s dev.json under environments.<env>.provision and environments.<env>.mirror (schema: schemas/dev.schema.json).

  • Dry-run by default. Every wrangler mutation prints intent and does nothing unless --apply is passed. CI and a bare invocation never touch live Cloudflare.
  • Data-sync is opt-in, off by default. mirror.enabled must be explicitly true; otherwise mirror is a clean skip — no prod data moves.
  • Redaction is mandatory. When the mirror is enabled, mirror.redaction must declare a policy (drop_tables / columns / patterns); the mirror refuses (exit 2) otherwise. Copying prod data into a less-guarded test env without sanitization is the capability’s top risk.
  • The two human decisions never auto-enable. The redaction policy and the test-env secrets are handed back — not automated — by the human-gate emitter (~/.claude/scripts/setup-promotion/human-gate.js, #929). It files a single self-contained todo and the mirror stays off until you clear it and add the completed label; the scaffolder resumes the mirror step only on that done-signal, and a todo closed without completed is cancelled (never resumes). Query the gate with human-gate.js gate. See Promotion-pipeline setup → Step 5.
Terminal window
# Ensure the declared test resources exist (idempotent; dry-run unless --apply)
test-env.js provision --env test [--project DIR] [--apply]
# Mirror prod → test: export → redact → import, then record freshness
test-env.js mirror --env test [--project DIR] [--apply]
# Report when the test data was last mirrored
test-env.js status --env test [--project DIR]
{
"environments": {
"prod": { "db": { "database": "app-prod-db", "adapter": "d1" } },
"test": {
"deploy": { "adapter": "cloudflare" },
"db": { "database": "app-test-db", "adapter": "d1" },
"provision": { "pages_project": "app-test", "d1_database": "app-test-db" },
"mirror": {
"enabled": true,
"source": "prod",
"redaction": {
"drop_tables": ["secrets", "sessions"],
"columns": ["users.email", "users.phone"],
"patterns": [{ "match": "[\\w.]+@[\\w.]+\\.\\w+", "replace": "redacted@example.test" }]
}
}
}
}
}
Layer Effect
drop_tables The table’s CREATE/INSERT statements are removed from the dump entirely.
columns Declared table.column values are set to NULL in column-listed INSERTs (quote-aware — commas inside quoted strings are handled).
patterns Regex find/replace across the whole dump — a second net for free-text PII (emails in note/log columns).

End-to-end test deploy + migrate + health (AC-06)

Section titled “End-to-end test deploy + migrate + health (AC-06)”

The pipeline composes the existing deploy framework: the Cloudflare adapter maps test → --preview, and deploy.sh --env test runs check→build→migrate→deploy→verify. A full live run on a real CF project is a deliberate, credentialed operation — it touches live Cloudflare and real prod data, so it is not run autonomously. The verification procedure:

Terminal window
# 1. Provision the test resources
test-env.js provision --env test --project <repo> --apply
# 2. Mirror prod data (redacted) into the test D1
test-env.js mirror --env test --project <repo> --apply
# 3. Deploy + migrate + health-check the test environment
~/.claude/scripts/deploy/deploy.sh --env test --project <repo>
# 4. Confirm freshness was recorded
test-env.js status --env test --project <repo>

A green run of steps 1–4 against one CF project satisfies AC-06.

Generalization path for non-CF stacks (AC-07)

Section titled “Generalization path for non-CF stacks (AC-07)”

The engine is CF-first but the model generalizes — the schema’s provision/mirror blocks and the dry-run/opt-in/redaction discipline are stack-agnostic. Per stack, only the export/import/provision primitives change:

Stack Provision Export (source) Import (target)
Cloudflare / D1 (implemented) wrangler pages project create, wrangler d1 create wrangler d1 export wrangler d1 execute --file
node / Postgres create test DB/role (psql/createdb) pg_dump psql -f
node / SQLite create the db file sqlite3 .dump sqlite3 < dump
Supabase supabase projects create (or branch) supabase db dump supabase db push / psql
Drupal (external — nanawalld8) n/a (Acquia owns the pipeline) drush sql-dump drush sql-cli

The redaction stage (redactSql) operates on dumped SQL and is already stack-agnosticdrop_tables/columns/patterns apply to any SQL dump, so only the transport primitives (the wrangler calls in probeExisting/run) are swapped per stack. Implementing a new stack means adding its provision/export/import commands behind the same opt-in + mandatory-redaction gates; the contract and the safety model do not change.