P3D Pack 1 — Design Addendum (Post-Inventory)
P3D Pack 1 — Design Addendum (Post-Inventory Patch)
Date: 2026-05-11 Author: Opus 4.7 Base: design/p3d-pack1-iu-canonical-contract-and-tac-iu-reconciliation-design.md Inventory: reports/p3d-pack1-readonly-inventory-report.md GPT review: reviews/gpt-review-step1-checkpoint-pack1-inventory-final-before-resume-2026-05-11.md
1. Inventory-Confirmed: EVOLVE Still Valid
- 0 canonical_address overlap (86 TAC + 12 IU pilot) → clean migration space
- IU native has gateway + 6 functions + 12 dot_config keys → stronger governance than TAC
- TAC has richer schema (20 UV cols vs 9 IU UV cols) → IU must be EXTENDED before migration
- Publication membership (86 members, render_order dense) → separate family, keep as-is
2. Full Column Mapping — TAC Logical Unit ↔ IU information_unit
| TAC column | IU column | Status | Action |
|---|---|---|---|
id (uuid PK) |
id (uuid PK) |
✅ Compatible | Map |
canonical_address (text UNIQUE) |
canonical_address (text UNIQUE) |
✅ Compatible | Map |
doc_code (text) |
— | ❌ Missing in IU | ADD column (structural binding per P5) |
parent_id (uuid self-ref) |
parent_or_container_ref (uuid) |
✅ Rename | Map (name differs) |
sort_order (int) |
sort_order (int, nullable) |
✅ Compatible | Map (IU has it, nullable) |
section_type (text FK vocab) |
— | ❌ Missing in IU | ADD column (FK to tac_section_type_vocab) |
section_code (text) |
— | ❌ Missing in IU | ADD column or profile JSONB |
owner (text) |
owner_ref (text) |
✅ Rename | Map |
identity_profile (jsonb GIN) |
identity_profile (jsonb GIN) |
✅ Compatible | Map |
tier (text) |
— | ❌ Missing in IU | Profile JSONB (derived/cache) |
lifecycle_status (text) |
lifecycle_status (text) |
✅ Compatible | Map (vocab reconciliation needed) |
created_at / updated_at |
created_at / updated_at |
✅ Compatible | Map |
| — | unit_kind (text) |
IU-only | Set law_unit for migrated rows |
| — | conformance_status (text) |
IU-only | Set open for migrated rows |
| — | content_anchor_ref (text) |
IU-only | Compute from UV |
| — | version_anchor_ref (uuid FK) |
IU-only | Set to latest UV |
| — | created_by / updated_by (text NOT NULL) |
IU-only | Set migration_pack1 |
| — | deleted_at |
IU-only | NULL |
3. Full Column Mapping — TAC unit_version ↔ IU unit_version
| TAC column | IU column | Status | Action |
|---|---|---|---|
id (uuid PK) |
id (uuid PK) |
✅ Compatible | Map |
logical_unit_id (uuid FK) |
unit_id (uuid FK) |
✅ Rename | Map |
body (text) |
body (text NOT NULL) |
✅ Compatible | Map |
content_hash (text) |
content_hash (text NOT NULL) |
⚠️ Same column, different hash algo? | Investigate (§4) |
version_number (int) |
version_seq (int) |
✅ Rename | Map |
lifecycle_status (text FK vocab) |
lifecycle_status (text) |
✅ Vocab reconcile needed | Map |
content_profile (jsonb) |
content_profile (jsonb) |
✅ Compatible | Map |
created_at |
created_at |
✅ Compatible | Map |
title (text) |
— | ❌ Missing in IU UV | ADD column |
description (text) |
— | ❌ Missing in IU UV | ADD column |
review_state (text FK vocab) |
— | ❌ Missing in IU UV | ADD column |
provenance (text) |
— | ❌ Missing in IU UV | ADD column or profile |
editor (text) |
— | ❌ Missing in IU UV | ADD column or profile |
enacted_at (timestamptz) |
— | ❌ Missing in IU UV | ADD column |
length_flag (text) |
— | ❌ Missing in IU UV | Profile JSONB |
length_exception_reason (text) |
— | ❌ Missing in IU UV | Profile JSONB |
vector_sync_status (text) |
— | ❌ Missing in IU UV | ADD column (vector contract) |
vector_synced_at (timestamptz) |
— | ❌ Missing in IU UV | ADD column |
vector_chunk_count (int) |
— | ❌ Missing in IU UV | ADD column |
updated_at |
— | ❌ Missing in IU UV | ADD column |
| — | created_by (text NOT NULL) |
IU-only | Set migration_pack1 |
4. Hash Semantics — MUST RESOLVE BEFORE MIGRATION
Finding: 86/86 TAC UV + 19/19 IU UV have content_hash ≠ md5(body).
Investigation needed (Phase 2 preflight):
-- Discover actual hash algorithm
SELECT content_hash, md5(body), length(content_hash),
content_hash = md5(body) AS md5_match,
content_hash = encode(sha256(body::bytea), 'hex') AS sha256_match
FROM tac_unit_version LIMIT 3;
-- Same for IU native
SELECT content_hash, md5(body), length(content_hash),
content_hash = md5(body) AS md5_match
FROM unit_version LIMIT 3;
Possible explanations: (a) SHA-256 instead of MD5, (b) hash includes title+description+profile (per P5 design §5.2), (c) normalized/trimmed body before hash.
Rule: Migration must preserve existing hash for TAC rows. IU UV hash computation rule must be documented and applied consistently.
5. TAC-Rich Fields Handling Strategy
| Category | Fields | Strategy | Rationale |
|---|---|---|---|
| Universal (every unit needs) | title, description, review_state, enacted_at, updated_at | ADD columns to IU UV | Core workflow fields, query/filter critical |
| Universal (governance) | provenance, editor | ADD columns to IU UV | Audit trail |
| Vector contract | vector_sync_status, vector_synced_at, vector_chunk_count | ADD columns to IU UV | Per unified vector contract SSOT |
| Structural (law-specific) | section_type, doc_code | ADD columns to IU LU | Filter/query critical for law_unit |
| Nice-to-have | section_code, tier, length_flag, length_exception_reason | Store in profile JSONB | Derived/cache, not query-critical |
Total new columns: ~8 on information_unit (if doc_code + section_type added) + ~9 on unit_version.
6. Vocab Reconciliation
| Vocab table | TAC usage | IU native usage | Reconciliation |
|---|---|---|---|
tac_lifecycle_vocab (LU) |
FK from tac_logical_unit.lifecycle_status | Plain text, default draft |
IU ADD FK to same vocab OR create shared vocab |
tac_lifecycle_vocab (UV) |
FK from tac_unit_version.lifecycle_status | Plain text, default draft |
Same |
tac_review_state_vocab |
FK from tac_unit_version.review_state | N/A (no column) | ADD column + FK |
tac_section_type_vocab |
FK from tac_logical_unit.section_type | N/A (no column) | ADD column + FK |
tac_publication_type_vocab |
FK from tac_publication.publication_type | N/A (separate family) | Keep as-is |
Recommendation: Rename vocab tables to drop tac_ prefix (e.g., lifecycle_vocab, review_state_vocab, section_type_vocab) since they're universal, not TAC-specific. Or keep tac_ prefix and add FK from IU to same tables.
7. Publication Membership — Preserved As-Is
tac_publication + tac_publication_member remain separate tables (publication is a different family). During EVOLVE migration, tac_publication_member.logical_unit_id will point to the new IU row (same UUID if we INSERT with explicit id).
Key: Migration must preserve UUID PK values so publication_member FK bindings remain valid.
8. Phase 2 Scope (DDL Extend IU Schema)
Phase 2 adds columns to IU native, does NOT migrate data yet.
| Table | New columns | Default | Nullable |
|---|---|---|---|
| information_unit | doc_code TEXT | NULL | YES (non-law units don't have doc_code) |
| information_unit | section_type TEXT | NULL | YES (+ FK to section_type_vocab later) |
| information_unit | section_code TEXT | NULL | YES |
| unit_version | title TEXT | NULL | YES (backfill for existing pilot rows) |
| unit_version | description TEXT | NULL | YES |
| unit_version | review_state TEXT | 'unreviewed' | YES |
| unit_version | provenance TEXT | 'PROV-AI' | YES |
| unit_version | editor TEXT | NULL | YES |
| unit_version | enacted_at TIMESTAMPTZ | NULL | YES |
| unit_version | updated_at TIMESTAMPTZ | now() | NO (add trigger) |
| unit_version | vector_sync_status TEXT | 'pending' | YES |
| unit_version | vector_synced_at TIMESTAMPTZ | NULL | YES |
| unit_version | vector_chunk_count INT | 0 | YES |
Not in Phase 2: data migration, view creation, TAC table modification, vocab rename.
Pack 1 Design Addendum | Post-Inventory | 2026-05-11 | Opus 4.7