08 — DP6 Subscription / Routing / Executor Boundary
08 — DP6 — Subscription / Routing / Executor Boundary
DESIGN-ONLY. Cites Điều 45 §11 (all), §13.4, §16.1 (Điều 37 vocab). No DDL, no DML.
§1. Goal
Establish:
- A clear event-side subscription model (who reads which events) — refining the existing
event_subscription3-row config without amending its schema. - A new job-side subscription model (
job_subscription) so executors can scope what they claim (perjob_kind, perevent_domain, per priority floor). - A ratified executor whitelist matching §11.5 verbatim, plus the explicit non-executor list (MOT) per §13.4.
- A routing/permission model so that producers cannot enqueue jobs targeted at executors they are not authorised to drive; consumers cannot consume events they have no subscription for.
- Forward seam for customer-care/email/message (doc 12) and future Kestra adapter.
§2. Current state
| Concern | Live |
|---|---|
event_subscription |
3 rows: 2 for role:health_owner (system/issue_opened, system/issue_resolved), 1 for agency:sysop (stream=alert). |
| Recipient resolution rule | "broadcast fallback" implied (per 23-P3D4C0X §D); not enforced. |
| Job-side subscription | ❌ none |
| Executor whitelist | named in §11.5 but no live CHECK / enforcement table |
| MOT enforcement | §13.4 codified; no live registry |
§3. Proposed design
§3.1 Event-side subscription (unchanged shape, additive content)
event_subscription schema is preserved as-is. Only contents change (Council ratification of new rows):
NON-EXECUTABLE DESIGN SKETCH — DO NOT APPLY (rows, not schema)
(recipient_ref, event_domain, event_type, event_stream, scope_subject_table, scope_filter, mute)
('role:workflow_admin', 'staging', NULL, 'birth', NULL, '{}', false)
('role:workflow_admin', 'staging', NULL, 'update', NULL, '{}', false)
('role:dot_executor', 'dot', NULL, NULL, NULL, '{}', false)
('agency:integrity', 'health', NULL, 'alert', NULL, '{}', false)
('role:kg_indexer', 'kg', NULL, NULL, NULL, '{}', false)
('role:auto_instantiator','iu', 'template.instance_auto_composed', 'update', NULL, '{}', false)
Each addition follows §6.4 inclusion rubric AND Điều 37 actor vocab (role:, agency:, user:, agent:).
§3.2 Recipient resolution rule (codified)
resolution_rule:
steps:
1: exact_match # (recipient_ref, event_domain, event_type, event_stream) matches a non-muted row
2: domain_wildcard # event_type=NULL match within (recipient_ref, event_domain)
3: stream_wildcard # event_stream=NULL match within (recipient_ref, event_domain, event_type)
4: domain_only # (recipient_ref, event_domain) with both type and stream NULL
5: broadcast_fallback # warn-only: surface as `v_subscription_broadcast_used`
warn_threshold:
broadcast_used_percentage: 5 # >5% of events resolved via broadcast emits a warning event
broadcast_fallback is not deny-by-default (would break current behaviour and the 131k existing system/issue_opened events with role-only subscriptions). It IS observable, so a watchdog can detect drift.
§3.3 Job-side subscription — job_subscription
A new table parallel to event_subscription:
NON-EXECUTABLE DESIGN SKETCH — DO NOT APPLY
table public.job_subscription (
subscription_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
executor_id text NOT NULL, -- e.g. 'iu_outbound_default', 'hermes_batch_a'
executor_kind text NOT NULL
CHECK (executor_kind IN ('DOT','Agent','Hermes','Codex','PG_worker','external_worker','future_Kestra_adapter')),
job_kind text NULL, -- NULL = all kinds for this executor
event_domain text NULL, -- NULL = all domains
priority_floor smallint NOT NULL DEFAULT 0,
max_parallel integer NOT NULL DEFAULT 1 CHECK (max_parallel >= 1),
enabled boolean NOT NULL DEFAULT false,
notes text NULL,
registered_by text NOT NULL,
registered_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (executor_id, job_kind, event_domain)
);
fn_job_claim (DP2) consults job_subscription to scope p_kinds and respect max_parallel.
§3.4 Executor whitelist enforcement
Verbatim from §11.5. Already encoded as CHECK on job_queue.executor (DP2), queue_heartbeat.executor_kind (DP4), consumer_registry.executor (DP5), job_subscription.executor_kind (this DP).
forbidden_executor_values = {'MOT', 'future_MOT_executor', ...anything_outside_whitelist}
MOT enforcement: consumer_registry.executor='MOT' REFUSED at insert by CHECK; job_queue.executor='MOT' REFUSED at insert by CHECK; queue_heartbeat.executor_kind='MOT' REFUSED at insert by CHECK. MOT cannot register a heartbeat, cannot subscribe, cannot claim.
§3.5 MOT vs queue (the §13.4 boundary)
mot_boundary:
MOT_role:
- emits_job_graph_via_fn_mot_graph_emit # design seam — doc 11
- generates_workflow_instances_from_template_config
- delegates_execution_to_queue_executors
MOT_NOT_role:
- claims_job_queue_row
- holds_lease
- writes_heartbeat
- registers_in_subscription_or_consumer_registry
enforcement:
- CHECK on job_queue.executor refuses 'MOT'
- CHECK on queue_heartbeat.executor_kind refuses 'MOT'
- CHECK on consumer_registry.executor refuses 'MOT'
- CHECK on job_subscription.executor_kind refuses 'MOT'
§4. Lifecycle / status
event_subscriptionrow:mutetoggle is the lifecycle.job_subscriptionrow:enabledtoggle is the lifecycle.- Executor identity has its own lifecycle in
queue_heartbeat.registered_at.
§5. Indexes / performance
NON-EXECUTABLE DESIGN SKETCH — DO NOT APPLY
INDEX ix_event_subscription_lookup
ON event_subscription(event_domain, event_type, event_stream)
WHERE mute = false;
INDEX ix_job_subscription_claim
ON job_subscription(executor_id, job_kind, event_domain)
WHERE enabled = true;
Subscription tables stay small (<1000 rows expected); lookup is O(1) per resolution step.
§6. Security / governance
event_subscriptioninsert/update isworkflow_admin.job_subscriptioninsert/update isworkflow_admin(per-executor delegation possible via Điều 37 agency model).- Executor cannot self-grant subscription; registration is an out-of-band operator action.
- Recipient_ref must be a known D37 actor — referential check is policy-level (no FK to actor table because actor table isn't a single table; D37 model is multi-source).
§7. Rollback / disable
- Per-row:
mute=true(event) /enabled=false(job). - Per-executor: deregister from
queue_heartbeat(silences via DP4 stale-check). - Global:
dot_config.queue.subscription_resolution.enabled='false'→ resolver falls through to broadcast (legacy behaviour).
§8. Healthcheck / observability
NON-EXECUTABLE DESIGN SKETCH — DO NOT APPLY
view v_subscription_broadcast_used AS
SELECT event_domain, event_type, count(*) AS broadcast_events_24h
FROM event_outbox e
WHERE e.created_at > now() - interval '24 hours'
AND NOT EXISTS (
SELECT 1 FROM event_subscription s
WHERE s.mute = false
AND 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)
)
GROUP BY event_domain, event_type;
view v_executor_subscription_health AS
SELECT js.executor_id, count(*) AS rules, max(js.enabled::int) AS any_enabled
FROM job_subscription js GROUP BY js.executor_id;
Both views feed v_queue_health (DP4) for D31/D43 visibility.
§9. Compatibility with Điều 45 v1.0
| Clause | Compliance |
|---|---|
| §11.1 producer/queue/executor ASCII | ✅ DP6 carves the executor side of the boundary |
| §11.2 rules 1–4 | ✅ executor cannot bypass queue substrate; claim is gated by job_subscription |
| §11.3 DOT relation | ✅ DOT is a registered executor; no special path |
| §11.4 Agent/Hermes/Codex | ✅ each registers as an executor with heartbeat + subscription |
| §11.5 executor whitelist | ✅ CHECK in 4 places verbatim |
| §13.4 MOT NOT executor | ✅ CHECK refuses MOT in all 4 surfaces |
| §16.1 Điều 37 actor vocab | ✅ recipient_ref uses role: / agency: / user: / agent: |
§10. Implementation prerequisites
- DP2 (
job_queue) + DP4 (queue_heartbeat) must precede this. - Council ratification of additional
event_subscriptionrows (Phase 2 of doc 14). - Operator runbook for executor registration (out of scope of this design pack).
§11. Open questions
| # | Question | Routed to |
|---|---|---|
| DP6-Q1 | Approve broadcast-fallback as warn-only (not deny-by-default)? | Council |
| DP6-Q2 | Approve job_subscription table at Phase 1, or defer to Phase 3? |
Council |
| DP6-Q3 | Approve MOT enforcement via CHECK in 4 places (vs single registry table)? | Council |
| DP6-Q4 | Per-executor max_parallel default: 1, 4, 16? |
Council |
| DP6-Q5 | Should recipient_ref get a FK target table (single source of D37 actors)? |
Council + Điều 37 |
| DP6-Q6 | Should consumer_registry (DP5) and event_subscription (this DP) merge into one routing table? |
Council |
§12. Self-test
self_test:
cites_dieu45_section: §11,§13.4,§16.1
defines_status_lifecycle_compatible_with_§6_7: n/a (routing)
defines_idempotency_key: n/a
defines_retry_dlq: inherits DP3
defines_lease: inherits DP3
defines_observability_view: v_subscription_broadcast_used, v_executor_subscription_health
defines_dot_config_disable_flag: queue.subscription_resolution.enabled
defines_executor_set_compatible_with_§11_5: yes (CHECK in 4 surfaces)
no_vector_in_transient: yes
signal_not_data: yes
pg_sot: yes
rollback_concept: per-row mute/disable + global flag
no_pg_cron_dependency_phase_1: yes
no_pg_18_dependency: yes
no_mutation_authored: yes
DP6 design. No mutation. Authored 2026-05-26 by Claude Opus 4.7 (1M).