@astrale-os/kernel-client 0.6.0-beta.81 → 0.6.0-beta.83

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 (60) hide show
  1. package/dist/client/admission.js +1 -9
  2. package/dist/client/client.js +3 -1
  3. package/dist/client/index.d.ts +1 -1
  4. package/dist/client/index.js +1 -1
  5. package/dist/client/routing-control.d.ts +11 -3
  6. package/dist/client/routing-control.js +18 -3
  7. package/dist/errors/stale-route-error.d.ts +14 -3
  8. package/dist/errors/stale-route-error.js +15 -3
  9. package/dist/errors/transport-error.d.ts +3 -2
  10. package/dist/index.d.ts +1 -1
  11. package/dist/schema/introspection.js +10 -6
  12. package/dist/schema/schema.js +10 -2
  13. package/dist/session/content.js +4 -27
  14. package/dist/session/context/index.d.ts +2 -2
  15. package/dist/session/context/index.js +2 -2
  16. package/dist/session/context/limits.d.ts +1 -0
  17. package/dist/session/context/limits.js +1 -0
  18. package/dist/session/discovery/bundle.js +1 -1
  19. package/dist/session/discovery/discovery.d.ts +12 -4
  20. package/dist/session/discovery/discovery.js +28 -19
  21. package/dist/session/discovery/index.d.ts +8 -1
  22. package/dist/session/discovery/index.js +5 -8
  23. package/dist/session/discovery/legacy/index.d.ts +38 -0
  24. package/dist/session/discovery/legacy/index.js +38 -0
  25. package/dist/session/discovery/legacy/intrinsic.d.ts +33 -0
  26. package/dist/session/discovery/legacy/intrinsic.js +67 -0
  27. package/dist/session/discovery/legacy/projection.d.ts +8 -0
  28. package/dist/session/discovery/legacy/projection.js +17 -0
  29. package/dist/session/discovery/{publication.d.ts → legacy/publication.d.ts} +8 -5
  30. package/dist/session/discovery/{publication.js → legacy/publication.js} +13 -60
  31. package/dist/session/discovery/release.d.ts +29 -0
  32. package/dist/session/discovery/release.js +81 -0
  33. package/dist/session/discovery/response.d.ts +3 -0
  34. package/dist/session/discovery/response.js +59 -0
  35. package/dist/session/discovery/snapshot.d.ts +9 -2
  36. package/dist/session/discovery/snapshot.js +2 -2
  37. package/dist/session/discovery/transport.d.ts +1 -3
  38. package/dist/session/exchange/options.d.ts +12 -4
  39. package/dist/session/exchange/options.js +11 -3
  40. package/dist/session/exchange/presentation.d.ts +3 -2
  41. package/dist/session/index.d.ts +1 -1
  42. package/dist/session/reference.d.ts +3 -6
  43. package/dist/session/reference.js +0 -4
  44. package/dist/session/routing/cache.d.ts +1 -1
  45. package/dist/session/routing/cache.js +18 -2
  46. package/dist/session/routing/dispatch.js +42 -41
  47. package/dist/session/session.d.ts +21 -4
  48. package/dist/session/session.js +25 -13
  49. package/dist/session/session.typecheck.js +20 -0
  50. package/dist/transport/auto/transport.d.ts +0 -1
  51. package/dist/transport/auto/transport.js +0 -2
  52. package/dist/transport/http/response.d.ts +1 -1
  53. package/dist/transport/http/response.js +2 -6
  54. package/dist/transport/http/transport.d.ts +5 -3
  55. package/dist/transport/http/transport.js +40 -19
  56. package/dist/transport/index.d.ts +1 -1
  57. package/dist/transport/transport.d.ts +10 -2
  58. package/dist/transport/websocket/transport.d.ts +0 -3
  59. package/dist/transport/websocket/transport.js +1 -16
  60. package/package.json +6 -7
@@ -25,17 +25,9 @@ function admitPath(input) {
25
25
  }
26
26
  function admitInput(input) {
27
27
  try {
28
- return freezeValue(portable.accept(input));
28
+ return portable.acceptImmutable(input);
29
29
  }
30
30
  catch (cause) {
31
31
  throw new TypeError('Client input must be a portable Value.', { cause });
32
32
  }
33
33
  }
34
- function freezeValue(input) {
35
- if (input !== null && typeof input === 'object' && !Object.isFrozen(input)) {
36
- for (const value of Object.values(input))
37
- freezeValue(value);
38
- Object.freeze(input);
39
- }
40
- return input;
41
- }
@@ -3,7 +3,7 @@ import { HttpTransport } from '../transport/http/index.js';
3
3
  import { authenticated, resolveCredential } from './credential.js';
4
4
  import { admitRequest, invocation } from './request.js';
5
5
  import { requireBinary, requireStream, requireValue } from './result.js';
6
- import { admittedMissError, admittedStaleRouteError } from './routing-control.js';
6
+ import { admittedDeploymentRetiredError, admittedMissError, admittedStaleRouteError, } from './routing-control.js';
7
7
  const controlledDispatch = new WeakMap();
8
8
  /** Package-private redirect continuation path; intentionally absent from the public Client facade. */
