If deploy_deps succeeds (gitea up + healthy) but _enrich_deps_with_sso subsequently raises,
deps_state stays {} in main(). The finally block's `if deps_state:` guard is falsy and gitea
teardown is skipped entirely — violates §9 teardown-sacred invariant.
BACKLOG-drone.md: ADV-drone-02 filed (MEDIUM) with exact failure path trace, risk analysis,
and three fix options. REVIEW-drone.md: ADV-drone-02 summary + standing break-it probes updated
(negative-control, secrets-in-logs, concurrent-run probes analysed structurally). BUILDER-INBOX
created with must-fix notice and suggested minimal patch.
Must be fixed + tested before M1 can be claimed. Adversary veto standing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.4 KiB
REVIEW — phase drone (drone enrollment with gitea SCM dep)
Adversary: Adversary loop / Claude
Phase plan: /srv/cc-ci/cc-ci-plan/plan-phase-drone-enroll.md
Started: 2026-06-11T21:30Z
Verdicts
(awaiting Builder claims)
Pre-verification probes (Adversary-initiated, before any Builder claim)
P0 verification — /etc/timezone on cc-ci host
Verified: 2026-06-11T21:30Z
ssh cc-ci 'test -f /etc/timezone && cat /etc/timezone'
# → UTC
ssh cc-ci 'ls -la /etc/localtime /etc/timezone'
# → /etc/localtime -> /etc/zoneinfo/UTC
# → /etc/timezone -> /etc/static/timezone (content: UTC)
Result: P0 SATISFIED. Both /etc/timezone (content UTC) and /etc/localtime exist. The gitea recipe's bind mounts (/etc/timezone:ro and /etc/localtime:ro) will succeed. The host-config fix from commit 3bde76f is live.
Pre-probe: drone recipe versions
ssh cc-ci 'abra recipe versions drone --machine'
- Latest:
1.9.0+2.26.0(drone/drone:2.26.0) - Previous:
1.8.0+2.25.0(drone/drone:2.25.0) - Upgrade tier: viable (2 published versions; upgrade 1.8 → 1.9 is the natural choice)
Pre-probe: gitea recipe versions
ssh cc-ci 'abra recipe versions gitea --machine'
- Latest:
3.5.3+1.24.2-rootless(gitea + postgres) - Previous:
3.5.2+1.24.2-rootless - Gitea uses postgres by default (not sqlite3). The sqlite3 overlay exists but is non-default.
- The
compose.sqlite3.ymlsetsGITEA_DB_TYPE=sqlite3— if gitea is used as a dep without postgres, sqlite3 is the right choice (simpler dep deploy, less resource overhead). - Upgrade tier: viable for gitea as a dep, but the phase plan scope only requires drone's upgrade tier. Gitea as a dep is deployed at the PR version; upgrade tier for the dep is out of scope per plan §1.
Pre-probe: drone recipe structure
The compose.gitea.yml overlay requires:
GITEA_CLIENT_IDin.envGITEA_DOMAINin.envclient_secretswarm secret
The drone.env.tmpl conditionally injects DRONE_GITEA_CLIENT_SECRET from secret "client_secret"
when DRONE_GITEA_CLIENT_ID is set. So the install hook must:
- Create gitea admin user + admin token via API
- Create OAuth2 application via
POST /api/v1/user/applications/oauth2 - Set
GITEA_CLIENT_ID,GITEA_DOMAIN,COMPOSE_FILE(to include compose.gitea.yml) in drone's.env - Insert
client_secretinto drone's swarm secrets
Pre-probe: SCM-configured test teeth
The drone health endpoint /healthz returns OK regardless of SCM connectivity. This means a drone
deployed WITHOUT gitea wiring would also pass a health check.
Verified the correct approach by querying the live drone instance:
curl -ski --max-redirs 0 https://drone.ci.commoninternet.net/login | grep location
# → location: https://git.autonomic.zone/login/oauth/authorize?client_id=ab4cdb9d-...&redirect_uri=...
GET /login (no-follow) → 303 redirect to <gitea-domain>/login/oauth/authorize?client_id=<id>&...
The correct "SCM-configured" test:
GET https://<drone-domain>/loginwithallow_redirects=False- Assert response is 302/303
- Assert
Locationheader starts withhttps://<gitea-domain>/login/oauth/authorize - Assert
client_idquery param matches the OAuth2 app we created in gitea
Why this has teeth: a drone deployed WITHOUT DRONE_GITEA_CLIENT_ID + DRONE_GITEA_SERVER
(i.e., just the base compose.yml without compose.gitea.yml) would NOT redirect to the gitea
domain — it would either error or redirect to a GitHub OAuth URL. The test is falsified by a
misconfigured drone.
Adversary position (pre-claim): the SCM-configured test MUST use the /login redirect mechanism
(or equivalent API proof of gitea wiring). A bare /healthz check is INSUFFICIENT and will be
flagged as a test without teeth. The redirect target must point to the TEST-RUN gitea instance (the
dep deployed by the harness), NOT to git.autonomic.zone (that would prove nothing).
Pre-probe: recipe mirrors
# drone: NOT mirrored on git.autonomic.zone/recipe-maintainers/drone (404)
# gitea: NOT mirrored on git.autonomic.zone/recipe-maintainers/gitea (404)
Both need to be mirrored before !testme can be used. Builder must follow the recipe mirror+PR flow
(plan §4.1 / recipe-create-pr.md). This is expected and not a blocker — it's in scope.
Pre-claim findings (before M1 is claimed)
ADV-drone-01 — test_scm_configured redirect bug (CRITICAL)
Filed: 2026-06-11T21:37Z — see BACKLOG-drone.md for full details.
test_login_redirects_to_gitea_dep uses urllib.request.urlopen (follow-all-redirects). The
chain is: drone /login → 303 → gitea OAuth authorize → 302 → gitea /user/login (unauthenticated).
final_url is /user/login, so parsed.path == "/login/oauth/authorize" is always False.
The test always fails, even for a correctly wired drone.
Fix: capture only drone's first redirect (no-follow pattern; capture Location header from 303).
This must be fixed before M1 can be claimed. If M1 is claimed without this fix, I will VETO.
RESOLVED @2026-06-11T21:52Z: Builder fixed in commit 7e7e84d. _CaptureOneRedirect raises
HTTPError on 303, test reads Location header directly. Verified against live drone: captures
/login/oauth/authorize path ✅. Unit tests 10/10 PASS cold. ADV-drone-01 CLOSED.
ADV-drone-02 — dep orphan on SSO-enrichment failure (MEDIUM)
Filed: 2026-06-11T22:10Z — see BACKLOG-drone.md for full details.
deps_state = {} is initialised empty in main(). _provision_deps calls deploy_deps first
(gitea deployed + healthy, $CCCI_DEPS_FILE written), then _enrich_deps_with_sso. If the
enrichment step raises (e.g. setup_gitea_oauth API call fails), _provision_deps re-raises and
the deps_state = _provision_deps(...) assignment (line 1034) never completes. In the finally
block, if deps_state: is falsy → dep teardown block is entirely skipped. The gitea container
and volumes are orphaned at their deterministic domain.
Teardown-sacred (§9) violated in failure path.
Required fix before M1: option A (fallback teardown from $CCCI_DEPS_FILE in the finally block
when deps_state is empty) or option B (separate deploy from enrichment tracking). See BACKLOG.
Status: OPEN — must be fixed before M1 can be claimed.
Standing break-it probes
- Verify drone WITHOUT gitea wiring fails SCM-configured test (negative control) — defer to M2 CI run; requires live deploy; structural analysis confirms
install_steps.shno-ops on absent deps file and test detects wrongnetloc/pathin redirect URL - Verify gitea teardown doesn't orphan containers when drone test fails mid-run — structural PASS for normal test failures (finally block guaranteed); GAP filed as ADV-drone-02 for SSO-enrichment failure before deps_state populated
- Verify no secrets (OAuth client secret, admin token) appear in drone logs/dashboard — defer to M2 CI run; structural review of sso.py + install_steps.sh shows client_secret not printed in happy path;
_scrub()+ D6 redaction in run_redacted() provide belt-and-suspenders - Verify two concurrent runs don't collide on gitea/drone domains or OAuth apps — structural PASS: domain is
dep_domain(parent_recipe, pr, ref, dep_recipe)— hash of 4 inputs; two concurrent !testme runs on different PRs or refs produce distinct 6-hex domains; per-run ABRA_DIR isolation prevents recipe tree conflicts