Documentation
¶
Overview ¶
Package channels is the daemon channels framework (Phase 13 / Slice 9a, UX-02): a Channel interface + Registry that the bootServe lifecycle mounts as a fail-soft subsystem, with Telegram (internal/channels/telegram) the first real channel.
This file is the dependency ANCHOR for Phase 13's Wave-1 substrate. The external deps (telebot.v4 transport, qrterminal onboarding QR) are pinned by amendment #58 and gated through the package legitimacy checkpoint (Plan 13-01 Task 1), but their first real consumers land in later Wave plans (13-05..13-08). Without a blank import here `go mod tidy` would drop the pins from go.mod (nothing imports them yet), breaking the amendment #58 CI pin gate (the literal `gopkg.in/telebot.v4 v4.0.0-beta.9` grep). The blank imports keep them in the DIRECT require block until the consumers replace them. This mirrors the project's existing anchor idiom (internal/cron/tzdata.go blank-imports time/tzdata; the OTel train is anchored the same way).
NOTE (Plan 13-03): `golang.org/x/image` is no longer anchored here — its real consumer internal/channels/telegram/tables.go now imports opentype + gomono + gomonobold genuinely, keeping x/image DIRECT at the amendment-#58 pin v0.41.0.
NOTE (Plan 13-07): `github.com/mdp/qrterminal/v3` is no longer anchored here — its real consumer internal/setup/qr.go now imports it genuinely (printQR's terminal ASCII onboarding QR, D-04), keeping qrterminal DIRECT at the amendment-#58 pin v3.2.1 (verified: `go mod tidy` + `go build ./...` stay green after the anchor's removal, mirroring the 13-03 x/image removal).
telebot.v4 stays anchored: although the telegram channel files now import it genuinely, the anchor is the amendment-#58 CI pin gate's stable grep target and is left untouched (out of this plan's scope — a pre-existing redundancy, not a regression).
Index ¶
- type Channel
- type Deliverer
- type Registry
- func (r *Registry) DeliverToIdentity(ctx context.Context, identityID, text string) (bool, error)
- func (r *Registry) Register(c Channel)
- func (r *Registry) SetEnabledOverride(predicate func(name string) (enabled, ok bool))
- func (r *Registry) StartAll(ctx context.Context) error
- func (r *Registry) StopAll(ctx context.Context) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Channel ¶
type Channel interface {
// Name keys AURA_CHANNEL_<upper(Name)>_ENABLED — e.g. "telegram".
Name() string
// Start launches the channel (e.g. the polling goroutine) and returns once
// started, NOT on each turn. A failed Start is aggregated by the Registry,
// never fatal to the daemon (fail-soft).
Start(ctx context.Context) error
// Stop drains the channel gracefully (goleak-clean). Idempotent: a Stop on a
// never-started channel is a no-op.
Stop(ctx context.Context) error
// IsHealthy is the health probe surface (e.g. /setup/status).
IsHealthy() bool
}
Channel is the narrow lifecycle contract every daemon channel implements (Telegram now; WhatsApp/Discord future). It mirrors the codebase's narrow- interface idiom (internal/agent/tools.Tool) and the fail-soft daemon-subsystem posture of the scheduler + AG-UI server mounted in bootServe.
CRITICAL (research §1): Start takes NO fanout subscriber. The PRD's older Start(ctx, sub) sketch is wrong for this codebase — fanout is built PER TURN inside the channel (Subscribe-before-Run, one Fanout per runner.Turn), never once at channel start. The channel holds the *runner.Runner and builds a fresh Fanout inside each turn handler. Keep Start(ctx) clean; the per-turn wiring is internal to the channel.
type Deliverer ¶
type Deliverer interface {
Deliver(ctx context.Context, identityID, text string) (delivered bool, err error)
}
Deliverer is an OPTIONAL channel capability: a started Channel MAY also implement it. The Registry runtime-asserts ch.(Deliverer) and skips a channel that does not — zero registry change for a future channel.
The tri-state return is load-bearing — the dispatch precedence (Phase 20 R4, plan 20-03) and the Registry fan-out (R2) both depend on it:
(false, nil) = not my user → try the next channel (true, nil) = delivered → stop (false, err) = owns-but-failed → stop, do NOT try siblings
(false, err) must NOT be read as "try the next channel": that would let one identity's message leak to a second channel on a transient error (double- delivery). The contract travels with the type so every consumer honors it.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds the set of daemon channels and aggregates their lifecycle. It mirrors the map-backed internal/agent/tools.Registry idiom (NewRegistry / Register) and the fail-soft daemon-subsystem posture of bootServe: one channel's Start failure is logged and aggregated via errors.Join but never aborts the others or the daemon (research §1 / Pattern 2).
The zero Registry is not usable — call NewRegistry (the channels/started maps must be non-nil). StartAll/StopAll are safe to call once each per registry; StopAll only stops channels StartAll actually started, and is idempotent.
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry returns an empty, ready-to-use Registry.
func (*Registry) DeliverToIdentity ¶
DeliverToIdentity fans a push out to the started channel that owns identityID, in a deterministic sorted-by-name order (Phase 20 Fork 4 / D-05 — NEVER Go map iteration order, which is nondeterministic the moment a 2nd Deliverer lands). A started Channel that does not implement Deliverer is skipped (it cannot push). The tri-state Deliverer contract drives the fan-out: the first channel to deliver wins (returns true,nil); an owning channel that fails stops the fan-out with (false, err) and never asks a sibling (no double-delivery); when no channel owns the identity it returns (false, nil) so the caller falls back to its route.
The lock is held only to snapshot r.started — a Deliver call can block on the network, so it runs unlocked (mirrors StopAll's snapshot-then-release idiom).
func (*Registry) Register ¶
Register adds a channel under its Name. A later Register with the same Name replaces the earlier one (mirrors tools.Registry).
func (*Registry) SetEnabledOverride ¶
SetEnabledOverride installs the flag-driven override predicate. predicate(name) returns (enabled, ok); ok=false defers to the AURA_CHANNEL_<NAME>_ENABLED env gate. Passing nil clears the override.
func (*Registry) StartAll ¶
StartAll starts every enabled channel. A channel is enabled when its override (if any) says so, else when AURA_CHANNEL_<upper(Name)>_ENABLED is not "false" (default true). Disabled channels are skipped (never started). Start failures are logged and aggregated with errors.Join — one failure never aborts the siblings (fail-soft). Started channels are tracked so StopAll stops only them.