notification

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 18, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

Documentation

Overview

Package notification is the application half of the Notification context: the consumer that turns events into records, and the delivery that turns a record into a message.

Neither is a use case, and both are deliberately absent from the catalogue in domain-model.md §5. The catalogue is the list of things a person, an agent or a rule can ask for (arc42 §4), and "notify everybody about this event, now" is not one of them - the way to influence what is sent is the preference, not a call. Registering them would put a "send email to everybody" action on three channels, which is a button nobody should be given.

Index

Constants

View Source
const (
	OpeningEventOpened = "OPENED"
	OpeningEventClosed = "CLOSED"
)

The two events a notice is about.

View Source
const ConsumerName = "notification"

ConsumerName is how this subscriber is known in the deduplication and in the logs.

A constant, and stable across versions: renaming it makes every event it has already seen look new, which for this subscriber means sending every notification of the retention window again (core/port/eventbus).

Variables

This section is empty.

Functions

This section is empty.

Types

type AccessReader

type AccessReader interface {
	Find(ctx context.Context, id shared.ID) (work.EmergencyAccess, string, error)
}

AccessReader answers an emergency access with its hub's name - the work repository's Find.

type AddressLink struct {
	Token       secret.Secret
	Address     string
	Locale      string
	DisplayName string
}

AddressLink is what the minter answers; an empty address is "nothing to send".

type AddressMinter

type AddressMinter interface {
	MintAddressLink(ctx context.Context, tenantID, requestID shared.ID) (AddressLink, error)
}

AddressMinter is the identity service's seam: one method and one value, so this package points at nothing the identity service owns.

type DeliverNotification

type DeliverNotification struct {
	Notifications repository.Notifications
	Preferences   repository.Preferences
	Accounts      identityrepo.Accounts
	Items         Entries
	// Rules and Subscriptions name the subjects that are not an entry: the rule the
	// message says was switched off, the subscription it says is no longer called. Optional -
	// nil leaves the title out, which is the withheld sentence rather than a wrong one.
	Rules         RuleReader
	Subscriptions SubscriptionReader
	// Accesses names the emergency access a member is told of. Optional like the two above: nil
	// leaves the hub, the opener's words and the times out, which the withheld sentence survives.
	Accesses AccessReader
	// Grants and Places name the role somebody was given: the grant, and the hub or collection it
	// is on (an entry is read through Items). Grants nil leaves the role out of the message - the
	// membership's withheld sentence; Places nil leaves the place's name out.
	Grants     GrantReader
	Places     PlaceReader
	Mail       mail.Sender
	Renderer   i18n.Renderer
	UnitOfWork persistence.UnitOfWork
	Clock      clock.Clock
	// Workspaces answers the workspace's own default language, the second link of the chain a
	// recipient's locale is resolved down (i18n-l10n.md §2) - an invited person has not chosen
	// one yet, by definition, and the invitation is the first thing they read. Optional: nil
	// skips to the installation's default.
	Workspaces WorkspaceReader
	// FallbackLocale is the installation's default, the last link of the same chain.
	FallbackLocale string
	// BaseURL is where this installation lives, so an email can carry a link somebody can click. A
	// relative path in an email is a dead link.
	BaseURL string
	// Redemptions mints the credential an invitation mail carries: minted here, at
	// delivery, so the queue's payload stays identifiers-only and the plaintext exists exactly
	// once, in the message on its way out. Nil composes the plain link, which is what an
	// installation without the sign-in flow sent before the flow existed.
	Redemptions RedemptionMinter
	// Signals is the observability slice. Optional, like everywhere else in this package.
	Signals Signals
}

DeliverNotification sends one notification.

It owns its transactions, because it cannot live inside one: rendering is cheap and sending is not, and a transaction held open across an SMTP conversation is what observability-reliability.md §8 forbids. Three parts, in the order the media reconciliation uses for the same reason:

  1. Read what the message is about - the record, the recipient, the entry - in one transaction.
  2. Render and send, outside any transaction.
  3. Write the outcome back, in a transaction of its own.

Safe to run twice, which is what lets it declare itself detached (queue.Detached), and the second run is not free of consequence: a process that died between the send and the write-back leaves a message sent and a record that still says pending, and the retry sends it again. That is the honest trade of at-least-once for email, and it is the right side of it - a duplicate notification is a nuisance, and a lost one is the thing somebody needed to know.

func (DeliverNotification) Execute

