@nylorun/runtime 0.19.1-beta → 0.20.0-beta

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 (61) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +9 -5
  3. package/dist/adapters/streams/s2.js +5 -11
  4. package/dist/api/components.d.ts +171 -169
  5. package/dist/api/http/define.d.ts +3 -2
  6. package/dist/api/http/define.js +10 -11
  7. package/dist/api/openapi.js +31 -3
  8. package/dist/execution/types.d.ts +1 -1
  9. package/dist/host/app.d.ts +3 -0
  10. package/dist/host/app.js +33 -4
  11. package/dist/host/create-host.d.ts +6 -0
  12. package/dist/host/create-host.js +1 -0
  13. package/dist/host/main.js +11 -4
  14. package/dist/host/stack-config.js +1 -1
  15. package/dist/infra/index.d.ts +2 -1
  16. package/dist/infra/index.js +0 -1
  17. package/dist/infra/readiness.d.ts +6 -6
  18. package/dist/infra/readiness.js +2 -5
  19. package/dist/management-openapi.json +430 -107
  20. package/dist/openapi.json +366 -227
  21. package/dist/store/postgres/connect.d.ts +10 -0
  22. package/dist/store/postgres/connect.js +30 -3
  23. package/dist/store/postgres/control.d.ts +14 -0
  24. package/dist/store/postgres/control.js +187 -0
  25. package/dist/store/postgres/drizzle/0015_control_signals.sql +11 -0
  26. package/dist/store/postgres/drizzle/meta/_journal.json +7 -0
  27. package/dist/store/postgres/schema.d.ts +120 -0
  28. package/dist/store/postgres/schema.js +20 -0
  29. package/dist/store/postgres/store.js +15 -0
  30. package/dist/store/types.d.ts +40 -0
  31. package/dist/streams/types.d.ts +6 -29
  32. package/dist/streams/types.js +5 -4
  33. package/dist/tenant/auth.d.ts +16 -6
  34. package/dist/tenant/auth.js +41 -17
  35. package/dist/tenant/commands.js +10 -6
  36. package/dist/tenant/context.d.ts +4 -0
  37. package/dist/tenant/control.d.ts +26 -0
  38. package/dist/tenant/control.js +35 -0
  39. package/dist/tenant/ephemeral.js +1 -0
  40. package/dist/tenant/http.d.ts +4 -2
  41. package/dist/tenant/http.js +4 -2
  42. package/dist/tenant/identity-file.d.ts +16 -12
  43. package/dist/tenant/identity-file.js +41 -29
  44. package/dist/tenant/issuers.d.ts +4 -3
  45. package/dist/tenant/issuers.js +22 -16
  46. package/dist/tenant/reset.d.ts +2 -2
  47. package/dist/tenant/reset.js +8 -2
  48. package/dist/tenant/resource-server.d.ts +56 -0
  49. package/dist/tenant/resource-server.js +62 -0
  50. package/dist/tenant/runtime.d.ts +1 -1
  51. package/dist/tenant/runtime.js +7 -1
  52. package/dist/tenant/scheduler.d.ts +1 -1
  53. package/dist/tenant/scheduler.js +1 -1
  54. package/dist/tenant/streams.d.ts +16 -22
  55. package/dist/tenant/streams.js +7 -87
  56. package/dist/tenant/sweep.js +9 -1
  57. package/dist/version.d.ts +1 -1
  58. package/dist/version.js +1 -1
  59. package/package.json +3 -3
  60. package/dist/streams/control.d.ts +0 -13
  61. package/dist/streams/control.js +0 -25
@@ -27,7 +27,8 @@ export interface RouteAccess {
27
27
  /** The subject scopes that reach the route: any one of them, none (`never`), or all (`any`). */
28
28
  readonly scopes: readonly SubjectScope[] | "never" | "any";
29
29
  /**
30
- * Public data: a request with no credential at all (no `Authorization`) is served too. A credential that is sent is still checked, so a wrong one stays the opaque 404.
30
+ * Public data: a request with no credential at all (no `Authorization`) is served too. A
31
+ * credential that is sent is still checked, so a wrong one is `401 credential_invalid`.
31
32
  */
32
33
  readonly anonymous?: boolean;
33
34
  /**
@@ -49,7 +50,7 @@ export declare function authenticateCaller(c: Context<TenantEnv>,
49
50
  anonymous?: boolean,
50
51
  /** The keys the route takes; a Runtime API route's by default. */
51
52
  keys?: KeyAccess): Promise<AuthScope>;
