@astrale-os/kernel-protocol 0.5.0-beta.9 → 1.0.0-beta.1

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 (198) hide show
  1. package/dist/artifact/index.d.ts +1 -1
  2. package/dist/artifact/index.js +1 -1
  3. package/dist/artifact/ref.d.ts +2 -0
  4. package/dist/artifact/ref.js +4 -3
  5. package/dist/codecs/index.d.ts +1 -2
  6. package/dist/codecs/index.js +1 -2
  7. package/dist/codecs/media-types.d.ts +0 -1
  8. package/dist/codecs/media-types.js +0 -1
  9. package/dist/errors/index.d.ts +1 -0
  10. package/dist/errors/index.js +1 -0
  11. package/dist/errors/retry.d.ts +23 -0
  12. package/dist/errors/retry.js +44 -0
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/invocation/admission.d.ts +2 -2
  16. package/dist/invocation/admission.js +5 -5
  17. package/dist/invocation/binary.d.ts +1 -0
  18. package/dist/invocation/binary.js +21 -4
  19. package/dist/invocation/binding-codecs.d.ts +3 -2
  20. package/dist/invocation/binding-codecs.js +1 -6
  21. package/dist/invocation/delegate.d.ts +0 -20
  22. package/dist/invocation/delegate.js +2 -34
  23. package/dist/invocation/http/binary.d.ts +3 -1
  24. package/dist/invocation/http/binary.js +6 -1
  25. package/dist/invocation/http/index.d.ts +2 -1
  26. package/dist/invocation/http/index.js +2 -1
  27. package/dist/invocation/http/negotiation.js +3 -10
  28. package/dist/invocation/http/request.d.ts +11 -0
  29. package/dist/invocation/http/request.js +69 -0
  30. package/dist/invocation/index.d.ts +3 -3
  31. package/dist/invocation/index.js +3 -3
  32. package/dist/invocation/redirect.d.ts +10 -12
  33. package/dist/invocation/redirect.js +10 -22
  34. package/dist/invocation/request.js +2 -2
  35. package/dist/invocation/routing.d.ts +14 -4
  36. package/dist/invocation/routing.js +35 -20
  37. package/dist/invocation/schema.d.ts +6 -4
  38. package/dist/invocation/schema.js +5 -11
  39. package/dist/invocation/service-reason.d.ts +20 -15
  40. package/dist/invocation/service-reason.js +27 -19
  41. package/dist/invocation/stale-route.d.ts +3 -3
  42. package/dist/invocation/target.d.ts +5 -2
  43. package/dist/invocation/target.js +25 -3
  44. package/dist/invocation/websocket/binary.js +1 -0
  45. package/dist/invocation/websocket/index.d.ts +1 -1
  46. package/dist/invocation/websocket/index.js +1 -1
  47. package/dist/invocation/websocket/subprotocol.d.ts +1 -2
  48. package/dist/invocation/websocket/subprotocol.js +3 -4
  49. package/dist/issuer/claims.d.ts +7 -0
  50. package/dist/issuer/claims.js +7 -0
  51. package/dist/issuer/index.d.ts +2 -0
  52. package/dist/issuer/index.js +2 -0
  53. package/dist/issuer/paths.d.ts +2 -0
  54. package/dist/issuer/paths.js +1 -0
  55. package/dist/issuer/workflow-engine.d.ts +18 -0
  56. package/dist/issuer/workflow-engine.js +59 -0
  57. package/dist/{publication → release}/bindings/callable.js +6 -6
  58. package/dist/release/bindings/invocation.js +13 -0
  59. package/dist/release/bindings/view.d.ts +11 -0
  60. package/dist/release/bindings/view.js +54 -0
  61. package/dist/release/build.d.ts +6 -0
  62. package/dist/release/build.js +7 -0
  63. package/dist/release/bundle-admission.d.ts +21 -0
  64. package/dist/{publication → release}/bundle-admission.js +15 -20
  65. package/dist/{publication → release}/bundle-descriptor.js +6 -6
  66. package/dist/release/bundle-evidence.d.ts +3 -0
  67. package/dist/release/bundle-evidence.js +13 -0
  68. package/dist/{publication → release}/bundle.d.ts +5 -2
  69. package/dist/{publication → release}/bundle.js +13 -7
  70. package/dist/{publication → release}/delivery/attestation.d.ts +1 -0
  71. package/dist/{publication → release}/delivery/claims.d.ts +7 -2
  72. package/dist/{publication → release}/delivery/claims.js +20 -13
  73. package/dist/release/delivery/delivery.d.ts +11 -0
  74. package/dist/{publication → release}/delivery/delivery.js +7 -12
  75. package/dist/{publication → release}/delivery/error.d.ts +2 -0
  76. package/dist/{publication → release}/delivery/error.js +1 -0
  77. package/dist/{publication → release}/delivery/index.d.ts +5 -1
  78. package/dist/{publication → release}/delivery/index.js +4 -0
  79. package/dist/{publication → release}/delivery/limits.d.ts +2 -1
  80. package/dist/release/delivery/limits.js +5 -0
  81. package/dist/{publication → release}/delivery/request.d.ts +10 -9
  82. package/dist/{publication → release}/delivery/request.js +9 -10
  83. package/dist/{publication → release}/delivery-declaration.js +4 -4
  84. package/dist/release/delivery-endpoint.js +22 -0
  85. package/dist/release/digest.d.ts +13 -0
  86. package/dist/{publication/etag.js → release/digest.js} +1 -6
  87. package/dist/release/error.d.ts +7 -0
  88. package/dist/release/error.js +11 -0
  89. package/dist/release/index.d.ts +17 -0
  90. package/dist/release/index.js +10 -0
  91. package/dist/release/issuer.d.ts +5 -0
  92. package/dist/release/issuer.js +34 -0
  93. package/dist/release/manifest.d.ts +6 -0
  94. package/dist/release/manifest.js +22 -0
  95. package/dist/release/reference.d.ts +26 -0
  96. package/dist/release/reference.js +70 -0
  97. package/dist/release/release.d.ts +60 -0
  98. package/dist/release/release.js +125 -0
  99. package/dist/release/requirements.d.ts +41 -0
  100. package/dist/release/requirements.js +306 -0
  101. package/dist/{publication → release}/route.js +24 -24
  102. package/dist/{publication → release}/schema.js +4 -4
  103. package/dist/release/served.d.ts +27 -0
  104. package/dist/release/served.js +56 -0
  105. package/dist/release/shape.d.ts +8 -0
  106. package/dist/routes/admission.d.ts +1 -1
  107. package/dist/routes/admission.js +7 -85
  108. package/dist/routes/binding.d.ts +3 -3
  109. package/dist/routes/binding.js +11 -21
  110. package/dist/routes/compiler.d.ts +2 -2
  111. package/dist/routes/compiler.js +7 -3
  112. package/dist/routes/credential.d.ts +12 -2
  113. package/dist/routes/credential.js +9 -4
  114. package/dist/routes/decode.d.ts +5 -2
  115. package/dist/routes/decode.js +24 -17
  116. package/dist/routes/deployment-retired.d.ts +30 -0
  117. package/dist/routes/deployment-retired.js +96 -0
  118. package/dist/routes/encode.js +12 -5
  119. package/dist/routes/entries.d.ts +23 -0
  120. package/dist/routes/entries.js +150 -0
  121. package/dist/routes/evidence.d.ts +3 -3
  122. package/dist/routes/evidence.js +7 -33
  123. package/dist/routes/header.d.ts +9 -0
  124. package/dist/routes/header.js +34 -0
  125. package/dist/routes/index.d.ts +2 -2
  126. package/dist/routes/index.js +2 -2
  127. package/dist/routes/mapping.d.ts +1 -1
  128. package/dist/routes/mapping.js +4 -4
  129. package/dist/routes/match.d.ts +1 -1
  130. package/dist/routes/placement.d.ts +2 -2
  131. package/dist/routes/plan.d.ts +1 -1
  132. package/dist/routes/request.d.ts +3 -1
  133. package/dist/routes/reservation.d.ts +4 -2
  134. package/dist/routes/reservation.js +14 -8
  135. package/dist/routes/routing.d.ts +18 -6
  136. package/dist/routes/routing.js +28 -49
  137. package/dist/routes/select.d.ts +1 -1
  138. package/dist/routes/table.d.ts +3 -2
  139. package/dist/routes/target.d.ts +5 -0
  140. package/dist/routes/target.js +16 -0
  141. package/dist/routes/template.d.ts +0 -1
  142. package/dist/routes/template.js +0 -3
  143. package/dist/runtime/artifact.d.ts +5 -4
  144. package/dist/runtime/artifact.js +7 -4
  145. package/dist/runtime/index.d.ts +1 -1
  146. package/dist/runtime/index.js +1 -1
  147. package/dist/schema/installation/codec.d.ts +4 -0
  148. package/dist/schema/installation/codec.js +70 -0
  149. package/dist/schema/installation/index.d.ts +2 -0
  150. package/dist/schema/installation/index.js +1 -0
  151. package/dist/schema/installation/introspection/codec.d.ts +5 -2
  152. package/dist/schema/installation/introspection/codec.js +307 -195
  153. package/dist/schema/installation/introspection/index.d.ts +2 -2
  154. package/dist/schema/installation/introspection/index.js +1 -1
  155. package/dist/schema/installation/introspection/result.d.ts +72 -25
  156. package/dist/schema/installation/result.d.ts +9 -0
  157. package/dist/schema/installation/result.js +1 -0
  158. package/package.json +21 -75
  159. package/dist/codecs/msgpack.d.ts +0 -4
  160. package/dist/codecs/msgpack.js +0 -50
  161. package/dist/publication/bindings/invocation.js +0 -13
  162. package/dist/publication/bindings/view.d.ts +0 -7
  163. package/dist/publication/bindings/view.js +0 -28
  164. package/dist/publication/bundle-admission.d.ts +0 -4
  165. package/dist/publication/bundle-evidence.d.ts +0 -3
  166. package/dist/publication/bundle-evidence.js +0 -13
  167. package/dist/publication/content.d.ts +0 -10
  168. package/dist/publication/delivery/delivery.d.ts +0 -9
  169. package/dist/publication/delivery/limits.js +0 -4
  170. package/dist/publication/delivery-endpoint.js +0 -22
  171. package/dist/publication/error.d.ts +0 -7
  172. package/dist/publication/error.js +0 -11
  173. package/dist/publication/etag.d.ts +0 -6
  174. package/dist/publication/index.d.ts +0 -17
  175. package/dist/publication/index.js +0 -10
  176. package/dist/publication/manifest.d.ts +0 -6
  177. package/dist/publication/manifest.js +0 -30
  178. package/dist/publication/publication.d.ts +0 -36
  179. package/dist/publication/publication.js +0 -133
  180. package/dist/publication/reference.d.ts +0 -17
  181. package/dist/publication/reference.js +0 -68
  182. package/dist/publication/requirements.d.ts +0 -26
  183. package/dist/publication/requirements.js +0 -180
  184. package/dist/routes/release-unavailable.d.ts +0 -8
  185. package/dist/routes/release-unavailable.js +0 -32
  186. /package/dist/{publication → release}/bindings/callable.d.ts +0 -0
  187. /package/dist/{publication → release}/bindings/index.d.ts +0 -0
  188. /package/dist/{publication → release}/bindings/index.js +0 -0
  189. /package/dist/{publication → release}/bindings/invocation.d.ts +0 -0
  190. /package/dist/{publication → release}/bundle-descriptor.d.ts +0 -0
  191. /package/dist/{publication → release}/declaration.d.ts +0 -0
  192. /package/dist/{publication → release}/declaration.js +0 -0
  193. /package/dist/{publication → release}/delivery/attestation.js +0 -0
  194. /package/dist/{publication → release}/delivery-declaration.d.ts +0 -0
  195. /package/dist/{publication → release}/delivery-endpoint.d.ts +0 -0
  196. /package/dist/{publication → release}/route.d.ts +0 -0
  197. /package/dist/{publication → release}/schema.d.ts +0 -0
  198. /package/dist/{publication/content.js → release/shape.js} +0 -0
