Documentation
¶
Overview ¶
Package hookclient is the Epic 10 hook transport (E10.1, G4): the thin poster that `swarm hook <event>` uses to send an authenticated status callback to the daemon socket, plus the daemon-side Decode that reverses it.
A hook invocation reads its per-session authentication from the environment injected at spawn — session id, live token, daemon socket path, and a monotonic sequence — and composes an engine.Callback (FromEnv). Post writes it to the daemon socket; the daemon Decodes it and feeds engine.HandleCallback, which authenticates it (S6/G5) and applies the status. Post/Decode are an inverse pair; the wire encoding is theirs to choose (JSON here). The token is installed per invocation and never persisted, so a hook cannot be spoofed by another local process (ADR-004).
Index ¶
- Constants
- func CaptureEnv(events []string) string
- func CapturesRaw(getenv func(string) string, event string) bool
- func Decode(r io.Reader) (engine.Callback, error)
- func FromEnv(getenv func(string) string, event string, payload map[string]string) (engine.Callback, error)
- func Post(socketPath string, cb engine.Callback) error
- func PostToShim(hookSocketPath string, cb engine.Callback) (acked bool, err error)
- type HookPath
Constants ¶
const ( EnvSessionID = "SWARM_SESSION_ID" EnvToken = "SWARM_HOOK_TOKEN" // EnvSocket intentionally reuses the daemon's socket variable // (daemon.EnvSocket == "SWARM_DAEMON_SOCK") so the hook dials the same socket // the daemon serves. EnvSocket = "SWARM_DAEMON_SOCK" // EnvSequenceFile names the per-session monotonic counter FILE the daemon // injects at spawn (G5). Each `swarm hook` invocation atomically increments it // to obtain a strictly increasing sequence, so per-event callbacks are never // rejected as replays. This is the sole sequence source. EnvSequenceFile = "SWARM_HOOK_SEQ_FILE" // EnvCapture lists the session adapter's capture=raw events (ADR-010 §6), // comma-separated. A hook invocation keeps the CLI's own event body only for an // event named here. // // It is injected for the same reason the token is: the hook runs as a child of // the AGENT and knows its event name but not the adapter that declared it, so // the core resolves adapter.CaptureEvents once at spawn and hands down the // answer. An empty or absent value means this session captures nothing, which is // every adapter that implements no capture extension. EnvCapture = "SWARM_HOOK_CAPTURE" )
Environment keys the daemon injects into a session's hook environment at spawn.
const EnvHookSocket = "SWARM_SHIM_HOOK_SOCK"
EnvHookSocket names the per-session shim hook-socket path injected at spawn -- the hookclient-side twin of EnvSocket. Unset (an old shim, or a daemon that has not yet wired the hook socket) means "no shim hook socket": PostSmart goes straight to Post, unchanged from today.
Variables ¶
This section is empty.
Functions ¶
func CaptureEnv ¶
CaptureEnv encodes capture rows for EnvCapture; CapturesRaw decodes them. They are an inverse pair, like Post/Decode, so the separator is one package's business and not a convention two packages have to agree on by hand.
func CapturesRaw ¶
CapturesRaw reports whether event is one of the capture rows the core declared for this session. It is total: an unset, empty or malformed value captures nothing, which is the safe answer — a body kept for nobody is bytes on the socket, and the daemon's own adapter lookup is what ultimately admits it.
func Decode ¶
Decode reads a single callback that Post wrote. It is the daemon-side inverse of Post.
func FromEnv ¶
func FromEnv(getenv func(string) string, event string, payload map[string]string) (engine.Callback, error)
FromEnv composes a Callback from the injected environment plus the event name and payload the hook wiring supplies. getenv abstracts os.Getenv for testing. A missing token fails rather than compose a tokenless (unauthenticatable) callback (S6, client side).
func PostToShim ¶
PostToShim dials hookSocketPath and posts cb with the same JSON encoding Post uses, then waits up to postAckTimeout for the shim's single ack byte.
- a dial failure returns acked=false with a non-nil err.
- a successful post with no ack observed before the deadline returns acked=false, err=nil -- reachable, not (yet) confirmed; the caller MAY retry.
- a successful post whose ack byte arrives returns acked=true, err=nil.
Types ¶
type HookPath ¶
type HookPath string
HookPath names which transport actually carried a post.
func PostSmart ¶
PostSmart is the CLI's one entrypoint (requirement 7). hookSocketPath=="" skips straight to the daemon path -- no dial attempt at all, so an old shim never pays a timeout. Otherwise: try PostToShim; a dial failure falls back to the daemon path immediately (no retry against a socket that plainly is not there); an ack-less-but-reachable outcome is retried against the SAME shim socket with the SAME cb up to hookShimPostRetries times before giving up and falling back. The returned HookPath names whichever transport actually carried the post.