fn_cut_apply Failure-State Bug Fix
Phase E — fn_cut_apply failure-state bug fix
md5
ce488b884200893e38ffb17bca4baceb → 007e20974e25a3e7dd8f9884800a3637
Pre-fix behavior (the bug)
-- pre-fix
PERFORM public.fn_cut_request_transition(p_cut_request_id, 'cut_in_progress', ...);
v_alias_out := public.fn_iu_op_cut(...);
v_run_id := (v_alias_out->>'cut_run_id')::uuid; -- always NULL (wrong key name)
IF v_run_id IS NULL THEN
v_run_id := (v_alias_out->>'run_id')::uuid; -- gets the audit run_id even on refusal
END IF;
UPDATE public.cut_request SET cut_run_id = v_run_id, cut_done_at = now() WHERE ...;
PERFORM public.fn_cut_request_transition(p_cut_request_id, 'cut_done', ...);
When fn_iu_op_cut returned {ok:false, refusal_code:'composer_gate_closed', run_id:<uuid>} (because fn_iu_cut_from_manifest G7 refused), fn_cut_apply still wrote cut_run_id + cut_done_at and transitioned to cut_done. The Agent then had to use the legal cut_done→cut_failed transition to record the actual state — producing a phantom successful-cut audit trail on a failed request.
Post-fix behavior
Three changes:
1. Re-run preflight BEFORE transitioning to cut_in_progress
v_preflight := public.fn_iu_cut_preflight_validate(v_req.manifest_staging_record_id, NULL, p_actor);
v_pre_ready := COALESCE((v_preflight->>'cut_readiness_ok')::boolean, false);
IF NOT v_pre_ready THEN
INSERT INTO public.dot_iu_command_run (...) VALUES ('dot_cut_apply', ..., 'refused', ..., 'preflight_refused', ...);
RETURN jsonb_build_object(
'ok', false, 'verdict', 'refused',
'cut_request_id', p_cut_request_id,
'refusal_code', 'preflight_refused',
'status', v_req.status,
'preflight', v_preflight);
END IF;
If composer is closed (or any other preflight gate fails), the request stays at mark_verified rather than landing in cut_in_progress just to fall to cut_failed. Cleaner state machine.
2. Inspect alias result — never trust silence
v_alias_out := public.fn_iu_op_cut(...);
v_inner := v_alias_out->'inner_result';
v_inner_ok := COALESCE((v_inner->>'ok')::boolean, false);
v_applied := COALESCE((v_inner->>'applied')::boolean, false);
v_refusal := v_inner->>'refusal_code';
IF NOT v_inner_ok OR (p_apply AND NOT v_applied) THEN
-- Alias refused. Do NOT write cut_run_id/cut_done_at. cut_in_progress -> cut_failed.
PERFORM public.fn_cut_request_transition(p_cut_request_id, 'cut_failed', p_actor, ...);
PERFORM public.fn_cut_request_signal('cut.cut_failed', ...);
RETURN jsonb_build_object('ok',false,'verdict','refused','refusal_code',v_refusal,
'status','cut_failed','alias_result',v_alias_out, ...);
END IF;
The wrapper looks INTO inner_result.ok and inner_result.applied (the keys actually produced by fn_iu_cut_from_manifest). No more dependency on cut_run_id / run_id key fishing.
3. Persist cut_run_id + cut_done_at ONLY on success
v_run_id := COALESCE((v_alias_out->>'run_id')::uuid, (v_inner->>'run_id')::uuid);
UPDATE public.cut_request SET cut_run_id = v_run_id, cut_done_at = now() WHERE ...;
PERFORM public.fn_cut_request_transition(p_cut_request_id, 'cut_done', p_actor, ...);
These three lines are now guarded by the IF NOT v_inner_ok OR (p_apply AND NOT v_applied) early-return above. No phantom cut_done going forward.
Live verification (T4, inside BEGIN/ROLLBACK)
Setup: revert cut_request to mark_verified with cut_run_id=NULL, cut_done_at=NULL. Call fn_cut_apply(c7133284…, true, 'regression_T4') with composer=false.
| output | value |
|---|---|
ok |
false |
verdict |
refused |
refusal_code |
preflight_refused |
status |
mark_verified (no transition to cut_in_progress!) |
| post cut_request.status | mark_verified |
| post cut_request.cut_run_id | NULL |
| post cut_request.cut_done_at | NULL |
| IU count delta | 0 (still 200 total) |
State-machine paths covered
| from | event | to |
|---|---|---|
mark_verified |
preflight refused at fn_cut_apply | mark_verified (no transition; return refusal) |
mark_verified |
preflight ok → fn_iu_op_cut refuses | cut_in_progress → cut_failed (refusal_code threaded into metadata) |
mark_verified |
preflight ok → fn_iu_op_cut applies | cut_in_progress → cut_done (cut_run_id + cut_done_at persisted) |
Pinned md5 of unchanged neighbours
| function | md5 |
|---|---|
fn_iu_op_cut |
66b813e50205448eb01170aebec614df |
fn_iu_cut_from_manifest |
c5d556bc22cc2d255c0484b5a969ebc5 |
fn_cut_request_transition |
c392d3e156159852a97889df1af04165 |