Mechanical reformat — no logic changes. 398 files changed, 19125 insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100). Isolated per D-13-08 for reviewability.
12 KiB
12 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | user_setup | must_haves | |||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 08-gitea-ci | 04 | execute | 4 |
|
|
false |
|
|
|
Purpose: Every merge to main produces an immutable, traceable image (D-04) plus a moving :latest pointer, with zero credential exposure (ROADMAP criterion 6 is a hard requirement).
Output: a publish job in ci.yml gated on push → main.
<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/08-gitea-ci/08-RESEARCH.md @.planning/research/PITFALLS.md @.planning/phases/08-gitea-ci/08-01-SUMMARY.md @apps/api/Dockerfile<artifacts_this_phase_produces>
.gitea/workflows/ci.yml(EXTENDED — adds the publish job; closes the phase) </artifacts_this_phase_produces>
<interface_context> Confirmed facts (do not re-derive):
- Git remote:
https://git.bergerhouse.net/luckberg/familysync.git→ registry hostgit.bergerhouse.net, ownerluckberg. Image:git.bergerhouse.net/luckberg/familysync-api(08-RESEARCH §Registry Details). - Dockerfile is multi-stage with a
productiontarget that builds API + PWA and serves both on :3000. It MUST be built from the REPO ROOT with-f apps/api/Dockerfile .(the Dockerfile header and 08-RESEARCH §Dockerfile Build Context say so — building from apps/api/ fails because it copies the root workspace manifest + lockfile). - Milestone =
v1.1(PROJECT.md "Current Milestone"). Per D-04, expose it as the workflow-levelenv.MILESTONE(already added in Plan 02) rather than hardcoding inline; update at milestone boundaries. - Short SHA =
${GITHUB_SHA:0:7}(CONFIRMED available in Gitea Actions; 08-RESEARCH). If 08-01-SUMMARY found GITHUB_SHA unavailable, fall back togit rev-parse --short=7 HEAD. - Registry auth: PAT with write:package in repo secret
GITEA_REGISTRY_PAT(created Plan 01).GITHUB_TOKEN/GITEA_TOKENCANNOT push packages (08-RESEARCH; Gitea forum) — must use the PAT. Username =luckberg. - Pitfall 13: NEVER
docker login -p $TOKEN(token leaks to the log / process list). ALWAYSecho "$PAT" | docker login git.bergerhouse.net -u luckberg --password-stdin. </interface_context>
Steps:
1. `uses: actions/checkout@v4`.
2. Compute tags (id: tags). Derive `SHORT_SHA=${GITHUB_SHA:0:7}` (fallback `git rev-parse --short=7 HEAD` if 08-01 flagged GITHUB_SHA missing). Use the workflow-level `${{ env.MILESTONE }}` (= v1.1). Emit two outputs:
`latest=git.bergerhouse.net/luckberg/familysync-api:latest`
`sha_tag=git.bergerhouse.net/luckberg/familysync-api:${MILESTONE}-${SHORT_SHA}`
(write to `$GITHUB_OUTPUT`).
3. Docker login via stdin (Pitfall 13 — the load-bearing security step):
`echo "${{ secrets.GITEA_REGISTRY_PAT }}" | docker login git.bergerhouse.net --username luckberg --password-stdin`
NEVER use `-p`/`--password` with the token as an argument. Do not `echo` the secret anywhere else; do not set it as a plain env var.
4. Build + push from REPO ROOT:
`docker build --target production -f apps/api/Dockerfile -t <latest> -t <sha_tag> .`
then `docker push <latest>` and `docker push <sha_tag>`.
5. Final `if: always()` step: `docker logout git.bergerhouse.net || true` to drop the stored credential from the runner after push.
Use `docker/login-action`/`docker/build-push-action` ONLY if 08-01-SUMMARY confirmed they resolve AND you prefer them; the shell `docker login --password-stdin` + `docker build`/`docker push` form is the safer first iteration (08-RESEARCH §Pattern 6 note) and is the recommended path.
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| Repo secret → docker login | PAT crosses into the job; the single highest-value secret in this phase |
| publish job → registry | Authenticated push to the package registry |
STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|---|---|---|---|---|
| T-08-PAT | Information Disclosure | GITEA_REGISTRY_PAT in publish job | mitigate | docker login --password-stdin exclusively — token piped via stdin, never an -p/--password argument (Pitfall 13). Token referenced only as ${{ secrets.GITEA_REGISTRY_PAT }} (Gitea masks registered secrets in logs); never echoed elsewhere; docker logout after push. Grep gate forbids -p/--password forms. Checkpoint requires a line-by-line log audit (ROADMAP criterion 6). This is the load-bearing mitigation for the phase. |
| T-08-09 | Spoofing | DEV_AUTH_BYPASS bleed into publish | mitigate | Publish job never sets DEV_AUTH_BYPASS (grep gate scoped to the publish: block); the bypass is confined to the harness job (Plan 03). |
| T-08-10 | Tampering | wrong build context | mitigate | Build from repo root with -f apps/api/Dockerfile . (Dockerfile requires root context for the workspace manifest + lockfile); building from apps/api/ would fail or produce a broken image. |
</threat_model>
- ci.yml passes the Task grep gate (push-to-main guard, --password-stdin, no -p, production target from root, both tags, no DEV_AUTH_BYPASS in publish block). - After merge: both tags present in the Gitea registry; log audit shows no plaintext PAT.<success_criteria>
- CI-02: on merge to main, the API production image is built and pushed to the Gitea registry under :latest + :v1.1- (ROADMAP criterion 5; D-04).
- Registry credentials never appear in plaintext in the CI logs (ROADMAP criterion 6; Pitfall 13) — the load-bearing security outcome of the phase.
- Publish runs only on push to main; DEV_AUTH_BYPASS never bleeds into it. </success_criteria>