# STATUS — phase cfold (custom-folder collapse)

**Phase:** cfold — collapse `functional/`+`playwright/` into `custom/`
**Builder:** autonomic-bot
**Updated:** 2026-06-13

---

## M1 — PASS

Gate result: `REVIEW-cfold.md` 2026-06-12T16:20Z -> **M1 PASS**

Inputs for verification:
- Implementation commit: `44e0242` (`feat(cfold): canonicalize custom test layout`)

Completed in this checkpoint:
- discovery.py: `custom/` canonical + deprecated aliases with warnings
- `git mv` all 64 custom tests (60 functional + 4 playwright) across 20 recipes
- helper modules moved alongside their tests into `custom/`
- sys.path refs updated in mailu lifecycle overlays
- docs updated (`README.md`, `recipe-customization.md`, `testing.md`, `enroll-recipe.md`)
- unit tests updated (`test_discovery.py`, `test_discovery_phase2.py`, `test_manifest.py`)
- manifest.py now reports canonical `custom` counts

WHAT:
- M1 implementation is complete: custom-test discovery is canonicalized to `custom/`, deprecated
  aliases warn loudly instead of silently dropping coverage, all cc-ci custom tests/helpers moved to
  `tests/<recipe>/custom/`, manifest counts are canonicalized, and the placement-rule docs/unit tests
  were updated.

HOW:
- `git ls-files "tests/*/custom/test_*.py" | wc -l`
- `git ls-files "tests/*/functional/*" "tests/*/playwright/*"`
- `for recipe in bluesky-pds cryptpad custom-html custom-html-tiny discourse drone ghost hedgedoc immich keycloak lasuite-docs lasuite-drive lasuite-meet mailu matrix-synapse mattermost-lts mumble n8n plausible uptime-kuma; do count=$(git ls-files "tests/$recipe/custom/test_*.py" | wc -l); printf "%s %s\n" "$recipe" "$count"; done`
- `nix shell nixpkgs#python311Packages.pytest -c pytest tests/unit/test_discovery.py tests/unit/test_discovery_phase2.py tests/unit/test_manifest.py -q`

EXPECTED:
- Total canonical custom tests: `64`
- Old tracked trees: no output for `functional/*` or `playwright/*`
- Per-recipe counts exactly match the baseline table below
- Focused unit suite: `18 passed`

WHERE:
- Discovery + alias warnings: `runner/harness/discovery.py`
- Canonical manifest counts: `runner/harness/manifest.py`
- Migrated custom tests/helpers: `tests/*/custom/`
- Focused unit coverage: `tests/unit/test_discovery.py`, `tests/unit/test_discovery_phase2.py`, `tests/unit/test_manifest.py`
- Placement-rule docs: `docs/recipe-customization.md`, `docs/testing.md`, `docs/enroll-recipe.md`, `README.md`

Adversary verdict:
- `machine-docs/REVIEW-cfold.md` lines 52-77
- PASS facts include: 64 canonical custom tests, zero old tracked custom trees, focused unit suite `18 passed`, deprecated-alias warning probe green, normalized `(recipe, filename)` coverage set preserved exactly (`missing []`, `extra []`).

---

## DONE

Phase result: `REVIEW-cfold.md` 2026-06-13T04:11:00Z -> **M2 PASS**

Done criteria satisfied:
- M1 PASS at `REVIEW-cfold.md` 2026-06-12T16:20Z
- M2 PASS at `REVIEW-cfold.md` 2026-06-13T04:11:00Z
- Full real-CI `!testme` sweep green across all 20 enrolled recipes with canonical `custom/` coverage intact
- Zero leaked live `-pr` stacks after the sweep

Final proof points:
- Ghost blocker closure: build `585` on PR #5 ref `d42d0f7c7cf9` -> `level 5`, all stages pass, custom JUnit `4`, upgrade JUnit `2`
- Same-code-path Ghost repro after the fix: `/var/lib/cc-ci-runs/ghost-repro-cfold-3/results.json` -> `install=pass`, `upgrade=pass`
- cfold implementation commit: `44e0242`
- Ghost closure fix commit: `d44f799`

---

