hub v0.130.0: OS updates, guest fast lane — rings, per-box switch, OS releases approved from ring 0, os-report, os_update desired block

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-04 10:56:39 +02:00
parent 6ed79cd2e9
commit c5f91174f6
25 changed files with 1338 additions and 2 deletions
+6
View File
@@ -50,6 +50,7 @@ type Poker interface {
type Handler struct {
store *store.Store
offsiteKeys OffsiteKeyService // decision 69 key registrar (nil → 503)
osUpdates OSUpdateService // `11` §8 step 2, the guest fast lane (nil → 503, no block merged)
apiKey string
resendAPIKey string
fromEmail string
@@ -288,6 +289,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
case r.Method == http.MethodPost && strings.HasPrefix(path, "/hosts/") && strings.HasSuffix(path, "/pbs/consume-token"):
hostID := strings.TrimSuffix(strings.TrimPrefix(path, "/hosts/"), "/pbs/consume-token")
h.handleConsumePBSToken(w, r, hostID)
// OS updates (hub v0.130.0): the agent's report after every OS-leg run — per-host key, self-scoped.
case r.Method == http.MethodPost && strings.HasPrefix(path, "/hosts/") && strings.HasSuffix(path, "/os-report"):
hostID := strings.TrimSuffix(strings.TrimPrefix(path, "/hosts/"), "/os-report")
h.handleOSReport(w, r, hostID)
// Desired-state serving (slice 10A) — per-host-key, self-scoped (a host reads only its own).
case r.Method == http.MethodGet && strings.HasPrefix(path, "/hosts/") && strings.HasSuffix(path, "/desired-state"):
hostID := strings.TrimSuffix(strings.TrimPrefix(path, "/hosts/"), "/desired-state")
@@ -1747,6 +1752,7 @@ func (h *Handler) handleGetDesiredState(w http.ResponseWriter, r *http.Request,
// S2: merge the hub-OWNED wireguard block at read time (no peer → pass-through unchanged;
// the stored operator blob is never modified). See api/wg.go mergeWireguard.
desired = h.mergeWireguard(pathHostID, desired)
desired = h.mergeOSUpdate(pathHostID, desired)
resp := map[string]interface{}{
"generation": host.DesiredGeneration,
"desired_state": json.RawMessage(desired), // opaque to the hub — agent owns the schema