@astrale-domains/services 0.4.8 → 0.5.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.
- package/README.md +153 -816
- package/dist/schema/index.d.ts +4 -5
- package/dist/schema/index.js +2 -3
- package/dist/schema/modules/deployment/classes/cloudflare-deployment.d.ts +4 -321
- package/dist/schema/modules/deployment/classes/cloudflare-deployment.js +1 -19
- package/dist/schema/modules/deployment/classes/current-deployment.d.ts +20 -0
- package/dist/schema/modules/deployment/classes/current-deployment.js +13 -0
- package/dist/schema/modules/deployment/classes/{deployment-on-line.d.ts → deployment-of.d.ts} +3 -3
- package/dist/schema/modules/deployment/classes/deployment-of.js +11 -0
- package/dist/schema/modules/deployment/classes/deployment.d.ts +9 -57
- package/dist/schema/modules/deployment/classes/deployment.js +9 -23
- package/dist/schema/modules/deployment/classes/index.d.ts +2 -3
- package/dist/schema/modules/deployment/classes/index.js +2 -3
- package/dist/schema/modules/deployment/errors/deployment-not-found.d.ts +2 -5
- package/dist/schema/modules/deployment/errors/deployment-not-found.js +2 -5
- package/dist/schema/modules/deployment/errors/index.d.ts +0 -2
- package/dist/schema/modules/deployment/errors/index.js +0 -2
- package/dist/schema/modules/deployment/functions/index.d.ts +0 -10
- package/dist/schema/modules/deployment/functions/index.js +0 -10
- package/dist/schema/modules/deployment/functions/retire-deployment.d.ts +8 -7
- package/dist/schema/modules/deployment/functions/retire-deployment.js +8 -7
- package/dist/schema/modules/deployment/index.d.ts +6 -6
- package/dist/schema/modules/deployment/index.js +7 -5
- package/dist/schema/modules/deployment/policies/index.d.ts +1 -4
- package/dist/schema/modules/deployment/policies/index.js +1 -4
- package/dist/schema/modules/deployment/policies/own-deployment.d.ts +1 -1
- package/dist/schema/modules/deployment/policies/own-deployment.js +6 -7
- package/dist/schema/modules/deployment/policies/traverse-owned-service-deployment.d.ts +2 -0
- package/dist/schema/modules/deployment/policies/traverse-owned-service-deployment.js +7 -0
- package/dist/schema/modules/deployment/states/deployment-lifecycle.d.ts +6 -9
- package/dist/schema/modules/deployment/states/deployment-lifecycle.js +6 -7
- package/dist/schema/modules/deployment/types/deployment-hosting.d.ts +9 -28
- package/dist/schema/modules/deployment/types/deployment-hosting.js +3 -3
- package/dist/schema/modules/deployment/types/deployment-request.d.ts +12 -40
- package/dist/schema/modules/deployment/types/deployment-request.js +17 -9
- package/dist/schema/modules/deployment/types/deployment-response.d.ts +7 -15
- package/dist/schema/modules/deployment/types/deployment-response.js +2 -2
- package/dist/schema/modules/deployment/types/deployment-retire.d.ts +1 -1
- package/dist/schema/modules/deployment/types/deployment-secret.d.ts +0 -39
- package/dist/schema/modules/deployment/types/deployment-secret.js +0 -34
- package/dist/schema/modules/deployment/types/deployment-summary.d.ts +14 -45
- package/dist/schema/modules/deployment/types/deployment-summary.js +9 -8
- package/dist/schema/modules/deployment/types/index.d.ts +7 -3
- package/dist/schema/modules/deployment/types/index.js +4 -2
- package/dist/schema/modules/{service → deployment}/types/service-deployment-artifact-module.d.ts +2 -20
- package/dist/schema/modules/deployment/types/service-deployment-artifact-module.js +10 -0
- package/dist/schema/modules/{service → deployment}/types/service-deployment-assets.d.ts +4 -20
- package/dist/schema/modules/{service → deployment}/types/service-deployment-assets.js +4 -7
- package/dist/schema/modules/service/classes/cloudflare-service.d.ts +321 -0
- package/dist/schema/modules/service/classes/cloudflare-service.js +35 -0
- package/dist/schema/modules/service/classes/index.d.ts +1 -1
- package/dist/schema/modules/service/classes/index.js +1 -1
- package/dist/schema/modules/service/classes/service.d.ts +18 -107
- package/dist/schema/modules/service/classes/service.js +16 -27
- package/dist/schema/modules/service/errors/index.d.ts +1 -0
- package/dist/schema/modules/service/errors/index.js +1 -0
- package/dist/schema/modules/service/errors/service-not-found.d.ts +7 -0
- package/dist/schema/modules/service/errors/service-not-found.js +8 -0
- package/dist/schema/modules/service/functions/delete-service.d.ts +1 -1
- package/dist/schema/modules/service/functions/delete-service.js +1 -1
- package/dist/schema/modules/{deployment/functions/deploy-cloudflare-deployment.d.ts → service/functions/deploy-service.d.ts} +20 -35
- package/dist/schema/modules/service/functions/deploy-service.js +15 -0
- package/dist/schema/modules/service/functions/describe-hosting.d.ts +8 -0
- package/dist/schema/modules/service/functions/describe-hosting.js +11 -0
- package/dist/schema/modules/service/functions/index.d.ts +5 -10
- package/dist/schema/modules/service/functions/index.js +5 -10
- package/dist/schema/modules/{deployment/functions/list-cloudflare-deployments.d.ts → service/functions/list-service-deployments.d.ts} +8 -12
- package/dist/schema/modules/service/functions/list-service-deployments.js +10 -0
- package/dist/schema/modules/service/functions/list-service-logs.d.ts +2 -1
- package/dist/schema/modules/service/functions/list-service-logs.js +1 -1
- package/dist/schema/modules/service/functions/list-service-schedules.d.ts +1 -1
- package/dist/schema/modules/service/functions/list-service-schedules.js +1 -1
- package/dist/schema/modules/service/functions/list-service-secret-names.d.ts +1 -1
- package/dist/schema/modules/service/functions/list-service-secret-names.js +1 -1
- package/dist/schema/modules/service/functions/promote-service.d.ts +14 -0
- package/dist/schema/modules/service/functions/promote-service.js +4 -0
- package/dist/schema/modules/service/functions/replace-service-schedules.d.ts +1 -1
- package/dist/schema/modules/service/functions/replace-service-schedules.js +1 -4
- package/dist/schema/modules/service/functions/set-service-secret.d.ts +4 -2
- package/dist/schema/modules/service/functions/set-service-secret.js +1 -1
- package/dist/schema/modules/service/functions/transfer-service.d.ts +14 -0
- package/dist/schema/modules/service/functions/transfer-service.js +4 -0
- package/dist/schema/modules/service/index.d.ts +5 -5
- package/dist/schema/modules/service/index.js +5 -4
- package/dist/schema/modules/service/policies/act-as.d.ts +6 -0
- package/dist/schema/modules/service/policies/act-as.js +10 -0
- package/dist/schema/modules/service/policies/index.d.ts +1 -0
- package/dist/schema/modules/service/policies/index.js +1 -0
- package/dist/schema/modules/service/states/service-lifecycle.d.ts +7 -20
- package/dist/schema/modules/service/states/service-lifecycle.js +6 -21
- package/dist/schema/modules/service/types/index.d.ts +1 -13
- package/dist/schema/modules/service/types/index.js +1 -8
- package/dist/schema/modules/service/types/service-management.d.ts +65 -24
- package/dist/schema/modules/service/types/service-management.js +46 -21
- package/dist/schema/schema.d.ts +252 -758
- package/dist/schema/schema.js +7 -11
- package/package.json +4 -11
- package/dist/scaffold/create-service.mjs +0 -77
- package/dist/scaffold/template/.env.example +0 -2
- package/dist/scaffold/template/README.md +0 -37
- package/dist/scaffold/template/gitignore +0 -6
- package/dist/scaffold/template/scripts/build.mjs +0 -10
- package/dist/scaffold/template/scripts/deploy.mjs +0 -50
- package/dist/scaffold/template/src/account.js +0 -26
- package/dist/scaffold/template/src/index.js +0 -32
- package/dist/scaffold/template/src/job.js +0 -19
- package/dist/schema/modules/deployment/classes/deployment-line.d.ts +0 -23
- package/dist/schema/modules/deployment/classes/deployment-line.js +0 -24
- package/dist/schema/modules/deployment/classes/deployment-on-line.js +0 -11
- package/dist/schema/modules/deployment/classes/line-owned-by.d.ts +0 -26
- package/dist/schema/modules/deployment/classes/line-owned-by.js +0 -19
- package/dist/schema/modules/deployment/errors/deployment-label-not-found.d.ts +0 -12
- package/dist/schema/modules/deployment/errors/deployment-label-not-found.js +0 -11
- package/dist/schema/modules/deployment/errors/deployment-registry-only.d.ts +0 -11
- package/dist/schema/modules/deployment/errors/deployment-registry-only.js +0 -12
- package/dist/schema/modules/deployment/functions/deploy-cloudflare-deployment.js +0 -14
- package/dist/schema/modules/deployment/functions/describe-deployment-hosting.d.ts +0 -8
- package/dist/schema/modules/deployment/functions/describe-deployment-hosting.js +0 -11
- package/dist/schema/modules/deployment/functions/expire-deployment-previews.d.ts +0 -16
- package/dist/schema/modules/deployment/functions/expire-deployment-previews.js +0 -14
- package/dist/schema/modules/deployment/functions/list-cloudflare-deployment-secret-names.d.ts +0 -5
- package/dist/schema/modules/deployment/functions/list-cloudflare-deployment-secret-names.js +0 -4
- package/dist/schema/modules/deployment/functions/list-cloudflare-deployments.js +0 -10
- package/dist/schema/modules/deployment/functions/list-deployment-secret-names.d.ts +0 -4
- package/dist/schema/modules/deployment/functions/list-deployment-secret-names.js +0 -6
- package/dist/schema/modules/deployment/functions/read-deployment-last-calls.d.ts +0 -10
- package/dist/schema/modules/deployment/functions/read-deployment-last-calls.js +0 -13
- package/dist/schema/modules/deployment/functions/retain-deployment.d.ts +0 -12
- package/dist/schema/modules/deployment/functions/retain-deployment.js +0 -13
- package/dist/schema/modules/deployment/functions/set-cloudflare-deployment-secret.d.ts +0 -18
- package/dist/schema/modules/deployment/functions/set-cloudflare-deployment-secret.js +0 -7
- package/dist/schema/modules/deployment/functions/set-deployment-secret.d.ts +0 -14
- package/dist/schema/modules/deployment/functions/set-deployment-secret.js +0 -3
- package/dist/schema/modules/deployment/policies/act-for-organization.d.ts +0 -6
- package/dist/schema/modules/deployment/policies/act-for-organization.js +0 -10
- package/dist/schema/modules/deployment/policies/own-line.d.ts +0 -5
- package/dist/schema/modules/deployment/policies/own-line.js +0 -10
- package/dist/schema/modules/deployment/policies/traverse-owned-line-deployment.d.ts +0 -2
- package/dist/schema/modules/deployment/policies/traverse-owned-line-deployment.js +0 -7
- package/dist/schema/modules/deployment/policies/traverse-owned-line.d.ts +0 -2
- package/dist/schema/modules/deployment/policies/traverse-owned-line.js +0 -7
- package/dist/schema/modules/deployment/types/deployment-retention.d.ts +0 -49
- package/dist/schema/modules/deployment/types/deployment-retention.js +0 -45
- package/dist/schema/modules/service/classes/cloudflare-worker.d.ts +0 -352
- package/dist/schema/modules/service/classes/cloudflare-worker.js +0 -41
- package/dist/schema/modules/service/functions/delete-service-secret.d.ts +0 -11
- package/dist/schema/modules/service/functions/delete-service-secret.js +0 -3
- package/dist/schema/modules/service/functions/delete-worker-secret.d.ts +0 -11
- package/dist/schema/modules/service/functions/delete-worker-secret.js +0 -7
- package/dist/schema/modules/service/functions/delete-worker.d.ts +0 -9
- package/dist/schema/modules/service/functions/delete-worker.js +0 -7
- package/dist/schema/modules/service/functions/deploy-cloudflare-worker.d.ts +0 -109
- package/dist/schema/modules/service/functions/deploy-cloudflare-worker.js +0 -9
- package/dist/schema/modules/service/functions/list-worker-logs.d.ts +0 -12
- package/dist/schema/modules/service/functions/list-worker-logs.js +0 -7
- package/dist/schema/modules/service/functions/list-worker-schedules.d.ts +0 -4
- package/dist/schema/modules/service/functions/list-worker-schedules.js +0 -7
- package/dist/schema/modules/service/functions/list-worker-secret-names.d.ts +0 -4
- package/dist/schema/modules/service/functions/list-worker-secret-names.js +0 -7
- package/dist/schema/modules/service/functions/migrate-worker-ingress.d.ts +0 -24
- package/dist/schema/modules/service/functions/migrate-worker-ingress.js +0 -26
- package/dist/schema/modules/service/functions/replace-worker-schedules.d.ts +0 -11
- package/dist/schema/modules/service/functions/replace-worker-schedules.js +0 -7
- package/dist/schema/modules/service/functions/set-worker-secret.d.ts +0 -12
- package/dist/schema/modules/service/functions/set-worker-secret.js +0 -7
- package/dist/schema/modules/service/types/deploy-cloudflare-worker.d.ts +0 -286
- package/dist/schema/modules/service/types/deploy-cloudflare-worker.js +0 -51
- package/dist/schema/modules/service/types/legacy/service-deployment-content.js +0 -22
- package/dist/schema/modules/service/types/service-deployment-artifact-module.js +0 -13
- package/dist/schema/modules/service/types/service-deployment-resource-limits.d.ts +0 -6
- package/dist/schema/modules/service/types/service-deployment-resource-limits.js +0 -7
- package/dist/schema/modules/service/types/service-readiness.d.ts +0 -6
- package/dist/schema/modules/service/types/service-readiness.js +0 -6
- package/dist/schema/modules/service/types/service-secret-operation-rejection.d.ts +0 -9
- package/dist/schema/modules/service/types/service-secret-operation-rejection.js +0 -5
- /package/dist/schema/modules/{service → deployment}/types/content/index.d.ts +0 -0
- /package/dist/schema/modules/{service → deployment}/types/content/index.js +0 -0
- /package/dist/schema/modules/{service → deployment}/types/content/limits.d.ts +0 -0
- /package/dist/schema/modules/{service → deployment}/types/content/limits.js +0 -0
- /package/dist/schema/modules/{service → deployment}/types/service-deployment-content.d.ts +0 -0
- /package/dist/schema/modules/{service → deployment}/types/service-deployment-content.js +0 -0
package/README.md
CHANGED
|
@@ -1,767 +1,186 @@
|
|
|
1
1
|
# Services
|
|
2
2
|
|
|
3
|
-
`services.astrale.ai`
|
|
4
|
-
|
|
3
|
+
`services.astrale.ai` hosts Services: provider-backed compute an Identity owns. A Service is the
|
|
4
|
+
stable identity of one origin in one environment. Every deploy adds an immutable deployment of it,
|
|
5
|
+
and the Service's URL serves the one it currently points at, like a project and its deployments.
|
|
6
|
+
Provider protocols stay behind a neutral SDK Integration, and the graph keeps only admitted
|
|
7
|
+
lifecycle evidence.
|
|
5
8
|
|
|
6
9
|
## Model
|
|
7
10
|
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- the
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
it
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
Only a runtime whose Environment vars set `SERVICES_DEPLOYMENT_HOST` (the deployments' routing
|
|
124
|
-
domain) hosts deployments; every other runtime answers `hosting-unavailable`. Such a runtime refuses
|
|
125
|
-
to boot without `SERVICES_DEPLOYMENT_KERNEL` (the Admin Kernel's issuer URL),
|
|
126
|
-
`SERVICES_SECRET_DIGEST_KEY` (at least 32 UTF-8 bytes, kept for the life of the data plane: the
|
|
127
|
-
recorded digests depend on it), the `SERVICE_DEPLOYMENTS` dispatch binding to the environment's
|
|
128
|
-
deployments namespace, and `CLOUDFLARE_SCHEDULE_KV_ID`, the routing KV that activates a label, and
|
|
129
|
-
refuses a `SERVICES_PLATFORM_OWNER` that is not an Identity id. Hosting is declared once, by the
|
|
130
|
-
Environment's `vars` in `astrale.config.ts`: the same vars decide the `SERVICE_DEPLOYMENTS` binding,
|
|
131
|
-
and the configuration refuses a secrets file that sets a hosting var. [Real-Kernel
|
|
132
|
-
proof](tests/kernel/README.md).
|
|
133
|
-
|
|
134
|
-
### Retention and retirement
|
|
135
|
-
|
|
136
|
-
A deployment is a `preview` until the registry Domain marks it `published`; only a preview expires.
|
|
137
|
-
Four more `CloudflareDeployment` Methods manage what stays served. Retirement, retention and
|
|
138
|
-
revival name a deployment by its label, script etag, artifact tag and configuration digest, never
|
|
139
|
-
by a provider version id.
|
|
140
|
-
|
|
141
|
-
- `retire` is the instance Method of the line holder's emergency stop. Any authenticated identity
|
|
142
|
-
may call it, because a publisher holds no `can_use` on Services' Functions and an `authorized`
|
|
143
|
-
Method requires it of every non-Root caller: before anything is read, the Kernel answers
|
|
144
|
-
`OwnDeployment` for the caller on its own session, and any other caller finds no such deployment
|
|
145
|
-
(`DEPLOYMENT_NOT_FOUND`). If the Admin instance ever grants publishers `can_use` on it, it becomes
|
|
146
|
-
`authorized` with the `OwnDeployment` Policy instead. It withdraws the deployment for good in two
|
|
147
|
-
phases. The graph records it `retiring` first, so no deploy reuses it; the same Mutation evicts
|
|
148
|
-
the operation lease of any operation writing the script, such as an in-place rewrite of its
|
|
149
|
-
secrets, which then records nothing. Then the routing KV gets the tombstone
|
|
150
|
-
`retirement:<label>` = `withdrawn`. The dispatcher
|
|
151
|
-
answers a label with a tombstone `DEPLOYMENT_RETIRED` (`410`) and keeps serving its
|
|
152
|
-
`deployment.json` from `record:<label>`, which stays. The first call answers `retiring` with
|
|
153
|
-
`retryAfterMs`. A call 65 seconds after the tombstone, once the dispatcher's retirement probe
|
|
154
|
-
answers `DEPLOYMENT_RETIRED`, deletes the script and its signing key and answers `retired`. A
|
|
155
|
-
withdrawn label is never served again: deploying the same release is refused
|
|
156
|
-
`deployment-retired`.
|
|
157
|
-
- `retain` marks one preview `published`. Only the registry Domain's principal calls it, acting as
|
|
158
|
-
itself; it is the installed, unfrozen Domain whose origin is `SERVICES_REGISTRY_ORIGIN`. Services
|
|
159
|
-
reads the caller's Domain node by id under its own credential, which is why it requires read on
|
|
160
|
-
the Kernel's `Domain` Class; nothing the caller presents decides it. An authenticated caller
|
|
161
|
-
carries Identity ids but no Domain origin, and read on `Identity` does not reach a Domain's node.
|
|
162
|
-
Every instance that installs this Services version grants that read, so Services can read the
|
|
163
|
-
origin and frozen flag of each Domain installed there; only the Admin instance's hosting runtime
|
|
164
|
-
uses it. The requirement goes once the Kernel gives a handler its caller's Domain origin. A line
|
|
165
|
-
holder, or an identity acting for the registry, is refused `DEPLOYMENT_REGISTRY_ONLY`. The mark
|
|
166
|
-
is monotonic and idempotent, and it never undoes a retirement in progress: a preview marked after
|
|
167
|
-
its expiry started still retires, and a redeploy of the same release revives it.
|
|
168
|
-
- `lastCalls` returns the last call of up to 100 labels the caller holds (as the Kernel answers
|
|
169
|
-
`OwnDeployment` on the caller's session), or any labels for the registry; an unknown label and
|
|
170
|
-
another organisation's both answer `DEPLOYMENT_LABEL_NOT_FOUND`. It reads the data points the dispatcher writes per admitted call, through the Analytics
|
|
171
|
-
Engine SQL API, and the last call recorded in the graph. A label nobody called reads `null`. It
|
|
172
|
-
reads only the facts Services recorded, so it answers for a listed deployment whose stored
|
|
173
|
-
record this SDK no longer admits.
|
|
174
|
-
- `expirePreviews` is the registry's pass. A preview expires 30 days after its activation (its
|
|
175
|
-
creation, or its revival) or its last call, whichever is later; every activation records its
|
|
176
|
-
instant, and a pass refuses an active preview without one rather than expire it on its creation. The pass is a plan unless the runtime sets
|
|
177
|
-
`SERVICES_PREVIEW_EXPIRY=enforce` and the caller passes `apply: true`: it answers the `planned`
|
|
178
|
-
labels and retires none. Applied, it retires them in two phases with the tombstone `expired` and
|
|
179
|
-
answers `retired` and `pending` labels; the next pass completes the pending ones. Either way it
|
|
180
|
-
records later calls, so each preview's `expiresAt` follows its traffic. `limit` bounds the
|
|
181
|
-
deployment writes of one pass (64 by default), and retirements come first.
|
|
182
|
-
|
|
183
|
-
An expired label revives. When the same owner deploys the same release (same label, so same
|
|
184
|
-
artifact and configuration) again, the deployment resumes from `retired`. It is uploaded
|
|
185
|
-
create-only with a new signing key, and the activation republishes the same `record:<label>`
|
|
186
|
-
before it removes the `expired` tombstone. An instance that pins the label is served the same
|
|
187
|
-
release digest at the same URL. A `withdrawn` tombstone is never removed.
|
|
188
|
-
|
|
189
|
-
`SERVICES_REGISTRY_ORIGIN` and `SERVICES_PREVIEW_EXPIRY` (`plan`, the default, or `enforce`) are
|
|
190
|
-
Environment vars of a hosting runtime, refused in a secrets file and refused without
|
|
191
|
-
`SERVICES_DEPLOYMENT_HOST`. Without a registry origin no caller is the registry, and `retain` and
|
|
192
|
-
`expirePreviews` are refused. `admin` and the frozen `admin-beta` name `admin.astrale.ai` and keep
|
|
193
|
-
expiry a plan.
|
|
194
|
-
Reading last calls needs the API token's Account Analytics Read permission. `pnpm
|
|
195
|
-
provider:deployment-audit` lists the deployments namespace read-only
|
|
196
|
-
([operator](providers/cloudflare/operator/README.md)).
|
|
197
|
-
|
|
198
|
-
The View, CLI, and agent consumers read the named Service Queries directly; there is intentionally
|
|
199
|
-
no list Function wrapping `listServicesQuery`. Deployment, secrets, schedules, logs, and deletion
|
|
200
|
-
are Workflows because they coordinate admitted caller intent with external effects. Receiver Methods
|
|
201
|
-
are authorized by the ownership Policy before their Workflows begin. `service-hosting`
|
|
202
|
-
is the provider-neutral compute Integration contract. Cloudflare is its first Provider.
|
|
203
|
-
|
|
204
|
-
`Service` declares seven abstract management contracts. `CloudflareWorker` owns their executable
|
|
205
|
-
Methods (`setSecret`, `deleteSecret`, `secrets`, `setSchedule`, `schedules`, `logs`, `migrateIngress`, `delete`) and
|
|
206
|
-
applies `OwnService` to each one. Workflow addresses are `CloudflareWorker.*`; abstract `Service.*`
|
|
207
|
-
keys are not executable. A future provider Class must supply its own concrete declarations and
|
|
208
|
-
implementations. Properties and ownership relationships retain their inherited meaning.
|
|
209
|
-
|
|
210
|
-
## Operating hosted deployments
|
|
211
|
-
|
|
212
|
-
### Recorded choices
|
|
213
|
-
|
|
214
|
-
The deployment model left four choices open (decisions D12, D13 and D16 of the deployment and
|
|
215
|
-
versioning spec, and the rule for deployment secrets). Services implements them as follows.
|
|
216
|
-
|
|
217
|
-
| Choice | Implemented here |
|
|
218
|
-
| --- | --- |
|
|
219
|
-
| How a line reads in a deployment URL | Only the platform organisation (`SERVICES_PLATFORM_OWNER`) registers or deploys readable lines, which read `<readable>-<fingerprint>`, such as `agencies-staging-svkrguuhpcvmxxas`. Every other line is its 16-character fingerprint alone, derived from the origin and environment, never a stored UUID. A line keeps the addressing it was registered with, whoever holds it later: a deploy with the other addressing is refused `address-mismatch`, and a readable deploy for any other organisation `invalid-deployment`. |
|
|
220
|
-
| Which deployment runs a Domain's crons | None. A hosted deployment's frozen configuration has no trigger, a Workers for Platforms script cannot own one, and the dispatcher's minute fan-out reads only the `cron:` keys of Services, which no deployment has. The spec's recommendation, crons on the deployment of the version the author designates (by default the highest stable one), needs the registry and is not implemented by the foundation. |
|
|
221
|
-
| Where deployments live | In the Admin instance's Services, on a line held by the publisher's organisation, outside every instance that installs them. Uninstalling a Domain, deleting an instance or deleting its Services never touches them. The organisation is an Identity of the Admin instance that the deploy request names; a request that names none deploys for the caller itself. |
|
|
222
|
-
| Deployment secrets | A deploy binds the environment's current secrets to the new deployment only; every other deployment keeps its own. A secret rotates in place on one deployment, which keeps its script, URL, issuer and key: by `setSecret`, or by deploying the identical release again. The signing key never rotates. |
|
|
223
|
-
|
|
224
|
-
### Rotate a deployment's secrets
|
|
225
|
-
|
|
226
|
-
Rotating a secret never creates a deployment. Two calls write a new value in place on one
|
|
227
|
-
deployment, and on no other:
|
|
228
|
-
|
|
229
|
-
- deploying the identical release again (same build and configuration) with the new value in the
|
|
230
|
-
environment's secrets: it is the same label, `deploy` answers `reused`, and its `secrets.state` is
|
|
231
|
-
`updated` when a value changed;
|
|
232
|
-
- `setSecret` on the deployment, for one name its frozen configuration declares.
|
|
233
|
-
|
|
234
|
-
A deployment is called by the `id` that `list` returns. Every call below runs on the Admin instance
|
|
235
|
-
(`--admin`, or `--admin=<bookmark>`), as an identity that acts for the organisation holding the line
|
|
236
|
-
(`--as <identity>` when it is not the bookmark's default), with an `astrale` CLI whose `call` takes
|
|
237
|
-
`--admin` and `-d @file`. Put `key=value` params before `--admin`.
|
|
238
|
-
|
|
239
|
-
```sh
|
|
240
|
-
astrale call /:services.astrale.ai:class.CloudflareDeployment:list \
|
|
241
|
-
origin=agencies.example.com environment=staging --admin --json
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
`list` answers `{ "deployments": [...] }` with each deployment's `id`, `label`, `url`, `state` and
|
|
245
|
-
declared `record`.
|
|
246
|
-
|
|
247
|
-
Never pass a secret value in argv, where `ps` and shell history keep it. Write the input to a file
|
|
248
|
-
that only you can read (`umask 077`, then your editor), outside the repository:
|
|
11
|
+
- **`Service` / `CloudflareService`**: one `(origin, environment)`. Its `name` is the opaque
|
|
12
|
+
16-character fingerprint the SDK derives from them (`deploymentLine({ addressing: 'opaque' })`);
|
|
13
|
+
it is unique among Services and is the DNS label of `url`, `https://<name>.<routing domain>`.
|
|
14
|
+
`state` is `active`, or `deleting` while its deletion retires its deployments.
|
|
15
|
+
- **`service_owned_by`**: every Service has exactly one owner, an Identity: a user, a group or a
|
|
16
|
+
service account. It is chosen once, when the first deploy creates the Service, and changes only
|
|
17
|
+
by `transfer`.
|
|
18
|
+
- **`Deployment` / `CloudflareDeployment`**: one immutable release of a Service: one build with
|
|
19
|
+
its frozen configuration, served by one Workers for Platforms script at its own URL,
|
|
20
|
+
`https://<label>.<routing domain>`, where `label` is `<name>-<content16>`. It never changes code:
|
|
21
|
+
a new release is a new deployment. Its lifecycle is `deploying` → `active` → `retiring` →
|
|
22
|
+
`retired`, or `failed`; a retired deployment is never served again.
|
|
23
|
+
- **`deployment_of`**: the Service a deployment is a version of. A deployment has no owner of its
|
|
24
|
+
own: it belongs to whoever owns its Service.
|
|
25
|
+
- **`current_deployment`**: the deployment the Service's URL serves and its crons run on. Promoting
|
|
26
|
+
another deployment moves this Edge; a rollback is the promotion of an older one.
|
|
27
|
+
|
|
28
|
+
Secret values stay write-only at Cloudflare and never enter a Mutation or the graph. Schedules
|
|
29
|
+
are provider state. Logs are read from the provider when asked.
|
|
30
|
+
|
|
31
|
+
## Owner and authority
|
|
32
|
+
|
|
33
|
+
One Policy decides everything: `OwnService`, the subject is the owner of the Service. The Kernel
|
|
34
|
+
evaluates it over the caller and every Identity the caller reaches through `extends_with`, with
|
|
35
|
+
deny-wins. Nothing is selected at check time: a caller in no group reaches its own Services, a
|
|
36
|
+
caller in three groups also reaches the Services of each.
|
|
37
|
+
|
|
38
|
+
- A deploy that creates a Service makes the caller its owner, or the `owner` it names when the
|
|
39
|
+
caller is or acts as that Identity (`ActAs`).
|
|
40
|
+
- A service account deploys a Service when it owns it, or when it is a member of the group that
|
|
41
|
+
owns it (`Group.assignMember` writes the `extends_with` Edge). A member has every right on every
|
|
42
|
+
Service of the group, deletion included.
|
|
43
|
+
- `OwnDeployment` gives a deployment to the owner of its Service.
|
|
44
|
+
|
|
45
|
+
Every Method is `authenticated`: a deployer's identities on the Admin instance hold no `can_use` on
|
|
46
|
+
Services' Functions, which an `authorized` Method requires of every non-Root caller. Each handler
|
|
47
|
+
asks the Kernel `Identity.can` for the caller on its own session before it reads anything: a caller
|
|
48
|
+
who does not own the Service finds none (`SERVICE_NOT_FOUND`, `DEPLOYMENT_NOT_FOUND`), and a deploy
|
|
49
|
+
of another owner's Service is refused `service-owned-elsewhere`. The day the Admin instance grants
|
|
50
|
+
deployers `can_use` on these Methods, they become `authorized` with the Policy and the handler
|
|
51
|
+
checks go.
|
|
52
|
+
|
|
53
|
+
`list` reads Services under the Services Domain credential and keeps those the Kernel answers
|
|
54
|
+
`OwnService` for, in batches: a caller-mode read does not reach the Identity end of
|
|
55
|
+
`service_owned_by` for an owner the caller reaches through `extends_with`
|
|
56
|
+
([kernel#1138](https://github.com/astrale-os/kernel/issues/1138)).
|
|
57
|
+
|
|
58
|
+
## Methods
|
|
59
|
+
|
|
60
|
+
Call them on the Admin instance (`astrale call ... --admin`).
|
|
61
|
+
|
|
62
|
+
| Method | On | What it does |
|
|
63
|
+
| --- | --- | --- |
|
|
64
|
+
| `deploy` | `CloudflareService` (static) | Hosts one release (`DeploymentRequestV1`) as a deployment of the Service of its origin and environment, creating the Service on its first deploy, and by default (`promote` absent or `true`) makes the Service serve it. |
|
|
65
|
+
| `hosting` | `CloudflareService` (static) | The routing domain Services and deployments are addressed under; names are always opaque. |
|
|
66
|
+
| `list` | `CloudflareService` (static) | The deployments of the Services the caller owns, narrowed to an origin and environment when given, each with `current`. |
|
|
67
|
+
| `promote` | `CloudflareService` | Points the Service at one of its active deployments, named by its label. |
|
|
68
|
+
| `transfer` | `CloudflareService` | Gives the Service to another owner the caller is or acts as. |
|
|
69
|
+
| `setSecret`, `secrets` | `CloudflareService` | Rewrites one declared secret of the current deployment in place; lists the names it binds, never a value. |
|
|
70
|
+
| `setSchedule`, `schedules` | `CloudflareService` | Replaces or reads the Service's UTC cron set; each cron fires on the current deployment. |
|
|
71
|
+
| `logs` | `CloudflareService` | Recent logs of the current deployment, or of one deployment named by `label`. |
|
|
72
|
+
| `delete` | `CloudflareService` | Stops routing the Service, retires every deployment of it, then deletes it and frees its name. |
|
|
73
|
+
| `retire` | `CloudflareDeployment` | Withdraws one deployment for good. The current deployment is never retired alone. |
|
|
74
|
+
|
|
75
|
+
## Deploying
|
|
76
|
+
|
|
77
|
+
`astrale-domain deploy <environment>` with the Astrale adapter calls `CloudflareService.deploy`.
|
|
78
|
+
`deploy` is a non-durable Action: its input holds the environment's secrets, and no Execution, step
|
|
79
|
+
result or journal entry keeps them.
|
|
80
|
+
|
|
81
|
+
1. Before any read it recomputes the label from the Service name, the build digest and the frozen
|
|
82
|
+
configuration, recomputes the build digest from the uploaded bytes, admits the record with the
|
|
83
|
+
SDK, and refuses a service binding, a dispatch namespace, a router other than the logical
|
|
84
|
+
`admin-router`, a reserved binding name, or secrets other than exactly the declared ones.
|
|
85
|
+
2. It reads the Service of the fingerprint under the Services Domain credential and proves the
|
|
86
|
+
caller's authority on the caller's own session: `OwnService` on an existing Service, `ActAs` on
|
|
87
|
+
the owner it names for a new one.
|
|
88
|
+
3. A new label is created under a graph lease. The provider uploads it create-only with
|
|
89
|
+
`ASTRALE_BUILD_DIGEST`, `ASTRALE_SETTINGS`, a signing key generated for this deployment alone,
|
|
90
|
+
and the request's secrets; checks that the uploaded version serves its readiness paths; then
|
|
91
|
+
activates it through the routing KV `record:<label>`. The graph records it active with its key
|
|
92
|
+
id, script etag and keyed HMAC-SHA256 digests of its secrets.
|
|
93
|
+
4. The same release again is the same label: `deploy` answers `reused`. Its secrets are compared
|
|
94
|
+
with the recorded digests: `unchanged`, or rewritten in place on that deployment alone,
|
|
95
|
+
`updated`.
|
|
96
|
+
5. When it promotes, `current_deployment` moves to the deployment, fenced on what the Service
|
|
97
|
+
served, and the routing KV `current:<name>` names its label. A routing write that failed is
|
|
98
|
+
answered `service-unavailable`; deploying or promoting again converges.
|
|
99
|
+
|
|
100
|
+
`WORKER_URL` and `ASTRALE_BUILD_DIGEST` are bound by the host, never by the author: Services refuses
|
|
101
|
+
them as caller variables or secrets.
|
|
102
|
+
|
|
103
|
+
## Routing and crons
|
|
104
|
+
|
|
105
|
+
The Services dispatcher serves one routing domain:
|
|
106
|
+
|
|
107
|
+
- `https://<label>.<routing domain>` serves that deployment: `record:<label>` activates it, and
|
|
108
|
+
`retirement:<label>` answers `DEPLOYMENT_RETIRED` (`410`).
|
|
109
|
+
- `https://<name>.<routing domain>` serves the deployment `current:<name>` names, through the same
|
|
110
|
+
path, so a retired deployment is never served through its Service either.
|
|
111
|
+
- Every minute the dispatcher reads the `cron:<name>` keys and posts each due cron to the current
|
|
112
|
+
deployment of that Service, at `https://cron.internal/__scheduled`.
|
|
113
|
+
|
|
114
|
+
## Secrets
|
|
115
|
+
|
|
116
|
+
Services never keeps a value. A deploy binds the request's secrets to the new deployment only;
|
|
117
|
+
every other deployment keeps its own. `setSecret` rewrites one name the current deployment's frozen
|
|
118
|
+
configuration declares, in place, under the deployment's graph lease: it compares the keyed
|
|
119
|
+
digests, writes only changed values, forgets the digests of the names it writes first and records
|
|
120
|
+
the new ones once every write succeeded. The provider serves a new value a few seconds later. A
|
|
121
|
+
new name, or removing one, is a new release; the signing key never rotates.
|
|
122
|
+
|
|
123
|
+
Never pass a secret value in argv. Write the input to a file only you can read, outside the
|
|
124
|
+
repository:
|
|
249
125
|
|
|
250
126
|
```json
|
|
251
127
|
{ "name": "API_TOKEN", "value": "<the new value>" }
|
|
252
128
|
```
|
|
253
129
|
|
|
254
130
|
```sh
|
|
255
|
-
astrale call "@$
|
|
131
|
+
astrale call "@$SERVICE::services.astrale.ai:class.CloudflareService.method.setSecret" \
|
|
256
132
|
--admin -d @rotate.json --json
|
|
257
133
|
```
|
|
258
134
|
|
|
259
|
-
|
|
260
|
-
|
|
135
|
+
Put the new value in the environment's secrets as well: the next deploy binds the values it
|
|
136
|
+
carries.
|
|
137
|
+
|
|
138
|
+
## Rollback and retirement
|
|
261
139
|
|
|
262
140
|
```sh
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
141
|
+
astrale call /:services.astrale.ai:class.CloudflareService:list \
|
|
142
|
+
origin=agencies.example.com environment=production --admin --json
|
|
143
|
+
astrale call "@$SERVICE::services.astrale.ai:class.CloudflareService.method.promote" \
|
|
144
|
+
label=$PREVIOUS_LABEL --admin --json
|
|
266
145
|
```
|
|
267
146
|
|
|
268
|
-
`
|
|
147
|
+
`retire` withdraws one deployment in two phases: the first call records it `retiring` and writes
|
|
148
|
+
its tombstone, and a call after `retryAfterMs` (once every dispatcher reads the tombstone, about 65
|
|
149
|
+
seconds) deletes its script and key and answers `retired`.
|
|
269
150
|
|
|
270
151
|
```sh
|
|
271
|
-
astrale call "@$
|
|
152
|
+
astrale call "@$DEPLOYMENT::services.astrale.ai:class.CloudflareDeployment.method.retire" \
|
|
153
|
+
--admin --json
|
|
272
154
|
```
|
|
273
155
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
| `{ "name": "API_TOKEN" }` | The value is written, or was already the one served. Propagation is eventually consistent: the provider serves a new value about 4 s later. | Nothing. Calls in those seconds may still see the old value. |
|
|
279
|
-
| `secret-undeclared` | The deployment's frozen configuration binds no such name. Nothing was written. | A new name is a new release: declare it and deploy. |
|
|
280
|
-
| `service-unavailable` with `retryAfter` | The deployment is being created, another operation holds it, or the provider failed. | The same call again after `retryAfter`. A rerun converges, whatever an interrupted one wrote. |
|
|
281
|
-
| `service-unavailable` without `retryAfter` | The deployment cannot be rotated in place: its creation failed, it is retiring or retired, or the script under its label is not the recorded one. `message` says which. | Deploy the release again after a failed creation. A retired deployment serves nothing. Ask the Services operator about a script that is not the recorded one. |
|
|
282
|
-
| `DEPLOYMENT_NOT_FOUND` | The caller neither holds the deployment's line nor acts for its holder. Nothing was read. | Call as an identity of the organisation holding the line. |
|
|
283
|
-
| `DEPLOYMENT_HOSTING_UNAVAILABLE` | The call reached a Kernel other than the Admin instance's, or the Services runtime there hosts no deployments (its Environment sets no `SERVICES_DEPLOYMENT_HOST`). | Call with `--admin`. If the Admin instance still answers it, ask the Services operator. |
|
|
284
|
-
|
|
285
|
-
### A leaked secret
|
|
286
|
-
|
|
287
|
-
A deploy never rewrites another deployment's secrets, so a leaked value stays bound to every
|
|
288
|
-
deployment that still serves it until each one is rotated:
|
|
289
|
-
|
|
290
|
-
1. Put the new value everywhere a deploy reads the environment's secrets from: the secrets file the
|
|
291
|
-
environment's adapter names (its `secrets` option, such as `.env.staging`) and every CI secret
|
|
292
|
-
store that deploys the environment. From then on, until step 5, no deploy may run with the old
|
|
293
|
-
value: an identical redeploy with it would write it back in place (`secrets.state: updated`).
|
|
294
|
-
2. Rotate every deployment of that origin and environment that serves, with the input file above.
|
|
295
|
-
`list` shows a deployment only once it has served: a deployment still being created, or whose
|
|
296
|
-
creation failed, is not listed. The block reads the list on each run, so run it again until
|
|
297
|
-
every reply is `{ "name": ... }`. A deployment already rotated answers the same without writing
|
|
298
|
-
anything, and one that answered `service-unavailable` with `retryAfter` takes the rotation on a
|
|
299
|
-
later run.
|
|
300
|
-
|
|
301
|
-
```sh
|
|
302
|
-
for ID in $(astrale call /:services.astrale.ai:class.CloudflareDeployment:list \
|
|
303
|
-
origin=agencies.example.com environment=staging --admin --json \
|
|
304
|
-
| jq -r '.deployments[] | select(.state == "active") | .id'); do
|
|
305
|
-
astrale call "@$ID::services.astrale.ai:class.CloudflareDeployment.method.setSecret" \
|
|
306
|
-
--admin -d @rotate.json --json
|
|
307
|
-
done
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
An identical redeploy rotates only the deployment of the current release; older ones need
|
|
311
|
-
`setSecret`. Only `active` deployments rotate in place: a retiring or retired one answers
|
|
312
|
-
`service-unavailable` without `retryAfter`, so revoking the value at its source (step 5) is what
|
|
313
|
-
covers it. A deployment still being created, or whose creation failed, is step 3's.
|
|
314
|
-
3. Take over every creation that a deploy started before step 1: that deploy may have read the old
|
|
315
|
-
value. Its deployment stays `deploying`, or `failed`, until a deploy records it active, so `list`
|
|
316
|
-
does not show it and `setSecret` refuses it; yet it serves as soon as its deploy has published
|
|
317
|
-
its record. Lease expiry alone does not stop that deploy. Activation checks only that the
|
|
318
|
-
deployment still holds the lease the deploy took, so a deploy that stalls past its lease, which
|
|
319
|
-
ends 10 minutes after it started, can still activate the deployment with the old value until
|
|
320
|
-
another deploy takes it over.
|
|
321
|
-
|
|
322
|
-
Find these deployments on the Admin instance (`ADMIN_INSTANCE` is its instance bookmark, as in
|
|
323
|
-
[Transfer a line](#transfer-a-line)). The query answers the deployments of the lines you hold or
|
|
324
|
-
act for, those still being created included. Keep the `label` of each one whose `state` is
|
|
325
|
-
`deploying` or `failed` and whose `record` names this origin and environment. When the answer
|
|
326
|
-
has a `page.next`, the same query with `--cursor <page.next>` reads the next page.
|
|
327
|
-
|
|
328
|
-
```sh
|
|
329
|
-
astrale query --class /:services.astrale.ai:class.CloudflareDeployment --limit 100 --json \
|
|
330
|
-
-i "$ADMIN_INSTANCE"
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
For each label, deploy its release again, identical (the same build and configuration; with
|
|
334
|
-
adapter-astrale, `astrale-domain deploy <environment>` from the same source), now that the
|
|
335
|
-
secrets file holds the new value. Repeat until it answers `deployed` for that label:
|
|
336
|
-
- while the stalled deploy's lease runs, it answers `service-unavailable` with `retryAfter`, the
|
|
337
|
-
end of that lease: deploy again then;
|
|
338
|
-
- once the lease has expired, it takes the deployment over, so the stalled deploy can no longer
|
|
339
|
-
activate it, and creates it with the current secrets, rewriting them on any script the stalled
|
|
340
|
-
deploy left;
|
|
341
|
-
- after a failed creation it retries with the current secrets; once the stalled deploy has
|
|
342
|
-
activated the deployment, it reuses it and rewrites its secrets in place
|
|
343
|
-
(`secrets.state: updated`).
|
|
344
|
-
|
|
345
|
-
A release that does not rebuild identically has another label and takes nothing over: the
|
|
346
|
-
stalled deployment keeps the value its deploy wrote until step 5 revokes it. Then run step 2's
|
|
347
|
-
block once more.
|
|
348
|
-
4. Deployments in the platform dispatch namespace have no Services node, and neither `list` nor the
|
|
349
|
-
query shows them. They are the platform Domains deployed through adapter-cloudflare's namespace
|
|
350
|
-
mode: Shell, the Services runtime, Issues, ai-gateway, closing, grc, integrations and ui, each
|
|
351
|
-
once it has moved there. Rotate each one in place with the platform namespace's tooling, with
|
|
352
|
-
its operator file sourced
|
|
353
|
-
([operator README](./providers/cloudflare/operator/README.md#operating-platform-deployments)): one
|
|
354
|
-
label and one declared name per run, the value read on stdin from a file that holds only the new
|
|
355
|
-
value, outside the repository and readable only by you.
|
|
356
|
-
|
|
357
|
-
```sh
|
|
358
|
-
pnpm provider:platform-secret -- --label <label> --name API_TOKEN < new-value
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
Or deploy its identical release again. A platform deploy that started before step 1 may have
|
|
362
|
-
read the old value too: once it has returned, rotate the deployment it made or reused. Admin is
|
|
363
|
-
not one of them: it runs on its own bootstrap Worker, and its secrets rotate through that
|
|
364
|
-
Worker's own deploy tooling. The Services runtime is one of them once the Admin instance runs on
|
|
365
|
-
a deployment of [`admin`](#admin-hosted-runtime); its identical redeploy is
|
|
366
|
-
`pnpm run deploy admin`, with the new value in `.env.admin`. Until that move it is the frozen
|
|
367
|
-
`admin-beta` Worker: put the new value in `.env.admin-beta` (and the same in `.env.admin`, which
|
|
368
|
-
keeps its values) and deploy it again with `pnpm run deploy admin-beta`, from the commit it was
|
|
369
|
-
last deployed from so that only its secrets change.
|
|
370
|
-
5. Revoke the leaked value where it was issued once step 2's block answered `{ "name": ... }` for
|
|
371
|
-
every deployment and every redeploy of step 3 answered `deployed`, or at once when the leak
|
|
372
|
-
outweighs the failures of deployments not rotated yet.
|
|
373
|
-
6. Delete `rotate.json` and `new-value`.
|
|
374
|
-
|
|
375
|
-
### Transfer a line
|
|
376
|
-
|
|
377
|
-
A line belongs to the first organisation that deploys on it. No Services Method moves it: an
|
|
378
|
-
operator transfers it as the Admin instance's root, with one Mutation on its `line_owned_by` Edge.
|
|
379
|
-
Its deployments stay on the line and keep serving. From that Mutation on, the new holder lists,
|
|
380
|
-
rotates and deploys them, and the previous holder and its members no longer see them.
|
|
381
|
-
|
|
382
|
-
1. Find the line and its holder on the Admin instance (`ADMIN_INSTANCE` is its bookmark,
|
|
383
|
-
`ADMIN_ROOT` its root identity, `LINE` the line's id):
|
|
384
|
-
|
|
385
|
-
```sh
|
|
386
|
-
astrale query --class /:services.astrale.ai:class.DeploymentLine --limit 100 --json \
|
|
387
|
-
-i "$ADMIN_INSTANCE" --as "$ADMIN_ROOT"
|
|
388
|
-
astrale query "@$LINE" --edge /:services.astrale.ai:class.line_owned_by \
|
|
389
|
-
--direction outgoing --json -i "$ADMIN_INSTANCE" --as "$ADMIN_ROOT"
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
Pick the line whose `origin` and `environment` are the ones to transfer. The first query reads
|
|
393
|
-
one page: when its answer has a `page.next`, more lines exist, and the same query with
|
|
394
|
-
`--cursor <page.next>` reads the next page.
|
|
395
|
-
|
|
396
|
-
2. Write the Mutation. A held line moves in one atomic Mutation that deletes the current holder's
|
|
397
|
-
Edge and creates the new one; if the current holder is not the one named, nothing moves:
|
|
398
|
-
|
|
399
|
-
```json
|
|
400
|
-
{
|
|
401
|
-
"preconditions": [],
|
|
402
|
-
"operations": [
|
|
403
|
-
{
|
|
404
|
-
"op": "edge.delete",
|
|
405
|
-
"edge": {
|
|
406
|
-
"class": "services.astrale.ai:class.line_owned_by",
|
|
407
|
-
"source": "@<line-id>",
|
|
408
|
-
"target": "@<current-holder-id>"
|
|
409
|
-
}
|
|
410
|
-
},
|
|
411
|
-
{
|
|
412
|
-
"op": "edge.create",
|
|
413
|
-
"class": "services.astrale.ai:class.line_owned_by",
|
|
414
|
-
"source": "@<line-id>",
|
|
415
|
-
"target": "@<new-organization-id>",
|
|
416
|
-
"props": {}
|
|
417
|
-
}
|
|
418
|
-
]
|
|
419
|
-
}
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
A line without a holder, after its organisation was deleted, takes the `edge.create` operation
|
|
423
|
-
alone. The Kernel refuses a second holder, so that form never moves a held line.
|
|
424
|
-
|
|
425
|
-
3. Check the document offline, then apply it:
|
|
426
|
-
|
|
427
|
-
```sh
|
|
428
|
-
astrale mutate --file transfer.json --dry
|
|
429
|
-
astrale mutate --file transfer.json -i "$ADMIN_INSTANCE" --as "$ADMIN_ROOT"
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
A readable line stays readable: give it only to the platform organisation, since any other one is
|
|
433
|
-
refused on it. Changing `SERVICES_PLATFORM_OWNER` therefore means transferring every readable line
|
|
434
|
-
to the new platform organisation. Deleting an organisation that holds lines leaves them without a
|
|
435
|
-
holder (see [Model](#model)): their deployments keep serving, and nobody can list, rotate or deploy
|
|
436
|
-
them until they are transferred.
|
|
437
|
-
|
|
438
|
-
### Orphans
|
|
439
|
-
|
|
440
|
-
A hosted deployment belongs to its line, never to the instances that install it: deleting or
|
|
441
|
-
retiring an instance leaves it alone, and the orphan audit and reclaim of the
|
|
442
|
-
[operator README](./providers/cloudflare/operator/README.md) never select it. What can be left over:
|
|
443
|
-
|
|
444
|
-
| Situation | What holds | Then |
|
|
445
|
-
| --- | --- | --- |
|
|
446
|
-
| A deploy interrupted before activation | The graph keeps the deployment `deploying` under its lease, or `failed`, and `list` does not show it. An uploaded script serves nothing until its deploy publishes its record; one interrupted after that serves while the graph still says `deploying` or `failed`. Until another deploy takes it over, the interrupted deploy can still activate it, even after its lease expired. | Deploy the same release again. It resumes on the same label, takes over an expired lease, and adopts the uploaded script only if its tags name the same artifact, line, key and Admin Kernel, rewriting its secrets with the environment's current ones. |
|
|
447
|
-
| A line whose organisation was deleted | No organisation holds it; every deploy on it is refused `line-owned-elsewhere`. Its deployments keep serving, and nobody can rotate them. | Transfer the line. |
|
|
448
|
-
| A script under a label that is not the recorded one | `deploy` answers `deployment-conflict` and `setSecret` `service-unavailable` without `retryAfter`; nothing is written to it. | The operator compares its `astrale-artifact:`, `astrale-line:`, `astrale-key:` and `astrale-install:` tags with the deployment's record and key. |
|
|
449
|
-
|
|
450
|
-
## Shared public ingress
|
|
451
|
-
|
|
452
|
-
New Services use the environment's proxied wildcard DNS, wildcard Worker route and wildcard
|
|
453
|
-
certificate. Services observes the expected dispatcher route and probes a never-registered hostname
|
|
454
|
-
before uploading a Worker without an existing Custom Domain. It never creates a per-Service Custom
|
|
455
|
-
Domain or certificate. Missing shared ingress is an operator provisioning issue; it is not repaired
|
|
456
|
-
by consuming another certificate during a Service deployment.
|
|
457
|
-
|
|
458
|
-
After activating routing, deployment waits for public dispatcher visibility with a bounded budget.
|
|
459
|
-
This is separate from optional candidate application health checks. An uncertain activation retains
|
|
460
|
-
the Service record and can be retried with the same owner-scoped service key.
|
|
461
|
-
|
|
462
|
-
Existing exact Custom Domains and their immutable certificate locators are retained on ordinary
|
|
463
|
-
updates. Detaching old hostnames is a separate migration, coordinated with Service operations;
|
|
464
|
-
upgrading this Runtime does not perform that migration. A Service without an exact domain persists
|
|
465
|
-
`tenantDomainState: 'absent'`.
|
|
466
|
-
Deletion handles both placements and never removes shared ingress infrastructure.
|
|
467
|
-
|
|
468
|
-
Cloudflare callers that must traverse same-zone public Worker routes need
|
|
469
|
-
`global_fetch_strictly_public`. Audit existing callers before migrating their exact hostnames;
|
|
470
|
-
explicit runtime flags retain their authored meaning. A rollback after creating wildcard Services
|
|
471
|
-
must use a Runtime that understands explicit absent placement, not an older certificate-required
|
|
472
|
-
implementation.
|
|
473
|
-
|
|
474
|
-
### Migrating existing Services
|
|
475
|
-
|
|
476
|
-
The owner calls `CloudflareWorker.migrateIngress` with an `action` (`detach`, `complete`, or
|
|
477
|
-
`restore`) and a deterministic public health `probe`: a local GET `path`, expected successful
|
|
478
|
-
`status`, and the exact response body's SHA-256 in `bodySha256`. The Workflow acquires the existing
|
|
479
|
-
Service operation lease and verifies provider ownership, active dispatch, wildcard TLS and health
|
|
480
|
-
before detaching the exact Custom Domain. It does not redeploy the Worker or alter its routing KV,
|
|
481
|
-
secrets or schedules.
|
|
482
|
-
|
|
483
|
-
After `detach`, observe real traffic for at least 30 minutes before calling `complete`. Until
|
|
484
|
-
completion, the graph retains the previous domain and certificate locators with
|
|
485
|
-
`tenantDomainDetachedAt`; completion clears them after provider cleanup and another successful
|
|
486
|
-
health check. Cloudflare can automatically delete the generated certificate as soon as its Custom
|
|
487
|
-
Domain is detached. `restore` recreates the exact domain, waits for active TLS and records the actual
|
|
488
|
-
replacement certificate locator. A `pending` result requires a later retry; it is not completion.
|
|
489
|
-
|
|
490
|
-
The consumer runtimes are frozen ([`frozen-environments.json`](./frozen-environments.json)): the
|
|
491
|
-
original R1 runtime at `https://services.beta.astrale.ai`, which 1Pact instances pin and which
|
|
492
|
-
serves that issuer's discovery and keys, `https://services-v2.beta.astrale.ai`, which retains that
|
|
493
|
-
issuer and key, and production at `https://services.astrale.ai`. They keep serving the code they run
|
|
494
|
-
today and no Environment deploys them. Do not uninstall Services or replace a runtime underneath
|
|
495
|
-
installations that still use its Schema. An instance rollback must restore any detached domain
|
|
496
|
-
before reinstalling the original endpoint, and must first be qualified with real retained Service
|
|
497
|
-
data.
|
|
156
|
+
`delete` marks the Service `deleting`, removes `current:<name>` and `cron:<name>`, and retires every
|
|
157
|
+
deployment. It answers `deleting` with `retryAfterMs` until the last one is retired, then deletes
|
|
158
|
+
the Service with its deployments in one Mutation and answers `deleted`. The name is then free
|
|
159
|
+
again.
|
|
498
160
|
|
|
499
161
|
## Admin-hosted runtime
|
|
500
162
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
instance's Services already manage. Its plain vars name the router Worker
|
|
505
|
-
(`ASTRALE_ROUTER_WORKER`), the routing domain under which hosted deployments are addressed
|
|
506
|
-
(`SERVICES_DEPLOYMENT_HOST`, served by the Services dispatcher), the Admin instance's Kernel, the
|
|
507
|
-
only one it hosts deployments for (`SERVICES_DEPLOYMENT_KERNEL`, `https://admin.eu.beta.astrale.ai/api`),
|
|
508
|
-
and the registry Domain (`SERVICES_REGISTRY_ORIGIN`). These vars are the only declaration of
|
|
509
|
-
hosting: they also bind `SERVICE_DEPLOYMENTS`. Its secrets file must set
|
|
510
|
-
`SERVICES_SECRET_DIGEST_KEY`, the key of each hosted deployment's secret digest, and the dispatch
|
|
511
|
-
namespace and routing KV the runtime boots with. The runtime keys HMAC-SHA256 with the UTF-8 bytes
|
|
512
|
-
of the digest key as written, never base64-decoded, so the configuration refuses, before any
|
|
513
|
-
deployment effect, a file without them, a digest key shorter than 32 UTF-8 bytes, or a file that
|
|
514
|
-
sets a hosting var. `SERVICES_PLATFORM_OWNER` is not set yet: until it names the platform
|
|
515
|
-
organisation's Identity id on the Admin instance, every line is opaque and a readable deploy is
|
|
516
|
-
refused.
|
|
517
|
-
|
|
518
|
-
Two Environments deploy it:
|
|
519
|
-
|
|
520
|
-
- **`admin`**, the canonical one. Every `pnpm run deploy admin` makes one immutable deployment of
|
|
521
|
-
the release in the platform dispatch namespace (`astrale-platform`), on the readable line
|
|
522
|
-
`services-admin-6ze5tudscrzgbrbv`, at `https://<label>.platform.astrale.ai`. The deployment is
|
|
523
|
-
its own issuer, signs with a key generated for it, and binds its candidate namespace
|
|
524
|
-
(`SERVICE_CANDIDATES`) and the deployments namespace (`SERVICE_DEPLOYMENTS`) at its create-only
|
|
525
|
-
upload, both derived from `CLOUDFLARE_DISPATCH_NAMESPACE`. Nothing is ever replaced in place: a
|
|
526
|
-
new release is a new URL, which the Admin instance moves to by a consented install. Redeploying
|
|
527
|
-
the same release reuses its deployment and rewrites only that deployment's secrets in place. Its
|
|
528
|
-
secrets file is `.env.admin`.
|
|
529
|
-
- **`admin-beta`**, frozen (`providers/cloudflare/operator/provision/legacy/admin-beta.ts`). It is
|
|
530
|
-
a Worker of its own, `services-domain-admin-beta` at `https://services-admin.beta.astrale.ai`,
|
|
531
|
-
that retains the issuer `https://services.beta.astrale.ai` and its signing key, as services-v2
|
|
532
|
-
does, so the Admin instance installs it without an issuer change (OPS-3). The cost: a Worker built
|
|
533
|
-
from main holds the key that 1Pact's installations trust. Keep the key only in managed secret
|
|
534
|
-
storage and in this Worker's `ASTRALE_SIGNING_IDENTITY` secret. It changes only for a same-Schema
|
|
535
|
-
fix the Admin instance needs before it moves; everything else goes to `admin`. It is deleted from
|
|
536
|
-
this repository by D19, once the Admin instance runs on `admin` and OPS-6 retired the Worker.
|
|
537
|
-
|
|
538
|
-
### Set up the frozen admin-beta runtime (OPS-3)
|
|
539
|
-
|
|
540
|
-
The operator sets it up once, from a commit of `main` that carries the Services CT16 adoption
|
|
541
|
-
(`bindings.dispatchNamespaces` and `settings.durableOptions` in the deployment configuration, with
|
|
542
|
-
the tenant refusal) and pins an SDK release that carries the CT16 extension (R6a' or later). An
|
|
543
|
-
admin-beta built without that adoption refuses every deploy of an adapter-astrale that carries it
|
|
544
|
-
with `1003 FUNCTION_INPUT_INVALID`, and the move to `admin` over the deployments such a runtime wrote
|
|
545
|
-
is refused with `4001 DATA_MIGRATION_REQUIRED` (see [OPS-5](#move-the-admin-instance-to-admin-ops-5)).
|
|
546
|
-
|
|
547
|
-
1. Read what the Admin instance's Services are bound to, read-only:
|
|
548
|
-
`astrale introspect services.astrale.ai -i <admin-instance> --json`. Record `revision` and
|
|
549
|
-
`publication.identity.issuer`, and the frozen runtime whose `/.well-known/astrale/domain.json`
|
|
550
|
-
declares that revision: it is the rollback URL. Go on only if the issuer is
|
|
551
|
-
`https://services.beta.astrale.ai`; any other issuer makes step 6 an issuer change, which this
|
|
552
|
-
runtime does not take (use the [move to `admin`](#move-the-admin-instance-to-admin-ops-5)
|
|
553
|
-
instead). Check as well that the Admin instance's Kernel issuer is the
|
|
554
|
-
`SERVICES_DEPLOYMENT_KERNEL` var; otherwise every deploy answers `hosting-unavailable`. Before
|
|
555
|
-
the first readable deploy, read the platform organisation's Identity id there and set it as the
|
|
556
|
-
`SERVICES_PLATFORM_OWNER` var in a reviewed change.
|
|
557
|
-
2. Create `.env.admin-beta` (mode 600) from `.env.example` with the beta data-plane coordinates the
|
|
558
|
-
Admin instance's Services manage today (account, token, dispatch namespace, routing domain
|
|
559
|
-
`svc.eu.beta.astrale.ai`, schedule KV, dispatcher, zone, Admin target) and a new
|
|
560
|
-
`SERVICES_SECRET_DIGEST_KEY` that no other data plane uses, e.g. `openssl rand -base64 32` (32
|
|
561
|
-
random bytes, kept as their 44-character base64 text; the configuration refuses fewer than 32
|
|
562
|
-
UTF-8 bytes). It is generated once here, copied unchanged into `.env.admin` at OPS-5, and never
|
|
563
|
-
regenerated: the recorded secret digests of every hosted deployment depend on it.
|
|
564
|
-
3. Restore the retained `https://services.beta.astrale.ai` signing identity, the one services-v2 was
|
|
565
|
-
deployed with, from managed secret storage to `.astrale/services-beta-identity.json` (mode 600).
|
|
566
|
-
Never generate a key for this runtime: the frozen R1 Worker publishes the issuer's keys, so a new
|
|
567
|
-
key would never verify. Check that the issuer publishes the restored key:
|
|
568
|
-
|
|
569
|
-
```sh
|
|
570
|
-
node --input-type=module -e '
|
|
571
|
-
const { readFileSync } = await import("node:fs")
|
|
572
|
-
const { kid, x } = JSON.parse(readFileSync(".astrale/services-beta-identity.json", "utf8"))
|
|
573
|
-
const issuer = "https://services.beta.astrale.ai"
|
|
574
|
-
const { jwks_uri } = await (await fetch(`${issuer}/.well-known/openid-configuration`)).json()
|
|
575
|
-
const { keys } = await (await fetch(jwks_uri)).json()
|
|
576
|
-
if (!keys.some((key) => key.kid === kid && key.x === x)) throw new Error("key not published")
|
|
577
|
-
console.log(`issuer publishes kid ${kid}`)
|
|
578
|
-
'
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
4. Deploy once with `pnpm run deploy admin-beta`. It creates only the `services-domain-admin-beta`
|
|
582
|
-
Worker and its route; nothing is installed. Do not run `pnpm provider:bootstrap` or a
|
|
583
|
-
`provider:route-*` command for this runtime: on the beta data plane they redeploy the shared
|
|
584
|
-
dispatcher and DNS routing that serve 1Pact's tenants (see the
|
|
585
|
-
[operator README](./providers/cloudflare/operator/README.md)).
|
|
586
|
-
5. Rehearse step 6 first on a disposable instance whose Host runs the Admin Host's Kernel release and
|
|
587
|
-
whose Services are bound to the same issuer and revision as in step 1, with at least one Service.
|
|
588
|
-
The install must succeed and the Service must stay manageable (list, secrets, logs). Then install
|
|
589
|
-
the step 1 rollback URL there again: if the Kernel refuses it, step 6 is one-way, and that must be
|
|
590
|
-
recorded before step 6 runs.
|
|
591
|
-
6. Install on the Admin instance only:
|
|
592
|
-
`astrale domain install https://services-admin.beta.astrale.ai --allow-identity-override -i <admin-instance>`.
|
|
593
|
-
The only consent is the identity override, because the origin `services.astrale.ai` differs from
|
|
594
|
-
the serving host; the issuer does not change. On any refusal, stop: never uninstall Services on
|
|
595
|
-
the Admin instance to get around it, which would drop the Service records it holds. Never repoint
|
|
596
|
-
Admin's catalog entry for `services.astrale.ai` at this runtime: consumer flows, 1Pact's among
|
|
597
|
-
them, install Services by origin through that entry.
|
|
598
|
-
|
|
599
|
-
### Move the Admin instance to admin (OPS-5)
|
|
600
|
-
|
|
601
|
-
OPS-5 is the production operation that moves the Admin instance's Services off the frozen
|
|
602
|
-
`admin-beta` Worker onto a deployment of `admin`. Before acting, write a compatibility record and
|
|
603
|
-
know the rollback path. Afterwards, verify the Admin instance and add an entry to the operations
|
|
604
|
-
log. 1Pact's instances and every consumer instance are out of scope: they keep the Services they
|
|
605
|
-
pin, and nothing here touches them.
|
|
606
|
-
|
|
607
|
-
Preconditions:
|
|
608
|
-
|
|
609
|
-
1. The Admin instance installs `admin-beta` (OPS-3), built with the Services CT16 adoption; its
|
|
610
|
-
rollback URL is `https://services-admin.beta.astrale.ai`.
|
|
611
|
-
2. The Admin instance's Host runs a Kernel that lists installed releases and takes issuer consent
|
|
612
|
-
(astrale-os/kernel#1124, in a released Host image, OPS-4). An older Kernel refuses
|
|
613
|
-
`--allow-issuer-change` before any install, so the move waits.
|
|
614
|
-
3. The operator's `astrale` CLI is a release with install by URL (astrale-os/cli#582) and issuer
|
|
615
|
-
consent (astrale-os/cli#583).
|
|
616
|
-
4. This package pins an SDK release whose adapter-cloudflare deploys namespace mode with dispatch
|
|
617
|
-
namespace bindings and durable options (astrale-os/sdk#599, on the CT16 extension of
|
|
618
|
-
astrale-os/sdk#587), and the publishers' adapter-astrale passes `dispatchNamespaces: []`
|
|
619
|
-
(astrale-os/sdk#598).
|
|
620
|
-
5. The platform namespace is provisioned (`pnpm provider:platform-bootstrap`, OPS-1) and the deploy
|
|
621
|
-
token may manage scripts in it.
|
|
622
|
-
|
|
623
|
-
Steps:
|
|
624
|
-
|
|
625
|
-
0. **Schema.** A move, and a rollback, is a Schema upgrade: the Kernel checks the data the Admin
|
|
626
|
-
instance stores against the Schema of the URL it installs. Read the installed `revision`
|
|
627
|
-
(`astrale introspect services.astrale.ai -i <admin-instance> --json`, as recorded at OPS-3
|
|
628
|
-
step 1) and, after step 2, the deployment's `schema.revision` in `services-admin.json`. Equal
|
|
629
|
-
revisions are the same Schema. Different revisions make the move a Schema upgrade: list what
|
|
630
|
-
changed in `services/schema/` between the OPS-3 commit and this one, and let the rehearsal
|
|
631
|
-
(step 3) decide. When admin-beta and `admin` are built from commits whose Services Schema is
|
|
632
|
-
the same (both with the CT16 adoption), only the compiled form can differ.
|
|
633
|
-
1. **Secrets.** Create `.env.admin` (mode 600) as a copy of `.env.admin-beta`. The values must be
|
|
634
|
-
the same, the `SERVICES_SECRET_DIGEST_KEY` above all: the new deployment manages the same hosted
|
|
635
|
-
deployments, in the same dispatch namespace and routing KV, and their recorded secret digests
|
|
636
|
-
depend on that key. No signing identity file: the deploy generates the deployment's key.
|
|
637
|
-
2. **Deploy.** From a clean checkout of `main`, with `CLOUDFLARE_ACCOUNT_ID` and
|
|
638
|
-
`CLOUDFLARE_API_TOKEN` of the platform account set, run
|
|
639
|
-
`pnpm run deploy admin --json > services-admin.json` (no `--`: pnpm passes it on literally and
|
|
640
|
-
`astrale-domain` refuses it as an unknown flag). Check that
|
|
641
|
-
`<url>/.well-known/astrale/release.json` serves the printed release digest and that
|
|
642
|
-
`<url>/.well-known/astrale/deployment.json` serves its record.
|
|
643
|
-
3. **Rehearse** on a disposable instance whose Host runs the Admin Host's Kernel release, whose
|
|
644
|
-
Services pin `admin-beta`, with at least one Service and one hosted deployment: move it (step 4),
|
|
645
|
-
verify it (step 5), roll it back ([Rollback](#rollback-ops-5)) and move it forward again.
|
|
646
|
-
4. **Move** the Admin instance:
|
|
647
|
-
|
|
648
|
-
```sh
|
|
649
|
-
astrale domain install "$(jq -r .url services-admin.json)" \
|
|
650
|
-
--allow-issuer-change=services.astrale.ai -i <admin-instance> --json
|
|
651
|
-
```
|
|
652
|
-
|
|
653
|
-
- The frozen Worker's issuer and the deployment's are not on one line, so consent names the
|
|
654
|
-
origin: a bare `--allow-issuer-change` covers only a new deployment of the same line, and the
|
|
655
|
-
install is refused without the origin.
|
|
656
|
-
- The replaced issuer keeps working while its in-flight work drains. Do not pass
|
|
657
|
-
`--revoke-previous`.
|
|
658
|
-
- If the Kernel refuses an installed dependent of Services, install Services together with a
|
|
659
|
-
compatible deployment of that dependent in one grouped command
|
|
660
|
-
(`astrale domain install <services-url> <dependent-url> …`); a refusal installs nothing.
|
|
661
|
-
- If the Kernel answers `4001 DATA_MIGRATION_REQUIRED` ("Schema change requires an explicit
|
|
662
|
-
data migration."), data the Admin instance stores does not fit the deployment's Schema, and
|
|
663
|
-
`reason.details.requirements` names each Class (an admin-beta built without the CT16 adoption
|
|
664
|
-
names `CloudflareDeployment` and `deployment_on_line`). The whole operation is refused: the
|
|
665
|
-
pin does not move, nothing activates and no data changes, although the deployment did receive
|
|
666
|
-
the install request. The Admin instance stays on admin-beta, which keeps answering. Fix
|
|
667
|
-
forward with an `admin` release whose Schema admits the stored data; this repository has no
|
|
668
|
-
data migration. `SCHEMA_UPGRADE_INCOMPATIBLE` (4002) and `SCHEMA_DEPENDENTS_INCOMPATIBLE`
|
|
669
|
-
have the same remedy.
|
|
670
|
-
- On any other refusal, stop. Never uninstall Services on the Admin instance.
|
|
671
|
-
5. **Verify**, with reads only, then one hosted deploy:
|
|
672
|
-
- `astrale introspect services.astrale.ai -i <admin-instance> --json` names the new release,
|
|
673
|
-
pinned exactly, with the deployment URL as issuer;
|
|
674
|
-
- `astrale call /:services.astrale.ai:class.CloudflareDeployment:hosting -i <admin-instance> --json`
|
|
675
|
-
answers the hosting of `svc.eu.beta.astrale.ai`;
|
|
676
|
-
- the Services View lists the Admin instance's Services and their secrets names;
|
|
677
|
-
- a publisher's `astrale-domain deploy <environment>` of a Domain with the astrale adapter into
|
|
678
|
-
the Admin instance still makes, or reuses, its hosted deployment.
|
|
679
|
-
6. **Point bootstrap discovery at the deployment.** Admin's bootstrap discovery for `services`
|
|
680
|
-
moves to this deployment's `release.json` (A11), and Admin is redeployed (OPS-7). Never repoint
|
|
681
|
-
Admin's catalog entry for `services.astrale.ai`: consumer flows install Services by origin
|
|
682
|
-
through it.
|
|
683
|
-
|
|
684
|
-
A later release moves the same way: deploy `admin`, then install its URL, with the step 0 Schema
|
|
685
|
-
check. Its issuer is another deployment URL on the same line, which the bare `--allow-issuer-change`
|
|
686
|
-
covers by the consent rule (CT24); the host proof does not exercise that case, so pass
|
|
687
|
-
`--allow-issuer-change=services.astrale.ai` if the CLI asks for the origin.
|
|
688
|
-
|
|
689
|
-
#### Rollback (OPS-5)
|
|
690
|
-
|
|
691
|
-
Reinstall the frozen Worker with consent. It still serves, because only OPS-6 retires it:
|
|
163
|
+
Services are hosted for the Admin instance's Kernel alone: its graph is the registry of Service
|
|
164
|
+
names, while the deployments namespace and the routing KV are shared. A Domain Worker serves every
|
|
165
|
+
Kernel that installs it; any other Kernel finds hosting unavailable before anything is read.
|
|
692
166
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
167
|
+
The `admin` Environment sets the plain vars `ASTRALE_ROUTER_WORKER`, `SERVICES_DEPLOYMENT_HOST` (the
|
|
168
|
+
routing domain) and `SERVICES_DEPLOYMENT_KERNEL` (the Admin Kernel's issuer URL). These vars are the
|
|
169
|
+
only declaration of hosting and also bind `SERVICE_DEPLOYMENTS`. Its secrets file, `.env.admin`,
|
|
170
|
+
sets `SERVICES_SECRET_DIGEST_KEY` (at least 32 UTF-8 bytes), `CLOUDFLARE_DISPATCH_NAMESPACE` and
|
|
171
|
+
`CLOUDFLARE_SCHEDULE_KV_ID`; the configuration refuses a file without them or one that sets a
|
|
172
|
+
hosting var.
|
|
697
173
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
fixes forward, never a redeploy of admin-beta. If step 6 already repointed bootstrap discovery,
|
|
704
|
-
repoint it back too.
|
|
705
|
-
|
|
706
|
-
### Retire admin-beta (OPS-6)
|
|
707
|
-
|
|
708
|
-
Once the Admin instance runs on `admin` and no installation on any Host pins
|
|
709
|
-
`https://services-admin.beta.astrale.ai` (`astrale introspect services.astrale.ai` on each instance
|
|
710
|
-
that installs Services), retire the `services-domain-admin-beta` Worker and its route
|
|
711
|
-
`services-admin.beta.astrale.ai`, and delete its `ASTRALE_SIGNING_IDENTITY` secret, its copy of the
|
|
712
|
-
services.beta key. Keep the issuer host `services.beta.astrale.ai` and the key itself: the frozen
|
|
713
|
-
R1 and services-v2 Workers that 1Pact pins still sign with them. Then D19 deletes the frozen
|
|
714
|
-
`admin-beta` Environment from this repository.
|
|
715
|
-
|
|
716
|
-
### Before publishers deploy
|
|
717
|
-
|
|
718
|
-
The Admin instance's graph is the registry of lines. Before the first publisher deploys:
|
|
719
|
-
|
|
720
|
-
1. This runtime is installed on the Admin instance only, as above; that instance's Kernel issuer is
|
|
721
|
-
`SERVICES_DEPLOYMENT_KERNEL`; and `SERVICES_PLATFORM_OWNER` names the platform organisation before
|
|
722
|
-
its first readable deploy.
|
|
723
|
-
2. Each publisher has an organisation on the Admin instance: an Identity, a Shell User for a single
|
|
724
|
-
publisher or a Shell Group otherwise. The first deploy of an origin in an environment gives that
|
|
725
|
-
line to the organisation it names, and only a [transfer](#transfer-a-line) moves it.
|
|
726
|
-
3. Its members and CI identities are Identities of the Admin instance that act for it through
|
|
727
|
-
Kernel `extends_with`. A Shell Group's `assignMember` writes that Edge for each User or Group it
|
|
728
|
-
assigns. A caller that does not act for the organisation it names is refused before any write.
|
|
729
|
-
4. The Admin instance's graph is backed up with the registry it holds: only it records who holds
|
|
730
|
-
each line, each deployment's record and its secret digests. A restore loses what happened after
|
|
731
|
-
its backup: lines registered since are free for the next organisation that deploys, and secret
|
|
732
|
-
digests may name values older than the ones served.
|
|
733
|
-
|
|
734
|
-
## Plain background Service starter
|
|
735
|
-
|
|
736
|
-
The package also ships an isolated scaffolding binary (the root import remains Schema-only):
|
|
737
|
-
|
|
738
|
-
```sh
|
|
739
|
-
npm exec --package @astrale-domains/services -- create-astrale-service my-worker
|
|
740
|
-
cd my-worker
|
|
741
|
-
npm install
|
|
742
|
-
# Edit service.json and src/job.js for the existing business Domain.
|
|
743
|
-
npm run deploy -- --instance development --as developer
|
|
744
|
-
```
|
|
745
|
-
|
|
746
|
-
The generator creates local source only and refuses an existing destination. It does not install
|
|
747
|
-
dependencies, create/register accounts, grant permissions, set secrets, enable schedules or deploy.
|
|
748
|
-
The generated project owns a plain HTTP/cron entry, an ES256 account session, a job implementation
|
|
749
|
-
and build/deploy scripts. No Application, Schema, Publication, Domain signer or frontend is generated.
|
|
750
|
-
Deployment calls the public Services API through the installed Astrale CLI and requires an explicit
|
|
751
|
-
instance or URL. The project README explains account setup, secret custody, private cron and cleanup.
|
|
752
|
-
No tooling dependency is added to the Schema runtime or imported from the package root.
|
|
174
|
+
`pnpm run deploy admin` makes one immutable deployment of this release in the platform dispatch
|
|
175
|
+
namespace. Deploying never installs: install the deployed URL with
|
|
176
|
+
`astrale domain install <url> -i <instance>`. Operator tooling (platform namespace bootstrap,
|
|
177
|
+
dispatcher, routing, audits) is described in
|
|
178
|
+
[`providers/cloudflare/operator/README.md`](providers/cloudflare/operator/README.md).
|
|
753
179
|
|
|
754
180
|
## Domain development
|
|
755
181
|
|
|
756
|
-
Services
|
|
757
|
-
|
|
758
|
-
and the [real-Kernel suite](tests/kernel/README.md)). `pnpm run deploy admin` makes an immutable
|
|
759
|
-
deployment of this release on the `admin` line of the platform namespace. Deploying never
|
|
760
|
-
installs: installation is always a separate, explicit `astrale domain install <url> -i <instance>`
|
|
761
|
-
on the intended Kernel. There is no watch mode: `astrale-domain dev` serves only direct-mode
|
|
762
|
-
Environments.
|
|
763
|
-
|
|
764
|
-
To validate without deploying:
|
|
182
|
+
Services is developed against its tests: the fake Cloudflare account and the
|
|
183
|
+
[real-Kernel suite](tests/kernel/README.md).
|
|
765
184
|
|
|
766
185
|
```sh
|
|
767
186
|
pnpm typecheck
|
|
@@ -771,85 +190,3 @@ pnpm frontend:build
|
|
|
771
190
|
pnpm build
|
|
772
191
|
pnpm package
|
|
773
192
|
```
|
|
774
|
-
|
|
775
|
-
The deployed module named by `entry` must be present in `modules`. Modules and assets carry
|
|
776
|
-
`content: { encoding: 'identity' | 'gzip', decodedSize, contentBase64 }`; large encoded files use
|
|
777
|
-
`contentBase64Chunks` instead, with each string bounded to 512 KiB. `decodedSize` is the exact original
|
|
778
|
-
byte length. Compression changes transport only: provider uploads and deployment digests use the
|
|
779
|
-
reconstructed bytes. Services validates complete gzip streams (including their checksum), exact sizes,
|
|
780
|
-
and canonical Base64 before any graph or provider operation. Individual decoded files remain bounded
|
|
781
|
-
to 7.5 MiB per module and 12 MiB per asset; all decoded deployment material shares a 32 MiB budget.
|
|
782
|
-
|
|
783
|
-
The previous top-level `contentBase64` and `contentBase64Chunks` fields remain accepted and are
|
|
784
|
-
explicitly deprecated. Roll out Services and install its new schema on consumer instances before
|
|
785
|
-
publishing the compressed SDK adapter. Old SDK clients continue working against the new Services;
|
|
786
|
-
new SDK requests are refused by an old schema without provider effects. The adapter never retries a
|
|
787
|
-
deployment using a different wire representation after an uncertain outcome.
|
|
788
|
-
|
|
789
|
-
Module names are unique and secrets use environment-variable identifiers. Plain and revisioned artifacts are
|
|
790
|
-
distinct modes and cannot replace each other at one Service record. Five-field cron expressions are
|
|
791
|
-
supported when `CLOUDFLARE_SCHEDULE_KV_ID` is configured.
|
|
792
|
-
|
|
793
|
-
Plain deployments that omit `compatibilityFlags` use `nodejs_compat`,
|
|
794
|
-
`nodejs_compat_populate_process_env`, and `global_fetch_strictly_public`. Public `fetch()` calls
|
|
795
|
-
therefore traverse Cloudflare's public routes, including same-zone Astrale endpoints, instead of
|
|
796
|
-
bypassing those routes for the zone's origin. This changes routing, not Kernel authorization.
|
|
797
|
-
An explicitly supplied flags array replaces these defaults, including `[]`; low-level callers
|
|
798
|
-
own that complete runtime configuration. Existing Workers change only on redeployment.
|
|
799
|
-
|
|
800
|
-
Revisioned deployments require explicit runtime configuration and upload it unchanged to both the
|
|
801
|
-
candidate and the serving Worker. Those exact flags participate in the deployment identity;
|
|
802
|
-
Services must not inject defaults after its computation. The Cloudflare adapter already includes
|
|
803
|
-
public-fetch routing in the generated configuration before building the artifact and its digest.
|
|
804
|
-
|
|
805
|
-
SDK-authored Domains should deploy through `@astrale-os/adapter-astrale`, which projects the
|
|
806
|
-
admitted Cloudflare artifact into the revisioned Services request and verifies the live Publication.
|
|
807
|
-
Installation is never part of a deploy: install the deployed URL with
|
|
808
|
-
`astrale domain install <url> -i <instance>` as its own step. Services never interprets or caches
|
|
809
|
-
a Domain definition, Publication, Schema revision, issuer, or callable inventory. Manual callers likewise
|
|
810
|
-
deploy provider compute first and then install with the same command.
|
|
811
|
-
|
|
812
|
-
Revisioned deployments may request `readiness: { paths: ['/health'] }`. Services executes these
|
|
813
|
-
bounded local GET paths through its private candidate dispatch binding and requires HTTP 200 before
|
|
814
|
-
public promotion. It never follows redirects or reads the response body as Application metadata.
|
|
815
|
-
For an existing Service, required checks need every retained secret supplied in the candidate
|
|
816
|
-
configuration. Missing values fail before staging or promotion. A caller may explicitly request
|
|
817
|
-
`readiness: { paths: ['/health'], required: false }` to preserve updates with Worker-only secrets:
|
|
818
|
-
Services then retains those secrets on the serving Worker and returns
|
|
819
|
-
`candidateReadiness: 'unavailable-retained-secrets'` without invoking the incomplete candidate.
|
|
820
|
-
An executed, successful check returns `candidateReadiness: 'passed'`. Failed checks still refuse
|
|
821
|
-
promotion even when `required` is false; this is not a fallback for broken candidates.
|
|
822
|
-
Checks share a 30-second budget, shortened by invocation cancellation/deadline. A failed check leaves
|
|
823
|
-
the public Worker unchanged and removes the newly owned candidate. Unchecked provider-only requests
|
|
824
|
-
retain their existing contract.
|
|
825
|
-
|
|
826
|
-
The updated Astrale adapter requests optional `/health` preflight automatically: SDK Workers
|
|
827
|
-
initialize their Runtime before serving it. No Project option is needed. The adapter reports
|
|
828
|
-
unavailable preflight explicitly and always verifies the public Publication, Bundle, and identity
|
|
829
|
-
after deployment. Private preflight alone never proves complete product correctness.
|
|
830
|
-
Provision and deploy the updated Services Runtime before using this adapter cohort. Older strict
|
|
831
|
-
Services schemas reject the new field; the adapter must not retry without the safety check.
|
|
832
|
-
|
|
833
|
-
An uncertain provider promotion is adopted only after observing its exact, non-secret attempt
|
|
834
|
-
receipt alongside the artifact tag. Matching code alone is insufficient: Cloudflare's script ETag
|
|
835
|
-
can remain unchanged when only secrets change. Services-owned operation leases serialize callers;
|
|
836
|
-
operators making direct provider changes must coordinate with those operations. The code ETag is
|
|
837
|
-
not a lock for concurrent configuration changes.
|
|
838
|
-
|
|
839
|
-
If a Kernel interruption prevents recording a deployment outcome, the Service can remain busy
|
|
840
|
-
until its retained operation lease expires (currently 30 minutes from acquisition or renewal).
|
|
841
|
-
Retry the same deployment after the expiry reported in the rejection; it reuses the Service and
|
|
842
|
-
provider identity. Do not clear the lease or create another Service to bypass an uncertain outcome.
|
|
843
|
-
An operation without a recorded expiry needs operator inspection, not a guessed retry deadline.
|
|
844
|
-
|
|
845
|
-
A definitively rejected update restores a previously ready Service only when the Provider observes
|
|
846
|
-
the same serving script digest and tenant routing coordinates. The restoration is atomic and fenced
|
|
847
|
-
by the current operation receipt; it restores the previous name and description and clears the
|
|
848
|
-
rejected attempt. The deployment still returns a rejection, but the retained Service remains
|
|
849
|
-
manageable. Failed creations, uncertain outcomes, mismatched observations, and unresolved previous
|
|
850
|
-
attempts remain failed rather than claiming that the old deployment is ready. This verification adds
|
|
851
|
-
no provider request to a successful deployment.
|
|
852
|
-
|
|
853
|
-
For the complete live owner-management and non-owner-denial journey, see
|
|
854
|
-
[remote acceptance](tests/remote/README.md). Run it against an isolated instance;
|
|
855
|
-
ordinary unit tests do not prove callable admission or real provider telemetry.
|