  2. User-facing log emitted at preemption start via IBM Cloud Logging.
+  3. Use ListInstances which supports filtering by availability.class = "spot" for efficient bulk detection in a single API call. There is even `InstanceStatusReason.Code = "stopped_by_preemption"`.
+- **30-second best-effort grace period** before forced shutdown.
+
+### Spot Constraints
+
+Spot instances require:
+
+- `reservation_affinity.policy = disabled`
+- No dedicated host or host group placement
+
+```go
+type InstanceAvailabilityPrototype struct {
+	// The availability class for the virtual server instance:
+	// - `spot`: The virtual server instance may be preempted.
+	// - `standard`: The virtual server instance will not be preempted.
+	//
+	// If `spot` is specified, the virtual server instance:
+	// - `reservation_affinity.policy` must be `disabled`
+	// - `placement_target` must not specify a dedicated host or dedicated host group.
+	//
+	// If unspecified, the default for `availability_class` from the profile will be used.
+	Class *string `json:"class,omitempty"`
+}
+```
+
+- [**Only GPU and Flex profiles**](https://cloud.ibm.com/docs/vpc?topic=vpc-spot-instances-virtual-servers#spot-instances-details) support spot
+
+---
+
+## High Level Design
+
+![hld.svg](../assets/spot-lifecycle-hld.svg)
+
+1. The Scheduler filters available spot offerings and creates a NodeClaim with the most suitable option.
+2. The VPC Instance Provider fulfills it by calling CreateInstance with spot-specific parameters.
+3. When IBM preempts the instance, the Interruption Controller detects it, marks that offering as unavailable in the cache, and deletes the NodeClaim.
+4. The Scheduler then provisions a replacement, but with the preempted offering filtered out, picking a different available offering.
+
+---
+
+## Design Decisions
+
+### 1. Preemption policy: `"delete"` or `"stop"`
+
+- We are choosing here between reliability of signal or simplicity of cleanup and extra cost.
+- With preemption = `"stop"`:
+  1. IBM stops the instance.
+  2. We poll, find it stopped with explicit status reason of stopped_by_preemption.
+  3. We call DeleteInstance to clean it up.
+  4. We mark cache + delete NodeClaim.
+- With preemption = `"delete"`:
+  1. IBM deletes the instance.
+  2. We poll, notice a known spot instance is missing.
+  3. We mark cache + delete NodeClaim.
+- The issue with the `"delete"` is the false positives. The deletion could be for any other reasons like manual deletion for example. and in this case we will mark the offering as unavailable.
+- We will go with `"stop"` because it gives an unambiguous detection signal.
+
+### 2. Discount factor for spot pricing
+
+- There is no public API for spot pricing in IBM.
+- We can initially go with `spotPrice = onDemandPrice × discountFactor`
+
+### 3. Per zone offerings
+
+- The current IBM code creates one offering with all zones.
+
+[instancetype.go#L726](https://github.com/kubernetes-sigs/karpenter-provider-ibm-cloud/blob/0ecb082e09c5a3276395ea555b44370fdceef15d/pkg/providers/common/instancetype/instancetype.go#L726)
+
+- We will change it to per-zone offerings. If spot capacity is preempted in us-south-1, we need to mark only (us-south-1, spot) as unavailable, not the same instance type in us-south-2 or us-south-3.
+
+### 4. Cache key includes capacity type
+
+- The UnavailableOfferings cache key will be capacityType:instanceType:zone
+
+[controller.go#L460](https://github.com/kubernetes-sigs/karpenter-provider-ibm-cloud/blob/0ecb082e09c5a3276395ea555b44370fdceef15d/pkg/controllers/interruption/controller.go#L460)
+
+- Additionally, the current cache is not read anywhere. This implementation will wire it for the first time.
+
+### 5. Interruption detection
+
+We have three options here:
+
+1. OS-level graceful shutdown signal
+
+- IBM sends a graceful shutdown command to the guest OS at the start of preemption. However, this would require introducing a mechanism to propagate the OS-level signal to Karpenter.
+
+2. IBM Cloud Logs
+
+- IBM Cloud Logging emits a structured platform log at the moment a spot instance is selected for preemption ([messageId: instance.00002](https://cloud.ibm.com/docs/vpc?topic=vpc-logging#:~:text=instance.00002,selected%20for%20preemption.)). This signal is available before the 30-second grace period.
+- We can (A) poll the Logs API or (B) configure IBM Cloud Event Notifications to push preemption events to Karpenter.
+- However, according to the [documentation](https://cloud.ibm.com/docs/logs-router?topic=logs-router-getting-started&utm_source=chatgpt.com#:~:text=To%20configure%20platform%20logs%2C%20you%20must%20configure%20tenants%20and%20targets%20(destinations)%20in%20your%20IBM%20Cloud%20account.) platform logs require explicit configurations in IBM Cloud Logs Routing, making it unsuitable as the default detection mechanism.
+
+3. Poll VPC API (ListInstances)
+
+- This is the most reasonable option as a start. will keep polling the VPC API and checking instance status reasons. Note: the 60s for polling is what we [currently use in reconcile](https://github.com/kubernetes-sigs/karpenter-provider-ibm-cloud/blob/0ecb082e09c5a3276395ea555b44370fdceef15d/pkg/controllers/interruption/controller.go#L148) and there is no strong reason to change it.
+
+![poll1.svg](../assets/spot-interruption-controller.svg)
+
+- To summarize our options:
+
+|  | Option 1: OS-level Shutdown | Option 2A: Logs API Polling | Option 2B: Cloud Logs + Event Notifications | Option 3: VPC API Polling |
+| --- | --- | --- | --- | --- |
+| Signal | Guest OS shutdown command | Platform log `instance.00002` | Platform log `instance.00002` (delivered as event) | `stopped_by_preemption` |
+| Delivery | Push (inside VM) | Pull | Push | Pull |
+| Detection timing | At preemption start | At preemption start | At preemption start (near real-time) | After preemption completes |
+| Extra setup required | Agent on every spot node (to relay signal to control plane) | Cloud Logs Routing | Cloud Logs + Event Notifications + alert rule + webhook/queue | None |
+| Works for all users | Only if agent deployed | Only if Cloud Logs configured | Only if full pipeline configured | Yes |
+| Complexity | High | Medium | High | Low |
+
+---
+
+## Implementation Plan
