@astrale-os/kernel-server 0.5.0-beta.69 → 0.5.0-beta.70

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 (62) hide show
  1. package/dist/cors/cors.js +9 -3
  2. package/dist/index.d.ts +8 -3
  3. package/dist/index.js +7 -2
  4. package/dist/invocation/index.d.ts +1 -1
  5. package/dist/invocation/index.js +1 -1
  6. package/dist/invocation/legacy/index.d.ts +20 -0
  7. package/dist/invocation/legacy/index.js +20 -0
  8. package/dist/invocation/{publication-route.d.ts → legacy/publication-route.d.ts} +6 -1
  9. package/dist/invocation/{publication-route.js → legacy/publication-route.js} +5 -1
  10. package/dist/invocation/release-route.d.ts +26 -0
  11. package/dist/invocation/release-route.js +55 -0
  12. package/dist/invocation/types.d.ts +16 -4
  13. package/dist/issuer/routes.js +13 -0
  14. package/dist/issuer/types.d.ts +5 -0
  15. package/dist/release/delivery/handler.d.ts +13 -0
  16. package/dist/{publication → release}/delivery/handler.js +33 -10
  17. package/dist/release/delivery/types.d.ts +21 -0
  18. package/dist/release/index.d.ts +3 -0
  19. package/dist/release/index.js +2 -0
  20. package/dist/release/legacy/delivery.d.ts +18 -0
  21. package/dist/release/legacy/delivery.js +2 -0
  22. package/dist/release/legacy/index.d.ts +39 -0
  23. package/dist/release/legacy/index.js +38 -0
  24. package/dist/{publication → release/legacy}/snapshot.d.ts +14 -4
  25. package/dist/{publication → release/legacy}/snapshot.js +11 -2
  26. package/dist/release/legacy/table.d.ts +5 -0
  27. package/dist/{publication → release/legacy}/table.js +2 -2
  28. package/dist/{publication → release/legacy}/types.d.ts +13 -4
  29. package/dist/release/served.d.ts +30 -0
  30. package/dist/release/served.js +14 -0
  31. package/dist/release/snapshot.d.ts +26 -0
  32. package/dist/release/snapshot.js +54 -0
  33. package/dist/release/table.d.ts +4 -0
  34. package/dist/release/table.js +11 -0
  35. package/dist/release/types.d.ts +33 -0
  36. package/dist/release/types.js +1 -0
  37. package/dist/routes/index.d.ts +1 -1
  38. package/dist/routes/index.js +1 -1
  39. package/dist/routes/routes.d.ts +9 -6
  40. package/dist/routes/routes.js +135 -76
  41. package/dist/server.js +66 -45
  42. package/dist/transport/codecs.d.ts +2 -2
  43. package/dist/transport/codecs.js +4 -3
  44. package/dist/transport/http/handler.d.ts +3 -2
  45. package/dist/transport/http/handler.js +18 -15
  46. package/dist/transport/http/render.d.ts +4 -0
  47. package/dist/transport/http/render.js +11 -3
  48. package/dist/transport/websocket/session.d.ts +3 -2
  49. package/dist/transport/websocket/session.js +15 -15
  50. package/dist/transport/websocket/upgrade.d.ts +2 -2
  51. package/dist/transport/websocket/upgrade.js +2 -4
  52. package/dist/types.d.ts +19 -4
  53. package/package.json +7 -8
  54. package/dist/publication/delivery/handler.d.ts +0 -9
  55. package/dist/publication/delivery/types.d.ts +0 -11
  56. package/dist/publication/index.d.ts +0 -3
  57. package/dist/publication/index.js +0 -2
  58. package/dist/publication/table.d.ts +0 -4
  59. /package/dist/{publication → release}/delivery/index.d.ts +0 -0
  60. /package/dist/{publication → release}/delivery/index.js +0 -0
  61. /package/dist/{publication → release}/delivery/types.js +0 -0
  62. /package/dist/{publication → release/legacy}/types.js +0 -0
package/dist/cors/cors.js CHANGED
@@ -62,12 +62,18 @@ export function prepareCors(input) {
62
62
  return new Response(null, { status: 403, headers: response(request) });
63
63
  }
64
64
  const allowedMethods = [...routeMethods].filter((method) => methods?.has(method) ?? true);
65
- const allowedHeaders = requestedHeaders.length > 0 ? requestedHeaders : [...routeHeaders];
65
+ // Advertise the whole header allowance, not an echo of the request: Chromium and WebKit keep
66
+ // one preflight result per origin and URL, so an echo would preflight again whenever a later
67
+ // call adds a permitted header. Requested names, already admitted, add only what a
68
+ // wildcard-header route cannot enumerate.
69
+ const allowedHeaders = new Set([...routeHeaders].filter((name) => headers?.has(name) ?? true));
70
+ for (const name of requestedHeaders)
71
+ allowedHeaders.add(name);
66
72
  const output = new Headers(response(request));
67
73
  output.set('access-control-allow-methods', [...allowedMethods, 'OPTIONS'].join(', '));
68
74
  output.set('access-control-max-age', String(PREFLIGHT_MAX_AGE_SECONDS));
