# BACKLOG — phase drone (drone enrollment with gitea SCM dep) **Phase plan:** `/srv/cc-ci/cc-ci-plan/plan-phase-drone-enroll.md` --- ## Build backlog _(Builder's section — Adversary read-only)_ ### M1 tasks - [x] Read plan + Adversary pre-probes - [x] Create phase state files (STATUS/JOURNAL/BACKLOG/REVIEW init) - [ ] Implement `setup_gitea_oauth()` in `runner/harness/sso.py` - [ ] Extend `_enrich_deps_with_sso` in `runner/run_recipe_ci.py` for gitea - [ ] Create `tests/gitea/recipe_meta.py` - [ ] Create `tests/drone/recipe_meta.py` - [ ] Create `tests/drone/install_steps.sh` - [ ] Create `tests/drone/functional/test_scm_configured.py` - [ ] Create `tests/drone/PARITY.md` - [ ] Write unit tests for new harness surface - [ ] Mirror drone + gitea on git.autonomic.zone (for M2 CI path) - [ ] Open !testme PR for drone recipe - [ ] Claim M1 ### M2 tasks (after M1 PASS) - [ ] CI run via !testme on drone PR — full lifecycle green - [ ] Screenshot real + visually verified - [ ] Level recorded - [ ] DEFERRED updated (build-creation gap narrowed + signed off) - [ ] Operator summary written - [ ] Claim M2 --- ## Adversary findings ### ADV-drone-01 [adversary] test_scm_configured follows all redirects — assertion always fails **Filed:** 2026-06-11T21:37Z **Severity:** CRITICAL — SCM-configured test is always failing, even for a correctly wired drone **Defect:** `tests/drone/functional/test_scm_configured.py::test_login_redirects_to_gitea_dep` uses `urllib.request.urlopen(req, context=ctx)` which follows ALL redirect hops. The redirect chain for a correctly-wired drone is: 1. `GET /login` → 303 → `https:///login/oauth/authorize?client_id=...&...` 2. Gitea (unauthenticated user) → 302 → `https:///user/login?redirect_to=...` 3. Final: `https:///user/login` (200 OK) The test asserts `parsed.path == "/login/oauth/authorize"` but `final_url` is `/user/login`. **The assertion ALWAYS fails even when drone is correctly wired.** **Verified:** reproduced against the live drone.ci.commoninternet.net: ``` python3 -c " import ssl, urllib.request, urllib.parse ctx = ssl.create_default_context(); ctx.check_hostname = False; ctx.verify_mode = ssl.CERT_NONE req = urllib.request.Request('https://drone.ci.commoninternet.net/login', method='GET') with urllib.request.urlopen(req, timeout=30, context=ctx) as resp: print(resp.geturl()) # → https://git.autonomic.zone/user/login (NOT /login/oauth/authorize) " ``` **Root cause:** The test was designed around the first-redirect check (per REVIEW-drone.md pre-probe) but implemented as a follow-all check. The pre-probe used `curl --max-redirs 0` to capture the Location header — the test must replicate this, not `urlopen(follow=True)`. **Required fix:** Capture ONLY drone's first redirect (the 303 → gitea OAuth authorize), stop before gitea's own redirects. One correct pattern: ```python class _CaptureOneRedirect(urllib.request.HTTPRedirectHandler): def http_error_302(self, req, fp, code, msg, headers): raise urllib.error.HTTPError(req.full_url, code, msg, headers, fp) http_error_303 = http_error_302 opener = urllib.request.build_opener( _CaptureOneRedirect(), urllib.request.HTTPSHandler(context=ctx), ) try: opener.open(f"https://{live_app}/login", timeout=30) pytest.fail("Expected redirect from /login but got 200") except urllib.error.HTTPError as e: if e.code not in (302, 303): raise AssertionError(f"Expected 302/303 from /login, got {e.code}") redirect_url = e.headers.get("Location") or e.headers.get("location", "") parsed = urllib.parse.urlparse(redirect_url) # now check parsed.netloc == gitea_domain and parsed.path == "/login/oauth/authorize" ``` **Also note:** The unit test `test_scm_redirect_assertions` tests the URL assertion logic correctly (with pre-supplied URLs), but does NOT test the redirect-capture mechanism. A unit test for `_CaptureOneRedirect` behavior against a mock HTTP server would be ideal, but at minimum the integration test must use this pattern. **Repro steps:** 1. Deploy a correctly-wired drone (with gitea dep, compose.gitea.yml, DRONE_GITEA_CLIENT_ID set) 2. Run `test_login_redirects_to_gitea_dep` 3. It will FAIL with `AssertionError: Final URL path is '/user/login', expected '/login/oauth/authorize'` 4. This is a false failure — the assertion is about the URL AFTER gitea's own redirect, not drone's redirect **Resolution:** Builder fixes test to use no-follow-first-redirect pattern. Adversary re-verifies by running the test against a live wired drone after fix. - [ ] OPEN — awaiting Builder fix