func (d DeliverNotification) Execute(
	ctx context.Context, tenantID, notificationID shared.ID, lastAttempt bool,
) error

Execute sends the notification the job names, and reports whether it worked.

`lastAttempt` is the queue's judgement rather than this service's: the attempt budget lives on the job, and a record that counted its own would be a second budget disagreeing with the first.

type Entries

type Entries interface {
	Find(ctx context.Context, id shared.ID) (work.WorkItem, error)
}

Entries and Members are the slices of the work repositories this package reads, and they are slices for the reason Queue is: a consumer that held the whole item repository could complete an entry, move it, or write its children, and none of that is anything a notification does. What is declared here is the entire reach of the notification path into the work management context - one read of an entry, one read of its member list.

type GrantReader

type GrantReader interface {
	Find(ctx context.Context, id shared.ID) (identity.Grant, error)
}

GrantReader answers a membership by its identifier - the identity repository's Find.

type Members

type Members interface {
	List(ctx context.Context, itemID shared.ID) ([]shared.ID, error)
}

Members is one entry's member list.

type OpeningNotice

type OpeningNotice struct {
	AccountID shared.ID
	Event     string
	Until     time.Time
	Ended     string
}

OpeningNotice is one queued notice, as the job carries it.

type PlaceReader

type PlaceReader interface {
	Find(ctx context.Context, id shared.ID) (work.Container, error)
}

PlaceReader answers a hub or a collection by its identifier - the container repository's Find.

type PrivateReach

type PrivateReach interface {
	Hidden(ctx context.Context, actor appshared.ActorContext, path []identity.Scope) (bool, error)
}

PrivateReach is the authoriser's question about a private hub, asked about each person a notification would go to (access.Service.Hidden).

type ProviderFinder

type ProviderFinder interface {
	Find(ctx context.Context, id shared.ID) (identity.IdentityProvider, error)
}

ProviderFinder answers whether a provider still exists, which is all a disconnection's notice asks of it.

type Queue

type Queue interface {
	Enqueue(ctx context.Context, request queue.Request) (shared.ID, error)
}

Queue is the slice of the job queue this package needs. Narrow rather than the whole port, so that what a consumer can do to the queue is visible in one line: it can ask for work, and it cannot claim, complete or fail anything.

type RecordEmergencyAccess

type RecordEmergencyAccess struct {
	Notifications repository.Notifications
	Accounts      identityrepo.Accounts
	Preferences   repository.Preferences
	Jobs          Queue
	Clock         clock.Clock
	IDs           clock.IDGenerator
}

RecordEmergencyAccess tells every member of a private hub that the workspace's owner opened it (ADR-0073 §4).

It runs inside the transaction that opens the hub, so the access and every member's record commit together or not at all: nobody reads the hub before its members are told. A record is written for every member whatever happens to the mail - suppressed for somebody without an address - because the record is also what the web app shows them (identity.md §22).

The category is never switched off; the preference still decides whether the mail names the hub. Delivered under its own job kind (queue.KindEmergencyAccessEmail).

func (RecordEmergencyAccess) EmergencyAccessOpened

func (r RecordEmergencyAccess) EmergencyAccessOpened(
	ctx context.Context, access work.EmergencyAccess, members []shared.ID,
) error

EmergencyAccessOpened writes one record per member and queues the deliveries.

type RecordInvitation

type RecordInvitation struct {
	Notifications repository.Notifications
	Accounts      identityrepo.Accounts
	Jobs          Queue
	Clock         clock.Clock
	IDs           clock.IDGenerator
	Signals       Signals
}

RecordInvitation turns the queued invitation job into a message.

An invitation is queued in the transaction that creates the account, so that the seat and the message exist together or neither does. This is the other end of that queue - and it is deliberately not a second delivery path. The job writes a record and queues the same `notification.deliver` job every other notification uses, so there is one place that renders an email and one place that sends it.

It runs inside the runner's transaction, which is what makes it exactly-once: the record, the delivery job and the invitation job's own completion commit together (core/port/queue). That is why this handler is not detached and the delivery is.

func (RecordInvitation) Execute

func (r RecordInvitation) Execute(
	ctx context.Context, tenantID, accountID, invitedBy shared.ID,
) error

Execute writes the invitation's record and queues its delivery.

The preference is not consulted, and there is no branch here that could consult it: the invitation is the one category no preference may switch off (domain.Category.Suppressible), and the setting that would switch it off sits behind the door this message unlocks.

