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
- Variables
- func AttestationHash(a RuntimeAttestation) (string, error)
- func CanonicalBytes(p RuntimePolicy) ([]byte, error)
- func Hash(p RuntimePolicy) (string, error)
- func HashBytes(canonical []byte) string
- type AcceptanceDecision
- type ActivationRequest
- type AttemptRef
- type Audience
- type Capabilities
- type Decision
- type Gate
- type GrandfatherResult
- type Keyring
- type Mode
- type Module
- func (m *Module) ActivatePolicy(ctx context.Context, req ActivationRequest) (PolicySnapshot, error)
- func (m *Module) AttestRuntime(ctx context.Context, req RuntimeAttestationRequest) (RuntimeAttestation, error)
- func (m *Module) CancelAttempt(ctx context.Context, ref AttemptRef) error
- func (m *Module) ConsumeAttempt(ctx context.Context, ref AttemptRef) (Decision, *ProviderAuthorization, error)
- func (m *Module) DeferAttempt(ctx context.Context, ref AttemptRef) error
- func (m *Module) InspectAttestation(ctx context.Context) (RuntimeAttestation, error)
- func (m *Module) InspectPolicy(ctx context.Context) (PolicySnapshot, error)
- func (m *Module) PrepareExternalTx(ctx context.Context, tx pgx.Tx, messageID string) (AcceptanceDecision, OperationRef, error)
- func (m *Module) PrepareNotificationTx(ctx context.Context, tx pgx.Tx, ref NotificationRef) (OperationRef, error)
- func (m *Module) PrepareProtectionNoticeTx(ctx context.Context, tx pgx.Tx, ref ProtectionNoticeRef) (OperationRef, error)
- func (m *Module) PreparePublicFeedback(ctx context.Context, ref PublicFeedbackRef) (OperationRef, error)
- func (m *Module) RedeemProviderCall(ctx context.Context, auth ProviderAuthorization) error
- func (m *Module) RegisterOperatorRecipients(ctx context.Context, actor, reason string) (inserted []int, err error)
- func (m *Module) Reserve(ctx context.Context, ref OperationRef) (Decision, AttemptRef, error)
- func (m *Module) SettleProvider(ctx context.Context, settlement ProviderSettlement) error
- type NotificationRef
- type NotificationSource
- type OperationRef
- type OperatorRecipients
- type PolicySnapshot
- type PolicySource
- type ProtectionNoticeRef
- type ProviderAuthorization
- func (a ProviderAuthorization) Attempt() AttemptRef
- func (a ProviderAuthorization) AuthorizedRecipients() []string
- func (a ProviderAuthorization) IsZero() bool
- func (a ProviderAuthorization) Purpose() Purpose
- func (a ProviderAuthorization) ValidateEnvelope(recipients []string) (ProviderHeaders, error)
- type ProviderHeaders
- type ProviderSettlement
- type PublicFeedbackRef
- type Purpose
- type RuntimeAttestation
- type RuntimeAttestationRequest
- type RuntimePolicy
- type Scope
- type Secrets
- type SettlementOutcome
- type TenantHeaderMode
- type TenantMode
- type ToggleMode
Constants ¶
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.
const ( ReasonAccountPaused = "account_paused" ReasonAccountDailyBudget = "account_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" // 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" )
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.
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.
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.
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 ¶
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") // 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.
var ( // 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.
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") // 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.
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.
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 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.
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" )
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.
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"`
}
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
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 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
}
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 ¶
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 ¶
ActiveVersion reports which key new material is signed under.
func (*Keyring) Sign ¶
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.
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.
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 ¶
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 (*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 ¶
func (m *Module) AttestRuntime(ctx context.Context, req RuntimeAttestationRequest) (RuntimeAttestation, error)
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.
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) 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) InspectAttestation ¶
func (m *Module) InspectAttestation(ctx context.Context) (RuntimeAttestation, error)
InspectAttestation reads the current runtime attestation.
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) 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 and every failing webhook is a health warning. 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) 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) 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) 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.
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 NewWebhookHealthNotificationRef ¶ added in v1.8.8
func NewWebhookHealthNotificationRef(webhookID string) NotificationRef
NewWebhookHealthNotificationRef references a webhook whose health episode is being reported to its owner.
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" )
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 returns a defensive copy of the exact final envelope.
Only the protection notifier uses it: that path is the one caller that does not already know its recipient, because the address is resolved under lock at final authorization and deliberately never persisted in plaintext. Every other caller composed its own envelope and must not re-derive one 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
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
}
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"`
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"`
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) 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.
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" )