26 KiB
STATUS — Phase 2 (per-recipe test authoring)
Phase plan (SSOT): /srv/cc-ci/cc-ci-plan/plan-phase2-recipe-tests.md
Loop state for THIS phase: STATUS-2 / BACKLOG-2 / REVIEW-2 / JOURNAL-2 (DECISIONS.md shared).
Phase 1/1b/1c/1d/1e STATUS/BACKLOG/REVIEW files are HISTORY (all DONE) — not this phase's state.
Phase
Phase 2 authors per-recipe test content on top of the corrected Phase 1/1d/1e shared harness.
Per the plan, for every maintained Co-op Cloud recipe (§5 target set), the cc-ci tests/<recipe>/
tree must carry:
- Phase-1d/1e lifecycle overlays (assertion-only, additive) —
test_install.py,test_upgrade.py,test_backup.py,test_restore.py+ops.pypre-op seeds. - Parity-ported tests from
references/recipe-maintainer/recipe-info/<recipe>/tests/*.py, one-to-one (P2), with aPARITY.mdmapping table. - ≥2 NEW recipe-specific functional tests (P3) — characteristic behavior, not just
status==200. - Real backup data-integrity (P4): seed → backup → mutate → restore → assert seeded data survived.
- Dependency resolution (P5): recipes that need other apps (SSO providers, DBs) deploy them in-run.
- Playwright (P6) where the app's core UX is a UI flow.
- Docs (P8):
docs/enroll-recipe.mdupdated with the per-recipe test contract + worked example.
Definition of Done (Phase 2) — P1–P8, each Adversary cold-verified in REVIEW-2
- P1 — Coverage. Every recipe in §5 target set has a
tests/<recipe>/suite enrolled and a full green!testmerun (install + upgrade + backup-restore). - P2 — Parity port. Every
recipe-info/<recipe>/tests/*.pyhas a comparable cc-ci test;tests/<recipe>/PARITY.mdrecords the mapping; non-ports documented in DECISIONS.md. - P3 — Recipe-specific depth. Each recipe has ≥2 new functional tests beyond parity (characteristic behavior, real assertions on app state/responses).
- P4 — Backup data-integrity is real. Seed → backup → mutate → restore → assert seeded data survived (recipe-aware, not health-only). Pattern already proven in Phase 1e on custom-html.
- P5 — Dependencies handled. Recipes with deps declare them; harness deploys deps within the
run (respecting
MAX_TESTS); SSO setup runs automatically. - P6 — Browser flows where they matter (D3). UI-centric recipes have a Playwright test of the core flow (login, create-an-object, etc.).
- P7 — No weakened tests, no corners cut. Every assertion is real; nothing skip/xfail'd, mocked, or health-only stand-in. Any "untestable" claim is a true env-level blocker with Adversary sign-off.
- P8 — Docs.
docs/enroll-recipe.mdupdated with the per-recipe test contract (§4.1) and a worked example; a new engineer can add a recipe's full suite from the docs.
Milestones (plan §6)
- Q0 — Harness additions (HTTP/convergence, OIDC-flow, dep resolver, backup data-integrity, TTY
abra). Reference recipe (custom-html) uses them for full parity+specific suite, green via
!testme. - Q1 — Pattern proof (custom-html + n8n): full parity + ≥2 specific + real backup data-integrity.
- Q2 — SSO providers (keycloak + authentik); reusable SSO-setup/OIDC-flow harness e2e.
- Q3 — SSO-dependent suite (lasuite-docs, lasuite-drive, lasuite-meet, cryptpad, immich); deps auto-deployed, SSO setup automated, parity + specific.
- Q4 — Remaining recipes (matrix-synapse, mumble, bluesky-pds, ghost, mattermost-lts, discourse, plausible, uptime-kuma, mailu, drone).
- Q5 — Completeness + docs; flip
## DONE.
In flight
Q3.2 lasuite-drive — Adversary FAILed (F2-12); fix e1147b5 validating (NOT re-claimed yet).
The Adversary's cold re-run hit the upgrade tier failing: abra's converge monitor FATAs while the
NEW collabora 25.04.9.4.1 healthcheck is still in start_period (my WOPI pre-gate fixed the OLD
collabora; the new one's convergence was still abra-impatient → flaky: 3× green for me, 1× fail cold).
Fix e1147b5: upgrade chaos redeploy uses abra … -c (no impatient converge monitor) + perform_upgrade
OWNS a stricter convergence wait (services N/N + app health + collabora WOPI READY_PROBE) bounded by
DEPLOY_TIMEOUT. Validating across multiple full runs (/root/ccci-drive-f212-v1.log …) before re-claim.
cryptpad F2-9 — RESOLVED (test landed 05d0dc1, 3/3 green; Adversary to close). New
tests/cryptpad/playwright/test_pad_content_roundtrip.py does the §4.3 create-pad → type → FRESH
browser context → read-back (proves E2E-encrypted server persistence). Full harness suite run pending.
Working next unblocked items meanwhile.
Q3 + Q4 — recipe enrollment sprint. After capacity unblock + Adversary checkpoint, landed:
- Q3.1 lasuite-docs partial (parity + 2 specific + Q2.4 test_oidc_with_keycloak); deeper OIDC ports deferred in DEFERRED.md.
- Q3.4 cryptpad partial (parity + 2 specific); create-pad deferred F2-9 conditional (must lift before Phase-2 DONE).
- Q4.1 matrix-synapse FULL (parity-aligned + 3 specific incl. §4.3 register-and-message).
- Q4.3 bluesky-pds FULL (4 functional incl. §4.3 account+post round-trip via goat CLI; F2-8 closed).
- Q4.4 ghost FULL (parity + 3 specific; create-post deferred in DEFERRED.md).
- Q4.8 uptime-kuma FULL (parity + 2 specific; create-monitor deferred in DEFERRED.md).
Harness change: lifecycle.deploy_app + run_recipe_ci.py + deps.py now thread
recipe_meta.DEPLOY_TIMEOUT into abra.deploy(timeout=...) so heavy-recipe Python subprocess
timeout matches the recipe's internal TIMEOUT.
DEFERRED.md (machine-docs/) — new orchestrator-canonical deferral registry; 9 entries open.
Remaining substantial: Q3.2 lasuite-drive (needs mirror), Q3.3 lasuite-meet (mirrored), Q3.5 immich (needs mirror), Q4.2/Q4.5-7/Q4.9-10 (mostly need mirror). The mirror-and-enroll path is established (recipe-create-pr skill); pausing this sprint for Adversary cold-verify.
Adversary findings — Builder response
F2-11 — FIXED, awaiting Adversary re-verify (commit: git log --oneline | grep 'F2-11').
SSO-dep "deps-not-ready"
SKIP no longer yields a GREEN !testme.
- WHAT: when a recipe declares
DEPSandsetup_custom_testsfails (deps not ready) so its@requires_deps(SSO/OIDC) tests SKIP, the run now reports FAIL (overall=1), not green — while generic-tier failure-isolation is preserved (install/upgrade/backup/restore results stand). - WHERE (code):
tests/conftest.py::pytest_collection_modifyitems— now counts the requires_deps tests it skips and appends the count to$CCCI_DEPS_SKIP_REPORT.runner/run_recipe_ci.py— setsCCCI_DEPS_SKIP_REPORT(run-scoped temp, neardepsfile); after teardown sums the count intorequires_deps_skipped; RUN SUMMARY annotates the custom tier (custom: pass (N requires_deps SKIPPED ... SSO UNVERIFIED)); new pure predicatesso_dep_unverified(declared, deps_ready, requires_deps_skipped)flipsoverall=1.tests/unit/test_f211_sso_skip.py— 7 new unit tests.
- HOW to verify (both deploy-free, rate-limit-independent):
ssh cc-ci 'cd /root/cc-ci && cc-ci-run -m pytest tests/unit -q'→ EXPECTED: 35 passed (28 prior + 7 F2-11).- Cold real-test signal proof:
ssh cc-ci 'cd /root/cc-ci && rm -f /tmp/f211-skip.txt && CCCI_DEPS_READY=0 \ CCCI_DEPS_NOT_READY_REASON=boom CCCI_DEPS_SKIP_REPORT=/tmp/f211-skip.txt \ cc-ci-run -m pytest tests/lasuite-docs/functional/test_oidc_with_keycloak.py -rs; \ cat /tmp/f211-skip.txt'→ EXPECTED:1 skipped, pytest exit 0 (the hazard), and/tmp/f211-skip.txt==1. Since lasuite-docs declaresDEPS=["keycloak"], the orchestrator computessso_dep_unverified(["keycloak"], False, 1)=True→overall=1.
- NOT verified by a live run yet: full e2e (real deploy with forced setup_custom_tests failure →
observe
overall=1) is deferred until the Docker Hub rate limit (## Blocked) lifts. The two proofs above cover the predicate, the conftest signal on real files, and the count flow; only the straight-line read→sum→predicate→overall wiring is unexercised by a live deploy.
Gate
Gate: Q3.2 lasuite-drive — CLAIMED @2026-05-29, awaiting Adversary.
WHAT. lasuite-drive (the heaviest Phase-2 stack: 12 services incl. collabora + onlyoffice + minio/S3 + postgres, OIDC-dependent) now runs its full lifecycle GREEN, repeatably — install + upgrade (prev→PR-head chaos crossover) + backup + restore + custom (health + MinIO round-trip + OIDC password-grant), via two fixes:
- Install-time OIDC wiring (commit
a151489) — the orchestrator provisions the per-run realm on the live-warm keycloak BEFORE the singleabra app deploy, andtests/lasuite-drive/install_steps.shwrites the OIDC env + client secret into that one deploy. This eliminates the flaky post-deploy--force --chaos12-service reconverge the oldsetup_custom_tests.shdid (collabora WOPI-discovery race; JOURNAL Step 0). New per-recipeOIDC_AT_INSTALLmeta flag + reusable_provision_deps()helper; legacy post-deploy path unchanged for all other dep recipes (gated onnot oidc_at_install). - collabora-ready upgrade gate + DEPLOY_TIMEOUT plumbing (commit
4b38b66) —ops.py::pre_upgradewaits for collabora WOPI discovery (/hosting/discoveryoncollabora-<domain>) → 200 BEFORE the chaos redeploy, so it no longer SIGTERMs a still-booting collabora (which caused exit 70 / "FATA deploy failed" in run 1);DEPLOY_TIMEOUTnow threads to the upgradechaos_redeploy(was abra's 900s default vs the .env internal TIMEOUT 1500s).
HOW (Adversary, cold, on cc-ci):
ssh cc-ci 'cd /root/<your-clone> && git pull && RECIPE=lasuite-drive PR=0 cc-ci-run runner/run_recipe_ci.py'
EXPECTED:
- RUN SUMMARY:
deploy-count = 1 (expect 1);install/upgrade/backup/restore/customallpass. tests/lasuite-drive/functional/test_oidc_with_keycloak.py::test_oidc_password_grant_against_dep_keycloakPASSED (NOT skipped) — real password-grant JWT against a per-run realm on warm keycloak.test_minio_storagePASSED (real S3 upload→list→cat readback round-trip inside the minio container).- Data-integrity:
test_upgrade_preserves_data(ci_marker survives prev→PR-head chaos crossover) + backup/restore ci_marker survive. - Log shows
install-time OIDC: deps provisioned+install_steps: OIDC env wired(no post-deploy reconverge) andpre_upgrade: collabora WOPI discovery ready (200)before the upgrade redeploy. - Clean teardown: post-run
docker stack ls | grep lasuanddocker volume ls | grep lasuboth empty.
WHERE. Commits a151489 (Part A) + 4b38b66 (upgrade gate). Files: runner/run_recipe_ci.py
(_provision_deps, OIDC_AT_INSTALL branch, _perform_op timeout), runner/harness/lifecycle.py
(chaos_redeploy timeout), runner/harness/generic.py (perform_upgrade timeout),
tests/lasuite-drive/{install_steps.sh,setup_custom_tests.sh,ops.py,recipe_meta.py}.
3× repeat-green (flakiness gone, not absent-once): /root/ccci-drive-q32a-r2.log,
…-r3.log, …-r4.log — each full-suite green, deploy-count=1, OIDC PASSED, clean teardown
(run 1 …-r1.log showed the upgrade-tier failure that 4b38b66 fixed). Step-0 root-cause logs in
JOURNAL-2 (2026-05-29). DEFERRED.md disk-blocker entry CLOSED (host grew to 64G); flaky-OIDC
BACKLOG-2 Q3.2a item now resolved.
Gate: Q2 — Adversary PASS @2026-05-28 (REVIEW-2 ## Q2 — PASS @2026-05-28 (re-verify after F2-5 fix + F2-6 collateral resolution); cold e2e on /root/adv-verify HEAD 874bfbb:
deploy-count=2, all 5 assertions PASS, DEPS teardown clean, post-run docker stack/volume/secret
with 'keyc|lasuite' filter all empty; NO VETO). F2-5 + F2-6 CLOSED; F2-7 stands as open scope
(authentik backend in harness.sso when Q2.2 enrolls). Builder may advance to Q3 — already in
flight (Q3.1 partial @ 874bfbb, Q5.1 docs @ b2151af).
Acceptance per plan §6 Q2: "a dependent recipe deploys its provider + runs an OIDC login test in one run." Proven cold:
Objective evidence pointers (Q2):
-
Q2.1 keycloak parity + 2 NEW specific tests — commit
d5f5e86:tests/keycloak/functional/test_health_check.py— parity port.tests/keycloak/functional/test_password_grant_token.py— password grant, JWT decoded, claims (iss/azp/typ/exp/iat) validated.tests/keycloak/functional/test_create_client_and_use.py— admin-API client CRUD + client_credentials grant + JWT azp/iss validation + idempotent cleanup.oidc_integration.pyparity deferred to Q3 (cross-recipe; see PARITY.md note).- Bumped DEPLOY_TIMEOUT + HTTP_TIMEOUT to 900s.
- Cold e2e (log
/root/ccci-q2-keycloak-r3.log): all 5 stages PASS, deploy-count=1,head_ref=666649a6 == chaos-version=666649a6, version10.7.0+26.6.1 → 10.7.1+26.6.2.
-
Q2.3 dep resolver + SSO-setup harness primitives — commit
4d6b040:runner/harness/deps.py— declared_deps + dep_domain + deploy_deps + teardown_deps + JSON run state. Subsumes Q0.4 (dep resolver).runner/harness/sso.py— setup_keycloak_realm + oidc_password_grant + assert_discovery_endpoint. Reusable by every SSO-dependent recipe (Q3 will exercise).runner/run_recipe_ci.py— wired in dep deploy BEFORE recipe-under-test, dep teardown AFTER in finally (reverse order). DG4.1 expected count = 1 + len(deps).tests/conftest.py—deps_appsfixture exposes dep domains to dependent tests.- 7 new unit tests in
tests/unit/test_deps.py; 28/28 unit tests PASS cold.
-
F2-5 fix — dep teardown verify=True — commit
c6e94af, log/root/ccci-f25-verify.log:runner/harness/deps.py::teardown_depsnow useslifecycle.teardown_app(..., verify=True)so residuals raiseTeardownError. Errors are logged per-dep but we continue to other deps; a combinedTeardownErroris raised after all attempts.runner/run_recipe_ci.pycatches the depTeardownErrorin finally, surfaces viadep_teardown_errorin the run summary + non-zero exit code.- Cold-verified: lasuite-docs+keycloak dep e2e PASSED clean (3 custom + 2 lifecycle install =
5 PASS); post-run cc-ci state has NO leftover keycloak (
docker stack ls | grep keyc→ empty;docker volume ls | grep keyc→ empty;docker secret ls | grep keyc→ empty). - deploy-count=2, expected 2.
-
Q2.4 acceptance (the gate) — commit
9e88741, log/root/ccci-q24-lasuite-keycloak.log:tests/lasuite-docs/recipe_meta.pydeclaresDEPS = ["keycloak"].tests/lasuite-docs/functional/test_oidc_with_keycloak.py:- Asserts
deps_apps["keycloak"]is the per-run dep domain. - Calls
harness.sso.setup_keycloak_realm→ realm/client/user. - GETs OIDC discovery; asserts
issuer == https://<kc>/realms/lasuite-docs. - Performs password grant → JWT; asserts iss/azp/typ/exp claims.
- Asserts
- Cold-run output:
===== DEPS: ['keycloak'] ===== dep: deploying keycloak -> keyc-c12afe.ci.commoninternet.net dep: keycloak ready @ keyc-c12afe.ci.commoninternet.net ===== TIER: install ===== 2 PASS (generic + cc-ci overlay) ===== TIER: custom ===== 1 PASS (test_oidc_password_grant_against_dep_keycloak) ===== DEPS teardown ===== ===== RUN SUMMARY ===== deploy-count = 2 (expect 2)
-
F2-3 systemic fix — commit
47f7cb4:runner/harness/browser.py::goto_with_retrycentralizes the F2-3 try/except PlaywrightError pattern; applied to all install overlays using page.goto (custom-html, n8n, keycloak, cryptpad, lasuite-docs) + the custom-html playwright/test_browser_smoke. Cold e2e (custom-html, log/root/ccci-q2-customhtml-r2.log): all 5 stages PASS, deploy-count=1, HC1 non-vacuous.
Reference command for Adversary (cold, on cc-ci):
ssh cc-ci 'cd /root/<your-clone> && \
cc-ci-run -m pytest tests/unit -v && \
RECIPE=keycloak cc-ci-run runner/run_recipe_ci.py && \
RECIPE=lasuite-docs STAGES=install,custom cc-ci-run runner/run_recipe_ci.py'
Gate: Q1 — Adversary PASS @2026-05-28 (REVIEW-2 ## Q1 — PASS @2026-05-28 (re-verify after F2-3 + F2-4 fixes); cold e2e on /root/adv-verify HEAD fc89552 → all 5 stages PASS,
deploy-count=1, HC1 non-vacuous; F2-3 + F2-4 CLOSED; NO VETO). Builder may advance to Q2.
Objective evidence pointers (Q1):
- custom-html (Q1.1) — already cold-verified in Q0 PASS. Same evidence stands: full e2e green, HC1 non-vacuous, deploy-count=1; PARITY.md + functional/ + playwright/ in place.
- n8n (Q1.2) — full e2e on cc-ci (log
/root/ccci-q1-n8n-r3.log):- HC1 PR-head proof:
head_ref=63dd3e0f == chaos-version=63dd3e0f, version3.1.0+2.9.4 → 3.2.0+2.20.6. - Deploy-count = 1 (DG4.1 holds).
- Lifecycle tier results (generic + cc-ci overlay both PASS at each stage):
- install: generic
test_servingPASS + cc-citest_serving_and_editorPASS (the robust Playwright poll handles n8n's /healthz-200-before-/-route-registered window). - upgrade: generic
test_upgrade_reconvergesPASS + cc-citest_upgrade_preserves_dataPASS (markerupgrade-surviveswritten into /home/node/.n8n byops.pre_upgradesurvived the chaos redeploy of PR-head). - backup: generic
test_backup_artifactPASS + cc-citest_backup_captures_statePASS (markeroriginalfromops.pre_backupcaptured byabra app backup create). - restore: generic
test_restore_healthyPASS + cc-citest_restore_returns_statePASS (marker mutated tomutatedbyops.pre_restore, restored tooriginal— real backup data-integrity).
- install: generic
- Custom tier results (4 PASS — log
/root/ccci-q1-n8n-r4.logpost-F2-4/F2-3 fix):tests/n8n/functional/test_health_check.py::test_n8n_returns_200— parity port (HTTP 200 from/), withSOURCE: recipe-info/n8n/tests/health_check.pycomment.tests/n8n/functional/test_workflow_roundtrip.py::test_workflow_create_and_read_back— plan §4.3 prescribed create+read-back: owner setup → POST /rest/workflows → GET /rest/workflows/; assert id/name/nodes round-trip. (F2-4 fix.)tests/n8n/functional/test_rest_settings.py::test_rest_settings_returns_json_with_known_keys— polls/rest/settingsuntil content-type isapplication/json(rejecting the "n8n is starting up" placeholder HTML), then asserts known public-settings keys (userManagement/defaultLocale/authCookie) in thedataenvelope.tests/n8n/functional/test_login_state.py::test_login_endpoint_returns_json— polls/rest/loginuntil content-type isapplication/json, proves auth subsystem initialized.
- PARITY.md complete:
tests/n8n/PARITY.md— parity row forhealth_check.py, rationale for the 2 recipe-specific tests, data-integrity + playwright sections.
- HC1 PR-head proof:
- Q1 has no Adversary findings yet. No tests skipped/weakened; rejecting-the-placeholder pattern in the new functional tests is non-vacuous (a stuck-booting n8n that only serves the placeholder fails the test).
Reference command for Adversary (cold, on cc-ci):
ssh cc-ci 'cd /root/<your-clone> && RECIPE=n8n cc-ci-run runner/run_recipe_ci.py'
Gate: Q0 — Adversary PASS @2026-05-28 (REVIEW-2 ## Q0 — PASS @2026-05-28; cold re-verify on
/root/adv-verify HEAD 0b834e9 → 21 unit PASS + e2e PASS; NO VETO). F2-1 closed; F2-2 (scope
observation) acknowledged.
Prior Q0 claim detail (commit 5741e88 — F2-1 fix landed on top of the original Q0 changeset). Acceptance evidence (per plan §6 Q0): a reference recipe
(custom-html) uses the new harness additions for a full parity + specific suite, green via the
existing run path. F2-1 (test_custom_tests_repo_local_gated stale assertion) closed by Builder; cold
re-run on cc-ci → 21/21 PASS including the previously-failing test. F2-2 (scope observation:
OIDC-flow + dep resolver not in Q0) acknowledged — those primitives implement when Q2/Q3 consume
them; BACKLOG-2 Q0.4 remains open and explicitly deferred.
Objective evidence pointers (Q0):
- Harness additions landed
runner/harness/http.py— canonical Phase-2 recipe-test HTTP API (vendored fromreferences/recipe-maintainer/utils/tests/helpers.py):http_get,http_post,http_request,retry_http_get,retry_http_post,wait_for_http,assert_converges. JSON + form bodies, transport-failure → status=0.runner/harness/discovery.custom_testsrecurses intotests/<recipe>/functional/andtests/<recipe>/playwright/(Phase 2 §4.1 layout) while excluding lifecycletest_<op>.pynames; HC2 repo-local gate continues to apply.- TTY abra wrapper already present in
runner/harness/abra.py::_run_pty(Phase 1d) — reused.
- Unit-test proof (deterministic, cc-ci; post-F2-1 fix commit
5741e88)cc-ci-run -m pytest tests/unit -v→ 21 passed in 5.38s (the previously-failingtest_custom_tests_repo_local_gatednow passes; synthetic-recipe + monkeypatch fixture):- 8× pre-existing
tests/unit/test_discovery.py(overlay + HC2 gate, regressed). - 2× new
tests/unit/test_discovery_phase2.py(functional/+playwright/ recursion + HC2 gate still applies to subdirs). - 11× new
tests/unit/test_http.py(in-process http.server fixture — JSON parsing, 4xx-with-body, non-JSON body, transport-failure=0, headers, JSON+form POST, retry convergence, retry timeout, wait_for_http, assert_converges return value).
- 8× pre-existing
- End-to-end proof (custom-html on cc-ci, the reference recipe)
RECIPE=custom-html cc-ci-run runner/run_recipe_ci.py(log/root/ccci-q0-customhtml-full.log):- install/upgrade/backup/restore/custom all PASS, deploy-count=1.
- HC1 PR-head proof:
head_ref=8a026066 == chaos-version=8a026066, version1.10.0→1.11.0. - 5 lifecycle assertions (generic + cc-ci overlay across 4 ops) + 4 custom-stage assertions
(3 functional + 1 playwright). Reference command for Adversary cold re-run:
RECIPE=custom-html cc-ci-run runner/run_recipe_ci.py.
- Per-recipe contract artifact landed
tests/custom-html/PARITY.md— parity row forhealth_check.py, rationale for the 2 recipe-specific tests + the data-integrity + playwright sections.tests/custom-html/functional/{test_health_check.py,test_content_roundtrip.py,test_content_type_header.py}— parity port + 2 NEW recipe-specific tests; each parity file carries theSOURCE: recipe-info/custom-html/tests/<file>comment for audit.tests/custom-html/playwright/test_browser_smoke.py— Phase-2 P6 home.
Reference command for Adversary (cold, on cc-ci):
ssh cc-ci 'cd /root/cc-ci && cc-ci-run -m pytest tests/unit -v && RECIPE=custom-html cc-ci-run runner/run_recipe_ci.py'
Blocked
(none) — the Docker Hub rate-limit block is RESOLVED @2026-05-28 ~22:10Z. Awaiting Adversary re-verify of the 3 conditions (immediate relief already confirmed by Adversary in REVIEW-2).
Docker Hub rate-limit fix — DONE (registry-creds finding, plan §1.5), all 3 conditions met.
Operator provided a read-only PAT (DOCKERHUB_USERNAME=nptest2 + DOCKERHUB_TOKEN in .testenv).
Wired declaratively; verify commands + expected outcomes for the Adversary:
- Authenticated 200-limit from account source (Adversary already CONFIRMED in REVIEW-2). Re-check:
ssh cc-ci→docker info | grep Username=nptest2; an authenticated manifest HEAD showsratelimit-limit: 200;w=21600anddocker-ratelimit-source: b662dd8b-…(account hash, NOT IP68.14.43.142). - Swarm SERVICE-task pulls authenticate — PROVEN with an uncached image:
ssh cc-ci 'cd /root/cc-ci && RECIPE=n8n STAGES=install cc-ci-run runner/run_recipe_ci.py'→ EXPECTED:install: pass, deploy-count=1, NOtoomanyrequests; the swarm task pullsn8nio/n8n:2.20.6to 1/1. During the run the account counter decrements (197→196 resolution →195 agent layer pull, source = account hash) — the agent pull is billed to the account, not the anon IP. (n8n images were uncached, so this is a real fresh-pull test, not a cached false-pass.) Conclusion: abradocker stack deploypropagates the cred on this single-node swarm; no--with-registry-authflag or pre-pull needed. - Declarative persistence across a 1c rebuild — PAT sops-encrypted (
secrets/secrets.yamlkeydockerhub_auth= base64("nptest2:PAT"), submodulecdd5e0a);nix/modules/secrets.nixaddssops.secrets.dockerhub_auth+sops.templates."docker-config.json"→ renders/root/.docker/config.json(0600 root) at activation. Verify: afternixos-rebuild switch,ls -l /root/.docker/config.json→ symlink to/run/secrets/rendered/docker-config.json; the activation log showsadding rendered secret: docker-config.json. Recorded in DECISIONS.md ("Docker Hub auth: declarative config.json via sops").
Bonus unblocked: Q3.2 lasuite-drive base deploy now CONVERGES (all 12 services incl.
onlyoffice+collabora) — RECIPE=lasuite-drive STAGES=install → install: pass, deploy-count=1
(commit before this; the rate limit was the only blocker). Q3.2 specifics (OIDC/WOPI/upload) are next.
Earlier Gitea outage (RESOLVED @~21:08Z). git.autonomic.zone returned blanket 404 for ~1.5h
(backend down; same from my sandbox AND cc-ci). Reconciled: pulled + pushed queued commits. The 3
watchdog pings during the outage were phantoms (Adversary's failed push retries); nothing lost.
Prior bootstrap state: access re-verified @2026-05-28: ssh cc-ci ok (root, NixOS 24.11), Gitea
API HTTP 200, wildcard DNS resolves to gateway 143.244.213.108.
Carryover from Phase 1e (not blockers for Phase 2)
- F1e-2 [adversary] — concurrent same-recipe
abra recipe fetchrace inrunner/run_recipe_ci.py::fetch_recipe. Pre-existing in Phase 1d; not a 1e regression. Drone capsMAX_TESTS=1today, so practical impact bounded. Tracked for Phase-2 breadth-ramp if concurrent recipe runs become routine.