KB-499E

04 fn_iu_verify_mark Gate Fix

5 min read Revision 1
dieu44verify-markdelegationfix2026-05-27

04 — fn_iu_verify_mark Gate Fix (delegation to preflight)

Before

fn_iu_verify_mark (md5 1db15847b1c48e3b86568b712a15cfd6) implemented Axes A–D inline. It approved any manifest that passed those axes plus coverage + digest checks. It did NOT check:

  • per-piece section_type vocab presence
  • canonical_address collision with existing IU
  • per-piece publication_type vocab
  • title derivability
  • per-piece local_piece_id uniqueness (relied on MARK)

Consequently, manifests could reach lifecycle_status=approved while a known CUT-time refusal still applied.

After

fn_iu_verify_mark md5 c9c0553f3184bfaa3f2ee5488b5ff46c:

SELECT * INTO v_rec FROM iu_core.iu_staging_record WHERE staging_record_id=p_staging_record_id;
... staging_kind / lifecycle preconditions unchanged ...

-- Single source of truth: delegate.
v_preflight := public.fn_iu_cut_preflight_validate(p_staging_record_id, NULL, p_actor);
v_ok       := COALESCE((v_preflight->>'cut_readiness_ok')::boolean, false);

IF NOT v_ok THEN
  IF p_apply THEN
    UPDATE iu_core.iu_staging_record
       SET lifecycle_status='rejected',
           metadata = COALESCE(metadata,'{}'::jsonb) || jsonb_build_object(
             'verify_mark_problems', v_preflight->'problems',
             'verify_mark_preflight', v_preflight,
             'verify_mark_at', now())
     WHERE staging_record_id=p_staging_record_id;
    INSERT INTO public.dot_iu_command_run(...) VALUES (...);
  END IF;
  RETURN jsonb_build_object('ok',false,'verdict','rejected',
    'axis_a_ok', v_preflight->'axis_a_ok',
    'axis_b_ok', v_preflight->'axis_b_ok',
    'axis_c_ok', v_preflight->'axis_c_ok',
    'axis_d_ok', v_preflight->'axis_d_ok',
    'cut_readiness_ok', false,
    'problems', v_preflight->'problems',
    'preflight', v_preflight);
END IF;

IF p_apply THEN
  -- approval_doc_id + p_approver required; sets lifecycle='approved' + audit
  ...
END IF;

RETURN jsonb_build_object('ok',true,'verdict','approved',
  'axis_a_ok',true,'axis_b_ok',true,'axis_c_ok',true,'axis_d_ok',true,
  'cut_readiness_ok', true, 'preflight', v_preflight);

Behavioural diff

scenario before mig 055 after mig 055
Axis D fails (e.g. unit_kind=law_section) already rejected (post mig 054) rejected with full preflight artifact in metadata
section_type ∉ vocab approved (silent gap) rejected at verify_mark with E1 problem
canonical_address collision approved → CUT raises mid-loop rejected at verify_mark with E2 problem
title not derivable approved → fn_iu_create RAISES title required rejected at verify_mark with E4 problem
publication_type ∉ vocab (when present) approved → fn_iu_create RAISES rejected at verify_mark with E3 problem
duplicate local_piece_id MARK already refused, verify_mark passed if it got there rejected at verify_mark with E5 problem (redundancy)
valid manifest approved approved + verify_mark_cut_readiness_ok=true + preflight artifact pinned in metadata

Audit trail enrichments

On reject (p_apply=true), metadata now pins:

  • verify_mark_problems (jsonb array of indexed strings)
  • verify_mark_preflight (full preflight verdict for forensic replay)
  • verify_mark_at

dot_iu_command_run row now carries the full preflight in evidence.

On approve (p_apply=true), metadata pins:

  • verify_mark_axis_a/b/c/d (all true)
  • verify_mark_cut_readiness_ok (true)
  • verify_mark_preflight (full preflight)
  • verify_mark_at

What is unchanged

  • Pre-checks: staging_kind='mark_manifest', lifecycle_status='pending_review' (identical gates as before).
  • Apply path: approved_at = now(), approved_by = p_approver, approval_doc_id = p_approval_doc_id.
  • Reject path: lifecycle → rejected (sets iu_staging_record_approved_consistency_chk-compatible state).
  • Return-shape compatibility: same top-level keys (ok, verdict, axis_*_ok, problems) plus new cut_readiness_ok and preflight.
  • fn_iu_op_verify_mark alias body — md5 bf20bd19... untouched (governance pin honored).

Why delegation, not inline expansion

  1. Single source of truth. Future CUT-time refusal additions go in one place.
  2. Inspection. Operators can call fn_iu_cut_preflight_validate directly on any lifecycle (including already-approved manifests like 9fa4685e) to get a verdict without mutation.
  3. STABLE classification. Preflight is read-only and can be called from views/triggers without write-permission risk.
  4. Governance pinning. Alias body unaffected; only the underlying function changes — preserves the contract surface.
Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.6-iu-cut-verify-mark-cut-readiness-gate/04-verify-mark-gate-fix.md