@serve.zone/interfaces 31.8.0 → 32.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/.smartconfig.json +1 -0
  2. package/changelog.md +512 -0
  3. package/dist_ts/00_commitinfo_data.js +3 -3
  4. package/dist_ts/appstore/types.d.ts +4 -6
  5. package/dist_ts/appstore/types.js +5 -5
  6. package/dist_ts/data/backup.d.ts +3 -26
  7. package/dist_ts/data/cluster.d.ts +36 -11
  8. package/dist_ts/data/cluster.js +33 -22
  9. package/dist_ts/data/clusteringress.d.ts +8 -0
  10. package/dist_ts/data/clusteringress.js +13 -8
  11. package/dist_ts/data/clusternode.d.ts +121 -74
  12. package/dist_ts/data/clusternode.js +109 -132
  13. package/dist_ts/data/clusterruntime.d.ts +39 -0
  14. package/dist_ts/data/clusterruntime.js +11 -0
  15. package/dist_ts/data/coremail.d.ts +3 -7
  16. package/dist_ts/data/coremail.js +2 -2
  17. package/dist_ts/data/coremail.runtime.d.ts +0 -1
  18. package/dist_ts/data/coremail.runtime.js +5 -22
  19. package/dist_ts/data/deploymentadoption.d.ts +0 -2
  20. package/dist_ts/data/deploymentadoption.js +40 -6
  21. package/dist_ts/data/deploymentoperation.js +16 -3
  22. package/dist_ts/data/deploymentpreflight.d.ts +0 -2
  23. package/dist_ts/data/deploymentpreflight.js +1 -1
  24. package/dist_ts/data/immutableimage.d.ts +38 -21
  25. package/dist_ts/data/immutableimage.js +45 -5
  26. package/dist_ts/data/index.d.ts +5 -1
  27. package/dist_ts/data/index.js +5 -1
  28. package/dist_ts/data/isolatedrestore.d.ts +139 -21
  29. package/dist_ts/data/isolatedrestore.golden.d.ts +0 -1
  30. package/dist_ts/data/isolatedrestore.golden.js +6 -9
  31. package/dist_ts/data/isolatedrestore.js +176 -40
  32. package/dist_ts/data/mail.d.ts +1 -1
  33. package/dist_ts/data/networkaddressplan.d.ts +78 -0
  34. package/dist_ts/data/networkaddressplan.js +114 -0
  35. package/dist_ts/data/networkauthority.d.ts +20 -22
  36. package/dist_ts/data/networkauthority.js +29 -43
  37. package/dist_ts/data/networkcredential.d.ts +10 -10
  38. package/dist_ts/data/networkcredential.js +16 -16
  39. package/dist_ts/data/networkdelivery.d.ts +18 -18
  40. package/dist_ts/data/networkdelivery.js +16 -16
  41. package/dist_ts/data/networklease.d.ts +18 -7
  42. package/dist_ts/data/networklease.js +29 -7
  43. package/dist_ts/data/networknode.d.ts +37 -0
  44. package/dist_ts/data/networknode.js +6 -0
  45. package/dist_ts/data/networkprojection.d.ts +59 -60
  46. package/dist_ts/data/networkprojection.js +53 -57
  47. package/dist_ts/data/networkprotection.d.ts +27 -28
  48. package/dist_ts/data/networkprotection.js +36 -36
  49. package/dist_ts/data/networkrouter.d.ts +11 -11
  50. package/dist_ts/data/networkrouter.js +1 -1
  51. package/dist_ts/data/networksigning.d.ts +16 -18
  52. package/dist_ts/data/networksigning.js +20 -21
  53. package/dist_ts/data/nodecredential.d.ts +4 -6
  54. package/dist_ts/data/nodeenrollment.d.ts +35 -40
  55. package/dist_ts/data/nodeenrollment.js +57 -72
  56. package/dist_ts/data/privatenetwork.d.ts +21 -24
  57. package/dist_ts/data/privatenetwork.js +39 -54
  58. package/dist_ts/data/runtimeassignment.d.ts +50 -34
  59. package/dist_ts/data/runtimeassignment.js +79 -45
  60. package/dist_ts/data/runtimeconfig.d.ts +21 -22
  61. package/dist_ts/data/runtimeconfig.js +37 -100
  62. package/dist_ts/data/runtimesession.d.ts +100 -62
  63. package/dist_ts/data/runtimesession.js +98 -79
  64. package/dist_ts/data/runtimeterminalreceipt.d.ts +28 -29
  65. package/dist_ts/data/runtimeterminalreceipt.js +42 -42
  66. package/dist_ts/data/secret.d.ts +18 -25
  67. package/dist_ts/data/secret.js +12 -52
  68. package/dist_ts/data/servicecredential.d.ts +14 -16
  69. package/dist_ts/data/servicecredential.js +17 -28
  70. package/dist_ts/data/serviceruntime.d.ts +128 -0
  71. package/dist_ts/data/serviceruntime.js +92 -0
  72. package/dist_ts/data/sparknodereport.d.ts +27 -14
  73. package/dist_ts/data/sparknodereport.js +35 -28
  74. package/dist_ts/data/webpush.d.ts +0 -1
  75. package/dist_ts/index.d.ts +2 -1
  76. package/dist_ts/index.js +3 -2
  77. package/dist_ts/platform/objectstorageretention.d.ts +21 -24
  78. package/dist_ts/platform/objectstorageretention.js +6 -18
  79. package/dist_ts/platform/storage.d.ts +1 -3
  80. package/dist_ts/platform/storagemigration.d.ts +0 -7
  81. package/dist_ts/platform/storagemigration.golden.d.ts +0 -1
  82. package/dist_ts/platform/storagemigration.golden.js +8 -12
  83. package/dist_ts/platform/storagemigration.js +6 -51
  84. package/dist_ts/platform/types.d.ts +2 -2
  85. package/dist_ts/private/canonicaljson.d.ts +9 -1
  86. package/dist_ts/private/canonicaljson.js +14 -2
  87. package/dist_ts/private/dataproperties.d.ts +62 -0
  88. package/dist_ts/private/dataproperties.js +117 -0
  89. package/dist_ts/private/networkcontract.d.ts +14 -10
  90. package/dist_ts/private/networkcontract.js +15 -1
  91. package/dist_ts/private/producercontract.d.ts +11 -0
  92. package/dist_ts/private/producercontract.js +21 -0
  93. package/dist_ts/private/releaseversion.d.ts +37 -0
  94. package/dist_ts/private/releaseversion.js +48 -0
  95. package/dist_ts/private/runtimepolicy.d.ts +31 -0
  96. package/dist_ts/private/runtimepolicy.js +90 -0
  97. package/dist_ts/private/tokenlist.d.ts +9 -0
  98. package/dist_ts/private/tokenlist.js +23 -0
  99. package/dist_ts/protocol.d.ts +137 -0
  100. package/dist_ts/protocol.js +255 -0
  101. package/dist_ts/requests/admin.d.ts +8 -8
  102. package/dist_ts/requests/backup.d.ts +102 -112
  103. package/dist_ts/requests/cluster.d.ts +47 -10
  104. package/dist_ts/requests/cluster.js +1 -1
  105. package/dist_ts/requests/clusteringress.d.ts +9 -0
  106. package/dist_ts/requests/clusteringress.js +1 -1
  107. package/dist_ts/requests/config.d.ts +3 -3
  108. package/dist_ts/requests/corestore.d.ts +0 -3
  109. package/dist_ts/requests/corestore.js +5 -9
  110. package/dist_ts/requests/network.d.ts +87 -31
  111. package/dist_ts/requests/node.d.ts +15 -28
  112. package/dist_ts/requests/pallet.d.ts +13 -13
  113. package/dist_ts/requests/runtimesession.d.ts +53 -41
  114. package/dist_ts/requests/secret.d.ts +4 -5
  115. package/dist_ts/requests/secret.js +1 -5
  116. package/dist_ts/requests/service.d.ts +60 -0
  117. package/dist_ts/runtime.corestore.d.ts +56 -74
  118. package/dist_ts/runtime.corestore.databaseexportpackage.d.ts +30 -36
  119. package/dist_ts/runtime.corestore.databaseexportpackage.golden.d.ts +11 -12
  120. package/dist_ts/runtime.corestore.databaseexportpackage.golden.js +54 -71
  121. package/dist_ts/runtime.corestore.databaseexportpackage.js +9 -25
  122. package/dist_ts/runtime.corestore.js +35 -135
  123. package/dist_ts/runtime.corestore.relay.js +2 -7
  124. package/dist_ts/runtime.d.ts +38 -48
  125. package/dist_ts/runtime.js +20 -77
  126. package/dist_ts/runtime.networkprojection.d.ts +4 -4
  127. package/dist_ts/runtime.networkprojection.js +18 -18
  128. package/dist_ts/runtime.workloadinit.d.ts +23 -24
  129. package/dist_ts/runtime.workloadinit.js +6 -13
  130. package/package.json +2 -2
  131. package/readme.md +540 -269
  132. package/ts/00_commitinfo_data.ts +3 -3
  133. package/ts/appstore/types.ts +4 -6
  134. package/ts/data/backup.ts +3 -38
  135. package/ts/data/cluster.ts +51 -34
  136. package/ts/data/clusteringress.ts +12 -12
  137. package/ts/data/clusternode.ts +204 -232
  138. package/ts/data/clusterruntime.ts +45 -0
  139. package/ts/data/coremail.runtime.ts +4 -21
  140. package/ts/data/coremail.ts +3 -7
  141. package/ts/data/deploymentadoption.ts +46 -4
  142. package/ts/data/deploymentoperation.ts +16 -1
  143. package/ts/data/deploymentpreflight.ts +0 -2
  144. package/ts/data/immutableimage.ts +71 -24
  145. package/ts/data/index.ts +5 -1
  146. package/ts/data/isolatedrestore.golden.ts +5 -9
  147. package/ts/data/isolatedrestore.ts +339 -54
  148. package/ts/data/mail.ts +1 -1
  149. package/ts/data/networkaddressplan.ts +175 -0
  150. package/ts/data/networkauthority.ts +45 -61
  151. package/ts/data/networkcredential.ts +24 -24
  152. package/ts/data/networkdelivery.ts +27 -27
  153. package/ts/data/networklease.ts +40 -13
  154. package/ts/data/networknode.ts +37 -0
  155. package/ts/data/networkprojection.ts +119 -124
  156. package/ts/data/networkprotection.ts +54 -55
  157. package/ts/data/networkrouter.ts +14 -14
  158. package/ts/data/networksigning.ts +29 -32
  159. package/ts/data/nodecredential.ts +4 -6
  160. package/ts/data/nodeenrollment.ts +96 -114
  161. package/ts/data/privatenetwork.ts +58 -74
  162. package/ts/data/runtimeassignment.ts +143 -94
  163. package/ts/data/runtimeconfig.ts +62 -122
  164. package/ts/data/runtimesession.ts +185 -138
  165. package/ts/data/runtimeterminalreceipt.ts +72 -73
  166. package/ts/data/secret.ts +28 -76
  167. package/ts/data/servicecredential.ts +33 -43
  168. package/ts/data/serviceruntime.ts +208 -0
  169. package/ts/data/sparknodereport.ts +48 -36
  170. package/ts/data/webpush.ts +0 -1
  171. package/ts/index.ts +2 -0
  172. package/ts/platform/objectstorageretention.ts +36 -55
  173. package/ts/platform/storage.ts +1 -3
  174. package/ts/platform/storagemigration.golden.ts +7 -11
  175. package/ts/platform/storagemigration.ts +5 -55
  176. package/ts/platform/types.ts +2 -2
  177. package/ts/private/canonicaljson.ts +13 -1
  178. package/ts/private/dataproperties.ts +139 -0
  179. package/ts/private/networkcontract.ts +23 -9
  180. package/ts/private/producercontract.ts +19 -0
  181. package/ts/private/releaseversion.ts +70 -0
  182. package/ts/private/runtimepolicy.ts +105 -0
  183. package/ts/private/tokenlist.ts +30 -0
  184. package/ts/protocol.ts +364 -0
  185. package/ts/requests/admin.ts +11 -11
  186. package/ts/requests/backup.ts +112 -117
  187. package/ts/requests/cluster.ts +58 -13
  188. package/ts/requests/clusteringress.ts +9 -0
  189. package/ts/requests/config.ts +3 -3
  190. package/ts/requests/corestore.ts +4 -16
  191. package/ts/requests/network.ts +90 -41
  192. package/ts/requests/node.ts +21 -38
  193. package/ts/requests/pallet.ts +17 -17
  194. package/ts/requests/runtimesession.ts +63 -51
  195. package/ts/requests/secret.ts +5 -10
  196. package/ts/requests/service.ts +81 -0
  197. package/ts/runtime.corestore.databaseexportpackage.golden.ts +77 -95
  198. package/ts/runtime.corestore.databaseexportpackage.ts +51 -73
  199. package/ts/runtime.corestore.relay.ts +1 -10
  200. package/ts/runtime.corestore.ts +118 -268
  201. package/ts/runtime.networkprojection.ts +30 -30
  202. package/ts/runtime.ts +83 -158
  203. package/ts/runtime.workloadinit.ts +36 -48
  204. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.d.ts +0 -902
  205. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.golden.d.ts +0 -35
  206. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.golden.js +0 -315
  207. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.js +0 -3315
  208. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.d.ts +0 -291
  209. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.golden.d.ts +0 -30
  210. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.golden.js +0 -408
  211. package/dist_ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.js +0 -1126
  212. package/ts/runtime.cloudlylegacydeploymentsettlement.golden.ts +0 -421
  213. package/ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.golden.ts +0 -510
  214. package/ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.ts +0 -2470
  215. package/ts/runtime.cloudlylegacydeploymentsettlement.ts +0 -6630
package/readme.md CHANGED
@@ -14,10 +14,10 @@ pnpm add @serve.zone/interfaces
14
14
 
15
15
  ## Public API
16
16
 
17
- The root export exposes five namespaces:
17
+ The root export exposes six namespaces:
18
18
 
19
19
  ```typescript
20
- import { appstore, data, platform, platformservice, requests } from '@serve.zone/interfaces';
20
+ import { appstore, data, platform, platformservice, protocol, requests } from '@serve.zone/interfaces';
21
21
  ```
22
22
 
23
23
  | Namespace | Purpose |
