dot-iu-cutter v0.5 — Lifecycle Enactment Design · Live Lifecycle Survey (G1 PASS) (doc 1 of 6)
dot-iu-cutter v0.5 — Lifecycle Enactment Design · Live Lifecycle Survey
doc 1 of 6 · 2026-05-20 · READ-ONLY SURVEY — NO MUTATION
phase : G1 — live lifecycle survey outcome : PASS — production lifecycle surface fully mapped production_mutation : NONE (read-only queries via context_pack_readonly)
0. Survey scope
This document maps the LIVE production lifecycle surface for public.information_unit + public.unit_version as of 2026-05-20 06:40Z, in preparation for designing a canonical draft → enacted transition path for the 60 ICX-CONST IUs born in v0.5 leg-A CUT.
The survey is exhaustively read-only. All queries were executed under context_pack_readonly role with statement_timeout 5s and hard LIMIT 500. No SET, no DDL, no transactions written. The survey is reproducible from the SQL listed below.
1. Authoritative inventory — 60 ICX-CONST IUs uniformly draft
SELECT COUNT(*) AS n_icx_const,
COUNT(*) FILTER (WHERE lifecycle_status='draft') AS n_draft,
array_agg(DISTINCT lifecycle_status) AS statuses
FROM public.information_unit
WHERE canonical_address LIKE 'ICX-CONST%';
n_icx_const : 60
n_draft : 60
statuses : ['draft']
non_draft_count : 0
SELECT lifecycle_status, COUNT(*) AS n,
MIN(created_at) AS first_seen,
MAX(updated_at) AS last_touched
FROM public.information_unit
GROUP BY lifecycle_status ORDER BY n DESC;
public.information_unit:
lifecycle_status_uniform : 'draft' (158 rows, 60 ICX-CONST + 98 pre-CUT pilots)
first_seen : 2026-05-05T07:07:26.107923Z
last_touched : 2026-05-20T04:18:21.854512Z
public.unit_version:
lifecycle_status_uniform : 'draft' (165 rows, 60 ICX-CONST v1 + 105 pre-CUT pilots)
review_state : NULL on all 165 rows (column exists, never populated)
⇒ No prior enactment has happened in this production database. Every row in both tables is in the column-default 'draft' state. The 60 ICX-CONST IUs are part of, but not all of, this draft population.
2. information_unit lifecycle surface
2.1 Column definitions (19 cols total)
lifecycle_status : text NOT NULL DEFAULT 'draft'::text
conformance_status : text NOT NULL DEFAULT 'open'::text
No other lifecycle-named column exists on information_unit. No enacted_at, no effective_at, no published_at on this table.
2.2 Constraints (4 total, none on lifecycle_status)
information_unit_pkey : PRIMARY KEY (id)
information_unit_canonical_address_key : UNIQUE (canonical_address)
fk_iu_version_anchor : FOREIGN KEY (version_anchor_ref)
REFERENCES unit_version(id)
DEFERRABLE INITIALLY DEFERRED
trg_iu_birth_gate_layer2 : CONSTRAINT TRIGGER (deferred AFTER)
⇒ There is no CHECK constraint on information_unit.lifecycle_status. Any text value is currently DDL-legal; validation is policy-enforced inside functions only.
2.3 Triggers on information_unit (5 user triggers)
| order | trigger | timing/event | function | sec.def |
|---|---|---|---|---|
| 1 | trg_aa_iu_gateway_write_guard | BEFORE INSERT OR UPDATE | public.fn_iu_gateway_write_guard() |
YES |
| 2 | trg_iu_birth_gate_layer1 | BEFORE INSERT | public.fn_iu_birth_gate_layer1() |
no |
| 3 | trg_iu_updated_at | BEFORE UPDATE | public.fn_iu_updated_at() |
no |
| 4 | trg_birth_information_unit | AFTER INSERT | public.fn_birth_registry_auto('__birth_synthetic_id__') |
no |
| 5 | trg_iu_birth_gate_layer2 | AFTER INSERT OR UPDATE (DEFERRABLE) | public.fn_iu_birth_gate_layer2() |
no |
Critical implications for an UPDATE on information_unit:
- The gateway guard (#1) fires on UPDATE just as it does on INSERT. Any UPDATE not carrying an allowed
app.canonical_writermarker is REJECTED. - The birth-gate layer-1 (#2) is BEFORE INSERT only — does NOT re-run on UPDATE. So PILOT-ONLY warnings about
P-pub1/P-pub2(publication_authority_ref / publication_type_ref) do NOT block an UPDATE today. trg_iu_updated_atrewritesupdated_at = now()on every UPDATE — automatic.trg_iu_birth_gate_layer2fires AFTER UPDATE (deferred) and re-checksversion_anchor_ref/content_anchor_refconsistency. As long as the UPDATE leaves these untouched, layer-2 passes.
2.4 Gateway policy mechanism (re-confirmed)
iu_create.gateway.mode : enforced
iu_create.gateway.marker_key : app.canonical_writer
iu_create.gateway.marker_value : fn_iu_create
iu_create.gateway.allowed_marker_values : fn_iu_create,fn_iu_apply_edit_draft
iu_create.gateway.direct_insert_policy : block_after_guard
iu_create.gateway.exempt_policy : none_active
iu_create.gateway.canonical_function : public.fn_iu_create(text,text,text,text,text,text,text,text,uuid)
iu_create.gateway.plan_function : public.fn_iu_create_plan(text,text,text,text,text,text,text,text,uuid)
iu_create.gateway.policy_doc_path : knowledge/dev/laws/dieu44-trien-khai/design/22-p3-iu-creation-gateway-scope.md
iu_create.gateway.readme_path : knowledge/dev/laws/dieu44-trien-khai/readme/iu-create-gateway-readme.md
The guard function body (md5 6907fa4e…26d7, 1364 chars) reads app.canonical_writer via current_setting(key, true) and accepts the write iff the value is one of the comma-separated entries in iu_create.gateway.allowed_marker_values. Adding a new canonical writer is a SINGLE-ROW dot_config update + a new SECDEF function that sets the marker — no code change to the guard.
2.5 Table-level ACL (per pg_class.relacl)
public.information_unit:
directus : arwdDxt (owner; INS/SEL/UPD/DEL/TRUNC/REF/TRIGGER)
context_pack_readonly : r (SELECT only — survey role)
cutter_exec : r (SELECT only — write must go through SECDEF fn)
cutter_verify : r (SELECT only)
public.unit_version:
directus : arwdDxt
context_pack_readonly : r
cutter_exec : r
cutter_verify : r
public.birth_registry:
directus : arwdDxt
incomex : r
context_pack_readonly : r
⇒ cutter_exec (the cutter_agent role) has NO direct INSERT or UPDATE on either table. All writes already route through SECDEF functions. This is the same posture used by fn_iu_create (executed under directus inside the SECDEF body, called by cutter_exec from outside).
3. unit_version lifecycle surface
3.1 Column definitions (16 cols total)
lifecycle_status : text NOT NULL DEFAULT 'draft'::text
enacted_at : timestamp with time zone NULL -- KEY: column EXISTS, never set
review_state : text NULL -- never populated (NULL on all 165 rows)
provenance : text NULL
editor : text NULL
updated_at : timestamp with time zone NULL
content_profile : jsonb NOT NULL DEFAULT '{}'::jsonb
⇒ unit_version.enacted_at already exists as a nullable timestamp. This is strong design intent: when a version is enacted, its enacted_at must be set. Our fn_iu_enact design will write enacted_at = now() on the current version-anchor's UV row.
3.2 Triggers on unit_version (2 user triggers + 1 sandbox)
| order | trigger | timing/event | function | sec.def |
|---|---|---|---|---|
| 1 | trg_aa_uv_gateway_write_guard | BEFORE INSERT OR UPDATE | public.fn_iu_gateway_write_guard() (SAME function as IU) |
YES |
| 2 | trg_aa_iu_notif_version | AFTER INSERT | public.fn_iu_notif_version() |
YES |
sandbox_tac.unit_version has its own pre-insert trigger that is irrelevant to public.
⇒ The gateway also guards unit_version UPDATE. So a draft→enacted transition on UV must also be performed by a function that sets app.canonical_writer to an allowed value.
4. Existing IU-domain functions (24 total) — gap on lifecycle transitions
Filtered to public.fn_iu_*:
exist (canonical write path):
fn_iu_create SECDEF (p_canonical_address, p_title, p_body, p_actor, p_unit_kind, p_section_type, p_owner_ref, p_publication_type, p_parent_ref)
fn_iu_apply_edit_draft SECDEF (p_draft_id uuid, p_actor text, p_review_note text)
fn_iu_create_plan SECDEF (...)
fn_iu_create_edit_draft SECDEF (...)
fn_iu_save SECDEF (...)
fn_iu_edit / fn_iu_edit_plan
fn_iu_comment / fn_iu_comment_edit_draft
fn_iu_mark_read / fn_iu_unread / fn_iu_notification_board / fn_iu_notif_*
exist (helpers, no write):
fn_iu_classify_existing (p_addr text)
fn_iu_create_preflight ()
fn_iu_resolve_default (p_explicit, p_config_key, p_vocab_prefix)
fn_iu_verify_invariants (p_addr text) → jsonb
NOT FOUND (full pg_proc scan):
fn_iu_enact — does NOT exist
fn_iu_publish — does NOT exist
fn_iu_activate — does NOT exist
fn_iu_promote — does NOT exist
fn_iu_lifecycle — does NOT exist
fn_iu_lifecycle_transition — does NOT exist
fn_iu_finalize — does NOT exist
⇒ The lifecycle gap is unambiguous: there is NO canonical function that can transition an IU from draft to enacted today. Any direct UPDATE attempt is fail-closed by the gateway guard (because no allowed app.canonical_writer value covers an enactment transition).
5. Sibling lifecycle infrastructure in the database
The database already has FOUR parallel lifecycle subsystems. Each gives us a template to follow or a coupling to avoid.
5.1 LAW / NORMATIVE subsystem — {draft, enacted, retired} with content-immutability
Triggers exist:
| table | trigger | function | sec.def |
|---|---|---|---|
| approval_requests | trg_apr_lifecycle | fn_enforce_apr_lifecycle | no |
| normative_registry | nrm_enacted_must_have_approval | fn_nrm_enacted_must_have_approval | no |
| normative_registry | nrm_enacted_must_have_enforcement | fn_nrm_enacted_must_have_enforcement | no |
| normative_registry | nrm_enacted_immutable | fn_nrm_enacted_immutable | no |
| (a law table) | (trg) | fn_law_enacted_immutable | no |
| (a law table) | (trg) | fn_law_enacted_must_have_enforcement | no |
fn_law_enacted_immutable body (verbatim) — TEMPLATE for our trg_iu_enacted_immut:
IF OLD.status = 'enacted' THEN
IF NEW.status = 'retired' THEN RETURN NEW; END IF;
IF (NEW.article_number, NEW.name, NEW.version, NEW.status,
NEW.category, NEW.scope_summary, NEW.kb_path,
NEW.enacted_session, NEW.council_score,
NEW.composition_level, NEW.species_code)
IS DISTINCT FROM
(OLD.article_number, OLD.name, OLD.version, OLD.status, ...)
THEN
RAISE EXCEPTION 'Enacted law BẤT BIẾN. Tạo Amendment thay vì UPDATE. Hoặc retire trước.';
END IF;
END IF;
RETURN NEW;
fn_nrm_enacted_immutable (verbatim, slightly stricter):
IF OLD.status = 'enacted' THEN
IF NEW.status = 'retired' THEN RETURN NEW; END IF;
IF NEW.status != OLD.status THEN
RAISE EXCEPTION 'Enacted normative document is immutable. Only retire allowed.';
END IF;
IF NEW.name IS DISTINCT FROM OLD.name OR NEW.sections IS DISTINCT FROM OLD.sections
OR NEW.version IS DISTINCT FROM OLD.version OR NEW.doc_type IS DISTINCT FROM OLD.doc_type THEN
RAISE EXCEPTION 'Enacted document content is immutable. Create amendment instead.';
END IF;
END IF;
5.2 TAC subsystem — {draft, enacted, superseded, retired} (4-state, SECDEF)
public.tac_uv_lifecycle_vocab rows (verbatim):
| code | name | sort_order |
|---|---|---|
| draft | Bản nháp | 10 |
| enacted | Đã ban hành | 20 |
| superseded | Bị thay | 30 |
| retired | Đã rút | 40 |
public.tac_lu_lifecycle_vocab rows:
| code | name | sort_order |
|---|---|---|
| active | Hoạt động | 10 |
| draft_only | Chỉ bản nháp | 20 |
| retired | Đã rút | 30 |
Trigger fired on public.tac_unit_version:
trigger : trg_tac_enacted_immut (sec.def YES)
function: public.fn_tac_enacted_immut
fn_tac_enacted_immut body (verbatim) — the BEST TEMPLATE we have, because it operates on UV-shape rows (body/title/description/content_profile):
IF OLD.lifecycle_status = 'enacted' AND TG_OP = 'UPDATE' THEN
IF (NEW.body IS DISTINCT FROM OLD.body)
OR (NEW.title IS DISTINCT FROM OLD.title)
OR (NEW.description IS DISTINCT FROM OLD.description)
OR (NEW.content_profile IS DISTINCT FROM OLD.content_profile)
THEN
RAISE EXCEPTION 'INV-ENACTED-IMMUT: cannot modify body/title/description/content_profile of enacted unit_version %', OLD.id
USING ERRCODE = 'check_violation';
END IF;
END IF;
IF TG_OP = 'DELETE' AND OLD.lifecycle_status = 'enacted' THEN
RAISE EXCEPTION 'INV-ENACTED-IMMUT: cannot delete enacted unit_version %', OLD.id
USING ERRCODE = 'check_violation';
END IF;
RETURN COALESCE(NEW, OLD);
5.3 Generic transition subsystem — fn_transition_lifecycle(p_collection, p_entity_id, ...)
Exists in public. Signature:
public.fn_transition_lifecycle(
p_collection text,
p_entity_ids integer[],
p_new_status text,
p_reason text,
p_performed_by text
) RETURNS jsonb
Hard-coded vocab: {draft, active, deprecated, retired} (NOT our 4-state vocab; uses active not enacted).
Writes to public.lifecycle_log which has integer entity_id — INCOMPATIBLE with our UUID-keyed information_unit. Cannot be reused.
5.4 public.lifecycle_log schema (16 cols)
id : integer NOT NULL serial
entity_collection : varchar NOT NULL
entity_id : integer NOT NULL -- ⚠ INTEGER, breaks for UUID-keyed IU
entity_code : varchar NULL
from_status : varchar NOT NULL
to_status : varchar NOT NULL
terminal_reason : varchar NULL DEFAULT 'none'
transition_type : varchar NULL
reason : text NULL
performed_by : varchar NOT NULL
performed_at : timestamptz NULL DEFAULT now()
related_entity_collection : varchar NULL
related_entity_id : integer NULL -- ⚠ INTEGER
related_entity_code : varchar NULL
approval_ref : text NULL
metadata : jsonb NULL DEFAULT '{}'::jsonb
⇒ Conclusion: a new UUID-keyed lifecycle log table is required for IU. Reusing lifecycle_log would either require an ALTER (high blast radius) or an ID-mapping hack (technical debt).
6. Function bodies relevant to the design — verbatim selections
6.1 fn_iu_apply_edit_draft — global-DB lifecycle coupling (CRITICAL)
This function contains the following sensitive block (verbatim):
SELECT count(DISTINCT lifecycle_status), min(lifecycle_status)
INTO v_lc_count, v_uv_lifecycle
FROM public.unit_version;
IF v_lc_count != 1 OR v_uv_lifecycle IS NULL OR btrim(v_uv_lifecycle)='' THEN
RETURN jsonb_build_object('status','lifecycle_ambiguous',
'guidance','Not uniquely determined.',
'next_action','stop_for_gpt_review');
END IF;
This is a GLOBAL count across ALL rows in public.unit_version. The function uses it to decide which lifecycle_status to stamp on a newly-applied edit-draft version. The current behavior: every UV in the database has lifecycle_status='draft', so v_lc_count = 1, the check passes, and new versions inherit 'draft'.
⇒ AFTER enacting the 60 ICX-CONST UVs, v_lc_count will become 2 ('draft' for 105 rows + 'enacted' for 60 rows). Every future call to fn_iu_apply_edit_draft will fail with 'lifecycle_ambiguous', irrespective of which IU is being edited. This is a HARD BREAKAGE of all in-place edit flows.
This coupling MUST be addressed before enactment proceeds. It is surfaced as OQ-1 in [[dot-iu-cutter-v0-5-03-design-options-analysis-2026-05-20]] and folded into the recommended design in [[dot-iu-cutter-v0-5-04-recommended-lifecycle-enactment-contract-2026-05-20]].
6.2 fn_iu_create — canonical writer marker pattern (verbatim relevant block)
PERFORM set_config('app.canonical_writer', 'fn_iu_create', true);
v_iu_id := gen_random_uuid();
v_uv_id := gen_random_uuid();
v_hash := public.fn_content_hash(p_body);
INSERT INTO public.information_unit(...) VALUES(...);
INSERT INTO public.unit_version(id, unit_id, body, content_hash, version_seq, created_by)
VALUES (v_uv_id, v_iu_id, p_body, v_hash, 1, btrim(p_actor));
UPDATE public.information_unit
SET version_anchor_ref = v_uv_id,
content_anchor_ref = v_uv_id::text
WHERE id = v_iu_id;
set_config(key, value, true) — the third arg true makes the setting transaction-local and auto-cleared at COMMIT/ROLLBACK. Our fn_iu_enact will use the same pattern with set_config('app.canonical_writer','fn_iu_enact',true).
6.3 fn_iu_verify_invariants — re-usable precondition probe
Returns a jsonb with five invariants:
i1_iu_exists : IU row exists at canonical_address
i2_uv_linked : unit_version row is linked (version_anchor_ref resolves)
i3_anchors_exact : version_anchor_ref::text == content_anchor_ref
AND unit_version.unit_id == iu.id
i4_birth_exists : birth_registry has 'information_unit::<iu_id>' entry
i5_uv_birth_ok : per collection_registry.birth_code_strategy
all_pass = (i1 ∧ i2 ∧ i3 ∧ i4 ∧ i5)
Used inside fn_iu_create (post-write) and fn_iu_apply_edit_draft. Reusable as a precondition probe inside fn_iu_enact — invariants are a necessary condition for enactment (if any of i1..i5 fail, the IU is structurally broken and enactment must NOT proceed).
7. Governance / audit linkage state (cutter_governance schema)
The cutter_governance schema exists with 24 tables (confirmed via pg_class join pg_namespace):
cutter_governance schema (24 tables):
cut_change_set -- leg-B governed recording
cut_change_set_affected_row
review_decision -- the GPT/User ruling rows
manifest_envelope
manifest_unit_block
dot_pair_signature -- DOT-991 / DOT-992 signing
verify_result -- M2 write-VERIFY
decision_backlog_entry / _history / _dependency / _sweep_log
source_document_registry / source_document_version_registry / source_family_registry
address_template_registry / authority_override / canonical_address_alias
entity_kind_registry / entity_reference_registry
grammar_profile / grammar_profile_level / grammar_profile_status_marker
matcher_config_registry / metadata_key_registry
context_pack_readonly does NOT have SELECT on these tables (relacl probe returned can_select=false for cut_change_set, dot_pair_signature, manifest_envelope, review_decision, verify_result). The design must take their existence and shape as documented (from prior memory + KB), not as live-queried, for this scope.
Linked governance rows currently live for the 60-IU CUT (from MEMORY closeouts):
linked_change_set_id : 456c6830-a747-4b53-ac2f-665e25e12cd0
linked_manifest_envelope_id : 638cf363-f45a-4bb3-b9bb-928c5e24c15b
linked_review_decision_id : 29c88a7b-60f7-41bd-af45-43cc9b9f41c0
linked_executor_signature_id: 3a249063-e33a-406a-9302-2e9e646a0938
verify_result_id : 18278460-438c-4fb4-bf9c-997c82447f92
verifier_signature_id : f5c3ee34-7f9f-4af3-879d-1bdcf5508a8f
These IDs are inputs the enactment design must accept (as foreign references) but does not own.
8. Survey summary — design constraints distilled
hard constraints (must hold in any design):
C-1: writes to public.information_unit and public.unit_version go through
fn_iu_gateway_write_guard; only canonical writers in
iu_create.gateway.allowed_marker_values are permitted.
C-2: cutter_exec role has SELECT-only on both tables.
C-3: information_unit.lifecycle_status has NO CHECK constraint;
semantic vocab is policy-enforced inside functions only.
C-4: unit_version.enacted_at already exists (nullable timestamptz)
— design must populate it on enactment.
C-5: fn_iu_apply_edit_draft contains a GLOBAL "uniform lifecycle_status
across all UVs" check that breaks the instant any UV transitions
out of 'draft'. Either patch this function or sequence enactment
after that path is hardened (see OQ-1).
C-6: trg_iu_birth_gate_layer1 is BEFORE INSERT only — UPDATE-only
enactment will not re-trigger layer-1 P-pub1/P-pub2 warnings.
C-7: trg_iu_birth_gate_layer2 is AFTER INSERT OR UPDATE (DEFERRED);
enactment UPDATE must leave version_anchor_ref/content_anchor_ref
untouched to avoid re-checks.
C-8: existing public.lifecycle_log is INTEGER-keyed, incompatible
with UUID-keyed IU; a new iu_lifecycle_log table is required
OR audit must be carried inside cutter_governance.decision_backlog_*.
soft constraints (design should respect):
S-1: Adopt the tac_uv_lifecycle_vocab 4-state vocab
{draft, enacted, superseded, retired} — already canonical in TAC.
S-2: Mirror fn_tac_enacted_immut pattern for enacted-IU immutability.
S-3: Keep audit linkage to cutter_governance (review_decision_id,
cut_change_set_id) as soft FKs in the new log table — full FK
is impossible cross-schema while context_pack_readonly cannot
SELECT cutter_governance.
S-4: Detector / L3 monitoring is deferred per Pack 22 closure;
speed-bump gateway is the production posture.
forbidden in any design:
F-1: No DROP TRIGGER trg_aa_iu_gateway_write_guard (would re-open
direct-INSERT/UPDATE).
F-2: No widening of iu_create.gateway.allowed_marker_values beyond
the strict minimum (must be specific function name).
F-3: No exempt_policy = 'constitution_first_enact' OR similar
one-shot UPDATE (explicitly rejected by Pack 22-P3 doctrine
and by the prior assessment as OPT-E3 NOT_RECOMMENDED).
F-4: No direct UPDATE on production rows from any role.
9. G1 disposition
G1_live_lifecycle_survey : PASS
production_mutation : NONE
next : G2 — existing docs/code discovery review
(see [[dot-iu-cutter-v0-5-02-existing-lifecycle-docs-code-review-2026-05-20]])
Related KB documents:
- [[dot-iu-cutter-v0-5-02-existing-lifecycle-docs-code-review-2026-05-20]] — discover phase
- [[dot-iu-cutter-v0-5-03-design-options-analysis-2026-05-20]] — OPT-E1/E2/E3 comparison
- [[dot-iu-cutter-v0-5-04-recommended-lifecycle-enactment-contract-2026-05-20]] — function contract
- [[dot-iu-cutter-v0-5-05-grant-verification-rollback-plan-2026-05-20]] — operations
- [[dot-iu-cutter-v0-5-06-final-lifecycle-design-report-2026-05-20]] — final
- Prior [[dot-iu-cutter-v0-5-04-lifecycle-enactment-assessment-2026-05-20]] (in
v0.5-post-cut-verify-governed-recording-release-readiness/)