Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
50f5bb6
feat(schedule): record run state in both runner modes and stop treati…
vishr Sep 5, 2026
f138dfa
feat(schedule): finalise one journal record per scheduled run from Ex…
vishr Sep 5, 2026
62af206
feat(schedule): read run records, timer state and per-run logs from t…
vishr Sep 5, 2026
98e559d
feat(status): read scheduled-run outcomes, attempts and next elapse f…
vishr Sep 5, 2026
5fa60a8
docs: implementation plan for scheduled-job runs (#155)
vishr Sep 5, 2026
92f0a4b
feat(cli): ob schedule list, history and logs read the host's run rec…
vishr Sep 5, 2026
26035dc
feat(schema): schedule.retry and schedule.notify with validation agai…
vishr Sep 5, 2026
2e4e263
feat(schedule): bounded retry in the runner and per-outcome notificat…
vishr Sep 5, 2026
6d7f375
feat(schema): declared inputs for scheduled jobs, rendered as environ…
vishr Sep 5, 2026
39f8880
feat(schedule): manual activations consume declared inputs; hosts nee…
vishr Sep 5, 2026
f10a651
feat(schedule): operator-initiated runs with validated inputs, journa…
vishr Sep 5, 2026
1e818f8
feat(cli): ob schedule run starts a scheduled job now with validated …
vishr Sep 5, 2026
4525c5b
docs(site): retry, run records, notify and manual runs for scheduled …
vishr Sep 5, 2026
3c2a092
fix(schedule): guard the cleanup trap before the state path is set
vishr Sep 5, 2026
573eb52
fix(schedule): write the run record with explicit journal fields and …
vishr Sep 5, 2026
4042679
refactor(schedule): the run record is the only verdict
vishr Sep 5, 2026
48cc3f1
fix(schedule): act on the local review
vishr Sep 5, 2026
b4762c8
fix(schedule): act on the Copilot review
vishr Sep 6, 2026
e09ce67
fix(schedule): act on the second local review
vishr Sep 6, 2026
5bf92a1
fix(schedule): act on the second Copilot review
vishr Sep 6, 2026
ec1cdc9
fix(schedule): act on the third local review
vishr Sep 6, 2026
d8d94e9
fix(schedule): record a stand-aside without touching the running job'…
vishr Sep 6, 2026
bcac161
fix(schema): give a list field a list default
vishr Sep 6, 2026
aad9230
fix(schedule): name the stand-aside note the same on both sides
vishr Sep 6, 2026
342820f
fix(schedule): report a manual run's own failure, and stop guessing a…
vishr Sep 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions cmd/ob-docgen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,17 @@ func defaultOf(node map[string]any) string {
if !ok {
return ""
}
// A list default is an array in the schema, because that is the only
// thing a JSON Schema default for an array property may be. Printing it
// with Go's slice syntax puts `[failure timeout]` in a reference table
// nobody writes that way; the table reads as the value would be written.
if items, ok := v.([]any); ok {
parts := make([]string, 0, len(items))
for _, item := range items {
parts = append(parts, fmt.Sprint(item))
}
return strings.Join(parts, ", ")
}
return fmt.Sprint(v)
}

Expand Down
24 changes: 2 additions & 22 deletions cmd/ob/ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,28 +64,8 @@ func addOpsCommands(root *cobra.Command, g *globalFlags) {
proxyCmd.AddCommand(proxyApplyCmd)
root.AddCommand(proxyCmd)

// schedule apply — explicitly reconcile runner-owned host timers after an
// Onebox upgrade, without coupling package installation to remote mutation.
scheduleCmd := &cobra.Command{Use: "schedule", Short: "manage host timers for scheduled jobs",
Long: "Manage the systemd timers generated for scheduled jobs.\n\n" +
"Timers outlive the Onebox process and the package installed on the operator\n" +
"workstation. `apply` explicitly reconciles their units after a runner or\n" +
"configuration change without deploying a release.",
Args: cobra.NoArgs, RunE: showCommandHelp}
var scheduleBreakLock bool
scheduleApplyCmd := &cobra.Command{
Use: "apply",
Short: "reconcile scheduled-job units without deploying a release",
Long: "Converge every declared scheduled-job timer, service, runner, and failure notifier to what the current Onebox runner generates.\n\nTaken under the application lock and fence so a deploy or host-fired job cannot modify the same runtime concurrently. This is the explicit post-upgrade path; upgrading the local package never mutates a remote host by itself.",
RunE: func(cmd *cobra.Command, _ []string) error {
return runMutation(cmd, g, onebox.ExecuteRequest{
Kind: onebox.KindScheduleApply, BreakLock: scheduleBreakLock,
}, "schedule apply")
},
}
scheduleApplyCmd.Flags().BoolVar(&scheduleBreakLock, "break-lock", false, "break a stale operation lock after inspecting its holder")
scheduleCmd.AddCommand(scheduleApplyCmd)
root.AddCommand(scheduleCmd)
// schedule apply | list | history | logs — see schedule.go.
addScheduleCommands(root, g)

// secrets list | edit | push
secretsCmd := &cobra.Command{Use: "secrets", Short: "SOPS-encrypted secrets",
Expand Down
78 changes: 41 additions & 37 deletions cmd/ob/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,43 +76,47 @@ const (
// Cobra's native human output. Keeping this list closed makes adding a command
// an explicit CLI-contract decision instead of silently inheriting behavior.
var cliOutputMatrix = map[string]cliOutputClass{
"ob abort": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob approve": {Class: cliClassFiniteEnvelope, JSON: true},
"ob audit": {Class: cliClassFiniteEnvelope, JSON: true},
"ob bootstrap": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob canonical": {Class: cliClassFiniteEnvelope, JSON: true},
"ob deploy": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob destroy": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob doctor": {Class: cliClassFiniteEnvelope, JSON: true},
"ob eject": {Class: cliClassFiniteEnvelope, JSON: true},
"ob exec": {Class: cliClassOperatorPassthrough, NDJSON: true},
"ob init": {Class: cliClassFiniteEnvelope, JSON: true},
"ob backup create": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob backup enable": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob backup disable": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob backup drill": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob backup restore": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob backup prune": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob backup verify": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob backup status": {Class: cliClassFiniteEnvelope, JSON: true},
"ob job plan": {Class: cliClassFiniteEnvelope, JSON: true},
"ob job run": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob logs": {Class: cliClassOperatorPassthrough, JSON: true, NDJSON: true},
"ob plan": {Class: cliClassFiniteEnvelope, JSON: true},
"ob preflight": {Class: cliClassFiniteEnvelope, JSON: true},
"ob preview": {Class: cliClassFiniteEnvelope, JSON: true},
"ob proxy apply": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob resume": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob rollback": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob schedule apply": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob schema": {Class: cliClassFiniteEnvelope, JSON: true},
"ob secrets edit": {Class: cliClassTrustedEditor, JSON: true},
"ob secrets list": {Class: cliClassFiniteEnvelope, JSON: true},
"ob secrets push": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob service apply": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob status": {Class: cliClassFiniteEnvelope, JSON: true},
"ob validate": {Class: cliClassFiniteEnvelope, JSON: true},
"ob version": {Class: cliClassFiniteEnvelope, JSON: true},
"ob abort": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob approve": {Class: cliClassFiniteEnvelope, JSON: true},
"ob audit": {Class: cliClassFiniteEnvelope, JSON: true},
"ob bootstrap": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob canonical": {Class: cliClassFiniteEnvelope, JSON: true},
"ob deploy": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob destroy": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob doctor": {Class: cliClassFiniteEnvelope, JSON: true},
"ob eject": {Class: cliClassFiniteEnvelope, JSON: true},
"ob exec": {Class: cliClassOperatorPassthrough, NDJSON: true},
"ob init": {Class: cliClassFiniteEnvelope, JSON: true},
"ob backup create": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob backup enable": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob backup disable": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob backup drill": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob backup restore": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob backup prune": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob backup verify": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob backup status": {Class: cliClassFiniteEnvelope, JSON: true},
"ob job plan": {Class: cliClassFiniteEnvelope, JSON: true},
"ob job run": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob logs": {Class: cliClassOperatorPassthrough, JSON: true, NDJSON: true},
"ob plan": {Class: cliClassFiniteEnvelope, JSON: true},
"ob preflight": {Class: cliClassFiniteEnvelope, JSON: true},
"ob preview": {Class: cliClassFiniteEnvelope, JSON: true},
"ob proxy apply": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob resume": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob rollback": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob schedule apply": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob schedule history": {Class: cliClassFiniteEnvelope, JSON: true},
"ob schedule list": {Class: cliClassFiniteEnvelope, JSON: true},
"ob schedule logs": {Class: cliClassOperatorPassthrough, JSON: true, NDJSON: true},
"ob schedule run": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob schema": {Class: cliClassFiniteEnvelope, JSON: true},
"ob secrets edit": {Class: cliClassTrustedEditor, JSON: true},
"ob secrets list": {Class: cliClassFiniteEnvelope, JSON: true},
"ob secrets push": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob service apply": {Class: cliClassFiniteStream, JSON: true, NDJSON: true},
"ob status": {Class: cliClassFiniteEnvelope, JSON: true},
"ob validate": {Class: cliClassFiniteEnvelope, JSON: true},
"ob version": {Class: cliClassFiniteEnvelope, JSON: true},
}

type cliExitError struct {
Expand Down
78 changes: 41 additions & 37 deletions cmd/ob/output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,43 +490,47 @@ workloads:

func TestLeafOutputMatrixIsClosedAndHasNoAliases(t *testing.T) {
wantMatrix := map[string]cliOutputClass{
"ob abort": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob approve": {Class: "finite_envelope", JSON: true},
"ob audit": {Class: "finite_envelope", JSON: true},
"ob bootstrap": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob canonical": {Class: "finite_envelope", JSON: true},
"ob deploy": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob destroy": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob doctor": {Class: "finite_envelope", JSON: true},
"ob eject": {Class: "finite_envelope", JSON: true},
"ob exec": {Class: "operator_passthrough", NDJSON: true},
"ob init": {Class: "finite_envelope", JSON: true},
"ob job plan": {Class: "finite_envelope", JSON: true},
"ob backup create": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob backup enable": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob backup disable": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob backup drill": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob backup restore": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob backup prune": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob backup verify": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob backup status": {Class: "finite_envelope", JSON: true},
"ob job run": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob logs": {Class: "operator_passthrough", JSON: true, NDJSON: true},
"ob plan": {Class: "finite_envelope", JSON: true},
"ob preflight": {Class: "finite_envelope", JSON: true},
"ob preview": {Class: "finite_envelope", JSON: true},
"ob proxy apply": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob resume": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob rollback": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob schedule apply": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob schema": {Class: "finite_envelope", JSON: true},
"ob secrets edit": {Class: "trusted_editor", JSON: true},
"ob secrets list": {Class: "finite_envelope", JSON: true},
"ob secrets push": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob service apply": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob status": {Class: "finite_envelope", JSON: true},
"ob validate": {Class: "finite_envelope", JSON: true},
"ob version": {Class: "finite_envelope", JSON: true},
"ob abort": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob approve": {Class: "finite_envelope", JSON: true},
"ob audit": {Class: "finite_envelope", JSON: true},
"ob bootstrap": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob canonical": {Class: "finite_envelope", JSON: true},
"ob deploy": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob destroy": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob doctor": {Class: "finite_envelope", JSON: true},
"ob eject": {Class: "finite_envelope", JSON: true},
"ob exec": {Class: "operator_passthrough", NDJSON: true},
"ob init": {Class: "finite_envelope", JSON: true},
"ob job plan": {Class: "finite_envelope", JSON: true},
"ob backup create": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob backup enable": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob backup disable": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob backup drill": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob backup restore": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob backup prune": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob backup verify": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob backup status": {Class: "finite_envelope", JSON: true},
"ob job run": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob logs": {Class: "operator_passthrough", JSON: true, NDJSON: true},
"ob plan": {Class: "finite_envelope", JSON: true},
"ob preflight": {Class: "finite_envelope", JSON: true},
"ob preview": {Class: "finite_envelope", JSON: true},
"ob proxy apply": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob resume": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob rollback": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob schedule apply": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob schedule history": {Class: "finite_envelope", JSON: true},
"ob schedule list": {Class: "finite_envelope", JSON: true},
"ob schedule logs": {Class: "operator_passthrough", JSON: true, NDJSON: true},
"ob schedule run": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob schema": {Class: "finite_envelope", JSON: true},
"ob secrets edit": {Class: "trusted_editor", JSON: true},
"ob secrets list": {Class: "finite_envelope", JSON: true},
"ob secrets push": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob service apply": {Class: "finite_stream", JSON: true, NDJSON: true},
"ob status": {Class: "finite_envelope", JSON: true},
"ob validate": {Class: "finite_envelope", JSON: true},
"ob version": {Class: "finite_envelope", JSON: true},
}
if !reflect.DeepEqual(cliOutputMatrix, wantMatrix) {
t.Fatalf("CLI output matrix changed:\ngot %#v\nwant %#v", cliOutputMatrix, wantMatrix)
Expand Down
Loading