P2-02 (server side): schedule reconciliation push + ack handling
Server is now the source of truth for the agent's cron set.
* Helpers in schedule_push.go:
- loadScheduleSetPayload reads the host's schedules + canonical
version into the wire shape.
- pushScheduleSetOnConn writes directly to a just-handshaken conn
(avoids racing against Hub.Register on a brand-new connection).
- pushScheduleSetAsync is the post-CRUD flavour — no-op when the
host is offline (the next reconnect's on-hello path catches it
up, so a missed push is non-fatal).
- applyScheduleAck records what version the agent has confirmed.
* onAgentHello restructured: was returning early when the host had
no repo credentials, which made the schedule push unreachable for
fresh hosts. Split into pushRepoCredsOnHello (silent no-op on
ErrNotFound) + pushScheduleSetOnConn (always runs). Empty schedule
list is a valid push: tells the agent to drop stale cron entries.
* WS dispatcher gains an OnScheduleAck hook on HandlerDeps; the
http server wires it to applyScheduleAck. MsgScheduleAck moves
out of the "TODO(P2)" group into a real case that decodes the
payload and forwards to the callback.
* Schedule CRUD handlers each fire pushScheduleSetAsync after the
audit-log write so the agent picks up changes within seconds.
Tests cover:
- On-hello push of an already-created schedule, agent acks,
applied_schedule_version flips on the host row.
- Connect-then-CRUD: empty initial push (version 0), then a
follow-on push at version 1 after the operator creates a
schedule via REST.
Agent-side `schedule.set` handler (parse, replace local cron,
emit `schedule.ack`) is the remainder of P2-02 and lands with
P2-03's local scheduler.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,9 @@ type HandlerDeps struct {
|
||||
// layer to push host_credentials down as a config.update before
|
||||
// the agent starts asking for jobs. Optional; nil = no-op.
|
||||
OnHello func(ctx context.Context, hostID string, conn *Conn)
|
||||
// OnScheduleAck is called when an agent confirms it has applied
|
||||
// a particular schedule version (P2-02 reconciliation). Optional.
|
||||
OnScheduleAck func(ctx context.Context, hostID string, version int64, appliedAt time.Time)
|
||||
}
|
||||
|
||||
// AgentHandler is the http.Handler that owns /ws/agent. Agents
|
||||
@@ -255,7 +258,17 @@ func dispatchAgentMessage(ctx context.Context, c *Conn, hostID string, env api.E
|
||||
}
|
||||
}
|
||||
|
||||
case api.MsgRepoStats, api.MsgScheduleAck, api.MsgCommandResult:
|
||||
case api.MsgScheduleAck:
|
||||
var p api.ScheduleAckPayload
|
||||
if err := env.UnmarshalPayload(&p); err != nil {
|
||||
slog.Warn("ws: bad schedule.ack payload", "host_id", hostID, "err", err)
|
||||
break
|
||||
}
|
||||
if deps.OnScheduleAck != nil {
|
||||
deps.OnScheduleAck(ctx, hostID, p.Version, p.AppliedAt)
|
||||
}
|
||||
|
||||
case api.MsgRepoStats, api.MsgCommandResult:
|
||||
// TODO(P2): persist these projections.
|
||||
slog.Debug("ws msg not yet handled", "type", env.Type, "host_id", hostID)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user