@@ -25,6 +25,7 @@ import { appstore, data, platform, platformservice, requests } from '@serve.zone
25
25
  | `appstore` | App Store catalog, manifest, service requirement, and upgrade contracts. |
26
26
  | `data` | Durable platform object shapes such as clusters, services, deployments, images, domains, DNS entries, secrets, users, status, settings, backups, registries, BaseOS metadata, and task executions. |
27
27
  | `requests` | TypedRequest contracts for Cloudly and serve.zone control-plane RPC methods. |
28
+ | `protocol` | The handshake two peers exchange when a session opens: what each side speaks, the oldest peer it accepts, and the named refusal when they cannot serve one session. |
28
29
  | `platform` | Current platform-service contracts for email, SMS, push notifications, letters, AI, databases, object storage, logging, backups, and SIP. |
29
30
  | `platformservice` | Legacy platform-service namespace kept for older consumers that still depend on the previous layout. |
30
31
 
@@ -44,8 +45,8 @@ import {
44
45
  release identity, attestation, approval, authority, digest, and validator
45
46
  contracts. Its runtime dependency graph is limited to the immutable-image digest
46
47
  validator; unlike `@serve.zone/interfaces/runtime`, it does not load the general
47
- runtime graph, `plugins.js`, SmartCrypto, TypedRequest, Corestore, or settlement
48
- runtime modules.
48
+ runtime graph, `plugins.js`, SmartCrypto, TypedRequest, or Corestore runtime
49
+ modules.
49
50
 
50
51
  ### Directional Image Streams
51
52
 
@@ -94,7 +95,7 @@ these shared contracts alone do not implement an endpoint.
94
95
  unauthenticated request shape must update. Its response remains the public
95
96
  `IClusterNode`, never a persisted backend document.
96
97
 
97
- `data.INodeCredentialMetadataV1` separates `spark` and `pallet` purposes and
98
+ `data.INodeCredentialMetadata` separates `spark` and `pallet` purposes and
98
99
  exposes lifecycle state, generation, rotation-required status and session epoch,
99
100
  but no bearer, hash, socket peer or controller process identity. Infrastructure
100
101
  credentials do not grant organization or workload permissions.
@@ -115,16 +116,39 @@ before enabling rotation or Pallet enrollment.
115
116
 
116
117
  ### Spark Host Reporting
117
118
 
118
- `data.sparkNodeHeartbeatV2Contract` defines the HTTPS POST endpoint
119
- `/spark/v2/nodes/heartbeat` and transport byte limits. Its exact request snapshot
120
- contains a Spark-purpose bearer and `ISparkNodeReportV2`: fractional host CPU,
121
- memory and disk observations, Linux host facts, and the verified bundle version,
122
- source commit and manifest digest. It has no Docker or container-count fields.
123
- Local runtime readiness remains distinct from unverified workload readiness.
119
+ `data.sparkNodeHeartbeatContract` defines the HTTPS POST endpoint
120
+ `/spark/nodes/heartbeat`, transport byte limits and `protocolRefusalStatus`, the
121
+ status a refused offer is answered with. Its exact request snapshot contains a
122
+ Spark-purpose bearer, the sender's `protocol` offer and `ISparkNodeReport`:
123
+ fractional host CPU, memory and disk observations, Linux host facts, and the
124
+ verified bundle version, source commit and manifest digest. It has no Docker or
125
+ container-count fields. Local runtime readiness remains distinct from unverified
126
+ workload readiness.
127
+
128
+ The route reads the bounded body, reads the offer with `protocol.readProtocolOffer`,
129
+ negotiates it against its own `sparkNode` offer and answers a refusal with
130
+ `protocolRefusalStatus` and an `IProtocolRefusal` body — all before the node is
131
+ authenticated, so a peer of another major is refused by name instead of by a
132
+ credential verdict it cannot act on. The receipt carries the controller's own
133
+ offer, so an accepted node judges the session it just reported into.
134
+
135
+ The Swarm-era Spark runtime keeps its own four routes in
136
+ `data.sparkSwarmNodeContracts`, each stating the same three facts as the route
137
+ above — `heartbeat` (`/spark/swarm-nodes/heartbeat`), `metricsSample`, `actionResult`
138
+ and `swarmObservation`, every one of them an `{ endpoint, maxRequestBytes,
139
+ maxResponseBytes }` — so one bounded read serves every Spark route. The bounds
140
+ differ because the bodies do: a heartbeat states the node's whole runtime
141
+ description, a metrics-sample answer carries every action queued for that node, and
142
+ one observation carries up to 1024 observed Swarm nodes. That is a second runtime,
143
+ not an older version of this one: a worker in `coreflow-node` mode posts those
144
+ bodies and a pallet node posts this one, and the family retires with coreflow
145
+ rather than with this release. `ISparkSwarmNodeHeartbeatRequest` is now owned here,
146
+ so Cloudly and Spark no longer carry private copies of it, and every Swarm-era body
147
+ — request and answer alike — carries `protocol` and is judged in the same order.
124
148
 
125
149
  Cloudly must authenticate the bearer against current Spark authority and fence
126
150
  credential generation and session epoch transactionally before recording host
127
- liveness. `ISparkNodeHeartbeatReceiptV2` contains only server-derived acceptance
151
+ liveness. `ISparkNodeHeartbeatReceipt` contains only server-derived acceptance
128
152
  evidence; the sender must match its node ID and credential generation to its exact
129
153
  active identity. Neither a receipt nor the sender's observation time grants
130
154
  current authority or workload readiness. Persist only the report and receipt in
@@ -138,27 +162,28 @@ listener, credentials, persistence, freshness or shutdown.
138
162
 
139
163
  ### Independent Node Enrollment
140
164
 
141
- `requests.node` defines `getNodeEnrollmentStateV1` for current Spark-authenticated
142
- adoption preconditions and `enrollNodeV1` for atomic Spark/Pallet enrollment. These
165
+ `requests.node` defines `getNodeEnrollmentState` for current Spark-authenticated
166
+ adoption preconditions and `enrollNode` for atomic Spark/Pallet enrollment. These
143
167
  contracts do not implement endpoints, authentication, a database or a daemon.
144
168
 
145
169
  The enrollment proposal binds the canonical HTTPS Cloudly origin, hostname,
146
170
  enrollment identity, original bootstrap proof hash and an exact ordered pair of
147
171
  independently prepared Spark/Pallet credential hashes and CAS counters. Fresh Jump
148
172
  starts both at zero; existing-node adoption rotates current Spark and requires
149
- Pallet authority to be absent. `snapshotNodeEnrollmentV1` returns a detached,
150
- frozen snapshot; `computeNodeEnrollmentV1Digest` hashes domain-separated strict
151
- canonical JSON. Neither helper generates or persists credential material.
173
+ Pallet authority to be absent. `snapshotNodeEnrollment` returns a detached,
174
+ frozen snapshot; `computeNodeEnrollmentDigest` hashes strict canonical JSON
175
+ under `nodeEnrollmentContract.digestDomain`, `serve.zone/node-enrollment`.
176
+ Neither helper generates or persists credential material.
152
177
 
153
178
  Transport proof is excluded from the digest to permit exact lost-response
154
179
  recovery. Bootstrap proof must hash to the original proof hash and is allowed only
155
180
  before a receipt exists. Pending-Spark proof must hash to the prepared Spark hash
156
- and is allowed only for an existing exact receipt. `verifyNodeEnrollmentProofV1`
181
+ and is allowed only for an existing exact receipt. `verifyNodeEnrollmentProof`
157
182
  checks this binding and mode only: Cloudly must derive receipt state itself and
158
183
  fence live bootstrap/credential authority in the same owned transaction. Current
159
184
  Jump codes are 12 random bytes encoded as 16 canonical base64url characters.
160
185
 
161
- `bindNodeEnrollmentAcknowledgementV1` checks the complete digest, both owners,
186
+ `bindNodeEnrollmentAcknowledgement` checks the complete digest, both owners,
162
187
  exact next generations and unchanged adopted node ID, returning a detached frozen
163
188
  acknowledgement. Authenticate Cloudly before accepting it. The acknowledgement is
164
189
  immutable commit evidence, not ongoing authority or workload readiness. Server
@@ -168,20 +193,20 @@ local activation independently and converge after restart; there is no shared
168
193
  plaintext vault or cross-local-database ACID claim. Never log proof/request bodies
169
194
  or hashes, put them in URLs, or expose them through transport hooks.
170
195
 
171
- `requests.pallet` defines the node-local `preparePalletNodeEnrollmentV1`,
172
- `bindPalletNodeEnrollmentV1` and `activatePalletNodeEnrollmentV1` methods. They
196
+ `requests.pallet` defines the node-local `preparePalletNodeEnrollment`,
197
+ `bindPalletNodeEnrollment` and `activatePalletNodeEnrollment` methods. They
173
198
  belong only on the protected root-owned Unix control socket, never a network
174
199
  router. Preparation requests initial Pallet authority (zero generation and
175
200
  session epoch) and returns only its independently retained credential hash.
176
- `snapshotPalletNodeEnrollmentPreparationV1` and
177
- `bindPalletNodeEnrollmentPreparationV1` validate that exact exchange.
201
+ `snapshotPalletNodeEnrollmentPreparation` and
202
+ `bindPalletNodeEnrollmentPreparation` validate that exact exchange.
178
203
 
179
204
  Local confirmations explicitly distinguish a durable pending binding from the
180
- exact current active Pallet identity. `bindPalletNodeEnrollmentBoundV1` verifies
205
+ exact current active Pallet identity. `bindPalletNodeEnrollmentBound` verifies
181
206
  the whole enrollment digest and its pre-assignment node ID (null for fresh Jump).
182
- `snapshotPalletNodeEnrollmentActivationV1` captures the exact activation request,
207
+ `snapshotPalletNodeEnrollmentActivation` captures the exact activation request,
183
208
  rejecting extra wrapper fields as well as malformed nested data.
184
- `bindPalletNodeEnrollmentActiveV1` takes that complete activation request and
209
+ `bindPalletNodeEnrollmentActive` takes that complete activation request and
185
210
  verifies the full Cloudly acknowledgement, including both owners and the assigned
186
211
  node ID. Both confirmation binders return detached frozen
187
212
  snapshots captured before hashing. These are pure content checks: authenticate
@@ -189,7 +214,7 @@ the local transport separately, and have Pallet prove its current owned state
189
214
  before issuing a confirmation. A historical receipt is insufficient, and Pallet
190
215
  activation does not imply Spark activation or workload readiness.
191
216
 
192
- `palletNodeEnrollmentV1Contract` specifies one four-byte unsigned big-endian
217
+ `palletNodeEnrollmentContract` specifies one four-byte unsigned big-endian
193
218
  length-prefixed UTF-8 JSON frame per direction, followed by write-half-close, at
194
219
  most 32768 request bytes and 16384 response bytes excluding the prefix. Implement
195
220
  the listener with half-open response support, strict EOF/trailing-byte rejection,
@@ -199,14 +224,14 @@ No bearer, generic rotation operation or database access is part of this IPC API
199
224
 
200
225
  ### Service Machine Credentials
201
226
 
202
- `requests.admin.getServiceMachineCredentialV1` inspects redacted metadata;
203
- `mutateServiceMachineCredentialV1` creates, rotates or revokes authority for one
227
+ `requests.admin.getServiceMachineCredential` inspects redacted metadata;
228
+ `mutateServiceMachineCredential` creates, rotates or revokes authority for one
204
229
  exact existing service and organization. `ensure` requires absent authority and
205
230
  a null expected generation. Rotation and revocation require the exact current
206
231
  generation. Retain the mutation ID and complete input for retry; a receipt replay
207
232
  returns historical metadata without restoring authority.
208
233
 
209
- `IServiceMachineGrantV1` currently grants only `platform:session`. This is separate
234
+ `IServiceMachineGrant` currently grants only `platform:session`. This is separate
210
235
  from deployment grants, human membership and infrastructure permissions. The
211
236
  backend must authenticate each JWT against the current active credential
212
237
  generation, expiry and exact service ownership. Structural snapshot helpers do
@@ -224,7 +249,7 @@ each request against live canonical membership and policy, reserve aliases
224
249
  transactionally, reject cross-organization attachments, and retain historical
225
250
  receipts. These contracts do not implement the backend or node enforcement.
226
251
 
227
- `data.buildPrivateNetworkFqdnV1` produces
252
+ `data.buildPrivateNetworkFqdn` produces
228
253
  `<alias>.n-<26 lowercase base32 characters>.o-<26 lowercase base32 characters>.internal.`
229
254
  from server-owned immutable DNS keys. Aliases are lowercase ASCII labels unique
230
255
  per network. Display-name edits do not rename DNS. A sole attachment selects its
@@ -233,7 +258,7 @@ suffix. Fully qualified names remain unambiguous across networks. Network policy
233
258
  permits member-to-member connectivity and explicitly configures or denies external
234
259
  DNS forwarding. Creating an alias does not publish public DNS, ingress or ports.
235
260
 
236
- `data.composeServicePrivateNetworkDnsPolicyV1(membership, networks)` derives the
261
+ `data.composeServicePrivateNetworkDnsPolicy(membership, networks)` derives the
237
262
  service's network suffixes, the single selected search suffix, and the intersection
238
263
  of external forwarding permissions. Supply every attached network exactly once,
239
264
  with active state and matching canonical organization/DNS keys. Missing, extra,
@@ -254,7 +279,7 @@ one second. DNS expiry does not prove packet revocation or permit identity reuse
254
279
  Retirement and attachment responses describe desired or historical metadata;
255
280
  consumers must separately track outstanding node revocations and readiness.
256
281
 
257
- `IRuntimeNetworkProtectedAuthorityV1` declares the complete controller-owned IPv4
282
+ `IRuntimeNetworkProtectedAuthority` declares the complete controller-owned IPv4
258
283
  protection set, disjoint private workload/transit pools, protected resolver and
259
284
  platform endpoints, and every affected egress authority, including offline nodes.
260
285
  Its snapshot and digest helpers bind the exact predecessor and all declaration
@@ -265,7 +290,7 @@ the declaration is not a revocation acknowledgement. Prefix arrays are sorted
265
290
  lexically and nonoverlapping; pools and endpoints are sorted by ID; egress owners
266
291
  are ordered by canonical JSON of `[nodeId, runtimeNamespace]`.
267
292
 
268
- `IRuntimeNetworkProtectionReceiptV1` reports a worker's durable, joined native
293
+ `IRuntimeNetworkProtectionReceipt` reports a worker's durable, joined native
269
294
  protection journal separately from projection admission. It binds a complete
270
295
  protected-authority reference, node/controller scope, authenticated reporter,
271
296
  boot identity and exact native journal reference to a monotonic receipt chain.
@@ -273,10 +298,10 @@ A null `nativeBarrier` explicitly removes allocation eligibility. A boot or
273
298
  protection change requires new native journal evidence. These portable values
274
299
  do not inspect a kernel or authenticate their own producer.
275
300
 
276
- `reportRuntimeNetworkProtectionV1` uses the current authenticated physical peer.
301
+ `reportRuntimeNetworkProtection` uses the current authenticated physical peer.
277
302
  Its request binder accepts a historical outbox only against receiver-owned
278
303
  reporter history and identifies that use as historical. The response must match
279
- the exact sent receipt. `bindRuntimeNetworkAllocationProtectionV1` requires the
304
+ the exact sent receipt. `bindRuntimeNetworkAllocationProtection` requires the
280
305
  current persisted protection receipt and durable session for every declared
281
306
  egress owner, in canonical owner order. Missing, unavailable, stale-protection
282
307
  and superseded-session evidence reject. An offline owner cannot be omitted.
@@ -285,15 +310,15 @@ reserving any handoff or workload address. No generic independent-fence flag is
285
310
  provided; such a mechanism needs its own verified owner. Protection receipts
286
311
  neither prove workload readiness nor release packet/address/name quarantine.
287
312
 
288
- `IRuntimeNetworkHandoffLeaseV1` binds an immutable node/router incarnation to its
313
+ `IRuntimeNetworkHandoffLease` binds an immutable node/router incarnation to its
289
314
  protected-authority reference, transit subnet and peers, protocol-qualified source
290
315
  ports, nonzero conntrack zone and 128-bit label. The label is exactly 32 lowercase
291
316
  hex characters. Port ranges are inclusive, sorted by protocol then first port,
292
- and disjoint within each protocol. `bindRuntimeNetworkHandoffLeaseV1` verifies both
317
+ and disjoint within each protocol. `bindRuntimeNetworkHandoffLease` verifies both
293
318
  digests, scope and transit-pool containment and returns detached values. It does
294
319
  not authenticate a projection or attest to an actual namespace/interface.
295
320
 
296
- `snapshotRuntimeNetworkHandoffLedgerV1` verifies at most 256 retained leases for
321
+ `snapshotRuntimeNetworkHandoffLedger` verifies at most 256 retained leases for
297
322
  one node/controller epoch, including quarantined allocations. It rejects host
298
323
  handoff-port collisions even across router/runtime namespace changes, and zone or
299
324
  label reuse within one router incarnation. The caller must transactionally supply
@@ -304,7 +329,7 @@ permits handoff identity reuse. Signed complete projections, live interface proo
304
329
  and the separate router/host apply journal remain responsibilities of Cloudly and
305
330
  Pallet; neither packet admission nor readiness follows from these helpers.
306
331
 
307
- `IRuntimeNetworkWorkloadLeaseV1` is the immutable material referenced by
332
+ `IRuntimeNetworkWorkloadLease` is the immutable material referenced by
308
333
  `assignment.workload.network`. It binds one execution attempt and router
309
334
  incarnation to a workload pool, dedicated unbridged `linkSubnet`, workload
310
335
  `address`, exact `/32` `sourcePrefix`, and router-side `gateway`. The gateway
@@ -312,11 +337,11 @@ also supplies `dnsServer` on port 53, because CRI DNS settings cannot encode a
312
337
  port. Canonical private point-to-point subnets through `/31` are accepted; the
313
338
  owning CNI implementation must qualify its chosen layout. The source prefix,
314
339
  link subnet, VPN control prefix and host/router transit subnet have different
315
- roles. `bindRuntimeNetworkWorkloadLeaseToAssignmentV1` verifies immutable
340
+ roles. `bindRuntimeNetworkWorkloadLeaseToAssignment` verifies immutable
316
341
  assignment identity without a circular assignment digest. Membership, aliases,
317
342
  readiness and projection revisions never change this address material.
318
343
 
319
- `IRuntimeNetworkProjectionV1` contains complete relevant network definitions,
344
+ `IRuntimeNetworkProjection` contains complete relevant network definitions,
320
345
  service memberships and endpoint leases, including remote ready endpoints and
321
346
  declared services with no replicas. It carries the current protected authority,
322
347
  exact historical allocation authorities, local handoff, a maximum 15-minute DNS
@@ -329,7 +354,7 @@ and 4,096 directed grants. These transport bounds do not assert native capacity;
329
354
  the node must preflight the actual SmartVPN, Smartnftables and DNS limits before
330
355
  effectful application.
331
356
 
332
- `validateRuntimeNetworkProjectionV1` checks complete digest and scope bindings,
357
+ `validateRuntimeNetworkProjection` checks complete digest and scope bindings,
333
358
  pool containment, disjoint link subnets, network/organization keys, aliases,
334
359
  resolver authorization and ready-replica uniqueness. It does not authenticate
335
360
  controller inventory, prove observation provenance or authorize allocation reuse.
@@ -338,7 +363,7 @@ retain all affected offline authorities and complete its allocation/barrier
338
363
  transactions before issuance.
339
364
 
340
365
  An endpoint may publish node ports through the optional
341
- `IRuntimeNetworkEndpointV1.publishedPorts`. Each `IRuntimeNetworkPublishedPortV1`
366
+ `IRuntimeNetworkEndpoint.publishedPorts`. Each `IRuntimeNetworkPublishedPort`
342
367
  binds one `hostPort` (1..65535, unique per node and protocol across the
343
368
  projection) to the workload's `targetPort`, optionally on an explicit uplink
344
369
  `hostIp` (never the wildcard or loopback; binding it to the node's uplink is
@@ -349,23 +374,23 @@ An endpoint that publishes nothing omits the member entirely and keeps its exact
349
374
  canonical bytes and digest; a present member is never an empty list, so
350
375
  "publishes nothing" has exactly one encoding.
351
376
 
352
- Operators author that authorization through `getServicePublishedPortsV1` and
353
- `setServicePublishedPortsV1`, modelled on the private-network membership pair.
354
- `IServicePublishedPortsV1` returns the current authorized list with its policy
355
- `revision`, or null when a service never had one. `ISetServicePublishedPortsV1`
356
- returns the accepted `IServicePublishedPortsV1` document with its new
377
+ Operators author that authorization through `getServicePublishedPorts` and
378
+ `setServicePublishedPorts`, modelled on the private-network membership pair.
379
+ `IServicePublishedPorts` returns the current authorized list with its policy
380
+ `revision`, or null when a service never had one. `ISetServicePublishedPorts`
381
+ returns the accepted `IServicePublishedPorts` document with its new
357
382
  `revision` and a `replayed` flag, and carries `expectedRevision` (null for the first document), a retained
358
- `mutationId` for replay, and `IRuntimeNetworkPublishedPortRequestV1` entries of
359
- exactly `protocol`, `hostPort` and `targetPort`: v1 accepts no `hostIp` (an
360
- omitted address means the node uplink) and no ranges. An empty list is a valid
383
+ `mutationId` for replay, and `IRuntimeNetworkPublishedPortRequest` entries of
384
+ exactly `protocol`, `hostPort` and `targetPort`: the request accepts no `hostIp`
385
+ (an omitted address means the node uplink) and no ranges. An empty list is a valid
361
386
  policy meaning the service publishes nothing. Callers never supply an
362
387
  `authorization` reference: Cloudly derives each projection entry's reference
363
388
  from the accepted policy revision and digest, and node-level host-port placement
364
389
  stays Cloudly's decision.
365
390
 
366
- `getRuntimeNetworkProjectionPacketGrantsV1` derives directed member connectivity
391
+ `getRuntimeNetworkProjectionPacketGrants` derives directed member connectivity
367
392
  and explicit local public/platform egress independently of DNS readiness.
368
- `composeRuntimeNetworkProjectionDnsViewsV1` builds one union view per local
393
+ `composeRuntimeNetworkProjectionDnsViews` builds one union view per local
369
394
  workload source address, with every authorized FQDN and complete ready-replica A
370
395
  set. An empty array declares NODATA; absent/unauthorized names remain NXDOMAIN,
371
396
  and IPv4-only service names have AAAA NODATA. The view carries the existing
@@ -384,16 +409,16 @@ asynchronous validation and output retains projection order. This lets Pallet
384
409
  compose only its currently attached sources without duplicating membership rules;
385
410
  the references themselves do not prove native attachments or DNS authority.
386
411
 
387
- The optional `IRuntimeNetworkProjectionV1.router` member carries explicit
388
- router-origin egress selections and withdrawals. Existing persisted V1
389
- projections omit it, retain their exact canonical bytes/digest/signature and grant
390
- no router-origin flows. New issuers can select `resolverIds` and
412
+ The optional `IRuntimeNetworkProjection.router` member carries explicit
413
+ router-origin egress selections and withdrawals. A projection that omits it
414
+ retains its exact canonical bytes, digest and signature and grants no
415
+ router-origin flows. New issuers can select `resolverIds` and
391
416
  `platformEndpointIds` from the current protected inventory; an empty selection
392
417
  denies all router egress. DNS forwarding policy, workload egress and inventory
393
418
  presence alone supply no router grant. The whole member is signed with the
394
419
  projection; it is not an unsigned runtime option.
395
420
 
396
- `getRuntimeNetworkProjectionRouterPacketGrantsV1` validates the complete projection
421
+ `getRuntimeNetworkProjectionRouterPacketGrants` validates the complete projection
397
422
  and returns up to 96 detached exact grants. Each binds the current local handoff
398
423
  reference and its router transit source address to a selected destination ID,
399
424
  IPv4 address, protocol and port. Resolver selection explicitly authorizes both
@@ -413,7 +438,7 @@ must compose both workload and router grants and their separate withdrawals;
413
438
  they must also fence the actual native source, routing and process lifetimes.
414
439
  DNS expiry or readiness changes do not revoke packet authority.
415
440
 
416
- `admitRuntimeNetworkProjectionV1` checks exact predecessor/replay identity and
441
+ `admitRuntimeNetworkProjection` checks exact predecessor/replay identity and
417
442
  rejects changed same-generation content, regressed membership/readiness evidence,
418
443
  changed immutable address/DNS keys, and omitted packet grants without explicit
419
444
  withdrawal. Pending withdrawals/tombstones must carry forward until the caller
@@ -422,7 +447,7 @@ application receipt. That reference is never taken from an incoming ACK. Keep
422
447
  the accepted history and apply journal until native effects and DNS/allocation
423
448
  quarantine are settled; a complete new snapshot does not erase that history.
424
449
 
425
- `IRuntimeNetworkSigningAuthorityV1` is separate from human JWT signing. Install
450
+ `IRuntimeNetworkSigningAuthority` is separate from human JWT signing. Install
426
451
  its current revision through the existing authenticated physical connection and
427
452
  commit it with a durable CAS fence. Newly enrolled nodes may bootstrap the
428
453
  controller's current key generation; existing trust cannot be reset to do so.
@@ -432,9 +457,12 @@ requires newly signed authority for DNS. It does not deny outstanding packets
432
457
  or permit allocation reuse.
433
458
 
434
459
  The Node-only `@serve.zone/interfaces/runtime` export supplies
435
- `createRuntimeNetworkSigningKeyV1`, `signRuntimeNetworkProjectionV1` and
436
- `verifyRuntimeNetworkProjectionV1`. The signature covers the complete canonical
437
- projection, signing-authority reference and dedicated signature domain. Verification
460
+ `createRuntimeNetworkSigningKey`, `signRuntimeNetworkProjection` and
461
+ `verifyRuntimeNetworkProjection`. Signing produces
462
+ `ISignedRuntimeNetworkProjection { authority, projection, signature }`, and the signature
463
+ covers the canonical envelope `{ domain, authority, projection }` under the dedicated
464
+ `serve.zone/runtime-network-projection-signature` domain, so it binds the complete
465
+ projection and the exact signing-authority revision. Verification
438
466
  requires receiver-owned trusted authority and node scope; an envelope contains
439
467
  no key to trust. Private KeyObjects stay process-local; Cloudly persists their
440
468
  exported material only through its encrypted internal secret store. The dedicated
@@ -442,17 +470,17 @@ exported material only through its encrypted internal secret store. The dedicate
442
470
  peer. Their response acknowledges durable admission only, never application,
443
471
  readiness, independent fencing or pool activation.
444
472
 
445
- The `getRuntimeManagedVpnCredentialV1` method contract in `requests.runtimesession`
446
- uses `IGetRuntimeManagedVpnCredentialRequestV1` and
447
- `IGetRuntimeManagedVpnCredentialResponseV1`. Its request carries the exact current
473
+ The `getRuntimeManagedVpnCredential` method contract in `requests.runtimesession`
474
+ uses `IGetRuntimeManagedVpnCredentialRequest` and
475
+ `IGetRuntimeManagedVpnCredentialResponse`. Its request carries the exact current
448
476
  session and signed projection reference. The secret response repeats those
449
477
  bindings and supplies a QUIC relay tuple, managed authority ID, Noise server
450
478
  public key, client keypair and expiry in Unix milliseconds. Keep the response
451
479
  process-local; never persist, hash, log or embed it in a signed projection.
452
480
 
453
- `snapshotGetRuntimeManagedVpnCredentialRequestV1` and its response counterpart
481
+ `snapshotGetRuntimeManagedVpnCredentialRequest` and its response counterpart
454
482
  capture closed detached JSON shapes and canonical 32-byte key encodings.
455
- `bindGetRuntimeManagedVpnCredentialRequestV1(request, projection, trustedSession)`
483
+ `bindGetRuntimeManagedVpnCredentialRequest(request, projection, trustedSession)`
456
484
  validates the complete projection digest and binds node, controller, namespace,
457
485
  physical-session identity and projection reference. The response binder takes
458
486
  `(response, request, projection, trustedSession, now, renewal = null)` and additionally requires
@@ -557,6 +585,14 @@ and every window against its clock, when it admits it. The run must have:
557
585
  verifies no signature. `bindHoldRuntimeNetworkDnsLeaseRenewalsResponse(response, sentRequest)`
558
586
  requires `held` to equal the number of renewals sent.
559
587
 
588
+ A hold is the one call whose outcome the controller cannot read back from its own records, so the
589
+ refusal is the whole answer. `data.holdRuntimeNetworkDnsLeaseRenewalsRefusals` is the frozen list
590
+ of names a relay leads its refusal with — `escrow-run-invalid`, `escrow-node-not-carried`,
591
+ `escrow-run-foreign-session`, `escrow-run-stale`, `escrow-run-unordered` and `escrow-cloudly-only`
592
+ — with `data.THoldRuntimeNetworkDnsLeaseRenewalsRefusal` as their union. The relay writes
593
+ `<name>: <reason>` and the controller matches the name, so neither side keeps its own copy of the
594
+ vocabulary.
595
+
560
596
  The horizon is also the revocation latency: a detached cluster keeps DNS authority until its last
561
597
  held window ends. So a controller must not release an address or a DNS name before the latest
562
598
  `expiresAt` it has signed for that node. Renewals extend DNS only. They grant no packet authority
@@ -568,7 +604,7 @@ node may use: the admitted renewal's if there is one, else the projection's own.
568
604
  bare `IRuntimeNetworkDnsLeaseRenewal` that admission takes and returns, which is what a node
569
605
  stores; Cloudly passes `signed.renewal`. It checks digests and that the renewal names exactly that
570
606
  projection, but verifies no signature. Controller and node use it for every limit tied to the
571
- window, and `bindGetRuntimeManagedVpnCredentialResponseV1` takes the same bare admitted renewal as
607
+ window, and `bindGetRuntimeManagedVpnCredentialResponse` takes the same bare admitted renewal as
572
608
  an optional last argument, so a newly issued managed-VPN credential may run to the end of the
573
609
  renewed window instead of the projection's original one.
574
610
 
@@ -594,17 +630,83 @@ const { renewal: admittedRenewal } = await data.admitRuntimeNetworkDnsLeaseRenew
594
630
  const window = await data.getRuntimeNetworkEffectiveDnsWindow(admittedProjection, admittedRenewal);
595
631
  ```
596
632
 
633
+ ### Runtime Network Address Plan
634
+
635
+ `data.IRuntimeNetworkAddressPlan` is the address space an administrator gives the runtime network,
636
+ and the single input Cloudly composes the protected authority from:
637
+ `{ id: 'address-plan', revision, prefixes, pools, resolvers, platformEndpoints, vpn: { controlPrefix, relay } }`.
638
+ Pools, resolvers and platform endpoints use the protected authority's own element shapes, and the
639
+ relay uses the managed-VPN credential's `{ id, address, port }`. `platformEndpoints` never lists the
640
+ relay: the composed authority adds it, in id order, as the UDP platform endpoint every node dials.
641
+
642
+ `snapshotRuntimeNetworkAddressPlan` refuses any plan that could describe an authority the authority
643
+ contract refuses. It runs `snapshotRuntimeNetworkProtectedAuthority` twice, once over the plan's
644
+ own sets and once with the relay added, so overlapping pools, more than 16 resolvers, a resolver or
645
+ endpoint outside `prefixes` or inside a pool, and a relay that `prefixes` do not cover are all
646
+ refused by that one validator. On top it requires the VPN `controlPrefix` to be a private prefix no
647
+ longer than /30 (`runtimeNetworkAddressPlanContract.maximumControlPrefixLength`) inside `prefixes`
648
+ that overlaps no pool and holds no resolver or platform endpoint, the relay included. No minimum
649
+ length is stated: the private-range rule already keeps it at /8 or longer.
650
+ `composeRuntimeNetworkProtectedAuthority(plan, frame)` is the composition itself: the producer
651
+ supplies the frame (`TRuntimeNetworkProtectedAuthorityFrame`: authority id, generation, controller,
652
+ previous reference and egress authorities) and receives the complete authority with its digest, so
653
+ the validator and the protection producer can never compose differently.
654
+
655
+ `admitRuntimeNetworkAddressPlanChange(previous | null, next)` is the pure decision for a plan write at
656
+ the following revision; any other revision is a contract violation. Leases and the managed VPN
657
+ outlive a plan revision, so it answers:
658
+
659
+ - `unchanged` when the content equals the stored plan; Cloudly writes nothing;
660
+ - `refused` with `plan-vpn-immutable` when the VPN differs, `plan-pool-removed` when any pool is gone
661
+ or changed its purpose or prefix, and `plan-prefix-shrunk` when the new prefixes, however split,
662
+ no longer cover every address the old ones did;
663
+ - `accepted` with the ids of the resolvers and platform endpoints the change removes or changes. An
664
+ entry that keeps its id but differs in any field counts, so moving it goes through the same
665
+ reference check as removing it.
666
+
667
+ `TRuntimeNetworkAddressPlanRefusal` adds `plan-resolver-referenced` and `plan-endpoint-referenced`,
668
+ which only Cloudly can decide against live references, so every plan refusal has one type.
669
+ `requests.network` carries `setRuntimeNetworkAddressPlan { identity, expectedRevision, plan }`
670
+ (`expectedRevision: null` when no plan exists yet) and `getRuntimeNetworkAddressPlan`, which answers
671
+ `null` until the first plan is set.
672
+
673
+ ### Node Network Readiness
674
+
675
+ `requests.network.getRuntimeNetworkNodeReadiness { identity, nodeId }` answers
676
+ `data.IRuntimeNetworkNodeReadiness`: `ready: true` with nothing missing, or `ready: false` with at
677
+ least one `TRuntimeNetworkNodeReadinessGap`. The gaps follow the node producer's order: the node
678
+ joins the protected authority's egress (`egress-pending`), every egress owner reports current
679
+ protection (`protection-receipts-pending`, naming those `owners`, at least one), a handoff is reserved
680
+ (`handoff-pending`), the router selection carries it (`selection-pending`), the node's acknowledged
681
+ projection carries it (`projection-pending`), and the node is a managed VPN member
682
+ (`vpn-member-pending`).
683
+
684
+ `data.IRuntimeRouterIncarnation { id, incarnation }` is a node's router incarnation, from which its
685
+ handoff and router incarnation ids derive; `snapshotRuntimeRouterIncarnation` checks the record.
686
+ `reincarnateRuntimeNetworkRouter { identity, nodeId, expectedIncarnation }` advances it, so the node
687
+ producer reserves the router a fresh handoff, and answers with the new incarnation.
688
+
597
689
  ### Node-Bound Runtime Assignments
598
690
 
599
- The `data` namespace exports `IRuntimeAssignmentV1`, canonical digest helpers,
600
- `evaluateRuntimeAssignmentV1`, and assignment observation validators/evaluators
691
+ The `data` namespace exports `IRuntimeAssignment`, canonical digest helpers,
692
+ `evaluateRuntimeAssignment`, and assignment observation validators/evaluators
601
693
  for Cloudly/Onebox controllers and Pallet nodes. These are shared contracts;
602
694
  they do not install a runtime, schedule containers or expose an RPC endpoint.
603
695
 
604
696
  Each assignment binds a controller incarnation, organization, stable node and
605
697
  replica IDs, one immutable execution attempt, image/configuration and opaque
606
698
  network/storage/secret references. Group and site IDs are nullable topology
607
- provenance. All fields except the digest itself participate in a domain-separated
699
+ provenance. Those workload references are also the authority vocabulary:
700
+ `data.runtimeWorkloadAuthorities` is `['network', 'secrets', 'storage']`, and
701
+ `data.requiredWorkloadAuthorities(workload)` names the ones this workload cannot
702
+ run without — filtered out of that vocabulary and sorted, so the answer is
703
+ duplicate-free and compares byte for byte against the `resolvedAuthorities` a node
704
+ stated when it registered. `data.isResolvedWorkloadAuthorities(value)` is the same
705
+ rule read the other way, for a receiver checking a list it was sent. Canonical is
706
+ strictly increasing in the code-unit order of the names themselves — a rule a peer
707
+ in another language implements from the vocabulary alone, rather than from the
708
+ order this package happens to declare it in — and both directions compare through
709
+ that one rule, so neither can drift from the other. All fields except the digest itself participate in a domain-separated
608
710
  SHA-256 digest. Digests provide integrity and compare-and-swap identity;
609
711
  authentication must separately establish the trusted controller/node scope.
610
712
 
@@ -630,10 +732,10 @@ does not authorize storage relocation or prove fencing.
630
732
 
631
733
  ### Resolved Runtime Configuration
632
734
 
633
- `data.IRuntimeConfigV1` seals the public environment, working directory, resource
735
+ `data.IRuntimeConfig` seals the public environment, working directory, resource
634
736
  and identity policies, target ports, readiness timing and log limits together with
635
- the immutable image invocation. `computeRuntimeConfigV1Digest` hashes that exact
636
- content; `bindRuntimeConfigV1ToAssignment` checks the config reference, service,
737
+ the immutable image invocation. `computeRuntimeConfigDigest` hashes that exact
738
+ content; `bindRuntimeConfigToAssignment` checks the config reference, service,
637
739
  organization, image and selected platform against one assignment. Secret material,
638
740
  registry credentials and host paths remain outside the configuration.
639
741
 
@@ -648,14 +750,14 @@ Existing numeric configurations retain their exact meaning and digest.
648
750
 
649
751
  ### Authenticated Runtime Sessions
650
752
 
651
- `requests.runtimesession` defines `registerPalletRuntimeSessionV1`,
652
- `applyRuntimeAssignmentV1`, `reportRuntimeAssignmentObservationV1`, and
653
- `getRuntimeRegistryCredentialV1`. Pallet initiates WSS through its configured
753
+ `requests.runtimesession` defines `registerPalletRuntimeSession`,
754
+ `applyRuntimeAssignment`, `reportRuntimeAssignmentObservation`, and
755
+ `getRuntimeRegistryCredential`. Pallet initiates WSS through its configured
654
756
  Cloudly HTTPS origin, or, on a relayed cluster, through the relay origin its
655
757
  cluster publishes in [`ICluster.data.relay`](#cluster-relay-block) while Cloudly
656
758
  remains the credential authority. The receiver derives the physical peer, process instance
657
759
  and registration idempotency; callers cannot supply those authorities. A serialized
658
- `IRuntimeSessionBindingV1` provides content to compare with authenticated peer
760
+ `IRuntimeSessionBinding` provides content to compare with authenticated peer
659
761
  state and does not itself authenticate a connection. Registry credentials are
660
762
  ephemeral, bounded and authorized against an exact current `run` assignment.
661
763
 
@@ -670,14 +772,31 @@ classify content; authentication and atomic receipt persistence remain server-ow
670
772
 
671
773
  A registration is identified by its node, its credential generation and
672
774
  `nodeInstanceNonce`: a canonical lowercase UUID that Pallet generates once per
673
- process start and sends on every registration. Version 31.1 adds that optional
674
- field. One physical socket may carry the registrations of many nodes — a cluster
675
- relay forwards a whole cluster — so the transport alone can no longer tell a
676
- node restart from a replay. A controller must require the nonce whenever the
677
- registering peer is such a relay, and may accept its absence only from a
678
- directly connected node, whose own socket names its process. The nonce is not a
679
- secret, is not authentication, and grants nothing on its own: the bearer still
680
- authenticates every registration.
775
+ process start and sends on every registration. From 32.0.0 the field is required,
776
+ so a controller no longer has a nonce-less case to decide. One physical socket may
777
+ carry the registrations of many nodes — a cluster relay forwards a whole cluster —
778
+ so the transport alone can no longer tell a node restart from a replay. The nonce
779
+ is not a secret, is not authentication, and grants nothing on its own: the bearer
780
+ still authenticates every registration.
781
+
782
+ The registration also states `resolvedAuthorities`: which of
783
+ `data.runtimeWorkloadAuthorities` (`network`, `secrets`, `storage`) this node
784
+ build can resolve, sorted and duplicate-free so a replay compares byte for byte.
785
+ It is a capability of the registering build, never a permission: a controller
786
+ refuses an assignment whose `data.requiredWorkloadAuthorities(workload)` the list
787
+ does not cover, instead of a node accepting work it cannot carry out. It is not
788
+ part of the registration identity — a changed build is a new process and
789
+ therefore a new nonce — so a replay that states different authorities is a
790
+ conflict rather than an update.
791
+
792
+ Every registration carries `protocol`, and the controller judges the body in the
793
+ one server order `protocol.readProtocolOffer` states: the bounded body, the offer,
794
+ `protocol.negotiateProtocol` against this session kind's own offer, and only then
795
+ the exact-key validator, the bearer and any mutation. For a node that order is what
796
+ keeps a refused registration from touching its packet access.
797
+ `registerPalletRuntimeSession` answers `{ session, protocol }`, and
798
+ `data.bindRegisterPalletRuntimeSessionResponse` binds that answer to the request
799
+ the node sent and negotiates the two offers from the node's side.
681
800
 
682
801
  A changed nonce, or a changed credential generation, is a new registration: the
683
802
  controller mints a new binding and a new session epoch and fences the node's
@@ -687,15 +806,16 @@ transport reconnect: a node that rotates behind a relay registers again with its
687
806
  unchanged nonce and its new generation on the same live socket, and the other
688
807
  nodes that socket carries are untouched.
689
808
 
690
- `registerCloudlyClientSessionV1` separately authenticates a current human or
691
- machine JWT on a physical TypedSocket so the server can own its identity tag.
692
- Both registration methods reject HTTP transport. Sensitive requests and registry
809
+ `registerCloudlyClientSession` separately authenticates a current human or
810
+ machine JWT on a physical TypedSocket so the server can own its identity tag; it
811
+ carries the same offer, judged in the same order before the JWT, and answers with
812
+ the server's own offer. Both registration methods reject HTTP transport. Sensitive requests and registry
693
813
  responses must be excluded from hooks, logs, diagnostics and durable journals.
694
814
 
695
815
  ### Joined Runtime Terminal Receipts
696
816
 
697
- `data.IRuntimeAssignmentTerminalReceiptV1` and
698
- `requests.runtimesession.reportRuntimeAssignmentTerminalReceiptV1` carry a separate
817
+ `data.IRuntimeAssignmentTerminalReceipt` and
818
+ `requests.runtimesession.reportRuntimeAssignmentTerminalReceipt` carry a separate
699
819
  receipt for the exact Pallet attempt's joined terminal execution. Sequence 1 binds
700
820
  the generation 2 stop assignment; sequence 2 binds generation 3 removal and the
701
821
  exact stop receipt digest. Both retain the original generation 1 run reference.
@@ -713,7 +833,7 @@ terminal operation but cannot skip the stop/remove chain.
713
833
 
714
834
  The synchronous snapshot helpers validate bounded exact data only. Digest,
715
835
  assignment-chain and receipt-chain helpers do not prove native execution or
716
- authenticate a reporter. `bindRuntimeReporterSessionV1` and the terminal report
836
+ authenticate a reporter. `bindRuntimeReporterSession` and the terminal report
717
837
  binder require the current physical session and immutable server-indexed reporter
718
838
  history from their trusted owners. Reconnect recovery preserves every original
719
839
  receipt field and returns `historical-recovery`; it never restores active
@@ -727,26 +847,77 @@ only the named Pallet attempt. They do not authorize storage relocation, prove
727
847
  whole-host or Docker/Coreflow absence, grant another controller or organization
728
848
  authority, or replace independently qualified readiness and routing evidence.
729
849
 
730
- ### Offline Database Settlement And Packaging
731
-
732
- The Node-only `@serve.zone/interfaces/runtime` export includes isolated V1
733
- metadata and control codecs for describing and validating settlement of one
734
- immutable `smartdb.database.export.v1` file offline. The protocol binds exact
735
- source bytes to source authority A, an
736
- HMAC-authenticated rehearsal, a distinct later production-transform authority
737
- B, an HMAC-authenticated transform authorization, exact transformed-core bytes,
738
- a deterministic HMAC-authenticated terminal journal, exact complete output
739
- bytes, an HMAC-authenticated complete-output receipt, and a bounded result.
740
- The journal ID seed excludes `_id` and `mac`; its HMAC input excludes only
741
- `mac`. Only the external complete-output receipt identifies the final output
742
- bytes. These contracts make no transaction or committed-write claim and leave
743
- the existing transaction settlement contracts unchanged. They neither carry
744
- the export bytes or Base64 content nor execute the settlement.
745
-
746
- The same runtime export provides metadata-only Corestore database-export
747
- package contracts. A package request carries exact input length and SHA-256,
748
- approved source-backup and complete-output-receipt digests, a path-safe attempt
749
- ID, and an explicit `absent` or `exact` allocation expectation. It never carries
850
+ ### Cluster Runtime Phase
851
+
852
+ `data.IClusterRuntime { id, phase, generation, changedAt, changedBy }` records which runtime owns a
853
+ cluster's workloads: `TClusterRuntimePhase` is `swarm`, `draining`, `switching` or `pallet`. It is
854
+ stored beside the cluster document, because a cluster update merges caller data and no caller may
855
+ move a phase with it; `generation` is its compare-and-swap counter. `snapshotClusterRuntime` checks
856
+ the record. Which phase may follow which belongs to the cutover, whose requests ship with it.
857
+
858
+ `requests.cluster.getClusterRuntime { identity, clusterId }` answers `{ runtime, blockers }`. Each
859
+ `TClusterRuntimeBlocker` names what it is about: cluster-wide blockers (`relay-not-enabled`,
860
+ `relay-not-registered`, `address-plan-missing`, `vpn-unconfigured`) name nothing, node blockers
861
+ (`node-without-pallet-credential`, `node-arch-unsupported`) name the `nodeId`, and service blockers
862
+ (`service-multi-cluster-scope`, `service-authority-unsupported`, `platform-binding-present`) name
863
+ the `serviceId`.
864
+
865
+ ### Service Runtime Specs And Status
866
+
867
+ `data.IServiceRuntimeSpec` is what Cloudly runs for one service on a Pallet cluster:
868
+ `{ id, organizationId, clusterId, placement, replicasPerNode, hold, profile, network, revision }`.
869
+ It is stored complete and beside the service document, so a hold written by a cutover never races
870
+ an operator's service edit.
871
+
872
+ - `placement` is `{ kind: 'every-node' }` or `{ kind: 'nodes', nodeIds }`, with `nodeIds` sorted,
873
+ unique, never empty and at most `maximumPinnedNodes`, the protected authority's egress limit.
874
+ - `replicasPerNode` runs from 1 to 64; Cloudly refuses a service desiring more than `maximumReplicas` (64) as `replica-limit`.
875
+ - `profile { cpuMillis, memoryBytes, readiness, logs, readonlyRootfs, runAs }` is the runtime config's
876
+ execution policy ahead of any image: the same readiness and log shapes, and the same CPU, memory
877
+ (at least 16 MiB), identity, log, readiness timing and HTTP request rules, which both validators
878
+ share. `runAs: null` runs the image's own OCI user. Only the service's target ports decide whether
879
+ a readiness probe's target exists: the runtime config requires the service to declare it, and the
880
+ producer refuses a service that does not as `readiness-target-undeclared`.
881
+ - `network` is `{ mode: 'isolated' }` or `{ mode: 'attached', publicEgress, platformEndpointIds }`
882
+ with sorted unique endpoint ids.
883
+
884
+ `snapshotServiceRuntimeSpec` checks all of that; whether the cluster, the nodes and the endpoints
885
+ exist is Cloudly's. `requests.service.setServiceRuntimeSpec { identity, serviceId, expectedRevision, spec }`
886
+ writes `TServiceRuntimeSpecWritableData`, and Cloudly copies the organization from the service.
887
+ `getServiceRuntimeSpec` answers `{ spec, status }`: both `null` before the first spec, and `status`
888
+ `null` until the producer first decides the spec. A status never comes without its spec.
889
+
890
+ `data.IServiceRuntimeStatus { id, specRevision, state, reason, changedAt }` is the producer's verdict
891
+ on one spec revision, and each reason belongs to exactly one state. `converged` and `converging`
892
+ carry `reason: null`. `waiting` carries a `TServiceRuntimeWaitingReason`: `node-network-pending`,
893
+ `conflict` or `failure-backoff`. `refused` carries a `TServiceRuntimeRefusal`:
894
+ `cluster-runtime-missing`, `organization-missing`, `image-plan-missing`, `platform-unsupported`,
895
+ `secrets-unsupported`, `volumes-unsupported`, `port-protocol-unsupported`,
896
+ `readiness-target-undeclared`, `environment-too-large`, `no-eligible-node` or `replica-limit`. A
897
+ refusal never retires running slots. `snapshotServiceRuntimeStatus` enforces the pairing.
898
+
899
+ ### Runtime Assignment Views
900
+
901
+ `data.IServiceRuntimeAssignmentView` is one replica slot's current assignment as an administrator
902
+ reads it: `serviceId`, `replicaId`, `nodeId`, the current `assignment` reference, its `disposition`,
903
+ the latest accepted `observation { phase, ready, observedAt, sequence }` (which may cite an earlier
904
+ revision), `terminalReceiptCount` (0 to 2) and `delivery`. `delivered` means the node's own evidence,
905
+ an observation or a terminal receipt, cites the current revision; until then it is `undelivered`.
906
+
907
+ `requests.service.getServiceRuntimeAssignments { identity, serviceId }` answers every slot of one
908
+ service. `requests.cluster.getClusterRuntimeAssignments { identity, clusterId, cursor, limit }` pages
909
+ through every slot on a cluster's nodes; `limit` runs from 1 to
910
+ `serviceRuntimeContract.maximumAssignmentPageSize` (256), and Cloudly refuses any other.
911
+ `requests.service.pushServiceRuntimeChanged { serviceId }` tells admin UIs that a service's spec,
912
+ status or assignments changed; it carries no state, so a late push never overwrites a newer read.
913
+
914
+ ### Corestore Database Export Packaging
915
+
916
+ The Node-only `@serve.zone/interfaces/runtime` export provides metadata-only
917
+ Corestore database-export package contracts. A package request carries exact
918
+ input length and SHA-256, approved source-backup and complete-output-receipt
919
+ digests, a path-safe attempt ID, and an explicit `absent` or `exact` allocation
920
+ expectation. It never carries
750
921
  the export bytes or Base64 content. Immutable completion binds the normal
751
922
  Corestore database backup receipt and exact closure identity; delivery wrappers
752
923
  may report exact replay without changing completion. Release is permitted only
@@ -754,10 +925,18 @@ against the caller's exact fsynced closure and receipt identities. API-token
754
925
  authentication remains transport authority, and these Corestore contracts do
755
926
  not claim to verify Cloudly authority-B HMACs.
756
927
 
757
- Exact source, transformed-core, and complete-output identities include one
758
- terminating LF and are limited to 96 MiB plus that LF. Journal-induced output
759
- growth is limited to 64 KiB. Package control metadata is limited to 16 KiB and
760
- keeps the existing 256 MiB Corestore closure limit.
928
+ Exact input identities include one terminating LF and are limited to 96 MiB
929
+ plus that LF. Package control metadata is limited to 16 KiB and keeps the
930
+ existing 256 MiB Corestore closure limit.
931
+
932
+ One media type and one backup format are stated here for every process that
933
+ handles a Corestore database backup: `runtime.corestoreDatabaseBackupMediaType`
934
+ (`application/vnd.serve-zone.corestore-database-backup`) is the type an archive
935
+ is served and stored under, and `runtime.corestoreDatabaseBackupFormat`
936
+ (`corestore.database.backup`) is the format a receipt states. Corestore's
937
+ control server, its export packager, Onebox and the cluster relay import them
938
+ instead of writing the bytes again, so the side that writes a transfer and the
939
+ side that reads it cannot disagree about its type.
761
940
 
762
941
  Service creation carries canonical ownership separately from caller-writable
763
942
  service data. `requests.service.IRequest_Any_Cloudly_CreateService` requires a
@@ -766,13 +945,97 @@ server-managed ownership field. Consumers should use
766
945
  `data.validateOrganizationId()` to validate the identifier shape and verify
767
946
  the request's ownership authorization separately before persistence.
768
947
 
948
+ ## Protocol handshake
949
+
950
+ The protocol version is the installed `@serve.zone/interfaces` release. There is no protocol
951
+ number beside it, no per-shape version suffix and no `schemaVersion` field: one release of this
952
+ package is exactly one contract, so naming the release states everything about what a peer sends
953
+ and what it can read.
954
+
955
+ Two peers exchange an offer when a session opens and refuse an incompatibility by name:
956
+
957
+ ```typescript
958
+ import { protocol } from '@serve.zone/interfaces';
959
+ import { TypedResponseError } from '@api.global/typedrequest';
960
+
961
+ // One offer per session kind a deployable serves, built once at start-up. A deployable that
962
+ // depends on nothing newer than its own major accepts that whole major.
963
+ const [installedMajor] = protocol.protocolVersion.split('.');
964
+ const ownOffer = protocol.createProtocolOffer(`${installedMajor}.0.0`);
965
+
966
+ // Server, on every carrier: the bounded body, then the offer, then everything else — the
967
+ // exact-key validator, the bearer and any mutation.
968
+ const peerOffer = protocol.readProtocolOffer(requestBody);
969
+ const negotiation = protocol.negotiateProtocol(ownOffer, peerOffer);
970
+ if (!negotiation.compatible) {
971
+ throw new TypedResponseError(
972
+ protocol.describeProtocolRefusal(negotiation.refusal),
973
+ negotiation.refusal,
974
+ );
975
+ }
976
+
977
+ // Client, on the payload of a failed hop: `TypedResponseError.errorData` on a TypedRequest, the
978
+ // parsed body on the Spark heartbeat's refusal status. Everything else is not a refusal.
979
+ const refusal = protocol.readProtocolRefusal(errorData);
980
+ if (refusal) {
981
+ logger.warn(protocol.describeProtocolRefusal(refusal));
982
+ // A refusal stands until an operator upgrades one of the two sides, so the client waits.
983
+ setTimeout(offerAgain, protocol.refusedOfferRetryIntervalMs);
984
+ }
985
+ ```
986
+
987
+ | Export | Purpose |
988
+ | --- | --- |
989
+ | `protocolVersion` | The installed release, taken from the package's own commit info. |
990
+ | `IProtocolOffer` | `{ interfacesVersion; minimumPeerVersion }` — what a side speaks and the oldest peer it accepts. |
991
+ | `createProtocolOffer(minimumPeerVersion)` | This build's offer. A minimum above the installed release, or in another major, throws here — at start-up, not at the first registration. |
992
+ | `validateProtocolOffer(value, path?)` | Every reason an offer is refused, as an array of messages; empty for a valid offer. |
993
+ | `validateProtocolOfferOn(body, path?)` | The same reasons for the `protocol` key of a body, read as a data property. The naming twin of `readProtocolOffer`, for validators that list reasons instead of throwing. |
994
+ | `readProtocolOffer(body)` | The offer on a body, read from the `protocol` key alone and returned detached. |
995
+ | `negotiateProtocol(local, remote)` | `{ compatible: true }` or `{ compatible: false; refusal }`. Pure; throws when either side's offer names something that is not a release version. |
996
+ | `IProtocolRefusal` | `{ code: 'protocol-incompatible'; reason; refusing; refused }`, carried as `TypedResponseError.errorData` or as a plain HTTP JSON body. |
997
+ | `TProtocolIncompatibility` | `'major-mismatch'`, `'peer-below-minimum'`, `'self-below-peer-minimum'`. |
998
+ | `readProtocolRefusal(value)` | The refusal inside an error payload (`TypedResponseError.errorData`) or a response body, or `null` when the value is not one. |
999
+ | `describeProtocolRefusal(refusal)` | One log line naming both sides, opening with the frozen `protocol-incompatible` code. |
1000
+ | `refusedOfferRetryIntervalMs` | `300_000` — the shared cadence at which a refused client offers again. |
1001
+
1002
+ `negotiateProtocol` checks in a fixed order: different majors are `major-mismatch` even when a
1003
+ minimum would also fail, then a remote below the local minimum is `peer-below-minimum`, then a
1004
+ local below the remote minimum is `self-below-peer-minimum`. The last two are mirror images of one
1005
+ another — one holding on this side means the other holds on the peer — and they can never hold at
1006
+ once, which would require each version to be below the other. An offer that does not name two
1007
+ release versions is a local defect, not an incompatibility between two peers: `negotiateProtocol`
1008
+ throws on it, and a peer-sent body is read with `readProtocolOffer` first, which refuses it by
1009
+ name. Both offers and refusals come back frozen and detached from the values they were read from.
1010
+
1011
+ **Every carrier reads the offer once, as data.** A `protocol` key, or a member of the offer under
1012
+ it, that is an accessor rather than a plain value is refused as "must use its exact schema" and is
1013
+ never invoked — by `readProtocolOffer`, by `validateProtocolOfferOn`, by `validateProtocolOffer`
1014
+ and by `readProtocolRefusal` alike. No snapshot, validator or client on any carrier runs code a
1015
+ peer attached to a body, and none of them can observe one value while a session is served by
1016
+ another. This is stated here and nowhere else: the families that carry an offer go through these
1017
+ functions rather than restating the rule.
1018
+
1019
+ A carrier that negotiates puts the offer in a top-level `protocol` key on both its request and its
1020
+ response, and a server reads it before it validates the rest of the body, authenticates or changes
1021
+ any state. Validators in this package are exact-key, so a body from a later major is unreadable by
1022
+ an older build; reading the offer first is what turns that into a named refusal instead of a
1023
+ generic contract rejection, and it keeps a refused peer's credentials, sessions and packet access
1024
+ untouched.
1025
+
1026
+ Versions are `MAJOR.MINOR.PATCH` and nothing else — no prerelease, no build metadata, no `v`
1027
+ prefix, no leading zeros — and are compared per numeric component, so `32.10.0` is above `32.9.0`.
1028
+ `minimumPeerVersion` is in the same major as `interfacesVersion` and never above it. Each
1029
+ deployable owns its minimums, one per session kind it serves, and raises one only in the commit
1030
+ that starts depending on a later interfaces minor, for that session kind alone.
1031
+
769
1032
  ## Secrets v24 Pre-Cutover Contract
770
1033
 
771
1034
  Version 24 retains the value-free v23 architecture while replacing the remaining
772
1035
  pre-cutover lifecycle and runtime authority gaps. Secret values enter Cloudly
773
1036
  only as strict SmartCrypto X25519 envelopes or an explicit bounded
774
- server-generation request. Coreflow receives a full digest-verified schema-v2
775
- manifest plus one sealed envelope per pinned SecretVersion through the Node-only
1037
+ server-generation request. Coreflow receives a full digest-verified manifest
1038
+ plus one sealed envelope per pinned SecretVersion through the Node-only
776
1039
  runtime export.
777
1040
 
778
1041
  This is a contract release before consumer cutover. It does **not** claim that
@@ -796,7 +1059,7 @@ The current secret contract includes:
796
1059
  `requests.secret.IReq_GetSecretIngressRecipient` contract with method
797
1060
  `getSecretIngressRecipient`, and fixed-order create/rotate/App Store context
798
1061
  builders.
799
- - `ISecretEnvelopeAdmissionBindingV1` under the Node-only runtime export binds
1062
+ - `ISecretEnvelopeAdmissionBinding` under the Node-only runtime export binds
800
1063
  an exact envelope and request context to one recipient generation. The
801
1064
  binding is reproducible and does not itself prove admission. Retiring-key
802
1065
  retries require a trusted Cloudly mutation receipt created atomically while
@@ -808,12 +1071,12 @@ The current secret contract includes:
808
1071
  non-issuable `purge-pending` lifecycle and revisioned
809
1072
  `pending`/`erasing`/`failed`/`succeeded` operation keep external erasure
810
1073
  durable across retries without treating preflight as authorization.
811
- - Schema-v2 `IResolvedSecretManifest` contracts, stable Docker resource naming,
1074
+ - `IResolvedSecretManifest` contracts, stable Docker resource naming,
812
1075
  WorkloadInit map/wrapper helpers, and `ISealedResolvedSecretMaterial` under
813
1076
  `@serve.zone/interfaces/runtime`.
814
1077
  - Two-step Coreflow X25519 recipient enrollment and exact recipient lifecycle
815
1078
  validators.
816
- - Mandatory `IImmutableContainerInvocationV1` evidence on immutable image
1079
+ - Mandatory `IImmutableContainerInvocation` evidence on immutable image
817
1080
  deployment plans.
818
1081
 
819
1082
  ### Secrets v24 Runtime Registration And Reporting
@@ -826,7 +1089,7 @@ Coreflow connection:
826
1089
  empty state or the complete valid recipient set for the cluster derived from
827
1090
  the verified JWT.
828
1091
  - `getCoreflowSecretRuntimeRegistrationExpectation` returns either an
829
- `available` v2 expectation or an explicit unavailable reason. The available
1092
+ `available` expectation or an explicit unavailable reason. The available
830
1093
  expectation binds the live reporter session, active recipient, fresh
831
1094
  generation-fenced target authority, and active WorkloadInit approval through
832
1095
  `expectationDigest`.
@@ -835,17 +1098,19 @@ Coreflow connection:
835
1098
  credential, reconciles manager consensus privately, and publishes one fresh
836
1099
  single-Swarm target authority or a targetless unavailable state. Structural
837
1100
  contract validation does not itself establish manager consensus.
838
- - `TSparkSwarmObservationV2` lets workers report only their local Swarm node ID,
1101
+ - `TSparkSwarmObservation` lets workers report only their local Swarm node ID,
839
1102
  because Docker does not expose the Swarm cluster ID to workers. Cloudly may
840
1103
  associate that node with a cluster only through authenticated node scope and
841
- accepted manager consensus. The v2 transport contract designates
842
- `/spark/v2/nodes/swarm-observation`. A conforming Spark sender must retain one
843
- exact request until it receives a request-bound acceptance receipt. A
844
- conforming Cloudly consumer must authenticate the node before inspecting
845
- replay state, derive cluster scope from that persisted identity, and never
846
- register, translate, or accept v1 observations as an authority fallback.
847
- - `computeSparkSwarmObservationV2Digest` uses strict canonical JSON and the
848
- `serve.zone/spark-swarm-observation-v2` domain. A reporter session starts at
1104
+ accepted manager consensus. The transport contract designates
1105
+ `sparkSwarmNodeContracts.swarmObservation`, whose endpoint is
1106
+ `/spark/swarm-nodes/swarm-observation` and whose `maxRequestBytes` covers the
1107
+ largest snapshot this validator accepts; every body on it carries the sender's
1108
+ `protocol` offer. A conforming Spark sender must retain one exact
1109
+ request until it receives a request-bound acceptance receipt. A conforming
1110
+ Cloudly consumer must authenticate the node before inspecting replay state and
1111
+ derive cluster scope from that persisted identity.
1112
+ - `computeSparkSwarmObservationDigest` uses strict canonical JSON and the
1113
+ `serve.zone/spark-swarm-observation` domain. A reporter session starts at
849
1114
  sequence one. The contract requires a Cloudly consumer to accept only the
850
1115
  exact next sequence with a strictly advancing `observedAt`, while an exact
851
1116
  same-sequence/same-digest retry returns the byte-equivalent persisted receipt
@@ -855,25 +1120,42 @@ Coreflow connection:
855
1120
  sequence-one observation must still advance the permanent timestamp
856
1121
  high-water mark. The consumer must persist acceptance state and its receipt
857
1122
  in one atomic boundary.
858
- - `validateSparkSwarmObservationV2` validates exact structure and attached
859
- snapshots, while `validateSparkSwarmObservationV2Request` and
860
- `validateSparkSwarmObservationV2Response` validate transport and request
1123
+ - `validateSparkSwarmObservation` validates exact structure and attached
1124
+ snapshots, while `validateSparkSwarmObservationRequest` and
1125
+ `validateSparkSwarmObservationResponse` validate transport and request
861
1126
  binding. None of these functions authenticates a node, persists replay state,
862
1127
  establishes manager consensus, or creates runtime authority.
863
1128
  - WorkloadInit approval binds a clean stable release identity, version-tagged
864
1129
  OCI index, exact amd64 and arm64 platform/executable digests, policy
865
1130
  generation, and a Cloudly summary of detached Cosign DSSE/SLSA verification.
866
1131
  Public shape and digest validators do not verify the signature, public-key
867
- trust root, or private Cloudly policy.
1132
+ trust root, or private Cloudly policy. The provenance identifiers a verifier
1133
+ matches are `workloadInitReleaseContract.buildType`
1134
+ (`https://serve.zone/buildtypes/workloadinit-gitzone-tsdocker`) and
1135
+ `builderId` (`https://serve.zone/builders/workloadinit-manual-release`); the
1136
+ in-toto statement type, the SLSA predicate type, the Cosign bundle format and
1137
+ the OCI index media type keep the exact bytes their own specifications define.
1138
+ An approval carries no shape version, and
1139
+ `IWorkloadInitApprovalAuthorityReference` states only the counter
1140
+ `authorityGeneration` beside the approval digest it points at.
868
1141
  - `coreflowSecretRuntimeRegistrationTagId` is the sole dedicated TypedSocket
869
1142
  tag identifier. Its payload is exactly
870
- `ICoreflowSecretRuntimeRegistrationV2`; cluster scope comes from the verified
1143
+ `ICoreflowSecretRuntimeRegistration`; cluster scope comes from the verified
871
1144
  connection identity rather than the tag.
872
- - `ICoreflowSecretRuntimeRegistrationV2` references the exact expectation,
1145
+ - `ICoreflowSecretRuntimeRegistration` references the exact expectation,
873
1146
  target generation/digest, and WorkloadInit authority. It must cover every
874
1147
  Cloudly-node/Swarm-cluster/Swarm-node identity and approved per-platform
875
1148
  manifest and installed-executable digest without self-asserting placement or
876
1149
  approval.
1150
+ - `capabilities` is a sorted, duplicate-free list of `data.TCoreflowRuntimeCapability`
1151
+ names drawn from `data.coreflowRuntimeCapabilities`, judged by
1152
+ `data.isCoreflowRuntimeCapabilityList`. A build states what it can do by name;
1153
+ which shape it speaks is the interfaces version both peers exchanged. Every
1154
+ name in `data.requiredCoreflowRuntimeCapabilities` must be present. That list
1155
+ is the vocabulary minus `data.optionalCoreflowRuntimeCapabilities`, which
1156
+ holds `corestore-inventory` alone: it is the one capability a build may lack,
1157
+ because it depends on the node's Corestore deployment rather than on the
1158
+ contracts the build was compiled against.
877
1159
  - `validateCoreflowSecretRuntimeRegistration` compares the registration with a
878
1160
  trusted expectation built by Cloudly, including the live reporter session.
879
1161
  Missing, extra, duplicate, reordered, or mismatched node evidence fails
@@ -955,7 +1237,7 @@ const storageConfig: appstore.IAppStoreVersionConfig = {
955
1237
  delivery: {
956
1238
  type: 'file',
957
1239
  targetPath: '/run/secrets/backup-archive.json',
958
- format: 'servezone-object-storage-v1',
1240
+ format: 'servezone-object-storage',
959
1241
  uid: 1000,
960
1242
  gid: 1000,
961
1243
  mode: 0o400,
@@ -964,10 +1246,10 @@ const storageConfig: appstore.IAppStoreVersionConfig = {
964
1246
  },
965
1247
  ],
966
1248
  requiresFeatures: [
967
- appstore.appStoreStorageFeatureIds.bindingsV2,
968
- appstore.appStoreStorageFeatureIds.filesystemV1,
969
- appstore.appStoreStorageFeatureIds.objectStorageV2,
970
- appstore.appStoreStorageFeatureIds.objectStorageFileV2,
1249
+ appstore.appStoreStorageFeatureIds.bindings,
1250
+ appstore.appStoreStorageFeatureIds.filesystem,
1251
+ appstore.appStoreStorageFeatureIds.objectStorage,
1252
+ appstore.appStoreStorageFeatureIds.objectStorageFile,
971
1253
  ],
972
1254
  };
973
1255
  ```
@@ -1191,7 +1473,7 @@ Common data contracts include:
1191
1473
  - `IDomain`, `IDnsEntry`, and traffic contracts for routing and DNS management.
1192
1474
  - Traffic and gateway route contracts including `ICoretrafficPortRouteConfig`, routing `portRoutes`, and `IGatewayClientRoute` client-owned route views. Gateway route intent supports optional match `domains`, `transport`, and `remoteIngress`, plus explicit route `priority` and `managedRouteKind`. Ownership can combine `hostname` with `routeRef` so a normal route and a path-specific managed route for the same hostname reconcile independently.
1193
1475
  - Value-free `ISecretMetadata`, `ISecretVersionMetadata`, and `ISecretSetMetadata`
1194
- contracts for operator views, plus schema-v2 exact-version
1476
+ contracts for operator views, plus exact-version
1195
1477
  `IResolvedSecretManifest` contracts for cluster delivery. Manifest helpers
1196
1478
  bind immutable image rollout and invocation evidence, enforce canonical
1197
1479
  digests and globally unique launcher/file targets, and track per-cluster
@@ -1213,7 +1495,7 @@ Common data contracts include:
1213
1495
 
1214
1496
  ### Cluster Relay Block
1215
1497
 
1216
- `ICluster.data.relay` is the optional `IClusterRelayV1 { schemaVersion: 1; origin: string }`
1498
+ `ICluster.data.relay` is the optional `IClusterRelay { origin: string }`
1217
1499
  that states how cluster-side components reach this cluster's relay. It is absent on a cluster
1218
1500
  whose nodes still target Cloudly directly, so its presence is what marks a cluster as relayed.
1219
1501
 
@@ -1222,7 +1504,7 @@ session registers over, as described in [Authenticated Runtime Sessions](#authen
1222
1504
  It must be an `https:` origin with no path, query, fragment, userinfo or trailing slash, so that
1223
1505
  `new URL(origin).origin === origin`; an explicit port is allowed, and because `URL` drops a
1224
1506
  default port and lowercases the host, `https://relay.example:443` and `https://Relay.example`
1225
- are not canonical origins. `validateClusterRelayV1(value, path?)` returns one named reason per
1507
+ are not canonical origins. `validateClusterRelay(value, path?)` returns one named reason per
1226
1508
  violation and an empty array for a valid block.
1227
1509
 
1228
1510
  The relay's certificate subject is not a second field: every consumer derives it from the origin
@@ -1230,31 +1512,41 @@ with `clusterRelayCertificateDomainName(relay)`, which returns the origin's host
1230
1512
  Cloudly issues the certificate for and owns the A record for. An origin naming an address literal
1231
1513
  therefore has no issuable certificate.
1232
1514
 
1515
+ `data.clusterRelayUndispatchedRefusals` names the three registration refusals that end a relay's
1516
+ custody — `relay-cluster-not-enabled`, `relay-node-foreign` and `relay-sequence-not-advancing` —
1517
+ with `data.TClusterRelayUndispatchedRefusal` as their union. Each is Cloudly reading its own
1518
+ records and stating that it does not dispatch to this relay, so a relay that hears one drops what
1519
+ it holds for its nodes instead of waiting. A refusal that only says Cloudly could not verify the
1520
+ relay states nothing about dispatch and is deliberately not in the list.
1521
+
1233
1522
  ```typescript
1234
- const relay: data.IClusterRelayV1 = {
1235
- schemaVersion: 1,
1523
+ const relay: data.IClusterRelay = {
1236
1524
  origin: 'https://relay.cluster-a.serve.zone:8443',
1237
1525
  };
1238
1526
 
1239
- const reasons = data.validateClusterRelayV1(relay); // []
1527
+ const reasons = data.validateClusterRelay(relay); // []
1240
1528
  const certificateDomainName = data.clusterRelayCertificateDomainName(relay);
1241
1529
  // 'relay.cluster-a.serve.zone'
1242
1530
  ```
1243
1531
 
1244
- Enabling a relay is one operator flip per cluster. `enableClusterRelayV1 { identity, clusterId }`
1245
- answers with the block Cloudly published, and `disableClusterRelayV1` removes it and answers with
1532
+ Enabling a relay is one operator flip per cluster. `enableClusterRelay { identity, clusterId }`
1533
+ answers with the block Cloudly published, and `disableClusterRelay` removes it and answers with
1246
1534
  an explicit `relay: null`. The caller names only the cluster: Cloudly derives the origin itself
1247
1535
  from that cluster and its own public hostname, and refuses the request when no DNS zone it
1248
1536
  manages matches the derived name, so a relay name that nothing can publish is never written.
1249
1537
  Both requests push the cluster configuration to the connected relay afterwards, so a relay learns
1250
1538
  the change without reconnecting.
1251
1539
 
1252
- Once its socket is authenticated with `registerCloudlyClientSessionV1`, the relay sends
1253
- `registerClusterRelayV1` carrying `IClusterRelayRegistrationV1`: the relay build, the cluster node
1254
- it runs on, the exact endpoint it bound for its cluster's Pallet nodes, and a
1540
+ Once its socket is authenticated with `registerCloudlyClientSession`, the relay sends
1541
+ `registerClusterRelay` carrying `IRegisterClusterRelayRequest`: the relay build, the cluster node
1542
+ it runs on, the exact endpoint it bound for its cluster's Pallet nodes, its `protocol` offer, and a
1255
1543
  `registrationSequence` that counts up once per registration within one relay process and restarts
1256
- at `0` when that process restarts. `validateClusterRelayRegistrationV1(value, path?)` returns one
1257
- named reason per violation, like every validator here, and checks shape only. `listenAddress` is
1544
+ at `0` when that process restarts. `validateRegisterClusterRelayRequest(value, path?)` returns one
1545
+ named reason per violation, like every validator here, and checks shape only; it validates the
1546
+ offer through the one grammar `protocol` owns. Cloudly reads and negotiates that offer before it
1547
+ takes the verified cluster connection or decides the sequence, so a refused registration consumes
1548
+ no sequence, and the accepted answer carries Cloudly's own offer beside the origin. `relay.version`
1549
+ stays the relay build — a different fact from `protocol.interfacesVersion`, the contract it speaks. `listenAddress` is
1258
1550
  an IPv4 literal because Cloudly publishes it verbatim as the A record of the relay's certificate
1259
1551
  domain name — a hostname would need a `CNAME` and an IPv6 address an `AAAA` record, so admitting
1260
1552
  either is an additive change once Cloudly publishes it. A loopback or link-local address is
@@ -1285,8 +1577,8 @@ present with an empty one. `validateClusterRelayRegistration(value, path?)` retu
1285
1577
  reason per violation, as every validator here does.
1286
1578
 
1287
1579
  ```typescript
1288
- const registration: data.IClusterRelayRegistrationV1 = {
1289
- schemaVersion: 1,
1580
+ const registration: data.IRegisterClusterRelayRequest = {
1581
+ protocol: protocol.createProtocolOffer('32.0.0'),
1290
1582
  relay: {
1291
1583
  version: '4.0.0',
1292
1584
  nodeId: 'node-a',
@@ -1296,7 +1588,7 @@ const registration: data.IClusterRelayRegistrationV1 = {
1296
1588
  registrationSequence: 0,
1297
1589
  };
1298
1590
 
1299
- const registrationReasons = data.validateClusterRelayRegistrationV1(registration); // []
1591
+ const registrationReasons = data.validateRegisterClusterRelayRequest(registration); // []
1300
1592
  ```
1301
1593
 
1302
1594
  How the relay reaches each node's Corestore is part of the same document: see
@@ -1330,7 +1622,7 @@ credential is excluded.
1330
1622
  | --- | --- | --- |
1331
1623
  | `pushClusterRouteTable` | Cloudly → relay | `{ table }` → `{ appliedRevision }` |
1332
1624
  | `getClusterRouteTable` | relay → Cloudly | `{}` → `{ table \| null }` |
1333
- | `registerClusterIngress` | ingress → relay → Cloudly | `{ bearer, version, nodeId, capabilities? }` → `{ accepted: true, revision \| null }` |
1625
+ | `registerClusterIngress` | ingress → relay → Cloudly | `{ bearer, version, nodeId, capabilities?, protocol }` → `{ accepted: true, revision \| null, protocol }` |
1334
1626
  | `getClusterIngressRouteTable` | ingress → relay | `{ knownRevision \| null }` → `{ table \| null }` |
1335
1627
  | `getClusterTrafficStatistics` | Cloudly → relay → ingress | `{ fromDayUtc, toDayUtc, cursor?, limit }` → `{ statistics, nextCursor? }` |
1336
1628
 
@@ -1352,7 +1644,11 @@ Isolation on that shared listener is therefore by **authorization, not by routin
1352
1644
  connection whose registration Cloudly accepted is the ingress, `getClusterIngressRouteTable` and
1353
1645
  `getClusterTrafficStatistics` are answered for that connection alone, and any other connection —
1354
1646
  including a Pallet node's — is refused by name. `validateClusterIngressRegistration(value, path?)`
1355
- checks shape only: whether the bearer is the one Cloudly minted is Cloudly's to verify.
1647
+ checks shape only: whether the bearer is the one Cloudly minted is Cloudly's to verify. It reads
1648
+ the registration's `protocol` offer through the one grammar `protocol` owns, and both hops judge
1649
+ the body in the one server order `protocol.readProtocolOffer` states — the offer is negotiated
1650
+ before this shape check and before the bearer — so a relay refuses a peer it cannot serve without
1651
+ asking Cloudly, and an accepted registration carries the accepting side's own offer back.
1356
1652
 
1357
1653
  `IClusterTrafficStatistics` is one UTC day of one hostname's traffic on one node —
1358
1654
  `requests`, `bytesIn`, `bytesOut`, `connections` and the backend failures the proxy can observe
@@ -1373,6 +1669,7 @@ const ingressReasons = data.validateClusterIngressRegistration({
1373
1669
  bearer: ingressBearerFromAssignmentEnvironment,
1374
1670
  version: '3.0.0',
1375
1671
  nodeId: 'node-a',
1672
+ protocol: protocol.createProtocolOffer('32.0.0'),
1376
1673
  }); // []
1377
1674
  ```
1378
1675
 
@@ -1380,7 +1677,7 @@ const ingressReasons = data.validateClusterIngressRegistration({
1380
1677
 
1381
1678
  A route to a node's published host ports needs the address that node answers on, and only the
1382
1679
  node can prove it: it holds the uplink observation the address belongs to. It therefore states it
1383
- on the network report it already sends — `IReportRuntimeNetworkProtectionRequestV1` gains the
1680
+ on the network report it already sends — `IReportRuntimeNetworkProtectionRequest` gains the
1384
1681
  optional `uplinkAddress`, a unicast IPv4 literal that is neither loopback nor link-local
1385
1682
  (`runtimeNodeUplinkAddress`). Absence stays absent: a node that states no address is told apart
1386
1683
  from one that states a value, and nothing downstream defaults or infers one. Cloudly stores what
@@ -1609,7 +1906,7 @@ metadata, `data.normalizeCoreMailControlBootstrap` for startup authority, and
1609
1906
  normalizers reject unknown fields, noncanonical mailboxes,
1610
1907
  ambiguous active recipient ownership, malformed SHA-256 values, and
1611
1908
  credential-lifecycle inconsistencies. CoreMail credential verifiers use the
1612
- versioned `argon2id-v1` format and the exact policy exported as
1909
+ `argon2id` format and the exact policy exported as
1613
1910
  `data.coreMailCredentialVerifierPolicy`.
1614
1911
 
1615
1912
  `data.ICoreMailDesiredState.smtp` is the optional CoreMail SMTP submission
@@ -1637,7 +1934,7 @@ canonical environment keys for the verifier-only bootstrap payload and the
1637
1934
  separate control and gateway secret values; no consumer may derive or embed
1638
1935
  plaintext material in desired state.
1639
1936
 
1640
- CoreMail schema v2 keeps durable authority at the stable tenant, service, and
1937
+ The CoreMail contracts keep durable authority at the stable tenant, service, and
1641
1938
  binding identity while revisions, config epochs, and composite
1642
1939
  `credentialId`/`version` values fence sessions and new actions. Credential
1643
1940
  versions are authority-wide monotonic and unique even when rotation changes the
@@ -1672,8 +1969,8 @@ consumed only by the first durable insertion of an idempotency identity, while
1672
1969
  replays consume none. Pending inbound includes every state except
1673
1970
  `acknowledged`. These semantics are exported as `data.coreMailQuotaPolicy`.
1674
1971
  Every binding carries finite `messagesPerMinute`, `messagesPerDay`, and
1675
- `maxPendingInbound` values; omitted or unlimited quotas are not valid schema-v2
1676
- desired state.
1972
+ `maxPendingInbound` values; omitted or unlimited quotas are not valid desired
1973
+ state.
1677
1974
  `data.coreMailRetentionPolicy` retains terminal outbound,
1678
1975
  acknowledged inbound, and idempotency receipts for 30 days and expired
1679
1976
  capabilities for 24 hours. Pending inbound is never age-purged.
@@ -1695,7 +1992,7 @@ kind-specific byte budget reports `PAYLOAD_LIMIT_EXCEEDED`.
1695
1992
 
1696
1993
  Control and gateway credential rotation is ordered: provision the candidate
1697
1994
  plaintext through resolved runtime secrets, publish and activate the matching
1698
- `argon2id-v1` verifier, roll or reconnect every affected replica, observe
1995
+ `argon2id` verifier, roll or reconnect every affected replica, observe
1699
1996
  per-task authentication and readiness, then mark the previous verifier
1700
1997
  `retiring` with `acceptUntil`. Remove the previous verifier and secret only
1701
1998
  after its acceptance window has elapsed and no session uses that composite
@@ -1726,7 +2023,7 @@ and a `coremail:<identifier>` management scope, validated by the existing
1726
2023
  `data.validateSecretManagementScope` and secret-metadata validators.
1727
2024
 
1728
2025
  `IServiceMailConfig.coreMail` publishes the service's CoreMail binding as public
1729
- metadata only: binding and composite credential identity, the `argon2id-v1`
2026
+ metadata only: binding and composite credential identity, the `argon2id`
1730
2027
  verification hash, the binding state, and any `retiring` predecessor with its
1731
2028
  `acceptUntil`. Plaintext never appears there, and the field is excluded from
1732
2029
  `data.TServiceWritableData`, so only the control plane writes it while the rest
@@ -1809,9 +2106,9 @@ async function acceptMaterial(
1809
2106
  }
1810
2107
  ```
1811
2108
 
1812
- Runtime material is shaped as `{ schemaVersion: 1, manifest, entries }`, where
2109
+ Runtime material is shaped as `{ manifest, entries }`, where
1813
2110
  each entry contains only `secretVersionId` and a strict X25519 envelope. The
1814
- helper verifies the canonical schema-v2 manifest digest, envelope context
2111
+ helper verifies the canonical manifest digest, envelope context
1815
2112
  digests, active recipient key, sorted exact one-to-one version coverage, every
1816
2113
  request fence, and the trusted local organization/cluster expectation. It never
1817
2114
  decrypts. Organization and cluster are derived from the verified cluster JWT
@@ -1828,16 +2125,13 @@ canonical token boundary, while
1828
2125
  plaintext directly from validated UTF-8 bytes without first constructing a
1829
2126
  JavaScript token string. Publication grants carry the service, binding,
1830
2127
  reconciliation generation, binding-request digest, target Secrets revision, and
1831
- bounded validity window. `computeCorestoreCredentialBindingRequestSha256()`
1832
- produces that digest from the exact value-free provider, scope, environment, and
1833
- optional retention request for persisted schema-v1 records. New issuers use
1834
- `TCorestoreCredentialBindingRequestV2` and
1835
- `validateCorestoreCredentialBindingRequestV2()`,
1836
- `createCorestoreCredentialBindingRequestDigestInputV2()`, and
1837
- `computeCorestoreCredentialBindingRequestSha256V2()`. The corresponding
1838
- unsuffixed APIs remain the schema-v1 boundary. Schema v2 binds every object-storage
1839
- request to the exact durable `bucketName`; schema-v1 records stay readable but
1840
- do not provide that authority. Object-storage plaintext keeps the logical
2128
+ bounded validity window. `TCorestoreCredentialBindingRequest` with
2129
+ `validateCorestoreCredentialBindingRequest()`,
2130
+ `createCorestoreCredentialBindingRequestDigestInput()`, and
2131
+ `computeCorestoreCredentialBindingRequestSha256()` produces that digest from
2132
+ the exact value-free provider, scope, environment, bucket, and optional
2133
+ retention request. Every object-storage request binds the exact durable
2134
+ `bucketName`. Object-storage plaintext keeps the logical
1841
2135
  `accessKeyId` and `secretAccessKey` fields while
1842
2136
  `createCorestoreCredentialSecretValues()` expands them to the six canonical
1843
2137
  Corestore `S3_*` and `AWS_*` Secret aliases. Receipts bind that exact key
@@ -1899,7 +2193,11 @@ answer is refused with one: the two states can never be read as each other.
1899
2193
  | `getClusterPlatformDesiredState` | relay → Cloudly | `{}` → `{ capabilities, providerConfigs, bindings }` |
1900
2194
  | `backupServiceDatabaseClosure` | relay → Cloudly | `{ backupId, serviceId, nodeId, descriptor, closure }` → `{ accepted }` |
1901
2195
  | `restoreServiceDatabaseClosure` | relay → Cloudly | `{ backupId, serviceId, nodeId }` → `{ descriptor, closure }` |
1902
- | `stageIsolatedRestoreArchive` | Cloudly → relay | `{ restoreGrant, clusterId, targetNodeName, restoreId, expectedRevision, object, archive }` → `{ state }` |
2196
+ | `prepareIsolatedRestoreOnNode` | Cloudly → relay | `{ nodeId, control }` → `{ progress }` |
2197
+ | `stageIsolatedRestoreArchive` | Cloudly → relay | `{ nodeId, control }` → `{ progress }` |
2198
+ | `getIsolatedRestoreProgressOnNode` | Cloudly → relay | `{ nodeId, control }` → `{ progress }` |
2199
+ | `executeIsolatedRestoreOnNode` | Cloudly → relay | `{ nodeId, control }` → `{ progress }` |
2200
+ | `cleanupIsolatedRestoreOnNode` | Cloudly → relay | `{ nodeId, control }` → `{ progress }` |
1903
2201
 
1904
2202
  **Placement is a field too.** `getClusterPlatformDesiredState` answers with
1905
2203
  `IClusterPlatformBindingPlacement { nodeId, binding }` rather than a bare binding list: a database
@@ -1931,14 +2229,53 @@ closure its own receipt contradicts.
1931
2229
 
1932
2230
  **Isolated restore carries its grant.** Corestore accepts exactly one authority on
1933
2231
  `/isolated-restore/*`: a compact RS256 grant that names one operation, expires within minutes and
1934
- binds the cluster, the node, the canonical resource mappings and the archive manifest. So
1935
- `coreflowPrepareIsolatedRestore`, `coreflowExecuteIsolatedRestore`,
1936
- `coreflowGetIsolatedRestoreStatus`, `coreflowCleanupIsolatedRestore` and
1937
- `stageIsolatedRestoreArchive` each carry `restoreGrant`, which the relay forwards untouched and can
1938
- neither widen nor mint. Each is sensitive ephemeral input: exclude the complete request from hooks,
1939
- logs and journals on every hop. Staging is one archive object per call — the manifest entry is the
1940
- descriptor — because Corestore writes staged objects one bounded chunk at a time under a
1941
- `write-object` grant, and turning a stream into those chunks is the relay's work.
2232
+ binds the cluster, the node name, the canonical resource mappings and the archive manifest. The
2233
+ five methods above are therefore one shape — `{ nodeId, control }` → `{ progress }` — where
2234
+ `control` is the body Corestore itself reads (`data.IIsolatedRestoreControlPrepareRequest`,
2235
+ `…WriteRequest`, `…StatusRequest`, `…ExecuteRequest`, `…CleanupRequest`) with the grant inside it.
2236
+ The relay forwards the control untouched and can neither widen it nor mint one; it adds no
2237
+ identity and no protocol offer either, because the cluster is the one the verified machine identity
2238
+ on the session `registerCloudlyClientSession` opened names, and the protocol offer was exchanged by
2239
+ that handshake and again by `registerClusterRelay`'s own `protocol` member. Every one of them is
2240
+ sensitive ephemeral input: the grant is a bearer credential, declared once on
2241
+ `data.IIsolatedRestoreControlAuthority`, so exclude the complete request from hooks, logs,
2242
+ diagnostics and durable journals on every hop.
2243
+
2244
+ **The order an operator's restore runs in.** Cloudly calls `createIsolatedRestore` with the node
2245
+ it dispatches to, then, on that node: `prepareIsolatedRestoreOnNode` with `expectedRevision: 0`,
2246
+ `stageIsolatedRestoreArchive` once per bounded chunk of every manifest object,
2247
+ `executeIsolatedRestoreOnNode`, and `cleanupIsolatedRestoreOnNode` when the rehearsal is over;
2248
+ `getIsolatedRestoreProgressOnNode` reads the same state without changing it. Staging is bounded by
2249
+ `data.isolatedRestoreContractLimits`: at most 512 KiB decoded per chunk, 4,096 chunks per object
2250
+ and 64 MiB per object, each chunk authenticated by its own `chunkSha256` against the immutable
2251
+ object descriptor. Every mutation carries the `revision` the node last stated as its
2252
+ `expectedRevision`, so a replay settles instead of racing.
2253
+
2254
+ **The node id routes, the node name is authorized.** `createIsolatedRestore` takes `targetNodeId`
2255
+ — the cluster document's node id, which is what a relay routes on — while the signed grant keeps
2256
+ `targetNodeName`, which is what the operator authorized and what Corestore verifies. Cloudly is
2257
+ the only party holding both, so `IIsolatedRestoreRecord` states both and every control call is
2258
+ dispatched by the id. `data.bindIsolatedRestoreProgressToNode(progress, { nodeId, restoreId })`
2259
+ is where an answer is tied back: the control body names neither the node nor the restore, so
2260
+ without it a relay could answer with another node's — or another restore's — perfectly canonical
2261
+ progress.
2262
+
2263
+ **What a node can state, in Corestore's own words.** `data.IIsolatedRestoreProgress` is Corestore's
2264
+ staging summary plus the node the control was carried to: `{ nodeId, restoreId, stagingArchiveId,
2265
+ status, revision, lastFence, expectedObjects, receivedObjects, completedMappings, totalMappings,
2266
+ preparedAt, updatedAt, executedAt?, stagedObject? }`, with `status` one of
2267
+ `data.isolatedRestoreProgressStatuses` — `prepared`, `executing`, `failed`, `executed`, `cleaned`.
2268
+ `stagedObject` is present only on a staged chunk and says where the next chunk continues; it is
2269
+ named apart from the `object` receipt Corestore's own write answer carries, which is a different
2270
+ shape and is not projected. `data.normalizeIsolatedRestoreProgress(value)` reads it exactly,
2271
+ refusing an unknown status, a count larger than the plan it belongs to, a `revision` or `lastFence`
2272
+ below 1 — a committed state carries both — an `executing` or `executed` answer that does not hold
2273
+ the whole archive, and an `executed` answer that left a mapping behind or does not say when it
2274
+ finished. A node states nothing of the control plane's record — no source backup, no requester, no
2275
+ verification — so Cloudly composes its record from this and its own knowledge, exactly as it does
2276
+ with the Corestore inventory. A relay that cannot reach the node answers one of
2277
+ `data.isolatedRestoreNodeControlRefusals` instead: `restore-node-not-carried`,
2278
+ `restore-node-unreachable`, or `restore-node-refused` followed by Corestore's own reason.
1942
2279
 
1943
2280
  `IIsolatedRestoreDatabaseMapping.target.databaseAllocation` carries Corestore's safe allocation
1944
2281
  reference, present exactly when the target database is allocation-managed. It lives inside the
@@ -1975,88 +2312,22 @@ recipient the relay enrolled with `beginSecretRecipientEnrollment` and
1975
2312
  `completeSecretRecipientEnrollment` — see [Corestore Runtime Credentials](#corestore-runtime-credentials).
1976
2313
  No credential travels in the cluster document, and none is read from the relay's environment.
1977
2314
 
1978
- ### Legacy Deployment Settlement
1979
-
1980
- The Node-only `/runtime` export also provides strict, exact-schema codecs for
1981
- Cloudly legacy deployment settlement plans, production requests, check/apply
1982
- summaries, stable outcomes, and scratch/production receipts. Authenticated
1983
- artifacts use domain-separated HMAC inputs and references derived from the full
1984
- authority-metadata SHA-256; authority-aware normalizers enforce metadata fences
1985
- and artifact-time validity windows. General authority-aware APIs require a
1986
- consumer-owned, constant-time HMAC verifier; private stdio authority-aware APIs
1987
- derive verification from their exact plaintext authority handoffs. They never
1988
- read the current wall clock.
1989
- Structural normalizers explicitly do not authenticate MACs. Literal portable
1990
- golden vectors contain only safe
1991
- metadata, summaries, digests, and MACs. The separately named
1992
- `ICloudlyLegacyDeploymentSettlementAuthorityHandoffPlaintextV1` parser/encoder
1993
- exists only for private maintenance-container interoperability and is never part
1994
- of a safe result, receipt, or golden summary.
1995
-
1996
- Fenced settlement contracts add deterministic execution-attempt identities and
1997
- prepared, committed, or aborted observations. Resolution bundles distinguish
1998
- `committed-schema1`, `committed-schema2`, `aborted-prepared`,
1999
- `observed-aborted`, `absent-tombstone`, and `legacy-no-op`; only the scratch-only
2000
- legacy no-op has no durable attempt observation. Source and execution authority
2001
- status are bound to the resolution's `observedAt`. Scratch attempts use the same
2002
- authority for both roles and require matching statuses. Historical verification
2003
- remains possible after expiry because status is derived only from the
2004
- authenticated observation time. Production recovery always uses receipt v2 and
2005
- a distinct execution authority B whose metadata names source authority A and
2006
- binds the authenticated plan, request bundle, and scratch receipt digests.
2007
- B is issued strictly after scratch completion; B's `requestedAt` and
2008
- `approvedAt` are independent of A's request timestamp and must remain within
2009
- B's own validity interval.
2010
-
2011
- Consumers make trust decisions through
2012
- `normalizeAuthenticatedCloudlyLegacyDeploymentSettlementResolutionForAuthorities()`,
2013
- `normalizeCloudlyLegacyDeploymentSettlementScratchExecutionBundleForAuthority()`,
2014
- `normalizeCloudlyLegacyDeploymentSettlementProductionExecutionBundleV2ForAuthorities()`,
2015
- `normalizeCloudlyLegacyDeploymentSettlementStdioRequestForAuthorities()`, and
2016
- `normalizeCloudlyLegacyDeploymentSettlementStdioResponseForRequestAuthorities()`.
2017
- Every resolution outcome, including an unapplied outcome, is carried in an
2018
- `IAuthenticatedCloudlyLegacyDeploymentSettlementResolutionV1` recovery
2019
- attestation under the dedicated resolution HMAC domain and signed by the
2020
- execution authority. The raw
2021
- `normalizeCloudlyLegacyDeploymentSettlementResolutionBundleForAuthorities()`
2022
- helper verifies referenced artifacts and status only; its result is not an
2023
- authenticated recovery claim. The non-stdio authority-aware APIs use the
2024
- caller-supplied HMAC verifier and never receive plaintext keys. The private
2025
- stdio variants accept no external verifier: they bind each HMAC to the
2026
- authority reference and key in the corresponding request handoff. Interfaces
2027
- never executes a settlement.
2028
-
2029
- The stdio contract exposes nine commands from `settlement-plan` through
2030
- `settlement-production-resolve`. Requests and responses are exact canonical
2031
- JSON followed by one LF, bounded by
2032
- `cloudlyLegacyDeploymentSettlementStdioLimits`, and responses use a
2033
- command-discriminated success payload or a finite secret-safe error. Private
2034
- authority handoffs and MongoDB descriptors belong only on protected request
2035
- transport. Production stdio requests reject equal decoded A and B HMAC keys and
2036
- wipe temporary comparison buffers. The handoff-derived verifier uses HMAC-SHA256
2037
- with constant-time MAC comparison, rejects wrong or swapped handoff keys, and
2038
- wipes temporary decoded key and MAC buffers. Handoffs and derived verifiers are
2039
- private in-process capabilities that must never be persisted or logged and must
2040
- be discarded when the stdio exchange ends. Verifier-only integrations do not
2041
- expose key material to Interfaces, so their callers must enforce the same key
2042
- separation.
2043
- Authority-aware response validation also binds settlement-request
2044
- `requestedAt`, production-authorization `requestedAt` and `approvedAt`, and
2045
- resolution `observedAt` to the corresponding request. Production check and
2046
- apply results must be issued by B no earlier than both A's request and B's
2047
- `approvedAt`. Callers remain responsible for process isolation, key
2048
- destruction, logging discipline, database access, and the actual settlement
2049
- transaction.
2315
+ ### Launcher Environment Delivery
2050
2316
 
2051
2317
  Launcher environment delivery uses stable `szsv-<base32-sha256>` Docker
2052
2318
  resource names and `/run/serve.zone/secrets/<resource>` source paths. The
2053
- nonsecret map is written to `/run/serve.zone/workloadinit-map-v1.json` with mode
2054
- 0444. Runtime assets under `/opt/serve.zone/runtime-assets` are read-only, and
2055
- the wrapper executes
2056
- `workloadinit run --map /run/serve.zone/workloadinit-map-v1.json -- <argv...>`
2057
- without a shell, symlink, or aggregate value file. Resources and maps remain
2058
- retained while any desired, applied, or previous-accepted manifest references
2059
- them and are deleted only after Docker confirms rollout or service removal.
2319
+ nonsecret map is written to `/run/serve.zone/workloadinit-map.json` with mode
2320
+ 0444. Runtime assets under `/opt/serve.zone/runtime-assets` are read-only, the
2321
+ wrapper itself lives at
2322
+ `/opt/serve.zone/runtime-assets/workloadinit/workloadinit`, and it executes
2323
+ `workloadinit run --map /run/serve.zone/workloadinit-map.json -- <argv...>`
2324
+ without a shell, symlink, or aggregate value file. A resource name is the
2325
+ lowercase base32 of a SHA-256 over `serve.zone/docker-secret-resource`, a NUL
2326
+ and the SecretVersion id, so every launcher secret of a service is republished
2327
+ under a new Docker resource name when a cluster moves to 32.0.0. Resources and
2328
+ maps remain retained while any desired, applied, or previous-accepted manifest
2329
+ references them and are deleted only after Docker confirms rollout or service
2330
+ removal.
2060
2331
 
2061
2332
  ## Platform Contracts
2062
2333