@coreplane/switchboard 1.267.1 → 1.267.3

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 (153) hide show
  1. package/dist/assets/deploy/cloudflare-memory/testDiagnostics.ts +44 -3
  2. package/dist/assets/deploy/cloudflare-memory/testDiagnosticsProtocol.ts +7 -0
  3. package/dist/assets/deploy/cloudflare-memory/testDiagnosticsReporter.ts +22 -0
  4. package/dist/assets/deploy/cloudflare-memory/testFetch.ts +21 -0
  5. package/dist/assets/deploy/cloudflare-memory/worker.ts +43 -0
  6. package/dist/assets/deploy/cloudflare-resident/runRegistration.ts +68 -0
  7. package/dist/assets/deploy/cloudflare-resident/worker.ts +379 -165
  8. package/dist/assets/deploy/cloudflare-sandbox/worker.ts +17 -2
  9. package/dist/assets/package-lock.json +51 -4
  10. package/dist/assets/package.json +1 -1
  11. package/dist/assets/source.json +3 -3
  12. package/dist/assets/src/agents/registry.ts +2 -2
  13. package/dist/assets/src/core/budgets.ts +2 -0
  14. package/dist/assets/src/core/publicationPush.ts +31 -9
  15. package/dist/assets/src/core/runEvents.ts +17 -1
  16. package/dist/assets/src/core/runRecord.ts +17 -1
  17. package/dist/assets/src/core/ship/coordinator.ts +14 -1
  18. package/dist/assets/src/core/trace/attrs.ts +22 -0
  19. package/dist/assets/src/core/trace/tracer.ts +10 -2
  20. package/dist/assets/src/core/trace/types.ts +12 -1
  21. package/dist/assets/src/core/trace/workerTrace.ts +2 -2
  22. package/dist/assets/src/execution/seedPlan.ts +13 -4
  23. package/dist/assets/web/dist/.vite/manifest.json +488 -488
  24. package/dist/assets/web/dist/assets/AppShell-CCPwPqvc.js +1 -0
  25. package/dist/assets/web/dist/assets/{CostChart-4TeDdbvJ.js → CostChart-Y5BqJKLA.js} +2 -2
  26. package/dist/assets/web/dist/assets/CostsPage-RRggVvIx.js +1 -0
  27. package/dist/assets/web/dist/assets/DeliveryPage-CuIBLfup.js +1 -0
  28. package/dist/assets/web/dist/assets/HomePage-BQmun9kS.js +1 -0
  29. package/dist/assets/web/dist/assets/InputMenu-ymuqElZB.js +1 -0
  30. package/dist/assets/web/dist/assets/{MetricsPage-BQBuIuYU.js → MetricsPage-D2YGsI8C.js} +1 -1
  31. package/dist/assets/web/dist/assets/NotFoundPage-DZ6ysTXd.js +1 -0
  32. package/dist/assets/web/dist/assets/PendingTurnRow-BPyJ2f-i.js +1 -0
  33. package/dist/assets/web/dist/assets/PlanePage-C_gVx0Dg.js +1 -0
  34. package/dist/assets/web/dist/assets/ResidentDetailPage-CT6v9INM.js +1 -0
  35. package/dist/assets/web/dist/assets/ResidentsIndexPage-CWqPFf40.js +1 -0
  36. package/dist/assets/web/dist/assets/{RunFoldRow-BOWvSoIv.js → RunFoldRow-C1w-mwch.js} +1 -1
  37. package/dist/assets/web/dist/assets/RunRoutePage-zWMHTzla.js +9 -0
  38. package/dist/assets/web/dist/assets/RunsIndexPage-DF16D2Gg.js +1 -0
  39. package/dist/assets/web/dist/assets/RunsTabs-D7rsC4GR.js +1 -0
  40. package/dist/assets/web/dist/assets/ScheduledPage-CyRs0fXN.js +1 -0
  41. package/dist/assets/web/dist/assets/SettingSelect-CEkCLVWd.js +1 -0
  42. package/dist/assets/web/dist/assets/SettingsPage-BRuMCHSj.js +1 -0
  43. package/dist/assets/web/dist/assets/SilentTurn-D4MMDjWO.js +2 -0
  44. package/dist/assets/web/dist/assets/SlackMark-DjJTK2tD.js +1 -0
  45. package/dist/assets/web/dist/assets/StatusDot-BIAQN7iD.js +1 -0
  46. package/dist/assets/web/dist/assets/Tooltip-CxiQ7luL.js +1 -0
  47. package/dist/assets/web/dist/assets/UnitRoutePage-BQB5mAAl.js +1 -0
  48. package/dist/assets/web/dist/assets/{angular-html-BRRhsdQE.js → angular-html-DR_aiq_z.js} +1 -1
  49. package/dist/assets/web/dist/assets/{angular-ts-BFj-mqCm.js → angular-ts-DdAdPC99.js} +1 -1
  50. package/dist/assets/web/dist/assets/{apl-CsBuy1IL.js → apl-CFsVwJXV.js} +1 -1
  51. package/dist/assets/web/dist/assets/{astro-cVaqxBZq.js → astro-CV6XNX3T.js} +1 -1
  52. package/dist/assets/web/dist/assets/{blade-MEP5mWha.js → blade-BGvEqTv7.js} +1 -1
  53. package/dist/assets/web/dist/assets/browser-BWnzWwaT.js +6 -0
  54. package/dist/assets/web/dist/assets/{c-D4NGQ_Pd.js → c-ViUYuFAx.js} +1 -1
  55. package/dist/assets/web/dist/assets/{chapel-BkWImsIy.js → chapel-CZ7_YbfP.js} +1 -1
  56. package/dist/assets/web/dist/assets/{cobol-RPDSPSET.js → cobol-Dc8kYvS6.js} +1 -1
  57. package/dist/assets/web/dist/assets/{coffee-DZ5z7oVX.js → coffee-C0musOvn.js} +1 -1
  58. package/dist/assets/web/dist/assets/{cpp-CrBtIaRZ.js → cpp-ToPQGcGd.js} +1 -1
  59. package/dist/assets/web/dist/assets/{crystal-DJesYi6f.js → crystal-DUJIaO8W.js} +1 -1
  60. package/dist/assets/web/dist/assets/{css-DO9mjau-.js → css-BTcEgWyQ.js} +1 -1
  61. package/dist/assets/web/dist/assets/{dist-XRrc0hhm.js → dist-I2OEmPqz.js} +2 -2
  62. package/dist/assets/web/dist/assets/{edge-C3VzbSW0.js → edge-D8sh1uAj.js} +1 -1
  63. package/dist/assets/web/dist/assets/{elixir-BkD_Vhfe.js → elixir-D5vOtHrB.js} +1 -1
  64. package/dist/assets/web/dist/assets/{elm-KIM0mFPk.js → elm-B_5xqTuz.js} +1 -1
  65. package/dist/assets/web/dist/assets/{erb-D2ruCqb2.js → erb-DBEct7_l.js} +1 -1
  66. package/dist/assets/web/dist/assets/{eventSource-Cv0AFlMi.js → eventSource-Ds6_gjPD.js} +1 -1
  67. package/dist/assets/web/dist/assets/{git-rebase-CnfbQTxA.js → git-rebase-lWoTL0Qh.js} +1 -1
  68. package/dist/assets/web/dist/assets/{glimmer-js-CbiwFXWH.js → glimmer-js-DNfSP4GT.js} +1 -1
  69. package/dist/assets/web/dist/assets/{glimmer-ts-DV8ii0Pv.js → glimmer-ts-DpPeWMLs.js} +1 -1
  70. package/dist/assets/web/dist/assets/{glsl-CgmznWkA.js → glsl-m6Ziz8q0.js} +1 -1
  71. package/dist/assets/web/dist/assets/{graphql-BK-bBYfH.js → graphql-shD9521e.js} +1 -1
  72. package/dist/assets/web/dist/assets/{hack-DnwTTP5n.js → hack-D_Mf_2gv.js} +1 -1
  73. package/dist/assets/web/dist/assets/{haml-DAVJsWwT.js → haml-BuEAF47S.js} +1 -1
  74. package/dist/assets/web/dist/assets/{handlebars-voOVfxFL.js → handlebars-D2aiSYtv.js} +1 -1
  75. package/dist/assets/web/dist/assets/{html-3FTqwaPk.js → html-CRjXJZot.js} +1 -1
  76. package/dist/assets/web/dist/assets/{html-derivative-CSlhLDQn.js → html-derivative-CYpQX_KK.js} +1 -1
  77. package/dist/assets/web/dist/assets/{http-Vx4ilPfS.js → http-D6vXMpc_.js} +1 -1
  78. package/dist/assets/web/dist/assets/{hurl-DxyZDE3R.js → hurl-B634cMJT.js} +1 -1
  79. package/dist/assets/web/dist/assets/{java-nWqdjNJG.js → java-BlINdAve.js} +1 -1
  80. package/dist/assets/web/dist/assets/{javascript-Dr82HOwp.js → javascript-BBEMmUOu.js} +1 -1
  81. package/dist/assets/web/dist/assets/{jinja-BhjLrPOt.js → jinja-BIA8TYHJ.js} +1 -1
  82. package/dist/assets/web/dist/assets/{jison-DilGIUa8.js → jison-aepfTbzH.js} +1 -1
  83. package/dist/assets/web/dist/assets/{json-CrOcvmhJ.js → json-DmYZ1boG.js} +1 -1
  84. package/dist/assets/web/dist/assets/{jsx-BW_RX-fo.js → jsx-GkaaF4_W.js} +1 -1
  85. package/dist/assets/web/dist/assets/{julia-CWH071DB.js → julia-DRtMztOz.js} +1 -1
  86. package/dist/assets/web/dist/assets/{just-DkhhoWBy.js → just-PvoeEWoy.js} +1 -1
  87. package/dist/assets/web/dist/assets/{latex-cuHJ4TUT.js → latex-Bujuo3AZ.js} +1 -1
  88. package/dist/assets/web/dist/assets/{liquid-DG6afYQN.js → liquid-B0h0HkS3.js} +1 -1
  89. package/dist/assets/web/dist/assets/{lua-C5CMnCPS.js → lua-6O-O1CSO.js} +1 -1
  90. package/dist/assets/web/dist/assets/main-DYwKyLY5.js +28 -0
  91. package/dist/assets/web/dist/assets/{marko-Dy2_DuC4.js → marko-XS_tpV0N.js} +1 -1
  92. package/dist/assets/web/dist/assets/{mdc-BMKb_EeA.js → mdc-DxULuTS1.js} +1 -1
  93. package/dist/assets/web/dist/assets/{nginx-og0RgMKf.js → nginx-n2RAfCKE.js} +1 -1
  94. package/dist/assets/web/dist/assets/{nim-CQxTQQP0.js → nim-DJxdqD8U.js} +1 -1
  95. package/dist/assets/web/dist/assets/{org-b5lkq_Pl.js → org-R9yxwZ2v.js} +1 -1
  96. package/dist/assets/web/dist/assets/{perl-D9X5_i_J.js → perl-zKAUtl73.js} +1 -1
  97. package/dist/assets/web/dist/assets/{php-CwUxvLX0.js → php-Dk50x1WE.js} +1 -1
  98. package/dist/assets/web/dist/assets/{pug-uOIOExr0.js → pug-C3TSg8JJ.js} +1 -1
  99. package/dist/assets/web/dist/assets/{qml-g5RoADf-.js → qml-CL1nqvCt.js} +1 -1
  100. package/dist/assets/web/dist/assets/{r-5LdAopD3.js → r-Bo44Yl7y.js} +1 -1
  101. package/dist/assets/web/dist/assets/{razor-DwrvFLYQ.js → razor-k2Dt2qXz.js} +1 -1
  102. package/dist/assets/web/dist/assets/{regexp-dGtAaxC-.js → regexp-Uy_HNe6G.js} +1 -1
  103. package/dist/assets/web/dist/assets/{rst-wFdW0brr.js → rst-U0zcRJ60.js} +1 -1
  104. package/dist/assets/web/dist/assets/{ruby-DPglN6I9.js → ruby-DUyqwP37.js} +1 -1
  105. package/dist/assets/web/dist/assets/{sas-CUbCs40d.js → sas-CxC8bRy1.js} +1 -1
  106. package/dist/assets/web/dist/assets/{scss-B-DUiI1c.js → scss-DaqXj6RQ.js} +1 -1
  107. package/dist/assets/web/dist/assets/{shellscript-lGOV7Ram.js → shellscript-CI49IoFh.js} +1 -1
  108. package/dist/assets/web/dist/assets/{shellsession-BDWtAAjr.js → shellsession-DJxotIOK.js} +1 -1
  109. package/dist/assets/web/dist/assets/{soy-zod_dcOy.js → soy-BjVAVuCU.js} +1 -1
  110. package/dist/assets/web/dist/assets/{sql-BG0HSSoM.js → sql-CzxBKQd1.js} +1 -1
  111. package/dist/assets/web/dist/assets/{sseReplay-BsNQPLmJ.js → sseReplay-BDVyc8HX.js} +3 -3
  112. package/dist/assets/web/dist/assets/{stata-BVUMLp1W.js → stata-DLMv_9_X.js} +1 -1
  113. package/dist/assets/web/dist/assets/{surrealql-DiSUbHKF.js → surrealql-BzbVEZbO.js} +1 -1
  114. package/dist/assets/web/dist/assets/{svelte-DZenT45Y.js → svelte-DTSfopUt.js} +1 -1
  115. package/dist/assets/web/dist/assets/{templ-BWKRNM0-.js → templ-BPxFlKQj.js} +1 -1
  116. package/dist/assets/web/dist/assets/{tex-CbMzBlkK.js → tex-B_KndQwK.js} +1 -1
  117. package/dist/assets/web/dist/assets/{ts-tags-CZsVorDN.js → ts-tags-CG7tgvBl.js} +1 -1
  118. package/dist/assets/web/dist/assets/{tsx-R1r3mH4W.js → tsx-CN9eqIdJ.js} +1 -1
  119. package/dist/assets/web/dist/assets/{twig-BjPMyjKH.js → twig-CKPbB5ca.js} +1 -1
  120. package/dist/assets/web/dist/assets/{typescript-Bx-66VN_.js → typescript-BQAXVjHJ.js} +1 -1
  121. package/dist/assets/web/dist/assets/{typst-fW037Eo6.js → typst-D3EaP9Ui.js} +1 -1
  122. package/dist/assets/web/dist/assets/{vue-D1V_GNxW.js → vue-DuQwQ__D.js} +1 -1
  123. package/dist/assets/web/dist/assets/{vue-html-C-4pTZ-s.js → vue-html-UEJn5cLD.js} +1 -1
  124. package/dist/assets/web/dist/assets/{vue-vine-C-QsX3hR.js → vue-vine-DDdijZY2.js} +1 -1
  125. package/dist/assets/web/dist/assets/{wallClock-x0hjr0J_.js → wallClock-DaewxNMY.js} +1 -1
  126. package/dist/assets/web/dist/assets/{xml-BJTJXPgC.js → xml-BezNDynk.js} +1 -1
  127. package/dist/assets/web/dist/assets/{xsl-Xc9lukqe.js → xsl-Bzb2zIr8.js} +1 -1
  128. package/dist/assets/web/dist/assets/{yaml-Dw-FMsQa.js → yaml-DOccVRRG.js} +1 -1
  129. package/dist/cli.js +868 -226
  130. package/package.json +1 -1
  131. package/dist/assets/web/dist/assets/AppShell-DPJVhUGP.js +0 -1
  132. package/dist/assets/web/dist/assets/CostsPage-8HDgvuWB.js +0 -1
  133. package/dist/assets/web/dist/assets/DeliveryPage-QyS6_Q0m.js +0 -1
  134. package/dist/assets/web/dist/assets/HomePage-Bm40r0wF.js +0 -1
  135. package/dist/assets/web/dist/assets/InputMenu-BRV35UWP.js +0 -1
  136. package/dist/assets/web/dist/assets/NotFoundPage-CDjMfCcj.js +0 -1
  137. package/dist/assets/web/dist/assets/PendingTurnRow-BqY-EaZR.js +0 -1
  138. package/dist/assets/web/dist/assets/PlanePage-VmzjNLAz.js +0 -1
  139. package/dist/assets/web/dist/assets/ResidentDetailPage-hjX3tRCh.js +0 -1
  140. package/dist/assets/web/dist/assets/ResidentsIndexPage-BKGnlCEh.js +0 -1
  141. package/dist/assets/web/dist/assets/RunRoutePage-D9ztf-uE.js +0 -9
  142. package/dist/assets/web/dist/assets/RunsIndexPage-DTYQbhtr.js +0 -1
  143. package/dist/assets/web/dist/assets/RunsTabs-C7Ua3XX5.js +0 -1
  144. package/dist/assets/web/dist/assets/ScheduledPage-BAYR0tuv.js +0 -1
  145. package/dist/assets/web/dist/assets/SettingSelect-CEeA4iuC.js +0 -1
  146. package/dist/assets/web/dist/assets/SettingsPage-BlYBmCG9.js +0 -1
  147. package/dist/assets/web/dist/assets/SilentTurn-CHfzRQW8.js +0 -2
  148. package/dist/assets/web/dist/assets/SlackMark-DB0MGiF5.js +0 -1
  149. package/dist/assets/web/dist/assets/StatusDot-BMkUFVE8.js +0 -1
  150. package/dist/assets/web/dist/assets/Tooltip-Dfot16IA.js +0 -1
  151. package/dist/assets/web/dist/assets/UnitRoutePage-BdZoT-4d.js +0 -1
  152. package/dist/assets/web/dist/assets/browser-CCImen5O.js +0 -6
  153. package/dist/assets/web/dist/assets/main-B32N1vUh.js +0 -28
