@nylorun/runtime 0.13.0-beta → 0.15.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 (309) hide show
  1. package/CHANGELOG.md +434 -0
  2. package/README.md +137 -41
  3. package/dist/adapters/execution/restate.d.ts +7 -2
  4. package/dist/adapters/execution/restate.js +40 -10
  5. package/dist/adapters/replication/pgoutput.d.ts +21 -0
  6. package/dist/adapters/replication/pgoutput.js +170 -0
  7. package/dist/adapters/streams/s2.js +37 -20
  8. package/dist/admin-openapi.json +570 -0
  9. package/dist/api/a2a/card.d.ts +15 -0
  10. package/dist/api/a2a/card.js +24 -0
  11. package/dist/api/a2a/endpoint.d.ts +8 -0
  12. package/dist/api/a2a/endpoint.js +81 -0
  13. package/dist/api/a2a/protocol.d.ts +144 -0
  14. package/dist/api/a2a/protocol.js +188 -0
  15. package/dist/api/a2a/routes.d.ts +19 -0
  16. package/dist/api/a2a/routes.js +289 -0
  17. package/dist/api/a2a/tasks.d.ts +50 -0
  18. package/dist/api/a2a/tasks.js +186 -0
  19. package/dist/api/ag-ui/endpoint.d.ts +8 -0
  20. package/dist/api/ag-ui/endpoint.js +126 -0
  21. package/dist/api/ag-ui/history.d.ts +7 -0
  22. package/dist/api/ag-ui/history.js +60 -0
  23. package/dist/api/ag-ui/routes.d.ts +32 -0
  24. package/dist/api/ag-ui/routes.js +305 -0
  25. package/dist/api/ag-ui/session-id.d.ts +1 -0
  26. package/dist/api/ag-ui/session-id.js +12 -0
  27. package/dist/api/ag-ui/sse.d.ts +9 -0
  28. package/dist/api/ag-ui/sse.js +7 -0
  29. package/dist/api/ag-ui/translate.d.ts +38 -0
  30. package/dist/api/ag-ui/translate.js +222 -0
  31. package/dist/api/components.d.ts +8065 -0
  32. package/dist/api/components.js +82 -0
  33. package/dist/api/gate/keys.d.ts +14 -0
  34. package/dist/api/gate/keys.js +59 -0
  35. package/dist/api/gate/routes.d.ts +34 -0
  36. package/dist/api/gate/routes.js +235 -0
  37. package/dist/api/http/app.d.ts +18 -0
  38. package/dist/api/http/app.js +58 -0
  39. package/dist/api/http/body.d.ts +4 -0
  40. package/dist/api/http/body.js +37 -0
  41. package/dist/api/http/define.d.ts +39 -0
  42. package/dist/api/http/define.js +122 -0
  43. package/dist/api/http/respond.d.ts +9 -0
  44. package/dist/api/http/respond.js +44 -0
  45. package/dist/api/http/routes/access.d.ts +24 -0
  46. package/dist/api/http/routes/access.js +266 -0
  47. package/dist/api/http/routes/actions.d.ts +8 -0
  48. package/dist/api/http/routes/actions.js +76 -0
  49. package/dist/api/http/routes/endpoints.d.ts +7 -0
  50. package/dist/api/http/routes/endpoints.js +100 -0
  51. package/dist/api/http/routes/sessions.d.ts +3 -0
  52. package/dist/api/http/routes/sessions.js +205 -0
  53. package/dist/api/http/routes/tenant.d.ts +11 -0
  54. package/dist/api/http/routes/tenant.js +244 -0
  55. package/dist/api/http/routes/vaults.d.ts +8 -0
  56. package/dist/api/http/routes/vaults.js +167 -0
  57. package/dist/api/openapi.d.ts +19 -0
  58. package/dist/api/openapi.js +137 -0
  59. package/dist/api/route.d.ts +12 -0
  60. package/dist/api/route.js +4 -0
  61. package/dist/core/bearer.d.ts +8 -0
  62. package/dist/core/bearer.js +11 -0
  63. package/dist/core/flow-host.d.ts +7 -15
  64. package/dist/core/flow-host.js +16 -41
  65. package/dist/core/sandbox-routes.d.ts +12 -7
  66. package/dist/core/sandbox-routes.js +24 -25
  67. package/dist/execution/memory.d.ts +8 -5
  68. package/dist/execution/memory.js +33 -13
  69. package/dist/execution/types.d.ts +38 -6
  70. package/dist/execution/types.js +12 -3
  71. package/dist/gates/contract.d.ts +51 -0
  72. package/dist/gates/contract.js +37 -0
  73. package/dist/gates/handler.d.ts +21 -0
  74. package/dist/gates/handler.js +55 -0
  75. package/dist/gates/http-client.d.ts +12 -0
  76. package/dist/gates/http-client.js +160 -0
  77. package/dist/gates/in-process.d.ts +32 -0
  78. package/dist/gates/in-process.js +42 -0
  79. package/dist/gates/inflight.d.ts +39 -0
  80. package/dist/gates/inflight.js +70 -0
  81. package/dist/gates/mcp-handler.d.ts +35 -0
  82. package/dist/gates/mcp-handler.js +142 -0
  83. package/dist/gates/meter.d.ts +24 -0
  84. package/dist/gates/meter.js +165 -0
  85. package/dist/gates/model-gate.d.ts +45 -0
  86. package/dist/gates/model-gate.js +1 -0
  87. package/dist/gates/tenant-vaults.d.ts +64 -0
  88. package/dist/gates/tenant-vaults.js +94 -0
  89. package/dist/gates/tool-calls.d.ts +51 -0
  90. package/dist/gates/tool-calls.js +64 -0
  91. package/dist/gates/tool-client.d.ts +12 -0
  92. package/dist/gates/tool-client.js +201 -0
  93. package/dist/gates/tool-contract.d.ts +116 -0
  94. package/dist/gates/tool-contract.js +64 -0
  95. package/dist/gates/tool-gate.d.ts +45 -0
  96. package/dist/gates/tool-gate.js +10 -0
  97. package/dist/host/admin-api.d.ts +23 -0
  98. package/dist/host/admin-api.js +74 -0
  99. package/dist/host/app.d.ts +34 -0
  100. package/dist/host/app.js +181 -0
  101. package/dist/host/config-for.d.ts +3 -1
  102. package/dist/host/config-for.js +3 -2
  103. package/dist/host/config.d.ts +11 -0
  104. package/dist/host/cors.d.ts +27 -0
  105. package/dist/host/cors.js +64 -0
  106. package/dist/host/create-host.d.ts +36 -8
  107. package/dist/host/create-host.js +161 -330
  108. package/dist/host/execution.d.ts +20 -18
  109. package/dist/host/execution.js +2 -2
  110. package/dist/host/gates.d.ts +58 -0
  111. package/dist/host/gates.js +127 -0
  112. package/dist/host/http.d.ts +20 -4
  113. package/dist/host/http.js +81 -23
  114. package/dist/host/index.d.ts +1 -1
  115. package/dist/host/main.js +189 -58
  116. package/dist/host/stack-config.d.ts +91 -19
  117. package/dist/host/stack-config.js +246 -17
  118. package/dist/infra/database.d.ts +2 -2
  119. package/dist/infra/execution.d.ts +5 -5
  120. package/dist/infra/execution.js +8 -6
  121. package/dist/infra/index.d.ts +1 -1
  122. package/dist/infra/workers.d.ts +17 -17
  123. package/dist/infra/workers.js +9 -9
  124. package/dist/keys/client.d.ts +9 -0
  125. package/dist/keys/client.js +70 -0
  126. package/dist/keys/contract.d.ts +24 -0
  127. package/dist/keys/contract.js +12 -0
  128. package/dist/keys/keys.d.ts +41 -0
  129. package/dist/keys/keys.js +50 -0
  130. package/dist/mcp/connect.d.ts +43 -3
  131. package/dist/mcp/connect.js +12 -5
  132. package/dist/mcp/pool.d.ts +39 -3
  133. package/dist/mcp/pool.js +74 -11
  134. package/dist/model/catalog.d.ts +2 -10
  135. package/dist/model/classify.d.ts +13 -0
  136. package/dist/model/classify.js +61 -0
  137. package/dist/model/models.d.ts +10 -1
  138. package/dist/model/models.js +14 -6
  139. package/dist/model/pi-model.d.ts +17 -2
  140. package/dist/model/pi-model.js +366 -222
  141. package/dist/openapi.json +25889 -0
  142. package/dist/record/append.d.ts +51 -0
  143. package/dist/record/append.js +20 -0
  144. package/dist/record/envelope.d.ts +19 -0
  145. package/dist/record/envelope.js +49 -0
  146. package/dist/record/index.d.ts +11 -0
  147. package/dist/record/index.js +11 -0
  148. package/dist/sandbox/manager.d.ts +13 -1
  149. package/dist/sandbox/manager.js +8 -11
  150. package/dist/sandbox/resolve.d.ts +33 -0
  151. package/dist/sandbox/resolve.js +81 -0
  152. package/dist/sandbox/session-sandbox.d.ts +11 -0
  153. package/dist/sandbox/session-sandbox.js +51 -0
  154. package/dist/sandbox/share.d.ts +14 -2
  155. package/dist/sandbox/share.js +33 -3
  156. package/dist/sandbox/tenant-config.d.ts +37 -0
  157. package/dist/sandbox/tenant-config.js +51 -0
  158. package/dist/sandbox/tools.d.ts +7 -9
  159. package/dist/sandbox/tools.js +11 -3
  160. package/dist/sandbox/types.d.ts +8 -2
  161. package/dist/store/postgres/connect.d.ts +25 -9
  162. package/dist/store/postgres/connect.js +27 -10
  163. package/dist/store/postgres/db.d.ts +16 -0
  164. package/dist/store/postgres/db.js +28 -0
  165. package/dist/store/postgres/drizzle/0000_foundation.sql +22 -0
  166. package/dist/store/postgres/drizzle/0001_baseline.sql +255 -0
  167. package/dist/store/postgres/drizzle/0002_stream_relay.sql +4 -0
  168. package/dist/store/postgres/drizzle/0003_tool_crossings.sql +9 -0
  169. package/dist/store/postgres/drizzle/meta/_journal.json +34 -0
  170. package/dist/store/postgres/migrate.d.ts +68 -0
  171. package/dist/store/postgres/migrate.js +183 -0
  172. package/dist/store/postgres/record-writer.d.ts +4 -0
  173. package/dist/store/postgres/record-writer.js +34 -0
  174. package/dist/store/postgres/record.d.ts +5 -0
  175. package/dist/store/postgres/record.js +60 -0
  176. package/dist/store/postgres/schema.d.ts +3096 -0
  177. package/dist/store/postgres/schema.js +363 -0
  178. package/dist/store/postgres/store.d.ts +2 -4
  179. package/dist/store/postgres/store.js +590 -434
  180. package/dist/store/postgres/tenant.d.ts +56 -0
  181. package/dist/store/postgres/tenant.js +154 -0
  182. package/dist/store/types.d.ts +183 -132
  183. package/dist/store/types.js +0 -1
  184. package/dist/streams/basin.d.ts +18 -1
  185. package/dist/streams/basin.js +34 -2
  186. package/dist/streams/control.d.ts +18 -0
  187. package/dist/streams/control.js +33 -0
  188. package/dist/streams/memory.d.ts +3 -1
  189. package/dist/streams/memory.js +14 -8
  190. package/dist/streams/relay/core.d.ts +35 -0
  191. package/dist/streams/relay/core.js +366 -0
  192. package/dist/streams/relay/types.d.ts +78 -0
  193. package/dist/streams/relay/types.js +8 -0
  194. package/dist/streams/types.d.ts +71 -36
  195. package/dist/streams/types.js +18 -53
  196. package/dist/tenant/access-policy.d.ts +30 -0
  197. package/dist/tenant/access-policy.js +52 -0
  198. package/dist/tenant/actions.d.ts +10 -149
  199. package/dist/tenant/actions.js +5 -167
  200. package/dist/tenant/advance.d.ts +7 -2
  201. package/dist/tenant/advance.js +125 -14
  202. package/dist/tenant/auth.d.ts +19 -17
  203. package/dist/tenant/auth.js +90 -93
  204. package/dist/tenant/browser.d.ts +15 -0
  205. package/dist/tenant/browser.js +39 -0
  206. package/dist/tenant/cause.d.ts +25 -0
  207. package/dist/tenant/cause.js +45 -0
  208. package/dist/tenant/commands.d.ts +18 -2
  209. package/dist/tenant/commands.js +111 -115
  210. package/dist/tenant/context.d.ts +101 -21
  211. package/dist/tenant/context.js +14 -10
  212. package/dist/tenant/delivery-token.d.ts +41 -0
  213. package/dist/tenant/delivery-token.js +104 -0
  214. package/dist/tenant/delivery.d.ts +62 -0
  215. package/dist/tenant/delivery.js +481 -0
  216. package/dist/tenant/effects.d.ts +20 -1
  217. package/dist/tenant/effects.js +159 -42
  218. package/dist/tenant/endpoints.d.ts +67 -0
  219. package/dist/tenant/endpoints.js +90 -0
  220. package/dist/tenant/ephemeral.d.ts +22 -7
  221. package/dist/tenant/ephemeral.js +83 -125
  222. package/dist/tenant/history.d.ts +64 -0
  223. package/dist/tenant/history.js +120 -0
  224. package/dist/tenant/http.d.ts +8 -9
  225. package/dist/tenant/http.js +13 -27
  226. package/dist/tenant/jwt.d.ts +16 -0
  227. package/dist/tenant/jwt.js +50 -0
  228. package/dist/tenant/model-setting.d.ts +2 -2
  229. package/dist/tenant/module.d.ts +17 -28
  230. package/dist/tenant/module.js +133 -279
  231. package/dist/tenant/outbound.d.ts +44 -0
  232. package/dist/tenant/outbound.js +127 -0
  233. package/dist/tenant/paths.d.ts +11 -16
  234. package/dist/tenant/paths.js +24 -68
  235. package/dist/tenant/pool.d.ts +1 -3
  236. package/dist/tenant/pool.js +1 -19
  237. package/dist/tenant/principals.d.ts +23 -11
  238. package/dist/tenant/principals.js +61 -49
  239. package/dist/tenant/reset.d.ts +0 -4
  240. package/dist/tenant/reset.js +0 -5
  241. package/dist/tenant/runtime.d.ts +28 -20
  242. package/dist/tenant/runtime.js +95 -64
  243. package/dist/tenant/scheduler.d.ts +10 -3
  244. package/dist/tenant/scheduler.js +9 -5
  245. package/dist/tenant/session-streams.d.ts +127 -0
  246. package/dist/tenant/session-streams.js +404 -0
  247. package/dist/tenant/sessions.d.ts +28 -4
  248. package/dist/tenant/sessions.js +118 -24
  249. package/dist/tenant/signing-keys.d.ts +55 -0
  250. package/dist/tenant/signing-keys.js +172 -0
  251. package/dist/tenant/slim.d.ts +8 -0
  252. package/dist/tenant/slim.js +21 -0
  253. package/dist/tenant/status.d.ts +3 -3
  254. package/dist/tenant/status.js +20 -13
  255. package/dist/tenant/store-pg.d.ts +9 -6
  256. package/dist/tenant/store-pg.js +60 -125
  257. package/dist/tenant/streams.d.ts +62 -85
  258. package/dist/tenant/streams.js +143 -226
  259. package/dist/tenant/subject-limits.d.ts +16 -0
  260. package/dist/tenant/subject-limits.js +45 -0
  261. package/dist/tenant/sweep.d.ts +1 -10
  262. package/dist/tenant/sweep.js +29 -69
  263. package/dist/tenant/tokens.d.ts +14 -0
  264. package/dist/tenant/tokens.js +180 -0
  265. package/dist/tenant/transcript.d.ts +48 -5
  266. package/dist/tenant/transcript.js +58 -1
  267. package/dist/tenant/types.d.ts +69 -98
  268. package/dist/tenant/types.js +4 -15
  269. package/dist/tenant/worker.d.ts +3 -1
  270. package/dist/tenant/worker.js +13 -7
  271. package/dist/vault/host-model.d.ts +31 -0
  272. package/dist/vault/host-model.js +57 -0
  273. package/dist/vault/service.d.ts +44 -17
  274. package/dist/vault/service.js +126 -121
  275. package/dist/version.d.ts +1 -1
  276. package/dist/version.js +1 -1
  277. package/package.json +25 -9
  278. package/dist/core/executors.d.ts +0 -44
  279. package/dist/core/executors.js +0 -95
  280. package/dist/store/memory.d.ts +0 -57
  281. package/dist/store/memory.js +0 -645
  282. package/dist/store/postgres/migrations/001_initial.d.ts +0 -27
  283. package/dist/store/postgres/migrations/001_initial.js +0 -196
  284. package/dist/store/postgres/migrations/002_session_owner.d.ts +0 -6
  285. package/dist/store/postgres/migrations/002_session_owner.js +0 -13
  286. package/dist/store/postgres/migrations/index.d.ts +0 -49
  287. package/dist/store/postgres/migrations/index.js +0 -76
  288. package/dist/store/postgres/names.d.ts +0 -11
  289. package/dist/store/postgres/names.js +0 -42
  290. package/dist/store/postgres/tenants.d.ts +0 -90
  291. package/dist/store/postgres/tenants.js +0 -214
  292. package/dist/streams/relay.d.ts +0 -37
  293. package/dist/streams/relay.js +0 -254
  294. package/dist/tenant/envelope.d.ts +0 -7
  295. package/dist/tenant/envelope.js +0 -22
  296. package/dist/tenant/live.d.ts +0 -92
  297. package/dist/tenant/live.js +0 -290
  298. package/dist/tenant/quarantine-error.d.ts +0 -6
  299. package/dist/tenant/quarantine-error.js +0 -9
  300. package/dist/tenant/quarantine.d.ts +0 -27
  301. package/dist/tenant/quarantine.js +0 -72
  302. package/dist/tenant/routes-tenant.d.ts +0 -12
  303. package/dist/tenant/routes-tenant.js +0 -137
  304. package/dist/tenant/routes.d.ts +0 -9
  305. package/dist/tenant/routes.js +0 -151
  306. package/dist/tenant/store-memory.d.ts +0 -16
  307. package/dist/tenant/store-memory.js +0 -111
  308. /package/dist/{store → record}/cursor.d.ts +0 -0
  309. /package/dist/{store → record}/cursor.js +0 -0
