@astrale-os/kernel-client 0.6.0-beta.82 → 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.
- package/dist/client/admission.js +1 -9
- package/dist/client/client.js +3 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/routing-control.d.ts +11 -3
- package/dist/client/routing-control.js +18 -3
- package/dist/errors/stale-route-error.d.ts +14 -3
- package/dist/errors/stale-route-error.js +15 -3
- package/dist/errors/transport-error.d.ts +3 -2
- package/dist/index.d.ts +1 -1
- package/dist/schema/introspection.js +10 -6
- package/dist/schema/schema.js +10 -2
- package/dist/session/content.js +4 -27
- package/dist/session/context/index.d.ts +1 -1
- package/dist/session/context/index.js +1 -1
- package/dist/session/context/limits.d.ts +1 -0
- package/dist/session/context/limits.js +1 -0
- package/dist/session/discovery/bundle.js +1 -1
- package/dist/session/discovery/discovery.d.ts +12 -4
- package/dist/session/discovery/discovery.js +28 -19
- package/dist/session/discovery/index.d.ts +8 -1
- package/dist/session/discovery/index.js +5 -8
- package/dist/session/discovery/legacy/index.d.ts +38 -0
- package/dist/session/discovery/legacy/index.js +38 -0
- package/dist/session/discovery/legacy/intrinsic.d.ts +33 -0
- package/dist/session/discovery/legacy/intrinsic.js +67 -0
- package/dist/session/discovery/legacy/projection.d.ts +8 -0
- package/dist/session/discovery/legacy/projection.js +17 -0
- package/dist/session/discovery/{publication.d.ts → legacy/publication.d.ts} +8 -5
- package/dist/session/discovery/{publication.js → legacy/publication.js} +13 -60
- package/dist/session/discovery/release.d.ts +29 -0
- package/dist/session/discovery/release.js +81 -0
- package/dist/session/discovery/response.d.ts +3 -0
- package/dist/session/discovery/response.js +59 -0
- package/dist/session/discovery/snapshot.d.ts +9 -2
- package/dist/session/discovery/snapshot.js +2 -2
- package/dist/session/discovery/transport.d.ts +1 -3
- package/dist/session/exchange/options.d.ts +13 -10
- package/dist/session/exchange/options.js +6 -5
- package/dist/session/exchange/presentation.d.ts +3 -2
- package/dist/session/index.d.ts +1 -1
- package/dist/session/reference.d.ts +3 -6
- package/dist/session/reference.js +0 -4
- package/dist/session/routing/dispatch.js +36 -37
- package/dist/session/session.d.ts +18 -3
- package/dist/session/session.js +21 -10
- package/dist/session/session.typecheck.js +14 -3
- package/dist/transport/auto/transport.d.ts +0 -1
- package/dist/transport/auto/transport.js +0 -2
- package/dist/transport/http/response.d.ts +1 -1
- package/dist/transport/http/response.js +2 -6
- package/dist/transport/http/transport.d.ts +5 -3
- package/dist/transport/http/transport.js +40 -19
- package/dist/transport/index.d.ts +1 -1
- package/dist/transport/transport.d.ts +10 -2
- package/dist/transport/websocket/transport.d.ts +0 -3
- package/dist/transport/websocket/transport.js +1 -16
- package/package.json +6 -7
package/dist/client/admission.js
CHANGED
|
@@ -25,17 +25,9 @@ function admitPath(input) {
|
|
|
25
25
|
}
|
|
26
26
|
function admitInput(input) {
|
|
27
27
|
try {
|
|
28
|
-
return
|
|
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
|
-
}
|
package/dist/client/client.js
CHANGED
|
@@ -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
|
}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/client/index.js
CHANGED
|
@@ -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,
|
|
2
|
-
import {
|
|
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(
|
|
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 {
|
|
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(
|
|
12
|
-
const error = new StaleRouteError(
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
26
|
+
return decode(request, result.value.body, limits);
|
|
23
27
|
}
|
package/dist/schema/schema.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
},
|
package/dist/session/content.js
CHANGED
|
@@ -115,23 +115,13 @@ function delay(ms, signal) {
|
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
117
|
async function verifiedDownload(input) {
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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';
|
|
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
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';
|
|
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
3
|
export { createSessionOperation, remainingSessionTime, runSessionOperation, sessionTransportOptions, } from './operation.js';
|
|
4
4
|
export { admitKernel, admitPolicy, allowEndpoint, kernelInvocationEndpoint, } from './policy.js';
|
|
@@ -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 './
|
|
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 {
|
|
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
|
|
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
|
|
17
|
-
constructor(kernel: IssuerId, policy: Readonly<SessionPolicy>,
|
|
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
|
-
|
|
9
|
+
releases;
|
|
9
10
|
bundles;
|
|
10
11
|
requireOpen;
|
|
11
12
|
acquisitionOperation;
|
|
12
13
|
retained;
|
|
13
14
|
flight;
|
|
14
|
-
|
|
15
|
-
constructor(kernel, policy,
|
|
15
|
+
releaseFlight;
|
|
16
|
+
constructor(kernel, policy, releases, bundles, requireOpen, acquisitionOperation) {
|
|
16
17
|
this.kernel = kernel;
|
|
17
18
|
this.policy = policy;
|
|
18
|
-
this.
|
|
19
|
+
this.releases = releases;
|
|
19
20
|
this.bundles = bundles;
|
|
20
21
|
this.requireOpen = requireOpen;
|
|
21
22
|
this.acquisitionOperation = acquisitionOperation;
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
+
release(operation) {
|
|
24
25
|
this.requireOpen();
|
|
25
|
-
const current = this.
|
|
26
|
+
const current = this.releaseFlight;
|
|
26
27
|
if (current !== undefined)
|
|
27
28
|
return runSessionOperation(operation, () => current);
|
|
28
|
-
const flight = this.
|
|
29
|
+
const flight = this.releases.revalidate(this.acquisitionOperation()).then((value) => {
|
|
29
30
|
this.requireOpen();
|
|
30
31
|
return value;
|
|
31
32
|
});
|
|
32
|
-
this.
|
|
33
|
+
this.releaseFlight = flight;
|
|
33
34
|
void flight
|
|
34
35
|
.finally(() => {
|
|
35
|
-
if (this.
|
|
36
|
-
this.
|
|
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.
|
|
62
|
+
this.releases.invalidate();
|
|
58
63
|
this.bundles.clear();
|
|
59
64
|
this.retained = undefined;
|
|
60
65
|
}
|
|
61
66
|
async acquireSnapshot(operation) {
|
|
62
|
-
let
|
|
67
|
+
let release = await this.release(operation);
|
|
63
68
|
let artifact;
|
|
64
69
|
try {
|
|
65
|
-
artifact = await this.bundles.get(
|
|
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.
|
|
71
|
-
if (current
|
|
75
|
+
const current = await this.release(operation);
|
|
76
|
+
if (designation(current) === designation(release))
|
|
72
77
|
throw cause;
|
|
73
|
-
|
|
74
|
-
artifact = await this.bundles.get(
|
|
78
|
+
release = current;
|
|
79
|
+
artifact = await this.bundles.get(publicationOf(release), operation);
|
|
75
80
|
}
|
|
76
81
|
this.requireOpen();
|
|
77
|
-
if (this.retained
|
|
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 './
|
|
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 './
|
|
4
|
-
|
|
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
|
-
|
|
7
|
-
|
|
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';
|