ADR-040 — Smoke tests run against a deployed environment through one recipe and one output contract
Status: accepted (2026-10-24, spec 091)
Context
Section titled “Context”/svc:ship ended when Argo CD reported dev healthy, which proves only that pods answer their probes. Specs 091 (dev),
093 (staging promotion) and 097 (app ship across repos) all need the same evidence: the spec’s checkable behaviour
works in a running environment. That evidence must come out of four shapes with four test runners (pytest, JUnit,
go test, Playwright). Repos that were generated long ago must get it through /shared:update-service.
Decision
Section titled “Decision”-
Spec side: a criterion whose bullet line ends with
(smoke)is smoke-checkable.cplat spec tracerequires a test file that names the id and carries the shape’s smoke tag (smoke_tagregex inshapes.yml, searched insmoke_globs). The association is per file, not per test. -
Repo side: every deployable template has
devbox run smoke, which runs the platform-ownedscripts/smoke.sh.- It needs
SMOKE_BASE_URL: without it, it exits 2 and runs nothing. - It runs only the smoke-tagged tests against that URL.
- It prints
PASS|FAIL|SKIP <path>::<name>lines and onesmoke: <p> passed, <f> failed, <s> skippedline (or0 smoke tests). - It exits 0 only when nothing failed.
testandcialways run with an emptySMOKE_BASE_URL, and smoke tests skip themselves without one. - It needs
-
Platform side:
scripts/cplat/smoke.pyis the only reader of that output. It owns the retry (once, after 30 s), the limit (600 s per attempt), the outcomespassed | passed-on-retry | red | timed-out | not-run, the run-recordsmokesection and thesmoke:issue signature (kind of ADR-033, filed in the service repo).not-runnever counts as passed. -
Where the environment is: the base URL comes from the linked gitops-app (
services.yamlexpose:,app.yamlhosts.<env>and the optionalurls.<env>). An explicit--base-urloverrides it. Before smoke runs, cplat checks that the URL answers at all. An unreachable URL isnot-run, never red. -
Red smoke in dev escalates as
codeand is filed in the service repo. Nothing is rolled back. -
Web smoke tests are Playwright tests, and the Playwright scaffold is part of every web-nextjs repo.
Consequences
Section titled “Consequences”- Specs 093 and 097 call
smoke.run_smoke()/cplat smoke run --base-url …and reuse the record section and the signature unchanged. - A new deployable shape must provide
smoke_tag, asmokerecipe and ascripts/smoke.shthat prints the contract lines.scripts/shapes.py checkenforces the recipe and the tag. - The per-file association can be fooled by a smoke tag on another test in the same file. The tester agents write one smoke test per criterion in its own clearly named test.
- The first web smoke run on a machine downloads Chromium.
cistays browser-free. - Smoke tests run against shared environments, so they must create their own uniquely named data and never touch data they did not create. This is a rule for the tester agents, not something the platform can check.