23-P3D4C0Y — Universal Phase 2 PoC Scope Plan (Design Note rev1)
23-P3D4C0Y — Universal Phase 2 PoC Scope Plan (Design Note rev1)
Date: 2026-05-08 Status: DESIGN rev1 — PoC scope/design review only. NO implementation. NO mutation. Prompt: knowledge/dev/laws/dieu44-trien-khai/prompts/23-p3d4c0y-universal-phase2-poc-scope-plan-prompt.md (rev2) Report: knowledge/dev/laws/dieu44-trien-khai/reports/23-p3d4c0y-universal-phase2-poc-scope-plan-report.md Reviewer: Claude Opus 4.7 (1M context) Upstream: P3D4C0X design rev1 + report (PASS) + GPT directive 2026-05-08 + P3D4C1 rev3 (paused) Decisions locked: UNIVERSAL_WITH_IU_COMPAT (Option C) | C-i Phase 2 first | event_type_registry MANDATORY | LISTEN/NOTIFY defer Phase 3 | Đ45 defer | P3D4C1 absorbed (no resume)
§0. Executive summary
Phase 2 PoC chọn system_issues (Đ22) làm domain đầu tiên cho universal event substrate. Lý do (PG inventory-backed): 8548 rows tổng, lifecycle 3-state đơn giản (open → resolved/archived), severity catalog đã match event_severity (critical/warning/info), zero IU surface, governance owner rõ qua entity_type/entity_code.
Universal core schema sketches: 5 tables non-executable — event_outbox, event_read, event_subscription, event_type_registry (MANDATORY), event_pending (HYBRID strategy). Worker + log table reuse P3D4C1 patterns, generalized.
Config: REUSE dot_config với namespace event.* (verified: 47 rows, dotted-namespace pattern đã có sẵn iu_create.gateway.*, iu_edit.policy.*, vocab.*, context_pack_*, hc_* — hoàn toàn tương thích).
IU compat C-i locked: zero dual-write, zero row migration, fn_iu_* API surface bất biến.
Đ43 alignment: ADDITIVE_ONLY — không section mới, dùng existing pg_query source cho red_zones/recent_alerts feed.
Recommendation: next_required_pack=P3D4C1U_UNIVERSAL_CORE_IMPLEMENTATION_PROMPT_REVIEW.
§A. PoC Domain Selection — PG read-only inventory
A.1 Inventory (read-only, executed via docker exec postgres psql -U directus -d directus)
| Candidate | Schema verified | Lifecycle | Actor field | Severity | Row count | Existing triggers | Volume profile |
|---|---|---|---|---|---|---|---|
system_issues (Đ22) |
✅ Full | status ∈ {open, resolved, archived} via CHECK |
source, source_system (no created_by) — resolved_by for closure |
✅ severity ∈ {critical, warning, info} via CHECK |
8 548 | 7 active triggers (birth/desc/guard/label) | bursty per HC sweep |
birth_registry (Đ0-G) |
✅ Full | status default 'born', certified bool, certified_at |
user_created, governance_role, dot_origin |
(no severity column) | 105 227 | 2 triggers | very high (every entity born) |
admin_fallback_log (Đ35-FB) |
✅ Full | status ∈ {applied, retroactive_documented, audit_overdue, rolled_back} via CHECK |
approved_by, dot_code, session_code |
(implicit via audit_overdue) |
22 | 1 trigger (trg_auto_apr_on_fallback) |
very low (rare) |
dot_tools execution log |
⚠️ NONE EXISTS | only last_executed timestamptz, no per-run rows |
n/a | n/a | n/a | n/a | (no execution log table — would require new table, out of PoC scope) |
A.2 PoC selection — system_issues
Verdict: poc_domain = system_issues ✅
Justification (multi-factor):
- Volume sane — 8 548 total; ~10s open/resolve daily → safe for trigger-based fact capture without overload.
- Lifecycle simplest — 3-state CHECK constraint already enforced (open → resolved | archived). Maps cleanly to event_types:
issue_opened,issue_resolved,issue_archived. - Severity native —
severityalready classified (critical/warning/info), 1:1 vớievent_outbox.event_severity. - Zero IU surface — no IU table/function/trigger touched; IU C-i compatibility requirement honored automatically.
- Governance owner derivable —
entity_type+entity_coderesolve target IU/Birth row → recipient routing via existing governance graph. - Existing trigger ecosystem mature — 7 active triggers (birth, description guard, label assign) prove AFTER INSERT trigger surface is well-exercised; adding 1 more append-only INSERT trigger is additive-only.
- Aligns with rev2 prompt §1 preferred candidate + GPT directive §1 ("Prefer system_issues / Đ22").
- Stream coverage — 3 of 7 streams exercised (
alert,health,update) → tests universal taxonomy without forcing all streams.
Rejected alternatives (with reason):
birth_registry— 105K rows + every entity births = trigger fan-out unacceptable for first PoC.admin_fallback_log— only 22 rows; insufficient signal for routing/grouping/lifecycle tests; tightly coupled với existingtrg_auto_apr_on_fallback.dot_toolsexecution log — table doesn't exist; out of PoC scope (would require new infra).
A.3 Event types proposed for PoC domain
| event_domain | event_type | event_stream | event_severity | Trigger source | Subject |
|---|---|---|---|---|---|
| system | issue_opened | alert | (mirror system_issues.severity) |
AFTER INSERT ON system_issues | system_issues row |
| system | issue_resolved | update | info | AFTER UPDATE OF status WHEN status='resolved' | system_issues row |
| system | issue_archived | update | info | AFTER UPDATE OF status WHEN status='archived' | system_issues row |
| system | red_zone_violation | alert | critical | AFTER INSERT/UPDATE WHEN severity='critical' | system_issues row |
PoC fires events idempotently — same (event_domain, event_type, event_subject_ref) collapses via UNIQUE partial index.
§B. Universal Core Schema (NON-EXECUTABLE sketches)
Fence: tất cả DDL trong §B là DESIGN ONLY. Không apply trên PG. P3D4C1U sẽ là pack thực thi.
B.1 event_outbox — durable event SoT
-- NON-EXECUTABLE SKETCH (P3D4C0X §C envelope, refined)
CREATE TABLE event_outbox (
-- Identity
event_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
-- Taxonomy
event_domain text NOT NULL,
event_type text NOT NULL,
event_stream text NOT NULL,
event_severity text,
-- Subject
event_subject_type text NOT NULL,
event_subject_ref uuid,
event_subject_table text NOT NULL,
canonical_address text NOT NULL CHECK (btrim(canonical_address) != ''),
-- Actor (producer)
actor_ref text NOT NULL CHECK (btrim(actor_ref) != ''),
source_system text NOT NULL,
source_function text,
-- Correlation / causation
correlation_id text,
causation_id uuid,
-- Provenance
source_document_ref text,
import_batch_ref text,
-- Payload — METADATA ONLY (denylist enforced)
payload_classification text NOT NULL DEFAULT 'safe_metadata'
CHECK (payload_classification IN ('safe_metadata','restricted')),
safe_payload jsonb NOT NULL DEFAULT '{}'::jsonb,
-- Timing
occurred_at timestamptz NOT NULL DEFAULT now(),
created_at timestamptz NOT NULL DEFAULT now(),
processed_at timestamptz,
-- Domain enum
CHECK (event_domain IN ('iu','birth_registry','governance','tac','kg','system','dot','health')),
-- Stream enum
CHECK (event_stream IN ('comment','review','update','birth','task','alert','health')),
-- Severity enum
CHECK (event_severity IS NULL OR event_severity IN ('info','warning','critical')),
-- Payload safety denylist (B.5)
CHECK (
NOT (safe_payload ? 'body') AND
NOT (safe_payload ? 'content') AND
NOT (safe_payload ? 'raw') AND
NOT (safe_payload ? 'vector') AND
NOT (safe_payload ? 'embedding') AND
NOT (safe_payload ? 'secret') AND
NOT (safe_payload ? 'token') AND
NOT (safe_payload ? 'password') AND
NOT (safe_payload ? 'ssn') AND
NOT (safe_payload ? 'personal_data')
)
);
-- Idempotency: same (domain, type, subject) at most once
CREATE UNIQUE INDEX uq_event_outbox_subject_type
ON event_outbox (event_domain, event_type, event_subject_table, event_subject_ref)
WHERE event_subject_ref IS NOT NULL;
-- Correlated idempotency (for events that can fire multiple times with different correlation)
CREATE UNIQUE INDEX uq_event_outbox_correlation
ON event_outbox (event_domain, event_type, event_subject_ref, correlation_id)
WHERE correlation_id IS NOT NULL;
-- Hot indexes
CREATE INDEX idx_event_outbox_domain_stream_created
ON event_outbox (event_domain, event_stream, created_at DESC);
CREATE INDEX idx_event_outbox_severity_created
ON event_outbox (event_severity, created_at DESC) WHERE event_severity IN ('warning','critical');
CREATE INDEX idx_event_outbox_subject
ON event_outbox (event_subject_table, event_subject_ref);
B.2 event_read — per-actor read state (universal)
-- NON-EXECUTABLE SKETCH
CREATE TABLE event_read (
event_id uuid NOT NULL REFERENCES event_outbox(event_id) ON DELETE CASCADE,
actor_ref text NOT NULL CHECK (btrim(actor_ref) != ''),
read_at timestamptz NOT NULL DEFAULT now(),
ack_at timestamptz, -- Phase 2 future (stream='task','alert')
PRIMARY KEY (event_id, actor_ref)
);
CREATE INDEX idx_event_read_actor ON event_read (actor_ref, read_at DESC);
Generalized từ iu_notification_read (P3D2). Universal constraint: UNIQUE (event_id, actor_ref) (PK đảm bảo). Implicit self-read computed (no row inserted) — thống nhất với P3D4C0X §D.4.
B.3 event_subscription — config-driven routing
-- NON-EXECUTABLE SKETCH
CREATE TABLE event_subscription (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
recipient_ref text NOT NULL CHECK (btrim(recipient_ref) != ''),
event_domain text, -- NULL = any
event_type text, -- NULL = any
event_stream text, -- NULL = any
scope_subject_table text, -- NULL = any
scope_filter jsonb NOT NULL DEFAULT '{}'::jsonb,
mute boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX idx_event_sub_recipient ON event_subscription (recipient_ref);
CREATE INDEX idx_event_sub_match ON event_subscription (event_domain, event_type, event_stream);
Phase 2 PoC seed:
('agency:sysop', 'system', NULL, 'alert', NULL, '{}', false)— sysop nhận tất cả system alerts.('role:health_owner', 'system', 'red_zone_violation', NULL, 'system_issues', '{}', false).- Default broadcast (no row matches) → fallback "all known actors except creator" — giữ behavior P3D2.
B.4 event_type_registry — MANDATORY (chống free-form)
-- NON-EXECUTABLE SKETCH
CREATE TABLE event_type_registry (
event_domain text NOT NULL,
event_type text NOT NULL,
event_stream text NOT NULL,
default_severity text, -- NULL = derive from producer
description text NOT NULL,
active boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (event_domain, event_type),
CHECK (event_stream IN ('comment','review','update','birth','task','alert','health')),
CHECK (default_severity IS NULL OR default_severity IN ('info','warning','critical'))
);
PoC seed (4 rows):
| event_domain | event_type | event_stream | default_severity | description |
|---|---|---|---|---|
| system | issue_opened | alert | (NULL — mirror row) | A new system_issues row was created. |
| system | issue_resolved | update | info | system_issues.status transitioned to resolved. |
| system | issue_archived | update | info | system_issues.status transitioned to archived. |
| system | red_zone_violation | alert | critical | system_issues with severity='critical' opened or persisted. |
B.4.1 Enforcement mechanism — choice with justification
| Option | Mechanism | Verdict |
|---|---|---|
| A | Compound FK event_outbox(event_domain, event_type) → event_type_registry(event_domain, event_type) |
Acceptable but rigid — adding a new event type requires registry INSERT before first emission, otherwise FK fail kills emitter. Plus FK on text-pair has slightly higher cost than UUID FK. |
| B ⭐ | BEFORE INSERT trigger on event_outbox validates (event_domain, event_type) exists in registry AND active=true. Mismatched stream → RAISE EXCEPTION with custom message. |
CHOSEN — flexibility for migration windows (deactivate vs delete), custom error text aids debugging, trigger overhead ~µs (single PK lookup). Allows runtime registry refresh without DDL. |
| C | Static CHECK constraint regenerated from registry on every change | Brittle — requires DDL re-issue on registry change; stale risk. |
| D | Registry only, no enforcement | Unsafe — turns universal event into free-form activity log. Rejected by GPT directive §4 hardening requirement. |
Decision: registry_enforcement = B (BEFORE INSERT trigger). Trigger sketch:
-- NON-EXECUTABLE SKETCH
CREATE FUNCTION fn_event_type_validate() RETURNS trigger LANGUAGE plpgsql AS $$
DECLARE r record;
BEGIN
SELECT event_stream, active INTO r FROM event_type_registry
WHERE event_domain = NEW.event_domain AND event_type = NEW.event_type;
IF NOT FOUND THEN
RAISE EXCEPTION 'event_type_registry: unknown (%, %)', NEW.event_domain, NEW.event_type;
END IF;
IF NOT r.active THEN
RAISE EXCEPTION 'event_type_registry: (% , %) is inactive', NEW.event_domain, NEW.event_type;
END IF;
IF r.event_stream <> NEW.event_stream THEN
RAISE EXCEPTION 'event_type_registry: stream mismatch — registry has %, got %', r.event_stream, NEW.event_stream;
END IF;
RETURN NEW;
END $$;
B.5 event_pending — staging (HYBRID strategy)
-- NON-EXECUTABLE SKETCH
CREATE TABLE event_pending (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
event_domain text NOT NULL,
event_type_hint text NOT NULL, -- final type resolved by worker
event_subject_table text NOT NULL,
event_subject_ref uuid,
canonical_address text NOT NULL,
actor_ref text NOT NULL,
correlation_id text,
source_document_ref text,
import_batch_ref text,
capture_payload jsonb NOT NULL DEFAULT '{}'::jsonb,
created_at timestamptz NOT NULL DEFAULT now(),
processed_at timestamptz,
error_count int NOT NULL DEFAULT 0,
last_error text
);
-- HYBRID: universal table + per-domain partial indexes for query plan tightness
CREATE INDEX idx_event_pending_unprocessed
ON event_pending (created_at) WHERE processed_at IS NULL;
CREATE INDEX idx_event_pending_system_unprocessed
ON event_pending (created_at) WHERE event_domain = 'system' AND processed_at IS NULL;
CREATE INDEX idx_event_pending_correlation
ON event_pending (correlation_id) WHERE correlation_id IS NOT NULL AND processed_at IS NULL;
B.5.1 Strategy choice — HYBRID
| Option | Pros | Cons | Verdict |
|---|---|---|---|
| UNIVERSAL (1 table) | Simplest ops, 1 worker, 1 advisory lock | All domains share index; large WHERE plans | Acceptable but slow at scale |
| PER_DOMAIN (N tables) | Tight indexes, permission isolation per role | N triggers/workers/lock to maintain; cross-domain ops hard | Overhead too early |
| HYBRID ⭐ | 1 table + per-domain partial index — best of both. Permission isolation via RLS or domain-scoped roles. | Slight DDL complexity (need to add partial index when new domain ships) | CHOSEN |
Decision: pending_strategy = HYBRID.
B.6 event_worker_log (universal)
-- NON-EXECUTABLE SKETCH
CREATE TABLE event_worker_log (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
run_at timestamptz NOT NULL DEFAULT now(),
domain_filter text, -- NULL = all-domain tick
pending_pre int NOT NULL DEFAULT 0,
pending_post int NOT NULL DEFAULT 0,
events_emitted int NOT NULL DEFAULT 0,
rollups_emitted int NOT NULL DEFAULT 0,
conflicts_skipped int NOT NULL DEFAULT 0,
rows_marked int NOT NULL DEFAULT 0,
duration_ms numeric,
error_count int NOT NULL DEFAULT 0,
error_text text
);
Generalized từ P3D4C1 rev3 §2B.
B.7 Config — REUSE dot_config
Inventory verified (read-only):
psql> SELECT count(*) FROM dot_config; → 47
psql> SELECT key FROM dot_config ORDER BY key;
→ dotted-namespace pattern in use:
iu_create.gateway.* (8 keys)
iu_edit.policy.*, iu_edit.schema.*
vocab.*
desc_template_*, hc_*, context_pack_*
→ schema (key text PK, value text NOT NULL, description text, updated_at timestamptz)
config_strategy = REUSE_DOT_CONFIG ✅ — schema fits, namespace pattern already standard.
PoC keys to seed (P3D4C1U scope, NOT this pack):
| Key | Default | Description |
|---|---|---|
event.global.debounce_seconds |
90 |
Universal debounce (clamped 60–300 by worker). |
event.global.batch_threshold |
2 |
Group rollup threshold. |
event.global.worker_lock_name |
event_worker |
hashtext(this) → advisory lock id. |
event.system.debounce_seconds |
30 |
Per-domain override (alerts faster). |
Justification: zero new infra; consistent với P3D4C1 rev3 §5 pattern; future domains chỉ thêm key, không thêm bảng.
§C. IU Compatibility C-i — LOCKED
C.1 Hard rules (non-negotiable, locked by P3D4C0X recommendation + GPT directive)
- ❌ KHÔNG dual-write IU triggers —
trg_aa_iu_notif_*continue writing only toiu_notification_event. - ❌ KHÔNG migrate IU rows to
event_outboxtrong Phase 2. - ❌ KHÔNG modify
fn_iu_unread,fn_iu_mark_read,fn_iu_notification_boardsignature hay body. - ❌ KHÔNG modify
iu_notification_event,iu_notification_readschema. - ✅
event_outboxco-exists riêng biệt; IU table vẫn là SoT cho IU domain. - ✅ Optional convenience view
v_event_unified(UNION ALL) cho cross-domain board query — defer creation đến Phase 2 tests need it; không phải blocker. - ✅ Optional new function
fn_event_unread(p_actor, p_domain text DEFAULT NULL)for cross-domain query — does NOT replacefn_iu_*.
C.2 Convergence (Phase 3, future, OUT-OF-SCOPE for P3D4C0Y)
C-ii roadmap (P3D4C0X §I.3) — one-time copy + view-redirect — chỉ kích hoạt sau khi Phase 2 PoC chứng minh stable + GPT/User approval. KHÔNG plan trong P3D4C0Y.
C.3 Verification gates
iu_no_dual_write = true(no trigger writes to both tables).iu_no_row_migration = true(no script copies rows in Phase 2).iu_fn_api_unchanged = true(front-door functions untouched).fn_iu_unread / fn_iu_mark_read / fn_iu_notification_board hashessnapshot before P3D4C1U; verified equal after.
§D. P3D4C1 Lessons Absorption
P3D4C1 rev3 (paused) is design material; concepts mapped to universal:
| P3D4C1 (IU-specific) concept | Universal P3D4C0Y mapping |
|---|---|
iu_notification_pending table |
event_pending (B.5) — adds event_domain, event_type_hint, correlation_id. |
fn_iu_notif_birth() AFTER INSERT trigger O(1) |
fn_event_capture_system_issues() AFTER INSERT/UPDATE — same O(1) shape: 1 INSERT into pending + 1 PK lookup. No COUNT/JOIN. |
fn_iu_notification_worker_tick() exception-safe with advisory lock |
fn_event_worker_tick(p_domain text DEFAULT NULL) — universal worker, group by (event_domain, COALESCE(source_document_ref, import_batch_ref, correlation_id)). Same EXCEPTION block + lock release. |
Debounce clamp LEAST(300, GREATEST(60, value)) |
Same — universal default 90s, per-domain override via event.{domain}.debounce_seconds. |
| Rollback policy "no-execute on success" | Same — rollback script written but not run; verification gate. |
Backdated test rows (UPDATE pending SET created_at = now()-interval '120s') |
Same test pattern — deterministic, no wall-clock wait. |
| Worker log diagnostic columns | Same — event_worker_log (B.6). |
| Constraint name verify (Step 0E) | Same — preflight inventory required in P3D4C1U. |
p3d4c1_lessons_absorbed = PASS (8/8 lessons reused, generalized).
P3D4C1 status: PAUSED_ABSORBED_INTO_UNIVERSAL — không resume IU-specific implementation. Future IU pending table chỉ thêm vào event_pending với event_domain='iu' ở Phase 3 convergence.
§E. Payload Safety (schema-level)
E.1 Classification field
event_outbox.payload_classification text NOT NULL DEFAULT 'safe_metadata' ∈ {safe_metadata, restricted}.
Phase 2 PoC: only safe_metadata accepted (CHECK enforced). restricted reserved for Phase 3 (encrypted-at-rest payload, separate access control).
E.2 Denylist enforcement (CHECK constraint, B.1)
Forbidden top-level keys in safe_payload:
body, content, raw, vector, embedding, secret, token, password, ssn, personal_data.
Implementation: top-level key denylist via CHECK (sufficient for PoC). Phase 2 hardening seam: BEFORE INSERT trigger that recursively scans nested keys (deferred — adds overhead, not needed for system_issues PoC payload shape).
E.3 Allowlist guidance (documented, not enforced)
PoC safe_payload for system_issues:
{
"issue_code": "ISS-12345",
"severity": "warning",
"occurrence_count": 3,
"source_system": "self_healing_v1.2",
"issue_class": "schema_drift",
"coalesce_key": "schema_drift:tac_publication"
}
ALLOWED: counts, status, codes, refs, classification keys.
NEVER: free-text body, error stack traces (those go to event_worker_log.error_text separately), business descriptions.
E.4 Idempotency rules
Two unique partial indexes (B.1):
(event_domain, event_type, event_subject_table, event_subject_ref) WHERE event_subject_ref IS NOT NULL— single-shot events per subject.(event_domain, event_type, event_subject_ref, correlation_id) WHERE correlation_id IS NOT NULL— correlated events (re-fire allowed once per correlation).
Producer uses INSERT … ON CONFLICT DO NOTHING semantics where re-fire is expected (worker rollups). Triggers may RAISE NOTICE on conflict for diagnostics.
payload_safety_schema = PASS, payload_denylist_enforced = PASS.
§F. Stream Taxonomy — task vs review
F.1 Resolution rule
| Stream | Definition | Trigger condition |
|---|---|---|
review |
Content needs evaluation/approval by recipient. Subject is a piece of content (draft, doc, publication). | Producer = author; recipient = reviewer pool; outcome = approve/reject content. |
task |
Imperative action to perform a procedure. Subject is a procedure invocation (DOT run, health check, workflow step). | Producer = system; recipient = role/agency; outcome = action executed. |
F.2 Overlap analysis
Borderline cases reviewed:
dot_apr_required— recipient must review the fallback evidence AND act (create retroactive APR). → Stream = review (content evaluation precedes procedure). Procedure tracking viacorrelation_idchain.checkpoint_due— pure procedural reminder. → Stream = task.approval_requested— content review. → Stream = review.
F.3 Verdict
task_review_resolved = SEPARATED ✅
Rule documented above; PoC system_issues uses {alert, update, health} only — task/review not exercised by this PoC, so resolution is forward-looking but not deferred.
GPT directive §3 ("test whether task and review overlap in practice, but do not change now") — honored: rule stated, no taxonomy change, PoC doesn't trigger any task/review event.
§G. Đ43 Alignment Gate
G.1 Specific checks
| Check | Result |
|---|---|
dot_config reuse — schema/contract change? |
❌ NO — only adds keys with event.* namespace, schema unchanged. |
system_health_checks overlap with universal events? |
❌ NO — Đ43 health checks remain SoT; universal event only emits notification about state transitions in system_issues (the consumer of HC). |
| New Đ43 section needed? | ❌ NO — red_zones section can use existing pg_query source mode (Đ43 §6) to render top-N recent universal alerts. |
| Context-pack registration for new tables? | ✅ YES — 5 new tables (event_outbox, event_read, event_subscription, event_type_registry, event_pending, event_worker_log) registered via existing table_registry infra (no Đ43 contract change). |
dot-context-pack-build machinery duplicated? |
❌ NO — Đ43 builder produces events into event_outbox (event_domain='system', event_type='context_pack_built/failed') — additive, doesn't duplicate. |
G.2 Verdict
dieu43_gate = ADDITIVE_ONLY ✅
GPT directive §7 confirms: "Additive-only if using existing Đ43 pg_query source mode and no section/schema change. If P3D4C0Y proposes a new Đ43 section or changes context-pack contract, require separate Đ43 review/amend."
P3D4C0Y proposes neither new section nor contract change. PASS additive-only path.
§H. Rollback / Failure-Mode Design
H.1 PoC failure scenarios + responses
| Failure | Response | Recovery time |
|---|---|---|
| Schema DDL fails partway through P3D4C1U apply | Inverse-order DROP TABLE for the 5+1 new tables (no IU touch). FK cascade handles event_read → event_outbox. |
Minutes |
Birth trigger on system_issues causes hot-path slowdown |
DROP TRIGGER + DROP FUNCTION; system_issues writes resume normal. Pending rows orphaned (truncate when ready). | Seconds |
| Worker hangs / stuck advisory lock | pg_try_advisory_lock returns false → next tick skips; manual SELECT pg_advisory_unlock(hashtext('event_worker')) if needed; cron unschedule via cron.unschedule('event-worker'). |
Minutes |
event_outbox grows unbounded |
Phase 2 retention seam: daily cron DELETE FROM event_outbox WHERE event_stream='update' AND processed_at IS NOT NULL AND created_at < now() - interval '90 days'. Severity=critical retained 1y. (Design only — implement when volume warrants.) |
N/A (preventive) |
event_type_registry validate trigger blocks legitimate emission |
INSERT row in registry first; trigger re-validates next emission. Worst case: temporary active=true flip in registry, no DDL. |
Seconds |
| Routing wrong recipient | event_subscription.mute=true row; or DELETE subscription. No event_outbox change needed. |
Seconds |
H.2 IU regression risk assessment
| Asset | Touched? | Risk |
|---|---|---|
iu_notification_event |
NO | ZERO |
iu_notification_read |
NO | ZERO |
fn_iu_unread, fn_iu_mark_read, fn_iu_notification_board |
NO | ZERO |
fn_iu_notif_comment/draft/version triggers |
NO | ZERO |
fn_iu_save, fn_iu_apply_edit_draft |
NO | ZERO |
unit_edit_comment, unit_edit_draft, unit_version, information_unit |
NO | ZERO |
iu_regression_risk = ZERO ✅. Verified: PoC domain is system_issues, not IU.
H.3 Test rollback protocol
rollback_executed = NO_ON_SUCCESS | YES_ON_FAIL | USER_APPROVED_DRILL — script verified syntactically + dependency-checked, executed only on failure or explicit approval (P3D4C1 rev3 pattern).
rollback_design = PASS.
§I. Routing model — Phase 2 minimal
| Recipient kind | actor_ref format | PoC seed |
|---|---|---|
| Sysop role | agency:sysop |
broadcast for event_domain='system' AND event_stream='alert' |
| Health owner | role:health_owner |
filter event_type='red_zone_violation' |
| Specific user | user:{login} |
none seeded for PoC (kept generic) |
Resolution view (P3D4C0X §D.2):
-- NON-EXECUTABLE SKETCH
CREATE VIEW v_event_recipient AS
SELECT e.event_id, s.recipient_ref, s.mute
FROM event_outbox e
JOIN event_subscription s
ON (s.event_domain IS NULL OR s.event_domain = e.event_domain)
AND (s.event_type IS NULL OR s.event_type = e.event_type)
AND (s.event_stream IS NULL OR s.event_stream = e.event_stream)
AND (s.scope_subject_table IS NULL OR s.scope_subject_table = e.event_subject_table)
WHERE NOT s.mute;
Phase 1 fallback (no subscription matches → broadcast minus self) implemented in fn_event_unread(), mirroring P3D2 IU behavior.
§J. Implementation Pack Recommendation
next_required_pack=P3D4C1U_UNIVERSAL_CORE_IMPLEMENTATION_PROMPT_REVIEW
poc_domain=system_issues
poc_domain_pg_evidence=row_count=8548; lifecycle=open|resolved|archived (CHECK); severity=critical|warning|info (CHECK); 7 active triggers; zero IU surface
core_tables_count=6 # event_outbox, event_read, event_subscription, event_type_registry, event_pending, event_worker_log
registry_enforcement=B # BEFORE INSERT trigger validating against event_type_registry
pending_strategy=HYBRID # 1 universal table + per-domain partial indexes
config_strategy=REUSE_DOT_CONFIG # 47-row inventory confirms namespace + schema fit
iu_compat=C-i (no dual-write, no row migration, fn_iu_* unchanged)
dieu43_gate=ADDITIVE_ONLY
estimated_implementation_effort=MEDIUM
listen_notify_status=DEFERRED_PHASE3
worker_strategy=PG_CRON_ONLY # 1 worker, multi-domain via group by event_domain
p3d4c1_status=PAUSED_ABSORBED_INTO_UNIVERSAL
P3D4C1U scope (proposal — separate prompt, requires GPT/User review):
- §1 Apply DDL §B (6 tables) — IDEMPOTENT.
- §2 Seed
event_type_registry(4 rows) +event_subscription(2 rows) +dot_config(4 keys). - §3 BEFORE INSERT trigger
fn_event_type_validateonevent_outbox. - §4 Capture trigger
fn_event_capture_system_issuesAFTER INSERT/UPDATE onsystem_issues. - §5 Universal worker
fn_event_worker_tick+ pg_cron schedule. - §6 Tests: 12+ deterministic backdated tests (parity với P3D4C1 rev3 §7 + cross-domain board test + subscription routing test + registry enforcement test + IU compat snapshot test).
- §7 Rollback script (data-safe, conditional drops).
§K. Verification Block
phase_status=PASS
poc_domain_selected=system_issues
poc_domain_pg_inventory=PASS
core_schema_designed=PASS
event_type_registry_included=PASS
registry_enforcement_mechanism=B
pending_strategy=HYBRID
config_strategy=REUSE_DOT_CONFIG
iu_compat_ci_locked=PASS
iu_no_dual_write=true
iu_no_row_migration=true
iu_fn_api_unchanged=true
p3d4c1_lessons_absorbed=PASS
payload_safety_schema=PASS
payload_denylist_enforced=PASS
task_review_resolved=SEPARATED
dieu43_gate=ADDITIVE_ONLY
rollback_design=PASS
not_activity_log=PASS # 4-event PoC type registry + 3-question rubric (P3D4C0X §M.3)
no_pg_mutation=true
no_directus_mutation=true
no_nuxt_code=true
no_iu_runtime_change=true
no_p3d4c1_resume=true
listen_notify_status=DEFERRED_PHASE3
hot_path_contract=O1_APPEND_ONLY
worker_strategy=PG_CRON_ONLY
recommendation=P3D4C1U_UNIVERSAL_CORE_IMPLEMENTATION_PROMPT_REVIEW
next_required_pack=P3D4C1U_UNIVERSAL_CORE_IMPLEMENTATION_PROMPT_REVIEW
P3D4C0Y rev1 design note | PoC scope plan | PG inventory-backed | Non-executable | KB-only | All hard boundaries honored | Awaiting GPT/User review