# DEFERRED — items parked for operator input

The single canonical registry of things the loops have deliberately decided **not to do
autonomously**, and that need operator input to move on. Filing here is the loops' explicit way
of saying *"we've considered this, we're not doing it on our own; the operator gets to decide
if/when it comes back"* — instead of a vague "Q4 follow-up" buried in a JOURNAL.

This list is **open-ended.** Items can sit here indefinitely; the operator reviews at their own
pace. There is **no obligation to close every item** — many will reasonably stay deferred for the
life of the project. Closing is operator-driven.

The Phase-4 cleanup pass should **surface** this list to the operator (so it's seen at least once
before the build is called done) — but does **not** force closure.

## Conventions
- **Append-only.** Either loop may file; never edit/delete someone else's entry. Closing = check
  the box + a one-liner pointing to the commit / PR / operator decision.
- **Each entry should clearly say what the loops would need from the operator** to lift the
  deferral (an opt-in flag, a resource decision, an architectural call, plain "go ahead and do
  it") — that's the actionable part for the operator skimming this list.
- A "Re-entry trigger" / IDEA cross-link is **optional** — include when there's a natural
  mechanism (e.g. an opt-in flag in `cc-ci-plan/IDEAS.md`); not every deferral has one, and many
  legitimately don't.

## Format (one item per entry)
```
### YYYY-MM-DD — <slug>
- [ ] **What:** <concrete description, link to file/test/spec>
- **Filed by:** <Builder|Adversary>, phase <id>
- **Reason for deferral:** <technical, scope, "more than needed for default CI", dependency>
- **Re-entry trigger:** <optional — what operator input / mechanism would bring it back>
- **Linked IDEA / BACKLOG:** <optional cross-ref>
```

---

## Open deferrals

### 2026-05-28 — matrix-synapse `compress_state.sh` port
- [ ] **What:** Port the upstream recipe-maintainer `recipe-info/matrix-synapse/tests/compress_state.sh`
      to a cc-ci functional test under `tests/matrix-synapse/functional/`. The original creates state
      groups WITHOUT edges (full snapshots — Synapse's bloat pattern), runs `synapse_auto_compressor`,
      and asserts row counts drop.
- **Filed by:** Builder, phase 2 (Q4.1 matrix-synapse PARITY pass)
- **Reason for deferral:** Needs N>>1 synthesized state groups on every fresh deploy. Cost/time
  tradeoff is real — too-small N loses the test's meaning (state-group bloat is by definition a
  large-state phenomenon), too-large N inflates per-run time. Defensible defer; operator-confirmed
  2026-05-28: heavier than needed for default CI.
- **Re-entry trigger:** the `--extra` opt-in flag (see linked IDEA) so this runs only when
  the operator explicitly asks for the heavy suite; or a dedicated long-running matrix instance.
- **Linked IDEA:** `cc-ci-plan/IDEAS.md` — *Optional `--extra` flag for heavy/operational tests*.

### 2026-05-28 — matrix-synapse `test_complexity_limit.sh` port
- [ ] **What:** Port `recipe-info/matrix-synapse/tests/test_complexity_limit.sh` — exercise Synapse's
      complexity-limit rejection of overly-complex events.
- **Filed by:** Builder, phase 2 (Q4.1 matrix-synapse PARITY pass)
- **Reason for deferral:** Load-test class; needs many-event setup. Operator-confirmed 2026-05-28:
  more than needed for a default matrix CI test.
- **Re-entry trigger:** the `--extra` opt-in flag (linked IDEA).
- **Linked IDEA:** `cc-ci-plan/IDEAS.md` — *Optional `--extra` flag for heavy/operational tests*.

### 2026-05-28 — matrix-synapse `test_purge.sh` port
- [ ] **What:** Port `recipe-info/matrix-synapse/tests/test_purge.sh` — exercise the recipe's
      `abra.sh db purge_history` / `db purge_room` admin helpers.
- **Filed by:** Builder, phase 2 (Q4.1 matrix-synapse PARITY pass)
- **Reason for deferral:** Recipe-helper-script tests, not synapse-behaviour tests (orthogonal to
  default Phase-2 coverage). Operator-confirmed 2026-05-28: more than needed for a default matrix
  CI test.
- **Re-entry trigger:** the `--extra` opt-in flag (linked IDEA) — so PRs touching the recipe's
  abra helper scripts can opt in to exercising them.
- **Linked IDEA:** `cc-ci-plan/IDEAS.md` — *Optional `--extra` flag for heavy/operational tests*.

### 2026-05-28 — matrix-synapse media upload/download roundtrip
- [ ] **What:** Add `tests/matrix-synapse/functional/test_media_upload_roundtrip.py` exercising
      `/_matrix/media/v3/upload` + `/_matrix/media/v3/download/<server>/<media_id>`.
- **Filed by:** Builder, phase 2 (Q4.1 matrix-synapse PARITY pass)
- **Reason for deferral:** Not in the Q4.1 first pass; the three currently-landed functional tests
  already cover Synapse's defining behaviour (register / room / message / federation).
- **Re-entry trigger:** Phase-2 follow-up (a recipe-coverage breadth pass) OR a PR that touches
  Synapse's media subsystem.
- **Linked IDEA:** —

### 2026-05-28 — lasuite-docs OIDC parity ports + create-a-doc deeper test
- [x] **CLOSED @2026-05-28** by Builder commits `41ede13` (SSO-dep refactor: deps-after-generic
      tiers + `tests/lasuite-docs/setup_custom_tests.sh` hook + `deps_creds` fixture) and
      `cd25f52` (functional/test_oidc_login.py parity port + functional/test_create_doc.py §4.3
      prescribed create-a-doc + read-back). Both tests marked @pytest.mark.requires_deps.
      Cold-verifiable: `RECIPE=lasuite-docs STAGES=install,custom cc-ci-run runner/run_recipe_ci.py`
      → 5 custom tests PASS (incl. the two new ones), deploy-count=2 (recipe + keycloak dep).
      `upload_conversion.py` parity (.md/.docx upload+conversion via authenticated
      `/api/v1.0/documents/<id>/upload`) remains as a Phase-2 follow-up below.

### 2026-05-28 — cryptpad create-a-pad + content round-trip Playwright test  — ✅ RESOLVED @2026-05-29
- [x] **RESOLVED @2026-05-29 (Builder, commits `05d0dc1` test + `656b68b` cold-timing fix).**
      `tests/cryptpad/playwright/test_pad_content_roundtrip.py` lands the §4.3 create-pad → type →
      FRESH-context read-back, **green in the full harness custom tier** (`/root/ccci-cryptpad-full3.log`:
      install/upgrade/backup/restore/custom all pass; `test_cryptpad_pad_content_survives_fresh_session`
      PASSED; deploy-count=1; clean teardown). Mapped empirically against CryptPad 2026.2.0 (the prior
      deferral cited 5.7.0 fragility): editor in nested `…/pad/ckeditor-inner.html`; `/pad/` DOES
      auto-create a fragment-keyed pad after ~15s cold init; patience-tuned (`goto_with_retry` + 240s
      hash-wait + reload). F2-9 (Adversary-owned) satisfied — left for the Adversary to close on
      cold-verify. (Detail below retained for audit.)
- [ ] **What:** Add `tests/cryptpad/playwright/test_pad_content_roundtrip.py` — exercise the full
      "open /pad/, type uniquely-marked content, reload, assert marker survives in the decrypted
      pad" lifecycle. The §4.3 prescribed CryptPad test.
- **Filed by:** Builder, phase 2 (Q3.4 cryptpad PARITY pass)
- **Reason for deferral:** CryptPad's pad-creation flow is **version-specific** in the release
  under test (10.6.0+5.7.0). `/pad/` does NOT auto-redirect to a fragment-keyed pad URL on visit;
  the UI selector for "new rich-text" varies across versions; three drafts each missed the right
  contract. The maximal subset that IS shipped (parity health_check + recipe-specific spa_assets
  + Playwright SPA-render with console-error filter) covers the same JS-pipeline initialization
  that create-a-pad relies on. F2-9 Adversary conditional sign-off granted with the explicit
  expectation this lifts before Phase-2 DONE.
- **Re-entry trigger:** Adversary's F2-9 sign-off requires this lifts BEFORE Phase-2 DONE — must
  pin a stable CryptPad app-launch contract (e.g. `/pad/?new=1` if supported, or a role-based
  Playwright accessibility-tree selector for "New Rich Text") + ship the create-and-read-back
  test. Q5.2 cold-sample MUST include this.
- **Linked IDEA:** —

### 2026-05-28 — uptime-kuma create-a-monitor (§4.3 prescribed)
- [x] **CLOSED @2026-06-11 (Builder, phase kuma):** `tests/uptime-kuma/playwright/test_monitor_wizard.py` implemented and proven in real CI. Playwright (option b) drives the actual browser; Socket.IO handled transparently. Flow: wizard admin-create → self-probe monitor (→ Up, real heartbeat row) + dead-port monitor (→ Down, proves probe engine). Commits: `8da59cf` (test) + `fe8922c` (M1 claim). Drone builds #460 + #462 both LEVEL 5 with `test_monitor_wizard [pass]`. M1+M2 Adversary PASSes in REVIEW-kuma.md. DEFERRED is closed.
- [x] **RE-ENTERED @2026-06-11:** operator approved — executing as phase `kuma` (cc-ci-plan/plan-phase-kuma-monitor.md).
- [ ] **What:** Add a test that completes uptime-kuma's first-run setup wizard via Socket.IO,
      logs in to obtain a JWT, creates a monitor (`monitor add` Socket.IO emit), and asserts the
      monitor appears in the listed-monitors response.
- **Filed by:** Builder, phase 2 (Q4.8 uptime-kuma enrollment)
- **Reason for deferral:** Requires a Socket.IO client primitive in `runner/harness/` (uptime-kuma
  uses Socket.IO for ALL real-time updates including setup + monitor CRUD). Today's tests
  (parity health + Socket.IO handshake + SPA branding) cover the same handshake + bundle the
  setup-then-monitor flow would use; adding a full Socket.IO client is a substantial harness
  primitive worth deferring until either (a) another recipe also needs Socket.IO interaction or
  (b) the `--extra` flag lands so this can live in `extra/`.
- **Re-entry trigger:** the `--extra` opt-in flag (linked IDEA) OR another recipe enrollment
  that requires Socket.IO client primitives in the harness (whichever comes first).
- **Linked IDEA:** `cc-ci-plan/IDEAS.md` — *Optional `--extra` flag for heavy/operational tests*.

### 2026-05-28 — ghost create-a-post round-trip (§4.3 prescribed) — ✅ RESOLVED @2026-05-30
- [x] **RESOLVED @2026-05-30 (Builder):** `tests/ghost/functional/test_post_roundtrip.py` (helper
  `_ghost.py`) authored + GREEN (`test_create_post_roundtrip PASSED`, full-lifecycle run
  `/root/ccci-ghost-pr1d.log`). Owner setup → admin session cookie → POST published post (unique
  marker) → GET read-back (title+html). Part of the Q4.4 ghost claim (STATUS-2 ## Gate Q4.4).
- [ ] **What:** Add `tests/ghost/functional/test_post_roundtrip.py` exercising Ghost's admin setup
      + token-auth + POST `/ghost/api/v3/admin/posts/` (create) + GET
      `/ghost/api/v3/admin/posts/<id>/` (read back), asserting the post round-trips.
- **Filed by:** Builder, phase 2 (Q4.4 ghost enrollment)
- **Reason for deferral:** Requires Ghost's first-run owner-setup flow (POST
  `/ghost/api/v3/admin/authentication/setup/` with per-run admin email+password as class-B
  run-scoped) + JWT token management for the admin API. The current 3 tests
  (parity health + content_api + admin_redirect) cover the same Ghost-server / API / admin-route
  surface; the create-post flow is the natural §4.3 deeper test and is doable, but adds setup
  state to manage. Reasonable to defer to the `--extra` flag rollout OR a Phase-2
  follow-up specifically for Q4 deeper tests.
- **Re-entry trigger:** the `--extra` opt-in flag (linked IDEA) OR a Q4 deeper-test pass
  before Phase-2 DONE if the Adversary calls for it (Phase-4 cleanup pass MUST review).
- **Linked IDEA:** `cc-ci-plan/IDEAS.md` — *Optional `--extra` flag for heavy/operational tests*.

### 2026-05-28 — Q2.2 authentik enrollment + `setup_authentik_realm` SSO backend
- [ ] **What:** Enroll authentik in cc-ci tests/ (mirror-and-enroll if not yet mirrored) + add a
      `setup_authentik_realm` (or equivalent provider-pluggable name) backend in
      `runner/harness/sso.py` mirroring the keycloak path; a dependent recipe should be able to
      declare `DEPS = ["authentik"]` and use the same `harness.sso.setup_<provider>_*` API.
- **Filed by:** Adversary (F2-7, Q2 checkpoint) → migrated to DEFERRED.md by Builder
- **Reason for deferral:** Q2.4 acceptance is already proven via keycloak; no Phase-2 dependent
  recipe yet REQUIRES authentik specifically (the lasuite-* recipes use keycloak; cryptpad's
  recipe-maintainer SSO test uses authentik but that parity port is already deferred above). The
  SSO harness's OIDC FLOW primitives (`oidc_password_grant`, `assert_discovery_endpoint`) are
  already provider-agnostic; only `setup_keycloak_realm` is keycloak-specific.
- **Re-entry trigger (NARROWED per operator SSO policy 2026-05-29):** ONLY when a recipe **genuinely
  REQUIRES authentik** (cannot work under keycloak). Dropped the former triggers — cryptpad's OIDC is
  now tested under **keycloak** (its upstream uses authentik but keycloak is equally valid), and
  **Phase-2 DONE is explicitly NOT gated on authentik** (no "prove pluggability"/second-provider/
  DONE-review trigger). keycloak is the default SSO provider for all recipe OIDC tests. See
  DECISIONS.md "SSO-provider policy".
- **Linked IDEA:** —

### 2026-05-29 — heavy-recipe upgrade tier needs more host disk (28GB too small) — CLOSED @2026-05-29
- [x] **CLOSED @2026-05-29:** orchestrator resized the cc-ci VM disk; filesystem auto-grew to **64G
      (44G free, 30% used)**, infra healthy, warm keycloak up. The disk constraint is resolved. The
      heavy-recipe upgrade tiers are now runnable. **Follow-on (now ACTIVE backlog, not a deferral):**
      run lasuite-drive's FULL lifecycle incl. the upgrade tier GREEN + Adversary cold-verify for the
      Q3.2 gate (per the Adversary, the upgrade tier is no longer validly deferrable); then re-confirm
      immich/lasuite-meet/lasuite-docs upgrade tiers. Tracked under BACKLOG-2 Q3.2.
      **UPDATE @2026-05-29:** lasuite-drive full lifecycle (incl. upgrade tier) is now **3× green**
      (commits `a151489` install-time OIDC + `4b38b66` collabora-ready upgrade gate; logs r2/r3/r4);
      Q3.2 CLAIMED, awaiting Adversary. The upgrade tier converged cleanly at 64G disk with the
      collabora-ready gate (the old 28GB pull-overflow concern below is moot at 64G). Remaining
      follow-on: re-confirm immich/lasuite-meet/lasuite-docs upgrade tiers when those recipes' gates run.
- [ ] **What:** The upgrade tier for the heaviest recipes cannot complete on the 28GB host. Proven
      on **lasuite-drive**: the prev→PR-head chaos upgrade crosses two multi-GB office image versions
      at once — onlyoffice/documentserver-de `9.2 → 9.3.1.2` (3.94GB each) + collabora/code
      `25.04.9.1.1 → 25.04.9.4.1` (~1GB) — so ~10GB of office images must coexist on disk during the
      in-place rolling update. The host has only ~14GB docker headroom over its ~13GB baseline (nix
      store ~9.6GB + infra images), so the PR-head pull hit 99% and the deploy failed. There is **no
      harness mitigation** (the prev images are *running* when the new must be pulled — cannot `rmi` a
      running image; nothing dangling to prune pre-upgrade). install/backup/restore/custom (single
      version, ~6GB) all fit and pass — only the upgrade tier overflows. Almost certainly also blocks
      the upgrade tier of other heavy recipes (lasuite-docs ships collabora; immich ships multi-GB ML
      images; lasuite-meet).
- **Filed by:** Builder, phase 2 (Q3.2 lasuite-drive full-lifecycle attempt)
- **Reason for deferral:** Class A1 EXTERNAL infra input — host disk size. Not improvisable; not a
      test-quality issue; the recipe legitimately bumps office image tags across releases.
- **Operator action to lift:** grow the cc-ci host disk (resize the droplet volume + online-grow the
      filesystem) to give heavy-recipe upgrade tiers transient headroom — ~+20GB would comfortably
      cover the dual-office-version crossover and the rest of the heavy set. Then re-run the full
      lasuite-drive lifecycle (and re-confirm immich/lasuite-meet/lasuite-docs upgrade tiers).
- **Re-entry trigger:** operator disk resize, OR Phase-2b pull-through cache + image-GC policy work.
- **Linked IDEA:** `cc-ci-plan/IDEAS.md` (pull-through cache / Phase 2b).

---

## Closed deferrals
(none yet — append `### YYYY-MM-DD — <slug> CLOSED (commit/PR)` here when re-entered.)

### 2026-05-28 — plausible (Q4.7) recipe enrollment
- [x] **CLOSED @2026-06-11 (operator housekeeping):** overtaken — plausible is enrolled and running in CI (§4.3 floor `71af595`); the full-lifecycle remainder is the Q4.7b entry below (recipe PR#3 green, operator merge pending).
- [ ] **What:** Enroll plausible in cc-ci with parity health_check + ≥2 specific tests (per
      plan §4.3: "track a test event, query it back"). `tests/plausible/recipe_meta.py` +
      `tests/plausible/functional/test_health_check.py` are drafted (commit pending) but the
      e2e fails: services converge but the served app returns HTTP 500 from `/` for the full
      600s HTTP_TIMEOUT window — config-class failure, not a deploy-timing issue.
- **Filed by:** Builder, phase 2
- **Reason for deferral:** The first deploy attempt set EXTRA_ENV={DISABLE_AUTH=true,
  DISABLE_REGISTRATION=true, SECRET_KEY_BASE=<64-char fixed>}. Stack converged 1/1 but the
  Phoenix app returned 500 the whole window. Likely missing required config (e.g. DATABASE_URL,
  MAILER vars, or a Phoenix bootstrap step). Diagnosing requires live container-log inspection
  + iterative env tuning — more debug time than fits a single autonomous loop pass.
- **Operator action to lift:** Either (a) iterate on plausible's required env / debug live
  logs in an interactive session; OR (b) re-enroll plausible after the operator confirms a
  working env recipe.
- **Linked IDEA:** —

### 2026-05-28 — lasuite-docs upload_conversion.py parity (.md/.docx upload + conversion)
- [ ] **What:** Port `recipe-info/lasuite-docs/tests/upload_conversion.py`. The original uploads
      a `.md` and a `.docx` to `POST /api/v1.0/documents/<id>/upload` and asserts the y-provider /
      docspec conversion paths fire (.md → yjs; .docx → BlockNote → yjs).
- **Filed by:** Builder, phase 2 (Q3.1 follow-up after the OIDC pieces closed)
- **Reason for deferral:** Builder priority — the §4.3 create-a-doc floor is met by
  test_create_doc.py (closed in the entry above). Upload/conversion exercises a distinct subsystem
  (y-provider + docspec) and adds two binary fixtures + a multi-service-readiness wait.
  Defensible defer; lift when the operator wants the deeper coverage OR Phase-4 reviews.

### 2026-05-29 — immich recipe needs a pg_dump backup hook for reliable DB restore (P4)
- [x] **CLOSED @2026-06-11:** cc-ci-authored immich recipe PR#2 (pg_dump hook) verified green; operator confirmed 2026-06-11 — merge pending, no further loop work.
- [ ] **What:** immich's upstream recipe backs up the LIVE postgres data VOLUME via restic
      (`backupbot.backup=true` on `database`, no pg_dump hook), so a DB row does NOT survive
      `abra app restore` (diagnosed: seed→backup→drop→restore→row absent; app healthy). Real
      backup data-integrity (P4) requires a consistent SQL dump. **Fix:** add the drive/meet pattern
      to the immich recipe — `pg_backup.sh` swarm-config + labels `backupbot.backup.pre-hook:
      "/pg_backup.sh backup"` + `backupbot.backup.volumes.postgres.path: "backup.sql"` +
      `backupbot.restore.post-hook: "/pg_backup.sh restore"` (adapt POSTGRES_USER=postgres,
      POSTGRES_DB=immich). Via the recipe-create-pr flow (mirror immich on recipe-maintainers → branch
      → cc-ci full-suite GREEN on the PR incl. restore tier → Adversary cold-verify → operator merge),
      exactly like the parked Q3.2b lasuite-drive recipe-robustness PR.
- **Filed by:** Builder, phase 2 (Q3.5 immich enrollment).
- **Reason for deferral:** UPSTREAM recipe defect; the proper fix is a recipe PR (we maintain it),
      which is operator-merge-gated — not a cc-ci/test change. immich's other tiers (install/upgrade/
      backup-artifact/restore-healthy/custom incl. §4.3 asset upload→readback→thumbnail) are GREEN.
- **Re-entry trigger:** pick up as a recipe-PR unit (parallel to Q3.2b); OR Adversary §7.1 sign-off on
      the documented maximal subset if a recipe PR is out of scope for Phase-2 DONE.
- **Linked IDEA:** —

### 2026-05-29 — discourse: upstream recipe pins removed bitnami images (undeployable)
- [x] **CLOSED @2026-06-11 (operator housekeeping):** superseded — discourse is enrolled and runs the full lifecycle in CI (L4 baseline run 184, 2026-06-05); the bitnami-pin blocker no longer applies.
- [ ] **What:** discourse (Q4.6) cannot be enrolled/tested because the recipe pins
      `image: bitnami/discourse:<tag>` (app + sidekiq) and **Docker Hub no longer serves any
      `bitnami/discourse:*` tag** (bitnami's 2024/2025 legacy migration). Proven on cc-ci:
      `docker pull bitnami/discourse:3.3.1` → `manifest unknown`; the swarm app task is `Rejected:
      "No such image: bitnami/discourse:3.3.1"`. The image IS available at
      `bitnamilegacy/discourse:3.3.1` (verified present). db(postgres)+redis deploy fine; only the
      bitnami-imaged app/sidekiq fail. Test scaffolding is staged (tests/discourse/: recipe_meta,
      postgres-P4 ops + backup/restore overlays, health) but the §4.3 create-a-topic test was never
      written/validated (deploy blocked before the app booted).
- **Filed by:** Builder, phase 2 (Q4.6 discourse smoke).
- **Reason for deferral:** UPSTREAM recipe + image-availability defect, not a cc-ci/test issue.
      Compounded: cc-ci's **install tier deploys the PREVIOUS published version** (0.6.3+3.1.2 →
      bitnami/discourse:3.1.2, also removed), so even a recipe-PR repointing to `bitnamilegacy/` only
      fixes the upgrade head + FUTURE installs once released — it does NOT make the install tier
      deployable under the current published versions (all bitnami/discourse tags gone). Same
      constraint class as plausible Q4.7b. Not improvisable by editing the in-repo compose (that would
      be testing a fork, not the published recipe).
- **Operator action to lift:** a discourse recipe-PR repointing app+sidekiq to a maintained image
      (`bitnamilegacy/discourse:<tag>` or another upstream) **AND a new published recipe version**, so
      a deployable published version exists for the install tier. Then re-run RECIPE=discourse + add
      the §4.3 create-a-topic test. (Broader: any other §5 recipe on a bitnami image may hit the same.)
- **Re-entry trigger:** upstream discourse recipe ships a deployable image version; OR operator
      approves a cc-ci-authored discourse recipe-PR + release.
- **Linked IDEA / BACKLOG:** Q4.6.

### 2026-05-29 — mailu: no backup config (P4 N/A) — recipe-PR to add backupbot
- [x] **CLOSED @2026-06-11 (phase mailu, Builder):** Mirror PR#3 (`add-backupbot-labels`, head
      `edc0201a79d3`) on `git.autonomic.zone/recipe-maintainers/mailu` adds backupbot v2 labels to
      `admin` service (`/data` SQLite) and `imap` service (`/mail` Maildir). Full lifecycle at PR head
      = LEVEL 5 (drone build #477): install/upgrade/backup/restore/functional all PASS; both
      `/data` (SQLite) and `/mail` (Maildir) seeded + wiped + verified restored. Adversary M1 PASS
      @2026-06-11T21:00Z. PR left open for operator merge. mailu's backup rung is now earned
      (`backup_capable=True`), not skipped. Phase mailu M1 PASS; M2 claim in progress.
- [x] **RE-ENTERED @2026-06-11:** operator approved the backupbot recipe-PR route — executing as phase `mailu` (cc-ci-plan/plan-phase-mailu-backup.md).
- [ ] **What:** mailu (Q4.9) ships **no `backupbot.backup` label** on any service, so cc-ci's
      backup/restore tiers cleanly SKIP (`backup_capable=False`) — P4 (backup data-integrity) is N/A
      for mailu as published (no backup mechanism to exercise). Durable fix = a recipe-PR adding
      backupbot labels (admin sqlite DB at /data + the `mailu` mail volume), mirroring the immich Q3.5
      / Q3.2b pattern.
- **Filed by:** Builder, phase 2 (Q4.9 mailu enrollment).
- **Reason for deferral:** UPSTREAM recipe has no backup config; adding it is a recipe change
      (operator-merge-gated via recipe-create-pr), not a cc-ci/test change. mailu install+upgrade+
      functional (create-mailbox + IMAP-login + send/receive mail-flow) are covered.
- **Re-entry trigger:** Adversary §7.1 sign-off accepting P4-N/A for mailu, OR operator approves a
      cc-ci-authored mailu backupbot recipe-PR.
- **Linked IDEA / BACKLOG:** Q4.9.

### 2026-05-29 — drone (Q4.10) blocked on host /etc/timezone deploy (gitea SCM dep) + scoped integration
- [x] **RE-ENTERED @2026-06-11:** operator approved — executing as phase `drone` (cc-ci-plan/plan-phase-drone-enroll.md); P0 host /etc/timezone deploy is orchestrator-owned.
- [x] **MAXIMAL SUBSET COMPLETE @2026-06-11T22:30Z — Adversary M2 PASS, build #506 L5.** All mandatory tiers (install+upgrade+functional+lint) pass; backup structural skip justified in PARITY.md; bridge-triggered !testme CI run confirmed `event:custom`. DEFERRED item progressed: (1) P0 host fix: DONE; (2) Integration MAXIMAL SUBSET: DONE. **Build-creation gap (§4.3) remains open** — deferred sub-item per original filing.
- **Adversary §7.1 sign-off on build-creation gap @2026-06-11T22:30Z:** The drone API build-creation flow (creating/running CI pipelines via drone's own API — requires drone OAuth token + `.drone.yml` + webhook) is accepted as a genuine, proportionate deferral. It is a harness capability gap, not a recipe gap. Drone boots with gitea SCM wired correctly (proven L5 in build #506); build-creation automation is a follow-on. SIGNED OFF. Remaining DEFERRED: build-creation API automation only.
- [ ] **What:** drone (Q4.10, LAST §5 recipe) cannot be enrolled until two things land:
      (1) **HOST FIX — operator-deploy needed:** drone is a CI server that REQUIRES a git-provider SCM
      to boot; the only viable dep is **gitea**, which the recipe binds `/etc/timezone:ro` from the
      host. NixOS `time.timeZone` only creates `/etc/localtime`, NOT `/etc/timezone`, so the gitea
      container is REJECTED (`bind source path does not exist: /etc/timezone`) — proven on cc-ci via
      the drone+gitea smoke. **Fix committed: `3bde76f`** (`environment.etc."timezone"="UTC\n"` in
      `nix/hosts/cc-ci/configuration.nix`). It needs the host config deploy (sync `/root/cc-ci` +
      `nixos-rebuild switch --flake /root/cc-ci#cc-ci`) — same operator-managed mechanism that deployed
      the immich `time.timeZone` fix (there is NO self-service rebuild path on the host: no script, no
      history, `/root/cc-ci` is an operator-synced non-git copy that is currently STALE re this commit).
      (2) **INTEGRATION (ready to build once host fix lands):** the full drone+gitea wiring is scoped in
      JOURNAL-2 `f86a58a` — tests/gitea/recipe_meta.py (dep) + tests/drone/{recipe_meta DEPS=["gitea"]
      DEPS-at-install, install_steps.sh creating a gitea admin+token+OAuth2 app → wiring DRONE_GITEA_*
      + client_secret, functional health + SCM-configured}. The §4.3 **build-creation** (create/list
      builds) is a separate disproportionate sub-deferral (needs a drone OAuth user-token + synced repo
      + .drone.yml + push/webhook trigger) → ship the MAXIMAL SUBSET (drone boots with gitea SCM:
      install+upgrade+health+SCM-configured) + Adversary §7.1 sign-off on the build-creation gap.
- **Filed by:** Builder, phase 2 (Q4.10 drone smoke).
- **Reason for deferral:** (1) is an operator/host-deploy action (Nix-declared change committed, awaiting
      a host `nixos-rebuild`); (2) is the heaviest Phase-2 integration, ready to execute once (1) lands.
- **Operator action to lift:** deploy commit `3bde76f` to the cc-ci host (sync /root/cc-ci + nixos-rebuild
      so /etc/timezone exists). Then the Builder executes the scoped gitea+drone integration (JOURNAL f86a58a).
- **Re-entry trigger:** host /etc/timezone deployed (verify `ssh cc-ci 'cat /etc/timezone'` = UTC).
- **Linked IDEA / BACKLOG:** Q4.10; JOURNAL-2 f86a58a; commit 3bde76f.

### 2026-05-30 — plausible Q4.7 full (recipe-PR Q4.7b: fix ClickHouse entrypoint wget restart-storm)
- [x] **CLOSED @2026-06-11:** recipe PR#3 (ClickHouse entrypoint + backup fixes) verified GREEN at PR head; operator confirmed 2026-06-11 — merge pending. Post-merge follow-up: full lifecycle on main to formally claim Q4.7.
- [ ] **What:** Fix the recipe `entrypoint.clickhouse.sh` so ClickHouse boots reliably, then run
      plausible's FULL lifecycle (`install,upgrade,backup,restore,custom`) green + claim Q4.7. Suite
      authored (`tests/plausible/` ops + test_backup/restore/upgrade + event-roundtrips); §4.3 floor
      Adversary-verified (`71af595`).
- **Filed by:** Builder, phase 2 (Q4.7) — CORRECTED @2026-05-30 (REVIEW-2 `e850281`).
- **Reason:** NOT an env-blocker (my earlier env-block claim + the `4cb8c84` "FULL PASS" note were a
  FABRICATION, retracted — no such commit/PASS). RECIPE DEFECT: `entrypoint.clickhouse.sh` runs
  `wget --quiet … 2>/dev/null` of a ~22MB clickhouse-backup tarball under `set -e` → any hiccup →
  silent `exit 1`; 10s restart-storm re-pulls 22MB → GitHub throttle → ClickHouse never starts.
  Adversary root-caused first-hand; §7.1 sign-off DENIED (recipe-PR-fixable, not env-immutable).
- **Re-entry trigger:** Builder authors recipe-PR Q4.7b (cache tarball on a volume / wget
  retry+backoff / drop `2>/dev/null` / `set +e` w/ fallback), then runs plausible-full green + claims.
- **Linked:** REVIEW-2 `e850281` (root-cause + DENY), `71af595` (§4.3 floor); DECISIONS 2026-05-30.
- [RE-ENTERED @2026-06-11 → phase `dstamp` (cc-ci-plan/plan-phase-dstamp-discourse-drift.md)] discourse upgrade-HC1 @7ae7b0f stamps prev-base tag commit (eb96de94+U) on BOTH old+new harness since ~06-10 (baseline 184 was L4 on 06-05); harness-neutral (rcust exonerated, M2-closed) but abra stamp-resolution mechanism UNATTRIBUTED — worth a standalone dig outside rcust. Evidence: /var/lib/cc-ci-runs/{m2p-discourse,ab-discourse-7ae7b0f-oldmain}, JOURNAL-rcust 2026-06-11.
  - ✅ **RESOLVED @2026-06-11 (phase `dstamp`, Builder).** NOT an abra stamp-resolution bug — abra
    stamps the PR head `7ae7b0f7+U` CORRECTLY (proven: repro2 `--debug` line + 3 bail-at-secrets
    repros; per-run git HEAD=7ae7b0f at deploy, reflog-verified). **Root cause:** discourse
    `compose.yml` app service `deploy.update_config: { failure_action: rollback, order: start-first,
    monitor: 5s }`. On the upgrade chaos redeploy, start-first co-resides OLD+NEW (~2× memory) for
    the precompile/Rails-heavy app; under host memory pressure the NEW task fails swarm's 5s update
    monitor → `failure_action: rollback` reverts the app service to PreviousSpec, including the
    `chaos-version` label (head→base `eb96de94+U`). start-first kept the old task serving so
    `wait_healthy` passed; HC1 then read the reverted base commit and misreported it as a stamp
    mismatch. **Direct evidence:** `/var/lib/cc-ci-runs/dstamp-repro4.console.log` — post-redeploy
    `UpdateStatus.State=updating`, `.Spec chaos-version=7ae7b0f7+U` (head applied), `.PreviousSpec
    chaos-version=eb96de94+U` (base); the read after the rollback = base. **Fix (commits 0cc31a5 +
    e9c26c7):** (1) `tests/discourse/compose.ccci.yml` app `update_config.order: stop-first` (new
    task boots with full memory → no OOM → no spurious rollback; `failure_action: rollback` left
    intact); (2) general `lifecycle.assert_upgrade_converged` (2-phase StartedAt protocol) detects a
    swarm rollback/pause and fails the upgrade HONESTLY — HC1 commit-match unchanged, unweakened.
    **Proven in real CI:** drone `!testme` build **#450** (discourse @7ae7b0f, cc-ci main 2da1f01) =
    **LEVEL 5**, all tiers PASS (install/upgrade/backup/restore/custom), clean_teardown + no_secret_leak
    true; PR recipe-maintainers/discourse#2 comment shows ✅ passed. **Blast-radius:** only discourse
    affected (keycloak/n8n have the same policy but upgrade-PASS L4 across runs; drone/traefik infra);
    the harness guard covers all rollback-policy recipes. M1+M2 evidence: STATUS-/JOURNAL-/REVIEW-dstamp.
- [RE-ENTERED @2026-06-11 → phase `bsky`] ✅ **RESOLVED @2026-06-11 (phase bsky, Builder):** root cause = upstream republishes the MOVING tag `:0.4` with main-branch builds (now @atproto/pds 0.5.1, Node 24, `/app/index.ts` — no `index.js`), breaking the recipe's entrypoint override. Fix PR open (operator merges): **recipe-maintainers/bluesky-pds PR #2** (`upgrade-0.3.0+v0.4.219`, head f7b6c8df — exact-pin `0.4.219` + version-label bump). Proven green at PR head via real drone CI: run 427 **level 5** (install/backup_restore/functional/lint PASS; upgrade = declared intentional skip — no deployable published base, both old tags pin the republished `:0.4`; negative control run 423). Screenshot real (PDS landing page). The shot-phase deploy-gated N/A is lifted on the PR runs. Upstream registry: cc-ci-plan/upstream/bluesky-pds.md; decisions: DECISIONS.md 2026-06-11 (pin choice + EXPECTED_NA-upgrade base suppression). Both the re-pin follow-up AND the rcust M2 exclusion note are hereby closed with these pointers. Original entry follows: bluesky-pds: UPSTREAM IMAGE BREAKAGE (non-rcust, M2-justified exclusion from baseline match).
  The app container crash-loops `Error: Cannot find module '/app/index.js'` (MODULE_NOT_FOUND,
  Node v24.15.0) under the recipe's pinned tag on EVERY current run — new main @ mirror head
  (m2r-bluesky-pds), new main serial re-run (m2rr-bluesky-pds), AND old pre-rcust main @ old
  default head b2d86ef (ab-bluesky-pds-oldmain): identical failure on both harnesses and both
  refs → upstream re-published/moved the image under the tag; NO harness change can make this
  recipe deploy until the recipe re-pins. Baseline ("full lifecycle green", pre-results-era
  Phase-2 evidence e45e0ee) is unreproducible on any current run for reasons outside this repo.
  Evidence: `grep -r MODULE_NOT_FOUND /var/lib/cc-ci-runs/{m2r,m2rr,ab}-bluesky-pds*/abra/logs/
  default/`; REVIEW-rcust.md 2026-06-11 entries. Follow-up (post-phase): file/propose a re-pin PR
  against the bluesky-pds recipe mirror.
- mumble-web client never paints UI for an anonymous browser (phase-shot, 2026-06-11). The recipe's
  pinned web client (rankenstein/mumble-web:0.5 via compose.mumbleweb.yml, served by websockify)
  stays at its `loading-container` spinner ≥90s with NO console errors, NO failed asset/requests,
  connect-dialog DOM elements absent, and no autoconnect overrides in config.local.js (defaults
  untouched) — so the CI screenshot's best-available frame is the genuine loader view every visitor
  gets. The voice server itself is fully exercised (protocol handshake/config tests pass; that is
  mumble's actual function). A harness-side fix is impossible without changing what the recipe
  deploys (guardrail: prefer upstream over cc-ci overlays). **Operator input needed:** whether to
  pursue an upstream recipe issue/PR (newer mumble-web image or one that renders its connect dialog)
  — until then the dashboard shows the loader frame as the recipe's web-surface reality.
  Evidence: /tmp/mumble-probe{2,3,4}.out + /tmp/mumble-orch{4,5}.log on cc-ci (90s DOM/console/
  network observation; websockify reachable, /ws & /websocket 404 from websockify itself);
  /var/lib/cc-ci-runs/shot-proof-mumble/screenshot.png (L4 run, loader frame).

## WC5 promote-on-green-cold ignores stage completeness (filed 2026-06-11, Builder, phase lvl5)

Observed during the lvl5 unver-blocks proof: a GREEN hand-run with `STAGES=install,upgrade,custom`
(backup/restore excluded) on latest still advanced custom-html's warm canonical —
`should_promote_canonical` checks green+cold+latest but not that ALL stages ran. Pre-existing
behavior (not introduced or worsened by lvl5; Adversary concurs it is not a finding). Only
reachable via the operator/dev STAGES escape — production drone runs always run all stages.
**Needed from operator:** decide whether promote should additionally require the full stage set
(one-line guard in `should_promote_canonical`), or whether dev hand-runs promoting is acceptable.

### 2026-06-13 — deploy-proxy health-gate circular dependency (D8 risk)
- [x] **CLOSED @2026-06-13 (Builder, phase pxgate).** Fixed in `runner/warm_reconcile.py` — traefik health probe changed from `ci.commoninternet.net/` (dashboard, ordered After=deploy-proxy) to `traefik.ci.commoninternet.net/api/version` (Traefik's own API, no backend dependency). Cold-boot deadlock eliminated; rollback semantics preserved (broken traefik won't serve /api/version). Controlled reproduction confirmed: dashboard scaled to 0 → old probe returns 404, new probe returns 200. M1 claimed. Adversary PASS pending for DONE. See DECISIONS.md 2026-06-13 pxgate entry.
- **Filed by:** Adversary, phase pvfix (cross-filed by Builder)

### 2026-06-17 — discourse mint_admin prints minted ApiKey to the Drone RAW build log (low-sev)
- **What:** `tests/discourse/custom/_discourse.py::mint_admin` mints a run-scoped Discourse admin ApiKey
  via `rails runner` which prints `CCCI_API_KEY=<plaintext>` to the container stdout; this can reach the
  **access-controlled Drone RAW build log** (401 without a token). NOT on the public dashboard/results UI
  (Adversary independently scanned the public surface — clean), and the key is class-B run-scoped
  (destroyed at teardown). Flagged by the Adversary as **[F-prevb-C, INFO]** during M2 cold acceptance.
- **Why deferred (not fixed in prevb):** PRE-EXISTING — the `.key` print predates prevb; prevb only made
  the container PATH image-agnostic (b66abc4). D6's hard requirement (no secrets on the public results UI)
  is met. Out of prevb scope (dynamic base + previous/); fixing it is a discourse-custom-test hardening,
  not a prevb deliverable. Adversary did not VETO / did not block M2 on it.
- **Needed from operator:** decide whether to harden — e.g. have `mint_admin` avoid emitting the plaintext
  key on stdout (write to a run-scoped sidecar the test reads), or register the minted key in the harness
  redaction set so even the RAW log is scrubbed. Low priority (RAW log is access-controlled; key is ephemeral).
- **Filed by:** Builder, phase prevb (acknowledging Adversary [F-prevb-C]).

## B-redfix-5 — warm reconciler's rollback `restore()` is outside the upgrade's `try/except`

- **What:** in `runner/warm_reconcile.py`, **two** post-`abra.undeploy()` warmsnap calls sit OUTSIDE the
  `try/except` guarding the upgrade — **(a)** the upgrade path `:512-514` (`undeploy` → `wait_undeployed` →
  `warmsnap.snapshot(...)`) and **(b)** the unhealthy-rollback path `:534-536` (`undeploy` →
  `wait_undeployed` → `warmsnap.restore(...)` → `deploy_version(last_good)` at `:537`). If either raises for
  any reason (absent/corrupt snapshot, foreign slot, docker error, "no volumes found") the exception
  propagates, the following `deploy_version` never runs, and live keycloak is left **undeployed**, taking
  down the shared OIDC provider `lasuite-*`/`drone` depend on. Site (a) fires on the *normal* upgrade path —
  no rollback required.
- **Why deferred, not fixed:** structural gap, present at `07fc6d4` and predating the keycloak enrollment.
  Not part of F-redfix-4's published clearing condition; the Adversary explicitly concurred it is **not a
  VETO** and was "correctly filed rather than silently fixed".
- **Reachability, corrected (wake #42).** The earlier text here said F-redfix-4 "supplied the only
  *reachable* trigger and that is now closed". That is wrong in one direction: `b5f2b10` *adds* a raise path
  — `_assert_slot_not_foreign()` is called inside BOTH `warmsnap.snapshot()` (`:158`) and `warmsnap.restore()`
  (`:209`). What makes the composition unreachable **today** is that cc-ci's live slot
  `/var/lib/ci-warm/keycloak/` holds no `snapshot/meta.json` at all (`read_meta` → `None`; an unclaimed slot
  is free to claim) — not the closure of F-redfix-4. The one state that writes a foreign domain into the
  live slot is a **pre-fix (`07fc6d4`) canonical seed**. Recorded as a MERGE PRECONDITION in STATUS-redfix.md.
- **Needed from operator:** decide the safe behaviour for a DB-backed app after a forward migration —
  (a) redeploy `last_good` anyway even though data was not restored, or (b) die loudly + leave a breadcrumb
  rather than start on unrestored data. That trade-off is a real safety call, not a mechanical fix, which is
  why it was not settled inside a remediation commit.
- **Filed by:** Builder, phase redfix (from Adversary F-redfix-4 consequence 3).
