Skip to content

Commit f9e175b

Browse files
committed
HYPERFLEET-1017 - refactor: rename aggregated Available condition to LastKnownReconciled
Rename the API-computed aggregated condition from Available to LastKnownReconciled. The adapter-level Available condition remains unchanged. Includes backward compatibility for legacy DB records, differentiated reason/message for false conditions, minItems: 3 enforcement in OpenAPI schemas, and updated documentation.
1 parent 60956b9 commit f9e175b

12 files changed

Lines changed: 4034 additions & 145 deletions

docs/api-operator-guide.md

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ A practical guide for deploying, configuring, and operating the HyperFleet API c
1717
- [Rules to accept/discard adapter reports](#rules-to-acceptdiscard-adapter-reports)
1818
- [Computing `observed_generation`](#computing-observed_generation)
1919
- [Computing `status.conditions[type==Ready].last_updated_time`](#computing-statusconditionstypereadylast_updated_time)
20-
- [Computing `status.conditions[type==Available].last_updated_time`](#computing-statusconditionstypeavailablelast_updated_time)
21-
- [Computing `last_transition_time` for both `Ready` and `Available`](#computing-last_transition_time-for-both-ready-and-available)
20+
- [Computing `status.conditions[type==LastKnownReconciled].last_updated_time`](#computing-statusconditionstypelastknownreconciledlast_updated_time)
21+
- [Computing `last_transition_time` for both `Ready` and `LastKnownReconciled`](#computing-last_transition_time-for-both-ready-and-lastknownreconciled)
2222
3. [Configuration Reference](#3-configuration-reference)
2323
- [Adapter Requirements (REQUIRED)](#31-adapter-requirements-required)
2424
- [Database Configuration](#32-database-configuration)
@@ -108,7 +108,7 @@ Resource (e.g., Cluster)
108108

109109
2. **Automatic Version Tracking (generation)**: Every time you update the `spec`, the API automatically increments the `generation` counter. This allows distributed adapters to detect when they need to reconcile infrastructure changes.
110110

111-
3. **Observed State (status)**: Adapters report their progress and results back to the API via status endpoints. The API aggregates these reports into unified resource-level conditions (e.g., `Ready`, `Available`).
111+
3. **Observed State (status)**: Adapters report their progress and results back to the API via status endpoints. The API aggregates these reports into unified resource-level conditions (e.g., `Ready`, `LastKnownReconciled`).
112112

113113
4. **Filtering (labels)**: Labels are key-value pairs you can attach to resources for organization and filtering (e.g., `environment: production`, `region: us-east-1`). E.g., Sentinel instances can define resource selectors based on labels to watch specific subsets of resources, enabling horizontal scaling across multiple Sentinel deployments.
114114

@@ -147,7 +147,13 @@ GET /api/hyperfleet/v1/clusters/{id}
147147
"status": {
148148
"conditions": [
149149
{
150-
"type": "Available",
150+
"type": "Reconciled",
151+
"status": "True",
152+
"observed_generation": 1,
153+
"last_transition_time": "2026-03-10T07:56:35Z"
154+
},
155+
{
156+
"type": "LastKnownReconciled",
151157
"status": "True",
152158
"observed_generation": 1,
153159
"last_transition_time": "2026-03-10T07:56:35Z"
@@ -164,7 +170,7 @@ GET /api/hyperfleet/v1/clusters/{id}
164170
"updated_time": "2026-03-10T07:56:35Z"
165171
}
166172

167-
→ API returns aggregated status with Available and Ready conditions
173+
→ API returns aggregated status with Reconciled, LastKnownReconciled, and Ready conditions
168174

169175
# 3. View adapter statuses
170176
GET /api/hyperfleet/v1/clusters/{id}/statuses
@@ -323,7 +329,7 @@ HyperFleet API aggregates the condition values reported by adapters associated w
323329
| Condition | Meaning | When True |
324330
|-----------|---------|-----------|
325331
| **Ready** | Resource is fully reconciled at current spec | All registered adapters report `Available=True` at the **current** `resource.spec.generation` |
326-
| **Available** | Resource is operational at any known good configuration | All registered adapters report `Available=True` (at any generation) |
332+
| **LastKnownReconciled** | Resource is operational at any known good configuration | All registered adapters report `Available=True` for a common `observed_generation`, or sticky-true is preserved when adapters are transitioning to a new generation |
327333

328334
**Note**: The meaning of the field `last_updated_time` for the aggregated conditions has special meaning. It doesn't reflect the last time it was updated from adapters but the OLDEST time it can be considered to be valid.
329335

@@ -337,16 +343,16 @@ The resource `status.conditions` array contains:
337343
- `True`: All required adapters `conditions[type=Available].status==True` at current spec generation
338344
- `False`: Any other combination of conditions
339345

340-
- **Available** - The resource is reconciled at a generation of the spec, current or past
346+
- **LastKnownReconciled** - The resource is reconciled at a generation of the spec, current or past
341347
- This condition is stateful meaning that is computed taking into account its previous values of `status` and `observed_generation`
342348
- This condition is "best effort", since there are cases that can not be covered correctly.
343349
- `True`:
344350
- All required adapters `conditions[type=Available].status==True` for the same `observed_generation`
345351
- Current value `status==True` and required adapters `conditions[type=Available]` at mixed `observed_generation`
346352
- `False`: Any other combination of conditions
347-
- e.g. `Available=True` for `observed_generation==1`
348-
- One adapter reports `Available=False` for `observed_generation=1` `Available` transitions to `False`
349-
- One adapter reports `Available=False` for `observed_generation=2` `Available` keeps its `True` status
353+
- e.g. `LastKnownReconciled=True` for `observed_generation==1`
354+
- One adapter reports `Available=False` for `observed_generation=1` `LastKnownReconciled` transitions to `False`
355+
- One adapter reports `Available=False` for `observed_generation=2` `LastKnownReconciled` keeps its `True` status
350356

351357
- One **per-adapter** condition for each required adapter that has reported, mirroring the adapter's `conditions[type=Available]`:
352358
- `type`: Derived from the adapter name — PascalCase with `Successful` suffix (e.g., `adapter1` → `Adapter1Successful`, `my-adapter` → `MyAdapterSuccessful`)
@@ -392,10 +398,10 @@ These are API examples for a resource and resource statuses:
392398
"last_transition_time": "2021-01-01T10:00:00Z"
393399
},
394400
{
395-
"type": "Available",
401+
"type": "LastKnownReconciled",
396402
"status": "True",
397-
"reason": "All adapters reported Available True for the same generation",
398-
"message": "All adapters reported Available True for the same generation",
403+
"reason": "AllAdaptersReconciled",
404+
"message": "All required adapters report Available=True for the tracked generation",
399405
"observed_generation": 1,
400406
"created_time": "2021-01-01T10:00:00Z",
401407
"last_updated_time": "2021-01-01T10:00:00Z",
@@ -497,23 +503,23 @@ These are API examples for a resource and resource statuses:
497503
When a resource is created:
498504

499505
- Initial `generation` is 1 and aggregated conditions are evaluated
500-
- `observed_generation` for `Ready` and `Available` aggregated conditions is 1
501-
- `last_updated_time` and `last_transition_time` for `Ready` and `Available` aggregated conditions is `resource.last_updated_time`
506+
- `observed_generation` for `Ready` and `LastKnownReconciled` aggregated conditions is 1
507+
- `last_updated_time` and `last_transition_time` for `Ready` and `LastKnownReconciled` aggregated conditions is `resource.last_updated_time`
502508

503509
When a resource is changed:
504510

505511
- `resource.generation` gets incremented and aggregated conditions are re-evaluated
506512
- `status.conditions[type==Ready].observed_generation` always follows `resource.generation`
507-
- `status.conditions[type==Available].observed_generation` changes when all required adapters `condition[type==Available].observed_generation==resource.generation` otherwise remains unchanged.
513+
- `status.conditions[type==LastKnownReconciled].observed_generation` changes when all required adapters `condition[type==Available].observed_generation==resource.generation` otherwise remains unchanged.
508514

509515
##### Computing `observed_generation`
510516

511517
- For `Ready` it always matches `resource.generation`
512-
- For `Available`:
518+
- For `LastKnownReconciled`:
513519
- If all required adapters have a common `observed_generation` it will match the common value
514520
- If required adapters have mixed `observed_generation`
515-
- If `Available` is `True`, `observed_generation` remains at its current value
516-
- If `Available` is `False`, `observed_generation` will get the value of the `max(condition[type==Available].observed_generation)`
521+
- If `LastKnownReconciled` is `True`, `observed_generation` remains at its current value
522+
- If `LastKnownReconciled` is `False`, `observed_generation` will get the value of the `max(condition[type==Available].observed_generation)`
517523

518524
##### Computing `status.conditions[type==Ready].last_updated_time`
519525

@@ -526,14 +532,14 @@ The meaning of `last_updated_time` in the aggregated conditions refers to the ne
526532
- Why do we want to keep the "oldest" value? because if it is too old, we need to trigger a reconciliation
527533
- When some required adapter conditions `condition[type==Available].observed_generation==resource.generation` then `last_updated_time=min(statuses[].conditions[type==Available && observed_generation==resource.generation].observed_time)`
528534

529-
##### Computing `status.conditions[type==Available].last_updated_time`
535+
##### Computing `status.conditions[type==LastKnownReconciled].last_updated_time`
530536

531537
- If all required adapters have `condition[type==Available].observed_generation` at the same value then `last_updated_time=min(statuses[].conditions[type==Available].observed_time)`
532538
- If not all required adapters have `condition[type==Available].observed_generation` at the same value:
533539
- If any adapter at current `observed_generation==X` has `conditions[type==Available].status==False` then `last_updated_time=min(adapters[type==Available && observed_generation==X].observed_time`
534540
- In any other case `last_updated_time` is kept unchanged
535541

536-
##### Computing `last_transition_time` for both `Ready` and `Available`
542+
##### Computing `last_transition_time` for both `Ready` and `LastKnownReconciled`
537543

538544
- Meaning is last time this condition’s status (True / False) changed, regardless of the existing and new `observed_generation`
539545
- This property is stateful since it relies on the existing value to determine if there has been a transition

docs/api-resources.md

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,17 @@ POST /api/hyperfleet/v1/clusters/{cluster_id}/statuses
5353
"status": {
5454
"conditions": [
5555
{
56-
"type": "Available",
56+
"type": "Reconciled",
57+
"status": "False",
58+
"reason": "AwaitingAdapters",
59+
"message": "Waiting for adapters to report status",
60+
"observed_generation": 1,
61+
"created_time": "2025-01-01T00:00:00Z",
62+
"last_updated_time": "2025-01-01T00:00:00Z",
63+
"last_transition_time": "2025-01-01T00:00:00Z"
64+
},
65+
{
66+
"type": "LastKnownReconciled",
5767
"status": "False",
5868
"reason": "AwaitingAdapters",
5969
"message": "Waiting for adapters to report status",
@@ -79,7 +89,7 @@ POST /api/hyperfleet/v1/clusters/{cluster_id}/statuses
7989

8090
</details>
8191

82-
**Note**: Status initially has `Available=False` and `Ready=False` conditions until adapters report status.
92+
**Note**: Status initially has `Reconciled=False`, `LastKnownReconciled=False`, and `Ready=False` conditions until adapters report status.
8393

8494
### Get Cluster
8595

@@ -108,10 +118,20 @@ POST /api/hyperfleet/v1/clusters/{cluster_id}/statuses
108118
"status": {
109119
"conditions": [
110120
{
111-
"type": "Available",
121+
"type": "Reconciled",
112122
"status": "True",
113-
"reason": "ResourceAvailable",
114-
"message": "Cluster is accessible",
123+
"reason": "ReadyAllReconciled",
124+
"message": "All required adapters reported Available=True or Finalized=True at the current generation",
125+
"observed_generation": 1,
126+
"created_time": "2025-01-01T00:00:00Z",
127+
"last_updated_time": "2025-01-01T00:00:00Z",
128+
"last_transition_time": "2025-01-01T00:00:00Z"
129+
},
130+
{
131+
"type": "LastKnownReconciled",
132+
"status": "True",
133+
"reason": "AllAdaptersReconciled",
134+
"message": "All required adapters report Available=True for the tracked generation",
115135
"observed_generation": 1,
116136
"created_time": "2025-01-01T00:00:00Z",
117137
"last_updated_time": "2025-01-01T00:00:00Z",
@@ -304,7 +324,17 @@ POST /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}/statuses
304324
"status": {
305325
"conditions": [
306326
{
307-
"type": "Available",
327+
"type": "Reconciled",
328+
"status": "False",
329+
"reason": "AwaitingAdapters",
330+
"message": "Waiting for adapters to report status",
331+
"observed_generation": 1,
332+
"created_time": "2025-01-01T00:00:00Z",
333+
"last_updated_time": "2025-01-01T00:00:00Z",
334+
"last_transition_time": "2025-01-01T00:00:00Z"
335+
},
336+
{
337+
"type": "LastKnownReconciled",
308338
"status": "False",
309339
"reason": "AwaitingAdapters",
310340
"message": "Waiting for adapters to report status",
@@ -361,10 +391,17 @@ POST /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}/statuses
361391
"status": {
362392
"conditions": [
363393
{
364-
"type": "Available",
394+
"type": "Reconciled",
395+
"status": "True",
396+
"reason": "ReadyAllReconciled",
397+
"message": "All required adapters reported Available=True or Finalized=True at the current generation",
398+
"observed_generation": 1
399+
},
400+
{
401+
"type": "LastKnownReconciled",
365402
"status": "True",
366-
"reason": "ResourceAvailable",
367-
"message": "NodePool is accessible",
403+
"reason": "AllAdaptersReconciled",
404+
"message": "All required adapters report Available=True for the tracked generation",
368405
"observed_generation": 1
369406
},
370407
{
@@ -446,8 +483,9 @@ See **[search.md](search.md)** for complete documentation.
446483
The status object contains synthesized conditions computed from adapter reports:
447484

448485
- `conditions` - Array of resource conditions, including:
449-
- **Available** - Whether resource is running at any known good configuration
450-
- **Ready** - Whether all adapters have processed current spec generation
486+
- **Reconciled** - Whether all adapters have reconciled at the current spec generation
487+
- **LastKnownReconciled** - Whether resource is running at any known good configuration
488+
- **Ready** *(deprecated — use Reconciled)* - Whether all adapters have processed current spec generation
451489
- Additional conditions from adapters (with `observed_generation`, timestamps)
452490

453491
### Condition Fields

docs/search.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,14 @@ Label keys must contain only lowercase letters (a-z), digits (0-9), and undersco
9898

9999
Query resources by status conditions: `status.conditions.<Type>='<Status>'`
100100

101-
Condition types must be PascalCase (`Ready`, `Available`) and status must be `True` or `False` for resource conditions.
101+
Condition types must be PascalCase (`Ready`, `LastKnownReconciled`) and status must be `True` or `False` for resource conditions.
102102

103103
**Note:** Only the `=` operator is supported for condition queries. Other operators (`!=`, `<`, `>`, `in`, etc.) will return an error. The `NOT` operator is not supported with condition queries (`status.conditions.<Type>` or `status.conditions.<Type>.<Subfield>`) and will return a `400 Bad Request` error. Use the inverse condition value instead (e.g., `status.conditions.Ready='False'` rather than `NOT status.conditions.Ready='True'`).
104104

105105
```bash
106106
# Find available clusters
107107
curl -G "http://localhost:8000/api/hyperfleet/v1/clusters" \
108-
--data-urlencode "search=status.conditions.Available='True'"
108+
--data-urlencode "search=status.conditions.LastKnownReconciled='True'"
109109

110110
# Find clusters that are not ready
111111
curl -G "http://localhost:8000/api/hyperfleet/v1/clusters" \

0 commit comments

Comments
 (0)