KB-136F
18000x · 05 — Template Mutation Invariants Proof (shared-graph, copy-on-instance=NO, supersede does NOT propagate)
5 min read Revision 1
iu-corev0.618000xmutation-invariantshared-graphsupersedemanifest-digest
18000x · 05 — Template Mutation Invariants Proof
Question
If an operator supersedes a piece that is shared between a template and N instances, does the mutation silently propagate?
Bounded BEGIN/ROLLBACK proof
ops/.../iu_core_18000x_mutation_invariant_proof.sql —
- Capture pre-supersede digests of
tpl:wf:onboarding/v1andtpl-inst:wf:onboarding/2026-05-25-demo. - Call
fn_iu_supersede('iu_core/template/15000x/wf-onboarding-v1/step-0-d0-account', actor, NULL, NULL, reason, tool, NULL, dry_run=true). - Force
fn_iu_collection_manifest_refreshon BOTH collections (composer gate flipped on inside TX, off before COMMIT — but here ROLLBACK reverts both anyway). - Compare digests.
- ROLLBACK.
- Post-rollback durability assertion.
Live transcript (excerpted)
-- pre-state digests
collection_key | manifest_digest
----------------------------------------+----------------------------------
tpl-inst:wf:onboarding/2026-05-25-demo | 2d3d37b60fd32963ddd7a6eef81aaad6
tpl:wf:onboarding/v1 | 2d3d37b60fd32963ddd7a6eef81aaad6
-- supersede dry_run probe
{"field": "review_decision_id", "status": "invalid_input",
"guidance": "Required. Supersede must reference a cutter_governance.review_decision row.",
"next_action": "record_review_decision_first"}
-- manifest refresh round-trip (both collections)
{"ok": true, "piece_count": 5, "manifest_digest": "2d3d37b60fd32963ddd7a6eef81aaad6"} v1
{"ok": true, "piece_count": 5, "manifest_digest": "2d3d37b60fd32963ddd7a6eef81aaad6"} instance
-- post-supersede digests
collection_key | pre_digest | post_digest | digest_unchanged
----------------------------------------+----------------------------------+----------------------------------+------------------
tpl-inst:wf:onboarding/2026-05-25-demo | 2d3d37b60fd32963ddd7a6eef81aaad6 | 2d3d37b60fd32963ddd7a6eef81aaad6 | t
tpl:wf:onboarding/v1 | 2d3d37b60fd32963ddd7a6eef81aaad6 | 2d3d37b60fd32963ddd7a6eef81aaad6 | t
ROLLBACK
Invariant policy
- Shared-graph by default —
fn_iu_compose'siu_idbranch attaches an existing IU by reference; pieces are NOT duplicated when instantiating from a template. Theiu_idis the same object. - Copy-on-instance = NO — there is no in-place copy. An instance and its template hold the same
iu_ids in their memberships. - Mutation does NOT propagate silently —
fn_iu_supersede(andfn_iu_piece_split/fn_iu_piece_mergeby symmetry) creates a newiu_idand aniu_supersede_set(or split/merge ledger) link; it does not rewrite any existing membership row. Becausefn_iu_collection_manifest_refreshhashes(piece_order, iu_id, piece_role)ordered (see[[feedback-manifest-digest-keyed-on-piece-graph]]), the digest input is byte-identical, so the digest is byte-identical. - Explicit propagation only — to upgrade an existing instance to "use the superseded piece," an operator must explicitly compose a v(N+1) of the template (with the new
iu_id) and re-instantiate. This is exactly the version chain pattern 18000x adds in migration 032.
Implications for operators
- You can safely supersede a template piece — existing instances will continue to render the pre-supersede content (the
iu_idthey reference is unchanged). - You should always ship a new template version when superseding content if you want the new content reflected in downstream instances. Use
dot_iu_register_template_versionwithprevious_collection_idto make the chain explicit. - The observability view flags drift —
v_iu_template_observability.digest_diverged_instancescounts instances whose digest no longer matches the template (a divergence that can only happen if an operator manually added or removed pieces from an instance).
Counter-example (NOT proved here; documented as the only known way to mutate)
If an operator runs fn_iu_collection_remove_piece(instance_id, old_iu_id) + fn_iu_collection_add_piece(instance_id, new_iu_id) directly on the instance, the instance's manifest will diverge from the template — which is the intended behaviour for hand-edited instances and is surfaced by v_iu_template_observability.digest_diverged_instances.