## M2 — PASS

Gate: M2 — CLAIMED, awaiting Adversary

Current work item:
- full real-CI `!testme` sweep is now green across the enrolled recipe set, including the formerly-blocking
  Ghost PR head
- Ghost's upgrade blocker was fixed in cc-ci via the `tests/ghost/compose.ccci.yml` overlay: the app now
  waits in its entrypoint for the replacement DB socket before starting during the base->head crossover,
  while preserving Ghost's normal `/abra-entrypoint.sh node current/index.js` boot path
- bridge replay-guard fix remains live on `cc-ci` (image tag `eb32876581d9`); the Ghost duplicate-trigger
  side issue is separately closed and no longer affects the cfold sweep result

### M2 baseline matrix (built from live PR heads + fresh post-cfold evidence)

| Recipe | PR / ref | Expected level | Custom tests | Fresh evidence |
|---|---|---:|---:|---|
| bluesky-pds | PR #2 `f7b6c8df` | 5 | 4 | build `556` -> L5 |
| cryptpad | PR #5 `9c18c176` | 5 | 4 | build `554` -> L5 |
| custom-html | PR #2 `db9a9502` | 5 | 4 | build `541` -> L5 |
| custom-html-tiny | PR #7 `526502ba` | 5 | 1 | build `510` -> L5 |
| discourse | PR #2 `b7d8a244` | 5 | 3 | build `521` -> L5 |
| drone | PR #1 `049438e1` | 5 | 1 | build `506` -> L5 |
| ghost | PR #5 `d42d0f7c` | 5 | 4 | build `585` -> L5 |
| hedgedoc | PR #1 `441c411c` | 5 | 2 | build `555` -> L5 |
| immich | PR #2 `17f1649c` | 5 | 3 | build `522` -> L5 |
| keycloak | PR #3 `bfe0d16f` | 5 | 3 | build `553` -> L5 |
| lasuite-docs | PR #5 `8a06cfc2` | 5 | 5 | build `523` -> L5 |
| lasuite-drive | PR #2 `6771622b` | 5 | 3 | build `524` -> L5 |
| lasuite-meet | PR #6 `05cdafb5` | 5 | 3 | build `525` -> L5 |
| mailu | PR #4 `682ccaaa` | 5 | 3 | build `526` -> L5 |
| matrix-synapse | PR #2 `72f0176a` | 5 | 3 | build `527` -> L5 |
| mattermost-lts | PR #2 `966c6d61` | 5 | 3 | build `529` -> L5 |
| mumble | PR #1 `2b50b2f7` | 5 | 5 | build `558` -> L5 |
| n8n | PR #5 `989c44b3` | 5 | 4 | build `528` -> L5 |
| plausible | PR #3 `709a294d` | 5 | 2 | build `530` -> L5 |
| uptime-kuma | PR #3 `b0ce7942` | 5 | 4 | build `531` -> L5 |

### Ghost closure

`ghost` was the final M2 blocker and is now green on the real `!testme` path.

- Historical failing same-ref comparison remains the strongest pre-fix proof:
  - build `559` on `d42d0f7c7cf9` -> L1; install/backup/restore/custom/lint pass, upgrade fail
  - build `585` on `d42d0f7c7cf9` -> L5; install/upgrade/backup/restore/custom/lint pass
- Root cause of the upgrade failure: during the base->head crossover, Ghost's app task started before the
  replacement DB service was accepting connections, so the new task exited on `ENOTFOUND`/`ECONNREFUSED`
  against `${STACK_NAME}_db` and swarm paused the update before the head spec could settle.
- Fix landed in `cc-ci` commit `d44f799` (`fix(cfold): wait for ghost db in entrypoint`):
  `tests/ghost/compose.ccci.yml` now keeps the existing 15m app/db healthcheck grace and wraps the app
  `entrypoint` with a tiny TCP wait that execs the normal `/abra-entrypoint.sh node current/index.js`
  path only after the DB socket is reachable.
