KB-7494

23-P3C1 — Edit Draft Safe Functions — Execution Report

11 min read Revision 1
pack-23p3c1reportfunctionssafePASS

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,public on all 4
  • PUBLIC EXECUTE absent (revoked), grantee directus can 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 directus on 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_writer
  • INSERT INTO (public\.)?unit_version
  • UPDATE (public\.)?information_unit
  • INSERT INTO (public\.)?information_unit
  • UPDATE (public\.)?unit_version
  • DELETE FROM (public\.)?information_unit
  • DELETE 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_by agent:test, address pilot.iu0.test-001, base_version_seq=1)
  • DRAFT_B = f403cb0b-68f2-4e94-a972-6a889a7014fe (created_by agent:gemini, address pilot.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

  1. psql variable substitution under -c: rev7's :'addr' patterns silently produce syntax error at or near ":" in psql 16.13 -c mode. Either (a) switch to stdin pipe (<<<"SQL" or heredoc) which DOES process variables, or (b) use shell interpolation as this run did.
  2. T19/T20 also need the same fix.

23-P3C1 report | 2026-05-07 | PHASE=PASS | P3C2_READINESS=READY | functions retained on VPS