-- +goose Up -- The intake half of a book: what `POST /books` needs to move a row through -- `uploading → parsing → not_started | rejected` and what a backstop sweep needs to finish that -- walk when the process doing it died halfway. -- -- Until this pack the three intake statuses existed in the contract and in the DDL check and had no -- writer at all: a book could only be registered by the dev CLI, already parsed, straight into -- `not_started`. alter table books -- How many times parsing has been CLAIMED for this book. Parsing is one $0 call of the engine -- (`tmctl manifest`), and it fails in two different ways: the source cannot be cut (the book's -- own fault, terminal) or the engine could not be run at all (the deployment's fault, and the -- next sweep may well succeed). Only the second is retried, and only this counter bounds that -- retry — without it a misconfigured host would re-run the engine over every uploaded book -- forever. add column parse_attempts integer not null default 0, -- When the current parse was claimed. It is the CLAIM, not a timestamp for display: a book in -- `parsing` whose claim is older than the grace is a parse whose process is gone, and the sweep -- may take it again. Null means no claim is outstanding. add column parse_started_at timestamptz, -- Why a book was rejected, in the platform's own closed vocabulary — never the engine's text, -- which reads like pipeline internals and must not cross the seam (contract §Problem, PT-33). -- ⚠ NOT on the wire: contract v0 gives a book a `rejected` status and no reason field, so this -- is written for an operator and for whatever later pack adds one. Inventing the field here is -- not this zone's right (the precedent is last_resync_at, register row PD-150). add column reject_reason text not null default ''; -- The sweep asks one question — "which books are stuck in intake" — every few seconds, and the -- answer is a handful of rows in a table that grows with every book of every account. create index books_intake_idx on books (status, added_at) where status in ('uploading', 'parsing'); -- +goose Down drop index books_intake_idx; alter table books drop column reject_reason, drop column parse_started_at, drop column parse_attempts;