package provision import ( "context" "time" ) // Clock abstracts time for testability (Replace drains on a timer). type Clock interface { Now() time.Time // Sleep blocks for d or until ctx is done, returning ctx.Err() on cancel. Sleep(ctx context.Context, d time.Duration) error } // realClock is the production Clock. type realClock struct{} func (realClock) Now() time.Time { return time.Now().UTC() } func (realClock) Sleep(ctx context.Context, d time.Duration) error { t := time.NewTimer(d) defer t.Stop() select { case <-ctx.Done(): return ctx.Err() case <-t.C: return nil } } // Prober verifies a node is reachable before it joins the directory. // // The full version (three-way probe cross-check, doc/04 §4.2) depends on the // probe fleet (task #15). Until then a simplified Prober is injected: // "heartbeat present + an overseas reachability check". Replace calls WaitReady // after a node is created; on success the node is promoted to up. type Prober interface { // WaitReady blocks until the node passes simplified probing, or returns an // error on timeout / ctx cancellation / probe failure. WaitReady(ctx context.Context, node *Node) error } // AlertHook receives operational alerts (doc/04 §5.3 — TG bot in production). // Replace / CreateNode call Fire on boot failure, probe timeout, and capacity // protection breaches. type AlertHook interface { Fire(ctx context.Context, alert Alert) } // AlertKind classifies an alert. type AlertKind string const ( AlertBootFailed AlertKind = "boot_failed" AlertProbeTimeout AlertKind = "probe_timeout" AlertReplaceFail AlertKind = "replace_failed" AlertCapacity AlertKind = "capacity_low" ) // Alert is a single operational alert payload. type Alert struct { Kind AlertKind NodeUUID string Pool Pool Message string // FailCount is the running failure counter for capacity-protection alerts. FailCount int } // noopAlert discards alerts; used when no hook is configured. type noopAlert struct{} func (noopAlert) Fire(context.Context, Alert) {} // BootstrapIssuer issues one-time enrollment tokens (task #5). // *mtls.BootstrapTokenManager satisfies this interface. type BootstrapIssuer interface { IssueToken(ctx context.Context, nodeUUID string) (string, error) } // CloudInitRenderer renders the node cloud-init document from a template, // injecting the node UUID and bootstrap token (template lives at // infra/cloud-init/node.yaml.tmpl, task #6). type CloudInitRenderer interface { Render(data CloudInitData) (string, error) } // CloudInitData is the template context for the node cloud-init document. type CloudInitData struct { NodeUUID string BootstrapToken string Region string Role Role Tier Tier // ControlPlaneURL is the mTLS enrollment endpoint the agent registers to. ControlPlaneURL string }