Regression Safety — 3/3 refusal proofs + 15/15 forbiddens (2026-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).