sendingpolicy

package
v1.15.2 Latest Latest
Warning

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

Go to latest
Published: Sep 30, 2026 License: Apache-2.0 Imports: 29 Imported by: 0

Documentation

Overview

Package sendingpolicy owns the authoritative sending-protection runtime policy: the typed payload, its closed enums and numeric invariants, its RFC 8785 canonical form, and the compare-and-swap activation that advances it.

Nothing here enforces anything. Task 2 establishes the authority that later slices read; every mode in the generation-zero policy is disabled, and the module is deliberately inert until a reviewed activation says otherwise.

Index

Constants

View Source
const (
	PauseClassOperator = "operator"
	PauseClassAbuse    = "abuse"
	PauseClassBilling  = "billing"
	PauseClassSystem   = "system"
)

Pause classes (account_sending_controls.pause_class, migration 122). The class records WHY an account is paused; `abuse` is the one with a consequence beyond sending: purging an account paused for abuse writes long-lived abuse tombstones and keeps its deleted-account summary for the abuse hold (docs/design/account-soft-deletion.md §4.5).

View Source
const (
	MaxAccessRequestUseCase    = 2000
	MaxAccessRequestRecipients = 1000
	MaxAccessRequestVolume     = 1000000
)

Request bounds, mirrored by the table CHECKs and the API schema.

View Source
const (
	// FeedbackOutcomeCorrelated: a recipient matched its retained HMAC and
	// its evidence was applied (to a live account, or to a non-customer
	// purpose that has no account to aggregate into).
	FeedbackOutcomeCorrelated = "correlated"
	// FeedbackOutcomeDeadAccount: correlated, but the customer account is
	// gone — provenance-only, no aggregate, no customer state.
	FeedbackOutcomeDeadAccount = "dead_account"
	// FeedbackOutcomeUnmatchedRecipient: the event correlated but this
	// recipient is not in the authorized envelope (or its key is not held).
	FeedbackOutcomeUnmatchedRecipient = "unmatched_recipient"
	// FeedbackOutcomeUncorrelatedWithMarker: e2a's attempt marker is present
	// but no retained correlation answers it. The alerting outcome.
	FeedbackOutcomeUncorrelatedWithMarker = "uncorrelated_with_marker"
	// FeedbackOutcomeUncorrelated: no marker and no provider-id match —
	// mail from before B8, from another deployment on the topic, or past
	// its retention.
	FeedbackOutcomeUncorrelated = "uncorrelated"
	// FeedbackOutcomeDuplicate: the provider event id was already recorded.
	FeedbackOutcomeDuplicate = "duplicate"
)

Feedback ingestion outcomes, the bounded `outcome` label of e2a_sending_feedback_ingested_total. One sample per matched or unmatched recipient of a first-seen event, one per uncorrelated or duplicate event.

View Source
const (
	LedgerTableOperations       = "sending_provider_operations"
	LedgerTableReservations     = "sending_budget_reservations"
	LedgerTableCounters         = "sending_budget_counters"
	LedgerTableNoticeEvents     = "sending_protection_notice_events"
	LedgerTableNoticeDeliveries = "sending_protection_notice_deliveries"
	LedgerTableControlEvents    = "account_sending_control_events"
	LedgerTableAccessEvents     = "external_sending_access_events"
)

Ledger retention table labels. They double as the metric's closed `table` label set (e2a_janitor_rows_deleted_total), so they carry no identifiers.

View Source
const (
	// LedgerRunComplete: every table was drained to its horizon.
	LedgerRunComplete = "complete"
	// LedgerRunPartial: at least one table hit its per-run batch cap; the
	// backlog continues next run. Expected on the first runs after rollout.
	LedgerRunPartial = "partial"
	// LedgerRunFailed: at least one table's batch failed (lock or statement
	// timeout, connection loss). Non-fatal: the next run retries.
	LedgerRunFailed = "failed"
)

Run outcomes for the e2a_sending_ledger_retention_runs_total metric.

View Source
const (
	EnvFeedbackHMACKeys      = "E2A_SENDING_FEEDBACK_HMAC_KEYS"
	EnvOperatorRecipientsMap = "E2A_SENDING_PROTECTION_OPERATOR_EMAILS"
)

Environment variable names, fixed by the B1a prewire: the ops assemblers render exactly these keys, and docker-compose passes them through to the server environment.

View Source
const (
	ReasonAccountPaused       = "account_paused"
	ReasonAccountDailyBudget  = "account_daily_budget_exhausted"
	ReasonAccountSharedBudget = "account_shared_daily_budget_exhausted"
	ReasonGlobalAllBudget     = "global_all_budget_exhausted"
	ReasonGlobalProbation     = "global_probation_budget_exhausted"
	ReasonGlobalCritical      = "global_critical_budget_exhausted"
	ReasonGlobalViolation     = "global_violation_budget_exhausted"
	ReasonTenantNotReady      = "ses_tenant_not_ready"
	ReasonRecipientSuperseded = "notice_recipient_superseded"
	ReasonAccountDeleted      = "account_deleted"
	// ReasonSourceUnavailable means the durable source row this operation was
	// derived from is gone, so there is nothing left to send.
	ReasonSourceUnavailable = "source_unavailable"
	// ReasonNoticeSettled means the notice delivery already reached a terminal
	// state; re-sending it would duplicate a logical notice.
	ReasonNoticeSettled = "notice_already_settled"
	// ReasonTenantUnnamed means the policy requires a tenant header but the
	// account has no tenant name to send.
	ReasonTenantUnnamed = "ses_tenant_unnamed"
	// ReasonClassChanged means the message's reputation class stopped matching
	// the immutable class its operation was derived from, so the operation no
	// longer describes the send.
	ReasonClassChanged = "reputation_class_changed"
	// ReasonSendingIdentityUnverified means the domain this message would send
	// as no longer has a verified sending identity, so the ramp has no proven
	// scope to charge and the send waits for the customer to finish verifying.
	ReasonSendingIdentityUnverified = "sending_identity_unverified"
	// ReasonRampUnavailable means the ramp ledger permanently refused this
	// message — a domain that changed hands, a reservation already settled, a
	// stored schedule that no longer validates. No retry of this operation can
	// change the answer.
	ReasonRampUnavailable = "sending_ramp_unavailable"
)

Reason codes for a hold. These are machine-readable and appear in metrics and lifecycle events, so they are part of the operator contract even though no public API surfaces them in this slice.

View Source
const (
	WebhookHealthKindWarning  = "warning"
	WebhookHealthKindDisabled = "disabled"
)

Webhook health episode kinds. They mirror the notification job's own vocabulary; the notify package asserts the two agree.

View Source
const ContractLevel = 0

ContractLevel is the compiled provider-call closure contract this binary advertises. It stays 0 until Task 12's closure guard proves every SES path requires authorization; Task 12 is the only change allowed to raise it.

View Source
const MaxAccessRequestListing = maxAccessRequestListing

MaxAccessRequestListing exposes the listing bound for callers that report truncation.

View Source
const PolicyFeatureExternalSendingAccess = "external_sending_access"

PolicyFeatureExternalSendingAccess is the capability marker for the optional `external_sending_access` runtime-policy object.

View Source
const PolicyFeatureExternalSendingUnlocks = "external_sending_unlocks"

PolicyFeatureExternalSendingUnlocks is the capability marker for the optional `external_sending_access.unlocks` key. A binary without it rejects a policy carrying the key (unknown field, fail closed), so the deploy gate must see it on every serving slot before activating such a policy.

View Source
const ReasonExternalSendingNotEnabled = "external_sending_not_enabled"

ReasonExternalSendingNotEnabled is the gate hold reason for a message the account may not send to its recipients. It is terminal: nothing a retry can do changes it, and mail must never sit queued until an approval arrives.

View Source
const ReasonRampCapacity = "sending_ramp_capacity_exhausted"

ReasonRampCapacity is the machine-readable hold reason for a domain that has used its ramp allowance for the day.

View Source
const SchemaVersion = 1

SchemaVersion is the only policy schema this binary understands. A stored policy carrying any other version is rejected before it can reach a provider authorization decision — an older binary must never silently reinterpret a newer operator's payload.

View Source
const SystemPolicySubject = "e2a-system"

SystemPolicySubject is the fixed policy subject for operational and public-feedback mail. It is a sentinel reference rather than a real account row: no customer state may authorize or block a pause notice, and there is no users row to pause. Phase 7 binds it to the e2a-system SES tenant.

Variables

View Source
var (
	// ErrStaleExternalAccessRevision means the grant moved since the operator
	// inspected it. Zero rows were written; inspect again before retrying.
	ErrStaleExternalAccessRevision = errors.New("sendingpolicy: stale external sending access revision")
	// ErrAccountNotFound means no users row has that id.
	ErrAccountNotFound = errors.New("sendingpolicy: account not found")
	// ErrAccessRequestNotFound means no request with that id exists for the
	// account (or at all).
	ErrAccessRequestNotFound = errors.New("sendingpolicy: sending access request not found")
	// ErrAccessRequestNotPending means the request was already decided.
	ErrAccessRequestNotPending = errors.New("sendingpolicy: sending access request is not pending")
	// ErrAccessRequestRateLimited means the account already submitted the
	// maximum number of requests in the rolling window.
	ErrAccessRequestRateLimited = errors.New("sendingpolicy: too many sending access requests")
	// ErrInvalidAccessRequest means a request field failed validation.
	ErrInvalidAccessRequest = errors.New("sendingpolicy: invalid sending access request")
	// ErrSendingAccessNotRestricted means the account is not currently
	// restricted (enforcement does not bind it, it is already approved, or
	// an available unlock already lifts it): there is nothing to request,
	// so no row is written and no operator is notified.
	ErrSendingAccessNotRestricted = errors.New("sendingpolicy: external sending is not restricted for this account")
)
View Source
var (
	// ErrAttemptStale means the reference names an attempt that is no longer
	// the operation's current one, or one whose state forbids the requested
	// transition. It is always zero writes to the ledger and zero provider
	// calls.
	ErrAttemptStale = errors.New("sendingpolicy: attempt is stale")
	// ErrProviderCallStarted means the attempt already opened a socket, so its
	// capacity cannot be given back. Retrying needs a new ordinal, not this one.
	ErrProviderCallStarted = errors.New("sendingpolicy: provider call already started")
	// ErrAuthorizationInvalid means a redemption failed its final recheck. The
	// attempt is invalidated and a strictly greater ordinal must be allocated
	// before any provider call.
	ErrAuthorizationInvalid = errors.New("sendingpolicy: provider authorization is no longer valid")
	// ErrEnvelopeUnavailable means the operation's authorized envelope could
	// not be resolved from durable state or the caller's reference.
	ErrEnvelopeUnavailable = errors.New("sendingpolicy: authorized envelope is unavailable")
)

