KB-52C2

Root Cause Classification — Gate Consistency

5 min read Revision 1
iu-cutverify-approve-cut-gateroot-causegate-consistencycomposer-gate

Phase B — Root Cause Classification

Six diagnostic questions

Q1. Why did VERIFY_MARK report cut_readiness_ok=true while composer_enabled=false?

Because fn_iu_cut_preflight_validate (mig 055) checked only manifest correctness (axes A–D + E1..E7). It never read iu_core.composer_enabled. VERIFY_MARK delegates wholesale to preflight, so it inherited the blind spot.

Q2. Does fn_iu_cut_preflight_validate currently check the composer gate?

No (pre-fix). Live md5 914e26d61de0de914408af5cdc679c07 shows only 8 gates: A, B, C, D, E1..E7. There was no E8 / composer / runtime gate. Mig 056 adds E8 composer_gate_open.

Q3. Does APPROVE re-run preflight at p_apply=true time?

Yes, via delegation to the same function — but that function had no E8. So re-running preflight at apply time still missed the composer gate.

Pre-fix APPROVE also had no cut_request cross-binding — it could approve a manifest whose cut_request was already in cut_in_progress/cut_done/cut_failed.

Q4. Does fn_cut_apply re-run preflight immediately before CUT?

No (pre-fix). It transitioned mark_verified→cut_in_progress, then called fn_iu_op_cut. The alias delegates to fn_iu_cut_from_manifest, which does check composer (G7) and returns {ok:false, refusal_code:'composer_gate_closed', run_id:<v_run_id>}.

Q5. Why did fn_cut_apply write cut_run_id/cut_done_at/cut_done even though fn_iu_cut_from_manifest refused?

Pre-fix fn_cut_apply did no result inspection. It read v_alias_out->>'cut_run_id' (NULL — alias returns run_id key, not cut_run_id) and fell through to defensive alias_out->>'run_id' (the refusal's generated run_id). Then unconditionally:

UPDATE public.cut_request
   SET cut_run_id  = v_run_id,
       cut_done_at = now()
 WHERE cut_request_id = p_cut_request_id;
PERFORM public.fn_cut_request_transition(..., 'cut_done', ...);

So a refusal looked like a success, and the Agent had to use the legal cut_done→cut_failed transition to record the actual outcome.

Q6. Which exact function owns the failure-state bug?

  • fn_cut_apply (primary) — unconditional cut_done write.
  • fn_iu_cut_from_manifest generates a run_id even on refusal (defensible — it's an audit run id, not a CUT run id) but callers must distinguish ok-true from ok-false. The wrapper fn_iu_op_cut echoes both applied and refusal_code; the bug is purely in the consumer.

Q7. Is CUT itself wrong, or did CUT correctly refuse?

CUT (fn_iu_cut_from_manifest) was correct: it honoured G7 composer_gate. The upstream gates (VERIFY, APPROVE) failed to stop earlier because they didn't model E8. The downstream caller (fn_cut_apply) failed to read the refusal result.

Classification

root_cause:
  primary:
    class: verify_approve_cut_gate_inconsistency
    detail: |
      fn_iu_cut_preflight_validate was missing E8 composer_gate_open.
      VERIFY/APPROVE therefore returned cut_readiness_ok=true while CUT would refuse.
  secondary:
    class: approve_rubber_stamp_design
    detail: |
      fn_iu_verify_mark(p_apply=true) re-ran the same preflight (no E8) and
      had no cut_request cross-binding. fn_cut_verify_mark also ignored the
      inner verdict and transitioned cut_request to mark_verified purely on
      p_approve=true.
  tertiary:
    class: cut_apply_failure_state_bug
    detail: |
      fn_cut_apply persisted cut_run_id + cut_done_at and called
      fn_cut_request_transition(cut_done) regardless of whether the alias
      refused, leaving a phantom successful-cut state on cut_failed rows.
  not_root:
    - manifest_schema       (Axis A/B/C/D + E1..E7 all PASS for Điều 39)
    - unit_kind             (axis_d_ok=true; mig 054 holds)
    - Codex_execution       (manifest produced correctly)
    - DOT_copy              (source_hash + bytes match)
    - fn_iu_op_cut          (echoes refusal_code + applied correctly)
    - fn_iu_cut_from_manifest (G7 composer gate correctly refuses)

Strategy

Strict refusal at each gate. No vocab widening, no silent value mapping, no composer-gate weakening, no alias contract change. Single TX surgical patch on the three problem functions (fn_iu_cut_preflight_validate, fn_iu_verify_mark+fn_cut_verify_mark, fn_cut_apply).

Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.6-iu-cut-verify-approve-cut-gate-consistency-fix/02-root-cause-classification.md