Skip to content

Commit 5c4f280

Browse files
authored
Merge branch 'main' into spot-schdule-phase5
2 parents d35ece0 + 96d1bec commit 5c4f280

25 files changed

Lines changed: 2169 additions & 95 deletions

.claude/CHANGELOG.md

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -90,48 +90,6 @@ resolver/watch.
9090

9191
---
9292

93-
<<<<<<< Updated upstream
94-
=======
95-
## [2026-06-14 16:00] - Auto-VEX pre-submission sign-off gate (ADR 0008)
96-
97-
**Author:** Erick Bourgeois
98-
99-
### Changed
100-
- `docs/adr/0008-autovex-presubmission-gate.md`: NEW ADR — commit a frozen
101-
snapshot of auto-VEX inputs + canonical output; enforce with a byte-exact CI
102-
gate (PR hard-fail). No CALM impact (CI/CD policy). Index updated in
103-
`docs/adr/README.md`.
104-
- `Makefile`: NEW `vex-auto` (regenerate `.vex/auto/*` from `.vex/snapshot/` with
105-
pinned `--id`/`--author`/`--timestamp`) and `vex-auto-check`
106-
(`vex-auto` + `git diff --exit-code -- .vex/auto`) targets; added both to
107-
`.PHONY`.
108-
- `.vex/snapshot/`: NEW frozen generator inputs (`grype.json`, `sbom-bootstrap.json`,
109-
`symbols.txt`, `timestamp.txt`) + `README.md` documenting the sign-off contract
110-
and how to refresh from a real CI capture. Seeded as a valid empty bootstrap.
111-
- `.vex/auto/`: NEW committed canonical output (`vex.auto-presence.json`,
112-
`vex.auto-reachability.json`) — the human-signed-off baseline.
113-
- `.github/workflows/build.yaml`: NEW `validate-autovex` job — runs
114-
`make vex-auto-check` on `pull_request` + `push`, hard-failing when committed
115-
auto-VEX is stale.
116-
- `docs/src/security/vex.md`: NEW "Auto-VEX is signed off before submission"
117-
section documenting the committed baseline, workflow, and gate scope.
118-
119-
### Why
120-
Auto-VEX statements are machine-authored suppressions that previously appeared
121-
only in CI artifacts and were merged into the signed release VEX with no human
122-
review until the Security team's post-hoc counter-signature. This adds an
123-
**upstream** checkpoint: maintainers run the generators, review the suppressions,
124-
and commit them (sign-off) before submission; CI refuses any change whose
125-
committed auto-VEX no longer matches a fresh, deterministic regeneration.
126-
127-
### Impact
128-
- [ ] Breaking change
129-
- [ ] Requires cluster rollout
130-
- [x] Config change only (CI gate + repo convention)
131-
- [ ] Documentation only
132-
133-
---
134-
13593
## [2026-06-14 14:30] - Spot-schedule RBAC + admission + threat model (Phase 4)
13694

13795
**Author:** Erick Bourgeois
@@ -276,7 +234,6 @@ build on this.
276234
- [ ] Config change only
277235
- [ ] Documentation only
278236

279-
>>>>>>> Stashed changes
280237
## [2026-06-13 16:30] - Spot-schedule provider API: CRD scaffolding + ScheduledMachine v1beta1 (Phase 1)
281238

282239
**Author:** Erick Bourgeois

Cargo.lock

Lines changed: 72 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ warp = { version = "0.4", default-features = false, features = ["server"] }
7070
toml = "1.1"
7171
# SHA-256 for the kata-config agent's content/drift hashing and (Phase 4)
7272
# the applied-hash node annotation. RustCrypto, pure-Rust, no system deps.
73-
sha2 = "0.10"
73+
sha2 = "0.11"
7474

7575
# Linux-only — used by the reclaim-agent's rung 2 netlink proc connector
7676
# subscriber (`src/netlink_proc.rs`). The portable byte parsers in that

deploy/admission/validatingadmissionpolicy.yaml

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@ spec:
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.

deploy/crds/scheduledmachine.yaml

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,47 @@ spec:
673673
(Pending, ShuttingDown, Inactive, Disabled, Terminated, Error) is
674674
reported as `False`.
675675
type: boolean
676+
spotSchedule:
677+
description: |-
678+
Spot-schedule provider resolution state (ADR 0006), present only when
679+
`spec.spotSchedule` is set. Carries the last resolved/held provider
680+
`active` value (the input to hold-last-state composition), the
681+
resolution reason/message, and the provider's observed generation.
682+
nullable: true
683+
properties:
684+
active:
685+
description: |-
686+
Last known provider `status.active`. Held across an unresolved reconcile
687+
(hold-last-state); `None` until the provider first resolves.
688+
nullable: true
689+
type: boolean
690+
lastTransitionTime:
691+
description: When `active` last transitioned (RFC3339).
692+
nullable: true
693+
type: string
694+
message:
695+
description: Human-readable resolution detail.
696+
nullable: true
697+
type: string
698+
providerGeneration:
699+
description: The provider's `status.observedGeneration` at the last resolution.
700+
format: int64
701+
nullable: true
702+
type: integer
703+
reason:
704+
description: Machine-readable resolution reason (a `REASON_SPOT_SCHEDULE_*` value).
705+
nullable: true
706+
type: string
707+
resolved:
708+
description: |-
709+
Whether the referenced provider resolved into an authoritative verdict
710+
this reconcile. `false` mirrors a `SpotScheduleResolved=False`
711+
condition — the provider CRD is absent, the object is missing, it
712+
exposes no `status.active`, or it is not `Ready`.
713+
type: boolean
714+
required:
715+
- resolved
716+
type: object
676717
type: object
677718
required:
678719
- spec
@@ -1204,6 +1245,47 @@ spec:
12041245
(Pending, ShuttingDown, Inactive, Disabled, Terminated, Error) is
12051246
reported as `False`.
12061247
type: boolean
1248+
spotSchedule:
1249+
description: |-
1250+
Spot-schedule provider resolution state (ADR 0006), present only when
1251+
`spec.spotSchedule` is set. Carries the last resolved/held provider
1252+
`active` value (the input to hold-last-state composition), the
1253+
resolution reason/message, and the provider's observed generation.
1254+
nullable: true
1255+
properties:
1256+
active:
1257+
description: |-
1258+
Last known provider `status.active`. Held across an unresolved reconcile
1259+
(hold-last-state); `None` until the provider first resolves.
1260+
nullable: true
1261+
type: boolean
1262+
lastTransitionTime:
1263+
description: When `active` last transitioned (RFC3339).
1264+
nullable: true
1265+
type: string
1266+
message:
1267+
description: Human-readable resolution detail.
1268+
nullable: true
1269+
type: string
1270+
providerGeneration:
1271+
description: The provider's `status.observedGeneration` at the last resolution.
1272+
format: int64
1273+
nullable: true
1274+
type: integer
1275+
reason:
1276+
description: Machine-readable resolution reason (a `REASON_SPOT_SCHEDULE_*` value).
1277+
nullable: true
1278+
type: string
1279+
resolved:
1280+
description: |-
1281+
Whether the referenced provider resolved into an authoritative verdict
1282+
this reconcile. `false` mirrors a `SpotScheduleResolved=False`
1283+
condition — the provider CRD is absent, the object is missing, it
1284+
exposes no `status.active`, or it is not `Ready`.
1285+
type: boolean
1286+
required:
1287+
- resolved
1288+
type: object
12071289
type: object
12081290
required:
12091291
- spec

0 commit comments

Comments
 (0)