Sentinel errors for the authorization surface.

View Source
var (
	// ErrSourceUnavailable means the referenced durable source row is absent,
	// deleted, or not of the shape its constructor promised. It is always
	// fail-closed: no operation is created, so no provider call can follow.
	ErrSourceUnavailable = errors.New("sendingpolicy: notification source is unavailable")
	// ErrAudienceNotAllowed means a notice event was asked for an audience its
	// kind forbids — the global guardrail has no owner to blame.
	ErrAudienceNotAllowed = errors.New("sendingpolicy: audience is not allowed for this notice")
	// ErrNoticeSettled means the notice delivery already reached a terminal
	// state, so there is nothing left to send.
	ErrNoticeSettled = errors.New("sendingpolicy: notice delivery is already settled")
)

Sentinel errors for the preparation surface.

View Source
var (
	// ErrStaleGeneration means the stored policy generation moved between the
	// operator reading it and this write. Zero rows were written.
	ErrStaleGeneration = errors.New("sendingpolicy: stale policy generation")
	// ErrStaleAttestation means the attestation revision or its four-field
	// hash did not match what the caller expected. Zero rows were written.
	ErrStaleAttestation = errors.New("sendingpolicy: stale runtime attestation")
	// ErrAttestationCommitUnchanged means Commit returned an error and the
	// mandatory reread proved the reviewed prior revision/hash are still
	// current. The same forward request is safe to retry if still intended.
	ErrAttestationCommitUnchanged = errors.New("sendingpolicy: attestation commit left state unchanged")
	// ErrAttestationCommitUnknown means Commit returned an error and the
	// mandatory reread could not prove either the exact requested next state or
	// the unchanged prior state. The caller must inspect and fence/restore.
	ErrAttestationCommitUnknown = errors.New("sendingpolicy: attestation commit outcome is unknown")
	// ErrPolicyHashMismatch means the reviewed hash does not describe the
	// policy actually being submitted.
	ErrPolicyHashMismatch = errors.New("sendingpolicy: policy hash mismatch")
	// ErrRegistryConflict means an operator-recipient version already exists
	// with a different key identity or commitment. Append-only history is
	// never rewritten, so this is always zero writes.
	ErrRegistryConflict = errors.New("sendingpolicy: operator recipient registry conflict")
	// ErrOperatorRecipientUnavailable means the policy-selected logical
	// version is absent from either this process's immutable secret map or the
	// permanent registry. Policy activation must fail before writing anything.
	ErrOperatorRecipientUnavailable = errors.New("sendingpolicy: selected operator recipient is unavailable")
	// ErrBillingContractTooLow means a budget-enforcement transition was
	// attempted without both active and rollback billing images attested at
	// sending-protection contract level 1 or higher.
	ErrBillingContractTooLow = errors.New("sendingpolicy: billing contract is too low for budget enforcement")
	// ErrInvalidBillingDigest means an attestation did not name a canonical
	// immutable sha256 image digest. Empty is reserved for contract level 0.
	ErrInvalidBillingDigest = errors.New("sendingpolicy: invalid billing image digest")
	// ErrAlreadyGrandfathered means the one-shot ramp-grandfathering marker
	// already exists. The retry is a documented no-op: it can never widen the
	// grandfathered set, so it performs zero writes.
	ErrAlreadyGrandfathered = errors.New("sendingpolicy: sending domains were already grandfathered")
)

Sentinel errors. Callers distinguish "you lost a race, re-read and decide" from "your request was malformed", because the correct operator response to each is different: one is a retry, the other is a fix.

View Source
var ErrEnvelopeMismatch = errors.New("sendingpolicy: envelope does not match the authorized recipients")

ErrEnvelopeMismatch means the envelope a caller is about to submit is not the envelope that was authorized. It is returned before any redemption or network I/O, because a mismatch here is either a bug or an attempt to reuse one authorization for different recipients.

View Source
var ErrExternalAccessDisabled = errors.New("sendingpolicy: external sending access is disabled on this deployment")

ErrExternalAccessDisabled means the deployment's policy leaves external sending access disabled (absent or mode disabled). The status and request surfaces answer "not available" rather than inventing a state.

View Source
var ErrProviderMessageIDConflict = errors.New("sendingpolicy: attempt already settled with a different provider message id")

ErrProviderMessageIDConflict means an attempt is being settled with a different provider message id than the one already bound to it. One attempt is exactly one DATA transaction and SES assigns exactly one id to it, so a second, different id is evidence of two physical sends for one charged attempt — the invariant this module exists to hold — and is never absorbed.

Functions

func AttestationHash

func AttestationHash(a RuntimeAttestation) (string, error)

AttestationHash is the lowercase SHA-256 an operator passes back as -expected-attestation-sha256. It covers only the four state fields, never the revision: the revision is compared separately and explicitly.

func CanonicalBytes

func CanonicalBytes(p RuntimePolicy) ([]byte, error)

CanonicalBytes exposes the canonical form for storage and operator readback.

func HITLNotificationOperationID added in v1.9.0

func HITLNotificationOperationID(messageID string) string

HITLNotificationOperationID is the operation id of the approval request for one held message. Deriving it from the message makes PrepareNotificationTx idempotent per hold and lets the worker bind a job's reference to its source the way the message worker does.

func Hash

func Hash(p RuntimePolicy) (string, error)

Hash returns the lowercase hex SHA-256 of the canonical form. This is the value an operator reviews and then passes back as -expected-policy-sha256, so it must be derived from exactly the bytes that get stored.

func HashBytes

func HashBytes(canonical []byte) string

HashBytes returns the lowercase hex SHA-256 of already-canonical bytes. The store uses it to re-derive a stored row's hash without re-canonicalizing, so a row whose bytes drifted from its recorded hash is detected rather than silently re-blessed.

func IsHITLNotificationOperationID added in v1.9.0

func IsHITLNotificationOperationID(id string) bool

IsHITLNotificationOperationID reports whether an id has the source-derived shape above. An id of any other shape — migration 113 stamped adopted notify jobs with `op_<md5>` — is a pre-derivation reference: its source is still the job's own, so a worker re-derives rather than refuses it.

func IsWebhookHealthOperationID added in v1.9.0

func IsWebhookHealthOperationID(id string) bool

IsWebhookHealthOperationID reports whether an id has the episode-derived shape; see IsHITLNotificationOperationID for what any other shape means.

func NormalizeOwnerMailbox added in v1.10.0

func NormalizeOwnerMailbox(email string) string

NormalizeOwnerMailbox is the one normalization applied to both the stored proof and the account's current email: trimmed and lower-cased, matching the envelope normalization. No provider-specific alias folding (dots, plus tags) — that would widen the exception beyond the mailbox actually proven.

func NormalizeProviderMessageID added in v1.9.0

func NormalizeProviderMessageID(id string) string

NormalizeProviderMessageID reduces a provider message id to the bare form the provider itself reports in delivery feedback.

The SMTP relay returns SES's id angle-bracketed and qualified with the region domain (<id@us-east-2.amazonses.com>) because that is the on-wire Message-ID replies must anchor on; SES's SNS feedback carries the same id BARE. The correlation row exists so feedback can find its attempt, and two writers — the synchronous worker and the delayed feedback finalizer — must agree on one spelling or the second one is refused as a conflict. Every write and comparison goes through this function; readers should too.

func SendingAccessDecisionOperationID added in v1.13.0

func SendingAccessDecisionOperationID(requestID string) string

SendingAccessDecisionOperationID is the operation id of the decision notice for one request. A request is decided exactly once, so keying the operation by it makes a second preparation resume the same operation instead of minting a second notice.

func SetExternalAccessObserver added in v1.10.0

func SetExternalAccessObserver(o ExternalAccessObserver)

SetExternalAccessObserver installs the process-wide decision observer.

func SetFeedbackObserver added in v1.15.0

func SetFeedbackObserver(o FeedbackObserver)

SetFeedbackObserver installs the process-wide ingestion observer.

func SetLedgerRetentionObserver added in v1.15.2

func SetLedgerRetentionObserver(o LedgerRetentionObserver)

SetLedgerRetentionObserver installs the process-wide janitor observer. Set once at startup by the composition root; nil disables it.

func ValidPauseClass added in v1.11.0

func ValidPauseClass(c string) bool

ValidPauseClass reports whether c is a known pause class.

func WebhookHealthOperationID added in v1.9.0

func WebhookHealthOperationID(webhookID, kind string, episode time.Time) string

WebhookHealthOperationID is the operation id of one webhook health episode: the kind plus the timestamp the sweep stamped when it flipped the state (warn_notified_at or auto_disabled_at). A webhook that recovers and fails again is a new episode with a new operation.

Types

type AcceptanceDecision added in v1.8.8

type AcceptanceDecision string

AcceptanceDecision is what an API acceptance surface learns from PrepareExternalTx: whether this account may still queue outbound mail at all. It deliberately carries no budget verdict — budgets are decided immediately before the provider call, not at acceptance, so that a queued message is held rather than rejected when the account runs out of daily capacity.

const (
	// AcceptanceAccept means the send may be durably queued.
	AcceptanceAccept AcceptanceDecision = "accept"
	// AcceptanceSendingPaused means the account is paused; the caller rejects
	// the request rather than queueing mail that can never leave.
	AcceptanceSendingPaused AcceptanceDecision = "sending_paused"
)
const AcceptanceExternalSendingNotEnabled AcceptanceDecision = "external_sending_not_enabled"

AcceptanceExternalSendingNotEnabled refuses acceptance of a message whose envelope the account may not reach.

type AccessRequest added in v1.10.0

type AccessRequest struct {
	ID                  string
	State               string
	UseCase             string
	Recipients          string
	ExpectedDailyVolume int
	CreatedAt           time.Time
	DecidedAt           *time.Time
	// FromExemptAccount is set by SubmitAccessRequest when the filing
	// account's server-owned account_class is exempt from the rule
	// (system/internal). Such a request needs no operator decision, so the
	// caller skips the operator notification. Not part of the customer view.
	FromExemptAccount bool
}

AccessRequest is one customer approval request.

type AccessRequestInput added in v1.10.0

type AccessRequestInput struct {
	UseCase             string
	Recipients          string
	ExpectedDailyVolume int
}

AccessRequestInput is the bounded customer-supplied part of a request. The account is never part of it: callers bind it from the authenticated principal.

type AccessRequestListing added in v1.13.0

