Skip to content

What is crucible/sink

One inlet fanning out to many destinations

crucible/sink is the suite’s egress seam: a fire-and-forget fan-out emitter. A service calls one method:

m.Sink(ctx, payload)

The payload fans out to every attached destination: a SQL table, a DynamoDB item, a StatsD counter, a webhook, a log line. The call site never names which destinations are wired; that wiring lives in one place, set up once, and the emit path stays a single line forever.

Like every Crucible module, sink is built from thin seams with no-op defaults and no forced dependencies. The core imports only the standard library and crucible/telemetry; every vendor SDK lives in its own optional sub-module, so you pull in exactly the destinations you use and nothing else.

A Manifold holds a set of Outlets and dispatches each payload to all of them. Manifold.Sink is the only emit path and it returns nothing. Failures go to the configured logger and metrics, not back up the call stack.

flowchart LR
    P([payload]) --> M{{Manifold}}
    M --> A[sql]
    M --> B[dynamo]
    M --> C[statsd]
    M --> D[webhook]
    M -.errors.-> L[/slog logger + metrics/]

That is the whole idea: one inlet, many outlets, fire and forget. A caller who genuinely needs confirmation for one critical destination holds that Outlet directly and calls it for an honest per-destination error, but the fan-out itself never blocks or surprises the call site.

The state kernel decides what should happen and emits effects as pure data; it performs no IO. sink is a natural place to send those effects: the host’s dispatch loop hands each effect to a Manifold, and the fan-out carries it to the outside world. Neither module imports the other. They compose through the optional bridge when you want them together, and stand completely alone when you do not.

sink is the first of a small family of bring-your-own-adapter IO seams (broker, source, and friends are on the roadmap), each defaulting to a no-op and forcing nothing third-party on the consumer.