Files
cc-ci/machine-docs/REVIEW.md

547 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# REVIEW — cc-ci Adversary (append-only)
This file is owned by the **Adversary** loop (§6.1). The Builder seeds this stub at bootstrap and
does not edit it afterward. Adversary appends milestone/D-item verdicts (`<id>: PASS @<ts>` +
evidence, or `FAIL` + a finding in `BACKLOG.md ## Adversary findings`), and may write `## VETO`.
<!-- Adversary verdicts below -->
## M0 — Foundations: PASS @2026-05-26T21:35Z
Verified cold (fresh shell, own clone `/srv/cc-ci/cc-ci-adv`, isolated host build dir
`/root/cc-ci-advverify`, no reuse of Builder's `/root/cc-ci`).
Acceptance — "`systemctl is-system-running` healthy after a rebuild from the repo" + Builder's
sops claim:
- **Repo rebuilds cc-ci:** synced M0 commit `deb4a0f` (git-archive, no .git) to host, ran
`nixos-rebuild build --flake .#cc-ci``BUILD EXIT 0`, produced
`…-nixos-system-nixos-24.11.20250630.50ab793`. Current HEAD also builds clean.
- **System health:** `systemctl is-system-running``running`; `systemctl --failed` → 0 units.
- **sops decrypt:** `/run/secrets/test_secret` present, mode `400 root:root`, 41 bytes, value
begins `cc-c…` (matches claimed generated `cc-ci-m0-…`). `secrets/secrets.yaml` is genuinely
encrypted (2× `ENC[…]` + sops metadata block).
- **D6 leak probe (early):** the decrypted plaintext value appears **0 times** across *all* git
history (`git grep -F over git rev-list --all`) and 0× in plaintext in `secrets.yaml`. No leak.
Note (not a finding; context for the M1 gate): the *running* system is already ahead of M0 — its
closure includes docker, `unit-swarm-init`, and **traefik** units (`traefik.yml`,
`traefik-stack.yml`, `unit-traefik-deploy`) that are **not yet committed** (HEAD `ab839ae` is
swarm-only, no traefik). Expected mid-M1 churn, but the Traefik config must be committed to the
repo before M1 is claimed or it fails D8 reproducibility — will check at the M1 gate.
## M1 — Swarm + abra target: PASS @2026-05-26T22:20Z
Verified cold from own clone; deployed my **own** probe recipe via abra (not trusting the Builder's
hand-test). Acceptance "a recipe deployed via abra is reachable over HTTPS at
`*.ci.commoninternet.net`, then fully torn down leaving no volumes" + orchestrator's M1 checklist
(ad).
- **(a) Real coop-cloud/traefik recipe (not hand-rolled):** `docker service ls`
`traefik_…_app` (`traefik:v3.6.15`) + `…_socket-proxy` (lscr.io socket-proxy) — the canonical
recipe layout, deployed via abra (`scripts/deploy-proxy.sh`). `modules/traefik.nix` is deleted.
- **(b) Wildcard on web-secure + proxy overlay:** static `traefik.yml` has `web-secure: :443`
(web→web-secure 301 redirect, verified live). File provider `/etc/traefik/file-provider.yml`:
`tls.certificates: [{certFile:/run/secrets/ssl_cert, keyFile:/run/secrets/ssl_key}]`; swarm
secrets `…_ssl_cert_v1`/`…_ssl_key_v1` mounted (2909 B / 227 B = the pre-issued cert). My probe
app `advm1probe_…_app` was attached to the `proxy` overlay.
- **E2E (cold deploy):** `abra app new custom-html -D advm1probe.ci.commoninternet.net` (forced
`LETS_ENCRYPT_ENV=""`) → `deploy succeeded 🟢`. Via SOCKS proxy: **HTTP 200**; served cert
`subject: CN=*.ci.commoninternet.net`, SAN-matched, `SSL certificate verify ok`, issuer LE E8 —
i.e. the **pre-issued wildcard**, NOT a per-host ACME cert.
- **(c) No Gandi/DNS token, no ACME credential:** repo (all history) clean; on host the only
gandi/dns-challenge strings are **commented-out** recipe-template options (`#GANDI_…`,
`#SECRET_GANDIV5_…`) holding no value. Active traefik env = `LETS_ENCRYPT_ENV=` (empty),
`WILDCARDS_ENABLED=1`, `compose.wildcard.yml`. `staging`/`production` certResolvers are *defined*
in traefik.yml (stock template) but **referenced by no router**; both acme.json are **0 bytes**;
**0 ACME lines in traefik logs**. No ACME ever fires. (Hardening risk filed — see findings.)
- **(d) Manual renewal documented:** DECISIONS.md — operator re-issues at same paths, then
`abra app secret rm … ssl_cert` + re-insert at bumped version; install.md "Renewed out-of-band;
never ACME here."
- **Teardown:** `abra app undeploy` + `volume remove` → post-teardown services/containers/volumes/
secrets for the probe **all 0**. Also independently confirmed the Builder's `cchtml1` test left 0
runtime resources (only its inert `.env` config file remains, harmless).
Verdict: **M1 PASS.** Not a hard fail on (c) — no token/credential exists and no ACME fires — but
the inert ACME resolvers + test-app default `LETS_ENCRYPT_ENV=production` are a latent hazard that
goes live when the harness deploys apps; filed as `[adversary]` for M4.
<!-- M2 live-trigger probe @2026-05-26T23:30Z: this push should create Drone build #4 -->
## M2 — Drone online: PASS @2026-05-26T23:32Z
Verified cold from own clone. Acceptance: "push to cc-ci triggers a visible green Drone build."
- **Drone server healthy:** `https://drone.ci.commoninternet.net/healthz` → HTTP 200 via gateway.
Exec runner (`drone-runner-exec.service`) active, `polling the remote server capacity=2 type=exec`.
- **Repo wired:** in Drone's DB the `recipe-maintainers/cc-ci` repo is `repo_active=1`,
`repo_config=.drone.yml`. Gitea↔Drone OAuth proven by the in-pipeline `clone` step succeeding
against the private repo (build can't clone without working OAuth/repo token).
- **Push→green, independently triggered:** I pushed my own commit `91a8e8d` (a REVIEW.md change) →
Drone created **build #4**, `build_event=push`, `build_trigger=@hook` (Gitea webhook), and it ran
**`success`**: stage `self-test` exit 0, steps `clone`+`hello` both exit 0. Builds #1#3 (Builder
commits) likewise all `success` via `@hook`. (My earlier M0/M1 review pushes predate the
`.drone.yml`, so correctly produced no builds.)
- **Visible logs (D7 precondition):** `logs` table holds per-step log blobs for every build; Drone
UI/API serve them. Full D7 UX is M8.
Verdict: **M2 PASS.** No new findings.
## M3 — Comment bridge: PRE-CLAIM PROGRESS (not yet PASS) @2026-05-26T23:48Z
M3 is **Blocked** in STATUS (Gitea not delivering webhooks), so not a gate verdict yet. But the
bridge is deployed and I independently hammered its auth/filter logic — the part I can verify
regardless of the delivery leg (and which survives a pivot to API polling). Probes were live POSTs
to `https://ci.commoninternet.net/hook` via the SOCKS proxy, with HMAC signatures I computed from
the on-host secret (read with root; value never printed/committed):
| probe | expect | got |
|---|---|---|
| no `X-Gitea-Signature` | 401 | **401** |
| bad signature | 401 | **401** |
| valid sig, event=`ping` (not issue_comment) | 204 | **204** |
| valid sig, `!testmexyz` on a real PR | 204 (no trigger) | **204** |
| valid sig, `!testme` but issue is not a PR | 204 | **204** |
| valid sig, `!testme` on PR, action=`edited` | 204 | **204** |
| valid sig, `!testme` on real PR, **non-collaborator** | 403 | **403** |
So: HMAC fail-closed + timing-safe (`compare_digest`, verified before body parse), `!testmexyz`
correctly ignored (exact trimmed match), non-PR ignored, and a non-collaborator is rejected (403;
collaborator status re-checked via Gitea API, not trusted from the signed payload). Source review
of `bridge/bridge.py` found no auth bypass.
**Blocker independently corroborated (operator-side):** the bridge hook *is* registered + active on
`recipe-maintainers/cc-ci` (id 210, events `[issue_comment]``ci.commoninternet.net/hook`), and
the bot is not a Gitea site-admin (`GET /admin/hooks` → 403) nor org owner, so it genuinely cannot
inspect/change Gitea's `[webhook] ALLOWED_HOST_LIST`. Endorse STATUS `## Blocked`: needs operator
allowlisting or the documented poll-the-API fallback.
**Still UNVERIFIED for an M3 PASS:** (1) the positive path — a valid collaborator `!testme` actually
starts a build + posts the PR comment end-to-end; (2) real Gitea→bridge delivery (or the polling
pivot). Will complete both when M3 is claimed.
**Noted for M7 (not a finding yet):** the Drone-managed Gitea webhook (id 209) carries its webhook
secret as a `?secret=` query param in the hook URL (Drone default; admin-only in Gitea, not in cc-ci
git / CI logs / dashboard). Will adjudicate against D6 at M7.
## M4 — Harness + install stage: VERIFICATION IN PROGRESS (no verdict yet) @2026-05-27T00:35Z
M4 is CLAIMED. Code review done; runtime checks so far:
- **A1 CLOSED** (see BACKLOG): harness forces `LETS_ENCRYPT_ENV=""` every deploy; live app
`cust-c95a69` served the wildcard cert, 0 ACME lines, no certresolver.
- **Happy-path teardown works:** a prior run's app `cust-e084bd` was fully torn down (gone) — not
an orphan; earlier ambiguity was a run cycling apps.
- **Two teardown-robustness defects filed (A2, A3):** janitor's `-pr` filter is dead code under the
`cust-<hex>` naming (no crash-orphan reaping); teardown is best-effort/unverified and deletes the
`.env` even on failed undeploy (silent orphan, run still green).
- **Deferred to next idle tick (a Builder harness run is active now; sequential-only):** my own
cold install run (green install + Playwright + clean teardown verification) and the §6 kill-mid-run
probe to test A3 empirically. Verdict (PASS/FAIL) follows that.
## M4 — Harness + install stage: PASS @2026-05-27T01:05Z
Verified by my **own** cold harness run (`RECIPE=custom-html REF=advcold… cc-ci-run
runner/run_recipe_ci.py`, app `cust-cfeb6a`, isolated from a Builder run that happened to run
concurrently as `cust-3c1970` — no collision, distinct domains/volumes/secrets):
- **Install stage green:** `test_install.py` → 2 passed (27s): `test_http_reachable` (HTTPS 200 via
gateway) + `test_playwright_page` (real Chromium loads the live app, status 200, served HTML).
- **Guaranteed teardown:** after the run, `cust-cfeb6a` left **0** services / volumes / secrets /
containers / `.env` — fully clean. Infra (traefik/drone/bridge/backups) untouched.
- A1 closed (no-ACME enforced). **Open robustness findings A2 (dead `-pr` janitor) + A3 (unverified
best-effort teardown)** concern the *crash* path (finalizer-skipped), not this happy-path run;
they don't block M4's literal acceptance but must be resolved before DONE (D2 teardown guarantee).
Kill-mid-run probe to substantiate A2/A3 deferred until the host is idle.
Verdict: **M4 PASS.**
## M5 — Upgrade + backup/restore stages: PASS @2026-05-27T01:05Z
Same cold run, stages 2 and 3 — both genuine end-to-end (no mocks; assertions reviewed in source
and not softened):
- **Upgrade green:** `test_upgrade.py` → 1 passed (41s). Deploys the **previous published version**
(`previous_version` = `recipe_versions[-2]`), writes a marker into the volume-backed html dir,
upgrades to latest (`abra upgrade`), then asserts HTTP 200 **and** the marker survives — a real
version change with data persistence across the volume (`cust-…_content`), not a no-op.
- **Backup/restore green:** `test_backup.py` → 1 passed (37s). Writes `original`, `abra backup`,
mutates to `mutated` (asserted), `abra restore`, then asserts the served content is back to
`original` ("restore did not return the pre-mutation state"). Real backup→mutate→restore cycle
via backup-bot-two.
- Teardown clean (same `cust-cfeb6a` 0-remnant check above covers all three stages — same domain
reused per stage).
Verdict: **M5 PASS.**
## M6 — Recipe-local tests + second recipe: VERIFICATION IN PROGRESS (no verdict yet) @2026-05-27T01:48Z
M6 CLAIMED. Host has been continuously busy (Builder M6.5 ramp), so deploy-based checks are
deferred to an idle window; static + evidence review so far:
- **custom-html 3-stage:** already verified cold by me (see M5 PASS) — green + clean teardown.
- **D4 recipe-local discovery — code genuine:** `run_recipe_ci.snapshot_recipe_tests` copies the
recipe-shipped `tests/` before abra re-checkouts to a version tag, then `run_recipe_local` deploys
the app and runs those tests against the LIVE app via `CCCI_BASE_URL`/`CCCI_APP_DOMAIN`, merged as
a separate stage with guaranteed teardown. Demo branch `recipe-maintainers/custom-html@
ci/d4-recipe-local` confirmed to ship `tests/test_recipe_local.py` (Gitea API). Will run it cold to
confirm the stage executes+passes.
- **keycloak (#2) install — test genuine:** `/realms/master` 200 health + real Playwright admin
console login (waits for the username field). `recipe_meta.py` (HEALTH_PATH/timeouts) confirms D5
"no harness surgery". Empirical keycloak reproduction deferred (heavy deploy; idle window).
- **Filed [adversary] A4** (concurrency): same-recipe concurrent runs share `~/.abra/recipes/<recipe>`
with no isolation/lock/concurrency-cap — a collision vector for the §6 concurrency check; to
confirm empirically.
Pending for idle host: cold D4 run, keycloak reproduce, A2/A3 kill-probe re-test, A4 concurrency test.
## D6/M7 — preliminary leak scan of published Drone logs (PASS so far; M7 not yet claimed) @2026-05-27T02:05Z
Host-safe probe while the host was busy. Pulled Drone's `database.sqlite`, dumped all 42 `logs`
rows (~25.5k chars of published per-step build output), scanned:
- **Known infra secrets — 0 leaks:** webhook HMAC (64), drone token (32), gitea token (40) each
appear **0×** in the logs (exact `grep -F`).
- **No value patterns:** 0 matches for `password|secret|token = <value>`.
- The only long hex/base64 hits are **git commit SHAs** in `git clone/merge` output — benign.
Caveat: current Drone logs are hello-world + self-test; the full M7/D6 test must also cover
app-generated secrets (e.g. keycloak DB passwords) in recipe-run logs AND the dashboard (M8). This
is a clean baseline, not the final D6 verdict. (DB copy was scanned off-box and deleted; no secret
value printed or committed.)
## M3 — Comment bridge: PASS @2026-05-27T03:13Z
Verified cold against the NEW design (orchestrator change: polling-PRIMARY + org-membership auth;
webhook now optional). Re-reviewed `bridge/bridge.py` (256 lines) — sound — then live-probed the
running bridge + Drone:
- **`!testme` triggers a run ≤60s:** I posted `!testme` (comment 13708) on PR #1 at epoch
1779847690 → bridge `[poll] triggered build 35` → Drone build 35 created at 1779847702 =
**12s** latency. (Build is `failure` only because `RECIPE=cc-ci` has no `tests/cc-ci/`; the
trigger + event=custom recipe-CI pipeline fired correctly — integration is live.)
- **Re-commenting re-runs:** my new comment 13708 → build 35, distinct from the earlier
comment 13705 → build 26. Distinct comment ids each fire once (dedup via `_claim`).
- **Other comments do NOT trigger:** I posted `!testmexyz`**no** build created, no bridge
trigger log. Exact trimmed match enforced.
- **Auth enforced (org-membership, fail-closed):** `GET /orgs/recipe-maintainers/members/<u>`
autonomic-bot & notplants → 204 (allowed), `definitely-not-a-member-zzz9` → 404 (rejected).
`is_authorized` returns True only on 204/allowlist; anything else (incl. errors) → False.
- **Link back:** bridge posted run-link comment 13706 ("cc-ci: started CI run … → drone…/recip…").
- **Concurrency cap live:** runner `capacity=1` (`DRONE_RUNNER_CAPACITY=1`) + pipeline
`concurrency:limit:1` — recipe-CI builds serialize.
Verdict: **M3 PASS.** (Polling is outbound read+comment only — no repo-admin; webhook optional.)
Note: full bridge→3-stage-recipe-CI E2E on a *real recipe* PR is the Builder's in-flight
integration item / D10 — build 35 shows the pipeline wiring works; green-on-a-real-recipe is M10.
## D6 — leak scan extended to recipe-CI build logs (still clean) @2026-05-27T04:05Z
Followup to the earlier hello-world scan: scanned the logs of all 7 `event=custom` recipe-CI builds
(~26.7k chars — these ran real `abra app deploy` + `abra app secret generate`, so generated app
secrets *could* surface here). Result: **0** `password|secret = <value>` patterns, **0** "secret
generated/inserted" value lines (abra doesn't echo secret values), and every long hex/base64 hit is
benign — Nix store paths, git SHAs, Drone workspace dir names (`<rand16>/drone/src`), pytest
tracebacks. No app-secret leak in published recipe-run logs. (Full M7/D6 verdict still pending the
dashboard (M8) leak check + final M7 claim.)
## M6 — Recipe-local tests + second recipe: PASS @2026-05-27T04:43Z
Acceptance: "both recipes green (custom-html 3-stage; keycloak install) + recipe-local merged",
plus D4/D5. Verified by a mix of my own cold runs + deep Drone-log corroboration (keycloak's 31-min
deploy made a self-rerun impractical on the contended host, so I read the actual build #39 logs, not
a Builder summary):
- **custom-html 3-stage:** my own cold run (see M5 PASS) — install/upgrade/backup green, 0 orphans.
- **keycloak (#2) full 3-stage — build #39 (event=custom, RECIPE=keycloak, success):** actual log
lines show `PASSED test_realm_endpoint_healthy`, `PASSED test_playwright_admin_login` (install,
510s), `PASSED test_upgrade_preserves_realm` (upgrade, 610s — DB realm survived), `PASSED
test_backup_mutate_restore` (backup, 495s — realm restored). Three separate reported stages (D2).
Tests are genuine (admin REST + real Playwright admin-console login; reviewed source — not mocked).
Post-run: **0** keycloak services/volumes (clean teardown).
- **D4 recipe-local — verified by my OWN run:** `RECIPE=custom-html SRC=…/custom-html
REF=ci/d4-recipe-local` → recipe-shipped `tests/test_recipe_local.py` snapshotted to a temp dir
(immune to abra's version re-checkout), deployed the app, ran
`test_recipe_local_serves_content PASSED` against the LIVE app via `CCCI_BASE_URL`, merged as a
`recipe-local` stage; clean teardown (0 `cust-` leftovers).
- **D5 (no harness surgery):** keycloak enrolled via `tests/keycloak/` + `recipe_meta.py` only; no
changes to shared `runner/harness` code. enroll-recipe.md documents the flow.
Verdict: **M6 PASS.** (keycloak full 3-stage also satisfies the first M6.5 breadth slot.)
## M6.5 — breadth ramp: RUNNING EVIDENCE (no verdict yet — recipes 56 + gate pending) @2026-05-27T06:12Z
Deep-corroborating each recipe's canonical Drone recipe-ci build from its actual logs (genuine
3-stage assertions, not summaries). Confirmed green so far (categories in parens):
- **custom-html** (simple/stateless) — build #33 + my own cold 3-stage run (M4/M5).
- **keycloak** (SSO + DB-backed) — build #39: realm health + Playwright admin login (install),
`test_upgrade_preserves_realm`, `test_backup_mutate_restore` (M6 verdict).
- **cryptpad** (stateful, no external DB) — build #46: `test_http_reachable`,
`test_playwright_loads_cryptpad`, `test_upgrade_preserves_data`, `test_backup_mutate_restore`.
- **matrix-synapse** (large-volume / DB + media store) — build #51: `test_client_api_healthy`,
`test_client_api_advertises_versions`, `test_upgrade_preserves_data`, `test_backup_mutate_restore`.
All three stages reported separately per build (D2). Categories covered: simple, SSO/DB, stateful,
large-volume. **Remaining:** recipe #5/#6 (multi-service+S3/object-storage, e.g. lasuite; and the
6th for breadth) + the M6.5 gate. Final M6.5/D10 verdict after those + the §6 concurrency check.
## Reconciliation @2026-05-27T06:18Z (watchdog ping)
Checked all standing claims: **every CLAIMED milestone gate through M6 is Adversary-PASS** —
M0 @21:35, M1 @22:20, M2 @23:32, M3 @03:13, M4 @01:05, M5 @01:05, M6 @04:43 (all <24h). The
"Gate: M0/M1/M2/M3 — CLAIMED, awaiting Adversary" strings still present in STATUS.md §Gates are
**stale** (already cleared here); a watchdog scanning that section may false-positive on them —
Builder may want to annotate them PASS. **No open milestone claim right now:** M6.5 is in-flight
(4/6 recipes corroborated green: custom-html/keycloak/cryptpad/matrix-synapse; recipes 56 + the
M6.5 gate pending), M7/M8/M9/M10 not yet claimed. Open findings: A2 (live janitor sweep pending an
idle host; mechanism already verified). Nothing for me to verify is currently blocked on me.
## M6.5 — Breadth ramp (recipes 36): PASS @2026-05-27T07:25Z
Acceptance: "recipes 36 each full three-stage green; enrolling N≥3 needed no shared-harness changes."
All six recipes' canonical Drone recipe-ci builds deep-corroborated from their actual logs (genuine
assertions + 3 separately-reported stages each; clean teardown):
- **cryptpad** #46 (stateful) — http + Playwright, `test_upgrade_preserves_data`, `test_backup_mutate_restore`.
- **matrix-synapse** #51 (large-volume/DB+media) — `test_client_api_healthy`/`_advertises_versions`,
`test_upgrade_preserves_data`, `test_backup_mutate_restore`.
- **lasuite-docs** #57 (multi-service + S3/MinIO) — `test_http_reachable`, `test_playwright_loads_frontend`,
`test_upgrade_preserves_data`, `test_backup_mutate_restore`.
- **n8n** #63 (workflow) — `test_healthz`, `test_playwright_loads_editor`, `test_upgrade_preserves_data`,
`test_backup_mutate_restore`.
(recipes 12 custom-html #33/keycloak #39 verified under M4/M5/M6.)
- **D5 (no harness surgery) verified:** grepped shared harness (`runner/harness`, `conftest`,
`run_recipe_ci`) — **no per-recipe branching** (`if recipe==…`); the only recipe names there are
comments. Per-recipe quirks (cryptpad SANDBOX_DOMAIN, health paths, timeouts) live in
`tests/<recipe>/recipe_meta.py` and are consumed via the generic `EXTRA_ENV`/meta hook in
`deploy_app`. Enrolling a recipe = `tests/<recipe>/` + `recipe_meta.py` only.
- **bluesky→n8n swap is plan-sanctioned + documented** (DECISIONS): bluesky-pds needs TLS-passthrough
to an in-container caddy doing its own ACME — incompatible with the no-DNS-token/no-ACME design;
documented non-CI'd recipe (per §2's explicit allowance). The 5 required D10 categories
(simple/SSO+DB/stateful/large-volume/multi-service+S3) are covered without it.
Verdict: **M6.5 PASS.** Note: these builds were triggered as recipe-ci custom builds (RECIPE param);
the **real `!testme`-on-a-PR** end-to-end for the breadth set is D10/M10, still to verify.
## M7 — Secrets hardening (D6): PASS @2026-05-27T07:55Z
Acceptance: "Adversary's secret-grep over published logs finds nothing; rotation doc followed."
Verified the §9 hard rule (no plaintext secret in git, logs, or UI) across ALL surfaces:
- **Published Drone logs — clean:** dumped every `logs` row across all builds (~119k chars; incl. the
6 recipe runs that generate app secrets). The 3 infra secrets (webhook HMAC / drone token / gitea
token, read from `/run/secrets`) each appear **0×**; no `password|secret|token=<value>` patterns;
long-token hits are git SHAs / nix paths / Drone workspace names (benign).
- **Dashboard — clean:** `https://ci.commoninternet.net/` (200) + `/badge/*.svg`: 0 secret patterns,
0 infra-secret values.
- **Git (all history) — clean:** each infra secret **0×**; `secrets/secrets.yaml` is sops-encrypted
(7× `ENC[…]`). No plaintext infra secret committed.
- **Redaction filter** (`run_recipe_ci.run_stage_redacted`): masks any `/run/secrets/*` value (≥8
chars) in stage stdout before it reaches Drone. Present as a safety net; 0 `REDACTED` markers in
logs = no secret was ever echoed in the first place.
- **Rotation doc (`docs/secrets.md`) matches reality:** `.sops.yaml` has exactly the documented two
recipients — host key `age1h90ut…` (from cc-ci's ed25519 SSH host key) + off-box master recovery
`age1cmk26t…`; sops-nix decrypts to `/run/secrets/<name>` (0400 root) using the SSH host key
(verified at M0 + present now). A1/A2 split + rotation steps are coherent.
Minor (not a finding): the redaction list covers infra secrets only, not per-run generated app
secrets — but abra doesn't echo generated secrets (recipe logs clean) so no app-secret ever surfaced.
Verdict: **M7 PASS.**
## M8 — Dashboard (D7): PASS @2026-05-27T08:10Z
Acceptance: "overview matches reality across several runs; outcomes mirrored to PR comments."
- **Overview matches reality:** `https://ci.commoninternet.net/` lists all 6 enrolled recipes, each
`success` with the **exact canonical build #s I independently corroborated** (cryptpad #46,
custom-html #33, keycloak #39, lasuite-docs #57, matrix-synapse #51, n8n #63) + relative "last run"
times; cc-ci itself correctly excluded; 30s auto-refresh; YunoHost-CI-like recipe table + status
badges, dark theme.
- **Status badges:** `/badge/keycloak.svg` encodes `success` (per-recipe embeddable badge).
- **PR-comment outcome reflection:** on PR #1 the bridge posted a start comment (id 13709 → run #35)
and a **final-outcome** comment (id 13712: "run for `cc-ci` @ `d397720a` ❌ **failure** → …/76") —
mirrors the final pass/fail and links the run. (Failure case shown; success path is the same code.)
- **No secret leak** on the dashboard/badges (verified under M7).
Verdict: **M8 PASS.** (A green ✅ outcome reflected on a *real recipe* PR is exercised at D10/M10.)
## M10/D10 — independent confirmation of the Docker Hub rate-limit blocker @2026-05-27T10:25Z
The Builder filed lasuite-docs upgrade failing on Docker Hub anonymous pull rate limits (A1 registry
creds needed; 5/6 recipes green via real `!testme`). I disbelieved and verified — it is **real, not a
masked harness defect**:
- Queried Docker Hub's rate-limit headers from cc-ci's own source IP (68.14.43.142):
`ratelimit-limit: 100;w=21600`, **`ratelimit-remaining: 1`** — i.e. ~1 anonymous pull left in the
6h window. The D10 breadth runs (6 recipes, lasuite alone = 9 images) drained the anonymous quota.
- lasuite Drone builds (#88/#92 failure, #93 killed) show no `toomanyrequests` in pytest output —
expected, because a rate-limited pull manifests at the docker/swarm task layer (deploy/health
timeout), not in the test log; the header check is the direct proof.
- The CI system itself is sound: lasuite install + backup are green; only the upgrade stage (most
image pulls) is gated, and only by the external quota. This is precisely the plan's anticipated A1
input (§1.5/§4.4: "rate-limit failure traced to this is a finding, then request creds").
**Consequence for DONE:** D10 requires all 6 recipes green via real `!testme` with all 3 stages.
lasuite-docs upgrade cannot reliably pass without authenticated registry pulls. **This is an
operator-action blocker** (provide Docker Hub creds → sops `secrets/`), analogous to the M3 webhook
whitelist. Not a VETO of system quality; a missing external input. DONE must wait until lasuite's
upgrade goes green via `!testme` (creds provided, or quota-window retry verified stable).
## M10/D10 — real-!testme proof: 5/6 VERIFIED (6th blocked on registry creds) @2026-05-27T10:42Z
Independently verified the full real-`!testme` path (D1 trigger + D2 three genuine stages + D7
outcome reflection) for 5 of 6 recipes, from a cold read of Drone + bridge logs + Gitea PR comments:
| recipe | build | bridge poll-trigger (real !testme) | stages | result |
|---|---|---|---|---|
| custom-html | #84 | PR#2 comment 13717 | 3 (4 asserts) | success |
| keycloak | #86 | PR#1 comment 13719 | 3 (4 asserts) | success |
| matrix-synapse | #87 | PR#1 comment 13720 | 3 (4 asserts) | success |
| n8n | #89 | PR#1 comment 13722 | 3 (4 asserts) | success |
| cryptpad | #90 | PR#2 comment 13727 | 3 (4 asserts) | success |
- Each build is `event=custom` with `REF`=PR-head sha (tests the PR's code, D1), 3 separately-reported
stages install/upgrade/backup (D2), and the bridge logged a genuine `[poll] triggered build N …
by autonomic-bot` for each (real comment, not a manual build).
- **Outcome reflection (D7):** verified on keycloak PR#1 — `!testme` → bridge comment "run for
`keycloak` @ 04400dff ✅ **passed** → …" (success path; ❌ failure path seen earlier on cc-ci).
- **6th recipe lasuite-docs:** install+backup green via `!testme`, **upgrade blocked** on the
Docker Hub anon rate limit (independently confirmed: remaining 1/100). Category = multi-service +
S3/object-storage; until its upgrade is green via `!testme`, **D10 is not fully met** (5/6).
Verdict: **D10 PARTIAL (5/6)** — pass for 5; the 6th awaits operator registry creds. No system defect;
the gap is the external pull quota. DONE must wait for lasuite's 3rd stage green via `!testme`.
## M9/D8 — Reproducibility: core PROVEN; full live blank-VM rebuild pending registry creds @2026-05-27T10:52Z
D8 ("entire server declared in the flake; rebuildable from scratch per docs/install.md; Adversary
rebuilds on a throwaway VM OR documents why infeasible + what was tested"). Done so far:
- **Nix-level reproducibility PROVEN (strongest evidence the repo *is* the server):** synced repo
**HEAD** (clean `git archive`, no .git) to an isolated host dir, ran `nixos-rebuild build
--flake .#cc-ci` → `BUILD EXIT 0`, and the built closure
`…m1pdvbhlmlj3x3gn0x83rgwcgssks7qs-nixos-system…` is **byte-identical to `/run/current-system`**.
So the entire running server (swarm, drone, traefik reconcile, comment-bridge, dashboard,
backupbot, sops secrets) is fully declared in the repo with **zero uncommitted drift** — a clean
rebuild reproduces it exactly. (`nixos-rebuild build` is not rate-limited; image pulls happen at
swarm runtime.)
- **docs/install.md is a complete from-scratch path:** operator preconditions (A1) + the whole
install = clone + one `nixos-rebuild switch` (reconcile oneshots auto-converge proxy/drone/bridge/
dashboard) + one-time `bootstrap-drone-oauth.sh`. Accurate vs. the verified architecture.
- **Deferred (per plan's documented-alternative allowance):** a full from-scratch LIVE deploy on a
blank NixOS VM (incus available) pulls every recipe/infra image at swarm runtime → hits the **same
Docker Hub anon rate limit** confirmed under M10 (remaining 1/100). Since DONE is already gated on
those operator registry creds, I will do the throwaway-VM live rebuild **when creds arrive**
(unblocks D8 live + D10 lasuite together) rather than wall against the quota now.
Status: **D8 reproducibility core PASS (Nix + docs); live blank-VM rebuild pending creds** — to
complete before DONE.
## D9 — Documentation: PASS @2026-05-27T10:55Z
Acceptance: "README + docs/ explain architecture, enroll a recipe, add/run tests locally, operate/
rotate secrets, debug a failed run; a new engineer can enroll a recipe and get a green run using
only the docs." Reviewed the full set:
- **architecture.md** — components, the `!testme` flow, network/TLS, resource safety.
- **enroll-recipe.md** — mirror the recipe → add `tests/<recipe>/` tree → recipe-local (D4) → add to
bridge poll list → optional webhook → run locally. Matches the verified enroll mechanism (D5: I
confirmed enrolling needs only `tests/<recipe>/`+`recipe_meta.py`, no harness surgery).
- **runbook.md** — where to look, common failure modes, orphans/cleanup, re-run/trigger by hand,
cancel a stuck build (debug a failed run).
- **secrets.md** — sops model + rotation (verified accurate vs reality under M7).
- **install.md** — from-scratch server build (verified reproducible under M9/D8).
- **README** — entrypoint, `!testme` overview, repo layout.
The enroll flow documented matches what I exercised hands-on for D4/M6 (custom-html recipe-local) and
what the Builder used for recipes 26 with no harness changes. Coverage is complete & accurate.
Verdict: **D9 PASS.**
## Scrutiny — lasuite `abra app upgrade -c` (no-converge-checks) is NOT a test-softening @2026-05-27T11:45Z
The Builder's fix (575efb5) for lasuite's upgrade "convergence failure" adds `-c` to `abra app
upgrade`. Per the anti-drift rule I checked whether this weakens the test to make a red pass — it
does **not**:
- `-c` disables only **abra's** convergence poll, which false-fails a slow 9-service rolling upgrade
(stop-first roll while pulling new images) even when services do converge.
- The harness's own verification post-upgrade is fully intact and is the real gate:
`test_upgrade_preserves_data` → `upgrade_app` → **`wait_healthy`** (= `services_converged`: every
stack service N/N replicas, looped up to recipe_meta `DEPLOY_TIMEOUT`=900s + HTTP health loop),
then asserts `http_get ∈ {200,301,302}` **and** a real `psql` read that the pre-upgrade
`ci_marker` row survived ("postgres data did not survive the upgrade").
- So a genuinely failed upgrade (services never reach N/N, app unhealthy, or DB data lost) **still
fails** the stage. The change trades abra's buggy/impatient check for the harness's more patient +
more meaningful one.
Cleared as legitimate. **Still required for D10 6/6:** an empirical lasuite upgrade **green via real
`!testme`**, whose build log I'll confirm shows genuine convergence (N/N) + the data-survival
assertion passing — not just absence of an abra error.
## M10/D10 — Proof: 6/6 PASS @2026-05-27T11:57Z
All six recipes now green via REAL `!testme` PRs, all three stages genuinely exercised — the 6th
(lasuite-docs) corroborated this tick:
- **lasuite-docs build #108** (event=custom, REF=9f685240=PR#1 head): real trigger confirmed in
bridge log (`[poll] triggered build 108 for lasuite-docs@9f685240 (PR #1, comment 13738) by
autonomic-bot`). 3 stages green: install (`test_http_reachable`, `test_playwright_loads_frontend`,
148s); **upgrade `test_upgrade_preserves_data` PASSED (141s)** — with the `-c` fix, the harness's
own `wait_healthy` (9 services N/N) + the `psql` data-survival check passed (no "did not survive"),
so the upgrade genuinely converged + DB data persisted (NOT hollowed by `-c`); backup
`test_backup_mutate_restore` PASSED (158s).
- Full D10 set (all via real `!testme`, comment-reflected): custom-html #84 (simple), keycloak #86
(SSO/identity+DB), matrix-synapse #87 (large-volume/DB+media), n8n #89 (workflow), cryptpad #90
(stateful), lasuite-docs #108 (multi-service+S3/object-storage). All 5 required categories covered.
- Registry creds (A1) turned out NOT to be required — the real blocker was abra's false-convergence
check (fixed by `-c`); the rate limit was transient (quota recovered). Creds remain a documented
good-to-have for robustness.
Verdict: **D10 PASS (6/6).**
## D8 — Reproducible server: PASS (documented-alternative) @2026-05-27T12:00Z
D8 accepts either a throwaway-VM rebuild OR "documenting why a full from-scratch rebuild was
infeasible and what was tested instead." A full from-scratch **live** rebuild on a throwaway host is
**infeasible by design**, for two immovable reasons I verified:
1. **sops is bound to cc-ci's host identity** — `modules/secrets.nix` decrypts via
`/etc/ssh/ssh_host_ed25519_key`; `.sops.yaml` recipients are only cc-ci's host age key + the
master recovery key. A throwaway VM (different host key) is not a recipient → cannot decrypt the
infra secrets → drone/bridge/etc. can't start without operator re-keying.
2. **Operator preconditions are cc-ci-specific** — the pre-issued wildcard cert
(`/var/lib/ci-certs/live`) and the DNS `*.ci.commoninternet.net → gateway → (passthrough) cc-ci`
point at cc-ci itself; they can't be reproduced on a throwaway VM (operator-owned, immovable).
**What was tested instead (stronger than a fresh-VM rebuild):** synced repo HEAD (clean, no .git) to
an isolated dir and `nixos-rebuild build --flake .#cc-ci` produced a closure **byte-identical to
`/run/current-system`** — i.e. the entire running server (swarm, drone, traefik reconcile,
comment-bridge, dashboard, backupbot, sops) is fully declared in the repo with **zero uncommitted
drift**; a clean rebuild reproduces it exactly. install.md is an accurate single-`nixos-rebuild`
from-scratch path + the documented operator preconditions. Every component was independently verified
live on cc-ci (M0M10).
Verdict: **D8 PASS** (Nix reproducibility proven byte-for-byte; throwaway-VM live rebuild infeasible
by design — documented per the plan's explicit allowance).
## DONE-readiness (Adversary) @2026-05-27T12:00Z
All D1D10 have an Adversary PASS dated within 24h, and findings A1A4 are all closed. **No VETO.**
| D | verdict | when |
|---|---|---|
| D1 trigger | PASS | M3 03:13 + D10 real-!testme runs |
| D2 3-stage matrix | PASS | M4/M5/M6 + D10 6/6 (real, 3 stages each) |
| D3 Playwright | PASS | live in every recipe install/D10 run |
| D4 recipe-local | PASS | M6 (own run) |
| D5 per-recipe tree / no harness surgery | PASS | M6.5 |
| D6 secrets | PASS | M7 (grep clean: logs+dashboard+git) |
| D7 results UX | PASS | M8 (overview matches reality + PR outcome) |
| D8 reproducible server | PASS | byte-identical build==running + documented-alt |
| D9 docs | PASS | full docs set reviewed |
| D10 six recipes via !testme | PASS (6/6) | #84/#86/#87/#89/#90/#108 |
From the Adversary side, the DONE handshake (§6.1) is **CLEARED** — Builder may flip STATUS → DONE.
(Note: registry creds remain a documented good-to-have for rate-limit robustness, not a DONE blocker.)
## Adversary sign-off on DONE @2026-05-27T12:12Z
STATUS shows `## DONE` (Builder, 1c10fa5). Final cold reality check confirms it is not a ledger lie:
- All D1D10 carry an Adversary PASS dated 2026-05-27 (<24h); findings A1A4 all **closed**; **no
standing `## VETO`**.
- Live system: `systemctl is-system-running` → running, 0 failed units.
- Dashboard (`ci.commoninternet.net`): **6/6 recipes success**, matching the corroborated Drone
builds (#84/#86/#87/#89/#90/#108, all real-`!testme`, 3 genuine stages each).
- Steady state clean: **0** orphaned `<tag>-<6hex>` test apps/volumes; teardown + janitor verified.
The DONE is **confirmed**. Adversary loop terminating — exit condition met (STATUS `## DONE` + fresh
PASS logged for every D1D10). Standing note: Docker Hub registry creds remain a documented
good-to-have for rate-limit robustness (not a correctness gap).
---
## SUPERSEDED by Phase 1c (appended @2026-05-27 18:55Z)
The Phase-1 D8 verdict above (and the "throwaway-VM live rebuild **infeasible by design**" wording
at lines ~485502) is **CORRECTED / superseded** by Phase 1c. The premise no longer holds: the
project's own recovery age key decrypts the repo's secrets on a fresh host, and the wildcard cert is
now sops-in-git — so a from-scratch live rebuild IS feasible and has been **performed and verified**.
Adversary cold-proved it 2026-05-27: a blank NixOS Incus VM + the two git repos + the single
bootstrap age key → one `nixos-rebuild switch` → fully-converged cc-ci, byte-identical (`ld19aj2`),
0 failed, 6 stacks 1/1, cert decrypted from git, TLS leaf == git cert. See REVIEW-1c.md (W4/C4/C5
PASS). D8 is now honest: static byte-identical **plus** live throwaway rebuild; "infeasible by design"
is withdrawn.