IU CUT Operational Pipeline — 02 Status Surface + mig 052
02 — Operational status surface: cut_request state machine + mig 052
Design decision
iu_core.iu_staging_record already carries lifecycle fields (lifecycle_status, vector_excluded, approved_at, consumed_at, expires_at, cleaned_at) but its row exists only after content has been staged. The mission requires a state to exist before copy (the requested state) and to survive after the staging rows are cleaned. A separate top-level entity is needed.
Decision: new public.cut_request table owns the lifecycle. It links to two NVSZ rows per request — the source_copy (created at COPY) and the mark_manifest (created at MARK by the existing alias). The NVSZ rows can be cleaned independently; cut_request survives as audit.
DDL summary (mig 052, single TX COMMITTED)
CREATE TABLE public.cut_request (
cut_request_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
source_ref text NOT NULL,
source_kind text NOT NULL DEFAULT 'kb_document',
requested_by text NOT NULL,
requested_at timestamptz NOT NULL DEFAULT now(),
status text NOT NULL DEFAULT 'requested', -- 13-value CHECK
source_path text,
source_hash text,
source_bytes bigint,
copy_staging_record_id uuid REFERENCES iu_core.iu_staging_record(staging_record_id),
copied_at timestamptz,
manifest_staging_record_id uuid REFERENCES iu_core.iu_staging_record(staging_record_id),
manifest_digest text,
marked_at timestamptz,
mark_verified_at timestamptz,
mark_verdict text, -- approved | rejected (CHECK)
mark_approval_doc_id text,
cut_run_id uuid,
cut_done_at timestamptz,
cut_verified_at timestamptz,
cut_verdict text, -- verified | failed (CHECK)
completed_at timestamptz,
cleanup_scheduled_at timestamptz,
cleaned_at timestamptz,
metadata jsonb NOT NULL DEFAULT '{}'
);
CREATE TABLE public.cut_request_transition ( ... ); -- bigserial PK, append-only audit log
Indexes: cut_request(status), cut_request(source_ref), cut_request(requested_at DESC), cut_request_transition(cut_request_id, occurred_at).
State machine (13 statuses, legal transitions)
requested → copied → mark_in_progress → {marked, mark_rejected}
mark_rejected → mark_in_progress (redo)
marked → {mark_verified, mark_rejected}
mark_verified → cut_in_progress → {cut_done, cut_failed}
cut_done → {cut_verified, cut_failed}
cut_verified → completed → cleanup_scheduled → cleaned
cut_failed → terminal (until human)
cleaned → terminal
Illegal transitions raise illegal cut_request transition X->Y (legal=...). See D31-g and D31-d/e/f.
Vocab (13 dot_config rows under vocab.cut_request_status.*)
vocab.cut_request_status.requested
vocab.cut_request_status.copied
vocab.cut_request_status.mark_in_progress
vocab.cut_request_status.marked
vocab.cut_request_status.mark_verified
vocab.cut_request_status.mark_rejected
vocab.cut_request_status.cut_in_progress
vocab.cut_request_status.cut_done
vocab.cut_request_status.cut_verified
vocab.cut_request_status.cut_failed
vocab.cut_request_status.completed
vocab.cut_request_status.cleanup_scheduled
vocab.cut_request_status.cleaned
CHECK constraint on cut_request.status mirrors the same 13 values (vocab is informational + governance; the CHECK is enforcement).
Functions (9 new in public.)
| Function | Role | Calls |
|---|---|---|
fn_cut_request_transition(uuid, text, text, jsonb) |
internal: assert legal transition + log | (none) |
fn_cut_request_signal(text, uuid, text, jsonb) |
internal: gate-respecting signal-only enqueue | fn_job_enqueue |
fn_cut_request_add(text, text, text) |
REQUEST | fn_cut_request_signal |
fn_cut_copy_to_staging(uuid, text, text) |
COPY (server-side pg_read_file) |
transitions, signal |
fn_cut_mark_staged_file(uuid, jsonb, text, text) |
MARK (wraps alias) | fn_iu_op_mark_file, transitions, signal |
fn_cut_verify_mark(uuid, bool, text, text, text) |
VERIFY_MARK (wraps alias) | fn_iu_op_verify_mark |
fn_cut_apply(uuid, bool, text) |
CUT (wraps alias) | fn_iu_op_cut |
fn_cut_verify_cut(uuid, text) |
VERIFY_CUT (wraps alias) | fn_iu_op_verify_cut |
fn_cut_complete(uuid, text) |
COMPLETE — sets cleanup_scheduled_at = now() + 15 days, then transitions completed → cleanup_scheduled |
transitions, signal |
Views (4 new)
v_cut_requests_by_status -- count grouped by status
v_cut_request_ready_to_mark -- status='copied'
v_cut_request_ready_to_cut -- status='mark_verified'
v_cut_request_ready_to_verify_cut -- status='cut_done'
Reuse vs new
| Layer | Used | Notes |
|---|---|---|
| Operator aliases | fn_iu_op_mark_file, fn_iu_op_verify_mark, fn_iu_op_cut, fn_iu_op_verify_cut |
bodies UNCHANGED |
| Queue enqueue | fn_job_enqueue |
reused; payload signal-only |
| Storage | iu_core.iu_staging_record, iu_core.iu_staging_payload |
new rows added, schema untouched |
| Vocab | dot_config.vocab.* |
extends Phase 3B pattern |
No alias bodies were edited; no event_outbox mutation; no Qdrant write; no pg_cron.
Dry-run
Mig 052 was applied first with COMMIT; rewritten to ROLLBACK; via sed (file md5 a994db0c2b5043862defbfa533f7e70f). Dry-run output showed 9 CREATE FUNCTION + 4 CREATE VIEW + 2 CREATE TABLE all clean, ROLLBACK reset to 0 new objects. Live apply then committed identical statements.