Persist operation state in server and reuse returned snapshots
This commit is contained in:
63
docs/product/application-state.md
Normal file
63
docs/product/application-state.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Harbor application state v1
|
||||
|
||||
`GET /api/state` is the canonical Harbor domain snapshot. Successful POST and DELETE endpoints return the same snapshot as `state` while retaining their v0 response fields for compatibility.
|
||||
|
||||
```json
|
||||
{
|
||||
"apiVersion": 1,
|
||||
"revision": 42,
|
||||
"generatedAt": "2026-07-11T15:00:00.000Z",
|
||||
"mode": "client",
|
||||
"subscription": {
|
||||
"status": "ready",
|
||||
"host": "provider.example/…",
|
||||
"fetchedAt": "2026-07-11T14:58:00.000Z",
|
||||
"userInfo": {}
|
||||
},
|
||||
"selection": {
|
||||
"desiredServerId": "Amsterdam",
|
||||
"appliedServerId": "Amsterdam"
|
||||
},
|
||||
"connection": {
|
||||
"desired": "running",
|
||||
"process": "running",
|
||||
"startedAt": "2026-07-11T14:59:10.000Z",
|
||||
"lastError": null
|
||||
},
|
||||
"route": {
|
||||
"mode": "local-vpn",
|
||||
"gatewayAddress": null,
|
||||
"lastVerifiedAt": null,
|
||||
"reason": "auto"
|
||||
},
|
||||
"operation": {
|
||||
"kind": null,
|
||||
"status": "idle",
|
||||
"startedAt": null,
|
||||
"error": null
|
||||
},
|
||||
"servers": []
|
||||
}
|
||||
```
|
||||
|
||||
The backend owns subscription metadata, servers, desired/applied selection, desired/process connection state, route and current operation. React may keep only unsaved form values, pending selection and visual state. Browser transport freshness is not part of this contract.
|
||||
|
||||
## Revision rules
|
||||
|
||||
`revision` is persisted in the existing `state.json` and increases on externally visible transitions, including operation start/completion/failure, import, refresh, forget, apply, start, stop and Gateway Auto changes. `generatedAt` is response metadata and does not change revision by itself.
|
||||
|
||||
A consumer must eventually apply only snapshots whose revision is at least its current revision. The frontend comparison and stale/offline transport envelope are intentionally handled by TASK-002 and TASK-003.
|
||||
|
||||
## Desired and applied state
|
||||
|
||||
`selection.desiredServerId` records the user's requested server. `selection.appliedServerId` changes only after its sing-box configuration has been applied. Likewise, `connection.desired` records intent while `connection.process` reports the observed runtime. A failed operation can therefore leave desired and applied values different without pretending that the request succeeded.
|
||||
|
||||
Server IDs are currently derived from the existing trimmed subscription tag. Stable IDs across cosmetic renames are deferred to TASK-008.
|
||||
|
||||
## Compatibility and migration
|
||||
|
||||
No path, volume or file is renamed. A legacy `state.json` without `revision`, `appliedTag` or `connectionDesired` is normalized on read: revision starts at `0`, the legacy `selectedTag` is treated as both desired and applied, and connection intent is inferred from the existing config. The next domain write stores the added fields. Existing unknown fields remain untouched.
|
||||
|
||||
During the v0 compatibility window, the snapshot also exposes `selectedTag`, `singboxRunning`, `servers[].tag`, `gatewayAuto` and the other previous GET fields. Mutation responses retain their previous result fields and add `state`. The canonical `subscription` object never contains the full subscription URL.
|
||||
|
||||
Rollback is code-only: deploy the previous build. The added persisted fields are ignored by the previous implementation, so no data rewrite is needed. Revisions created by the newer build remain harmless integers in `state.json`.
|
||||
Reference in New Issue
Block a user