69
- if (allowedHeaders.length > 0) {
70
- output.set('access-control-allow-headers', allowedHeaders.join(', '));
75
+ if (allowedHeaders.size > 0) {
76
+ output.set('access-control-allow-headers', [...allowedHeaders].join(', '));
71
77
  }
72
78
  appendVary(output, 'access-control-request-method');
73
79
  appendVary(output, 'access-control-request-headers');
package/dist/index.d.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  export { createServer, prepareServer } from './server.js';
2
2
  export { DeliveryUnavailableError, IssuerKeysUnavailableError } from './errors/index.js';
3
- export { preparePublicationSnapshot } from './publication/index.js';
4
- export * as publication from './publication/index.js';
5
- export type { Application, ApplicationHost, CorsConfig, ExecutionControl, Handler, HandlerRequest, HandlerResult, HandlerStream, HealthProbe, InvocationContext, InvocationSource, RequestBackground, RequestEvidence, RequestSleep, Issuer, exchange, PreparedServer, PublicationSnapshot, PublicationSnapshotInput, PublicationSource, Server, ServerCloseOptions, ServerConfig, ServerLimits, ServerOptions, WebSocketAdapter, WebSocketHandlers, WebSocketLike, WebSocketUpgradeOptions, } from './types.js';
3
+ export { releaseProjectionRouteDecision, releaseRouteDecision, type RouteDecision, } from './invocation/index.js';
4
+ export { prepareReleaseSnapshot } from './release/index.js';
5
+ export * as release from './release/index.js';
6
+ /** @deprecated Successor: `prepareReleaseSnapshot`; serves v3-input destinations only. */
7
+ export { preparePublicationSnapshot } from './release/legacy/index.js';
8
+ /** @deprecated Successor: `release`; the Publication snapshot of a v3-input destination. */
9
+ export * as publication from './release/legacy/index.js';
10
+ export type { Application, ApplicationHost, CorsConfig, ExecutionControl, Handler, HandlerRequest, HandlerResult, HandlerStream, HealthProbe, InvocationContext, InvocationSource, RequestBackground, RequestEvidence, RequestSleep, Issuer, exchange, PreparedServer, PublicationSnapshot, PublicationSnapshotInput, PublicationSource, ReleaseSnapshot, ReleaseSnapshotInput, ReleaseSource, Server, ServerCloseOptions, ServerConfig, ServerLimits, ServerOptions, WebSocketAdapter, WebSocketHandlers, WebSocketLike, WebSocketUpgradeOptions, } from './types.js';
package/dist/index.js CHANGED
@@ -1,4 +1,9 @@
1
1
  export { createServer, prepareServer } from './server.js';
2
2
  export { DeliveryUnavailableError, IssuerKeysUnavailableError } from './errors/index.js';
3
- export { preparePublicationSnapshot } from './publication/index.js';
4
- export * as publication from './publication/index.js';
3
+ export { releaseProjectionRouteDecision, releaseRouteDecision, } from './invocation/index.js';
4
+ export { prepareReleaseSnapshot } from './release/index.js';
5
+ export * as release from './release/index.js';
6
+ /** @deprecated Successor: `prepareReleaseSnapshot`; serves v3-input destinations only. */
7
+ export { preparePublicationSnapshot } from './release/legacy/index.js';
8
+ /** @deprecated Successor: `release`; the Publication snapshot of a v3-input destination. */
9
+ export * as publication from './release/legacy/index.js';
@@ -1,4 +1,4 @@
1
1
  export { defaultSleep, invocationContext } from './context.js';
2
2
  export { captureInvocationSource, type InvocationSource, type NextInvocationId, } from './identity.js';
3
- export { publicationRouteDecision, type PublicationRouteDecision } from './publication-route.js';
3
+ export { admitVia, releaseProjectionRouteDecision, releaseRouteDecision, type RouteDecision, } from './release-route.js';
4
4
  export type { ApplicationHost, ExecutionControl, Handler, HandlerRequest, HandlerResult, HandlerStream, InvocationContext, RequestBackground, RequestEvidence, RequestSleep, } from './types.js';
@@ -1,3 +1,3 @@
1
1
  export { defaultSleep, invocationContext } from './context.js';
2
2
  export { captureInvocationSource, } from './identity.js';
3
- export { publicationRouteDecision } from './publication-route.js';
3
+ export { admitVia, releaseProjectionRouteDecision, releaseRouteDecision, } from './release-route.js';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Routed admission at a v3-input destination: #508 same-schema and schema-drift continuity
3
+ * (`publicationRouteDecision`) admits a `PublicationVia` whose ETag or Schema differs from the
4
+ * served Publication while origin and issuer stay equal, so a legacy stable Worker keeps serving
5
+ * the installations made before the release snapshot with its latest code (Routage [.57929]). It
6
+ * applies only to a Server built from a v2/v3 Publication, never to a release snapshot.
7
+ *
8
+ * @deprecated Use `releaseRouteDecision` and `releaseProjectionRouteDecision`
9
+ * (`../release-route.ts`), which admit only the exact served release or its exact projection and
10
+ * answer every other relation with DEPLOYMENT_RETIRED.
11
+ *
12
+ * Short-term consumers: the v3-input destinations of `server/release/legacy/` (legacy direct-mode
13
+ * Workers, SDK Workers without a bound build digest, the Host's intrinsic publishers).
14
+ *
15
+ * Removal, in the separate breaking release that deletes `server/release/legacy/`, under its
16
+ * condition. Then delete this folder, its laws (SV-PUBLICATION-CONTINUITY,
17
+ * SV-PUBLICATION-ADMITS-LEGACY-VIA) and the `publication` branch of `admitVia`
18
+ * (`../release-route.ts`).
19
+ */
20
+ export { publicationRouteDecision, type PublicationRouteDecision } from './publication-route.js';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Routed admission at a v3-input destination: #508 same-schema and schema-drift continuity
3
+ * (`publicationRouteDecision`) admits a `PublicationVia` whose ETag or Schema differs from the
4
+ * served Publication while origin and issuer stay equal, so a legacy stable Worker keeps serving
5
+ * the installations made before the release snapshot with its latest code (Routage [.57929]). It
6
+ * applies only to a Server built from a v2/v3 Publication, never to a release snapshot.
7
+ *
8
+ * @deprecated Use `releaseRouteDecision` and `releaseProjectionRouteDecision`
9
+ * (`../release-route.ts`), which admit only the exact served release or its exact projection and
10
+ * answer every other relation with DEPLOYMENT_RETIRED.
11
+ *
12
+ * Short-term consumers: the v3-input destinations of `server/release/legacy/` (legacy direct-mode
13
+ * Workers, SDK Workers without a bound build digest, the Host's intrinsic publishers).
14
+ *
15
+ * Removal, in the separate breaking release that deletes `server/release/legacy/`, under its
16
+ * condition. Then delete this folder, its laws (SV-PUBLICATION-CONTINUITY,
17
+ * SV-PUBLICATION-ADMITS-LEGACY-VIA) and the `publication` branch of `admitVia`
18
+ * (`../release-route.ts`).
19
+ */
20
+ export { publicationRouteDecision } from './publication-route.js';
@@ -1,11 +1,16 @@
1
1
  import type { ErrorPayload } from '@astrale-os/kernel-protocol/errors';
2
2
  import type { Publication } from '@astrale-os/kernel-protocol/publication/publication';
3
3
  import type { PublicationRef } from '@astrale-os/kernel-protocol/publication/reference';
4
+ /** @deprecated Successor: `RouteDecision`, which also answers a retired deployment. */
4
5
  export type PublicationRouteDecision = {
5
6
  readonly kind: 'admitted';
6
7
  } | {
7
8
  readonly kind: 'rejected';
8
9
  readonly error: ErrorPayload;
9
10
  };
10
- /** Compare untrusted installed routing evidence with the one captured serving Publication. */
11
+ /**
12
+ * Accept ETag or Schema changes only while origin and issuer remain equal.
13
+ *
14
+ * @deprecated Use `releaseRouteDecision` or `releaseProjectionRouteDecision` for exact release admission.
15
+ */
11
16
  export declare function publicationRouteDecision(current: Publication, installed: PublicationRef): PublicationRouteDecision;
@@ -1,7 +1,11 @@
1
1
  import { CODES } from '@astrale-os/kernel-protocol/errors';
2
2
  import { SERVICE_TRUST_REQUIRED } from '@astrale-os/kernel-protocol/invocation/service-reason';
3
3
  import { compareReference } from '@astrale-os/kernel-protocol/publication/reference';
4
- /** Compare untrusted installed routing evidence with the one captured serving Publication. */
4
+ /**
5
+ * Accept ETag or Schema changes only while origin and issuer remain equal.
6
+ *
7
+ * @deprecated Use `releaseRouteDecision` or `releaseProjectionRouteDecision` for exact release admission.
8
+ */
5
9
  export function publicationRouteDecision(current, installed) {
6
10
  switch (compareReference(current, installed)) {
7
11
  case 'exact':
@@ -0,0 +1,26 @@
1
+ import type { ErrorPayload } from '@astrale-os/kernel-protocol/errors';
2
+ import type { PublicationVia, Via } from '@astrale-os/kernel-protocol/invocation';
3
+ import type { DomainRelease, ReleaseDigest, ReleaseRef } from '@astrale-os/kernel-protocol/release';
4
+ import type { PublicationRef } from '@astrale-os/kernel-protocol/release/legacy';
5
+ import type { Served } from '../release/served.js';
6
+ import type { ReleaseSnapshot } from '../release/types.js';
7
+ export type RouteDecision = {
8
+ readonly kind: 'admitted';
9
+ } | {
10
+ readonly kind: 'rejected';
11
+ readonly error: ErrorPayload;
12
+ }
13
+ /** DEPLOYMENT_RETIRED includes the requested release only when the witness identifies it. */
14
+ | {
15
+ readonly kind: 'retired';
16
+ readonly release?: ReleaseDigest;
17
+ };
18
+ /**
19
+ * Only the exact served release admits. Never report the served digest as retired when
20
+ * the witness pairs it with a different revision or issuer.
21
+ */
22
+ export declare function releaseRouteDecision(current: DomainRelease, installed: ReleaseRef): RouteDecision;
23
+ /** Only the exact v3 projection reference admits, including its ETag, Schema and issuer. */
24
+ export declare function releaseProjectionRouteDecision(current: ReleaseSnapshot, installed: PublicationRef): RouteDecision;
25
+ /** Admit routing before credential resolution; Publication sources accept only legacy witnesses. */
26
+ export declare function admitVia(served: Served | undefined, via: Via | PublicationVia): RouteDecision;
@@ -0,0 +1,55 @@
1
+ import { CODES } from '@astrale-os/kernel-protocol/errors';
2
+ import { compareReferences, reference } from '@astrale-os/kernel-protocol/release';
3
+ import { compareReference } from '@astrale-os/kernel-protocol/release/legacy';
4
+ import { publicationRouteDecision } from './legacy/index.js';
5
+ const ADMITTED = Object.freeze({ kind: 'admitted' });
6
+ const RETIRED = Object.freeze({ kind: 'retired' });
7
+ /**
8
+ * Only the exact served release admits. Never report the served digest as retired when
9
+ * the witness pairs it with a different revision or issuer.
10
+ */
11
+ export function releaseRouteDecision(current, installed) {
12
+ switch (compareReferences(reference(current), installed)) {
13
+ case 'exact':
14
+ return ADMITTED;
15
+ case 'release-drift':
16
+ return installed.digest === current.digest
17
+ ? RETIRED
18
+ : Object.freeze({ kind: 'retired', release: installed.digest });
19
+ case 'origin-drift':
20
+ return invalid('Routed release origin differs from the served Domain.');
21
+ }
22
+ }
23
+ /** Only the exact v3 projection reference admits, including its ETag, Schema and issuer. */
24
+ export function releaseProjectionRouteDecision(current, installed) {
25
+ switch (compareReference(current.publication, installed)) {
26
+ case 'exact':
27
+ return ADMITTED;
28
+ case 'same-schema':
29
+ case 'schema-drift':
30
+ case 'identity-drift':
31
+ return RETIRED;
32
+ case 'origin-drift':
33
+ return invalid('Routed Publication origin differs from the served Domain.');
34
+ }
35
+ }
36
+ /** Admit routing before credential resolution; Publication sources accept only legacy witnesses. */
37
+ export function admitVia(served, via) {
38
+ if ('release' in via) {
39
+ return served?.kind === 'release'
40
+ ? releaseRouteDecision(served.release, via.release)
41
+ : invalid('Release routing requires a release-backed destination.');
42
+ }
43
+ if (served === undefined) {
44
+ return invalid('Publication routing requires a Publication-backed destination.');
45
+ }
46
+ return served.kind === 'release'
47
+ ? releaseProjectionRouteDecision(served.snapshot, via.publication)
48
+ : publicationRouteDecision(served.publication, via.publication);
49
+ }
50
+ function invalid(message) {
51
+ return Object.freeze({
52
+ kind: 'rejected',
53
+ error: Object.freeze({ code: CODES.INVALID_REQUEST, message }),
54
+ });
55
+ }
@@ -2,7 +2,8 @@ import type { CredentialInput } from '@astrale-os/kernel-core/auth';
2
2
  import type { BinaryRequestBody, CallableResult } from '@astrale-os/kernel-core/callable';
3
3
  import type { Value } from '@astrale-os/kernel-dsl/value';
4
4
  import type * as protocol from '@astrale-os/kernel-protocol/invocation';
5
- import type { PublicationSnapshot } from '../publication/index.js';
5
+ import type { ReleaseSnapshot } from '../release/index.js';
6
+ import type { PublicationSnapshot } from '../release/legacy/index.js';
6
7
  /** Optional platform retention and timer adaptation for one Fetch invocation. */
7
8
  export interface ApplicationHost {
8
9
  waitUntil(promise: Promise<unknown>): void;
@@ -51,11 +52,22 @@ export interface HandlerRequest {
51
52
  readonly credential?: CredentialInput;
52
53
  /** Source-only effective delegation forwarded unchanged to Runtime. */
53
54
  readonly delegate?: protocol.Delegate;
54
- /** Destination routing witness forwarded after same-origin, same-identity Server admission. */
55
- readonly via?: protocol.Via;
55
+ /**
56
+ * Destination routing witness forwarded after Server admission: at a release-backed destination
57
+ * a `Via` naming its exact release or a `PublicationVia` naming its exact v3 projection; at a
58
+ * v3-input destination only a `PublicationVia`.
59
+ */
60
+ readonly via?: protocol.Via | protocol.PublicationVia;
56
61
  /** Source-only local-target or remote-revision Schema Registration precondition. */
57
62
  readonly schema?: protocol.SchemaTarget;
58
- /** Exact Server-pinned serving generation for a Publication-backed request. */
63
+ /** Exact Server-pinned release for a request at a release-backed destination. */
64
+ readonly release?: ReleaseSnapshot;
65
+ /**
66
+ * Exact Server-pinned Publication snapshot for a request at a v3-input destination.
67
+ *
68
+ * @deprecated Successor: `release`. Present only on a Server built from a v2/v3 Publication;
69
+ * deleted with `server/release/legacy/`.
70
+ */
59
71
  readonly publication?: PublicationSnapshot;
60
72
  readonly context: InvocationContext;
61
73
  }
@@ -2,6 +2,7 @@ import { configuration } from '@astrale-os/kernel-protocol/issuer/configuration'
2
2
  import { hosted } from '@astrale-os/kernel-protocol/issuer/hosted';
3
3
  import { keySet } from '@astrale-os/kernel-protocol/issuer/key-set';
4
4
  import { paths as issuerPaths } from '@astrale-os/kernel-protocol/issuer/paths';
5
+ import { workflowEngine } from '@astrale-os/kernel-protocol/issuer/workflow-engine';
5
6
  import { admissionRefusal, IssuerKeysUnavailableError, retryAfterHeaders } from '../errors/index.js';
6
7
  import { mount } from './exchange.js';
7
8
  export function issuerRouteReservations(input, tokenExchange = false) {
@@ -13,6 +14,9 @@ export function issuerRouteReservations(input, tokenExchange = false) {
13
14
  if (tokenExchange) {
14
15
  reservations.push(Object.freeze({ path: paths.tokenExchange, methods: Object.freeze(['POST']) }));
15
16
  }
17
+ if (input.workflowEngineKeys !== undefined) {
18
+ reservations.push(Object.freeze({ path: paths.workflowEngine, methods: Object.freeze(['GET']) }));
19
+ }
16
20
  if (input.loadIssuerPublicKeys !== undefined) {
17
21
  const base = new URL(input.issuer).pathname.replace(/\/+$/u, '');
18
22
  reservations.push(Object.freeze({
@@ -50,6 +54,10 @@ export function mountIssuer(app, input, cors, active, timeoutMs, tokenExchange)
50
54
  tokenExchange: tokenExchange !== undefined,
51
55
  });
52
56
  app.get(paths.configuration, (context) => context.json(discovery, 200, headers(context.req.raw)));
57
+ if (input.workflowEngineKeys !== undefined) {
58
+ const engine = workflowEngine(issuer, input.workflowEngineKeys);
59
+ app.get(paths.workflowEngine, (context) => context.json(engine, 200, headers(context.req.raw)));
60
+ }
53
61
  if (cors.enabled) {
54
62
  app.options(paths.configuration, (context) => cors.preflight(context.req.raw, { methods: ['GET'] }));
55
63
  app.options(paths.keys, (context) => cors.preflight(context.req.raw, { methods: ['GET'] }));
@@ -152,9 +160,14 @@ export function admitIssuer(input) {
152
160
  }
153
161
  const loadPublicKeys = input.loadPublicKeys;
154
162
  const loadIssuerPublicKeys = input.loadIssuerPublicKeys;
163
+ // A Kernel that drives an engine publishes it; invalid keys fail closed at boot.
164
+ const workflowEngineKeys = input.workflowEngineKeys === undefined
165
+ ? undefined
166
+ : workflowEngine(issuer, input.workflowEngineKeys).identity_keys;
155
167
  return Object.freeze({
156
168
  issuer,
157
169
  signingAlgorithm,
170
+ ...(workflowEngineKeys === undefined ? {} : { workflowEngineKeys }),
158
171
  loadPublicKeys: (signal) => loadPublicKeys.call(input, signal),
159
172
  ...(loadIssuerPublicKeys === undefined
160
173
  ? {}
@@ -8,4 +8,9 @@ export interface Issuer {
8
8
  loadPublicKeys(signal: AbortSignal): Promise<readonly JsonWebKey[]>;
9
9
  /** Optional hosted-key discovery for issuers below `<issuer>/iss/:thumbprint`. */
10
10
  readonly loadIssuerPublicKeys?: (issuer: IssuerId, signal: AbortSignal) => Promise<readonly JsonWebKey[]>;
11
+ /**
12
+ * Request identity keys of the workflow engine this Kernel drives, published at
13
+ * `<issuer>/.well-known/astrale/workflow-engine.json` so Domains verify the engine's requests.
14
+ */
15
+ readonly workflowEngineKeys?: readonly string[];
11
16
  }
@@ -0,0 +1,13 @@
1
+ import type { ActiveOperations } from '../../lifecycle/index.js';
2
+ import type { Served } from '../served.js';
3
+ import type { Provider } from './types.js';
4
+ export interface Limits {
5
+ readonly maximumDeliveryBytes: number;
6
+ readonly invocationTimeoutMs: number;
7
+ }
8
+ /**
9
+ * Serve one delivery request against the exact snapshot that selected its URL, in the request's
10
+ * version: Delivery v2 names the served release, Delivery v1 the served Publication (a release's
11
+ * frozen v3 projection). A v3-input destination keeps its Delivery v1 decoder.
12
+ */
13
+ export declare function handleDelivery(request: Request, snapshot: Served, provider: Provider | undefined, active: ActiveOperations, limits: Limits, corsFor: (exposedHeaders?: readonly string[]) => Readonly<Record<string, string>>): Promise<Response>;
@@ -1,10 +1,16 @@
1
1
  import * as codecs from '@astrale-os/kernel-protocol/codecs';
2
- import * as protocolPublication from '@astrale-os/kernel-protocol/publication';
2
+ import * as release from '@astrale-os/kernel-protocol/release';
3
3
  import { admissionRefusal, DeliveryUnavailableError, retryAfterHeaders, } from '../../errors/index.js';
4
4
  import { RequestBodyInvalidError, RequestBodyTooLargeError } from '../../errors/index.js';
5
5
  import { readBoundedBody } from '../../transport/index.js';
6
- const delivery = protocolPublication.delivery;
7
- /** Serve one delivery request against the exact Publication snapshot that selected its URL. */
6
+ import { servedDocument } from '../served.js';
7
+ const delivery = release.delivery;
8
+ const publicationDelivery = release.legacy.delivery;
9
+ /**
10
+ * Serve one delivery request against the exact snapshot that selected its URL, in the request's
11
+ * version: Delivery v2 names the served release, Delivery v1 the served Publication (a release's
12
+ * frozen v3 projection). A v3-input destination keeps its Delivery v1 decoder.
13
+ */
8
14
  export async function handleDelivery(request, snapshot, provider, active, limits, corsFor) {
9
15
  const cors = corsFor();
10
16
  if (request.method !== 'POST') {
@@ -13,7 +19,7 @@ export async function handleDelivery(request, snapshot, provider, active, limits
13
19
  if (provider === undefined) {
14
20
  return response({ error: 'Delivery is unavailable.' }, 503, cors);
15
21
  }
16
- const advertised = snapshot.publication.delivery;
22
+ const advertised = servedDocument(snapshot).delivery;
17
23
  if (advertised === undefined) {
18
24
  return response({ error: 'Delivery is unavailable.' }, 404, cors);
19
25
  }
@@ -60,7 +66,10 @@ export async function handleDelivery(request, snapshot, provider, active, limits
60
66
  }
61
67
  let input;
62
68
  try {
63
- input = delivery.acceptRequest(decoded);
69
+ input =
70
+ snapshot.kind === 'release'
71
+ ? delivery.acceptRequest(decoded)
72
+ : publicationDelivery.acceptRequest(decoded);
64
73
  }
65
74
  catch (cause) {
66
75
  if (cause instanceof delivery.Error && cause.code === 'DELIVERY_REQUEST_INVALID') {
@@ -68,8 +77,13 @@ export async function handleDelivery(request, snapshot, provider, active, limits
68
77
  }
69
78
  throw cause;
70
79
  }
71
- if (input.etag !== snapshot.publication.etag) {
72
- return response({ error: 'Publication ETag differs from request.' }, 409, cors);
80
+ const named = input.version === 2 ? input.release : input.etag;
81
+ const served = input.version === 2 ? servedRelease(snapshot) : snapshot.publication.etag;
82
+ if (named !== served) {
83
+ const error = input.version === 2
84
+ ? 'Release digest differs from request.'
85
+ : 'Publication ETag differs from request.';
86
+ return response({ error }, 409, cors);
73
87
  }
74
88
  let candidate;
75
89
  try {
@@ -86,7 +100,8 @@ export async function handleDelivery(request, snapshot, provider, active, limits
86
100
  }
87
101
  let created;
88
102
  try {
89
- created = delivery.accept(candidate);
103
+ created =
104
+ input.version === 2 ? delivery.accept(candidate) : publicationDelivery.accept(candidate);
90
105
  }
91
106
  catch (cause) {
92
107
  if (cause instanceof delivery.Error) {
@@ -94,8 +109,12 @@ export async function handleDelivery(request, snapshot, provider, active, limits
94
109
  }
95
110
  throw cause;
96
111
  }
97
- if (created.publication.etag !== snapshot.publication.etag) {
98
- return response({ error: 'Delivery provider returned a different Publication.' }, 500, cors);
112
+ const delivered = created.version === 2 ? created.release.digest : created.publication.etag;
113
+ if (delivered !== served) {
114
+ const error = created.version === 2
115
+ ? 'Delivery provider returned a different release.'
116
+ : 'Delivery provider returned a different Publication.';
117
+ return response({ error }, 500, cors);
99
118
  }
100
119
  const encoded = codecs.json.encode(created);
101
120
  if (encodedSize(encoded) > limits.maximumDeliveryBytes) {
@@ -109,6 +128,10 @@ export async function handleDelivery(request, snapshot, provider, active, limits
109
128
  operation.settle();
110
129
  }
111
130
  }
131
+ /** Digest a Delivery v2 must name: only a release-backed destination serves one. */
132
+ function servedRelease(snapshot) {
133
+ return snapshot.kind === 'release' ? snapshot.release.digest : undefined;
134
+ }
112
135
  function bearer(input) {
113
136
  if (input === null)
114
137
  return undefined;
@@ -0,0 +1,21 @@
1
+ import type * as release from '@astrale-os/kernel-protocol/release';
2
+ export interface Context {
3
+ /** Omitted for a public endpoint; otherwise the exact admitted retrieval credential. */
4
+ readonly token?: release.delivery.Token;
5
+ readonly signal: AbortSignal;
6
+ }
7
+ /** Provider-owned retrieval, construction, and attestation signing. */
8
+ export interface Provider {
9
+ /**
10
+ * Answer in the request's version: a Delivery v2 carrying the served release for a v2 request,
11
+ * and a Delivery v1 carrying the served Publication (at a release-backed destination, its frozen
12
+ * v3 projection) for a deprecated v1 request. A v3-input destination passes only v1 requests.
13
+ * Throw DeliveryUnavailableError only for an expected dependency outage.
14
+ *
15
+ * A release-backed destination (`ServerOptions.release`) passes v2 requests, so its provider
16
+ * must answer both versions. The deprecated v1-only `publication.delivery.Provider` still
17
+ * type-checks here, because method parameters are compared bivariantly, but every v2 request
18
+ * it receives fails with 500 `Delivery provider returned an invalid response.`
19
+ */
20
+ create(request: release.delivery.Request | release.legacy.delivery.Request, context: Context): Promise<release.delivery.Delivery | release.legacy.delivery.Delivery>;
21
+ }
@@ -0,0 +1,3 @@
1
+ export { prepareReleaseSnapshot } from './snapshot.js';
2
+ export type { ReleaseSnapshot, ReleaseSnapshotInput, ReleaseSource } from './types.js';
3
+ export * as delivery from './delivery/index.js';
@@ -0,0 +1,2 @@
1
+ export { prepareReleaseSnapshot } from './snapshot.js';
2
+ export * as delivery from './delivery/index.js';
@@ -0,0 +1,18 @@
1
+ import type * as release from '@astrale-os/kernel-protocol/release';
2
+ import type { Context as DeliveryContext } from '../delivery/types.js';
3
+ /** @deprecated Successor: `release.delivery.Context`, the same retrieval context. */
4
+ export type Context = DeliveryContext;
5
+ /**
6
+ * The Delivery v1 provider of a v3-input destination, which only ever receives v1 requests. It
7
+ * remains assignable to `release.delivery.Provider`, so existing providers keep configuring a
8
+ * Server unchanged. Pass it only with `ServerOptions.publication`: a release-backed destination
9
+ * also passes Delivery v2 requests, which this provider cannot answer.
10
+ *
11
+ * @deprecated Successor: `release.delivery.Provider`, which answers a v2 request over the release
12
+ * and a v1 request over its frozen v3 projection.
13
+ */
14
+ export interface Provider {
15
+ create(request: release.legacy.delivery.Request, context: Context): Promise<release.legacy.delivery.Delivery>;
16
+ }
17
+ /** @deprecated Successor: `release.delivery.handleDelivery`, which serves both snapshots. */
18
+ export { handleDelivery, type Limits } from '../delivery/handler.js';
@@ -0,0 +1,2 @@
1
+ /** @deprecated Successor: `release.delivery.handleDelivery`, which serves both snapshots. */
2
+ export { handleDelivery } from '../delivery/handler.js';
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The Publication snapshot of a v3-input destination: a Server built from a v2/v3 Publication
3
+ * serves `domain.json`, its bundle, Delivery v1 and its native routes exactly as before the release
4
+ * snapshot, and keeps the #508 same-schema and schema-drift continuity
5
+ * (`server/invocation/legacy/publication-route.ts`). Its answers are pinned byte for byte by the
6
+ * golden transcript of `server/__tests__/transcripts.test.ts`.
7
+ *
8
+ * @deprecated Use `ServerOptions.release` with `prepareReleaseSnapshot`, which serves a
9
+ * `DomainRelease` at `release.json` beside its frozen v3 projection at `domain.json` and admits
10
+ * only exact routing.
11
+ *
12
+ * Short-term consumers:
13
+ * - the legacy direct-mode Workers built by an SDK that seals v2/v3 only: the domains platform
14
+ * Domains, Shell, the Services `development` environment and `ui:domain`, until they serve
15
+ * releases;
16
+ * - an SDK Worker whose deployment host binds no build digest, which serves today's v3 document;
17
+ * - the Host's intrinsic Kernel and Host Domain publishers (`host/kernel/publication/`) and the
18
+ * FalkorDB qualification remote Domain, until the Host serves its intrinsic release.
19
+ *
20
+ * Removal, in a separate breaking release: the conditions of `protocol/release/legacy/index.ts`
21
+ * hold (no stable Worker serves a v2/v3 document, no installation pins one), no supported SDK
22
+ * builds a v3-input Worker, and the intrinsic Kernel and Host Domains serve a release. Then
23
+ * delete, in the same PR:
24
+ * - this folder with its `.spec` (its v1-only Delivery `Provider` included), the `publication`
25
+ * namespace, `preparePublicationSnapshot` and the Publication snapshot types of
26
+ * `server/index.ts` and `server/.spec/api.d.ts`, and the `./publication/snapshot` subpath
27
+ * (source and `dist`) of `server/package.json`;
28
+ * - `ServerOptions.publication` (`server/types.ts`) and `HandlerRequest.publication`
29
+ * (`server/invocation/types.ts`), with the `publication` branch of `Served`
30
+ * (`server/release/served.ts`) and every `kind === 'publication'` branch it selects in
31
+ * `server/routes/routes.ts`, `server/release/delivery/handler.ts` and `admitVia`;
32
+ * - `server/invocation/legacy/` (laws SV-PUBLICATION-CONTINUITY, SV-PUBLICATION-ADMITS-LEGACY-VIA);
33
+ * - the golden transcript that records a v3-input server, keeping the one of a server without a
34
+ * source.
35
+ */
36
+ export { preparePublicationSnapshot, readPreparedPublicationSnapshot, type PreparedPublicationSnapshot, } from './snapshot.js';
37
+ export type { PublicationSnapshot, PublicationSnapshotInput, PublicationSource } from './types.js';
38
+ /** @deprecated The Delivery v1 boundary of a v3-input destination; use `release.delivery`. */
39
+ export * as delivery from './delivery.js';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The Publication snapshot of a v3-input destination: a Server built from a v2/v3 Publication
3
+ * serves `domain.json`, its bundle, Delivery v1 and its native routes exactly as before the release
4
+ * snapshot, and keeps the #508 same-schema and schema-drift continuity
5
+ * (`server/invocation/legacy/publication-route.ts`). Its answers are pinned byte for byte by the
6
+ * golden transcript of `server/__tests__/transcripts.test.ts`.
7
+ *
8
+ * @deprecated Use `ServerOptions.release` with `prepareReleaseSnapshot`, which serves a
9
+ * `DomainRelease` at `release.json` beside its frozen v3 projection at `domain.json` and admits
10
+ * only exact routing.
11
+ *
12
+ * Short-term consumers:
13
+ * - the legacy direct-mode Workers built by an SDK that seals v2/v3 only: the domains platform
14
+ * Domains, Shell, the Services `development` environment and `ui:domain`, until they serve
15
+ * releases;
16
+ * - an SDK Worker whose deployment host binds no build digest, which serves today's v3 document;
17
+ * - the Host's intrinsic Kernel and Host Domain publishers (`host/kernel/publication/`) and the
18
+ * FalkorDB qualification remote Domain, until the Host serves its intrinsic release.
19
+ *
20
+ * Removal, in a separate breaking release: the conditions of `protocol/release/legacy/index.ts`
21
+ * hold (no stable Worker serves a v2/v3 document, no installation pins one), no supported SDK
22
+ * builds a v3-input Worker, and the intrinsic Kernel and Host Domains serve a release. Then
23
+ * delete, in the same PR:
24
+ * - this folder with its `.spec` (its v1-only Delivery `Provider` included), the `publication`
25
+ * namespace, `preparePublicationSnapshot` and the Publication snapshot types of
26
+ * `server/index.ts` and `server/.spec/api.d.ts`, and the `./publication/snapshot` subpath
27
+ * (source and `dist`) of `server/package.json`;
28
+ * - `ServerOptions.publication` (`server/types.ts`) and `HandlerRequest.publication`
29
+ * (`server/invocation/types.ts`), with the `publication` branch of `Served`
30
+ * (`server/release/served.ts`) and every `kind === 'publication'` branch it selects in
31
+ * `server/routes/routes.ts`, `server/release/delivery/handler.ts` and `admitVia`;
32
+ * - `server/invocation/legacy/` (laws SV-PUBLICATION-CONTINUITY, SV-PUBLICATION-ADMITS-LEGACY-VIA);
33
+ * - the golden transcript that records a v3-input server, keeping the one of a server without a
34
+ * source.
35
+ */
36
+ export { preparePublicationSnapshot, readPreparedPublicationSnapshot, } from './snapshot.js';
37
+ /** @deprecated The Delivery v1 boundary of a v3-input destination; use `release.delivery`. */
38
+ export * as delivery from './delivery.js';
@@ -1,16 +1,26 @@
1
1
  import type { Domain } from '@astrale-os/kernel-dsl/v1';
2
2
  import type * as publication from '@astrale-os/kernel-protocol/publication';
3
- import type * as routes from '@astrale-os/kernel-protocol/routes';
3
+ import type { Table } from '@astrale-os/kernel-protocol/routes/legacy';
4
4
  import type { PublicationSnapshot, PublicationSnapshotInput } from './types.js';
5
+ /** @deprecated Successor: `PreparedReleaseSnapshot`. */
5
6
  export interface PreparedPublicationSnapshot {
7
+ readonly kind: 'publication';
6
8
  readonly snapshot: PublicationSnapshot;
7
9
  readonly publication: publication.Publication;
8
10
  readonly publicationBytes: Uint8Array;
9
11
  readonly bundle: Uint8Array;
10
- readonly table: routes.Table;
12
+ readonly table: Table;
11
13
  readonly domain: Domain;
12
14
  }
13
- /** Admit and detach one revision-coherent serving snapshot before publication. */
15
+ /**
16
+ * Admit and detach one revision-coherent serving snapshot.
17
+ *
18
+ * @deprecated Use `prepareReleaseSnapshot` to serve a release and its v3 projection.
19
+ */
14
20
  export declare function preparePublicationSnapshot(input: PublicationSnapshotInput): PublicationSnapshot;
15
- /** Internal proof that a PublicationSource returned a Server-prepared snapshot without cloning it. */
21
+ /**
22
+ * Internal proof that a PublicationSource returned a Server-prepared snapshot without cloning it.
23
+ *
24
+ * @deprecated Successor: `readPreparedReleaseSnapshot`.
25
+ */
16
26
  export declare function readPreparedPublicationSnapshot(input: unknown): PreparedPublicationSnapshot | undefined;
@@ -3,7 +3,11 @@ import { verifyBundleBytes } from '@astrale-os/kernel-protocol/publication/bundl
3
3
  import { accept as acceptPublication, encode as encodePublication, } from '@astrale-os/kernel-protocol/publication/publication';
4
4
  import { admitTable } from './table.js';
5
5
  const snapshots = new WeakMap();
6
- /** Admit and detach one revision-coherent serving snapshot before publication. */
6
+ /**
7
+ * Admit and detach one revision-coherent serving snapshot.
8
+ *
9
+ * @deprecated Use `prepareReleaseSnapshot` to serve a release and its v3 projection.
10
+ */
7
11
  export function preparePublicationSnapshot(input) {
8
12
  if (input === null || typeof input !== 'object' || !(input.bundle instanceof Uint8Array)) {
9
13
  throw new TypeError('Publication snapshot input is invalid.');
@@ -28,6 +32,7 @@ export function preparePublicationSnapshot(input) {
28
32
  const publicationBytes = encodePublication(admittedPublication);
29
33
  const snapshot = Object.freeze({ publication: admittedPublication });
30
34
  const prepared = Object.freeze({
35
+ kind: 'publication',
31
36
  snapshot,
32
37
  publication: admittedPublication,
33
38
  publicationBytes,
@@ -38,7 +43,11 @@ export function preparePublicationSnapshot(input) {
38
43
  snapshots.set(snapshot, prepared);
39
44
  return snapshot;
40
45
  }
41
- /** Internal proof that a PublicationSource returned a Server-prepared snapshot without cloning it. */
46
+ /**
47
+ * Internal proof that a PublicationSource returned a Server-prepared snapshot without cloning it.
48
+ *
49
+ * @deprecated Successor: `readPreparedReleaseSnapshot`.
50
+ */
42
51
  export function readPreparedPublicationSnapshot(input) {
43
52
  if (input === null || typeof input !== 'object')
44
53
  return undefined;