52
- /** No route: authenticated first, so an unknown credential stays the opaque 404. */
53
+ /** No route: authenticated first, so only a known credential learns there is no such route. */
53
54
  export declare function routeNotFound(c: Context<TenantEnv>): Promise<Response>;
54
55
  /** Which keys a route's credentials take. */
55
56
  export declare function keyAccess(access: RouteAccess): KeyAccess;
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { PROTOCOL_VERSION } from "@nylorun/core/compatibility";
3
- import { authenticate, requireScopes } from "../../tenant/auth.js";
3
+ import { authenticate, challengeMetadata, requireScopes, } from "../../tenant/auth.js";
4
4
  import { fail } from "../../tenant/http.js";
5
5
  import { ProtocolRejected, Rejected } from "../components.js";
6
6
  const SCHEMES = {
@@ -38,7 +38,7 @@ keys) {
38
38
  return { kind: "anonymous" };
39
39
  return await authenticate(tenant, incoming, keys);
40
40
  }
41
- /** No route: authenticated first, so an unknown credential stays the opaque 404. */
41
+ /** No route: authenticated first, so only a known credential learns there is no such route. */
42
42
  export async function routeNotFound(c) {
43
43
  // Any known key, of either API, learns only that there is no such route.
44
44
  await authenticateCaller(c, false, { application: true, management: true });
@@ -64,8 +64,9 @@ export function declaredRoute(method, segments) {
64
64
  }
65
65
  function authenticated(access) {
66
66
  return async (c, next) => {
67
- const scope = await authenticateCaller(c, access.anonymous === true, keyAccess(access));
68
- requireScopes(scope, access.scopes);
67
+ const keys = keyAccess(access);
68
+ const scope = await authenticateCaller(c, access.anonymous === true, keys);
69
+ requireScopes(scope, access.scopes, challengeMetadata(c.env.tenant, c.env.incoming, keys));
69
70
  c.set("scope", scope);
70
71
  await next();
71
72
  };
@@ -99,15 +100,13 @@ export function tenantRoute(api, access, route, handler) {
99
100
  request: { ...route.request, headers },
100
101
  responses: {
101
102
  400: rejected("Invalid headers, path, body or cursor"),
102
- ...(takes("token")
103
- ? {
104
- 401: rejected("The token expired (`token_expired`), or its issuer's keys cannot be fetched now (`issuer_unavailable`)"),
105
- }
106
- : {}),
103
+ 401: rejected(takes("token")
104
+ ? "No credential (`credential_required`), one the Tenant does not accept (`credential_invalid`), a token that expired (`token_expired`), or a token whose issuer's keys cannot be fetched now (`issuer_unavailable`). `WWW-Authenticate: Bearer` names the protected resource metadata (`resource_metadata`)"
105
+ : "No credential (`credential_required`), or one the Tenant does not accept (`credential_invalid`), with a `WWW-Authenticate: Bearer` challenge"),
107
106
  403: rejected(takes("management")
108
107
  ? "Not allowed for this credential, a key of the other API (`key_role_mismatch`), a key from a browser (`origin_rejected`) or a management key acting for a subject"
109
- : "Not allowed for this credential or subject scope, a key of the other API (`key_role_mismatch`), or an application key from a browser (`origin_rejected`)"),
110
- 404: rejected("Not found, or a credential the Tenant does not know (`not_found`)"),
108
+ : "Not allowed for this credential or subject scope (`scope_required`; a token also gets `WWW-Authenticate: Bearer error=\"insufficient_scope\"` and the scope), a key of the other API (`key_role_mismatch`), or an application key from a browser (`origin_rejected`)"),
109
+ 404: rejected("Not found (`not_found`)"),
111
110
  426: { description: "`Nylorun-Protocol` missing or unsupported", content: { "application/json": { schema: ProtocolRejected } } },
112
111
  503: rejected("The Tenant's storage or streams are unavailable"),
113
112
  ...route.responses,
@@ -12,6 +12,7 @@
12
12
  import { OpenAPIRegistry, OpenApiGeneratorV32, } from "@asteasolutions/zod-to-openapi";
13
13
  import { HOST_PROTOCOL } from "@nylorun/core/compatibility";
14
14
  import { HealthResponseSchema, ReadyResponseSchema } from "@nylorun/core/contracts";
15
+ import { z } from "zod";
15
16
  import { RUNTIME_VERSION } from "../version.js";
16
17
  import { tenantApi } from "./http/app.js";
17
18
  const PROTOCOL = {
@@ -25,9 +26,21 @@ const bearer = (description, bearerFormat) => ({
25
26
  ...(bearerFormat ? { bearerFormat } : {}),
26
27
  description,
27
28
  });
29
+ /** RFC 9728 protected resource metadata, as `tenant/resource-server.ts` builds it. */
30
+ const ProtectedResourceMetadataSchema = z
31
+ .object({
32
+ resource: z.string().meta({ description: "The Runtime API: `NYLORUN_PUBLIC_URL`, else the origin the request reached" }),
33
+ authorization_servers: z
34
+ .array(z.string())
35
+ .meta({ description: "The identity file's issuers, in its order: a client usually signs in at the first" }),
36
+ scopes_supported: z.array(z.string()).meta({ description: "The scopes the issuers' tokens may carry, `studio` aside" }),
37
+ bearer_methods_supported: z.array(z.literal("header")),
38
+ resource_name: z.string(),
39
+ })
40
+ .meta({ id: "ProtectedResourceMetadata" });
28
41
  /**
29
- * `/health`, `/ready` and the documents: what every listener answers without a key. The
30
- * `/openapi.json` alias is served but left out of the documents.
42
+ * `/health`, `/ready`, the documents and the OAuth metadata: what every listener answers
43
+ * without a key. The `/openapi.json` alias is served but left out of the documents.
31
44
  */
32
45
  function hostRoutes() {
33
46
  const registry = new OpenAPIRegistry();
@@ -59,6 +72,20 @@ function hostRoutes() {
59
72
  503: { description: "Not ready: `checks` says what is not" },
60
73
  },
61
74
  });
75
+ route({
76
+ method: "get",
77
+ path: "/.well-known/oauth-protected-resource",
78
+ tags: ["Host"],
79
+ summary: "Get the protected resource metadata",
80
+ description: "OAuth 2.0 Protected Resource Metadata (RFC 9728): the identity providers whose access tokens the Runtime API accepts (the identity file's trusted issuers) and their scopes. Nylorun is never the authorization server: a client signs in at one of `authorization_servers` and presents its token as `Authorization: Bearer`. A `401` from the Runtime API names this document in `WWW-Authenticate` (`resource_metadata`). Needs no key or `Nylorun-Protocol`, and accepts an `Origin`.",
81
+ responses: {
82
+ 200: {
83
+ description: "The metadata",
84
+ content: { "application/json": { schema: ProtectedResourceMetadataSchema } },
85
+ },
86
+ 404: { description: "The Runtime trusts no identity provider (no identity file)" },
87
+ },
88
+ });
62
89
  route({
63
90
  method: "get",
64
91
  path: "/openapi/runtime.json",
@@ -164,11 +191,12 @@ const RUNTIME_TAGS = [
164
191
  {
165
192
  name: "Service",
166
193
  group: "Service",
167
- description: "Check a Runtime is up and speaks your protocol, ask who your credential is, and read the public keys of the tokens it signs. `/health`, `/ready`, the JWKS and the documents need no key.",
194
+ description: "Check a Runtime is up and speaks your protocol, ask who your credential is, find the identity providers it takes tokens from, and read the public keys of the tokens it signs. `/health`, `/ready`, the protected resource metadata, the JWKS and the documents need no key.",
168
195
  operations: [
169
196
  "GET /health",
170
197
  "GET /ready",
171
198
  "GET /v1/me",
199
+ "GET /.well-known/oauth-protected-resource",
172
200
  "GET /v1/access/jwks",
173
201
  "GET /openapi/runtime.json",
174
202
  ],
@@ -91,7 +91,7 @@ export interface SandboxResult {
91
91
  export interface WorkerHandlers {
92
92
  /**
93
93
  * Advances one session (§10.5). `signal` aborts when the Worker stops or the
94
- * session is cancelled through the control stream. Throw only on
94
+ * session is cancelled through the control bus. Throw only on
95
95
  * infrastructure errors.
96
96
  */
97
97
  advance(tenantId: string, sessionId: string, signal: AbortSignal): Promise<AdvanceResult>;
@@ -1,5 +1,6 @@
1
1
  import { Hono } from "hono";
2
2
  import type { Logger, NodeBindings, TenantModule } from "../tenant/types.js";
3
+ import { type ResourceServerConfig } from "../tenant/resource-server.js";
3
4
  export type HostEnv = {
4
5
  Bindings: NodeBindings;
5
6
  Variables: {
@@ -19,6 +20,8 @@ export interface HostAppOptions {
19
20
  ok: boolean;
20
21
  checks: Record<string, boolean>;
21
22
  }>;
23
+ /** The public URL and trusted issuers the OAuth metadata and challenges name. */
24
+ resourceServer?: ResourceServerConfig;
22
25
  /** The listener is listening. */
23
26
  listening(): boolean;
24
27
  closing(): boolean;
package/dist/host/app.js CHANGED
@@ -3,10 +3,13 @@ import { RESPONSE_ALREADY_SENT } from "@hono/node-server/utils/response";
3
3
  import { checkCompatibility, HOST_PROTOCOL, PROTOCOL_HEADER, TENANT_HEADER, } from "@nylorun/core/compatibility";
4
4
  import { RUNTIME_VERSION } from "../version.js";
5
5
  import { findTenantRoute } from "../api/http/app.js";
6
+ import { keyAccess } from "../api/http/define.js";
7
+ import { bearerChallenge, PROTECTED_RESOURCE_PATH, protectedResourceMetadata, protectedResourceMetadataUrl, } from "../tenant/resource-server.js";
6
8
  import { managementDocument, runtimeDocument } from "../api/openapi.js";
7
9
  import { headerValue, isJsonContentType, jsonResponse, opaqueNotFoundResponse, protocolRejectedResponse, pathnameIsLogged, redactRoutePath, rejectedResponse, requestHasBody, } from "./http.js";
8
10
  export function createHostApp(options) {
9
11
  const { module, logger } = options;
12
+ const resourceServer = options.resourceServer ?? {};
10
13
  const app = new Hono();
11
14
  app.use(async (c, next) => {
12
15
  const started = Date.now();
@@ -28,9 +31,10 @@ export function createHostApp(options) {
28
31
  app.use(async (c, next) => {
29
32
  const { incoming } = c.env;
30
33
  if (headerValue(incoming, "origin") !== undefined) {
31
- // Only Tenant routes; the Tenant then accepts only a trusted issuer's token from a
32
- // browser.
33
- if (pathnameOf(incoming).split("/").filter(Boolean)[0] !== "v1")
34
+ // Only Tenant routes, where the Tenant then accepts only a trusted issuer's token from a
35
+ // browser, and the public OAuth metadata a browser client reads before it signs in.
36
+ if (pathnameOf(incoming).split("/").filter(Boolean)[0] !== "v1" &&
37
+ !isProtectedResourcePath(pathnameOf(incoming)))
34
38
  return rejectedResponse(403, "origin_rejected", "Browser Origin headers are not accepted");
35
39
  }
36
40
  // CORS is the operator's proxy's: an OPTIONS request gets no CORS header.
@@ -83,13 +87,35 @@ export function createHostApp(options) {
83
87
  return jsonResponse(200, runtimeDocument(), { "cache-control": "no-cache" });
84
88
  if (pathname === "/openapi/management.json" && incoming.method === "GET")
85
89
  return jsonResponse(200, managementDocument(), { "cache-control": "no-cache" });
90
+ // RFC 9728: where the Runtime API's tokens come from. None without trusted issuers.
91
+ if (isProtectedResourcePath(pathname) && incoming.method === "GET") {
92
+ const metadata = protectedResourceMetadata(resourceServer, incoming);
93
+ return metadata
94
+ ? jsonResponse(200, metadata, { "cache-control": "no-cache" })
95
+ : rejectedResponse(404, "not_found", "This Runtime trusts no identity provider");
96
+ }
86
97
  // Tenant routes: protocol → the Tenant → selection → the Tenant's routes.
87
98
  const tenantHeader = headerValue(incoming, TENANT_HEADER);
88
99
  const named = tenantHeader === undefined || tenantHeader.trim() === "" ? undefined : tenantHeader.trim();
89
100
  const protocol = headerValue(incoming, PROTOCOL_HEADER);
101
+ // A generic OAuth client sends neither header: answer it the challenge, not the 426.
102
+ const route = tenantRouteOf(incoming);
103
+ if (protocol === undefined &&
104
+ headerValue(incoming, "authorization") === undefined &&
105
+ route !== undefined &&
106
+ route.anonymous !== true) {
107
+ const metadata = keyAccess(route).application
108
+ ? protectedResourceMetadataUrl(resourceServer, incoming)
109
+ : undefined;
110
+ return jsonResponse(401, {
111
+ status: "rejected",
112
+ code: "credential_required",
113
+ message: "A bearer credential is required",
114
+ }, { "www-authenticate": bearerChallenge({ resourceMetadata: metadata }) });
115
+ }
90
116
  // A capability link is opened without the header; when one is sent, it is checked.
91
117
  if (!protocolAccepted(protocol) &&
92
- !(protocol === undefined && tenantRouteOf(incoming)?.unversioned === true))
118
+ !(protocol === undefined && route?.unversioned === true))
93
119
  return protocolRejectedResponse();
94
120
  const resolution = await module.resolve();
95
121
  if (resolution.kind !== "open") {
@@ -138,6 +164,9 @@ export function createHostApp(options) {
138
164
  }
139
165
  /** The methods the Runtime serves, for an `OPTIONS` answer's `Allow`. */
140
166
  const ALLOWED_METHODS = "GET, POST, PUT, DELETE, OPTIONS";
167
+ function isProtectedResourcePath(pathname) {
168
+ return pathname === PROTECTED_RESOURCE_PATH || pathname.startsWith(`${PROTECTED_RESOURCE_PATH}/`);
169
+ }
141
170
  /** The Tenant route a request names, if any; a malformed path names none. */
142
171
  function tenantRouteOf(incoming) {
143
172
  const segments = [];
@@ -1,3 +1,4 @@
1
+ import type { ResourceServerConfig } from "../tenant/resource-server.js";
1
2
  import type { Logger, TenantModule } from "../tenant/types.js";
2
3
  import type { HostConfigFile } from "./config.js";
3
4
  import type { ContainerListen } from "./stack-config.js";
@@ -25,6 +26,11 @@ export interface CreateHostOptions {
25
26
  ok: boolean;
26
27
  checks: Record<string, boolean>;
27
28
  }>;
29
+ /**
30
+ * The public URL and trusted issuers behind `/.well-known/oauth-protected-resource` and the
31
+ * challenge a request without credential or protocol gets (protocol 9). Absent: no metadata.
32
+ */
33
+ resourceServer?: ResourceServerConfig;
28
34
  /**
29
35
  * Shutdown steps around closing the Tenant. `close()` runs them whatever asked for it
30
36
  * (SIGTERM in `host/main.ts`): the listener stops, then
@@ -20,6 +20,7 @@ export function createHost(options) {
20
20
  coreVersion,
21
21
  pid,
22
22
  ...(options.readiness ? { readiness: options.readiness } : {}),
23
+ ...(options.resourceServer ? { resourceServer: options.resourceServer } : {}),
23
24
  listening: () => Boolean(server?.listening),
24
25
  closing: () => closing,
25
26
  });
package/dist/host/main.js CHANGED
@@ -71,9 +71,10 @@ function loadJson(path) {
71
71
  /**
72
72
  * The trusted issuers of the identity file (`NYLORUN_IDENTITY_FILE`, Host feature
73
73
  * `trusted-issuers`). A missing or malformed file stops the boot, naming the issuer and field;
74
- * an issuer's JWKS is fetched only when a token needs it, so one that is down does not.
74
+ * a key the file does not define is logged and ignored. An issuer's JWKS is fetched only when a
75
+ * token needs it, so one that is down does not stop the boot.
75
76
  */
76
- function loadIssuers(path) {
77
+ function loadIssuers(path, logger) {
77
78
  let text;
78
79
  try {
79
80
  text = readFileSync(path, "utf8");
@@ -81,7 +82,9 @@ function loadIssuers(path) {
81
82
  catch (error) {
82
83
  throw new Error(`NYLORUN_IDENTITY_FILE names ${path}, which cannot be read: ${error instanceof Error ? error.message : String(error)}`);
83
84
  }
84
- return createTrustedIssuers(parseIdentityFile(text, path));
85
+ return createTrustedIssuers(parseIdentityFile(text, path, {
86
+ warn: (message) => logger.warn("identity_file_key_ignored", { message }),
87
+ }));
85
88
  }
86
89
  /** The bootstrap management key: 64 hex characters, surrounding whitespace ignored. */
87
90
  function loadManagementKey(path) {
@@ -224,8 +227,8 @@ export async function main() {
224
227
  const config = loadJson(paths.config);
225
228
  const credentials = loadJson(paths.credentials);
226
229
  const identityFile = stack.tenant?.identityFile;
227
- const issuers = identityFile ? loadIssuers(identityFile) : undefined;
228
230
  const logger = createHostLogger();
231
+ const issuers = identityFile ? loadIssuers(identityFile, logger) : undefined;
229
232
  if (issuers)
230
233
  logger.info("trusted_issuers", {
231
234
  file: identityFile,
@@ -362,6 +365,10 @@ export async function main() {
362
365
  coreVersion: coreVersion(),
363
366
  ...(stack.listen ? { listen: stack.listen } : {}),
364
367
  ...(infra.readiness ? { readiness: infra.readiness } : {}),
368
+ resourceServer: {
369
+ ...(issuers ? { issuers } : {}),
370
+ ...(stack.publicUrl ? { publicUrl: stack.publicUrl } : {}),
371
+ },
365
372
  // SIGTERM closes the Host this way: stop the Worker and the relay, close the Tenant, then end the infrastructure clients.
366
373
  shutdown: {
367
374
  beforeTenants: async () => {
@@ -66,7 +66,7 @@ export const DEFAULT_SERVICES = new Set([
66
66
  ]);
67
67
  /** Services of the blueprint this release doesn't have yet. */
68
68
  const LATER_SERVICES = [
69
- "sandboxd",
69
+ "sandboxes",
70
70
  ];
71
71
  const ROLE_SERVICES = {
72
72
  api: ["core"],
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * The Host's infrastructure clients, built from the stack configuration: the
3
3
  * Postgres pool, Durable Session Execution and Durable Streams, each only when
4
- * its endpoints are configured, plus the readiness that covers them.
4
+ * its endpoints are configured, plus the readiness that covers Postgres and
5
+ * Restate (not S2: `readiness.ts`).
5
6
  *
6
7
  * Building a client connects to nothing: the pool connects on first query,
7
8
  * Restate and S2 on first call. Nothing here starts a Worker (see
@@ -20,7 +20,6 @@ export function createInfra(config, options = {}) {
20
20
  const probes = infraProbes({
21
21
  ...(database ? { database } : {}),
22
22
  ...(execution ? { execution } : {}),
23
- ...(streams ? { streams } : {}),
24
23
  });
25
24
  const readiness = Object.keys(probes).length > 0
26
25
  ? logTransitions(createReadiness(probes), options.logger)
@@ -1,20 +1,21 @@
1
1
  /**
2
2
  * Aggregate readiness of the Host's infrastructure (architecture §13, §14.3):
3
- * the Runtime's `/ready` covers Postgres, Restate and S2. s2-lite's image has
4
- * no health check of its own, so this is the only place its health shows.
3
+ * the Runtime's `/ready` covers Postgres and Restate. S2 is left out (D48): it only
4
+ * serves API listeners (history, SSE, AG-UI, A2A), so an outage degrades those reads
5
+ * and never makes the Runtime unready. Its health shows in the Tenant's status
6
+ * (`GET /v1/tenant`, `streams.reachable`).
5
7
  *
6
8
  * Every probe runs on each call, in parallel, bounded by one timeout. A probe
7
9
  * that throws or outlives the timeout marks its check `false`. Only check
8
10
  * names and booleans are reported; the errors stay in the report for logs.
9
11
  */
10
12
  import type { DurableExecution } from "../execution/types.js";
11
- import type { DurableStreams } from "../streams/types.js";
12
13
  import { type PostgresClient } from "./database.js";
13
14
  /** Resolves when the dependency answers; rejects otherwise. Must honour `signal`. */
14
15
  export type Probe = (signal: AbortSignal) => Promise<void>;
15
16
  export interface ReadinessReport {
16
17
  ok: boolean;
17
- /** One entry per configured dependency, e.g. `{ postgres, restate, s2 }`. */
18
+ /** One entry per configured dependency, e.g. `{ postgres, restate }`. */
18
19
  checks: Record<string, boolean>;
19
20
  /** Why each failing check failed. Not for unauthenticated responses. */
20
21
  errors: Record<string, string>;
@@ -25,9 +26,8 @@ export declare const READINESS_TIMEOUT_MS = 2000;
25
26
  export declare function createReadiness(probes: Readonly<Record<string, Probe>>, options?: {
26
27
  timeoutMs?: number;
27
28
  }): Readiness;
28
- /** The standard checks for whichever infrastructure the Host was built with. */
29
+ /** The standard checks for whichever infrastructure the Host was built with. Never S2 (D48). */
29
30
  export declare function infraProbes(infra: {
30
31
  database?: PostgresClient;
31
32
  execution?: DurableExecution;
32
- streams?: DurableStreams;
33
33
  }): Record<string, Probe>;
@@ -1,6 +1,5 @@
1
1
  import { probeDatabase } from "./database.js";
2
2
  import { probeExecution } from "./execution.js";
3
- import { probeStreams } from "./streams.js";
4
3
  /** Default bound on one readiness pass. Compose probes `/ready` every 2 s with a 5 s timeout. */
5
4
  export const READINESS_TIMEOUT_MS = 2000;
6
5
  export function createReadiness(probes, options = {}) {
@@ -18,16 +17,14 @@ export function createReadiness(probes, options = {}) {
18
17
  return { ok: Object.values(checks).every(Boolean), checks, errors };
19
18
  };
20
19
  }
21
- /** The standard checks for whichever infrastructure the Host was built with. */
20
+ /** The standard checks for whichever infrastructure the Host was built with. Never S2 (D48). */
22
21
  export function infraProbes(infra) {
23
22
  const probes = {};
24
- const { database, execution, streams } = infra;
23
+ const { database, execution } = infra;
25
24
  if (database)
26
25
  probes.postgres = (signal) => probeDatabase(database, signal);
27
26
  if (execution)
28
27
  probes.restate = (signal) => probeExecution(execution, signal);
29
- if (streams)
30
- probes.s2 = (signal) => probeStreams(streams, signal);
31
28
  return probes;
32
29
  }
33
30
  /** Runs one probe under a timeout; returns its failure message, or undefined when ready. */