Documentation
¶
Overview ¶
Package webhook sends one event to one subscriber (automation.md §3.1).
It is an outbound adapter and nothing else: what to send and to whom was decided by the fan-out, and what a failure means is decided by the aggregate. What lives here is the part that needs the network - the body, the signature, the call through the guard, and the translation of an HTTP answer into "try again" or "stop".
Index ¶
Constants ¶
const ( EventIDHeader = "X-Hubtask-Event-Id" EventTypeHeader = "X-Hubtask-Event-Type" AttemptHeader = "X-Hubtask-Delivery-Attempt" )
The headers automation.md §3.1 names, beside the signature.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Deliverer ¶
type Deliverer struct {
Subscriptions repository.WebhookSubscriptions
Deliveries repository.WebhookDeliveries
Events Events
Outcomes Outcomes
// Encryptor opens the sealed signing secret. This is the only place a webhook secret is ever
// in memory in clear, and it is in memory for the length of one HMAC.
Encryptor crypto.Encryptor
Signer security.WebhookSigner
// Client is the guarded one, always. A webhook target is an egress channel exactly as a backup
// target is: a private range or the cloud metadata address is refused here unless the
// installation has deliberately released private networks (rule 6, T-07).
Client httpport.Port
// UnitOfWork is this handler's own, because it is Detached: the call to somebody else's server
// happens between two short transactions rather than inside one long one. Holding a database
// connection for as long as a subscriber's server feels like taking is exactly what
// observability-reliability.md §8 forbids.
UnitOfWork persistence.UnitOfWork
// Jobs is where the next attempt is asked for. A job per attempt rather than a repeating job,
// so that each attempt has its own row and the log can answer "this event was tried eight
// times over two days and these are the answers".
Jobs Queue
Clock clock.Clock
IDs clock.IDGenerator
// Source is what the CloudEvent names as its origin. The installation's own identifier, so a
// subscriber receiving from two installations can tell them apart.
Source string
// NextAttempt is the backoff, given the attempts made so far. Injected for the runner's
// reason: this layer decides when to retry, not how far apart.
NextAttempt func(attempt int) time.Duration
// Entitlements is what the workspace's plan leaves out (multi-tenancy.md §4.2). Nil includes
// every feature.
Entitlements adminrepo.Entitlements
// Signals carries each settled attempt into the metrics. Nil skips; the composition root
// always wires it.
Signals Signals
}
Deliverer sends one delivery. It is a queue handler, so the retry ladder is the queue's and the backoff policy is the resilience adapter's - eight attempts reaching a day is a schedule this system already knows how to keep.
func (Deliverer) OwnsItsTransactions ¶
func (Deliverer) OwnsItsTransactions()
OwnsItsTransactions declares the exception this handler needs (queue.Detached).
What it gives up is the atomicity everybody else gets for free, and that is acceptable here for the reason the interface asks about: a delivery is safe to run twice. At-least-once is the contract ADR-0007 states, a subscriber deduplicates on X-Hubtask-Event-Id, and a repeat that finds its attempt row already settled stops without sending anything.
func (Deliverer) Run ¶
Run makes one attempt.
It returns no error for a target that refused, and that is the shape the whole discipline rests on: a failed delivery is this system working correctly, and a job that failed would be retried by the queue on the queue's schedule rather than on the delivery's, and would eventually reach the queue's dead letter rather than the delivery log an operator reads. The retry is asked for explicitly instead.
type Events ¶
Events is the slice of the outbox this adapter reads: one event, by its identifier.
A delivery renders the event as it was rather than carrying a copy in the job payload, so a retry two days later sends what the first attempt would have - and a job row does not become a second place a workspace's content lives.
type Outcomes ¶
type Outcomes interface {
Delivered(ctx context.Context, subscriptionID shared.ID) error
Failed(ctx context.Context, subscriptionID shared.ID, code string, terminal bool) error
}
Outcomes is what an attempt reports back, and what decides the failure run and the auto-disable.
Declared here rather than imported, because an outbound adapter may not call a use case (project-structure.md §2, and the gate that says so). The composition root puts the two together; what this package knows is that something answers these two questions.
type PlatformDeliverer ¶
type PlatformDeliverer struct {
Subscriptions adminrepo.PlatformSubscriptions
Deliveries adminrepo.PlatformDeliveries
Events PlatformEvents
// Encryptor opens the sealed secret for the length of one HMAC.
Encryptor crypto.Encryptor
Signer security.WebhookSigner
// Client is the guarded one (rule 6): a private range or the metadata address is refused
// unless the installation released private networks.
Client httpport.Port
// UnitOfWork is this handler's own: the call to the platform happens between two short
// transactions, never inside one.
UnitOfWork persistence.UnitOfWork
Jobs Queue
Clock clock.Clock
Source string
NextAttempt func(attempt int) time.Duration
Signals PlatformSignals
}
PlatformDeliverer sends one installation event to one platform subscription (UC-INS-14): the workspace webhook's signature, headers and retry ladder, and none of its switching off. A delivery that ends without arriving stays as a dead letter the operator redelivers, and the subscription stays as it was - the operator is told by A-21, not by the subscription going quiet.
func (PlatformDeliverer) OwnsItsTransactions ¶
func (PlatformDeliverer) OwnsItsTransactions()
OwnsItsTransactions declares the exception: a delivery is safe to run twice, since a platform deduplicates on X-Hubtask-Event-Id and an attempt is recorded only against the attempt it read.
type PlatformEvents ¶
type PlatformEvents interface {
FindEvent(ctx context.Context, id shared.ID) (event.Envelope, error)
}
PlatformEvents is the slice of the installation's events a delivery reads: one, by its id.
type PlatformSignals ¶
type PlatformSignals interface {
PlatformDelivery(ctx context.Context, result, statusClass string)
}
PlatformSignals carries each settled attempt into the metrics, as the workspace's deliveries do.
type Queue ¶
Queue is where the next attempt is asked for. The same one line the fan-out needs, declared again for the same reason.
type Signals ¶
Signals is the delivery's trace in the metrics (observability-reliability.md §4): every settled attempt, by what became of it - ok, retry, dead - and the class of the answer. SLO-6's error rate excludes 4xx recipient errors, which is exactly what the class label makes possible. A local interface rather than the metrics adapter, because adapters do not know each other.