@astrale-domains/services 0.4.5 → 0.4.7

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 (126) hide show
  1. package/README.md +278 -12
  2. package/dist/schema/index.d.ts +2 -0
  3. package/dist/schema/index.js +1 -0
  4. package/dist/schema/modules/deployment/classes/cloudflare-deployment.d.ts +463 -0
  5. package/dist/schema/modules/deployment/classes/cloudflare-deployment.js +30 -0
  6. package/dist/schema/modules/deployment/classes/deployment-line.d.ts +23 -0
  7. package/dist/schema/modules/deployment/classes/deployment-line.js +24 -0
  8. package/dist/schema/modules/deployment/classes/deployment-on-line.d.ts +17 -0
  9. package/dist/schema/modules/deployment/classes/deployment-on-line.js +11 -0
  10. package/dist/schema/modules/deployment/classes/deployment.d.ts +189 -0
  11. package/dist/schema/modules/deployment/classes/deployment.js +56 -0
  12. package/dist/schema/modules/deployment/classes/index.d.ts +5 -0
  13. package/dist/schema/modules/deployment/classes/index.js +5 -0
  14. package/dist/schema/modules/deployment/classes/line-owned-by.d.ts +26 -0
  15. package/dist/schema/modules/deployment/classes/line-owned-by.js +19 -0
  16. package/dist/schema/modules/deployment/errors/deployment-hosting-unavailable.d.ts +7 -0
  17. package/dist/schema/modules/deployment/errors/deployment-hosting-unavailable.js +8 -0
  18. package/dist/schema/modules/deployment/errors/deployment-label-not-found.d.ts +12 -0
  19. package/dist/schema/modules/deployment/errors/deployment-label-not-found.js +11 -0
  20. package/dist/schema/modules/deployment/errors/deployment-not-found.d.ts +10 -0
  21. package/dist/schema/modules/deployment/errors/deployment-not-found.js +11 -0
  22. package/dist/schema/modules/deployment/errors/deployment-registry-only.d.ts +11 -0
  23. package/dist/schema/modules/deployment/errors/deployment-registry-only.js +12 -0
  24. package/dist/schema/modules/deployment/errors/index.d.ts +4 -0
  25. package/dist/schema/modules/deployment/errors/index.js +4 -0
  26. package/dist/schema/modules/deployment/functions/deploy-cloudflare-deployment.d.ts +170 -0
  27. package/dist/schema/modules/deployment/functions/deploy-cloudflare-deployment.js +14 -0
  28. package/dist/schema/modules/deployment/functions/describe-deployment-hosting.d.ts +8 -0
  29. package/dist/schema/modules/deployment/functions/describe-deployment-hosting.js +11 -0
  30. package/dist/schema/modules/deployment/functions/expire-deployment-previews.d.ts +16 -0
  31. package/dist/schema/modules/deployment/functions/expire-deployment-previews.js +14 -0
  32. package/dist/schema/modules/deployment/functions/index.d.ts +11 -0
  33. package/dist/schema/modules/deployment/functions/index.js +11 -0
  34. package/dist/schema/modules/deployment/functions/list-cloudflare-deployment-secret-names.d.ts +5 -0
  35. package/dist/schema/modules/deployment/functions/list-cloudflare-deployment-secret-names.js +4 -0
  36. package/dist/schema/modules/deployment/functions/list-cloudflare-deployments.d.ts +58 -0
  37. package/dist/schema/modules/deployment/functions/list-cloudflare-deployments.js +10 -0
  38. package/dist/schema/modules/deployment/functions/list-deployment-secret-names.d.ts +4 -0
  39. package/dist/schema/modules/deployment/functions/list-deployment-secret-names.js +6 -0
  40. package/dist/schema/modules/deployment/functions/read-deployment-last-calls.d.ts +10 -0
  41. package/dist/schema/modules/deployment/functions/read-deployment-last-calls.js +13 -0
  42. package/dist/schema/modules/deployment/functions/retain-deployment.d.ts +12 -0
  43. package/dist/schema/modules/deployment/functions/retain-deployment.js +13 -0
  44. package/dist/schema/modules/deployment/functions/retire-deployment.d.ts +23 -0
  45. package/dist/schema/modules/deployment/functions/retire-deployment.js +17 -0
  46. package/dist/schema/modules/deployment/functions/set-cloudflare-deployment-secret.d.ts +18 -0
  47. package/dist/schema/modules/deployment/functions/set-cloudflare-deployment-secret.js +7 -0
  48. package/dist/schema/modules/deployment/functions/set-deployment-secret.d.ts +14 -0
  49. package/dist/schema/modules/deployment/functions/set-deployment-secret.js +3 -0
  50. package/dist/schema/modules/deployment/index.d.ts +8 -0
  51. package/dist/schema/modules/deployment/index.js +6 -0
  52. package/dist/schema/modules/deployment/policies/act-for-organization.d.ts +6 -0
  53. package/dist/schema/modules/deployment/policies/act-for-organization.js +10 -0
  54. package/dist/schema/modules/deployment/policies/index.d.ts +5 -0
  55. package/dist/schema/modules/deployment/policies/index.js +5 -0
  56. package/dist/schema/modules/deployment/policies/own-deployment.d.ts +2 -0
  57. package/dist/schema/modules/deployment/policies/own-deployment.js +12 -0
  58. package/dist/schema/modules/deployment/policies/own-line.d.ts +5 -0
  59. package/dist/schema/modules/deployment/policies/own-line.js +10 -0
  60. package/dist/schema/modules/deployment/policies/traverse-owned-line-deployment.d.ts +2 -0
  61. package/dist/schema/modules/deployment/policies/traverse-owned-line-deployment.js +7 -0
  62. package/dist/schema/modules/deployment/policies/traverse-owned-line.d.ts +2 -0
  63. package/dist/schema/modules/deployment/policies/traverse-owned-line.js +7 -0
  64. package/dist/schema/modules/deployment/states/deployment-lifecycle.d.ts +30 -0
  65. package/dist/schema/modules/deployment/states/deployment-lifecycle.js +19 -0
  66. package/dist/schema/modules/deployment/states/index.d.ts +2 -0
  67. package/dist/schema/modules/deployment/states/index.js +1 -0
  68. package/dist/schema/modules/deployment/types/deployment-hosting.d.ts +94 -0
  69. package/dist/schema/modules/deployment/types/deployment-hosting.js +19 -0
  70. package/dist/schema/modules/deployment/types/deployment-record.d.ts +35 -0
  71. package/dist/schema/modules/deployment/types/deployment-record.js +31 -0
  72. package/dist/schema/modules/deployment/types/deployment-request.d.ts +161 -0
  73. package/dist/schema/modules/deployment/types/deployment-request.js +40 -0
  74. package/dist/schema/modules/deployment/types/deployment-response.d.ts +109 -0
  75. package/dist/schema/modules/deployment/types/deployment-response.js +36 -0
  76. package/dist/schema/modules/deployment/types/deployment-retention.d.ts +49 -0
  77. package/dist/schema/modules/deployment/types/deployment-retention.js +45 -0
  78. package/dist/schema/modules/deployment/types/deployment-retire.d.ts +30 -0
  79. package/dist/schema/modules/deployment/types/deployment-retire.js +18 -0
  80. package/dist/schema/modules/deployment/types/deployment-secret-digest.d.ts +17 -0
  81. package/dist/schema/modules/deployment/types/deployment-secret-digest.js +14 -0
  82. package/dist/schema/modules/deployment/types/deployment-secret.d.ts +59 -0
  83. package/dist/schema/modules/deployment/types/deployment-secret.js +52 -0
  84. package/dist/schema/modules/deployment/types/deployment-summary.d.ts +165 -0
  85. package/dist/schema/modules/deployment/types/deployment-summary.js +38 -0
  86. package/dist/schema/modules/deployment/types/frozen-configuration.d.ts +52 -0
  87. package/dist/schema/modules/deployment/types/frozen-configuration.js +55 -0
  88. package/dist/schema/modules/deployment/types/index.d.ts +20 -0
  89. package/dist/schema/modules/deployment/types/index.js +10 -0
  90. package/dist/schema/modules/service/classes/cloudflare-worker.d.ts +60 -24
  91. package/dist/schema/modules/service/classes/service.d.ts +7 -7
  92. package/dist/schema/modules/service/functions/delete-service-secret.d.ts +1 -1
  93. package/dist/schema/modules/service/functions/delete-service.d.ts +1 -1
  94. package/dist/schema/modules/service/functions/delete-worker-secret.d.ts +1 -1
  95. package/dist/schema/modules/service/functions/delete-worker.d.ts +1 -1
  96. package/dist/schema/modules/service/functions/deploy-cloudflare-worker.d.ts +37 -1
  97. package/dist/schema/modules/service/functions/list-service-logs.d.ts +1 -1
  98. package/dist/schema/modules/service/functions/list-service-schedules.d.ts +1 -1
  99. package/dist/schema/modules/service/functions/list-service-secret-names.d.ts +1 -1
  100. package/dist/schema/modules/service/functions/list-worker-logs.d.ts +1 -1
  101. package/dist/schema/modules/service/functions/list-worker-schedules.d.ts +1 -1
  102. package/dist/schema/modules/service/functions/list-worker-secret-names.d.ts +1 -1
  103. package/dist/schema/modules/service/functions/migrate-worker-ingress.d.ts +1 -1
  104. package/dist/schema/modules/service/functions/replace-service-schedules.d.ts +1 -1
  105. package/dist/schema/modules/service/functions/replace-worker-schedules.d.ts +1 -1
  106. package/dist/schema/modules/service/functions/set-service-secret.d.ts +1 -1
  107. package/dist/schema/modules/service/functions/set-worker-secret.d.ts +3 -3
  108. package/dist/schema/modules/service/index.d.ts +2 -2
  109. package/dist/schema/modules/service/index.js +1 -1
  110. package/dist/schema/modules/service/types/content/index.d.ts +1 -0
  111. package/dist/schema/modules/service/types/content/index.js +1 -0
  112. package/dist/schema/modules/service/types/content/limits.d.ts +7 -0
  113. package/dist/schema/modules/service/types/content/limits.js +7 -0
  114. package/dist/schema/modules/service/types/deploy-cloudflare-worker.d.ts +110 -0
  115. package/dist/schema/modules/service/types/index.d.ts +2 -1
  116. package/dist/schema/modules/service/types/index.js +1 -1
  117. package/dist/schema/modules/service/types/legacy/service-deployment-content.js +22 -0
  118. package/dist/schema/modules/service/types/service-deployment-artifact-module.d.ts +23 -0
  119. package/dist/schema/modules/service/types/service-deployment-artifact-module.js +5 -3
  120. package/dist/schema/modules/service/types/service-deployment-assets.d.ts +36 -0
  121. package/dist/schema/modules/service/types/service-deployment-assets.js +5 -3
  122. package/dist/schema/modules/service/types/service-deployment-content.d.ts +18 -11
  123. package/dist/schema/modules/service/types/service-deployment-content.js +17 -8
  124. package/dist/schema/schema.d.ts +740 -23
  125. package/dist/schema/schema.js +11 -0
  126. package/package.json +15 -11
