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 to fn_iu_create for each nested call);
  • dry_run path returns status='dry_run_ok' and writes nothing;
  • mutation path loops fn_iu_create for 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
Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.6-iu-core-10000x-piece-platform-dot-lifecycle-open-goal/03-split-migration-027.md