KB-5A2B

5000x · runtime/350 · auto-refresh trigger production pilot

4 min read Revision 1
iu-core5000xruntime-350auto-refresh-triggerproduction-pilotstatement-level

03 — runtime/350 · auto-refresh trigger 5000x production pilot

1. Why a second pilot

4000x's runtime/340 proved migration 024 fires durably once, on one row, on information_unit. Three invariants from sandbox/230 were covered only under BEGIN/ROLLBACK:

  • statement-level granularity (a bulk UPDATE over N rows fires the trigger ONCE, not N times);
  • both triggers (information_unit AND iu_metadata_tag) share the same gate + function;
  • closing the gate mid-transaction silences the trigger in real time.

runtime/350 promotes all three to durable production proofs.

2. Sequence (single transaction, COMMIT)

350.1  precondition: gate=false
350.2  UPDATE dot_config SET value='true' WHERE key='iu_core.three_axis_auto_refresh_enabled'
350.3  BULK UPDATE information_unit SET updated_at=now() WHERE id IN (3 enacted)
350.4  → 1 refresh_log row added (statement-level: 3 rows, 1 fire)         ✓ PASS
350.5  BULK UPDATE iu_metadata_tag SET assigned_at=assigned_at WHERE id IN (3)
350.6  → 2 refresh_log rows total since marker (IU side + tag side)        ✓ PASS
350.7  UPDATE dot_config SET value='false'
350.8  UPDATE information_unit again (single row, post-close)
350.9  → still 2 rows since marker — gate closed silenced trigger          ✓ PASS
350.10 final_state: gate=false / cache_healthy=t / in_sync=t / errors=0    ✓ PASS
350.11 fail-closed: exception if rows ≠ 2 or trigger_error_log > 0         ✓ PASS
350.12 rename the 2 audit rows actor=iu_lifecycle_trigger → iu_5000x_pilot
COMMIT

3. Durable side effects

  • iu_three_axis_envelope_refresh_log: +2 rows under actor='iu_5000x_pilot', both outcome='skipped_in_sync', dry_run=false, forced=false, upserted_count=0, deleted_count=0, drift_pre/post={…}.
  • iu_three_axis_envelope_trigger_error_log: unchanged at 0 rows.
  • dot_config.iu_core.three_axis_auto_refresh_enabled: back to false (was true only inside this transaction).
  • All other tables unchanged.

The actor='iu_5000x_pilot' tag (350.12) lets the rollback delete exactly this run's rows without disturbing the 4000x runtime/340 rows (actor='iu_lifecycle_trigger') or any prior macro's audit rows.

4. Why SET assigned_at = assigned_at is the right tag-side trigger probe

iu_metadata_tag has no updated_at column — it has assigned_at. The semantically-cleanest no-op rewrite is therefore SET assigned_at = assigned_at. Postgres still treats this as a row update for MVCC purposes (a new tuple is written), so the AFTER UPDATE FOR EACH STATEMENT trigger fires. Statement-level granularity is preserved: 3 updated rows → exactly one trigger invocation.

5. Why this does not introduce drift

  • The IU-side UPDATE only touches updated_at, which is not a column the envelope projects. fn_iu_three_axis_envelope_drift_check sees no change in view_count or table_count; outcome stays skipped_in_sync.
  • The tag-side UPDATE rewrites assigned_at to itself — the column is not part of iu_metadata_tag_registry-driven derivation, so axis B's derived tags don't move; outcome stays skipped_in_sync.

Both UPDATEs are pure no-ops at the envelope level; the trigger is the only durable observer.

6. Rollback

sql/iu-core/runtime/rollback/350_…rollback.sql:

DELETE FROM public.iu_three_axis_envelope_refresh_log
 WHERE actor = 'iu_5000x_pilot';

A single-row predicate, scoped only to this run. Does NOT touch:

  • runtime/340 rows (actor='iu_lifecycle_trigger') — these stay as historical record;
  • the gate value (already false at COMMIT);
  • the trigger function, triggers, or error log (migration 024 rollback handles those).

7. Five-layer impact

layer impact
PG +2 audit rows under actor='iu_5000x_pilot'; gate transient
Directus none
Nuxt none
AgentData +1 KB report (this doc)
Qdrant none
Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.6-iu-core-5000x-nuxt-pilot-monitoring-rollout-open-goal/03-runtime-350-auto-refresh-pilot.md