package/README.md CHANGED
@@ -11,6 +11,26 @@ protocols behind neutral SDK Integrations and stores only admitted lifecycle evi
11
11
  - Owners may be Shell users, local Kernel roots, or application/CI identities. Services requires only
12
12
  the Kernel Schema; a Shell installation is not required to deploy. Ownership
13
13
  remains exact-subject scoped, and existing Shell-user ownership edges remain valid.
14
+ - a `DeploymentLine` is one `(origin, environment)`, named by its SDK fingerprint. `line_owned_by`
15
+ gives it to at most one organisation Identity, first come. Members and CI identities read the
16
+ line and its deployments through Kernel `extends_with` (`OwnLine`, `OwnDeployment`). A line is
17
+ never reassigned. Deleting its deployments, the organisation's Services or the organisation
18
+ itself leaves the line refused to every other organisation (`line-owned-elsewhere`). Only an
19
+ operator transfer moves the holder Edge.
20
+ - the line Queries, `admitDeploymentLine` and the deployment Mutations run under the Services
21
+ Domain credential and trust the organisation they are given. Under a caller's credential the
22
+ Policies hide a foreign line, which would read as a free one. Their caller must first prove that
23
+ the requesting identity is that organisation or acts for it: `Identity.can` on `OwnLine` for a
24
+ held line, the identity itself or its `extends_with` for a registration.
25
+ - each `Deployment` (`CloudflareDeployment`) sits on one line through `deployment_on_line` and is
26
+ visible to whoever holds that line. A deployment is recorded only while its organisation holds
27
+ the line. Its label must name the line, and its record must name the line's origin and
28
+ environment. [Real-Kernel proof](tests/kernel/README.md).
29
+ - an operator deleting an organisation that holds lines deletes its `line_owned_by` Edges in the
30
+ same Mutation: the Kernel deletes a Node only together with every incident Edge. Its lines stay
31
+ registered with no holder and are refused to every organisation. A transfer is a root
32
+ `createEdge` of `line_owned_by` from the line to the new organisation; a line has one holder at
33
+ most.
14
34
  - secret values stay write-only at Cloudflare and never enter Astrale mutations or graph state.
