catalogue: carry and validate a container schedule (ADR 0053)
A container may declare schedule: "<cron>", the recurring twin of run-once. The resolver already carries a resource's keys through untouched, so schedule reaches the rendered host declaration on its own; what belongs here is refusing, near its author, what the host would otherwise refuse far away. The manifest parser refuses a schedule that is not a string, one that is not a well-formed five-field cron (cron.go: fields, ranges, *, comma, dash, slash), and the contradictory pair run-once + schedule -- a container runs once and gates, or on a cadence, or stays up, never two. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
@@ -734,23 +734,50 @@ func ParseManifest(raw []byte) (Manifest, error) {
|
||||
if fmt.Sprint(r["type"]) != "container" {
|
||||
continue
|
||||
}
|
||||
raw, present := r["run-once"]
|
||||
if !present {
|
||||
continue
|
||||
}
|
||||
once, ok := raw.(bool)
|
||||
if !ok {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s declares run-once on %v as a %T; run-once is true or false",
|
||||
m.Module, r["id"], raw))
|
||||
continue
|
||||
}
|
||||
if once {
|
||||
if _, hasRestart := r["restart-on"]; hasRestart {
|
||||
var runOnce bool
|
||||
if raw, present := r["run-once"]; present {
|
||||
once, ok := raw.(bool)
|
||||
if !ok {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s declares %v as run-once and with restart-on; a run-once step runs to "+
|
||||
"completion rather than staying running to be restarted (novox/hq ADR 0052)",
|
||||
m.Module, r["id"]))
|
||||
"%s declares run-once on %v as a %T; run-once is true or false",
|
||||
m.Module, r["id"], raw))
|
||||
} else {
|
||||
runOnce = once
|
||||
if once {
|
||||
if _, hasRestart := r["restart-on"]; hasRestart {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s declares %v as run-once and with restart-on; a run-once step runs to "+
|
||||
"completion rather than staying running to be restarted (novox/hq ADR 0052)",
|
||||
m.Module, r["id"]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// **A scheduled container runs on a recurring cadence** (novox/hq ADR 0053), the recurring
|
||||
// twin of run-once. The control plane carries the field to the host unchanged (the resolver
|
||||
// copies every key of a resource, so `schedule` reaches the rendered declaration on its own);
|
||||
// what belongs here is refusing, near its author, a value the host would only refuse far away.
|
||||
// Three things: a value that is not a string, a string that is not a valid cron, and the pair
|
||||
// run-once + schedule — a container runs once, or on a cadence, or stays up, never two.
|
||||
if raw, present := r["schedule"]; present {
|
||||
cron, ok := raw.(string)
|
||||
switch {
|
||||
case !ok:
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s declares schedule on %v as a %T; a schedule is a five-field cron string",
|
||||
m.Module, r["id"], raw))
|
||||
default:
|
||||
if err := validateCron(cron); err != nil {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s declares schedule %q on %v, which is not a valid cron: %v",
|
||||
m.Module, cron, r["id"], err))
|
||||
}
|
||||
if runOnce {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s declares %v as both run-once and schedule; a container runs once and "+
|
||||
"gates, or on a cadence, or stays up — never two (novox/hq ADR 0053)",
|
||||
m.Module, r["id"]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user