P2B-P0 — IU/UV Schema Inspection Report
P2B-P0 — IU/UV Schema Inspection Report
Date: 2026-05-05 | Mode: READ-ONLY | Status: COMPLETE Source: live PG inspection on VPS 38.242.240.89 (
docker exec -i postgres psql -U directus -d directus) Controlling prompt: P2B-P0 rev2
§3.1 Actual columns
information_unit (15 cols)
| # | column | type | nullable | default | INSERT requirement |
|---|---|---|---|---|---|
| 1 | id | uuid | NO | gen_random_uuid() | optional |
| 2 | canonical_address | text | NO | — | REQUIRED |
| 3 | unit_kind | text | NO | — | REQUIRED (vocab-checked) |
| 4 | lifecycle_status | text | NO | 'draft' | optional |
| 5 | content_anchor_ref | text | YES | — | REQUIRED at COMMIT (L2) |
| 6 | version_anchor_ref | uuid | YES | — | REQUIRED at COMMIT (L2, FK→unit_version, DEFERRABLE) |
| 7 | owner_ref | text | NO | — | REQUIRED |
| 8 | parent_or_container_ref | uuid | YES | — | optional |
| 9 | conformance_status | text | NO | 'open' | optional |
| 10 | identity_profile | jsonb | NO | '{}' | REQUIRED non-empty (title, owner_lookup_ref, primary_section_type_ref by L1) |
| 11 | created_at | timestamptz | NO | now() | optional |
| 12 | updated_at | timestamptz | NO | now() | optional (auto via trg_iu_updated_at) |
| 13 | created_by | text | NO | — | REQUIRED |
| 14 | updated_by | text | NO | — | REQUIRED |
| 15 | deleted_at | timestamptz | YES | — | optional |
NOT NULL without default → must be in payload:
canonical_address, unit_kind, owner_ref, created_by, updated_by.
Plus identity_profile defaults to {} but L1 trigger requires non-empty fields, so effectively REQUIRED with content.
unit_version (9 cols)
| # | column | type | nullable | default | INSERT requirement |
|---|---|---|---|---|---|
| 1 | id | uuid | NO | gen_random_uuid() | optional |
| 2 | unit_id | uuid | NO | — | REQUIRED (FK→information_unit) |
| 3 | body | text | NO | — | REQUIRED |
| 4 | content_hash | text | NO | — | REQUIRED |
| 5 | version_seq | integer | NO | — | REQUIRED |
| 6 | lifecycle_status | text | NO | 'draft' | optional |
| 7 | content_profile | jsonb | NO | '{}' | optional |
| 8 | created_at | timestamptz | NO | now() | optional |
| 9 | created_by | text | NO | — | REQUIRED |
NOT NULL without default → must be in payload:
unit_id, body, content_hash, version_seq, created_by.
§3.2 Constraints summary
information_unit
- PK:
information_unit_pkeyon(id) - UNIQUE:
information_unit_canonical_address_keyon(canonical_address) - FK:
fk_iu_version_anchor→unit_version(id)DEFERRABLE INITIALLY DEFERRED - Constraint trigger:
trg_iu_birth_gate_layer2(DEFERRABLE INITIALLY DEFERRED) — L2 birth gate
unit_version
- PK:
unit_version_pkeyon(id) - UNIQUE:
uq_unit_version_seqon(unit_id, version_seq) - FK:
unit_version_unit_id_fkey→information_unit(id)(NOT deferrable)
FK references INTO IU/UV (Q11)
Inbound (public schema relevant):
information_unit.version_anchor_ref→unit_version(id)(DEFERRABLE)unit_version.unit_id→information_unit(id)
Sandbox-only refs (irrelevant to pilot): sandbox_tac.change_set_member, sandbox_tac.publication_member.
Circular FK between IU and UV is resolved via DEFERRABLE on fk_iu_version_anchor — must INSERT inside a transaction with SET CONSTRAINTS DEFERRED or rely on default-deferred behavior.
§3.3 Triggers summary
information_unit (4 triggers)
| trigger | timing | function | role |
|---|---|---|---|
trg_iu_birth_gate_layer1 |
BEFORE INSERT | fn_iu_birth_gate_layer1 |
row-level field + vocab validation |
trg_birth_information_unit |
AFTER INSERT | fn_birth_registry_auto('__birth_synthetic_id__') |
auto-write into birth_registry |
trg_iu_birth_gate_layer2 |
AFTER INSERT/UPDATE (CONSTRAINT, DEFERRED) | fn_iu_birth_gate_layer2 |
commit-time anchor consistency |
trg_iu_updated_at |
BEFORE UPDATE | fn_iu_updated_at |
maintains updated_at |
unit_version
0 triggers. No birth trigger on UV (consistent with birth_code_strategy='subordinate').
Birth-gate logic extracted (operational impact on INSERT):
- L1 (BEFORE INSERT) rejects unless ALL of:
canonical_address,unit_kind,owner_refnon-emptylifecycle_status,conformance_statusnon-NULLunit_kindexists asdot_configkeyvocab.unit_kind.<value>identity_profile->>'title'non-emptyidentity_profile->>'owner_lookup_ref'non-emptyidentity_profile->>'primary_section_type_ref'non-empty AND indot_configvocab.section_type.<value>identity_profile->>'publication_type_ref'(if present) must be indot_configvocab.publication_type.<value>- WARNING-only (PILOT):
publication_authority_ref,publication_type_refmissing — production will block
- L2 (deferred AFTER) rejects unless:
content_anchor_refnon-emptyversion_anchor_refnon-NULLversion_anchor_refpoints to aunit_versionrow withunit_id = NEW.idcontent_anchor_ref = version_anchor_ref::text
Implication: IU pilot INSERT must be done in a single transaction with the matching UV row, with SET CONSTRAINTS ALL DEFERRED (or rely on default deferred), and content_anchor_ref must equal the UV uuid as text.
§3.4 Current counts (baselines)
| table | count | expected |
|---|---|---|
| information_unit | 0 | 0 ✅ |
| unit_version | 0 | 0 (baseline captured) |
| birth_registry (collection_name=information_unit) | 0 | 0 ✅ |
| birth_registry (collection_name=unit_version) | 0 | 0 (baseline captured) |
All four tables are empty. Clean slate for P2B-P1 pilot.
§3.5 Collection metadata (collection_registry)
| collection | governance_role | birth_code_strategy | birth_code_column | birth_identity_source | description_policy |
|---|---|---|---|---|---|
| information_unit | observed | synthetic_id | (null) | manual | structured_exempt |
| unit_version | observed | subordinate | (null) | manual | structured_exempt |
Notes:
- IU uses
synthetic_idstrategy → birth code =__birth_synthetic_id__sentinel passed to trigger; identity captured fromidentity_profile. - UV is
subordinate→ no own birth row (consistent with 0 birth triggers on UV). description_policy=structured_exempt→ no description backfill required from PROV-HUMAN gate.
§3.6 Audit invariants
| invariant | observed | note |
|---|---|---|
fn_birth_registry_auto md5 |
1f729b3571a74963089bb3ef388217f3 |
record for drift detection |
trg_birth_* count (NOT internal) |
31 | governance baseline |
§3.7 RLS status
pg_policiesfor IU/UV: 0 policies.relrowsecurity = false,relforcerowsecurity = falsefor both tables.- (Note: a sandbox copy of
unit_versionappears inpg_class; public schema rows are RLS-off.)
Impact on INSERT: none. Direct PG inserts as directus role unaffected.
§3.8 Minimum INSERT payload — candidate fields
Hard-required by NOT-NULL-without-default
information_unit:
canonical_address(text, UNIQUE)unit_kind(text, must matchvocab.unit_kind.<value>indot_config)owner_ref(text)created_by(text)updated_by(text)
unit_version:
unit_id(uuid → IU.id)body(text)content_hash(text)version_seq(integer; UNIQUE per unit_id)created_by(text)
Hard-required by birth-gate L1 (effectively required even though identity_profile has default)
identity_profile JSONB must contain at minimum:
{
"title": "<non-empty>",
"owner_lookup_ref": "<non-empty>",
"primary_section_type_ref": "<value with matching vocab.section_type.<value> row in dot_config>"
}
Optional but warned: publication_authority_ref, publication_type_ref.
Hard-required by birth-gate L2 (commit-time)
version_anchor_ref= UV row idcontent_anchor_ref=version_anchor_ref::text- The referenced UV row must have
unit_id = IU.id
Default-able / can be omitted
IU: id, lifecycle_status, conformance_status, identity_profile (but see L1), created_at, updated_at, parent_or_container_ref, deleted_at
UV: id, lifecycle_status, content_profile, created_at
Proposed minimum valid payload shape (single transaction)
BEGIN;
SET CONSTRAINTS ALL DEFERRED;
WITH iu AS (
INSERT INTO information_unit (
id, canonical_address, unit_kind, owner_ref,
created_by, updated_by, identity_profile
) VALUES (
gen_random_uuid(),
'iu/pilot/0001',
'<unit_kind_in_vocab>',
'<owner_ref>',
'<actor>',
'<actor>',
jsonb_build_object(
'title','Pilot IU 0001',
'owner_lookup_ref','<owner_lookup>',
'primary_section_type_ref','<section_type_in_vocab>'
)
) RETURNING id
), uv AS (
INSERT INTO unit_version (
id, unit_id, body, content_hash, version_seq, created_by
)
SELECT
gen_random_uuid(), iu.id,
'<body>', '<sha256-or-similar>', 1, '<actor>'
FROM iu
RETURNING id, unit_id
)
UPDATE information_unit
SET version_anchor_ref = uv.id,
content_anchor_ref = uv.id::text
FROM uv
WHERE information_unit.id = uv.unit_id;
COMMIT; -- L2 fires here, then trg_birth_information_unit on the IU insert (AFTER INSERT, already fired immediately)
Note:
trg_birth_information_unitis AFTER INSERT (NOT deferred), so it fires immediately after the IU INSERT, before UV exists. This is fine because the birth trigger only writes tobirth_registryfromidentity_profileand does not require anchors. The L2 deferred constraint trigger handles anchor consistency at commit.
§3.9 Risks / blockers before P2B-P1
- vocab dependencies (BLOCKING): Need confirmed values for:
dot_configkeyvocab.unit_kind.<X>— pick a validunit_kinddot_configkeyvocab.section_type.<Y>— pick a validprimary_section_type_ref- (optional)
vocab.publication_type.<Z>ifpublication_type_refis set → P2B-P1 must query these before crafting payload.
- Circular FK timing: Requires single-tx flow with deferred constraints, or NULL-then-UPDATE sequence as drafted above. Cannot do plain two separate inserts.
content_anchor_ref↔version_anchor_refequality: Hard-coded equality check (text == uuid::text). Don't deviate.- Birth-trigger AFTER-INSERT ordering: Birth registry row is written from
identity_profilesnapshot at IU INSERT time — make sure profile is final before INSERT (not patched later). canonical_addressUNIQUE: Pilot must use a distinct address.owner_ref/owner_lookup_ref: Format/vocabulary not enforced by trigger but expected by downstream registries — confirm shape with GPT/User before P1.- No PROV-HUMAN backfill needed:
description_policy=structured_exempt. - No RLS: Direct PG insert path is unblocked at row-security layer.
Non-issues observed:
- Empty UV table is consistent with empty IU; no orphaned UVs to migrate.
- 31 birth triggers is consistent with prior governance baseline; not abnormal.
§3.10 Hard-stop confirmation
P2B-P0 COMPLETE. HARD STOP. Chờ GPT/User review trước P2B-P1.
No DDL executed. No INSERT/UPDATE/DELETE executed. No row mutations. Read-only queries only.
Generated 2026-05-05 by Opus 4.7 (Claude Code) via P2B-P0 rev2 prompt. 12 PG queries + 2 supplementary function-def reads for trigger logic capture.