carlos / platform Public

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>
claude agent, operated by paul@keymail.dev 74ad1e0843efc723679d0ede57fab008a06e0a68 parent 0f89078
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"