@astrale-os/sdk 0.6.0-beta.21 → 0.6.0-beta.23

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 (58) hide show
  1. package/README.md +0 -4
  2. package/dist/application/workflow/durable.d.ts +12 -8
  3. package/dist/deployment/adapter/adapter.d.ts +16 -5
  4. package/dist/deployment/adapter/define.d.ts +1 -1
  5. package/dist/deployment/adapter/define.js +40 -1
  6. package/dist/deployment/adapter/release/command.d.ts +33 -0
  7. package/dist/deployment/adapter/release/command.js +1 -0
  8. package/dist/deployment/adapter/release/context.d.ts +3 -4
  9. package/dist/deployment/adapter/release/index.d.ts +1 -0
  10. package/dist/deployment/adapter/release/list.d.ts +11 -10
  11. package/dist/deployment/address/configuration.d.ts +12 -0
  12. package/dist/deployment/address/configuration.js +11 -0
  13. package/dist/deployment/address/index.d.ts +3 -3
  14. package/dist/deployment/address/index.js +2 -3
  15. package/dist/deployment/address/label.d.ts +5 -5
  16. package/dist/deployment/address/label.js +5 -5
  17. package/dist/deployment/address/record.d.ts +21 -0
  18. package/dist/deployment/address/record.js +34 -0
  19. package/dist/deployment/address/summary.d.ts +17 -40
  20. package/dist/deployment/address/summary.js +1 -117
  21. package/dist/deployment/index.d.ts +1 -1
  22. package/dist/deployment/verify/index.d.ts +1 -1
  23. package/dist/deployment/verify/index.js +1 -1
  24. package/dist/deployment/verify/readiness/index.d.ts +7 -0
  25. package/dist/deployment/verify/readiness/index.js +17 -0
  26. package/dist/deployment/worker/address.d.ts +26 -0
  27. package/dist/deployment/worker/address.js +43 -0
  28. package/dist/deployment/worker/build.d.ts +60 -0
  29. package/dist/deployment/worker/build.js +172 -0
  30. package/dist/deployment/worker/configuration.d.ts +73 -0
  31. package/dist/deployment/worker/configuration.js +165 -0
  32. package/dist/deployment/worker/content.d.ts +38 -0
  33. package/dist/deployment/worker/content.js +169 -0
  34. package/dist/deployment/worker/index.d.ts +29 -0
  35. package/dist/deployment/worker/index.js +29 -0
  36. package/dist/deployment/worker/limits.d.ts +30 -0
  37. package/dist/deployment/worker/limits.js +30 -0
  38. package/dist/deployment/worker/record.d.ts +29 -0
  39. package/dist/deployment/worker/record.js +69 -0
  40. package/dist/deployment/worker/request.d.ts +118 -0
  41. package/dist/deployment/worker/request.js +280 -0
  42. package/dist/deployment/worker/response.d.ts +104 -0
  43. package/dist/deployment/worker/response.js +213 -0
  44. package/dist/execution/workflows/run.js +18 -6
  45. package/dist/tooling/cli/adapter-command.d.ts +35 -0
  46. package/dist/tooling/cli/adapter-command.js +66 -0
  47. package/dist/tooling/cli/arguments.d.ts +7 -1
  48. package/dist/tooling/cli/arguments.js +53 -1
  49. package/dist/tooling/cli/help.js +11 -8
  50. package/dist/tooling/cli/immutable-deployment.d.ts +2 -2
  51. package/dist/tooling/cli/index.d.ts +4 -2
  52. package/dist/tooling/cli/index.js +3 -2
  53. package/dist/tooling/cli/list.d.ts +5 -5
  54. package/dist/tooling/cli/list.js +16 -14
  55. package/dist/tooling/cli/orchestrate.js +18 -0
  56. package/dist/tooling/cli/publish/index.d.ts +4 -4
  57. package/dist/tooling/cli/publish/index.js +8 -9
  58. package/package.json +5 -1
package/README.md CHANGED
@@ -345,10 +345,6 @@ did not derive.
345
345
  - **Record.** `acceptDeploymentRecord` admits a `DeploymentRecordV1` only when its provider
346
346
  script is the label that its own fields derive. A host admits the record a deploy declares with
347
347
  it before storing it, so every stored record decodes.
348
- - **Platform summary.** `acceptPlatformDeploymentSummary` admits the `summary:<label>` a platform
349
- dispatch namespace keeps beside `record:<label>`: the deployment's `DeploymentSummaryV1` without
350
- its record, owned by `platform`, named by its label, with no provider version id and no
351
- `lastCallAt` or `expiresAt`. `encodePlatformDeploymentSummary` gives its one canonical text.
352
348
 
