@astrale-os/kernel-protocol 0.5.0-beta.10

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 (224) hide show
  1. package/LICENSE +202 -0
  2. package/dist/artifact/index.d.ts +1 -0
  3. package/dist/artifact/index.js +1 -0
  4. package/dist/artifact/ref.d.ts +10 -0
  5. package/dist/artifact/ref.js +70 -0
  6. package/dist/codecs/codec.d.ts +13 -0
  7. package/dist/codecs/codec.js +6 -0
  8. package/dist/codecs/index.d.ts +4 -0
  9. package/dist/codecs/index.js +4 -0
  10. package/dist/codecs/json.d.ts +2 -0
  11. package/dist/codecs/json.js +20 -0
  12. package/dist/codecs/media-types.d.ts +2 -0
  13. package/dist/codecs/media-types.js +2 -0
  14. package/dist/codecs/msgpack.d.ts +4 -0
  15. package/dist/codecs/msgpack.js +50 -0
  16. package/dist/errors/codes.d.ts +23 -0
  17. package/dist/errors/codes.js +22 -0
  18. package/dist/errors/http-status.d.ts +2 -0
  19. package/dist/errors/http-status.js +25 -0
  20. package/dist/errors/index.d.ts +5 -0
  21. package/dist/errors/index.js +5 -0
  22. package/dist/errors/payload.d.ts +8 -0
  23. package/dist/errors/payload.js +37 -0
  24. package/dist/errors/protocol-error.d.ts +6 -0
  25. package/dist/errors/protocol-error.js +11 -0
  26. package/dist/errors/reason.d.ts +7 -0
  27. package/dist/errors/reason.js +43 -0
  28. package/dist/index.d.ts +8 -0
  29. package/dist/index.js +8 -0
  30. package/dist/invocation/admission.d.ts +23 -0
  31. package/dist/invocation/admission.js +177 -0
  32. package/dist/invocation/binary.d.ts +11 -0
  33. package/dist/invocation/binary.js +24 -0
  34. package/dist/invocation/binding-codecs.d.ts +8 -0
  35. package/dist/invocation/binding-codecs.js +27 -0
  36. package/dist/invocation/cancellation.d.ts +8 -0
  37. package/dist/invocation/cancellation.js +16 -0
  38. package/dist/invocation/correlation.d.ts +2 -0
  39. package/dist/invocation/correlation.js +7 -0
  40. package/dist/invocation/delegate.d.ts +24 -0
  41. package/dist/invocation/delegate.js +43 -0
  42. package/dist/invocation/http/binary.d.ts +12 -0
  43. package/dist/invocation/http/binary.js +132 -0
  44. package/dist/invocation/http/index.d.ts +4 -0
  45. package/dist/invocation/http/index.js +4 -0
  46. package/dist/invocation/http/negotiation.d.ts +3 -0
  47. package/dist/invocation/http/negotiation.js +207 -0
  48. package/dist/invocation/http/stream.d.ts +5 -0
  49. package/dist/invocation/http/stream.js +15 -0
  50. package/dist/invocation/http/trace.d.ts +6 -0
  51. package/dist/invocation/http/trace.js +24 -0
  52. package/dist/invocation/idempotency.d.ts +10 -0
  53. package/dist/invocation/idempotency.js +13 -0
  54. package/dist/invocation/identity.d.ts +16 -0
  55. package/dist/invocation/identity.js +55 -0
  56. package/dist/invocation/index.d.ts +20 -0
  57. package/dist/invocation/index.js +20 -0
  58. package/dist/invocation/miss.d.ts +9 -0
  59. package/dist/invocation/miss.js +17 -0
  60. package/dist/invocation/redirect.d.ts +40 -0
  61. package/dist/invocation/redirect.js +81 -0
  62. package/dist/invocation/request.d.ts +21 -0
  63. package/dist/invocation/request.js +54 -0
  64. package/dist/invocation/response.d.ts +19 -0
  65. package/dist/invocation/response.js +38 -0
  66. package/dist/invocation/routing.d.ts +14 -0
  67. package/dist/invocation/routing.js +34 -0
  68. package/dist/invocation/schema.d.ts +9 -0
  69. package/dist/invocation/schema.js +19 -0
  70. package/dist/invocation/service-reason.d.ts +48 -0
  71. package/dist/invocation/service-reason.js +85 -0
  72. package/dist/invocation/stale-route.d.ts +12 -0
  73. package/dist/invocation/stale-route.js +19 -0
  74. package/dist/invocation/stream.d.ts +27 -0
  75. package/dist/invocation/stream.js +44 -0
  76. package/dist/invocation/target.d.ts +3 -0
  77. package/dist/invocation/target.js +11 -0
  78. package/dist/invocation/trace.d.ts +9 -0
  79. package/dist/invocation/trace.js +86 -0
  80. package/dist/invocation/version.d.ts +2 -0
  81. package/dist/invocation/version.js +1 -0
  82. package/dist/invocation/websocket/binary.d.ts +3 -0
  83. package/dist/invocation/websocket/binary.js +71 -0
  84. package/dist/invocation/websocket/index.d.ts +2 -0
  85. package/dist/invocation/websocket/index.js +2 -0
  86. package/dist/invocation/websocket/subprotocol.d.ts +10 -0
  87. package/dist/invocation/websocket/subprotocol.js +29 -0
  88. package/dist/issuer/configuration.d.ts +14 -0
  89. package/dist/issuer/configuration.js +86 -0
  90. package/dist/issuer/error.d.ts +7 -0
  91. package/dist/issuer/error.js +11 -0
  92. package/dist/issuer/exchange.d.ts +25 -0
  93. package/dist/issuer/exchange.js +129 -0
  94. package/dist/issuer/hosted.d.ts +2 -0
  95. package/dist/issuer/hosted.js +7 -0
  96. package/dist/issuer/index.d.ts +6 -0
  97. package/dist/issuer/index.js +6 -0
  98. package/dist/issuer/key-set.d.ts +6 -0
  99. package/dist/issuer/key-set.js +48 -0
  100. package/dist/issuer/paths.d.ts +6 -0
  101. package/dist/issuer/paths.js +10 -0
  102. package/dist/issuer/url.d.ts +2 -0
  103. package/dist/issuer/url.js +34 -0
  104. package/dist/publication/bindings/callable.d.ts +8 -0
  105. package/dist/publication/bindings/callable.js +34 -0
  106. package/dist/publication/bindings/index.d.ts +3 -0
  107. package/dist/publication/bindings/index.js +3 -0
  108. package/dist/publication/bindings/invocation.d.ts +5 -0
  109. package/dist/publication/bindings/invocation.js +13 -0
  110. package/dist/publication/bindings/view.d.ts +7 -0
  111. package/dist/publication/bindings/view.js +28 -0
  112. package/dist/publication/bundle-admission.d.ts +4 -0
  113. package/dist/publication/bundle-admission.js +65 -0
  114. package/dist/publication/bundle-descriptor.d.ts +9 -0
  115. package/dist/publication/bundle-descriptor.js +19 -0
  116. package/dist/publication/bundle-evidence.d.ts +3 -0
  117. package/dist/publication/bundle-evidence.js +13 -0
  118. package/dist/publication/bundle.d.ts +9 -0
  119. package/dist/publication/bundle.js +44 -0
  120. package/dist/publication/content.d.ts +10 -0
  121. package/dist/publication/content.js +55 -0
  122. package/dist/publication/declaration.d.ts +11 -0
  123. package/dist/publication/declaration.js +1 -0
  124. package/dist/publication/delivery/attestation.d.ts +6 -0
  125. package/dist/publication/delivery/attestation.js +10 -0
  126. package/dist/publication/delivery/claims.d.ts +16 -0
  127. package/dist/publication/delivery/claims.js +61 -0
  128. package/dist/publication/delivery/delivery.d.ts +9 -0
  129. package/dist/publication/delivery/delivery.js +29 -0
  130. package/dist/publication/delivery/error.d.ts +7 -0
  131. package/dist/publication/delivery/error.js +11 -0
  132. package/dist/publication/delivery/index.d.ts +6 -0
  133. package/dist/publication/delivery/index.js +6 -0
  134. package/dist/publication/delivery/limits.d.ts +4 -0
  135. package/dist/publication/delivery/limits.js +4 -0
  136. package/dist/publication/delivery/request.d.ts +15 -0
  137. package/dist/publication/delivery/request.js +52 -0
  138. package/dist/publication/delivery-declaration.d.ts +4 -0
  139. package/dist/publication/delivery-declaration.js +25 -0
  140. package/dist/publication/delivery-endpoint.d.ts +11 -0
  141. package/dist/publication/delivery-endpoint.js +22 -0
  142. package/dist/publication/error.d.ts +7 -0
  143. package/dist/publication/error.js +11 -0
  144. package/dist/publication/etag.d.ts +6 -0
  145. package/dist/publication/etag.js +79 -0
  146. package/dist/publication/index.d.ts +17 -0
  147. package/dist/publication/index.js +10 -0
  148. package/dist/publication/manifest.d.ts +6 -0
  149. package/dist/publication/manifest.js +30 -0
  150. package/dist/publication/publication.d.ts +36 -0
  151. package/dist/publication/publication.js +133 -0
  152. package/dist/publication/reference.d.ts +17 -0
  153. package/dist/publication/reference.js +68 -0
  154. package/dist/publication/requirements.d.ts +26 -0
  155. package/dist/publication/requirements.js +180 -0
  156. package/dist/publication/route.d.ts +37 -0
  157. package/dist/publication/route.js +154 -0
  158. package/dist/publication/schema.d.ts +7 -0
  159. package/dist/publication/schema.js +15 -0
  160. package/dist/routes/admission.d.ts +3 -0
  161. package/dist/routes/admission.js +89 -0
  162. package/dist/routes/binding.d.ts +6 -0
  163. package/dist/routes/binding.js +27 -0
  164. package/dist/routes/collision.d.ts +2 -0
  165. package/dist/routes/collision.js +16 -0
  166. package/dist/routes/compiler.d.ts +8 -0
  167. package/dist/routes/compiler.js +50 -0
  168. package/dist/routes/credential.d.ts +8 -0
  169. package/dist/routes/credential.js +118 -0
  170. package/dist/routes/decode.d.ts +5 -0
  171. package/dist/routes/decode.js +147 -0
  172. package/dist/routes/encode.d.ts +4 -0
  173. package/dist/routes/encode.js +145 -0
  174. package/dist/routes/error-response.d.ts +5 -0
  175. package/dist/routes/error-response.js +31 -0
  176. package/dist/routes/error.d.ts +8 -0
  177. package/dist/routes/error.js +14 -0
  178. package/dist/routes/evidence.d.ts +5 -0
  179. package/dist/routes/evidence.js +38 -0
  180. package/dist/routes/index.d.ts +19 -0
  181. package/dist/routes/index.js +16 -0
  182. package/dist/routes/mapping.d.ts +5 -0
  183. package/dist/routes/mapping.js +126 -0
  184. package/dist/routes/match.d.ts +4 -0
  185. package/dist/routes/match.js +7 -0
  186. package/dist/routes/placement.d.ts +5 -0
  187. package/dist/routes/placement.js +18 -0
  188. package/dist/routes/plan.d.ts +24 -0
  189. package/dist/routes/plan.js +1 -0
  190. package/dist/routes/release-unavailable.d.ts +8 -0
  191. package/dist/routes/release-unavailable.js +32 -0
  192. package/dist/routes/request.d.ts +26 -0
  193. package/dist/routes/request.js +53 -0
  194. package/dist/routes/reservation.d.ts +3 -0
  195. package/dist/routes/reservation.js +37 -0
  196. package/dist/routes/routing.d.ts +22 -0
  197. package/dist/routes/routing.js +87 -0
  198. package/dist/routes/scalar.d.ts +7 -0
  199. package/dist/routes/scalar.js +61 -0
  200. package/dist/routes/select.d.ts +3 -0
  201. package/dist/routes/select.js +16 -0
  202. package/dist/routes/stream.d.ts +14 -0
  203. package/dist/routes/stream.js +50 -0
  204. package/dist/routes/table.d.ts +9 -0
  205. package/dist/routes/table.js +1 -0
  206. package/dist/routes/target.d.ts +13 -0
  207. package/dist/routes/target.js +3 -0
  208. package/dist/routes/template.d.ts +8 -0
  209. package/dist/routes/template.js +227 -0
  210. package/dist/runtime/artifact.d.ts +47 -0
  211. package/dist/runtime/artifact.js +176 -0
  212. package/dist/runtime/index.d.ts +1 -0
  213. package/dist/runtime/index.js +1 -0
  214. package/dist/schema/index.d.ts +1 -0
  215. package/dist/schema/index.js +1 -0
  216. package/dist/schema/installation/index.d.ts +1 -0
  217. package/dist/schema/installation/index.js +1 -0
  218. package/dist/schema/installation/introspection/codec.d.ts +5 -0
  219. package/dist/schema/installation/introspection/codec.js +272 -0
  220. package/dist/schema/installation/introspection/index.d.ts +2 -0
  221. package/dist/schema/installation/introspection/index.js +1 -0
  222. package/dist/schema/installation/introspection/result.d.ts +33 -0
  223. package/dist/schema/installation/introspection/result.js +1 -0
  224. package/package.json +163 -0
