---
phase: 08-gitea-ci
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - .gitea/workflows/runner-probe.yml
autonomous: false
requirements: [CI-01, CI-02]
user_setup:
  - service: gitea-actions-runner
    why: 'CI cannot run without a registered act_runner; 0 runners currently registered on git.bergerhouse.net'
    dashboard_config:
      - task: 'Install + register act_runner on the Unraid host against git.bergerhouse.net, prefer Docker-executor mode (service containers require it)'
        location: 'Unraid Community Applications → act_runner template; register with a runner-registration token from Gitea → Site Admin → Actions → Runners'
  - service: gitea-registry-pat
    why: 'Publish job (CI-02) authenticates to the Gitea container registry; GITHUB_TOKEN/GITEA_TOKEN cannot push packages'
    env_vars:
      - name: GITEA_REGISTRY_PAT
        source: 'Gitea → Settings → Applications → Generate Token with write:package (+ read:package) scope; add as repo secret GITEA_REGISTRY_PAT'

must_haves:
  truths:
    - 'A runner-probe workflow runs on the gsd/phase-08-gitea-ci branch and prints Node/pnpm versions, runner mode, Docker access, action resolution, and Playwright WebKit dep installability'
    - 'The probe surfaces whether the runner is Docker-executor (services: works) or host-executor (docker run fallback needed) — the answer that forks W1/W2 DB bring-up'
    - 'An act_runner is registered and visible in the Gitea Actions runners list (operator action)'
    - 'A GITEA_REGISTRY_PAT repo secret with write:package scope exists (operator action)'
  artifacts:
    - path: '.gitea/workflows/runner-probe.yml'
      provides: 'Probe-only workflow answering runner unknowns P-01..P-13'
      contains: 'runner-probe'
  key_links:
    - from: '.gitea/workflows/runner-probe.yml'
      to: 'the registered act_runner'
      via: 'runs-on: ubuntu-latest (runner has no self-hosted label), on: push to gsd/phase-08-gitea-ci'
      pattern: "runs-on:\\s*ubuntu-latest"
---

<objective>
Establish the Gitea CI foundation by (a) registering the act_runner and creating the registry PAT (operator actions), and (b) landing a probe-only workflow that answers every runner unknown BEFORE any real test/build/publish step is trusted. This is Pitfall 12 (runner-probe-first) and the critical fork in 08-RESEARCH §Runner-Probe Checklist: several downstream design choices (service containers vs docker run, action resolution, reporter override, artifact upload fork, WebKit deps) depend on the probe's answers.

Purpose: De-risk every assumption (A1–A10 in 08-RESEARCH Assumptions Log) on the actual Unraid runner so Waves 1–2 are written against confirmed behavior, not guesses. Per D-03 the real CI lives in one ci.yml; the probe is a separate throwaway workflow on the feature branch.

Output: `.gitea/workflows/runner-probe.yml`, a registered runner, and a stored registry PAT.
</objective>

<execution_context>
@$HOME/.claude/gsd-core/workflows/execute-plan.md
@$HOME/.claude/gsd-core/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/08-gitea-ci/08-CONTEXT.md
@.planning/phases/08-gitea-ci/08-RESEARCH.md
@.planning/research/PITFALLS.md
</context>

<artifacts_this_phase_produces>

- `.gitea/workflows/runner-probe.yml` (NEW — this plan)
- `.gitea/workflows/ci.yml` (NEW — Plans 02/03/04)
  </artifacts_this_phase_produces>

<tasks>

<task type="checkpoint:human-action" gate="blocking-human">
  <name>Task 1: Register act_runner + create registry PAT (operator-only)</name>
  <what-built>Nothing automated — these are infrastructure actions outside the repo that the executor cannot perform (08-VALIDATION Manual-Only table; CI-01/CI-02 prerequisites).</what-built>
  <how-to-verify>
    1. On the Unraid host, install/register act_runner against https://git.bergerhouse.net using a runner-registration token from Gitea → Site Admin → Actions → Runners. PREFER Docker-executor mode — service containers (the MariaDB the CI needs) only work in Docker mode (08-RESEARCH §Critical fork, Assumption A1). If only host mode is available, that is acceptable; the probe (Task 2) will detect it and Waves 1–2 will use the docker-run fallback.
    2. Confirm the runner appears with status "idle"/online in Gitea → Site Admin → Actions → Runners.
    3. In Gitea → Settings → Applications, generate a token with `write:package` (and `read:package`) scope. Add it as a repository secret named `GITEA_REGISTRY_PAT` (repo → Settings → Actions → Secrets). Do NOT paste the token anywhere in the repo.
  </how-to-verify>
  <resume-signal>Type "runner registered" once the runner is online AND the GITEA_REGISTRY_PAT secret exists, or describe what is blocking (e.g. host-mode only).</resume-signal>