type RecordNotifications

type RecordNotifications struct {
	Notifications repository.Notifications
	Preferences   repository.Preferences
	Accounts      identityrepo.Accounts
	Items         Entries
	ItemMembers   Members
	Jobs          Queue
	Clock         clock.Clock
	IDs           clock.IDGenerator
	// Signals is the observability slice. Optional: the consumer runs without it, which is what
	// keeps a metrics adapter from being a dependency of the notification path.
	Signals Signals
	// Reach drops a recipient who does not reach the private hub the entry is in - a former
	// assignee, somebody on its member list who has since left the hub (D6, ADR-0073 §1): no record,
	// no mail. Nil only in a build that has no private hubs to keep.
	Reach PrivateReach
}

RecordNotifications is the outbox consumer: it decides who is to be told about an event and writes one record per person.

It runs inside the dispatcher's transaction, which is the whole reliability argument (ADR-0007): the records it writes, the delivery jobs it queues and the consumption record that says this event was handled all commit together. A process that dies halfway leaves none of them, and the event is handed over again.

It reaches nothing outside the database, and that is the rule rather than an accident. The email is a job, because an SMTP server inside a transaction holds a database connection for as long as somebody else's machine feels like taking (observability-reliability.md §8) - and because the comment that caused the notification must commit whether or not anybody can be told about it.

func (RecordNotifications) Deliver

func (r RecordNotifications) Deliver(ctx context.Context, envelope event.Envelope) error

Deliver writes one record per person the event concerns.

An error stops the dispatcher's whole batch, which is why the only errors it produces are real ones: a recipient whose account has gone is not an error but a record nobody can be sent, and a preference that says no is not an error but a record that says why.

func (RecordNotifications) Name

func (r RecordNotifications) Name() string

Name identifies the subscriber in the deduplication and in the logs.

func (RecordNotifications) Wants

func (r RecordNotifications) Wants(eventType event.Type) bool

Wants reports whether this event concerns anybody.

The three in `categories`. An invitation is not here, because it is not an event: it is queued in the transaction that creates the account (RecordInvitation), rather than announced by an event invented for a message that already has a delivery path.

type RecordPasswordOpening

type RecordPasswordOpening struct {
	Memberships identityrepo.Memberships
	Jobs        Queue
}

RecordPasswordOpening queues the notices. It implements the control plane's OpeningNotices.

func (RecordPasswordOpening) PasswordClosed

func (r RecordPasswordOpening) PasswordClosed(
	ctx context.Context, tenantID shared.ID, ended string,
) error

PasswordClosed tells every administrator that the opening ended, and how.

func (RecordPasswordOpening) PasswordOpened

func (r RecordPasswordOpening) PasswordOpened(
	ctx context.Context, tenantID shared.ID, until time.Time,
) error

PasswordOpened tells every administrator that the password is open until `until`.

type RecordPrivateHubTrashed

type RecordPrivateHubTrashed struct {
	Notifications repository.Notifications
	Accounts      identityrepo.Accounts
	Preferences   repository.Preferences
	Jobs          Queue
	Clock         clock.Clock
	IDs           clock.IDGenerator
}

RecordPrivateHubTrashed tells the workspace's owners that a private hub lost its last member and went to the trash (ADR-0073 §5).

A membership message about nothing: no entry, no hub and no person. The hub's name is for its members only, and there are none left; the person may have just been erased by the very act that left the hub empty. What the owners can act on is that it is there, and the administration's list of private hubs shows it with its size and the day it goes.

The ordinary path otherwise: the preference is honoured, the send is a job. It runs inside the caller's transaction, so the message and the deletion it reports commit together.

func (RecordPrivateHubTrashed) PrivateHubTrashed

func (r RecordPrivateHubTrashed) PrivateHubTrashed(
	ctx context.Context, tenantID shared.ID, recipients []shared.ID,
) error

PrivateHubTrashed writes one record per recipient and queues the deliveries.

type RecordReminder

type RecordReminder struct {
	Notifications repository.Notifications
	Preferences   repository.Preferences
	Accounts      identityrepo.Accounts
	Jobs          Queue
	Clock         clock.Clock
	IDs           clock.IDGenerator
	Signals       Signals
}

RecordReminder turns a reminder that has come due into records and the jobs that send them.

Deliberately not a second delivery path, for the reason the invitation is not one: it writes the same record every other notification writes and queues the same `notification.deliver` job, so there is one place that renders an email and one place that sends it. What is different is only where the recipients come from - a reminder names them, or means the assignee and the entry's members - and that is the firing service's question, not this one's.