@@ -0,0 +1,34 @@
1
+ import * as auth from '@astrale-os/kernel-core/auth';
2
+ import { fail } from './error.js';
3
+ const STRONG_DIGEST = /^"(sha256:[0-9a-f]{64})"$/u;
4
+ /** The digest one header value names as exactly one strong quoted digest, else undefined. */
5
+ export function strongDigest(input) {
6
+ return STRONG_DIGEST.exec(input)?.[1];
7
+ }
8
+ /** Read one reserved header, which occurs at most once in any spelling. */
9
+ export function reservedHeader(input, name) {
10
+ if (input instanceof Headers)
11
+ return input.get(name) ?? undefined;
12
+ let found;
13
+ for (const [candidate, value] of Object.entries(input ?? {})) {
14
+ if (candidate.toLowerCase() !== name || value === undefined)
15
+ continue;
16
+ if (found !== undefined || (Array.isArray(value) && value.length !== 1)) {
17
+ fail('NATIVE_ROUTE_ROUTING_INVALID', `Header ${name} must occur at most once.`);
18
+ }
19
+ found = typeof value === 'string' ? value : value[0];
20
+ }
21
+ return found;
22
+ }
23
+ /** Admit the routing Kernel issuer one native route request names in header `name`. */
24
+ export function acceptIssuerHeader(encoded, name) {
25
+ try {
26
+ return auth.issuer.accept(encoded);
27
+ }
28
+ catch (cause) {
29
+ if (cause instanceof auth.AuthValueError) {
30
+ return fail('NATIVE_ROUTE_ROUTING_INVALID', `${name} is invalid.`, cause);
31
+ }
32
+ throw cause;
33
+ }
34
+ }
@@ -12,8 +12,8 @@ export type { Plan, PlanEntry } from './plan.js';
12
12
  export type { Route, Table } from './table.js';