9
9
  export function dispatchTrace(client, credential, call, options, trace) {
@@ -73,6 +73,8 @@ export class Client {
73
73
  throw admittedStaleRouteError(result.current, result.invocation);
74
74
  if (result.kind === 'miss')
75
75
  throw admittedMissError(result.invocation);
76
+ if (result.kind === 'deployment-retired')
77
+ throw admittedDeploymentRetiredError(result);
76
78
  return result;
77
79
  }
78
80
  }
@@ -3,4 +3,4 @@ export { acceptCall } from './admission.js';
3
3
  export type { AuthenticatedClient, RemoteCredential } from './credential.js';
4
4
  export { call, type Call, type ClientDispatchOptions, type ClientDispatchOptionsWithBody, } from './request.js';
5
5
  export type { Binary, BinaryResponse, Input, RedirectResult, Result, Stream, StreamResult, ValueResult, } from './result.js';
6
- export { isAdmittedMissError, isAdmittedStaleRouteError } from './routing-control.js';
6
+ export { isAdmittedDeploymentRetiredError, isAdmittedMissError, isAdmittedStaleRouteError, } from './routing-control.js';
@@ -1,4 +1,4 @@
1
1
  export { Client, dispatchTrace } from './client.js';
2
2
  export { acceptCall } from './admission.js';
3
3
  export { call, } from './request.js';
4
- export { isAdmittedMissError, isAdmittedStaleRouteError } from './routing-control.js';
4
+ export { isAdmittedDeploymentRetiredError, isAdmittedMissError, isAdmittedStaleRouteError, } from './routing-control.js';
@@ -1,10 +1,18 @@
1
- import type { invocation as protocol, publication } from '@astrale-os/kernel-protocol';
2
- import { MissError, StaleRouteError } from '../errors/index.js';
1
+ import type { invocation as protocol, release } from '@astrale-os/kernel-protocol';
2
+ import type { DeploymentRetired } from '../transport/index.js';
3
+ import { MissError, ResponseError, StaleRouteError } from '../errors/index.js';
3
4
  /** Create a private routing witness only after exact reserved-control decoding. */
4
5
  export declare function admittedMissError(invocation: protocol.InvocationId): MissError;
5
6
  /** Create a private routing witness only after exact reserved-control decoding. */
6
- export declare function admittedStaleRouteError(currentEtag: publication.Etag, invocation: protocol.InvocationId): StaleRouteError;
7
+ export declare function admittedStaleRouteError(current: release.Sha256Digest, invocation: protocol.InvocationId): StaleRouteError;
8
+ /**
9
+ * Raise one decoded retirement control as the typed error every Client reports for it,
10
+ * `ResponseError(3002, DEPLOYMENT_RETIRED)`, privately witnessed for Session recovery.
11
+ */
12
+ export declare function admittedDeploymentRetiredError(control: DeploymentRetired): ResponseError;
7
13
  /** Distinguish decoded routing control from public or caller-supplied lookalike errors. */
8
14
  export declare function isAdmittedMissError(input: unknown): input is MissError;
9
15
  /** Distinguish decoded routing control from public or caller-supplied lookalike errors. */
10
16
  export declare function isAdmittedStaleRouteError(input: unknown): input is StaleRouteError;
17
+ /** Distinguish a decoded retirement control from a lookalike `DEPLOYMENT_RETIRED` response. */
18
+ export declare function isAdmittedDeploymentRetiredError(input: unknown): input is ResponseError;
@@ -1,6 +1,8 @@
1
- import { MissError, StaleRouteError } from '../errors/index.js';
1
+ import { CODES } from '@astrale-os/kernel-protocol/errors';
2
+ import { MissError, ResponseError, StaleRouteError } from '../errors/index.js';
2
3
  const admittedMisses = new WeakSet();
3
4
  const admittedStaleRoutes = new WeakSet();
5
+ const admittedRetirements = new WeakSet();
4
6
  /** Create a private routing witness only after exact reserved-control decoding. */