@@ -1,11 +1,16 @@
1
1
  import { afterEach, beforeEach, type TestContext } from "vitest";
2
+ import { systemClock } from "../../src/core/trace/clock.ts";
2
3
  import {
3
4
  assertNoPendingBackgroundTasks,
4
5
  backgroundTaskDiagnostics,
5
6
  beginBackgroundTaskDiagnostics,
6
7
  endBackgroundTaskDiagnostics,
7
8
  } from "./backgroundTasks.ts";
8
- import { MEMORY_DIAGNOSTICS_ANNOTATION, type MemoryTestAnnotation } from "./testDiagnosticsProtocol.ts";
9
+ import {
10
+ MEMORY_DIAGNOSTICS_ANNOTATION,
11
+ type MemoryTestAnnotation,
12
+ type MemoryTestOperation,
13
+ } from "./testDiagnosticsProtocol.ts";
9
14
 
10
15
  type TimerKind = "timeout" | "interval";
11
16
  type TimerHandler = (...args: unknown[]) => unknown;
@@ -24,6 +29,7 @@ interface RunningTest {
24
29
  name: string;
25
30
  trackTimers: boolean;
26
31
  timers: Map<ReturnType<typeof setTimeout>, PendingTimer>;
32
+ operations: MemoryTestOperation[];
27
33
  }
