@astrale-os/kernel-protocol 0.5.0-beta.67 → 0.5.0-beta.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/dist/codecs/index.d.ts +1 -2
  2. package/dist/codecs/index.js +1 -2
  3. package/dist/codecs/media-types.d.ts +0 -1
  4. package/dist/codecs/media-types.js +0 -1
  5. package/dist/index.d.ts +3 -1
  6. package/dist/index.js +3 -1
  7. package/dist/invocation/admission.d.ts +2 -2
  8. package/dist/invocation/admission.js +5 -5
  9. package/dist/invocation/binding-codecs.d.ts +1 -1
  10. package/dist/invocation/binding-codecs.js +1 -6
  11. package/dist/invocation/http/negotiation.js +3 -10
  12. package/dist/invocation/index.d.ts +5 -2
  13. package/dist/invocation/index.js +4 -2
  14. package/dist/invocation/legacy/index.d.ts +29 -0
  15. package/dist/invocation/legacy/index.js +28 -0
  16. package/dist/invocation/legacy/route.d.ts +14 -0
  17. package/dist/invocation/legacy/route.js +25 -0
  18. package/dist/invocation/legacy/via.d.ts +30 -0
  19. package/dist/invocation/legacy/via.js +15 -0
  20. package/dist/invocation/redirect.d.ts +15 -12
  21. package/dist/invocation/redirect.js +9 -21
  22. package/dist/invocation/request.js +2 -2
  23. package/dist/invocation/routing.d.ts +19 -5
  24. package/dist/invocation/routing.js +50 -20
  25. package/dist/invocation/schema.d.ts +3 -8
  26. package/dist/invocation/schema.js +3 -10
  27. package/dist/invocation/service-reason.d.ts +13 -13
  28. package/dist/invocation/service-reason.js +13 -12
  29. package/dist/invocation/stale-route.d.ts +1 -1
  30. package/dist/invocation/target.d.ts +3 -0
  31. package/dist/invocation/target.js +8 -1
  32. package/dist/invocation/websocket/index.d.ts +1 -1
  33. package/dist/invocation/websocket/index.js +1 -1
  34. package/dist/invocation/websocket/subprotocol.d.ts +1 -2
  35. package/dist/invocation/websocket/subprotocol.js +3 -4
  36. package/dist/issuer/claims.d.ts +7 -0
  37. package/dist/issuer/claims.js +7 -0
  38. package/dist/issuer/index.d.ts +2 -0
  39. package/dist/issuer/index.js +2 -0
  40. package/dist/issuer/paths.d.ts +2 -0
  41. package/dist/issuer/paths.js +1 -0
  42. package/dist/issuer/workflow-engine.d.ts +18 -0
  43. package/dist/issuer/workflow-engine.js +59 -0
  44. package/dist/{publication → release}/bindings/callable.js +6 -6
  45. package/dist/release/bindings/invocation.js +13 -0
  46. package/dist/{publication → release}/bindings/view.d.ts +1 -1
  47. package/dist/{publication → release}/bindings/view.js +10 -10
  48. package/dist/release/build.d.ts +6 -0
  49. package/dist/release/build.js +7 -0
  50. package/dist/release/bundle-admission.d.ts +21 -0
  51. package/dist/{publication → release}/bundle-admission.js +18 -18
  52. package/dist/{publication → release}/bundle-descriptor.js +6 -6
  53. package/dist/release/bundle-evidence.d.ts +3 -0
  54. package/dist/release/bundle-evidence.js +13 -0
  55. package/dist/{publication → release}/bundle.d.ts +2 -2
  56. package/dist/{publication → release}/bundle.js +9 -9
  57. package/dist/{publication → release}/delivery/attestation.d.ts +1 -0
  58. package/dist/release/delivery/claims.d.ts +21 -0
  59. package/dist/release/delivery/claims.js +68 -0
  60. package/dist/release/delivery/delivery.d.ts +11 -0
  61. package/dist/release/delivery/delivery.js +24 -0
  62. package/dist/{publication → release}/delivery/error.d.ts +2 -0
  63. package/dist/{publication → release}/delivery/error.js +1 -0
  64. package/dist/{publication → release}/delivery/index.d.ts +6 -1
  65. package/dist/{publication → release}/delivery/index.js +5 -0
  66. package/dist/{publication → release}/delivery/limits.d.ts +2 -1
  67. package/dist/release/delivery/limits.js +5 -0
  68. package/dist/release/delivery/request.d.ts +21 -0
  69. package/dist/release/delivery/request.js +60 -0
  70. package/dist/{publication → release}/delivery-declaration.js +4 -4
  71. package/dist/release/delivery-endpoint.js +22 -0
  72. package/dist/release/digest.d.ts +13 -0
  73. package/dist/{publication/etag.js → release/digest.js} +1 -10
  74. package/dist/release/error.d.ts +7 -0
  75. package/dist/release/error.js +11 -0
  76. package/dist/release/index.d.ts +21 -0
  77. package/dist/release/index.js +14 -0
  78. package/dist/release/issuer.d.ts +5 -0
  79. package/dist/release/issuer.js +34 -0
  80. package/dist/release/legacy/bindings.d.ts +14 -0
  81. package/dist/release/legacy/bindings.js +11 -0
  82. package/dist/release/legacy/bundle-admission.d.ts +6 -0
  83. package/dist/release/legacy/bundle-admission.js +10 -0
  84. package/dist/release/legacy/bundle-evidence.d.ts +8 -0
  85. package/dist/release/legacy/bundle-evidence.js +11 -0
  86. package/dist/release/legacy/bundle.d.ts +24 -0
  87. package/dist/release/legacy/bundle.js +31 -0
  88. package/dist/release/legacy/delivery/attestation.d.ts +6 -0
  89. package/dist/release/legacy/delivery/attestation.js +1 -0
  90. package/dist/{publication → release/legacy}/delivery/claims.d.ts +9 -0
  91. package/dist/{publication → release/legacy}/delivery/claims.js +7 -2
  92. package/dist/release/legacy/delivery/delivery.d.ts +18 -0
  93. package/dist/{publication → release/legacy}/delivery/delivery.js +6 -2
  94. package/dist/release/legacy/delivery/error.d.ts +16 -0
  95. package/dist/release/legacy/delivery/error.js +6 -0
  96. package/dist/release/legacy/delivery/index.d.ts +13 -0
  97. package/dist/release/legacy/delivery/index.js +13 -0
  98. package/dist/release/legacy/delivery/limits.d.ts +21 -0
  99. package/dist/release/legacy/delivery/limits.js +22 -0
  100. package/dist/release/legacy/delivery/request.d.ts +26 -0
  101. package/dist/{publication → release/legacy}/delivery/request.js +8 -3
  102. package/dist/release/legacy/delivery-declaration.d.ts +9 -0
  103. package/dist/release/legacy/delivery-declaration.js +11 -0
  104. package/dist/release/legacy/error.d.ts +26 -0
  105. package/dist/release/legacy/error.js +114 -0
  106. package/dist/release/legacy/etag.d.ts +12 -0
  107. package/dist/release/legacy/etag.js +14 -0
  108. package/dist/release/legacy/index.d.ts +75 -0
  109. package/dist/{publication → release/legacy}/index.js +7 -2
  110. package/dist/release/legacy/profiles.d.ts +23 -0
  111. package/dist/release/legacy/profiles.js +36 -0
  112. package/dist/release/legacy/projection.d.ts +13 -0
  113. package/dist/release/legacy/projection.js +25 -0
  114. package/dist/release/legacy/publication.d.ts +90 -0
  115. package/dist/release/legacy/publication.js +124 -0
  116. package/dist/release/legacy/reference.d.ts +47 -0
  117. package/dist/{publication → release/legacy}/reference.js +27 -3
  118. package/dist/release/legacy/requirements.d.ts +30 -0
  119. package/dist/release/legacy/requirements.js +20 -0
  120. package/dist/{publication → release}/manifest.js +6 -6
  121. package/dist/release/reference.d.ts +26 -0
  122. package/dist/release/reference.js +70 -0
  123. package/dist/release/release.d.ts +60 -0
  124. package/dist/release/release.js +123 -0
  125. package/dist/{publication → release}/requirements.d.ts +11 -4
  126. package/dist/{publication → release}/requirements.js +58 -39
  127. package/dist/{publication → release}/route.js +24 -24
  128. package/dist/{publication → release}/schema.js +4 -4
  129. package/dist/release/served.d.ts +26 -0
  130. package/dist/release/served.js +65 -0
  131. package/dist/release/shape.d.ts +8 -0
  132. package/dist/routes/admission.d.ts +1 -1
  133. package/dist/routes/admission.js +7 -85
  134. package/dist/routes/binding.d.ts +3 -3
  135. package/dist/routes/binding.js +10 -19
  136. package/dist/routes/compiler.d.ts +2 -2
  137. package/dist/routes/compiler.js +3 -2
  138. package/dist/routes/credential.d.ts +12 -1
  139. package/dist/routes/credential.js +11 -3
  140. package/dist/routes/decode.d.ts +5 -2
  141. package/dist/routes/decode.js +19 -6
  142. package/dist/routes/deployment-retired.d.ts +30 -0
  143. package/dist/routes/deployment-retired.js +96 -0
  144. package/dist/routes/encode.js +7 -6
  145. package/dist/routes/entries.d.ts +23 -0
  146. package/dist/routes/entries.js +150 -0
  147. package/dist/routes/evidence.d.ts +3 -3
  148. package/dist/routes/evidence.js +7 -34
  149. package/dist/routes/header.d.ts +9 -0
  150. package/dist/routes/header.js +34 -0
  151. package/dist/routes/index.d.ts +5 -2
  152. package/dist/routes/index.js +5 -2
  153. package/dist/routes/legacy/index.d.ts +29 -0
  154. package/dist/routes/legacy/index.js +29 -0
  155. package/dist/routes/legacy/routing.d.ts +24 -0
  156. package/dist/routes/legacy/routing.js +45 -0
  157. package/dist/routes/legacy/table.d.ts +34 -0
  158. package/dist/routes/legacy/table.js +50 -0
  159. package/dist/routes/mapping.d.ts +1 -1
  160. package/dist/routes/mapping.js +4 -4
  161. package/dist/routes/match.d.ts +1 -1
  162. package/dist/routes/placement.d.ts +2 -2
  163. package/dist/routes/plan.d.ts +1 -1
  164. package/dist/routes/request.d.ts +5 -3
  165. package/dist/routes/reservation.d.ts +4 -2
  166. package/dist/routes/reservation.js +4 -3
  167. package/dist/routes/routing.d.ts +30 -6
  168. package/dist/routes/routing.js +34 -49
  169. package/dist/routes/select.d.ts +1 -1
  170. package/dist/routes/table.d.ts +3 -2
  171. package/dist/routes/target.d.ts +5 -0
  172. package/dist/routes/target.js +16 -0
  173. package/dist/runtime/artifact.d.ts +2 -2
  174. package/dist/runtime/artifact.js +2 -2
  175. package/dist/schema/installation/codec.d.ts +6 -1
  176. package/dist/schema/installation/codec.js +10 -7
  177. package/dist/schema/installation/introspection/codec.d.ts +11 -2
  178. package/dist/schema/installation/introspection/codec.js +184 -47
  179. package/dist/schema/installation/introspection/index.d.ts +1 -1
  180. package/dist/schema/installation/introspection/result.d.ts +61 -1
  181. package/dist/schema/installation/legacy/index.d.ts +39 -0
  182. package/dist/schema/installation/legacy/index.js +39 -0
  183. package/dist/schema/installation/legacy/installation.d.ts +9 -0
  184. package/dist/schema/installation/legacy/installation.js +28 -0
  185. package/dist/schema/installation/legacy/introspection.d.ts +10 -0
  186. package/dist/schema/installation/legacy/introspection.js +70 -0
  187. package/dist/schema/installation/legacy/msgpack.d.ts +1 -0
  188. package/dist/schema/installation/legacy/msgpack.js +143 -0
  189. package/package.json +63 -36
  190. package/dist/codecs/msgpack.d.ts +0 -4
  191. package/dist/codecs/msgpack.js +0 -53
  192. package/dist/publication/bindings/invocation.js +0 -13
  193. package/dist/publication/bundle-admission.d.ts +0 -4
  194. package/dist/publication/bundle-evidence.d.ts +0 -3
  195. package/dist/publication/bundle-evidence.js +0 -13
  196. package/dist/publication/content.d.ts +0 -10
  197. package/dist/publication/delivery/delivery.d.ts +0 -9
  198. package/dist/publication/delivery/limits.js +0 -4
  199. package/dist/publication/delivery/request.d.ts +0 -15
  200. package/dist/publication/delivery-endpoint.js +0 -22
  201. package/dist/publication/error.d.ts +0 -7
  202. package/dist/publication/error.js +0 -11
  203. package/dist/publication/etag.d.ts +0 -6
  204. package/dist/publication/index.d.ts +0 -17
  205. package/dist/publication/legacy-requirements.d.ts +0 -12
  206. package/dist/publication/legacy-requirements.js +0 -23
  207. package/dist/publication/publication.d.ts +0 -38
  208. package/dist/publication/publication.js +0 -146
  209. package/dist/publication/reference.d.ts +0 -17
  210. package/dist/routes/release-unavailable.d.ts +0 -8
  211. package/dist/routes/release-unavailable.js +0 -32
  212. /package/dist/{publication → release}/bindings/callable.d.ts +0 -0
  213. /package/dist/{publication → release}/bindings/index.d.ts +0 -0
  214. /package/dist/{publication → release}/bindings/index.js +0 -0
  215. /package/dist/{publication → release}/bindings/invocation.d.ts +0 -0
  216. /package/dist/{publication → release}/bundle-descriptor.d.ts +0 -0
  217. /package/dist/{publication → release}/declaration.d.ts +0 -0
  218. /package/dist/{publication → release}/declaration.js +0 -0
  219. /package/dist/{publication → release}/delivery/attestation.js +0 -0
  220. /package/dist/{publication → release}/delivery-declaration.d.ts +0 -0
  221. /package/dist/{publication → release}/delivery-endpoint.d.ts +0 -0
  222. /package/dist/{publication → release}/manifest.d.ts +0 -0
  223. /package/dist/{publication → release}/route.d.ts +0 -0
  224. /package/dist/{publication → release}/schema.d.ts +0 -0
  225. /package/dist/{publication/content.js → release/shape.js} +0 -0
