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:
@@ -192,6 +192,19 @@ func (s *Server) pushRepoCredsToAgent(ctx context.Context, hostID string, blob r
|
||||
// The conn argument is used directly (rather than via the hub) so we
|
||||
// don't race a brand-new register against an old still-closing conn.
|
||||
func (s *Server) onAgentHello(ctx context.Context, hostID string, conn *ws.Conn) {
|
||||
s.pushRepoCredsOnHello(ctx, hostID, conn)
|
||||
// Push the current schedule set in the same on-hello window so
|
||||
// the agent's local cron is in sync before any command.run lands.
|
||||
// An empty schedule list is a valid push: it tells the agent to
|
||||
// drop any cron entries left over from a previous deployment.
|
||||
// Always runs, even when the host has no repo credentials yet.
|
||||
s.pushScheduleSetOnConn(ctx, hostID, conn)
|
||||
}
|
||||
|
||||
// pushRepoCredsOnHello loads + decrypts + sends the host's repo
|
||||
// credentials. Silent no-op when the host has nothing on file
|
||||
// (the operator hasn't bound creds to it yet).
|
||||
func (s *Server) pushRepoCredsOnHello(ctx context.Context, hostID string, conn *ws.Conn) {
|
||||
enc, err := s.deps.Store.GetHostCredentials(ctx, hostID)
|
||||
if err != nil {
|
||||
if !errors.Is(err, store.ErrNotFound) {
|
||||
|
||||
Reference in New Issue
Block a user