@@ -9,6 +9,150 @@ The format is based on the regulated environment requirements:
99
1010---
1111
12+ ## [ 2026-06-14 14:30] - Spot-schedule RBAC + admission + threat model (Phase 4)
13+
14+ ** Author:** Erick Bourgeois
15+
16+ ### Changed
17+ - ` deploy/deployment/rbac/clusterrole.yaml ` : NEW rule — ` get;list;watch ` on
18+ ` spotschedules.5spot.finos.org ` ` * ` (read-only, no write). Unblocks the Phase 3
19+ provider watch; least-privilege per ADR 0006 §6 (providers are untrusted
20+ inputs the controller never writes).
21+ - ` deploy/admission/validatingadmissionpolicy.yaml ` : ** security fix** —
22+ ` matchConstraints.apiVersions ` now covers ` v1beta1 ` as well as ` v1alpha1 ` .
23+ Since Phase 1 made ` v1beta1 ` the served/storage version, every ` v1beta1 `
24+ ScheduledMachine (i.e. every ` spotSchedule ` machine) was ** bypassing the
25+ entire VAP** — the bootstrap/infra allowlist and privilege-escalation guards
26+ included. Also: the four schedule rules are now guarded with
27+ ` !has(object.spec.schedule) || … ` (schedule is optional since v1beta1, so a
28+ valid spotSchedule-only machine is no longer wrongly rejected), and NEW rules
29+ 7a (at least one of schedule/spotSchedule) + 7b (spotSchedule group pin)
30+ mirror the CRD CEL and ` validate_activation_source() ` .
31+ - ` docs/src/security/threat-model.md ` : NEW actor (spot-schedule provider,
32+ untrusted), threat T8a (provider-driven activation control — mitigated:
33+ read-only RBAC, same-namespace, group-pinned, auditable) and D5 (provider
34+ flapping — partially mitigated: back-off + lifecycle timers + transition
35+ metric/alert).
36+ - ` docs/src/security/admission-validation.md ` : documented the dual-version
37+ match, the optional-schedule guard, and rules 7a/7b.
38+ - ` docs/src/operations/monitoring.md ` : suggested ` SpotScheduleProviderFlapping `
39+ and ` SpotScheduleProviderUnresolved ` Prometheus alerts.
40+ - ` docs/architecture/calm/architecture.json ` : ` rel-controller-spot-schedule-watch `
41+ least-privilege control evidence now cites the implemented ClusterRole + VAP.
42+ ` make calm-validate ` clean.
43+
44+ ### Why
45+ Phase 4: realize ADR 0006 §6's least-privilege, providers-are-untrusted posture
46+ in the deployed manifests, and bring the threat model current. The VAP
47+ version-match fix also closes an admission-bypass regression that Phase 1's
48+ v1beta1 bump introduced.
49+
50+ ### Impact
51+ - [ ] Breaking change
52+ - [x] Requires cluster rollout (re-apply ClusterRole + ValidatingAdmissionPolicy)
53+ - [ ] Config change only
54+ - [ ] Documentation only
55+
56+ ## [ 2026-06-14 11:00] - Spot-schedule dynamic event-driven provider watch (Phase 3)
57+
58+ ** Author:** Erick Bourgeois
59+
60+ ### Changed
61+ - ` src/reconcilers/spot_schedule_watch.rs ` : NEW. ` ReverseIndex ` (pure:
62+ ` (gvk, ns, name) ` → referencing ` ScheduledMachine ` s, with per-SM record so a
63+ changed/removed ` spec.spotSchedule ` updates precisely) + ` provider_key_for ` .
64+ ` SpotScheduleWatchManager ` owns the index and lazily runs ** one
65+ ` kube::runtime::watcher ` stream per referenced provider GVK** (event-driven,
66+ not polling); ` sync_watchers ` reconciles the running watcher set against the
67+ index's referenced GVKs (spawn new, abort orphaned). Per-GVK task resolves the
68+ ` ApiResource ` via ` discovery::pinned_kind ` with back-off
69+ (` SPOT_SCHEDULE_DISCOVERY_RETRY_SECS ` , handles CRD-installed-late and
70+ CRD-deleted-while-watched), maps each provider event back through the index,
71+ and enqueues the referencing SMs.
72+ - ` src/main.rs ` : a standalone ` ScheduledMachine ` reflector feeds SM apply/delete
73+ into the manager (index rebuilt from the reflector's initial list on restart —
74+ nothing stored outside Kubernetes); a second ` Controller::reconcile_on `
75+ consumes the manager's mpsc so provider ` status.active ` flips trigger SM
76+ reconciles at watch latency.
77+ - ` src/constants.rs ` : ` SPOT_SCHEDULE_EVENT_CHANNEL_CAP ` ,
78+ ` SPOT_SCHEDULE_DISCOVERY_RETRY_SECS ` .
79+ - ` src/reconcilers/mod.rs ` : export the watch types + ` compose_should_be_active ` .
80+ - Tests: ` spot_schedule_watch_tests.rs ` (13 — ReverseIndex register/replace/
81+ deregister/lookup/gvks, ` provider_key_for ` , manager observe/forget watcher
82+ lifecycle incl. shared-GVK and watcher-stop-on-last-deref);
83+ ` tests/integration_spot_schedule.rs ` (4 — create→active, flip→inactive,
84+ delete→unresolved+hold/fail-inactive across a mock kube API, and the watch
85+ index→reconcile mapping). 658 lib tests green.
86+ - Docs: contract page status → Phase 3.
87+
88+ ### Why
89+ Phase 3 of the spot-schedule provider roadmap: remove the Phase-2 pull latency.
90+ Provider status transitions are now a reconcile ** trigger** (a true watch over
91+ the ` spotschedules.5spot.finos.org ` group), not something re-checked only on the
92+ controller's own timer — satisfying the project's watch-not-poll rule. Realizes
93+ the ` rel-controller-spot-schedule-watch ` relationship + ` flow-spot-schedule-activation `
94+ already modeled in CALM at Phase 0 (no ADR/CALM change).
95+
96+ ### Impact
97+ - [ ] Breaking change
98+ - [x] Requires cluster rollout (new controller image; RBAC for
99+ ` spotschedules.5spot.finos.org ` get/list/watch lands in Phase 4)
100+ - [ ] Config change only
101+ - [ ] Documentation only
102+
103+ ## [ 2026-06-13 18:45] - Spot-schedule resolver + composition + status surface (Phase 2)
104+
105+ ** Author:** Erick Bourgeois
106+
107+ ### Changed
108+ - ` src/reconcilers/spot_schedule.rs ` : NEW resolver module. ` SpotScheduleVerdict `
109+ enum (` Active ` /` Inactive ` /` Unresolved ` ); pure ` verdict_from_status ` (duck-typed
110+ ` status.active ` + ` Ready ` extraction); async ` resolve_spot_schedule `
111+ (` discovery::pinned_kind ` + ` get_opt ` ). Transient API errors propagate (retry);
112+ CRD-not-installed / object-absent / status-missing / not-Ready are ` Unresolved `
113+ verdicts, never errors or panics. ** Ready semantics finalized:** absent ` Ready `
114+ ⇒ ` active ` authoritative; present non-` True ` ` Ready ` ⇒ unresolved (resolves an
115+ internal inconsistency in ADR 0006 §4 / contract table — both updated).
116+ - ` src/reconcilers/helpers.rs ` : NEW ` compose_should_be_active ` — AND composition
117+ of the inline schedule and the provider verdict, with hold-last-state on
118+ Unresolved (fail-inactive when never resolved). ` killSwitch ` precedence is
119+ upstream.
120+ - ` src/crd.rs ` : replaced Phase-1 ` effective_schedule() ` with ` is_enabled() ` (a
121+ spotSchedule-only machine is always enabled; the provider governs activity).
122+ NEW ` SpotScheduleStatus ` + ` ScheduledMachineStatus.spot_schedule ` field (the
123+ durable hold-last-state surface: ` resolved ` /` active ` /` reason ` /` message ` /
124+ ` providerGeneration ` /` lastTransitionTime ` ).
125+ - ` src/reconcilers/scheduled_machine.rs ` : ` reconcile_inner ` now evaluates the
126+ optional inline schedule, resolves the provider (pull-on-reconcile), composes
127+ ` should_be_active ` , records metrics, and patches ` status.spotSchedule `
128+ (` persist_spot_schedule_status ` , a ` /status ` merge patch that coexists with the
129+ phase/condition writes). Handler ` enabled ` guards use ` is_enabled() ` .
130+ - ` src/metrics.rs ` : NEW ` fivespot_spot_schedule_resolutions_total{namespace,kind,result} ` ,
131+ ` _resolution_errors_total{namespace,kind,reason} ` ,
132+ ` _transitions_total{namespace,kind} ` (+ record fns; bounded cardinality, never
133+ keyed by name).
134+ - ` deploy/crds/scheduledmachine.yaml ` regenerated (status.spotSchedule);
135+ ` docs/src/reference/api.md ` regenerated.
136+ - Tests: ` spot_schedule_tests.rs ` (verdict truth table + 3 mock-client resolve
137+ paths), ` helpers_tests.rs ` (12-case composition truth table),
138+ ` scheduled_machine_tests.rs ` (persist ` /status ` patch), ` metrics_tests.rs ` ,
139+ ` crd_tests.rs ` (status round-trip + is_enabled). 645 lib tests green.
140+ - Docs: ADR 0006 + contract page ` Ready ` clarification; contract page status →
141+ Phase 2; ` docs/src/operations/monitoring.md ` spot-schedule metrics table.
142+
143+ ### Why
144+ Phase 2 of the spot-schedule provider roadmap: make a ` spec.spotSchedule `
145+ reference actually drive the machine's active/inactive decision (pull-on-reconcile),
146+ with the ADR-specified hold-last-state fail-safe and an observable status surface.
147+ The event-driven watch (Phase 3) and the reference-provider controller (Phase 5)
148+ build on this.
149+
150+ ### Impact
151+ - [ ] Breaking change
152+ - [x] Requires cluster rollout (apply regenerated CRDs + new controller image)
153+ - [ ] Config change only
154+ - [ ] Documentation only
155+
12156## [ 2026-06-13 16:30] - Spot-schedule provider API: CRD scaffolding + ScheduledMachine v1beta1 (Phase 1)
13157
14158** Author:** Erick Bourgeois
0 commit comments