@@ -0,0 +1,123 @@
1
+ import * as portable from '@astrale-os/kernel-dsl/value';
2
+ import { admitBuild } from './build.js';
3
+ import { admitDigest, sha256Hex } from './digest.js';
4
+ import { fail } from './error.js';
5
+ import { servedUrl } from './issuer.js';
6
+ import { admitServedContent } from './served.js';
7
+ import { deepFreeze, exact, record } from './shape.js';
8
+ /** Path of the release a deployment serves beneath its serving issuer. */
9
+ export const PATH = '/.well-known/astrale/release.json';
10
+ /** Media type of `DomainRelease` v4. */
11
+ export const MEDIA_TYPE = 'application/vnd.astrale.domain-release+json;v=4';
12
+ /** Resolve the canonical release URL beneath one exact HTTP(S) serving issuer, keeping its path. */
13
+ export function url(issuer) {
14
+ return servedUrl(issuer, PATH);
15
+ }
16
+ const CONTENT_FIELDS = [
17
+ 'format',
18
+ 'version',
19
+ 'origin',
20
+ 'identity',
21
+ 'build',
22
+ 'schema',
23
+ 'requirements',
24
+ 'manifest',
25
+ 'bindings',
26
+ 'routes',
27
+ 'delivery',
28
+ ];
29
+ /**
30
+ * Release digest of canonical release content: SHA-256 of its canonical JSON. The content must be
31
+ * exactly what `seal` produces, a sealed release without its digest; content that `seal` would
32
+ * canonicalize or refuse throws `ReleaseError` instead of yielding a digest no decoder accepts.
33
+ * Authored content gets its digest from `seal(content).digest`.
34
+ */
35
+ export function digestOf(input) {
36
+ const value = record(input, 'RELEASE_INVALID', 'Expected release content.');
37
+ exact(value, CONTENT_FIELDS, 'RELEASE_INVALID');
38
+ return hash(admitCanonical(value));
39
+ }
40
+ /** Validate release content, canonicalize its View bindings, and set its release digest. */
41
+ export function seal(input) {
42
+ const authored = record(input, 'RELEASE_INVALID', 'Expected release content.');
43
+ exact(authored, CONTENT_FIELDS, 'RELEASE_INVALID');
44
+ const canonical = admitContent(admitContent(authored, 'canonicalize'), 'admit');
45
+ return release(canonical, hash(canonical));
46
+ }
47
+ /**
48
+ * Admit one wire release: exact fields at every level, every value in the canonical form `seal`
49
+ * produces, and a digest matching its content. One release therefore has exactly one wire
50
+ * document: `encode(accept(input))` is the canonical JSON of `input`.
51
+ */
52
+ export function accept(input) {
53
+ const value = record(input, 'RELEASE_INVALID', 'Expected a DomainRelease object.');
54
+ exact(value, [...CONTENT_FIELDS, 'digest'], 'RELEASE_INVALID');
55
+ const { digest: received, ...wire } = value;
56
+ const content = admitCanonical(wire);
57
+ const digest = admitDigest(received);
58
+ if (digest !== hash(content)) {
59
+ fail('RELEASE_DIGEST_MISMATCH', 'Release digest does not match its canonical content.');
60
+ }
61
+ return release(content, digest);
62
+ }
63
+ /** Encode one release as its canonical JSON bytes. */
64
+ export function encode(input) {
65
+ return portable.json.encode(input);
66
+ }
67
+ function admitContent(value, views) {
68
+ if (value.format !== 'astrale.domain.release' || value.version !== 4) {
69
+ fail('RELEASE_INVALID', 'Unsupported release format or version.');
70
+ }
71
+ const served = admitServedContent(value, { views });
72
+ const build = admitBuild(value.build);
73
+ return deepFreeze({
74
+ format: 'astrale.domain.release',
75
+ version: 4,
76
+ origin: served.origin,
77
+ identity: served.identity,
78
+ build,
79
+ schema: served.schema,
80
+ requirements: served.requirements,
81
+ ...(served.manifest === undefined ? {} : { manifest: served.manifest }),
82
+ bindings: served.bindings,
83
+ routes: served.routes,
84
+ ...(served.delivery === undefined ? {} : { delivery: served.delivery }),
85
+ });
86
+ }
87
+ /** Shared validators normalize legacy values; releases require their exact canonical spelling. */
88
+ function admitCanonical(value) {
89
+ const content = admitContent(value, 'admit');
90
+ const canonical = portable.json.serialize(content);
91
+ let received;
92
+ try {
93
+ received = portable.json.serialize(value);
94
+ }
95
+ catch (cause) {
96
+ if (cause instanceof portable.BoundaryError) {
97
+ return fail('RELEASE_INVALID', 'Release content is not in canonical form.', cause);
98
+ }
99
+ throw cause;
100
+ }
101
+ if (received !== canonical)
102
+ fail('RELEASE_INVALID', 'Release content is not in canonical form.');
103
+ return content;
104
+ }
105
+ function hash(content) {
106
+ return `sha256:${sha256Hex(portable.json.encode(content))}`;
107
+ }
108
+ function release(content, digest) {
109
+ return deepFreeze({
110
+ format: content.format,
111
+ version: content.version,
112
+ origin: content.origin,
113
+ identity: content.identity,
114
+ digest,
115
+ build: content.build,
116
+ schema: content.schema,
117
+ requirements: content.requirements,
118
+ ...(content.manifest === undefined ? {} : { manifest: content.manifest }),
119
+ bindings: content.bindings,
120
+ routes: content.routes,
121
+ ...(content.delivery === undefined ? {} : { delivery: content.delivery }),
122
+ });
123
+ }
@@ -25,10 +25,17 @@ export interface CoreCapabilityRequirement {
25
25
  readonly classes?: NonEmpty<ClassCapabilityRequirement>;
26
26
  readonly authority?: CapabilityAuthorityRequirements;
27
27
  }