type AccessRequestListing struct {
	ID                  string
	AccountID           string
	State               string
	CreatedAt           time.Time
	DecidedAt           *time.Time
	ExpectedDailyVolume int
	// Approved is the account's CURRENT shared-identity grant.
	Approved bool
}

AccessRequestListing is one row of the operator's request queue. Account id, state and numbers only — no customer free text and no address.

type AccountPauseChange added in v1.11.0

type AccountPauseChange struct {
	AccountID string
	Paused    bool
	// Class is required for a pause; ignored for a resume (which keeps it).
	Class string
	// EvidenceRef is an optional private reference (e.g. an incident id)
	// carried into the deleted-account summary. Never customer-visible.
	EvidenceRef string
	Actor       string
	Reason      string
}

AccountPauseChange is an operator pause or resume.

type AccountPauseRecord added in v1.11.0

type AccountPauseRecord struct {
	AccountID     string
	State         string
	PauseClass    string
	Reason        string
	EvidenceRef   string
	AccountStatus string // live | trashed
	UpdatedAt     time.Time
}

AccountPauseRecord is the operator readback of an account's pause state.

func (AccountPauseRecord) ReadOnly added in v1.11.1

func (r AccountPauseRecord) ReadOnly() bool

ReadOnly reports whether the record describes a read-only account: sending paused with pause class abuse. It mirrors identity.Store.AccountReadOnly, which the request guards consult; TestAccountPauseReadbackReportsReadOnly pins the two against each other.

type ActivationRequest

type ActivationRequest struct {
	ExpectedGeneration int64
	Policy             RuntimePolicy
	Actor              string
	Reason             string

	// GrandfatherCurrentSendingDomains performs the one-shot phase-3 snapshot
	// in the same transaction as the policy CAS: insert the singleton marker,
	// lock the domains table against concurrent sender transitions, and flip
	// every currently sending-verified, ramp-inactive domain to exempt. A
	// second attempt fails with ErrAlreadyGrandfathered and writes nothing.
	GrandfatherCurrentSendingDomains bool
}

ActivationRequest is one reviewed policy change.

type AttemptRef added in v1.8.8

type AttemptRef struct {
	// contains filtered or unexported fields
}

AttemptRef names one durable submission attempt: the module-allocated ordinal on a provider operation. River's own job.Attempt is deliberately not used — River retries and provider submissions are different clocks, and conflating them is how one logical message ends up making several unaccounted SES calls.

func (AttemptRef) Attempt added in v1.8.8

func (a AttemptRef) Attempt() int

Attempt exposes the durable submission ordinal, for logging.

func (AttemptRef) IsZero added in v1.8.8

func (a AttemptRef) IsZero() bool

IsZero reports an unset reference.

func (AttemptRef) OperationID added in v1.8.8

func (a AttemptRef) OperationID() string

OperationID exposes the operation this attempt belongs to, for logging.

type Audience added in v1.8.8

type Audience string

Audience is the closed recipient class of a protection notice. Owner mail goes to the affected customer; operator mail goes to the version of the operator mailbox map that the runtime policy currently selects.

const (
	AudienceOwner    Audience = "owner"
	AudienceOperator Audience = "operator"
)

type Bucket added in v1.15.0

type Bucket string

Bucket is the detector classification of one recipient's best evidence.

const (
	BucketNone          Bucket = "none"
	BucketDelivered     Bucket = "delivered"
	BucketTerminalOther Bucket = "terminal_other"
	BucketHardBounce    Bucket = "hard_bounce"
	BucketComplaint     Bucket = "complaint"
)

func (Bucket) Rank added in v1.15.0

func (b Bucket) Rank() int

Rank is the deterministic evidence order none < delivered < terminal_other < hard_bounce < complaint. Higher evidence replaces lower; a delayed lower-ranked callback never erases an observed hard bounce or complaint.

type Capabilities

type Capabilities struct {
	SendingProtectionContract int               `json:"sending_protection_contract"`
	RuntimePolicySource       string            `json:"runtime_policy_source"`
	OperatorCommitments       map[string]string `json:"operator_notice_recipient_commitments"`
	// PolicyFeatures lists the optional runtime-policy objects this binary
	// understands. The ops deploy gate compares it across the active and
	// candidate slots before activating a policy that carries one: an older
	// binary rejects the unknown key (fail closed), so activation must wait
	// until every serving slot lists the feature.
	PolicyFeatures []string `json:"runtime_policy_features"`
}

Capabilities is the operator-facing readback printed by -print-capabilities and compared across blue/green slots by the ops deploy gate. It carries commitments only: no addresses, no key material, no customer data.

func BuildCapabilities

func BuildCapabilities(source PolicySource, secrets Secrets) Capabilities

BuildCapabilities assembles the readback. A missing operator map yields an empty commitments object rather than an error: the self-host disabled mode legitimately has none, and the deploy gate treats absence as absence.

type Decision added in v1.8.8

type Decision struct {
	Allow    bool
	Reason   string
	RetryAt  time.Time
	Terminal bool
}

Decision is allow-or-hold. A hold carries the earliest time a retry could plausibly succeed — for a daily budget that is the next UTC midnight, which lets the worker snooze rather than spin.

Terminal separates "come back later" from "this can never proceed". Without it every hold reads as retryable, and a worker faced with an operation that is permanently void — its account deleted, its notice already sent, its reputation class no longer the one it was derived from — would snooze on it forever instead of failing the message once. A terminal hold carries no RetryAt because there is no time at which the answer changes.

type Derivation added in v1.15.0

type Derivation struct {
	Bucket Bucket
	// Repair is true when the event proves the address should be suppressed:
	// an eligible hard bounce, a genuine complaint, or a suppression-list
	// subtype (SES already refuses it; the local list must agree).
	Repair bool
	// Source is the suppressions.source the repair records.
	Source string
}

Derivation is a bucket plus whether the event should repair the local suppression list and, if so, under which source.

func DeriveBucket added in v1.15.0

func DeriveBucket(kind delivery.EventKind, bounceType, bounceSubType, complaintSubType string) Derivation

DeriveBucket maps a full event kind and its retained subtypes to the detector bucket. Every terminal bucket contributes one denominator unit; only hard bounce and complaint contribute numerators.

type ExternalAccess added in v1.10.0

type ExternalAccess interface {
	ExternalAccessPreflight(ctx context.Context, userID, agentID string, recipients []string) (ExternalAccessVerdict, error)
	ExternalAccessStatus(ctx context.Context, userID string) (ExternalAccessStatus, error)
	PriorDecidedAccessRequests(ctx context.Context, userID, excludeRequestID string) (decidedCount int, lastOutcome string, err error)
}

ExternalAccess is the narrow role the API surface uses: preflight, the status readback, and the prior-request history the operator notice surfaces. It carries no mutation.

type ExternalAccessChange added in v1.10.0

type ExternalAccessChange struct {
	AccountID        string
	Approved         bool
	ExpectedRevision int64
	Actor            string
	Reason           string
	// RequestID optionally names the pending customer request this change
	// decides. Approving marks it approved; a revocation never touches
	// request history.
	RequestID string
}

ExternalAccessChange is one audited operator change to the grant.

type ExternalAccessChangeResult added in v1.10.0

type ExternalAccessChangeResult struct {
	Record ExternalAccessRecord
	// NoOp is true when the grant already had the requested state at the
	// expected revision: nothing was written.
	NoOp bool
}

ExternalAccessChangeResult reports what a change did.

type ExternalAccessObserver added in v1.10.0

type ExternalAccessObserver func(stage, route, mode string)

ExternalAccessObserver receives one bounded (stage, route, mode) sample per evaluation in shadow or enforce mode. Set once at startup by the composition root (telemetry.Metrics.ExternalAccessDecision); nil = no-op.

type ExternalAccessRecord added in v1.10.0

type ExternalAccessRecord struct {
	AccountID          string
	Approved           bool
	Revision           int64
	ChangedAt          *time.Time
	Paused             bool
	PaidEntitled       bool
	OwnerVerified      bool
	EnforcementApplies bool
	PendingRequestID   string
	// AvailableUnlocks is the effective unlock set of the governing policy
	// (nil when the control is disabled), so the operator sees whether the
	// paid entitlement or a verified domain would lift the restriction.
	AvailableUnlocks []ExternalUnlock
}

ExternalAccessRecord is the operator readback for one account.

type ExternalAccessRoute added in v1.10.0

type ExternalAccessRoute string

ExternalAccessRoute names which step of the rule decided a verdict. It is a closed, bounded vocabulary suitable for metrics and logs.

const (
	// RouteNotApplicable: feature off, outside the cohort, or exempt class.
	RouteNotApplicable ExternalAccessRoute = "not_applicable"
	// RouteCustomIdentity: sent as the account's own verified domain.
	RouteCustomIdentity ExternalAccessRoute = "custom_identity"
	// RouteOperatorApproval: the operator grant.
	RouteOperatorApproval ExternalAccessRoute = "operator_approval"
	// RoutePaidEntitlement: the billing-issued paid-base entitlement.
	RoutePaidEntitlement ExternalAccessRoute = "paid_entitlement"
	// RouteRestrictedRecipients: every recipient is the verified owner
	// mailbox or a live agent of the same account.
	RouteRestrictedRecipients ExternalAccessRoute = "restricted_recipients"
	// RouteDenied: at least one recipient is outside the allowed set.
	RouteDenied ExternalAccessRoute = "denied"
)

type ExternalAccessStatus added in v1.10.0

type ExternalAccessStatus struct {
	// EnforcementApplies is true exactly when the mode is enforce, the
	// account is in the cutoff cohort, and it is not a system/internal
	// class. It stays true for approved accounts.
	EnforcementApplies bool
	// SharedExternalApproved reports the operator grant.
	SharedExternalApproved bool
	// PaidExternalSendingEntitled reports the billing-issued entitlement.
	PaidExternalSendingEntitled bool
	// OwnerRecipientVerified reports valid proof for the current mailbox.
	OwnerRecipientVerified bool
	// AvailableUnlocks is the deployment's effective unlock set, in
	// canonical order: which routes can lift the restriction. Never empty.
	AvailableUnlocks []ExternalUnlock
}

ExternalAccessStatus is the account-eligibility readback behind the additive `sending_access` object on GET /v1/account. Booleans only; it describes eligibility, not a promise that a given send will pass pause, quota, content or domain checks.

type ExternalAccessVerdict added in v1.10.0