353
349
  ```ts
354
350
  import {
@@ -36,25 +36,29 @@ export interface DurableExecution {
36
36
  */
37
37
  export interface DurableStepAuthority<DomainValue extends Domain = Domain> {
38
38
  /**
39
- * Kernel Session acting as the Domain. Each `invoke` without an explicit `idempotencyKey` carries
40
- * one derived from the Execution, the Step and its position, so a retried Step reaches the same
41
- * durable child Execution.
39
+ * Kernel Session acting as the Domain. Each `invoke` of a durable callable without an explicit
40
+ * `idempotencyKey` carries one derived from the Execution, the Step and its position, so a
41
+ * retried Step reaches the same durable child Execution. An `invoke` of any other callable
42
+ * carries no key, which the Kernel would refuse, and runs again when the Step is retried.
42
43
  */
43
44
  readonly kernel: BoundClientSession;
44
45
  readonly query: QueryExecutor<DomainValue>;
45
- /** Each Mutation carries a key derived from the Execution, the Step and its position. */
46
+ /**
47
+ * Each Mutation carries a key derived from the Execution, the Step and its position. The Kernel
48
+ * commits a receipt with the write and answers a retried Step with its first result.
49
+ */
46
50
  readonly mutate: MutationExecutor<DomainValue>;
47
51
  }
48
52
  export interface DurableStep<DomainValue extends Domain = Domain> {
49
53
  /**
50
54
  * Run `execute` once and journal its portable output; a replay returns the journaled output. A
51
- * Step may run again when its outcome was not journaled, so its effects must be idempotent.
55
+ * Step may run again when its outcome was not journaled: its Mutations and durable calls are
56
+ * answered from their keys, and any other effect must be idempotent.
52
57
  */
53
58
  run<Output>(id: string, execute: (authority: DurableStepAuthority<DomainValue>) => Output | Promise<Output>): Promise<Output>;
54
59
  /**
55
- * One journaled Mutation. Give it a zero-match precondition on a natural key (for example the
56
- * Execution id) and map that failure in `reject`, so a Step replayed after its commit finds its
57
- * own effect instead of repeating it.
60
+ * One journaled Mutation. A Step replayed after its commit receives the result of that commit
61
+ * from the Kernel's receipt instead of writing again.
58
62
  */
59
63
  mutate<Input, Output>(id: string, mutation: Mutation<DomainValue, Input, Output>, input: Input): Promise<Output>;
60
64
  }
@@ -1,7 +1,7 @@
1
- import type { FrozenConfigurationV1 } from '../address/index.js';
1
+ import type { DeploymentConfiguration } from '../address/index.js';
2
2
  import type { Bundler } from './bundle/index.js';
3
3
  import type { ArtifactSource, DeployContext, DeployResult, PrepareContext } from './legacy/index.js';
4
- import type { ConfigureContext, ListContext, ListedDeployment, Placement, PlacementContext, ReleaseDeployContext, ReleaseDeployResult, RetainContext } from './release/index.js';
4
+ import type { AdapterCommand, ConfigureContext, ListContext, ListedDeployment, Placement, PlacementContext, ReleaseDeployContext, ReleaseDeployResult, RetainContext } from './release/index.js';
5
5
  /** What `admit` receives when `defineProject` binds an adapter to one Environment. */
6
6
  export interface AdapterEnvironmentContext {
7
7
  readonly environment: string;
@@ -26,8 +26,12 @@ export interface Adapter<Parameters extends object = object> {
26
26
  readonly bundler: Bundler;
27
27
  /** Refuse parameters that cannot serve this Environment, before any effect. */
28
28
  admit?(parameters: Parameters, context: AdapterEnvironmentContext): void;
29
- /** Freeze what a deployment of this Environment binds beside its build. Pure. */
30
- configure(parameters: Parameters, context: ConfigureContext): FrozenConfigurationV1;
29
+ /**
30
+ * Freeze what a deployment of this Environment binds beside its build: a `FrozenConfigurationV1`
31
+ * for a host that binds a generated Worker's settings itself, a `WorkerConfigurationV1` for a
32
+ * Worker host that binds only what it is asked (Admin's Services). Pure.
33
+ */
34
+ configure(parameters: Parameters, context: ConfigureContext): DeploymentConfiguration;
31
35
  /** Where the host places this Environment's deployments. No effect at the host. */
32
36
  placement(parameters: Parameters, context: PlacementContext): Promise<Placement>;
33
37
  /** Create, or reuse, the immutable deployment of one sealed release. */
@@ -44,6 +48,12 @@ export interface Adapter<Parameters extends object = object> {
44
48
  * transient. Absent: the registry marks it (Admin's Services).
45
49
  */
46
50
  retain?(parameters: Parameters, context: RetainContext): Promise<void>;
51
+ /**
52
+ * Provider commands, by name: `astrale-domain <adapter name> <command> <environment>` runs one
53
+ * on an Environment this adapter deploys (adapter-cloudflare's `plan` and `init` provision the
54
+ * host on the operator's own account). Every effect is the adapter's; the CLI only routes.
55
+ */
56
+ readonly commands?: Readonly<Record<string, AdapterCommand<Parameters>>>;
47
57
  /**
48
58
  * @deprecated Legacy direct-mode path (CT33) only: place a build at a stable target. Replacement:
49
59
  * `bundler`, `configure` and `placement`. Deleted with the legacy path (D10).
@@ -62,11 +72,12 @@ export interface AdapterInput<Parameters extends object> {
62
72
  readonly version: string;
63
73
  readonly bundler: Bundler;
64
74
  admit?(parameters: Parameters, context: AdapterEnvironmentContext): void;
65
- configure(parameters: Parameters, context: ConfigureContext): FrozenConfigurationV1;
75
+ configure(parameters: Parameters, context: ConfigureContext): DeploymentConfiguration;
66
76
  placement(parameters: Parameters, context: PlacementContext): Promise<Placement>;
67
77
  deployRelease(parameters: Parameters, context: ReleaseDeployContext): Promise<ReleaseDeployResult>;
68
78
  list?(parameters: Parameters, context: ListContext): Promise<readonly ListedDeployment[]>;
69
79
  retain?(parameters: Parameters, context: RetainContext): Promise<void>;
80
+ readonly commands?: Readonly<Record<string, AdapterCommand<Parameters>>>;
70
81
  /** @deprecated Legacy direct-mode path (CT33) only. Replacement: `configure`, `placement`. */
71
82
  prepare?(parameters: Parameters, context: PrepareContext): Promise<ArtifactSource>;
72
83
  /** @deprecated Legacy direct-mode path (CT33) only. Replacement: `deployRelease`. */
@@ -3,7 +3,7 @@ import type { Adapter, AdapterInput } from './adapter.js';
3
3
  import type { LegacyAdapter, LegacyAdapterInput } from './legacy/index.js';
4
4
  /**
5
5
  * Capture one receiver-bound provider adapter (adapter interface v2): a required `bundler`,
6
- * `configure`, `placement` and `deployRelease`, optionally `list` and `retain`, and `prepare` with
6
+ * `configure`, `placement` and `deployRelease`, optionally `list`, `retain` and provider `commands`, and `prepare` with
7
7
  * `deploy` only while it also serves the legacy direct-mode path.
8
8
  */
9
9
  export declare function defineAdapter<Parameters extends object>(input: AdapterInput<Parameters>): Adapter<Parameters>;
@@ -12,6 +12,7 @@ const FIELDS = [
12
12
  'deployRelease',
13
13
  'list',
14
14
  'retain',
15
+ 'commands',
15
16
  'prepare',
16
17
  'deploy',
17
18
  'secretsFile',
@@ -22,7 +23,9 @@ const OPTIONAL_RELEASE_OPERATIONS = ['list', 'retain'];
22
23
  /** The operations of the legacy direct-mode path. */
23
24
  const STABLE_TARGET_OPERATIONS = ['prepare', 'deploy'];
24
25
  /** Every operation an adapter may declare, in the order an admitted adapter lists them. */
25
- const OPERATIONS = FIELDS.filter((key) => key !== 'name' && key !== 'version' && key !== 'bundler');
26
+ const OPERATIONS = FIELDS.filter((key) => key !== 'name' && key !== 'version' && key !== 'bundler' && key !== 'commands');
27
+ /** A provider command name, `astrale-domain <adapter> <command>`, and an option name. */
28
+ const COMMAND_NAME = /^[a-z][a-z0-9-]{0,31}$/u;
26
29
  export function defineAdapter(input) {
27
30
  const value = record(input, 'Adapter definition');
28
31
  if (Reflect.ownKeys(value).some((key) => !FIELDS.includes(key))) {
@@ -47,12 +50,16 @@ export function defineAdapter(input) {
47
50
  key,
48
51
  capture(receiver, key),
49
52
  ]));
53
+ if (value.commands !== undefined && !release) {
54
+ throw new TypeError(`Adapter ${name} declares commands without adapter interface v2.`);
55
+ }
50
56
  const adapter = Object.freeze({
51
57
  kind: 'adapter',
52
58
  name,
53
59
  version,
54
60
  bundler,
55
61
  ...operations,
62
+ ...(value.commands === undefined ? {} : { commands: admitCommands(name, value.commands) }),
56
63
  });
57
64
  admittedAdapters.add(adapter);
58
65
  return adapter;
@@ -131,6 +138,38 @@ function missingBundler(name) {
131
138
  'to the adapter as its Bundle (a Cloudflare Worker adapter declares ' +
132
139
  '`cloudflareWorkerBundler` from `@astrale-os/adapter-cloudflare/build`).');
133
140
  }
141
+ /**
142
+ * Capture the provider commands of one adapter: each a lower-case name, a one-line `summary`,
143
+ * optional `options` (name to one line of help) and a receiver-bound `run`.
144
+ */
145
+ function admitCommands(adapter, input) {
146
+ const commands = record(input, `Adapter ${adapter} commands`);
147
+ const admitted = {};
148
+ for (const name of Reflect.ownKeys(commands)) {
149
+ if (typeof name !== 'string' || !COMMAND_NAME.test(name)) {
150
+ throw new TypeError(`Adapter ${adapter} declares an invalid command name.`);
151
+ }
152
+ const command = record(commands[name], `Adapter ${adapter} command ${name}`);
153
+ const options = command.options === undefined ? {} : record(command.options, 'Command options');
154
+ if (Reflect.ownKeys(command).some((key) => key !== 'summary' && key !== 'options' && key !== 'run') ||
155
+ typeof command.summary !== 'string' ||
156
+ command.summary.length === 0 ||
157
+ typeof command.run !== 'function' ||
158
+ Reflect.ownKeys(options).some((key) => typeof key !== 'string' ||
159
+ !COMMAND_NAME.test(key) ||
160
+ key === 'json' ||
161
+ typeof options[key] !== 'string')) {
162
+ throw new TypeError(`Adapter ${adapter} command ${name} must declare a summary, string options other than ` +
163
+ 'json and run.');
164
+ }
165
+ admitted[name] = Object.freeze({
166
+ summary: command.summary,
167
+ options: Object.freeze({ ...options }),
168
+ run: command.run.bind(command),
169
+ });
170
+ }
171
+ return Object.freeze(admitted);
172
+ }
134
173
  function invalidFields() {
135
174
  throw new TypeError('Adapter definition contains missing or unknown fields.');
136
175
  }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * One command an adapter declares for its provider (`astrale-domain <adapter> <command>
