KB-1500

Phase 2 — Bounded BEGIN/ROLLBACK Proof Results

6 min read Revision 1
dieu45phase2bounded-proofrollback2026-05-26

Bounded BEGIN/ROLLBACK Proof Results

Setup

  • Channel: SSH contabo → docker exec postgrespsql workflow_admin@directus
  • File: /tmp/dieu45_phase2_proof.sql
  • Strategy: full proof executed inside one BEGIN; … ROLLBACK; block. Durable rows (passive marker, new dot_config keys, new fns) created by mig 051 already in place BEFORE the proof TX and survive ROLLBACK.

Pre-proof state (outside TX, already-applied Phase 2)

Object Rows
job_queue 0
job_dead_letter 0
queue_heartbeat 1 (passive marker)
dot_config key Value
queue.job_substrate.enabled false
queue.lease.reaper_enabled false
queue.lease.reaper_dry_run_only true
queue.worker.enabled false

Step-by-step proof outcomes (all PASS)

Step 0 — Bounded gate flip inside TX

UPDATE dot_config flips inside TX:

  • queue.job_substrate.enabled = true
  • queue.lease.reaper_enabled = true
  • queue.lease.reaper_dry_run_only = false

All three reverted by ROLLBACK.

Step 1 — Enqueue 3 jobs

Three fn_job_enqueue calls created phase2_proof_retryable, phase2_proof_dlq_bound, phase2_proof_ack — all state='queued', attempts=0, max_attempts=5, distinct idempotency keys.

Step 2 — Claim 3 jobs

fn_job_claim(p_lease_owner='phase2_proof_worker_A', p_limit=3) → all 3 transitioned queued → leased with lease_owner and lease_until = now() + 300s.

Step 3 — Simulate stale leases

UPDATE … SET lease_until = now() - interval '10 minutes' on the 2 jobs to be reaped. phase2_proof_dlq_bound also had attempts := 4 preloaded so attempts+1=5=max_attempts.

Step 4 — Dry-run reaper

fn_job_reap_stale_leases_dry_run() returned:

{"stale_lease_count":2,"limit":100,"mutation":false,"evaluated_at":"…",
 "jobs":[
   {"job_id":"5f2a…","attempts":0,"job_kind":"phase2_proof_retryable","overdue_seconds":600,"would_action":"reset_to_retry_waiting","max_attempts":5,…},
   {"job_id":"4f1e…","attempts":4,"job_kind":"phase2_proof_dlq_bound","overdue_seconds":600,"would_action":"move_to_dead_letter","max_attempts":5,…}
 ]}

Step 5 — Apply reaper

fn_job_reap_stale_leases_apply(p_actor='phase2_proof_reaper') returned:

{"refused":false,"reset_count":1,"dead_letter_count":1,"actor":"phase2_proof_reaper",
 "actions":[
   {"action":"reset_to_retry_waiting","job_id":"5f2a…","attempts":1,"backoff_sec":10,"scheduled_at":"…"},
   {"action":"moved_to_dead_letter","job_id":"4f1e…","attempts":5,
    "dead_letter":{"dead_letter_id":"9a4f…","state":"dead_letter","refused":false,…}}
 ]}

job_queue after reap:

job_kind state attempts last_error
phase2_proof_ack leased 0 (null)
phase2_proof_dlq_bound dead_letter 5 lease_expired_reaped_at_max_attempts
phase2_proof_retryable retry_waiting 1 lease_expired_reaped

job_dead_letter after reap: 1 row, triage_status='pending', attempts=5.

Step 6 — Triage update

fn_job_dead_letter_triage_update(…, p_triage_status='acknowledged', p_triage_note='phase2 bounded proof triage', p_actor='phase2_proof_triage') succeeded, transitioning pending → acknowledged.

Step 7 — DLQ requeue dry-run (gate off)

fn_job_dead_letter_requeue_dry_run(…) returned with replay_gate_enabled=false and would_action="refused: queue.dlq.replay_enabled=false". idempotency_collision.blocked=false.

Step 8 — Ack non-stale leased job

fn_job_ack(job_id=<ack job>, lease_owner='phase2_proof_worker_A', summary='…') returned {state:'succeeded', finished_at:…}. Confirms non-stale path intact.

Step 9 — Heartbeat tick

fn_queue_heartbeat_tick('phase2_proof_executor','external_worker','ok','{"phase":"phase2_proof"}') returned {ticks_total:1, last_tick_status:'ok', refused:false, …}. queue_heartbeat now has 2 rows in TX.

Step 10 — Stale check classification

v_queue_health: executors_fresh=1, executors_warning=0, executors_stale=1. fn_queue_stale_check(p_threshold_seconds=300): stale_count=1 (the legacy iu_outbound_default marker), phase2_proof_executor not stale.

Step 11 — Refusal proofs (7 negative tests)

# Test Refusal reason
11a fn_queue_heartbeat_register_passive(executor='iu_outbound_default'…) already_registered
11b fn_job_dead_letter_triage_update(…, 'bogus_status', …) invalid_triage_status (with allowed list returned)
11c fn_job_dead_letter_triage_update(…, 'pending', actor='') actor_required
11d apply reaper with reaper_dry_run_only=true toggled queue.lease.reaper_dry_run_only=true
11e apply reaper with reaper_enabled=false toggled queue.lease.reaper_enabled=false
11f requeue_dry_run('00000000-…') dead_letter_not_found
11g fn_job_enqueue with payload_json='{"vector":[…]}' CHECK violation job_queue_payload_safe_check (denylist key vector blocked)

ROLLBACK

All proof TX work reverted. Post-ROLLBACK snapshot:

Object Rows
job_queue 0
job_dead_letter 0
queue_heartbeat 1 (passive marker survives)
dot_config key Value
queue.job_substrate.enabled false ✓ restored
queue.lease.reaper_enabled false ✓ restored
queue.lease.reaper_dry_run_only true ✓ restored
queue.worker.enabled false

Passive marker executor_name='iu_outbound_default', last_tick_at='2026-05-22 11:31:41.928657+00', last_tick_status='warn'unchanged by ROLLBACK because it was committed as part of mig 051 BEFORE the proof TX opened.

Verdict

11/11 proof steps PASS. No durable mutation by the proof itself. All gate refusals correct. All payload-denylist CHECKs intact. Lease reaper exercise covers both branches (reset and DLQ). Triage workflow functional. Heartbeat protocol armed and functional. Silent gap surfaces deterministically.

Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.6-dieu45-phase-2-heartbeat-activation-lease-governance/05-bounded-proof-results.md