type ExternalAccessVerdict struct {
	// Mode is the policy mode the verdict was computed under.
	Mode Mode
	// Route is the rule step that decided.
	Route ExternalAccessRoute
	// Allowed is the effective answer. In shadow mode a denial is computed
	// (Route == RouteDenied) but Allowed stays true.
	Allowed bool
	// Paused is set by the preflight when the account's sending is paused.
	// Pause wins over every access answer: the caller must report the
	// pause, never a restriction a payment or approval could appear to fix.
	Paused bool
}

ExternalAccessVerdict is the outcome of one evaluation.

func (ExternalAccessVerdict) Denied added in v1.10.0

func (v ExternalAccessVerdict) Denied() bool

Denied reports an enforced refusal.

type ExternalSendingAccessPolicy added in v1.10.0

type ExternalSendingAccessPolicy struct {
	Mode                     Mode   `json:"mode"`
	AccountsCreatedAtOrAfter string `json:"accounts_created_at_or_after"`
	// Unlocks is the set of routes that may lift the restriction, drawn from
	// the closed ExternalUnlock vocabulary. It is optional with omitempty on
	// purpose: absent means every unlock (the behavior before the key
	// existed), so a policy that never mentions it keeps its reviewed hash.
	// Present, it must be non-empty and must contain operator_approval — an
	// empty set would make the restriction unreachable by any route, and the
	// request/approve path must always be able to lift it.
	Unlocks []ExternalUnlock `json:"unlocks,omitempty"`
}

ExternalSendingAccessPolicy restricts which recipients an account inside the rollout cohort may reach through a shared sending identity.

The cutoff is an immutable instant written by an operator, never computed at startup or first send: a rollback, a restart, or a second slot must all agree on exactly which accounts are in the cohort, and "now" is different on each of them.

func (ExternalSendingAccessPolicy) Allows added in v1.13.0

Allows reports whether the policy lets the given route lift the restriction. An absent set allows every route (the legacy behavior).

func (ExternalSendingAccessPolicy) AvailableUnlocks added in v1.13.0

func (p ExternalSendingAccessPolicy) AvailableUnlocks() []ExternalUnlock

AvailableUnlocks returns the effective unlock set in canonical order: the configured set, or the whole vocabulary when the key is absent. Always a fresh slice.

func (ExternalSendingAccessPolicy) Cutoff added in v1.10.0

Cutoff parses the validated cohort cutoff.

type ExternalUnlock added in v1.13.0

type ExternalUnlock string

ExternalUnlock names one route that can lift the external-sending restriction for an account inside the cohort. Closed vocabulary.

const (
	// UnlockOperatorApproval is the audited operator grant
	// (account_sending_controls.external_sending_approved). Always present.
	UnlockOperatorApproval ExternalUnlock = "operator_approval"
	// UnlockVerifiedDomain lets a message sent as the account's own
	// currently verified custom domain reach external recipients.
	UnlockVerifiedDomain ExternalUnlock = "verified_domain"
	// UnlockPaidEntitlement lets the billing-issued paid-base entitlement
	// (account_limits.external_sending_entitled) lift the restriction.
	UnlockPaidEntitlement ExternalUnlock = "paid_entitlement"
)

type FeedbackGCStats added in v1.15.0

type FeedbackGCStats struct {
	Events       int64
	Recipients   int64
	Correlations int64
	Outcomes     int64
}

FeedbackGCStats reports what one retention pass removed.

type FeedbackMaintenanceArgs added in v1.15.0

type FeedbackMaintenanceArgs struct{}

FeedbackMaintenanceArgs is the periodic retention job.

func (FeedbackMaintenanceArgs) Kind added in v1.15.0

type FeedbackMaintenanceWorker added in v1.15.0

type FeedbackMaintenanceWorker struct {
	river.WorkerDefaults[FeedbackMaintenanceArgs]
	// contains filtered or unexported fields
}

FeedbackMaintenanceWorker runs one retention pass over feedback provenance and daily outcome aggregates.

func NewFeedbackMaintenanceWorker added in v1.15.0

func NewFeedbackMaintenanceWorker(module *Module) *FeedbackMaintenanceWorker

NewFeedbackMaintenanceWorker builds the retention worker over a module. Exported so the janitor — the one component here that DELETES evidence — can be driven directly by a test rather than only through River.

func (*FeedbackMaintenanceWorker) Work added in v1.15.0

type FeedbackObserver added in v1.15.0

type FeedbackObserver func(outcome, bucket string)

FeedbackObserver receives one bounded (outcome, bucket) sample per ingestion result. Set once at startup by the composition root (telemetry.Metrics.SendingFeedbackIngested); nil = no-op. Never carries an address, account, correlation, or provider id.

type FeedbackReconcileArgs added in v1.15.0

type FeedbackReconcileArgs struct{}

FeedbackReconcileArgs is the periodic retention backstop.

func (FeedbackReconcileArgs) Kind added in v1.15.0

type FeedbackReconcileStats added in v1.15.0

type FeedbackReconcileStats struct {
	// StampedCorrelations are customer correlations whose account no longer
	// exists but which had no expiry yet.
	StampedCorrelations int64
	// StampedEvents are events given their correlation's expiry.
	StampedEvents int64
	// OrphanEvents are events whose correlation was already gone.
	OrphanEvents int64
}

FeedbackReconcileStats reports what one retention reconciliation changed.

type FeedbackReconcileWorker added in v1.15.0

type FeedbackReconcileWorker struct {
	river.WorkerDefaults[FeedbackReconcileArgs]
	// contains filtered or unexported fields
}

FeedbackReconcileWorker stamps the post-deletion horizon on provenance of accounts that are gone but were never stamped, and sweeps orphaned events. See Module.ReconcileFeedbackRetention.

func NewFeedbackReconcileWorker added in v1.15.0

func NewFeedbackReconcileWorker(module *Module) *FeedbackReconcileWorker

NewFeedbackReconcileWorker builds the reconcile worker over a module.

func (*FeedbackReconcileWorker) Work added in v1.15.0

type Gate added in v1.8.8

type Gate interface {
	PrepareExternalTx(ctx context.Context, tx pgx.Tx, messageID string) (AcceptanceDecision, OperationRef, error)
	PrepareNotificationTx(context.Context, pgx.Tx, NotificationRef) (OperationRef, error)
	PrepareProtectionNoticeTx(context.Context, pgx.Tx, ProtectionNoticeRef) (OperationRef, error)
	PreparePublicFeedback(context.Context, PublicFeedbackRef) (OperationRef, error)
	Reserve(context.Context, OperationRef) (Decision, AttemptRef, error)
	ConsumeAttempt(context.Context, AttemptRef) (Decision, *ProviderAuthorization, error)
	RedeemProviderCall(context.Context, ProviderAuthorization) error
	DeferAttempt(context.Context, AttemptRef) error
	CancelAttempt(context.Context, AttemptRef) error
	SettleProvider(context.Context, ProviderSettlement) error
	SettleOperation(context.Context, OperationRef, SettlementOutcome, string) error
	LookupOperation(context.Context, string) (OperationRef, error)
}

Gate is the provider-authorization surface: the only way anything in this codebase is permitted to hand a message to SES.

The shape is deliberate. A caller cannot ask "am I allowed?" and then act on the answer later, because an allow is not a boolean — it is a single-use ProviderAuthorization bound to one durable attempt, which the SMTP adapter must redeem immediately before it opens a socket. That removes the entire class of bug where a decision goes stale between the check and the call: a pause, a plan downgrade, a policy change, a midnight rollover, or a competing worker all invalidate the token rather than being raced.

func NewGate added in v1.8.8

func NewGate(pool *pgxpool.Pool, secrets Secrets, source PolicySource, configPolicy RuntimePolicy) Gate

NewGate binds the module to the deployment's policy authority and returns it as the narrow provider-authorization role.

The source is a constructor argument rather than a runtime lookup because it decides where authority lives, and that must not be able to change under a decision in flight. A hosted deployment reads the audited database singleton; a self-host reads the config file it already validated at startup.

type GrandfatherResult

type GrandfatherResult struct {
	DomainsExempted int64
}

GrandfatherResult reports what the one-shot snapshot did.

type Keyring

type Keyring struct {
	// contains filtered or unexported fields
}

Keyring is the immutable HMAC keyring loaded from E2A_SENDING_FEEDBACK_HMAC_KEYS. One version signs new material; every version stays available to verify feedback that was signed before a rotation.

func LoadKeyring

func LoadKeyring(raw string) (*Keyring, error)

LoadKeyring parses and validates the keyring secret. Every failure is fatal at startup: a deployment that cannot sign feedback correlations must not come up believing it can.

func (*Keyring) ActiveVersion

func (k *Keyring) ActiveVersion() int

ActiveVersion reports which key new material is signed under.

func (*Keyring) Sign

func (k *Keyring) Sign(msg []byte) (version int, mac []byte)

Sign returns the HMAC-SHA256 of msg under the active key, with the version it used so a verifier can select the same key after a rotation.

func (*Keyring) Verify

func (k *Keyring) Verify(version int, msg, mac []byte) bool

Verify checks msg against mac under a specific version. An unknown version is a failure, never a fallback to the active key: accepting material signed by a key this process does not hold would defeat the correlation entirely.

func (*Keyring) Versions

func (k *Keyring) Versions() []int

Versions lists every loaded version in ascending order. Used by the deploy gate to prove one slot's keyring is a superset of the other's.

type LedgerRetentionArgs added in v1.15.2

type LedgerRetentionArgs struct{}

LedgerRetentionArgs is the periodic ledger retention job.

func (LedgerRetentionArgs) Kind added in v1.15.2

func (LedgerRetentionArgs) Kind() string

type LedgerRetentionObserver added in v1.15.2

type LedgerRetentionObserver interface {
	JanitorRowsDeleted(table string, count int)
	SendingLedgerRetentionRun(outcome string)
}

LedgerRetentionObserver receives the janitor's bounded samples. The process telemetry backend (telemetry.Metrics) satisfies it. Never carries an id.

type LedgerRetentionStats added in v1.15.2

type LedgerRetentionStats struct {
	Deleted map[string]int64
	// Capped names the tables that hit the per-run batch cap.
	Capped []string
	// Failed maps a table label to the error that stopped it this run.
	Failed map[string]error
	// RetainedOperations counts expired operations kept because an attempt,
	// a live job, an in-flight message, or a pending notice still needs them
	// (capped at retainedCountCap). Diagnostic only.
	RetainedOperations int64
}

LedgerRetentionStats reports one run. Deleted is keyed by table label.

func (LedgerRetentionStats) Err added in v1.15.2

func (s LedgerRetentionStats) Err() error

Err joins the per-table failures, nil when there were none.

func (LedgerRetentionStats) Outcome added in v1.15.2

func (s LedgerRetentionStats) Outcome() string

