23-P3C1 — Edit Draft Safe Functions — Execution Report
23-P3C1 — Edit Draft Safe Functions — Execution Report
Date: 2026-05-07 Prompt: knowledge/dev/laws/dieu44-trien-khai/prompts/23-p3c1-iu-edit-draft-safe-functions-prompt.md (rev7) Companion: knowledge/dev/laws/dieu44-trien-khai/design/23-p3c1-ux-state-foundation-notes.md (rev2) Log: /tmp/23-p3c1.20260507-061720.log Run: VPS 38.242.240.89 — postgres container — DB
directus
Verdict
- phase_status=PASS
- p3c2_readiness=READY
- All 21 tests PASS (T1–T21, including T11b/T15b sub-checks)
- 4 functions created with intended signatures
- SECURITY DEFINER +
search_path=pg_catalog,publicon all 4 - PUBLIC EXECUTE absent (revoked), grantee
directuscan EXECUTE all 4 - 2 draft rows retained, 4 comment rows retained
- IU/UV counts unchanged (6→6 / 6→6)
- Source safety verified: no IU/UV/canonical_writer write paths
- UX evidence JSON samples captured
- ux_hooks=timestamp_ok,actor_card_guc_deferred,per_actor_watermark_deferred
Execution note
Script rev7 (psql :'var' substitution) failed at T-preflight because psql -c "..." does NOT process \set/-v variable interpolation in psql 16.13 when SQL contains :'name' adjacent to operators (e.g., !=:'addr_a'). Patch: replaced all -v NAME=val ... -c "...:'NAME'..." calls with shell interpolation (-c "...'$VAR'..."). No semantics changed; the test addresses contain no shell-special characters.
This shell-interpolation patch should be folded into prompt rev8 — current rev7 cannot execute under psql 16 + -c mode.
Phase 1 — Preflight (PASS)
| Check | Result |
|---|---|
Tables unit_edit_draft, unit_edit_comment exist |
PASS |
| Required columns (24 cols across 4 tables) | PASS (cols=0 missing) |
dot_config gateway value iu_create.gateway.allowed_marker_values |
fn_iu_create,fn_iu_apply_edit_draft |
fn_content_hash(text) exists |
PASS |
| Target functions absent (4 P3C1 + fn_iu_apply_edit_draft + fn_iu_edit) | PASS |
| Owner / grantees discovered from fn_iu_create | owner=directus, grantees=directus |
unit_version lifecycle distinct values |
draft |
| TEST_ADDR_A / TEST_ADDR_B distinct | A=pilot.iu0.test-001, B=pilot.p2.20260506-045033.e0ae7ec5 |
| Baseline counts | IU=6, UV=6, D=0, C=0 |
Phase 2 — Create Functions (PASS, FN_EXIT=0)
CREATE FUNCTION × 4 ← fn_iu_edit_plan, fn_iu_create_edit_draft, fn_iu_comment_edit_draft, fn_iu_comment
REVOKE × 4
DO (GRANT EXECUTE to directus × 4)
COMMIT
Signatures (verified)
| # | Function | Signature | Volatility | Security | search_path |
|---|---|---|---|---|---|
| 1 | fn_iu_edit_plan |
(p_address text, p_body text, p_actor text, p_title text DEFAULT NULL) RETURNS jsonb |
STABLE | DEFINER | pg_catalog, public |
| 2 | fn_iu_create_edit_draft |
(p_address text, p_body text, p_actor text, p_reason text DEFAULT NULL, p_title text DEFAULT NULL) RETURNS jsonb |
VOLATILE | DEFINER | pg_catalog, public |
| 3 | fn_iu_comment_edit_draft |
(p_draft_id uuid, p_author text, p_body text, p_kind text DEFAULT 'general', p_author_type text DEFAULT 'agent') RETURNS jsonb |
VOLATILE | DEFINER | pg_catalog, public |
| 4 | fn_iu_comment |
(p_address text, p_author text, p_body text, p_kind text DEFAULT 'general', p_author_type text DEFAULT 'agent', p_context jsonb DEFAULT '{}'::jsonb) RETURNS jsonb |
VOLATILE | DEFINER | pg_catalog, public |
Security ACL
- PUBLIC EXECUTE: absent on all 4 (REVOKE applied)
- GRANT EXECUTE: granted to role
directuson all 4 (T19:has_function_privilege('directus', sig, 'EXECUTE')='t'for every signature)
Phase 3 — Tests (21/21 PASS)
| Test | Status | Detail |
|---|---|---|
| T1 plan: identical body → no_change | PASS | |
| T2 plan: new body → plan_ok | PASS | |
| T3 plan: bad address → iu_not_found | PASS | |
| T4 plan: empty body → invalid_input | PASS | |
| T5 create draft A → draft_created | PASS | A=ea7fc2eb-d026-4e0e-bfe0-e12b0e0bc9f4 |
| T6 create draft identical body → no_change | PASS | |
| T7 comment_edit_draft on A (review) → comment_added | PASS | |
| T8 comment_edit_draft bad uuid → draft_not_found | PASS | |
| T9 fn_iu_comment, single open draft → comment_added (selected_by=only_open_draft) | PASS | |
| T10 create draft B → draft_created | PASS | B=f403cb0b-68f2-4e94-a972-6a889a7014fe |
| T11 fn_iu_comment, 2 open drafts no context → multiple_open_drafts | PASS | |
| T11b comment count unchanged after multiple_open_drafts | PASS | |
| T12 context.draft_id=A → comment_added (selected_by=context_draft_id) | PASS | |
| T13 context.select=latest → comment_added (selected_by=latest) | PASS | |
| T14 fn_iu_comment ADDR_B (no drafts) → no_open_draft | PASS | |
| T15 ADDR_B + draft_id from ADDR_A → draft_not_for_address | PASS | |
| T15b comment count unchanged after draft_not_for_address | PASS | |
| T16 draft delta = +2 (0→2) | PASS | |
| T17 comment delta = +4 (0→4) | PASS | |
| T18 IU/UV counts unchanged (6→6 / 6→6) | PASS | |
| T19 SECURITY DEFINER + search_path + PUBLIC absent + grantee can execute | PASS | |
| T20 guidance text present in 3 sample errors | PASS | |
| T21 source safety: 0 functions reference IU/UV write paths or app.canonical_writer | PASS (T21=0) |
TEST_FAIL=0 → no cleanup invoked, functions retained.
Counts (before → after)
| Table | Before | After | Δ |
|---|---|---|---|
information_unit |
6 | 6 | 0 |
unit_version |
6 | 6 | 0 |
unit_edit_draft |
0 | 2 | +2 |
unit_edit_comment |
0 | 4 | +4 |
test_rows_retained=true — 2 drafts + 4 comments left in place per prompt requirement (no cleanup ran because TEST_FAIL=0).
Comment composition (4 total):
- T7: comment_edit_draft direct on draft A (kind=review)
- T9: fn_iu_comment auto-route (only_open_draft → A)
- T12: fn_iu_comment context.draft_id=A
- T13: fn_iu_comment select=latest (→ B, the newer draft)
Source safety (T21)
Regex against pg_proc.prosrc for the 4 functions checked:
app\.canonical_writerINSERT INTO (public\.)?unit_versionUPDATE (public\.)?information_unitINSERT INTO (public\.)?information_unitUPDATE (public\.)?unit_versionDELETE FROM (public\.)?information_unitDELETE FROM (public\.)?unit_version
Result: 0 matches. The functions only INSERT into unit_edit_draft / unit_edit_comment and SELECT-read from information_unit / unit_version / dot_config. No IU/UV/gateway write path; no apply/wrapper logic.
UX Evidence (JSON samples from final block)
1. fn_iu_edit_plan(ADDR_A, 'ux', 't') → plan_ok
{
"policy": "auto_apply",
"status": "plan_ok",
"unit_id": "3ffbbaa5-f22a-4df4-8bc6-c27eab2787fe",
"guidance": "Ready.",
"next_action": "fn_iu_create_edit_draft",
"open_draft_count": 2,
"base_content_hash": "7bab3f7add2c0bc03105415724abd381abcad035d06e2428d8543c211a7f57e8",
"draft_content_hash": "07302499974f21b9e32dcccf30d83d15c17ad96c2e2c3b6d99e34780aba9b217",
"auto_apply_eligible": true,
"current_version_seq": 1
}
2. fn_iu_comment(ADDR_B, 'x', 'hi') → no_open_draft
{
"status": "no_open_draft",
"guidance": "No draft. Use fn_iu_edit.",
"next_action": "fn_iu_edit"
}
3. fn_iu_comment(ADDR_A, 'x', 'm') → multiple_open_drafts (2 open after T5+T10)
{
"drafts": [
{
"reason": "alt",
"draft_id": "f403cb0b-68f2-4e94-a972-6a889a7014fe",
"created_at": "2026-05-07T04:17:27.707532+00:00",
"created_by": "agent:gemini",
"hash_preview": "fb0e5dcb8551f005",
"title_preview": null,
"base_version_seq": 1
},
{
"reason": "test reason",
"draft_id": "ea7fc2eb-d026-4e0e-bfe0-e12b0e0bc9f4",
"created_at": "2026-05-07T04:17:26.640464+00:00",
"created_by": "agent:test",
"hash_preview": "57ce5f6738e254a3",
"title_preview": null,
"base_version_seq": 1
}
],
"status": "multiple_open_drafts",
"guidance": "Choose draft_id or {\"select\":\"latest\"}.",
"open_count": 2,
"next_action": "fn_iu_comment_edit_draft"
}
Drafts ordered newest-first by created_at DESC per UX notes §1.
UX Hooks Status
ux_hooks=timestamp_ok,actor_card_guc_deferred,per_actor_watermark_deferred
| Hook | Status | Note |
|---|---|---|
| Timestamp discipline | timestamp_ok | All inserts use DEFAULT now() on created_at. Multi-draft listing ORDER BY created_at DESC (newest-first). Verified via UX evidence sample 3 (timestamps differ by ~1s, newer first). |
Auto-signature / actor card via session GUC app.iu_actor |
actor_card_guc_deferred | P3C1 still requires explicit p_actor / p_author. The 2-line COALESCE(NULLIF(btrim(p_actor),''), current_setting('app.iu_actor', true)) hook documented in companion §2 is not implemented; awaiting approval per UX notes. |
Per-actor last-view watermark (unit_edit_review_state table) |
per_actor_watermark_deferred | Table not created (out of P3C1 scope: "Hard Boundaries — No table DDL"). Future P3D/P3E hook. Companion §3 spec retained. |
Pilots (retained for P3C2)
- DRAFT_A =
ea7fc2eb-d026-4e0e-bfe0-e12b0e0bc9f4(created_byagent:test, addresspilot.iu0.test-001, base_version_seq=1) - DRAFT_B =
f403cb0b-68f2-4e94-a972-6a889a7014fe(created_byagent:gemini, addresspilot.iu0.test-001, base_version_seq=1) - ADDR_A =
pilot.iu0.test-001 - ADDR_B =
pilot.p2.20260506-045033.e0ae7ec5
Both drafts draft_status='open' — ready as inputs for P3C2 fn_iu_apply_edit_draft testing.
AI Interface (drop-in for next session)
fn_iu_edit_plan(address, body, actor[, title]) -- preview
fn_iu_create_edit_draft(address, body, actor[, reason[, title]]) -- create draft
fn_iu_comment_edit_draft(draft_id, author, body[, kind[, author_type]]) -- direct comment
fn_iu_comment(address, author, body[, kind[, author_type[, context]]]) -- auto-route comment
-- context: {} | {"draft_id":"…"} | {"select":"latest"}
fn_iu_apply_edit_draft / fn_iu_edit -- P3C2 (not yet created)
Hard Boundaries Honored
- ❌ No table DDL — verified
- ❌ No trigger / gateway changes — verified
- ❌ No IU / UV writes — verified by T18 (counts unchanged) + T21 (source regex)
- ❌ No vector / cleanup / retry — none performed
- ❌ No apply / wrapper functions (
fn_iu_apply_edit_draft,fn_iu_edit) — confirmed absent post-run
Followups for prompt rev8
- psql variable substitution under
-c: rev7's:'addr'patterns silently producesyntax error at or near ":"in psql 16.13-cmode. Either (a) switch to stdin pipe (<<<"SQL"or heredoc) which DOES process variables, or (b) use shell interpolation as this run did. - T19/T20 also need the same fix.
23-P3C1 report | 2026-05-07 | PHASE=PASS | P3C2_READINESS=READY | functions retained on VPS