@@ -0,0 +1,86 @@
1
+ import { fail } from './error.js';
2
+ import { acceptIssuer } from './url.js';
3
+ const ALGORITHM = /^[A-Za-z0-9_-]{1,64}$/u;
4
+ export function configuration(issuer, algorithms, options = {}) {
5
+ const admitted = acceptIssuer(issuer);
6
+ const acceptedAlgorithms = acceptAlgorithms(algorithms);
7
+ const tokenExchange = acceptConfigurationOptions(options).tokenExchange === true;
8
+ return deepFreeze({
9
+ issuer: admitted,
10
+ jwks_uri: `${admitted}/.well-known/jwks.json`,
11
+ subject_types_supported: ['public'],
12
+ id_token_signing_alg_values_supported: acceptedAlgorithms,
13
+ ...(tokenExchange ? { token_exchange_endpoint: `${admitted}/.well-known/astrale/token` } : {}),
14
+ });
15
+ }
16
+ export function acceptConfiguration(input, expectedIssuer) {
17
+ const value = record(input);
18
+ exact(value, ['issuer', 'jwks_uri', 'subject_types_supported', 'id_token_signing_alg_values_supported'], ['token_exchange_endpoint']);
19
+ const admitted = acceptIssuer(value.issuer);
20
+ if (expectedIssuer !== undefined && admitted !== acceptIssuer(expectedIssuer)) {
21
+ fail('ISSUER_DISCOVERY_MISMATCH', 'Issuer discovery does not match the requested issuer.');
22
+ }
23
+ if (value.jwks_uri !== `${admitted}/.well-known/jwks.json`) {
24
+ fail('ISSUER_DISCOVERY_INVALID', 'Issuer discovery key URL is not canonical.');
25
+ }
26
+ if (!Array.isArray(value.subject_types_supported) ||
27
+ value.subject_types_supported.length !== 1 ||
28
+ value.subject_types_supported[0] !== 'public') {
29
+ fail('ISSUER_DISCOVERY_INVALID', 'Issuer discovery subject types are invalid.');
30
+ }
31
+ const endpoint = value.token_exchange_endpoint;
32
+ if (endpoint !== undefined && endpoint !== `${admitted}/.well-known/astrale/token`) {
33
+ fail('ISSUER_DISCOVERY_INVALID', 'Issuer token-exchange URL is not canonical.');
34
+ }
35
+ return configuration(admitted, acceptAlgorithms(value.id_token_signing_alg_values_supported), {
36
+ tokenExchange: endpoint !== undefined,
37
+ });
38
+ }
39
+ function acceptConfigurationOptions(input) {
40
+ if (input === null || typeof input !== 'object' || Array.isArray(input)) {
41
+ fail('ISSUER_DISCOVERY_INVALID', 'Issuer configuration options are invalid.');
42
+ }
43
+ const keys = Reflect.ownKeys(input);
44
+ if (keys.some((key) => key !== 'tokenExchange')) {
45
+ fail('ISSUER_DISCOVERY_INVALID', 'Issuer configuration options contain unexpected fields.');
46
+ }
47
+ if (input.tokenExchange !== undefined && typeof input.tokenExchange !== 'boolean') {
48
+ fail('ISSUER_DISCOVERY_INVALID', 'Issuer token-exchange option is invalid.');
49
+ }
50
+ return input;
51
+ }
52
+ export function acceptAlgorithms(input) {
53
+ if (!Array.isArray(input) || input.length === 0) {
54
+ fail('ISSUER_DISCOVERY_INVALID', 'Issuer algorithms must be a non-empty array.');
55
+ }
56
+ const algorithms = input.map((algorithm) => {
57
+ if (typeof algorithm !== 'string' || !ALGORITHM.test(algorithm)) {
58
+ fail('ISSUER_DISCOVERY_INVALID', 'Issuer algorithm is invalid.');
59
+ }
60
+ return algorithm;
61
+ });
62
+ if (new Set(algorithms).size !== algorithms.length) {
63
+ fail('ISSUER_DISCOVERY_INVALID', 'Issuer algorithms must be unique.');
64
+ }
65
+ return Object.freeze(algorithms);
66
+ }
67
+ function record(input) {
68
+ if (input === null || typeof input !== 'object' || Array.isArray(input)) {
69
+ fail('ISSUER_DISCOVERY_INVALID', 'Issuer discovery is invalid.');
70
+ }
71
+ return input;
72
+ }
73
+ function exact(input, required, optional = []) {
74
+ const keys = Reflect.ownKeys(input);
75
+ if (required.some((field) => !Object.hasOwn(input, field)) ||
76
+ keys.some((key) => typeof key !== 'string' || (!required.includes(key) && !optional.includes(key)))) {
77
+ fail('ISSUER_DISCOVERY_INVALID', 'Issuer discovery document contains unexpected fields.');
78
+ }
79
+ }
80
+ function deepFreeze(input) {
81
+ if (input === null || typeof input !== 'object' || Object.isFrozen(input))
82
+ return input;
83
+ for (const value of Object.values(input))
84
+ deepFreeze(value);
85
+ return Object.freeze(input);
86
+ }
@@ -0,0 +1,7 @@
1
+ export type DiscoveryErrorCode = 'ISSUER_DISCOVERY_INVALID' | 'ISSUER_DISCOVERY_MISMATCH' | 'ISSUER_KEY_SET_INVALID';
2
+ export declare class DiscoveryError extends Error {
3
+ readonly code: DiscoveryErrorCode;
4
+ readonly name = "DiscoveryError";
5
+ constructor(code: DiscoveryErrorCode, message: string, options?: ErrorOptions);
6
+ }
7
+ export declare function fail(code: DiscoveryErrorCode, message: string, cause?: unknown): never;
@@ -0,0 +1,11 @@
1
+ export class DiscoveryError extends Error {
2
+ code;
3
+ name = 'DiscoveryError';
4
+ constructor(code, message, options) {
5
+ super(message, options);
6
+ this.code = code;
7
+ }
8
+ }
9
+ export function fail(code, message, cause) {
10
+ throw new DiscoveryError(code, message, cause === undefined ? undefined : { cause });
11
+ }
@@ -0,0 +1,25 @@
1
+ import type { ErrorPayload } from '../errors/index.js';
2
+ export declare const MEDIA_TYPE: 'application/vnd.astrale+json';
3
+ export interface Request {
4
+ readonly token: string;
5
+ }
6
+ export interface Response {
7
+ readonly token: string;
8
+ readonly expiresAt: number;
9
+ }
10
+ export interface ErrorResponse {
11
+ readonly error: ErrorPayload;
12
+ }
13
+ export interface HttpRequest {
14
+ readonly method: string;
15
+ readonly url: string;
16
+ readonly authorization: string | null;
17
+ readonly accept: string | null;
18
+ }
19
+ export declare function request(token: string): Request;
20
+ export declare function acceptRequest(input: unknown): Request;
21
+ export declare function response(token: string, expiresAt: number): Response;
22
+ export declare function acceptResponse(input: unknown): Response;
23
+ export declare function acceptErrorResponse(input: unknown): ErrorResponse;
24
+ export declare function acceptHttpRequest(input: HttpRequest): Request;
25
+ export declare function responseHeaders(): Readonly<Record<string, string>>;
@@ -0,0 +1,129 @@
1
+ import { acceptPayload, CODES, ProtocolError } from '../errors/index.js';
2
+ export const MEDIA_TYPE = 'application/vnd.astrale+json';
3
+ export function request(token) {
4
+ return acceptRequest({ token });
5
+ }
6
+ export function acceptRequest(input) {
7
+ const value = exactObject(input, ['token'], 'token-exchange request');
8
+ return Object.freeze({ token: nonEmptyText(value.token, 'token-exchange request.token') });
9
+ }
10
+ export function response(token, expiresAt) {
11
+ return acceptResponse({ token, expiresAt });
12
+ }
13
+ export function acceptResponse(input) {
14
+ const value = exactObject(input, ['token', 'expiresAt'], 'token-exchange response');
15
+ if (!Number.isSafeInteger(value.expiresAt) || value.expiresAt <= 0) {
16
+ throw new TypeError('token-exchange response.expiresAt must be a positive safe integer.');
17
+ }
18
+ return Object.freeze({
19
+ token: nonEmptyText(value.token, 'token-exchange response.token'),
20
+ expiresAt: value.expiresAt,
21
+ });
22
+ }
23
+ export function acceptErrorResponse(input) {
24
+ const value = exactObject(input, ['error'], 'token-exchange error response');
25
+ return Object.freeze({ error: acceptPayload(value.error, 'token-exchange error response.error') });
26
+ }
27
+ export function acceptHttpRequest(input) {
28
+ const value = exactObject(input, ['method', 'url', 'authorization', 'accept'], 'token-exchange HTTP request');
29
+ if (value.method !== 'POST') {
30
+ fail(CODES.METHOD_NOT_ALLOWED, 'Token exchange supports POST only.');
31
+ }
32
+ if (typeof value.url !== 'string')
33
+ invalid('Token-exchange URL is invalid.');
34
+ let url;
35
+ try {
36
+ url = new URL(value.url);
37
+ }
38
+ catch {
39
+ invalid('Token-exchange URL is invalid.');
40
+ }
41
+ if (url.search !== '' || url.hash !== '') {
42
+ invalid('Token-exchange URL must not contain a query or fragment.');
43
+ }
44
+ if (value.authorization === null) {
45
+ fail(CODES.AUTH_MISSING, 'Authentication is required.');
46
+ }
47
+ if (typeof value.authorization !== 'string') {
48
+ fail(CODES.AUTH_INVALID, 'Token is invalid.');
49
+ }
50
+ const authorization = /^Bearer ([^\s,]+)$/iu.exec(value.authorization);
51
+ if (authorization === null)
52
+ fail(CODES.AUTH_INVALID, 'Token is invalid.');
53
+ if (value.accept !== null) {
54
+ if (typeof value.accept !== 'string' || !acceptsJson(value.accept)) {
55
+ invalid('Token-exchange response representation is not accepted.');
56
+ }
57
+ }
58
+ return request(authorization[1]);
59
+ }
60
+ export function responseHeaders() {
61
+ return Object.freeze({
62
+ 'content-type': MEDIA_TYPE,
63
+ 'cache-control': 'no-store',
64
+ pragma: 'no-cache',
65
+ 'x-content-type-options': 'nosniff',
66
+ });
67
+ }
68
+ function acceptsJson(input) {
69
+ for (const member of input.split(',')) {
70
+ const parts = member.split(';');
71
+ const media = parts.shift()?.trim().toLowerCase();
72
+ if (media === undefined)
73
+ continue;
74
+ let quality = 1;
75
+ let valid = media === MEDIA_TYPE || media === 'application/json' || media === '*/*';
76
+ for (const parameter of parts) {
77
+ const [rawName, rawValue, ...extra] = parameter.split('=');
78
+ if (extra.length > 0 || rawName?.trim().toLowerCase() !== 'q') {
79
+ valid = false;
80
+ break;
81
+ }
82
+ const value = rawValue?.trim();
83
+ if (value === undefined || !/^(?:0(?:\.\d{0,3})?|1(?:\.0{0,3})?)$/u.test(value)) {
84
+ valid = false;
85
+ break;
86
+ }
87
+ quality = Number(value);
88
+ }
89
+ if (valid && quality > 0)
90
+ return true;
91
+ }
92
+ return false;
93
+ }
94
+ function invalid(message) {
95
+ fail(CODES.INVALID_REQUEST, message);
96
+ }
97
+ function fail(code, message) {
98
+ throw new ProtocolError({ code, message });
99
+ }
100
+ function exactObject(input, expected, label) {
101
+ if (input === null || typeof input !== 'object' || Array.isArray(input)) {
102
+ throw new TypeError(`${label} must be an object.`);
103
+ }
104
+ const prototype = Object.getPrototypeOf(input);
105
+ if (prototype !== Object.prototype && prototype !== null) {
106
+ throw new TypeError(`${label} must be a plain object.`);
107
+ }
108
+ const output = {};
109
+ const keys = Reflect.ownKeys(input);
110
+ if (keys.length !== expected.length)
111
+ throw new TypeError(`${label} must be exact.`);
112
+ for (const key of keys) {
113
+ if (typeof key !== 'string' || !expected.includes(key)) {
114
+ throw new TypeError(`${label} contains an unexpected field.`);
115
+ }
116
+ const descriptor = Object.getOwnPropertyDescriptor(input, key);
117
+ if (descriptor === undefined || !descriptor.enumerable || !('value' in descriptor)) {
118
+ throw new TypeError(`${label}.${key} must be an enumerable data field.`);
119
+ }
120
+ output[key] = descriptor.value;
121
+ }
122
+ return output;
123
+ }
124
+ function nonEmptyText(input, label) {
125
+ if (typeof input !== 'string' || input.length === 0) {
126
+ throw new TypeError(`${label} must be non-empty text.`);
127
+ }
128
+ return input;
129
+ }
@@ -0,0 +1,2 @@
1
+ import type { IssuerId } from '@astrale-os/kernel-core/auth/issuer';
2
+ export declare function hosted(base: string, thumbprint: string): IssuerId | undefined;
@@ -0,0 +1,7 @@
1
+ import { acceptIssuer } from './url.js';
2
+ const THUMBPRINT = /^[A-Za-z0-9_-]{43}$/u;
3
+ export function hosted(base, thumbprint) {
4
+ if (typeof thumbprint !== 'string' || !THUMBPRINT.test(thumbprint))
5
+ return undefined;
6
+ return acceptIssuer(`${acceptIssuer(base)}/iss/${thumbprint}`);
7
+ }
@@ -0,0 +1,6 @@
1
+ export { acceptConfiguration, configuration, type Configuration, type ConfigurationOptions, } from './configuration.js';
2
+ export { DiscoveryError, type DiscoveryErrorCode } from './error.js';
3
+ export { hosted } from './hosted.js';
4
+ export { acceptKeySet, keySet, type KeySet } from './key-set.js';
5
+ export { paths, type Paths } from './paths.js';
6
+ export * as exchange from './exchange.js';
@@ -0,0 +1,6 @@
1
+ export { acceptConfiguration, configuration, } from './configuration.js';
2
+ export { DiscoveryError } from './error.js';
3
+ export { hosted } from './hosted.js';
4
+ export { acceptKeySet, keySet } from './key-set.js';
5
+ export { paths } from './paths.js';
6
+ export * as exchange from './exchange.js';
@@ -0,0 +1,6 @@
1
+ import { type JsonWebKey } from '@astrale-os/kernel-core/auth/jwk';
2
+ export interface KeySet {
3
+ readonly keys: readonly JsonWebKey[];
4
+ }
5
+ export declare function keySet(keys: readonly JsonWebKey[], algorithms: readonly [string, ...string[]]): KeySet;
6
+ export declare function acceptKeySet(input: unknown, algorithms: readonly [string, ...string[]]): KeySet;
@@ -0,0 +1,48 @@
1
+ import { AuthValueError } from '@astrale-os/kernel-core/auth/error';
2
+ import { acceptPublic } from '@astrale-os/kernel-core/auth/jwk';
3
+ import { acceptAlgorithms } from './configuration.js';
4
+ import { fail } from './error.js';
5
+ export function keySet(keys, algorithms) {
6
+ if (!Array.isArray(keys))
7
+ fail('ISSUER_KEY_SET_INVALID', 'Issuer keys must be an array.');
8
+ const acceptedAlgorithms = new Set(acceptAlgorithms(algorithms));
9
+ const admittedKeys = keys.map((key) => admitKey(key, acceptedAlgorithms));
10
+ return deepFreeze({ keys: admittedKeys });
11
+ }
12
+ export function acceptKeySet(input, algorithms) {
13
+ if (input === null || typeof input !== 'object' || Array.isArray(input)) {
14
+ fail('ISSUER_KEY_SET_INVALID', 'Issuer key set is invalid.');
15
+ }
16
+ const value = input;
17
+ const fields = Reflect.ownKeys(value);
18
+ if (fields.length !== 1 || fields[0] !== 'keys' || !Array.isArray(value.keys)) {
19
+ fail('ISSUER_KEY_SET_INVALID', 'Issuer key set is invalid.');
20
+ }
21
+ return keySet(value.keys, algorithms);
22
+ }
23
+ function admitKey(input, algorithms) {
24
+ let accepted;
25
+ try {
26
+ accepted = acceptPublic(input);
27
+ }
28
+ catch (cause) {
29
+ if (cause instanceof AuthValueError) {
30
+ return fail('ISSUER_KEY_SET_INVALID', 'Issuer public key is invalid.', cause);
31
+ }
32
+ throw cause;
33
+ }
34
+ if (accepted.alg !== undefined && !algorithms.has(accepted.alg)) {
35
+ fail('ISSUER_KEY_SET_INVALID', 'Issuer key algorithm is not advertised.');
36
+ }
37
+ if (accepted.use !== undefined && accepted.use !== 'sig') {
38
+ fail('ISSUER_KEY_SET_INVALID', 'Issuer key is not admitted for signature verification.');
39
+ }
40
+ return Object.freeze({ ...accepted });
41
+ }
42
+ function deepFreeze(input) {
43
+ if (input === null || typeof input !== 'object' || Object.isFrozen(input))
44
+ return input;
45
+ for (const value of Object.values(input))
46
+ deepFreeze(value);
47
+ return Object.freeze(input);
48
+ }
@@ -0,0 +1,6 @@
1
+ export interface Paths {
2
+ readonly configuration: string;
3
+ readonly keys: string;
4
+ readonly tokenExchange: string;
5
+ }
6
+ export declare function paths(issuer: string): Paths;
@@ -0,0 +1,10 @@
1
+ import { acceptIssuer } from './url.js';
2
+ export function paths(issuer) {
3
+ const admitted = acceptIssuer(issuer);
4
+ const pathname = new URL(admitted).pathname.replace(/\/+$/u, '');
5
+ return Object.freeze({
6
+ configuration: `${pathname}/.well-known/openid-configuration`,
7
+ keys: `${pathname}/.well-known/jwks.json`,
8
+ tokenExchange: `${pathname}/.well-known/astrale/token`,
9
+ });
10
+ }
@@ -0,0 +1,2 @@
1
+ import { type IssuerId } from '@astrale-os/kernel-core/auth/issuer';
2
+ export declare function acceptIssuer(input: unknown): IssuerId;
@@ -0,0 +1,34 @@
1
+ import { AuthValueError } from '@astrale-os/kernel-core/auth/error';
2
+ import { accept as acceptIssuerId } from '@astrale-os/kernel-core/auth/issuer';
3
+ import { fail } from './error.js';
4
+ export function acceptIssuer(input) {
5
+ if (typeof input !== 'string') {
6
+ fail('ISSUER_DISCOVERY_INVALID', 'Issuer must be an absolute HTTP(S) URL.');
7
+ }
8
+ let url;
9
+ try {
10
+ url = new URL(input);
11
+ }
12
+ catch (cause) {
13
+ if (cause instanceof TypeError) {
14
+ return fail('ISSUER_DISCOVERY_INVALID', 'Issuer must be an absolute HTTP(S) URL.', cause);
15
+ }
16
+ throw cause;
17
+ }
18
+ if ((url.protocol !== 'https:' && url.protocol !== 'http:') ||
19
+ url.username !== '' ||
20
+ url.password !== '' ||
21
+ url.hash !== '' ||
22
+ url.search !== '') {
23
+ fail('ISSUER_DISCOVERY_INVALID', 'Issuer must be HTTP(S) without credentials, query, or fragment.');
24
+ }
25
+ try {
26
+ return acceptIssuerId(input.replace(/\/+$/u, ''));
27
+ }
28
+ catch (cause) {
29
+ if (cause instanceof AuthValueError) {
30
+ return fail('ISSUER_DISCOVERY_INVALID', 'Issuer is invalid.', cause);
31
+ }
32
+ throw cause;
33
+ }
34
+ }
@@ -0,0 +1,8 @@
1
+ import { Key } from '@astrale-os/kernel-dsl/v1/addressing';
2
+ import type { InvocationEndpoints } from './invocation.js';
3
+ export interface CallableBinding {
4
+ readonly callable: Key.Callable;
5
+ readonly endpoint: InvocationEndpoints;
6
+ }
7
+ export declare function admitCallableBinding(input: unknown, origin: string): CallableBinding;
8
+ export declare function admitCallableKey(input: unknown, origin: string): Key.Callable;
@@ -0,0 +1,34 @@
1
+ import { callableAddress, Key } from '@astrale-os/kernel-dsl/v1/addressing';
2
+ import { exact, record } from '../content.js';
3
+ import { fail } from '../error.js';
4
+ import { acceptInvocationEndpoints } from './invocation.js';
5
+ export function admitCallableBinding(input, origin) {
6
+ const value = record(input, 'PUBLICATION_BINDING_INVALID', 'Callable binding is invalid.');
7
+ exact(value, ['callable', 'endpoint'], 'PUBLICATION_BINDING_INVALID');
8
+ return Object.freeze({
9
+ callable: admitCallableKey(value.callable, origin),
10
+ endpoint: acceptInvocationEndpoints(value.endpoint),
11
+ });
12
+ }
13
+ export function admitCallableKey(input, origin) {
14
+ let callable;
15
+ try {
16
+ callable = Key(String(input));
17
+ }
18
+ catch (cause) {
19
+ if (cause instanceof TypeError) {
20
+ return fail('PUBLICATION_BINDING_INVALID', 'Callable key is invalid.', cause);
21
+ }
22
+ throw cause;
23
+ }
24
+ if (Key.origin(callable) !== origin) {
25
+ fail('PUBLICATION_BINDING_INVALID', 'Binding must name one callable owned by the root Domain.');
26
+ }
27
+ try {
28
+ callableAddress(callable);
29
+ }
30
+ catch (cause) {
31
+ return fail('PUBLICATION_BINDING_INVALID', 'Binding must name one callable owned by the root Domain.', cause);
32
+ }
33
+ return callable;
34
+ }
@@ -0,0 +1,3 @@
1
+ export { admitCallableBinding, admitCallableKey, type CallableBinding } from './callable.js';
2
+ export { acceptInvocationEndpoints, type InvocationEndpoints } from './invocation.js';
3
+ export { admitViewBinding, type ViewBinding } from './view.js';
@@ -0,0 +1,3 @@
1
+ export { admitCallableBinding, admitCallableKey } from './callable.js';
2
+ export { acceptInvocationEndpoints } from './invocation.js';
3
+ export { admitViewBinding } from './view.js';
@@ -0,0 +1,5 @@
1
+ export interface InvocationEndpoints {
2
+ readonly http: string;
3
+ readonly websocket?: string;
4
+ }
5
+ export declare function acceptInvocationEndpoints(input: unknown): InvocationEndpoints;
@@ -0,0 +1,13 @@
1
+ import { absoluteHttpUrl, absoluteUrl, exact, record } from '../content.js';
2
+ export function acceptInvocationEndpoints(input) {
3
+ const value = record(input, 'PUBLICATION_BINDING_INVALID', 'Invocation endpoints are invalid.');
4
+ exact(value, ['http', 'websocket'], 'PUBLICATION_BINDING_INVALID');
5
+ const http = absoluteHttpUrl(value.http, true, 'PUBLICATION_BINDING_INVALID');
6
+ const websocket = value.websocket === undefined
7
+ ? undefined
8
+ : absoluteUrl(value.websocket, ['ws:', 'wss:'], true, 'PUBLICATION_BINDING_INVALID');
9
+ return Object.freeze({
10
+ http,
11
+ ...(websocket === undefined ? {} : { websocket }),
12
+ });
13
+ }
@@ -0,0 +1,7 @@
1
+ import { Key } from '@astrale-os/kernel-dsl/v1/addressing';
2
+ export interface ViewBinding {
3
+ readonly view: Key.View;
4
+ readonly href: string;
5
+ readonly handshake: 'shell' | 'none';
6
+ }
7
+ export declare function admitViewBinding(input: unknown, origin: string): ViewBinding;
@@ -0,0 +1,28 @@
1
+ import { Key } from '@astrale-os/kernel-dsl/v1/addressing';
2
+ import { absoluteHttpUrl, exact, record } from '../content.js';
3
+ import { fail } from '../error.js';
4
+ export function admitViewBinding(input, origin) {
5
+ const value = record(input, 'PUBLICATION_VIEW_INVALID', 'Published View binding is invalid.');
6
+ exact(value, ['view', 'href', 'handshake'], 'PUBLICATION_VIEW_INVALID');
7
+ let view;
8
+ try {
9
+ view = Key(String(value.view));
10
+ }
11
+ catch (cause) {
12
+ if (cause instanceof TypeError) {
13
+ return fail('PUBLICATION_VIEW_INVALID', 'View key is invalid.', cause);
14
+ }
15
+ throw cause;
16
+ }
17
+ if (Key.origin(view) !== origin || !view.slice(view.indexOf(':') + 1).startsWith('view.')) {
18
+ fail('PUBLICATION_VIEW_INVALID', 'View binding must name a root View.');
19
+ }
20
+ if (value.handshake !== 'shell' && value.handshake !== 'none') {
21
+ fail('PUBLICATION_VIEW_INVALID', 'View handshake is invalid.');
22
+ }
23
+ return Object.freeze({
24
+ view,
25
+ href: absoluteHttpUrl(value.href, true, 'PUBLICATION_VIEW_INVALID'),
26
+ handshake: value.handshake,
27
+ });
28
+ }
@@ -0,0 +1,4 @@
1
+ import * as dslBundle from '@astrale-os/kernel-dsl/v1/bundle';
2
+ import type { Publication } from './publication.js';
3
+ export declare function acceptBundle(input: Publication, artifact: unknown): dslBundle.Bundle;
4
+ export declare function decodeBundle(input: Publication, bytes: Uint8Array): dslBundle.Bundle;
@@ -0,0 +1,65 @@
1
+ import { Key, MethodKey } from '@astrale-os/kernel-dsl/v1/addressing';
2
+ import * as dslBundle from '@astrale-os/kernel-dsl/v1/bundle';
3
+ import { revision } from '@astrale-os/kernel-dsl/v1/schema/revision';
4
+ import { acceptBundleValue, decodeBundleValue, verifyBundleBytes } from './bundle.js';
5
+ import { fail } from './error.js';
6
+ import { validateRequirements } from './requirements.js';
7
+ export function acceptBundle(input, artifact) {
8
+ const accepted = acceptBundleValue(artifact);
9
+ verifyBundleBytes(input, dslBundle.encode(accepted));
10
+ validateInventory(input, accepted);
11
+ return accepted;
12
+ }
13
+ export function decodeBundle(input, bytes) {
14
+ const decoded = decodeBundleValue(input, bytes);
15
+ validateInventory(input, decoded);
16
+ return decoded;
17
+ }
18
+ function validateInventory(publication, bundle) {
19
+ const root = bundle.root;
20
+ if (root.origin !== publication.origin || revision(root) !== publication.schema.revision) {
21
+ fail('PUBLICATION_INVALID', 'Bundle root does not match the Publication schema.');
22
+ }
23
+ validateRequirements(root, bundle.closure, publication.requirements);
24
+ const callables = concreteCallables(root);
25
+ const publishedCallables = new Set(publication.bindings.callables.map(({ callable }) => callable));
26
+ if (publishedCallables.size !== callables.size ||
27
+ [...callables].some((key) => !publishedCallables.has(key))) {
28
+ fail('PUBLICATION_BINDING_INVALID', 'Callable bindings must exhaust the root Domain.');
29
+ }
30
+ const views = new Set(Object.keys(root.views).map((name) => Key.of({ origin: root.origin, kind: 'view', name })));
31
+ const publishedViews = new Set(publication.bindings.views.map(({ view }) => view));
32
+ if (publishedViews.size !== views.size || [...views].some((key) => !publishedViews.has(key))) {
33
+ fail('PUBLICATION_VIEW_INVALID', 'View bindings must exhaust the root Domain.');
34
+ }
35
+ if (publication.manifest?.entrypoint !== undefined &&
36
+ (publication.manifest.entrypoint.origin !== root.origin ||
37
+ publication.manifest.entrypoint.kind !== 'view' ||
38
+ root.views[publication.manifest.entrypoint.name] === undefined)) {
39
+ fail('PUBLICATION_INVALID', 'Manifest entrypoint does not exist in the root schema.');
40
+ }
41
+ for (const route of publication.routes) {
42
+ if (!callables.has(route.callable)) {
43
+ fail('PUBLICATION_ROUTE_INVALID', `Route ${route.name} names an unknown callable.`);
44
+ }
45
+ }
46
+ }
47
+ function concreteCallables(root) {
48
+ const result = new Set();
49
+ for (const name of Object.keys(root.functions)) {
50
+ result.add(Key.of({ origin: root.origin, kind: 'function', name }));
51
+ }
52
+ addClassMethods(result, root.origin, root.classes);
53
+ return result;
54
+ }
55
+ function addClassMethods(result, origin, definitions) {
56
+ for (const [owner, declaration] of Object.entries(definitions)) {
57
+ if (declaration.kind !== 'node')
58
+ continue;
59
+ for (const [name, method] of Object.entries(declaration.methods)) {
60
+ if (method.inheritance === 'abstract')
61
+ continue;
62
+ result.add(MethodKey.of({ origin, kind: 'class', name: owner }, name));
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,9 @@
1
+ import * as artifact from '../artifact/index.js';
2
+ export declare const BUNDLE_MEDIA_TYPE: 'application/vnd.astrale.domain-bundle+json;v=1';
3
+ export interface BundleDescriptor {
4
+ readonly href: string;
5
+ readonly ref: artifact.Ref & {
6
+ readonly mediaType: typeof BUNDLE_MEDIA_TYPE;
7
+ };
8
+ }
9
+ export declare function admitBundleDescriptor(input: unknown): BundleDescriptor;
@@ -0,0 +1,19 @@
1
+ import * as artifact from '../artifact/index.js';
2
+ import { absoluteHttpUrl, exact, record } from './content.js';
3
+ import { fail } from './error.js';
4
+ export const BUNDLE_MEDIA_TYPE = 'application/vnd.astrale.domain-bundle+json;v=1';
5
+ export function admitBundleDescriptor(input) {
6
+ const value = record(input, 'PUBLICATION_INVALID', 'Bundle descriptor is invalid.');
7
+ exact(value, ['href', 'ref'], 'PUBLICATION_INVALID');
8
+ const ref = artifact.accept(value.ref);
9
+ if (ref.mediaType !== BUNDLE_MEDIA_TYPE) {
10
+ fail('PUBLICATION_INVALID', 'Bundle media type is unsupported.');
11
+ }
12
+ if (ref.size < 1) {
13
+ fail('PUBLICATION_INVALID', 'Bundle size is invalid.');
14
+ }
15
+ return Object.freeze({
16
+ href: absoluteHttpUrl(value.href, true, 'PUBLICATION_INVALID'),
17
+ ref: Object.freeze({ ...ref, mediaType: BUNDLE_MEDIA_TYPE }),
18
+ });
19
+ }
@@ -0,0 +1,3 @@
1
+ import type { Publication } from './publication.js';
2
+ /** Verify exact artifact bytes without decoding an authored Schema Bundle. */
3
+ export declare function verifyBundleBytes(publication: Publication, bytes: Uint8Array): void;
@@ -0,0 +1,13 @@
1
+ import { fail } from './error.js';
2
+ import { sha256Hex } from './etag.js';
3
+ /** Verify exact artifact bytes without decoding an authored Schema Bundle. */
4
+ export function verifyBundleBytes(publication, bytes) {
5
+ if (!(bytes instanceof Uint8Array)) {
6
+ fail('PUBLICATION_INVALID', 'Bundle artifact bytes are invalid.');
7
+ }
8
+ const descriptor = publication.schema.bundle;
9
+ if (bytes.byteLength !== descriptor.ref.size ||
10
+ `sha256:${sha256Hex(bytes)}` !== descriptor.ref.digest) {
11
+ fail('PUBLICATION_INVALID', 'Bundle artifact does not match its descriptor.');
12
+ }
13
+ }