channels

package
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Jun 20, 2026 License: MIT Imports: 10 Imported by: 0

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

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

func (r *Registry) DeliverToIdentity(ctx context.Context, identityID, text string) (bool, error)

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

func (r *Registry) Register(c Channel)

Register adds a channel under its Name. A later Register with the same Name replaces the earlier one (mirrors tools.Registry).

func (*Registry) SetEnabledOverride

func (r *Registry) SetEnabledOverride(predicate func(name string) (enabled, ok bool))

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

func (r *Registry) StartAll(ctx context.Context) error

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.

func (*Registry) StopAll

func (r *Registry) StopAll(ctx context.Context) error

StopAll stops every channel StartAll started, aggregating drain errors with errors.Join. It is idempotent: a channel is removed from the started set once stopped, so a second StopAll (or one before StartAll) is a clean no-op.

Directories

Path Synopsis
Package telegram — this file is the per-turn AG-UI fanout consumer (the Phase-12 seam).
Package telegram — this file is the per-turn AG-UI fanout consumer (the Phase-12 seam).

Jump to

Keyboard shortcuts

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