@astrale-os/kernel-server 0.5.0-beta.7 → 0.5.0-beta.70

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 (215) hide show
  1. package/dist/capture.d.ts +0 -1
  2. package/dist/capture.js +0 -1
  3. package/dist/cors/cors.d.ts +0 -1
  4. package/dist/cors/cors.js +11 -4
  5. package/dist/cors/index.d.ts +0 -1
  6. package/dist/cors/index.js +0 -1
  7. package/dist/cors/types.d.ts +0 -1
  8. package/dist/cors/types.js +0 -1
  9. package/dist/errors/admission.d.ts +7 -0
  10. package/dist/errors/admission.js +22 -0
  11. package/dist/errors/availability.d.ts +0 -1
  12. package/dist/errors/availability.js +0 -1
  13. package/dist/errors/classify.d.ts +8 -1
  14. package/dist/errors/classify.js +15 -1
  15. package/dist/errors/index.d.ts +2 -2
  16. package/dist/errors/index.js +2 -2
  17. package/dist/errors/request-body.d.ts +0 -1
  18. package/dist/errors/request-body.js +0 -1
  19. package/dist/errors/response-body.d.ts +0 -1
  20. package/dist/errors/response-body.js +0 -1
  21. package/dist/index.d.ts +8 -4
  22. package/dist/index.js +7 -3
  23. package/dist/invocation/context.d.ts +3 -3
  24. package/dist/invocation/context.js +2 -2
  25. package/dist/invocation/identity.d.ts +1 -2
  26. package/dist/invocation/identity.js +1 -2
  27. package/dist/invocation/index.d.ts +1 -2
  28. package/dist/invocation/index.js +1 -2
  29. package/dist/invocation/legacy/index.d.ts +20 -0
  30. package/dist/invocation/legacy/index.js +20 -0
  31. package/dist/invocation/{publication-route.d.ts → legacy/publication-route.d.ts} +7 -5
  32. package/dist/invocation/{publication-route.js → legacy/publication-route.js} +7 -13
  33. package/dist/invocation/release-route.d.ts +26 -0
  34. package/dist/invocation/release-route.js +55 -0
  35. package/dist/invocation/types.d.ts +20 -7
  36. package/dist/invocation/types.js +0 -1
  37. package/dist/issuer/exchange.d.ts +0 -1
  38. package/dist/issuer/exchange.js +20 -10
  39. package/dist/issuer/index.d.ts +0 -1
  40. package/dist/issuer/index.js +0 -1
  41. package/dist/issuer/routes.d.ts +0 -1
  42. package/dist/issuer/routes.js +33 -5
  43. package/dist/issuer/types.d.ts +5 -1
  44. package/dist/issuer/types.js +0 -1
  45. package/dist/lifecycle/active.d.ts +4 -1
  46. package/dist/lifecycle/active.js +48 -11
  47. package/dist/lifecycle/index.d.ts +0 -1
  48. package/dist/lifecycle/index.js +0 -1
  49. package/dist/release/delivery/handler.d.ts +13 -0
  50. package/dist/{publication → release}/delivery/handler.js +42 -15
  51. package/dist/{publication → release}/delivery/index.d.ts +0 -1
  52. package/dist/{publication → release}/delivery/index.js +0 -1
  53. package/dist/release/delivery/types.d.ts +21 -0
  54. package/dist/release/delivery/types.js +1 -0
  55. package/dist/release/index.d.ts +3 -0
  56. package/dist/release/index.js +2 -0
  57. package/dist/release/legacy/delivery.d.ts +18 -0
  58. package/dist/release/legacy/delivery.js +2 -0
  59. package/dist/release/legacy/index.d.ts +39 -0
  60. package/dist/release/legacy/index.js +38 -0
  61. package/dist/release/legacy/snapshot.d.ts +26 -0
  62. package/dist/release/legacy/snapshot.js +55 -0
  63. package/dist/release/legacy/table.d.ts +5 -0
  64. package/dist/{publication → release/legacy}/table.js +5 -6
  65. package/dist/release/legacy/types.d.ts +31 -0
  66. package/dist/release/legacy/types.js +1 -0
  67. package/dist/release/served.d.ts +30 -0
  68. package/dist/release/served.js +14 -0
  69. package/dist/release/snapshot.d.ts +26 -0
  70. package/dist/release/snapshot.js +54 -0
  71. package/dist/release/table.d.ts +4 -0
  72. package/dist/release/table.js +11 -0
  73. package/dist/release/types.d.ts +33 -0
  74. package/dist/release/types.js +1 -0
  75. package/dist/routes/index.d.ts +1 -2
  76. package/dist/routes/index.js +1 -2
  77. package/dist/routes/routes.d.ts +9 -7
  78. package/dist/routes/routes.js +256 -123
  79. package/dist/server.d.ts +0 -1
  80. package/dist/server.js +110 -48
  81. package/dist/transport/body.d.ts +0 -1
  82. package/dist/transport/body.js +0 -1
  83. package/dist/transport/codecs.d.ts +2 -3
  84. package/dist/transport/codecs.js +4 -4
  85. package/dist/transport/credential.d.ts +0 -1
  86. package/dist/transport/credential.js +0 -1
  87. package/dist/transport/http/binary-request.d.ts +11 -0
  88. package/dist/transport/http/binary-request.js +102 -0
  89. package/dist/transport/http/handler.d.ts +8 -3
  90. package/dist/transport/http/handler.js +95 -45
  91. package/dist/transport/http/index.d.ts +0 -1
  92. package/dist/transport/http/index.js +0 -1
  93. package/dist/transport/http/render.d.ts +9 -4
  94. package/dist/transport/http/render.js +150 -5
  95. package/dist/transport/index.d.ts +0 -1
  96. package/dist/transport/index.js +0 -1
  97. package/dist/transport/router.d.ts +0 -1
  98. package/dist/transport/router.js +0 -1
  99. package/dist/transport/stream-error.d.ts +0 -1
  100. package/dist/transport/stream-error.js +0 -1
  101. package/dist/transport/types.d.ts +1 -2
  102. package/dist/transport/types.js +0 -1
  103. package/dist/transport/websocket/index.d.ts +0 -1
  104. package/dist/transport/websocket/index.js +0 -1
  105. package/dist/transport/websocket/session.d.ts +3 -3
  106. package/dist/transport/websocket/session.js +30 -30
  107. package/dist/transport/websocket/upgrade.d.ts +2 -3
  108. package/dist/transport/websocket/upgrade.js +7 -7
  109. package/dist/types.d.ts +32 -6
  110. package/dist/types.js +0 -1
  111. package/package.json +12 -15
  112. package/dist/capture.d.ts.map +0 -1
  113. package/dist/capture.js.map +0 -1
  114. package/dist/cors/cors.d.ts.map +0 -1
  115. package/dist/cors/cors.js.map +0 -1
  116. package/dist/cors/index.d.ts.map +0 -1
  117. package/dist/cors/index.js.map +0 -1
  118. package/dist/cors/types.d.ts.map +0 -1
  119. package/dist/cors/types.js.map +0 -1
  120. package/dist/errors/availability.d.ts.map +0 -1
  121. package/dist/errors/availability.js.map +0 -1
  122. package/dist/errors/classify.d.ts.map +0 -1
  123. package/dist/errors/classify.js.map +0 -1
  124. package/dist/errors/index.d.ts.map +0 -1
  125. package/dist/errors/index.js.map +0 -1
  126. package/dist/errors/request-body.d.ts.map +0 -1
  127. package/dist/errors/request-body.js.map +0 -1
  128. package/dist/errors/response-body.d.ts.map +0 -1
  129. package/dist/errors/response-body.js.map +0 -1
  130. package/dist/index.d.ts.map +0 -1
  131. package/dist/index.js.map +0 -1
  132. package/dist/invocation/context.d.ts.map +0 -1
  133. package/dist/invocation/context.js.map +0 -1
  134. package/dist/invocation/identity.d.ts.map +0 -1
  135. package/dist/invocation/identity.js.map +0 -1
  136. package/dist/invocation/index.d.ts.map +0 -1
  137. package/dist/invocation/index.js.map +0 -1
  138. package/dist/invocation/publication-route.d.ts.map +0 -1
  139. package/dist/invocation/publication-route.js.map +0 -1
  140. package/dist/invocation/types.d.ts.map +0 -1
  141. package/dist/invocation/types.js.map +0 -1
  142. package/dist/issuer/exchange.d.ts.map +0 -1
  143. package/dist/issuer/exchange.js.map +0 -1
  144. package/dist/issuer/index.d.ts.map +0 -1
  145. package/dist/issuer/index.js.map +0 -1
  146. package/dist/issuer/routes.d.ts.map +0 -1
  147. package/dist/issuer/routes.js.map +0 -1
  148. package/dist/issuer/types.d.ts.map +0 -1
  149. package/dist/issuer/types.js.map +0 -1
  150. package/dist/lifecycle/active.d.ts.map +0 -1
  151. package/dist/lifecycle/active.js.map +0 -1
  152. package/dist/lifecycle/index.d.ts.map +0 -1
  153. package/dist/lifecycle/index.js.map +0 -1
  154. package/dist/publication/delivery/handler.d.ts +0 -10
  155. package/dist/publication/delivery/handler.d.ts.map +0 -1
  156. package/dist/publication/delivery/handler.js.map +0 -1
  157. package/dist/publication/delivery/index.d.ts.map +0 -1
  158. package/dist/publication/delivery/index.js.map +0 -1
  159. package/dist/publication/delivery/types.d.ts +0 -12
  160. package/dist/publication/delivery/types.d.ts.map +0 -1
  161. package/dist/publication/delivery/types.js +0 -2
  162. package/dist/publication/delivery/types.js.map +0 -1
  163. package/dist/publication/index.d.ts +0 -4
  164. package/dist/publication/index.d.ts.map +0 -1
  165. package/dist/publication/index.js +0 -3
  166. package/dist/publication/index.js.map +0 -1
  167. package/dist/publication/snapshot.d.ts +0 -16
  168. package/dist/publication/snapshot.d.ts.map +0 -1
  169. package/dist/publication/snapshot.js +0 -39
  170. package/dist/publication/snapshot.js.map +0 -1
  171. package/dist/publication/table.d.ts +0 -4
  172. package/dist/publication/table.d.ts.map +0 -1
  173. package/dist/publication/table.js.map +0 -1
  174. package/dist/publication/types.d.ts +0 -23
  175. package/dist/publication/types.d.ts.map +0 -1
  176. package/dist/publication/types.js +0 -2
  177. package/dist/publication/types.js.map +0 -1
  178. package/dist/publication/validator.d.ts +0 -4
  179. package/dist/publication/validator.d.ts.map +0 -1
  180. package/dist/publication/validator.js +0 -17
  181. package/dist/publication/validator.js.map +0 -1
  182. package/dist/routes/index.d.ts.map +0 -1
  183. package/dist/routes/index.js.map +0 -1
  184. package/dist/routes/routes.d.ts.map +0 -1
  185. package/dist/routes/routes.js.map +0 -1
  186. package/dist/server.d.ts.map +0 -1
  187. package/dist/server.js.map +0 -1
  188. package/dist/transport/body.d.ts.map +0 -1
  189. package/dist/transport/body.js.map +0 -1
  190. package/dist/transport/codecs.d.ts.map +0 -1
  191. package/dist/transport/codecs.js.map +0 -1
  192. package/dist/transport/credential.d.ts.map +0 -1
  193. package/dist/transport/credential.js.map +0 -1
  194. package/dist/transport/http/handler.d.ts.map +0 -1
  195. package/dist/transport/http/handler.js.map +0 -1
  196. package/dist/transport/http/index.d.ts.map +0 -1
  197. package/dist/transport/http/index.js.map +0 -1
  198. package/dist/transport/http/render.d.ts.map +0 -1
  199. package/dist/transport/http/render.js.map +0 -1
  200. package/dist/transport/index.d.ts.map +0 -1
  201. package/dist/transport/index.js.map +0 -1
  202. package/dist/transport/router.d.ts.map +0 -1
  203. package/dist/transport/router.js.map +0 -1
  204. package/dist/transport/stream-error.d.ts.map +0 -1
  205. package/dist/transport/stream-error.js.map +0 -1
  206. package/dist/transport/types.d.ts.map +0 -1
  207. package/dist/transport/types.js.map +0 -1
  208. package/dist/transport/websocket/index.d.ts.map +0 -1
  209. package/dist/transport/websocket/index.js.map +0 -1
  210. package/dist/transport/websocket/session.d.ts.map +0 -1
  211. package/dist/transport/websocket/session.js.map +0 -1
  212. package/dist/transport/websocket/upgrade.d.ts.map +0 -1
  213. package/dist/transport/websocket/upgrade.js.map +0 -1
  214. package/dist/types.d.ts.map +0 -1
  215. package/dist/types.js.map +0 -1