- Focused same-code-path repro after the fix:
  - `/var/lib/cc-ci-runs/ghost-repro-cfold-3/results.json` -> `install=pass`, `upgrade=pass`
  - log `/root/ghost-repro-cfold-3.log` includes
    `upgrade-converged: ghos-ce3c44_ci_commoninternet_net_app swarm UpdateStatus=completed`
    and `upgrade->PR-head: head_ref=d42d0f7c chaos-version=d42d0f7c+U version=1.2.0+6.21.2-alpine->1.4.0+6.44.0-alpine`

### Fresh Adversary state

- `REVIEW-cfold.md` 2026-06-12T23:45:11Z: cold Ghost follow-up audit only, no new finding, no M2 claim pending.
- `REVIEW-cfold.md` 2026-06-13T00:23:55Z: cold M2 artifact/teardown audit only, no new finding, no M2
  claim pending; zero leaked live `-pr` stacks confirmed.

WHAT:
- M2 is now met: the full real-CI `!testme` recipe sweep is green, the formerly-blocking Ghost recipe is
  green again on the same PR head that previously failed, custom-tier coverage remains intact, and there
  are zero leaked live `-pr` stacks.

HOW:
- `ssh cc-ci 'tok=$(cat /run/secrets/bridge_drone_token); curl -fsS -H "Authorization: Bearer $tok" https://drone.ci.commoninternet.net/api/repos/recipe-maintainers/cc-ci/builds/585 | jq -r "[.number,.status,.after,.params.RECIPE,.params.PR,.params.REF] | @tsv"'`
- `ssh cc-ci 'jq -r "{level,recipe,ref,results,stages:(.stages|map({name,status}))}" /var/lib/cc-ci-runs/585/results.json'`
- `ssh cc-ci 'printf "ghost custom junit="; ls /var/lib/cc-ci-runs/585/junit/custom__cc-ci__*.xml | wc -l; printf " ghost upgrade junit="; ls /var/lib/cc-ci-runs/585/junit/upgrade*.xml | wc -l'`
- `ssh cc-ci 'printf "live_pr_apps="; docker stack ls --format "{{.Name}}" | grep -c -- "-pr" || true'`
- `ssh cc-ci 'jq -r ".results, .stages" /var/lib/cc-ci-runs/ghost-repro-cfold-3/results.json'`

EXPECTED:
- Drone build query returns build `585`, status `success`, `after=d44f799de945d0775933aad58726d46509154a64`, recipe `ghost`, PR `5`, ref `d42d0f7c7cf9946077a583ffa3f7c96abfe94a77`
- `results.json` for build `585` shows `level: 5` and `results.install=pass`, `results.upgrade=pass`, `results.backup=pass`, `results.restore=pass`, `results.custom=pass`; stages include `install`, `upgrade`, `backup`, `restore`, `custom`, `lint` all `pass`
- JUnit counts for build `585`: `ghost custom junit=4`, `ghost upgrade junit=2`
- Teardown check returns `live_pr_apps=0`
- Focused repro `ghost-repro-cfold-3` shows `install=pass`, `upgrade=pass`

WHERE:
- Fix commit: `d44f799` (`fix(cfold): wait for ghost db in entrypoint`)
- Ghost overlay: `tests/ghost/compose.ccci.yml`
- Real CI proof: `/var/lib/cc-ci-runs/585/results.json`, `/var/lib/cc-ci-runs/585/junit/`
- Focused repro proof: `/var/lib/cc-ci-runs/ghost-repro-cfold-3/results.json`, `/root/ghost-repro-cfold-3.log`

---

## Baseline (pre-cfold) — custom test count per recipe

| Recipe | Count |
|--------|-------|
| bluesky-pds | 4 |
| cryptpad | 4 |
| custom-html | 4 |
| custom-html-tiny | 1 |
| discourse | 3 |
| drone | 1 |
| ghost | 4 |
| hedgedoc | 2 |
| immich | 3 |
| keycloak | 3 |
| lasuite-docs | 5 |
| lasuite-drive | 3 |
| lasuite-meet | 3 |
| mailu | 3 |
| matrix-synapse | 3 |
| mattermost-lts | 3 |
| mumble | 5 |
| n8n | 4 |
| plausible | 2 |
| uptime-kuma | 4 |
| **TOTAL** | **64** |