Outcome classifies the run for the run-outcome metric.

type LedgerRetentionWorker added in v1.15.2

type LedgerRetentionWorker struct {
	river.WorkerDefaults[LedgerRetentionArgs]
	// contains filtered or unexported fields
}

LedgerRetentionWorker runs one GCLedger pass.

func NewLedgerRetentionWorker added in v1.15.2

func NewLedgerRetentionWorker(module *Module) *LedgerRetentionWorker

NewLedgerRetentionWorker builds the ledger janitor over a module. Exported so a test can drive the component that deletes ledger rows directly.

func (*LedgerRetentionWorker) Timeout added in v1.15.2

Timeout matches the cleanup janitor's budget: every batch autocommits, so a cut is safe and the next run resumes.

func (*LedgerRetentionWorker) Work added in v1.15.2

Work never returns a table failure to River: a failure is logged and counted, and the next hourly run retries. Returning it would only make River re-run the whole pass on its retry schedule against the same lock.

type MaintenanceJobs added in v1.15.0

type MaintenanceJobs struct {
	// contains filtered or unexported fields
}

MaintenanceJobs registers the sending-protection retention periodics: the feedback provenance pass, its reconcile backstop, and the ledger janitor. Implements jobs.Registrar.

func NewMaintenanceJobs added in v1.15.0

func NewMaintenanceJobs(module *Module) *MaintenanceJobs

NewMaintenanceJobs builds the registrar over the gate's module.

func (*MaintenanceJobs) RegisterJobs added in v1.15.0

func (m *MaintenanceJobs) RegisterJobs(w *river.Workers) []*river.PeriodicJob

type Mode

type Mode string

Mode is a three-state rollout control. The three states are independent per control: a policy may run the budget in shadow while the detector is still disabled, and vice versa.

const (
	// ModeDisabled computes nothing and blocks nothing.
	ModeDisabled Mode = "disabled"
	// ModeShadow computes the decision and records it, but never denies.
	ModeShadow Mode = "shadow"
	// ModeEnforce computes the decision and acts on it.
	ModeEnforce Mode = "enforce"
)

type Module

type Module struct {
	// contains filtered or unexported fields
}

Module is the single concrete owner of sending-protection policy state. Later slices expose narrow role interfaces (Gate, FeedbackProcessor, Admin) backed by this same object; the Postgres store stays private because there is only ever one adapter.

func NewModule

func NewModule(pool *pgxpool.Pool, secrets Secrets) *Module

NewModule binds the module to a pool and the immutable trust roots parsed at startup. It performs no I/O: a server that never reads the policy (self-host on config source) must not pay for a query.

func NewPolicyModule added in v1.10.0

func NewPolicyModule(pool *pgxpool.Pool, secrets Secrets, source PolicySource, configPolicy RuntimePolicy) *Module

NewPolicyModule binds a module to a pool, the trust roots and the deployment's policy authority. NewGate returns the same object narrowed to the provider-authorization role; operator commands and the API use the other narrow roles (ExternalAccess, ExternalAccessAdmin) of this one owner.

func (*Module) ActivatePolicy

func (m *Module) ActivatePolicy(ctx context.Context, req ActivationRequest) (PolicySnapshot, error)

ActivatePolicy advances the policy by exactly one generation, under a compare-and-swap on the generation the operator reviewed.

Everything happens in one transaction holding the singleton's row lock, so a concurrent activation either waits and then loses on generation, or wins and makes this one lose. There is no path that writes the policy without also writing its audit event.

func (*Module) AttestRuntime

AttestRuntime compare-and-swaps the billing attestation on both the expected revision and the canonical hash of the four fields it replaces.

Requiring both is what defeats an ABA retry: a delayed writer whose four expected fields happen to match a later state is still rejected, because the revision advanced past it. A higher revision is always stale, never a candidate for replacement.

func (*Module) CancelAttempt added in v1.8.8

func (m *Module) CancelAttempt(ctx context.Context, ref AttemptRef) error

CancelAttempt gives back both ledgers for a terminal local cancellation such as a suppression match — the ramp half only while nothing has been authorized to send this message. See cancelRamp.

func (*Module) ConsumeAttempt added in v1.8.8

func (m *Module) ConsumeAttempt(ctx context.Context, ref AttemptRef) (Decision, *ProviderAuthorization, error)

ConsumeAttempt is the final pre-I/O authorization: the one place where the account's live state, the current policy generation, today's UTC date, and every applicable pool are checked together under lock.

It is a full re-evaluation, not a confirmation of what Reserve decided. Everything Reserve saw may have changed: the policy can have been activated, the plan downgraded, the account paused, the day rolled over, a control armed or disarmed. Re-deriving from scratch — including releasing units Reserve took on scopes that no longer apply — is what makes a policy change between the two calls impossible to slip past.

func (*Module) DeclineExternalAccessRequest added in v1.10.0

func (m *Module) DeclineExternalAccessRequest(ctx context.Context, accountID, requestID, actor string) error

DeclineExternalAccessRequest records a support decision to decline a pending request without touching the grant.

func (*Module) DeferAttempt added in v1.8.8

func (m *Module) DeferAttempt(ctx context.Context, ref AttemptRef) error

DeferAttempt gives back only the sending budget for a rate deferral.

The ramp reservation is deliberately retained: a message deferred by the per-agent rate limiter has not been rejected by the provider and has not used a ramp day, so releasing its ramp claim would let the same message re-qualify a stage it already qualified.

func (*Module) EffectiveDetectorWindowDays added in v1.15.0

func (m *Module) EffectiveDetectorWindowDays(ctx context.Context) (int, error)

EffectiveDetectorWindowDays reads the detector window from the effective policy.

func (*Module) EffectiveFeedbackRetention added in v1.15.0

func (m *Module) EffectiveFeedbackRetention(ctx context.Context) (time.Duration, error)

EffectiveFeedbackRetention is the post-account-deletion horizon for retained feedback provenance, from the effective policy — the same accessor the retention janitor reads, so the purge seal, the janitor's backstop stamp, and non-customer correlations all agree on a database- source deployment whose activated policy differs from the config file.

func (*Module) ExcludesFeedbackDomain added in v1.15.0

func (m *Module) ExcludesFeedbackDomain(domain string) bool

ExcludesFeedbackDomain reports whether deliveries to domain are excluded from the detector denominator by configuration (the simulator or a configured shared agent domain). The composition-root test reads it.

func (*Module) ExternalAccessPreflight added in v1.10.0

func (m *Module) ExternalAccessPreflight(ctx context.Context, userID, agentID string, recipients []string) (ExternalAccessVerdict, error)

ExternalAccessPreflight evaluates a send before anything is persisted: the composed sender (agent) and the full To/Cc/Bcc envelope. It is guidance for a fast, clean 403 — acceptance and final authorization repeat the check against durable state, so a race after this call cannot bypass it.

func (*Module) ExternalAccessStatus added in v1.10.0

func (m *Module) ExternalAccessStatus(ctx context.Context, userID string) (ExternalAccessStatus, error)

ExternalAccessStatus reads the account's eligibility.

func (*Module) GCFeedback added in v1.15.0

func (m *Module) GCFeedback(ctx context.Context, now time.Time, windowDays int) (FeedbackGCStats, error)

GCFeedback removes feedback provenance past its horizon and daily outcome rows outside the detector window plus one UTC day of safety. Customer correlations carry no expiry while the account exists; the account purge's seal transaction (identity.Store.purgeAccount) stamps one when the account becomes irrecoverable, so this pass is what makes the post-purge retention real. A trashed-but-restorable account is not stamped.

Events and recipients are removed BY THEIR CORRELATION, in the statement that removes it, rather than by their own expiry column: an event whose expiry was never stamped (a pre-fix race with the purge seal left some with NULL) must still go when its correlation does. Events are also removed by their own stamped expiry, which is always the correlation's.

func (*Module) GCLedger added in v1.15.2

func (m *Module) GCLedger(ctx context.Context, now time.Time) (LedgerRetentionStats, error)

GCLedger runs one retention pass over the sending ledger. It never returns early on a table's failure: each table is swept independently and failures are reported in the stats, so one lock timeout cannot stall the others. The returned error is non-nil only for a cancelled context.

func (*Module) InspectAccountPause added in v1.11.0

func (m *Module) InspectAccountPause(ctx context.Context, accountID string) (AccountPauseRecord, error)

InspectAccountPause reads an account's pause state (any trash state).

func (*Module) InspectAttestation

func (m *Module) InspectAttestation(ctx context.Context) (RuntimeAttestation, error)

InspectAttestation reads the current runtime attestation.

func (*Module) InspectExternalAccess added in v1.10.0

func (m *Module) InspectExternalAccess(ctx context.Context, accountID string) (ExternalAccessRecord, error)

InspectExternalAccess reads the grant, its revision and every other fact an operator needs to understand the effect of a change — notably the paid entitlement, which a revocation does NOT remove.

func (*Module) InspectPolicy

func (m *Module) InspectPolicy(ctx context.Context) (PolicySnapshot, error)

InspectPolicy reads the current policy without locking. It is the readback half of every operator command and the dry-run of an activation.

func (*Module) LatestAccessRequest added in v1.10.0

func (m *Module) LatestAccessRequest(ctx context.Context, userID string) (*AccessRequest, error)

LatestAccessRequest returns the account's most recent request, or nil.

func (*Module) ListAccessRequests added in v1.13.0

func (m *Module) ListAccessRequests(ctx context.Context, all bool) (reqs []AccessRequestListing, truncated bool, err error)

ListAccessRequests returns the operator's queue, each row with the account's current grant: pending requests oldest first (review order), or with all every request NEWEST first, so the bound drops the oldest history rather than the latest activity. truncated reports that more rows exist than the bound returned. The new-request email is a notification, not the system of record.

func (*Module) LookupOperation added in v1.9.0

func (m *Module) LookupOperation(ctx context.Context, operationID string) (OperationRef, error)

LookupOperation recovers a reference to an operation that already exists.

This is not a constructor: it returns a reference only for a durable operation row, and the reference carries an id and advisory fields exactly as a deserialized River argument does — every Gate method reloads the row under lock, so recovering a reference grants nothing. It exists for the reconciler, which learns of provider evidence by message id long after the worker and its token are gone.

func (*Module) PrepareExternalTx added in v1.8.8

func (m *Module) PrepareExternalTx(ctx context.Context, tx pgx.Tx, messageID string) (AcceptanceDecision, OperationRef, error)

PrepareExternalTx derives the provider operation for an accepted outbound customer message, inside the same transaction that durably inserts it.