13
13
  export type { RouteTarget } from './target.js';
14
14
  export { place } from './placement.js';
15
- export { decodeReleaseUnavailable, HTTP_ROUTE_RELEASE_UNAVAILABLE, HTTP_ROUTE_RELEASE_UNAVAILABLE_STATUS, releaseUnavailableHeaders, type ReleaseUnavailable, } from './release-unavailable.js';
16
- export { decodeRouting, decodeMiss, decodeStale, HTTP_ROUTE_PUBLICATION, HTTP_ROUTE_ISSUER, HTTP_ROUTE_MISS, HTTP_ROUTE_MISS_STATUS, HTTP_ROUTE_STALE, HTTP_ROUTE_STALE_STATUS, missHeaders, staleHeaders, type MissControl, type StaleControl, } from './routing.js';
15
+ export { decodeDeploymentRetired, deploymentRetiredBody, deploymentRetiredHeaders, HTTP_DEPLOYMENT_RETIRED, HTTP_DEPLOYMENT_RETIRED_STATUS, type DeploymentRetired, } from './deployment-retired.js';
16
+ export { decodeRouting, decodeMiss, decodeStale, HTTP_ROUTE_DIGEST, HTTP_ROUTE_ISSUER, HTTP_ROUTE_MISS, HTTP_ROUTE_MISS_STATUS, HTTP_ROUTE_STALE, HTTP_ROUTE_STALE_STATUS, missHeaders, staleHeaders, type MissControl, type ServedReferences, type StaleControl, } from './routing.js';
17
17
  export { select } from './select.js';
18
18
  export { decodeStreamFrame, encodeStreamFrame, STREAM_MEDIA_TYPE, type StreamFrame, } from './stream.js';
19
19
  export { templatesOverlap } from './template.js';
@@ -9,8 +9,8 @@ export { RouteError } from './error.js';
9
9
  export { match } from './match.js';
10
10
  export {} from './request.js';
11
11
  export { place } from './placement.js';
12
- export { decodeReleaseUnavailable, HTTP_ROUTE_RELEASE_UNAVAILABLE, HTTP_ROUTE_RELEASE_UNAVAILABLE_STATUS, releaseUnavailableHeaders, } from './release-unavailable.js';
13
- export { decodeRouting, decodeMiss, decodeStale, HTTP_ROUTE_PUBLICATION, HTTP_ROUTE_ISSUER, HTTP_ROUTE_MISS, HTTP_ROUTE_MISS_STATUS, HTTP_ROUTE_STALE, HTTP_ROUTE_STALE_STATUS, missHeaders, staleHeaders, } from './routing.js';
12
+ export { decodeDeploymentRetired, deploymentRetiredBody, deploymentRetiredHeaders, HTTP_DEPLOYMENT_RETIRED, HTTP_DEPLOYMENT_RETIRED_STATUS, } from './deployment-retired.js';
13
+ export { decodeRouting, decodeMiss, decodeStale, HTTP_ROUTE_DIGEST, HTTP_ROUTE_ISSUER, HTTP_ROUTE_MISS, HTTP_ROUTE_MISS_STATUS, HTTP_ROUTE_STALE, HTTP_ROUTE_STALE_STATUS, missHeaders, staleHeaders, } from './routing.js';
14
14
  export { select } from './select.js';
15
15
  export { decodeStreamFrame, encodeStreamFrame, STREAM_MEDIA_TYPE, } from './stream.js';
16
16
  export { templatesOverlap } from './template.js';
@@ -1,4 +1,4 @@
1
- import type { HttpRoute } from '../publication/index.js';
1
+ import type { HttpRoute } from '../release/route.js';
2
2
  export declare function validateMapping(route: HttpRoute, fields: readonly string[] | undefined, instanceMethod: boolean): void;
3
3
  export declare function validateWireMapping(route: HttpRoute): void;
4
4
  export declare function validateCredentialMapping(route: HttpRoute): void;
@@ -1,8 +1,8 @@
1
1
  import { HTTP_IDEMPOTENCY_KEY } from '../invocation/index.js';
