CI: define the four pipeline lanes (dev-artifact, fast, slow, tag) — #67 #80

Merged
robert merged 1 commit from tooling/ci-pipeline into main 2026-09-04 00:36:58 +02:00
Owner

Implements #67: the four CI lanes from PROCESS.md/TESTING.md, as .forgejo/workflows/*.yml, plus
the three pinned toolchain images they run inside.

This is workflow-definition work, untested by execution. No runner is registered against this
repo, so nothing in this PR has actually run on a live pipeline. It needs an extra-careful human
read, not a green-checkmark skim. docs/CI.md is the map from PROCESS.md/TESTING.md onto these
files and tags every non-obvious claim [VERIFIED] / [REASONED] / [UNVERIFIED] — start there.

Hard exclusion, on purpose

This PR does not register a runner, does not generate or touch a runner-registration token, and
does not run or configure act_runner.
That's explicitly Robert's step — it needs a token from
the Forgejo web UI, and a registered runner executes arbitrary workflow code against this repo, a
different trust tier than a code PR. See "What Robert still has to do" below.

Workflow directory: .forgejo/workflows/, not .gitea/workflows/

Verified against https://forgejo.org/docs/latest/user/actions/overview/ (2026-09-04):
.forgejo/workflows/ is primary; the only documented fallback is .github/workflows/ — Forgejo's
docs never mention .gitea/workflows/ at all. The original issue text ("Files:
.gitea/workflows/ci.yml") was simply wrong; the 2026-09-03 update's .forgejo/workflows/ is
correct and is what this PR uses. Left a comment on #67 with this finding, per its own ask.

What's built

  • dev-artifact.yml — every push, any branch, ungated. Builds a debug .pbw (all three
    targetPlatforms — see the file for why "emery only" wasn't worth it given DEV.md's measured
    2.171s build time) and a debug .apk, then publishes both to a single rolling pre-release
    (tag dev-latest) that's anonymously downloadable from Robert's phone browser — the repo is
    public, so no login or API token is needed to fetch a release asset. "Structurally untaggable"
    per PROCESS.md: the tag dev-latest can never match tag-lane.yml's v*.*.* filter, and
    tag-lane.yml never downloads a dev-artifact output — it always rebuilds from the tagged commit.
  • fast-lane.yml — push + PR, 120s target / 180s hard fail (timeout-minutes: 3 per job).
    JVM tests (:companion:core, real), pebble build (real), gitleaks-on-the-diff / ktlint / detekt
    / clang-format (real, tool defaults since no project lint config exists yet), a meta-test
    asserting the workflow declares NFR-C7's required jobs (real). Host C tests and generated-code
    freshness check for their prerequisite (#68, #7/#53) and skip with a named issue reference if
    absent — each activates itself the moment its issue lands, no further edit needed here.
  • slow-lane.yml — push to main. Relies on fast-lane.yml re-running on the same merge
    commit for the "everything in the fast lane, plus" half (both files trigger on push to main).
    Adds: JVM coverage via Kover with a real bootstrap-and-ratchet implementation of TESTING.md §3.1
    (writes docs/ci/coverage-floor.json on first green run, ratchets it thereafter, commits with
    [skip ci]), assembleRelease + an inline NFR-S2 dependency deny-list check (real), a -Werror
    build attempt (flagged [UNVERIFIED] — whether pebble build/waf honours a CFLAGS env override
    wasn't confirmed tonight), an emulator install+screenshot smoke test (flagged [UNVERIFIED] —
    whether the docker-executor container gets KVM access for QEMU). Host C coverage and replay
    regression skip-guard on #68/#23.
  • tag-lane.yml — push tag v*.*.*. Self-contained rather than layered on the other three via
    uses:, because this session couldn't verify Forgejo Actions supports workflow_call at all, and
    this is the one lane where an unverified YAML feature failing silently is least acceptable. Real,
    working checks for G11 (partial), G12 (field-report currency — date math against
    docs/field/*.md), G13 (quarantine-marker grep), G15 (TODO-threshold grep in TESTING.md), G9
    (full-history gitleaks), plus a waiver-sanity check (annotated tag with a non-empty message).
    Rebuilds the .pbw/.apk fresh from the tagged commit and publishes a real (non-prerelease)
    release — same [UNVERIFIED] release-API-auth assumption as dev-artifact.yml (see below). Two
    gates (host-c-suite, replay-and-emulator-gates) hard-fail today because #68/#23 don't
    exist — correct behaviour: a release gate with nothing to check is a fail, not a pass (TESTING.md
    G15's own point), so this lane cannot produce a release yet, on purpose.
  • Three pinned toolchain images (tooling/docker/{pebble-toolchain,android-toolchain,ci-tools})
    — pebble-tool/SDK 4.33.1 pinned to Python 3.13 with a build-time assertion (D54), JDK 21 + Android
    platform 37 + a Gradle cache warmed at image-build time, and gitleaks/ktlint/detekt/clang-format.
    Built once, only ever pulled by CI. tooling/docker/README.md has the exact build/push commands
    and explains why pushing them is a manual step: the automatic per-run token is confirmed unable to
    push to the package registry (unrelated to the runner-token exclusion above — a different,
    independently-confirmed limitation, see the README).
  • NFR-S3: no workflow here requires a manually-configured secret. The only token used anywhere
    is the automatic per-run secrets.GITHUB_TOKEN/FORGEJO_TOKEN, injected by the platform itself,
    not something Robert has to create or paste in.
  • Badge + docs table row in README.md; docs/CI.md is the full map, written to TESTING.md's
    own [VERIFIED]/[RECALLED]/[REASONED] tagging convention (here [UNVERIFIED] instead of
    [RECALLED], since nothing was recalled — everything not directly checked tonight is named as an
    open gap instead).

What Robert still has to do by hand

  1. Build and push the three toolchain images (tooling/docker/README.md).
  2. Register the self-hosted act_runner — label pedalpebble, docker executor (required for
    the container: image overrides every job here uses). Not done in this PR, deliberately.
  3. Push a branch and watch dev-artifact.yml/fast-lane.yml run for real the first time. Expect at
    least two things to need adjusting: the KVM device option on the emulator-smoke jobs, and
    confirming (or fixing) the release-asset-upload auth assumption.
  4. Update docs/CI.md's [UNVERIFIED] tags once the first real run answers them.

Assumptions flagged as unverified (full list and reasoning in docs/CI.md §6)

  • The automatic token can create a release and upload release assets (confirmed for repo-write in
    general; confirmed not for package-registry push; release assets sit in neither confirmed
    bucket).
  • pebble build honours a CFLAGS env override for -Werror (slow/tag lane builds).
  • The docker-executor's containers get /dev/kvm access for the emulator-smoke jobs.
  • workflow_call/reusable workflows aren't used anywhere, specifically because this couldn't be
    confirmed as supported.
  • The default AGP debug/release APK output filename (companion-{debug,release-unsigned}.apk) —
    reasoned from the module name, not confirmed against a real build.

Claude-Session: https://claude.ai/code/session_01DAoXbRmJUf2uxNYBfdAXPt

Implements #67: the four CI lanes from PROCESS.md/TESTING.md, as `.forgejo/workflows/*.yml`, plus the three pinned toolchain images they run inside. **This is workflow-definition work, untested by execution.** No runner is registered against this repo, so nothing in this PR has actually run on a live pipeline. It needs an extra-careful human read, not a green-checkmark skim. `docs/CI.md` is the map from PROCESS.md/TESTING.md onto these files and tags every non-obvious claim `[VERIFIED]` / `[REASONED]` / `[UNVERIFIED]` — start there. ## Hard exclusion, on purpose **This PR does not register a runner, does not generate or touch a runner-registration token, and does not run or configure `act_runner`.** That's explicitly Robert's step — it needs a token from the Forgejo web UI, and a registered runner executes arbitrary workflow code against this repo, a different trust tier than a code PR. See "What Robert still has to do" below. ## Workflow directory: `.forgejo/workflows/`, not `.gitea/workflows/` Verified against <https://forgejo.org/docs/latest/user/actions/overview/> (2026-09-04): `.forgejo/workflows/` is primary; the *only* documented fallback is `.github/workflows/` — Forgejo's docs never mention `.gitea/workflows/` at all. The original issue text ("Files: `.gitea/workflows/ci.yml`") was simply wrong; the 2026-09-03 update's `.forgejo/workflows/` is correct and is what this PR uses. Left a comment on #67 with this finding, per its own ask. ## What's built - **`dev-artifact.yml`** — every push, any branch, ungated. Builds a debug `.pbw` (all three `targetPlatforms` — see the file for why "emery only" wasn't worth it given DEV.md's measured 2.171s build time) and a debug `.apk`, then publishes both to a single **rolling pre-release** (tag `dev-latest`) that's anonymously downloadable from Robert's phone browser — the repo is public, so no login or API token is needed to fetch a release asset. "Structurally untaggable" per PROCESS.md: the tag `dev-latest` can never match `tag-lane.yml`'s `v*.*.*` filter, and `tag-lane.yml` never downloads a dev-artifact output — it always rebuilds from the tagged commit. - **`fast-lane.yml`** — push + PR, 120s target / **180s hard fail** (`timeout-minutes: 3` per job). JVM tests (`:companion:core`, real), `pebble build` (real), gitleaks-on-the-diff / ktlint / detekt / clang-format (real, tool defaults since no project lint config exists yet), a meta-test asserting the workflow declares NFR-C7's required jobs (real). Host C tests and generated-code freshness check for their prerequisite (#68, #7/#53) and skip with a named issue reference if absent — each activates itself the moment its issue lands, no further edit needed here. - **`slow-lane.yml`** — push to `main`. Relies on `fast-lane.yml` re-running on the same merge commit for the "everything in the fast lane, plus" half (both files trigger on `push` to `main`). Adds: JVM coverage via Kover with a real bootstrap-and-ratchet implementation of TESTING.md §3.1 (writes `docs/ci/coverage-floor.json` on first green run, ratchets it thereafter, commits with `[skip ci]`), `assembleRelease` + an inline NFR-S2 dependency deny-list check (real), a `-Werror` build attempt (flagged `[UNVERIFIED]` — whether `pebble build`/waf honours a `CFLAGS` env override wasn't confirmed tonight), an emulator install+screenshot smoke test (flagged `[UNVERIFIED]` — whether the docker-executor container gets KVM access for QEMU). Host C coverage and replay regression skip-guard on #68/#23. - **`tag-lane.yml`** — push tag `v*.*.*`. Self-contained rather than layered on the other three via `uses:`, because this session couldn't verify Forgejo Actions supports `workflow_call` at all, and this is the one lane where an unverified YAML feature failing silently is least acceptable. Real, working checks for G11 (partial), G12 (field-report currency — date math against `docs/field/*.md`), G13 (quarantine-marker grep), G15 (TODO-threshold grep in TESTING.md), G9 (full-history gitleaks), plus a waiver-sanity check (annotated tag with a non-empty message). Rebuilds the `.pbw`/`.apk` fresh from the tagged commit and publishes a real (non-prerelease) release — same `[UNVERIFIED]` release-API-auth assumption as `dev-artifact.yml` (see below). Two gates (`host-c-suite`, `replay-and-emulator-gates`) **hard-fail** today because #68/#23 don't exist — correct behaviour: a release gate with nothing to check is a fail, not a pass (TESTING.md G15's own point), so **this lane cannot produce a release yet**, on purpose. - **Three pinned toolchain images** (`tooling/docker/{pebble-toolchain,android-toolchain,ci-tools}`) — pebble-tool/SDK 4.33.1 pinned to Python 3.13 with a build-time assertion (D54), JDK 21 + Android platform 37 + a Gradle cache warmed at image-build time, and gitleaks/ktlint/detekt/clang-format. Built once, only ever *pulled* by CI. `tooling/docker/README.md` has the exact build/push commands and explains why pushing them is a manual step: the automatic per-run token is confirmed unable to push to the package registry (unrelated to the runner-token exclusion above — a different, independently-confirmed limitation, see the README). - **NFR-S3**: no workflow here requires a manually-configured secret. The only token used anywhere is the automatic per-run `secrets.GITHUB_TOKEN`/`FORGEJO_TOKEN`, injected by the platform itself, not something Robert has to create or paste in. - **Badge + docs table row** in `README.md`; `docs/CI.md` is the full map, written to TESTING.md's own `[VERIFIED]`/`[RECALLED]`/`[REASONED]` tagging convention (here `[UNVERIFIED]` instead of `[RECALLED]`, since nothing was recalled — everything not directly checked tonight is named as an open gap instead). ## What Robert still has to do by hand 1. Build and push the three toolchain images (`tooling/docker/README.md`). 2. Register the self-hosted `act_runner` — label `pedalpebble`, **docker** executor (required for the `container:` image overrides every job here uses). Not done in this PR, deliberately. 3. Push a branch and watch `dev-artifact.yml`/`fast-lane.yml` run for real the first time. Expect at least two things to need adjusting: the KVM device option on the emulator-smoke jobs, and confirming (or fixing) the release-asset-upload auth assumption. 4. Update `docs/CI.md`'s `[UNVERIFIED]` tags once the first real run answers them. ## Assumptions flagged as unverified (full list and reasoning in `docs/CI.md` §6) - The automatic token can create a release and upload release assets (confirmed for repo-write in general; confirmed *not* for package-registry push; release assets sit in neither confirmed bucket). - `pebble build` honours a `CFLAGS` env override for `-Werror` (slow/tag lane builds). - The docker-executor's containers get `/dev/kvm` access for the emulator-smoke jobs. - `workflow_call`/reusable workflows aren't used anywhere, specifically because this couldn't be confirmed as supported. - The default AGP debug/release APK output filename (`companion-{debug,release-unsigned}.apk`) — reasoned from the module name, not confirmed against a real build. Claude-Session: https://claude.ai/code/session_01DAoXbRmJUf2uxNYBfdAXPt
Define the four CI lanes: dev-artifact, fast, slow and tag (#67)
Some checks failed
dev-artifact / build-pbw (push) Has been cancelled
dev-artifact / build-apk (push) Has been cancelled
dev-artifact / publish (push) Has been cancelled
fast-lane / host-c-tests (push) Has been cancelled
fast-lane / jvm-tests (push) Has been cancelled
fast-lane / pebble-build (push) Has been cancelled
fast-lane / lint-and-secrets (push) Has been cancelled
fast-lane / meta-declares-required-jobs (push) Has been cancelled
fast-lane / host-c-tests (pull_request) Has been cancelled
fast-lane / jvm-tests (pull_request) Has been cancelled
fast-lane / pebble-build (pull_request) Has been cancelled
fast-lane / lint-and-secrets (pull_request) Has been cancelled
fast-lane / meta-declares-required-jobs (pull_request) Has been cancelled
754f668248
Implements PROCESS.md's two-lane design (and the slow/tag lanes it builds on)
as .forgejo/workflows/*.yml, plus the three pinned toolchain images the
lanes run inside. This is workflow-definition work, verified by reading
Forgejo Actions' current docs rather than by execution — no runner is
registered against this repo yet, so nothing here has actually run.
docs/CI.md is the map from PROCESS.md/TESTING.md onto the workflow files,
tags every claim [VERIFIED]/[REASONED]/[UNVERIFIED], and lists exactly what
Robert still has to do by hand (build+push the three images, register the
runner). Neither this commit nor this session touched runner registration
or any runner-registration token, on purpose.

Workflow directory: .forgejo/workflows/, confirmed against Forgejo's own
docs — .gitea/workflows/ is not a recognised fallback at all (only
.github/workflows/ is), so the issue's original "Files" line was wrong.
Noted on the issue.

Jobs whose prerequisite artifact doesn't exist yet (#68's host C harness,
#7/#53's codegen, #23's replay harness) skip with a named issue reference
on the fast/slow lanes and hard-fail on the tag lane, so each activates
itself the moment its issue lands with no further edit here, and the tag
lane never silently passes a gate that has nothing to check yet.

Closes #67

Claude-Session: https://claude.ai/code/session_01DAoXbRmJUf2uxNYBfdAXPt
robert merged commit 0f49804e69 into main 2026-09-04 00:36:58 +02:00
Sign in to join this conversation.
No description provided.