KB-47A6

90000x · 04 — Verify MARK (Migration 039)

9 min read Revision 1
iu-core90000xverify-markfn_iu_verify_markdot_iu_verify_mark_manifestaxis-a-b-cmigration-039authored-ready2026-05-25

90000x · 04 — Verify MARK

Phase: D Status: AUTHORED-READY Migration: 039 D9 delta after apply: +1 fn (fn_iu_verify_mark), +1 DOT (dot_iu_verify_mark_manifest).

Purpose

Transition pending_reviewapproved or rejected, with axis A/B/C structural checks and coverage byte-match. Only approved rows are eligible for cut by fn_iu_cut_from_manifest. CHECK in mig 037 will refuse CUT without approved_at + approval_doc_id.

What it checks

Check ID What Failure raises problem
M-pieces manifest.pieces is non-empty JSON array manifest.pieces missing or empty
Axis A source_position values dense + monotonic from 1 (min=1, holes=0) Axis A: positions not dense from 1 (min=X, max=Y, holes=Z)
Axis B every piece has both piece_role (vocab from fn_iu_create) AND section_type Axis B: piece_role or section_type missing on at least one piece
Axis C every parent_local_id (if set) resolves to a sibling local_piece_id within the manifest Axis C: parent_local_id resolves outside manifest
Coverage coverage_proof.covered_bytes == manifest.source_bytes coverage_proof.covered_bytes ≠ manifest.source_bytes
Digest manifest_digest matches ^[0-9a-f]{32}$ (32-hex; full canonical-JSON recompute is operator side per doc 03 §3 whitespace_collapse_v1) manifest_digest is not 32-hex

Migration 039 — authored

-- IU Core 90000x · Migration 039 · Verify MARK
BEGIN;

CREATE OR REPLACE FUNCTION fn_iu_verify_mark(
  p_staging_record_id uuid,
  p_apply             boolean DEFAULT false,
  p_approval_doc_id   text    DEFAULT NULL,
  p_approver          text    DEFAULT NULL,
  p_actor             text    DEFAULT 'fn_iu_verify_mark'
) RETURNS jsonb LANGUAGE plpgsql AS $$
DECLARE
  v_rec iu_core.iu_staging_record;
  v_manifest jsonb; v_coverage jsonb; v_pieces jsonb;
  v_positions int[]; v_min int; v_max int; v_holes int;
  v_problems text[] := '{}';
  v_axis_a boolean := false; v_axis_b boolean := false; v_axis_c boolean := false;