5
7
  export function admittedMissError(invocation) {
6
8
  const error = new MissError(invocation);
@@ -8,11 +10,20 @@ export function admittedMissError(invocation) {
8
10
  return error;
9
11
  }
10
12
  /** Create a private routing witness only after exact reserved-control decoding. */
11
- export function admittedStaleRouteError(currentEtag, invocation) {
12
- const error = new StaleRouteError(currentEtag, invocation);
13
+ export function admittedStaleRouteError(current, invocation) {
14
+ const error = new StaleRouteError(current, invocation);
13
15
  admittedStaleRoutes.add(error);
14
16
  return error;
15
17
  }
18
+ /**
19
+ * Raise one decoded retirement control as the typed error every Client reports for it,
20
+ * `ResponseError(3002, DEPLOYMENT_RETIRED)`, privately witnessed for Session recovery.
21
+ */
22
+ export function admittedDeploymentRetiredError(control) {
23
+ const error = new ResponseError(CODES.NOT_FOUND, control.message, control.invocation, control.reason);
24
+ admittedRetirements.add(error);
25
+ return error;
26
+ }
16
27
  /** Distinguish decoded routing control from public or caller-supplied lookalike errors. */
17
28
  export function isAdmittedMissError(input) {
18
29
  return input instanceof MissError && admittedMisses.has(input);
@@ -21,3 +32,7 @@ export function isAdmittedMissError(input) {
21
32
  export function isAdmittedStaleRouteError(input) {
22
33
  return input instanceof StaleRouteError && admittedStaleRoutes.has(input);
23
34
  }
35
+ /** Distinguish a decoded retirement control from a lookalike `DEPLOYMENT_RETIRED` response. */
36
+ export function isAdmittedDeploymentRetiredError(input) {
37
+ return input instanceof ResponseError && admittedRetirements.has(input);
38
+ }
@@ -1,9 +1,20 @@
1
- import type { invocation as protocol, publication } from '@astrale-os/kernel-protocol';
1
+ import type { invocation as protocol, publication, release } from '@astrale-os/kernel-protocol';
2
2
  import { ClientError } from './client-error.js';
3
3
  /** Client-visible stale-route error shape; public instances are not routing-control witnesses. */
4
4
  export declare class StaleRouteError extends ClientError {
5
- readonly currentEtag: publication.Etag;
6
5
  readonly invocation: protocol.InvocationId;
7
6
  readonly name = "StaleRouteError";
8
- constructor(currentEtag: publication.Etag, invocation: protocol.InvocationId);
7
+ /**
8
+ * Digest the destination reports it currently serves, an untrusted hint the Session never
9
+ * installs. Until the breaking release (AM-1) it is a v3 ETag: the served Publication's, or the
10
+ * frozen v3 projection's on a v4 deployment.
11
+ */
12
+ readonly current: release.Sha256Digest;
13
+ /**
14
+ * The same digest as `current` under its Publication name.
15
+ *
16
+ * @deprecated Successor: `current`, emitted by the breaking release (AM-1) as the release digest.
17
+ */
18
+ readonly currentEtag: publication.Etag;
19
+ constructor(current: release.Sha256Digest, invocation: protocol.InvocationId);
9
20
  }
@@ -1,12 +1,24 @@
1
1
  import { ClientError } from './client-error.js';
2
2
  /** Client-visible stale-route error shape; public instances are not routing-control witnesses. */
3
3
  export class StaleRouteError extends ClientError {
4
- currentEtag;
5
4
  invocation;
6
5
  name = 'StaleRouteError';
7
- constructor(currentEtag, invocation) {
6
+ /**
7
+ * Digest the destination reports it currently serves, an untrusted hint the Session never
8
+ * installs. Until the breaking release (AM-1) it is a v3 ETag: the served Publication's, or the
9
+ * frozen v3 projection's on a v4 deployment.
10
+ */
11
+ current;
12
+ /**
13
+ * The same digest as `current` under its Publication name.
14
+ *
15
+ * @deprecated Successor: `current`, emitted by the breaking release (AM-1) as the release digest.
16
+ */
17
+ currentEtag;
18
+ constructor(current, invocation) {
8
19
  super('The destination Publication route is stale.');
9
- this.currentEtag = currentEtag;
10
20
  this.invocation = invocation;
21
+ this.current = current;
22
+ this.currentEtag = current;
11
23
  }
12
24
  }
@@ -4,9 +4,10 @@ import { ClientError } from './client-error.js';
4
4
  export type TransportDelivery = 'not-sent' | 'unknown';
5
5
  /** Observable transport phase in which communication stopped. */
6
6
  export type TransportPhase = 'connect' | 'send' | 'receive' | 'timeout' | 'closed' | 'unknown';
7
+ export type AcquisitionResource = 'release' | 'publication' | 'bundle';
7
8
  export type TransportContext = {
8
9
  readonly kind: 'acquisition';
9
- readonly resource: 'publication' | 'bundle';
10
+ readonly resource: AcquisitionResource;
10
11
  } | {
11
12
  readonly kind: 'invocation';
12
13
  readonly delivery: TransportDelivery;
@@ -14,7 +15,7 @@ export type TransportContext = {
14
15
  };
15
16
  export interface AcquisitionTransportErrorOptions extends ErrorOptions {
16
17
  readonly phase: TransportPhase;
17
- readonly resource: 'publication' | 'bundle';
18
+ readonly resource: AcquisitionResource;
18
19
  }
19
20
  export interface InvocationTransportErrorOptions extends ErrorOptions {
20
21
  readonly phase: TransportPhase;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { Target, targetPath } from '@astrale-os/kernel-core/callable';
2
2
  export { Client, call, type AuthenticatedClient, type Call, type ClientDispatchOptions, type ClientDispatchOptionsWithBody, type ClientOptions, type Binary, type BinaryResponse, type Input, type RemoteCredential, type Result, type Stream, type StreamResult, type ValueResult, } from './client/index.js';
3
3
  export * from './errors/index.js';
4
- export { type RequestOptions, type MissControl, type StaleControl, type Transport, type TransportRequest, type TransportResult, } from './transport/index.js';
4
+ export { type RequestOptions, type DeploymentRetired, type MissControl, type StaleControl, type Transport, type TransportRequest, type TransportResult, } from './transport/index.js';
5
5
  export { HttpTransport, type Fetch, type HttpTransportOptions } from './transport/http/index.js';
6
6
  export { WebSocketTransport, type WebSocketConstructor, type WebSocketTransportOptions, } from './transport/websocket/index.js';
7
7
  export { AutoTransport, formatClientWarning, type AutoTransportOptions, type ClientWarning, } from './transport/auto/index.js';
@@ -1,23 +1,27 @@
1
1
  import { Path } from '@astrale-os/kernel-core/path';
2
2
  import { introspect as introspectRef } from '@astrale-os/kernel-core/schema/kernel/syscalls/references';
3
- import { loadMsgpack } from '@astrale-os/kernel-protocol/codecs';
4
3
  import * as schema from '@astrale-os/kernel-protocol/schema';
4
+ import * as legacy from '@astrale-os/kernel-protocol/schema/installation/legacy';
5
5
  import { call } from '../client/index.js';
6
6
  const INTROSPECT_PATH = Path.project(introspectRef).raw;
7
7
  export async function introspect(invoke, limits, request, options) {
8
- // The evidence is MessagePack behind a lazy import: fetch it with the request, not after it.
9
- // A failed preload is surfaced by decode, which imports the same modules again.
10
- void loadMsgpack().catch(() => undefined);
11
8
  const result = await invoke(call(INTROSPECT_PATH, request), options);
12
9
  if (result.kind !== 'binary') {
13
10
  throw new TypeError('Schema introspection returned a non-binary result.');
14
11
  }
15
- if (result.value.mediaType !== schema.installation.introspection.MEDIA_TYPE) {
12
+ const { mediaType } = result.value;
13
+ const decode = mediaType === schema.installation.introspection.MEDIA_TYPE
14
+ ? schema.installation.introspection.decode
15
+ : // Deprecated: Hosts older than beta.127 (see protocol/schema/installation/legacy).
16
+ mediaType === legacy.introspection.MEDIA_TYPE
17
+ ? legacy.introspection.decode
18
+ : undefined;
19
+ if (decode === undefined) {
16
20
  throw new TypeError('Schema introspection returned an unsupported media type.');
17
21
  }
18
22
  if (!(result.value.body instanceof Uint8Array)) {
19
23
  await result.value.body[Symbol.asyncIterator]().return?.();
20
24
  throw new TypeError('Schema introspection returned a streaming binary result.');
21
25
  }
22
- return schema.installation.introspection.decode(request, result.value.body, limits);
26
+ return decode(request, result.value.body, limits);
23
27
  }
@@ -2,6 +2,7 @@ import { Path } from '@astrale-os/kernel-core/path';
2
2
  import { uninstallOutput } from '@astrale-os/kernel-core/schema/kernel/syscalls/contracts';
3
3
  import { install, uninstall } from '@astrale-os/kernel-core/schema/kernel/syscalls/references';
4
4
  import * as protocolSchema from '@astrale-os/kernel-protocol/schema';
5
+ import * as legacy from '@astrale-os/kernel-protocol/schema/installation/legacy';
5
6
  import { call as invocation } from '../client/index.js';
6
7
  import { acceptOutput } from './admission.js';
7
8
  import { introspect } from './introspection.js';
@@ -19,14 +20,21 @@ export function createSchema(call, limits) {
19
20
  if (result.kind !== 'binary') {
20
21
  throw new TypeError('Schema install returned a non-binary result.');
21
22
  }
22
- if (result.value.mediaType !== protocolSchema.installation.MEDIA_TYPE) {
23
+ const { mediaType } = result.value;
24
+ const decode = mediaType === protocolSchema.installation.MEDIA_TYPE
25
+ ? protocolSchema.installation.decode
26
+ : // Deprecated: Hosts older than beta.127 (see protocol/schema/installation/legacy).
27
+ mediaType === legacy.MEDIA_TYPE
28
+ ? legacy.decode
29
+ : undefined;
30
+ if (decode === undefined) {
23
31
  throw new TypeError('Schema install returned an unsupported media type.');
24
32
  }
25
33
  if (!(result.value.body instanceof Uint8Array)) {
26
34
  await result.value.body[Symbol.asyncIterator]().return?.();
27
35
  throw new TypeError('Schema install returned a streaming binary result.');
28
36
  }
29
- const decoded = await protocolSchema.installation.decode(result.value.body, installationResultLimit(admittedLimits.maximumPublicationBytes, request.domains.length));
37
+ const decoded = await decode(result.value.body, installationResultLimit(admittedLimits.maximumPublicationBytes, request.domains.length));
30
38
  proveInstallResult(request, decoded);
31
39
  return decoded;
32
40
  },
@@ -115,23 +115,13 @@ function delay(ms, signal) {
115
115
  });
116
116
  }
117
117
  async function verifiedDownload(input) {
118
- const raw = input.headers?.['x-astrale-blob-size'];
119
- if (raw === undefined || !/^(?:0|[1-9][0-9]*)$/u.test(raw)) {
118
+ // The HTTP transport sets `length` only from an admitted declared length, and its byte stream
119
+ // enforces that exact count through the final read.
120
+ if (input.body instanceof Uint8Array || !('length' in input) || input.length === undefined) {
120
121
  await cancelBinary(input);
121
122
  throw new ProtocolError('Content download omitted its exact content size.');
122
123
  }
123
- const expected = Number(raw);
124
- if (!Number.isSafeInteger(expected)) {
125
- await cancelBinary(input);
126
- throw new ProtocolError('Content download content size is invalid.');
127
- }
128
- if (input.body instanceof Uint8Array) {
129
- if (input.body.byteLength !== expected) {
130
- throw new ProtocolError('Content download ended before its exact content size.');
131
- }
132
- return input;
133
- }
134
- return Object.freeze({ ...input, body: exactContentBytes(input.body, expected) });
124
+ return input;
135
125
  }
136
126
  async function cancelBinary(input) {
137
127
  if (input.body instanceof Uint8Array)
@@ -143,16 +133,3 @@ async function cancelBinary(input) {
143
133
  // Preserve the admission error after the response stream has been closed.
144
134
  }
145
135
  }
146
- async function* exactContentBytes(body, expected) {
147
- let received = 0;
148
- for await (const bytes of body) {
149
- received += bytes.byteLength;
150
- if (received > expected) {
151
- throw new ProtocolError('Content download exceeded its exact content size.');
152
- }
153
- yield bytes;
154
- }
155
- if (received !== expected) {
156
- throw new ProtocolError('Content download ended before its exact content size.');
157
- }
158
- }
@@ -1,4 +1,4 @@
1
1
  export { admitControlOptions, type SessionControlOptions } from './control.js';
2
- export { DEFAULT_MAXIMUM_BUNDLE_BYTES, DEFAULT_MAXIMUM_PUBLICATION_BYTES, DEFAULT_MAXIMUM_RESPONSE_BYTES, DEFAULT_MAXIMUM_ROUTES, DEFAULT_TIMEOUT_MS, platformDuration, positive, } from './limits.js';
3
- export { createSessionOperation, runSessionOperation, sessionTransportOptions, type SessionOperation, } from './operation.js';
2
+ export { DEFAULT_MAXIMUM_BUNDLE_BYTES, DEFAULT_MAXIMUM_PUBLICATION_BYTES, DEFAULT_MAXIMUM_RELEASE_BYTES, DEFAULT_MAXIMUM_RESPONSE_BYTES, DEFAULT_MAXIMUM_ROUTES, DEFAULT_TIMEOUT_MS, platformDuration, positive, } from './limits.js';
3
+ export { createSessionOperation, remainingSessionTime, runSessionOperation, sessionTransportOptions, type SessionOperation, } from './operation.js';
4
4
  export { admitKernel, admitPolicy, allowEndpoint, kernelInvocationEndpoint, type SessionPolicy, } from './policy.js';
@@ -1,4 +1,4 @@
1
1
  export { admitControlOptions } from './control.js';
2
- export { DEFAULT_MAXIMUM_BUNDLE_BYTES, DEFAULT_MAXIMUM_PUBLICATION_BYTES, DEFAULT_MAXIMUM_RESPONSE_BYTES, DEFAULT_MAXIMUM_ROUTES, DEFAULT_TIMEOUT_MS, platformDuration, positive, } from './limits.js';
3
- export { createSessionOperation, runSessionOperation, sessionTransportOptions, } from './operation.js';
2
+ export { DEFAULT_MAXIMUM_BUNDLE_BYTES, DEFAULT_MAXIMUM_PUBLICATION_BYTES, DEFAULT_MAXIMUM_RELEASE_BYTES, DEFAULT_MAXIMUM_RESPONSE_BYTES, DEFAULT_MAXIMUM_ROUTES, DEFAULT_TIMEOUT_MS, platformDuration, positive, } from './limits.js';
3
+ export { createSessionOperation, remainingSessionTime, runSessionOperation, sessionTransportOptions, } from './operation.js';
4
4
  export { admitKernel, admitPolicy, allowEndpoint, kernelInvocationEndpoint, } from './policy.js';
@@ -1,3 +1,4 @@
1
+ export declare const DEFAULT_MAXIMUM_RELEASE_BYTES: number;
1
2
  export declare const DEFAULT_MAXIMUM_PUBLICATION_BYTES: number;
2
3
  export declare const DEFAULT_MAXIMUM_BUNDLE_BYTES: number;
3
4
  export declare const DEFAULT_MAXIMUM_RESPONSE_BYTES: number;
@@ -1,3 +1,4 @@
1
+ export const DEFAULT_MAXIMUM_RELEASE_BYTES = 1024 * 1024;
1
2
  export const DEFAULT_MAXIMUM_PUBLICATION_BYTES = 1024 * 1024;
2
3
  export const DEFAULT_MAXIMUM_BUNDLE_BYTES = 16 * 1024 * 1024;
3
4
  export const DEFAULT_MAXIMUM_RESPONSE_BYTES = 16 * 1024 * 1024;
@@ -2,7 +2,7 @@ import { bundleUrl } from '@astrale-os/kernel-protocol/publication';
2
2
  import { decodeBundleValue } from '@astrale-os/kernel-protocol/publication/bundle';
3
3
  import { ClientError } from '../../errors/index.js';
4
4
  import { allowEndpoint } from '../context/index.js';
5
- import { boundedResponse, requireExactResponse } from './publication.js';
5
+ import { boundedResponse, requireExactResponse } from './response.js';
6
6
  import { runAcquisition } from './transport.js';
7
7
  /** Internal evidence that a selected immutable artifact is no longer served. */
8
8
  export class BundleUnavailableError extends ClientError {
@@ -1,22 +1,30 @@
1
1
  import type { IssuerId } from '@astrale-os/kernel-core/auth/issuer';
2
2
  import type { publication } from '@astrale-os/kernel-protocol';
3
3
  import type { SessionOperation, SessionPolicy } from '../context/index.js';
4
- import type { PublicationDiscovery } from './publication.js';
4
+ import type { IntrinsicRelease } from './release.js';
5
5
  import type { SessionSnapshot } from './snapshot.js';
6
6
  import { type BundleDiscovery } from './bundle.js';
7
+ /** Revalidating reader of the source Kernel's intrinsic release. */
8
+ interface IntrinsicReleases {
9
+ revalidate(operation: SessionOperation): Promise<IntrinsicRelease>;
10
+ invalidate(): void;
11
+ }
7
12
  export declare class SessionDiscovery {
8
13
  readonly kernel: IssuerId;
9
14
  private readonly policy;
10
- private readonly publications;
15
+ private readonly releases;
11
16
  private readonly bundles;
12
17
  private readonly requireOpen;
13
18
  private readonly acquisitionOperation;
14
19
  private retained?;
15
20
  private flight?;
16
- private publicationFlight?;
17
- constructor(kernel: IssuerId, policy: Readonly<SessionPolicy>, publications: PublicationDiscovery, bundles: BundleDiscovery, requireOpen: () => void, acquisitionOperation: () => SessionOperation);
21
+ private releaseFlight?;
22
+ constructor(kernel: IssuerId, policy: Readonly<SessionPolicy>, releases: IntrinsicReleases, bundles: BundleDiscovery, requireOpen: () => void, acquisitionOperation: () => SessionOperation);
23
+ release(operation: SessionOperation): Promise<IntrinsicRelease>;
24
+ /** @deprecated Successor: `release`. The v3 document of the current release. */
18
25
  publication(operation: SessionOperation): Promise<publication.Publication>;
19
26
  snapshot(operation: SessionOperation): Promise<SessionSnapshot>;
20
27
  clear(): void;
21
28
  private acquireSnapshot;
22
29
  }
30
+ export {};
@@ -1,43 +1,48 @@
1
1
  import { runSessionOperation } from '../context/index.js';
2
2
  import { rootViewCatalog } from '../views/index.js';
3
3
  import { BundleUnavailableError } from './bundle.js';
4
+ import { designation, publicationOf } from './legacy/index.js';
4
5
  import { createSessionSnapshot } from './snapshot.js';
5
6
  export class SessionDiscovery {
6
7
  kernel;
7
8
  policy;
8
- publications;
9
+ releases;
9
10
  bundles;
10
11
  requireOpen;
11
12
  acquisitionOperation;
12
13
  retained;
13
14
  flight;
14
- publicationFlight;
15
- constructor(kernel, policy, publications, bundles, requireOpen, acquisitionOperation) {
15
+ releaseFlight;
16
+ constructor(kernel, policy, releases, bundles, requireOpen, acquisitionOperation) {
16
17
  this.kernel = kernel;
17
18
  this.policy = policy;
18
- this.publications = publications;
19
+ this.releases = releases;
19
20
  this.bundles = bundles;
20
21
  this.requireOpen = requireOpen;
21
22
  this.acquisitionOperation = acquisitionOperation;
22
23
  }
23
- publication(operation) {
24
+ release(operation) {
24
25
  this.requireOpen();
25
- const current = this.publicationFlight;
26
+ const current = this.releaseFlight;
26
27
  if (current !== undefined)
27
28
  return runSessionOperation(operation, () => current);
28
- const flight = this.publications.revalidate(this.acquisitionOperation()).then((value) => {
29
+ const flight = this.releases.revalidate(this.acquisitionOperation()).then((value) => {
29
30
  this.requireOpen();
30
31
  return value;
31
32
  });
32
- this.publicationFlight = flight;
33
+ this.releaseFlight = flight;
33
34
  void flight
34
35
  .finally(() => {
35
- if (this.publicationFlight === flight)
36
- this.publicationFlight = undefined;
36
+ if (this.releaseFlight === flight)
37
+ this.releaseFlight = undefined;
37
38
  })
38
39
  .catch(() => undefined);
39
40
  return runSessionOperation(operation, () => flight);
40
41
  }
42
+ /** @deprecated Successor: `release`. The v3 document of the current release. */
43
+ async publication(operation) {
44
+ return publicationOf(await this.release(operation));
45
+ }
41
46
  snapshot(operation) {
42
47
  this.requireOpen();
43
48
  const current = this.flight;
@@ -54,30 +59,34 @@ export class SessionDiscovery {
54
59
  return runSessionOperation(operation, () => flight);
55
60
  }
56
61
  clear() {
57
- this.publications.invalidate();
62
+ this.releases.invalidate();
58
63
  this.bundles.clear();
59
64
  this.retained = undefined;
60
65
  }
61
66
  async acquireSnapshot(operation) {
62
- let publication = await this.publication(operation);
67
+ let release = await this.release(operation);
63
68
  let artifact;
64
69
  try {
65
- artifact = await this.bundles.get(publication, operation);
70
+ artifact = await this.bundles.get(publicationOf(release), operation);
66
71
  }
67
72
  catch (cause) {
68
73
  if (!(cause instanceof BundleUnavailableError))
69
74
  throw cause;
70
- const current = await this.publication(operation);
71
- if (current.etag === publication.etag)
75
+ const current = await this.release(operation);
76
+ if (designation(current) === designation(release))
72
77
  throw cause;
73
- publication = current;
74
- artifact = await this.bundles.get(publication, operation);
78
+ release = current;
79
+ artifact = await this.bundles.get(publicationOf(release), operation);
75
80
  }
76
81
  this.requireOpen();
77
- if (this.retained?.publication.etag === publication.etag)
82
+ if (this.retained !== undefined &&
83
+ designation(this.retained.release) === designation(release)) {
78
84
  return this.retained;
85
+ }
86
+ // Bundle and root Views come from the v3 document until View provenance names the release.
87
+ const publication = publicationOf(release);
79
88
  const views = rootViewCatalog(publication, artifact, this.policy);
80
- const snapshot = createSessionSnapshot(this.kernel, publication, artifact, views);
89
+ const snapshot = createSessionSnapshot(this.kernel, release, publication, artifact, views);
81
90
  this.retained = snapshot;
82
91
  return snapshot;
83
92
  }
@@ -2,11 +2,15 @@ import type { IssuerId } from '@astrale-os/kernel-core/auth/issuer';
2
2
  import type { publication } from '@astrale-os/kernel-protocol';
3
3
  import type { Fetch } from '../../transport/http/index.js';
4
4
  import type { SessionOperation, SessionPolicy } from '../context/index.js';
5
+ import type { IntrinsicRelease } from './release.js';
5
6
  import type { SessionSnapshot } from './snapshot.js';
7
+ export type { IntrinsicRelease } from './release.js';
6
8
  export type { SessionSnapshot } from './snapshot.js';
7
- export { boundedResponse, requireExactResponse } from './publication.js';
9
+ export { boundedResponse, requireExactResponse } from './response.js';
8
10
  export interface SessionDiscovery {
9
11
  readonly kernel: IssuerId;
12
+ release(operation: SessionOperation): Promise<IntrinsicRelease>;
13
+ /** @deprecated Successor: `release`. */
10
14
  publication(operation: SessionOperation): Promise<publication.Publication>;
11
15
  snapshot(operation: SessionOperation): Promise<SessionSnapshot>;
12
16
  clear(): void;
@@ -15,6 +19,9 @@ export interface SessionDiscoveryOptions {
15
19
  readonly kernel: IssuerId;
16
20
  readonly fetch: Fetch;
17
21
  readonly policy: Readonly<SessionPolicy>;
22
+ /** Bounds the source Kernel's own `release.json`. */
23
+ readonly maximumReleaseBytes: number;
24
+ /** Bounds the version 3 Publication a Host that predates `release.json` serves at `domain.json`. */
18
25
  readonly maximumPublicationBytes: number;
19
26
  readonly maximumBundleBytes: number;
20
27
  readonly requireOpen: () => void;
@@ -1,14 +1,11 @@
1
1
  import { BundleDiscovery } from './bundle.js';
2
2
  import { SessionDiscovery as SessionDiscoveryImplementation } from './discovery.js';
3
- import { PublicationDiscovery } from './publication.js';
4
- export { boundedResponse, requireExactResponse } from './publication.js';
3
+ import { IntrinsicReleaseDiscovery, PublicationDiscovery } from './legacy/index.js';
4
+ import { ReleaseDiscovery } from './release.js';
5
+ export { boundedResponse, requireExactResponse } from './response.js';
5
6
  export function createSessionDiscovery(options) {
6
- return new SessionDiscoveryImplementation(options.kernel, options.policy, new PublicationDiscovery({
7
- kernel: options.kernel,
8
- fetch: options.fetch,
9
- policy: options.policy,
10
- maximumBytes: options.maximumPublicationBytes,
11
- }), new BundleDiscovery({
7
+ const source = { kernel: options.kernel, fetch: options.fetch, policy: options.policy };
8
+ return new SessionDiscoveryImplementation(options.kernel, options.policy, new IntrinsicReleaseDiscovery(new ReleaseDiscovery({ ...source, maximumBytes: options.maximumReleaseBytes }), new PublicationDiscovery({ ...source, maximumBytes: options.maximumPublicationBytes })), new BundleDiscovery({
12
9
  fetch: options.fetch,
13
10
  policy: options.policy,
14
11
  maximumBytes: options.maximumBundleBytes,
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Compatibility discovery for Hosts without `release.json` and callers of the Publication API.
3
+ * Release-backed Hosts expose a locally computed v3 projection to those callers.
4
+ *
5
+ * Superseded by `ClientSession.release()` and `SessionSnapshot.release` (`ReleaseDiscovery`,
6
+ * `IntrinsicRelease`), and, for the stale hint, by `StaleRouteError.current`.
7
+ *
8
+ * Short-term consumers:
9
+ * - Hosts whose Kernel predates intrinsic `release.json`: they answer it with 404 (a browser sees
10
+ * a `TypeError`, the 404 carrying no CORS headers) and serve only their version 3 Publication at
11
+ * `domain.json`. Every Host does until it runs a Kernel release that serves `release.json`; the
12
+ * 1Pact path keeps one, because the client in GUI and Admin reads the Kernel .117 Host;
13
+ * - callers of the Publication API: Shell's `KernelClient.publication()` (and shell-react's graph
14
+ * store through it) and Admin's remote instance gateway (`SessionSnapshot.publication`), until
15
+ * each reads `release()` / `release`;
16
+ * - the snapshot's bundle and root Views, whose provenance (`ResolvedView.etag`) keeps the v3 ETag
17
+ * until the breaking release (AM-1).
18
+ *
19
+ * Removal, in a separate breaking release: no supported Host predates intrinsic `release.json`
20
+ * (never while GUI and Admin talk to the Kernel .117 Host), no supported consumer reads the
21
+ * Publication API, and the breaking release of AM-1 emits release digests on every
22
+ * Kernel-to-client wire value. Then delete, in the same PR:
23
+ * - this folder, its tests (the pre-v4 cases of `session/__tests__/release.test.ts`) and its laws;
24
+ * `SessionDiscovery` then reads `ReleaseDiscovery` directly, reports its 404 as a Client failure
25
+ * and designates a snapshot by release digest, so canonical code only narrows `IntrinsicRelease`
26
+ * to the `DomainRelease`;
27
+ * - the `'publication'` acquisition resource of `TransportError`, and the `domain.json` bound of
28
+ * `ClientSessionOptions.maximumPublicationBytes`, which keeps bounding the Publications Schema
29
+ * results carry until those name releases;
30
+ * - `ClientSession.publication()`, `SessionDiscovery.publication` and `SessionSnapshot.publication`
31
+ * with their `.spec` entries; `BundleDiscovery` and `rootViewCatalog` then take the release,
32
+ * whose View provenance is `ResolvedView.release`;
33
+ * - `StaleRouteError.currentEtag`, from law CLIENT-LEGACY-WIRE-FIELDS and its test, with the
34
+ * Protocol fields of law PROTOCOL-LEGACY-WIRE-FIELDS.
35
+ */
36
+ export { designation, IntrinsicReleaseDiscovery, type ReleaseReader } from './intrinsic.js';
37
+ export { PublicationDiscovery, type PublicationDiscoveryOptions } from './publication.js';
38
+ export { publicationOf } from './projection.js';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Compatibility discovery for Hosts without `release.json` and callers of the Publication API.
3
+ * Release-backed Hosts expose a locally computed v3 projection to those callers.
4
+ *
5
+ * Superseded by `ClientSession.release()` and `SessionSnapshot.release` (`ReleaseDiscovery`,
6
+ * `IntrinsicRelease`), and, for the stale hint, by `StaleRouteError.current`.
7
+ *
8
+ * Short-term consumers:
9
+ * - Hosts whose Kernel predates intrinsic `release.json`: they answer it with 404 (a browser sees
10
+ * a `TypeError`, the 404 carrying no CORS headers) and serve only their version 3 Publication at
11
+ * `domain.json`. Every Host does until it runs a Kernel release that serves `release.json`; the
12
+ * 1Pact path keeps one, because the client in GUI and Admin reads the Kernel .117 Host;
13
+ * - callers of the Publication API: Shell's `KernelClient.publication()` (and shell-react's graph
14
+ * store through it) and Admin's remote instance gateway (`SessionSnapshot.publication`), until
15
+ * each reads `release()` / `release`;
16
+ * - the snapshot's bundle and root Views, whose provenance (`ResolvedView.etag`) keeps the v3 ETag
17
+ * until the breaking release (AM-1).
18
+ *
19
+ * Removal, in a separate breaking release: no supported Host predates intrinsic `release.json`
20
+ * (never while GUI and Admin talk to the Kernel .117 Host), no supported consumer reads the
21
+ * Publication API, and the breaking release of AM-1 emits release digests on every
22
+ * Kernel-to-client wire value. Then delete, in the same PR:
23
+ * - this folder, its tests (the pre-v4 cases of `session/__tests__/release.test.ts`) and its laws;
24
+ * `SessionDiscovery` then reads `ReleaseDiscovery` directly, reports its 404 as a Client failure
25
+ * and designates a snapshot by release digest, so canonical code only narrows `IntrinsicRelease`
26
+ * to the `DomainRelease`;
27
+ * - the `'publication'` acquisition resource of `TransportError`, and the `domain.json` bound of
28
+ * `ClientSessionOptions.maximumPublicationBytes`, which keeps bounding the Publications Schema
29
+ * results carry until those name releases;
30
+ * - `ClientSession.publication()`, `SessionDiscovery.publication` and `SessionSnapshot.publication`
31
+ * with their `.spec` entries; `BundleDiscovery` and `rootViewCatalog` then take the release,
32
+ * whose View provenance is `ResolvedView.release`;
33
+ * - `StaleRouteError.currentEtag`, from law CLIENT-LEGACY-WIRE-FIELDS and its test, with the
34
+ * Protocol fields of law PROTOCOL-LEGACY-WIRE-FIELDS.
35
+ */
36
+ export { designation, IntrinsicReleaseDiscovery } from './intrinsic.js';
37
+ export { PublicationDiscovery } from './publication.js';
38
+ export { publicationOf } from './projection.js';