KB-2989

Regression Safety — 3/3 refusal proofs + 15/15 forbiddens (2026-05-27)

6 min read Revision 1
iu-cutregression-safetyrefusal-proofsforbiddensalias-md5-pinned2026-05-27

06 — Regression Safety

All checks run on live DB after COMMIT of contract fix. All results captured at 2026-05-27 ~04:01 UTC.

R.1 — Refusal proofs (BEGIN/ROLLBACK)

Three tests, all PASS (no live mutation):

T1 — bad unit_kind law_section refused at MARK boundary

SELECT public.fn_cut_mark_staged_file(
  '146f1520-aaa2-4bda-af2c-06a8f76cd36a'::uuid,
  '[{"local_piece_id":"p1","content_text":"dummy","canonical_address":"TEST#1",
     "source_position":"1","piece_role":"heading","section_type":"heading",
     "unit_kind":"law_section"}]'::jsonb,
  'regression_test_actor'
);

Result:

ERROR: fn_cut_mark_staged_file: piece[0].unit_kind law_section not in dot_config vocab.unit_kind.* (cut_manifest_piece_schema_v1)

PASS

T2 — missing unit_kind refused

Same call without unit_kind field.

Result:

ERROR: fn_cut_mark_staged_file: piece[0].unit_kind is required (cut_manifest_piece_schema_v1)

PASS

T3 — valid law_unit passes schema gate

Same call with unit_kind: "law_unit". Expected: passes schema gate, then is refused by the downstream status guard (cut_request 146f1520-... is at mark_verified, not copied or mark_rejected).

Result:

ERROR: fn_cut_mark_staged_file: cannot mark from status mark_verified — must be 'copied' or 'mark_rejected' (re-mark path)

This proves the schema gate accepts law_unit and fails only at the next pipeline step. PASS

R.2 — No durable state mutation by this macro

Resource Pre Post Delta
information_unit total 200 200 0
information_unit Điều 39 0 0 0
iu_vector_sync_point 152 152 0
cut_request 146f1520 status mark_verified mark_verified unchanged
cut_request 146f1520 cut_run_id NULL NULL unchanged
iu_staging_record 9fa4685e lifecycle_status approved approved unchanged
event_outbox total 139894 139894 0
production_documents table absent absent unchanged
pg_cron extension absent absent unchanged

R.3 — Vocab not widened

SELECT key, value FROM dot_config WHERE key LIKE 'vocab.unit_kind.%' ORDER BY key;
key value
vocab.unit_kind.design_doc_section design_doc_section
vocab.unit_kind.law_unit law_unit

2 rows pre, 2 rows post. NOT widened.

R.4 — No silent mapping added

SELECT count(*) FROM dot_config WHERE key LIKE '%law_section%';

Result: 0 rows. NO mapping table, NO synonym row added.

R.5 — Alias contract preserved (5/5 governance md5 pinned)

Alias md5 (post) Expected (pinned) Match
fn_iu_cut_from_manifest c5d556bc22cc2d255c0484b5a969ebc5 c5d556bc22cc2d255c0484b5a969ebc5 YES
fn_iu_op_cut 66b813e50205448eb01170aebec614df 66b813e50205448eb01170aebec614df YES
fn_iu_op_mark_file ffaa47fff7a906d93060141661080cd4 ffaa47fff7a906d93060141661080cd4 YES
fn_iu_op_verify_mark bf20bd1929998073865808d17b1dd648 bf20bd1929998073865808d17b1dd648 YES
fn_iu_op_verify_cut ac61dade6519694310cbfd75d8b549fb ac61dade6519694310cbfd75d8b549fb YES

5/5 match. Contract preserved.

R.6 — Gates safe at exit

Gate Value
queue.job_substrate.enabled false
iu_core.composer_enabled false
queue.heartbeat.enabled true
queue.dlq.replay_enabled false

No gate flipped. Heartbeat continues ticking; substrate/composer/replay remain off.

R.7 — Forbidden actions (15/15 honored)

Forbidden Honored
Run live CUT yes — not run
Run VERIFY_CUT yes — not run
Run COMPLETE yes — not run
jsonb_set patch on existing manifest payload yes — none
Touch Qdrant yes — VSP 152 unchanged
Touch production_documents yes — table absent
Install pg_cron yes — extension absent
Start broad worker yes — none started
Bypass fn_iu_create validation yes — none bypassed (validation site untouched)
Widen unit_kind vocab without governance yes — vocab not widened
Change MARK/CUT alias body yes — 5/5 alias md5 pinned
Add silent semantic mapping yes — no mapping rows added
Open parallel cut_request to bypass state yes — none opened
Extend state machine in this macro yes — fn_cut_request_transition unchanged
Flip runtime gates yes — all 4 unchanged

R.8 — Function body md5 (before vs after)

Function md5 pre md5 post Changed?
fn_cut_mark_staged_file 67721b42a55315858e3c377654c2a5b1 e85065acb9996623e0ef1f654d991df6 YES (intended)
fn_iu_verify_mark 04e5191c430a142712bfe63089863d44 1db15847b1c48e3b86568b712a15cfd6 YES (intended)
fn_iu_create (unchanged) (unchanged) no
fn_iu_resolve_default (unchanged) (unchanged) no
fn_cut_request_transition (unchanged) (unchanged) no
fn_cut_request_add (unchanged) (unchanged) no
5 MARK/CUT aliases (md5 pinned, see R.5) (md5 pinned, see R.5) no

Exactly 2 functions changed, both intentional, both CREATE OR REPLACE in single TX.

Conclusion

Code-side contract fix is regression-safe: all forbiddens honored, all governance pins preserved, no durable data mutation, refusal contract proven. The data-side Điều 39 rerun is deferred (see sections 05 and 07).

Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.6-iu-cut-dieu39-unit-kind-root-cause-and-contract-fix/06-regression-safety.md