Skip to main content
Proposed (STA-836, STA-837, STA-839, STA-826, STA-714 integrations part). It extends 0223, 0356 and 0224.
Amended 2026-10-26 (STA-1235), decision 1: config notifications.target names the built-in target when a host passes none, messages (the default, unchanged) or delivery (the OS notifications service’s delivery.message.send@1: the template key, a category code, the template values, the rendered mail or null, and the Market). Notification({ category, messageCategory }) may name the delivery service’s own category code beside the three-case category. Recipient.address(email) addresses a person with no account yet (an invitation, a partner lead): a target takes it through an optional addressPayload, and the driver refuses it before queueing anything when the target has none. This is the one case where an address leaves the producer; a person with a principal is always addressed by id. Scope recipients stay refused.

Context

The OS notifications and audit services filed gaps: the notifier driver can only reach messages.message.send@1 with a mail-only input; an in-app entry has no product; ProviderResolver answers one Connection and reads the market only from context; no generic market-pack ProviderDefaults driver ships; webhooks have no S3 target and no entitlement gate. Payroll needs a connector contract (RV-50: no own payroll).

Decision

  1. Notifier target. NotificationsNotifierDriver takes a NotifierTarget: any action ref and a payload mapper that receives the template key, category, principal, market (from a bound MarketContext), data and each rendered channel, and answers the action’s input or null to skip. The default stays messages.message.send@1 with the mail.
  2. In-app product. InAppMessage.product is required, and InAppStore.list filters by product, for a per-product inbox and the ethical wall.
  3. Failover list. ProviderResolver#candidates answers every Connection to try: the tenant’s own (mode own), then the Market’s platform ones (mode managed), each in pack order. resolve is its first entry, loaded lazily. Both take the Market as an argument and fall back to MarketContext. Whether an own Connection may fail over to a managed one is the caller’s policy. ProviderChoice.connectionId is optional: a bring-your-own-only provider is offered with no platform Connection. MarketPackProviderDefaultsDriver reads the pack’s providers.<capability> list (live entries, pack order) through the host’s ProviderPackSource, as payments’ driver does.
  4. Webhooks. An endpoint URL s3://<bucket>/<prefix>?region= receives each delivery as an object keyed <prefix>/<type>/<yyyy>/<mm>/<dd>/<eventId>.json, the signed headers as metadata, written by the s3 WebhookObjectSink with the platform role; a tenant’s own bucket grants that role by bucket policy, so no tenant credential is stored. A registration row may name an entitlement; register refuses a gated event without the grant and the fan-out rechecks it, through WebhookEntitlementSource (fail closed when unbound).
  5. Payroll. IntegrationCategory.Payroll and PayrollConnector.pushPayInputs(batch, idempotencyKey) (hours, sessions, tips, commission per employee, with the provider’s employee id and pay code from the tenant’s mapping), checked by assertPayInputs; MemoryPayrollConnector in ./testing. The ZenHR, Kombo and Gusto connectors and the data-transfer export live with the capability’s owner and are not decided here.

Consequences

  • A notifications service with another module or input needs no fork of the driver.
  • Channel gateways stop repeating the ADR 0356 order rule.
  • The audit SIEM stream is gated by plan in one place and can land in a bucket.

Enforcement

planned — the tests beside each file: notifications-notifier.driver.test.tsx, in-app-store.fake.test.ts, provider-resolver.service.test.ts, market-pack-provider-defaults.driver.test.ts, deliver-webhook.action.test.ts, webhook-endpoint.service.test.ts, memory-payroll.connector.test.ts.

Supersedes and sources

Supersedes: none. Sources: OS services/notifications/SPEC.md gaps 1, 3, 5, 6 and 8; OS services/audit/SPEC.md gap 7; RV-50; RV-26; R3-SO-05; STA-836; STA-837; STA-839; STA-826; STA-714.