It runs inside the firing pass's transaction, which is what makes it exactly-once: the records, the delivery jobs, the reminder's own PENDING → SENT transition and the job's completion commit together. A process that dies halfway leaves none of them, and the reminder is still pending when the job is claimed again.

func (RecordReminder) Execute

func (r RecordReminder) Execute(
	ctx context.Context, tenantID, itemID shared.ID, recipients []shared.ID,
) error

Execute writes one record per recipient and queues what is to be sent.

The preference is consulted, unlike the invitation's: somebody may switch reminders off, and the suppressed record is what tells them afterwards why they heard nothing. Nobody is named as the actor - a reminder is caused by the clock rather than by a person - which is also what keeps the domain's self-caused rule from suppressing a reminder somebody set for themselves.

type RecordRetentionWarning

type RecordRetentionWarning struct {
	Notifications repository.Notifications
	Accounts      identityrepo.Accounts
	Memberships   identityrepo.Memberships
	Members       Members
	Preferences   repository.Preferences
	Jobs          Queue
	Clock         clock.Clock
	IDs           clock.IDGenerator
	Signals       Signals
	// Reach drops an administrator - of the workspace above all - who does not reach the private
	// hub the entry is in (D6, ADR-0073 §1): the warning names the entry.
	Reach PrivateReach
}

RecordRetentionWarning tells the people who can stop it that a rule is about to act (data-retention.md §6).

The ordinary notification path rather than a channel of its own: the preference is honoured, the record is deduplicated on the entry, and the send is a job like every other. What is different is only what caused it - not somebody's action and no domain event, but a retention pass concluding that an object's period has run out.

Why it carries no event

`event_id` is null, which the column allows "for the invitation, which is not an event". A marking is the third of those: it is a decision the engine made about a row, and there is no envelope that says so. What makes it happen once is the marking itself - an entry is marked once and the phase that marks it does not see it again.

Who is told

The rule says, in the vocabulary §2 gives it: the entry's own members, the administrators of the container it sits in, the administrators of the workspace. The first is a list the entry carries; the other two are the role matrix's answer, read along the entry's path, and neither of them is a list a rule's author can widen - a recipient outside the closed set is refused where the rule is written.

func (RecordRetentionWarning) Warn

Warn writes one record per person and queues the deliveries.

It runs inside the caller's transaction - the sweep's marking write - so the warnings and the marking they are about commit together. A warning that committed without the marking would tell somebody their work is going when nothing had decided that; a marking that committed without its warnings would take the entry through its grace period in silence, which is the failure §6 is about.

One person who is both an entry member and an administrator is told once. The record is per person and per entry, not per reason they qualify.

type RecordRoleGranted

type RecordRoleGranted struct {
	Notifications repository.Notifications
	Accounts      identityrepo.Accounts
	Preferences   repository.Preferences
	Jobs          Queue
	Clock         clock.Clock
	IDs           clock.IDGenerator
	Signals       Signals
}

RecordRoleGranted tells a person that they were given a role (UC-NOT-01 check 9).

A grant is not an event - membership changes are in the audit trail, not the outbox (domain-model.md §4) - so the record is written in the grant's own transaction, as the emergency access writes its notices: the role and the record commit together or neither does, and once the grant is there nothing can lose the message. The record names the grant and nothing else; the mail reads the role, the scope and the place's name from it when it is sent, and a grant revoked before then takes its record along.

MEMBERSHIP's switch and its title preference decide, as for being added to an entry: both are "you are now part of this" to the person reading it.

func (RecordRoleGranted) RoleGranted

func (r RecordRoleGranted) RoleGranted(ctx context.Context, grant identity.Grant, actorID shared.ID) error

RoleGranted writes the person's record and queues the delivery.

A grant to a group tells nobody. Check 9 is about the person given a role; a group's members did not change, the group's reach did, and a grant to a large group would mail every member at once about a change none of them asked about - joining a group mails nobody either.

type RecordRuleDisabled

type RecordRuleDisabled struct {
	Notifications repository.Notifications
	Accounts      identityrepo.Accounts
	Preferences   repository.Preferences
	Jobs          Queue
	Clock         clock.Clock
	IDs           clock.IDGenerator
	Signals       Signals
}

RecordRuleDisabled tells the writer of an automation rule that it has switched itself off after a run of failures (automation.md §2).

