# HANDOFF — SciAgent / ImageHub
_Updated: 2026-06-29 (session-end — Gitea Actions CI/CD pipeline) · branch `main` · **40 commits LOCAL/unpushed** · 🟢 HMW-mode OFF_

## TL;DR
- Stood up the repo's **first CI/CD** — **Gitea Actions** on the self-hosted box `103.149.170.102:3000` (Gitea 1.26.2). Previously deploy was manual Docker Compose, **no CI**.
- Pipeline `.gitea/workflows/ci-cd.yml` = **backend** (per-file pytest + throwaway Postgres) · **frontend** (typecheck/build/vitest across workspaces) · **deploy** (host-mode `docker compose up -d` on push to main). Local commit `c2e869b`.
- **One hard gate left: NO act_runner is installed** → all runs queue, nothing executes/deploys. User must run `scripts/setup-gitea-runner.sh` on the box (I have no SSH there).

## Shipped this session — commit `c2e869b` (local only)
- **`.gitea/workflows/ci-cd.yml`** — 3 jobs. backend: `pip install be0/requirements-dev.txt` then **pytest PER FILE** (loop) vs a `postgres:16-alpine` service (per-file avoids asyncpg cross-module event-loop contamination, [[be0-test-harness-reality]]). frontend: node 20, `npm ci`, `npm run typecheck` + `build`, `npm test --workspaces --if-present` (vitest in shared/investigator/publisher). deploy (`runs-on: deploy`, host): clone/reset **persistent `/srv/sciagent`** (NOT ephemeral — prod compose bind-mounts `./assets/minio-data`+`./be0`), write `.env` from secret `PROD_ENV`, `deploy-prod.sh --no-pull` + `check-prod-stack.sh`.
- **`be0/requirements-dev.txt`** — pytest + pytest-asyncio (neither was pinned).
- **`scripts/setup-gitea-runner.sh`** — act_runner 0.2.11 bootstrap (Docker+compose+node+systemd, labels `ci:docker://catthehacker/ubuntu:act-22.04,deploy:host`). ⚠️ runner registration token baked in (already public on Gitea mirror; rotatable).
- **Done via Gitea admin API (keychain user `oneness`, is_admin):** enabled Actions unit · stored secret `PROD_ENV` (valid prod `.env`, `PUBLIC_HOST=103.149.170.102`, fresh hex PG/MinIO pw + b64 JWT, `AUTH_MAIL_LOG_ONLY=1` placeholder) · minted runner token · pushed workflow+reqs to Gitea (workflow `state: active`).
- **Mirror refreshed** to current code: Gitea `main` now a **1212-file clean snapshot** (was 2026-06-14 / 965 files; now incl. all 4 monorepo FEs + the workflow). Leak-checked clean. Detail: [[gitea-cicd-pipeline]], [[gitea-mirror-and-tracked-secrets]].

## Current state
- Migrations 001…027 · 6 be0 routers · monorepo 4 FEs (`fe0` legacy standalone) + `@ump/shared`.
- Gitea workflow active; **runners online: 0**. PROD_ENV set; SMTP unfilled.
- Verify this session = artifact-level only (bash -n, pip syntax, YAML parse) — **no app code changed**, so BE/FE suites not re-run.

## Next — P1 (start here)
1. **Install the runner** (user, needs root on the box — I have no SSH): `curl -fsSL http://103.149.170.102:3000/tlam89/sciagent/raw/branch/main/scripts/setup-gitea-runner.sh | sudo bash`. Then ping me → I verify it's Online (API) + watch the first run (backend→frontend→deploy), report PASS/FAIL with logs.
2. **Fill SMTP** in `PROD_ENV` secret (else OTP/reset mail only logs). Give me `SMTP_*` → I update the secret via API.
3. (Decision) fe0 vs frontend_user port role — deferred this session (fe0 NOT deployed; user confirmed it was a slip).

## Open threads / risks
- 🔴 **NO runner = pipeline does nothing.** This is the blocker for all execution/deploy.
- 🔴 **40 commits LOCAL/unpushed to origin** — push to GitHub origin BLOCKED (history has `.env` secrets + 1.8 GB PII `assets/` → rotate + `git filter-repo` first). Gitea mirror is current; origin is not. Do NOT `git push origin`.
- First deploy = **fresh empty stack** (new Postgres via initdb migrations, empty MinIO) — no dev data carried over (assets/ excluded by design).
- Caught near-miss (documented): `git add -A` + `:(exclude)assets` did NOT exclude → leak-check stopped it pre-push. Reliable mirror method now in [[gitea-mirror-and-tracked-secrets]].
- CLAUDE.md still STALE (says "no CI"; says migr 014 / 3 routers / `fe0`).

## Quick commands
- Gitea API (admin): `CRED=$(printf 'protocol=http\nhost=103.149.170.102:3000\n\n'|git credential fill); U=…;P=…` then `curl -u $U:$P http://103.149.170.102:3000/api/v1/repos/tlam89/sciagent/actions/runners` (check online) / `…/actions/tasks` (runs).
- Runner install (on box, root): see P1 #1.
- Re-mint runner token: `curl -s -X POST -u $U:$P http://103.149.170.102:3000/api/v1/repos/tlam89/sciagent/actions/runners/registration-token`.

## Reality flags
- CI lives on **Gitea** (`103.149.170.102:3000`), NOT GitHub. Push to Gitea = clean orphan snapshot convention (excl `.env`/`assets`/`.claude`/`CLAUDE.md`). Origin (GitHub) push stays blocked.
- **Push ≠ deploy.** Even with the runner up, deploy only fires on push to Gitea `main`. This session = local commit only; nothing deployed, nothing pushed to origin.
- 🟢 HMW-mode OFF. No sub-agents spawned this session (main-agent + API + git only).
