@astrale-os/kernel-protocol 0.5.0-beta.9 → 0.5.0
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/artifact/index.d.ts +1 -1
- package/dist/artifact/index.js +1 -1
- package/dist/artifact/ref.d.ts +2 -0
- package/dist/artifact/ref.js +4 -3
- package/dist/codecs/index.d.ts +1 -2
- package/dist/codecs/index.js +1 -2
- package/dist/codecs/media-types.d.ts +0 -1
- package/dist/codecs/media-types.js +0 -1
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.js +1 -0
- package/dist/errors/retry.d.ts +25 -0
- package/dist/errors/retry.js +46 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/invocation/admission.d.ts +2 -2
- package/dist/invocation/admission.js +5 -5
- package/dist/invocation/binary.d.ts +1 -0
- package/dist/invocation/binary.js +21 -4
- package/dist/invocation/binding-codecs.d.ts +3 -2
- package/dist/invocation/binding-codecs.js +1 -6
- package/dist/invocation/delegate.d.ts +0 -20
- package/dist/invocation/delegate.js +2 -34
- package/dist/invocation/http/binary.d.ts +3 -1
- package/dist/invocation/http/binary.js +6 -1
- package/dist/invocation/http/index.d.ts +2 -1
- package/dist/invocation/http/index.js +2 -1
- package/dist/invocation/http/negotiation.js +3 -10
- package/dist/invocation/http/request.d.ts +11 -0
- package/dist/invocation/http/request.js +69 -0
- package/dist/invocation/index.d.ts +5 -4
- package/dist/invocation/index.js +4 -3
- package/dist/invocation/maintenance.d.ts +14 -0
- package/dist/invocation/maintenance.js +18 -0
- package/dist/invocation/redirect.d.ts +10 -12
- package/dist/invocation/redirect.js +10 -22
- package/dist/invocation/request.d.ts +3 -0
- package/dist/invocation/request.js +9 -2
- package/dist/invocation/response.d.ts +6 -1
- package/dist/invocation/response.js +26 -15
- package/dist/invocation/routing.d.ts +14 -4
- package/dist/invocation/routing.js +35 -20
- package/dist/invocation/schema.d.ts +6 -4
- package/dist/invocation/schema.js +5 -11
- package/dist/invocation/service-reason.d.ts +20 -15
- package/dist/invocation/service-reason.js +27 -19
- package/dist/invocation/stale-route.d.ts +3 -3
- package/dist/invocation/target.d.ts +5 -2
- package/dist/invocation/target.js +25 -3
- package/dist/invocation/websocket/binary.js +1 -0
- package/dist/invocation/websocket/index.d.ts +1 -1
- package/dist/invocation/websocket/index.js +1 -1
- package/dist/invocation/websocket/subprotocol.d.ts +1 -2
- package/dist/invocation/websocket/subprotocol.js +3 -4
- package/dist/issuer/claims.d.ts +7 -0
- package/dist/issuer/claims.js +7 -0
- package/dist/issuer/index.d.ts +2 -0
- package/dist/issuer/index.js +2 -0
- package/dist/issuer/paths.d.ts +2 -0
- package/dist/issuer/paths.js +1 -0
- package/dist/issuer/workflow-engine.d.ts +18 -0
- package/dist/issuer/workflow-engine.js +59 -0
- package/dist/{publication → release}/bindings/callable.js +6 -6
- package/dist/release/bindings/invocation.js +13 -0
- package/dist/release/bindings/view.d.ts +11 -0
- package/dist/release/bindings/view.js +54 -0
- package/dist/release/build.d.ts +6 -0
- package/dist/release/build.js +7 -0
- package/dist/release/bundle-admission.d.ts +21 -0
- package/dist/{publication → release}/bundle-admission.js +15 -20
- package/dist/{publication → release}/bundle-descriptor.js +6 -6
- package/dist/release/bundle-evidence.d.ts +3 -0
- package/dist/release/bundle-evidence.js +13 -0
- package/dist/{publication → release}/bundle.d.ts +5 -2
- package/dist/{publication → release}/bundle.js +13 -7
- package/dist/{publication → release}/delivery/attestation.d.ts +1 -0
- package/dist/{publication → release}/delivery/claims.d.ts +7 -2
- package/dist/{publication → release}/delivery/claims.js +20 -13
- package/dist/release/delivery/delivery.d.ts +11 -0
- package/dist/{publication → release}/delivery/delivery.js +7 -12
- package/dist/{publication → release}/delivery/error.d.ts +2 -0
- package/dist/{publication → release}/delivery/error.js +1 -0
- package/dist/{publication → release}/delivery/index.d.ts +5 -1
- package/dist/{publication → release}/delivery/index.js +4 -0
- package/dist/{publication → release}/delivery/limits.d.ts +2 -1
- package/dist/release/delivery/limits.js +5 -0
- package/dist/{publication → release}/delivery/request.d.ts +10 -9
- package/dist/{publication → release}/delivery/request.js +9 -10
- package/dist/{publication → release}/delivery-declaration.js +4 -4
- package/dist/release/delivery-endpoint.js +22 -0
- package/dist/release/digest.d.ts +13 -0
- package/dist/{publication/etag.js → release/digest.js} +1 -6
- package/dist/release/error.d.ts +7 -0
- package/dist/release/error.js +11 -0
- package/dist/release/index.d.ts +17 -0
- package/dist/release/index.js +10 -0
- package/dist/release/issuer.d.ts +5 -0
- package/dist/release/issuer.js +34 -0
- package/dist/release/manifest.d.ts +6 -0
- package/dist/release/manifest.js +22 -0
- package/dist/release/reference.d.ts +26 -0
- package/dist/release/reference.js +70 -0
- package/dist/release/release.d.ts +60 -0
- package/dist/release/release.js +125 -0
- package/dist/release/requirements.d.ts +41 -0
- package/dist/release/requirements.js +306 -0
- package/dist/{publication → release}/route.js +24 -24
- package/dist/{publication → release}/schema.js +4 -4
- package/dist/release/served.d.ts +27 -0
- package/dist/release/served.js +56 -0
- package/dist/release/shape.d.ts +8 -0
- package/dist/routes/admission.d.ts +1 -1
- package/dist/routes/admission.js +7 -85
- package/dist/routes/binding.d.ts +3 -3
- package/dist/routes/binding.js +11 -21
- package/dist/routes/compiler.d.ts +2 -2
- package/dist/routes/compiler.js +7 -3
- package/dist/routes/credential.d.ts +12 -2
- package/dist/routes/credential.js +9 -4
- package/dist/routes/decode.d.ts +5 -2
- package/dist/routes/decode.js +24 -17
- package/dist/routes/deployment-retired.d.ts +30 -0
- package/dist/routes/deployment-retired.js +96 -0
- package/dist/routes/encode.js +12 -5
- package/dist/routes/entries.d.ts +23 -0
- package/dist/routes/entries.js +150 -0
- package/dist/routes/evidence.d.ts +3 -3
- package/dist/routes/evidence.js +7 -33
- package/dist/routes/header.d.ts +9 -0
- package/dist/routes/header.js +34 -0
- package/dist/routes/index.d.ts +2 -2
- package/dist/routes/index.js +2 -2
- package/dist/routes/mapping.d.ts +1 -1
- package/dist/routes/mapping.js +4 -4
- package/dist/routes/match.d.ts +1 -1
- package/dist/routes/placement.d.ts +2 -2
- package/dist/routes/plan.d.ts +1 -1
- package/dist/routes/request.d.ts +3 -1
- package/dist/routes/reservation.d.ts +4 -2
- package/dist/routes/reservation.js +14 -8
- package/dist/routes/routing.d.ts +18 -6
- package/dist/routes/routing.js +28 -49
- package/dist/routes/select.d.ts +1 -1
- package/dist/routes/table.d.ts +3 -2
- package/dist/routes/target.d.ts +5 -0
- package/dist/routes/target.js +16 -0
- package/dist/routes/template.d.ts +0 -1
- package/dist/routes/template.js +0 -3
- package/dist/runtime/artifact.d.ts +5 -4
- package/dist/runtime/artifact.js +7 -4
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/schema/installation/codec.d.ts +9 -0
- package/dist/schema/installation/codec.js +82 -0
- package/dist/schema/installation/index.d.ts +2 -0
- package/dist/schema/installation/index.js +1 -0
- package/dist/schema/installation/introspection/codec.d.ts +5 -2
- package/dist/schema/installation/introspection/codec.js +319 -193
- package/dist/schema/installation/introspection/index.d.ts +2 -2
- package/dist/schema/installation/introspection/index.js +1 -1
- package/dist/schema/installation/introspection/result.d.ts +84 -25
- package/dist/schema/installation/result.d.ts +15 -0
- package/dist/schema/installation/result.js +1 -0
- package/package.json +21 -75
- package/dist/codecs/msgpack.d.ts +0 -4
- package/dist/codecs/msgpack.js +0 -50
- package/dist/publication/bindings/invocation.js +0 -13
- package/dist/publication/bindings/view.d.ts +0 -7
- package/dist/publication/bindings/view.js +0 -28
- package/dist/publication/bundle-admission.d.ts +0 -4
- package/dist/publication/bundle-evidence.d.ts +0 -3
- package/dist/publication/bundle-evidence.js +0 -13
- package/dist/publication/content.d.ts +0 -10
- package/dist/publication/delivery/delivery.d.ts +0 -9
- package/dist/publication/delivery/limits.js +0 -4
- package/dist/publication/delivery-endpoint.js +0 -22
- package/dist/publication/error.d.ts +0 -7
- package/dist/publication/error.js +0 -11
- package/dist/publication/etag.d.ts +0 -6
- package/dist/publication/index.d.ts +0 -17
- package/dist/publication/index.js +0 -10
- package/dist/publication/manifest.d.ts +0 -6
- package/dist/publication/manifest.js +0 -30
- package/dist/publication/publication.d.ts +0 -36
- package/dist/publication/publication.js +0 -133
- package/dist/publication/reference.d.ts +0 -17
- package/dist/publication/reference.js +0 -68
- package/dist/publication/requirements.d.ts +0 -26
- package/dist/publication/requirements.js +0 -180
- package/dist/routes/release-unavailable.d.ts +0 -8
- package/dist/routes/release-unavailable.js +0 -32
- /package/dist/{publication → release}/bindings/callable.d.ts +0 -0
- /package/dist/{publication → release}/bindings/index.d.ts +0 -0
- /package/dist/{publication → release}/bindings/index.js +0 -0
- /package/dist/{publication → release}/bindings/invocation.d.ts +0 -0
- /package/dist/{publication → release}/bundle-descriptor.d.ts +0 -0
- /package/dist/{publication → release}/declaration.d.ts +0 -0
- /package/dist/{publication → release}/declaration.js +0 -0
- /package/dist/{publication → release}/delivery/attestation.js +0 -0
- /package/dist/{publication → release}/delivery-declaration.d.ts +0 -0
- /package/dist/{publication → release}/delivery-endpoint.d.ts +0 -0
- /package/dist/{publication → release}/route.d.ts +0 -0
- /package/dist/{publication → release}/schema.d.ts +0 -0
- /package/dist/{publication/content.js → release/shape.js} +0 -0
|
@@ -1,29 +1,24 @@
|
|
|
1
|
-
import { accept as
|
|
1
|
+
import { accept as acceptRelease } from '../release.js';
|
|
2
|
+
import { deepFreeze } from '../shape.js';
|
|
2
3
|
import { acceptAttestation } from './attestation.js';
|
|
3
4
|
import { fail } from './error.js';
|
|
5
|
+
/** Admit one Delivery v2 envelope: its release is admitted whole, its attestation stays opaque. */
|
|
4
6
|
export function accept(input) {
|
|
5
7
|
if (input === null || typeof input !== 'object' || Array.isArray(input)) {
|
|
6
8
|
fail('DELIVERY_INVALID', 'Expected a Delivery object.');
|
|
7
9
|
}
|
|
8
10
|
const value = input;
|
|
9
|
-
const expected = new Set(['format', 'version', '
|
|
11
|
+
const expected = new Set(['format', 'version', 'release', 'attestation']);
|
|
10
12
|
if (Object.keys(value).some((field) => !expected.has(field))) {
|
|
11
13
|
fail('DELIVERY_INVALID', 'Delivery contains unknown fields.');
|
|
12
14
|
}
|
|
13
|
-
if (value.format !== 'astrale.domain.delivery' || value.version !==
|
|
15
|
+
if (value.format !== 'astrale.domain.delivery' || value.version !== 2) {
|
|
14
16
|
fail('DELIVERY_INVALID', 'Unsupported Delivery format or version.');
|
|
15
17
|
}
|
|
16
18
|
return deepFreeze({
|
|
17
19
|
format: 'astrale.domain.delivery',
|
|
18
|
-
version:
|
|
19
|
-
|
|
20
|
+
version: 2,
|
|
21
|
+
release: acceptRelease(value.release),
|
|
20
22
|
attestation: acceptAttestation(value.attestation),
|
|
21
23
|
});
|
|
22
24
|
}
|
|
23
|
-
function deepFreeze(value) {
|
|
24
|
-
if (value === null || typeof value !== 'object' || Object.isFrozen(value))
|
|
25
|
-
return value;
|
|
26
|
-
for (const nested of Object.values(value))
|
|
27
|
-
deepFreeze(nested);
|
|
28
|
-
return Object.freeze(value);
|
|
29
|
-
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
/** Failure code of a Delivery request, envelope, or claims set, whatever its version. */
|
|
1
2
|
export type DeliveryErrorCode = 'DELIVERY_INVALID' | 'DELIVERY_REQUEST_INVALID' | 'DELIVERY_CLAIMS_INVALID';
|
|
3
|
+
/** Reported by every Delivery decoder, v1 and v2. */
|
|
2
4
|
export declare class DeliveryError extends Error {
|
|
3
5
|
readonly code: DeliveryErrorCode;
|
|
4
6
|
readonly name = "DeliveryError";
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delivery v2: the challenge-bound envelope carrying the exact `DomainRelease` a Kernel fetched at
|
|
3
|
+
* `release.json`, bound to its release digest. Only the version 2 request is admitted.
|
|
4
|
+
*/
|
|
1
5
|
export { type Attestation } from './attestation.js';
|
|
2
6
|
export { acceptClaims, type Claims } from './claims.js';
|
|
3
7
|
export { DeliveryError as Error, type DeliveryErrorCode as ErrorCode } from './error.js';
|
|
4
8
|
export { accept, type Delivery } from './delivery.js';
|
|
5
9
|
export { ATTESTATION_MAX_BYTES, NONCE_MAX_LENGTH, NONCE_MIN_LENGTH, PURPOSE } from './limits.js';
|
|
6
|
-
export { acceptRequest, type
|
|
10
|
+
export { acceptRequest, type Request, type Token } from './request.js';
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delivery v2: the challenge-bound envelope carrying the exact `DomainRelease` a Kernel fetched at
|
|
3
|
+
* `release.json`, bound to its release digest. Only the version 2 request is admitted.
|
|
4
|
+
*/
|
|
1
5
|
export {} from './attestation.js';
|
|
2
6
|
export { acceptClaims } from './claims.js';
|
|
3
7
|
export { DeliveryError as Error } from './error.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/** Purpose of Delivery v2 attestations, which bind a release digest. */
|
|
2
|
+
export declare const PURPOSE: 'astrale.domain.delivery.v2';
|
|
2
3
|
export declare const NONCE_MIN_LENGTH = 16;
|
|
3
4
|
export declare const NONCE_MAX_LENGTH = 256;
|
|
4
5
|
export declare const ATTESTATION_MAX_BYTES = 16384;
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { type IssuerId } from '@astrale-os/kernel-core/auth/issuer';
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
readonly version: 1;
|
|
5
|
-
readonly kernel: IssuerId;
|
|
6
|
-
readonly nonce: string;
|
|
7
|
-
readonly etag: Etag;
|
|
8
|
-
}
|
|
9
|
-
/** Exact bearer credential admitted by a Delivery transport boundary. */
|
|
2
|
+
import type { ReleaseDigest } from '../digest.js';
|
|
3
|
+
/** Exact bearer credential admitted by a Delivery transport boundary; never inside a document. */
|
|
10
4
|
export interface Token {
|
|
11
5
|
readonly kind: 'bearer';
|
|
12
6
|
readonly token: string;
|
|
13
7
|
}
|
|
14
|
-
/**
|
|
8
|
+
/** A requesting Kernel's challenge for the one exact release it fetched at `release.json`. */
|
|
9
|
+
export interface Request {
|
|
10
|
+
readonly version: 2;
|
|
11
|
+
readonly kernel: IssuerId;
|
|
12
|
+
readonly nonce: string;
|
|
13
|
+
readonly release: ReleaseDigest;
|
|
14
|
+
}
|
|
15
|
+
/** Admit the exact version-two challenge at a Delivery transport boundary. */
|
|
15
16
|
export declare function acceptRequest(input: unknown): Request;
|
|
@@ -2,19 +2,19 @@ import { AuthValueError } from '@astrale-os/kernel-core/auth/error';
|
|
|
2
2
|
import { accept as acceptIssuer } from '@astrale-os/kernel-core/auth/issuer';
|
|
3
3
|
import { fail } from './error.js';
|
|
4
4
|
import { NONCE_MAX_LENGTH, NONCE_MIN_LENGTH } from './limits.js';
|
|
5
|
-
|
|
5
|
+
const FIELDS = ['version', 'kernel', 'nonce', 'release'];
|
|
6
|
+
/** Admit the exact version-two challenge at a Delivery transport boundary. */
|
|
6
7
|
export function acceptRequest(input) {
|
|
7
8
|
if (input === null || typeof input !== 'object' || Array.isArray(input)) {
|
|
8
9
|
fail('DELIVERY_REQUEST_INVALID', 'Expected a Delivery request.');
|
|
9
10
|
}
|
|
10
|
-
const expected = ['version', 'kernel', 'nonce', 'etag'];
|
|
11
11
|
const value = {};
|
|
12
12
|
const fields = Reflect.ownKeys(input);
|
|
13
|
-
if (fields.length !==
|
|
13
|
+
if (fields.length !== FIELDS.length) {
|
|
14
14
|
fail('DELIVERY_REQUEST_INVALID', 'Delivery request is invalid.');
|
|
15
15
|
}
|
|
16
16
|
for (const field of fields) {
|
|
17
|
-
if (typeof field !== 'string' || !
|
|
17
|
+
if (typeof field !== 'string' || !FIELDS.includes(field)) {
|
|
18
18
|
fail('DELIVERY_REQUEST_INVALID', 'Delivery request is invalid.');
|
|
19
19
|
}
|
|
20
20
|
const descriptor = Object.getOwnPropertyDescriptor(input, field);
|
|
@@ -23,14 +23,13 @@ export function acceptRequest(input) {
|
|
|
23
23
|
}
|
|
24
24
|
value[field] = descriptor.value;
|
|
25
25
|
}
|
|
26
|
-
if (
|
|
27
|
-
value.version !== 1 ||
|
|
26
|
+
if (value.version !== 2 ||
|
|
28
27
|
typeof value.kernel !== 'string' ||
|
|
29
28
|
typeof value.nonce !== 'string' ||
|
|
30
29
|
value.nonce.length < NONCE_MIN_LENGTH ||
|
|
31
30
|
value.nonce.length > NONCE_MAX_LENGTH ||
|
|
32
|
-
typeof value.
|
|
33
|
-
!/^sha256:[0-9a-f]{64}$/u.test(value.
|
|
31
|
+
typeof value.release !== 'string' ||
|
|
32
|
+
!/^sha256:[0-9a-f]{64}$/u.test(value.release)) {
|
|
34
33
|
fail('DELIVERY_REQUEST_INVALID', 'Delivery request is invalid.');
|
|
35
34
|
}
|
|
36
35
|
let kernel;
|
|
@@ -44,9 +43,9 @@ export function acceptRequest(input) {
|
|
|
44
43
|
throw cause;
|
|
45
44
|
}
|
|
46
45
|
return Object.freeze({
|
|
47
|
-
version:
|
|
46
|
+
version: 2,
|
|
48
47
|
kernel,
|
|
49
48
|
nonce: value.nonce,
|
|
50
|
-
|
|
49
|
+
release: value.release,
|
|
51
50
|
});
|
|
52
51
|
}
|
|
@@ -3,23 +3,23 @@ import { fail } from './error.js';
|
|
|
3
3
|
/** Place one provider-neutral Delivery declaration beneath an exact serving issuer. */
|
|
4
4
|
export function placeDelivery(input, issuer, bundleHref) {
|
|
5
5
|
if (input === null || typeof input !== 'object' || Array.isArray(input)) {
|
|
6
|
-
fail('
|
|
6
|
+
fail('RELEASE_INVALID', 'Delivery declaration is invalid.');
|
|
7
7
|
}
|
|
8
8
|
if (Reflect.ownKeys(input).some((key) => key !== 'path' && key !== 'authentication') ||
|
|
9
9
|
typeof input.path !== 'string' ||
|
|
10
10
|
!input.path.startsWith('/')) {
|
|
11
|
-
fail('
|
|
11
|
+
fail('RELEASE_INVALID', 'Delivery declaration is invalid.');
|
|
12
12
|
}
|
|
13
13
|
let href;
|
|
14
14
|
try {
|
|
15
15
|
const url = new URL(input.path, issuer);
|
|
16
16
|
if (url.origin !== new URL(issuer).origin || url.search !== '' || url.hash !== '') {
|
|
17
|
-
fail('
|
|
17
|
+
fail('RELEASE_INVALID', 'Delivery declaration must be one relative path.');
|
|
18
18
|
}
|
|
19
19
|
href = url.href;
|
|
20
20
|
}
|
|
21
21
|
catch (cause) {
|
|
22
|
-
return fail('
|
|
22
|
+
return fail('RELEASE_INVALID', 'Delivery declaration path is invalid.', cause);
|
|
23
23
|
}
|
|
24
24
|
return admitEndpoint({ href, method: 'POST', authentication: input.authentication }, bundleHref);
|
|
25
25
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { fail } from './error.js';
|
|
2
|
+
import { absoluteHttpUrl, exact, record } from './shape.js';
|
|
3
|
+
export function admitEndpoint(input, publicUrl) {
|
|
4
|
+
if (input === undefined)
|
|
5
|
+
return undefined;
|
|
6
|
+
const value = record(input, 'RELEASE_INVALID', 'Delivery endpoint is invalid.');
|
|
7
|
+
exact(value, ['href', 'method', 'authentication'], 'RELEASE_INVALID');
|
|
8
|
+
const href = absoluteHttpUrl(value.href, true, 'RELEASE_INVALID');
|
|
9
|
+
if (new URL(href).origin !== new URL(publicUrl).origin || value.method !== 'POST') {
|
|
10
|
+
fail('RELEASE_INVALID', 'Delivery endpoint must be same-origin POST.');
|
|
11
|
+
}
|
|
12
|
+
const authentication = record(value.authentication, 'RELEASE_INVALID', 'Delivery authentication is invalid.');
|
|
13
|
+
exact(authentication, ['kind'], 'RELEASE_INVALID');
|
|
14
|
+
if (authentication.kind !== 'public' && authentication.kind !== 'bearer') {
|
|
15
|
+
fail('RELEASE_INVALID', 'Delivery authentication kind is invalid.');
|
|
16
|
+
}
|
|
17
|
+
return Object.freeze({
|
|
18
|
+
href,
|
|
19
|
+
method: 'POST',
|
|
20
|
+
authentication: Object.freeze({ kind: authentication.kind }),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type Sha256Digest = `sha256:${string}`;
|
|
2
|
+
/**
|
|
3
|
+
* Digest of one canonical `DomainRelease`: its build, its addresses and issuer, and through the
|
|
4
|
+
* address the variables frozen at deploy. One per deployment.
|
|
5
|
+
*/
|
|
6
|
+
export type ReleaseDigest = Sha256Digest;
|
|
7
|
+
/**
|
|
8
|
+
* Hash of the bytes one build produced (modules, assets, schema, requirements). An address, key or
|
|
9
|
+
* secret never enters those bytes, so the same build has the same digest in every environment.
|
|
10
|
+
*/
|
|
11
|
+
export type BuildDigest = Sha256Digest;
|
|
12
|
+
export declare function admitDigest(input: unknown): Sha256Digest;
|
|
13
|
+
export declare function sha256Hex(input: Uint8Array): string;
|
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
import * as portable from '@astrale-os/kernel-dsl/value';
|
|
2
1
|
import { fail } from './error.js';
|
|
3
2
|
const DIGEST = /^sha256:[0-9a-f]{64}$/u;
|
|
4
3
|
export function admitDigest(input) {
|
|
5
4
|
if (typeof input !== 'string' || !DIGEST.test(input)) {
|
|
6
|
-
fail('
|
|
5
|
+
fail('RELEASE_INVALID', 'Digest is invalid.');
|
|
7
6
|
}
|
|
8
7
|
return input;
|
|
9
8
|
}
|
|
10
|
-
export function computeEtag(input) {
|
|
11
|
-
const bytes = portable.json.encode(input);
|
|
12
|
-
return `sha256:${sha256Hex(bytes)}`;
|
|
13
|
-
}
|
|
14
9
|
export function sha256Hex(input) {
|
|
15
10
|
const bitLength = input.length * 8;
|
|
16
11
|
const paddedLength = Math.ceil((input.length + 9) / 64) * 64;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type ReleaseErrorCode = 'RELEASE_INVALID' | 'RELEASE_DIGEST_MISMATCH' | 'RELEASE_BINDING_INVALID' | 'RELEASE_ROUTE_INVALID' | 'RELEASE_VIEW_INVALID';
|
|
2
|
+
export declare class ReleaseError extends Error {
|
|
3
|
+
readonly code: ReleaseErrorCode;
|
|
4
|
+
readonly name = "ReleaseError";
|
|
5
|
+
constructor(code: ReleaseErrorCode, message: string, options?: ErrorOptions);
|
|
6
|
+
}
|
|
7
|
+
export declare function fail(code: ReleaseErrorCode, message: string, cause?: unknown): never;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export class ReleaseError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
name = 'ReleaseError';
|
|
4
|
+
constructor(code, message, options) {
|
|
5
|
+
super(message, options);
|
|
6
|
+
this.code = code;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function fail(code, message, cause) {
|
|
10
|
+
throw new ReleaseError(code, message, cause === undefined ? undefined : { cause });
|
|
11
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { MEDIA_TYPE, PATH, accept, digestOf, encode, seal, url, type Content, type DomainRelease, } from './release.js';
|
|
2
|
+
export type { Build } from './build.js';
|
|
3
|
+
export { acceptReference, compareReferences, matchesReference, reference, type ReleaseRef, type ReleaseReferenceRelation, } from './reference.js';
|
|
4
|
+
export { acceptBundle, decodeBundle, type ServedRelease } from './bundle-admission.js';
|
|
5
|
+
export { BUNDLE_MEDIA_TYPE, bundleUrl, describeBundle, type BundleDescriptor } from './bundle.js';
|
|
6
|
+
export { verifyBundleBytes } from './bundle-evidence.js';
|
|
7
|
+
export type { Declaration, DeliveryDeclaration } from './declaration.js';
|
|
8
|
+
export { placeDelivery } from './delivery-declaration.js';
|
|
9
|
+
export type { Authentication as DeliveryAuthentication, Endpoint as DeliveryEndpoint, } from './delivery-endpoint.js';
|
|
10
|
+
export type { BuildDigest, ReleaseDigest, Sha256Digest } from './digest.js';
|
|
11
|
+
export { ReleaseError, type ReleaseErrorCode } from './error.js';
|
|
12
|
+
export type { Manifest } from './manifest.js';
|
|
13
|
+
export { ClassOperationOrder, admitRequirements, capabilityKeys, validateRequirements, type CapabilityAuthorityRequirements, type CoreCapabilityRequirement, type CapabilityRequirements, type ClassCapabilityRequirement, type ClassOperation, type NonEmpty, type Requirements, } from './requirements.js';
|
|
14
|
+
export type { BodyPick, CredentialSource, HttpMethod, HttpRoute, HttpRouteDeclaration, RouteBody, RouteHeaders, SingleCredentialSource, } from './route.js';
|
|
15
|
+
export type { Schema } from './schema.js';
|
|
16
|
+
export { acceptInvocationEndpoints, type CallableBinding, type InvocationEndpoints, type ViewBinding, } from './bindings/index.js';
|
|
17
|
+
export * as delivery from './delivery/index.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { MEDIA_TYPE, PATH, accept, digestOf, encode, seal, url, } from './release.js';
|
|
2
|
+
export { acceptReference, compareReferences, matchesReference, reference, } from './reference.js';
|
|
3
|
+
export { acceptBundle, decodeBundle } from './bundle-admission.js';
|
|
4
|
+
export { BUNDLE_MEDIA_TYPE, bundleUrl, describeBundle } from './bundle.js';
|
|
5
|
+
export { verifyBundleBytes } from './bundle-evidence.js';
|
|
6
|
+
export { placeDelivery } from './delivery-declaration.js';
|
|
7
|
+
export { ReleaseError } from './error.js';
|
|
8
|
+
export { ClassOperationOrder, admitRequirements, capabilityKeys, validateRequirements, } from './requirements.js';
|
|
9
|
+
export { acceptInvocationEndpoints, } from './bindings/index.js';
|
|
10
|
+
export * as delivery from './delivery/index.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type IssuerId } from '@astrale-os/kernel-core/auth/issuer';
|
|
2
|
+
/** Admit the exact serving issuer a served release document names. */
|
|
3
|
+
export declare function admitIssuer(input: unknown): IssuerId;
|
|
4
|
+
/** Resolve one served path beneath one exact HTTP(S) serving issuer, keeping its complete path. */
|
|
5
|
+
export declare function servedUrl(issuer: string, path: `/${string}`): string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AuthValueError } from '@astrale-os/kernel-core/auth/error';
|
|
2
|
+
import { accept as acceptIssuerId } from '@astrale-os/kernel-core/auth/issuer';
|
|
3
|
+
import { fail } from './error.js';
|
|
4
|
+
/** Admit the exact serving issuer a served release document names. */
|
|
5
|
+
export function admitIssuer(input) {
|
|
6
|
+
try {
|
|
7
|
+
return acceptIssuerId(String(input));
|
|
8
|
+
}
|
|
9
|
+
catch (cause) {
|
|
10
|
+
if (cause instanceof AuthValueError) {
|
|
11
|
+
return fail('RELEASE_INVALID', 'Release issuer is invalid.', cause);
|
|
12
|
+
}
|
|
13
|
+
throw cause;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** Resolve one served path beneath one exact HTTP(S) serving issuer, keeping its complete path. */
|
|
17
|
+
export function servedUrl(issuer, path) {
|
|
18
|
+
const admitted = admitIssuer(issuer);
|
|
19
|
+
let value;
|
|
20
|
+
try {
|
|
21
|
+
value = new URL(admitted);
|
|
22
|
+
}
|
|
23
|
+
catch (cause) {
|
|
24
|
+
return fail('RELEASE_INVALID', 'Release issuer is not an HTTP(S) URL.', cause);
|
|
25
|
+
}
|
|
26
|
+
if ((value.protocol !== 'https:' && value.protocol !== 'http:') ||
|
|
27
|
+
value.username !== '' ||
|
|
28
|
+
value.password !== '' ||
|
|
29
|
+
value.search !== '' ||
|
|
30
|
+
value.hash !== '') {
|
|
31
|
+
fail('RELEASE_INVALID', 'Release issuer is not an HTTP(S) URL.');
|
|
32
|
+
}
|
|
33
|
+
return `${admitted.replace(/\/+$/u, '')}${path}`;
|
|
34
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface Manifest {
|
|
2
|
+
readonly logo?: string;
|
|
3
|
+
}
|
|
4
|
+
/** The root Schema owns the Domain name and entrypoint in every release. */
|
|
5
|
+
export declare function assertAuthoredManifest(input: unknown): void;
|
|
6
|
+
export declare function admitManifest(input: unknown): Manifest | undefined;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { fail } from './error.js';
|
|
2
|
+
import { exact, record } from './shape.js';
|
|
3
|
+
/** The root Schema owns the Domain name and entrypoint in every release. */
|
|
4
|
+
export function assertAuthoredManifest(input) {
|
|
5
|
+
if (input === null || typeof input !== 'object')
|
|
6
|
+
return;
|
|
7
|
+
for (const removed of ['title', 'entrypoint']) {
|
|
8
|
+
if (Object.hasOwn(input, removed)) {
|
|
9
|
+
fail('RELEASE_INVALID', `Manifest ${removed} is removed: the root schema declares the Domain name and entrypoint.`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function admitManifest(input) {
|
|
14
|
+
if (input === undefined)
|
|
15
|
+
return undefined;
|
|
16
|
+
const value = record(input, 'RELEASE_INVALID', 'Manifest is invalid.');
|
|
17
|
+
exact(value, ['logo'], 'RELEASE_INVALID');
|
|
18
|
+
if (value.logo !== undefined && typeof value.logo !== 'string') {
|
|
19
|
+
fail('RELEASE_INVALID', 'Manifest logo must be a string.');
|
|
20
|
+
}
|
|
21
|
+
return Object.freeze(value.logo === undefined ? {} : { logo: value.logo });
|
|
22
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Revision } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
2
|
+
import { type Key } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
3
|
+
import type { ReleaseDigest } from './digest.js';
|
|
4
|
+
import type { DomainRelease } from './release.js';
|
|
5
|
+
/** Compact untrusted projection that witnesses one exact `DomainRelease`. */
|
|
6
|
+
export interface ReleaseRef {
|
|
7
|
+
readonly origin: Key.Origin;
|
|
8
|
+
readonly identity: DomainRelease['identity'];
|
|
9
|
+
readonly revision: Revision;
|
|
10
|
+
readonly digest: ReleaseDigest;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Relation between two `ReleaseRef`s. A release digest commits to identity, schema and build, so
|
|
14
|
+
* within one origin anything but the exact reference designates another release.
|
|
15
|
+
*/
|
|
16
|
+
export type ReleaseReferenceRelation = 'exact' | 'release-drift' | 'origin-drift';
|
|
17
|
+
export declare function reference(input: DomainRelease): ReleaseRef;
|
|
18
|
+
export declare function acceptReference(input: unknown): ReleaseRef;
|
|
19
|
+
/**
|
|
20
|
+
* Classify a received reference against the expected one. A different origin is origin drift.
|
|
21
|
+
* Within one origin the reference is exact only when every coordinate equals the expected one: for
|
|
22
|
+
* references of admitted releases that is digest equality, and a received reference that names
|
|
23
|
+
* the expected digest beside any other coordinate witnesses no release, so it is release drift.
|
|
24
|
+
*/
|
|
25
|
+
export declare function compareReferences(input: ReleaseRef, expected: ReleaseRef): ReleaseReferenceRelation;
|
|
26
|
+
export declare function matchesReference(input: DomainRelease, expected: ReleaseRef): boolean;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { AuthValueError } from '@astrale-os/kernel-core/auth/error';
|
|
2
|
+
import { accept as acceptIssuerId } from '@astrale-os/kernel-core/auth/issuer';
|
|
3
|
+
import { patterns } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
4
|
+
import { admitDigest } from './digest.js';
|
|
5
|
+
import { fail } from './error.js';
|
|
6
|
+
import { deepFreeze, exact, record } from './shape.js';
|
|
7
|
+
export function reference(input) {
|
|
8
|
+
return deepFreeze({
|
|
9
|
+
origin: input.origin,
|
|
10
|
+
identity: { ...input.identity },
|
|
11
|
+
revision: input.schema.revision,
|
|
12
|
+
digest: input.digest,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export function acceptReference(input) {
|
|
16
|
+
const value = record(input, 'RELEASE_INVALID', 'Release reference is invalid.');
|
|
17
|
+
exact(value, ['origin', 'identity', 'revision', 'digest'], 'RELEASE_INVALID');
|
|
18
|
+
if (typeof value.origin !== 'string' || !patterns.origin.test(value.origin)) {
|
|
19
|
+
fail('RELEASE_INVALID', 'Release reference origin is invalid.');
|
|
20
|
+
}
|
|
21
|
+
const origin = value.origin;
|
|
22
|
+
const identity = record(value.identity, 'RELEASE_INVALID', 'Release reference identity is invalid.');
|
|
23
|
+
exact(identity, ['issuer', 'subject'], 'RELEASE_INVALID');
|
|
24
|
+
const issuer = acceptIssuer(identity.issuer);
|
|
25
|
+
if (identity.subject !== origin) {
|
|
26
|
+
fail('RELEASE_INVALID', 'Release reference subject must equal its root origin.');
|
|
27
|
+
}
|
|
28
|
+
if (typeof value.revision !== 'string' || !patterns.revision.test(value.revision)) {
|
|
29
|
+
fail('RELEASE_INVALID', 'Release reference revision is invalid.');
|
|
30
|
+
}
|
|
31
|
+
return deepFreeze({
|
|
32
|
+
origin,
|
|
33
|
+
identity: { issuer, subject: origin },
|
|
34
|
+
revision: value.revision,
|
|
35
|
+
digest: admitDigest(value.digest),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Classify a received reference against the expected one. A different origin is origin drift.
|
|
40
|
+
* Within one origin the reference is exact only when every coordinate equals the expected one: for
|
|
41
|
+
* references of admitted releases that is digest equality, and a received reference that names
|
|
42
|
+
* the expected digest beside any other coordinate witnesses no release, so it is release drift.
|
|
43
|
+
*/
|
|
44
|
+
export function compareReferences(input, expected) {
|
|
45
|
+
if (input.origin !== expected.origin)
|
|
46
|
+
return 'origin-drift';
|
|
47
|
+
return input.digest === expected.digest &&
|
|
48
|
+
input.revision === expected.revision &&
|
|
49
|
+
input.identity.issuer === expected.identity.issuer &&
|
|
50
|
+
input.identity.subject === expected.identity.subject
|
|
51
|
+
? 'exact'
|
|
52
|
+
: 'release-drift';
|
|
53
|
+
}
|
|
54
|
+
export function matchesReference(input, expected) {
|
|
55
|
+
return compareReferences(reference(input), expected) === 'exact';
|
|
56
|
+
}
|
|
57
|
+
function acceptIssuer(input) {
|
|
58
|
+
if (typeof input !== 'string') {
|
|
59
|
+
fail('RELEASE_INVALID', 'Release reference issuer is invalid.');
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
return acceptIssuerId(input);
|
|
63
|
+
}
|
|
64
|
+
catch (cause) {
|
|
65
|
+
if (cause instanceof AuthValueError) {
|
|
66
|
+
return fail('RELEASE_INVALID', 'Release reference issuer is invalid.', cause);
|
|
67
|
+
}
|
|
68
|
+
throw cause;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { IssuerId } from '@astrale-os/kernel-core/auth/issuer';
|
|
2
|
+
import type { Key } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
3
|
+
import type { CallableBinding, ViewBinding } from './bindings/index.js';
|
|
4
|
+
import type { Build } from './build.js';
|
|
5
|
+
import type { Endpoint as DeliveryEndpoint } from './delivery-endpoint.js';
|
|
6
|
+
import type { ReleaseDigest } from './digest.js';
|
|
7
|
+
import type { Manifest } from './manifest.js';
|
|
8
|
+
import type { Requirements } from './requirements.js';
|
|
9
|
+
import type { HttpRoute } from './route.js';
|
|
10
|
+
import type { Schema } from './schema.js';
|
|
11
|
+
/** Path of the release a deployment serves beneath its serving issuer. */
|
|
12
|
+
export declare const PATH: '/.well-known/astrale/release.json';
|
|
13
|
+
/** Media type of `DomainRelease` v4. */
|
|
14
|
+
export declare const MEDIA_TYPE: 'application/vnd.astrale.domain-release+json;v=4';
|
|
15
|
+
/** Resolve the canonical release URL beneath one exact HTTP(S) serving issuer, keeping its path. */
|
|
16
|
+
export declare function url(issuer: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Immutable deployment document pinned by the Kernel. Its digest covers the schema, serving
|
|
19
|
+
* identity and code through the build digest; `version` identifies the document format.
|
|
20
|
+
*/
|
|
21
|
+
export interface DomainRelease {
|
|
22
|
+
readonly format: 'astrale.domain.release';
|
|
23
|
+
readonly version: 4;
|
|
24
|
+
readonly origin: Key.Origin;
|
|
25
|
+
readonly identity: {
|
|
26
|
+
readonly issuer: IssuerId;
|
|
27
|
+
/** Always the root origin. */
|
|
28
|
+
readonly subject: string;
|
|
29
|
+
};
|
|
30
|
+
readonly digest: ReleaseDigest;
|
|
31
|
+
readonly build: Build;
|
|
32
|
+
readonly schema: Schema;
|
|
33
|
+
readonly requirements: Requirements;
|
|
34
|
+
readonly manifest?: Manifest;
|
|
35
|
+
readonly bindings: {
|
|
36
|
+
readonly callables: readonly CallableBinding[];
|
|
37
|
+
readonly views: readonly ViewBinding[];
|
|
38
|
+
};
|
|
39
|
+
readonly routes: readonly HttpRoute[];
|
|
40
|
+
readonly delivery?: DeliveryEndpoint;
|
|
41
|
+
}
|
|
42
|
+
/** Content of a release before its digest is computed. */
|
|
43
|
+
export type Content = Omit<DomainRelease, 'digest'>;
|
|
44
|
+
/**
|
|
45
|
+
* Release digest of canonical release content: SHA-256 of its canonical JSON. The content must be
|
|
46
|
+
* exactly what `seal` produces, a sealed release without its digest; content that `seal` would
|
|
47
|
+
* canonicalize or refuse throws `ReleaseError` instead of yielding a digest no decoder accepts.
|
|
48
|
+
* Authored content gets its digest from `seal(content).digest`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function digestOf(input: Content): ReleaseDigest;
|
|
51
|
+
/** Validate release content, canonicalize its View bindings, and set its release digest. */
|
|
52
|
+
export declare function seal(input: Content): DomainRelease;
|
|
53
|
+
/**
|
|
54
|
+
* Admit one wire release: exact fields at every level, every value in the canonical form `seal`
|
|
55
|
+
* produces, and a digest matching its content. One release therefore has exactly one wire
|
|
56
|
+
* document: `encode(accept(input))` is the canonical JSON of `input`.
|
|
57
|
+
*/
|
|
58
|
+
export declare function accept(input: unknown): DomainRelease;
|
|
59
|
+
/** Encode one release as its canonical JSON bytes. */
|
|
60
|
+
export declare function encode(input: DomainRelease): Uint8Array;
|