The same path RecordWebhookDisabled takes, and for the same reasons: the preference is honoured, the record is deduplicated, and the send is a job like every other. A rule that stopped acting and told nobody is a rule whose owner discovers the silence weeks later, by which time the work it was supposed to do has not been done and nobody knows since when.

The recipient is the rule's **author**, not the account it runs as. A service account has nobody behind it to read a message; the person who wrote the rule is the one who can fix it.

It carries no event, exactly as the webhook's does. The trigger is the fifth consecutive failed run, and there is no envelope that says so - the column allows a null. The deduplication index therefore does not apply, which is right: a rule disables itself once, so the caller is already the thing that happens once.

func (RecordRuleDisabled) RuleDisabled

func (r RecordRuleDisabled) RuleDisabled(
	ctx context.Context, rule automation.Rule, at time.Time,
) error

RuleDisabled records the message and queues its delivery.

It runs inside the engine's transaction, so the record and the rule's new state commit together. A notification that committed without the disabling would tell somebody their rule had stopped while it carried on.

type RecordWayInChanged

type RecordWayInChanged struct {
	Jobs Queue
}

RecordWayInChanged queues the notice. It implements the identity writers' WayInNotices.

func (RecordWayInChanged) WayInChanged

func (r RecordWayInChanged) WayInChanged(
	ctx context.Context, tenantID, accountID shared.ID, change identity.WayInChange,
) error

WayInChanged queues one notice for the account, inside the caller's transaction.

type RecordWebhookDisabled

type RecordWebhookDisabled struct {
	Notifications repository.Notifications
	Accounts      identityrepo.Accounts
	Preferences   repository.Preferences
	Jobs          queue.Queue
	Clock         clock.Clock
	IDs           clock.IDGenerator
	Signals       Signals
}

RecordWebhookDisabled tells the owner of a webhook subscription that this system has stopped calling their server (automation.md §3.1).

The ordinary notification path rather than a new channel: the preference is honoured, the record is deduplicated, and the send is a job like every other. What is different is only what caused it - nobody's action and no domain event, but a run of failures the system concluded something from.

Why it carries no event

`event_id` is null, which the column allows "for the invitation, which is not an event". A disabled subscription is the second of those: the trigger is the eighth failed attempt of the third dead-lettered delivery, and there is no envelope that says so. The deduplication index therefore does not apply, which is correct here - the aggregate disables itself once, so the caller is already the thing that happens once.

func (RecordWebhookDisabled) WebhookDisabled

func (r RecordWebhookDisabled) WebhookDisabled(
	ctx context.Context, tenantID, recipientID, subscriptionID shared.ID,
) error

WebhookDisabled records the message and queues its delivery.

It runs inside the caller's transaction - the delivery adapter's short write - so the record and the subscription's new state commit together. A notification that committed without the disabling would tell somebody their integration had stopped while it carried on.

type RedemptionMinter

type RedemptionMinter interface {
	MintRedemptionToken(ctx context.Context, tenantID, accountID shared.ID) (secret.Secret, error)
}

RedemptionMinter is the identity service's seam. The empty secret means the account is no longer waiting, and the mail carries the plain link.

type ResetLink struct {
	Token       secret.Secret
	HasPassword bool
	// First is a link to set a first password, under ADR-0077 §3's fallback.
	First bool
	// Connect is a link to connect the workspace's provider, where the password is off (ADR-0078 §1).
	Connect bool
	Address string
	Locale  string
}

ResetLink is what the minter answers. Declared here rather than imported so that this package points at nothing the identity service owns - the seam is one method and one value.

type ResetMinter

type ResetMinter interface {
	MintResetToken(ctx context.Context, tenantID, accountID shared.ID) (ResetLink, error)
}

ResetMinter is the identity service's seam. It answers the plaintext token, the address, and whether this account signs in with a password at all.

type RetentionWarning

type RetentionWarning struct {
	TenantID shared.ID
	// ItemID is the entry the warning is about, and what the message links to.
	ItemID shared.ID
	// Path is the entry's chain - tenant, hub, collection - which is what the administrators are
	// resolved along.
	Path []identity.Scope
	// Recipients is the rule's audience, in the closed set of data-retention.md §2.
	Recipients []lifecycle.Recipient
}

RetentionWarning is one marked entry and what the rule said about warning people.

type RuleReader

type RuleReader interface {
	Find(ctx context.Context, id shared.ID) (automation.Rule, error)
}

