18000x · Operator Runbook addendum (template versioning + bulk + internal LIVE event + auto-instantiate + rollback recipes)
IU Core · Product Factory · Operator Runbook (18000x addendum)
Extends
ops/15000x-…/OPERATOR_RUNBOOK.mdwith 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 todirectuson 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_refusedcollection_not_foundtemplate_not_marked(collection must already be iniu_collection_template_registry)previous_not_found/previous_not_markedfamily_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 viadot_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.