It returns an acceptance verdict rather than a budget verdict on purpose. Budgets are decided immediately before the provider call, so a customer who has used today's allowance still gets their message queued and sent after midnight; only a paused account is refused at the door, because queueing mail that can never leave is worse than saying no.

func (*Module) PrepareNotificationTx added in v1.8.8

func (m *Module) PrepareNotificationTx(ctx context.Context, tx pgx.Tx, ref NotificationRef) (OperationRef, error)

PrepareNotificationTx derives the provider operation for platform mail a customer's own action triggered.

These are attributed to and budgeted against the triggering customer, not the platform, because a customer controls how much of this mail exists: every held message is an approval email, every failing webhook is a health warning, and every filed access request (at most three per 30 days) can earn one decision notice. Their From identity is platform-owned, so they also carry the shared reputation class and the stricter shared-domain cap that comes with it.

func (*Module) PrepareProtectionNoticeTx added in v1.8.8

func (m *Module) PrepareProtectionNoticeTx(ctx context.Context, tx pgx.Tx, ref ProtectionNoticeRef) (OperationRef, error)

PrepareProtectionNoticeTx allocates or resumes the one stable operation for a committed notice event and audience.

Stability is the point. A pause notice may be retried for days; every physical retry must be a greater submission ordinal on the SAME operation, so that the ledger can prove at most one socket per ordinal and so a retry can never mint a second logical notice. Deliberately no recipient is resolved here: the owner address is whatever it is at final authorization, and binding it now would mail a retired address after a legitimate account edit.

func (*Module) PreparePublicFeedback added in v1.8.8

func (m *Module) PreparePublicFeedback(ctx context.Context, ref PublicFeedbackRef) (OperationRef, error)

PreparePublicFeedback derives the operation for one /api/feedback fan-out.

The unauthenticated endpoint has no account to charge, but its mail leaves through the same provider and damages the same reputation, so it consumes the platform and probation pools. Neither the recipient set nor the purpose comes from the request: the submission ID is server-minted and the envelope is configuration, which is what stops the form from becoming an open relay.

func (*Module) PriorDecidedAccessRequests added in v1.14.0

func (m *Module) PriorDecidedAccessRequests(ctx context.Context, userID, excludeRequestID string) (decidedCount int, lastOutcome string, err error)

PriorDecidedAccessRequests reports how many of the account's OTHER requests have already been decided, and the outcome ("approved" or "declined") of the most recently decided one. excludeRequestID omits the request currently being filed/notified (it is not yet decided, but is excluded defensively in case it somehow already has a terminal state by the time this is read). decidedCount is 0 and lastOutcome is "" when the account has no decided history. It is a plain read with no enablement gate: a caller composing an operator notice already knows the control is enabled (the request row it is about could not exist otherwise).

func (*Module) ProcessProviderFeedback added in v1.15.0

func (m *Module) ProcessProviderFeedback(ctx context.Context, fb delivery.ProviderFeedback) (delivery.FeedbackResult, error)

ProcessProviderFeedback implements delivery.FeedbackProcessor: it moves detector evidence for one signed notification and reports the account suppressions that notification proves. It deliberately writes NO suppression itself — the live message path owns that row when a message survives, and the consumer applies the repair through RepairSuppressionsTx only when none does.

func (*Module) ReconcileFeedbackRetention added in v1.15.0

func (m *Module) ReconcileFeedbackRetention(ctx context.Context, now time.Time, retention time.Duration) (FeedbackReconcileStats, error)

ReconcileFeedbackRetention closes the gaps the purge seal cannot: it stamps expires_at = now + retention on every customer correlation whose source account no longer has a users row (erased before the seal stamped anything, or a correlation authorized in a race with the purge), gives every unstamped event its correlation's expiry, and deletes events whose correlation is already gone. Idempotent: only NULL expiries are written. Migration 124 runs the same predicate once for the backlog; this pass is the standing backstop.

A trashed account still has its users row, so it is never stamped here — the same rule the seal follows.

func (*Module) RedeemProviderCall added in v1.8.8

func (m *Module) RedeemProviderCall(ctx context.Context, auth ProviderAuthorization) error

RedeemProviderCall consumes the single-use authorization immediately before the socket opens.

It exists because ConsumeAttempt's transaction has committed by the time the adapter runs, and everything it checked can have changed in the meantime. Re-proving the whole chain here — policy, owner, delivery, operation, ordinal, nonce, recipient selector — costs one short transaction and closes the window in which an owner edit, a policy rotation, a supersession, or a mixed-slot secret rotation could mail a retired address.

func (*Module) RegisterOperatorRecipients

func (m *Module) RegisterOperatorRecipients(ctx context.Context, actor, reason string) (inserted []int, err error)

RegisterOperatorRecipients inserts registry rows for versions that are not yet recorded, and refuses any disagreement with history.

The table's triggers already make UPDATE and DELETE impossible, so the only way to be wrong here is to insert a row that contradicts an existing one. That is checked explicitly and fails the whole transaction, because a partial registration would leave the operator unable to tell which versions are trustworthy.

func (*Module) RepairSuppressions added in v1.15.0

func (m *Module) RepairSuppressions(ctx context.Context, accountRef string, repairs []delivery.FeedbackRepair) ([]delivery.FeedbackRepair, error)

RepairSuppressions is RepairSuppressionsTx in a transaction of its own, for operator tooling and tests that have no enclosing transaction.

func (*Module) RepairSuppressionsTx added in v1.15.0

func (m *Module) RepairSuppressionsTx(ctx context.Context, tx pgx.Tx, accountRef string, repairs []delivery.FeedbackRepair) ([]delivery.FeedbackRepair, error)

RepairSuppressionsTx writes the account-wide suppressions a signed event proved, for the case where no live message row can own them, inside the caller's transaction. Upserts go through suppressionsync, so a row re-proven here advances its generation and clears a pending removal. It returns only the repairs that inserted a new row: an address already suppressed — manually, by an earlier bounce — is refreshed, never reported, so the consumer announces nothing for it.

func (*Module) Reserve added in v1.8.8

func (m *Module) Reserve(ctx context.Context, ref OperationRef) (Decision, AttemptRef, error)

Reserve allocates this operation's current durable submission attempt and, as an optimization, tries to take its capacity early.

It is explicitly not authority to submit. Its value is that a worker learns about an exhausted budget before it does the expensive work of composing and signing a message, and that the durable ordinal is allocated exactly once per provider opportunity. That ordinal allocation is the load-bearing half: after a crash, timeout, ambiguous SMTP result, or ordinary River retry, the next execution sees a confirmed row and allocates N+1 rather than reusing an ordinal that may already have reached the network.

func (*Module) SetAccountPause added in v1.11.0

func (m *Module) SetAccountPause(ctx context.Context, req AccountPauseChange) (AccountPauseRecord, error)

SetAccountPause pauses or resumes an account's sending, recording the class, the reason and an audit event. An abuse class is never downgraded by a later pause, and a resume keeps the class and evidence reference (it appends a resume event instead of erasing the history). It deliberately works on accounts in ANY trash state: pausing a trashed account for abuse is the lever that makes its eventual purge write abuse tombstones ("paused, then they deleted"). A pause never touches external-sending approval, and a resume starts a new detector epoch.

Lock order follows the gate's: users (FOR SHARE), then the control row.

func (*Module) SetExternalAccess added in v1.10.0

func (m *Module) SetExternalAccess(ctx context.Context, req ExternalAccessChange) (ExternalAccessChangeResult, error)

SetExternalAccess approves or revokes the grant under a compare-and-swap on the revision the operator inspected.

Lock order follows the gate's normative order — users, then the account control row — so a change serializes against an in-flight ConsumeAttempt for the same account instead of deadlocking with it. The users row is taken FOR SHARE (the gate's own mode) and the control row FOR UPDATE through the same upsert ensureAccountControl uses. Approval never clears a pause.

func (*Module) SettleOperation added in v1.9.0

func (m *Module) SettleOperation(ctx context.Context, ref OperationRef, outcome SettlementOutcome, providerMessageID string) error

SettleOperation applies a delayed authoritative provider outcome to the attempt of an operation that most recently opened a socket.

It exists for the two callers that hold evidence but no token: the worker that finds provider-accept evidence already recorded on a row it is about to re-drive, and the terminal reconciler settling a stranded row from that same evidence. Neither can name an ordinal — the token that could is gone with the process that held it — but both know which OPERATION the evidence belongs to, and the only attempt evidence can describe is the latest one that dialed.

func (*Module) SettleProvider added in v1.8.8

func (m *Module) SettleProvider(ctx context.Context, settlement ProviderSettlement) error

SettleProvider records an authoritative provider outcome.

It changes only the custom-domain ramp ledger, never the sending budget: the budget was spent when the attempt was authorized, and SES rejecting a message does not give back the reputation exposure of having asked. Idempotent by construction, because both the synchronous success branch and the delayed delivery-feedback finalizer call it for the same attempt.

func (*Module) SubmitAccessRequest added in v1.10.0

func (m *Module) SubmitAccessRequest(ctx context.Context, userID string, in AccessRequestInput) (AccessRequest, bool, error)

SubmitAccessRequest files a request for the account, or returns the account's existing pending request unchanged (created=false). Submission is therefore idempotent while a request is pending; after a decline a new request (an appeal) may be filed within the rolling-window cap.

func (*Module) VerifyKeyringCoverage added in v1.15.0

func (m *Module) VerifyKeyringCoverage(ctx context.Context) error

VerifyKeyringCoverage fails closed when a retained, unexpired recipient row was signed under a key version this process does not hold. Feedback for those rows could never be matched, which would leave the detector silently blind; refusing to start is the alert.

A deployment with NO keyring configured is not checked: it signs nothing and matches nothing by design (self-host with every control disabled), and bricking such a server over rows an earlier configuration wrote would turn a disabled feature into an outage. Removing a key version while it is still in use is the case this guards.

func (*Module) WithClock added in v1.15.0

func (m *Module) WithClock(fn func() time.Time) *Module

WithClock pins the module's ingestion clock (feedback day assignment and retention); nil restores time.Now.

func (*Module) WithFeedbackExcludedDomains added in v1.15.0

func (m *Module) WithFeedbackExcludedDomains(domains ...string) *Module

WithFeedbackExcludedDomains configures the deployment's shared agent domains (config shared_domain): a delivery to an agent this deployment hosts is free for any sender to manufacture and must not dilute the detector's denominator. The SES mailbox simulator is always excluded. Domains are canonicalized (IDNA ASCII, lower case).

type NotificationRef added in v1.8.8

type NotificationRef struct {
	// contains filtered or unexported fields
}

