ci: build, test, and run Signage inside a pinned Ekklesia on every change
Step 1 of module CI. Signage had none: its tests passed while three defects shipped that appear only inside Ekklesia, in a real browser (#4, #5, #6). Step 2, a platform-published harness every module uses, is proposed in Ekklesia ADR 0047 (Ekklesia #59). Signage switches to it once it exists.
Jobs, for branch protection
Workflow CI (.gitea/workflows/ci.yml). Gitea reports each job as CI / <job> (pull_request) and CI / <job> (push).
Job
What it proves
lint
biome lint
typecheck
the server, browser, and browser-test projects
test
unit, browser, and display contract tests
build
the platform's own gates against exactly this module set (boundary rules, module manifest, migrations), then the build
integration
needs test and build. Builds the deployment image with the platform's Dockerfile, then runs the platform's split-topology smoke against it: pairs a kiosk display, restarts it on the same profile, and runs ci/display-scenarios (120 s watches)
How Signage gets a platform
ci/platform.env pins one platform commit.
ci/assemble.sh fetches the platform's main and refuses a pinned commit that is not on main, so CI only runs reviewed platform code. It then checks out by SHA and copies this checkout into modules/signage (tracked files only).
ci/pnpm-lock.yaml is the assembled workspace's lockfile, and every job installs with --frozen-lockfile. ci/update-lock.sh regenerates it with the platform's pinned pnpm. This is the instance repository's pattern.
Trust rules, as in the platform
actions pinned by full SHA: the platform's own checkout and setup-node pins;
persist-credentials: false on every checkout;
no secrets, and no pull_request_target;
permissions: contents: read, declared; the workflow header notes Gitea 1.24 ignores it.
The pinned commit f50e8f4 is the head of Ekklesia #56, which adds the display-device scenarios this job runs.
Until #56 merges, ci/assemble.sh fails with "the pinned platform commit … is not on the platform's main". That is the check working.
Ekklesia's default merge style is a merge commit, so after #56 merges, f50e8f4 is on main and this pin is valid without a change. If #56 is squashed instead, I'll re-pin to the squash commit.
Scenario list
ci/display-scenarios starts with two-pages, which passes on today's Signage. Pairing and a clean restart always run first. Each fix PR adds the scenario it makes pass:
## ci: build, test, and run Signage inside a pinned Ekklesia on every change
Step 1 of module CI. Signage had none: its tests passed while three defects shipped that appear only inside Ekklesia, in a real browser (#4, #5, #6). Step 2, a platform-published harness every module uses, is proposed in Ekklesia ADR 0047 (Ekklesia #59). Signage switches to it once it exists.
### Jobs, for branch protection
Workflow `CI` (`.gitea/workflows/ci.yml`). Gitea reports each job as `CI / <job> (pull_request)` and `CI / <job> (push)`.
| Job | What it proves |
|---|---|
| `lint` | `biome lint` |
| `typecheck` | the server, browser, and browser-test projects |
| `test` | unit, browser, and display contract tests |
| `build` | the platform's own gates against exactly this module set (boundary rules, module manifest, migrations), then the build |
| `integration` | needs `test` and `build`. Builds the deployment image with the platform's Dockerfile, then runs the platform's split-topology smoke against it: pairs a kiosk display, restarts it on the same profile, and runs `ci/display-scenarios` (120 s watches) |
### How Signage gets a platform
- `ci/platform.env` pins **one platform commit**.
- `ci/assemble.sh` fetches the platform's `main` and **refuses a pinned commit that is not on `main`**, so CI only runs reviewed platform code. It then checks out by SHA and copies this checkout into `modules/signage` (tracked files only).
- `ci/pnpm-lock.yaml` is the assembled workspace's lockfile, and every job installs with `--frozen-lockfile`. `ci/update-lock.sh` regenerates it with the platform's pinned pnpm. This is the instance repository's pattern.
### Trust rules, as in the platform
- actions pinned by full SHA: the platform's own `checkout` and `setup-node` pins;
- `persist-credentials: false` on every checkout;
- no secrets, and no `pull_request_target`;
- `permissions: contents: read`, declared; the workflow header notes Gitea 1.24 ignores it.
### ⚠ It goes green only after Ekklesia #56 merges
- The pinned commit `f50e8f4` is the head of Ekklesia #56, which adds the display-device scenarios this job runs.
- Until #56 merges, `ci/assemble.sh` fails with "the pinned platform commit … is not on the platform's main". That is the check working.
- Ekklesia's default merge style is a merge commit, so after #56 merges, `f50e8f4` is on `main` and this pin is valid **without a change**. If #56 is squashed instead, I'll re-pin to the squash commit.
### Scenario list
`ci/display-scenarios` starts with `two-pages`, which passes on today's Signage. Pairing and a clean restart always run first. Each fix PR adds the scenario it makes pass:
- #5 adds `session-blip` and `stream-failure`;
- #6 adds `stream-recycle`.
### Verified locally
In a workspace assembled exactly as CI does (Ekklesia `f50e8f4` + this branch):
- the frozen install passes;
- `lint`, `typecheck` and `test` (198) pass;
- the platform gates pass;
- the build passes.
The harness, run against Signage with #4–#6 merged locally:
- heartbeats `200`;
- the roster shows the kiosk `offlineReady=true, cachedAssets=3`;
- pairing and restart pass. The full scenario run is still going.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Signage had no CI. Its tests passed while three defects shipped that
appear only inside Ekklesia, in a real browser. Every job now assembles
Signage into a pinned platform commit, as an instance does, and
installs that workspace with --frozen-lockfile:
- lint, typecheck, test: Signage's own checks;
- build: the platform's gates against exactly this module set (boundary
rules, module manifest, migrations), then the build;
- integration: the deployment image from the platform's Dockerfile,
then the platform's split-topology smoke. It pairs a kiosk display,
restarts it on the same profile, and runs the scenarios in
ci/display-scenarios.
The supporting files:
- ci/platform.env pins one platform commit. ci/assemble.sh fetches the
platform's main and refuses a pinned commit that is not on it, so CI
only runs reviewed platform code.
- ci/pnpm-lock.yaml is the assembled workspace's lockfile, regenerated
with ci/update-lock.sh.
Same trust rules as the platform: actions pinned by SHA,
persist-credentials false, no secrets, no pull_request_target.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
ci: build, test, and run Signage inside a pinned Ekklesia on every change
Step 1 of module CI. Signage had none: its tests passed while three defects shipped that appear only inside Ekklesia, in a real browser (#4, #5, #6). Step 2, a platform-published harness every module uses, is proposed in Ekklesia ADR 0047 (Ekklesia #59). Signage switches to it once it exists.
Jobs, for branch protection
Workflow
CI(.gitea/workflows/ci.yml). Gitea reports each job asCI / <job> (pull_request)andCI / <job> (push).lintbiome linttypechecktestbuildintegrationtestandbuild. Builds the deployment image with the platform's Dockerfile, then runs the platform's split-topology smoke against it: pairs a kiosk display, restarts it on the same profile, and runsci/display-scenarios(120 s watches)How Signage gets a platform
ci/platform.envpins one platform commit.ci/assemble.shfetches the platform'smainand refuses a pinned commit that is not onmain, so CI only runs reviewed platform code. It then checks out by SHA and copies this checkout intomodules/signage(tracked files only).ci/pnpm-lock.yamlis the assembled workspace's lockfile, and every job installs with--frozen-lockfile.ci/update-lock.shregenerates it with the platform's pinned pnpm. This is the instance repository's pattern.Trust rules, as in the platform
checkoutandsetup-nodepins;persist-credentials: falseon every checkout;pull_request_target;permissions: contents: read, declared; the workflow header notes Gitea 1.24 ignores it.⚠ It goes green only after Ekklesia #56 merges
f50e8f4is the head of Ekklesia #56, which adds the display-device scenarios this job runs.ci/assemble.shfails with "the pinned platform commit … is not on the platform's main". That is the check working.f50e8f4is onmainand this pin is valid without a change. If #56 is squashed instead, I'll re-pin to the squash commit.Scenario list
ci/display-scenariosstarts withtwo-pages, which passes on today's Signage. Pairing and a clean restart always run first. Each fix PR adds the scenario it makes pass:session-blipandstream-failure;stream-recycle.Verified locally
In a workspace assembled exactly as CI does (Ekklesia
f50e8f4+ this branch):lint,typecheckandtest(198) pass;The harness, run against Signage with #4–#6 merged locally:
200;offlineReady=true, cachedAssets=3;🤖 Generated with Claude Code