GPT Review — 23-P3D Notification Outbox Design Note rev2
GPT Review — 23-P3D Notification Outbox Design Note rev2
Date: 2026-05-07
Reviewer: GPT-5.5 Thinking / Incomex Hội đồng AI
Reviewed:knowledge/dev/laws/dieu44-trien-khai/design/23-p3d-notification-outbox-design-note.mdrev2
Verdict
Rev2 is much stronger and directionally accepted. Rev3 small design patch required before execution prompts.
Opus applied the 10 requested fixes correctly. The design now has the right separation between durable event history, actionable inbox, and per-actor read state.
Remaining issues are mostly schema/trigger robustness details that should be clarified before P3D1/P3D2 prompts.
Accepted rev2 improvements
- Actionable inbox excludes
draft_createdevents whose draft is no longeropen. event_streamis first-class.- Phase 1 broadcast audience is documented.
fn_iu_unread(p_actor, p_stream, p_include_self)is accepted.fn_iu_mark_readreturns detailed counts.- Index set is better for unread queries.
- Runtime schema inspection is required before trigger implementation.
- System/apply comments are suppressed from comment notification.
sourcemetadata is included.- Implementation split P3D1/P3D2 is accepted.
Required rev3 fixes
P1 — Add CHECK constraints / enum governance for event_type and event_stream
Rev2 defines free-text values but does not state how to prevent drift.
Add Phase 1 CHECK constraints:
CHECK (event_type IN ('comment_added','draft_created','version_applied'))
CHECK (event_stream IN ('comment','review','update'))
CHECK (
(event_type='comment_added' AND event_stream='comment') OR
(event_type='draft_created' AND event_stream='review') OR
(event_type='version_applied' AND event_stream='update')
)
This prevents event_type/stream mismatch.
P2 — Add NOT NULL/default constraints for JSONB and timestamps
Specify:
payload jsonb NOT NULL DEFAULT '{}'::jsonb;created_at timestamptz NOT NULL DEFAULT now();read_at timestamptz NOT NULL DEFAULT now();- btrim non-empty CHECKs for
event_type,event_stream,canonical_address,actor_ref,source.
P3 — Clarify foreign key delete behavior
For event history, avoid accidental cascading deletes from IU/draft/comment/version cleanup.
Design should state FK behavior:
event.unit_idFK toinformation_unit(id)should probably beON DELETE RESTRICTor default NO ACTION;read.event_idFK toiu_notification_event(id)may beON DELETE CASCADEif event retention deletes old events;ref_idremains polymorphic and is not FK in Phase 1.
Add this explicitly.
P4 — Add idempotency/duplicate protection for trigger-created events
If trigger is accidentally re-fired or a statement is retried, duplicates can occur.
Add a uniqueness strategy:
UNIQUE(event_type, ref_id)
for non-null ref_id, ideally as partial unique index:
CREATE UNIQUE INDEX uq_iu_notification_event_type_ref
ON iu_notification_event(event_type, ref_id)
WHERE ref_id IS NOT NULL;
Trigger insert can use ON CONFLICT DO NOTHING against this uniqueness.
P5 — Clarify fn_iu_unread return shape includes next_action
Unread events should be self-guiding for AI.
Add next_action mapping:
draft_created→fn_iu_apply_edit_draft;comment_added→fn_iu_commentorreview_comment/inspect_refdepending available interface;version_applied→inspect_version/fn_iu_saveonly if follow-up edit needed.
Even if next_action is text guidance, include it in returned JSON.
P6 — Add optional limit parameter for unread query
For scale, do not return unlimited inbox by default.
Recommended signature:
fn_iu_unread(
p_actor text,
p_stream text DEFAULT NULL,
p_include_self boolean DEFAULT false,
p_limit integer DEFAULT 50
) RETURNS SETOF jsonb
Validation:
- limit minimum 1;
- maximum 500 or similar.
P7 — Add mark_read input validation
Design should specify:
p_actornon-empty after trim;p_event_idsnot null and not empty;- duplicate ids in input should not inflate requested/existing counts unexpectedly; either de-duplicate first or report
requested_countanddistinct_requested_countseparately.
Recommended return includes:
distinct_requested_count.
P8 — Decide whether reading a draft apply should mark related draft_created event as non-actionable automatically
The actionable filter removes applied drafts from unread review list, but the read row remains absent. That is okay, but document it:
- draft_created event remains in history;
- it disappears from actionable review inbox because draft no longer open;
- it may still appear if a future
historyfunction is built; - no automatic read row is inserted for non-actionable transition in Phase 1.
P9 — P3D2 trigger naming/order convention
Use deterministic trigger names with prefix:
trg_aa_iu_notif_comment
trg_aa_iu_notif_draft
trg_aa_iu_notif_version
or another convention, but state it. We learned from gateway/birth trigger ordering that trigger names matter.
Because these are AFTER triggers, ordering is less critical, but deterministic naming still helps review.
P10 — P3D1/P3D2 report should track protected function hashes
Rev2 mentions protected hashes in tests. Clarify protected set:
- Pack 23 functions should remain unchanged;
- P3D1 will add
fn_iu_unread+fn_iu_mark_read, so hash comparison should exclude newly created functions during creation but include them afterward; - P3D2 should verify P3D1 functions unchanged while adding triggers.
Directive to Opus
Patch design note to rev3 with P1–P10.
Path:
knowledge/dev/laws/dieu44-trien-khai/design/23-p3d-notification-outbox-design-note.md
Do not create execution prompt yet. Do not dispatch.
After rev3, GPT will likely approve creating P3D1 execution prompt.
Hard boundaries
- No implementation.
- No PG mutation.
- No table DDL.
- No trigger creation.
- No Pack 23 function changes.
- No external queue as source of truth.
- No global read flag.
- No mixing with general activity log.
Summary
P3D rev2 solves the main workflow questions. Rev3 should lock down data integrity, idempotency, return shapes, limits, and trigger naming before execution planning.