Skip to content

Commit 96b791f

Browse files
committed
Phase 1: add spotSchedule to CRD and move to v1beta1
Signed-off-by: Erick Bourgeois <erick@jeb.ca>
1 parent 6733d81 commit 96b791f

20 files changed

Lines changed: 2003 additions & 60 deletions

.claude/CHANGELOG.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,67 @@ The format is based on the regulated environment requirements:
99

1010
---
1111

12+
## [2026-06-13 16:30] - Spot-schedule provider API: CRD scaffolding + ScheduledMachine v1beta1 (Phase 1)
13+
14+
**Author:** Erick Bourgeois
15+
16+
### Changed
17+
- `src/crd.rs`: `ScheduledMachine` bumped to **`v1beta1`** (storage) — `schedule`
18+
is now `Option<ScheduleSpec>` and a new `spot_schedule: Option<SpotScheduleRef>`
19+
field is added (ADR 0006). New `SpotScheduleRef` type (group-pinned `apiVersion`
20+
via `x-kubernetes-validations` CEL, `deny_unknown_fields`, same-namespace
21+
`name`). New `ScheduledMachineSpec::effective_schedule()` accessor +
22+
`ScheduleSpec::inactive_placeholder()` (a spotSchedule-only machine evaluates
23+
inactive until the Phase 2 resolver lands). New frozen, **deprecated**
24+
`pub mod v1alpha1` ScheduledMachine (served for back-compat; superset relation
25+
keeps `conversion: None` lossless — ADR 0007). New reference provider type
26+
`CapitalMarketsSchedule` (group `spotschedules.5spot.finos.org/v1alpha1`):
27+
spec (timezone/sessions/holidays/earlyCloses) + duck-typed
28+
`status.active`/conditions/observedGeneration.
29+
- `src/constants.rs`: `API_VERSION`/`API_VERSION_FULL``v1beta1` (controller's
30+
storage version, used for owner refs); new `API_VERSION_LEGACY`,
31+
`SPOT_SCHEDULE_API_GROUP`, `SPOT_SCHEDULE_API_VERSION`,
32+
`CONDITION_TYPE_SPOT_SCHEDULE_RESOLVED`, and the four
33+
`REASON_SPOT_SCHEDULE_*` resolution reasons.
34+
- `src/bin/crdgen.rs`: rewritten to `merge_crds(v1alpha1, v1beta1, storage=v1beta1)`
35+
for `ScheduledMachine` (+ inject the at-least-one-of `schedule`/`spotSchedule`
36+
CEL into the v1beta1 spec schema) and emit the `CapitalMarketsSchedule` CRD;
37+
now writes both files under `deploy/crds/` directly. `Makefile` `crds` target
38+
updated (no stdout redirect).
39+
- `src/bin/crddoc.rs`: documents the v1beta1/v1alpha1 versioning, the
40+
`spotSchedule` field, and the at-least-one-of rule; `docs/src/reference/api.md`
41+
regenerated.
42+
- `src/reconcilers/scheduled_machine.rs`: route `spec.schedule` reads through
43+
`effective_schedule()` (optional schedule); call the new
44+
`validate_activation_source` in `reconcile_inner`'s defence-in-depth block.
45+
- `src/reconcilers/helpers.rs`: new `validate_activation_source` — runtime mirror
46+
of the CRD spec-level CEL (at least one of `schedule`/`spotSchedule`, plus the
47+
`spotschedules.5spot.finos.org` group pin), so the invariant holds even
48+
without the apiserver CEL (pre-1.25, pre-existing stored objects, direct API
49+
writes). The CRD CEL is structural-schema validation (apiserver-enforced), not
50+
a VAP — this is the belt-and-braces runtime guard.
51+
- `deploy/crds/scheduledmachine.yaml` (multi-version, regenerated) +
52+
`deploy/crds/capitalmarketsschedule.yaml` (NEW).
53+
- `examples/scheduledmachine-spot-schedule.yaml`,
54+
`examples/capitalmarketsschedule.yaml`: NEW.
55+
- Tests: `src/crd_tests.rs` adds SpotScheduleRef round-trip/group-pin,
56+
effective_schedule, spotSchedule-only round-trip, multi-version single-storage
57+
invariant, and CapitalMarketsSchedule coverage; all SM-spec literals across
58+
`*_tests.rs` + `tests/` updated for `Option` schedule + `spot_schedule`.
59+
- Docs: `docs/src/installation/crds.md`, `docs/src/reference/spot-schedule-contract.md`.
60+
61+
### Why
62+
Phase 1 of the spot-schedule provider roadmap: introduce the `spec.spotSchedule`
63+
contract surface and the reference provider CRD, landed on a new `ScheduledMachine`
64+
`v1beta1` version (first exercise of the ADR 0007 multi-version machinery). The
65+
controller-side resolver/watch follow in later phases.
66+
67+
### Impact
68+
- [ ] Breaking change
69+
- [x] Requires cluster rollout (apply regenerated CRDs: `kubectl apply -f deploy/crds/`)
70+
- [ ] Config change only
71+
- [ ] Documentation only
72+
1273
## [2026-06-13 14:00] - ADR 0006/0007 + CALM: pluggable spot-schedule provider contract (Phase 0)
1374