28
34
  interface TimerOriginals {
29
35
  setTimeout: typeof globalThis.setTimeout;
@@ -167,6 +173,40 @@ function poolWorker(): string {
167
173
  return `pool ${poolId ?? "unknown"} / worker ${workerId ?? "unknown"}`;
168
174
  }
169
175
 
176
+ export async function recordMemoryTestOperation<T>(label: string, run: () => Promise<T>): Promise<T> {
177
+ const running = diagnosticGlobal()[RUNNING];
178
+ const operation: MemoryTestOperation = { label, state: "pending" };
179
+ running?.operations.push(operation);
180
+ const started = systemClock();
181
+ try {
182
+ const result = await run();
183
+ operation.state = "passed";
184
+ return result;
185
+ } catch (error) {
186
+ operation.state = "failed";
187
+ throw error;
188
+ } finally {
189
+ operation.durationMs = Math.max(0, systemClock() - started);
190
+ }
191
+ }
192
+
193
+ export function recordMemoryTestRequest(label: string, fetch: () => Promise<Response>): Promise<Response>;
194
+ export function recordMemoryTestRequest<T>(
195
+ label: string,
196
+ fetch: () => Promise<Response>,
197
+ consume: (response: Response) => T | Promise<T>,
198
+ ): Promise<T>;
199
+ export function recordMemoryTestRequest<T>(
200
+ label: string,
201
+ fetch: () => Promise<Response>,
202
+ consume?: (response: Response) => T | Promise<T>,
203
+ ): Promise<Response | T> {
204
+ return recordMemoryTestOperation(label, async () => {
205
+ const response = await fetch();
206
+ return consume ? consume(response) : response;
207
+ });
208
+ }
209
+
170
210
  function pendingTimerLabels(running: RunningTest): string[] {
171
211
  running.trackTimers = false;
172
212
  return [...running.timers.values()].map((timer) => timer.label);
@@ -191,19 +231,20 @@ export function installMemoryTestDiagnostics(): void {
191
231
  installTimerTrap();
192
232
 
193
233
  beforeEach((context) => {
194
- diagnosticGlobal()[RUNNING] = { name: testName(context), trackTimers: true, timers: new Map() };
234
+ diagnosticGlobal()[RUNNING] = { name: testName(context), trackTimers: true, timers: new Map(), operations: [] };
195
235
  beginBackgroundTaskDiagnostics();
196
236
  });
197
237
 
198
238
  afterEach((context) => {
199
239
  const root = diagnosticGlobal();
200
- const running = root[RUNNING] ?? { name: testName(context), trackTimers: false, timers: new Map() };
240
+ const running = root[RUNNING] ?? { name: testName(context), trackTimers: false, timers: new Map(), operations: [] };
201
241
  const background = backgroundTaskDiagnostics();
202
242
  const timers = pendingTimerLabels(running);
203
243
  const annotation: MemoryTestAnnotation = {
204
244
  poolWorker: poolWorker(),
205
245
  ...background,
206
246
  pendingTimers: timers,
247
+ operations: running.operations.map((operation) => ({ ...operation })),
207
248
  };
208
249
 
209
250
  const problems: Error[] = [];
@@ -1,10 +1,17 @@
1
1
  export const MEMORY_DIAGNOSTICS_ANNOTATION = "memory-worker-diagnostics";
2
2
 
3
+ export interface MemoryTestOperation {
4
+ label: string;
5
+ state: "pending" | "passed" | "failed";
6
+ durationMs?: number;
7
+ }
8
+
3
9
  export interface MemoryTestAnnotation {
4
10
  poolWorker: string;
5
11
  registeredBackgroundTasks: string[];
6
12
  pendingPromises: string[];
7
13
  pendingTimers: string[];
14
+ operations: MemoryTestOperation[];
8
15
  }
9
16
 
10
17
  export interface MemoryTestDiagnostic extends MemoryTestAnnotation {
@@ -30,6 +30,7 @@ function annotationFor(testCase: TestCase): MemoryTestAnnotation {
30
30
  registeredBackgroundTasks: [],
31
31
  pendingPromises: [],
32
32
  pendingTimers: [],
33
+ operations: [],
33
34
  };
34
35
  }
35
36
 
@@ -84,5 +85,26 @@ export class MemoryDiagnosticsReporter implements Reporter {
84
85
  ? "no completed tests"
85
86
  : slowest.map((test) => `${Math.round(test.durationMs)}ms ${test.test}`).join("; ");
86
87
  this.#writeLog(`[memory diagnostics] slowest ${slowest.length}: ${summary}`);
88
+ for (const test of this.#tests) {
89
+ if (test.operations.length === 0 || (test.state !== "failed" && !slowest.includes(test))) continue;
90
+ const selected =
91
+ test.state === "failed"
92
+ ? [
93
+ ...new Set([
94
+ ...test.operations.filter((operation) => operation.state === "pending"),
95
+ ...test.operations.slice(-8),
96
+ ]),
97
+ ]
98
+ : [...test.operations].sort((a, b) => (b.durationMs ?? Infinity) - (a.durationMs ?? Infinity)).slice(0, 3);
99
+ const operations = selected
100
+ .map(
101
+ (operation) =>
102
+ `${operation.label} ${operation.state} ${operation.durationMs === undefined ? "pending" : `${operation.durationMs}ms`}`,
103
+ )
104
+ .join("; ");
105
+ this.#writeLog(
106
+ `[memory diagnostics] ${test.test}: ${selected.length}/${test.operations.length} operation(s): ${operations}`,
107
+ );
108
+ }
87
109
  }
88
110
  }
@@ -0,0 +1,21 @@
1
+ import { SELF } from "cloudflare:test";
2
+ import { recordMemoryTestRequest } from "./testDiagnostics.ts";
3
+
4
+ /** A request stays pending until the test has consumed its response body. */
5
+ export function fetchMemoryTest(url: string, init?: RequestInit): Promise<Response>;
6
+ export function fetchMemoryTest<T>(
7
+ url: string,
8
+ init: RequestInit | undefined,
9
+ consume: (response: Response) => T | Promise<T>,
10
+ ): Promise<T>;
11
+ export function fetchMemoryTest<T>(
12
+ url: string,
13
+ init?: RequestInit,
14
+ consume?: (response: Response) => T | Promise<T>,
15
+ ): Promise<Response | T> {
16
+ const method = init?.method ?? "GET";
17
+ const pathname = new URL(url).pathname;
18
+ return consume
19
+ ? recordMemoryTestRequest(`${method} ${pathname}`, () => SELF.fetch(url, init), consume)
20
+ : recordMemoryTestRequest(`${method} ${pathname}`, () => SELF.fetch(url, init));
21
+ }
@@ -1674,6 +1674,11 @@ export class RunHistoryDO extends DurableObject<Env> {
1674
1674
  tools_json TEXT NOT NULL,
1675
1675
  state_json TEXT NOT NULL
1676
1676
  );
1677
+ CREATE TABLE IF NOT EXISTS resident_claim_clock (
1678
+ id INTEGER PRIMARY KEY CHECK (id = 1),
1679
+ value INTEGER NOT NULL
1680
+ );
1681
+ INSERT OR IGNORE INTO resident_claim_clock (id, value) VALUES (1, 0);
1677
1682
  CREATE TABLE IF NOT EXISTS run_steps (
1678
1683
  run_id TEXT NOT NULL,
1679
1684
  step INTEGER NOT NULL,
@@ -2920,6 +2925,33 @@ export class RunHistoryDO extends DurableObject<Env> {
2920
2925
  return r ? rowToLive(r) : undefined;
2921
2926
  }
2922
2927
 
2928
+ /** A later ledger owner gets a larger attachment fence, even for the same run ID. */
2929
+ async residentClaim(
2930
+ runId: string,
2931
+ gen: string,
2932
+ threadKey: string,
2933
+ ): Promise<{ ok: true; fence: number } | { ok: false; reason: "fenced" | "unknown-run" }> {
2934
+ let out: { ok: true; fence: number } | { ok: false; reason: "fenced" | "unknown-run" } = {
2935
+ ok: false,
2936
+ reason: "unknown-run",
2937
+ };
2938
+ this.ctx.storage.transactionSync(() => {
2939
+ const row = this.liveRow(runId);
2940
+ if (!row) return;
2941
+ if (row.ownerGen !== gen || row.threadKey !== threadKey) {
2942
+ out = { ok: false, reason: "fenced" };
2943
+ return;
2944
+ }
2945
+ this.sql.exec(`UPDATE resident_claim_clock SET value = value + 1 WHERE id = 1`);
2946
+ const fence = this.sql
2947
+ .exec<{ value: number }>(`SELECT value FROM resident_claim_clock WHERE id = 1`)
2948
+ .toArray()[0]?.value;
2949
+ if (fence === undefined || !Number.isSafeInteger(fence)) throw new Error("resident claim fence unavailable");
2950
+ out = { ok: true, fence };
2951
+ });
2952
+ return out;
2953
+ }
2954
+
2923
2955
  /** The `runs` table's column migrations, run at every construction and
2924
2956
  * idempotent: a table created before a column existed gains it, with the
2925
2957
  * value a row written back then should read. The run-visibility stamp: a
@@ -5318,6 +5350,7 @@ const LEDGER_ROUTES = new Set([
5318
5350
  "/runs/coordinator/wake",
5319
5351
  "/runs/decision-record/reserve",
5320
5352
  "/runs/claim",
5353
+ "/runs/resident-claim",
5321
5354
  "/runs/heartbeat",
5322
5355
  "/runs/append",
5323
5356
  "/runs/live-state",
@@ -5924,6 +5957,16 @@ async function handleLedger(pathname: string, body: unknown, env: Env): Promise<
5924
5957
  );
5925
5958
  return r.ok ? json(r) : json(r, 409);
5926
5959
  }
5960
+ if (pathname === "/runs/resident-claim") {
5961
+ const runId = parseRunId(b.runId);
5962
+ if (!runId.ok) return json({ error: runId.error }, 400);
5963
+ const g = gen(b.gen);
5964
+ if (!g.ok) return json({ error: g.error }, 400);
5965
+ if (typeof b.threadKey !== "string" || b.threadKey.length === 0 || b.threadKey.length > 256)
5966
+ return json({ error: "threadKey must be a non-empty string" }, 400);
5967
+ const result = await stub.residentClaim(runId.value, g.value, b.threadKey);
5968
+ return result.ok ? json(result) : json(result, 409);
5969
+ }
5927
5970
  if (pathname === "/runs/live") return json({ runs: await stub.listLive() });
5928
5971
  if (pathname === "/runs/reclaim") {
5929
5972
  const g = gen(b.gen);
@@ -0,0 +1,68 @@
1
+ /** A command is counted separately. Between commands, a registration protects
2
+ * the run only through its budget or the sweep's inactivity boundary. */
3
+ export function registeredRunNeedsProtection(
4
+ lastAttachAt: string | undefined,
5
+ opInFlight: number,
6
+ cutoff: number,
7
+ deadlineAt?: number,
8
+ now?: number,
9
+ ): boolean {
10
+ if (opInFlight > 0) return false; // the operation counter already covers it
11
+ if (deadlineAt !== undefined && now !== undefined && Number.isSafeInteger(deadlineAt)) return now <= deadlineAt;
12
+ const last = Date.parse(lastAttachAt ?? "");
13
+ return !Number.isFinite(last) || last >= cutoff;
14
+ }
15
+
16
+ /** Only a provably live registration owned by this run can bypass the gates
17
+ * that keep new work off a draining or stale resident. */
18
+ export function registeredRunAllowsClaim(
19
+ registration: { runId?: string; ownerGen?: string; ownerFence?: number } | undefined,
20
+ runId: string | undefined,
21
+ ownerGen: string | undefined,
22
+ ownerFence?: number,
23
+ ): boolean {
24
+ if (!registration) return true;
25
+ if (ownerFence !== undefined && registration.ownerFence !== undefined)
26
+ return (
27
+ ownerFence > registration.ownerFence ||
28
+ (ownerFence === registration.ownerFence && registration.runId === runId && registration.ownerGen === ownerGen)
29
+ );
30
+ if (registration.ownerFence !== undefined) return false;
31
+ if (ownerFence !== undefined) return true; // first ledger-backed claim over a legacy row
32
+ return registration.runId === runId && registration.ownerGen === ownerGen;
33
+ }
34
+
35
+ export function registeredRunAllowsReattach(
36
+ registration: { runId?: string; ownerGen?: string; ownerFence?: number; deadlineAt?: number } | undefined,
37
+ runId: string | undefined,
38
+ now: number,
39
+ graceMs: number,
40
+ ownerGen?: string,
41
+ ownerFence?: number,
42
+ ): boolean {
43
+ const deadlineAt = registration?.deadlineAt;
44
+ return (
45
+ runId !== undefined &&
46
+ registration?.runId === runId &&
47
+ registeredRunAllowsClaim(registration, runId, ownerGen, ownerFence) &&
48
+ deadlineAt !== undefined &&
49
+ Number.isSafeInteger(deadlineAt) &&
50
+ now <= deadlineAt + graceMs
51
+ );
52
+ }
53
+
54
+ /** A release may affect only the registration opened by its run. Legacy
55
+ * callers may release legacy registrations, but cannot release a named run. */
56
+ export function registeredRunOwnsRelease(
57
+ registration: { runId?: string; ownerGen?: string; ownerFence?: number } | undefined,
58
+ runId: string | undefined,
59
+ ownerGen?: string,
60
+ ownerFence?: number,
61
+ ): boolean {
62
+ return (
63
+ registration !== undefined &&
64
+ registration.runId === runId &&
65
+ registration.ownerGen === ownerGen &&
66
+ registration.ownerFence === ownerFence
67
+ );
68
+ }