KB-39ED
10000x · 03 — Migration 027: Piece SPLIT primitive (fn_iu_piece_split + iu_split_set)
4 min read Revision 1
iu-corev0.610000xmigration-027splitfn_iu_piece_splitiu_split_setlineage
10000x · 03 — Migration 027: Piece SPLIT primitive
File
sql/iu-core/027_piece_split_primitive.sql (transactional, \set ON_ERROR_STOP on; BEGIN/COMMIT). ~333 lines.
Substrate
Table public.iu_split_set
id uuid PK DEFAULT gen_random_uuid()
source_iu_id uuid NOT NULL REFERENCES information_unit(id)
source_canonical_address text NOT NULL
child_iu_ids uuid[] NOT NULL
child_canonical_addresses text[] NOT NULL
actor text NOT NULL
review_decision_id uuid NOT NULL
change_set_id uuid
reason text
tool_revision text
idempotency_key text NOT NULL UNIQUE
created_at timestamptz NOT NULL DEFAULT now()
rolled_back_at timestamptz
rolled_back_by text
rolled_back_reason text
CHECK (cardinality(child_iu_ids) = cardinality(child_canonical_addresses))
CHECK (cardinality(child_iu_ids) >= 2)
INDEX (source_iu_id, created_at DESC)
Append-only; rolled-back rows keep the row + stamp rolled_back_at.
Function public.fn_iu_piece_split
(p_source_canonical_address text,
p_child_specs jsonb,
p_actor text,
p_review_decision_id uuid,
p_change_set_id uuid DEFAULT NULL,
p_reason text DEFAULT NULL,
p_tool_revision text DEFAULT NULL,
p_dry_run boolean DEFAULT false
) RETURNS jsonb
SECURITY DEFINER
SET search_path = pg_catalog, public
Safety pattern (mirrors fn_iu_supersede / fn_iu_retire):
- input validation -> jsonb diagnostic on failure;
- per-child shape validation (canonical_address / title / body required; empty body refused unless
body='__EMPTY__'); - duplicate child canonical_address refused;
- source IU FOR UPDATE lock;
- cutter_governance.review_decision FK probe (live discovery: PK col is
review_decision_id); - optional cut_change_set probe (soft warning);
- idempotency_key = SHA-256(source ||
||| sorted children); pg_advisory_xact_lock(hashtext('iu_piece_split:' || source_id));app.canonical_writer='fn_iu_piece_split'marker (reset tofn_iu_createfor each nested call);- dry_run path returns
status='dry_run_ok'and writes nothing; - mutation path loops
fn_iu_createfor each child, then INSERTs the ledger row; - returns
status='split_recorded'with the full lineage payload.
Why source piece is NOT auto-superseded
Per "reversible by default": source's lifecycle transition is a separate governed action. After children are enacted, operator invokes dot_iu_supersede_piece separately. This keeps each step atomic + reversible.
Live apply
BEGIN
CREATE TABLE
CREATE INDEX
COMMENT
CREATE FUNCTION
COMMENT
COMMIT
Initial apply at 12:58:49 UTC; FK-column fix re-applied at 13:05:00 UTC. Both idempotent (CREATE OR REPLACE, IF NOT EXISTS).
Rollback file
sql/iu-core/rollback/027_piece_split_primitive.rollback.sql:
- DROP FUNCTION fn_iu_piece_split(text,jsonb,text,uuid,uuid,text,text,boolean)
- DROP INDEX idx_iu_split_set_source
- DROP TABLE iu_split_set