feat(tests): smoke.sh template — generic per-module smoke + drift detection #40

Merged
pdurlej merged 1 commit from claude/orders/tests-smoke-template into main 2026-05-03 20:56:43 +02:00
Owner

Summary

Cross-module open loop closure: every Phase 02 v2 runbook (PR #29, #30, #32, #33, #34) references tests/smoke.sh as declared-but-not-yet-written. This PR provides the template — a generic per-module smoke test that reads each module.yaml and runs 5+1 checks against runtime.

What it checks

  1. manifest-existsmodules/<id>/module.yaml file present
  2. schema-validmodule.yaml validates against schema/module.schema.json
  3. container-running — docker container running on declared spec.runtime.host
  4. image-digest-match — live container image digest matches manifest's spec.runtime.image_observed (drift detection)
  5. health-httpcurl spec.health.http.url returns expected_status
  6. (optional) smoke-extramodules/<id>/tests/smoke-extra.sh hook for module-specific checks (e.g., honcho-postgres can verify pgvector extension installed; karakeep can verify search returns results)

Real drift caught during validation

Running the new script on n8n-worker immediately surfaced drift (separate from this PR's scope, just noting):

  • Manifest image_observed: sha256:72e2242d5d3b… (audit 2026-05-02)
  • Live container: sha256:74660bbc672e…

Tag-only pinning (UPTIME_KUMA_IMAGE env carries n8nio/n8n:2.9.1 without @sha256:) + restart: unless-stopped → docker pulled newer patch since manifest audit. Exactly the case smoke tests should catch. Will surface as separate small PR (n8n-worker manifest re-audit).

Usage

tests/smoke.sh <module-id>           # human-readable output
tests/smoke.sh --all                 # iterate every module in modules/
tests/smoke.sh --json <module-id>    # machine-readable JSON

Exit codes: 0 = all PASS, 1 = any FAIL, 2 = usage/dependency error.

Dependencies

  • bash 4+ (macOS: brew install bash for v5+)
  • python3 with pyyaml + jsonschema (pip3 install jsonschema pyyaml)
  • ssh with ~/.ssh/config Host entries for module hosts
  • curl

Intentionally NOT yq — reduced dependency surface by reusing python3 (already required for jsonschema) for manifest field reading via inline scripts. One fewer thing for operator/CI to install.

Validation

Module PASS FAIL SKIP Notes
n8n-worker 3 1 2 image drift caught (separate fix needed)
uptime-kuma 5 0 1 no smoke-extra defined

NOT in scope (separate PRs)

  • Per-module smoke-extra.sh implementations (one PR per interesting module)
  • CI integration (run smoke.sh in Forgejo Actions on PRs touching modules/<id>/)
  • n8n-worker image drift fix (real finding, separate PR)
  • README in tests/ (tests/smoke.sh --help is sufficient docs for now)

Cross-module impact

Every wave 2/3/4+ runbook can now mark Smoke test path: tests/smoke.sh declared but file not yet written (cross-module open loop) as FIXED. Future module.yaml writers can rely on this template existing.

Test plan

  • bash -n tests/smoke.sh — syntax OK
  • tests/smoke.sh --help — usage prints
  • tests/smoke.sh n8n-worker — runs end-to-end (drift catch validated)
  • tests/smoke.sh uptime-kuma — all 5 PASS
  • tests/smoke.sh --json <id> — valid JSON output (parseable by jq)
  • tests/smoke.sh nonexistent-module — exits 1 with manifest-exists:FAIL
  • (Future) Run tests/smoke.sh --all and inspect aggregate pass rate as Phase 02 progress signal
## Summary Cross-module open loop closure: every Phase 02 v2 runbook (PR #29, #30, #32, #33, #34) references `tests/smoke.sh` as declared-but-not-yet-written. This PR provides the template — a generic per-module smoke test that reads each `module.yaml` and runs 5+1 checks against runtime. ## What it checks 1. **`manifest-exists`** — `modules/<id>/module.yaml` file present 2. **`schema-valid`** — `module.yaml` validates against `schema/module.schema.json` 3. **`container-running`** — docker container running on declared `spec.runtime.host` 4. **`image-digest-match`** — live container image digest matches manifest's `spec.runtime.image_observed` (**drift detection**) 5. **`health-http`** — `curl spec.health.http.url` returns `expected_status` 6. **(optional) `smoke-extra`** — `modules/<id>/tests/smoke-extra.sh` hook for module-specific checks (e.g., honcho-postgres can verify pgvector extension installed; karakeep can verify search returns results) ## Real drift caught during validation Running the new script on `n8n-worker` immediately surfaced drift (separate from this PR's scope, just noting): - Manifest `image_observed: sha256:72e2242d5d3b…` (audit 2026-05-02) - Live container: `sha256:74660bbc672e…` Tag-only pinning (`UPTIME_KUMA_IMAGE` env carries `n8nio/n8n:2.9.1` without `@sha256:`) + `restart: unless-stopped` → docker pulled newer patch since manifest audit. Exactly the case smoke tests should catch. Will surface as separate small PR (n8n-worker manifest re-audit). ## Usage ```bash tests/smoke.sh <module-id> # human-readable output tests/smoke.sh --all # iterate every module in modules/ tests/smoke.sh --json <module-id> # machine-readable JSON ``` Exit codes: `0` = all PASS, `1` = any FAIL, `2` = usage/dependency error. ## Dependencies - `bash 4+` (macOS: `brew install bash` for v5+) - `python3` with `pyyaml` + `jsonschema` (`pip3 install jsonschema pyyaml`) - `ssh` with `~/.ssh/config` Host entries for module hosts - `curl` **Intentionally NOT `yq`** — reduced dependency surface by reusing python3 (already required for jsonschema) for manifest field reading via inline scripts. One fewer thing for operator/CI to install. ## Validation | Module | PASS | FAIL | SKIP | Notes | |---|---|---|---|---| | n8n-worker | 3 | 1 | 2 | image drift caught (separate fix needed) | | uptime-kuma | 5 | 0 | 1 | no smoke-extra defined | ## NOT in scope (separate PRs) - Per-module `smoke-extra.sh` implementations (one PR per interesting module) - CI integration (run smoke.sh in Forgejo Actions on PRs touching `modules/<id>/`) - n8n-worker image drift fix (real finding, separate PR) - README in `tests/` (`tests/smoke.sh --help` is sufficient docs for now) ## Cross-module impact Every wave 2/3/4+ runbook can now mark `Smoke test path: tests/smoke.sh declared but file not yet written (cross-module open loop)` as FIXED. Future module.yaml writers can rely on this template existing. ## Test plan - [ ] `bash -n tests/smoke.sh` — syntax OK - [ ] `tests/smoke.sh --help` — usage prints - [ ] `tests/smoke.sh n8n-worker` — runs end-to-end (drift catch validated) - [ ] `tests/smoke.sh uptime-kuma` — all 5 PASS - [ ] `tests/smoke.sh --json <id>` — valid JSON output (parseable by `jq`) - [ ] `tests/smoke.sh nonexistent-module` — exits 1 with `manifest-exists:FAIL` - [ ] (Future) Run `tests/smoke.sh --all` and inspect aggregate pass rate as Phase 02 progress signal
Cross-module open loop closure: every Phase 02 v2 runbook references
tests/smoke.sh as declared-but-not-yet-written. This PR provides the
template — a generic per-module smoke test that reads each module.yaml
and runs 5 checks against runtime.

What it checks (per module):
1. manifest-exists  — modules/<id>/module.yaml file present
2. schema-valid     — module.yaml validates against schema/module.schema.json
3. container-running — docker container running on declared spec.runtime.host
4. image-digest-match — live container image digest matches manifest's
   spec.runtime.image_observed (DRIFT DETECTION — caught real drift on
   n8n-worker during template validation: live sha256:74660bbc672e vs
   manifest sha256:72e2242d5d3b, audit was 2026-05-02; tag-only pinning +
   restart: unless-stopped pulled newer patch — separate fix, not in scope)
5. health-http      — curl spec.health.http.url returns expected_status
6. (optional) smoke-extra — modules/<id>/tests/smoke-extra.sh hook for
   module-specific checks (e.g., honcho-postgres can verify pgvector
   extension installed, karakeep can verify search returns results)

Usage:
  tests/smoke.sh <module-id>           # human-readable
  tests/smoke.sh --all                 # iterate all modules
  tests/smoke.sh --json <module-id>    # machine-readable

Exit codes: 0 = all PASS, 1 = any FAIL, 2 = usage/dependency error.

Dependencies: bash 4+, python3 + pyyaml + jsonschema, ssh, curl. NOT yq —
intentionally avoided to reduce dependency surface (jsonschema already
requires python3 + pyyaml; reusing same python for manifest reading via
inline scripts).

Validation against current modules:
- n8n-worker: 3 PASS, 1 FAIL (image drift surfaced — real finding, not
  template bug), 2 SKIP (no health.http.url, no smoke-extra)
- uptime-kuma: 5 PASS, 0 FAIL, 1 SKIP (no smoke-extra)

Per-module extra hooks: deferred to module-specific PRs. Template path is
modules/<id>/tests/smoke-extra.sh — when present and executable, called
with module-id as $1; non-zero exit = FAIL with first line of output as
detail. Keeps generic smoke.sh stable while modules can add specific
checks (e.g., postgres extension verification, search index sanity).

Cross-module impact: every wave 2/3/4+ runbook can now mark
"Smoke test path: tests/smoke.sh declared but file not yet written" as
FIXED. Future module.yaml writers can rely on this template existing.

NOT in scope (separate PRs):
- Per-module smoke-extra.sh implementations (one PR per interesting module)
- CI integration (run smoke.sh in Forgejo Actions on relevant PRs)
- n8n-worker image drift fix (real finding from validation, separate PR)
- README in tests/ (script --help is sufficient docs for now)

Length: 292 lines bash. Tested on macOS bash 5+ + linux bash 4+.
Sign in to join this conversation.
No reviewers
No labels
W6d-automerge-calibration
agent/claude-code
agent/codex
agent/hermes
agent/iskra
agent/ollama
agent/patchwarden
automerge-candidate
class/security-sensitive
cutover-gate
dependency/blocked
dependency/blocks-others
dependency/cross-repo
dependency/needs-confirmation
domain:agents
domain:ci
domain:docs
domain:forgejo
domain:infra
domain:memory
domain:runtime
domain:signal
domain:ux
flow/architecture
flow/blocked
flow/deployed
flow/done
flow/implementation
flow/intake
flow/maintained
flow/observed
flow/ready
flow/refining
flow/retired
flow/review
iterating
judge/codex-candidate
judge/hermes-candidate
judge/low-confidence
judge/needs-refinement
judge/operator-needed
judge/p0
judge/p1
judge/p2
judge/p3
judge/park
judge/patchwarden-candidate
judge/stale-priority
kind/adr
kind/bug
kind/chore
kind/feature
kind/infra
kind/ops
kind/refactor
kind/research
large-impact
merge/auto
merge/manual
merge/manual-dependency-conflict
merge/manual-failing-tests
merge/manual-merge-conflict
merge/manual-missing-review
merge/manual-operator-preference
merge/manual-red-zone
merge/manual-security-sensitive
merge/manual-unclear-scope
merge/manual-unknown
meta
mode:operator-only
mode:patchwarden-iskra-approved
mode:safe-auto
needs-operator-decision
needs-triage
not-ready
observed/erroring
observed/needs-followup
observed/pending
observed/retire-candidate
observed/unused
observed/used
operator-emotional
owner-attention
phase/02
phase/03
priority:p0
priority:p1
priority:p2
priority:p3
proposed
ready-for-agent
ready-for-operator
recovery
review:claude-reviewed
review:codex-reviewed
review:dziadek-reviewed
review:needs-human
risk/exposure
risk/process
risk/product
risk/runtime
safety:external-write
safety:no-prod-mutation
safety:prod-impact
safety:secret-touch
size/large
size/medium
size/small
size/tiny
size/unknown
source/adr
source/agent-generated
source/manual
source/operator-chat
source/voice-note
status:blocked
status:codex-ready
status:merged:pending-evidence
status:needs-evidence
status:operator-needed
status:parked
tier/full
tier/lite
tier/stacked
tier:0-platform-substrate
tier:1-iskra-value-layer
tier:2-tools-products-modules
type:bug
type:chore
type:docs
type:feat
type:policy
type:research
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pdurlej/platform!40
No description provided.