docs: propose ADR 0047, a published harness every module runs in CI #59

Merged
Zayin_Admin merged 2 commits from docs/adr-0047-module-harness into main 2026-10-06 02:00:39 +00:00
Member

docs: propose ADR 0047, a published harness every module runs in CI

Proposal only: no implementation in this PR. It is step 2 of the module CI plan. The implementation PR follows once the decision is accepted.

Why

Signage reached production with three defects that appear only when it runs inside Ekklesia in a real browser:

  • every heartbeat was refused with a 400;
  • a display that failed one request at start-up never recovered without a reload;
  • a blob leak exhausted the browser.

Its tests stubbed the host, and it had no CI. The platform smoke runs only the template. Each module repository would otherwise copy deployment scripts that then drift.

What it decides

  • A versioned harness image, ekklesia-module-harness:<platform version>, built from the release tag and pushed by the platform's release CI (v* tags only, never overwritten). Modules pin it by digest.
  • What it does:
    • assembles the module into that platform;
    • runs the platform's own gates (boundaries, modules:validate, migrations:validate, build);
    • builds the image, starts the split reference deployment, and explicitly migrates a fresh database;
    • mounts every declared UI surface in headless Chromium: the primary UI, the display as a paired device, and contributions.
  • What fails it:
    • console errors, unhandled rejections, unexpected 4xx/5xx responses, and refused requests;
    • a 2-minute display soak: remounts, offline state, realtime health, live object URLs, heap growth;
    • generic no-dead-ends injections: the first request to each start-up route, and the first realtime connection, are refused, and the display must recover without a reload.
  • Both ends of the range: a module tests the lowest and newest platform versions in compatibility.platform. Both are pinned, with a lockfile per version that the harness can regenerate.
  • Extra smoke steps go in ekklesia.harness.json, beside the manifest. This is a deliberate deviation from "in their manifest": manifest versions are strict, host-validated hard cutovers (ADR 0033), and test steps have no runtime meaning.
  • Later: the module-trust check. Recorded table, bucket and egress access, compared with what the manifest declares. Egress needs a manifest declaration that doesn't exist yet, and is a separate decision.
  • The template workflow stays short: one docker run of the harness, by digest.

