KB-574F

Điều 39 Rerun DEFERRED (PARTIAL_WITH_EXACT_GAP — state-machine gap)

6 min read Revision 1
iu-cutdieu39rerun-deferredpartial-with-exact-gapstate-machine-gap2026-05-27

05 — Điều 39 Rerun (DEFERRED)

Decision: NOT executed in this macro

Per operator directive (Option C): rerun MARK and VERIFY_MARK for Điều 39 are deferred to a subsequent macro after the rollback-policy gap is resolved. This macro returns PARTIAL_WITH_EXACT_GAP.

Why deferred

Current state:

cut_request_id : 146f1520-aaa2-4bda-af2c-06a8f76cd36a
status         : mark_verified
manifest       : 9fa4685e-... (lifecycle_status=approved)

Both options for re-MARKing have unacceptable side effects:

Option A — open a new cut_request (rejected)

SELECT public.fn_cut_request_add(
  p_source_ref   := 'knowledge/dev/laws/dieu39-knowledge-graph-law.md',
  p_source_kind  := 'kb_document',
  p_requested_by := 'operator-dieu39-rerun'
);

Pros: simple, no state-machine change.

Cons: orphans 146f1520-... at mark_verified indefinitely. Pollutes operational view of in-flight cut_requests with a stuck row. Operator rejected for operational hygiene.

Option B — extend state machine (rejected)

-- inside fn_cut_request_transition:
WHEN 'mark_verified' THEN ARRAY['cut_in_progress', 'mark_rejected']  -- add second target

Pros: single cut_request can be re-used; clean re-MARK path.

Cons: changes the state-machine fn body and the transition contract. This is a state-machine policy decision, not a unit_kind fix — scope creep. Operator rejected as out of scope for this macro.

Option C — STOP here (chosen)

The code-side contract fix is COMMITTED and regression-proven. The data-side rerun of Điều 39 is deferred. The mission returns PARTIAL_WITH_EXACT_GAP documenting the precise blocker.

What would the rerun look like (spec for next macro)

When the rollback-policy gap is resolved (in a separate macro), the Điều 39 rerun should:

  1. Reject the current approved manifest (audit):

    UPDATE iu_core.iu_staging_record
       SET lifecycle_status = 'rejected',
           metadata = metadata || jsonb_build_object(
             'rejected_at', now(),
             'rejected_by', 'operator-dieu39-rerun',
             'rejected_reason', 'pre_unit_kind_gate_manifest',
             'rejected_root_cause', '16/16 pieces had unit_kind=law_section which is not in dot_config vocab.unit_kind.* — refused at fn_iu_create during CUT attempt',
             'prior_manifest_digest', 'aded6af91fb9643fb2ea99ff024a1ede'
           )
     WHERE staging_record_id = '9fa4685e-d35a-45d4-aee7-aa2836785ca5';
    
  2. Transition cut_request mark_verified -> mark_rejected (legal after state-machine widening):

    SELECT public.fn_cut_request_transition(
      '146f1520-aaa2-4bda-af2c-06a8f76cd36a',
      'mark_rejected',
      'operator-dieu39-rerun',
      '{"reason":"manifest_pre_unit_kind_gate","prior_manifest_digest":"aded6af9..."}'::jsonb
    );
    
  3. Re-build pieces from the same source_copy payload (no re-COPY needed — copy_staging_record_id=365cdc10-... is intact), with every piece's unit_kind=law_unit instead of law_section. All other fields (local_piece_id, content_text, canonical_address, source_position, piece_role, section_type, parent_local_id) copied verbatim.

  4. Call MARK:

    SELECT public.fn_cut_mark_staged_file(
      p_cut_request_id := '146f1520-aaa2-4bda-af2c-06a8f76cd36a',
      p_pieces         := '<rebuilt 16-piece array>'::jsonb,
      p_actor          := 'operator-dieu39-rerun'
    );
    

    Expected: status -> mark_in_progress -> marked; new manifest_staging_record_id; new manifest_digest (different from aded6af9... since unit_kind values differ).

  5. Dry-run VERIFY_MARK:

    SELECT public.fn_iu_verify_mark(
      p_staging_record_id := '<new_manifest_sr>',
      p_apply             := false
    );
    

    Expected: {ok:true, verdict:'approved', axis_a_ok:true, axis_b_ok:true, axis_c_ok:true, axis_d_ok:true}.

  6. Apply VERIFY_MARK with new approval_doc_id:

    SELECT public.fn_iu_verify_mark(
      p_staging_record_id := '<new_manifest_sr>',
      p_apply             := true,
      p_approval_doc_id   := 'knowledge/current-state/reports/dieu39-rerun-after-unit-kind-fix-2026-XX-XX.md',
      p_approver          := 'operator'
    );
    

    Expected: status -> mark_verified; manifest lifecycle_status='approved'.

  7. STOP at ready_for_cut=true. Do not run CUT in that macro unless explicitly authorized.

What was NOT done now (proven by section 06)

  • Old manifest 9fa4685e-... lifecycle_status STILL approved (NOT changed to rejected)
  • cut_request 146f1520-... status STILL mark_verified (no transition recorded after this macro's start)
  • No new cut_request rows created
  • No new staging records created
  • No new IUs created (information_unit total = 200, unchanged)

Conclusion

This phase is DEFERRED and is the explicit PARTIAL_WITH_EXACT_GAP of this macro. The code-side gate (Phase D) is sufficient to prevent recurrence: any future MARK call with unit_kind=law_section or any unknown value is refused at the boundary. The data-side cleanup of the specific stuck Điều 39 cut_request requires the next macro's rollback-policy decision.

Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.6-iu-cut-dieu39-unit-kind-root-cause-and-contract-fix/05-dieu39-rerun-mark-verify-approval.md