KB-6B8F

IU CUT Operational Pipeline — 02 Status Surface + mig 052

6 min read Revision 1
iu-cut-pipelinecut-requeststate-machinemig-05213-statuses2026-05-26

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).

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.

Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.6-iu-cut-operational-pipeline-copy-mark-verify-cut/02-operational-status-surface.md