15
35
  - schedules remain provider state; only the owner may read or replace them.
16
36
  - logs are remote evidence admitted by the Cloudflare Provider before a Workflow returns them.
@@ -22,6 +42,157 @@ protocols behind neutral SDK Integrations and stores only admitted lifecycle evi
22
42
  - the provider supplies `WORKER_URL` as the Service's canonical HTTPS URL to both candidate and
23
43
  public Workers. It is reserved provider metadata, not a caller variable or secret. Applications
24
44
  therefore keep one issuer even when requests arrive over HTTP or through a dispatcher alias.
45
+ - `ASTRALE_BUILD_DIGEST` is reserved the same way: a Worker's build digest is bound by its host,
46
+ never by its author, so Services refuses it as a caller variable or secret on every deployment
47
+ path and in secret management.
48
+
49
+ ## Immutable deployments
50
+
51
+ The Admin-hosted Services runtime hosts publishers' releases as immutable deployments, one per
52
+ label, on the publisher's line. Three static `CloudflareDeployment` Methods serve them, and two
53
+ instance Methods rotate one deployment's secrets; any authenticated identity of the Admin instance
54
+ may call them. A Domain Worker serves every Kernel that
55
+ installs it, but deployments are hosted for the Admin instance's Kernel alone
56
+ (`SERVICES_DEPLOYMENT_KERNEL`): its graph is the line registry, while the deployments namespace and
57
+ the routing KV are shared. Any other Kernel finds hosting unavailable before anything is read.
58
+
59
+ - `deploy` takes a `DeploymentRequestV1`: the release's origin, environment, addressing, label,
60
+ build digest, frozen configuration, record fields, Worker bundle and the environment's current
61
+ secrets. It is a non-durable Action, never a Workflow, so no Execution, step result or journal
62
+ entry holds its input. Before any read it recomputes the build digest and the label, admits the
63
+ record with the SDK, and refuses a service binding, a router other than the logical
64
+ `admin-router`, a reserved binding name, or secrets other than exactly the declared ones. Only
65
+ the platform organisation (`SERVICES_PLATFORM_OWNER`) deploys on readable lines.
66
+ - `deploy` then observes the line under the Services Domain credential and proves, on the caller's
67
+ own session, that the caller acts for the organisation it names (`OwnLine` on a held line,
68
+ `ActForOrganization` for a new one) before any Mutation. A new label is created under a graph
69
+ lease. The provider uploads it create-only, with `ASTRALE_BUILD_DIGEST`, `ASTRALE_SETTINGS` (router
70
+ binding `ROUTER`), a signing key Services generates for this deployment alone, and the request's
71
+ secrets. It adopts a script only when resuming a deployment the graph already held from an earlier
72
+ attempt, and only when the script's tags name the same artifact, line and key and this Kernel's
73
+ installation; any other script under the label is another deployment's, refused
74
+ `deployment-conflict`, and its secrets are never written. It checks that the uploaded version
75
+ itself serves before it activates the label through the routing KV `record:<label>`. The graph then records the deployment active with its key id, script etag and
76
+ a keyed HMAC-SHA256 digest of its secrets; no value, private key or version id is stored.
77
+ - the same release again is the same label: `deploy` answers `reused`. Its secrets are the
78
+ environment's current values: when they match the recorded digests it answers `unchanged` and
79
+ writes nothing; otherwise it rewrites the changed values in place on that deployment alone and
80
+ answers `updated` (same script, URL, issuer and key, no new release). Every other deployment of
81
+ the line keeps its own secrets.
82
+ - `setSecret({ name, value })` and `secrets({})` are instance Methods of one `CloudflareDeployment`.
83
+ `setSecret` rewrites one secret in place on that deployment only, and only a name its frozen
84
+ configuration declares (`secret-undeclared` otherwise); there is no `deleteSecret`, since a bound
85
+ name is part of the release, and the signing key never rotates. `secrets` lists the names its
86
+ script binds, never a value. Like `deploy`, any authenticated identity may call them, because a
87
+ publisher holds no `can_use` on Services' Functions and an `authorized` Method requires it of
88
+ every non-Root caller: before anything is read, the Kernel answers `OwnDeployment` for the caller
89
+ on its own session, and any other caller finds no such deployment (`DEPLOYMENT_NOT_FOUND`). If the
90
+ Admin instance ever grants publishers `can_use` on these Methods, they become `authorized` with
91
+ the `OwnDeployment` Policy instead. Both are non-durable Actions, so no Execution, step result or
92
+ journal entry holds a value.
93
+ - a `setSecret` refusal is `{ kind: 'rejected', reason, message, retryAfter? }`.
94
+ `service-unavailable` with `retryAfter` (the deployment is being created, another operation
95
+ holds it, or the provider failed) is retried then; without `retryAfter` the deployment cannot be
96
+ rotated in place (its creation failed, it is retiring or retired, or the script under its label is
97
+ not the recorded one, which is the operator's to inspect), and the message says what can.
98
+ - an in-place rewrite, by `deploy` or `setSecret`, runs under the deployment's graph lease. It
99
+ compares the values with the keyed digests and writes only the changed ones; it first forgets the
100
+ digests of the names it writes and records the new digests only once every write succeeded, so a
101
+ recorded digest always names the value last written. A provider failure answers
102
+ `service-unavailable` with `retryAfter`, and a rerun, with any values, converges. The script is
103
+ first checked to be the recorded one (code hash, artifact tag, key tag, installation tag); a
104
+ rewrite never touches another script. Workers for Platforms keeps the script's name, code hash,
105
+ tags and bindings across a secret write and mints a new provider version, which serves the new
106
+ value a few seconds later (OPS-2 X1): propagation is eventually consistent, and nothing records a
107
+ version id.
108
+ - `hosting` returns the routing domain and how the caller's lines read (readable for the platform
109
+ organisation, opaque otherwise). `list` returns the deployments of the lines the caller holds. It
110
+ reads the held lines under the Services Domain credential, narrowed in the Query to the origin
111
+ and environment asked for, and keeps the lines a batched `Identity.can(OwnLine)` grants the
112
+ caller: a caller-mode read sees none of the lines a caller holds through `extends_with`
113
+ ([kernel#1138](https://github.com/astrale-os/kernel/issues/1138), the removal trigger of this
114
+ filter). One listing reads at most 16,384 matching held lines and fails beyond, so a listing
115
+ without an origin is bounded by the platform's held lines. A deployment whose stored record or
116
+ configuration this Services SDK no longer admits, for instance after a stricter decoder, is
117
+ still listed, from the facts Services owns, with its record exactly as stored (typed as JSON for
118
+ that row): the listing never fails on it, and the consumer admits the record itself. Deploy,
119
+ retain, retire, `setSecret`, `secrets` and `expirePreviews` fail closed on such a deployment
120
+ with an undeclared internal error (5000) and write nothing; `lastCalls` answers from its facts.
121
+ - deleting a Service, or an instance and its Services, never addresses a publisher's deployment.
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 characters, kept for the life of the environment: 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-beta` names `admin.astrale.ai` and keeps expiry a plan.
193
+ Reading last calls needs the API token's Account Analytics Read permission. `pnpm
194
+ provider:deployment-audit` lists the deployments namespace read-only
195
+ ([operator](providers/cloudflare/operator/README.md)).
25
196
 
26
197
  The View, CLI, and agent consumers read the named Service Queries directly; there is intentionally
27
198
  no list Function wrapping `listServicesQuery`. Deployment, secrets, schedules, logs, and deletion
@@ -75,13 +246,96 @@ health check. Cloudflare can automatically delete the generated certificate as s
75
246
  Domain is detached. `restore` recreates the exact domain, waits for active TLS and records the actual
76
247
  replacement certificate locator. A `pending` result requires a later retry; it is not completion.
77
248
 
78
- The beta replacement serves at `https://services-v2.beta.astrale.ai` with the retained
79
- `https://services.beta.astrale.ai` issuer and signing key. Keep the original runtime and identity
80
- discovery available for existing installations. Adopt the replacement's public URL per instance
81
- before invoking its additive migration API; do not uninstall Services or replace the shared R1
82
- runtime underneath installations that still use its Schema. An instance rollback must restore any
83
- detached domain before reinstalling the original endpoint, and must first be qualified with real
84
- retained Service data.
249
+ The consumer runtimes are frozen ([`frozen-environments.json`](./frozen-environments.json)): the
250
+ original R1 runtime at `https://services.beta.astrale.ai`, which 1Pact instances pin and which
251
+ serves that issuer's discovery and keys, `https://services-v2.beta.astrale.ai`, which retains that
252
+ issuer and key, and production at `https://services.astrale.ai`. They keep serving the code they run
253
+ today and no Environment deploys them. Do not uninstall Services or replace a runtime underneath
254
+ installations that still use its Schema. An instance rollback must restore any detached domain
255
+ before reinstalling the original endpoint, and must first be qualified with real retained Service
256
+ data.
257
+
258
+ ## Admin-hosted runtime
259
+
260
+ The `admin-beta` Environment is the Services runtime of the Admin instance, the one that hosts
261
+ publishers' deployments outside the instances that install them. It is a Worker of its own,
262
+ `services-domain-admin-beta` at `https://services-admin.beta.astrale.ai`, on the beta data plane the
263
+ Admin instance's Services already manage. Its plain vars name the router Worker
264
+ (`ASTRALE_ROUTER_WORKER`), the routing domain under which deployments are addressed
265
+ (`SERVICES_DEPLOYMENT_HOST`, served by the Services dispatcher), the Admin instance's Kernel, the
266
+ only one it hosts deployments for (`SERVICES_DEPLOYMENT_KERNEL`, `https://admin.eu.beta.astrale.ai/api`),
267
+ and the registry Domain (`SERVICES_REGISTRY_ORIGIN`). These vars are the only declaration of
268
+ hosting: they also bind `SERVICE_DEPLOYMENTS`. `.env.admin-beta` must set
269
+ `SERVICES_SECRET_DIGEST_KEY`, the key of each hosted deployment's secret digest, and the dispatch
270
+ namespace and routing KV the runtime boots with. The runtime keys HMAC-SHA256 with the UTF-8 bytes
271
+ of the digest key as written, never base64-decoded, so the configuration refuses, before any
272
+ deployment effect, a file without them, a digest key shorter than 32 UTF-8 bytes, or a file that
273
+ sets a hosting var. `SERVICES_PLATFORM_OWNER` is not set yet: until it names the platform
274
+ organisation's Identity id on the Admin instance, every line is opaque and a readable deploy is
275
+ refused.
276
+
277
+ It retains the issuer `https://services.beta.astrale.ai` and its signing key, as services-v2 does.
278
+ The Admin instance's Services are bound to that issuer, and the Kernel refuses an issuer change when
279
+ it replaces an installed Domain (`issuer-changed`); neither the Admin Host's Kernel nor the CLI
280
+ offers a consented issuer change yet. The cost: a Worker built from main holds the key that 1Pact's
281
+ installations trust. Keep the key only in managed secret storage and in this Worker's
282
+ `ASTRALE_SIGNING_IDENTITY` secret. The Admin instance moves to an issuer of its own when the
283
+ canonical Admin-hosted runtime replaces this one through a consented issuer change; this Worker and
284
+ its copy of the key are retired after that reinstall.
285
+
286
+ Only the Admin instance installs this runtime; consumer instances keep the Services they pin.
287
+ The operator sets it up once:
288
+
289
+ 1. Read what the Admin instance's Services are bound to, read-only:
290
+ `astrale introspect services.astrale.ai -i <admin-instance> --json`. Record `revision` and
291
+ `publication.identity.issuer`, and the frozen runtime whose `/.well-known/astrale/domain.json`
292
+ declares that revision: it is the rollback URL. Go on only if the issuer is
293
+ `https://services.beta.astrale.ai`; any other issuer makes step 6 an issuer change, which the
294
+ Kernel refuses. Check as well that the Admin instance's Kernel issuer is admin-beta's
295
+ `SERVICES_DEPLOYMENT_KERNEL`; otherwise every deploy answers `hosting-unavailable`. Before the
296
+ first readable deploy, read the platform organisation's Identity id there and set it as
297
+ admin-beta's `SERVICES_PLATFORM_OWNER` var in a reviewed change to `astrale.config.ts`.
298
+ 2. Create `.env.admin-beta` (mode 600) from `.env.example` with the beta data-plane coordinates the
299
+ Admin instance's Services manage today (account, token, dispatch namespace, routing domain
300
+ `svc.eu.beta.astrale.ai`, schedule KV, dispatcher, zone, Admin target) and a new
301
+ `SERVICES_SECRET_DIGEST_KEY` that no other environment uses, e.g. `openssl rand -base64 32` (32
302
+ random bytes, kept as their 44-character base64 text).
303
+ 3. Restore the retained `https://services.beta.astrale.ai` signing identity, the one services-v2 was
304
+ deployed with, from managed secret storage to `.astrale/services-beta-identity.json` (mode 600).
305
+ Never generate a key for this runtime: the frozen R1 Worker publishes the issuer's keys, so a new
306
+ key would never verify. Never leave that key at `.astrale/identity.json`, the development
307
+ Environment's default identity: `pnpm dev` would bind it to development Workers. Check that the
308
+ issuer publishes the restored key:
309
+
310
+ ```sh
311
+ node --input-type=module -e '
312
+ const { readFileSync } = await import("node:fs")
313
+ const { kid, x } = JSON.parse(readFileSync(".astrale/services-beta-identity.json", "utf8"))
314
+ const issuer = "https://services.beta.astrale.ai"
315
+ const { jwks_uri } = await (await fetch(`${issuer}/.well-known/openid-configuration`)).json()
316
+ const { keys } = await (await fetch(jwks_uri)).json()
317
+ if (!keys.some((key) => key.kid === kid && key.x === x)) throw new Error("key not published")
318
+ console.log(`issuer publishes kid ${kid}`)
319
+ '
320
+ ```
321
+
322
+ 4. Deploy once with `pnpm run deploy admin-beta`. It creates only the `services-domain-admin-beta`
323
+ Worker and its route; nothing is installed. Do not run `pnpm provider:bootstrap` or a
324
+ `provider:route-*` command for this runtime: on the beta data plane they redeploy the shared
325
+ dispatcher and DNS routing that serve 1Pact's tenants (see the
326
+ [operator README](./providers/cloudflare/operator/README.md)).
327
+ 5. Rehearse step 6 first on a disposable instance whose Host runs the Admin Host's Kernel release and
328
+ whose Services are bound to the same issuer and revision as in step 1, with at least one Service.
329
+ The install must succeed and the Service must stay manageable (list, secrets, logs). Then install
330
+ the step 1 rollback URL there again: if the Kernel refuses it, step 6 is one-way, and that must be
331
+ recorded before step 6 runs.
332
+ 6. Install on the Admin instance only, directly:
333
+ `astrale domain install https://services-admin.beta.astrale.ai --direct --allow-identity-override -i <admin-instance>`.
334
+ The only consent is the identity override, because the origin `services.astrale.ai` differs from
335
+ the serving host; the issuer does not change. On any refusal, stop: never uninstall Services on
336
+ the Admin instance to get around it, which would drop the Service records it holds. Never repoint
337
+ Admin's catalog entry for `services.astrale.ai` at this runtime: consumer flows, 1Pact's among
338
+ them, install Services by origin through that entry.
85
339
 
