# coachpapa — agents.md

Project constitution. Inherits from `/srv/CLAUDE.md` (server-scope) and
`/srv/apps/CLAUDE.md` (tenant-scope, interim). Only rules meeting the
admission rubric (failure-backed, tool-enforceable, decision-encoding, or
triggerable) belong here — no generic advice a model already applies.

Model-independence: this file is the source of truth. `CLAUDE.md` is a
symlink to it so Claude Code loads the same content.

## Read first (in order)
1. **`brief.md`** — what this project is; stack decision; source material.
2. **`.logs/planning/coachpapa-prototype-2026.md`** — the build plan. Locked
   decisions, data model, milestones, gates, out-of-scope list.
3. **`/srv/projects/standards/README.md`** — server-wide coding standards.
   Stack is decided, so the dispatch is: `coding.md` → `react.md` →
   `frontend.md`. All three apply.
4. **`.logs/handoff.md`** — newest-first; latest session state.

## Project-specific rules

- **The name picker is not authentication, and must never become it.** Through
  P1 the app shows a technician name picker for *attribution only*, behind the
  id-auth gate that actually protects the site. It guards nothing and stores no
  credential. Do not add a password field, a session store, or a users table to
  it — `coding.md` forbids app-built login, and real identity arrives at P2 via
  Pattern B. All identity resolution goes through `app/services/identity.py`
  returning one `CurrentActor`; routers never read headers or session state
  directly, which is what keeps the P2 swap a one-file change.

- **Do not register the BW app-client until the app draws its own BW login.**
  `srv-gw app-client-register` **auto-ungates the host** — registering early
  makes the site briefly public. To hide it mid-build use
  `srv-gw id-site-set --host coachpapa.demoing.info --lan-only true`, never a
  password gate (a gate would hijack the app's own `/login`).

- **Code lives in `main/`; runtime state does not.** `main/` is code only
  (v8 §3.2). Uploaded media (`data/`) and DB dumps (`backups/`) sit at the
  workspace root and mount into the container. The compose file, `.env`,
  `.project.json` stay at the workspace root because `srv-gw deploy` requires
  the compose file there — a documented deviation, not an accident.

- **Uploaded photos are never served from a static directory.** They are plant
  floor images behind a gated site; they go through an authenticated app route
  or nowhere. A guessable media URL is a leak.

- **The out-of-scope list in the plan (§9) is binding.** Mike's documents reach
  for AI part-matching, label printing, vendor report automation, ERP hooks and
  PLC triggers. Scope creep is this project's single largest risk. If asked for
  one of those, the answer is "after the capture loop is proven."

## Operational
- **Type:** custom, deployed via the gateway. Port `172.17.0.1:3140`.
  Domain `coachpapa.demoing.info`, id-auth gated (shared password).
- **Manage:** `srv-gw {deploy,restart,logs,status} --project coachpapa`.
- **Deploy semantics:** this stack has a Docker build step, so deploys are
  `srv-gw deploy --project coachpapa --build` (a couple of minutes), not an
  instant restart. `srv-gw restart` only restarts the existing image.
- **Deploy confirmation:** `main/VERSION` is surfaced at `/api/meta` and in the
  UI footer. Every change bumps it and adds a newest-first `CHANGELOG.md` line.
  The visible version is how you confirm a deploy actually landed — there is no
  built-vs-deployed drift check covering tenant apps.
- **Write access:** `coachpapa-dev` Unix group (setgid + default ACL). If a
  fresh terminal session can't write to project files, run
  `newgrp coachpapa-dev` once. New files need `chmod 664` / dirs `775` (or
  `srv-gw fix-permissions --project coachpapa`) so the service user can read them.
- **Create new subdirectories under `sg coachpapa-dev`.** If the session's
  supplementary groups don't include `coachpapa-dev`, the kernel **silently
  strips the setgid bit** on `chmod 2775` — `chmod -v` reports success while
  `stat` still shows `775`, and every file created underneath then lands with
  group `rian` instead of `coachpapa-dev`. Use
  `sg coachpapa-dev -c 'mkdir -p <dir> && chmod 2775 <dir>'`. `srv-gw
  fix-permissions` fixes the project root but does not recurse setgid into new
  subdirectories. (Hit on 2026-07-30 creating `main/`.)
- **Gates are scripts.** A milestone is done when `main/scripts/gate-pN.sh`
  exits 0 — never when it looks done.

## Layout
```
/srv/apps/coachpapa/          # workspace — docs and runtime state
├── brief.md  agents.md  CLAUDE.md -> agents.md
├── .logs/{planning/, handoff.md, diary.md}
├── .memory/  .archive/  notes/
├── docker-compose.yml        # app + postgres sidecar; build context ./main
├── .env                      # gateway-managed (PROJECT_NAME, PORT) — do not edit
├── .app.env                  # app secrets, mode 600
├── data/                     # uploaded media (not web-served)
├── backups/                  # nightly pg_dump
└── main/                     # the instance — code only
    ├── VERSION  CHANGELOG.md  Makefile  Dockerfile
    ├── alembic/  app/{routers,services,models}/  frontend/  scripts/
```

The v8 target is instance-granular gateway registration (`create-project`
workspace + `add-instance` deliverable, T3.1). Until that lands the registry
knows only the project, and the compose file must sit at the workspace root.
The folder shape above is already correct, so T3.1 is a re-registration, not a
restructure. Full deviation ledger: the plan, §3.

## Standing tasks (delete when done)
- **Pattern B auth is not wired.** Identity is still the name picker; the swap
  point is `app/services/identity.py`. Read plan §6 before starting, especially
  the registration-ordering trap.
- **`main/scripts/backup-db.sh` is not scheduled.** It needs a crontab line in
  rian's own shell; the script header carries it. Until then there is no
  automatic dump.
- Assignment UI: the API accepts `assigned_to_id`, nothing calls it yet.
