2727
2828 matchConstraints :
2929 resourceRules :
30+ # Both served versions must be validated (ADR 0007). v1beta1 is the
31+ # storage/current version (adds spec.spotSchedule and an optional
32+ # spec.schedule); v1alpha1 is the frozen, deprecated legacy version. A
33+ # version omitted here would BYPASS every validation below.
3034 - apiGroups : ["5spot.finos.org"]
31- apiVersions : ["v1alpha1"]
35+ apiVersions : ["v1alpha1", "v1beta1" ]
3236 resources : ["scheduledmachines"]
3337 operations : ["CREATE", "UPDATE"]
3438
@@ -117,11 +121,19 @@ spec:
117121 (format: <positive integer> followed by s, m, or h)
118122 reason : Invalid
119123
124+ # NOTE on the schedule rules below: `spec.schedule` is OPTIONAL since
125+ # v1beta1 (ADR 0006) — a machine may instead delegate to a spotSchedule
126+ # provider. Each schedule rule is therefore guarded with
127+ # `!has(object.spec.schedule) || …` so a valid spotSchedule-only machine is
128+ # not rejected. The "at least one of schedule/spotSchedule" invariant is
129+ # enforced by rule 7a below (and by the CRD's own x-kubernetes-validations).
130+
120131 # ── 4. Cron XOR explicit day/hour windows ────────────────────────────────
121132 # Using cron together with daysOfWeek or hoursOfDay is ambiguous.
122133 # The controller silently ignores daysOfWeek/hoursOfDay when cron is set;
123134 # we reject the spec at admission to prevent silent data loss.
124135 - expression : |
136+ !has(object.spec.schedule) ||
125137 !has(object.spec.schedule.cron) ||
126138 (object.spec.schedule.daysOfWeek.size() == 0 &&
127139 object.spec.schedule.hoursOfDay.size() == 0)
@@ -132,6 +144,7 @@ spec:
132144
133145 # ── 5. Without cron, both day and hour windows must be provided ───────────
134146 - expression : |
147+ !has(object.spec.schedule) ||
135148 has(object.spec.schedule.cron) ||
136149 (object.spec.schedule.daysOfWeek.size() > 0 &&
137150 object.spec.schedule.hoursOfDay.size() > 0)
@@ -145,6 +158,7 @@ spec:
145158 # or a comma-separated combination (mon-wed,fri-sun).
146159 # Trivially true for empty arrays (cron path).
147160 - expression : |
161+ !has(object.spec.schedule) ||
148162 object.spec.schedule.daysOfWeek.all(d,
149163 d.matches('^(mon|tue|wed|thu|fri|sat|sun)(-(mon|tue|wed|thu|fri|sat|sun))?(,(mon|tue|wed|thu|fri|sat|sun)(-(mon|tue|wed|thu|fri|sat|sun))?)*$'))
150164 message: >-
@@ -158,13 +172,35 @@ spec:
158172 # at runtime by the schedule evaluator.
159173 # Trivially true for empty arrays (cron path).
160174 - expression : |
175+ !has(object.spec.schedule) ||
161176 object.spec.schedule.hoursOfDay.all(h,
162177 h.matches('^[0-9]{1,2}(-[0-9]{1,2})?(,[0-9]{1,2}(-[0-9]{1,2})?)*$'))
163178 message: >-
164179 spec.schedule.hoursOfDay items must be hours or ranges
165180 (e.g. '9', '9-17', '0-9,18-23')
166181 reason: Invalid
167182
183+ # ── 7a. At least one activation source ────────────────────────────────────
184+ # A machine must declare an inline schedule, a spotSchedule provider, or
185+ # both (ADR 0006). Mirrors the CRD x-kubernetes-validations and the runtime
186+ # validate_activation_source() in src/reconcilers/helpers.rs.
187+ - expression : " has(object.spec.schedule) || has(object.spec.spotSchedule)"
188+ message : >-
189+ at least one of spec.schedule or spec.spotSchedule must be set
190+ reason : Invalid
191+
192+ # ── 7b. spotSchedule.apiVersion group pin ─────────────────────────────────
193+ # The provider reference group MUST be spotschedules.5spot.finos.org
194+ # (ADR 0006 §2 / §6) — providers are untrusted inputs and the controller's
195+ # read-only RBAC is scoped to exactly this group. Mirrors the CRD field CEL
196+ # and the runtime group check.
197+ - expression : |
198+ !has(object.spec.spotSchedule) ||
199+ object.spec.spotSchedule.apiVersion.startsWith('spotschedules.5spot.finos.org/')
200+ message: >-
201+ spec.spotSchedule.apiVersion group must be spotschedules.5spot.finos.org
202+ reason: Invalid
203+
168204 # ── 8. bootstrapSpec.apiVersion must use a namespaced API group ───────────
169205 # Core API versions (e.g. "v1") have no '/' and must never be used for
170206 # bootstrap resources — rejects the same case as validate_api_group() at runtime.
0 commit comments