From 7dba78df7fdc7ee479b893eb71b660ee82bd7573 Mon Sep 17 00:00:00 2001 From: Dmitrii Andreev Date: Fri, 12 Jun 2026 13:25:01 -0500 Subject: [PATCH] HYPERFLEET-1163 - docs: add v0.2.0 to v1.0.0 upgrade guide --- .../release/v0.2.0-to-v1.0.0-upgrade-guide.md | 583 ++++++++++++++++++ 1 file changed, 583 insertions(+) create mode 100644 hyperfleet/docs/release/v0.2.0-to-v1.0.0-upgrade-guide.md diff --git a/hyperfleet/docs/release/v0.2.0-to-v1.0.0-upgrade-guide.md b/hyperfleet/docs/release/v0.2.0-to-v1.0.0-upgrade-guide.md new file mode 100644 index 00000000..ef8693a3 --- /dev/null +++ b/hyperfleet/docs/release/v0.2.0-to-v1.0.0-upgrade-guide.md @@ -0,0 +1,583 @@ +--- +Status: Active +Owner: HyperFleet Team +Last Updated: 2026-06-12 +--- + +# HyperFleet v0.2.0 → v1.0.0 Upgrade Guide + +This guide provides migration guidance for operators and API consumers moving from HyperFleet v0.2.0 to v1.0.0. For the authoritative breaking-change checklist with Jira tickets and per-item evidence, see [v1.0.0 Breaking-Change Checklist](./v1.0.0/breaking-changes.md). + +> **Important:** v1.0.0 requires a fresh database. There is no data migration from v0.2.0. +> All existing v0.2.0 deployments are disposable integration environments — the upgrade is a +> **reconfigure-and-redeploy**, not an in-place migration. + +**Related documentation:** + +- [HYPERFLEET-1177 — Breaking-change checklist](https://redhat.atlassian.net/browse/HYPERFLEET-1177) — exhaustive verified checklist +- [HyperFleet Release Process](./hyperfleet-release-process.md) — release phases and artifacts +- [API Versioning Strategy](../../components/api-service/api-versioning.md) — versioning rules and compatibility guarantees + +--- + +## Table of Contents + +- [Upgrade Procedure](#upgrade-procedure) +- [Changes Covered](#changes-covered) +- [1. Configuration](#1-configuration) +- [2. API Contract](#2-api-contract) +- [3. Statuses Contract](#3-statuses-contract) +- [4. Operating Model](#4-operating-model) +- [5. New Features (Informational)](#5-new-features-informational) +- [Post-Upgrade Verification](#post-upgrade-verification) + +--- + +## Upgrade Procedure + +Because v1.0.0 uses a fresh database and introduces breaking changes to the status reporting verb (POST → PUT), condition names, and configuration schema, all components must be deployed together. A partial upgrade — such as running v1.0.0 adapters against a v0.2.0 API — will fail (the v0.2.0 API rejects PUT; the v1.0.0 API rejects POST). + +**Order of operations:** + +1. **Prepare v1.0.0 configuration files** — apply all configuration changes from [§1](#1-configuration). Validate against the v1.0.0 binary with a dry run if possible. +2. **Tear down the v0.2.0 environment** — stop all v0.2.0 components (API, Sentinel, adapters). Existing environments are disposable. +3. **Provision a fresh database** — do not reuse the v0.2.0 database (see [§4.1](#41-fresh-database--no-migration)). +4. **Deploy all v1.0.0 components together** — API server first (it initializes the database schema), then Sentinel, then adapters. All must target v1.0.0. +5. **Re-register resources** — create clusters, node pools, and other entities via the API. Adapters will discover and reconcile them. +6. **Run post-upgrade verification** — see [Post-Upgrade Verification](#post-upgrade-verification) below. + +--- + +## Changes Covered + +The authoritative list of all breaking changes is the [v1.0.0 Breaking-Change Checklist](./v1.0.0/breaking-changes.md). This guide provides narrative migration guidance for selected changes — it does not replace the checklist. + +The sections below cover: [Configuration](#1-configuration) · [API Contract](#2-api-contract) · [Statuses Contract](#3-statuses-contract) · [Operating Model](#4-operating-model) · [New Features](#5-new-features-informational) + +--- + +## 1. Configuration + +### 1.1 Tracing default changed + +#### What Changed + +The tracing configuration was reworked: + +- **Environment variable renamed:** `HYPERFLEET_LOGGING_OTEL_ENABLED` (v0.2.0) → `HYPERFLEET_TRACING_ENABLED` (v1.0.0). The old variable is deprecated and ignored. +- **API default changed:** `false` (v0.2.0) → `true` (v1.0.0). +- **Adapter default changed:** always-on with no toggle (v0.2.0) → `true` with `HYPERFLEET_TRACING_ENABLED` override (v1.0.0). + +Both API and adapter now default to **tracing enabled**. See [Tracing Standard](../../standards/tracing.md) for the full configuration reference. + +#### Required Action + +If you were setting the old `HYPERFLEET_LOGGING_OTEL_ENABLED` variable, switch to the new name: + +```bash +# v0.2.0 (no longer recognized) +HYPERFLEET_LOGGING_OTEL_ENABLED=true + +# v1.0.0 +HYPERFLEET_TRACING_ENABLED=true +``` + +To disable tracing explicitly: + +```bash +HYPERFLEET_TRACING_ENABLED=false +``` + +> **If you don't act:** Tracing is enabled by default in v1.0.0. If your deployment does not have a tracing backend configured, trace export will fail silently. If you were setting the old `HYPERFLEET_LOGGING_OTEL_ENABLED`, it is ignored and the new default (`true`) takes effect. + +--- + +### 1.2 Sentinel `message_decision` CEL configuration + +#### What Changed + +Sentinel's reconciliation decision logic moved from hardcoded Go code (v0.2.0) to configurable CEL expressions (v1.0.0). v1.0.0 ships with a default `message_decision` configuration that already references the renamed `condition("Reconciled")` — no manual update is needed. + +The default publishes a reconciliation event when a resource is not yet reconciled (new, generation mismatch, or debounce elapsed) or when a reconciled resource hasn't been checked in 30 minutes. Custom `message_decision` blocks in the Sentinel ConfigMap override this default. + +See [Sentinel Design — Message Decision](../../components/sentinel/sentinel.md) for the full CEL reference. + +#### Required Action + +**No action required** if you use the shipped default. If you have a **custom** `message_decision` block in your Sentinel ConfigMap that references `condition("Ready")`, update it to `condition("Reconciled")` per the [aggregated condition rename](#31-aggregated-condition-renames). + +**Custom config only — update condition name:** + +```yaml +# Change condition("Ready") → condition("Reconciled") in any custom params +message_decision: + params: + - name: ref_time + expr: 'condition("Reconciled").last_updated_time' + - name: is_reconciled + expr: 'condition("Reconciled").status == "True"' + # ... your custom params + result: 'is_reconciled || ...' +``` + +> **If you have a custom config referencing `condition("Ready")`:** The lookup returns a zero-value condition (`status=""`) because the `Ready` condition no longer exists. The expression evaluates incorrectly — the exact failure depends on your custom logic. + +--- + +### 1.3 JWT `identity_claim` for audit fields + +#### What Changed + +v1.0.0 adds an `identity_claim` setting that controls which JWT claim populates the `created_by` and `updated_by` audit fields on resources. The default is `email`. In v0.2.0, audit fields were not populated from JWT claims. + +#### Required Action + +Verify that the default `identity_claim` value (`email`) matches a claim your identity provider includes in JWT tokens. Override it only if needed: + +```yaml +server: + jwt: + identity_claim: preferred_username +``` + +> **If you don't act:** Audit fields (`created_by`, `updated_by`) are populated from the JWT `email` claim. If your identity provider does not include `email` in tokens, **mutating requests (POST, PUT, PATCH, DELETE) are rejected with 401 Unauthorized**. Read-only requests (GET) proceed without identity context. + +--- + +### 1.4 New entity types registered in v1.0.0 + +#### What Changed + +v1.0.0 adds three new entity types managed via the [Generic Resource Registry](../generic-resource-registry-design.md). Entity types are registered in Go code at startup — no configuration is needed to enable the built-in types. + +**v1.0.0 entity types:** + +| Kind | Plural | Parent | On Parent Delete | Required Adapters | +|------|--------|--------|------------------|-------------------| +| `Cluster` | `clusters` | — | — | `provisioner`, `lifecycle` | +| `NodePool` | `nodepools` | `Cluster` | `cascade` | `provisioner`, `lifecycle` | +| `WifConfig` | `wifconfigs` | — | — | — | +| `Channel` | `channels` | — | — | — | +| `Version` | `versions` | `Channel` | `restrict` | — | + +Entity types without `RequiredAdapters` (WifConfig, Channel, Version) are non-reconcilable — they have no aggregated conditions (see [§3.6](#36-non-reconcilable-resources)). + +The delete behavior for child entities is hardcoded in the entity descriptor (see [§4.2](#42-descriptor-driven-delete-model)). Deleting a Cluster cascade-deletes its NodePools. Deleting a Channel is blocked while it has Versions (`restrict`). + +#### Required Action + +**No action required** — all five entity types are built into v1.0.0. New endpoints are available automatically (see [§2.3](#23-new-endpoints)). + +--- + +### 1.5 Condition mapping rules + +#### What Changed + +A new optional `conditions` config block under the API configuration allows operators to define CEL-based condition mapping rules. These rules copy and transform adapter-reported conditions into customer-visible resource conditions. + +See [Condition Mapping Design](../../components/api-service/condition-mapping-design.md) for rule syntax and examples. + +#### Required Action + +**No action required** unless you want to expose provider-specific adapter conditions to API consumers. To configure: + +```yaml +conditions: + adapters: + clusters: + ROSAControlPlaneReady: + when: + expression: >- + adapter.name == "rosa-provisioner" + && has(condition) && condition.type == "ControlPlaneReady" + output: + status: + expression: condition.status + reason: + expression: condition.reason + message: + expression: condition.message +``` + +--- + +## 2. API Contract + +> **Note:** The API URI path remains `/api/hyperfleet/v1/...`. The component version bump from v0.2.0 to v1.0.0 does **not** change the API version prefix. +> See [API Versioning Strategy](../../components/api-service/api-versioning.md) for the distinction between component version and API version. + +### 2.1 Adapter status reporting: POST → PUT + +#### What Changed + +Adapter status reporting now uses `PUT` with upsert semantics (create-or-update). The `POST` verb is no longer accepted. The API handles upsert logic server-side — adapters always send the same PUT request regardless of whether it is the first report or a subsequent update. + +**Endpoint:** `PUT /api/hyperfleet/v1/clusters/{clusterId}/statuses` + +See [Adapter Status Contract](../../components/adapter/framework/adapter-status-contract.md) for the full contract. + +#### Required Action + +Change the HTTP method in all adapter status reporting code: + +**Before (v0.2.0):** + +```bash +curl -X POST \ + "${API_URL}/api/hyperfleet/v1/clusters/${CLUSTER_ID}/statuses" \ + -H "Content-Type: application/json" \ + -d @status.json +``` + +**After (v1.0.0):** + +```bash +curl -X PUT \ + "${API_URL}/api/hyperfleet/v1/clusters/${CLUSTER_ID}/statuses" \ + -H "Content-Type: application/json" \ + -d @status.json +``` + +> **If you don't act:** Status reports are rejected. The API returns 405 Method Not Allowed. + +--- + +### 2.2 New `Resource` response type for new entity types + +#### What Changed + +v1.0.0 introduces a generic `Resource` response type used by the new entity types (Channel, Version, WifConfig). This type uses a `kind` discriminator field and an untyped `spec` JSON object. + +**Cluster and NodePool responses are unchanged** — they continue to use their dedicated `Cluster` and `NodePool` response types from v0.2.0. + +**Generic `Resource` response (Channel, Version, WifConfig):** + +```json +{ + "id": "abc-123", + "kind": "Channel", + "name": "stable", + "href": "/api/hyperfleet/v1/channels/abc-123", + "spec": { ... }, + "labels": {}, + "status": { "conditions": [] }, + "generation": 1, + "created_time": "2026-06-01T00:00:00Z", + "updated_time": "2026-06-01T00:00:00Z", + "created_by": "user@example.com", + "updated_by": "user@example.com" +} +``` + +#### Required Action + +**No action required** for existing Cluster/NodePool consumers — those response types are unchanged. Consumers integrating with new entity types should use the `Resource` response shape above. + +--- + +### 2.3 New endpoints + +#### What Changed + +v1.0.0 adds the following endpoints via the [Generic Resource Registry](../generic-resource-registry-design.md): + +| Endpoint | Description | +|----------|-------------| +| `GET/POST /api/hyperfleet/v1/wifconfigs` | WIF (Workload Identity Federation) configuration resources (CRUD) | +| `GET/POST /api/hyperfleet/v1/channels` | Release channel resources (CRUD) | +| `GET/POST /api/hyperfleet/v1/channels/{parent_id}/versions` | Version resources nested under channels (CRUD) | +| `POST /api/hyperfleet/v1/clusters/{id}/force-delete` | Force-delete a stuck cluster (requires `Finalizing` state) | +| `POST /api/hyperfleet/v1/clusters/{id}/nodepools/{np_id}/force-delete` | Force-delete a stuck node pool | +| `DELETE /api/hyperfleet/v1/clusters/{id}/nodepools/{np_id}` | Soft-delete a node pool (not available in v0.2.0) | +| `PATCH /api/hyperfleet/v1/clusters/{id}/nodepools/{np_id}` | Patch a node pool (not available in v0.2.0) | + +#### Required Action + +**No action required** — these are additive endpoints. Existing client code continues to work. +Adapter teams integrating with new resource types should consult the [OpenAPI specification](https://github.com/openshift-hyperfleet/hyperfleet-api) for request/response shapes. + +> **Force-delete:** Available for clusters and node pools only. Requires the resource to be in `Finalizing` state (`deleted_time` set). See [Force Deletion Design](../force-deletion-design.md). + +--- + +### 2.4 Delete returns 202 Accepted + +#### What Changed + +`DELETE /api/hyperfleet/v1/clusters/{id}` is now functional. In v0.2.0, the DELETE handler was registered but returned `501 Not Implemented`. In v1.0.0, deletion is a two-phase process: + +1. **Soft delete:** API sets `deleted_time` on the resource and increments `generation`. Returns `202 Accepted`. +2. **Hard delete:** When all required adapters report `Finalized=True`, the API hard-deletes the records. + +Deleting a Cluster **always cascade-deletes all its NodePools** — there is no per-request opt-out (see [§4.2](#42-descriptor-driven-delete-model)). + +Attempting to delete a Channel with active Versions returns `409 Conflict`. + +#### Required Action + +If you had client code that called DELETE and handled the `501` error, update it to expect `202`: + +```go +if resp.StatusCode != http.StatusAccepted { + return fmt.Errorf("delete failed: %d", resp.StatusCode) +} +// Resource enters Finalizing state (deleted_time is set) — poll until fully deleted +``` + +> **If you don't act:** DELETE now succeeds and cascade-deletes child resources. Code that previously ignored 501 errors may inadvertently trigger deletions. + +> **Data-loss warning:** Deleting a Cluster always cascade-deletes all its NodePools, regardless of the caller's intent. There is no per-request override. If NodePool preservation is needed, delete NodePools individually first, or do not delete the parent Cluster. + +--- + +### 2.5 Resource references + +#### What Changed + +The `Resource` API type includes an optional `references` field for declaring non-ownership associations between resources. This field is present in the OpenAPI contract but not yet wired in the service layer — no entity descriptors currently declare reference constraints. + +See [Generic Resource Registry Design — §9](../generic-resource-registry-design.md) for the planned reference model. + +#### Required Action + +**No action required.** This is a contract-level placeholder for future use. + +--- + +### 2.6 Condition `reason` and `message` now optional + +#### What Changed + +The `reason` and `message` fields on adapter-reported conditions are now optional. They are still recommended for observability but are no longer required by the API. + +#### Required Action + +**No action required.** Continue providing `reason` and `message` for observability. Adapters that already provide these fields are unaffected. + +--- + +## 3. Statuses Contract + +### 3.1 Aggregated condition renames + +#### What Changed + +The two system-computed aggregated resource conditions were renamed: + +| v0.2.0 Name | v1.0.0 Name | Meaning | +|-------------|-------------|---------| +| `Ready` | `Reconciled` | All required adapters report `Available=True` at the current generation | +| `Available` | `LastKnownReconciled` | Sticky: once `Reconciled` is `True`, this remains `True` across generation bumps until a new reconciliation completes | + +The adapter-level required conditions (`Applied`, `Available`, `Health`) are **unchanged**. + +> **Name-collision warning:** The adapter-level condition `Available` still exists — it is a per-adapter condition reported in `PUT /statuses`. The *resource-level* aggregated condition formerly called `Available` is now `LastKnownReconciled`. Code that searches for `"Available"` by string may silently match the wrong layer. Always scope condition lookups to the correct level (adapter conditions vs. resource conditions). + +See [ADR-0007 — Conditions-Based Status Model](../../adrs/0007-conditions-based-status-model.md) (Evolution section) for the decision rationale. + +#### Required Action + +Update all code and configuration that reads aggregated conditions from the resource status: + +**Before (v0.2.0):** + +```go +ready := getCondition(resource.Status.Conditions, "Ready") +available := getCondition(resource.Status.Conditions, "Available") +``` + +**After (v1.0.0):** + +```go +reconciled := getCondition(resource.Status.Conditions, "Reconciled") +lastKnownReconciled := getCondition(resource.Status.Conditions, "LastKnownReconciled") +``` + +If you have a **custom** Sentinel `message_decision` referencing `condition("Ready")`, update it to `condition("Reconciled")` (see [§1.2](#12-sentinel-message_decision-cel-configuration)). The shipped default already references `Reconciled`. + +> **If you don't act:** Code that reads `Ready` or `Available` from resource conditions returns nil/empty. Custom Sentinel decision expressions referencing `condition("Ready")` evaluate incorrectly. + +--- + +### 3.2 `Finalized` condition for deletion flows + +#### What Changed + +Adapters must now report a `Finalized` condition during resource deletion flows. When `deleted_time` is set on a resource, adapters are expected to: + +1. Clean up their Kubernetes/cloud resources +2. Report `Finalized=True` in their status update + +The API gates hard-delete on **all** required adapters reporting `Finalized=True`. When `deleted_time` is not set, the `Finalized` condition is ignored. + +#### Required Action + +Add `Finalized` condition reporting to adapter deletion flow: + +**After (v1.0.0):** + +```json +{ + "adapter_name": "my-adapter", + "conditions": [ + { "type": "Applied", "status": "True" }, + { "type": "Available", "status": "True" }, + { "type": "Health", "status": "True" }, + { "type": "Finalized", "status": "True", "reason": "CleanupComplete", "message": "All resources deleted" } + ] +} +``` + +> **If you don't act:** Resources remain in `Finalizing` state indefinitely. The API never hard-deletes the records. Operators must use `POST /{id}/force-delete` to clear stuck resources. + +--- + +### 3.3 Resource conditions: True/False in steady state + +#### What Changed + +Resource-level conditions (in `status.conditions`) enforce a **True/False** contract in steady state. Adapter conditions may use `True`, `False`, or `Unknown`, but `Unknown` adapter conditions are automatically filtered out during condition mapping and never converted to resource conditions. + +**Exception:** `Unknown` may appear transiently during resource initialization, before any adapter has reported. Consumers must handle three values (`True`, `False`, `Unknown`) but can treat `Unknown` as equivalent to "not yet determined." + +#### Required Action + +**No action required.** Consumers should handle `Unknown` during initialization but can assume `True`/`False` once the resource has received its first adapter report. + +--- + +### 3.5 Mapped conditions + +#### What Changed + +In addition to the standard aggregated conditions (`Reconciled`, `LastKnownReconciled`) and per-adapter conditions, resource `status.conditions` may now contain **mapped conditions** — dynamically created from adapter conditions via operator-configured CEL mapping rules. + +See [Condition Mapping Design](../../components/api-service/condition-mapping-design.md) for rule syntax. + +#### Required Action + +**No action required** for existing consumers. If your code iterates over `status.conditions`, be aware that new condition types may appear beyond the standard aggregated set. + +--- + +### 3.6 Non-reconcilable resources + +#### What Changed + +New resource types — WifConfig, Channel, and Version — are **non-reconcilable** (they have no `RequiredAdapters`). They do not receive `Reconciled`/`LastKnownReconciled` aggregated conditions. Only reconcilable resources (Cluster, NodePool) receive aggregated conditions. + +#### Required Action + +**No action required** for existing adapter teams. Consumers building generic tooling should check the resource kind before expecting aggregated conditions. + +--- + +## 4. Operating Model + +### 4.1 Fresh database — no migration + +#### What Changed + +**v1.0.0 requires a fresh database initialization. No data migration from v0.2.0 is supported.** + +**v1.0.0 tables:** + +- `clusters` — carried over from v0.2.0 with new soft-delete columns (`deleted_time`, `deleted_by`) +- `node_pools` — carried over from v0.2.0 with new soft-delete columns +- `resources` — new generic table for v1.0.0 entity types (WifConfig, Channel, Version); labels stored as JSONB +- `adapter_statuses` — unchanged from v0.2.0 + +Status conditions remain JSONB columns on their respective tables. The `Ready` condition is migrated to `Reconciled` (see [§3.1](#31-aggregated-condition-renames)). + +All existing v0.2.0 data is discarded. All existing deployments are disposable integration environments owned by internal partner teams. + +#### Required Action + +1. Deploy v1.0.0 with a **fresh, empty database** +2. Do **not** attempt to connect v1.0.0 to a v0.2.0 database +3. Re-register all resources (clusters, node pools) via the API after deployment +4. Existing cluster state is lost — adapters must re-provision or re-discover + +> **If you don't act:** The API fails to start or exhibits undefined behavior when encountering the old schema. + +--- + +### 4.2 Cascade delete on child resources + +#### What Changed + +v1.0.0 introduces cascade delete for child resources. In v0.2.0, DELETE on clusters was not functional (`501 Not Implemented`), so no cascade behavior existed. + +The delete policy is hardcoded per entity type in the entity descriptor — operators cannot change it via configuration (see [§1.4](#14-new-entity-types-registered-in-v100) for the full table): + +- **NodePool** → `cascade`: deleting a Cluster cascade-deletes all its NodePools +- **Version** → `restrict`: deleting a Channel is blocked while it has active Versions + +#### Required Action + +Be aware that `DELETE /clusters/{id}` now cascade-deletes all child NodePools. There is no per-request opt-out. + +```bash +curl -X DELETE "${API_URL}/api/hyperfleet/v1/clusters/${ID}" +# Cascade-deletes all NodePools automatically +# Returns 409 Conflict if restrict-policy children exist (e.g., Channel with active Versions) +``` + +> **If you don't act:** Deleting a Cluster silently cascade-deletes all its NodePools. If NodePool preservation is needed, delete NodePools individually first, or do not delete the parent Cluster. + +--- + +### 4.3 Unknown config fields cause startup errors + +#### What Changed + +Any unexpected property in a configuration file triggers an error at load time. This prevents silent misconfiguration from typos or leftover v0.2.0 fields. + +#### Required Action + +Before deploying v1.0.0, remove any configuration properties that are not part of the v1.0.0 schema. Test your configuration against the v1.0.0 binary before deploying. + +> **If you don't act:** The application refuses to start with an error message identifying the unexpected field. + +--- + +### 4.4 No runtime config reloading + +#### Constraint + +HyperFleet applications do not support runtime configuration reloading. This is not a change from v0.2.0 but is called out here because the v1.0.0 upgrade involves significant configuration changes ([§1.1](#11-tracing-disabled-by-default)–[§1.5](#15-condition-mapping-rules)). All configuration changes require an application restart. + +#### Required Action + +**No action required** — plan for pod restarts when applying v1.0.0 configuration changes. + +--- + +## 5. New Features (Informational) + +These features are new in v1.0.0 and do not require action from existing operators or adapters: + +| Feature | Description | Reference | +|---------|-------------|-----------| +| **CEL Condition Mapping** | Operators can declaratively copy/transform adapter conditions into customer-visible resource conditions | [Condition Mapping Design](../../components/api-service/condition-mapping-design.md) | +| **Generic Resource Registry** | New entity types (WifConfig, Channel, Version) managed through a single `Resource` model and entity registry | [Generic Resource Registry Design](../generic-resource-registry-design.md) | +| **Resource References** | Non-ownership associations between resources with cardinality constraints and deletion restriction | [Generic Resource Registry Design — §9](../generic-resource-registry-design.md) | +| **Force Delete** | Operators can force-delete resources stuck in `Finalizing` state via `POST /{id}/force-delete` | [Force Deletion Design](../force-deletion-design.md) | +| **Automatic Hard Delete** | When all adapters report `Finalized=True`, the API hard-deletes records atomically within the status PUT transaction | [Hard Delete Design](../../components/api-service/hard-delete-design.md) | +| **Sentinel `condition()` CEL Helper** | Decision expressions can use `condition(name)` to access any condition type's fields | [Sentinel Design](../../components/sentinel/sentinel.md) | +| **OpenTelemetry Tracing** | When enabled, all components use OpenTelemetry with W3C Trace Context propagation | [Tracing Standard](../../standards/tracing.md) | + +--- + +## Post-Upgrade Verification + +After deploying v1.0.0, run this smoke-test checklist to confirm the upgrade succeeded: + +- [ ] **New entity endpoints available:** `GET /api/hyperfleet/v1/channels` and `GET /api/hyperfleet/v1/wifconfigs` return 200 +- [ ] **Resource creation works:** `POST /api/hyperfleet/v1/clusters` with a valid spec returns `201 Created` +- [ ] **Adapter status reporting works:** `PUT /api/hyperfleet/v1/clusters/{id}/statuses` returns `201 Created` +- [ ] **Aggregated conditions use new names:** `GET /api/hyperfleet/v1/clusters/{id}` shows `Reconciled` and `LastKnownReconciled` in `status.conditions` (not `Ready`/`Available`) +- [ ] **Sentinel publishes events:** Verify that Sentinel produces reconciliation CloudEvents after a resource is created (while `Reconciled` is `False`, Sentinel triggers adapter work) +- [ ] **Tracing (if enabled):** Confirm traces appear in your observability backend with W3C `traceparent` propagation across components +- [ ] **Configuration strict mode:** Verify that adding an unknown field to a component's config file causes a startup error (confirms [§4.3](#43-unknown-config-fields-cause-startup-errors) is active)