1475
**Author:** Erick Bourgeois

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,8 @@ run-local: ## Run operator locally
174174

175175
crds: ## Generate CRD YAML files from Rust types
176176
@echo "Generating CRD YAML files from src/crd.rs..."
177-
@cargo run --quiet --bin crdgen > deploy/crds/scheduledmachine.yaml
178-
@echo "✓ CRD YAML file generated: deploy/crds/scheduledmachine.yaml"
177+
@cargo run --quiet --bin crdgen
178+
@echo "✓ CRD YAML files generated under deploy/crds/"
179179

180180
crddoc: ## Generate API documentation from CRD types
181181
@echo "Generating API documentation..."
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
apiVersion: apiextensions.k8s.io/v1
2+
kind: CustomResourceDefinition
3+
metadata:
4+
name: capitalmarketsschedules.spotschedules.5spot.finos.org
5+
spec:
6+
group: spotschedules.5spot.finos.org
7+
names:
8+
categories: []
9+
kind: CapitalMarketsSchedule
10+
plural: capitalmarketsschedules
11+
shortNames:
12+
- cms
13+
singular: capitalmarketsschedule
14+
scope: Namespaced
15+
versions:
16+
- additionalPrinterColumns:
17+
- jsonPath: .status.active
18+
name: Active
19+
type: boolean
20+
- jsonPath: .spec.timezone
21+
name: Timezone
22+
type: string
23+
- jsonPath: .status.nextTransitionTime
24+
name: NextTransition
25+
type: string
26+
- jsonPath: .metadata.creationTimestamp
27+
name: Age
28+
type: date
29+
name: v1alpha1
30+
schema:
31+
openAPIV3Schema:
32+
description: Auto-generated derived type for CapitalMarketsScheduleSpec via `CustomResource`
33+
properties:
34+
spec:
35+
description: |-
36+
Reference spot-schedule provider (ADR 0006) modelling a capital-markets
37+
**exchange calendar**: trading sessions, statutory holidays, and early
38+
closes evaluated in a configured timezone. The provider controller (roadmap
39+
Phase 5) reconciles this `spec` into the duck-typed
40+
[`status.active`](CapitalMarketsScheduleStatus::active) boolean that a
41+
`ScheduledMachine.spec.spotSchedule` consumes; 5-Spot's controller reads
42+
only that status, never this spec.
43+
properties:
44+
earlyCloses:
45+
default: []
46+
description: Early-close overrides for specific dates.
47+
items:
48+
description: |-
49+
An early-close override: on `date` the market closes at the end of
50+
`closeHour` instead of its normal session end.
51+
properties:
52+
closeHour:
53+
description: Last active hour (0–23) on that date.
54+
maximum: 23.0
55+
minimum: 0.0
56+
type: integer
57+
date:
58+
description: Calendar date in `YYYY-MM-DD` (ISO-8601) form.
59+
pattern: ^[0-9]{4}-[0-9]{2}-[0-9]{2}$
60+
type: string
61+
required:
62+
- closeHour
63+
- date
64+
type: object
65+
type: array
66+
holidays:
67+
default: []
68+
description: |-
69+
Calendar dates (`YYYY-MM-DD`) on which the market is fully closed,
70+
overriding any session.
71+
items:
72+
pattern: ^[0-9]{4}-[0-9]{2}-[0-9]{2}$
73+
type: string
74+
maxItems: 366
75+
type: array
76+
sessions:
77+
default: []
78+
description: |-
79+
Trading sessions during which the market is open. Empty means the
80+
provider is never active on the session axis (still subject to
81+
holidays / early closes).
82+
items:
83+
description: |-
84+
A single trading session window, expressed with the same day/hour range
85+
syntax as [`ScheduleSpec`] (`mon-fri`, `9-17`, …). The market is "in
86+
session" when the current time falls inside any session.
87+
properties:
88+
daysOfWeek:
89+
default: []
90+
description: Days of the week this session runs (e.g. `["mon-fri"]`).
91+
items:
92+
type: string
93+
type: array
94+
hoursOfDay:
95+
default: []
96+
description: Hours of the day this session is open (e.g. `["9-16"]`).
97+
items:
98+
type: string
99+
type: array
100+
type: object
101+
type: array
102+
timezone:
103+
default: UTC
104+
description: |-
105+
IANA timezone the sessions, holidays, and early closes are evaluated in
106+
(e.g. `America/New_York`). Defaults to `UTC`.
107+
maxLength: 64
108+
pattern: ^[A-Za-z][A-Za-z0-9_+\-/]*$
109+
type: string
110+
type: object
111+
status:
112+
description: |-
113+
Runtime status of a [`CapitalMarketsSchedule`]. Satisfies the spot-schedule
114+
provider contract (ADR 0006): the authoritative signal is
115+
[`active`](Self::active); `conditions[type=Ready]`, `observedGeneration`,
116+
and `lastTransitionTime` are the recommended observability surface.
117+
nullable: true
118+
properties:
119+
active:
120+
default: false
121+
description: |-
122+
**The contract field.** `true` ⇒ a `ScheduledMachine` referencing this
123+
object should be active; `false` ⇒ it should be inactive.
124+
type: boolean
125+
conditions:
126+
default: []
127+
description: |-
128+
Standard Kubernetes conditions. A `Ready` condition is recommended so
129+
consumers can distinguish "computed and current" from "stale/unhealthy"
130+
(the latter is treated as *unresolved* by 5-Spot, not inactive).
131+
items:
132+
properties:
133+
lastTransitionTime:
134+
description: Last transition time (RFC3339 format)
135+
type: string
136+
message:
137+
description: Human-readable message
138+
type: string
139+
reason:
140+
description: One-word reason in CamelCase
141+
type: string
142+
status:
143+
description: 'Status: "True", "False", or "Unknown"'
144+
enum:
145+
- 'True'
146+
- 'False'
147+
- Unknown
148+
type: string
149+
type:
150+
description: Type of condition (e.g., "Ready", "`MachineReady`", "`ReferencesValid`")
151+
type: string
152+
required:
153+
- lastTransitionTime
154+
- message
155+
- reason
156+
- status
157+
- type
158+
type: object
159+
type: array
160+
lastTransitionTime:
161+
description: When `active` last transitioned (RFC3339).
162+
nullable: true
163+
type: string
164+
nextTransitionTime:
165+
description: |-
166+
When `active` is next expected to transition (RFC3339) — the next
167+
session/holiday boundary. Lets the provider requeue exactly at the
168+
boundary instead of polling.
169+
nullable: true
170+
type: string
171+
observedGeneration:
172+
description: The `metadata.generation` this status reflects, for staleness detection.
173+
format: int64
174+
nullable: true
175+
type: integer
176+
type: object
177+
required:
178+
- spec
179+
title: CapitalMarketsSchedule
180+
type: object
181+
served: true
182+
storage: true
183+
subresources:
184+
status: {}

0 commit comments

Comments
 (0)