Dentolize · Team-Managed Sandbox Config Walkthrough
On this pageWhat this PR touches (and doesn't)What to test, and howWhere to actually run thisThings this PR explicitly does not cover — don't file these as bugs

For Quality

What this PR touches (and doesn't)

This is a single-file change to .github/workflows/sandbox-ops.yml (+61/-0). There is no application code, no database migration, and no Dentolize product surface in the diff. QA here means validating a GitHub Actions workflow's logic, not exercising the clinic app — though the outcome of a correct redeploy is observable in the app (e.g. the WhatsApp Bot settings tab, see the Walkthrough).

What to test, and how

Since the compose step is a small, self-contained Python block reading two JSON env vars, it's realistic to test in isolation (outside GitHub Actions) by simulating SANDBOX_VARS / SANDBOX_SECRETS / REF_NAME and checking the printed JSON. The PR description claims this is already unit-tested (in the sandbox CLI's own test suite, outside /work/repo); the same cases are worth re-confirming end-to-end against a real workflow run:

CaseExpected outcomeWhy it matters
SANDBOX_MAIN_WHATSAPP=true (variable), branch = main{"whatsapp": true}Core scoping — branch-specific keys apply
SANDBOX_OTHER_WHATSAPP=true, branch = mainwhatsapp absent/defaultCross-branch isolation — no leak into an unrelated branch
SANDBOX_ALL_CRON=false, branch = main (no SANDBOX_MAIN_CRON){"cron": false}The "every branch" prefix works standalone
SANDBOX_ALL_CRON=false and SANDBOX_MAIN_CRON=true{"cron": true}Branch-specific overrides ALL on the same key
SANDBOX_MAIN_FOO=bar (variable) and SANDBOX_MAIN_FOO=baz (secret){"env": {"FOO": "baz"}}Secrets win over variables on a name clash
A variable/secret named github_token, or SANDBOX_MAIN_GITHUB_TOKENNever appears in outputNo path exists for github.token to be selected — worth confirming this stays true after any refactor of the prefix-matching loop
An unprefixed variable, e.g. plain SOME_REPO_VARExcludedOnly SANDBOX_ALL_* / SANDBOX_<REF>_* keys are read at all
No SANDBOX_* variables/secrets set at all{}Falls through to the CLI's own defaults — equivalent to today's main behavior
A branch name with special characters, e.g. feature/foo-barMaps to prefix SANDBOX_FEATURE_FOO_BAR_ref_key derivation via re.sub(r"[^A-Z0-9]+", "_", ...) — worth a case with consecutive separators (feature//foo) to confirm no double-underscore edge case breaks matching
WHATSAPP=false, WHATSAPP=0, WHATSAPP=no, WHATSAPP="", WHATSAPP=maybeOnly true/1/yes/on (case-insensitive) read as true; everything else, including nonsense strings, reads as falsetruthy() has no "invalid value" error path — a typo like WHATSAPP=treu silently becomes false rather than failing loudly

Where to actually run this

Because the compose step only executes if: inputs.action == 'redeploy', exercising it requires Actions → Sandbox Ops → redeploy against a real branch (a disposable non-main branch is safer for first-pass QA — the prefix mechanism works identically for any branch name once you know its SANDBOX_<REF>_ prefix). Confirm via the run log's 🧪 sandbox settings (from environment) group, then confirm the deployed sandbox's actual behavior matches (e.g. WhatsApp Bot settings tab no longer shows "Feature Disabled").

Things this PR explicitly does not cover — don't file these as bugs

  • reseed, reset-data, destroy, and update-docs actions do not read sandbox-Environment config — only redeploy does (sandbox-ops.yml:65). This is intentional, not a gap.
  • The automatic PR-open/PR-sync sandbox.yml flow is entirely untouched — this mechanism only fires through the manually-dispatched sandbox-ops.yml.
  • keep_on_merge has no environment-variable equivalent — expected, since it's a merge-time concept and main never merges.