3
+ * <environment>`), such as adapter-cloudflare's `plan` and `init`, which provision the host on the
4
+ * operator's own account. The CLI only routes: it loads the Project, selects the Environment, checks
5
+ * that its adapter is the one named and declares the command, and admits the options the command
6
+ * declares; everything the command does is the adapter's.
7
+ */
8
+ export interface AdapterCommand<Parameters extends object = object> {
9
+ /** One line: what the command does, for help. */
10
+ readonly summary: string;
11
+ /** The options it takes, `--<name> <value>`, each with one line of help. */
12
+ readonly options?: Readonly<Record<string, string>>;
13
+ run(parameters: Parameters, context: AdapterCommandContext): Promise<AdapterCommandResult>;
14
+ }
15
+ /** What an adapter command receives. */
16
+ export interface AdapterCommandContext {
17
+ readonly environment: string;
18
+ /** Parent-owned lifecycle cancellation. */
19
+ readonly signal: AbortSignal;
20
+ /** The options given, each one the command declares. */
21
+ readonly options: Readonly<Record<string, string>>;
22
+ }
23
+ /** What an adapter command answers; a command that cannot run rejects instead. */
24
+ export interface AdapterCommandResult {
25
+ /** 0 done; 1 refused or blocked (the command ran, and says why in `warnings`). */
26
+ readonly exitCode: 0 | 1;
27
+ /** Lines for a person, printed on stdout without `--json`. */
28
+ readonly lines: readonly string[];
29
+ /** Lines for stderr, printed in either case. */
30
+ readonly warnings?: readonly string[];
31
+ /** The JSON-serializable result `--json` prints. */
32
+ readonly result: unknown;
33
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,4 @@
1
- import type { DeploymentCommitV1, FrozenConfigurationV1, LineAddressing } from '../../address/index.js';
1
+ import type { DeploymentCommitV1, DeploymentConfiguration, LineAddressing } from '../../address/index.js';
2
2
  import type { Release } from '../../release/index.js';
3
3
  import type { Bundle } from '../bundle/index.js';
4
4
  /**
@@ -10,8 +10,7 @@ export interface ConfigureContext {
10
10
  readonly environment: string;
11
11
  /**
12
12
  * The names of the Environment's current secrets (the adapter's `secretsFile`), sorted and
13
- * unique: exactly the secrets `deployRelease` receives, which the configuration declares in
14
- * `bindings.secrets`.
13
+ * unique: exactly the secrets `deployRelease` receives, which the configuration declares.
15
14
  */
16
15
  readonly secrets: readonly string[];
17
16
  }
@@ -53,7 +52,7 @@ export interface ReleaseDeployContext {
53
52
  /** The environment-free bytes of the adapter's `bundler`, identified by `bundle.digest`. */
54
53
  readonly bundle: Bundle;
55
54
  /** What `configure` froze for this Environment. */
56
- readonly configuration: FrozenConfigurationV1;
55
+ readonly configuration: DeploymentConfiguration;
57
56
  /** Where `placement` said the host places the deployment. */
58
57
  readonly placement: Placement;
59
58
  /**
@@ -1,3 +1,4 @@
1
1
  export type { ConfigureContext, ListContext, Placement, PlacementContext, ReleaseDeployContext, RetainContext, } from './context.js';
2
+ export type { AdapterCommand, AdapterCommandContext, AdapterCommandResult } from './command.js';
2
3
  export type { DeploymentCallTarget, ListedDeployment } from './list.js';
3
4
  export type { DeploymentSecretsState, FailedReleaseDeployResult, MutationKnowledge, ReadyReleaseDeployResult, ReleaseDeploymentPhase, ReleaseDeployResult, } from './result.js';
@@ -11,8 +11,9 @@ import type { DeploymentSummaryV1 } from '../../address/index.js';
11
11
  * and the deployment it currently serves: `promote` (this deployment's label rolls the Service
12
12
  * back or forward to it), `setSecret` and `secrets`, `setSchedule` and `schedules`, `logs`,
13
13
  * `transfer` and `delete`.
14
- * - `namespace`: a deployment of a platform dispatch namespace, which has no Services node: the
15
- * namespace and the script the per-script tooling of that namespace takes.
14
+ * - `namespace`: a deployment of a Cloudflare dispatch namespace (adapter-cloudflare's namespace
15
+ * mode), which has no Services node: the namespace and the script, which the hosting kit
16
+ * (`@astrale-os/adapter-cloudflare/hosting`) retires, points a name at or schedules.
16
17
  */
17
18
  export type DeploymentCallTarget = {
18
19
  readonly kind: 'services';
@@ -27,11 +28,11 @@ export type DeploymentCallTarget = {
27
28
  };
28
29
  /**
29
30
  * One deployment as an adapter's `list` returns it: what its host knows of it, its record as the
30
- * host stores it, and where it is called. `lastCallAt`, and a preview's `expiresAt`, are as of the
31
- * listing: an adapter whose host records calls only now and then reads the calls counted since, and
32
- * one that cannot read them for want of a permission omits both rather than print stale values.
33
- * Admin's Services keeps every deployment until it is retired and counts no call: its deployments
34
- * are `published`, `withdrawn` once retiring, and carry neither clock.
31
+ * host stores it, and where it is called. Admin's Services keeps every deployment until it is
32
+ * retired and counts no call: its deployments are `published` and `withdrawn` once retiring. A
33
+ * Cloudflare dispatch namespace keeps no retention: its rows carry none, and carry their creation
34
+ * instant and key only while their script exists. No first-party adapter reports a last call or an
35
+ * expiry any more; the CLI still prints one an older adapter reports.
35
36
  *
36
37
  * The adapter does not admit the record. The listing admits it with `acceptDeploymentRecord`, and a
37
38
  * record it refuses (one a host on an older SDK still admits, say) never fails the listing: that
@@ -40,13 +41,13 @@ export type DeploymentCallTarget = {
40
41
  export interface ListedDeployment extends Omit<DeploymentSummaryV1, 'record'> {
41
42
  /**
42
43
  * The record as the host stores it, unadmitted, whatever value that is (JSON `null` included).
43
- * Absent when the host keeps no record of the deployment: a platform namespace keeps none until
44
- * it activates the deployment.
44
+ * Absent when the host keeps no record of the deployment: a dispatch namespace keeps none for a
45
+ * label it only retired.
45
46
  */
46
47
  readonly record?: unknown;
47
48
  /**
48
49
  * Whether the stable URL of the deployment's Service serves it. Present only where the host has
49
- * Services (Admin's Services); a platform namespace has none.
50
+ * Services (Admin's Services); a dispatch namespace has none.
50
51
  */
51
52
  readonly current?: boolean;
52
53
  readonly callTarget: DeploymentCallTarget;
@@ -1,4 +1,5 @@
1
1
  import type { Digest } from '@astrale-os/kernel-protocol/artifact';
2
+ import type { WorkerConfigurationV1 } from '../worker/configuration.js';
2
3
  export declare const FROZEN_CONFIGURATION_FORMAT = "astrale.deployment.configuration";
3
4
  export declare const FROZEN_CONFIGURATION_VERSION = 1;
4
5
  /**
@@ -70,6 +71,17 @@ export interface FrozenDurableOptions {
70
71
  };
71
72
  readonly stepTimeoutMs?: number;
72
73
  }
74
+ /**
75
+ * What one deployment freezes beside its build, as its adapter's `configure` returns it: a
76
+ * `FrozenConfigurationV1`, which a Cloudflare dispatch namespace binds (namespace mode), or a
77
+ * `WorkerConfigurationV1` (`@astrale-os/sdk/deployment/worker`), which a Worker host binds
78
+ * (Admin's Services). Its digest and the build digest make the deployment's label.
79
+ */
80
+ export type DeploymentConfiguration = FrozenConfigurationV1 | WorkerConfigurationV1;
81
+ /** The digest of one frozen configuration of either format, as the deployment's label keeps it. */
82
+ export declare function deploymentConfigurationDigest(configuration: DeploymentConfiguration): Digest;
83
+ /** Whether a configuration is a `FrozenConfigurationV1`, by its format alone (not admitted). */
84
+ export declare function isFrozenConfiguration(configuration: DeploymentConfiguration): configuration is FrozenConfigurationV1;
73
85
  /** A configuration before its canonical order: lists in any order, no format header. */
74
86
  export type FrozenConfigurationInput = Omit<FrozenConfigurationV1, 'format' | 'version'>;
75
87
  /** Freeze one deployment configuration in the one canonical order its digest is taken in. */
@@ -1,5 +1,6 @@
1
1
  import { json } from '@astrale-os/kernel-dsl/value';
2
2
  import { identify } from '@astrale-os/kernel-protocol/artifact';
3
+ import { isWorkerConfiguration, workerConfigurationDigest } from '../worker/configuration.js';
3
4
  export const FROZEN_CONFIGURATION_FORMAT = 'astrale.deployment.configuration';
4
5
  export const FROZEN_CONFIGURATION_VERSION = 1;
5
6
  /** Service binding through which an immutable deployment reaches the instance router. */
@@ -10,6 +11,16 @@ const COMPATIBILITY_DATE = /^\d{4}-\d{2}-\d{2}$/u;
10
11
  const BINDING_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/u;
11
12
  /** A Cloudflare dispatch namespace name. */
12
13
  const NAMESPACE_NAME = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/u;
14
+ /** The digest of one frozen configuration of either format, as the deployment's label keeps it. */
15
+ export function deploymentConfigurationDigest(configuration) {
16
+ return isWorkerConfiguration(configuration)
17
+ ? workerConfigurationDigest(configuration)
18
+ : configurationDigest(configuration);
19
+ }
20
+ /** Whether a configuration is a `FrozenConfigurationV1`, by its format alone (not admitted). */
21
+ export function isFrozenConfiguration(configuration) {
22
+ return configuration?.format === FROZEN_CONFIGURATION_FORMAT;
23
+ }
13
24
  /** Freeze one deployment configuration in the one canonical order its digest is taken in. */
14
25
  export function frozenConfiguration(input) {
15
26
  if (input === null || typeof input !== 'object')
@@ -1,5 +1,5 @@
1
- export { configurationDigest, frozenConfiguration, FROZEN_CONFIGURATION_FORMAT, FROZEN_CONFIGURATION_VERSION, type FrozenConfigurationInput, type FrozenDurableOptions, type FrozenConfigurationV1, } from './configuration.js';
1
+ export { configurationDigest, deploymentConfigurationDigest, frozenConfiguration, FROZEN_CONFIGURATION_FORMAT, FROZEN_CONFIGURATION_VERSION, isFrozenConfiguration, type DeploymentConfiguration, type FrozenConfigurationInput, type FrozenDurableOptions, type FrozenConfigurationV1, } from './configuration.js';
2
2
  export { DEPLOYMENT_LABEL, deploymentContent, deploymentLabel, deploymentUrl, parseDeploymentLabel, type DeploymentLabelParts, } from './label.js';
3
3
  export { deploymentLine, lineFingerprint, readablePart, type DeploymentLineInput, type LineAddressing, } from './line.js';
4
- export { acceptDeploymentRecord, DEPLOYMENT_RECORD_FORMAT, DEPLOYMENT_RECORD_VERSION, type DeploymentCommitV1, type DeploymentRecordV1, } from './record.js';
5
- export { acceptPlatformDeploymentSummary, encodePlatformDeploymentSummary, PLATFORM_OWNER, type DeploymentSummaryV1, type PlatformDeploymentSummaryV1, } from './summary.js';
4
+ export { acceptDeploymentRecord, decodeDeploymentRecord, DEPLOYMENT_RECORD_FORMAT, DEPLOYMENT_RECORD_MEDIA_TYPE, DEPLOYMENT_RECORD_VERSION, encodeDeploymentRecord, type DeploymentCommitV1, type DeploymentRecordDocument, type DeploymentRecordV1, } from './record.js';
5
+ export type { DeploymentSummaryV1 } from './summary.js';
@@ -1,5 +1,4 @@
1
- export { configurationDigest, frozenConfiguration, FROZEN_CONFIGURATION_FORMAT, FROZEN_CONFIGURATION_VERSION, } from './configuration.js';
1
+ export { configurationDigest, deploymentConfigurationDigest, frozenConfiguration, FROZEN_CONFIGURATION_FORMAT, FROZEN_CONFIGURATION_VERSION, isFrozenConfiguration, } from './configuration.js';
2
2
  export { DEPLOYMENT_LABEL, deploymentContent, deploymentLabel, deploymentUrl, parseDeploymentLabel, } from './label.js';
3
3
  export { deploymentLine, lineFingerprint, readablePart, } from './line.js';
4
- export { acceptDeploymentRecord, DEPLOYMENT_RECORD_FORMAT, DEPLOYMENT_RECORD_VERSION, } from './record.js';
5
- export { acceptPlatformDeploymentSummary, encodePlatformDeploymentSummary, PLATFORM_OWNER, } from './summary.js';
4
+ export { acceptDeploymentRecord, decodeDeploymentRecord, DEPLOYMENT_RECORD_FORMAT, DEPLOYMENT_RECORD_MEDIA_TYPE, DEPLOYMENT_RECORD_VERSION, encodeDeploymentRecord, } from './record.js';
@@ -1,6 +1,6 @@
1
1
  import type { Digest } from '@astrale-os/kernel-protocol/artifact';
2
2
  import type { BuildDigest } from '../build/manifest.js';
3
- import type { FrozenConfigurationV1 } from './configuration.js';
3
+ import type { DeploymentConfiguration } from './configuration.js';
4
4
  /**
5
5
  * Every deployment label: an optional readable part, the 16-character line fingerprint and the
6
6
  * 16-character content, both lower-case base32. A legacy provider name (a UUID, a stable Worker
@@ -16,11 +16,11 @@ export interface DeploymentLabelParts {
16
16
  }
17
17
  /**
18
18
  * The 16-character content of one deployment: the first 80 bits of
19
- * `sha256(utf8(buildDigest) ‖ 0x00 ‖ utf8(configurationDigest(configuration)))` in lower-case
20
- * base32. It covers the build and the frozen configuration, never a secret: one address serves
21
- * one release, and a changed variable gives another address.
19
+ * `sha256(utf8(buildDigest) ‖ 0x00 ‖ utf8(deploymentConfigurationDigest(configuration)))` in
20
+ * lower-case base32. It covers the build and the frozen configuration, never a secret: one address
21
+ * serves one release, and a changed variable gives another address.
22
22
  */
23
- export declare function deploymentContent(build: BuildDigest, configuration: FrozenConfigurationV1): string;
23
+ export declare function deploymentContent(build: BuildDigest, configuration: DeploymentConfiguration): string;
24
24
  /** The content of a build and a full configuration digest, as a deployment record holds them. */
25
25
  export declare function contentOf(build: BuildDigest, configuration: Digest): string;
26
26
  /**
@@ -1,4 +1,4 @@
1
- import { configurationDigest } from './configuration.js';
1
+ import { deploymentConfigurationDigest } from './configuration.js';
2
2
  import { first80 } from './fingerprint.js';
3
3
  /**
4
4
  * Every deployment label: an optional readable part, the 16-character line fingerprint and the
@@ -18,12 +18,12 @@ const MAXIMUM_LABEL_LENGTH = MAXIMUM_LINE_LENGTH + 17;
18
18
  const MAXIMUM_HOST_LENGTH = 253;
19
19
  /**
20
20
  * The 16-character content of one deployment: the first 80 bits of
21
- * `sha256(utf8(buildDigest) ‖ 0x00 ‖ utf8(configurationDigest(configuration)))` in lower-case
22
- * base32. It covers the build and the frozen configuration, never a secret: one address serves
23
- * one release, and a changed variable gives another address.
21
+ * `sha256(utf8(buildDigest) ‖ 0x00 ‖ utf8(deploymentConfigurationDigest(configuration)))` in
22
+ * lower-case base32. It covers the build and the frozen configuration, never a secret: one address
23
+ * serves one release, and a changed variable gives another address.
24
24
  */
25
25
  export function deploymentContent(build, configuration) {
26
- return contentOf(build, configurationDigest(configuration));
26
+ return contentOf(build, deploymentConfigurationDigest(configuration));
27
27
  }
28
28
  /** The content of a build and a full configuration digest, as a deployment record holds them. */
29
29
  export function contentOf(build, configuration) {
@@ -4,6 +4,11 @@ import type { ReleaseDigest } from '@astrale-os/kernel-protocol/release';
4
4
  import type { BuildDigest } from '../build/manifest.js';
5
5
  export declare const DEPLOYMENT_RECORD_FORMAT = "astrale.deployment-record";
6
6
  export declare const DEPLOYMENT_RECORD_VERSION = 1;
7
+ /**
8
+ * The media type of a `DeploymentRecordV1` document: what a Domain deployer publishes it with, and
9
+ * what a host serves it with at `/.well-known/astrale/deployment.json`.
10
+ */
11
+ export declare const DEPLOYMENT_RECORD_MEDIA_TYPE = "application/vnd.astrale.deployment-record+json;v=1";
7
12
  /** The commit a deployment was built from, as the deployer declared it. */
8
13
  export interface DeploymentCommitV1 {
9
14
  /** The full commit SHA, 40 hexadecimal digits in either case; git prints it in lower case. */
@@ -50,3 +55,19 @@ export interface DeploymentRecordV1 {
50
55
  * does, and never fails the whole listing over it.
51
56
  */
52
57
  export declare function acceptDeploymentRecord(input: unknown): DeploymentRecordV1;
58
+ /** The document of one record: what a Domain deployer publishes and a host serves verbatim. */
59
+ export interface DeploymentRecordDocument {
60
+ readonly contentType: string;
61
+ readonly bytes: Uint8Array;
62
+ }
63
+ /**
64
+ * The document of one record, as a Domain deployer publishes it: the record admitted, as the
65
+ * UTF-8 bytes of its RFC 8785 canonical JSON, with `DEPLOYMENT_RECORD_MEDIA_TYPE`. One record has
66
+ * one document, byte for byte, whoever writes it.
67
+ */
68
+ export declare function encodeDeploymentRecord(record: DeploymentRecordV1): DeploymentRecordDocument;
69
+ /**
70
+ * Admit the document of one record: `DEPLOYMENT_RECORD_MEDIA_TYPE`, and UTF-8 JSON whose value
71
+ * `acceptDeploymentRecord` admits. Anything else throws.
72
+ */
73
+ export declare function decodeDeploymentRecord(document: DeploymentRecordDocument): DeploymentRecordV1;
@@ -1,10 +1,16 @@
1
1
  import { patterns } from '@astrale-os/kernel-dsl/v1/addressing';
2
+ import { json } from '@astrale-os/kernel-dsl/value';
2
3
  import { isVersion } from '../../platform/versioning/version.js';
3
4
  import { ENVIRONMENT_NAME } from '../environment.js';
4
5
  import { contentOf, parseDeploymentLabel } from './label.js';
5
6
  import { deploymentLine } from './line.js';
6
7
  export const DEPLOYMENT_RECORD_FORMAT = 'astrale.deployment-record';
7
8
  export const DEPLOYMENT_RECORD_VERSION = 1;
9
+ /**
10
+ * The media type of a `DeploymentRecordV1` document: what a Domain deployer publishes it with, and
11
+ * what a host serves it with at `/.well-known/astrale/deployment.json`.
12
+ */
13
+ export const DEPLOYMENT_RECORD_MEDIA_TYPE = 'application/vnd.astrale.deployment-record+json;v=1';
8
14
  const DIGEST = /^sha256:[0-9a-f]{64}$/u;
9
15
  /** The full hexadecimal SHA-1 of a commit, in either case, as a deployment name reads it. */
10
16
  const SHA = /^[0-9a-f]{40}$/iu;
@@ -64,6 +70,34 @@ export function acceptDeploymentRecord(input) {
64
70
  commit: record.commit === null ? null : commit(record.commit),
65
71
  });
66
72
  }
73
+ /**
74
+ * The document of one record, as a Domain deployer publishes it: the record admitted, as the
75
+ * UTF-8 bytes of its RFC 8785 canonical JSON, with `DEPLOYMENT_RECORD_MEDIA_TYPE`. One record has
76
+ * one document, byte for byte, whoever writes it.
77
+ */
78
+ export function encodeDeploymentRecord(record) {
79
+ return Object.freeze({
80
+ contentType: DEPLOYMENT_RECORD_MEDIA_TYPE,
81
+ bytes: new TextEncoder().encode(json.serialize(acceptDeploymentRecord(record))),
82
+ });
83
+ }
84
+ /**
85
+ * Admit the document of one record: `DEPLOYMENT_RECORD_MEDIA_TYPE`, and UTF-8 JSON whose value
86
+ * `acceptDeploymentRecord` admits. Anything else throws.
87
+ */
88
+ export function decodeDeploymentRecord(document) {
89
+ if (document?.contentType !== DEPLOYMENT_RECORD_MEDIA_TYPE) {
90
+ throw new TypeError('Deployment record document has another media type.');
91
+ }
92
+ let value;
93
+ try {
94
+ value = JSON.parse(new TextDecoder('utf-8', { fatal: true, ignoreBOM: false }).decode(document.bytes));
95
+ }
96
+ catch (cause) {
97
+ throw new TypeError('Deployment record document is not UTF-8 JSON.', { cause });
98
+ }
99
+ return acceptDeploymentRecord(value);
100
+ }
67
101
  function commit(input) {
68
102
  const based = input !== null && typeof input === 'object' && Object.hasOwn(input, 'base');
69
103
  const value = exact(input, based ? ['sha', 'dirty', 'base'] : ['sha', 'dirty'], 'commit');
@@ -1,57 +1,34 @@
1
1
  import type { DeploymentRecordV1 } from './record.js';
2
2
  /**
3
3
  * What a host knows of one immutable deployment beside the record its deployer declared: where it
4
- * answers, who owns its line, its lifecycle and its retention. It names a deployment by its label
5
- * and URL, never by a provider version id. An adapter's `list` returns these; the host owns every
6
- * field but `record`, which it stores as declared.
4
+ * answers, who owns its line and its lifecycle. It names a deployment by its label and URL, never
5
+ * by a provider version id. An adapter's `list` returns these; the host owns every field but
6
+ * `record`, which it stores as declared.
7
7
  */
8
8
  export interface DeploymentSummaryV1 {
9
- /** The host's own identifier of the deployment (a graph node id on Admin's Services). */
9
+ /** The host's own identifier of the deployment (a graph node id on Admin's Services, the label on a dispatch namespace). */
10
10
  readonly id: string;
11
11
  readonly url: string;
12
12
  readonly label: string;
13
13
  readonly line: string;
14
- /** The Identity owning the line: a node path on Admin's Services, `platform` on a platform namespace. */
14
+ /** Who owns the line: a node path on Admin's Services, the dispatch namespace's name on Cloudflare. */
15
15
  readonly owner: string;
16
16
  readonly state: 'deploying' | 'active' | 'retiring' | 'retired' | 'failed';
17
- readonly retention: 'preview' | 'published';
18
- readonly createdAt: string;
17
+ /**
18
+ * `published` on Admin's Services, which keeps every deployment until it is retired. Absent on a
19
+ * dispatch namespace, which keeps no retention.
20
+ */
21
+ readonly retention?: 'preview' | 'published';
22
+ /** When the host created it; absent when the host no longer knows (its script is gone). */
23
+ readonly createdAt?: string;
24
+ /** An older adapter's report of the last call; no first-party adapter reports it any more. */
19
25
  readonly lastCallAt?: string;
20
- /** A preview's: 30 days after its activation or its last call, whichever is later (AM-123). */
26
+ /** An older adapter's preview expiry; no first-party adapter reports it any more. */
21
27
  readonly expiresAt?: string;
22
28
  readonly retiredAt?: string;
29
+ /** `withdrawn` once its owner retired it (`expired` only from an older adapter). */
23
30
  readonly retirement?: 'expired' | 'withdrawn';
24
- readonly signingKeyId: string;
31
+ /** The RFC 7638 thumbprint of its key; absent when the host no longer knows (its script is gone). */
32
+ readonly signingKeyId?: string;
25
33
  readonly record: DeploymentRecordV1;
26
34
  }
27
- /** The owner every summary of a platform namespace names: the platform holds all of its lines. */
28
- export declare const PLATFORM_OWNER = "platform";
29
- /**
30
- * `summary:<label>` in the routing KV of a platform dispatch namespace (CT20, AM-131): the
31
- * `DeploymentSummaryV1` of one deployment without its record, which stays at `record:<label>`.
32
- *
33
- * Its writers are the deployer (`cloudflare({ namespace })`), which writes it at activation,
34
- * before `record:<label>`, names in it the new key of a script it recreates under the label after
35
- * an operator deleted it, and marks it published (`retain`), and the platform retirement tooling
36
- * (SV6), which records a withdrawal. So it names the deployment by its label (`id` is the label),
37
- * is owned by `platform`, is `active` until withdrawn, and carries no `lastCallAt` or `expiresAt`:
38
- * nothing on the platform namespace keeps those clocks, and listings read them from the calls
39
- * dataset (AM-133). It never carries a provider version id or a secret digest: an in-place secret
40
- * write mints a new version id, which identifies nothing (OPS-2 X1).
41
- */
42
- export type PlatformDeploymentSummaryV1 = Omit<DeploymentSummaryV1, 'record' | 'lastCallAt' | 'expiresAt' | 'state' | 'retirement'> & {
43
- readonly state: 'active' | 'retiring' | 'retired';
44
- readonly retirement?: 'withdrawn';
45
- };
46
- /**
47
- * Admit one platform deployment summary exactly. Any member outside its shape is refused, a
48
- * provider version id or a clock among them; so is a summary whose id, line or URL is not its
49
- * label's, whose owner is not the platform, or whose state and retirement disagree. A reader that
50
- * meets a refused summary lists that label apart, never acting on it.
51
- */
52
- export declare function acceptPlatformDeploymentSummary(input: unknown): PlatformDeploymentSummaryV1;
53
- /**
54
- * The canonical JSON (RFC 8785) of one admitted platform summary, as `summary:<label>` holds it:
55
- * one summary has one text, so a writer that reads it back compares texts.
56
- */
57
- export declare function encodePlatformDeploymentSummary(summary: PlatformDeploymentSummaryV1): string;