86
340
  ## Plain background Service starter
87
341
 
@@ -107,8 +361,8 @@ No tooling dependency is added to the Schema runtime or imported from the packag
107
361
 
108
362
  Copy `.env.example` to `.env.dev` and fill the Cloudflare values.
109
363
 
110
- `pnpm dev` watches and deploys the development Environment remotely. `pnpm dev beta`
111
- selects beta; `pnpm beta` and `pnpm prod` deploy once. These bootstrap Environments
364
+ `pnpm dev` watches and deploys the development Environment remotely. `pnpm run deploy admin-beta`
365
+ deploys the [Admin-hosted runtime](#admin-hosted-runtime) once. These bootstrap Environments
112
366
  are deliberately deploy-only: install the resulting URL on the intended Kernel explicitly.
113
367
  Stopping development leaves the remote Worker available. No local Worker or tunnel is started.
114
368
 
@@ -123,9 +377,21 @@ pnpm build
123
377
  pnpm package
124
378
  ```
125
379
 
126
- The deployed module named by `entry` must be present in `modules`. Module contents use one Base64
127
- string or bounded Base64 chunks when one string cannot cross the portable Value boundary; names are
128
- unique and secrets use environment-variable identifiers. Plain and revisioned artifacts are
380
+ The deployed module named by `entry` must be present in `modules`. Modules and assets carry
381
+ `content: { encoding: 'identity' | 'gzip', decodedSize, contentBase64 }`; large encoded files use
382
+ `contentBase64Chunks` instead, with each string bounded to 512 KiB. `decodedSize` is the exact original
383
+ byte length. Compression changes transport only: provider uploads and deployment digests use the
384
+ reconstructed bytes. Services validates complete gzip streams (including their checksum), exact sizes,
385
+ and canonical Base64 before any graph or provider operation. Individual decoded files remain bounded
386
+ to 7.5 MiB per module and 12 MiB per asset; all decoded deployment material shares a 32 MiB budget.
387
+
388
+ The previous top-level `contentBase64` and `contentBase64Chunks` fields remain accepted and are
389
+ explicitly deprecated. Roll out Services and install its new schema on consumer instances before
390
+ publishing the compressed SDK adapter. Old SDK clients continue working against the new Services;
391
+ new SDK requests are refused by an old schema without provider effects. The adapter never retries a
392
+ deployment using a different wire representation after an uncertain outcome.
393
+
394
+ Module names are unique and secrets use environment-variable identifiers. Plain and revisioned artifacts are
129
395
  distinct modes and cannot replace each other at one Service record. Five-field cron expressions are
130
396
  supported when `CLOUDFLARE_SCHEDULE_KV_ID` is configured.
131
397
 
@@ -3,3 +3,5 @@ export type { ServicesDomain, ServicesSchema } from './schema.js';
3
3
  export { artifact, artifactModule, assetBundle, assetFile, deployServiceInput, deployServiceOutput, resourceLimits, revisionedDeployment, } from './modules/service/index.js';
4
4
  export type { ArtifactModule, AssetBundle, DeployServiceInput, DeployServiceOutput, ResourceLimits, } from './modules/service/index.js';
5
5
  export { CloudflareWorker, OwnService, Service, TraverseOwnedService, service_owned_by, } from './modules/service/index.js';
6
+ export { ActForOrganization, CloudflareDeployment, Deployment, DeploymentLine, OwnDeployment, OwnLine, TraverseOwnedLine, TraverseOwnedLineDeployment, deploymentCommit, deploymentHosting, deploymentLabel, deploymentLifecycle, deploymentRecord, deploymentRejection, deploymentRequest, deploymentResponse, deploymentRetention, deploymentRetirement, deploymentSecretDigests, deploymentSecretRejection, deploymentSummary, deployment_on_line, expirePreviewsOutput, frozenConfiguration, lastCallsOutput, listedDeploymentSummary, line_owned_by, MAXIMUM_LAST_CALL_LABELS, MAXIMUM_PREVIEW_EXPIRY_WRITES, retireDeploymentOutput, unadmittedDeploymentSummary, } from './modules/deployment/index.js';
7
+ export type { DeploymentHosting, DeploymentLifecycle, DeploymentLifecycleEvent, DeploymentRecord, DeploymentRejection, DeploymentRejectionReason, DeploymentRequest, DeploymentResponse, DeploymentRetention, DeploymentRetirement, DeploymentSecretDigest, DeploymentSecretRejection, DeploymentSummary, ExpirePreviewsResult, FrozenConfiguration, ListedDeploymentSummary, RetireDeploymentResult, UnadmittedDeploymentSummary, } from './modules/deployment/index.js';
@@ -1,3 +1,4 @@
1
1
  export { schema } from './schema.js';
2
2
  export { artifact, artifactModule, assetBundle, assetFile, deployServiceInput, deployServiceOutput, resourceLimits, revisionedDeployment, } from './modules/service/index.js';
3
3
  export { CloudflareWorker, OwnService, Service, TraverseOwnedService, service_owned_by, } from './modules/service/index.js';
4
+ export { ActForOrganization, CloudflareDeployment, Deployment, DeploymentLine, OwnDeployment, OwnLine, TraverseOwnedLine, TraverseOwnedLineDeployment, deploymentCommit, deploymentHosting, deploymentLabel, deploymentLifecycle, deploymentRecord, deploymentRejection, deploymentRequest, deploymentResponse, deploymentRetention, deploymentRetirement, deploymentSecretDigests, deploymentSecretRejection, deploymentSummary, deployment_on_line, expirePreviewsOutput, frozenConfiguration, lastCallsOutput, listedDeploymentSummary, line_owned_by, MAXIMUM_LAST_CALL_LABELS, MAXIMUM_PREVIEW_EXPIRY_WRITES, retireDeploymentOutput, unadmittedDeploymentSummary, } from './modules/deployment/index.js';