NotificationRef names one supported notification source row. It has no exported constructor taking a purpose: the two constructors below are the only way to make one, which is what stops a caller from labelling its own mail as operational to escape a budget.

func NewHITLNotificationRef added in v1.8.8

func NewHITLNotificationRef(messageID string) NotificationRef

NewHITLNotificationRef references a pending outbound message whose approval request is being sent. PrepareNotificationTx locks the owning agent and then the message, and requires the message to still be outbound and still awaiting review before deriving anything from it.

func NewSendingAccessDecisionNotificationRef added in v1.13.0

func NewSendingAccessDecisionNotificationRef(requestID string) NotificationRef

NewSendingAccessDecisionNotificationRef references a DECIDED external sending access request whose outcome is being reported to the account owner. PrepareNotificationTx reads the request row and refuses a request that is still pending (ErrSourceUnavailable): there is no decision to report, so nothing to authorize.

func NewWebhookHealthNotificationRef added in v1.8.8

func NewWebhookHealthNotificationRef(webhookID, kind string) NotificationRef

NewWebhookHealthNotificationRef references a webhook whose health episode of the given kind (WebhookHealthKindWarning / WebhookHealthKindDisabled) is being reported to its owner. PrepareNotificationTx reads the episode's timestamp from the locked webhook row; an unknown kind or an episode the sweep never stamped is ErrSourceUnavailable.

func (NotificationRef) Kind added in v1.9.0

func (r NotificationRef) Kind() string

Kind exposes the webhook health episode kind; empty for other sources.

func (NotificationRef) Source added in v1.9.0

Source exposes the notification source, for tests and logging.

func (NotificationRef) SourceID added in v1.9.0

func (r NotificationRef) SourceID() string

SourceID exposes the source row id.

type NotificationSource added in v1.8.8

type NotificationSource string

NotificationSource is the closed set of durable rows that may produce a customer notification.

const (
	// NotificationHITLMessage is a pending message awaiting human approval.
	NotificationHITLMessage NotificationSource = "hitl_message"
	// NotificationWebhookHealth is a webhook warning/disabled episode.
	NotificationWebhookHealth NotificationSource = "webhook_health"
	// NotificationSendingAccessDecision is an operator decision (approved
	// or declined) on an external sending access request, reported to the
	// account owner.
	NotificationSendingAccessDecision NotificationSource = "sending_access_decision"
)

type OperationRef added in v1.8.8

type OperationRef struct {
	// contains filtered or unexported fields
}

OperationRef names one durable provider operation. Purpose, attribution, and shared-reputation class are captured here for the caller's convenience, but they are advisory: every Gate method reloads the row under lock and uses the stored values, so a forged or stale ref grants exactly no authority.

func (OperationRef) ID added in v1.8.8

func (r OperationRef) ID() string

ID exposes the opaque operation identifier for logging and for the River argument round-trip. It is not a capability.

func (OperationRef) IsZero added in v1.8.8

func (r OperationRef) IsZero() bool

IsZero reports an unset reference.

func (OperationRef) MarshalJSON added in v1.8.8

func (r OperationRef) MarshalJSON() ([]byte, error)

MarshalJSON writes the versioned wire form.

func (OperationRef) Purpose added in v1.8.8

func (r OperationRef) Purpose() Purpose

Purpose exposes the derived purpose for metrics. Advisory, as above.

func (*OperationRef) UnmarshalJSON added in v1.8.8

func (r *OperationRef) UnmarshalJSON(raw []byte) error

UnmarshalJSON reads the versioned wire form and yields a reference carrying only an ID. The absent purpose/attribution fields are what force every Gate method to reload from the database instead of trusting deserialized state.

type OperatorRecipients

type OperatorRecipients struct {
	// contains filtered or unexported fields
}

OperatorRecipients is the immutable versioned operator mailbox map loaded from E2A_SENDING_PROTECTION_OPERATOR_EMAILS.

The addresses are secret deployment configuration, not part of the account model, so this type deliberately exposes commitments freely and mailboxes narrowly: capabilities and registry rows carry only the HMAC commitment, and only the notice sender ever resolves an actual address.

func LoadOperatorRecipients

func LoadOperatorRecipients(raw string) (*OperatorRecipients, error)

LoadOperatorRecipients parses and validates the operator map, deriving the key identity and per-version commitments that the registry and the capability readback compare against.

func (*OperatorRecipients) Commitment

func (o *OperatorRecipients) Commitment(version int) (string, bool)

Commitment returns the lowercase HMAC-SHA256 binding a version to its mailbox. This is what the append-only registry stores and what the capability readback advertises.

func (*OperatorRecipients) Commitments

func (o *OperatorRecipients) Commitments() map[string]string

Commitments returns a defensive copy of every version's commitment, keyed by canonical decimal version for the capability payload.

func (*OperatorRecipients) KeyID

func (o *OperatorRecipients) KeyID() string

KeyID is the lowercase HMAC-SHA256 of the commitment key over a fixed label. It identifies the key across slots and registry rows without revealing it.

func (*OperatorRecipients) Mailbox

func (o *OperatorRecipients) Mailbox(version int) (string, bool)

Mailbox resolves the address for a version. Only the notice sender calls it; nothing else in the system needs the plaintext.

func (*OperatorRecipients) Versions

func (o *OperatorRecipients) Versions() []int

Versions lists every configured version in ascending order.

type PolicySnapshot

type PolicySnapshot struct {
	Generation    int64
	SchemaVersion int
	Policy        RuntimePolicy
	PolicySHA256  string
	ActivatedAt   time.Time
	ActivatedBy   string
}

PolicySnapshot is the stored policy plus the metadata an operator needs to express the next compare-and-swap.

type PolicySource

type PolicySource string

PolicySource selects where the runtime policy is read from. Self-hosted deployments stay on the config file; the hosted deployment selects the database so that activation is an audited CAS rather than a redeploy.

const (
	// PolicySourceConfig is the default for every deployment, including
	// self-host, and is what a binary uses before B15 flips the hosted service.
	PolicySourceConfig PolicySource = "config"
	// PolicySourceDatabase reads the singleton policy row.
	PolicySourceDatabase PolicySource = "database"
)

func SourceFromConfig

func SourceFromConfig(cfg *config.Config) (PolicySource, error)

SourceFromConfig returns the validated policy source. An absent value is the config source, matching config.Validate: a deployment that never mentions sending protection stays on the file it already has.

type ProtectionNoticeRef added in v1.8.8

type ProtectionNoticeRef struct {
	// contains filtered or unexported fields
}

ProtectionNoticeRef names one already-committed notice event and audience. The event row must exist: notices are enqueued by the transaction that detects the violation, and the drain worker only ever resumes them.

func NewProtectionNoticeRef added in v1.8.8

func NewProtectionNoticeRef(eventID string, audience Audience) ProtectionNoticeRef

NewProtectionNoticeRef references one notice event/audience pair.

type ProviderAuthorization added in v1.8.8

type ProviderAuthorization struct {
	// contains filtered or unexported fields
}

ProviderAuthorization is the single-use permission to make exactly one SES call for exactly one durable attempt. It cannot be constructed outside this package, cannot be widened, and is worthless without the durable nonce that RedeemProviderCall consumes.

func (ProviderAuthorization) Attempt added in v1.8.8

func (a ProviderAuthorization) Attempt() AttemptRef

Attempt exposes the durable attempt this token authorizes, for logging.

func (ProviderAuthorization) AuthorizedRecipients added in v1.8.8

func (a ProviderAuthorization) AuthorizedRecipients() []string

AuthorizedRecipients is the normalized recipient set this token permits, in canonical order. The protection notifier and public feedback compose their envelope from it — their recipients are configuration the gate already resolved, never a customer-controlled list. Every other caller composed its own envelope from the source row and hands that to the seam, which proves it names exactly these mailboxes (ValidateEnvelope) before it dials; a mismatch there fails closed rather than being re-derived here.

func (ProviderAuthorization) IsZero added in v1.8.8

func (a ProviderAuthorization) IsZero() bool

IsZero reports an unset authorization.

func (ProviderAuthorization) Purpose added in v1.8.8

func (a ProviderAuthorization) Purpose() Purpose

Purpose exposes the derived purpose, for metrics.

func (ProviderAuthorization) ValidateEnvelope added in v1.8.8

func (a ProviderAuthorization) ValidateEnvelope(recipients []string) (ProviderHeaders, error)

ValidateEnvelope proves the caller's actual envelope is the authorized one and returns the provider header values derived from the token.

THE CONTRACT: submit exactly AuthorizedRecipients(). Ordering and letter case are free — those are presentation. The COUNT is not: the envelope must carry one entry per distinct mailbox, because the adapter issues one RCPT TO per entry and the budget charged one unit per distinct mailbox.

So a caller must collapse its own To/Cc/Bcc overlap before submitting. That is a real constraint on the SMTP adapter — a reply-all naming the same mailbox in To and Cc is an ordinary message, and reassembling the raw header lists would be rejected here. Handing back AuthorizedRecipients() is not a workaround for that; it is the intended call, and it is the only envelope this token was ever priced for.

The alternative — silently deduplicating whatever arrives — is what makes an envelope of fifty case-variant spellings of one mailbox pass as "the same recipient" while SES receives fifty RCPT TO commands. That is a 50x reputation amplifier on one unit of budget, which is precisely the quantity this module exists to bound.

type ProviderHeaders added in v1.8.8

type ProviderHeaders struct {
	AttemptCorrelationID string
	TenantRequired       bool
	TenantName           string
}

ProviderHeaders is what the SMTP adapter is allowed to learn from a token. The adapter derives its provider-owned headers only from these values and never accepts them as separate parameters, which is what makes header smuggling a compile-time impossibility rather than a review question.

type ProviderSettlement added in v1.8.8

type ProviderSettlement struct {
	Attempt AttemptRef
	Outcome SettlementOutcome
	// ProviderMessageID is the id SES assigned when it accepted the message.
	// It is bound to the attempt's feedback correlation so delivery feedback
	// that arrives by provider id — the common case — resolves to the same
	// attempt as feedback that arrives by the random attempt header. Only an
	// accepted settlement may carry one; a rejection has nothing to bind.
	ProviderMessageID string
}

ProviderSettlement pairs an attempt with its authoritative outcome.

type PublicFeedbackRef added in v1.8.8

type PublicFeedbackRef struct {
	// contains filtered or unexported fields
}

PublicFeedbackRef names one server-generated /api/feedback submission and the complete fixed recipient set configured for it. Request data reaches neither field: the ID is minted by the handler and the recipients come from configuration, so a submitter cannot add, replace, or redirect a recipient.

