console: readOfflineEvidence takes the move it reads for and drops a report that fails its contextual rules
The two callers (the step doors and the view) pass the move they hold; moves.ValidateOfflineReportFor drops, with a log line, a report from a box the move did not pin, a self-serve report without its payload, or a request-bound report more than 60 s in the future. nil is the legacy validation alone.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 file changed,
+13
−4
internal/console/apiinstancemove_offline.go+13 −4
diff --git a/internal/console/apiinstancemove_offline.go b/internal/console/apiinstancemove_offline.go| index 57c63db46..8d7b3a50f 100644 |
| --- a/internal/console/apiinstancemove_offline.go |
| +++ b/internal/console/apiinstancemove_offline.go |
| @@ -213,8 +213,13 @@ func (e offlineEvidence) byRole(role string) []offlineReport { |
| // readOfflineEvidence lists and reads every report of the move. A report |
| // that does not parse, fails moves.ValidateOfflineReport, or does not sit at |
| // the key its own fields name is not evidence and is skipped, which can only |
| -// make a door refuse. A listing or a read that fails is the error. |
| -func (s *Server) readOfflineEvidence(ctx context.Context, host, id string) (offlineEvidence, error) { |
| +// make a door refuse; so is one that fails the contextual rules for m, the |
| +// move the caller holds (moves.ValidateOfflineReportFor: the pinned |
| +// participants' identity, a self-serve move's role, phase and payload, and |
| +// a request-bound report's future skew), logged. m nil is the legacy |
| +// validation alone. A listing or a read that fails is the error. |
| +func (s *Server) readOfflineEvidence(ctx context.Context, host, id string, m *provision.MoveState) (offlineEvidence, error) { |
| + now := time.Now() |
| prefix := moves.OfflineReportPrefix(host, id) |
| if prefix == "" { |
| return offlineEvidence{}, nil |
| @@ -242,6 +247,10 @@ func (s *Server) readOfflineEvidence(ctx context.Context, host, id string) (offl |
| log.Printf("console: offline move %s: ignoring %s: not a report of this move", host, key) |
| continue |
| } |
| + if err := moves.ValidateOfflineReportFor(rep, m, now); err != nil { |
| + log.Printf("console: offline move %s: ignoring %s: %v", host, key, err) |
| + continue |
| + } |
| ev.reports = append(ev.reports, offlineReport{OfflineReport: rep, raw: raw}) |
| } |
| sort.Slice(ev.reports, func(i, j int) bool { |
| @@ -1253,7 +1262,7 @@ func (s *Server) offlineVerbDoor(verb string) http.HandlerFunc { |
| return |
| } |
| ctx := r.Context() |
| - ev, err := s.readOfflineEvidence(ctx, d.host, mv.ID) |
| + ev, err := s.readOfflineEvidence(ctx, d.host, mv.ID, mv) |
| if err != nil { |
| log.Printf("console: offline move %s: reports: %v", d.host, err) |
| jsonError(w, http.StatusBadGateway, "could not read the move's reports. try again") |
| @@ -1426,7 +1435,7 @@ func (s *Server) offlineViewOf(ctx context.Context, rec *provision.Record) offli |
| case provision.OfflineQueued, provision.OfflineStaging, provision.OfflineStopping, provision.OfflineSealing, provision.OfflineSealed, provision.OfflineLanding, provision.OfflineLanded: |
| v.CanAbort = true |
| } |
| - ev, err := s.readOfflineEvidence(ctx, rec.Host, mv.ID) |
| + ev, err := s.readOfflineEvidence(ctx, rec.Host, mv.ID, mv) |
| if err != nil { |
| log.Printf("console: offline move %s: reading the reports: %v", rec.Host, err) |
| v.Waiting = "could not read the move's reports. try again" |