</task>

<task type="auto">
  <name>Task 2: Author the runner-probe workflow</name>
  <files>.gitea/workflows/runner-probe.yml</files>
  <read_first>
    - .planning/phases/08-gitea-ci/08-RESEARCH.md (§Runner-Probe Checklist — the P-01..P-13 table is the canonical task list; §Critical fork Docker-vs-host)
    - .planning/research/PITFALLS.md (Pitfall 12 runner-probe-first, Pitfall 11 MariaDB-11 healthcheck)
    - apps/pwa/playwright.config.ts (reporter: 'github' under CI — probe must note whether annotations render)
  </read_first>
  <action>
    Create `.gitea/workflows/runner-probe.yml` as a probe-only, non-destructive workflow. Trigger: `on: push` filtered to `branches: [gsd/phase-08-gitea-ci]` (runs on the current feature branch; never on main). `runs-on: self-hosted`.

    The job MUST answer every check in 08-RESEARCH §Runner-Probe Checklist P-01..P-13. Implement each as a clearly-labeled step whose output is visible in the Gitea Actions log:
      - P-01 Node: `node --version` (note if not 22; then test `actions/setup-node@v4` with node-version 22 — P-08).
      - P-02 pnpm: `pnpm --version || (corepack enable pnpm && pnpm --version)`.
      - P-03 Runner mode (THE critical fork): print `cat /proc/1/cgroup | head -5`, `hostname`, and `ls -la /.dockerenv 2>&1` so the log shows whether the job runs in a Docker container (Docker-executor → services: works) or on bare host (host-executor → docker run fallback). State the conclusion explicitly in an `echo` line.
      - P-04 Docker socket: `docker info 2>&1 | head -20` and `docker ps 2>&1 | head`.
      - P-05 Service container spawn: add `services: mariadb: { image: mariadb:11, env: { MARIADB_ROOT_PASSWORD: root, MARIADB_DATABASE: familysync, MARIADB_USER: familysync, MARIADB_PASSWORD: testpass }, options: --health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=10 --health-start-period=30s }` to the probe job; a step runs `docker ps | grep -i maria || echo "no mariadb container visible (likely host mode)"`.
      - P-06 MariaDB reachability: try BOTH `mysql -h mariadb -P 3306 -u familysync -ptestpass -e "SELECT 1" 2>&1 | head` (Docker mode hostname) AND `mysql -h 127.0.0.1 ...` (host mode). Record which hostname resolves (do NOT fail the job if one path errors — capture both, `continue-on-error: true` on the step or `|| true`).
      - P-07 checkout: `uses: actions/checkout@v4` as the first real step; reaching subsequent steps proves it resolves.
      - P-08 setup-node: `uses: actions/setup-node@v4` with `node-version: '22'`; print resulting `node --version`.
      - P-09 cache: `uses: actions/cache@v4` with a throwaway key, wrapped `continue-on-error: true` — log whether it completes or hangs/times out (08-RESEARCH Pitfall 7).
      - P-10 Playwright WebKit deps: in `apps/pwa`, `npx playwright install --with-deps webkit chromium 2>&1 | tail -30` with `continue-on-error: true` — confirms WebKit system deps install without sudo/apt failure (A10).
      - P-11 artifact upload: write a dummy file and `uses: https://github.com/ChristopherHX/gitea-upload-artifact@v4` (NOT actions/upload-artifact@v4 — broken on Gitea per 08-RESEARCH) with `continue-on-error: true`; note whether the artifact appears in the Gitea UI.
      - P-13 short SHA: `echo "short sha = ${GITHUB_SHA:0:7}"` — confirms the D-04 tag expression produces 7 chars.
    Do NOT include P-12 (docker login/push) here — defer registry login to Plan 04 to avoid exercising the PAT before the publish job is designed. Add a final summary step that echoes a one-line verdict per fork (Docker vs host mode; cache usable y/n; WebKit deps ok y/n; upload-artifact fork works y/n) so the SUMMARY can record the answers.

    Keep the workflow non-destructive: no migrations, no pushes, no writes to main. All probe steps that may fail on this runner use `continue-on-error: true` or `|| true` so the probe reports findings instead of red-failing on an expected unknown.

  </action>
  <verify>
    <automated>test -f .gitea/workflows/runner-probe.yml && grep -q "runs-on: self-hosted" .gitea/workflows/runner-probe.yml && grep -q "healthcheck.sh --connect --innodb_initialized" .gitea/workflows/runner-probe.yml && grep -q "ChristopherHX/gitea-upload-artifact@v4" .gitea/workflows/runner-probe.yml && ! grep -q "actions/upload-artifact@v4" .gitea/workflows/runner-probe.yml && ! grep -q "mysqladmin" .gitea/workflows/runner-probe.yml && echo PROBE_OK</automated>
  </verify>
  <done>runner-probe.yml exists, triggers only on the feature branch, uses `healthcheck.sh --connect --innodb_initialized` (never mysqladmin), uses the gitea-upload-artifact fork (never actions/upload-artifact@v4), and contains a step for each of P-01..P-11 + P-13.</done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <name>Task 3: Run the probe and record the fork answers</name>
  <what-built>The runner-probe workflow (Task 2), pushed to the gsd/phase-08-gitea-ci branch so the now-registered runner executes it.</what-built>
  <how-to-verify>
    1. Ensure the branch is pushed: `git push origin gsd/phase-08-gitea-ci` (this commit triggers the probe).
    2. Open Gitea → repo → Actions; find the "runner-probe" workflow run.
    3. Read the log and record the answers to the fork questions:
       - P-03: Docker-executor mode or host-executor mode?  (drives Waves 1–2 DB bring-up)
       - P-05/P-06: did the MariaDB service container appear, and on which hostname (`mariadb` vs `127.0.0.1`)?
       - P-09: did actions/cache complete or hang?  (cache optional decision)
       - P-10: did `playwright install --with-deps webkit` succeed?  (WebKit feasibility)
       - P-11: did the gitea-upload-artifact fork upload successfully and appear in the UI?
    4. Confirm no secret/token is printed anywhere in the probe log (the probe must not touch the PAT).
  </how-to-verify>
  <resume-signal>Paste the fork answers (Docker vs host mode; service-container hostname; cache works y/n; WebKit deps y/n; artifact upload y/n) so the executor records them in the SUMMARY for Waves 1–2. Type "probe results recorded" to continue.</resume-signal>