28
- export interface PublicationRequirements {
28
+ export interface Requirements {
29
29
  readonly capabilities: CapabilityRequirements;
30
30
  }
31
- /** Admit the exact closed canonical requirements object used by Publication v3. */
32
- export declare function admitRequirements(input: unknown): PublicationRequirements;
31
+ /**
32
+ * Admit the exact closed canonical requirements object, the `core` form Publication v3 introduced.
33
+ */
34
+ export declare function admitRequirements(input: unknown): Requirements;
33
35
  /** Bind explicit capability targets to exact functional dependencies in the bundle closure. */
34
- export declare function validateRequirements(root: schemaLanguage.DomainSchema, closure: readonly schemaLanguage.DomainSchema[], requirements: PublicationRequirements): void;
36
+ export declare function validateRequirements(root: schemaLanguage.DomainSchema, closure: readonly schemaLanguage.DomainSchema[], requirements: Requirements): void;
37
+ /**
38
+ * Distinct Function, Method and Class Keys, including Core-scoped targets, in code-unit order.
39
+ * Core subjects and authority operations contribute no Keys. Results may span multiple origins.
40
+ */
41
+ export declare function capabilityKeys(capabilities: CapabilityRequirements): readonly SemanticKey[];
@@ -2,8 +2,8 @@ import { AuthorityOperationOrder } from '@astrale-os/kernel-core/schema/authorit
2
2
  import { acceptDefinitionRef, callableAddress, Key } from '@astrale-os/kernel-dsl/v1/addressing';
3
3
  import { isMethodExecutable } from '@astrale-os/kernel-dsl/v1/language';
4
4
  import { revision } from '@astrale-os/kernel-dsl/v1/schema/revision';
5
- import { array, deepFreeze, exact, record, unique } from './content.js';
6
5
  import { fail } from './error.js';
6
+ import { array, deepFreeze, exact, record, unique } from './shape.js';
7
7
  const KernelIdentityClass = Object.freeze({
8
8
  origin: 'kernel.astrale.ai',
9
9
  kind: 'class',
@@ -16,24 +16,26 @@ export const ClassOperationOrder = Object.freeze([
16
16
  'update',
17
17
  'delete',
18
18
  ]);
19
- /** Admit the exact closed canonical requirements object used by Publication v3. */
19
+ /**
20
+ * Admit the exact closed canonical requirements object, the `core` form Publication v3 introduced.
21
+ */
20
22
  export function admitRequirements(input) {
21
- const requirements = record(input, 'PUBLICATION_INVALID', 'Publication requirements are invalid.');
22
- exact(requirements, ['capabilities'], 'PUBLICATION_INVALID');
23
- const capabilities = record(requirements.capabilities, 'PUBLICATION_INVALID', 'Publication capability requirements are invalid.');
24
- exact(capabilities, ['authority', 'classes', 'functions', 'core'], 'PUBLICATION_INVALID');
23
+ const requirements = record(input, 'RELEASE_INVALID', 'Release requirements are invalid.');
24
+ exact(requirements, ['capabilities'], 'RELEASE_INVALID');
25
+ const capabilities = record(requirements.capabilities, 'RELEASE_INVALID', 'Release capability requirements are invalid.');
26
+ exact(capabilities, ['authority', 'classes', 'functions', 'core'], 'RELEASE_INVALID');
25
27
  const functions = capabilities.functions === undefined
26
28
  ? undefined
27
29
  : nonEmpty(capabilities.functions, 'Function capability requirements').map(admitCallable);
28
30
  if (functions !== undefined) {
29
- unique(functions, 'PUBLICATION_INVALID', 'Function capability requirement');
31
+ unique(functions, 'RELEASE_INVALID', 'Function capability requirement');
30
32
  canonical(functions, compare, 'Function capability requirements');
31
33
  }
32
34
  const classes = capabilities.classes === undefined
33
35
  ? undefined
34
36
  : nonEmpty(capabilities.classes, 'Class capability requirements').map(admitClassRequirement);
35
37
  if (classes !== undefined) {
36
- unique(classes.map((entry) => classKey(entry.class)), 'PUBLICATION_INVALID', 'Class capability requirement');
38
+ unique(classes.map((entry) => classKey(entry.class)), 'RELEASE_INVALID', 'Class capability requirement');
37
39
  canonical(classes, (left, right) => compare(classKey(left.class), classKey(right.class)), 'Class capability requirements');
38
40
  }
39
41
  const authority = capabilities.authority === undefined
@@ -43,7 +45,7 @@ export function admitRequirements(input) {
43
45
  ? undefined
44
46
  : nonEmpty(capabilities.core, 'Core capability requirements').map(admitCoreCapabilityRequirement);
45
47
  if (core !== undefined) {
46
- unique(core.map(({ subject }) => Key.of(subject)), 'PUBLICATION_INVALID', 'Core capability subject');
48
+ unique(core.map(({ subject }) => Key.of(subject)), 'RELEASE_INVALID', 'Core capability subject');
47
49
  canonical(core, (left, right) => compare(Key.of(left.subject), Key.of(right.subject)), 'Core capability requirements');
48
50
  }
49
51
  return deepFreeze({
@@ -62,28 +64,28 @@ export function admitRequirements(input) {
62
64
  });
63
65
  }
64
66
  function admitCoreCapabilityRequirement(input) {
65
- const requirement = record(input, 'PUBLICATION_INVALID', 'Core capability requirement is invalid.');
66
- exact(requirement, ['authority', 'classes', 'functions', 'subject'], 'PUBLICATION_INVALID');
67
+ const requirement = record(input, 'RELEASE_INVALID', 'Core capability requirement is invalid.');
68
+ exact(requirement, ['authority', 'classes', 'functions', 'subject'], 'RELEASE_INVALID');
67
69
  const subject = admitCoreRef(requirement.subject);
68
70
  const functions = requirement.functions === undefined
69
71
  ? undefined
70
72
  : nonEmpty(requirement.functions, 'Core capability Function requirements').map(admitCallable);
71
73
  if (functions !== undefined) {
72
- unique(functions, 'PUBLICATION_INVALID', 'Core capability Function requirement');
74
+ unique(functions, 'RELEASE_INVALID', 'Core capability Function requirement');
73
75
  canonical(functions, compare, 'Core capability Function requirements');
74
76
  }
75
77
  const classes = requirement.classes === undefined
76
78
  ? undefined
77
79
  : nonEmpty(requirement.classes, 'Core capability Class requirements').map(admitClassRequirement);
78
80
  if (classes !== undefined) {
79
- unique(classes.map((entry) => classKey(entry.class)), 'PUBLICATION_INVALID', 'Core capability Class requirement');
81
+ unique(classes.map((entry) => classKey(entry.class)), 'RELEASE_INVALID', 'Core capability Class requirement');
80
82
  canonical(classes, (left, right) => compare(classKey(left.class), classKey(right.class)), 'Core capability Class requirements');
81
83
  }
82
84
  const authority = requirement.authority === undefined
83
85
  ? undefined
84
86
  : admitAuthorityRequirements(requirement.authority);
85
87
  if (functions === undefined && classes === undefined && authority === undefined) {
86
- fail('PUBLICATION_INVALID', 'Core capability requirements cannot be empty.');
88
+ fail('RELEASE_INVALID', 'Core capability requirements cannot be empty.');
87
89
  }
88
90
  return Object.freeze({
89
91
  subject,
@@ -102,14 +104,14 @@ export function validateRequirements(root, closure, requirements) {
102
104
  for (const callable of requirements.capabilities.functions ?? []) {
103
105
  const dependency = capabilityOwner(root, dependencies, callable, 'Function');
104
106
  if (!hasCallable(dependency, callable)) {
105
- fail('PUBLICATION_INVALID', 'Function capability requirement names an unknown dependency callable.');
107
+ fail('RELEASE_INVALID', 'Function capability requirement names an unknown dependency callable.');
106
108
  }
107
109
  }
108
110
  for (const requirement of requirements.capabilities.classes ?? []) {
109
111
  const key = Key.of(requirement.class);
110
112
  const dependency = capabilityOwner(root, dependencies, key, 'Class');
111
113
  if (dependency.classes[requirement.class.name] === undefined) {
112
- fail('PUBLICATION_INVALID', 'Class capability requirement names an unknown dependency Class.');
114
+ fail('RELEASE_INVALID', 'Class capability requirement names an unknown dependency Class.');
113
115
  }
114
116
  }
115
117
  for (const requirement of requirements.capabilities.core ?? []) {
@@ -117,14 +119,14 @@ export function validateRequirements(root, closure, requirements) {
117
119
  if (requirement.subject.origin !== root.origin ||
118
120
  subject === undefined ||
119
121
  !classSatisfies(root, dependencies, subject.class, KernelIdentityClass)) {
120
- fail('PUBLICATION_INVALID', 'Core capability subject must name one local Core Identity.');
122
+ fail('RELEASE_INVALID', 'Core capability subject must name one local Core Identity.');
121
123
  }
122
124
  for (const callable of requirement.functions ?? []) {
123
125
  const owner = Key.origin(callable) === root.origin
124
126
  ? root
125
127
  : capabilityOwner(root, dependencies, callable, 'Function');
126
128
  if (!hasCallable(owner, callable)) {
127
- fail('PUBLICATION_INVALID', 'Core capability Function requirement names an unknown or non-executable callable.');
129
+ fail('RELEASE_INVALID', 'Core capability Function requirement names an unknown or non-executable callable.');
128
130
  }
129
131
  }
130
132
  for (const classRequirement of requirement.classes ?? []) {
@@ -133,11 +135,28 @@ export function validateRequirements(root, closure, requirements) {
133
135
  ? root
134
136
  : capabilityOwner(root, dependencies, key, 'Class');
135
137
  if (owner.classes[classRequirement.class.name] === undefined) {
136
- fail('PUBLICATION_INVALID', 'Core capability Class requirement names an unknown dependency Class.');
138
+ fail('RELEASE_INVALID', 'Core capability Class requirement names an unknown dependency Class.');
137
139
  }
138
140
  }
139
141
  }
140
142
  }
143
+ /**
144
+ * Distinct Function, Method and Class Keys, including Core-scoped targets, in code-unit order.
145
+ * Core subjects and authority operations contribute no Keys. Results may span multiple origins.
146
+ */
147
+ export function capabilityKeys(capabilities) {
148
+ const keys = new Set(capabilities.functions);
149
+ for (const requirement of capabilities.classes ?? [])
150
+ keys.add(Key.of(requirement.class));
151
+ for (const requirement of capabilities.core ?? []) {
152
+ for (const callable of requirement.functions ?? [])
153
+ keys.add(callable);
154
+ for (const classRequirement of requirement.classes ?? []) {
155
+ keys.add(Key.of(classRequirement.class));
156
+ }
157
+ }
158
+ return Object.freeze([...keys].sort(compare));
159
+ }
141
160
  function classSatisfies(root, dependencies, candidate, expected) {
142
161
  const expectedKey = Key.of(expected);
143
162
  const visited = new Set();
@@ -161,7 +180,7 @@ function exactDependencyClosure(root, closure) {
161
180
  for (const coordinate of Object.values(owner.dependencies)) {
162
181
  const dependency = supplied.get(coordinate.origin);
163
182
  if (dependency === undefined || revision(dependency) !== coordinate.revision) {
164
- fail('PUBLICATION_INVALID', 'Publication Bundle omits an exact dependency.');
183
+ fail('RELEASE_INVALID', 'Release bundle omits an exact dependency.');
165
184
  }
166
185
  if (reachable.has(dependency.origin))
167
186
  continue;
@@ -174,21 +193,21 @@ function exactDependencyClosure(root, closure) {
174
193
  }
175
194
  function admitCallable(input) {
176
195
  if (typeof input !== 'string' || !Key.is(input)) {
177
- return fail('PUBLICATION_INVALID', 'Function capability reference is invalid.');
196
+ return fail('RELEASE_INVALID', 'Function capability reference is invalid.');
178
197
  }
179
198
  try {
180
199
  callableAddress(input);
181
200
  }
182
201
  catch {
183
- return fail('PUBLICATION_INVALID', 'Function capability reference must name a Function or Method.');
202
+ return fail('RELEASE_INVALID', 'Function capability reference must name a Function or Method.');
184
203
  }
185
204
  return input;
186
205
  }
187
206
  function admitClassRequirement(input) {
188
- const requirement = record(input, 'PUBLICATION_INVALID', 'Class capability requirement is invalid.');
189
- exact(requirement, ['class', 'operations'], 'PUBLICATION_INVALID');
207
+ const requirement = record(input, 'RELEASE_INVALID', 'Class capability requirement is invalid.');
208
+ exact(requirement, ['class', 'operations'], 'RELEASE_INVALID');
190
209
  const operations = nonEmpty(requirement.operations, 'Class capability operations').map((entry) => admitOperation(entry, ClassOperationOrder, 'Class capability operation'));
191
- unique(operations, 'PUBLICATION_INVALID', 'Class capability operation');
210
+ unique(operations, 'RELEASE_INVALID', 'Class capability operation');
192
211
  canonical(operations, ordered(ClassOperationOrder), 'Class capability operations');
193
212
  return Object.freeze({
194
213
  class: admitClassRef(requirement.class),
@@ -196,10 +215,10 @@ function admitClassRequirement(input) {
196
215
  });
197
216
  }
198
217
  function admitAuthorityRequirements(input) {
199
- const authority = record(input, 'PUBLICATION_INVALID', 'Authority capability requirements are invalid.');
200
- exact(authority, ['grant', 'revoke'], 'PUBLICATION_INVALID');
218
+ const authority = record(input, 'RELEASE_INVALID', 'Authority capability requirements are invalid.');
219
+ exact(authority, ['grant', 'revoke'], 'RELEASE_INVALID');
201
220
  if (authority.grant === undefined && authority.revoke === undefined) {
202
- fail('PUBLICATION_INVALID', 'Authority capability requirements cannot be empty.');
221
+ fail('RELEASE_INVALID', 'Authority capability requirements cannot be empty.');
203
222
  }
204
223
  const grant = admitAuthorityOperations(authority.grant, 'grant');
205
224
  const revoke = admitAuthorityOperations(authority.revoke, 'revoke');
@@ -212,7 +231,7 @@ function admitAuthorityOperations(input, kind) {
212
231
  if (input === undefined)
213
232
  return undefined;
214
233
  const values = nonEmpty(input, `Authority ${kind} operations`).map((entry) => admitOperation(entry, AuthorityOperationOrder, `Authority ${kind} operation`));
215
- unique(values, 'PUBLICATION_INVALID', `authority ${kind} operation`);
234
+ unique(values, 'RELEASE_INVALID', `authority ${kind} operation`);
216
235
  canonical(values, ordered(AuthorityOperationOrder), `Authority ${kind} operations`);
217
236
  return Object.freeze(values);
218
237
  }
@@ -222,10 +241,10 @@ function admitClassRef(input) {
222
241
  value = acceptDefinitionRef(input);
223
242
  }
224
243
  catch {
225
- fail('PUBLICATION_INVALID', 'Class capability reference is invalid.');
244
+ fail('RELEASE_INVALID', 'Class capability reference is invalid.');
226
245
  }
227
246
  if (value.kind !== 'class')
228
- fail('PUBLICATION_INVALID', 'Class capability reference is invalid.');
247
+ fail('RELEASE_INVALID', 'Class capability reference is invalid.');
229
248
  return value;
230
249
  }
231
250
  function admitCoreRef(input) {
@@ -234,28 +253,28 @@ function admitCoreRef(input) {
234
253
  value = acceptDefinitionRef(input);
235
254
  }
236
255
  catch {
237
- fail('PUBLICATION_INVALID', 'Core capability subject is invalid.');
256
+ fail('RELEASE_INVALID', 'Core capability subject is invalid.');
238
257
  }
239
258
  if (value.kind !== 'core') {
240
- fail('PUBLICATION_INVALID', 'Core capability subject must be one local Core reference.');
259
+ fail('RELEASE_INVALID', 'Core capability subject must be one local Core reference.');
241
260
  }
242
261
  return value;
243
262
  }
244
263
  function nonEmpty(input, label) {
245
- const values = [...array(input, 'PUBLICATION_INVALID')];
264
+ const values = [...array(input, 'RELEASE_INVALID')];
246
265
  if (values.length === 0)
247
- fail('PUBLICATION_INVALID', `${label} cannot be empty.`);
266
+ fail('RELEASE_INVALID', `${label} cannot be empty.`);
248
267
  return values;
249
268
  }
250
269
  function admitOperation(input, vocabulary, label) {
251
270
  if (typeof input !== 'string' || !vocabulary.includes(input)) {
252
- return fail('PUBLICATION_INVALID', `${label} is unknown.`);
271
+ return fail('RELEASE_INVALID', `${label} is unknown.`);
253
272
  }
254
273
  return input;
255
274
  }
256
275
  function canonical(values, comparator, label) {
257
276
  if (values.some((entry, index) => index > 0 && comparator(values[index - 1], entry) >= 0)) {
258
- fail('PUBLICATION_INVALID', `${label} must be sorted canonically.`);
277
+ fail('RELEASE_INVALID', `${label} must be sorted canonically.`);
259
278
  }
260
279
  }
261
280
  function ordered(order) {
@@ -270,10 +289,10 @@ function classKey(input) {
270
289
  function capabilityOwner(root, dependencies, key, label) {
271
290
  const origin = Key.origin(key);
272
291
  if (origin === root.origin)
273
- fail('PUBLICATION_INVALID', `${label} capability target cannot be local.`);
292
+ fail('RELEASE_INVALID', `${label} capability target cannot be local.`);
274
293
  const dependency = dependencies.get(origin);
275
294
  if (dependency === undefined) {
276
- fail('PUBLICATION_INVALID', `${label} capability target must belong to the exact dependency closure.`);
295
+ fail('RELEASE_INVALID', `${label} capability target must belong to the exact dependency closure.`);
277
296
  }
278
297
  return dependency;
279
298
  }
@@ -1,31 +1,31 @@
1
1
  import { patterns } from '@astrale-os/kernel-dsl/v1/addressing';
2
2
  import { admitCallableKey } from './bindings/callable.js';
3
- import { absoluteHttpUrl, deepFreeze, exact, record } from './content.js';
4
3
  import { fail } from './error.js';
4
+ import { absoluteHttpUrl, deepFreeze, exact, record } from './shape.js';
5
5
  const ROUTE_NAME = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/u;
6
6
  export function admitRoute(input, origin) {
7
- const value = record(input, 'PUBLICATION_ROUTE_INVALID', 'Published HTTP route is invalid.');
7
+ const value = record(input, 'RELEASE_ROUTE_INVALID', 'Release HTTP route is invalid.');
8
8
  const common = admitRouteCommon(value, origin, 'href');
9
9
  return deepFreeze({
10
10
  ...common,
11
- href: absoluteHttpUrl(value.href, false, 'PUBLICATION_ROUTE_INVALID', true),
11
+ href: absoluteHttpUrl(value.href, false, 'RELEASE_ROUTE_INVALID', true),
12
12
  });
13
13
  }
14
14
  export function admitRouteDeclaration(input, origin) {
15
- const value = record(input, 'PUBLICATION_ROUTE_INVALID', 'HTTP route declaration is invalid.');
15
+ const value = record(input, 'RELEASE_ROUTE_INVALID', 'HTTP route declaration is invalid.');
16
16
  const common = admitRouteCommon(value, origin, 'path');
17
17
  if (typeof value.path !== 'string' || !value.path.startsWith('/')) {
18
- fail('PUBLICATION_ROUTE_INVALID', 'Route declaration path must be relative to one issuer.');
18
+ fail('RELEASE_ROUTE_INVALID', 'Route declaration path must be relative to one issuer.');
19
19
  }
20
20
  let parsed;
21
21
  try {
22
22
  parsed = new URL(value.path, 'https://placement.invalid');
23
23
  }
24
24
  catch (cause) {
25
- return fail('PUBLICATION_ROUTE_INVALID', 'Route declaration path is invalid.', cause);
25
+ return fail('RELEASE_ROUTE_INVALID', 'Route declaration path is invalid.', cause);
26
26
  }
27
27
  if (parsed.origin !== 'https://placement.invalid' || parsed.search !== '' || parsed.hash !== '') {
28
- fail('PUBLICATION_ROUTE_INVALID', 'Route declaration path cannot select an origin, query, or fragment.');
28
+ fail('RELEASE_ROUTE_INVALID', 'Route declaration path cannot select an origin, query, or fragment.');
29
29
  }
30
30
  return deepFreeze({ ...common, path: value.path });
31
31
  }
@@ -42,24 +42,24 @@ function admitRouteCommon(value, origin, location) {
42
42
  'headers',
43
43
  'body',
44
44
  'credential',
45
- ], 'PUBLICATION_ROUTE_INVALID');
45
+ ], 'RELEASE_ROUTE_INVALID');
46
46
  if (typeof value.name !== 'string' || !ROUTE_NAME.test(value.name)) {
47
- fail('PUBLICATION_ROUTE_INVALID', 'Route name is invalid.');
47
+ fail('RELEASE_ROUTE_INVALID', 'Route name is invalid.');
48
48
  }
49
49
  const callable = admitCallableKey(value.callable, origin);
50
50
  if (!['GET', 'POST', 'PUT', 'PATCH', 'DELETE', '*'].includes(value.method)) {
51
- fail('PUBLICATION_ROUTE_INVALID', 'Route method is invalid.');
51
+ fail('RELEASE_ROUTE_INVALID', 'Route method is invalid.');
52
52
  }
53
53
  if (value.default !== undefined && value.default !== true) {
54
- fail('PUBLICATION_ROUTE_INVALID', 'Route default may only be true.');
54
+ fail('RELEASE_ROUTE_INVALID', 'Route default may only be true.');
55
55
  }
56
56
  if (value.requestMethod !== undefined &&
57
57
  (typeof value.requestMethod !== 'string' || !patterns.memberName.test(value.requestMethod))) {
58
- fail('PUBLICATION_ROUTE_INVALID', 'Route request-method mapping is invalid.');
58
+ fail('RELEASE_ROUTE_INVALID', 'Route request-method mapping is invalid.');
59
59
  }
60
60
  if (value.receiver !== undefined &&
61
61
  (typeof value.receiver !== 'string' || !patterns.memberName.test(value.receiver))) {
62
- fail('PUBLICATION_ROUTE_INVALID', 'Route receiver is invalid.');
62
+ fail('RELEASE_ROUTE_INVALID', 'Route receiver is invalid.');
63
63
  }
64
64
  const query = stringRecord(value.query);
65
65
  const headers = headerRecord(value.headers);
@@ -83,13 +83,13 @@ function admitCredential(input) {
83
83
  return undefined;
84
84
  const values = Array.isArray(input) ? input : [input];
85
85
  if (values.length === 0)
86
- fail('PUBLICATION_ROUTE_INVALID', 'Credential source list is empty.');
86
+ fail('RELEASE_ROUTE_INVALID', 'Credential source list is empty.');
87
87
  const admitted = values.map((entry) => {
88
- const value = record(entry, 'PUBLICATION_ROUTE_INVALID', 'Credential source is invalid.');
89
- exact(value, ['in', 'name'], 'PUBLICATION_ROUTE_INVALID');
88
+ const value = record(entry, 'RELEASE_ROUTE_INVALID', 'Credential source is invalid.');
89
+ exact(value, ['in', 'name'], 'RELEASE_ROUTE_INVALID');
90
90
  if (value.in === 'header') {
91
91
  if (value.name !== undefined && (typeof value.name !== 'string' || value.name.length === 0)) {
92
- fail('PUBLICATION_ROUTE_INVALID', 'Credential header name is invalid.');
92
+ fail('RELEASE_ROUTE_INVALID', 'Credential header name is invalid.');
93
93
  }
94
94
  return Object.freeze({
95
95
  in: 'header',
@@ -101,14 +101,14 @@ function admitCredential(input) {
101
101
  value.name.length > 0) {
102
102
  return Object.freeze({ in: value.in, name: value.name });
103
103
  }
104
- return fail('PUBLICATION_ROUTE_INVALID', 'Credential source is invalid.');
104
+ return fail('RELEASE_ROUTE_INVALID', 'Credential source is invalid.');
105
105
  });
106
106
  return Array.isArray(input) ? Object.freeze(admitted) : admitted[0];
107
107
  }
108
108
  function admitBody(input) {
109
109
  if (input === undefined || input === '*')
110
110
  return input;
111
- const value = record(input, 'PUBLICATION_ROUTE_INVALID', 'Route body mapping is invalid.');
111
+ const value = record(input, 'RELEASE_ROUTE_INVALID', 'Route body mapping is invalid.');
112
112
  const output = {};
113
113
  for (const [field, pick] of Object.entries(value)) {
114
114
  if (typeof pick === 'string' && pick.length > 0)
@@ -121,7 +121,7 @@ function admitBody(input) {
121
121
  output[field] = { as: 'text' };
122
122
  }
123
123
  else {
124
- fail('PUBLICATION_ROUTE_INVALID', 'Route body pick is invalid.');
124
+ fail('RELEASE_ROUTE_INVALID', 'Route body pick is invalid.');
125
125
  }
126
126
  }
127
127
  return Object.freeze(output);
@@ -129,11 +129,11 @@ function admitBody(input) {
129
129
  function headerRecord(input) {
130
130
  if (input === undefined)
131
131
  return undefined;
132
- const value = record(input, 'PUBLICATION_ROUTE_INVALID', 'Route header mapping is invalid.');
132
+ const value = record(input, 'RELEASE_ROUTE_INVALID', 'Route header mapping is invalid.');
133
133
  const output = {};
134
134
  for (const [field, pick] of Object.entries(value)) {
135
135
  if (typeof pick !== 'string' || pick.length === 0) {
136
- fail('PUBLICATION_ROUTE_INVALID', 'Route header pick is invalid.');
136
+ fail('RELEASE_ROUTE_INVALID', 'Route header pick is invalid.');
137
137
  }
138
138
  output[field] = pick;
139
139
  }
@@ -142,11 +142,11 @@ function headerRecord(input) {
142
142
  function stringRecord(input) {
143
143
  if (input === undefined)
144
144
  return undefined;
145
- const value = record(input, 'PUBLICATION_ROUTE_INVALID', 'Route mapping is invalid.');
145
+ const value = record(input, 'RELEASE_ROUTE_INVALID', 'Route mapping is invalid.');
146
146
  const output = {};
147
147
  for (const [field, wire] of Object.entries(value)) {
148
148
  if (typeof wire !== 'string' || wire.length === 0) {
149
- fail('PUBLICATION_ROUTE_INVALID', 'Route wire name is invalid.');
149
+ fail('RELEASE_ROUTE_INVALID', 'Route wire name is invalid.');
150
150
  }
151
151
  output[field] = wire;
152
152
  }
@@ -1,12 +1,12 @@
1
1
  import { patterns } from '@astrale-os/kernel-dsl/v1/addressing';
2
2
  import { admitBundleDescriptor } from './bundle-descriptor.js';
3
- import { exact, record } from './content.js';
4
3
  import { fail } from './error.js';
4
+ import { exact, record } from './shape.js';
5
5
  export function admitSchema(input) {
6
- const value = record(input, 'PUBLICATION_INVALID', 'Published schema is invalid.');
7
- exact(value, ['revision', 'bundle'], 'PUBLICATION_INVALID');
6
+ const value = record(input, 'RELEASE_INVALID', 'Release schema is invalid.');
7
+ exact(value, ['revision', 'bundle'], 'RELEASE_INVALID');
8
8
  if (typeof value.revision !== 'string' || !patterns.revision.test(value.revision)) {
9
- fail('PUBLICATION_INVALID', 'Published schema revision is invalid.');
9
+ fail('RELEASE_INVALID', 'Release schema revision is invalid.');
10
10
  }
11
11
  return Object.freeze({
12
12
  revision: value.revision,
@@ -0,0 +1,26 @@
1
+ import type { IssuerId } from '@astrale-os/kernel-core/auth/issuer';
2
+ import type { ServedRelease } from './bundle-admission.js';
3
+ import type { Endpoint as DeliveryEndpoint } from './delivery-endpoint.js';
4
+ import type { Requirements } from './requirements.js';
5
+ /**
6
+ * Everything a served release document carries besides its format, version, digest and build:
7
+ * the content a bundle must match, the serving identity, and the optional Delivery endpoint.
8
+ */
9
+ export interface ServedContent extends ServedRelease {
10
+ readonly identity: {
11
+ readonly issuer: IssuerId;
12
+ readonly subject: string;
13
+ };
14
+ readonly delivery?: DeliveryEndpoint;
15
+ }
16
+ export interface ServedOptions {
17
+ /** `canonicalize` normalizes authored View bindings before sealing; `admit` requires canonical ones. */
18
+ readonly views: 'canonicalize' | 'admit';
19
+ /** Reads the requirements coordinate of the document version; canonical `core` by default. */
20
+ readonly requirements?: (input: unknown) => Requirements;
21
+ }
22
+ /**
23
+ * Admit the version-neutral content of one served release document. Every document version runs
24
+ * these checks in this order, so a document of any version admits exactly the same served content.
25
+ */
26
+ export declare function admitServedContent(value: Readonly<Record<string, unknown>>, options: ServedOptions): ServedContent;