Gitea 1.24 findings behind the "docker run, not a reusable workflow" choice

  • Gitea 1.24's runner can call a reusable workflow from another repository by cloning it, but that support is thinly documented and has known problems in practice (forum, reported failures).
  • Actions and reusable workflows from private repositories arrive only with Gitea 1.26 (go-gitea/gitea#32562).
  • Absolute uses: URLs for actions are supported on 1.24 (Gitea 1.24 comparison). A composite action would still only wrap the same docker run.
  • An image digest is equally immutable and reviewable, and works unchanged on 1.24, 1.26 and GitHub Actions.

Numbering

This ADR takes 0047 because it is written first. The display-presence/duplicate-pages ADR and the device-lifecycle ADR discussed in M0 become 0048 and 0049.

🤖 Generated with Claude Code

## docs: propose ADR 0047, a published harness every module runs in CI **Proposal only: no implementation in this PR.** It is step 2 of the module CI plan. The implementation PR follows once the decision is accepted. ### Why Signage reached production with three defects that appear only when it runs **inside** Ekklesia in a real browser: - every heartbeat was refused with a `400`; - a display that failed one request at start-up never recovered without a reload; - a blob leak exhausted the browser. Its tests stubbed the host, and it had no CI. The platform smoke runs only the template. Each module repository would otherwise copy deployment scripts that then drift. ### What it decides - **A versioned harness image**, `ekklesia-module-harness:<platform version>`, built from the release tag and pushed by the platform's release CI (`v*` tags only, never overwritten). Modules pin it **by digest**. - **What it does:** - assembles the module into that platform; - runs the platform's own gates (boundaries, `modules:validate`, `migrations:validate`, build); - builds the image, starts the split reference deployment, and explicitly migrates a fresh database; - mounts **every declared UI surface** in headless Chromium: the primary UI, the display as a paired device, and contributions. - **What fails it:** - console errors, unhandled rejections, unexpected 4xx/5xx responses, and refused requests; - a 2-minute display soak: remounts, offline state, realtime health, live object URLs, heap growth; - generic **no-dead-ends** injections: the first request to each start-up route, and the first realtime connection, are refused, and the display must recover without a reload. - **Both ends of the range:** a module tests the lowest and newest platform versions in `compatibility.platform`. Both are pinned, with a lockfile per version that the harness can regenerate. - **Extra smoke steps** go in `ekklesia.harness.json`, beside the manifest. This is a deliberate deviation from "in their manifest": manifest versions are strict, host-validated hard cutovers (ADR 0033), and test steps have no runtime meaning. - **Later: the module-trust check.** Recorded table, bucket and egress access, compared with what the manifest declares. Egress needs a manifest declaration that doesn't exist yet, and is a separate decision. - **The template workflow** stays short: one `docker run` of the harness, by digest. ### Gitea 1.24 findings behind the "`docker run`, not a reusable workflow" choice - Gitea 1.24's runner can call a reusable workflow from another repository by cloning it, but that support is thinly documented and has known problems in practice ([forum](https://forum.gitea.com/t/reusable-workflows-usability/10155), [reported failures](https://forum.gitea.com/t/simple-reusable-workflow-example-not-working/11960)). - Actions and reusable workflows from **private** repositories arrive only with Gitea 1.26 ([go-gitea/gitea#32562](https://github.com/go-gitea/gitea/pull/32562)). - Absolute `uses:` URLs for actions are supported on 1.24 ([Gitea 1.24 comparison](https://docs.gitea.com/1.24/usage/actions/comparison)). A composite action would still only wrap the same `docker run`. - An image digest is equally immutable and reviewable, and works unchanged on 1.24, 1.26 and GitHub Actions. ### Numbering This ADR takes 0047 because it is written first. The display-presence/duplicate-pages ADR and the device-lifecycle ADR discussed in M0 become 0048 and 0049. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Zayin_Admin left a comment
Owner

Approve in principle. Please add before accepting:

  1. Trust boundary: the harness needs the Docker socket (effectively root on the runner). First-party modules may run on the shared runner; untrusted third-party modules only on an isolated runner (separate VM or sandboxed/rootless Docker). State this in the decision.
  2. Expected errors: expected refusals and 4xx (injected failures, pre-pairing 401) are declared in ekklesia.harness.json; anything undeclared fails.
  3. Flakiness policy: document the soak and heap thresholds; no automatic retries that mask failures; flaky checks get quarantined with an issue.
  4. Cleanup guarantee: the harness tears down everything it starts, even on failure or cancellation, with labels that allow a sweep of leftovers.
  5. Optional: a nightly run against platform main as an early warning.
  6. Resources: document runner requirements (memory; one harness job at a time).
Approve in principle. Please add before accepting: 1. **Trust boundary:** the harness needs the Docker socket (effectively root on the runner). First-party modules may run on the shared runner; untrusted third-party modules only on an isolated runner (separate VM or sandboxed/rootless Docker). State this in the decision. 2. **Expected errors:** expected refusals and 4xx (injected failures, pre-pairing 401) are declared in ekklesia.harness.json; anything undeclared fails. 3. **Flakiness policy:** document the soak and heap thresholds; no automatic retries that mask failures; flaky checks get quarantined with an issue. 4. **Cleanup guarantee:** the harness tears down everything it starts, even on failure or cancellation, with labels that allow a sweep of leftovers. 5. **Optional:** a nightly run against platform main as an early warning. 6. Resources: document runner requirements (memory; one harness job at a time).
Zayin_Admin added 1 commit 2026-10-06 01:50:09 +00:00
docs: propose ADR 0047, a published harness every module runs in CI
CI / verify (pull_request) Successful in 53s
CI / infrastructure-smoke (pull_request) Successful in 21s
CI / migration-smoke (pull_request) Successful in 1m49s
CI / image-build (pull_request) Successful in 19s
CI / deploy-smoke (same-origin) (pull_request) Successful in 49s
CI / deploy-smoke (split) (pull_request) Canceled after 18s
3827841205
Signage reached production with three defects that appear only inside
Ekklesia in a real browser, and no CI that ran it there. The ADR
proposes:
- a versioned harness image, ekklesia-module-harness:<platform version>,
  built and pushed by the platform's release CI;
- what the harness does: assemble a module into that platform, validate
  and build it, migrate a disposable database, start the split reference
  deployment, and mount every declared UI surface in headless Chromium;
- what fails it: console errors, unexpected 4xx/5xx, unhandled
  rejections, a display soak, and generic no-dead-ends injections;
- testing at the lowest and newest platform versions of a module's
  range;
- extra smoke steps in ekklesia.harness.json, beside the manifest
  rather than in it;
- a later data-access and egress check against declarations;
- a short template workflow that calls the harness by digest with one
  `docker run`. Not a reusable workflow: thinly supported on Gitea
  1.24, and private sources only arrive in 1.26.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Zayin_Admin force-pushed docs/adr-0047-module-harness from f005893c13 to 3827841205 2026-10-06 01:50:09 +00:00 Compare
Zayin_Admin added 1 commit 2026-10-06 01:54:40 +00:00
docs: ADR 0047, nothing the harness starts outlives its job
CI / verify (pull_request) Successful in 58s
CI / infrastructure-smoke (pull_request) Successful in 21s
CI / migration-smoke (pull_request) Successful in 1m51s
CI / image-build (pull_request) Successful in 19s
CI / deploy-smoke (same-origin) (pull_request) Successful in 43s
CI / deploy-smoke (split) (pull_request) Successful in 57s
3a2f5d68f6
A job the runner kills never runs its own cleanup trap, and the
platform's CI leaked a whole migration stack that way. The harness
follows the platform's three-layer rule:
- everything it starts is named and labeled as a CI stack;
- it cleans up in a trap;
- the template workflow sweeps stale CI stacks before running it, and
  removes its own in a final if: always() step.

The sweep ships in the harness image.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Zayin_Admin merged commit 43911a088d into main 2026-10-06 02:00:39 +00:00
Zayin_Admin deleted branch docs/adr-0047-module-harness 2026-10-06 02:00:39 +00:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Zayin/Ekklesia#59