P3D Pack 1 Phase 4 — Vocab Prep Implementation Prompt DRAFT v7
P3D Pack 1 Phase 4 — Vocab Prep ONLY — Implementation Prompt DRAFT v7
Date: 2026-05-11 | Status: DRAFT v7 — G3 exact contract + hash provenance restore + agent wording fix Supersedes: DRAFT v6 (NOT APPROVED — three precision issues per GPT review) Species mapping: READ-ONLY candidate generation only (no INSERT) Hash provenance: DOCUMENT-ONLY (no executable code; preserves TAC source hash as provenance)
Philosophy:
If not certainly right, it is wrong.Not hardcoded does not mean "wrapped in a temp table"; it means every value is derived from a declared authoritative source at execution time, and the prompt states why that source is authoritative.
0. Scope and authoritative sources
| Category | Mode in v7 | Authoritative source (declared at runtime) | Why this source is authoritative |
|---|---|---|---|
vocab.section_type.* |
Executable candidate | public.tac_logical_unit.section_type DISTINCT non-null values currently used in production TAC rows |
These are the section_type values that already exist in TAC content and must be acceptable to the IU vocab registry before any TAC→IU reconciliation. The set is determined by production data, not by any prompt-supplied list. |
vocab.publication_type.* |
Executable candidate | public.tac_publication.publication_type DISTINCT non-null values currently used |
Same reasoning — publication_type values used by live publications must be present in IU vocab before reconciliation. |
vocab.unit_kind.law_unit |
Executable candidate | Pack 1 EVOLVE contract: TAC logical units become native IU law_unit rows |
TAC has no unit_kind column; law_unit is a migration-contract value, not a snapshot. The literal law_unit appears in this prompt only as a contract-required identifier, gated by fn_iu_create_plan resolution. |
Species mapping (entity_species, species_collection_map) |
Read-only candidate only | n/a — authoritative pairing is unresolved | Fuzzy ILIKE '%law%' matching is not production-safe. Candidates are reported for GPT/User decision. No INSERT. |
Hash provenance (unit_version.content_profile.source_hashes.tac_v1) |
Document-only | n/a — preserves original TAC tac_unit_version.content_hash as provenance reference; no re-hashing |
See §7 for the corrected contract. Re-hashing TAC content in the IU layer would lose the link to the original TAC commit; provenance must preserve the source hash by reference. |
0.1 Difference from v6 (top 3)
- G3 now performs EXACT function-contract verification (not containment):
count(*) = 1(no overload),pg_get_function_identity_arguments(oid)exact-string match against the 9-arg production identity, andprorettypeexactlyjsonb. v6 only checked that the 9 arg names were a subset ofproargnames— v7 is strict. - Section E hash provenance restored to Phase-3 discovery contract —
source_hashes.tac_v1preserves the originaltac_unit_version.content_hashas a provenance reference, not a re-computed hash of normalized content. The v6 wording (sha256(normalized_content_of_corresponding_tac_logical_unit)) was a regression to the pre-Phase-3 formulation and has been removed. - Agent-boundary wording corrected in §10 — v6 said "Do not run any agent", which contradicts the fact that this prompt is itself executed by the assigned Agent. v7 says "Do not dispatch nested/secondary agents from inside this task; this prompt itself may be executed only by the assigned Agent after GPT/User approval."
All other v6 mechanics (G1, G2, G4, G5, G6, G7 jsonb logic, delta computation, INSERT/RETURNING, rollback model, species read-only scope, Section C call, single-transaction model) are preserved byte-for-byte. Only probe address/title strings carry the version bump v6 → v7 for log clarity.
1. Hard boundaries
- No species INSERT, no
species_collection_mapwrite, no target-collection row literal. - No migration, DDL, function/trigger patch, Directus change, Qdrant/vector touch, collection promotion, edge materialization, birth-gate change.
- No hardcoded seed list, no fixed row count used as logic, no fuzzy
ILIKE '%law%'in executable paths. - No temp table created in one
psqlinvocation and referenced from another. - No description-prefix rollback as primary rollback. Exact keys only.
- Vocab INSERT executes only when G1..G6 pass, after dry-run preview, and only commits if G7 passes.
2. Preflight (bash setup)
set -euo pipefail
TS=$(date +%Y%m%d-%H%M%S)
RUN_MARKER="p3d-phase4-vocab-${TS}"
LOG="/tmp/${RUN_MARKER}.log"
exec > >(tee -a "$LOG") 2>&1
PG_CONTAINER="${PG_CONTAINER:-postgres}"
PG_DB="${PG_DB:-directus}"
PG_USER="${PG_USER:-directus}"
PSQL_BASE=(docker exec -i "$PG_CONTAINER" psql -U "$PG_USER" -d "$PG_DB" -v ON_ERROR_STOP=1)
echo "=== PHASE 4 VOCAB v7 — RUN=$RUN_MARKER ==="
ON_ERROR_STOP=1 ensures any server-side RAISE EXCEPTION propagates as a non-zero psql exit code. The bash set -euo pipefail then aborts the script. Combined with the single-transaction model below, this makes any gate failure a clean rollback + script abort.
3. Section A — Read-only preflight inspection (no temp tables, separate psql calls OK because read-only)
These calls only print state for the agent and report. They do not create temp tables, so they may be in independent psql sessions without consequence.
# A1: dot_config schema snapshot
"${PSQL_BASE[@]}" <<'SQL'
SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'dot_config'
ORDER BY ordinal_position;
SQL
# A2: dot_config constraints/indexes snapshot
"${PSQL_BASE[@]}" <<'SQL'
SELECT conname, contype, pg_get_constraintdef(oid) AS def
FROM pg_constraint
WHERE conrelid = 'public.dot_config'::regclass
ORDER BY conname;
SELECT indexname, indexdef
FROM pg_indexes
WHERE schemaname = 'public' AND tablename = 'dot_config'
ORDER BY indexname;
SQL
# A3: source tables snapshot
"${PSQL_BASE[@]}" <<'SQL'
SELECT relname
FROM pg_class
WHERE relname IN ('dot_config','tac_logical_unit','tac_publication')
AND relkind = 'r'
AND relnamespace = 'public'::regnamespace
ORDER BY relname;
SQL
# A4: current vocab.* state
"${PSQL_BASE[@]}" <<'SQL'
SELECT key, value
FROM public.dot_config
WHERE key LIKE 'vocab.%'
ORDER BY key;
SQL
# A5: read-only delta preview (no temp tables persisted)
"${PSQL_BASE[@]}" <<'SQL'
WITH delta AS (
SELECT 'section_type' AS category,
tlu.section_type AS code,
'vocab.section_type.' || tlu.section_type AS target_key
FROM public.tac_logical_unit tlu
WHERE tlu.section_type IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM public.dot_config dc
WHERE dc.key = 'vocab.section_type.' || tlu.section_type
)
GROUP BY tlu.section_type
UNION ALL
SELECT 'publication_type',
tp.publication_type,
'vocab.publication_type.' || tp.publication_type
FROM public.tac_publication tp
WHERE tp.publication_type IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM public.dot_config dc
WHERE dc.key = 'vocab.publication_type.' || tp.publication_type
)
GROUP BY tp.publication_type
UNION ALL
SELECT 'unit_kind', 'law_unit', 'vocab.unit_kind.law_unit'
WHERE NOT EXISTS (
SELECT 1 FROM public.dot_config dc
WHERE dc.key = 'vocab.unit_kind.law_unit'
)
)
SELECT * FROM delta ORDER BY category, code;
SQL
Agent must include A1..A5 output verbatim in the report. If A5 surfaces any value that does not look like a legitimate production identifier (e.g. obvious garbage), STOP and report; do not run Section B.
4. Section B — Executable vocab transaction (SINGLE psql call, SINGLE transaction)
This is the only section that writes. It must run as one psql invocation. Every temp table, every gate, every INSERT, and the planner verification execute inside one BEGIN..COMMIT. Any RAISE EXCEPTION rolls everything back automatically before commit.
"${PSQL_BASE[@]}" -v run_marker="$RUN_MARKER" <<'SQL'
BEGIN;
SET LOCAL lock_timeout = '5s';
SET LOCAL statement_timeout = '120s';
SET LOCAL p3d.run_marker = :'run_marker';
-- =========================================================================
-- G1 (executable) — dot_config has exact required columns
-- Authoritative source: information_schema.columns
-- =========================================================================
DO $$
DECLARE
required text[] := ARRAY['key','value','description','updated_at'];
col text;
missing text[] := ARRAY[]::text[];
BEGIN
FOREACH col IN ARRAY required LOOP
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'dot_config'
AND column_name = col
) THEN
missing := missing || col;
END IF;
END LOOP;
IF array_length(missing,1) IS NOT NULL THEN
RAISE EXCEPTION 'G1_FAIL dot_config missing required columns: %', missing;
END IF;
RAISE NOTICE 'G1_PASS dot_config has columns (key,value,description,updated_at)';
END $$;
-- =========================================================================
-- G2 (executable) — single-column UNIQUE/PK on dot_config(key)
-- Authoritative source: pg_constraint + pg_index
-- Required for ON CONFLICT (key) to be deterministic.
-- =========================================================================
DO $$
DECLARE
has_unique boolean;
BEGIN
SELECT
EXISTS (
SELECT 1
FROM pg_constraint c
JOIN pg_attribute a
ON a.attrelid = c.conrelid
AND a.attnum = ANY (c.conkey)
WHERE c.conrelid = 'public.dot_config'::regclass
AND c.contype IN ('u','p')
AND array_length(c.conkey,1) = 1
AND a.attname = 'key'
)
OR EXISTS (
SELECT 1
FROM pg_index ix
WHERE ix.indrelid = 'public.dot_config'::regclass
AND ix.indisunique
AND ix.indnkeyatts = 1
AND (
SELECT a.attname
FROM pg_attribute a
WHERE a.attrelid = ix.indrelid
AND a.attnum = ix.indkey[0]
) = 'key'
)
INTO has_unique;
IF NOT has_unique THEN
RAISE EXCEPTION 'G2_FAIL no UNIQUE/PK constraint or single-column unique index on dot_config(key); ON CONFLICT (key) is unsafe';
END IF;
RAISE NOTICE 'G2_PASS dot_config(key) is uniquely indexed';
END $$;
-- =========================================================================
-- G3 (executable) — source tables + EXACT planner function CONTRACT
-- v7 change: G3b now performs exact identity verification:
-- 1. exactly one function public.fn_iu_create_plan (no overload);
-- 2. pg_get_function_identity_arguments(oid) matches the expected
-- 9-arg production identity string verbatim;
-- 3. return type is exactly jsonb.
-- This is strictly stronger than v6's containment check on proargnames.
--
-- Expected identity (production verified post-v5 dispatch):
-- p_canonical_address text, p_title text, p_body text, p_actor text,
-- p_unit_kind text, p_section_type text, p_owner_ref text,
-- p_publication_type text, p_parent_ref uuid
--
-- Expected return type: jsonb
-- =========================================================================
DO $$
DECLARE
required_tables text[] := ARRAY['dot_config','tac_logical_unit','tac_publication'];
t text;
missing text[] := ARRAY[]::text[];
v_proc_count int;
v_oid oid;
v_identity_args text;
v_rettype text;
expected_identity_args constant text :=
'p_canonical_address text, p_title text, p_body text, p_actor text, '
|| 'p_unit_kind text, p_section_type text, p_owner_ref text, '
|| 'p_publication_type text, p_parent_ref uuid';
expected_rettype constant text := 'jsonb';
BEGIN
-- G3a: required source tables exist
FOREACH t IN ARRAY required_tables LOOP
IF NOT EXISTS (
SELECT 1 FROM pg_class
WHERE relname = t
AND relkind = 'r'
AND relnamespace = 'public'::regnamespace
) THEN
missing := missing || t;
END IF;
END LOOP;
IF array_length(missing,1) IS NOT NULL THEN
RAISE EXCEPTION 'G3_FAIL missing source tables: %', missing;
END IF;
-- G3b.1: exactly one function named public.fn_iu_create_plan (no overload).
-- Overload would make G7's named-arg call ambiguous.
SELECT count(*), max(p.oid)
INTO v_proc_count, v_oid
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname = 'public'
AND p.proname = 'fn_iu_create_plan';
IF v_proc_count = 0 THEN
RAISE EXCEPTION 'G3_FAIL function public.fn_iu_create_plan not found';
END IF;
IF v_proc_count > 1 THEN
RAISE EXCEPTION
'G3_FAIL public.fn_iu_create_plan has % overload(s); G7 named-arg call would be ambiguous',
v_proc_count;
END IF;
-- G3b.2: exact identity-arguments string match (positional + types + names)
v_identity_args := pg_get_function_identity_arguments(v_oid);
IF v_identity_args <> expected_identity_args THEN
RAISE EXCEPTION
'G3_FAIL fn_iu_create_plan identity arguments mismatch (oid=%); expected="%"; actual="%"',
v_oid, expected_identity_args, v_identity_args;
END IF;
-- G3b.3: exact return type
SELECT t.typname
INTO v_rettype
FROM pg_proc p
JOIN pg_type t ON t.oid = p.prorettype
WHERE p.oid = v_oid;
IF v_rettype <> expected_rettype THEN
RAISE EXCEPTION
'G3_FAIL fn_iu_create_plan return type mismatch (oid=%); expected="%"; actual="%"',
v_oid, expected_rettype, v_rettype;
END IF;
RAISE NOTICE
'G3_PASS source tables present; fn_iu_create_plan oid=% has exact 9-arg identity and returns jsonb',
v_oid;
END $$;
-- =========================================================================
-- G4 (executable) — required source columns exist on TAC tables
-- =========================================================================
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema='public' AND table_name='tac_logical_unit' AND column_name='section_type'
) THEN
RAISE EXCEPTION 'G4_FAIL tac_logical_unit.section_type missing';
END IF;
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema='public' AND table_name='tac_publication' AND column_name='publication_type'
) THEN
RAISE EXCEPTION 'G4_FAIL tac_publication.publication_type missing';
END IF;
RAISE NOTICE 'G4_PASS source columns present';
END $$;
-- =========================================================================
-- G6 (executable) — run_marker GUC is set and non-empty
-- (G5 is value-conflict gate, computed after delta tables exist; see below.)
-- =========================================================================
DO $$
DECLARE
rm text := current_setting('p3d.run_marker', true);
BEGIN
IF rm IS NULL OR length(btrim(rm)) = 0 THEN
RAISE EXCEPTION 'G6_FAIL run_marker GUC empty; bash must pass -v run_marker=...';
END IF;
RAISE NOTICE 'G6_PASS run_marker=%', rm;
END $$;
-- =========================================================================
-- Delta temp tables (live only inside THIS transaction)
--
-- Authoritative sources:
-- section_type <- live public.tac_logical_unit.section_type (DISTINCT, NOT NULL)
-- publication_type <- live public.tac_publication.publication_type (DISTINCT, NOT NULL)
-- unit_kind <- Pack 1 EVOLVE contract: native IU 'law_unit'
-- (NOT a TAC-discovered value; declared as contract requirement)
-- =========================================================================
CREATE TEMP TABLE _delta_section_type ON COMMIT DROP AS
SELECT DISTINCT tlu.section_type AS code
FROM public.tac_logical_unit tlu
WHERE tlu.section_type IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM public.dot_config dc
WHERE dc.key = 'vocab.section_type.' || tlu.section_type
);
CREATE TEMP TABLE _delta_pub_type ON COMMIT DROP AS
SELECT DISTINCT tp.publication_type AS code
FROM public.tac_publication tp
WHERE tp.publication_type IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM public.dot_config dc
WHERE dc.key = 'vocab.publication_type.' || tp.publication_type
);
CREATE TEMP TABLE _delta_unit_kind ON COMMIT DROP AS
SELECT 'law_unit'::text AS code
WHERE NOT EXISTS (
SELECT 1 FROM public.dot_config dc
WHERE dc.key = 'vocab.unit_kind.law_unit'
);
-- =========================================================================
-- G5 (executable) — no delta key already exists with a DIFFERENT value
-- Defence in depth against race conditions between delta and insert.
-- Delta WHERE clauses already exclude existing keys; G5 only fires if
-- a concurrent writer added a conflicting row between delta and gate.
-- =========================================================================
DO $$
DECLARE
v_conflicts int;
BEGIN
WITH delta AS (
SELECT 'vocab.section_type.' || code AS k, code AS v FROM _delta_section_type
UNION ALL
SELECT 'vocab.publication_type.' || code, code FROM _delta_pub_type
UNION ALL
SELECT 'vocab.unit_kind.' || code, code FROM _delta_unit_kind
)
SELECT count(*)
INTO v_conflicts
FROM delta d
JOIN public.dot_config dc ON dc.key = d.k
WHERE dc.value IS DISTINCT FROM d.v;
IF v_conflicts > 0 THEN
RAISE EXCEPTION 'G5_FAIL % delta key(s) already exist in dot_config with a DIFFERENT value', v_conflicts;
END IF;
RAISE NOTICE 'G5_PASS no value conflict between delta and existing dot_config rows';
END $$;
-- =========================================================================
-- Dry-run preview (informational; same transaction)
-- =========================================================================
SELECT 'section_type' AS category, code, 'vocab.section_type.' || code AS target_key FROM _delta_section_type
UNION ALL
SELECT 'publication_type', code, 'vocab.publication_type.' || code FROM _delta_pub_type
UNION ALL
SELECT 'unit_kind', code, 'vocab.unit_kind.' || code FROM _delta_unit_kind
ORDER BY category, code;
SELECT
(SELECT count(*) FROM _delta_section_type) AS delta_section_type,
(SELECT count(*) FROM _delta_pub_type) AS delta_publication_type,
(SELECT count(*) FROM _delta_unit_kind) AS delta_unit_kind;
-- =========================================================================
-- INSERT from deltas; capture exact inserted keys via RETURNING in same txn
-- =========================================================================
CREATE TEMP TABLE _inserted_keys (
category text NOT NULL,
key text PRIMARY KEY,
value text NOT NULL
) ON COMMIT DROP;
WITH ins AS (
INSERT INTO public.dot_config (key, value, description, updated_at)
SELECT 'vocab.section_type.' || code,
code,
current_setting('p3d.run_marker'),
now()
FROM _delta_section_type
ON CONFLICT (key) DO NOTHING
RETURNING key, value
)
INSERT INTO _inserted_keys (category, key, value)
SELECT 'section_type', key, value FROM ins;
WITH ins AS (
INSERT INTO public.dot_config (key, value, description, updated_at)
SELECT 'vocab.publication_type.' || code,
code,
current_setting('p3d.run_marker'),
now()
FROM _delta_pub_type
ON CONFLICT (key) DO NOTHING
RETURNING key, value
)
INSERT INTO _inserted_keys (category, key, value)
SELECT 'publication_type', key, value FROM ins;
WITH ins AS (
INSERT INTO public.dot_config (key, value, description, updated_at)
SELECT 'vocab.unit_kind.' || code,
code,
current_setting('p3d.run_marker'),
now()
FROM _delta_unit_kind
ON CONFLICT (key) DO NOTHING
RETURNING key, value
)
INSERT INTO _inserted_keys (category, key, value)
SELECT 'unit_kind', key, value FROM ins;
-- Print exact inserted keys BEFORE commit.
-- Agent MUST capture this output verbatim into the report; it is the
-- ONLY authoritative input for the rollback procedure (Section F).
SELECT category, key, value FROM _inserted_keys ORDER BY category, key;
SELECT count(*) AS total_inserted FROM _inserted_keys;
-- =========================================================================
-- G7 (executable) — planner must resolve law_unit (no unresolved_vocab)
-- v6/v7 unchanged: calls production 9-arg signature returning jsonb.
-- p_section_type and p_publication_type are derived LIVE from dot_config
-- (post-insert snapshot within this transaction).
-- p_actor uses the run_marker. p_parent_ref is NULL::uuid.
-- Planner call wrapped in BEGIN/EXCEPTION for clear G7_FAIL labelling.
-- =========================================================================
DO $$
DECLARE
v_plan jsonb;
v_section_type text;
v_publication_type text;
v_status text;
v_resolved_kind text;
BEGIN
-- Derive probe args from live dot_config (no hardcode)
SELECT value INTO v_section_type
FROM public.dot_config
WHERE key LIKE 'vocab.section_type.%'
ORDER BY key LIMIT 1;
SELECT value INTO v_publication_type
FROM public.dot_config
WHERE key LIKE 'vocab.publication_type.%'
ORDER BY key LIMIT 1;
-- Call planner with production 9-arg contract
BEGIN
v_plan := public.fn_iu_create_plan(
p_canonical_address := 'planner-probe/p3d-phase4-v7-' || current_setting('p3d.run_marker'),
p_title := 'Phase 4 v7 planner probe',
p_body := 'Probe body',
p_actor := current_setting('p3d.run_marker'),
p_unit_kind := 'law_unit',
p_section_type := v_section_type,
p_owner_ref := NULL,
p_publication_type := v_publication_type,
p_parent_ref := NULL::uuid
);
EXCEPTION WHEN OTHERS THEN
RAISE EXCEPTION 'G7_FAIL planner call exception: % [SQLSTATE %]', SQLERRM, SQLSTATE;
END;
-- Validate jsonb structure before reading keys
IF v_plan IS NULL THEN
RAISE EXCEPTION 'G7_FAIL planner returned NULL';
END IF;
IF jsonb_typeof(v_plan) <> 'object' THEN
RAISE EXCEPTION 'G7_FAIL planner returned non-object jsonb (typeof=%); plan=%',
jsonb_typeof(v_plan), v_plan;
END IF;
IF NOT (v_plan ? 'status') THEN
RAISE EXCEPTION 'G7_FAIL planner jsonb missing "status" key; plan=%', v_plan;
END IF;
IF NOT (v_plan ? 'resolved_unit_kind') THEN
RAISE EXCEPTION 'G7_FAIL planner jsonb missing "resolved_unit_kind" key; plan=%', v_plan;
END IF;
-- Assert vocab resolution succeeded
v_status := v_plan ->> 'status';
IF v_status ILIKE '%unresolved_vocab%' THEN
RAISE EXCEPTION 'G7_FAIL planner reports unresolved_vocab; plan=%', v_plan;
END IF;
v_resolved_kind := v_plan ->> 'resolved_unit_kind';
IF v_resolved_kind IS DISTINCT FROM 'law_unit' THEN
RAISE EXCEPTION 'G7_FAIL planner resolved_unit_kind=% (expected law_unit); plan=%',
v_resolved_kind, v_plan;
END IF;
RAISE NOTICE 'G7_PASS status=% resolved_unit_kind=% plan=%',
v_status, v_resolved_kind, v_plan;
END $$;
COMMIT;
SQL
If any gate raises an exception, the entire transaction rolls back and no dot_config row changes. The script exits non-zero (because of ON_ERROR_STOP=1 plus set -e); no further sections run.
5. Section C — Post-commit verification (read-only, separate psql call)
"${PSQL_BASE[@]}" -v run_marker="$RUN_MARKER" <<'SQL'
-- Persisted rows from THIS run
SELECT key, value, updated_at
FROM public.dot_config
WHERE description = :'run_marker'
ORDER BY key;
-- Full vocab.* state after run
SELECT key, value
FROM public.dot_config
WHERE key LIKE 'vocab.%'
ORDER BY key;
-- Standalone planner probe (post-commit sanity, 9-arg jsonb contract)
SELECT public.fn_iu_create_plan(
p_canonical_address := 'planner-probe/p3d-phase4-v7-postcommit',
p_title := 'Phase 4 v7 post-commit probe',
p_body := 'Probe body',
p_actor := :'run_marker',
p_unit_kind := 'law_unit',
p_section_type := (SELECT value FROM public.dot_config
WHERE key LIKE 'vocab.section_type.%' ORDER BY key LIMIT 1),
p_owner_ref := NULL,
p_publication_type := (SELECT value FROM public.dot_config
WHERE key LIKE 'vocab.publication_type.%' ORDER BY key LIMIT 1),
p_parent_ref := NULL::uuid
) AS planner_result;
SQL
Agent appends C1..C3 output to the report.
6. Section D — Species READ-ONLY candidate report (no INSERT)
"${PSQL_BASE[@]}" <<'SQL'
-- D1: active species catalog
SELECT species_code, species_name, composition_level, lifecycle_status
FROM public.entity_species
WHERE lifecycle_status = 'active'
ORDER BY species_code;
-- D2: current species_collection_map state (no filter to specific names)
SELECT scm.collection_name, scm.species_code, scm.composition_level
FROM public.species_collection_map scm
JOIN public.collection_registry cr ON cr.collection_name = scm.collection_name
ORDER BY scm.collection_name;
-- D3: governed/observed collections without species_code
SELECT collection_name, governance_role, migration_state, species_code, birth_code_strategy
FROM public.collection_registry
WHERE species_code IS NULL
AND governance_role IN ('observed','governed')
ORDER BY collection_name;
SQL
Status flags the agent must write into the report verbatim:
species_mapping_executable=false
species_mapping_requires_separate_GPT_User_approved_prompt=true
fuzzy_match_in_executable_path=false
No species_collection_map INSERT. No target-collection row literal. No subordinate literal. No ILIKE '%law%' in executable logic. D1..D3 are candidate reporting only; final mapping is decided by GPT + User in a separate, future prompt.
7. Section E — Hash provenance (DOCUMENT-ONLY)
No executable code in v7. This section documents the Phase-3-discovered contract for a future migration phase.
7.1 Provenance contract (corrected from v5/v6 regression)
The IU layer preserves the original TAC source hash as a provenance reference. It does NOT re-hash normalized content in the IU layer — re-hashing would lose the link to the original TAC commit and destroy the provenance trail.
unit_version.content_profile.source_hashes.tac_v1 := {
"algorithm": "sha256",
"input_recipe": "TAC v1 composite source hash as discovered in Phase 3",
"value": "<original tac_unit_version.content_hash>",
"source_table": "tac_unit_version",
"source_id": "<source tac_unit_version id>"
}
7.2 Field semantics
| Field | Meaning |
|---|---|
algorithm |
Hash algorithm used by TAC (sha256). Recorded for forward compatibility if TAC ever rotates algorithms. |
input_recipe |
Human-readable description of WHAT was hashed in TAC v1 (the "composite source hash" recipe discovered in Phase 3). |
value |
The actual hash digest — copied verbatim from tac_unit_version.content_hash of the source row. Not re-computed. |
source_table |
The TAC table the hash came from (tac_unit_version). |
source_id |
The primary key of the source TAC row, enabling round-trip lookup. |
7.3 Why preservation, not re-hashing
- Provenance integrity. The TAC hash is the immutable fingerprint of the TAC commit. Re-hashing in IU would produce a different value that cannot be cross-referenced with TAC history.
- No content normalization drift. TAC's hash recipe (Phase-3 discovery) may differ from any IU normalization. If IU re-normalizes and re-hashes, the values diverge silently.
- Forward compatibility. When a future migration adds a
tac_v2hash family, bothtac_v1andtac_v2provenance entries can coexist without re-computing legacy values.
7.4 Out of scope for this prompt
The target field unit_version.content_profile.source_hashes.tac_v1 is not provisioned in the current schema. No DDL, no backfill, no migration occurs in this prompt. This section records the contract so the next phase can implement it without re-deriving the rule.
Status flag:
hash_provenance_document_only=true
hash_provenance_contract=preserves_original_tac_unit_version_content_hash
8. Section F — Rollback procedure (EXACT KEYS ONLY)
Rollback is allowed only after Section B has committed and the agent has the exact inserted-keys list from the report (printed by the SELECT category, key, value FROM _inserted_keys statement before COMMIT).
"${PSQL_BASE[@]}" -v run_marker="$RUN_MARKER" <<'SQL'
-- PRIMARY rollback: delete by EXACT keys captured in the report.
-- Replace the ARRAY[...] literal with the verbatim key list from the report.
DELETE FROM public.dot_config
WHERE key = ANY(ARRAY[
-- 'vocab.section_type.<code>',
-- 'vocab.publication_type.<code>',
-- 'vocab.unit_kind.law_unit'
]::text[]);
-- Cross-check (NOT a rollback predicate, only verification):
-- after the exact-key DELETE, no row should remain with this run_marker.
SELECT count(*) AS leftover_with_marker
FROM public.dot_config
WHERE description = :'run_marker';
-- Expect: 0
SQL
Forbidden rollback patterns in v7:
-- FORBIDDEN: prefix/description pattern as primary rollback
DELETE FROM public.dot_config WHERE description LIKE 'p3d-phase4-vocab-%';
DELETE FROM public.dot_config WHERE description = :'run_marker'; -- not primary
Description-equality is allowed only as the cross-check shown above (must return zero after the exact-key DELETE). It is never the deletion predicate by itself.
9. Report requirements
Save to:
knowledge/dev/laws/dieu44-trien-khai/reports/p3d-pack1-phase4-vocab-species-seed-implementation-report.md
Mandatory contents:
RUN_MARKERvalue.- Section A output (A1..A5) verbatim.
- Section B notices: which gates emitted
Gn_PASS. If any gate failed, transcript of theRAISE EXCEPTIONline and "rolled back, no dot_config change". - Section B dry-run preview output (categories, codes, target keys, delta counts).
- Section B inserted-keys output (
_inserted_keysrows, total_inserted). - Section B G7 planner-probe result: the full
v_planjsonb printed in G7_PASS notice. - Section C output (C1 persisted rows, C2 full vocab state, C3 post-commit planner probe jsonb).
- Section D output (D1..D3) plus the three status flags from §6.
- Status flag block (verbatim):
phase4_draft_v7_executed=true|false
vocab_executable_committed=true|false
single_session_transaction=true
executable_stop_gates=true
temp_table_cross_session_bug_present=false
hardcoded_seed_list_present=false
species_mapping_executable=false
hash_provenance_document_only=true
hash_provenance_contract=preserves_original_tac_unit_version_content_hash
requires_GPT_User_review_before_dispatch=true
- Exact-key list for any future rollback (verbatim from
_inserted_keys).
10. Out of scope (do not do)
- Do not dispatch nested/secondary agents from inside this task; this prompt itself may be executed only by the assigned Agent after GPT/User approval.
- Do not patch any function or trigger.
- Do not touch Directus, Qdrant, collection_registry write, species_collection_map write, or birth-gate logic.
- Do not retry a failed gate by editing the prompt; report the failure and stop.
- Do not pre-compute a vocab list and paste it into the prompt — every value must come from a live SQL source declared in §0.
- Do not re-compute or normalize TAC content hashes in the IU layer (see §7); preserve the original
tac_unit_version.content_hashby reference only.
DRAFT v7 | G3 exact contract + hash provenance preserved + agent wording corrected | Single-session transaction | Executable gates | Exact-key rollback | No hardcode | 2026-05-11