KB-347D

08 — DP6 Subscription / Routing / Executor Boundary

11 min read Revision 1
design-packdieu-45dp6subscriptionroutingexecutor-boundarymot-not-executordesign-only

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:

  1. A clear event-side subscription model (who reads which events) — refining the existing event_subscription 3-row config without amending its schema.
  2. A new job-side subscription model (job_subscription) so executors can scope what they claim (per job_kind, per event_domain, per priority floor).
  3. A ratified executor whitelist matching §11.5 verbatim, plus the explicit non-executor list (MOT) per §13.4.
  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.
  5. 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_subscription row: mute toggle is the lifecycle.
  • job_subscription row: enabled toggle 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_subscription insert/update is workflow_admin.
  • job_subscription insert/update is workflow_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_subscription rows (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).

Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.6-dieu45-full-queue-orchestration-design-pack/08-DP6-subscription-routing-executor-boundary.md