BEGIN
  SELECT * INTO v_rec FROM iu_core.iu_staging_record WHERE staging_record_id=p_staging_record_id;
  IF NOT FOUND THEN RETURN jsonb_build_object('ok',false,'reason','not_found'); END IF;
  IF v_rec.staging_kind <> 'mark_manifest' THEN
    RETURN jsonb_build_object('ok',false,'reason','not a mark_manifest','staging_kind',v_rec.staging_kind);
  END IF;
  IF v_rec.lifecycle_status <> 'pending_review' THEN
    RETURN jsonb_build_object('ok',false,'reason','lifecycle must be pending_review','live',v_rec.lifecycle_status);
  END IF;

  SELECT payload_json INTO v_manifest FROM iu_core.iu_staging_payload
   WHERE staging_record_id=p_staging_record_id AND part_name='cut_manifest';
  SELECT payload_json INTO v_coverage FROM iu_core.iu_staging_payload
   WHERE staging_record_id=p_staging_record_id AND part_name='coverage_proof';

  v_pieces := v_manifest->'pieces';
  IF v_pieces IS NULL OR jsonb_typeof(v_pieces) <> 'array' OR jsonb_array_length(v_pieces) = 0 THEN
    v_problems := v_problems || 'manifest.pieces missing or empty';
  ELSE
    SELECT array_agg((p->>'source_position')::int ORDER BY (p->>'source_position')::int)
      INTO v_positions FROM jsonb_array_elements(v_pieces) p;
    v_min := v_positions[1]; v_max := v_positions[array_length(v_positions,1)];
    v_holes := v_max - v_min + 1 - array_length(v_positions,1);
    v_axis_a := (v_min=1) AND (v_holes=0);
    IF NOT v_axis_a THEN v_problems := v_problems || ('Axis A: positions not dense from 1 (min='||v_min||', max='||v_max||', holes='||v_holes||')'); END IF;

    v_axis_b := NOT EXISTS (
      SELECT 1 FROM jsonb_array_elements(v_pieces) p
      WHERE (p->>'piece_role') IS NULL OR (p->>'section_type') IS NULL
    );
    IF NOT v_axis_b THEN v_problems := v_problems || 'Axis B: piece_role or section_type missing on at least one piece'; END IF;

    v_axis_c := NOT EXISTS (
      SELECT 1 FROM jsonb_array_elements(v_pieces) p
      WHERE (p->>'parent_local_id') IS NOT NULL
        AND NOT EXISTS (
          SELECT 1 FROM jsonb_array_elements(v_pieces) p2
          WHERE p2->>'local_piece_id' = p->>'parent_local_id'
        )
    );
    IF NOT v_axis_c THEN v_problems := v_problems || 'Axis C: parent_local_id resolves outside manifest'; END IF;
  END IF;

  IF (v_coverage->>'covered_bytes') IS NULL
     OR (v_manifest->>'source_bytes') IS NULL
     OR (v_coverage->>'covered_bytes')::bigint <> (v_manifest->>'source_bytes')::bigint THEN
    v_problems := v_problems || 'coverage_proof.covered_bytes ≠ manifest.source_bytes';
  END IF;

  IF (v_manifest->>'manifest_digest') !~ '^[0-9a-f]{32}$' THEN
    v_problems := v_problems || 'manifest_digest is not 32-hex';
  END IF;

  IF array_length(v_problems,1) IS NOT NULL THEN
    IF p_apply THEN
      UPDATE iu_core.iu_staging_record
         SET lifecycle_status='rejected',
             metadata = metadata || jsonb_build_object('verify_mark_problems', to_jsonb(v_problems), 'verify_mark_at', now())
       WHERE staging_record_id=p_staging_record_id;
      INSERT INTO dot_iu_command_run (command_name,payload_json,actor,status)
      VALUES ('dot_iu_verify_mark_manifest',
              jsonb_build_object('staging_record_id',p_staging_record_id,'result','rejected','problems',to_jsonb(v_problems)),
              p_actor,'ok');
    END IF;
    RETURN jsonb_build_object('ok',false,'verdict','rejected',
      'axis_a_ok',v_axis_a,'axis_b_ok',v_axis_b,'axis_c_ok',v_axis_c,
      'problems', to_jsonb(v_problems));
  END IF;

  IF p_apply THEN
    IF p_approval_doc_id IS NULL OR p_approver IS NULL THEN
      RETURN jsonb_build_object('ok',false,'reason','approval_doc_id and p_approver required for apply=true');
    END IF;
    UPDATE iu_core.iu_staging_record
       SET lifecycle_status='approved',
           approved_at = now(), approved_by = p_approver, approval_doc_id = p_approval_doc_id,
           metadata = metadata || jsonb_build_object('verify_mark_axis_a',true,'verify_mark_axis_b',true,'verify_mark_axis_c',true,'verify_mark_at',now())
     WHERE staging_record_id=p_staging_record_id;
    INSERT INTO dot_iu_command_run (command_name,payload_json,actor,status)
    VALUES ('dot_iu_verify_mark_manifest',
            jsonb_build_object('staging_record_id',p_staging_record_id,'result','approved','approval_doc_id',p_approval_doc_id),
            p_actor,'ok');
  END IF;
  RETURN jsonb_build_object('ok',true,'verdict','approved','axis_a_ok',true,'axis_b_ok',true,'axis_c_ok',true);
END; $$;

INSERT INTO dot_iu_command_catalog (command_name, category, mutating, reversible, target_functions, registered_at)
VALUES ('dot_iu_verify_mark_manifest', 'verify', true, false, ARRAY['fn_iu_verify_mark']::text[], now());

COMMIT;

Rollback 039

BEGIN;
DELETE FROM dot_iu_command_catalog WHERE command_name='dot_iu_verify_mark_manifest';
DROP FUNCTION IF EXISTS fn_iu_verify_mark(uuid,boolean,text,text,text);
COMMIT;

Dry-run pattern

-- dry-run on a pending_review row, just inspect verdict
SELECT fn_iu_verify_mark(p_staging_record_id := '<uuid>', p_apply := false);

Result shape (PASS):

{"ok": true, "verdict": "approved", "axis_a_ok": true, "axis_b_ok": true, "axis_c_ok": true}

Result shape (FAIL):

{"ok": false, "verdict": "rejected", "axis_a_ok": false, "axis_b_ok": true, "axis_c_ok": true,
 "problems": ["Axis A: positions not dense from 1 (min=2, max=4, holes=0)"]}

Operator approval requirement

p_apply=true requires BOTH p_approval_doc_id (KB-immutable path like knowledge/.../approval-2026-05-25.md) AND p_approver (operator handle). This propagates into approved_at + approved_by + approval_doc_id on the staging row — the tighter consumed-consistency CHECK in 037 then refuses any CUT that lacks those fields.

  • [[project_dot_iu_cutter_v0_6_iu_core_80000x_operational_cut_workflow_mark_review_cut_verify_mark_review_cut_verify]] — 04-review-approval-checklist.md is the R0-R11 operator checklist that maps to approval_doc_id.
  • [[feedback-section-type-and-piece-role-vocab-enforced]] — Axis B vocab discovered from live CHECKs.
  • 06-three-axis-requirements.md of 80000x — full axis A/B/C invariants.
Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.6-iu-core-90000x-mark-to-cut-automated-pipeline-hardening/04-verify-mark.md