KB-1ED4

18000x · Operator Runbook addendum (template versioning + bulk + internal LIVE event + auto-instantiate + rollback recipes)

7 min read Revision 1
iu-corev0.618000xoperator-runbookaddendumtemplate-versioningbulk-instancerollback-recipes

IU Core · Product Factory · Operator Runbook (18000x addendum)

Extends ops/15000x-…/OPERATOR_RUNBOOK.md with the template-versioning, bulk instance, internal LIVE event, and auto-instantiate-from-event surfaces shipped in 18000x (migration 032). Audience: same as 15000x — release engineer with psql access to directus on Contabo + governed DOT commands.

What's new

1. Template versioning (dot_iu_register_template_version + dot_iu_retire_template_version)

-- register v1 as the root of a family
SELECT public.fn_iu_collection_register_template_version(
    '<v1_collection_id>'::uuid,
    'tpl:wf:onboarding',      -- template_family (the unversioned key)
    'v1',                     -- version_label (operator-visible)
    1,                        -- version_seq (uniqueness key with family)
    NULL,                     -- previous_collection_id (NULL = root)
    '<actor>');

-- register v(N+1) — auto-supersedes the predecessor's chain row
SELECT public.fn_iu_collection_register_template_version(
    '<v2_collection_id>'::uuid,
    'tpl:wf:onboarding',
    'v2', 2,
    '<v1_collection_id>'::uuid,
    '<actor>');

-- soft-retire (label/family kept; version_status='retired')
SELECT public.fn_iu_collection_retire_template_version(
    '<collection_id>'::uuid,
    '<actor>',
    '<reason>');

Refusals (sandbox-verified in sql/iu-core/sandbox/290_template_versioning_probe.sql):

  • invalid_input (NULL id / blank family / blank label / blank actor / version_seq < 1)
  • self_predecessor_refused
  • collection_not_found
  • template_not_marked (collection must already be in iu_collection_template_registry)
  • previous_not_found / previous_not_marked
  • family_seq_collision (different collection owns this family+seq)
  • version_not_found (retire on un-registered)

2. Cross-template observability — v_iu_template_observability

Single-row-per-template consolidated view. Always-safe to SELECT:

SELECT collection_key, template_family, version_label, version_seq, version_status,
       instance_count, digest_matching_instances, digest_diverged_instances,
       active_piece_count, template_without_version, version_without_template_marker
  FROM public.v_iu_template_observability
 ORDER BY template_family NULLS LAST, version_seq NULLS LAST;

Useful flags:

  • template_without_version=true → template is marked but not in any version chain (legitimate for single-version families; opt-in via dot_iu_register_template_version)
  • version_without_template_marker=true → chain row exists but registry row missing (should never happen; surface for ops alarm)
  • digest_diverged_instances>0 → at least one instance has been hand-edited away from its template digest

3. Bulk instance generation pattern

BEGIN;
UPDATE public.dot_config SET value='true' WHERE key='iu_core.composer_enabled';

DO $$
DECLARE v_template_id uuid := (SELECT id FROM public.iu_piece_collection
                                WHERE collection_key='tpl:wf:onboarding/v2');
        v_pieces uuid[]; v_pieces_json jsonb; v_result jsonb;
BEGIN
    SELECT array_agg(m.iu_id ORDER BY m.piece_order) INTO v_pieces
      FROM public.iu_piece_membership m
     WHERE m.collection_id = v_template_id AND m.membership_status='active';
    v_pieces_json := (SELECT jsonb_agg(jsonb_build_object('role','body','iu_id', x))
                        FROM unnest(v_pieces) AS x);
    FOR i IN 1..N LOOP
        v_result := public.fn_iu_compose(
            'tpl-inst:wf:onboarding/' || <date> || '-' || i::text,
            'workflow', '<title>', '<desc>', v_pieces_json, '<actor>-bulk');
        PERFORM public.fn_iu_collection_record_template_instance(
            (v_result->>'collection_id')::uuid, v_template_id,
            '<actor>-bulk', '<note>');
    END LOOP;
END $$;

UPDATE public.dot_config SET value='false' WHERE key='iu_core.composer_enabled';
COMMIT;

Every instance shares the template's manifest_digest (Phase F invariant). Verify via v_iu_template_observability.digest_matching_instances.

4. Bulk text-as-code export

# from the Mac repo (or any host with psql access)
python3 -c "
import json, pathlib, sys
sys.path.insert(0, '.')
from cutter_agent.iu_core.text_as_code import (...)
# build CollectionManifest objects from a JSON dump, serialize, roundtrip-verify
"

Or use the driver cutter_agent/iu_core/bulk_template_export_18000x.py (requires psql on PATH or PSQL_CMD env). Default output: iu-tree/_collections/<kind>/<sanitized_collection_key>.md.

5. Piece event runtime — internal LIVE bounded proof

BEGIN;
UPDATE public.dot_config SET value='true'  WHERE key='piece_event_runtime.emit_enabled';
UPDATE public.dot_config SET value='false' WHERE key='piece_event_runtime.dry_run_only';

SELECT public.fn_iu_piece_emit_event(
    'updated',                                           -- registered event_type
    '<canonical_address>', '<subject_ref>'::uuid,
    '<actor>', jsonb_build_object('macro','<name>'));

SELECT id, event_type, safe_payload->>'emit_mode' AS emit_mode
  FROM public.event_outbox
 WHERE event_domain='piece' AND safe_payload->>'macro'='<name>';

ROLLBACK;                                                -- reverts gates + row

Registered piece event_types: created, updated, split, merged, retired, superseded. Unknown types silently no-op.

6. Auto-instantiate-from-event sketch

See ops/18000x-…/iu_core_18000x_auto_instantiate_proof.sql for the bounded BEGIN/ROLLBACK pattern. No daemon / cron — productization deferred to ≥19000x.

Rollback recipes

Rollback migration 032 (REFUSED-guarded)

-- if version chain is empty (no live rows), rollback is silent:
\i sql/iu-core/rollback/032_template_versioning_and_observability.rollback.sql

-- if there ARE live version rows, force-drop:
SET iu_core.force_rollback_032='true';
\i sql/iu-core/rollback/032_template_versioning_and_observability.rollback.sql

Retire a template version (no row deletion)

SELECT public.fn_iu_collection_retire_template_version(
    '<collection_id>'::uuid, '<actor>', 'reason');

Soft-erase a bulk instance lineage (does NOT delete the collection)

DELETE FROM iu_collection_template_instance_lineage
 WHERE instantiated_by='<bulk_actor>';

The collection rows themselves stay (use the soft-delete / retire path instead of physical drop — FKs ON DELETE RESTRICT enforce this).

Healthcheck integration

fn_iu_collection_healthcheck() returns 8 surfaces (unchanged in 18000x). The two 032 views are observable directly:

SELECT * FROM v_iu_template_observability;
SELECT * FROM v_iu_template_version_chain WHERE template_family='<family>';

Add to your monitoring dashboard if useful.

Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.6-iu-core-18000x-template-versioning-bulk-scaleout-event-ops-open-goal/OPERATOR_RUNBOOK_18000X_ADDENDUM.md