2
2
  import { credentialSources } from './credential.js';
3
+ import { HTTP_DEPLOYMENT_RETIRED } from './deployment-retired.js';
3
4
  import { fail } from './error.js';
4
- import { HTTP_ROUTE_RELEASE_UNAVAILABLE } from './release-unavailable.js';
5
- import { HTTP_ROUTE_ISSUER, HTTP_ROUTE_MISS, HTTP_ROUTE_PUBLICATION, HTTP_ROUTE_STALE, } from './routing.js';
5
+ import { HTTP_ROUTE_DIGEST, HTTP_ROUTE_ISSUER, HTTP_ROUTE_MISS, HTTP_ROUTE_STALE, } from './routing.js';
6
6
  import { PLACEHOLDER } from './template.js';
7
7
  export function validateMapping(route, fields, instanceMethod) {
8
8
  if (fields === undefined) {
@@ -110,10 +110,10 @@ export function validateCredentialMapping(route) {
110
110
  }
111
111
  const RESERVED_HEADERS = new Set([
112
112
  HTTP_IDEMPOTENCY_KEY,
113
+ HTTP_DEPLOYMENT_RETIRED,
114
+ HTTP_ROUTE_DIGEST,
113
115
  HTTP_ROUTE_ISSUER,
114
116
  HTTP_ROUTE_MISS,
115
- HTTP_ROUTE_PUBLICATION,
116
- HTTP_ROUTE_RELEASE_UNAVAILABLE,
117
117
  HTTP_ROUTE_STALE,
118
118
  ]);
119
119
  export function isReservedHeader(input) {
@@ -1,4 +1,4 @@
1
1
  import type { HttpRequest } from './request.js';
2
2
  import type { Route, Table } from './table.js';
3
- export declare function match(table: Table, request: Pick<HttpRequest, 'url' | 'method'>): Route | undefined;
3
+ export declare function match(table: Pick<Table, 'entries'>, request: Pick<HttpRequest, 'url' | 'method'>): Route | undefined;
4
4
  export declare function methodMatches(entry: Route, method: string): boolean;
@@ -1,5 +1,5 @@
1
- import type * as publication from '../publication/index.js';
1
+ import type { HttpRoute } from '../release/route.js';
2
2
  import type { Plan } from './plan.js';
3
3
  /** Resolve a provider-neutral Plan beneath one exact serving issuer. */
4
- export declare function place(plan: Plan, issuer: string): readonly publication.HttpRoute[];
4
+ export declare function place(plan: Plan, issuer: string): readonly HttpRoute[];
5
5
  export declare function placePath(path: string, issuer: string): string;
@@ -1,6 +1,6 @@
1
1
  import type { FunctionAuth, Key, Revision } from '@astrale-os/kernel-dsl/v1';
2
2
  import type { OutputMode } from '@astrale-os/kernel-dsl/v1/language';
3
- import type { CredentialSource, HttpMethod, RouteBody, RouteHeaders } from '../publication/index.js';
3
+ import type { CredentialSource, HttpMethod, RouteBody, RouteHeaders } from '../release/route.js';
4
4
  import type { RouteTarget } from './target.js';
5
5
  export interface PlanEntry {
6
6
  readonly name: string;
@@ -1,10 +1,12 @@
1
1
  import type { Value } from '@astrale-os/kernel-dsl/value';
2
2
  import type { Call, Via } from '../invocation/index.js';
3
- import type { HttpMethod } from '../publication/index.js';
3
+ import type { HttpMethod } from '../release/route.js';
4
4
  /** Canonical invocation values presented to or returned from a native route mapping. */
5
5
  export interface Invocation {
6
6
  readonly call: Call;
7
7
  readonly credential?: string;
8
+ /** The request channel that carried `credential`; `cookie` is ambient (CSRF-exposed). */
9
+ readonly credentialIn?: 'header' | 'cookie' | 'query';
8
10
  readonly routing?: Via;
9
11
  readonly method?: Exclude<HttpMethod, '*'>;
10
12
  }
@@ -1,3 +1,5 @@
1
- import type { Publication } from '../publication/publication.js';
1
+ import type { DomainRelease } from '../release/release.js';
2
2
  import type { Route } from './table.js';
3
- export declare function rejectReserved(publication: Publication, entries: readonly Route[]): void;
3
+ /** The canonical Release fields read by route reservation admission. */
4
+ export type Placed = Pick<DomainRelease, 'origin' | 'identity' | 'schema' | 'bindings' | 'routes' | 'delivery'>;
5
+ export declare function rejectReserved(release: Placed, entries: readonly Route[]): void;
@@ -1,21 +1,27 @@
1
1
  import * as issuer from '../issuer/index.js';
2
- import { PATH as PUBLICATION_PATH } from '../publication/publication.js';
2
+ import { bundleUrl } from '../release/bundle.js';
3
+ import { PATH as RELEASE_PATH } from '../release/release.js';
3
4
  import { fail } from './error.js';
4
5
  import { templateUrl, templatesOverlap } from './template.js';
5
- const STATIC_RESERVED_PATHS = new Set([PUBLICATION_PATH]);
6
- export function rejectReserved(publication, entries) {
7
- const discovery = issuer.paths(publication.identity.issuer);
6
+ const STATIC_RESERVED_PATHS = new Set([RELEASE_PATH]);
7
+ export function rejectReserved(release, entries) {
8
+ const discovery = issuer.paths(release.identity.issuer);
8
9
  const reservedPaths = new Set([...STATIC_RESERVED_PATHS, discovery.configuration, discovery.keys]);
9
10
  const reservedUrls = [
10
- publication.schema.bundle.href,
11
- ...(publication.delivery === undefined ? [] : [publication.delivery.href]),
12
- ...publication.bindings.callables.flatMap(({ endpoint }) => [
11
+ release.schema.bundle.href,
12
+ ...(release.delivery === undefined ? [] : [release.delivery.href]),
13
+ ...release.bindings.callables.flatMap(({ endpoint }) => [
13
14
  endpoint.http,
14
15
  ...(endpoint.websocket === undefined ? [] : [httpUrlForWebSocket(endpoint.websocket)]),
15
16
  ]),
16
17
  ];
18
+ const addressed = bundleUrl(release.identity.issuer, release.schema.bundle.ref);
19
+ const bundlePattern = release.schema.bundle.href === addressed
20
+ ? `${addressed.slice(0, addressed.lastIndexOf('/') + 1)}{artifact}`
21
+ : undefined;
17
22
  for (const entry of entries) {
18
- const claimed = [...reservedPaths].some((path) => templateClaimsPath(entry.href, path)) ||
23
+ const claimed = (bundlePattern !== undefined && templatesOverlap(entry.href, bundlePattern)) ||
24
+ [...reservedPaths].some((path) => templateClaimsPath(entry.href, path)) ||
19
25
  reservedUrls.some((reserved) => templatesOverlap(entry.href, routeLocation(reserved)));
20
26
  if (claimed) {
21
27
  fail('NATIVE_ROUTE_RESERVED', `Route ${entry.name} claims reserved URL ${templateUrl(entry.href).href}.`);
@@ -1,22 +1,34 @@
1
1
  import type * as invocation from '../invocation/index.js';
2
- import type { Etag, PublicationRef } from '../publication/index.js';
2
+ import type { Sha256Digest } from '../release/digest.js';
3
+ import type { ReleaseRef } from '../release/reference.js';
3
4
  import type { HttpRequest } from './request.js';
4
5
  export interface StaleControl {
5
6
  readonly kind: 'stale';
6
- readonly current: Etag;
7
+ /** Current release digest. */
8
+ readonly current: Sha256Digest;
7
9
  }
8
10
  export interface MissControl {
9
11
  readonly kind: 'miss';
10
12
  }
11
- export declare const HTTP_ROUTE_PUBLICATION: 'if-match';
13
+ /** The references one release-backed destination answers native routes for. */
14
+ export interface ServedReferences {
15
+ readonly release: ReleaseRef;
16
+ }
17
+ /** If-Match carries the routed digest of a native route request (`invocation.routedDigest`). */
18
+ export declare const HTTP_ROUTE_DIGEST: 'if-match';
12
19
  export declare const HTTP_ROUTE_ISSUER: 'x-astrale-issuer';
13
20
  export declare const HTTP_ROUTE_STALE: 'x-astrale-route-stale';
14
21
  export declare const HTTP_ROUTE_STALE_STATUS: 412;
15
22
  export declare const HTTP_ROUTE_MISS: 'x-astrale-route-miss';
16
23
  export declare const HTTP_ROUTE_MISS_STATUS: 421;
17
- export declare function decodeRouting(input: Pick<HttpRequest, 'headers'>, publication: PublicationRef): invocation.Via | undefined;
18
- export declare function staleHeaders(current: Etag): Readonly<Record<string, string>>;
24
+ /**
25
+ * Decode a native route witness against the served release. A different strong digest is stale
26
+ * before issuer admission; otherwise the digest and issuer must occur together.
27
+ */
28
+ export declare function decodeRouting(input: Pick<HttpRequest, 'headers'>, served: ServedReferences): invocation.Via | StaleControl | undefined;
29
+ /** Emit the reserved stale header naming `current` (see `StaleControl.current`). */
30
+ export declare function staleHeaders(current: Sha256Digest): Readonly<Record<string, string>>;
19
31
  export declare function decodeStale(status: number, headers: HttpRequest['headers']): StaleControl | undefined;
20
32
  export declare function missHeaders(): Readonly<Record<string, string>>;
21
33
  export declare function decodeMiss(status: number, headers: HttpRequest['headers']): MissControl | undefined;
22
- export declare function encodeStrongEtag(input: Etag): string;
34
+ export declare function encodeStrongEtag(input: Sha256Digest): string;
@@ -1,36 +1,35 @@
1
- import * as auth from '@astrale-os/kernel-core/auth';
2
1
  import { fail } from './error.js';
3
- export const HTTP_ROUTE_PUBLICATION = 'if-match';
2
+ import { acceptIssuerHeader, reservedHeader, strongDigest } from './header.js';
3
+ /** If-Match carries the routed digest of a native route request (`invocation.routedDigest`). */
4
+ export const HTTP_ROUTE_DIGEST = 'if-match';
4
5
  export const HTTP_ROUTE_ISSUER = 'x-astrale-issuer';
5
6
  export const HTTP_ROUTE_STALE = 'x-astrale-route-stale';
6
7
  export const HTTP_ROUTE_STALE_STATUS = 412;
7
8
  export const HTTP_ROUTE_MISS = 'x-astrale-route-miss';
8
9
  export const HTTP_ROUTE_MISS_STATUS = 421;
9
- const STRONG_ETAG = /^"(sha256:[0-9a-f]{64})"$/u;
10
- export function decodeRouting(input, publication) {
11
- const encodedEtag = routingHeader(input.headers, HTTP_ROUTE_PUBLICATION);
12
- const encodedIssuer = routingHeader(input.headers, HTTP_ROUTE_ISSUER);
13
- if (encodedEtag === undefined && encodedIssuer === undefined)
10
+ /**
11
+ * Decode a native route witness against the served release. A different strong digest is stale
12
+ * before issuer admission; otherwise the digest and issuer must occur together.
13
+ */
14
+ export function decodeRouting(input, served) {
15
+ const encodedDigest = reservedHeader(input.headers, HTTP_ROUTE_DIGEST);
16
+ const encodedIssuer = reservedHeader(input.headers, HTTP_ROUTE_ISSUER);
17
+ if (encodedDigest === undefined && encodedIssuer === undefined)
14
18
  return undefined;
15
- if (encodedEtag === undefined || encodedIssuer === undefined) {
16
- fail('NATIVE_ROUTE_ROUTING_INVALID', `${HTTP_ROUTE_PUBLICATION} and ${HTTP_ROUTE_ISSUER} must occur together.`);
19
+ const named = encodedDigest === undefined ? undefined : strongDigest(encodedDigest);
20
+ if (named !== undefined && named !== served.release.digest) {
21
+ return Object.freeze({ kind: 'stale', current: served.release.digest });
17
22
  }
18
- const etag = decodeStrongEtag(encodedEtag, 'If-Match');
19
- if (etag !== publication.etag) {
20
- fail('NATIVE_ROUTE_ROUTING_INVALID', 'If-Match does not name the compiled Publication.');
23
+ if (encodedDigest === undefined || encodedIssuer === undefined) {
24
+ fail('NATIVE_ROUTE_ROUTING_INVALID', `${HTTP_ROUTE_DIGEST} and ${HTTP_ROUTE_ISSUER} must occur together.`);
21
25
  }
22
- let issuer;
23
- try {
24
- issuer = auth.issuer.accept(encodedIssuer);
26
+ if (named === undefined) {
27
+ fail('NATIVE_ROUTE_ROUTING_INVALID', 'If-Match must contain exactly one strong quoted digest.');
25
28
  }
26
- catch (cause) {
27
- if (cause instanceof auth.AuthValueError) {
28
- return fail('NATIVE_ROUTE_ROUTING_INVALID', `${HTTP_ROUTE_ISSUER} is invalid.`, cause);
29
- }
30
- throw cause;
31
- }
32
- return Object.freeze({ kind: 'via', publication, issuer });
29
+ const issuer = acceptIssuerHeader(encodedIssuer, HTTP_ROUTE_ISSUER);
30
+ return Object.freeze({ kind: 'via', release: served.release, issuer });
33
31
  }
32
+ /** Emit the reserved stale header naming `current` (see `StaleControl.current`). */
34
33
  export function staleHeaders(current) {
35
34
  return Object.freeze({
36
35
  [HTTP_ROUTE_STALE]: encodeStrongEtag(current),
@@ -38,22 +37,23 @@ export function staleHeaders(current) {
38
37
  });
39
38
  }
40
39
  export function decodeStale(status, headers) {
41
- const encoded = routingHeader(headers, HTTP_ROUTE_STALE);
40
+ const encoded = reservedHeader(headers, HTTP_ROUTE_STALE);
42
41
  if (encoded === undefined)
43
42
  return undefined;
44
43
  if (status !== HTTP_ROUTE_STALE_STATUS) {
45
44
  fail('NATIVE_ROUTE_ROUTING_INVALID', `Header ${HTTP_ROUTE_STALE} is reserved for status ${HTTP_ROUTE_STALE_STATUS}.`);
46
45
  }
47
- return Object.freeze({
48
- kind: 'stale',
49
- current: decodeStrongEtag(encoded, HTTP_ROUTE_STALE),
50
- });
46
+ const current = strongDigest(encoded);
47
+ if (current === undefined) {
48
+ fail('NATIVE_ROUTE_ROUTING_INVALID', `${HTTP_ROUTE_STALE} must contain exactly one strong quoted digest.`);
49
+ }
50
+ return Object.freeze({ kind: 'stale', current });
51
51
  }
52
52
  export function missHeaders() {
53
53
  return Object.freeze({ [HTTP_ROUTE_MISS]: '1', 'cache-control': 'no-store' });
54
54
  }
55
55
  export function decodeMiss(status, headers) {
56
- const encoded = routingHeader(headers, HTTP_ROUTE_MISS);
56
+ const encoded = reservedHeader(headers, HTTP_ROUTE_MISS);
57
57
  if (encoded === undefined)
58
58
  return undefined;
59
59
  if (status !== HTTP_ROUTE_MISS_STATUS || encoded !== '1') {
@@ -64,24 +64,3 @@ export function decodeMiss(status, headers) {
64
64
  export function encodeStrongEtag(input) {
65
65
  return `"${input}"`;
66
66
  }
67
- function decodeStrongEtag(input, label) {
68
- const matched = STRONG_ETAG.exec(input);
69
- if (matched === null) {
70
- fail('NATIVE_ROUTE_ROUTING_INVALID', `${label} must contain exactly one strong quoted Publication ETag.`);
71
- }
72
- return matched[1];
73
- }
74
- function routingHeader(input, name) {
75
- if (input instanceof Headers)
76
- return input.get(name) ?? undefined;
77
- let found;
78
- for (const [candidate, value] of Object.entries(input ?? {})) {
79
- if (candidate.toLowerCase() !== name || value === undefined)
80
- continue;
81
- if (found !== undefined || (Array.isArray(value) && value.length !== 1)) {
82
- fail('NATIVE_ROUTE_ROUTING_INVALID', `Header ${name} must occur at most once.`);
83
- }
84
- found = typeof value === 'string' ? value : value[0];
85
- }
86
- return found;
87
- }
@@ -1,3 +1,3 @@
1
1
  import type { Key } from '@astrale-os/kernel-dsl/v1';
2
2
  import type { Route, Table } from './table.js';
3
- export declare function select(table: Table, callable: Key.Callable, route?: string): Route | undefined;
3
+ export declare function select(table: Pick<Table, 'entries'>, callable: Key.Callable, route?: string): Route | undefined;
@@ -1,9 +1,10 @@
1
- import type { PublicationRef } from '../publication/index.js';
1
+ import type { ReleaseRef } from '../release/reference.js';
2
2
  import type { PlanEntry } from './plan.js';
3
3
  export interface Route extends Omit<PlanEntry, 'path'> {
4
4
  readonly href: string;
5
5
  }
6
+ /** Serving proof tied to one exact release and its resolved Domain. */
6
7
  export interface Table {
7
- readonly publication: PublicationRef;
8
+ readonly release: ReleaseRef;
8
9
  readonly entries: readonly Route[];
9
10
  }
@@ -1,3 +1,6 @@
1
+ import type { Domain } from '@astrale-os/kernel-dsl/v1';
2
+ import type { Key } from '@astrale-os/kernel-dsl/v1/addressing';
3
+ import type { MeaningFingerprint } from '@astrale-os/kernel-dsl/v1/schema/fingerprint';
1
4
  export type RouteTarget = {
2
5
  readonly kind: 'function';
3
6
  } | {
@@ -11,3 +14,5 @@ export type RouteTarget = {
11
14
  export declare function requiresReceiver(entry: {
12
15
  readonly target: RouteTarget;
13
16
  }): boolean;
17
+ /** Fingerprint of the route Domain's callable meaning, which every native route Target pins. */
18
+ export declare function routeFingerprint(domain: Pick<Domain, 'callable'>, callable: Key.Callable): MeaningFingerprint;
@@ -1,3 +1,19 @@
1
+ import { callableFingerprint } from '@astrale-os/kernel-dsl/v1/schema/fingerprint';
2
+ import { fail } from './error.js';
1
3
  export function requiresReceiver(entry) {
2
4
  return entry.target.kind === 'method' && entry.target.dispatch === 'instance';
3
5
  }
6
+ const FINGERPRINTS = new WeakMap();
7
+ /** Fingerprint of the route Domain's callable meaning, which every native route Target pins. */
8
+ export function routeFingerprint(domain, callable) {
9
+ const meaning = domain.callable(callable);
10
+ if (meaning === undefined) {
11
+ fail('NATIVE_ROUTE_BINDING_INVALID', `Route callable ${callable} is absent from its Domain.`);
12
+ }
13
+ let fingerprint = FINGERPRINTS.get(meaning);
14
+ if (fingerprint === undefined) {
15
+ fingerprint = callableFingerprint(meaning);
16
+ FINGERPRINTS.set(meaning, fingerprint);
17
+ }
18
+ return fingerprint;
19
+ }
@@ -1,7 +1,6 @@
1
1
  export declare const PLACEHOLDER: RegExp;
2
2
  export declare function validateTemplate(href: string): void;
3
3
  export declare function templateUrl(href: string): URL;
4
- export declare function routePattern(href: string): string;
5
4
  export declare function templatesOverlap(left: string, right: string): boolean;
6
5
  export declare function matchTemplate(template: string, input: string): Record<string, string> | undefined;
7
6
  export declare function encodeTemplate(value: unknown, catchall: string | undefined, host: boolean): string;
@@ -43,9 +43,6 @@ export function templateUrl(href) {
43
43
  throw cause;
44
44
  }
45
45
  }
46
- export function routePattern(href) {
47
- return href.replaceAll(PLACEHOLDER, (_whole, _name, catchall) => catchall === '+' ? '{+}' : catchall === '*' ? '{*}' : '{}');
48
- }
49
46
  export function templatesOverlap(left, right) {
50
47
  const leftUrl = templateUrl(left);
51
48
  const rightUrl = templateUrl(right);
@@ -1,7 +1,7 @@
1
1
  import type { IssuerId } from '@astrale-os/kernel-core/auth/issuer';
2
2
  import type { Key, Revision } from '@astrale-os/kernel-dsl/v1';
3
3
  import type { Digest, Ref } from '../artifact/index.js';
4
- import type { PublicationRequirements } from '../publication/index.js';
4
+ import type { Requirements } from '../release/requirements.js';
5
5
  export interface ExecutableDependency {
6
6
  readonly path: string;
7
7
  readonly ref: Ref;
@@ -31,13 +31,14 @@ export interface Module {
31
31
  /** Immutable portable closure selected by one local Schema installation source. */
32
32
  export interface RuntimeArtifact {
33
33
  readonly format: 'astrale.domain-runtime-artifact';
34
- readonly version: 2;
34
+ readonly version: 3;
35
35
  readonly issuer: IssuerId;
36
36
  readonly bundle: Ref;
37
- readonly requirements: PublicationRequirements;
37
+ readonly requirements: Requirements;
38
38
  readonly modules: readonly [Module, ...Module[]];
39
39
  }
40
- export declare const MEDIA_TYPE: 'application/vnd.astrale.domain-runtime+json;v=2';
40
+ export declare const MEDIA_TYPE: 'application/vnd.astrale.domain-runtime+json;v=3';
41
+ export declare function mediaType(_input: Pick<RuntimeArtifact, 'version'>): typeof MEDIA_TYPE;
41
42
  export declare function accept(input: unknown): RuntimeArtifact;
42
43
  export declare function encode(input: RuntimeArtifact): Uint8Array;
43
44
  export declare function runtimeDigest(input: RuntimeArtifact): Digest;
@@ -3,12 +3,15 @@ import { patterns } from '@astrale-os/kernel-dsl/v1/addressing';
3
3
  import * as portable from '@astrale-os/kernel-dsl/value';
4
4
  import { sha256 } from '@noble/hashes/sha2.js';
5
5
  import * as artifact from '../artifact/index.js';
6
- import { admitRequirements } from '../publication/index.js';
7
- export const MEDIA_TYPE = 'application/vnd.astrale.domain-runtime+json;v=2';
6
+ import { admitRequirements } from '../release/requirements.js';
7
+ export const MEDIA_TYPE = 'application/vnd.astrale.domain-runtime+json;v=3';
8
+ export function mediaType(_input) {
9
+ return MEDIA_TYPE;
10
+ }
8
11
  export function accept(input) {
9
12
  const root = record(input, 'Domain Runtime artifact');
10
13
  exact(root, ['bundle', 'format', 'issuer', 'modules', 'requirements', 'version']);
11
- if (root.format !== 'astrale.domain-runtime-artifact' || root.version !== 2) {
14
+ if (root.format !== 'astrale.domain-runtime-artifact' || root.version !== 3) {
12
15
  invalid('Domain Runtime artifact format or version is unsupported.');
13
16
  }
14
17
  if (typeof root.issuer !== 'string')
@@ -26,7 +29,7 @@ export function accept(input) {
26
29
  }
27
30
  return Object.freeze({
28
31
  format: 'astrale.domain-runtime-artifact',
29
- version: 2,
32
+ version: root.version,
30
33
  issuer: admittedIssuer,
31
34
  bundle,
32
35
  requirements,
@@ -1 +1 @@
1
- export { MEDIA_TYPE, accept, acceptExecutable, encode, executableClosure, executableDigest, runtimeDigest, type Digest, type Executable, type ExecutableDependency, type Export, type Module, type RuntimeArtifact, type Ref, } from './artifact.js';
1
+ export { MEDIA_TYPE, mediaType, accept, acceptExecutable, encode, executableClosure, executableDigest, runtimeDigest, type Digest, type Executable, type ExecutableDependency, type Export, type Module, type RuntimeArtifact, type Ref, } from './artifact.js';
@@ -1 +1 @@
1
- export { MEDIA_TYPE, accept, acceptExecutable, encode, executableClosure, executableDigest, runtimeDigest, } from './artifact.js';
1
+ export { MEDIA_TYPE, mediaType, accept, acceptExecutable, encode, executableClosure, executableDigest, runtimeDigest, } from './artifact.js';
@@ -0,0 +1,4 @@
1
+ import type { InstallResult } from './result.js';
2
+ export declare const MEDIA_TYPE: 'application/vnd.astrale.schema-installation.v3+json';
3
+ export declare function encode(result: InstallResult): Promise<Uint8Array>;
4
+ export declare function decode(bytes: Uint8Array, maximumBytes: number): Promise<InstallResult>;
@@ -0,0 +1,70 @@
1
+ import { installReceipt } from '@astrale-os/kernel-core/schema/kernel/syscalls/contracts';
2
+ import { json } from '@astrale-os/kernel-dsl/value';
3
+ import { acceptDomainInfo } from './introspection/index.js';
4
+ export const MEDIA_TYPE = 'application/vnd.astrale.schema-installation.v3+json';
5
+ const encoder = new TextEncoder();
6
+ export async function encode(result) {
7
+ return encoder.encode(json.serialize(accept(result)));
8
+ }
9
+ export async function decode(bytes, maximumBytes) {
10
+ if (!(bytes instanceof Uint8Array) ||
11
+ !Number.isSafeInteger(maximumBytes) ||
12
+ maximumBytes < 1 ||
13
+ bytes.byteLength > maximumBytes) {
14
+ throw new TypeError('Schema installation evidence is invalid or exceeds its bound.');
15
+ }
16
+ let value;
17
+ try {
18
+ value = json.decode(bytes, { limits: { maxBytes: maximumBytes } });
19
+ }
20
+ catch (cause) {
21
+ throw new TypeError('Schema installation evidence is malformed.', { cause });
22
+ }
23
+ return accept(value);
24
+ }
25
+ function accept(input) {
26
+ const value = record(input);
27
+ if (value.changed === true) {
28
+ exact(value, ['changed', 'receipt']);
29
+ const receipt = installReceipt.parse(value.receipt);
30
+ if (receipt.transitions.some((transition) => transition.intent.operation !== receipt.operation)) {
31
+ throw new TypeError('Schema installation Transition belongs to another operation.');
32
+ }
33
+ uniqueOrigins(receipt.transitions.map((transition) => transition.intent.origin), 'Schema installation Transitions');
34
+ return Object.freeze({
35
+ changed: true,
36
+ receipt,
37
+ });
38
+ }
39
+ if (value.changed === false) {
40
+ exact(value, ['changed', 'domains']);
41
+ if (!Array.isArray(value.domains) || value.domains.length === 0) {
42
+ throw new TypeError('Schema installation unchanged Domains are empty.');
43
+ }
44
+ const domains = value.domains.map(acceptDomainInfo);
45
+ uniqueOrigins(domains.map((domain) => domain.origin), 'Schema installation unchanged Domains');
46
+ return Object.freeze({
47
+ changed: false,
48
+ domains: Object.freeze(domains),
49
+ });
50
+ }
51
+ throw new TypeError('Schema installation result kind is invalid.');
52
+ }
53
+ function uniqueOrigins(origins, label) {
54
+ if (new Set(origins).size !== origins.length) {
55
+ throw new TypeError(`${label} contain duplicate origins.`);
56
+ }
57
+ }
58
+ function record(input) {
59
+ if (input === null || typeof input !== 'object' || Array.isArray(input)) {
60
+ throw new TypeError('Schema installation result is invalid.');
61
+ }
62
+ return input;
63
+ }
64
+ function exact(input, expected) {
65
+ const keys = Object.keys(input).sort();
66
+ const wanted = [...expected].sort();
67
+ if (keys.length !== wanted.length || keys.some((key, index) => key !== wanted[index])) {
68
+ throw new TypeError('Schema installation result fields are invalid.');
69
+ }
70
+ }
@@ -1 +1,3 @@
1
+ export { MEDIA_TYPE, decode, encode } from './codec.js';
1
2
  export * as introspection from './introspection/index.js';
3
+ export type { InstallResult } from './result.js';
@@ -1 +1,2 @@
1
+ export { MEDIA_TYPE, decode, encode } from './codec.js';
1
2
  export * as introspection from './introspection/index.js';
@@ -1,5 +1,8 @@
1
1
  import type { introspection } from '@astrale-os/kernel-core/schema';
2
- import type { Limits, ResultFor } from './result.js';
3
- export declare const MEDIA_TYPE: 'application/vnd.astrale.schema-introspection.v1+msgpack';
2
+ import type { DomainDependencies, DomainInfo, Limits, ResultFor } from './result.js';
3
+ export declare const MEDIA_TYPE: 'application/vnd.astrale.schema-introspection.v4+json';
4
4
  export declare function encode<Input extends introspection.Introspection>(input: Input, result: ResultFor<Input>): Promise<Uint8Array>;
5
5
  export declare function decode<Input extends introspection.Introspection>(input: Input, bytes: Uint8Array, limits: Limits): Promise<ResultFor<Input>>;
6
+ /** Admit dependency bindings: origin-ordered, one per origin, each label agreeing with its revisions. */
7
+ export declare function acceptDomainDependencies(input: unknown): DomainDependencies;
8
+ export declare function acceptDomainInfo(input: unknown): DomainInfo;