func NewPublicFeedbackRef added in v1.8.8

func NewPublicFeedbackRef(submissionID string, recipients []string) PublicFeedbackRef

NewPublicFeedbackRef references one submission and its configured envelope.

type Purpose added in v1.8.8

type Purpose string

Purpose is the closed set of reasons e2a may hand a message to SES. It is derived by a server-side constructor from a durable source row and persisted on the provider operation; no caller, River argument, or MIME header can select or change it. The values match the CHECK constraint on sending_provider_operations.purpose.

const (
	// PurposeCustomerMessage is mail a customer's agent composed.
	PurposeCustomerMessage Purpose = "customer_message"
	// PurposeCustomerNotification is platform mail a customer's own action
	// triggered — HITL approval requests and webhook-health warnings. It is
	// attributed to and budgeted against that customer precisely because a
	// customer can cause an unbounded amount of it.
	PurposeCustomerNotification Purpose = "customer_notification"
	// PurposeCriticalOperational is a pause notice. It must survive an abuse
	// wave that has exhausted every customer pool, so it draws on its own.
	PurposeCriticalOperational Purpose = "critical_operational"
	// PurposeViolationOperational is a budget-violation or global-guardrail
	// notice. Separate from critical so limit-driven mail — which an attacker
	// can provoke — cannot starve pause notices.
	PurposeViolationOperational Purpose = "violation_operational"
	// PurposePublicFeedback is the unauthenticated /api/feedback fan-out to a
	// fixed configured recipient set. It has no customer to attribute to but
	// still shares provider reputation, so it consumes the global pools.
	PurposePublicFeedback Purpose = "public_feedback_notification"
	// PurposeTrustedSystem is first-party prober/conformance traffic from
	// system and internal accounts. Unbudgeted by design: it is not
	// customer-triggerable, so its compromise is a credential incident rather
	// than an abuse-policy question.
	PurposeTrustedSystem Purpose = "trusted_system"
)

type RuntimeAttestation

type RuntimeAttestation struct {
	Revision                int64
	ActiveBillingDigest     string
	ActiveBillingContract   int
	RollbackBillingDigest   string
	RollbackBillingContract int
	UpdatedAt               time.Time
	UpdatedBy               string
}

RuntimeAttestation records which billing images the deployment has verified, so a policy activation cannot outrun the artifacts that support it.

type RuntimeAttestationRequest

type RuntimeAttestationRequest struct {
	ExpectedRevision int64
	ExpectedSHA256   string

	ActiveBillingDigest     string
	ActiveBillingContract   int
	RollbackBillingDigest   string
	RollbackBillingContract int

	Actor  string
	Reason string
}

RuntimeAttestationRequest is a CAS over both the revision and the canonical hash of the four fields being replaced.

type RuntimePolicy

type RuntimePolicy struct {
	AllCustomerGlobalDailyRecipients int      `json:"all_customer_global_daily_recipients"`
	BounceMinOutcomes                int      `json:"bounce_min_outcomes"`
	BouncePauseBasisPoints           int      `json:"bounce_pause_basis_points"`
	BudgetHoldMaxDays                int      `json:"budget_hold_max_days"`
	BudgetMode                       Mode     `json:"budget_mode"`
	ComplaintPauseBasisPoints        int      `json:"complaint_pause_basis_points"`
	CriticalOperationalDailyRecip    int      `json:"critical_operational_daily_recipients"`
	DailyUnlimitedPlanCodes          []string `json:"daily_unlimited_plan_codes"`
	DefaultAccountDailyRecipients    int      `json:"default_account_daily_recipients"`
	DetectorIntervalSeconds          int      `json:"detector_interval_seconds"`
	DetectorMode                     Mode     `json:"detector_mode"`
	DetectorWindowDays               int      `json:"detector_window_days"`
	// ExternalSendingAccess is the optional external-sending-access control.
	// It is a pointer with omitempty on purpose: a legacy payload without it
	// must canonicalize to exactly the bytes it always had, so every stored
	// policy hash stays valid across the upgrade. Absent means disabled.
	ExternalSendingAccess            *ExternalSendingAccessPolicy `json:"external_sending_access,omitempty"`
	OperatorNoticeRecipientVersion   int                          `json:"operator_notice_recipient_version"`
	ProbationGlobalDailyRecipients   int                          `json:"probation_global_daily_recipients"`
	RampDays                         int                          `json:"ramp_days"`
	RampEnabled                      bool                         `json:"ramp_enabled"`
	RampStartDaily                   int                          `json:"ramp_start_daily"`
	RampTargetDaily                  int                          `json:"ramp_target_daily"`
	SendingControlAuditRetentionDays int                          `json:"sending_control_audit_retention_days"`
	SendingFeedbackPostAcctRetention int                          `json:"sending_feedback_post_account_retention_days"`
	SharedDomainAccountDailyRecip    int                          `json:"shared_domain_account_daily_recipients"`
	SharedReputationBounceMinOutcome int                          `json:"shared_reputation_bounce_min_outcomes"`
	TenantHeaderCanaryAccountIDs     []string                     `json:"tenant_header_canary_account_ids"`
	TenantHeaderMode                 TenantHeaderMode             `json:"tenant_header_mode"`
	TenantProvisioningMode           ToggleMode                   `json:"tenant_provisioning_mode"`
	TenantSuppressionSyncMode        ToggleMode                   `json:"tenant_suppression_sync_mode"`
	ViolationOperationalDailyRecip   int                          `json:"violation_operational_daily_recipients"`
}

RuntimePolicy is the whole sending-protection policy as one immutable typed value. Field names and JSON keys are load-bearing: the canonical form of this struct is hashed, reviewed by a human, and then required by hash at activation, so renaming a key is a policy-breaking change.

func DisabledPolicy

func DisabledPolicy() RuntimePolicy

DisabledPolicy returns the generation-zero policy: every control off, every numeric bound at its documented default. Migration 112 seeds exactly this value, so canonicalizing it must reproduce the hash that migration recorded. The keyring and registry tests use that equality as their anchor fixture.

func FromConfig

func FromConfig(cfg *config.Config) (RuntimePolicy, error)

FromConfig assembles the typed runtime policy from the validated config the server itself would use.

The schedule fields come from the `sending_ramp` block and the rest from `sending_protection`, because sending_ramp stays the custom-domain ramp SSOT and duplicating its numbers into a second block is how the two drift apart. The result is validated here, so an operator command and the server agree on what the config means before either acts on it.

func ParsePolicy

func ParsePolicy(raw []byte) (RuntimePolicy, error)

ParsePolicy decodes and validates a stored or configured policy. Unknown fields are rejected: a payload written by a newer binary carrying a control this one does not implement must fail closed, not be silently ignored.

func (RuntimePolicy) AllControlsDisabled

func (p RuntimePolicy) AllControlsDisabled() bool

AllControlsDisabled reports whether every sending-protection control is off.

RampEnabled is deliberately not part of this: the custom-domain ramp predates sending protection and self-hosts already run it without the new secrets. What the secrets protect -- provider authorization signing and operator notices -- only activates with the controls below.

func (RuntimePolicy) ExternalSendingMode added in v1.10.0

func (p RuntimePolicy) ExternalSendingMode() Mode

ExternalSendingMode reports the effective mode: absent is disabled.

func (RuntimePolicy) Validate

func (p RuntimePolicy) Validate() error

Validate enforces every closed enum and numeric invariant. It is called on the config-parsed policy at startup and again on any policy read from the database, because a row written by a newer binary must not be trusted just because it parsed.

type Scope added in v1.8.8

type Scope string

Scope is a budget counter dimension. The values match the CHECK constraint on sending_budget_counters.scope.

const (
	ScopeGlobalAll          Scope = "global_all"
	ScopeGlobalProbation    Scope = "global_probation"
	ScopeAccountDaily       Scope = "account_daily"
	ScopeAccountSharedDaily Scope = "account_shared_daily"
	ScopeGlobalCritical     Scope = "global_critical"
	ScopeGlobalViolation    Scope = "global_violation"
)

type Secrets

type Secrets struct {
	Keyring    *Keyring
	Recipients *OperatorRecipients
}

Secrets bundles the two immutable trust roots a running server holds.

func LoadSecretsFromEnv

func LoadSecretsFromEnv(source PolicySource, policy RuntimePolicy) (Secrets, error)

LoadSecretsFromEnv loads and validates both B1a-prewired secrets.

Presence rules follow the plan exactly: a config-source deployment whose policy keeps every control disabled may omit them (self-host compatibility -- the secrets guard mechanisms that are not running). Database source, or any enabled control, requires both; and a value that is PRESENT must always be valid regardless of mode, because a malformed secret discovered at activation time is far worse than one discovered at boot. Errors are redacted by construction -- see keyring.go.

type SettlementOutcome added in v1.8.8

type SettlementOutcome string

SettlementOutcome is the closed set of authoritative provider results that move the ramp ledger. Retryable and ambiguous results are deliberately absent: they leave the reservation standing, because a message that might have been delivered must not release ramp capacity.

const (
	// SettlementProviderAccepted means SES took responsibility for the message.
	SettlementProviderAccepted SettlementOutcome = "provider_accepted"
	// SettlementProviderPermanentlyRejected means SES definitively refused it.
	SettlementProviderPermanentlyRejected SettlementOutcome = "provider_permanently_rejected"
)

type TenantHeaderMode

type TenantHeaderMode string

TenantHeaderMode controls the SES tenant header. Unlike Mode, its middle state is a canary over an explicit account list rather than a shadow computation — a header is either sent or not; there is nothing to simulate.

const (
	TenantHeaderDisabled TenantHeaderMode = "disabled"
	TenantHeaderCanary   TenantHeaderMode = "canary"
	TenantHeaderEnforce  TenantHeaderMode = "enforce"
)

type TenantMode added in v1.8.8

type TenantMode string

TenantMode is the closed tenant-header state carried by an authorization. It is resolved under the final account-control lock, so a job that was enqueued before a tenant flip still submits with the post-flip header.

const (
	// TenantModeNone means no X-SES-TENANT header. This is every deployment
	// before phase 7 and every self-host.
	TenantModeNone TenantMode = "none"
	// TenantModeRequired means the exact named tenant must be sent.
	TenantModeRequired TenantMode = "required"
)

type ToggleMode

type ToggleMode string

ToggleMode is a two-state control for operations that have no meaningful shadow: provisioning either creates tenants or it does not, and suppression sync either writes to the provider or it does not.

const (
	ToggleDisabled ToggleMode = "disabled"
	ToggleEnforce  ToggleMode = "enforce"
)

Jump to

Keyboard shortcuts

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