RuleReader answers a rule by its identifier - the automation repository's Find.

type SendAddressConfirmation

type SendAddressConfirmation struct {
	Links    AddressMinter
	Mail     mail.Sender
	Renderer i18n.Renderer
	// Workspaces and FallbackLocale are the recipient's language chain, SendPasswordReset's.
	Workspaces     WorkspaceReader
	UnitOfWork     persistence.UnitOfWork
	FallbackLocale string
	BaseURL        string
}

SendAddressConfirmation is the job handler.

func (SendAddressConfirmation) Execute

func (s SendAddressConfirmation) Execute(ctx context.Context, tenantID, requestID shared.ID) error

Execute mints the link and sends it. A request replaced, confirmed or run out is finished business: the job completes.

type SendPasswordOpening

type SendPasswordOpening struct {
	Accounts identityrepo.Accounts
	// Workspaces answers the workspace's name for the message, and its default language - the
	// second link of §2's chain.
	Workspaces     WorkspaceReader
	Mail           mail.Sender
	Renderer       i18n.Renderer
	UnitOfWork     persistence.UnitOfWork
	FallbackLocale string
	// BaseURL is where this installation lives: the message links to the sign-in settings.
	BaseURL string
}

SendPasswordOpening is the job handler's application half: it reads who and where, and sends.

func (SendPasswordOpening) Execute

func (s SendPasswordOpening) Execute(ctx context.Context, tenantID shared.ID, notice OpeningNotice) error

Execute sends one notice. An account that is gone, or has no address, is finished business: there is nothing a retry would find.

type SendPasswordReset

type SendPasswordReset struct {
	Resets   ResetMinter
	Mail     mail.Sender
	Renderer i18n.Renderer
	// Workspaces and FallbackLocale are §2's chain, DeliverNotification's verbatim: the account's
	// own language, otherwise the workspace's default, otherwise the installation's.
	Workspaces     WorkspaceReader
	UnitOfWork     persistence.UnitOfWork
	FallbackLocale string
	// BaseURL is where this installation lives. A relative path in an email is a dead link.
	BaseURL string
}

SendPasswordReset is the job handler.

func (SendPasswordReset) Execute

func (s SendPasswordReset) Execute(ctx context.Context, tenantID, accountID shared.ID) error

Execute mints the link and sends it.

An account that is no longer there, or that has no address, is finished business rather than a failure: the job completes, because there is nothing a retry would find.

type SendWayInChanged

type SendWayInChanged struct {
	Accounts identityrepo.Accounts
	// Workspaces answers the workspace's name for the message, and its default language.
	Workspaces WorkspaceReader
	// Providers is read for a disconnection: a provider that still exists was not removed after
	// all - the removal that queued the notice failed - and nothing is sent.
	Providers      ProviderFinder
	Mail           mail.Sender
	Renderer       i18n.Renderer
	UnitOfWork     persistence.UnitOfWork
	FallbackLocale string
	// BaseURL is where this installation lives: the message links back to it.
	BaseURL string
}

SendWayInChanged is the job handler's application half: it reads who and where, and sends.

func (SendWayInChanged) Execute

func (s SendWayInChanged) Execute(ctx context.Context, tenantID shared.ID, notice WayInNotice) error

Execute sends one notice. An account that is gone, or has no address, is finished business.

type Signals

type Signals interface {
	NotificationRecorded(ctx context.Context, category, channel, state string)
	NotificationSent(ctx context.Context, category, channel string, seconds float64)
	NotificationFailed(ctx context.Context, category, channel, reason string)
}

Signals is the slice of the metrics adapter this package reports through (observability-reliability.md §3.2).

The labels are the closed sets the domain defines - the category, the channel, the state and the suppression reasons - so there is no way for an unbounded label to reach a metric from here.

type SubscriptionReader

type SubscriptionReader interface {
	Find(ctx context.Context, id shared.ID) (integrationrepo.StoredSubscription, error)
}

SubscriptionReader answers a subscription by its identifier - the integration repository's Find.

type WayInNotice

type WayInNotice struct {
	AccountID shared.ID
	Change    identity.WayInChange
}

WayInNotice is one queued notice, as the job carries it.

type WorkspaceReader

type WorkspaceReader interface {
	Find(ctx context.Context) (identity.Workspace, error)
}

WorkspaceReader is the slice of the workspace repository a delivery needs: the row, for its default language, and nothing that writes.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL