KB-227F

P3D Pack 1 Read-Only Inventory Report

10 min read Revision 1
p3dpack1inventoryreportreadonly2026-05-11

P3D Pack 1 Read-Only Inventory Report

Date: 2026-05-11 Mode: READ_ONLY_INVENTORY Executed by: Claude Code agent (VPS 38.242.240.89, postgres container) Source prompt: knowledge/dev/laws/dieu44-trien-khai/prompts/p3d-pack1-readonly-inventory-prompt.md

Summary

phase_status=PASS
mode=READ_ONLY_INVENTORY
no_mutation_performed=true
fail_count=0
warn_count=0
log_path=/tmp/p3d-pack1-inventory-20260511-034750.log
report_path=knowledge/dev/laws/dieu44-trien-khai/reports/p3d-pack1-readonly-inventory-report.md

All 12 sections (S0..S12, including S7B) executed without SQL error.

Key findings

S0 — table existence

All six target tables present as ordinary relations (relkind='r'): tac_publication, tac_publication_member, tac_logical_unit, tac_unit_version, information_unit, unit_version.

S1 — tac_logical_unit

  • 13 columns. PK id uuid default gen_random_uuid(). canonical_address text NOT NULL (UNIQUE via tac_logical_unit_canonical_address_key).
  • Domain columns: doc_code, parent_id, sort_order, section_type, section_code, owner, identity_profile jsonb, tier, lifecycle_status (default draft_only), created_at/updated_at.
  • 86 rows. All sampled rows have lifecycle_status=draft_only. Sample includes D38-DIEU28 tree.

S2 — tac_unit_version

  • 20 columns including body, description, content_hash, lifecycle_status (default draft), review_state (default unreviewed), length_flag, length_exception_reason, content_profile jsonb, editor, provenance (default PROV-AI), vector_sync_status (default pending), vector_synced_at, vector_chunk_count, enacted_at.
  • 86 rows; all lifecycle_status=draft.
  • Note hash_mismatch_count = 86 against md5(body). All TAC unit_version rows have content_hash IS DISTINCT FROM md5(body). This is consistent with TAC using a different hash algorithm/payload than raw md5(body) (e.g. trimmed/normalized body or sha256-derived value); not a verdict failure under this read-only check, but flagged so Pack 1 design does not assume content_hash == md5(body).

S3 — tac_publication

  • 15 columns: doc_code, version, publication_type, name, owner, description, lifecycle_status (default proposed), enacted_at, council_score, approved_by, risk_tier (default medium), publication_profile jsonb.
  • 3 rows: DIEU-35 v5.2 (S178-FIX23, highest risk), DIEU-32 v1.1, DIEU-28 v2.0. All lifecycle_status=proposed. enacted_at NULL for all three.

S4 — tac_publication_member

  • 6 columns: id, publication_id, logical_unit_id, unit_version_id (all NOT NULL), render_order (default 0), created_at.
  • Render orders fully dense: DIEU-35 (36 members, 0..35, 36 distinct), DIEU-32 (23, 0..22, 23 distinct), DIEU-28 (27, 0..26, 27 distinct). No duplicate orders.

S5 — information_unit (native IU)

  • 16 columns: canonical_address UNIQUE, unit_kind, lifecycle_status (default draft), content_anchor_ref text, version_anchor_ref uuid (FK -> unit_version.id), owner_ref, parent_or_container_ref uuid, conformance_status (default open), identity_profile jsonb, created_by/updated_by NOT NULL, deleted_at, sort_order (nullable).
  • 12 rows, all pilot/test addresses (pilot.*, test/p3*), all lifecycle_status=draft, has_parent=false for all sampled rows.

S6 — unit_version (native UV)

  • 9 columns: unit_id (FK -> information_unit.id), body NOT NULL, content_hash NOT NULL, version_seq NOT NULL, lifecycle_status (default draft), content_profile jsonb, created_at, created_by NOT NULL. UNIQUE (unit_id, version_seq).
  • 19 rows, all lifecycle_status=draft.
  • Note hash_mismatch_count = 19 against md5(body) — same observation as TAC: content_hash is not raw md5(body). Pack 1 design must not assume that equivalence.

S7 — column comparison (logical unit)

TAC vs IU column overlap: only canonical_address, id, identity_profile, lifecycle_status, sort_order, created_at, updated_at are in both. TAC-only: doc_code, owner, parent_id, section_code, section_type, tier. IU-only: conformance_status, content_anchor_ref, created_by, deleted_at, owner_ref, parent_or_container_ref, unit_kind, updated_by, version_anchor_ref. Names are renamed (parent_id ↔ parent_or_container_ref; owner ↔ owner_ref) and TAC rich-section metadata (section_type/section_code/tier) has no IU counterpart.

S7B — column comparison (unit version)

Overlap: body, content_hash, content_profile, created_at, id, lifecycle_status. TAC-only review/length/vector/provenance fields (description, editor, enacted_at, length_exception_reason, length_flag, logical_unit_id, provenance, review_state, title, updated_at, vector_*, version_number). IU-only: created_by, unit_id, version_seq. Cardinality field renamed (logical_unit_idunit_id; version_numberversion_seq).

