@astrale-os/kernel-protocol 0.5.0-beta.45 → 0.5.0-beta.46

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.
@@ -1,5 +1,6 @@
1
1
  import * as portable from '@astrale-os/kernel-dsl/value';
2
2
  import { fail } from './error.js';
3
+ import { encodeLegacyRequirements } from './legacy-requirements.js';
3
4
  const DIGEST = /^sha256:[0-9a-f]{64}$/u;
4
5
  export function admitDigest(input) {
5
6
  if (typeof input !== 'string' || !DIGEST.test(input)) {
@@ -8,7 +9,10 @@ export function admitDigest(input) {
8
9
  return input;
9
10
  }
10
11
  export function computeEtag(input) {
11
- const bytes = portable.json.encode(input);
12
+ const wire = input.version === 2
13
+ ? { ...input, requirements: encodeLegacyRequirements(input.requirements) }
14
+ : input;
15
+ const bytes = portable.json.encode(wire);
12
16
  return `sha256:${sha256Hex(bytes)}`;
13
17
  }
14
18
  export function sha256Hex(input) {
@@ -1,4 +1,4 @@
1
- export { MEDIA_TYPE, PATH, url, seal, accept, encode, type Publication } from './publication.js';
1
+ export { MEDIA_TYPE, LEGACY_MEDIA_TYPE, mediaType, PATH, url, seal, accept, encode, type Publication, } from './publication.js';
2
2
  export { acceptBundle, decodeBundle } from './bundle-admission.js';
3
3
  export { BUNDLE_MEDIA_TYPE, describeBundle, type BundleDescriptor } from './bundle.js';
4
4
  export { verifyBundleBytes } from './bundle-evidence.js';
@@ -10,7 +10,7 @@ export { PublicationError, type PublicationErrorCode } from './error.js';
10
10
  export * as delivery from './delivery/index.js';
11
11
  export type { Authentication as DeliveryAuthentication, Endpoint as DeliveryEndpoint, } from './delivery-endpoint.js';
12
12
  export type { Manifest } from './manifest.js';
13
- export { ClassOperationOrder, admitRequirements, validateRequirements, type CapabilityAuthorityRequirements, type CapabilityProfileRequirement, type CapabilityRequirements, type ClassCapabilityRequirement, type ClassOperation, type NonEmpty, type PublicationRequirements, } from './requirements.js';
13
+ export { ClassOperationOrder, admitRequirements, validateRequirements, type CapabilityAuthorityRequirements, type CoreCapabilityRequirement, type CapabilityRequirements, type ClassCapabilityRequirement, type ClassOperation, type NonEmpty, type PublicationRequirements, } from './requirements.js';
14
14
  export { acceptReference, compareReference, compareReferences, matchesReference, reference, type PublicationRef, type PublicationReferenceRelation, } from './reference.js';
15
15
  export type { BodyPick, CredentialSource, HttpMethod, HttpRoute, HttpRouteDeclaration, RouteBody, RouteHeaders, SingleCredentialSource, } from './route.js';
16
16
  export type { Schema } from './schema.js';
@@ -1,4 +1,4 @@
1
- export { MEDIA_TYPE, PATH, url, seal, accept, encode } from './publication.js';
1
+ export { MEDIA_TYPE, LEGACY_MEDIA_TYPE, mediaType, PATH, url, seal, accept, encode, } from './publication.js';
2
2
  export { acceptBundle, decodeBundle } from './bundle-admission.js';
3
3
  export { BUNDLE_MEDIA_TYPE, describeBundle } from './bundle.js';
4
4
  export { verifyBundleBytes } from './bundle-evidence.js';
@@ -0,0 +1,12 @@
1
+ import type { PublicationRequirements } from './requirements.js';
2
+ /** Read the retired v2 wire coordinate without exposing it in canonical requirements. */
3
+ export declare function decodeLegacyRequirements(input: unknown): PublicationRequirements;
4
+ /** Preserve immutable v2 bytes and the historical installation-target digest vocabulary. */
5
+ export declare function encodeLegacyRequirements(input: PublicationRequirements): Readonly<{
6
+ capabilities: Readonly<{
7
+ functions?: import("./requirements.js").NonEmpty<import("@astrale-os/kernel-dsl/v1").Key.Callable>;
8
+ classes?: import("./requirements.js").NonEmpty<import("./requirements.js").ClassCapabilityRequirement>;
9
+ authority?: import("./requirements.js").CapabilityAuthorityRequirements;
10
+ profiles?: import("./requirements.js").NonEmpty<import("./requirements.js").CoreCapabilityRequirement> | undefined;
11
+ }>;
12
+ }>;
@@ -0,0 +1,23 @@
1
+ import { exact, record } from './content.js';
2
+ import { admitRequirements } from './requirements.js';
3
+ /** Read the retired v2 wire coordinate without exposing it in canonical requirements. */
4
+ export function decodeLegacyRequirements(input) {
5
+ const requirements = record(input, 'PUBLICATION_INVALID', 'Publication requirements are invalid.');
6
+ exact(requirements, ['capabilities'], 'PUBLICATION_INVALID');
7
+ const capabilities = record(requirements.capabilities, 'PUBLICATION_INVALID', 'Publication capability requirements are invalid.');
8
+ exact(capabilities, ['authority', 'classes', 'functions', 'profiles'], 'PUBLICATION_INVALID');
9
+ const { profiles, ...principal } = capabilities;
10
+ return admitRequirements({
11
+ capabilities: { ...principal, ...(profiles === undefined ? {} : { core: profiles }) },
12
+ });
13
+ }
14
+ /** Preserve immutable v2 bytes and the historical installation-target digest vocabulary. */
15
+ export function encodeLegacyRequirements(input) {
16
+ const { core, ...principal } = input.capabilities;
17
+ return Object.freeze({
18
+ capabilities: Object.freeze({
19
+ ...principal,
20
+ ...(core === undefined ? {} : { profiles: core }),
21
+ }),
22
+ });
23
+ }
@@ -9,12 +9,14 @@ import type { PublicationRequirements } from './requirements.js';
9
9
  import type { HttpRoute } from './route.js';
10
10
  import type { Schema } from './schema.js';
11
11
  export declare const PATH: '/.well-known/astrale/domain.json';
12
- export declare const MEDIA_TYPE: 'application/vnd.astrale.domain-publication+json;v=2';
12
+ export declare const MEDIA_TYPE: 'application/vnd.astrale.domain-publication+json;v=3';
13
+ export declare const LEGACY_MEDIA_TYPE: 'application/vnd.astrale.domain-publication+json;v=2';
14
+ export declare function mediaType(input: Pick<Publication, 'version'>): typeof MEDIA_TYPE | typeof LEGACY_MEDIA_TYPE;
13
15
  /** Resolve the canonical Publication URL beneath one exact HTTP(S) serving issuer. */
14
16
  export declare function url(input: string): string;
15
17
  export interface Publication {
16
18
  readonly format: 'astrale.domain.publication';
17
- readonly version: 2;
19
+ readonly version: 2 | 3;
18
20
  readonly origin: Key.Origin;
19
21
  readonly identity: {
20
22
  readonly issuer: IssuerId;
@@ -8,12 +8,17 @@ import { array, deepFreeze, exact, record, unique } from './content.js';
8
8
  import { admitEndpoint as admitDeliveryEndpoint } from './delivery-endpoint.js';
9
9
  import { fail } from './error.js';
10
10
  import { admitDigest, computeEtag } from './etag.js';
11
+ import { decodeLegacyRequirements, encodeLegacyRequirements } from './legacy-requirements.js';
11
12
  import { admitManifest } from './manifest.js';
12
13
  import { admitRequirements } from './requirements.js';
13
14
  import { admitRoute } from './route.js';
14
15
  import { admitSchema } from './schema.js';
15
16
  export const PATH = '/.well-known/astrale/domain.json';
16
- export const MEDIA_TYPE = 'application/vnd.astrale.domain-publication+json;v=2';
17
+ export const MEDIA_TYPE = 'application/vnd.astrale.domain-publication+json;v=3';
18
+ export const LEGACY_MEDIA_TYPE = 'application/vnd.astrale.domain-publication+json;v=2';
19
+ export function mediaType(input) {
20
+ return input.version === 2 ? LEGACY_MEDIA_TYPE : MEDIA_TYPE;
21
+ }
17
22
  /** Resolve the canonical Publication URL beneath one exact HTTP(S) serving issuer. */
18
23
  export function url(input) {
19
24
  const admitted = admitIssuer(input);
@@ -34,20 +39,23 @@ export function url(input) {
34
39
  return `${admitted.replace(/\/+$/u, '')}${PATH}`;
35
40
  }
36
41
  export function seal(input) {
37
- const canonical = withoutEtag(acceptPublication({ ...input, etag: 'canonicalizing' }, false, true));
38
- return acceptPublication({ ...canonical, etag: computeEtag(canonical) }, false);
42
+ const canonical = withoutEtag(acceptPublication({ ...input, etag: 'canonicalizing' }, 'seal'));
43
+ return acceptPublication({ ...canonical, etag: computeEtag(canonical) }, 'canonical');
39
44
  }
40
45
  export function accept(input) {
41
- const publication = acceptPublication(input, true);
46
+ const publication = acceptPublication(input, 'wire');
42
47
  if (publication.etag !== computeEtag(withoutEtag(publication))) {
43
48
  fail('PUBLICATION_ETAG_MISMATCH', 'Publication ETag does not match its canonical content.');
44
49
  }
45
50
  return publication;
46
51
  }
47
52
  export function encode(input) {
48
- return portable.json.encode(input);
53
+ const wire = input.version === 2
54
+ ? { ...input, requirements: encodeLegacyRequirements(input.requirements) }
55
+ : input;
56
+ return portable.json.encode(wire);
49
57
  }
50
- function acceptPublication(input, requireEtag, canonicalizeViews = false) {
58
+ function acceptPublication(input, mode) {
51
59
  const value = record(input, 'PUBLICATION_INVALID', 'Expected a Publication object.');
52
60
  exact(value, [
53
61
  'format',
@@ -62,7 +70,8 @@ function acceptPublication(input, requireEtag, canonicalizeViews = false) {
62
70
  'routes',
63
71
  'delivery',
64
72
  ], 'PUBLICATION_INVALID');
65
- if (value.format !== 'astrale.domain.publication' || value.version !== 2) {
73
+ if (value.format !== 'astrale.domain.publication' ||
74
+ (value.version !== 2 && value.version !== 3)) {
66
75
  fail('PUBLICATION_INVALID', 'Unsupported Publication format or version.');
67
76
  }
68
77
  if (typeof value.origin !== 'string' || !patterns.origin.test(value.origin)) {
@@ -76,13 +85,15 @@ function acceptPublication(input, requireEtag, canonicalizeViews = false) {
76
85
  fail('PUBLICATION_INVALID', 'Publication identity subject must equal its root origin.');
77
86
  }
78
87
  const publishedSchema = admitSchema(value.schema);
79
- const requirements = admitRequirements(value.requirements);
88
+ const requirements = value.version === 2 && mode === 'wire'
89
+ ? decodeLegacyRequirements(value.requirements)
90
+ : admitRequirements(value.requirements);
80
91
  const manifest = admitManifest(value.manifest);
81
92
  const bindingSet = record(value.bindings, 'PUBLICATION_BINDING_INVALID', 'Publication bindings are invalid.');
82
93
  exact(bindingSet, ['callables', 'views'], 'PUBLICATION_BINDING_INVALID');
83
94
  const callables = array(bindingSet.callables, 'PUBLICATION_BINDING_INVALID').map((entry) => admitCallableBinding(entry, origin));
84
95
  unique(callables.map(({ callable }) => callable), 'PUBLICATION_BINDING_INVALID', 'callable binding');
85
- const views = array(bindingSet.views, 'PUBLICATION_VIEW_INVALID').map((entry) => canonicalizeViews ? canonicalizeViewBinding(entry, origin) : admitViewBinding(entry, origin));
96
+ const views = array(bindingSet.views, 'PUBLICATION_VIEW_INVALID').map((entry) => mode === 'seal' ? canonicalizeViewBinding(entry, origin) : admitViewBinding(entry, origin));
86
97
  unique(views.map(({ view }) => view), 'PUBLICATION_VIEW_INVALID', 'View binding');
87
98
  const routes = array(value.routes, 'PUBLICATION_ROUTE_INVALID').map((entry) => admitRoute(entry, origin));
88
99
  unique(routes.map(({ name }) => name), 'PUBLICATION_ROUTE_INVALID', 'route name');
@@ -101,12 +112,12 @@ function acceptPublication(input, requireEtag, canonicalizeViews = false) {
101
112
  }
102
113
  }
103
114
  const delivery = admitDeliveryEndpoint(value.delivery, publishedSchema.bundle.href);
104
- const admittedEtag = requireEtag ? admitDigest(value.etag) : value.etag;
115
+ const admittedEtag = mode === 'wire' ? admitDigest(value.etag) : value.etag;
105
116
  if (typeof admittedEtag !== 'string')
106
117
  fail('PUBLICATION_INVALID', 'Publication ETag is missing.');
107
118
  return deepFreeze({
108
119
  format: 'astrale.domain.publication',
109
- version: 2,
120
+ version: value.version,
110
121
  origin,
111
122
  identity: { issuer, subject: origin },
112
123
  etag: admittedEtag,
@@ -16,10 +16,10 @@ export interface CapabilityRequirements {
16
16
  readonly functions?: NonEmpty<SemanticKey.Callable>;
17
17
  readonly classes?: NonEmpty<ClassCapabilityRequirement>;
18
18
  readonly authority?: CapabilityAuthorityRequirements;
19
- readonly profiles?: NonEmpty<CapabilityProfileRequirement>;
19
+ readonly core?: NonEmpty<CoreCapabilityRequirement>;
20
20
  }
21
21
  /** Installation-owned authority materialized onto one local Core Identity. */
22
- export interface CapabilityProfileRequirement {
22
+ export interface CoreCapabilityRequirement {
23
23
  readonly subject: CoreRef;
24
24
  readonly functions?: NonEmpty<SemanticKey.Callable>;
25
25
  readonly classes?: NonEmpty<ClassCapabilityRequirement>;
@@ -28,7 +28,7 @@ export interface CapabilityProfileRequirement {
28
28
  export interface PublicationRequirements {
29
29
  readonly capabilities: CapabilityRequirements;
30
30
  }
31
- /** Admit the exact closed Publication v2 requirements object. */
31
+ /** Admit the exact closed canonical requirements object used by Publication v3. */
32
32
  export declare function admitRequirements(input: unknown): PublicationRequirements;
33
33
  /** Bind explicit capability targets to exact functional dependencies in the bundle closure. */
34
34
  export declare function validateRequirements(root: schemaLanguage.DomainSchema, closure: readonly schemaLanguage.DomainSchema[], requirements: PublicationRequirements): void;
@@ -15,12 +15,12 @@ export const ClassOperationOrder = Object.freeze([
15
15
  'update',
16
16
  'delete',
17
17
  ]);
18
- /** Admit the exact closed Publication v2 requirements object. */
18
+ /** Admit the exact closed canonical requirements object used by Publication v3. */
19
19
  export function admitRequirements(input) {
20
20
  const requirements = record(input, 'PUBLICATION_INVALID', 'Publication requirements are invalid.');
21
21
  exact(requirements, ['capabilities'], 'PUBLICATION_INVALID');
22
22
  const capabilities = record(requirements.capabilities, 'PUBLICATION_INVALID', 'Publication capability requirements are invalid.');
23
- exact(capabilities, ['authority', 'classes', 'functions', 'profiles'], 'PUBLICATION_INVALID');
23
+ exact(capabilities, ['authority', 'classes', 'functions', 'core'], 'PUBLICATION_INVALID');
24
24
  const functions = capabilities.functions === undefined
25
25
  ? undefined
26
26
  : nonEmpty(capabilities.functions, 'Function capability requirements').map(admitCallable);
@@ -38,12 +38,12 @@ export function admitRequirements(input) {
38
38
  const authority = capabilities.authority === undefined
39
39
  ? undefined
40
40
  : admitAuthorityRequirements(capabilities.authority);
41
- const profiles = capabilities.profiles === undefined
41
+ const core = capabilities.core === undefined
42
42
  ? undefined
43
- : nonEmpty(capabilities.profiles, 'Capability profiles').map(admitCapabilityProfile);
44
- if (profiles !== undefined) {
45
- unique(profiles.map(({ subject }) => Key.of(subject)), 'PUBLICATION_INVALID', 'Capability profile subject');
46
- canonical(profiles, (left, right) => compare(Key.of(left.subject), Key.of(right.subject)), 'Capability profiles');
43
+ : nonEmpty(capabilities.core, 'Core capability requirements').map(admitCoreCapabilityRequirement);
44
+ if (core !== undefined) {
45
+ unique(core.map(({ subject }) => Key.of(subject)), 'PUBLICATION_INVALID', 'Core capability subject');
46
+ canonical(core, (left, right) => compare(Key.of(left.subject), Key.of(right.subject)), 'Core capability requirements');
47
47
  }
48
48
  return deepFreeze({
49
49
  capabilities: {
@@ -54,33 +54,35 @@ export function admitRequirements(input) {
54
54
  ? {}
55
55
  : { classes: Object.freeze(classes) }),
56
56
  ...(authority === undefined ? {} : { authority }),
57
- ...(profiles === undefined
57
+ ...(core === undefined
58
58
  ? {}
59
- : { profiles: Object.freeze(profiles) }),
59
+ : { core: Object.freeze(core) }),
60
60
  },
61
61
  });
62
62
  }
63
- function admitCapabilityProfile(input) {
64
- const profile = record(input, 'PUBLICATION_INVALID', 'Capability profile requirement is invalid.');
65
- exact(profile, ['authority', 'classes', 'functions', 'subject'], 'PUBLICATION_INVALID');
66
- const subject = admitCoreRef(profile.subject);
67
- const functions = profile.functions === undefined
63
+ function admitCoreCapabilityRequirement(input) {
64
+ const requirement = record(input, 'PUBLICATION_INVALID', 'Core capability requirement is invalid.');
65
+ exact(requirement, ['authority', 'classes', 'functions', 'subject'], 'PUBLICATION_INVALID');
66
+ const subject = admitCoreRef(requirement.subject);
67
+ const functions = requirement.functions === undefined
68
68
  ? undefined
69
- : nonEmpty(profile.functions, 'Capability profile Function requirements').map(admitCallable);
69
+ : nonEmpty(requirement.functions, 'Core capability Function requirements').map(admitCallable);
70
70
  if (functions !== undefined) {
71
- unique(functions, 'PUBLICATION_INVALID', 'Capability profile Function requirement');
72
- canonical(functions, compare, 'Capability profile Function requirements');
71
+ unique(functions, 'PUBLICATION_INVALID', 'Core capability Function requirement');
72
+ canonical(functions, compare, 'Core capability Function requirements');
73
73
  }
74
- const classes = profile.classes === undefined
74
+ const classes = requirement.classes === undefined
75
75
  ? undefined
76
- : nonEmpty(profile.classes, 'Capability profile Class requirements').map(admitClassRequirement);
76
+ : nonEmpty(requirement.classes, 'Core capability Class requirements').map(admitClassRequirement);
77
77
  if (classes !== undefined) {
78
- unique(classes.map((entry) => classKey(entry.class)), 'PUBLICATION_INVALID', 'Capability profile Class requirement');
79
- canonical(classes, (left, right) => compare(classKey(left.class), classKey(right.class)), 'Capability profile Class requirements');
78
+ unique(classes.map((entry) => classKey(entry.class)), 'PUBLICATION_INVALID', 'Core capability Class requirement');
79
+ canonical(classes, (left, right) => compare(classKey(left.class), classKey(right.class)), 'Core capability Class requirements');
80
80
  }
81
- const authority = profile.authority === undefined ? undefined : admitAuthorityRequirements(profile.authority);
81
+ const authority = requirement.authority === undefined
82
+ ? undefined
83
+ : admitAuthorityRequirements(requirement.authority);
82
84
  if (functions === undefined && classes === undefined && authority === undefined) {
83
- fail('PUBLICATION_INVALID', 'Capability profile requirements cannot be empty.');
85
+ fail('PUBLICATION_INVALID', 'Core capability requirements cannot be empty.');
84
86
  }
85
87
  return Object.freeze({
86
88
  subject,
@@ -109,26 +111,26 @@ export function validateRequirements(root, closure, requirements) {
109
111
  fail('PUBLICATION_INVALID', 'Class capability requirement names an unknown dependency Class.');
110
112
  }
111
113
  }
112
- for (const profile of requirements.capabilities.profiles ?? []) {
113
- const subject = root.core.nodes[profile.subject.name];
114
- if (profile.subject.origin !== root.origin ||
114
+ for (const requirement of requirements.capabilities.core ?? []) {
115
+ const subject = root.core.nodes[requirement.subject.name];
116
+ if (requirement.subject.origin !== root.origin ||
115
117
  subject === undefined ||
116
118
  !classSatisfies(root, dependencies, subject.class, KernelIdentityClass)) {
117
- fail('PUBLICATION_INVALID', 'Capability profile subject must name one local Core Identity.');
119
+ fail('PUBLICATION_INVALID', 'Core capability subject must name one local Core Identity.');
118
120
  }
119
- for (const callable of profile.functions ?? []) {
121
+ for (const callable of requirement.functions ?? []) {
120
122
  const dependency = capabilityOwner(root, dependencies, callable, 'Function');
121
123
  if (!hasCallable(dependency, callable)) {
122
- fail('PUBLICATION_INVALID', 'Capability profile Function requirement names an unknown dependency callable.');
124
+ fail('PUBLICATION_INVALID', 'Core capability Function requirement names an unknown dependency callable.');
123
125
  }
124
126
  }
125
- for (const requirement of profile.classes ?? []) {
126
- const key = Key.of(requirement.class);
127
- const owner = requirement.class.origin === root.origin
127
+ for (const classRequirement of requirement.classes ?? []) {
128
+ const key = Key.of(classRequirement.class);
129
+ const owner = classRequirement.class.origin === root.origin
128
130
  ? root
129
131
  : capabilityOwner(root, dependencies, key, 'Class');
130
- if (owner.classes[requirement.class.name] === undefined) {
131
- fail('PUBLICATION_INVALID', 'Capability profile Class requirement names an unknown dependency Class.');
132
+ if (owner.classes[classRequirement.class.name] === undefined) {
133
+ fail('PUBLICATION_INVALID', 'Core capability Class requirement names an unknown dependency Class.');
132
134
  }
133
135
  }
134
136
  }
@@ -229,10 +231,10 @@ function admitCoreRef(input) {
229
231
  value = acceptDefinitionRef(input);
230
232
  }
231
233
  catch {
232
- fail('PUBLICATION_INVALID', 'Capability profile subject is invalid.');
234
+ fail('PUBLICATION_INVALID', 'Core capability subject is invalid.');
233
235
  }
234
236
  if (value.kind !== 'core') {
235
- fail('PUBLICATION_INVALID', 'Capability profile subject must be one local Core reference.');
237
+ fail('PUBLICATION_INVALID', 'Core capability subject must be one local Core reference.');
236
238
  }
237
239
  return value;
238
240
  }
@@ -31,13 +31,15 @@ export interface Module {
31
31
  /** Immutable portable closure selected by one local Schema installation source. */
32
32
  export interface RuntimeArtifact {
33
33
  readonly format: 'astrale.domain-runtime-artifact';
34
- readonly version: 2;
34
+ readonly version: 2 | 3;
35
35
  readonly issuer: IssuerId;
36
36
  readonly bundle: Ref;
37
37
  readonly requirements: PublicationRequirements;
38
38
  readonly modules: readonly [Module, ...Module[]];
39
39
  }
40
- export declare const MEDIA_TYPE: 'application/vnd.astrale.domain-runtime+json;v=2';
40
+ export declare const MEDIA_TYPE: 'application/vnd.astrale.domain-runtime+json;v=3';
41
+ export declare const LEGACY_MEDIA_TYPE: 'application/vnd.astrale.domain-runtime+json;v=2';
42
+ export declare function mediaType(input: Pick<RuntimeArtifact, 'version'>): typeof MEDIA_TYPE | typeof LEGACY_MEDIA_TYPE;
41
43
  export declare function accept(input: unknown): RuntimeArtifact;
42
44
  export declare function encode(input: RuntimeArtifact): Uint8Array;
43
45
  export declare function runtimeDigest(input: RuntimeArtifact): Digest;
@@ -4,18 +4,26 @@ import * as portable from '@astrale-os/kernel-dsl/value';
4
4
  import { sha256 } from '@noble/hashes/sha2.js';
5
5
  import * as artifact from '../artifact/index.js';
6
6
  import { admitRequirements } from '../publication/index.js';
7
- export const MEDIA_TYPE = 'application/vnd.astrale.domain-runtime+json;v=2';
7
+ import { decodeLegacyRequirements, encodeLegacyRequirements, } from '../publication/legacy-requirements.js';
8
+ export const MEDIA_TYPE = 'application/vnd.astrale.domain-runtime+json;v=3';
9
+ export const LEGACY_MEDIA_TYPE = 'application/vnd.astrale.domain-runtime+json;v=2';
10
+ export function mediaType(input) {
11
+ return input.version === 2 ? LEGACY_MEDIA_TYPE : MEDIA_TYPE;
12
+ }
8
13
  export function accept(input) {
9
14
  const root = record(input, 'Domain Runtime artifact');
10
15
  exact(root, ['bundle', 'format', 'issuer', 'modules', 'requirements', 'version']);
11
- if (root.format !== 'astrale.domain-runtime-artifact' || root.version !== 2) {
16
+ if (root.format !== 'astrale.domain-runtime-artifact' ||
17
+ (root.version !== 2 && root.version !== 3)) {
12
18
  invalid('Domain Runtime artifact format or version is unsupported.');
13
19
  }
14
20
  if (typeof root.issuer !== 'string')
15
21
  invalid('Domain Runtime artifact issuer is invalid.');
16
22
  const admittedIssuer = acceptIssuer(root.issuer);
17
23
  const bundle = artifact.accept(root.bundle);
18
- const requirements = admitRequirements(root.requirements);
24
+ const requirements = root.version === 2
25
+ ? decodeLegacyRequirements(root.requirements)
26
+ : admitRequirements(root.requirements);
19
27
  if (!Array.isArray(root.modules) || root.modules.length === 0) {
20
28
  invalid('Domain Runtime artifact requires at least one Module.');
21
29
  }
@@ -26,7 +34,7 @@ export function accept(input) {
26
34
  }
27
35
  return Object.freeze({
28
36
  format: 'astrale.domain-runtime-artifact',
29
- version: 2,
37
+ version: root.version,
30
38
  issuer: admittedIssuer,
31
39
  bundle,
32
40
  requirements,
@@ -34,10 +42,15 @@ export function accept(input) {
34
42
  });
35
43
  }
36
44
  export function encode(input) {
37
- return portable.json.encode(accept(input));
45
+ return portable.json.encode(wire(accept(wire(input))));
38
46
  }
39
47
  export function runtimeDigest(input) {
40
- return digestBytes(portable.json.encode(input));
48
+ return digestBytes(portable.json.encode(wire(input)));
49
+ }
50
+ function wire(input) {
51
+ return input.version === 2
52
+ ? { ...input, requirements: encodeLegacyRequirements(input.requirements) }
53
+ : input;
41
54
  }
42
55
  export function acceptExecutable(input) {
43
56
  return executable(input, 0);
@@ -1 +1 @@
1
- export { MEDIA_TYPE, accept, acceptExecutable, encode, executableClosure, executableDigest, runtimeDigest, type Digest, type Executable, type ExecutableDependency, type Export, type Module, type RuntimeArtifact, type Ref, } from './artifact.js';
1
+ export { MEDIA_TYPE, LEGACY_MEDIA_TYPE, mediaType, accept, acceptExecutable, encode, executableClosure, executableDigest, runtimeDigest, type Digest, type Executable, type ExecutableDependency, type Export, type Module, type RuntimeArtifact, type Ref, } from './artifact.js';
@@ -1 +1 @@
1
- export { MEDIA_TYPE, accept, acceptExecutable, encode, executableClosure, executableDigest, runtimeDigest, } from './artifact.js';
1
+ export { MEDIA_TYPE, LEGACY_MEDIA_TYPE, mediaType, accept, acceptExecutable, encode, executableClosure, executableDigest, runtimeDigest, } from './artifact.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/kernel-protocol",
3
- "version": "0.5.0-beta.45",
3
+ "version": "0.5.0-beta.46",
4
4
  "description": "Portable Astrale invocation, publication, installation, routing, and issuer protocols",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -75,6 +75,10 @@
75
75
  "types": "./dist/publication/requirements.d.ts",
76
76
  "import": "./dist/publication/requirements.js"
77
77
  },
78
+ "./publication/legacy-requirements": {
79
+ "types": "./dist/publication/legacy-requirements.d.ts",
80
+ "import": "./dist/publication/legacy-requirements.js"
81
+ },
78
82
  "./publication/delivery": {
79
83
  "types": "./dist/publication/delivery/index.d.ts",
80
84
  "import": "./dist/publication/delivery/index.js"