style(13-03): apply Prettier formatting across repo

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.
This commit is contained in:
Lucas Berger
2026-06-11 20:35:18 -04:00
parent 4bc0445173
commit 982438dc10
398 changed files with 19050 additions and 16382 deletions
+258 -247
View File
@@ -7,6 +7,7 @@
---
<user_constraints>
## User Constraints (from CONTEXT.md)
### Locked Decisions
@@ -32,15 +33,17 @@
### Deferred Ideas (OUT OF SCOPE)
- ROADMAP.md bookkeeping error (Phase 8 marked completed at line 29 while line 204 says "Not started") — docs cleanup, not CI scope.
</user_constraints>
</user_constraints>
<phase_requirements>
## Phase Requirements
| ID | Description | Research Support |
|----|-------------|------------------|
| ID | Description | Research Support |
| ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| CI-01 | Every PR targeting `main` runs full regression — lint, typecheck (both apps), unit tests, API integration tests against a MariaDB service container, and the Phase 7 mobile Playwright harness (CI brings up API + PWA dev servers + MariaDB + DEV_AUTH_BYPASS) — result gates merge. | See §Architecture Patterns for job topology, §Service Containers for MariaDB, §Dev-Stack Bring-Up for harness orchestration, §Runner-Probe Checklist for what must be verified first. |
| CI-02 | On merge to `main`, API Docker image is built and published to the Gitea container registry. | See §Docker Registry Push for Gitea registry mechanics, §Image Tagging for `v1.1-<sha>` strategy. |
| CI-02 | On merge to `main`, API Docker image is built and published to the Gitea container registry. | See §Docker Registry Push for Gitea registry mechanics, §Image Tagging for `v1.1-<sha>` strategy. |
</phase_requirements>
---
@@ -59,16 +62,16 @@ For the publish job, the Gitea container registry path is `git.bergerhouse.net/l
## Architectural Responsibility Map
| Capability | Primary Tier | Secondary Tier | Rationale |
|------------|-------------|----------------|-----------|
| Workflow orchestration | CI runner (Gitea Actions) | — | Gitea Actions owns job scheduling |
| MariaDB service container | CI runner (act_runner Docker daemon) | — | Spawned as a sibling container by act_runner |
| API background process | CI runner (host or job container) | — | `pnpm build && node dist/index.js` in a step |
| Vite dev server | Playwright webServer config | — | Playwright starts it; reuseExistingServer=false in CI |
| DB seed (global-setup) | Playwright globalSetup | API (through mysql2 direct connection) | global-setup.ts connects directly to MariaDB |
| Docker image build | CI runner (Docker socket / DinD) | — | `docker build` in a workflow step |
| Container registry push | Gitea package registry | — | `docker push git.bergerhouse.net/luckberg/familysync-api` |
| Artifact upload (traces) | Gitea Actions artifact storage | — | Via `gitea-upload-artifact` fork (see §Artifacts) |
| Capability | Primary Tier | Secondary Tier | Rationale |
| ------------------------- | ------------------------------------ | -------------------------------------- | --------------------------------------------------------- |
| Workflow orchestration | CI runner (Gitea Actions) | — | Gitea Actions owns job scheduling |
| MariaDB service container | CI runner (act_runner Docker daemon) | — | Spawned as a sibling container by act_runner |
| API background process | CI runner (host or job container) | — | `pnpm build && node dist/index.js` in a step |
| Vite dev server | Playwright webServer config | — | Playwright starts it; reuseExistingServer=false in CI |
| DB seed (global-setup) | Playwright globalSetup | API (through mysql2 direct connection) | global-setup.ts connects directly to MariaDB |
| Docker image build | CI runner (Docker socket / DinD) | — | `docker build` in a workflow step |
| Container registry push | Gitea package registry | — | `docker push git.bergerhouse.net/luckberg/familysync-api` |
| Artifact upload (traces) | Gitea Actions artifact storage | — | Via `gitea-upload-artifact` fork (see §Artifacts) |
---
@@ -76,14 +79,14 @@ For the publish job, the Gitea container registry path is `git.bergerhouse.net/l
### Workflow Actions
| Action | Version | Purpose | Status |
|--------|---------|---------|--------|
| `actions/checkout` | `@v4` | Clone repo into job | [ASSUMED] Mirrored at `gitea.com/actions/checkout`; resolves from GitHub by default via `DEFAULT_ACTIONS_URL`. Probe confirms. |
| `actions/setup-node` | `@v4` | Pin Node.js 22 | [ASSUMED] Mirrored at `gitea.com/actions/setup-node`. Probe confirms. |
| `actions/cache` | `@v4` | pnpm store cache | [ASSUMED] Known networking issue: cache server runs in runner container but job container is on a different network. May time out. Probe is required — fall back to no-cache if it fails. |
| `https://github.com/ChristopherHX/gitea-upload-artifact` | `@v4` | Upload Playwright traces | [VERIFIED: github.com/ChristopherHX/gitea-upload-artifact] Required replacement for `actions/upload-artifact@v4` which detects Gitea as GHES and aborts. |
| `docker/login-action` | `@v3` | Authenticate to Gitea registry | [ASSUMED] Referenced from GitHub by absolute URL; probe confirms. |
| `docker/build-push-action` | `@v6` | Build and push Docker image | [ASSUMED] Referenced from GitHub by absolute URL; probe confirms. |
| Action | Version | Purpose | Status |
| -------------------------------------------------------- | ------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `actions/checkout` | `@v4` | Clone repo into job | [ASSUMED] Mirrored at `gitea.com/actions/checkout`; resolves from GitHub by default via `DEFAULT_ACTIONS_URL`. Probe confirms. |
| `actions/setup-node` | `@v4` | Pin Node.js 22 | [ASSUMED] Mirrored at `gitea.com/actions/setup-node`. Probe confirms. |
| `actions/cache` | `@v4` | pnpm store cache | [ASSUMED] Known networking issue: cache server runs in runner container but job container is on a different network. May time out. Probe is required — fall back to no-cache if it fails. |
| `https://github.com/ChristopherHX/gitea-upload-artifact` | `@v4` | Upload Playwright traces | [VERIFIED: github.com/ChristopherHX/gitea-upload-artifact] Required replacement for `actions/upload-artifact@v4` which detects Gitea as GHES and aborts. |
| `docker/login-action` | `@v3` | Authenticate to Gitea registry | [ASSUMED] Referenced from GitHub by absolute URL; probe confirms. |
| `docker/build-push-action` | `@v6` | Build and push Docker image | [ASSUMED] Referenced from GitHub by absolute URL; probe confirms. |
### No `pnpm/action-setup` needed
@@ -99,14 +102,14 @@ The repo root `package.json` declares `"packageManager": "pnpm@11.5.1"`. With No
Only `gitea-upload-artifact` is an external action introduced by this phase. All other tools are GitHub-maintained official actions or Docker-maintained actions that are well-established.
| Package / Action | Registry / Source | Age | Downloads | Source Repo | Verdict | Disposition |
|---------|----------|-----|-----------|-------------|---------|-------------|
| `actions/checkout@v4` | github.com/actions/checkout | 5+ yrs | Millions | github.com/actions/checkout | OK | Approved |
| `actions/setup-node@v4` | github.com/actions/setup-node | 5+ yrs | Millions | github.com/actions/setup-node | OK | Approved |
| `actions/cache@v4` | github.com/actions/cache | 5+ yrs | Millions | github.com/actions/cache | OK | Approved — but probe may fall back |
| `ChristopherHX/gitea-upload-artifact@v4` | github.com/ChristopherHX/gitea-upload-artifact | ~2 yrs | Moderate, known fix for Gitea | github.com/ChristopherHX/gitea-upload-artifact | OK | Approved — known and cited solution to v4 GHES blocker |
| `docker/login-action@v3` | github.com/docker/login-action | 4+ yrs | Millions | github.com/docker/login-action | OK | Approved |
| `docker/build-push-action@v6` | github.com/docker/build-push-action | 4+ yrs | Millions | github.com/docker/build-push-action | OK | Approved |
| Package / Action | Registry / Source | Age | Downloads | Source Repo | Verdict | Disposition |
| ---------------------------------------- | ---------------------------------------------- | ------ | ----------------------------- | ---------------------------------------------- | ------- | ------------------------------------------------------ |
| `actions/checkout@v4` | github.com/actions/checkout | 5+ yrs | Millions | github.com/actions/checkout | OK | Approved |
| `actions/setup-node@v4` | github.com/actions/setup-node | 5+ yrs | Millions | github.com/actions/setup-node | OK | Approved |
| `actions/cache@v4` | github.com/actions/cache | 5+ yrs | Millions | github.com/actions/cache | OK | Approved — but probe may fall back |
| `ChristopherHX/gitea-upload-artifact@v4` | github.com/ChristopherHX/gitea-upload-artifact | ~2 yrs | Moderate, known fix for Gitea | github.com/ChristopherHX/gitea-upload-artifact | OK | Approved — known and cited solution to v4 GHES blocker |
| `docker/login-action@v3` | github.com/docker/login-action | 4+ yrs | Millions | github.com/docker/login-action | OK | Approved |
| `docker/build-push-action@v6` | github.com/docker/build-push-action | 4+ yrs | Millions | github.com/docker/build-push-action | OK | Approved |
**Packages removed due to SLOP verdict:** none
**Packages flagged as suspicious SUS:** none
@@ -121,31 +124,33 @@ The runner-probe workflow lives at `.gitea/workflows/runner-probe.yml`, runs onl
### What the probe must answer
| # | Check | Command in Probe | What it confirms |
|---|-------|-----------------|-----------------|
| P-01 | Node.js version | `node --version` | Node 22 available or needs `setup-node` |
| P-02 | pnpm availability | `pnpm --version` OR `corepack enable pnpm && pnpm --version` | pnpm reachable; version matches 11.x |
| P-03 | Runner mode | `cat /proc/1/cgroup | head -5` and `hostname` and `ls /.dockerenv 2>/dev/null` | Is the job running in a Docker container (act_runner Docker mode) or on bare host? This is the critical fork: service containers only work in Docker mode. |
| P-04 | Docker socket access | `docker info 2>&1 | head -10` | Docker accessible from job; needed for service containers AND publish job |
| P-05 | Service container spawn | Add `services: mariadb: image: mariadb:11` to probe job; check if `docker ps` in a step shows the mariadb container | Service containers work at all |
| P-06 | MariaDB reachability | After P-05: `mysql -h 127.0.0.1 -P 3306 -u root -proot -e "SELECT 1"` (host runner) OR `-h mariadb` (job container) | Which hostname resolves to the MariaDB service |
| P-07 | `actions/checkout` | `uses: actions/checkout@v4` | Action resolves; DEFAULT_ACTIONS_URL is set to github.com |
| P-08 | `actions/setup-node` | `uses: actions/setup-node@v4` with `node-version: '22'` | setup-node works; pins Node 22 |
| P-09 | `actions/cache` | `uses: actions/cache@v4` with a test key | Cache works without timeout; if it hangs, confirm no-cache fallback |
| P-10 | Playwright deps (WebKit) | `npx playwright install --with-deps webkit chromium 2>&1 | tail -20` | System deps installed; no sudo/apt failures |
| P-11 | `upload-artifact` | `uses: https://github.com/ChristopherHX/gitea-upload-artifact@v4` with a dummy file | Upload succeeds; artifact appears in Gitea UI |
| P-12 | Docker login + push | `echo $SECRET | docker login git.bergerhouse.net --username luckberg --password-stdin` | Registry auth works with PAT |
| P-13 | `GITHUB_SHA` | `echo ${GITHUB_SHA:0:7}` | Short SHA expression produces 7-char string |
| # | Check | Command in Probe | What it confirms |
| ---- | ------------------------ | ------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| P-01 | Node.js version | `node --version` | Node 22 available or needs `setup-node` |
| P-02 | pnpm availability | `pnpm --version` OR `corepack enable pnpm && pnpm --version` | pnpm reachable; version matches 11.x |
| P-03 | Runner mode | `cat /proc/1/cgroup | head -5`and`hostname`and`ls /.dockerenv 2>/dev/null` | Is the job running in a Docker container (act_runner Docker mode) or on bare host? This is the critical fork: service containers only work in Docker mode. |
| P-04 | Docker socket access | `docker info 2>&1 | head -10` | Docker accessible from job; needed for service containers AND publish job |
| P-05 | Service container spawn | Add `services: mariadb: image: mariadb:11` to probe job; check if `docker ps` in a step shows the mariadb container | Service containers work at all |
| P-06 | MariaDB reachability | After P-05: `mysql -h 127.0.0.1 -P 3306 -u root -proot -e "SELECT 1"` (host runner) OR `-h mariadb` (job container) | Which hostname resolves to the MariaDB service |
| P-07 | `actions/checkout` | `uses: actions/checkout@v4` | Action resolves; DEFAULT_ACTIONS_URL is set to github.com |
| P-08 | `actions/setup-node` | `uses: actions/setup-node@v4` with `node-version: '22'` | setup-node works; pins Node 22 |
| P-09 | `actions/cache` | `uses: actions/cache@v4` with a test key | Cache works without timeout; if it hangs, confirm no-cache fallback |
| P-10 | Playwright deps (WebKit) | `npx playwright install --with-deps webkit chromium 2>&1 | tail -20` | System deps installed; no sudo/apt failures |
| P-11 | `upload-artifact` | `uses: https://github.com/ChristopherHX/gitea-upload-artifact@v4` with a dummy file | Upload succeeds; artifact appears in Gitea UI |
| P-12 | Docker login + push | `echo $SECRET | docker login git.bergerhouse.net --username luckberg --password-stdin` | Registry auth works with PAT |
| P-13 | `GITHUB_SHA` | `echo ${GITHUB_SHA:0:7}` | Short SHA expression produces 7-char string |
### Critical fork: Docker mode vs host mode (P-03)
**If job runs in a Docker container (Docker mode — the recommended act_runner default):**
- Service container hostname = service label name (e.g. `mariadb`)
- Job container and service container share a Docker network automatically
- `DB_HOST=mariadb` in job env; NO port mapping needed in workflow
- This is the GitHub-Actions-compatible path; service containers work as documented
**If job runs directly on host (host mode):**
- Service containers are NOT supported by act_runner's host executor [CITED: github.com/nektos/act/issues/2711]
- The plan must use a `docker run -d --name mariadb mariadb:11 ...` step instead of `services:`
- `DB_HOST=127.0.0.1` with port `3306:3306` mapping in the `docker run` step
@@ -156,28 +161,28 @@ The runner-probe workflow lives at `.gitea/workflows/runner-probe.yml`, runs onl
## CONFIRMED-vs-VERIFY Table
| Item | Status | Notes |
|------|--------|-------|
| Workflow file at `.gitea/workflows/ci.yml` | CONFIRMED | [CITED: docs.gitea.com/usage/actions/quickstart] |
| `on: pull_request` and `on: push` triggers | CONFIRMED | Standard GitHub Actions syntax; Gitea supports these [CITED: comparison page] |
| `actions/checkout@v4` resolves from GitHub | CONFIRMED (per docs) | DEFAULT_ACTIONS_URL defaults to github.com; VERIFY-ON-RUNNER (P-07) |
| `actions/setup-node@v4` resolves | ASSUMED | Mirrored at gitea.com/actions/setup-node; VERIFY-ON-RUNNER (P-08) |
| `actions/cache@v4` works in Docker mode | ASSUMED with caveat | Known networking issue between runner container and job container; VERIFY-ON-RUNNER (P-09) |
| `services:` key starts MariaDB in Docker mode | ASSUMED from GitHub Actions docs | act_runner implements this for Docker mode; does NOT implement for host mode [CITED: nektos/act#2711]; VERIFY-ON-RUNNER (P-03 + P-05) |
| MariaDB hostname in Docker mode = service name | ASSUMED from GitHub Actions semantics | "hostname automatically mapped to label name" for containerized jobs [CITED: docs.github.com]; VERIFY-ON-RUNNER (P-06) |
| MariaDB hostname in host mode = `127.0.0.1` | CONFIRMED for host-mode + port-mapped service | [CITED: firefart.at MySQL-GitHub-Actions] |
| `healthcheck.sh --connect --innodb_initialized` works in `options:` | CONFIRMED | [CITED: mariadb.com/docs healthcheck.sh page] |
| `mysqladmin ping` does NOT work with MariaDB 11 | CONFIRMED | `mysqladmin` binary was removed from the `mariadb:11` image [CITED: github.com/mage-os/github-actions/issues/365] |
| `actions/upload-artifact@v4` works natively on Gitea | CONFIRMED BROKEN | Gitea detected as GHES; v4 aborts with `reqPackageAccess` error [CITED: github.com/go-gitea/gitea/issues/31256] |
| `ChristopherHX/gitea-upload-artifact@v4` works | ASSUMED | Known workaround; VERIFY-ON-RUNNER (P-11) |
| `reporter: 'github'` renders annotations in Gitea | UNCONFIRMED | Gitea does not fully implement GitHub workflow commands; annotations likely silently ignored. VERIFY-ON-RUNNER — fall back to `['list', 'html']` if annotations don't appear |
| `GITHUB_SHA` available in Gitea Actions | CONFIRMED | Gitea uses GitHub-compatible env var names [CITED: forum.gitea.com/t/using-github-sha-or-gitea-sha] |
| Short SHA via `${GITHUB_SHA:0:7}` | CONFIRMED | Bash substring; same forum thread |
| Docker login to Gitea registry with PAT | CONFIRMED (approach) | `secrets.GITEA_TOKEN` does NOT work for packages [CITED: forum.gitea.com/t/proper-container-registry-procedure]; use PAT with `write:package` scope [CITED: docs.gitea.com/usage/packages/container] |
| Gitea registry image path: `git.bergerhouse.net/luckberg/<image>` | CONFIRMED | Registry uses `{host}/{owner}/{image}` format [CITED: docs.gitea.com/usage/packages/container] |
| `docker/login-action@v3` + `docker/build-push-action@v6` resolve | ASSUMED | Referenced by absolute GitHub URL; VERIFY-ON-RUNNER (P-12) |
| Playwright `--with-deps` installs system deps without sudo | CONFIRMED for most cases | Playwright handles su internally; may fail if runner has no internet/apt access [CITED: playwright.dev/docs/ci] |
| `npx playwright install` does NOT cache browser binaries | CONFIRMED (deliberate) | Playwright explicitly recommends against caching browser binaries in CI [CITED: playwright.dev/docs/ci] |
| Item | Status | Notes |
| ------------------------------------------------------------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Workflow file at `.gitea/workflows/ci.yml` | CONFIRMED | [CITED: docs.gitea.com/usage/actions/quickstart] |
| `on: pull_request` and `on: push` triggers | CONFIRMED | Standard GitHub Actions syntax; Gitea supports these [CITED: comparison page] |
| `actions/checkout@v4` resolves from GitHub | CONFIRMED (per docs) | DEFAULT_ACTIONS_URL defaults to github.com; VERIFY-ON-RUNNER (P-07) |
| `actions/setup-node@v4` resolves | ASSUMED | Mirrored at gitea.com/actions/setup-node; VERIFY-ON-RUNNER (P-08) |
| `actions/cache@v4` works in Docker mode | ASSUMED with caveat | Known networking issue between runner container and job container; VERIFY-ON-RUNNER (P-09) |
| `services:` key starts MariaDB in Docker mode | ASSUMED from GitHub Actions docs | act_runner implements this for Docker mode; does NOT implement for host mode [CITED: nektos/act#2711]; VERIFY-ON-RUNNER (P-03 + P-05) |
| MariaDB hostname in Docker mode = service name | ASSUMED from GitHub Actions semantics | "hostname automatically mapped to label name" for containerized jobs [CITED: docs.github.com]; VERIFY-ON-RUNNER (P-06) |
| MariaDB hostname in host mode = `127.0.0.1` | CONFIRMED for host-mode + port-mapped service | [CITED: firefart.at MySQL-GitHub-Actions] |
| `healthcheck.sh --connect --innodb_initialized` works in `options:` | CONFIRMED | [CITED: mariadb.com/docs healthcheck.sh page] |
| `mysqladmin ping` does NOT work with MariaDB 11 | CONFIRMED | `mysqladmin` binary was removed from the `mariadb:11` image [CITED: github.com/mage-os/github-actions/issues/365] |
| `actions/upload-artifact@v4` works natively on Gitea | CONFIRMED BROKEN | Gitea detected as GHES; v4 aborts with `reqPackageAccess` error [CITED: github.com/go-gitea/gitea/issues/31256] |
| `ChristopherHX/gitea-upload-artifact@v4` works | ASSUMED | Known workaround; VERIFY-ON-RUNNER (P-11) |
| `reporter: 'github'` renders annotations in Gitea | UNCONFIRMED | Gitea does not fully implement GitHub workflow commands; annotations likely silently ignored. VERIFY-ON-RUNNER — fall back to `['list', 'html']` if annotations don't appear |
| `GITHUB_SHA` available in Gitea Actions | CONFIRMED | Gitea uses GitHub-compatible env var names [CITED: forum.gitea.com/t/using-github-sha-or-gitea-sha] |
| Short SHA via `${GITHUB_SHA:0:7}` | CONFIRMED | Bash substring; same forum thread |
| Docker login to Gitea registry with PAT | CONFIRMED (approach) | `secrets.GITEA_TOKEN` does NOT work for packages [CITED: forum.gitea.com/t/proper-container-registry-procedure]; use PAT with `write:package` scope [CITED: docs.gitea.com/usage/packages/container] |
| Gitea registry image path: `git.bergerhouse.net/luckberg/<image>` | CONFIRMED | Registry uses `{host}/{owner}/{image}` format [CITED: docs.gitea.com/usage/packages/container] |
| `docker/login-action@v3` + `docker/build-push-action@v6` resolve | ASSUMED | Referenced by absolute GitHub URL; VERIFY-ON-RUNNER (P-12) |
| Playwright `--with-deps` installs system deps without sudo | CONFIRMED for most cases | Playwright handles su internally; may fail if runner has no internet/apt access [CITED: playwright.dev/docs/ci] |
| `npx playwright install` does NOT cache browser binaries | CONFIRMED (deliberate) | Playwright explicitly recommends against caching browser binaries in CI [CITED: playwright.dev/docs/ci] |
---
@@ -261,7 +266,7 @@ jobs:
--health-retries=10
--health-start-period=30s
env:
DB_HOST: mariadb # service label name — Docker mode only
DB_HOST: mariadb # service label name — Docker mode only
DB_PORT: 3306
DB_USER: familysync
DB_PASSWORD: testpass
@@ -278,32 +283,32 @@ jobs:
```yaml
# Source: [ASSUMED — standard workaround for host-mode runners]
steps:
- name: Start MariaDB
run: |
docker run -d --name mariadb \
-e MARIADB_ROOT_PASSWORD=root \
-e MARIADB_DATABASE=familysync \
-e MARIADB_USER=familysync \
-e MARIADB_PASSWORD=testpass \
-p 3306:3306 \
mariadb:11
- name: Wait for MariaDB
run: |
deadline=$((SECONDS + 90))
until healthcheck_output=$(docker exec mariadb healthcheck.sh --connect --innodb_initialized 2>&1) \
&& [ $? -eq 0 ]; do
if [ $SECONDS -ge $deadline ]; then
echo "MariaDB did not become ready in time"
docker logs mariadb | tail -30
exit 1
fi
sleep 3
done
echo "MariaDB ready"
env:
DB_HOST: 127.0.0.1 # host-mode: service on Docker host reachable via localhost
DB_PORT: 3306
steps:
- name: Start MariaDB
run: |
docker run -d --name mariadb \
-e MARIADB_ROOT_PASSWORD=root \
-e MARIADB_DATABASE=familysync \
-e MARIADB_USER=familysync \
-e MARIADB_PASSWORD=testpass \
-p 3306:3306 \
mariadb:11
- name: Wait for MariaDB
run: |
deadline=$((SECONDS + 90))
until healthcheck_output=$(docker exec mariadb healthcheck.sh --connect --innodb_initialized 2>&1) \
&& [ $? -eq 0 ]; do
if [ $SECONDS -ge $deadline ]; then
echo "MariaDB did not become ready in time"
docker logs mariadb | tail -30
exit 1
fi
sleep 3
done
echo "MariaDB ready"
env:
DB_HOST: 127.0.0.1 # host-mode: service on Docker host reachable via localhost
DB_PORT: 3306
```
### Pattern 3: API Background Process
@@ -314,35 +319,35 @@ jobs:
```yaml
# Source: [ASSUMED — standard CI background-process pattern]
- name: Build API
run: pnpm --filter @familysync/api build
env:
NODE_ENV: development
- name: Build API
run: pnpm --filter @familysync/api build
env:
NODE_ENV: development
- name: Start API
run: |
NODE_ENV=development \
DEV_AUTH_BYPASS=true \
DB_HOST=${{ env.DB_HOST }} \
DB_USER=familysync \
DB_PASSWORD=testpass \
DB_NAME=familysync \
node apps/api/dist/index.js &
echo $! > /tmp/api.pid
echo "API PID: $(cat /tmp/api.pid)"
- name: Start API
run: |
NODE_ENV=development \
DEV_AUTH_BYPASS=true \
DB_HOST=${{ env.DB_HOST }} \
DB_USER=familysync \
DB_PASSWORD=testpass \
DB_NAME=familysync \
node apps/api/dist/index.js &
echo $! > /tmp/api.pid
echo "API PID: $(cat /tmp/api.pid)"
- name: Wait for API (:3000)
run: |
deadline=$((SECONDS + 60))
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
if [ $SECONDS -ge $deadline ]; then
echo "API did not start in time"
kill $(cat /tmp/api.pid) 2>/dev/null || true
exit 1
fi
sleep 2
done
echo "API ready"
- name: Wait for API (:3000)
run: |
deadline=$((SECONDS + 60))
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
if [ $SECONDS -ge $deadline ]; then
echo "API did not start in time"
kill $(cat /tmp/api.pid) 2>/dev/null || true
exit 1
fi
sleep 2
done
echo "API ready"
```
**Why `node apps/api/dist/index.js` not `pnpm dev:api`:** The `dev` script is `node --watch dist/index.js` — it needs a prior `pnpm --filter @familysync/api build` (`tsc`). Running via `node` directly (without `--watch`) is cleaner for CI since the file watcher is irrelevant. D-discretion covers this choice.
@@ -353,14 +358,14 @@ jobs:
```yaml
# Source: [ASSUMED — confirmed in project memory and PITFALLS section]
- name: Run DB migrations
run: pnpm --filter @familysync/api db:migrate
env:
DB_HOST: ${{ env.DB_HOST }}
DB_PORT: 3306
DB_USER: familysync
DB_PASSWORD: testpass
DB_NAME: familysync
- name: Run DB migrations
run: pnpm --filter @familysync/api db:migrate
env:
DB_HOST: ${{ env.DB_HOST }}
DB_PORT: 3306
DB_USER: familysync
DB_PASSWORD: testpass
DB_NAME: familysync
```
Migrations live at `apps/api/src/db/migrations/`. The `db:migrate` script calls `drizzle-kit migrate` which applies existing SQL files — safe because the schema SQL is already in the repo (from `generate` runs during development). No `generate` step needed in CI unless the schema changed in the same PR.
@@ -371,30 +376,30 @@ Migrations live at `apps/api/src/db/migrations/`. The `db:migrate` script calls
```yaml
# Source: [ASSUMED — based on playwright.config.ts and global-setup.ts already in repo]
- name: Install Playwright browsers
run: npx playwright install --with-deps webkit chromium
working-directory: apps/pwa
- name: Install Playwright browsers
run: npx playwright install --with-deps webkit chromium
working-directory: apps/pwa
- name: Run Playwright harness
run: pnpm test:e2e
env:
CI: true
PLAYWRIGHT_BASE_URL: http://localhost:5173
DEV_AUTH_BYPASS: "true"
NODE_ENV: development
DB_HOST: ${{ env.DB_HOST }}
DB_PORT: 3306
DB_USER: familysync
DB_PASSWORD: testpass
DB_NAME: familysync
- name: Run Playwright harness
run: pnpm test:e2e
env:
CI: true
PLAYWRIGHT_BASE_URL: http://localhost:5173
DEV_AUTH_BYPASS: 'true'
NODE_ENV: development
DB_HOST: ${{ env.DB_HOST }}
DB_PORT: 3306
DB_USER: familysync
DB_PASSWORD: testpass
DB_NAME: familysync
- name: Upload test artifacts
if: failure()
uses: https://github.com/ChristopherHX/gitea-upload-artifact@v4
with:
name: playwright-traces-${{ github.run_id }}
path: apps/pwa/test-results/
retention-days: 14
- name: Upload test artifacts
if: failure()
uses: https://github.com/ChristopherHX/gitea-upload-artifact@v4
with:
name: playwright-traces-${{ github.run_id }}
path: apps/pwa/test-results/
retention-days: 14
```
**Note on `working-directory` for playwright install:** `npx playwright install` must be run from the package root where `@playwright/test` is installed — `apps/pwa/`. [ASSUMED]
@@ -403,36 +408,36 @@ Migrations live at `apps/api/src/db/migrations/`. The `db:migrate` script calls
```yaml
# Source: [ASSUMED — based on Gitea container registry docs and forum]
publish:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
publish:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- name: Compute image tags
id: tags
run: |
SHORT_SHA=${GITHUB_SHA:0:7}
MILESTONE="v1.1" # read from PROJECT.md in executor if preferred
echo "latest=git.bergerhouse.net/luckberg/familysync-api:latest" >> $GITHUB_OUTPUT
echo "sha_tag=git.bergerhouse.net/luckberg/familysync-api:${MILESTONE}-${SHORT_SHA}" >> $GITHUB_OUTPUT
- name: Compute image tags
id: tags
run: |
SHORT_SHA=${GITHUB_SHA:0:7}
MILESTONE="v1.1" # read from PROJECT.md in executor if preferred
echo "latest=git.bergerhouse.net/luckberg/familysync-api:latest" >> $GITHUB_OUTPUT
echo "sha_tag=git.bergerhouse.net/luckberg/familysync-api:${MILESTONE}-${SHORT_SHA}" >> $GITHUB_OUTPUT
- name: Docker login
run: |
echo "${{ secrets.GITEA_REGISTRY_PAT }}" | \
docker login git.bergerhouse.net \
--username luckberg \
--password-stdin
- name: Docker login
run: |
echo "${{ secrets.GITEA_REGISTRY_PAT }}" | \
docker login git.bergerhouse.net \
--username luckberg \
--password-stdin
- name: Build and push
run: |
docker build \
--target production \
-t ${{ steps.tags.outputs.latest }} \
-t ${{ steps.tags.outputs.sha_tag }} \
.
docker push ${{ steps.tags.outputs.latest }}
docker push ${{ steps.tags.outputs.sha_tag }}
- name: Build and push
run: |
docker build \
--target production \
-t ${{ steps.tags.outputs.latest }} \
-t ${{ steps.tags.outputs.sha_tag }} \
.
docker push ${{ steps.tags.outputs.latest }}
docker push ${{ steps.tags.outputs.sha_tag }}
```
**Secret name:** `GITEA_REGISTRY_PAT` — a PAT with `write:package` (and `read:package`) scope created by `luckberg`. Must be added to the repo secrets in Gitea UI before the publish job runs.
@@ -498,20 +503,20 @@ The orchestration order is critical. All of the following must be sequential wit
### Registry Details
| Property | Value | Source |
|----------|-------|--------|
| Registry host | `git.bergerhouse.net` | [CONFIRMED from git remote URL] |
| Image path format | `git.bergerhouse.net/{owner}/{image}` | [CITED: docs.gitea.com/usage/packages/container] |
| Image name | `git.bergerhouse.net/luckberg/familysync-api` | [ASSUMED — owner = `luckberg`, image name = `familysync-api`] |
| Auth method | PAT with `write:package` scope | [CONFIRMED: Gitea forum, registry docs] |
| Token variable | `secrets.GITEA_REGISTRY_PAT` | [ASSUMED — name chosen by planner/executor] |
| `docker login` approach | `echo $PAT \| docker login git.bergerhouse.net --username luckberg --password-stdin` | [CONFIRMED: Pitfall 13] |
| Property | Value | Source |
| ----------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------- |
| Registry host | `git.bergerhouse.net` | [CONFIRMED from git remote URL] |
| Image path format | `git.bergerhouse.net/{owner}/{image}` | [CITED: docs.gitea.com/usage/packages/container] |
| Image name | `git.bergerhouse.net/luckberg/familysync-api` | [ASSUMED — owner = `luckberg`, image name = `familysync-api`] |
| Auth method | PAT with `write:package` scope | [CONFIRMED: Gitea forum, registry docs] |
| Token variable | `secrets.GITEA_REGISTRY_PAT` | [ASSUMED — name chosen by planner/executor] |
| `docker login` approach | `echo $PAT \| docker login git.bergerhouse.net --username luckberg --password-stdin` | [CONFIRMED: Pitfall 13] |
### Image Tag Strategy (D-04)
| Tag | Example | Purpose |
|-----|---------|---------|
| `:latest` | `git.bergerhouse.net/luckberg/familysync-api:latest` | Moving pointer for easy pulls |
| Tag | Example | Purpose |
| ------------------------- | ---------------------------------------------------------- | ----------------------------- |
| `:latest` | `git.bergerhouse.net/luckberg/familysync-api:latest` | Moving pointer for easy pulls |
| `:<milestone>-<shortsha>` | `git.bergerhouse.net/luckberg/familysync-api:v1.1-4303a1b` | Immutable, rollback-traceable |
The milestone string `v1.1` is hardcoded in the workflow as `MILESTONE="v1.1"` for now (reading it from `PROJECT.md` dynamically adds complexity with minimal benefit). The executor can make it a workflow-level env var for easy updates.
@@ -606,14 +611,14 @@ This is because the Dockerfile copies the pnpm workspace manifest and lockfile f
## Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| MariaDB health check | Custom TCP-ping script | `healthcheck.sh --connect --innodb_initialized` | Ships in the `mariadb:11` image; handles InnoDB init correctly |
| Upload artifacts to Gitea | curl to Gitea API | `ChristopherHX/gitea-upload-artifact@v4` | upload-artifact v4 protocol is complex; the fork wraps it correctly |
| Docker registry auth | Hand-rolled auth header | `docker login --password-stdin` | Prevents PAT from appearing in process list |
| Playwright browser install | Manual apt package list | `npx playwright install --with-deps` | Playwright knows the correct system deps for each browser version |
| API readiness check | Arbitrary sleep | curl retry loop against `/health` | Sleep is flaky; a deterministic health poll is both faster and correct |
| CI MariaDB in host mode | `mysqladmin` ping loop | `docker exec mariadb healthcheck.sh --connect --innodb_initialized` | Avoids mysqladmin-missing error; reuses same logic as Docker healthcheck |
| Problem | Don't Build | Use Instead | Why |
| -------------------------- | ----------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| MariaDB health check | Custom TCP-ping script | `healthcheck.sh --connect --innodb_initialized` | Ships in the `mariadb:11` image; handles InnoDB init correctly |
| Upload artifacts to Gitea | curl to Gitea API | `ChristopherHX/gitea-upload-artifact@v4` | upload-artifact v4 protocol is complex; the fork wraps it correctly |
| Docker registry auth | Hand-rolled auth header | `docker login --password-stdin` | Prevents PAT from appearing in process list |
| Playwright browser install | Manual apt package list | `npx playwright install --with-deps` | Playwright knows the correct system deps for each browser version |
| API readiness check | Arbitrary sleep | curl retry loop against `/health` | Sleep is flaky; a deterministic health poll is both faster and correct |
| CI MariaDB in host mode | `mysqladmin` ping loop | `docker exec mariadb healthcheck.sh --connect --innodb_initialized` | Avoids mysqladmin-missing error; reuses same logic as Docker healthcheck |
---
@@ -621,25 +626,25 @@ This is because the Dockerfile copies the pnpm workspace manifest and lockfile f
### Test Framework
| Property | Value |
|----------|-------|
| API unit tests | Vitest 4.1.x, config at `apps/api/vitest.config.ts` |
| API test command | `pnpm --filter @familysync/api test` (= `vitest run`) |
| PWA unit tests | Vitest (same framework), command `pnpm --filter @familysync/pwa test` |
| E2E harness | `@playwright/test` 1.60.0, config at `apps/pwa/playwright.config.ts` |
| E2E command | `pnpm test:e2e` (from root) = `pnpm --filter @familysync/pwa test:e2e` = `playwright test` |
| Property | Value |
| ---------------- | ------------------------------------------------------------------------------------------ |
| API unit tests | Vitest 4.1.x, config at `apps/api/vitest.config.ts` |
| API test command | `pnpm --filter @familysync/api test` (= `vitest run`) |
| PWA unit tests | Vitest (same framework), command `pnpm --filter @familysync/pwa test` |
| E2E harness | `@playwright/test` 1.60.0, config at `apps/pwa/playwright.config.ts` |
| E2E command | `pnpm test:e2e` (from root) = `pnpm --filter @familysync/pwa test:e2e` = `playwright test` |
### Phase Requirements → Test Map
| Req ID | Behavior | Test Type | Automated Command | Exists? |
|--------|----------|-----------|-------------------|---------|
| CI-01 | PR gate triggers on `pull_request → main` | workflow trigger test | Push a PR and observe | After W0 |
| CI-01 | lint passes | CI step | `pnpm lint` | ✅ |
| CI-01 | typecheck both apps passes | CI step | `pnpm typecheck` | ✅ |
| CI-01 | unit tests pass | CI step | `pnpm test` | ✅ |
| CI-01 | API integration tests pass with MariaDB | CI step | `pnpm --filter @familysync/api test` + DB env | ✅ |
| CI-01 | Playwright harness passes in CI | CI step | `pnpm test:e2e` with CI=true | ✅ (Phase 7 specs) |
| CI-02 | Docker image pushed to Gitea registry on merge | CI step | `docker pull git.bergerhouse.net/luckberg/familysync-api:latest` | After W2 |
| Req ID | Behavior | Test Type | Automated Command | Exists? |
| ------ | ---------------------------------------------- | --------------------- | ---------------------------------------------------------------- | ------------------ |
| CI-01 | PR gate triggers on `pull_request → main` | workflow trigger test | Push a PR and observe | After W0 |
| CI-01 | lint passes | CI step | `pnpm lint` | ✅ |
| CI-01 | typecheck both apps passes | CI step | `pnpm typecheck` | ✅ |
| CI-01 | unit tests pass | CI step | `pnpm test` | ✅ |
| CI-01 | API integration tests pass with MariaDB | CI step | `pnpm --filter @familysync/api test` + DB env | ✅ |
| CI-01 | Playwright harness passes in CI | CI step | `pnpm test:e2e` with CI=true | ✅ (Phase 7 specs) |
| CI-02 | Docker image pushed to Gitea registry on merge | CI step | `docker pull git.bergerhouse.net/luckberg/familysync-api:latest` | After W2 |
### Sampling Rate
@@ -658,42 +663,44 @@ This is because the Dockerfile copies the pnpm workspace manifest and lockfile f
### Applicable ASVS Categories
| ASVS Category | Applies | Standard Control |
|---------------|---------|-----------------|
| V2 Authentication | no | Auth is not modified by this phase |
| V3 Session Management | no | Not modified |
| V4 Access Control | no | Not modified |
| V5 Input Validation | no | No new API endpoints |
| V6 Cryptography | yes (marginal) | PAT stored as Gitea repository secret; never in workflow YAML |
| ASVS Category | Applies | Standard Control |
| --------------------- | -------------- | ------------------------------------------------------------- |
| V2 Authentication | no | Auth is not modified by this phase |
| V3 Session Management | no | Not modified |
| V4 Access Control | no | Not modified |
| V5 Input Validation | no | No new API endpoints |
| V6 Cryptography | yes (marginal) | PAT stored as Gitea repository secret; never in workflow YAML |
### Known Threat Patterns for CI/Docker
| Pattern | STRIDE | Standard Mitigation |
|---------|--------|---------------------|
| PAT in workflow YAML | Information Disclosure | Store as `secrets.GITEA_REGISTRY_PAT`; never echo or print |
| Docker socket mount (if runner uses it) | Elevation of Privilege | Known risk; accepted for Unraid self-hosted runner per Gitea docs |
| DB creds in CI env | Information Disclosure | Use throwaway test creds (not production DB_PASSWORD); never reuse production secrets |
| `DEV_AUTH_BYPASS=true` in CI | Spoofing | Only active in harness job; never bleeds to publish job; global-setup guard refuses `NODE_ENV=production` |
| Pattern | STRIDE | Standard Mitigation |
| --------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------- |
| PAT in workflow YAML | Information Disclosure | Store as `secrets.GITEA_REGISTRY_PAT`; never echo or print |
| Docker socket mount (if runner uses it) | Elevation of Privilege | Known risk; accepted for Unraid self-hosted runner per Gitea docs |
| DB creds in CI env | Information Disclosure | Use throwaway test creds (not production DB_PASSWORD); never reuse production secrets |
| `DEV_AUTH_BYPASS=true` in CI | Spoofing | Only active in harness job; never bleeds to publish job; global-setup guard refuses `NODE_ENV=production` |
---
## Environment Availability
| Dependency | Required By | Available | Version | Fallback |
|------------|------------|-----------|---------|----------|
| Gitea instance | All | ✓ | 1.26.2 | — |
| Gitea Actions runner | All | Unknown — 0 registered | Unknown | Must register runner before Phase 8 can proceed |
| Docker on runner | service containers, publish | Unknown | Unknown | Phase 8 is blocked without Docker on runner |
| Node.js 22 on runner | fast-checks, integration | Unknown | Unknown | `actions/setup-node@v4` (probe P-01/P-08) |
| pnpm 11 on runner | All | Unknown | Unknown | `corepack enable pnpm` (probe P-02) |
| Internet access from runner | actions resolution, npm, Playwright install | Unknown | — | Probe P-07 confirms |
| Gitea registry PAT | CI-02 | Not yet created | — | Operator must create before publish job |
| Dependency | Required By | Available | Version | Fallback |
| --------------------------- | ------------------------------------------- | ---------------------- | ------- | ----------------------------------------------- |
| Gitea instance | All | ✓ | 1.26.2 | — |
| Gitea Actions runner | All | Unknown — 0 registered | Unknown | Must register runner before Phase 8 can proceed |
| Docker on runner | service containers, publish | Unknown | Unknown | Phase 8 is blocked without Docker on runner |
| Node.js 22 on runner | fast-checks, integration | Unknown | Unknown | `actions/setup-node@v4` (probe P-01/P-08) |
| pnpm 11 on runner | All | Unknown | Unknown | `corepack enable pnpm` (probe P-02) |
| Internet access from runner | actions resolution, npm, Playwright install | Unknown | — | Probe P-07 confirms |
| Gitea registry PAT | CI-02 | Not yet created | — | Operator must create before publish job |
**Missing dependencies with no fallback:**
- Gitea Actions runner on Unraid (0 registered) — must be installed and registered before any CI runs
- Docker on runner — if absent, service containers and publish job both fail; no CI-relevant fallback
**Missing dependencies with fallback:**
- Node.js 22 — `actions/setup-node@v4` installs it
- pnpm — `corepack enable pnpm` resolves it
@@ -701,18 +708,18 @@ This is because the Dockerfile copies the pnpm workspace manifest and lockfile f
## Assumptions Log
| # | Claim | Section | Risk if Wrong |
|---|-------|---------|---------------|
| A1 | Runner is configured in Docker mode (not host mode) | Service Containers, Dev-Stack Bring-Up | Entire `services:` approach breaks; must pivot to `docker run -d` pattern |
| A2 | `actions/checkout@v4` and `actions/setup-node@v4` resolve via DEFAULT_ACTIONS_URL=github.com | Standard Stack | CI fails at checkout; need to mirror or use absolute URLs |
| A3 | `actions/cache@v4` works without timeout in this runner's Docker network setup | Standard Stack | Cache steps time out; must remove and accept full install on every run |
| A4 | `ChristopherHX/gitea-upload-artifact@v4` uploads successfully to Gitea 1.26.2 | Standard Stack | No artifact upload on failure; lose traces; manual debug only |
| A5 | `docker/login-action@v3` and `docker/build-push-action@v6` resolve from GitHub | Standard Stack | Must use shell-level `docker login` + `docker build`/`docker push` instead |
| A6 | `reporter: 'github'` produces invisible output in Gitea (not rendered as annotations) | Anti-Patterns | If Gitea DOES render them, the `--reporter=list` override is unnecessary but harmless |
| A7 | `GITHUB_SHA` is available in Gitea Actions workflows | Image Tagging | Cannot compute short SHA via `${GITHUB_SHA:0:7}`; must use `git rev-parse --short HEAD` |
| A8 | Image name follows `git.bergerhouse.net/luckberg/familysync-api` convention | Registry Details | Push fails with 404; image name may need adjustment |
| A9 | MariaDB `--health-start-period=30s` is sufficient for initialization | Patterns | Flaky health-check failures on slow runners; tune upward |
| A10 | Playwright install `--with-deps` succeeds without root on runner | Dev-Stack Bring-Up | WebKit missing system libs; jobs fail with browser launch error |
| # | Claim | Section | Risk if Wrong |
| --- | -------------------------------------------------------------------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------- |
| A1 | Runner is configured in Docker mode (not host mode) | Service Containers, Dev-Stack Bring-Up | Entire `services:` approach breaks; must pivot to `docker run -d` pattern |
| A2 | `actions/checkout@v4` and `actions/setup-node@v4` resolve via DEFAULT_ACTIONS_URL=github.com | Standard Stack | CI fails at checkout; need to mirror or use absolute URLs |
| A3 | `actions/cache@v4` works without timeout in this runner's Docker network setup | Standard Stack | Cache steps time out; must remove and accept full install on every run |
| A4 | `ChristopherHX/gitea-upload-artifact@v4` uploads successfully to Gitea 1.26.2 | Standard Stack | No artifact upload on failure; lose traces; manual debug only |
| A5 | `docker/login-action@v3` and `docker/build-push-action@v6` resolve from GitHub | Standard Stack | Must use shell-level `docker login` + `docker build`/`docker push` instead |
| A6 | `reporter: 'github'` produces invisible output in Gitea (not rendered as annotations) | Anti-Patterns | If Gitea DOES render them, the `--reporter=list` override is unnecessary but harmless |
| A7 | `GITHUB_SHA` is available in Gitea Actions workflows | Image Tagging | Cannot compute short SHA via `${GITHUB_SHA:0:7}`; must use `git rev-parse --short HEAD` |
| A8 | Image name follows `git.bergerhouse.net/luckberg/familysync-api` convention | Registry Details | Push fails with 404; image name may need adjustment |
| A9 | MariaDB `--health-start-period=30s` is sufficient for initialization | Patterns | Flaky health-check failures on slow runners; tune upward |
| A10 | Playwright install `--with-deps` succeeds without root on runner | Dev-Stack Bring-Up | WebKit missing system libs; jobs fail with browser launch error |
---
@@ -748,6 +755,7 @@ This is because the Dockerfile copies the pnpm workspace manifest and lockfile f
## Sources
### Primary (HIGH confidence)
- [Gitea container registry docs](https://docs.gitea.com/usage/packages/container) — registry host format, image naming, PAT auth requirement
- [Gitea Actions comparison page](https://docs.gitea.com/usage/actions/comparison) — what is and isn't supported vs GitHub Actions
- [Gitea Actions quickstart](https://docs.gitea.com/usage/actions/quickstart) — `.gitea/workflows/` location confirmed
@@ -762,11 +770,13 @@ This is because the Dockerfile copies the pnpm workspace manifest and lockfile f
- [Playwright CI docs](https://playwright.dev/docs/ci) — `--with-deps` install, no-cache recommendation
### Secondary (MEDIUM confidence)
- [firefart.at: MySQL service with GitHub Actions](https://firefart.at/post/using-mysql-service-with-github-actions/) — service container pattern when job runs on host (port mapping, 127.0.0.1)
- [Gitea forum: service container not starting](https://forum.gitea.com/t/service-container-not-starting/9287) — evidence service containers are unreliable in some configurations; unresolved in forum
- Various community blog posts on Gitea Actions (chrisliebaer, botmonster) — cross-check on action resolution and registry
### Tertiary (LOW confidence / ASSUMED)
- All items tagged `[ASSUMED]` in this document — confirmed via training knowledge + community reports but not directly verified against the Unraid act_runner; confirmed by runner-probe
---
@@ -774,6 +784,7 @@ This is because the Dockerfile copies the pnpm workspace manifest and lockfile f
## Metadata
**Confidence breakdown:**
- Gitea Actions workflow syntax: HIGH — standard GitHub Actions YAML; confirmed supported
- Service containers: MEDIUM — Docker mode works per docs/act design; host mode does not; runner mode unknown
- MariaDB healthcheck: HIGH — confirmed in official docs and multiple issue threads