T2.3: Split domain.Job (durable) from domain.JobRuntime (transient)
Move all transient execution state off domain.Job into a new domain.JobRuntime, keyed by job ID: - domain: Job now holds only durable YAML fields; remove the yaml:"-" fields (LastRun/NextRun/LastState/Logs/Output) and NextDue. Add runtime.go with JobRuntime plus NewRuntime/NewRuntimes constructors, which now own the runtime-init logic moved out of normalizeJobs. - runner: RunJob no longer mutates the job; it is pure and returns the RunRecord for the caller to fold into the runtime. - scheduler: take a shared map[int]*JobRuntime and route status/next-run bookkeeping through runtimeFor(job); prepareNextRun writes a *JobRuntime. - storage: normalizeJobs touches only durable config. - gui: own the runtime map (NewRuntimes), share it with the scheduler, and read/write runtime state via runtimeFor; maintain the map by ID on add/edit/delete. - tests: update scheduler/storage tests to the split; tidy a pre-existing import-order nit in scheduler.go. This also satisfies T2.4 (storage load/save only Job, runtime init in domain.NewRuntime, round-trip tests), since removing the fields forced it. Runtime-map ownership remains GUI-side glue until T3.1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+14
-26
@@ -1,30 +1,18 @@
|
||||
package domain
|
||||
|
||||
import "time"
|
||||
|
||||
// Job is the user-visible scheduled command.
|
||||
//
|
||||
// Fields with yaml:"-" are deliberately runtime-only. They are useful in the GUI
|
||||
// while GoSentry is running, but writing them to jobs.yaml would make the jobs
|
||||
// file noisy and would mix durable configuration with transient execution state.
|
||||
// Job is the user-visible scheduled command. It contains only durable
|
||||
// configuration: every field is persisted to jobs.yaml. Transient execution
|
||||
// state (last run, next run, command output, in-memory activity) lives in a
|
||||
// separate JobRuntime so the jobs file stays a clean, hand-editable record of
|
||||
// configuration and never mixes in process-lifetime bookkeeping.
|
||||
type Job struct {
|
||||
ID int `yaml:"id"`
|
||||
Name string `yaml:"name"`
|
||||
Folder string `yaml:"folder,omitempty"`
|
||||
Schedule string `yaml:"schedule"`
|
||||
Command string `yaml:"command"`
|
||||
Arguments string `yaml:"arguments,omitempty"`
|
||||
SuccessExitCodes string `yaml:"success_exit_codes,omitempty"`
|
||||
StartOnly bool `yaml:"start_only,omitempty"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
LastRun string `yaml:"-"`
|
||||
NextRun string `yaml:"-"`
|
||||
LastState string `yaml:"-"`
|
||||
Logs []RunRecord `yaml:"-"`
|
||||
Output string `yaml:"-"`
|
||||
|
||||
// NextDue is kept as time.Time for scheduler comparisons. The formatted
|
||||
// NextRun string above exists only for display in the GUI and YAML rewriting
|
||||
// must not persist it.
|
||||
NextDue time.Time `yaml:"-"`
|
||||
ID int `yaml:"id"`
|
||||
Name string `yaml:"name"`
|
||||
Folder string `yaml:"folder,omitempty"`
|
||||
Schedule string `yaml:"schedule"`
|
||||
Command string `yaml:"command"`
|
||||
Arguments string `yaml:"arguments,omitempty"`
|
||||
SuccessExitCodes string `yaml:"success_exit_codes,omitempty"`
|
||||
StartOnly bool `yaml:"start_only,omitempty"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package domain
|
||||
|
||||
import "time"
|
||||
|
||||
// JobRuntime is the transient execution state for a Job. It is never written to
|
||||
// jobs.yaml: it is rebuilt from scratch each time GoSentry starts and is held in
|
||||
// memory keyed by Job.ID for the lifetime of the process. Keeping it separate
|
||||
// from Job is what lets the durable configuration file stay free of run records,
|
||||
// status strings, and scheduling bookkeeping.
|
||||
type JobRuntime struct {
|
||||
LastRun string
|
||||
NextRun string
|
||||
LastState string
|
||||
Output string
|
||||
Logs []RunRecord
|
||||
|
||||
// NextDue is the next scheduled execution time, kept as time.Time for
|
||||
// scheduler comparisons. NextRun above is its formatted display string and is
|
||||
// the only form shown in the GUI.
|
||||
NextDue time.Time
|
||||
}
|
||||
|
||||
// NewRuntime builds the initial runtime state for a freshly loaded or created
|
||||
// job. Enabled jobs start "Ready" and wait for the scheduler to compute their
|
||||
// first run; disabled jobs start "Paused".
|
||||
func NewRuntime(job Job) *JobRuntime {
|
||||
runtime := &JobRuntime{
|
||||
LastRun: "Never",
|
||||
Output: "No command output captured yet.",
|
||||
}
|
||||
if job.Enabled {
|
||||
runtime.LastState = "Ready"
|
||||
runtime.NextRun = "After start"
|
||||
} else {
|
||||
runtime.LastState = "Paused"
|
||||
runtime.NextRun = "Paused"
|
||||
}
|
||||
return runtime
|
||||
}
|
||||
|
||||
// NewRuntimes builds a runtime map for a slice of jobs, keyed by Job.ID. It is
|
||||
// the convenience entry point used when a whole jobs file has just been loaded.
|
||||
func NewRuntimes(jobs []Job) map[int]*JobRuntime {
|
||||
runtimes := make(map[int]*JobRuntime, len(jobs))
|
||||
for _, job := range jobs {
|
||||
runtimes[job.ID] = NewRuntime(job)
|
||||
}
|
||||
return runtimes
|
||||
}
|
||||
Reference in New Issue
Block a user