@@ -34,7 +34,9 @@ import { runDurable, runFlowDurable, } from "@nylorun/harness/run";
34
34
  import { aggregateWaits, cancelSiblingWork, isWorkflowManifest, wakeLinkedWorkflow, } from "../core/flow-host.js";
35
35
  import { isOwnershipLost, ownedTx } from "../store/ownership.js";
36
36
  import { linkedAgentOutput, rebaseSessionState, sessionToolsOf, turnManifestOf, } from "./session.js";
37
- import { prepareMcp, resolveEffect } from "./effects.js";
37
+ import { isRemoteMcpEffect, prepareMcp, recoversMcpCalls, recoversModelCalls, resolveEffect, } from "./effects.js";
38
+ import { slimModelEffects } from "./slim.js";
39
+ import { checkParity, foldTranscript, leanState, transcriptOf, transcriptUpdates, withTranscript, } from "./history.js";
38
40
  import { command } from "./commands.js";
39
41
  import { usesFixtureModel } from "./model-setting.js";
40
42
  import { toolFixtureModel } from "../core/provider.js";
@@ -85,6 +87,7 @@ export async function advance(ctx, id, signal) {
85
87
  else
86
88
  signal.addEventListener("abort", forward, { once: true });
87
89
  ctx.work.running.set(id, controller);
90
+ ctx.work.runningTurns.set(id, taken.session.activeTurnId ?? null);
88
91
  const heartbeat = startHeartbeat(ctx, lease, controller);
89
92
  let result = DONE;
90
93
  try {
@@ -110,8 +113,10 @@ export async function advance(ctx, id, signal) {
110
113
  finally {
111
114
  heartbeat.stop();
112
115
  signal.removeEventListener("abort", forward);
113
- if (ctx.work.running.get(id) === controller)
116
+ if (ctx.work.running.get(id) === controller) {
114
117
  ctx.work.running.delete(id);
118
+ ctx.work.runningTurns.delete(id);
119
+ }
115
120
  // Best effort: a release that fails leaves a lease that simply expires.
116
121
  await ctx.store
117
122
  .tx((t) => t.releaseOwnership(id, lease.owner, lease.epoch))
@@ -146,7 +151,10 @@ async function takeOwnership(ctx, id) {
146
151
  };
147
152
  const s = await t.assertEpoch(id, taken.epoch);
148
153
  if (taken.takeover)
149
- await takeOver(t, s);
154
+ await takeOver(t, s, {
155
+ recoversModelCalls: recoversModelCalls(ctx),
156
+ recoversMcpCalls: recoversMcpCalls(ctx),
157
+ });
150
158
  if (!s.checkpoint || !["running", "runnable"].includes(s.status)) {
151
159
  await t.releaseOwnership(id, lease.owner, lease.epoch);
152
160
  return { status: "idle" };
@@ -158,13 +166,20 @@ async function takeOwnership(ctx, id) {
158
166
  }
159
167
  /**
160
168
  * Takeover (§10.5 step 2, §11.4), inside the transaction that took ownership: the dead
161
- * owner's `invoking` effects become `uncertain` and are never invoked again. When one belongs
169
+ * owner's `invoking` effects become `uncertain` and are never invoked again, except model
170
+ * calls when the gate recovers them (P1.2) and remote MCP calls when the Tool Gate does (F4.1
171
+ * G3): those stay `invoking`, and the replay re-sends them. When one belongs
162
172
  * to the active turn, the session becomes `uncertain` too, with an `effect.uncertain` event.
163
173
  * Mutates and writes `s`. Returns the effect ids it marked.
164
174
  */
165
- export async function takeOver(t, s) {
175
+ export async function takeOver(t, s, options = {}) {
166
176
  const marked = [];
167
177
  for (const effect of await t.invokingEffects(s.id)) {
178
+ // Still running at the gate, or finished there: the replay re-sends it (P1.2).
179
+ if (options.recoversModelCalls && effect.request.kind === "model")
180
+ continue;
181
+ if (options.recoversMcpCalls && isRemoteMcpEffect(s, effect.request))
182
+ continue;
168
183
  effect.status = "uncertain";
169
184
  effect.error ??= "The Worker running this effect stopped before its outcome was recorded";
170
185
  await t.put("effects", effect.request.effectId, effect);
@@ -224,6 +239,9 @@ function startHeartbeat(ctx, lease, controller) {
224
239
  },
225
240
  };
226
241
  }
242
+ /** Segment rollover defaults (Model Calls §10), well inside the advance deadline. */
243
+ const ROLLOVER_STEPS = 50;
244
+ const ROLLOVER_MS = 20 * 60_000;
227
245
  /** Runs one segment and settles it. Throws only `ownership.lost` and infrastructure errors. */
228
246
  async function runSegment(ctx, lease, started, signal) {
229
247
  const id = lease.sessionId;
@@ -234,12 +252,24 @@ async function runSegment(ctx, lease, started, signal) {
234
252
  const { current, fixtureModel } = await ownedTx(ctx.store, id, lease.epoch, async (t, current) => {
235
253
  if (!isWorkflowManifest(current.manifest) && current.checkpoint) {
236
254
  rebaseSessionState(current, current.checkpoint.manifestHash);
255
+ await adoptStoredTranscript(t, current);
237
256
  const cp = current.checkpoint;
238
257
  current.checkpoint = { ...cp, state: current.state };
239
258
  await t.put("sessions", id, current);
240
259
  }
241
260
  return { current, fixtureModel: await usesFixtureModel(t) };
242
261
  });
262
+ // One history (P0.3): the engine resumes from the transcript folded from the record.
263
+ let startTranscript = [];
264
+ let engineCheckpoint = current.checkpoint;
265
+ if (!isWorkflowManifest(current.manifest) && current.checkpoint) {
266
+ const cp = current.checkpoint;
267
+ if (cp.state) {
268
+ startTranscript = await foldSession(ctx, current);
269
+ checkParity(id, startTranscript, transcriptOf(cp.state));
270
+ engineCheckpoint = { ...cp, state: withTranscript(cp.state, startTranscript) };
271
+ }
272
+ }
243
273
  const segment = fixtureModel ? { model: fixture } : {};
244
274
  const host = {
245
275
  resolveEffect: (e) => resolveEffect(ctx, e, signal, lease, segment),
@@ -254,10 +284,14 @@ async function runSegment(ctx, lease, started, signal) {
254
284
  })
255
285
  : await runDurable({
256
286
  manifest: turnManifestOf(current),
257
- checkpoint: current.checkpoint,
287
+ checkpoint: engineCheckpoint,
258
288
  signal,
259
289
  sessionTools: sessionToolsOf(current.mcpSnapshot),
260
290
  host,
291
+ yieldAfter: {
292
+ steps: ctx.config.rollover?.steps ?? ROLLOVER_STEPS,
293
+ ms: ctx.config.rollover?.ms ?? ROLLOVER_MS,
294
+ },
261
295
  });
262
296
  // The engine turns an abort into a `cancelled` (agents) or `failed` (workflows) result;
263
297
  // only a user cancel may settle that, and it already did.
@@ -265,8 +299,15 @@ async function runSegment(ctx, lease, started, signal) {
265
299
  if (signal.reason instanceof AdvanceDeadlineError &&
266
300
  (result.status === "cancelled" || result.status === "failed"))
267
301
  await settleFailure(ctx, lease, started, signal.reason);
268
- else
269
- await settle(ctx, lease, started, result);
302
+ else {
303
+ const state = !isWorkflowManifest(current.manifest) &&
304
+ (result.status === "yielded" || result.status === "completed" || result.status === "paused") &&
305
+ "result" in result
306
+ ? result.result?.state
307
+ : undefined;
308
+ const updates = state ? transcriptUpdates(startTranscript, transcriptOf(state)) : [];
309
+ await settle(ctx, lease, started, result, updates);
310
+ }
270
311
  }
271
312
  catch (error) {
272
313
  if (isOwnershipLost(error) || error instanceof SegmentStopped)
@@ -304,7 +345,7 @@ async function wakeWorkflowOf(ctx, t, agent, turnId, type, payload) {
304
345
  });
305
346
  }
306
347
  /** Record a segment result, then cancel failed siblings and wake a linked workflow. */
307
- async function settle(ctx, lease, s, result) {
348
+ async function settle(ctx, lease, s, result, updates = []) {
308
349
  const id = lease.sessionId;
309
350
  const siblingCancelIds = await ownedTx(ctx.store, id, lease.epoch, async (t, current) => {
310
351
  const siblings = [];
@@ -337,15 +378,38 @@ async function settle(ctx, lease, s, result) {
337
378
  await t.put("sessions", id, current);
338
379
  return siblings;
339
380
  }
381
+ if (result.status === "yielded") {
382
+ // The turn goes on in a new segment: same turn, next checkpoint, woken right away.
383
+ const finished = result.checkpoint;
384
+ await recordTranscript(t, id, s.activeTurnId, current, updates);
385
+ current.state = leanState(result.result.state);
386
+ current.checkpoint = {
387
+ ...finished,
388
+ segment: finished.segment + 1,
389
+ input: { kind: "continue" },
390
+ state: current.state,
391
+ };
392
+ current.status = "runnable";
393
+ current.waits = undefined;
394
+ await slimModelEffects(t, id, s.activeTurnId);
395
+ await t.put("sessions", id, current);
396
+ const segment = finished.segment + 1;
397
+ t.afterCommit(() => ctx.wake(id, {
398
+ reason: "rollover",
399
+ dedupeKey: `rollover:${s.activeTurnId}:${segment}`,
400
+ }));
401
+ return siblings;
402
+ }
340
403
  const flow = isWorkflowManifest(current.manifest);
341
404
  if (!flow) {
405
+ if (result.status !== "failed")
406
+ await recordTranscript(t, id, s.activeTurnId, current, updates);
342
407
  current.state =
343
408
  result.status === "failed"
344
409
  ? current.turnStartState
345
- : result.result.state;
410
+ : leanState(result.result.state);
346
411
  }
347
- current.checkpoint = result.checkpoint;
348
- await t.put("checkpoints", JSON.stringify([id, s.activeTurnId, result.checkpoint.segment]), { checkpoint: result.checkpoint, status: result.status });
412
+ current.checkpoint = leanCheckpoint(result.checkpoint);
349
413
  current.status = result.status;
350
414
  current.waits =
351
415
  result.status === "paused" ? result.result.pending : undefined;
@@ -370,6 +434,7 @@ async function settle(ctx, lease, s, result) {
370
434
  current.activeTurnId = null;
371
435
  if (s.activeTurnId)
372
436
  current.lastTurnId = s.activeTurnId;
437
+ await slimModelEffects(t, id, s.activeTurnId);
373
438
  }
374
439
  const type = `turn.${result.status}`;
375
440
  const payload = result.status === "completed"
@@ -425,11 +490,10 @@ async function settleFailure(ctx, lease, s, error) {
425
490
  return;
426
491
  current.status = "failed";
427
492
  current.state = current.turnStartState;
428
- if (current.checkpoint)
429
- await t.put("checkpoints", JSON.stringify([id, s.activeTurnId, current.checkpoint.segment]), { checkpoint: current.checkpoint, status: "failed" });
430
493
  current.error = error instanceof Error ? error.message : String(error);
431
494
  const payload = { message: current.error };
432
495
  await t.event(id, current.activeTurnId, "turn.failed", payload);
496
+ await slimModelEffects(t, id, current.activeTurnId);
433
497
  if (current.activeTurnId)
434
498
  current.lastTurnId = current.activeTurnId;
435
499
  current.activeTurnId = null;
@@ -437,3 +501,50 @@ async function settleFailure(ctx, lease, s, error) {
437
501
  await wakeWorkflowOf(ctx, t, current, s.activeTurnId, "turn.failed", payload);
438
502
  });
439
503
  }
504
+ /** `checkpoint` with its engine state stored lean (`history.ts`). */
505
+ function leanCheckpoint(checkpoint) {
506
+ const state = checkpoint?.state;
507
+ if (!checkpoint || !state)
508
+ return checkpoint;
509
+ return { ...checkpoint, state: leanState(state) };
510
+ }
511
+ /** Appends a segment's transcript edits; a snapshot moves `history.snapshot`. */
512
+ async function recordTranscript(t, id, turnId, s, updates) {
513
+ for (const update of updates) {
514
+ const event = await t.event(id, turnId, "transcript.updated", update);
515
+ if (update.keep === 0)
516
+ s.history = { from: s.history?.from ?? event.seq, snapshot: event.seq };
517
+ }
518
+ }
519
+ /**
520
+ * A session written before transcripts were recorded keeps its transcript on its row: record
521
+ * it once (the turn's starting transcript, then the turn so far), and store the row lean.
522
+ */
523
+ async function adoptStoredTranscript(t, s) {
524
+ if (s.history)
525
+ return;
526
+ const now = transcriptOf(s.state);
527
+ const start = s.activeTurnId ? transcriptOf(s.turnStartState) : now;
528
+ if (now.length === 0 && start.length === 0)
529
+ return;
530
+ let first;
531
+ const write = async (turnId, updates) => {
532
+ for (const update of updates) {
533
+ const event = await t.event(s.id, turnId, "transcript.updated", update);
534
+ first ??= event.seq;
535
+ }
536
+ };
537
+ await write(null, transcriptUpdates([], start));
538
+ if (s.activeTurnId)
539
+ await write(s.activeTurnId, transcriptUpdates(start, now));
540
+ s.history = { from: first ?? 0, snapshot: first ?? 0 };
541
+ s.state = leanState(s.state);
542
+ s.turnStartState = leanState(s.turnStartState);
543
+ }
544
+ /** The session's transcript, folded from its record from `history.from`. */
545
+ async function foldSession(ctx, s) {
546
+ const rows = await ctx.store
547
+ .record()
548
+ .readRange(ctx.store.tenantId, s.id, s.history?.from ?? 0, Number.MAX_SAFE_INTEGER);
549
+ return foldTranscript(rows.map((row) => row.body));
550
+ }
@@ -1,32 +1,34 @@
1
1
  /**
2
2
  * Tenant request authentication and authorization: a bearer token resolves to an application
3
- * principal or a registered executor; anything else is the opaque 404 (D5). An application
4
- * principal may act for a subject (`Nylorun-Subject`, `Nylorun-Scopes`); `authorize` then
5
- * limits the request to the routes its scopes allow, decided from the route alone.
3
+ * principal, a subject token or a delivery token; anything else is the opaque 404 (D5).
4
+ * An application principal may act for a subject (`Nylorun-Subject`, `Nylorun-Scopes`); a
5
+ * subject token names its subject itself. `authorize` limits both to the routes their scopes
6
+ * allow, decided from the route alone.
6
7
  */
7
8
  import type { IncomingMessage } from "node:http";
8
9
  import { type Action, type SubjectScope } from "@nylorun/core/contracts";
9
- import type { AuthScope, TenantContext } from "./context.js";
10
- export declare function authenticate(ctx: TenantContext, request: IncomingMessage): Promise<AuthScope>;
11
- /** What a subject needs for a route: any one of the scopes, or nothing grants it. */
12
- export type RouteAccess = readonly SubjectScope[] | "never";
10
+ import type { AuthScope, SessionAccess, TenantContext } from "./context.js";
11
+ import type { BrowserClient } from "./browser.js";
12
+ export declare function authenticate(ctx: TenantContext, request: IncomingMessage, client?: BrowserClient): Promise<AuthScope>;
13
13
  /**
14
- * The scopes a subject needs for `method path` (`path` starts with `v1`). `undefined` for a
15
- * route that does not exist. Operator and executor routes are `never`: reset, config seed,
16
- * executors, actions and the sandbox tool routes.
14
+ * What a subject needs for a route: any one of the scopes, nothing grants it (`never`), or
15
+ * any caller may (`any`, public data such as the JWKS). Each route declares it
16
+ * (`api/http/define.ts`).
17
17
  */
18
- export declare function routeAccess(method: string | undefined, path: readonly string[]): RouteAccess | undefined;
18
+ export type SubjectAccess = readonly SubjectScope[] | "never" | "any";
19
19
  /**
20
- * Limits a subject to the routes its scopes allow. Decided from the route alone, before any
21
- * lookup, so a refusal reveals nothing about which resources exist. A route the table does not
22
- * know is a 404 for a subject: new routes stay closed until they are added here. Application
23
- * and executor scopes pass through to the checks each route makes today.
20
+ * A subject, subject token or publishable key reaches a route only with one of the scopes it
21
+ * declares: `403 scope_required`, decided from the route alone before anything is read.
24
22
  */
25
- export declare function authorize(scope: AuthScope, method: string | undefined, path: readonly string[]): void;
23
+ export declare function requireScopes(scope: AuthScope, access: SubjectAccess): void;
24
+ export declare function accessOf(scope: AuthScope): SessionAccess | undefined;
26
25
  /** The subject whose sessions and vaults the request is limited to, if it acts for one. */
27
26
  export declare function ownerOf(scope: AuthScope): string | undefined;
28
27
  /** The application principal behind the request, acting for a subject or not. */
29
28
  export declare function requirePrincipal(scope: AuthScope): string;
30
- /** The executor scope must belong to the Action's agent. */
29
+ /**
30
+ * The caller may act on `action`: only the delivery token minted for it. Whether its generation
31
+ * is still the Action's is checked by each callback.
32
+ */
31
33
  export declare function scoped(scope: AuthScope, action: Action): void;
32
34
  export declare function requireApplication(scope: AuthScope): string;
@@ -1,7 +1,11 @@
1
1
  import { SCOPES_HEADER, SUBJECT_HEADER, } from "@nylorun/core/compatibility";
2
- import { parseSubjectHeaders, } from "@nylorun/core/contracts";
3
- import { hashToken } from "../core/executors.js";
2
+ import { DELIVERY_TOKEN_TYPE, parseSubjectHeaders, } from "@nylorun/core/contracts";
3
+ import { hashToken } from "../core/bearer.js";
4
4
  import { fail, failOpaque } from "./http.js";
5
+ import { looksLikeToken, verifySubjectToken } from "./tokens.js";
6
+ import { verifyDeliveryToken } from "./delivery-token.js";
7
+ import { tokenType } from "./jwt.js";
8
+ import { readPolicy } from "./access-policy.js";
5
9
  const SUBJECT_INVALID = { code: "subject_invalid" };
6
10
  /** One header's value, or `undefined`; a header sent twice is refused. */
7
11
  function singleHeader(request, name) {
@@ -14,19 +18,56 @@ function singleHeader(request, name) {
14
18
  return fail(400, `${name} must be sent once`, SUBJECT_INVALID);
15
19
  return values[0];
16
20
  }
17
- export async function authenticate(ctx, request) {
21
+ export async function authenticate(ctx, request, client) {
18
22
  const header = request.headers.authorization;
19
23
  const token = header?.startsWith("Bearer ") ? header.slice(7) : "";
20
24
  if (!token || !header?.startsWith("Bearer ")) {
25
+ // A publishable key alone: what the policy grants `anon` (nothing by default).
26
+ if (client && header === undefined) {
27
+ const { anon } = await ctx.store.tx((t) => readPolicy(t));
28
+ return {
29
+ kind: "publishable",
30
+ keyId: client.keyId,
31
+ scopes: new Set(anon.scopes),
32
+ agents: anon.agents === "*" ? "*" : new Set(anon.agents),
33
+ };
34
+ }
21
35
  ctx.config.logger.warn("credential rejected", {
22
36
  reason: "missing_bearer",
23
37
  });
24
38
  return failOpaque();
25
39
  }
40
+ if (looksLikeToken(token) && tokenType(token) === DELIVERY_TOKEN_TYPE) {
41
+ // Delivery tokens come back from the application's server, never from a browser.
42
+ if (request.headers.origin !== undefined)
43
+ fail(403, "Delivery tokens are not accepted from browsers", { code: "origin_rejected" });
44
+ const scope = await verifyDeliveryToken(ctx, token);
45
+ if (singleHeader(request, SUBJECT_HEADER) !== undefined ||
46
+ singleHeader(request, SCOPES_HEADER) !== undefined)
47
+ fail(403, "A delivery token cannot act for a subject");
48
+ return scope;
49
+ }
50
+ if (looksLikeToken(token)) {
51
+ const scope = await verifySubjectToken(ctx, token);
52
+ // Read only after verification, so an unknown token still sees the opaque 404.
53
+ if (singleHeader(request, SUBJECT_HEADER) !== undefined ||
54
+ singleHeader(request, SCOPES_HEADER) !== undefined)
55
+ fail(403, "A subject token cannot act for another subject");
56
+ return scope;
57
+ }
58
+ // Application keys never come from a browser or a shipped app: refused before they are even
59
+ // looked up.
60
+ if (request.headers.origin !== undefined)
61
+ fail(403, "Application keys are not accepted from browsers", {
62
+ code: "origin_rejected",
63
+ });
64
+ if (client)
65
+ fail(400, "Nylorun-Key is for browser and mobile clients, not with a Tenant key", {
66
+ code: "invalid_request",
67
+ });
26
68
  const tokenHash = hashToken(token);
27
69
  const principal = await ctx.store.tx((t) => t.principalByTokenHash(tokenHash));
28
- const executor = principal ? undefined : ctx.registry.find(tokenHash);
29
- if (!principal && !executor) {
70
+ if (!principal) {
30
71
  ctx.config.logger.warn("credential rejected", {
31
72
  reason: "unknown_token",
32
73
  });
@@ -35,11 +76,6 @@ export async function authenticate(ctx, request) {
35
76
  // Read only after authentication, so an unknown caller sees the opaque 404 either way.
36
77
  const subject = singleHeader(request, SUBJECT_HEADER);
37
78
  const scopes = singleHeader(request, SCOPES_HEADER);
38
- if (executor) {
39
- if (subject !== undefined || scopes !== undefined)
40
- fail(403, "An executor credential cannot act for a subject");
41
- return { kind: "executor", executor };
42
- }
43
79
  if (subject === undefined) {
44
80
  if (scopes !== undefined)
45
81
  fail(400, `${SCOPES_HEADER} requires ${SUBJECT_HEADER}`, SUBJECT_INVALID);
@@ -55,87 +91,17 @@ export async function authenticate(ctx, request) {
55
91
  scopes: parsed.scopes,
56
92
  };
57
93
  }
58
- const SESSIONS = ["sessions:own"];
59
- const VAULTS = ["vaults:own"];
60
- const SETTINGS = ["tenant:settings"];
61
94
  /**
62
- * The scopes a subject needs for `method path` (`path` starts with `v1`). `undefined` for a
63
- * route that does not exist. Operator and executor routes are `never`: reset, config seed,
64
- * executors, actions and the sandbox tool routes.
95
+ * A subject, subject token or publishable key reaches a route only with one of the scopes it
96
+ * declares: `403 scope_required`, decided from the route alone before anything is read.
65
97
  */
66
- export function routeAccess(method, path) {
67
- const [v1, resource, id, sub] = path;
68
- const n = path.length;
69
- if (v1 !== "v1" || !resource)
70
- return undefined;
71
- switch (resource) {
72
- case "executors":
73
- case "actions":
74
- return "never";
75
- case "agents":
76
- if (n === 2 && method === "GET")
77
- return ["agents:read", "agents:write"];
78
- if (n === 3 && method === "PUT")
79
- return ["agents:write"];
80
- return undefined;
81
- case "sessions":
82
- if (n === 2 && method === "GET")
83
- return SESSIONS;
84
- if (n === 3 && (method === "GET" || method === "PUT"))
85
- return SESSIONS;
86
- if (n === 4 && method === "GET" && (sub === "items" || sub === "events"))
87
- return SESSIONS;
88
- if (n === 4 && method === "POST" && sub === "commands")
89
- return SESSIONS;
90
- if (n === 5 && method === "POST" && sub === "sandbox")
91
- return "never";
92
- return undefined;
93
- case "vaults":
94
- if (n === 2)
95
- return method === "GET" || method === "POST" ? VAULTS : undefined;
96
- if (n === 3)
97
- return method === "GET" || method === "DELETE" ? VAULTS : undefined;
98
- if (sub !== "credentials")
99
- return undefined;
100
- if (n === 4)
101
- return method === "GET" || method === "POST" ? VAULTS : undefined;
102
- if (n === 5)
103
- return method === "GET" || method === "POST" || method === "DELETE"
104
- ? VAULTS
105
- : undefined;
106
- return undefined;
107
- case "tenant":
108
- if (n === 2 && method === "GET")
109
- return SETTINGS;
110
- if (n === 3 && id === "reset" && method === "POST")
111
- return "never";
112
- if (n === 4 && id === "config" && sub === "seed" && method === "PUT")
113
- return "never";
114
- if (n === 3 && method === "GET" && (id === "models" || id === "providers"))
115
- return ["tenant:settings", "agents:write"];
116
- if (n === 3 && method === "GET" && id === "sandbox")
117
- return SETTINGS;
118
- if (n === 3 && id === "model" && (method === "GET" || method === "PUT"))
119
- return SETTINGS;
120
- if (n === 4 && id === "model" && sub === "selection" && method === "PUT")
121
- return SETTINGS;
122
- return undefined;
123
- default:
124
- return undefined;
125
- }
126
- }
127
- /**
128
- * Limits a subject to the routes its scopes allow. Decided from the route alone, before any
129
- * lookup, so a refusal reveals nothing about which resources exist. A route the table does not
130
- * know is a 404 for a subject: new routes stay closed until they are added here. Application
131
- * and executor scopes pass through to the checks each route makes today.
132
- */
133
- export function authorize(scope, method, path) {
134
- if (scope.kind !== "subject")
98
+ export function requireScopes(scope, access) {
99
+ if (scope.kind !== "subject" &&
100
+ scope.kind !== "token" &&
101
+ scope.kind !== "publishable")
102
+ return;
103
+ if (access === "any")
135
104
  return;
136
- const access = routeAccess(method, path);
137
- if (access === undefined)
138
- fail(404, "Route not found");
139
105
  if (access === "never")
140
106
  fail(403, "This route is not available when acting for a subject", {
141
107
  code: "scope_required",
@@ -147,20 +113,51 @@ export function authorize(scope, method, path) {
147
113
  details: { scopes: access },
148
114
  });
149
115
  }
116
+ export function accessOf(scope) {
117
+ switch (scope.kind) {
118
+ case "application":
119
+ return undefined;
120
+ case "subject":
121
+ return { owner: scope.subject };
122
+ case "token":
123
+ return {
124
+ owner: scope.subject,
125
+ ...(scope.agents === "*" ? {} : { agents: scope.agents }),
126
+ };
127
+ // A publishable key alone owns nothing: no session or vault is ever reachable.
128
+ case "publishable":
129
+ // Nor does a request with no credential, on a route that serves public data.
130
+ case "anonymous":
131
+ return fail(404, "Not found");
132
+ // A delivery token reaches its Action's callbacks, never a session or vault.
133
+ case "delivery":
134
+ return fail(404, "Not found");
135
+ default: {
136
+ const unknown = scope;
137
+ return fail(404, `Unknown credential ${String(unknown.kind)}`);
138
+ }
139
+ }
140
+ }
150
141
  /** The subject whose sessions and vaults the request is limited to, if it acts for one. */
151
142
  export function ownerOf(scope) {
152
- return scope.kind === "subject" ? scope.subject : undefined;
143
+ return accessOf(scope)?.owner;
153
144
  }
154
145
  /** The application principal behind the request, acting for a subject or not. */
155
146
  export function requirePrincipal(scope) {
156
- if (scope.kind !== "executor")
147
+ if (scope.kind === "application" || scope.kind === "subject")
157
148
  return scope.principalId;
158
149
  return fail(403, "Application credential required");
159
150
  }
160
- /** The executor scope must belong to the Action's agent. */
151
+ /**
152
+ * The caller may act on `action`: only the delivery token minted for it. Whether its generation
153
+ * is still the Action's is checked by each callback.
154
+ */
161
155
  export function scoped(scope, action) {
162
- if (scope.kind !== "executor" || scope.executor.agentId !== action.agentId)
163
- fail(403, "Executor scope does not authorize this action");
156
+ if (scope.kind === "delivery" &&
157
+ scope.actionId === action.actionId &&
158
+ scope.agentId === action.agentId)
159
+ return;
160
+ fail(403, "This delivery token is for another Action");
164
161
  }
165
162
  export function requireApplication(scope) {
166
163
  if (scope.kind === "application")
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Which client app a request comes from (Host feature `browser-access`), decided before the
3
+ * bearer is looked at. `Nylorun-Key` names a publishable key of this Tenant; an `Origin` must
4
+ * be on that key's allowlist, and only then does the response carry CORS headers. The
5
+ * allowlist protects browsers from other sites' pages; it is not authentication, since a
6
+ * non-browser can send any `Origin`: tokens authorize.
7
+ */
8
+ import type { IncomingMessage, ServerResponse } from "node:http";
9
+ import type { TenantContext } from "./context.js";
10
+ /** The client app of a request: its publishable key, and the origin when from a browser. */
11
+ export interface BrowserClient {
12
+ readonly keyId: string;
13
+ readonly origin?: string;
14
+ }
15
+ export declare function identifyClient(ctx: TenantContext, request: IncomingMessage, response: ServerResponse): Promise<BrowserClient | undefined>;
@@ -0,0 +1,39 @@
1
+ import { PUBLISHABLE_KEY_HEADER } from "@nylorun/core/compatibility";
2
+ import { originAllowed } from "@nylorun/core/contracts";
3
+ import { browserOrigin, setCorsHeaders } from "../host/cors.js";
4
+ import { fail, failOpaque } from "./http.js";
5
+ function single(request, name) {
6
+ const raw = request.headers[name.toLowerCase()];
7
+ return Array.isArray(raw) ? raw[0] : raw;
8
+ }
9
+ export async function identifyClient(ctx, request, response) {
10
+ const key = single(request, PUBLISHABLE_KEY_HEADER);
11
+ const hasOrigin = request.headers.origin !== undefined;
12
+ if (key === undefined) {
13
+ if (hasOrigin)
14
+ fail(403, `Browser requests need ${PUBLISHABLE_KEY_HEADER}`, {
15
+ code: "origin_rejected",
16
+ });
17
+ return undefined;
18
+ }
19
+ const row = await ctx.store.tx((t) => t.publishableKeyByKey(key));
20
+ if (!row || row.revokedAt !== null) {
21
+ ctx.config.logger.warn("credential rejected", {
22
+ reason: "publishable_key_rejected",
23
+ });
24
+ return failOpaque();
25
+ }
26
+ if (!hasOrigin)
27
+ return { keyId: row.id };
28
+ const origin = browserOrigin(request);
29
+ const origins = JSON.parse(row.originsJson);
30
+ if (origin === undefined || !originAllowed(origins, origin)) {
31
+ ctx.config.logger.warn("credential rejected", {
32
+ reason: "origin_not_allowed",
33
+ keyId: row.id,
34
+ });
35
+ return failOpaque();
36
+ }
37
+ setCorsHeaders(response, origin);
38
+ return { keyId: row.id, origin };
39
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Why the Host's Tenant could not be opened (tenancy.md §5). A cause is about the Tenant's
3
+ * own data or files (its database is newer than this Runtime, its vault key is missing, the
4
+ * database has an old layout): the Host fails readiness, names the cause in
5
+ * `/v1/admin/status` and the logs, and answers Tenant requests with the opaque 404 until it is
6
+ * repaired and the Host restarted. A failure outside the Tenant (Postgres unreachable) is not
7
+ * a cause: it is `TenantUnavailableError`, and the next request tries again.
8
+ */
9
+ import type { TenantCause, TenantEnvelope } from "@nylorun/core/contracts";
10
+ export type { TenantCause };
11
+ /** Thrown by migrations, the Tenant bootstrap and the Tenant Runtime when the Tenant cannot open. */
12
+ export declare class TenantOpenError extends Error {
13
+ readonly code: TenantCause["code"];
14
+ readonly repair: string;
15
+ /** The Tenant, when it was read before opening failed (`/v1/admin/status` names it). */
16
+ envelope?: TenantEnvelope;
17
+ constructor(fields: TenantCause);
18
+ toCause(): TenantCause;
19
+ }
20
+ /** A `TenantOpenError` with the standard repair for `code`. */
21
+ export declare function openError(code: TenantCause["code"], message: string): TenantOpenError;
22
+ /** The cause an error carries, when it is a `TenantOpenError`. */
23
+ export declare function causeOf(error: unknown): TenantCause | undefined;
24
+ /** What an operator does about each cause. */
25
+ export declare function repairFor(code: TenantCause["code"]): string;