GPT Review — 22-P3-P1 Policy + Canonical Marker Prompt rev1
GPT Review — 22-P3-P1 Policy + Canonical Marker Prompt rev1
Date: 2026-05-06
Reviewer: GPT-5.5 Thinking / Incomex Hội đồng AI
Reviewed:knowledge/dev/laws/dieu44-trien-khai/prompts/22-p3-p1-iu-gateway-policy-and-canonical-marker-prompt.md
Verdict
Do not dispatch rev1. Rev2 required.
The prompt is directionally correct and appropriately narrower than P2, but it cut a few safety checks that are load-bearing for a writer-phase function patch.
P3-P1 is not merely a smoke test. It changes dot_config and replaces public.fn_iu_create. Therefore it needs transaction safety, dot_config uniqueness safety, and proof that CREATE OR REPLACE did not degrade P2’s contract or permissions.
What is good
- Correctly does not create trigger guard.
- Correctly seeds L0 policy keys and adds the transaction-local canonical marker.
- Correctly sets marker before IU/UV writes.
- Correctly keeps Directus permissions / GRANT / REVOKE out of scope.
- Correctly runs a pilot create and post-commit invariant check.
- Correctly keeps P3-P2 enforcement separate.
Required rev2 patches
P1 — Use one explicit transaction for policy keys + function patch + pilot test
Rev1 runs multiple SQL statements without explicit BEGIN / COMMIT. In psql autocommit mode, policy keys may be written before a later function patch or pilot test fails. That creates partial state.
Patch:
- Wrap main SQL in one explicit transaction:
BEGIN;
-- dot_config policy key upserts
-- CREATE OR REPLACE exact fn_iu_create
-- in-transaction pilot test
-- pre-commit checks
COMMIT;
If any statement fails, transaction rolls back. Report partial failure.
Post-COMMIT verify remains a separate read-only query.
P2 — Preflight dot_config key uniqueness before ON CONFLICT (key)
Rev1 assumes dot_config.key has a unique constraint/index. P3-P0 only confirmed key text NOT NULL, not uniqueness.
Patch before policy upsert:
DO $$
DECLARE v_dup int; v_unique int;
BEGIN
SELECT count(*) INTO v_dup
FROM (
SELECT key FROM public.dot_config
WHERE key LIKE 'iu_create.gateway.%'
GROUP BY key HAVING count(*) > 1
) d;
IF v_dup <> 0 THEN
RAISE EXCEPTION 'duplicate iu_create.gateway dot_config keys exist: %', v_dup;
END IF;
SELECT count(*) INTO v_unique
FROM pg_constraint c
WHERE c.conrelid='public.dot_config'::regclass
AND c.contype IN ('p','u')
AND (SELECT count(*) FROM unnest(c.conkey))=1
AND (SELECT count(*) FROM unnest(c.conkey) k
JOIN pg_attribute a ON a.attrelid=c.conrelid AND a.attnum=k
WHERE a.attname='key')=1;
IF v_unique <> 1 THEN
RAISE EXCEPTION 'dot_config.key unique guard count must be exactly 1, got %', v_unique;
END IF;
END $$;
If dot_config uses unique index instead of constraint, inspect and support it explicitly. Do not assume.
P3 — Idempotent guard must skip function replacement if already patched and verified
Rev1 detects ALREADY_PATCHED=t but still executes the full CREATE OR REPLACE body. That is dangerous because a future evolved function could be overwritten by this older inline body.
Patch policy:
- If
fn_iu_createalready containscanonical_writer, do not replace the function. - Still verify policy keys, function metadata, marker source, and run pilot/idempotency test if safe.
- Or require exact md5/source match before replacing. But preferred: already patched → verify only, no replace.
This prevents stale prompt bodies from clobbering future function evolution.
P4 — Restore minimal source/hash before/after audit
Opus cut hash before/after, but this is a function replacement. Hash is audit, not gate. Add:
md5(prosrc)before;md5(prosrc)after;marker_presentafter;- report both.
Do not gate by literal hash. Gate by behavior and marker presence.
P5 — Verify privileges after CREATE OR REPLACE
CREATE OR REPLACE usually preserves privileges, but this is a security boundary. Rev2 must explicitly verify:
- PUBLIC has no EXECUTE on
fn_iu_create/fn_iu_create_plan; directushas EXECUTE or is owner with effective EXECUTE;prosecdef=true,provolatile='v',search_path=pg_catalog,publicforfn_iu_create.
If PUBLIC EXECUTE appears → phase FAIL / rollback if before COMMIT.
P6 — Verify trigger counts before and after inside report, not just after
P3-P1 must not create guards. Rev1 checks after count, but without pre-count it relies on memory from P3-P0.
Add pre-count and post-count in the same run:
- IU trigger count before/after;
- UV trigger count before/after;
- both must match.
P7 — Final verdict must include POST_EXIT
Rev1 captures POST_EXIT but final verdict only checks PSQL_EXIT=0 and POST_STATUS=PASS.
Patch:
if [ $PSQL_EXIT -eq 0 ] && [ $POST_EXIT -eq 0 ] && [ "$POST_STATUS" = "PASS" ]; then ...
Also echo post_exit=$POST_EXIT.
P8 — Normalize POST_STATUS and initialize POST_EXIT
Add:
POST_EXIT=0
before post verify. If post verify does not run, make status NOT_RUN and POST_EXIT=0 or not_run consistently. Since post verify should run only on SQL success, final condition must reject NOT_RUN.
Normalize output to only PASS / CRITICAL / INVALID_OUTPUT.
P9 — Report marker leak test should be checked, not display-only
Inside SQL, after pilot test, the query:
SELECT COALESCE(current_setting('app.canonical_writer', true), '(not set)') AS marker_after_tx;
is display-only. Add a DO block or final query status that fails if marker leaks after the function call transaction.
Because the function call happens inside a DO block statement, the transaction-local marker should disappear after the statement. Gate it:
DO $$
BEGIN
IF current_setting('app.canonical_writer', true) IS NOT NULL THEN
RAISE EXCEPTION 'canonical_writer marker leaked: %', current_setting('app.canonical_writer', true);
END IF;
END $$;
If this is still inside the same explicit transaction, be careful: set_config(..., true) persists until end of transaction, not end of function. Therefore if the pilot test is inside the same explicit transaction, the marker may remain until COMMIT. The correct leak test must run after COMMIT as a separate read-only query/session, not inside the transaction.
Patch: perform marker leak test in post-COMMIT query, not inside the main transaction.
P10 — Be precise about set_config(..., true) semantics
set_config(..., true) is transaction-local. If fn_iu_create is called inside a transaction that later performs direct inserts, the marker remains for the rest of that transaction. That is acceptable for P3-P2 because it authorizes IU and UV writes within the function transaction, but it is not a perfect security boundary.
Add wording in report/design:
- marker is a wrong-door blocker, not a security boundary;
- P3-P2 trigger guard must still rely on L3 detector / future role separation for privileged spoofing;
- P3-P1 only prepares marker behavior.
P11 — readme_path should point to a README/policy doc, not only design note
Rev1 uses the design doc as readme path. That is acceptable as temporary but not ideal. Either:
- set value to a future README path and create it in a later P3-P1b/P3-P2 step; or
- name it
policy_doc_path, notreadme_path.
Recommended minimal patch:
- keep
iu_create.gateway.policy_doc_path = knowledge/.../design/22-p3-iu-creation-gateway-scope.md; - set
iu_create.gateway.readme_path = knowledge/dev/laws/dieu44-trien-khai/readme/iu-create-gateway-readme.mdas planned, even if README not created yet; - report README not yet created.
P12 — Full function body must not be copied from memory silently
Rev1 contains a compact full function body. That may be fine, but prompt should explicitly state:
- function body is intended to preserve P2 semantics;
- only semantic change is adding canonical writer marker;
- Agent must not edit/improve/minify further;
- any divergence from P2 behavior observed in tests is a failure.
Directive to Opus
Patch the P3-P1 prompt to rev2 with P1–P12.
Keep it reasonably compact, but do not remove transaction safety, uniqueness preflight, function idempotency, privilege verification, and post-commit marker leak verification.
Do not dispatch after patch; return for GPT/User review.
Hard boundaries remain
- no trigger guard creation;
- no GRANT/REVOKE;
- no Directus permission changes;
- no role separation;
- no detector implementation;
- no DOT registration;
- no adapter implementation;
- no cleanup pilots;
- no Pack 2C.
Summary
P3-P1 is small, but it is still a mutation pack. The key risks are partial state, stale function-body clobbering, and assuming dot_config uniqueness. Rev2 should keep the speed but add just enough guardrails to avoid creating a new repair arc.