Commit graph

2 commits

Author SHA1 Message Date
Claude
3262f1a050 Give ingestion a role that can write
ai-ingest built its DSN from PGUSER/PGPASSWORD and takes its environment from
~/ai/api.env, where PGUSER=agent_ro - SELECT and nothing else, deliberately,
because it is what the answer path runs as. So

    docker compose -f ~/ai-compose.yml run --rm ai-ingest --all

connected as a role that cannot INSERT INTO doc_chunks, and Phase 3 was
unrunnable exactly as the README documents it. Nothing had reached Phase 3 yet,
so nobody had hit it.

The failure would also have landed at the worst possible moment: at the final
INSERT, after the Docling parse, after a person had typed the header
confirmations for every file, and after a billed embeddings call - with a
permission error naming no cause.

  - ingest_rw moves to 003_roles.sql, at Phase 1 with the other roles. It is
    not a Phase 9 concept; ingestion has needed a writing role since Phase 3
    and never had one. 004 keeps only its grants on the upload queue, and its
    idempotent role creation so it still applies to an older database.
  - ingest.py connects through INGEST_DB_USER / INGEST_DB_PASSWORD, falling
    back to PGUSER only for a local shell where one pair is set.
  - require_write_access() checks INSERT, UPDATE and DELETE on doc_chunks
    before anything is parsed or embedded, and fails with the fix in the
    message. Falling back to PGUSER cannot smuggle agent_ro past it.
  - Keyword connection parameters rather than a URL: a generated password
    containing @ or / breaks a DSN string silently.
  - A missing doc_chunks now says "apply 001_schema.sql" instead of raising
    UndefinedTable.

Phase 1's gate gains the check that would have caught this: ingest_rw must be
able to write doc_chunks. An ingestion role that cannot write is the same class
of failure as an API role that can - it just surfaces two phases later.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:45:20 +10:00
Claude
98083cd8d6 Design Phase 9 - operator document management
Operators cannot add a document today: ingestion is CLI-only, needs a host
login and a TTY for confirm_header(), /datadisk/ai-docs is mounted read-only,
ai-api has no identity, and nothing in the stack has a role that can write
doc_chunks. This designs the way in, the way out, and control over what is in
the retrieval pool. Design and schema only - no router, worker or UI code yet.

Documents in (16.1-16.9, db/004):
  upload -> pre-scan -> review -> approve -> published, with the header
  confirmation moved from a terminal prompt to a review screen and recorded
  rather than discarded. A CHECK constraint refuses an approved row without a
  confirmed number, revision and effective date, so an API bug cannot skip it.
  Three roles: agent_ro unchanged, uploads_rw writes the queue only, ingest_rw
  writes doc_chunks and has no HTTP surface.

Documents out (16.10-16.11, db/005):
  --supersede needs a revision to keep, so a cancelled procedure cannot be
  withdrawn at all. Adds withdraw (immediate, reversible, audited), restore
  (refused while another revision is live) and purge (off by default). A
  column grant plus a trigger let the web-facing role make a document less
  citable and never more.

The pool (16.13-16.15, db/006):
  pool_enabled, orthogonal to superseded: one is a claim about the document,
  the other about the corpus. Retrieval requires both, so re-enabling a
  withdrawn document does not make it citable. Named profiles and a
  per-request override let a demo trim the corpus without mutating state on a
  shared live host, and every reduced-pool answer carries a banner with the
  document count, following the used_fixture_data precedent.

Two existing defects found and documented while designing this:
  - ai-ingest takes PGUSER=agent_ro from api.env, a SELECT-only role, so the
    Phase 3 command in the README cannot write doc_chunks (16.1).
  - ingest_file() always inserts superseded = FALSE, so `--all` re-ingests a
    superseded revision as live. --supersede survives only until the next bulk
    run (16.10).

One commit rather than three: the upload, withdrawal and pool designs
interleave in the same spec, README and compose files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:17:38 +10:00