Operator actions in the report reply (D1, R-314/R-279/R-177, decision 185)
The hub may ask the running controller for a CLOSED list of actions, carried in the report ACK (operator_actions) and answered on the next report (operator_action_results): offsite_backup_now, abandon_stop, abandon_extend (1-30 days), run_job (fill-watch, offsite-integrity, offsite-proof, disk-health-check). Unknown action/job/argument -> refused, nothing called. Once per id (in memory; every action is safe to repeat). - internal/report/opactions.go: the executor; results re-sent until the hub stops listing the id. - scheduler.RunNow: refuses unknown / already-running jobs; OnDemand(ctx) makes an operator's offsite-integrity run even when not due. - ExtendAbandon never shortens the countdown and refuses in the hub phase; StopAbandon reports failure when the hub cancel failed (it said success). - Report ACK read cap 4 KiB -> 64 KiB (an ACK over the cap dropped every field in it). 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:
@@ -1077,6 +1077,15 @@ func main() {
|
||||
RecordEscrowKeyHash: sett.SetHubEscrowKeySHA256,
|
||||
Logger: logger,
|
||||
}
|
||||
// R-314/R-279/R-177 (`09` §3 decision 185): the operator's closed list of actions, carried in the
|
||||
// report reply and run in THIS process (opactions_wiring.go). The result rides the next report;
|
||||
// a finished result fires an out-of-cycle report through the trigger once it exists.
|
||||
opActions := report.NewOperatorActions(ctx, operatorActionHandlers(backupMgr, sched, func() {
|
||||
if f, ok := opActionReportFire.Load().(func()); ok && f != nil {
|
||||
f()
|
||||
}
|
||||
}, logger), logger)
|
||||
report.SetOperatorActions(opActions)
|
||||
// Wire hub verification: update settings when hub reports customer status
|
||||
hubPusher.OnPushResponse = func(resp *report.PushResponse) {
|
||||
if resp.CustomerBlocked {
|
||||
@@ -1120,6 +1129,8 @@ func main() {
|
||||
// generation). The web gate reads it on the next request — no restart needed.
|
||||
claimSync := &report.ClaimSync{Settings: sett, Logger: logger}
|
||||
claimSync.Reconcile(resp.Claim)
|
||||
// Decision 185: the operator's pending actions (each acted on once per id).
|
||||
opActions.Reconcile(resp.OperatorActions)
|
||||
}
|
||||
// Wire hub push status into alert manager for dashboard alerts
|
||||
alertMgr.SetHubPushStatus(func() web.HubPushStatusData {
|
||||
@@ -1496,7 +1507,9 @@ func main() {
|
||||
// collision costs one skipped day, not a missed check, because due-ness makes tomorrow try
|
||||
// again. What would be a real defect is a slot that collides EVERY night; this is not one.
|
||||
sched.Daily("offsite-integrity", "06:00", func(ctx context.Context) error {
|
||||
runOffsiteIntegrityCheck(ctx, backupMgr, notifier, logger, false)
|
||||
// Decision 185: an operator's run_job runs the check even when it is not due — the operator
|
||||
// asked for a check, not for „is a check due". The schedule still checks due-ness.
|
||||
runOffsiteIntegrityCheck(ctx, backupMgr, notifier, logger, scheduler.OnDemand(ctx))
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -1811,6 +1824,7 @@ func main() {
|
||||
}
|
||||
reportTrigger = report.NewTrigger(fireReport, logger)
|
||||
go reportTrigger.Run(ctx)
|
||||
opActionReportFire.Store(reportTrigger.Fire)
|
||||
|
||||
// Direction-2 immediate-sync (v0.140.0): hold a hanging GET against the hub's wait channel
|
||||
// and fire the trigger the instant operator intent moves — so an operator save round-trips in
|
||||
|
||||
Reference in New Issue
Block a user