apply: a scheduled step is a container run on a cadence (ADR 0053)
The recurring twin of run-once, one modifier over: a container marked schedule: "<cron>" is run to completion on its cadence, not started as a service and not run once as a gate. The gating rule is deliberately reversed. Installing a schedule records it as present state and reports the node current at once (applySchedule) -- it never runs the container and does not gate what follows. A Scheduler, held for the life of the daemon and re-established from each applied declaration (the declaration is the source of truth, ADR 0018), fires the container off an injected clock. A run that exits non-zero is logged and never fails the apply or flips the node's state, because it happens outside the apply and the store entirely. Runs never stack: a run still going when the next is due is skipped, not started as a second copy. No new host shape and no new action -- schedule is a string on the container the host already has, and the host process runs the container itself rather than installing a system timer (the rejected option 1). A minimal five-field cron (declaration/cron.go) validates on arrival and computes the next due minute; time is injected so the scheduler is tested without the wall clock. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
@@ -533,6 +533,18 @@ type Container struct {
|
||||
// the digest of this declaration, so a re-apply does not re-run it unless the declaration
|
||||
// changed.
|
||||
RunOnce bool `json:"run-once,omitempty"`
|
||||
|
||||
// Schedule marks a container the host runs on a recurring cadence — a five-field cron
|
||||
// expression (novox/hq ADR 0053). It is the recurring twin of RunOnce: the same container, run
|
||||
// to completion, but again and again on the clock rather than once. Installing it does not run
|
||||
// it — the schedule is state that is present, like a running service, so the apply is current as
|
||||
// soon as it is recorded and does NOT gate what follows. The host's scheduler fires the
|
||||
// container when the cron is due, re-established from this declaration each apply because the
|
||||
// declaration is the source of truth (ADR 0018). A run that exits non-zero is recorded and never
|
||||
// fails the apply or flips the node's state; a run still going when the next is due is skipped
|
||||
// rather than stacked. It is exclusive with RunOnce and with restart-on: a container runs once
|
||||
// and gates, runs on a cadence, or stays up — never two of these.
|
||||
Schedule string `json:"schedule,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Container) Identity() string { return c.ID }
|
||||
@@ -551,6 +563,23 @@ func (c *Container) validate(where string, _ bool) []string {
|
||||
problems = append(problems, where+": a run-once container cannot also declare restart-on; "+
|
||||
"it runs to completion rather than staying running to be restarted")
|
||||
}
|
||||
// A container runs once and gates, on a cadence, or stays up — never two of these
|
||||
// (novox/hq ADR 0053). run-once and schedule are the two "runs to completion" lifecycles and
|
||||
// contradict each other, and a scheduled step does not stay running to be brought back by
|
||||
// restart-on either. Refused here on arrival, as the control plane refuses it near its author.
|
||||
if c.RunOnce && c.Schedule != "" {
|
||||
problems = append(problems, where+": a container is run-once or scheduled, not both; "+
|
||||
"run-once runs once and gates what follows, a schedule runs it again on a cadence")
|
||||
}
|
||||
if c.Schedule != "" && len(c.RestartOn) > 0 {
|
||||
problems = append(problems, where+": a scheduled container cannot also declare restart-on; "+
|
||||
"it runs to completion on its cadence rather than staying running to be restarted")
|
||||
}
|
||||
if c.Schedule != "" {
|
||||
if _, err := ParseCron(c.Schedule); err != nil {
|
||||
problems = append(problems, where+": "+err.Error())
|
||||
}
|
||||
}
|
||||
return append(problems, checkImage(where, c.Image)...)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user