</task>

</tasks>

<threat_model>

## Trust Boundaries

| Boundary                         | Description                                                                       |
| -------------------------------- | --------------------------------------------------------------------------------- |
| CI workflow → self-hosted runner | Untrusted-ish: workflow YAML executes on operator infra with Docker socket access |
| Repo secret store → workflow env | PAT crosses into the job; must never echo                                         |

## STRIDE Threat Register

| Threat ID | Category               | Component                                           | Disposition | Mitigation Plan                                                                                                                                                                                                                                                                                      |
| --------- | ---------------------- | --------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| T-08-01   | Information Disclosure | runner-probe.yml                                    | mitigate    | Probe NEVER references `secrets.GITEA_REGISTRY_PAT` or any secret; no `docker login` in the probe (P-12 deferred to Plan 04). Verified by checkpoint log audit.                                                                                                                                      |
| T-08-02   | Elevation of Privilege | Docker socket on runner                             | accept      | Docker socket access is inherent to act_runner Docker-executor mode; accepted per Gitea self-hosted docs (08-RESEARCH Security Domain).                                                                                                                                                              |
| T-08-SC   | Tampering              | gitea-upload-artifact@v4 (only new external action) | mitigate    | [VERIFIED] in 08-RESEARCH Package Legitimacy Audit (github.com/ChristopherHX/gitea-upload-artifact) as the cited Gitea fix for the upload-artifact@v4 GHES block; pinned at @v4. All other actions are official GitHub/Docker actions. No [ASSUMED]/[SUS] packages → no install checkpoint required. |

</threat_model>

<verification>
- runner-probe.yml present, branch-scoped, non-destructive; passes the Task 2 grep gate.
- Probe run observed in Gitea Actions; fork answers recorded in SUMMARY.
- Runner online; GITEA_REGISTRY_PAT secret created (operator confirmed).
- No secret material printed in any probe log line.
</verification>

<success_criteria>

- Maps to CI-01/CI-02 prerequisites and Pitfall 12: the runner environment is probed BEFORE any real test/build/publish step is designed.
- The Docker-vs-host fork (A1) is answered; the answer is recorded so Plans 02–04 pick the correct DB bring-up path.
- Operator infra (runner + PAT) is in place.
  </success_criteria>

<output>
Create `.planning/phases/08-gitea-ci/08-01-SUMMARY.md` when done. MUST record the probe fork answers (runner mode, service-container hostname, cache usable y/n, WebKit deps y/n, upload-artifact fork y/n) — Plans 02–04 consume them.
</output>