S8 — FKs / indexes / triggers

  • 13 FKs total. TAC side bound to vocab tables (tac_*_vocab) for lifecycle_status, review_state, section_type, publication_type. IU side: information_unit.version_anchor_ref -> unit_version.id (fk_iu_version_anchor); unit_version.unit_id -> information_unit.id.
  • 37 indexes on the six tables. Both sides have GIN on jsonb profile, btree on lifecycle_status, btree on canonical_address with UNIQUE.
  • 13 triggers: TAC has trg_tac_birth_gate_lu, trg_tac_birth_gate_uv, trg_tac_enacted_immut, trg_tac_uv_compute_derived, trg_tac_pm_consistency, trg_tac_pm_enacted_lock. IU has trg_aa_iu_gateway_write_guard, trg_birth_information_unit, trg_iu_birth_gate_layer1, trg_iu_birth_gate_layer2, trg_iu_updated_at. UV has trg_aa_iu_notif_version, trg_aa_uv_gateway_write_guard. All tgenabled='O' (enabled).

S9 — IU functions

All six expected functions present:

  • fn_iu_create(text,text,text,text,text,text,text,text,uuid) -> jsonb (9 args)
  • fn_iu_create_plan(text,text,text,text,text,text,text,text,uuid) -> jsonb (9 args)
  • fn_iu_apply_edit_draft(uuid,text,text) -> jsonb
  • fn_iu_edit(text,text,text,text,text,text) -> jsonb
  • fn_iu_save(text,text,text,text,text,text) -> jsonb
  • fn_iu_verify_invariants(text) -> jsonb

S10 — dot_config IU keys

12 keys present. Notable:

  • Gateway mode = enforced; direct_insert_policy = block_after_guard; exempt_policy = none_active.
  • iu_create.gateway.canonical_function = public.fn_iu_create(text,text,text,text,text,text,text,text,uuid) (matches S9 signature).
  • iu_create.gateway.allowed_marker_values = fn_iu_create,fn_iu_apply_edit_draft.
  • iu_edit.policy.default_mode = require_review; iu_edit.schema.version = p3b-v1.

S11 — canonical_address overlap

  • tac_lu_count=86, iu_count=12, overlap_count=0. No canonical_address is shared between TAC and native IU. The 12 IU rows are pilot/test fixtures (pilot.*, test/p3*); TAC rows are real DIEU-* law addresses.

S12 — publication_member binding

  • 86 total members across the 3 publications, null_lu=0, null_uv=0. All members fully bind both logical_unit_id and unit_version_id.

Impact on Pack 1 design (EVOLVE assumptions)

Confirmed assumptions:

  • TAC and native IU coexist as distinct schemas; no canonical_address collision today (S11).
  • Native IU/UV gateway is enforced (block_after_guard, enforced mode) and core IU functions are present and signature-stable (S9, S10).
  • Birth-gate triggers on both sides are enabled (S8C). Birth gate v2 hash matches Step-1 baseline.
  • Publication binding integrity holds: every TAC publication_member has both LU and UV references (S12).

Contradictions / friction points that Pack 1 must address explicitly:

  1. Hash semantics differ from md5(body) on both tac_unit_version and unit_version (100% mismatch in S2B/S6B). EVOLVE must not promote a "hash equivalence" check; specify the actual hashing rule for migration.
  2. Schema delta is non-trivial. S7/S7B show TAC-rich fields (section_type, section_code, tier, provenance, review_state, length_*, vector_*, editor, enacted_at, version_number, description) have no native IU/UV counterpart. EVOLVE either drops them, maps them into identity_profile/content_profile jsonb, or extends the native schema; current native schema has no place for them.
  3. Lifecycle vocab is FK-bound on TAC side (tac_*_lifecycle_vocab, tac_review_state_vocab, tac_section_type_vocab, tac_publication_type_vocab). Native IU/UV use plain text with defaults. Cross-table evolution must reconcile these vocabularies before any data move.
  4. Native IU has only pilot/test rows (12 rows, all pilot.* / test/*). TAC carries the real 86-row DIEU corpus across DIEU-28/32/35. EVOLVE direction (TAC -> native IU) is consistent with the data weight; no surprise content already in native IU that would block migration.
  5. Identifier renames (parent_idparent_or_container_ref, ownerowner_ref, logical_unit_idunit_id, version_numberversion_seq) are pure column-name mappings; no semantic mismatch detected, but Pack 1 mapping table must be explicit.

No fixes attempted. Reporting only as required by the prompt.

Boundaries honored

  • No DDL, no DML, no migration.
  • No new table/view/trigger/function/index.
  • No permission change.
  • No Nuxt code, no deploy/restart.
  • No DOT-119 execution/rewrite; no fn_birth_registry_auto modification.
  • No direct write to information_unit / unit_version.
  • No "Thông tin" filter added.
  • Only mutation: this KB report upload.