@@ -2,6 +2,7 @@ import { inspect as inspectCredential } from '@astrale-os/kernel-core/auth/crede
2
2
  import * as errors from '@astrale-os/kernel-protocol/errors';
3
3
  import * as exchangeProtocol from '@astrale-os/kernel-protocol/issuer/exchange';
4
4
  import { paths as issuerPaths } from '@astrale-os/kernel-protocol/issuer/paths';
5
+ import { admissionRefusal, retryAfterHeaders } from '../errors/index.js';
5
6
  const PROVIDER_CODES = new Set([
6
7
  errors.CODES.AUTH_INVALID,
7
8
  errors.CODES.AUTH_EXPIRED,
@@ -24,7 +25,10 @@ export function mount(app, issuer, provider, cors, active, timeoutMs) {
24
25
  }
25
26
  app.all(path, async (context) => {
26
27
  const request = context.req.raw;
27
- const respond = (payload, extra) => json(payload, errors.httpStatusOf(payload.error), request, cors, extra);
28
+ const respond = (payload, extra) => json(payload, errors.httpStatusOf(payload.error), request, cors, {
29
+ ...retryAfterHeaders(payload.error),
30
+ ...extra,
31
+ });
28
32
  let admitted;
29
33
  try {
30
34
  admitted = exchangeProtocol.acceptHttpRequest({
@@ -40,12 +44,9 @@ export function mount(app, issuer, provider, cors, active, timeoutMs) {
40
44
  }
41
45
  throw cause;
42
46
  }
43
- if (!active.isAccepting) {
44
- return respond(error('BACKEND_UNAVAILABLE', 'Token exchange is unavailable.'));
45
- }
46
47
  const operation = active.begin(timeoutMs, request.signal);
47
48
  if (operation === undefined) {
48
- return respond(error('LIMIT_EXCEEDED', 'Token-exchange limit exceeded.'));
49
+ return respond(exchangeProtocol.acceptErrorResponse({ error: admissionRefusal(active.isAccepting) }));
49
50
  }
50
51
  try {
51
52
  try {
@@ -60,6 +61,11 @@ export function mount(app, issuer, provider, cors, active, timeoutMs) {
60
61
  }
61
62
  throw cause;
62
63
  }
64
+ if (operation.timedOut) {
65
+ return respond(error('DEADLINE_EXCEEDED', 'Token-exchange deadline elapsed.'));
66
+ }
67
+ if (operation.signal.aborted)
68
+ throw operation.signal.reason;
63
69
  let output;
64
70
  try {
65
71
  output = await provider.exchange(admitted, { signal: operation.signal });
@@ -68,6 +74,8 @@ export function mount(app, issuer, provider, cors, active, timeoutMs) {
68
74
  if (operation.timedOut) {
69
75
  return respond(error('DEADLINE_EXCEEDED', 'Token-exchange deadline elapsed.'));
70
76
  }
77
+ if (operation.signal.aborted)
78
+ throw operation.signal.reason;
71
79
  if (cause instanceof errors.ProtocolError && PROVIDER_CODES.has(cause.payload.code)) {
72
80
  return respond(exchangeProtocol.acceptErrorResponse({ error: cause.payload }));
73
81
  }
@@ -76,6 +84,8 @@ export function mount(app, issuer, provider, cors, active, timeoutMs) {
76
84
  if (operation.timedOut) {
77
85
  return respond(error('DEADLINE_EXCEEDED', 'Token-exchange deadline elapsed.'));
78
86
  }
87
+ if (operation.signal.aborted)
88
+ throw operation.signal.reason;
79
89
  const response = exchangeProtocol.acceptResponse(output);
80
90
  if (inspectCredential(response.token).claims.exp !== response.expiresAt) {
81
91
  throw new TypeError('Token-exchange provider returned inconsistent expiry evidence.');
@@ -125,13 +135,13 @@ function error(code, message) {
125
135
  });
126
136
  }
127
137
  function json(value, status, request, cors, extra) {
138
+ const additional = new Headers(extra);
128
139
  const headers = new Headers(exchangeProtocol.responseHeaders());
129
- for (const [name, value] of Object.entries(cors.response(request)))
140
+ const exposed = additional.has('retry-after') ? ['retry-after'] : [];
141
+ for (const [name, value] of Object.entries(cors.response(request, exposed))) {
130
142
  headers.set(name, value);
131
- if (extra !== undefined) {
132
- for (const [name, value] of new Headers(extra))
133
- headers.set(name, value);
134
143
  }
144
+ for (const [name, value] of additional)
145
+ headers.set(name, value);
135
146
  return new Response(JSON.stringify(value), { status, headers });
136
147
  }
137
- //# sourceMappingURL=exchange.js.map
@@ -1,4 +1,3 @@
1
1
  export { admitIssuer, issuerRouteReservations, mountIssuer, type IssuerRouteReservation, } from './routes.js';
2
2
  export type * as exchange from './exchange.js';
3
3
  export type { Issuer } from './types.js';
4
- //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1 @@
1
1
  export { admitIssuer, issuerRouteReservations, mountIssuer, } from './routes.js';
2
- //# sourceMappingURL=index.js.map
@@ -11,4 +11,3 @@ export declare function issuerRouteReservations(input: Issuer, tokenExchange?: b
11
11
  export declare function mountIssuer(app: ServerRouter, input: Issuer, cors: CorsPolicy, active: ActiveOperations, timeoutMs: number, tokenExchange?: Provider): void;
12
12
  /** Admit static discovery configuration before Runtime Boot performs any durable work. */
13
13
  export declare function admitIssuer(input: Issuer): Issuer;
14
- //# sourceMappingURL=routes.d.ts.map
@@ -2,7 +2,8 @@ import { configuration } from '@astrale-os/kernel-protocol/issuer/configuration'
2
2
  import { hosted } from '@astrale-os/kernel-protocol/issuer/hosted';
3
3
  import { keySet } from '@astrale-os/kernel-protocol/issuer/key-set';
4
4
  import { paths as issuerPaths } from '@astrale-os/kernel-protocol/issuer/paths';
5
- import { IssuerKeysUnavailableError } from '../errors/index.js';
5
+ import { workflowEngine } from '@astrale-os/kernel-protocol/issuer/workflow-engine';
6
+ import { admissionRefusal, IssuerKeysUnavailableError, retryAfterHeaders } from '../errors/index.js';
6
7
  import { mount } from './exchange.js';
7
8
  export function issuerRouteReservations(input, tokenExchange = false) {
8
9
  const paths = issuerPaths(input.issuer);
@@ -13,6 +14,9 @@ export function issuerRouteReservations(input, tokenExchange = false) {
13
14
  if (tokenExchange) {
14
15
  reservations.push(Object.freeze({ path: paths.tokenExchange, methods: Object.freeze(['POST']) }));
15
16
  }
17
+ if (input.workflowEngineKeys !== undefined) {
18
+ reservations.push(Object.freeze({ path: paths.workflowEngine, methods: Object.freeze(['GET']) }));
19
+ }
16
20
  if (input.loadIssuerPublicKeys !== undefined) {
17
21
  const base = new URL(input.issuer).pathname.replace(/\/+$/u, '');
18
22
  reservations.push(Object.freeze({
@@ -32,11 +36,28 @@ export function mountIssuer(app, input, cors, active, timeoutMs, tokenExchange)
32
36
  ...cors.response(request),
33
37
  'cache-control': 'no-cache',
34
38
  });
39
+ // A lifecycle refusal names saturation or drain and presents its retry delay.
40
+ const refusal = (request) => {
41
+ const refused = admissionRefusal(active.isAccepting);
42
+ const retry = retryAfterHeaders(refused);
43
+ return {
44
+ body: { error: refused.message },
45
+ headers: {
46
+ ...cors.response(request, Object.keys(retry)),
47
+ 'cache-control': 'no-cache',
48
+ ...retry,
49
+ },
50
+ };
51
+ };
35
52
  const algorithm = input.signingAlgorithm ?? 'ES256';
36
53
  const discovery = configuration(issuer, [algorithm], {
37
54
  tokenExchange: tokenExchange !== undefined,
38
55
  });
39
56
  app.get(paths.configuration, (context) => context.json(discovery, 200, headers(context.req.raw)));
57
+ if (input.workflowEngineKeys !== undefined) {
58
+ const engine = workflowEngine(issuer, input.workflowEngineKeys);
59
+ app.get(paths.workflowEngine, (context) => context.json(engine, 200, headers(context.req.raw)));
60
+ }
40
61
  if (cors.enabled) {
41
62
  app.options(paths.configuration, (context) => cors.preflight(context.req.raw, { methods: ['GET'] }));
42
63
  app.options(paths.keys, (context) => cors.preflight(context.req.raw, { methods: ['GET'] }));
@@ -44,7 +65,8 @@ export function mountIssuer(app, input, cors, active, timeoutMs, tokenExchange)
44
65
  app.get(paths.keys, async (context) => {
45
66
  const operation = active.begin(timeoutMs, context.req.raw.signal);
46
67
  if (operation === undefined) {
47
- return context.json({ error: 'Issuer is unavailable while draining.' }, 503, headers(context.req.raw));
68
+ const refused = refusal(context.req.raw);
69
+ return context.json(refused.body, 503, refused.headers);
48
70
  }
49
71
  try {
50
72
  const keys = await input.loadPublicKeys(operation.signal);
@@ -76,7 +98,8 @@ export function mountIssuer(app, input, cors, active, timeoutMs, tokenExchange)
76
98
  return context.json({ error: 'Issuer not found.' }, 404, headers(context.req.raw));
77
99
  const operation = active.begin(timeoutMs, context.req.raw.signal);
78
100
  if (operation === undefined) {
79
- return context.json({ error: 'Issuer is unavailable while draining.' }, 503, headers(context.req.raw));
101
+ const refused = refusal(context.req.raw);
102
+ return context.json(refused.body, 503, refused.headers);
80
103
  }
81
104
  try {
82
105
  const keys = await hostedKeys(loadIssuerPublicKeys, hostedIssuer, operation.signal, algorithm);
@@ -100,7 +123,8 @@ export function mountIssuer(app, input, cors, active, timeoutMs, tokenExchange)
100
123
  return context.json({ error: 'Issuer not found.' }, 404, headers(context.req.raw));
101
124
  const operation = active.begin(timeoutMs, context.req.raw.signal);
102
125
  if (operation === undefined) {
103
- return context.json({ error: 'Issuer is unavailable while draining.' }, 503, headers(context.req.raw));
126
+ const refused = refusal(context.req.raw);
127
+ return context.json(refused.body, 503, refused.headers);
104
128
  }
105
129
  try {
106
130
  const keys = await hostedKeys(loadIssuerPublicKeys, hostedIssuer, operation.signal, algorithm);
@@ -136,9 +160,14 @@ export function admitIssuer(input) {
136
160
  }
137
161
  const loadPublicKeys = input.loadPublicKeys;
138
162
  const loadIssuerPublicKeys = input.loadIssuerPublicKeys;
163
+ // A Kernel that drives an engine publishes it; invalid keys fail closed at boot.
164
+ const workflowEngineKeys = input.workflowEngineKeys === undefined
165
+ ? undefined
166
+ : workflowEngine(issuer, input.workflowEngineKeys).identity_keys;
139
167
  return Object.freeze({
140
168
  issuer,
141
169
  signingAlgorithm,
170
+ ...(workflowEngineKeys === undefined ? {} : { workflowEngineKeys }),
142
171
  loadPublicKeys: (signal) => loadPublicKeys.call(input, signal),
143
172
  ...(loadIssuerPublicKeys === undefined
144
173
  ? {}
@@ -147,4 +176,3 @@ export function admitIssuer(input) {
147
176
  }),
148
177
  });
149
178
  }
150
- //# sourceMappingURL=routes.js.map
@@ -8,5 +8,9 @@ export interface Issuer {
8
8
  loadPublicKeys(signal: AbortSignal): Promise<readonly JsonWebKey[]>;
9
9
  /** Optional hosted-key discovery for issuers below `<issuer>/iss/:thumbprint`. */
10
10
  readonly loadIssuerPublicKeys?: (issuer: IssuerId, signal: AbortSignal) => Promise<readonly JsonWebKey[]>;
11
+ /**
12
+ * Request identity keys of the workflow engine this Kernel drives, published at
13
+ * `<issuer>/.well-known/astrale/workflow-engine.json` so Domains verify the engine's requests.
14
+ */
15
+ readonly workflowEngineKeys?: readonly string[];
11
16
  }
12
- //# sourceMappingURL=types.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=types.js.map
@@ -12,6 +12,8 @@ export interface ActiveOperation {
12
12
  /** Register cooperative cleanup and return an idempotent unregistration function. */
13
13
  onCancel(cancel: CancelWork): () => void;
14
14
  cancel(reason: string): void;
15
+ /** Replace the remaining budget after a trusted transport binding is admitted. */
16
+ extend(timeoutMs: number): void;
15
17
  settle(): void;
16
18
  readonly finished: Promise<void>;
17
19
  }
@@ -26,6 +28,8 @@ export declare class ActiveOperations {
26
28
  private changed;
27
29
  constructor(maximumConcurrentOperations: number);
28
30
  get isAccepting(): boolean;
31
+ /** Whether a newly arriving operation can be admitted without semantic dispatch. */
32
+ get isAvailable(): boolean;
29
33
  begin(timeoutMs: number, externalSignal?: AbortSignal): ActiveOperation | undefined;
30
34
  attachSocket(socket: ActiveSocket): boolean;
31
35
  detachSocket(socket: ActiveSocket): void;
@@ -37,4 +41,3 @@ export declare class ActiveOperations {
37
41
  private closeSockets;
38
42
  }
39
43
  export {};
40
- //# sourceMappingURL=active.d.ts.map
@@ -1,3 +1,6 @@
1
+ // Streaming iterators can need several microtasks to return after cancellation. Keep the
2
+ // cleanup join bounded even when a producer ignores its cancellation signal.
3
+ const CANCELLATION_CLEANUP_GRACE_MS = 250;
1
4
  export class ActiveOperations {
2
5
  maximumConcurrentOperations;
3
6
  accepting = true;
@@ -13,10 +16,13 @@ export class ActiveOperations {
13
16
  get isAccepting() {
14
17
  return this.accepting;
15
18
  }
19
+ /** Whether a newly arriving operation can be admitted without semantic dispatch. */
20
+ get isAvailable() {
21
+ return this.accepting && this.operations.size < this.maximumConcurrentOperations;
22
+ }
16
23
  begin(timeoutMs, externalSignal) {
17
- if (!this.accepting || this.operations.size >= this.maximumConcurrentOperations) {
24
+ if (!this.isAvailable)
18
25
  return undefined;
19
- }
20
26
  const operation = new Operation(Date.now() + timeoutMs, () => {
21
27
  this.operations.delete(operation);
22
28
  this.changed?.();
@@ -63,19 +69,32 @@ export class ActiveOperations {
63
69
  }
64
70
  close(deadline) {
65
71
  this.drain();
66
- this.closing ??= this.closeOnce(deadline);
72
+ if (this.closing === undefined) {
73
+ const flight = this.closeOnce(deadline);
74
+ this.closing = flight;
75
+ void flight.catch(() => {
76
+ // A failed close leaves admission terminal, but residual work may settle later.
77
+ // Let the same owner join that cleanup on its next close attempt.
78
+ if (this.closing === flight)
79
+ this.closing = undefined;
80
+ });
81
+ }
67
82
  return this.closing;
68
83
  }
69
84
  async closeOnce(deadline) {
70
85
  await this.waitUntilIdleOr(deadline);
86
+ const hasCancellationWork = [...this.operations].some((operation) => operation.hasCancellationWork);
71
87
  if (this.operations.size > 0) {
72
88
  for (const operation of this.operations)
73
89
  operation.cancel('server closing');
74
90
  }
75
91
  this.closeSockets();
76
- // Give cooperative cancellation and iterator returns one turn to settle. The absolute deadline
77
- // has already elapsed; an uncooperative operation must reject close rather than pin Host forever.
78
- await Promise.resolve();
92
+ if (hasCancellationWork) {
93
+ await this.waitUntilIdleOr(Date.now() + CANCELLATION_CLEANUP_GRACE_MS);
94
+ }
95
+ else {
96
+ await Promise.resolve();
97
+ }
79
98
  if (this.operations.size > 0 || this.backgroundTasks.size > 0) {
80
99
  this.closed = true;
81
100
  throw new Error(`Server close deadline elapsed with ${this.operations.size} active operation(s) and ${this.backgroundTasks.size} background task(s).`);
@@ -110,11 +129,11 @@ export class ActiveOperations {
110
129
  }
111
130
  }
112
131
  class Operation {
113
- deadline;
114
132
  onSettled;
115
133
  controller = new AbortController();
116
134
  cancelWork = new Set();
117
135
  timer;
136
+ deadlineValue;
118
137
  external;
119
138
  settled = false;
120
139
  cancelled = false;
@@ -122,15 +141,31 @@ class Operation {
122
141
  resolveFinished;
123
142
  finished;
124
143
  constructor(deadline, onSettled) {
125
- this.deadline = deadline;
126
144
  this.onSettled = onSettled;
145
+ this.deadlineValue = deadline;
127
146
  this.finished = new Promise((resolve) => {
128
147
  this.resolveFinished = resolve;
129
148
  });
130
- this.timer = setTimeout(() => {
149
+ this.timer = this.arm();
150
+ }
151
+ get deadline() {
152
+ return this.deadlineValue;
153
+ }
154
+ extend(timeoutMs) {
155
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2_147_483_647) {
156
+ throw new TypeError('Active operation timeout is invalid.');
157
+ }
158
+ if (this.cancelled || this.settled)
159
+ return;
160
+ clearTimeout(this.timer);
161
+ this.deadlineValue = Date.now() + timeoutMs;
162
+ this.timer = this.arm();
163
+ }
164
+ arm() {
165
+ return setTimeout(() => {
131
166
  this.timeout = true;
132
167
  this.cancel('invocation deadline exceeded');
133
- }, Math.min(Math.max(0, deadline - Date.now()), 2_147_483_647));
168
+ }, Math.min(Math.max(0, this.deadlineValue - Date.now()), 2_147_483_647));
134
169
  }
135
170
  link(signal) {
136
171
  if (signal === undefined)
@@ -148,6 +183,9 @@ class Operation {
148
183
  get timedOut() {
149
184
  return this.timeout;
150
185
  }
186
+ get hasCancellationWork() {
187
+ return this.cancelWork.size > 0;
188
+ }
151
189
  onCancel(cancel) {
152
190
  if (this.cancelled) {
153
191
  void settleCancellation(cancel, abortReason(this.controller.signal));
@@ -197,4 +235,3 @@ async function settleCancellation(cancel, reason) {
197
235
  function abortReason(signal) {
198
236
  return signal.reason instanceof Error ? signal.reason.message : 'operation cancelled';
199
237
  }
200
- //# sourceMappingURL=active.js.map
@@ -1,2 +1 @@
1
1
  export { ActiveOperations, type ActiveOperation, type ActiveSocket } from './active.js';
2
- //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1 @@
1
1
  export { ActiveOperations } from './active.js';
2
- //# sourceMappingURL=index.js.map
@@ -0,0 +1,13 @@
1
+ import type { ActiveOperations } from '../../lifecycle/index.js';
2
+ import type { Served } from '../served.js';
3
+ import type { Provider } from './types.js';
4
+ export interface Limits {
5
+ readonly maximumDeliveryBytes: number;
6
+ readonly invocationTimeoutMs: number;
7
+ }
8
+ /**
9
+ * Serve one delivery request against the exact snapshot that selected its URL, in the request's
10
+ * version: Delivery v2 names the served release, Delivery v1 the served Publication (a release's
11
+ * frozen v3 projection). A v3-input destination keeps its Delivery v1 decoder.
12
+ */
13
+ export declare function handleDelivery(request: Request, snapshot: Served, provider: Provider | undefined, active: ActiveOperations, limits: Limits, corsFor: (exposedHeaders?: readonly string[]) => Readonly<Record<string, string>>): Promise<Response>;
@@ -1,23 +1,34 @@
1
- import { codecs, publication as protocolPublication } from '@astrale-os/kernel-protocol';
2
- import { DeliveryUnavailableError } from '../../errors/index.js';
1
+ import * as codecs from '@astrale-os/kernel-protocol/codecs';
2
+ import * as release from '@astrale-os/kernel-protocol/release';
3
+ import { admissionRefusal, DeliveryUnavailableError, retryAfterHeaders, } from '../../errors/index.js';
3
4
  import { RequestBodyInvalidError, RequestBodyTooLargeError } from '../../errors/index.js';
4
5
  import { readBoundedBody } from '../../transport/index.js';
5
- const delivery = protocolPublication.delivery;
6
- /** Serve one delivery request against the exact Publication snapshot that selected its URL. */
7
- export async function handleDelivery(request, snapshot, provider, active, limits, cors) {
6
+ import { servedDocument } from '../served.js';
7
+ const delivery = release.delivery;
8
+ const publicationDelivery = release.legacy.delivery;
9
+ /**
10
+ * Serve one delivery request against the exact snapshot that selected its URL, in the request's
11
+ * version: Delivery v2 names the served release, Delivery v1 the served Publication (a release's
12
+ * frozen v3 projection). A v3-input destination keeps its Delivery v1 decoder.
13
+ */
14
+ export async function handleDelivery(request, snapshot, provider, active, limits, corsFor) {
15
+ const cors = corsFor();
8
16
  if (request.method !== 'POST') {
9
17
  return response({ error: 'Delivery requires POST.' }, 405, cors);
10
18
  }
11
19
  if (provider === undefined) {
12
20
  return response({ error: 'Delivery is unavailable.' }, 503, cors);
13
21
  }
14
- const advertised = snapshot.publication.delivery;
22
+ const advertised = servedDocument(snapshot).delivery;
15
23
  if (advertised === undefined) {
16
24
  return response({ error: 'Delivery is unavailable.' }, 404, cors);
17
25
  }
18
26
  const operation = active.begin(limits.invocationTimeoutMs, request.signal);
19
- if (operation === undefined)
20
- return response({ error: 'Server is draining.' }, 503, cors);
27
+ if (operation === undefined) {
28
+ const refusal = admissionRefusal(active.isAccepting);
29
+ const retry = retryAfterHeaders(refusal);
30
+ return response({ error: refusal.message }, 503, { ...corsFor(Object.keys(retry)), ...retry });
31
+ }
21
32
  try {
22
33
  const authorization = request.headers.get('authorization');
23
34
  const token = bearer(authorization);
@@ -55,7 +66,10 @@ export async function handleDelivery(request, snapshot, provider, active, limits
55
66
  }
56
67
  let input;
57
68
  try {
58
- input = delivery.acceptRequest(decoded);
69
+ input =
70
+ snapshot.kind === 'release'
71
+ ? delivery.acceptRequest(decoded)
72
+ : publicationDelivery.acceptRequest(decoded);
59
73
  }
60
74
  catch (cause) {
61
75
  if (cause instanceof delivery.Error && cause.code === 'DELIVERY_REQUEST_INVALID') {
@@ -63,8 +77,13 @@ export async function handleDelivery(request, snapshot, provider, active, limits
63
77
  }
64
78
  throw cause;
65
79
  }
66
- if (input.etag !== snapshot.publication.etag) {
67
- return response({ error: 'Publication ETag differs from request.' }, 409, cors);
80
+ const named = input.version === 2 ? input.release : input.etag;
81
+ const served = input.version === 2 ? servedRelease(snapshot) : snapshot.publication.etag;
82
+ if (named !== served) {
83
+ const error = input.version === 2
84
+ ? 'Release digest differs from request.'
85
+ : 'Publication ETag differs from request.';
86
+ return response({ error }, 409, cors);
68
87
  }
69
88
  let candidate;
70
89
  try {
@@ -81,7 +100,8 @@ export async function handleDelivery(request, snapshot, provider, active, limits
81
100
  }
82
101
  let created;
83
102
  try {
84
- created = delivery.accept(candidate);
103
+ created =
104
+ input.version === 2 ? delivery.accept(candidate) : publicationDelivery.accept(candidate);
85
105
  }
86
106
  catch (cause) {
87
107
  if (cause instanceof delivery.Error) {
@@ -89,8 +109,12 @@ export async function handleDelivery(request, snapshot, provider, active, limits
89
109
  }
90
110
  throw cause;
91
111
  }
92
- if (created.publication.etag !== snapshot.publication.etag) {
93
- return response({ error: 'Delivery provider returned a different Publication.' }, 500, cors);
112
+ const delivered = created.version === 2 ? created.release.digest : created.publication.etag;
113
+ if (delivered !== served) {
114
+ const error = created.version === 2
115
+ ? 'Delivery provider returned a different release.'
116
+ : 'Delivery provider returned a different Publication.';
117
+ return response({ error }, 500, cors);
94
118
  }
95
119
  const encoded = codecs.json.encode(created);
96
120
  if (encodedSize(encoded) > limits.maximumDeliveryBytes) {
@@ -104,6 +128,10 @@ export async function handleDelivery(request, snapshot, provider, active, limits
104
128
  operation.settle();
105
129
  }
106
130
  }
131
+ /** Digest a Delivery v2 must name: only a release-backed destination serves one. */
132
+ function servedRelease(snapshot) {
133
+ return snapshot.kind === 'release' ? snapshot.release.digest : undefined;
134
+ }
107
135
  function bearer(input) {
108
136
  if (input === null)
109
137
  return undefined;
@@ -124,4 +152,3 @@ function encodedSize(input) {
124
152
  function toBody(input) {
125
153
  return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
126
154
  }
127
- //# sourceMappingURL=handler.js.map
@@ -1,3 +1,2 @@
1
1
  export { handleDelivery, type Limits } from './handler.js';
2
2
  export type { Context, Provider } from './types.js';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1 @@
1
1
  export { handleDelivery } from './handler.js';
2
- //# sourceMappingURL=index.js.map
@@ -0,0 +1,21 @@
1
+ import type * as release from '@astrale-os/kernel-protocol/release';
2
+ export interface Context {
3
+ /** Omitted for a public endpoint; otherwise the exact admitted retrieval credential. */
4
+ readonly token?: release.delivery.Token;
5
+ readonly signal: AbortSignal;
6
+ }
7
+ /** Provider-owned retrieval, construction, and attestation signing. */
8
+ export interface Provider {
9
+ /**
10
+ * Answer in the request's version: a Delivery v2 carrying the served release for a v2 request,
11
+ * and a Delivery v1 carrying the served Publication (at a release-backed destination, its frozen
12
+ * v3 projection) for a deprecated v1 request. A v3-input destination passes only v1 requests.
13
+ * Throw DeliveryUnavailableError only for an expected dependency outage.
14
+ *
15
+ * A release-backed destination (`ServerOptions.release`) passes v2 requests, so its provider
16
+ * must answer both versions. The deprecated v1-only `publication.delivery.Provider` still
17
+ * type-checks here, because method parameters are compared bivariantly, but every v2 request
18
+ * it receives fails with 500 `Delivery provider returned an invalid response.`
19
+ */
20
+ create(request: release.delivery.Request | release.legacy.delivery.Request, context: Context): Promise<release.delivery.Delivery | release.legacy.delivery.Delivery>;
21
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export { prepareReleaseSnapshot } from './snapshot.js';
2
+ export type { ReleaseSnapshot, ReleaseSnapshotInput, ReleaseSource } from './types.js';
3
+ export * as delivery from './delivery/index.js';
@@ -0,0 +1,2 @@
1
+ export { prepareReleaseSnapshot } from './snapshot.js';
2
+ export * as delivery from './delivery/index.js';
@@ -0,0 +1,18 @@
1
+ import type * as release from '@astrale-os/kernel-protocol/release';
2
+ import type { Context as DeliveryContext } from '../delivery/types.js';
3
+ /** @deprecated Successor: `release.delivery.Context`, the same retrieval context. */
4
+ export type Context = DeliveryContext;
5
+ /**
6
+ * The Delivery v1 provider of a v3-input destination, which only ever receives v1 requests. It
7
+ * remains assignable to `release.delivery.Provider`, so existing providers keep configuring a
8
+ * Server unchanged. Pass it only with `ServerOptions.publication`: a release-backed destination
9
+ * also passes Delivery v2 requests, which this provider cannot answer.
10
+ *
11
+ * @deprecated Successor: `release.delivery.Provider`, which answers a v2 request over the release
12
+ * and a v1 request over its frozen v3 projection.
13
+ */
14
+ export interface Provider {
15
+ create(request: release.legacy.delivery.Request, context: Context): Promise<release.legacy.delivery.Delivery>;
16
+ }
17
+ /** @deprecated Successor: `release.delivery.handleDelivery`, which serves both snapshots. */
18
+ export { handleDelivery, type Limits } from '../delivery/handler.js';
@@ -0,0 +1,2 @@
1
+ /** @deprecated Successor: `release.delivery.handleDelivery`, which serves both snapshots. */
2
+ export { handleDelivery } from '../delivery/handler.js';
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The Publication snapshot of a v3-input destination: a Server built from a v2/v3 Publication
3
+ * serves `domain.json`, its bundle, Delivery v1 and its native routes exactly as before the release
4
+ * snapshot, and keeps the #508 same-schema and schema-drift continuity
5
+ * (`server/invocation/legacy/publication-route.ts`). Its answers are pinned byte for byte by the
6
+ * golden transcript of `server/__tests__/transcripts.test.ts`.
7
+ *
8
+ * @deprecated Use `ServerOptions.release` with `prepareReleaseSnapshot`, which serves a
9
+ * `DomainRelease` at `release.json` beside its frozen v3 projection at `domain.json` and admits
10
+ * only exact routing.
11
+ *
12
+ * Short-term consumers:
13
+ * - the legacy direct-mode Workers built by an SDK that seals v2/v3 only: the domains platform
14
+ * Domains, Shell, the Services `development` environment and `ui:domain`, until they serve
15
+ * releases;
16
+ * - an SDK Worker whose deployment host binds no build digest, which serves today's v3 document;
17
+ * - the Host's intrinsic Kernel and Host Domain publishers (`host/kernel/publication/`) and the
18
+ * FalkorDB qualification remote Domain, until the Host serves its intrinsic release.
19
+ *
20
+ * Removal, in a separate breaking release: the conditions of `protocol/release/legacy/index.ts`
21
+ * hold (no stable Worker serves a v2/v3 document, no installation pins one), no supported SDK
22
+ * builds a v3-input Worker, and the intrinsic Kernel and Host Domains serve a release. Then
23
+ * delete, in the same PR:
24
+ * - this folder with its `.spec` (its v1-only Delivery `Provider` included), the `publication`
25
+ * namespace, `preparePublicationSnapshot` and the Publication snapshot types of
26
+ * `server/index.ts` and `server/.spec/api.d.ts`, and the `./publication/snapshot` subpath
27
+ * (source and `dist`) of `server/package.json`;
28
+ * - `ServerOptions.publication` (`server/types.ts`) and `HandlerRequest.publication`
29
+ * (`server/invocation/types.ts`), with the `publication` branch of `Served`
30
+ * (`server/release/served.ts`) and every `kind === 'publication'` branch it selects in
31
+ * `server/routes/routes.ts`, `server/release/delivery/handler.ts` and `admitVia`;
32
+ * - `server/invocation/legacy/` (laws SV-PUBLICATION-CONTINUITY, SV-PUBLICATION-ADMITS-LEGACY-VIA);
33
+ * - the golden transcript that records a v3-input server, keeping the one of a server without a
34
+ * source.
35
+ */
36
+ export { preparePublicationSnapshot, readPreparedPublicationSnapshot, type PreparedPublicationSnapshot, } from './snapshot.js';
37
+ export type { PublicationSnapshot, PublicationSnapshotInput, PublicationSource } from './types.js';
38
+ /** @deprecated The Delivery v1 boundary of a v3-input destination; use `release.delivery`. */
39
+ export * as delivery from './delivery.js';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The Publication snapshot of a v3-input destination: a Server built from a v2/v3 Publication
3
+ * serves `domain.json`, its bundle, Delivery v1 and its native routes exactly as before the release
4
+ * snapshot, and keeps the #508 same-schema and schema-drift continuity
5
+ * (`server/invocation/legacy/publication-route.ts`). Its answers are pinned byte for byte by the
6
+ * golden transcript of `server/__tests__/transcripts.test.ts`.
7
+ *
8
+ * @deprecated Use `ServerOptions.release` with `prepareReleaseSnapshot`, which serves a
9
+ * `DomainRelease` at `release.json` beside its frozen v3 projection at `domain.json` and admits
10
+ * only exact routing.
11
+ *
12
+ * Short-term consumers:
13
+ * - the legacy direct-mode Workers built by an SDK that seals v2/v3 only: the domains platform
14
+ * Domains, Shell, the Services `development` environment and `ui:domain`, until they serve
15
+ * releases;
16
+ * - an SDK Worker whose deployment host binds no build digest, which serves today's v3 document;
17
+ * - the Host's intrinsic Kernel and Host Domain publishers (`host/kernel/publication/`) and the
18
+ * FalkorDB qualification remote Domain, until the Host serves its intrinsic release.
19
+ *
20
+ * Removal, in a separate breaking release: the conditions of `protocol/release/legacy/index.ts`
21
+ * hold (no stable Worker serves a v2/v3 document, no installation pins one), no supported SDK
22
+ * builds a v3-input Worker, and the intrinsic Kernel and Host Domains serve a release. Then
23
+ * delete, in the same PR:
24
+ * - this folder with its `.spec` (its v1-only Delivery `Provider` included), the `publication`
25
+ * namespace, `preparePublicationSnapshot` and the Publication snapshot types of
26
+ * `server/index.ts` and `server/.spec/api.d.ts`, and the `./publication/snapshot` subpath
27
+ * (source and `dist`) of `server/package.json`;
28
+ * - `ServerOptions.publication` (`server/types.ts`) and `HandlerRequest.publication`
29
+ * (`server/invocation/types.ts`), with the `publication` branch of `Served`
30
+ * (`server/release/served.ts`) and every `kind === 'publication'` branch it selects in
31
+ * `server/routes/routes.ts`, `server/release/delivery/handler.ts` and `admitVia`;
32
+ * - `server/invocation/legacy/` (laws SV-PUBLICATION-CONTINUITY, SV-PUBLICATION-ADMITS-LEGACY-VIA);
33
+ * - the golden transcript that records a v3-input server, keeping the one of a server without a
34
+ * source.
35
+ */
36
+ export { preparePublicationSnapshot, readPreparedPublicationSnapshot, } from './snapshot.js';
37
+ /** @deprecated The Delivery v1 boundary of a v3-input destination; use `release.delivery`. */
38
+ export * as delivery from './delivery.js';