diff --git a/BUILD-AI-CONTAINERS.md b/BUILD-AI-CONTAINERS.md index d03c45a..97974bf 100644 --- a/BUILD-AI-CONTAINERS.md +++ b/BUILD-AI-CONTAINERS.md @@ -582,7 +582,7 @@ Deployed early, deliberately: from here on, every experiment is traced. **Tasks** 1. `db/004_doc_uploads.sql` — the `doc_uploads` queue, the `live_documents` view, and the `uploads_rw` / `ingest_rw` roles. `agent_ro` gains nothing. Then `db/005_doc_actions.sql` — the `doc_actions` trail, the `withdrawn_documents` view, the column-level `superseded` grant and the withdraw-only trigger. -2. `/datadisk/ai-docs-inbox` — the writable staging area. **Check `df -h /datadisk` first.** Never on `/`. +2. `/datadisk/ai-docs-inbox` and `/datadisk/ai-docs-withdrawn` — the writable staging and archive areas. **Check `df -h /datadisk` first.** Never on `/`. Create them owned by uid 10002 *before* uncommenting the `ai-api` volume block in `compose/ai-compose.yml`, and drop `profiles: [worker]` from `ai-docs-worker` in the same commit that adds `worker.py` — both are guarded so that a deploy from `main` today starts nothing that does not exist yet. 3. `ai-api`: the `/docs/*` router — upload, list, detail, approve, reject, withdraw, restore, purge. Identity comes from Authelia's forwarded headers, never from the request body. Approval requires the publisher group and is refused without it, whatever Authelia allowed through. 4. `ai-docs-worker` — the `ingest` image with `worker.py` as its entrypoint. Long-running, `ai-internal` only, no published port. Pre-scans uploads for a header proposal, ingests approved ones, and completes withdrawals, restores and purges. `/datadisk/ai-docs-withdrawn` is created and mounted with the other two. - **While you are in `ingest.py`: make `mark_superseded()` move the superseded file out of the tree as well.** Without that, `--all` re-ingests it as live — see the defect note in section 16.10. diff --git a/compose/ai-compose.yml b/compose/ai-compose.yml index 8367b1e..03c3691 100644 --- a/compose/ai-compose.yml +++ b/compose/ai-compose.yml @@ -93,11 +93,19 @@ services: networks: [ai-internal, proxy] env_file: - /home/azureuser/ai/api.env # 0600, not in Git - volumes: - # Phase 9 - the upload inbox, and the ONLY writable path this container - # has. Deliberately not /datadisk/ai-docs: a file that has been uploaded - # but not yet approved must not be visible to `ai-ingest --all`. - - /datadisk/ai-docs-inbox:/inbox + # Phase 9 - the upload inbox, and the ONLY writable path this container + # gets. Deliberately not /datadisk/ai-docs: a file that has been uploaded + # but not yet approved must not be visible to `ai-ingest --all`. + # + # COMMENTED OUT UNTIL PHASE 9, on the same principle as caddy/ai-routes.caddy: + # add each piece at the phase that needs it. Docker creates a missing bind + # source as a ROOT-OWNED directory, and ai-api does not run as root - so + # deploying this before the directory exists gives you an inbox the API + # cannot write to, on the growing disk of a live shared host. + # Create it first, then uncomment: + # sudo install -d -o 10002 -g 10002 /datadisk/ai-docs-inbox + # volumes: + # - /datadisk/ai-docs-inbox:/inbox healthcheck: test: ["CMD", "python", "-m", "app_healthcheck"] interval: 30s @@ -171,6 +179,11 @@ services: dockerfile: Dockerfile image: yau/ai-ingest:local container_name: ai-docs-worker + # Kept out of `up -d` by the profile, exactly as ai-ingest is. worker.py + # does not exist yet, so an unguarded service here would crash-loop on a + # live shared host. Drop the profile in the same commit that adds the file. + # docker compose -f ~/ai-compose.yml --profile worker up -d ai-docs-worker + profiles: [worker] restart: unless-stopped depends_on: pg-ai: