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

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 (221) 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/index.d.ts +1 -0
  37. package/dist/issuer/index.js +1 -0
  38. package/dist/issuer/paths.d.ts +2 -0
  39. package/dist/issuer/paths.js +1 -0
  40. package/dist/issuer/workflow-engine.d.ts +18 -0
  41. package/dist/issuer/workflow-engine.js +59 -0
  42. package/dist/{publication → release}/bindings/callable.js +6 -6
  43. package/dist/release/bindings/invocation.js +13 -0
  44. package/dist/{publication → release}/bindings/view.d.ts +1 -1
  45. package/dist/{publication → release}/bindings/view.js +10 -10
  46. package/dist/release/build.d.ts +6 -0
  47. package/dist/release/build.js +7 -0
  48. package/dist/release/bundle-admission.d.ts +21 -0
  49. package/dist/{publication → release}/bundle-admission.js +18 -18
  50. package/dist/{publication → release}/bundle-descriptor.js +6 -6
  51. package/dist/release/bundle-evidence.d.ts +3 -0
  52. package/dist/release/bundle-evidence.js +13 -0
  53. package/dist/{publication → release}/bundle.d.ts +2 -2
  54. package/dist/{publication → release}/bundle.js +9 -9
  55. package/dist/{publication → release}/delivery/attestation.d.ts +1 -0
  56. package/dist/release/delivery/claims.d.ts +21 -0
  57. package/dist/release/delivery/claims.js +68 -0
  58. package/dist/release/delivery/delivery.d.ts +11 -0
  59. package/dist/release/delivery/delivery.js +24 -0
  60. package/dist/{publication → release}/delivery/error.d.ts +2 -0
  61. package/dist/{publication → release}/delivery/error.js +1 -0
  62. package/dist/{publication → release}/delivery/index.d.ts +6 -1
  63. package/dist/{publication → release}/delivery/index.js +5 -0
  64. package/dist/{publication → release}/delivery/limits.d.ts +2 -1
  65. package/dist/release/delivery/limits.js +5 -0
  66. package/dist/release/delivery/request.d.ts +21 -0
  67. package/dist/release/delivery/request.js +60 -0
  68. package/dist/{publication → release}/delivery-declaration.js +4 -4
  69. package/dist/release/delivery-endpoint.js +22 -0
  70. package/dist/release/digest.d.ts +13 -0
  71. package/dist/{publication/etag.js → release/digest.js} +1 -10
  72. package/dist/release/error.d.ts +7 -0
  73. package/dist/release/error.js +11 -0
  74. package/dist/release/index.d.ts +21 -0
  75. package/dist/release/index.js +14 -0
  76. package/dist/release/issuer.d.ts +5 -0
  77. package/dist/release/issuer.js +34 -0
  78. package/dist/release/legacy/bindings.d.ts +14 -0
  79. package/dist/release/legacy/bindings.js +11 -0
  80. package/dist/release/legacy/bundle-admission.d.ts +6 -0
  81. package/dist/release/legacy/bundle-admission.js +10 -0
  82. package/dist/release/legacy/bundle-evidence.d.ts +8 -0
  83. package/dist/release/legacy/bundle-evidence.js +11 -0
  84. package/dist/release/legacy/bundle.d.ts +24 -0
  85. package/dist/release/legacy/bundle.js +31 -0
  86. package/dist/release/legacy/delivery/attestation.d.ts +6 -0
  87. package/dist/release/legacy/delivery/attestation.js +1 -0
  88. package/dist/{publication → release/legacy}/delivery/claims.d.ts +9 -0
  89. package/dist/{publication → release/legacy}/delivery/claims.js +7 -2
  90. package/dist/release/legacy/delivery/delivery.d.ts +18 -0
  91. package/dist/{publication → release/legacy}/delivery/delivery.js +6 -2
  92. package/dist/release/legacy/delivery/error.d.ts +16 -0
  93. package/dist/release/legacy/delivery/error.js +6 -0
  94. package/dist/release/legacy/delivery/index.d.ts +13 -0
  95. package/dist/release/legacy/delivery/index.js +13 -0
  96. package/dist/release/legacy/delivery/limits.d.ts +21 -0
  97. package/dist/release/legacy/delivery/limits.js +22 -0
  98. package/dist/release/legacy/delivery/request.d.ts +26 -0
  99. package/dist/{publication → release/legacy}/delivery/request.js +8 -3
  100. package/dist/release/legacy/delivery-declaration.d.ts +9 -0
  101. package/dist/release/legacy/delivery-declaration.js +11 -0
  102. package/dist/release/legacy/error.d.ts +26 -0
  103. package/dist/release/legacy/error.js +114 -0
  104. package/dist/release/legacy/etag.d.ts +12 -0
  105. package/dist/release/legacy/etag.js +14 -0
  106. package/dist/release/legacy/index.d.ts +72 -0
  107. package/dist/{publication → release/legacy}/index.js +7 -2
  108. package/dist/release/legacy/profiles.d.ts +23 -0
  109. package/dist/release/legacy/profiles.js +36 -0
  110. package/dist/release/legacy/projection.d.ts +13 -0
  111. package/dist/release/legacy/projection.js +25 -0
  112. package/dist/release/legacy/publication.d.ts +90 -0
  113. package/dist/release/legacy/publication.js +124 -0
  114. package/dist/release/legacy/reference.d.ts +47 -0
  115. package/dist/{publication → release/legacy}/reference.js +27 -3
  116. package/dist/release/legacy/requirements.d.ts +30 -0
  117. package/dist/release/legacy/requirements.js +20 -0
  118. package/dist/{publication → release}/manifest.js +6 -6
  119. package/dist/release/reference.d.ts +26 -0
  120. package/dist/release/reference.js +70 -0
  121. package/dist/release/release.d.ts +60 -0
  122. package/dist/release/release.js +123 -0
  123. package/dist/{publication → release}/requirements.d.ts +11 -4
  124. package/dist/{publication → release}/requirements.js +58 -39
  125. package/dist/{publication → release}/route.js +24 -24
  126. package/dist/{publication → release}/schema.js +4 -4
  127. package/dist/release/served.d.ts +26 -0
  128. package/dist/release/served.js +65 -0
  129. package/dist/release/shape.d.ts +8 -0
  130. package/dist/routes/admission.d.ts +1 -1
  131. package/dist/routes/admission.js +7 -85
  132. package/dist/routes/binding.d.ts +3 -3
  133. package/dist/routes/binding.js +10 -19
  134. package/dist/routes/compiler.d.ts +2 -2
  135. package/dist/routes/compiler.js +3 -2
  136. package/dist/routes/credential.d.ts +1 -1
  137. package/dist/routes/decode.d.ts +5 -2
  138. package/dist/routes/decode.js +15 -4
  139. package/dist/routes/deployment-retired.d.ts +30 -0
  140. package/dist/routes/deployment-retired.js +96 -0
  141. package/dist/routes/encode.js +7 -6
  142. package/dist/routes/entries.d.ts +23 -0
  143. package/dist/routes/entries.js +150 -0
  144. package/dist/routes/evidence.d.ts +3 -3
  145. package/dist/routes/evidence.js +7 -34
  146. package/dist/routes/header.d.ts +9 -0
  147. package/dist/routes/header.js +34 -0
  148. package/dist/routes/index.d.ts +5 -2
  149. package/dist/routes/index.js +5 -2
  150. package/dist/routes/legacy/index.d.ts +29 -0
  151. package/dist/routes/legacy/index.js +29 -0
  152. package/dist/routes/legacy/routing.d.ts +24 -0
  153. package/dist/routes/legacy/routing.js +45 -0
  154. package/dist/routes/legacy/table.d.ts +34 -0
  155. package/dist/routes/legacy/table.js +50 -0
  156. package/dist/routes/mapping.d.ts +1 -1
  157. package/dist/routes/mapping.js +4 -4
  158. package/dist/routes/match.d.ts +1 -1
  159. package/dist/routes/placement.d.ts +2 -2
  160. package/dist/routes/plan.d.ts +1 -1
  161. package/dist/routes/request.d.ts +3 -3
  162. package/dist/routes/reservation.d.ts +4 -2
  163. package/dist/routes/reservation.js +4 -3
  164. package/dist/routes/routing.d.ts +30 -6
  165. package/dist/routes/routing.js +34 -49
  166. package/dist/routes/select.d.ts +1 -1
  167. package/dist/routes/table.d.ts +3 -2
  168. package/dist/routes/target.d.ts +5 -0
  169. package/dist/routes/target.js +16 -0
  170. package/dist/runtime/artifact.d.ts +2 -2
  171. package/dist/runtime/artifact.js +2 -2
  172. package/dist/schema/installation/codec.d.ts +6 -1
  173. package/dist/schema/installation/codec.js +10 -7
  174. package/dist/schema/installation/introspection/codec.d.ts +11 -2
  175. package/dist/schema/installation/introspection/codec.js +59 -41
  176. package/dist/schema/installation/introspection/result.d.ts +8 -1
  177. package/dist/schema/installation/legacy/index.d.ts +39 -0
  178. package/dist/schema/installation/legacy/index.js +39 -0
  179. package/dist/schema/installation/legacy/installation.d.ts +9 -0
  180. package/dist/schema/installation/legacy/installation.js +28 -0
  181. package/dist/schema/installation/legacy/introspection.d.ts +10 -0
  182. package/dist/schema/installation/legacy/introspection.js +70 -0
  183. package/dist/schema/installation/legacy/msgpack.d.ts +1 -0
  184. package/dist/schema/installation/legacy/msgpack.js +143 -0
  185. package/package.json +59 -36
  186. package/dist/codecs/msgpack.d.ts +0 -4
  187. package/dist/codecs/msgpack.js +0 -53
  188. package/dist/publication/bindings/invocation.js +0 -13
  189. package/dist/publication/bundle-admission.d.ts +0 -4
  190. package/dist/publication/bundle-evidence.d.ts +0 -3
  191. package/dist/publication/bundle-evidence.js +0 -13
  192. package/dist/publication/content.d.ts +0 -10
  193. package/dist/publication/delivery/delivery.d.ts +0 -9
  194. package/dist/publication/delivery/limits.js +0 -4
  195. package/dist/publication/delivery/request.d.ts +0 -15
  196. package/dist/publication/delivery-endpoint.js +0 -22
  197. package/dist/publication/error.d.ts +0 -7
  198. package/dist/publication/error.js +0 -11
  199. package/dist/publication/etag.d.ts +0 -6
  200. package/dist/publication/index.d.ts +0 -17
  201. package/dist/publication/legacy-requirements.d.ts +0 -12
  202. package/dist/publication/legacy-requirements.js +0 -23
  203. package/dist/publication/publication.d.ts +0 -38
  204. package/dist/publication/publication.js +0 -146
  205. package/dist/publication/reference.d.ts +0 -17
  206. package/dist/routes/release-unavailable.d.ts +0 -8
  207. package/dist/routes/release-unavailable.js +0 -32
  208. /package/dist/{publication → release}/bindings/callable.d.ts +0 -0
  209. /package/dist/{publication → release}/bindings/index.d.ts +0 -0
  210. /package/dist/{publication → release}/bindings/index.js +0 -0
  211. /package/dist/{publication → release}/bindings/invocation.d.ts +0 -0
  212. /package/dist/{publication → release}/bundle-descriptor.d.ts +0 -0
  213. /package/dist/{publication → release}/declaration.d.ts +0 -0
  214. /package/dist/{publication → release}/declaration.js +0 -0
  215. /package/dist/{publication → release}/delivery/attestation.js +0 -0
  216. /package/dist/{publication → release}/delivery-declaration.d.ts +0 -0
  217. /package/dist/{publication → release}/delivery-endpoint.d.ts +0 -0
  218. /package/dist/{publication → release}/manifest.d.ts +0 -0
  219. /package/dist/{publication → release}/route.d.ts +0 -0
  220. /package/dist/{publication → release}/schema.d.ts +0 -0
  221. /package/dist/{publication/content.js → release/shape.js} +0 -0
@@ -0,0 +1,65 @@
1
+ import { Key, patterns } from '@astrale-os/kernel-dsl/v1/addressing';
2
+ import { admitCallableBinding, admitViewBinding } from './bindings/index.js';
3
+ import { canonicalizeViewBinding } from './bindings/view.js';
4
+ import { admitEndpoint as admitDeliveryEndpoint } from './delivery-endpoint.js';
5
+ import { fail } from './error.js';
6
+ import { admitIssuer } from './issuer.js';
7
+ import { admitManifest } from './manifest.js';
8
+ import { admitRequirements } from './requirements.js';
9
+ import { admitRoute } from './route.js';
10
+ import { admitSchema } from './schema.js';
11
+ import { array, exact, record, unique } from './shape.js';
12
+ /**
13
+ * Admit the version-neutral content of one served release document. Every document version runs
14
+ * these checks in this order, so a document of any version admits exactly the same served content.
15
+ */
16
+ export function admitServedContent(value, options) {
17
+ if (typeof value.origin !== 'string' || !patterns.origin.test(value.origin)) {
18
+ fail('RELEASE_INVALID', 'Release origin is invalid.');
19
+ }
20
+ const origin = value.origin;
21
+ const identity = record(value.identity, 'RELEASE_INVALID', 'Release identity is invalid.');
22
+ exact(identity, ['issuer', 'subject'], 'RELEASE_INVALID');
23
+ const issuer = admitIssuer(identity.issuer);
24
+ if (identity.subject !== origin) {
25
+ fail('RELEASE_INVALID', 'Release identity subject must equal its root origin.');
26
+ }
27
+ const schema = admitSchema(value.schema);
28
+ const requirements = (options.requirements ?? admitRequirements)(value.requirements);
29
+ const manifest = admitManifest(value.manifest);
30
+ const bindingSet = record(value.bindings, 'RELEASE_BINDING_INVALID', 'Release bindings are invalid.');
31
+ exact(bindingSet, ['callables', 'views'], 'RELEASE_BINDING_INVALID');
32
+ const callables = array(bindingSet.callables, 'RELEASE_BINDING_INVALID').map((entry) => admitCallableBinding(entry, origin));
33
+ unique(callables.map(({ callable }) => callable), 'RELEASE_BINDING_INVALID', 'callable binding');
34
+ const views = array(bindingSet.views, 'RELEASE_VIEW_INVALID').map((entry) => options.views === 'canonicalize'
35
+ ? canonicalizeViewBinding(entry, origin)
36
+ : admitViewBinding(entry, origin));
37
+ unique(views.map(({ view }) => view), 'RELEASE_VIEW_INVALID', 'View binding');
38
+ const routes = array(value.routes, 'RELEASE_ROUTE_INVALID').map((entry) => admitRoute(entry, origin));
39
+ unique(routes.map(({ name }) => name), 'RELEASE_ROUTE_INVALID', 'route name');
40
+ for (const callable of new Set(routes.map(({ callable }) => callable))) {
41
+ if (routes.filter((route) => route.callable === callable && route.default === true).length > 1) {
42
+ fail('RELEASE_ROUTE_INVALID', `Callable ${callable} has more than one default route.`);
43
+ }
44
+ }
45
+ if (manifest?.entrypoint !== undefined) {
46
+ if (manifest.entrypoint.origin !== origin) {
47
+ fail('RELEASE_VIEW_INVALID', 'Manifest entrypoint must belong to the root origin.');
48
+ }
49
+ const key = Key.of(manifest.entrypoint);
50
+ if (!views.some(({ view }) => view === key)) {
51
+ fail('RELEASE_VIEW_INVALID', 'Manifest entrypoint must have a published View binding.');
52
+ }
53
+ }
54
+ const delivery = admitDeliveryEndpoint(value.delivery, schema.bundle.href);
55
+ return {
56
+ origin,
57
+ identity: { issuer, subject: origin },
58
+ schema,
59
+ requirements,
60
+ ...(manifest === undefined ? {} : { manifest }),
61
+ bindings: { callables, views },
62
+ routes,
63
+ ...(delivery === undefined ? {} : { delivery }),
64
+ };
65
+ }
@@ -0,0 +1,8 @@
1
+ import type { ReleaseErrorCode } from './error.js';
2
+ export declare function record(input: unknown, code: ReleaseErrorCode, message: string): Record<string, unknown>;
3
+ export declare function exact(value: Record<string, unknown>, allowed: readonly string[], code: ReleaseErrorCode): void;
4
+ export declare function array(input: unknown, code: ReleaseErrorCode): readonly unknown[];
5
+ export declare function unique(values: readonly string[], code: ReleaseErrorCode, label: string): void;
6
+ export declare function deepFreeze<T>(value: T): T;
7
+ export declare function absoluteHttpUrl(input: unknown, allowSearch: boolean, code: ReleaseErrorCode, allowTemplate?: boolean): string;
8
+ export declare function absoluteUrl(input: unknown, protocols: readonly string[], allowSearch: boolean, code: ReleaseErrorCode, allowTemplate?: boolean): string;
@@ -1,3 +1,3 @@
1
1
  import type { Table } from './table.js';
2
- /** Admit one emitted portable Table before Server composition. */
2
+ /** Admit one emitted portable release Table before Server composition. */
3
3
  export declare function acceptTable(input: unknown): Table;
@@ -1,89 +1,11 @@
1
- import { accept as acceptPortable } from '@astrale-os/kernel-dsl/value';
2
- import { acceptReference } from '../publication/reference.js';
3
- import { admitRoute } from '../publication/route.js';
4
- import { fail } from './error.js';
5
- const AUTH = new Set(['anonymous', 'authenticated', 'authorized']);
6
- const OUTPUT = new Set(['value', 'stream', 'binary']);
7
- /** Admit one emitted portable Table before Server composition. */
1
+ import { acceptReference } from '../release/reference.js';
2
+ import { acceptEntries, tableObject } from './entries.js';
3
+ /** Admit one emitted portable release Table before Server composition. */
8
4
  export function acceptTable(input) {
9
- const value = object(acceptPortable(input), 'Route Table');
10
- exact(value, ['publication', 'entries'], 'Route Table');
11
- const reference = acceptReference(value.publication);
12
- if (!Array.isArray(value.entries))
13
- invalid('Route Table entries');
14
- const entries = value.entries.map((entry) => acceptRoute(entry, reference.origin));
15
- if (new Set(entries.map(({ name }) => name)).size !== entries.length) {
16
- fail('NATIVE_ROUTE_BINDING_INVALID', 'Route Table names must be unique.');
17
- }
18
- return Object.freeze({ publication: reference, entries: Object.freeze(entries) });
19
- }
20
- function acceptRoute(input, origin) {
21
- const value = object(input, 'Route Table entry');
22
- exact(value, ['name', 'callable', 'target', 'auth', 'output', 'href', 'method', 'default'], 'Route Table entry', ['requestMethod', 'query', 'headers', 'body', 'credential']);
23
- if (typeof value.default !== 'boolean')
24
- invalid('Route Table default');
25
- if (typeof value.auth !== 'string' || !AUTH.has(value.auth)) {
26
- invalid('Route Table auth');
27
- }
28
- if (typeof value.output !== 'string' || !OUTPUT.has(value.output))
29
- invalid('Route Table output');
30
- const wire = admitRoute({
31
- name: value.name,
32
- callable: value.callable,
33
- href: value.href,
34
- method: value.method,
35
- ...(value.default === true ? { default: true } : {}),
36
- ...(value.requestMethod === undefined ? {} : { requestMethod: value.requestMethod }),
37
- ...(value.query === undefined ? {} : { query: value.query }),
38
- ...(value.headers === undefined ? {} : { headers: value.headers }),
39
- ...(value.body === undefined ? {} : { body: value.body }),
40
- ...(value.credential === undefined ? {} : { credential: value.credential }),
41
- ...receiver(value.target),
42
- }, origin);
43
- const { default: _default, receiver: _receiver, ...common } = wire;
5
+ const value = tableObject(input, ['release', 'entries']);
6
+ const reference = acceptReference(value.release);
44
7
  return Object.freeze({
45
- ...common,
46
- target: target(value.target),
47
- auth: value.auth,
48
- output: value.output,
49
- default: value.default,
8
+ release: reference,
9
+ entries: acceptEntries(value.entries, reference.origin),
50
10
  });
51
11
  }
52
- function target(input) {
53
- const value = object(input, 'Route target');
54
- if (value.kind === 'function') {
55
- exact(value, ['kind'], 'Route Function target');
56
- return Object.freeze({ kind: 'function' });
57
- }
58
- if (value.kind !== 'method' || (value.dispatch !== 'static' && value.dispatch !== 'instance')) {
59
- invalid('Route Method target');
60
- }
61
- if (value.dispatch === 'static') {
62
- exact(value, ['kind', 'dispatch'], 'Route static Method target');
63
- return Object.freeze({ kind: 'method', dispatch: 'static' });
64
- }
65
- exact(value, ['kind', 'dispatch', 'receiver'], 'Route instance Method target');
66
- if (typeof value.receiver !== 'string')
67
- invalid('Route receiver');
68
- return Object.freeze({ kind: 'method', dispatch: 'instance', receiver: value.receiver });
69
- }
70
- function receiver(input) {
71
- const admitted = target(input);
72
- return admitted.kind === 'method' && admitted.dispatch === 'instance'
73
- ? { receiver: admitted.receiver }
74
- : {};
75
- }
76
- function object(input, label) {
77
- if (input === null || typeof input !== 'object' || Array.isArray(input))
78
- invalid(label);
79
- return input;
80
- }
81
- function exact(value, required, label, optional = []) {
82
- const allowed = new Set([...required, ...optional]);
83
- if (Object.keys(value).some((key) => !allowed.has(key)) ||
84
- required.some((key) => !Object.hasOwn(value, key)))
85
- invalid(label);
86
- }
87
- function invalid(label) {
88
- return fail('NATIVE_ROUTE_BINDING_INVALID', `${label} is invalid.`);
89
- }
@@ -1,6 +1,6 @@
1
1
  import type { Domain } from '@astrale-os/kernel-dsl/v1';
2
- import type { Publication } from '../publication/publication.js';
2
+ import type { DomainRelease } from '../release/release.js';
3
3
  import type { Plan } from './plan.js';
4
4
  import type { Table } from './table.js';
5
- /** Bind one Plan to an exact placed Publication and matching resolved Domain. */
6
- export declare function bind(plan: Plan, deployed: Publication, domain: Domain, publicUrl?: string): Table;
5
+ /** Bind one Plan to an exact placed release and matching resolved Domain. */
6
+ export declare function bind(plan: Plan, deployed: DomainRelease, domain: Domain, publicUrl?: string): Table;
@@ -1,26 +1,17 @@
1
- import { json } from '@astrale-os/kernel-dsl/value';
2
- import { reference } from '../publication/reference.js';
1
+ import { reference } from '../release/reference.js';
2
+ import { placedEntries, placementMismatch } from './entries.js';
3
3
  import { fail } from './error.js';
4
- import { place } from './placement.js';
5
4
  import { rejectReserved } from './reservation.js';
6
- /** Bind one Plan to an exact placed Publication and matching resolved Domain. */
5
+ /** Bind one Plan to an exact placed release and matching resolved Domain. */
7
6
  export function bind(plan, deployed, domain, publicUrl = deployed.identity.issuer) {
8
- if (plan.origin !== deployed.origin ||
9
- plan.origin !== domain.origin ||
10
- plan.revision !== deployed.schema.revision ||
11
- plan.revision !== domain.revision) {
12
- fail('NATIVE_ROUTE_BINDING_INVALID', 'Route Plan, Publication, and Domain identity differ.');
7
+ const mismatch = placementMismatch(plan, deployed, domain, publicUrl);
8
+ if (mismatch === 'identity') {
9
+ fail('NATIVE_ROUTE_BINDING_INVALID', 'Route Plan, Release, and Domain identity differ.');
13
10
  }
14
- if (json.serialize(place(plan, publicUrl)) !== json.serialize(deployed.routes)) {
15
- fail('NATIVE_ROUTE_BINDING_INVALID', 'Publication routes differ from the placed Route Plan.');
11
+ if (mismatch === 'routes') {
12
+ fail('NATIVE_ROUTE_BINDING_INVALID', 'Release routes differ from the placed Route Plan.');
16
13
  }
17
- const entries = plan.entries.map((entry, index) => {
18
- const { path: _path, ...portable } = entry;
19
- return Object.freeze({ ...portable, href: deployed.routes[index].href });
20
- });
14
+ const entries = placedEntries(plan, deployed);
21
15
  rejectReserved(deployed, entries);
22
- return Object.freeze({
23
- publication: reference(deployed),
24
- entries: Object.freeze(entries),
25
- });
16
+ return Object.freeze({ release: reference(deployed), entries });
26
17
  }
@@ -1,8 +1,8 @@
1
1
  import type { Domain } from '@astrale-os/kernel-dsl/v1';
2
- import type * as publication from '../publication/index.js';
2
+ import type { HttpRouteDeclaration } from '../release/route.js';
3
3
  import type { Plan } from './plan.js';
4
4
  export interface CompileInput {
5
- readonly routes: readonly publication.HttpRouteDeclaration[];
5
+ readonly routes: readonly HttpRouteDeclaration[];
6
6
  readonly domain: Domain;
7
7
  }
8
8
  export declare function compile(input: CompileInput): Plan;
@@ -1,5 +1,6 @@
1
1
  import { isBinaryInput } from '@astrale-os/kernel-dsl/v1/language';
2
- import { admitRouteDeclaration } from '../publication/route.js';
2
+ import { withPublicationError } from '../release/legacy/error.js';
3
+ import { admitRouteDeclaration } from '../release/route.js';
3
4
  import { rejectCollisions } from './collision.js';
4
5
  import { fail } from './error.js';
5
6
  import { validateCredentialMapping, validateMapping, validateWireMapping } from './mapping.js';
@@ -9,7 +10,7 @@ export function compile(input) {
9
10
  if (input === null || typeof input !== 'object' || !Array.isArray(input.routes)) {
10
11
  throw new TypeError('Route compilation input is invalid.');
11
12
  }
12
- const authored = input.routes.map((entry) => admitRouteDeclaration(entry, input.domain.origin));
13
+ const authored = input.routes.map((entry) => withPublicationError(() => admitRouteDeclaration(entry, input.domain.origin)));
13
14
  const entries = authored.map((declaration) => {
14
15
  const meaning = input.domain.callable(declaration.callable);
15
16
  if (meaning === undefined || (isMethod(meaning) && !meaning.executable)) {
@@ -1,4 +1,4 @@
1
- import type { HttpRoute, SingleCredentialSource } from '../publication/index.js';
1
+ import type { HttpRoute, SingleCredentialSource } from '../release/route.js';
2
2
  import type { Route } from './table.js';
3
3
  export declare function credentialSources(route: Pick<HttpRoute, 'credential'>): readonly SingleCredentialSource[];
4
4
  export declare function firstCredentialIsAmbient(entry: Route): boolean;
@@ -1,5 +1,8 @@
1
1
  import type { Domain } from '@astrale-os/kernel-dsl/v1';
2
- import type { PublicationRef } from '../publication/index.js';
3
2
  import type { HttpRequest, Invocation } from './request.js';
3
+ import type { ServedReferences } from './routing.js';
4
4
  import type { Route } from './table.js';
5
- export declare function decode(entry: Route, request: HttpRequest, domain: Domain, served: PublicationRef): Invocation;
5
+ /** Decode one native route request served by a release-backed destination. */
6
+ export declare function decode(entry: Route, request: HttpRequest, domain: Domain, served: ServedReferences): Invocation;
7
+ /** Decode one native route request, reading its routing pair with `routing` at its fixed step. */
8
+ export declare function decodeInvocation(entry: Route, request: HttpRequest, domain: Domain, routing: (input: HttpRequest) => Invocation['routing']): Invocation;
@@ -7,9 +7,20 @@ import { methodMatches } from './match.js';
7
7
  import { bodyText, normalizeHeaders, parseJsonObject } from './request.js';
8
8
  import { decodeRouting } from './routing.js';
9
9
  import { decodeScalar } from './scalar.js';
10
- import { requiresReceiver } from './target.js';
10
+ import { requiresReceiver, routeFingerprint } from './target.js';
11
11
  import { matchTemplate, PLACEHOLDER } from './template.js';
12
+ /** Decode one native route request served by a release-backed destination. */
12
13
  export function decode(entry, request, domain, served) {
14
+ return decodeInvocation(entry, request, domain, (input) => {
15
+ const routing = decodeRouting(input, served);
16
+ if (routing?.kind === 'stale') {
17
+ fail('NATIVE_ROUTE_ROUTING_INVALID', 'If-Match does not name the served release.');
18
+ }
19
+ return routing;
20
+ });
21
+ }
22
+ /** Decode one native route request, reading its routing pair with `routing` at its fixed step. */
23
+ export function decodeInvocation(entry, request, domain, routing) {
13
24
  if (!methodMatches(entry, request.method))
14
25
  invalidRequest('HTTP method does not match the route.');
15
26
  const matched = matchTemplate(entry.href, request.url);
@@ -34,7 +45,7 @@ export function decode(entry, request, domain, served) {
34
45
  }
35
46
  const headers = normalizeHeaders(request.headers);
36
47
  const credential = readCredential(entry, url, headers);
37
- const routing = decodeRouting(request, served);
48
+ const routed = routing(request);
38
49
  const applicationHeaders = withoutCredentialHeaders(entry, headers);
39
50
  for (const [name, wire] of Object.entries(entry.query ?? {})) {
40
51
  const found = url.searchParams.getAll(wire);
@@ -113,13 +124,13 @@ export function decode(entry, request, domain, served) {
113
124
  }
114
125
  const target = Object.freeze({
115
126
  callable: entry.callable,
116
- revision: domain.revision,
127
+ fingerprint: routeFingerprint(domain, entry.callable),
117
128
  ...(receiver === undefined ? {} : { instance: receiver }),
118
129
  });
119
130
  return Object.freeze({
120
131
  call: Object.freeze({ target, input }),
121
132
  ...(credential === undefined ? {} : { credential }),
122
- ...(routing === undefined ? {} : { routing }),
133
+ ...(routed === undefined ? {} : { routing: routed }),
123
134
  });
124
135
  }
125
136
  function remainderFields(entry, domain) {
@@ -0,0 +1,30 @@
1
+ import { type IssuerId } from '@astrale-os/kernel-core/auth/issuer';
2
+ import type { DeploymentRetiredReason, ErrorResponse } from '../invocation/index.js';
3
+ import type { ReleaseDigest } from '../release/digest.js';
4
+ import type { HttpRequest } from './request.js';
5
+ /**
6
+ * Reserved control of a deployment that no longer serves. Every emitter (the deployment
7
+ * dispatchers, a release-snapshot server) answers every path with the same 410, headers and
8
+ * envelope, and never runs other code in its place.
9
+ */
10
+ export interface DeploymentRetired {
11
+ readonly kind: 'deployment-retired';
12
+ readonly reason: DeploymentRetiredReason;
13
+ }
14
+ export declare const HTTP_DEPLOYMENT_RETIRED: 'x-astrale-deployment-retired';
15
+ export declare const HTTP_DEPLOYMENT_RETIRED_STATUS: 410;
16
+ export declare function deploymentRetiredHeaders(): Readonly<Record<string, string>>;
17
+ /**
18
+ * The envelope every deployed client already decodes from any status: a not-found Service error
19
+ * carrying `DEPLOYMENT_RETIRED`, attributed to the deployment rather than to one request. An
20
+ * emitter passing a deployment that is not a canonical issuer origin, or a release that is not a
21
+ * release digest, gets a `TypeError` naming that argument.
22
+ */
23
+ export declare function deploymentRetiredBody(deployment: IssuerId, release?: ReleaseDigest): ErrorResponse;
24
+ /**
25
+ * Recognize only the reserved status, header, `no-store` and envelope together. A 410 without the
26
+ * header is ordinary application behavior; the header with another status or value, without
27
+ * `Cache-Control: no-store`, or beside any other body (including one correlated to a request or
28
+ * whose invocation source is not the retired deployment), is an invalid response.
29
+ */
30
+ export declare function decodeDeploymentRetired(status: number, headers: HttpRequest['headers'], body: unknown): DeploymentRetired | undefined;
@@ -0,0 +1,96 @@
1
+ import { is as isIssuer } from '@astrale-os/kernel-core/auth/issuer';
2
+ import { CONTENT_TYPE_JSON } from '../codecs/media-types.js';
3
+ import { CODES, ProtocolError } from '../errors/index.js';
4
+ import { DEPLOYMENT_RETIRED, decodeResponse, serviceErrorOf } from '../invocation/index.js';
5
+ import { fail } from './error.js';
6
+ import { reservedHeader } from './header.js';
7
+ export const HTTP_DEPLOYMENT_RETIRED = 'x-astrale-deployment-retired';
8
+ export const HTTP_DEPLOYMENT_RETIRED_STATUS = 410;
9
+ const MESSAGE = 'This deployment is retired.';
10
+ const DIGEST = /^sha256:[0-9a-f]{64}$/u;
11
+ export function deploymentRetiredHeaders() {
12
+ return Object.freeze({
13
+ [HTTP_DEPLOYMENT_RETIRED]: '1',
14
+ 'cache-control': 'no-store',
15
+ 'content-type': CONTENT_TYPE_JSON,
16
+ });
17
+ }
18
+ /**
19
+ * The envelope every deployed client already decodes from any status: a not-found Service error
20
+ * carrying `DEPLOYMENT_RETIRED`, attributed to the deployment rather than to one request. An
21
+ * emitter passing a deployment that is not a canonical issuer origin, or a release that is not a
22
+ * release digest, gets a `TypeError` naming that argument.
23
+ */
24
+ export function deploymentRetiredBody(deployment, release) {
25
+ assertEmitterInput(deployment, release);
26
+ return decodeRetired({
27
+ requestId: null,
28
+ invocation: { source: deployment, id: 'unavailable' },
29
+ error: {
30
+ code: CODES.NOT_FOUND,
31
+ message: MESSAGE,
32
+ data: {
33
+ code: DEPLOYMENT_RETIRED,
34
+ details: { deployment, ...(release === undefined ? {} : { release }) },
35
+ },
36
+ },
37
+ }).envelope;
38
+ }
39
+ /**
40
+ * Recognize only the reserved status, header, `no-store` and envelope together. A 410 without the
41
+ * header is ordinary application behavior; the header with another status or value, without
42
+ * `Cache-Control: no-store`, or beside any other body (including one correlated to a request or
43
+ * whose invocation source is not the retired deployment), is an invalid response.
44
+ */
45
+ export function decodeDeploymentRetired(status, headers, body) {
46
+ const encoded = reservedHeader(headers, HTTP_DEPLOYMENT_RETIRED);
47
+ if (encoded === undefined)
48
+ return undefined;
49
+ if (status !== HTTP_DEPLOYMENT_RETIRED_STATUS || encoded !== '1') {
50
+ fail('NATIVE_ROUTE_ROUTING_INVALID', `${HTTP_DEPLOYMENT_RETIRED} is reserved for status ${HTTP_DEPLOYMENT_RETIRED_STATUS} with value 1.`);
51
+ }
52
+ if (!cacheDirectives(headers).includes('no-store')) {
53
+ fail('NATIVE_ROUTE_ROUTING_INVALID', `${HTTP_DEPLOYMENT_RETIRED} requires Cache-Control: no-store.`);
54
+ }
55
+ return Object.freeze({ kind: 'deployment-retired', reason: decodeRetired(body).reason });
56
+ }
57
+ function decodeRetired(input) {
58
+ let envelope;
59
+ try {
60
+ envelope = decodeResponse(input);
61
+ }
62
+ catch (cause) {
63
+ if (cause instanceof ProtocolError)
64
+ return invalid(cause);
65
+ throw cause;
66
+ }
67
+ const failure = 'error' in envelope ? serviceErrorOf(envelope.error) : undefined;
68
+ if (failure === undefined ||
69
+ failure.data.code !== DEPLOYMENT_RETIRED ||
70
+ envelope.requestId !== null ||
71
+ envelope.invocation.source !== failure.data.details.deployment) {
72
+ return invalid();
73
+ }
74
+ return { envelope: envelope, reason: failure.data };
75
+ }
76
+ /** Every `Cache-Control` directive, lower-cased, across repeated fields in any spelling. */
77
+ function cacheDirectives(input) {
78
+ const fields = input instanceof Headers
79
+ ? [input.get('cache-control') ?? '']
80
+ : Object.entries(input ?? {})
81
+ .filter(([name]) => name.toLowerCase() === 'cache-control')
82
+ .flatMap(([, value]) => value ?? []);
83
+ return fields.flatMap((field) => field.split(',').map((d) => d.trim().toLowerCase()));
84
+ }
85
+ /** Emitter-side programmer invariant: never reported as a received-response decode failure. */
86
+ function assertEmitterInput(deployment, release) {
87
+ if (!isIssuer(deployment)) {
88
+ throw new TypeError('deploymentRetiredBody: deployment must be a canonical issuer origin.');
89
+ }
90
+ if (release !== undefined && (typeof release !== 'string' || !DIGEST.test(release))) {
91
+ throw new TypeError('deploymentRetiredBody: release must be a sha256 release digest.');
92
+ }
93
+ }
94
+ function invalid(cause) {
95
+ return fail('NATIVE_ROUTE_ROUTING_INVALID', `${HTTP_DEPLOYMENT_RETIRED} requires the deployment-retired error envelope.`, cause);
96
+ }
@@ -2,18 +2,19 @@ import { targetPath } from '@astrale-os/kernel-core/callable';
2
2
  import { Path } from '@astrale-os/kernel-core/path';
3
3
  import { callableAddress } from '@astrale-os/kernel-dsl/v1/addressing';
4
4
  import * as portable from '@astrale-os/kernel-dsl/value';
5
+ import { routedDigest } from '../invocation/index.js';
5
6
  import { firstCredentialIsAmbient, putCredential, specificHeaderWires, withoutCredentialHeaders, } from './credential.js';
6
7
  import { fail, invalidRequest } from './error.js';
7
8
  import { isReservedHeader } from './mapping.js';
8
- import { encodeStrongEtag, HTTP_ROUTE_ISSUER, HTTP_ROUTE_PUBLICATION } from './routing.js';
9
+ import { encodeStrongEtag, HTTP_ROUTE_DIGEST, HTTP_ROUTE_ISSUER } from './routing.js';
9
10
  import { asObject, encodeScalar, scalar } from './scalar.js';
10
- import { requiresReceiver } from './target.js';
11
+ import { requiresReceiver, routeFingerprint } from './target.js';
11
12
  import { authorityEnd, encodeTemplate, PLACEHOLDER } from './template.js';
12
13
  export function encode(entry, invocation, domain) {
13
14
  if (typeof invocation.call.target !== 'string' &&
14
- invocation.call.target.revision !== undefined &&
15
- invocation.call.target.revision !== domain.revision) {
16
- invalidRequest('Invocation Target revision does not match the selected route Domain.');
15
+ invocation.call.target.fingerprint !== undefined &&
16
+ invocation.call.target.fingerprint !== routeFingerprint(domain, entry.callable)) {
17
+ invalidRequest('Invocation Target fingerprint does not match the selected route Domain.');
17
18
  }
18
19
  const input = asObject(invocation.call.input);
19
20
  const target = Path.from(targetPath(invocation.call.target));
@@ -109,7 +110,7 @@ export function encode(entry, invocation, domain) {
109
110
  }
110
111
  }
111
112
  if (invocation.routing !== undefined) {
112
- headers[HTTP_ROUTE_PUBLICATION] = encodeStrongEtag(invocation.routing.publication.etag);
113
+ headers[HTTP_ROUTE_DIGEST] = encodeStrongEtag(routedDigest(invocation.routing));
113
114
  headers[HTTP_ROUTE_ISSUER] = invocation.routing.issuer;
114
115
  }
115
116
  url = parsed.toString();
@@ -0,0 +1,23 @@
1
+ import type { Domain } from '@astrale-os/kernel-dsl/v1';
2
+ import type { Object as JsonObject, Value as JsonValue } from '@astrale-os/kernel-dsl/value';
3
+ import type { Plan } from './plan.js';
4
+ import type { Placed } from './reservation.js';
5
+ import type { Route } from './table.js';
6
+ type Meaning = Pick<Domain, 'origin' | 'revision' | 'callable'>;
7
+ /**
8
+ * The coordinate in which a served document differs from one Plan placed at `publicUrl`: its
9
+ * origin or schema revision (`identity`), or its route wire (`routes`). Each Table words its own
10
+ * refusal after the document it binds.
11
+ */
12
+ export declare function placementMismatch(plan: Plan, deployed: Placed, domain: Domain, publicUrl: string): 'identity' | 'routes' | undefined;
13
+ /** The entries of one placed Plan at the hrefs its served document publishes. */
14
+ export declare function placedEntries(plan: Plan, deployed: Pick<Placed, 'routes'>): readonly Route[];
15
+ /** Whether entries carry exactly the route wire and Domain identity of one served document. */
16
+ export declare function sameServedWire(entries: readonly Route[], deployed: Pick<Placed, 'origin' | 'schema' | 'routes'>, domain: Meaning): boolean;
17
+ /** Refuse an entry whose target, auth or output differs from the Domain's callable meaning. */
18
+ export declare function verifyMeaning(entries: readonly Route[], domain: Meaning): void;
19
+ /** Admit one portable Table object holding exactly `fields`. */
20
+ export declare function tableObject(input: unknown, fields: readonly string[]): JsonObject;
21
+ /** Admit the entries of one Table bound to `origin`; an invalid route wire is a `ReleaseError`. */
22
+ export declare function acceptEntries(input: JsonValue | undefined, origin: string): readonly Route[];
23
+ export {};