@earendil-works/pi-durable 1.2.0 → 1.4.0

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 (142) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +128 -3
  3. package/dist/codemode/codemode.d.ts.map +1 -1
  4. package/dist/codemode/codemode.js +12 -4
  5. package/dist/codemode/codemode.js.map +1 -1
  6. package/dist/codemode/models.d.ts.map +1 -1
  7. package/dist/codemode/models.js +2 -0
  8. package/dist/codemode/models.js.map +1 -1
  9. package/dist/documents.d.ts +1 -1
  10. package/dist/documents.d.ts.map +1 -1
  11. package/dist/documents.js.map +1 -1
  12. package/dist/env/index.d.ts +1 -0
  13. package/dist/env/index.d.ts.map +1 -1
  14. package/dist/env/index.js.map +1 -1
  15. package/dist/env/node.d.ts.map +1 -1
  16. package/dist/env/node.js +11 -3
  17. package/dist/env/node.js.map +1 -1
  18. package/dist/harness/agent.d.ts +13 -2
  19. package/dist/harness/agent.d.ts.map +1 -1
  20. package/dist/harness/agent.js +39 -5
  21. package/dist/harness/agent.js.map +1 -1
  22. package/dist/harness/compaction.d.ts +3 -1
  23. package/dist/harness/compaction.d.ts.map +1 -1
  24. package/dist/harness/compaction.js +19 -4
  25. package/dist/harness/compaction.js.map +1 -1
  26. package/dist/harness/context.d.ts +5 -0
  27. package/dist/harness/context.d.ts.map +1 -1
  28. package/dist/harness/context.js +46 -12
  29. package/dist/harness/context.js.map +1 -1
  30. package/dist/harness/generation.d.ts.map +1 -1
  31. package/dist/harness/generation.js +67 -27
  32. package/dist/harness/generation.js.map +1 -1
  33. package/dist/harness/harness.d.ts +5 -2
  34. package/dist/harness/harness.d.ts.map +1 -1
  35. package/dist/harness/harness.js +78 -13
  36. package/dist/harness/harness.js.map +1 -1
  37. package/dist/harness/inbox.d.ts +2 -2
  38. package/dist/harness/inbox.d.ts.map +1 -1
  39. package/dist/harness/inbox.js +3 -3
  40. package/dist/harness/inbox.js.map +1 -1
  41. package/dist/harness/prompt.d.ts +1 -1
  42. package/dist/harness/prompt.d.ts.map +1 -1
  43. package/dist/harness/prompt.js +1 -1
  44. package/dist/harness/prompt.js.map +1 -1
  45. package/dist/harness/scheduler.d.ts +55 -10
  46. package/dist/harness/scheduler.d.ts.map +1 -1
  47. package/dist/harness/scheduler.js +617 -124
  48. package/dist/harness/scheduler.js.map +1 -1
  49. package/dist/harness/submissions.d.ts +7 -2
  50. package/dist/harness/submissions.d.ts.map +1 -1
  51. package/dist/harness/submissions.js +48 -17
  52. package/dist/harness/submissions.js.map +1 -1
  53. package/dist/harness/task-graph.d.ts +18 -1
  54. package/dist/harness/task-graph.d.ts.map +1 -1
  55. package/dist/harness/task-graph.js +50 -11
  56. package/dist/harness/task-graph.js.map +1 -1
  57. package/dist/harness/telemetry.d.ts +307 -0
  58. package/dist/harness/telemetry.d.ts.map +1 -0
  59. package/dist/harness/telemetry.js +136 -0
  60. package/dist/harness/telemetry.js.map +1 -0
  61. package/dist/harness/tool.d.ts.map +1 -1
  62. package/dist/harness/tool.js +54 -8
  63. package/dist/harness/tool.js.map +1 -1
  64. package/dist/harness/types.d.ts +110 -15
  65. package/dist/harness/types.d.ts.map +1 -1
  66. package/dist/harness/types.js.map +1 -1
  67. package/dist/harness/util.d.ts +4 -0
  68. package/dist/harness/util.d.ts.map +1 -1
  69. package/dist/harness/util.js +4 -0
  70. package/dist/harness/util.js.map +1 -1
  71. package/dist/index.d.ts +6 -4
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +3 -2
  74. package/dist/index.js.map +1 -1
  75. package/dist/session/session.d.ts +17 -6
  76. package/dist/session/session.d.ts.map +1 -1
  77. package/dist/session/session.js +64 -32
  78. package/dist/session/session.js.map +1 -1
  79. package/dist/session/transaction.d.ts +5 -1
  80. package/dist/session/transaction.d.ts.map +1 -1
  81. package/dist/session/transaction.js +26 -1
  82. package/dist/session/transaction.js.map +1 -1
  83. package/dist/storage/indexeddb.d.ts +60 -0
  84. package/dist/storage/indexeddb.d.ts.map +1 -0
  85. package/dist/storage/indexeddb.js +831 -0
  86. package/dist/storage/indexeddb.js.map +1 -0
  87. package/dist/storage/jsonl/storage.d.ts +16 -0
  88. package/dist/storage/jsonl/storage.d.ts.map +1 -1
  89. package/dist/storage/memory.js +1 -1
  90. package/dist/storage/memory.js.map +1 -1
  91. package/dist/storage/sqlite/cloudflare.d.ts +6 -43
  92. package/dist/storage/sqlite/cloudflare.d.ts.map +1 -1
  93. package/dist/storage/sqlite/cloudflare.js +6 -101
  94. package/dist/storage/sqlite/cloudflare.js.map +1 -1
  95. package/dist/storage/sqlite/database.d.ts +1 -36
  96. package/dist/storage/sqlite/database.d.ts.map +1 -1
  97. package/dist/storage/sqlite/database.js.map +1 -1
  98. package/dist/storage/sqlite/index.d.ts +2 -2
  99. package/dist/storage/sqlite/index.d.ts.map +1 -1
  100. package/dist/storage/sqlite/index.js.map +1 -1
  101. package/dist/storage/sqlite/migrations.d.ts +7 -6
  102. package/dist/storage/sqlite/migrations.d.ts.map +1 -1
  103. package/dist/storage/sqlite/migrations.js +116 -106
  104. package/dist/storage/sqlite/migrations.js.map +1 -1
  105. package/dist/storage/sqlite/node.d.ts +6 -39
  106. package/dist/storage/sqlite/node.d.ts.map +1 -1
  107. package/dist/storage/sqlite/node.js +8 -175
  108. package/dist/storage/sqlite/node.js.map +1 -1
  109. package/dist/storage/sqlite/schema.d.ts +45 -0
  110. package/dist/storage/sqlite/schema.d.ts.map +1 -0
  111. package/dist/storage/sqlite/schema.js +50 -0
  112. package/dist/storage/sqlite/schema.js.map +1 -0
  113. package/dist/storage/sqlite/storage.d.ts +10 -2
  114. package/dist/storage/sqlite/storage.d.ts.map +1 -1
  115. package/dist/storage/sqlite/storage.js +46 -43
  116. package/dist/storage/sqlite/storage.js.map +1 -1
  117. package/dist/testing/storage-conformance.d.ts.map +1 -1
  118. package/dist/testing/storage-conformance.js +17 -0
  119. package/dist/testing/storage-conformance.js.map +1 -1
  120. package/dist/timers.d.ts +21 -0
  121. package/dist/timers.d.ts.map +1 -0
  122. package/dist/timers.js +72 -0
  123. package/dist/timers.js.map +1 -0
  124. package/dist/tools/image-processor.d.ts +4 -1
  125. package/dist/tools/image-processor.d.ts.map +1 -1
  126. package/dist/tools/image-processor.js +8 -18
  127. package/dist/tools/image-processor.js.map +1 -1
  128. package/dist/tools/read.d.ts.map +1 -1
  129. package/dist/tools/read.js +1 -1
  130. package/dist/tools/read.js.map +1 -1
  131. package/dist/truncate.d.ts +3 -2
  132. package/dist/truncate.d.ts.map +1 -1
  133. package/dist/truncate.js +4 -34
  134. package/dist/truncate.js.map +1 -1
  135. package/dist/types.d.ts +130 -9
  136. package/dist/types.d.ts.map +1 -1
  137. package/dist/types.js.map +1 -1
  138. package/package.json +15 -5
  139. package/dist/harness/output.d.ts +0 -69
  140. package/dist/harness/output.d.ts.map +0 -1
  141. package/dist/harness/output.js +0 -318
  142. package/dist/harness/output.js.map +0 -1
@@ -1,13 +1,18 @@
1
1
  import { type Context } from "@earendil-works/chord";
2
+ import { type TelemetryContext, type TelemetrySpan } from "@earendil-works/pi-telemetry";
2
3
  import type { SessionImpl } from "../session/session.ts";
3
4
  import type { ConversationId, Storage, SubmissionId, SubmissionRecord, Tx } from "../types.ts";
4
5
  import { type QueueModes } from "./inbox.ts";
5
6
  import type { SettledSubmissionRecord, Submission, SubmissionDraft } from "./types.ts";
6
7
  type AbortResult = "aborted" | "already_placed" | "settled";
8
+ type Admission = {
9
+ readonly id: SubmissionId;
10
+ readonly status: "queued" | "placed" | "done";
11
+ };
7
12
  /** Admission, waits, and withdrawal of the durable submissions of one Harness. */
8
13
  export declare class Submissions {
9
14
  #private;
10
- constructor(session: SessionImpl, storage: Storage, now: () => number, queueModes: () => QueueModes, resume: () => void);
15
+ constructor(session: SessionImpl, storage: Storage, now: () => number, queueModes: () => QueueModes, resume: () => void, telemetry?: TelemetryContext);
11
16
  /** Admit a submission in one commit; see `admitSubmission()`. */
12
17
  submit(conversationId: ConversationId, draft: SubmissionDraft, context: Context): Promise<Submission>;
13
18
  /** Handle for an existing submission, or `undefined`. */
@@ -27,6 +32,6 @@ export declare class Submissions {
27
32
  * own) and starts a run, and an idle write appends its entry and settles `done`, or `stale` when its head reaches
28
33
  * before the active range.
29
34
  */
30
- export declare function admitSubmission(tx: Tx, conversationId: ConversationId, draft: SubmissionDraft, now: number, queueModes: QueueModes): Promise<SubmissionId>;
35
+ export declare function admitSubmission(tx: Tx, conversationId: ConversationId, draft: SubmissionDraft, now: number, queueModes: QueueModes, span?: TelemetrySpan): Promise<Admission>;
31
36
  export {};
32
37
  //# sourceMappingURL=submissions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"submissions.d.ts","sourceRoot":"","sources":["../../src/harness/submissions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAqC,MAAM,uBAAuB,CAAC;AAExF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAEX,cAAc,EAEd,OAAO,EACP,YAAY,EACZ,gBAAgB,EAChB,EAAE,EACF,MAAM,aAAa,CAAC;AAErB,OAAO,EASN,KAAK,UAAU,EAEf,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAc,uBAAuB,EAAE,UAAU,EAAE,eAAe,EAAa,MAAM,YAAY,CAAC;AAG9G,KAAK,WAAW,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS,CAAC;AAE5D,kFAAkF;AAClF,qBAAa,WAAW;;IAWvB,YACC,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,MAAM,EACjB,UAAU,EAAE,MAAM,UAAU,EAC5B,MAAM,EAAE,MAAM,IAAI,EAYlB;IAED,iEAAiE;IAC3D,MAAM,CAAC,cAAc,EAAE,cAAc,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAO1G;IAED,yDAAyD;IACnD,GAAG,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAG7E;IAEK,MAAM,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAI1E;IAEK,IAAI,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAY/E;IAED,kHAAkH;IAClH,KAAK,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,GAAG,WAAW,CAAC,CAa7G;CAQD;AAwCD;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CACpC,EAAE,EAAE,EAAE,EACN,cAAc,EAAE,cAAc,EAC9B,KAAK,EAAE,eAAe,EACtB,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,UAAU,GACpB,OAAO,CAAC,YAAY,CAAC,CAgEvB"}
1
+ {"version":3,"file":"submissions.d.ts","sourceRoot":"","sources":["../../src/harness/submissions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAqC,MAAM,uBAAuB,CAAC;AAExF,OAAO,EAGN,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAEX,cAAc,EAEd,OAAO,EACP,YAAY,EACZ,gBAAgB,EAChB,EAAE,EACF,MAAM,aAAa,CAAC;AAErB,OAAO,EASN,KAAK,UAAU,EAEf,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EAAc,uBAAuB,EAAE,UAAU,EAAE,eAAe,EAAa,MAAM,YAAY,CAAC;AAG9G,KAAK,WAAW,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS,CAAC;AAC5D,KAAK,SAAS,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAA;CAAE,CAAC;AAE9F,kFAAkF;AAClF,qBAAa,WAAW;;IAYvB,YACC,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,MAAM,EACjB,UAAU,EAAE,MAAM,UAAU,EAC5B,MAAM,EAAE,MAAM,IAAI,EAClB,SAAS,GAAE,gBAAyC,EAapD;IAED,iEAAiE;IAC3D,MAAM,CAAC,cAAc,EAAE,cAAc,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAyB1G;IAED,yDAAyD;IACnD,GAAG,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAG7E;IAEK,MAAM,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAI1E;IAEK,IAAI,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAY/E;IAED,kHAAkH;IAClH,KAAK,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,GAAG,WAAW,CAAC,CAa7G;CAQD;AAwCD;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CACpC,EAAE,EAAE,EAAE,EACN,cAAc,EAAE,cAAc,EAC9B,KAAK,EAAE,eAAe,EACtB,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,UAAU,EACtB,IAAI,CAAC,EAAE,aAAa,GAClB,OAAO,CAAC,SAAS,CAAC,CA2EpB"}
@@ -1,11 +1,15 @@
1
1
  import { copyJson } from "@earendil-works/chord";
2
+ import { withTelemetry } from "@earendil-works/chord/context";
3
+ import { createTypedSpanStarter, NOOP_TELEMETRY_CONTEXT, } from "@earendil-works/pi-telemetry";
2
4
  import { ConversationBusy } from "../errors.js";
3
5
  import { startRun } from "./generation.js";
4
6
  import { appendInput, applyBoundary, hasQueuedReset, InboxDoc, isDisplayOnly, isStale, pickInputEntry, prepareBoundary, removeInboxItem, } from "./inbox.js";
5
7
  import { LiveDoc } from "./live.js";
8
+ import { DURABLE_TELEMETRY_SCHEMA } from "./telemetry.js";
6
9
  import { closedError, Waiters } from "./util.js";
7
10
  /** Admission, waits, and withdrawal of the durable submissions of one Harness. */
8
11
  export class Submissions {
12
+ #telemetry;
9
13
  #session;
10
14
  #storage;
11
15
  #now;
@@ -15,7 +19,8 @@ export class Submissions {
15
19
  #resume;
16
20
  #waiters = new Waiters();
17
21
  #closed = false;
18
- constructor(session, storage, now, queueModes, resume) {
22
+ constructor(session, storage, now, queueModes, resume, telemetry = NOOP_TELEMETRY_CONTEXT) {
23
+ this.#telemetry = telemetry;
19
24
  this.#session = session;
20
25
  this.#storage = storage;
21
26
  this.#now = now;
@@ -30,8 +35,24 @@ export class Submissions {
30
35
  /** Admit a submission in one commit; see `admitSubmission()`. */
31
36
  async submit(conversationId, draft, context) {
32
37
  this.#resume();
33
- const id = await this.#session.commitWith((tx) => admitSubmission(tx, conversationId, draft, this.#now(), this.#queueModes()), context);
34
- return new SubmissionHandle(id, this);
38
+ const parent = draft.type === "input" ? this.#telemetry : context.telemetry;
39
+ return createTypedSpanStarter(parent, [DURABLE_TELEMETRY_SCHEMA])("pi.durable.submit", {
40
+ "submission.type": draft.type,
41
+ "conversation.id": conversationId,
42
+ }, async (span) => {
43
+ if (draft.type === "input")
44
+ span.addLink({ context: context.telemetry });
45
+ try {
46
+ const { id, status } = await this.#session.commitWith((tx) => admitSubmission(tx, conversationId, draft, this.#now(), this.#queueModes(), span), withTelemetry(span, context));
47
+ span.setAttributes({ "submission.id": id, result: status });
48
+ return new SubmissionHandle(id, this);
49
+ }
50
+ catch (error) {
51
+ // Error text may contain caller content. Record only the status.
52
+ span.setStatus({ status: "error" });
53
+ throw error;
54
+ }
55
+ });
35
56
  }
36
57
  /** Handle for an existing submission, or `undefined`. */
37
58
  async get(id, context) {
@@ -123,7 +144,7 @@ function checkInputEntry(entry) {
123
144
  * own) and starts a run, and an idle write appends its entry and settles `done`, or `stale` when its head reaches
124
145
  * before the active range.
125
146
  */
126
- export async function admitSubmission(tx, conversationId, draft, now, queueModes) {
147
+ export async function admitSubmission(tx, conversationId, draft, now, queueModes, span) {
127
148
  if (draft.type === "input" && draft.entry !== undefined)
128
149
  checkInputEntry(draft.entry);
129
150
  if (draft.requestId !== undefined) {
@@ -132,27 +153,34 @@ export async function admitSubmission(tx, conversationId, draft, now, queueModes
132
153
  if (existing.type !== draft.type) {
133
154
  throw new Error(`Request ${draft.requestId} already identifies a submission of type ${existing.type}`);
134
155
  }
135
- return existing.id;
156
+ return { id: existing.id, status: existing.status === "unanswered" ? "done" : existing.status };
136
157
  }
137
158
  }
138
- const requestId = draft.requestId === undefined ? {} : { requestId: draft.requestId };
159
+ const fields = { conversationId, requestId: draft.requestId, trace: span?.propagation?.inject(span) };
139
160
  if (draft.type === "write" &&
140
161
  isDisplayOnly(draft.entry) &&
141
162
  !hasQueuedReset((await tx.doc(InboxDoc, conversationId)).items)) {
142
163
  const entry = await tx.appendEntry(conversationId, draft.entry);
143
- const write = { conversationId, ...requestId, type: "write", status: "done", entry: entry.id };
144
- return (await tx.createSubmission(write)).id;
164
+ const write = { ...fields, type: "write", status: "done", entry: entry.id };
165
+ return { id: (await tx.createSubmission(write)).id, status: "done" };
145
166
  }
146
167
  const live = await tx.doc(LiveDoc, conversationId);
147
168
  const busy = live.run !== undefined;
169
+ if (draft.type === "input" &&
170
+ draft.whenBusy === "steer" &&
171
+ live.run !== undefined &&
172
+ span?.propagation !== undefined) {
173
+ const run = await tx.task(live.run.taskId);
174
+ if (run?.trace !== undefined)
175
+ span.addLink({ context: span.propagation.extract(run.trace) });
176
+ }
148
177
  if (busy && draft.type === "input" && draft.whenBusy === "reject")
149
178
  throw new ConversationBusy(conversationId);
150
179
  // A boundary reads the table, so it is prepared before the first table write; a busy one needs none.
151
180
  const boundary = busy ? undefined : await prepareBoundary(tx, conversationId, queueModes);
152
181
  if (boundary === undefined || boundary.inbox.items.length > 0) {
153
182
  const { id } = await tx.createSubmission({
154
- conversationId,
155
- ...requestId,
183
+ ...fields,
156
184
  type: draft.type,
157
185
  status: "queued",
158
186
  });
@@ -170,25 +198,28 @@ export async function admitSubmission(tx, conversationId, draft, now, queueModes
170
198
  items.push({ id, mode, content, ...entry });
171
199
  }
172
200
  if (boundary === undefined)
173
- return id;
201
+ return { id, status: "queued" };
174
202
  const { users } = await applyBoundary(tx, boundary, "final", now);
175
203
  if (users.length > 0)
176
204
  await startRun(tx, conversationId, live, users);
177
- return id;
205
+ return { id, status: draft.type === "write" ? "done" : users.at(-1) === id ? "placed" : "queued" };
178
206
  }
179
207
  if (draft.type === "write") {
180
208
  if (isStale(boundary, draft.entry)) {
181
209
  const stale = { status: "unanswered", reason: "stale" };
182
- return (await tx.createSubmission({ conversationId, ...requestId, type: "write", ...stale })).id;
210
+ return {
211
+ id: (await tx.createSubmission({ ...fields, type: "write", ...stale })).id,
212
+ status: "done",
213
+ };
183
214
  }
184
215
  const entry = await tx.appendEntry(conversationId, draft.entry);
185
- const write = { conversationId, ...requestId, type: "write", status: "done", entry: entry.id };
186
- return (await tx.createSubmission(write)).id;
216
+ const write = { ...fields, type: "write", status: "done", entry: entry.id };
217
+ return { id: (await tx.createSubmission(write)).id, status: "done" };
187
218
  }
188
219
  const entry = await appendInput(tx, conversationId, draft.content, draft.entry, now);
189
- const input = { conversationId, ...requestId, type: "input", status: "placed", entry: entry.id };
220
+ const input = { ...fields, type: "input", status: "placed", entry: entry.id };
190
221
  const { id } = await tx.createSubmission(input);
191
222
  await startRun(tx, conversationId, live, [id]);
192
- return id;
223
+ return { id, status: "placed" };
193
224
  }
194
225
  //# sourceMappingURL=submissions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"submissions.js","sourceRoot":"","sources":["../../src/harness/submissions.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Context, copyJson, type JsonRepresentation } from \"@earendil-works/chord\";\nimport { ConversationBusy } from \"../errors.ts\";\nimport type { SessionImpl } from \"../session/session.ts\";\nimport type {\n\tCommitPublication,\n\tConversationId,\n\tJsonObject,\n\tStorage,\n\tSubmissionId,\n\tSubmissionRecord,\n\tTx,\n} from \"../types.ts\";\nimport { startRun } from \"./generation.ts\";\nimport {\n\tappendInput,\n\tapplyBoundary,\n\thasQueuedReset,\n\tInboxDoc,\n\tisDisplayOnly,\n\tisStale,\n\tpickInputEntry,\n\tprepareBoundary,\n\ttype QueueModes,\n\tremoveInboxItem,\n} from \"./inbox.ts\";\nimport { LiveDoc } from \"./live.ts\";\nimport type { InputEntry, SettledSubmissionRecord, Submission, SubmissionDraft, UserInput } from \"./types.ts\";\nimport { closedError, Waiters } from \"./util.ts\";\n\ntype AbortResult = \"aborted\" | \"already_placed\" | \"settled\";\n\n/** Admission, waits, and withdrawal of the durable submissions of one Harness. */\nexport class Submissions {\n\treadonly #session: SessionImpl;\n\treadonly #storage: Storage;\n\treadonly #now: () => number;\n\t/** Read at each admission, on the Session line. */\n\treadonly #queueModes: () => QueueModes;\n\t/** Enable task scheduling; submitting or waiting asks for progress. */\n\treadonly #resume: () => void;\n\treadonly #waiters = new Waiters<SubmissionId, SettledSubmissionRecord>();\n\t#closed = false;\n\n\tconstructor(\n\t\tsession: SessionImpl,\n\t\tstorage: Storage,\n\t\tnow: () => number,\n\t\tqueueModes: () => QueueModes,\n\t\tresume: () => void,\n\t) {\n\t\tthis.#session = session;\n\t\tthis.#storage = storage;\n\t\tthis.#now = now;\n\t\tthis.#queueModes = queueModes;\n\t\tthis.#resume = resume;\n\t\tsession.observeCommits((publication) => this.#observe(publication));\n\t\tsession.subscribeClose(() => {\n\t\t\tthis.#closed = true;\n\t\t\tthis.#waiters.rejectAll(closedError(this.#session));\n\t\t});\n\t}\n\n\t/** Admit a submission in one commit; see `admitSubmission()`. */\n\tasync submit(conversationId: ConversationId, draft: SubmissionDraft, context: Context): Promise<Submission> {\n\t\tthis.#resume();\n\t\tconst id = await this.#session.commitWith(\n\t\t\t(tx) => admitSubmission(tx, conversationId, draft, this.#now(), this.#queueModes()),\n\t\t\tcontext,\n\t\t);\n\t\treturn new SubmissionHandle(id, this);\n\t}\n\n\t/** Handle for an existing submission, or `undefined`. */\n\tasync get(id: SubmissionId, context: Context): Promise<Submission | undefined> {\n\t\tconst record = await this.#session.readOnLine(() => this.#storage.submission(id, context));\n\t\treturn record === undefined ? undefined : new SubmissionHandle(record.id, this);\n\t}\n\n\tasync status(id: SubmissionId, context: Context): Promise<SubmissionRecord> {\n\t\tconst record = await this.#session.readOnLine(() => this.#storage.submission(id, context));\n\t\tif (record === undefined) throw new Error(`Submission ${id} does not exist`);\n\t\treturn record;\n\t}\n\n\tasync wait(id: SubmissionId, context: Context): Promise<SettledSubmissionRecord> {\n\t\tthis.#resume();\n\t\t// Check and register on the line so no settling publication falls between them.\n\t\tconst found = await this.#session.readOnLine(async () => {\n\t\t\tconst record = await this.#storage.submission(id, context);\n\t\t\tif (record === undefined) throw new Error(`Submission ${id} does not exist`);\n\t\t\tif (isSettled(record)) return { promise: Promise.resolve(record) };\n\t\t\t// Close rejects registered waiters synchronously and may begin during the read.\n\t\t\tif (this.#closed) throw closedError(this.#session);\n\t\t\treturn { promise: this.#waiters.add(id, context) };\n\t\t});\n\t\treturn found.promise;\n\t}\n\n\t/** Withdraw a queued submission and remove its inbox item; placed inputs and settled submissions are reported. */\n\tabort(id: SubmissionId, context: Context, conversationId?: ConversationId): Promise<AbortResult | \"not_found\"> {\n\t\treturn this.#session.commitWith(async (tx) => {\n\t\t\tconst record = await tx.submission(id);\n\t\t\tif (record === undefined || (conversationId !== undefined && record.conversationId !== conversationId)) {\n\t\t\t\treturn \"not_found\";\n\t\t\t}\n\t\t\tif (record.status === \"queued\") {\n\t\t\t\ttx.settleSubmission(id, { status: \"unanswered\", reason: \"aborted\" });\n\t\t\t\tawait removeInboxItem(tx, record.conversationId, id);\n\t\t\t\treturn \"aborted\";\n\t\t\t}\n\t\t\treturn record.status === \"placed\" ? \"already_placed\" : \"settled\";\n\t\t}, context);\n\t}\n\n\t#observe(publication: CommitPublication): void {\n\t\tfor (const change of publication.changes) {\n\t\t\tif (change.type !== \"submission\" || !isSettled(change.value)) continue;\n\t\t\tthis.#waiters.resolve(change.value.id, change.value);\n\t\t}\n\t}\n}\n\nclass SubmissionHandle implements Submission {\n\treadonly id: SubmissionId;\n\treadonly #submissions: Submissions;\n\n\tconstructor(id: SubmissionId, submissions: Submissions) {\n\t\tthis.id = id;\n\t\tthis.#submissions = submissions;\n\t}\n\n\tstatus(context: Context): Promise<SubmissionRecord> {\n\t\treturn this.#submissions.status(this.id, context);\n\t}\n\n\twait(context: Context): Promise<SettledSubmissionRecord> {\n\t\treturn this.#submissions.wait(this.id, context);\n\t}\n\n\tasync abort(context: Context): Promise<AbortResult> {\n\t\tconst result = await this.#submissions.abort(this.id, context);\n\t\tif (result === \"not_found\") throw new Error(`Submission ${this.id} does not exist`);\n\t\treturn result;\n\t}\n}\n\nfunction isSettled(record: SubmissionRecord): record is SettledSubmissionRecord {\n\treturn record.status === \"done\" || record.status === \"unanswered\";\n}\n\n/** Reject an input entry an untyped caller got wrong: its kind must be a non-empty string outside the `pi.` prefix. */\nfunction checkInputEntry(entry: InputEntry): void {\n\tconst { kind } = entry;\n\tif (typeof kind !== \"string\" || kind.length === 0 || kind.startsWith(\"pi.\")) {\n\t\tthrow new TypeError(\n\t\t\t`An input entry's kind must be a non-empty string not starting with \"pi.\", got ${String(kind)}`,\n\t\t);\n\t}\n}\n\n/**\n * Admit a submission inside a commit (spec §6); `Conversation.submit()` and conversation-owned compactions share it. A\n * known request ID returns its existing submission without writing. A display-only write (only `kind` and `data`) is\n * appended and settled `done` at once, busy or not, as it changes nothing the model sees; only behind a queued reset it\n * queues, so it lands after the reset instead of before it in the context the reset ends. A busy conversation queues anything else in\n * `pi.inbox`, or rejects `whenBusy: \"reject\"` input with `ConversationBusy`. An idle conversation with queued items\n * queues it behind them and runs a final boundary. Otherwise idle input places its entry (`pi.user`, or the input's\n * own) and starts a run, and an idle write appends its entry and settles `done`, or `stale` when its head reaches\n * before the active range.\n */\nexport async function admitSubmission(\n\ttx: Tx,\n\tconversationId: ConversationId,\n\tdraft: SubmissionDraft,\n\tnow: number,\n\tqueueModes: QueueModes,\n): Promise<SubmissionId> {\n\tif (draft.type === \"input\" && draft.entry !== undefined) checkInputEntry(draft.entry);\n\tif (draft.requestId !== undefined) {\n\t\tconst existing = await tx.submissionByRequest(conversationId, draft.requestId);\n\t\tif (existing !== undefined) {\n\t\t\tif (existing.type !== draft.type) {\n\t\t\t\tthrow new Error(`Request ${draft.requestId} already identifies a submission of type ${existing.type}`);\n\t\t\t}\n\t\t\treturn existing.id;\n\t\t}\n\t}\n\tconst requestId = draft.requestId === undefined ? {} : { requestId: draft.requestId };\n\tif (\n\t\tdraft.type === \"write\" &&\n\t\tisDisplayOnly(draft.entry) &&\n\t\t!hasQueuedReset((await tx.doc(InboxDoc, conversationId)).items)\n\t) {\n\t\tconst entry = await tx.appendEntry(conversationId, draft.entry);\n\t\tconst write = { conversationId, ...requestId, type: \"write\", status: \"done\", entry: entry.id } as const;\n\t\treturn (await tx.createSubmission(write)).id;\n\t}\n\tconst live = await tx.doc(LiveDoc, conversationId);\n\tconst busy = live.run !== undefined;\n\tif (busy && draft.type === \"input\" && draft.whenBusy === \"reject\") throw new ConversationBusy(conversationId);\n\t// A boundary reads the table, so it is prepared before the first table write; a busy one needs none.\n\tconst boundary = busy ? undefined : await prepareBoundary(tx, conversationId, queueModes);\n\tif (boundary === undefined || boundary.inbox.items.length > 0) {\n\t\tconst { id } = await tx.createSubmission({\n\t\t\tconversationId,\n\t\t\t...requestId,\n\t\t\ttype: draft.type,\n\t\t\tstatus: \"queued\",\n\t\t});\n\t\t// Hosts may leave optional fields `undefined`; drafts take strict JSON.\n\t\tconst value = copyJson(draft.type === \"write\" ? draft.entry : draft.content, {\n\t\t\tomitUndefinedProperties: true,\n\t\t});\n\t\tconst items = (boundary?.inbox ?? (await tx.doc(InboxDoc, conversationId))).items;\n\t\tif (draft.type === \"write\") items.push({ id, mode: \"write\", entry: value as JsonObject });\n\t\telse {\n\t\t\tconst mode = draft.whenBusy === \"steer\" ? \"steer\" : \"followUp\";\n\t\t\tconst content = value as JsonRepresentation<UserInput>;\n\t\t\tconst entry = draft.entry === undefined ? {} : { entry: pickInputEntry(draft.entry) };\n\t\t\titems.push({ id, mode, content, ...entry });\n\t\t}\n\t\tif (boundary === undefined) return id;\n\t\tconst { users } = await applyBoundary(tx, boundary, \"final\", now);\n\t\tif (users.length > 0) await startRun(tx, conversationId, live, users);\n\t\treturn id;\n\t}\n\tif (draft.type === \"write\") {\n\t\tif (isStale(boundary, draft.entry)) {\n\t\t\tconst stale = { status: \"unanswered\", reason: \"stale\" } as const;\n\t\t\treturn (await tx.createSubmission({ conversationId, ...requestId, type: \"write\", ...stale })).id;\n\t\t}\n\t\tconst entry = await tx.appendEntry(conversationId, draft.entry);\n\t\tconst write = { conversationId, ...requestId, type: \"write\", status: \"done\", entry: entry.id } as const;\n\t\treturn (await tx.createSubmission(write)).id;\n\t}\n\tconst entry = await appendInput(tx, conversationId, draft.content, draft.entry, now);\n\tconst input = { conversationId, ...requestId, type: \"input\", status: \"placed\", entry: entry.id } as const;\n\tconst { id } = await tx.createSubmission(input);\n\tawait startRun(tx, conversationId, live, [id]);\n\treturn id;\n}\n"]}
1
+ {"version":3,"file":"submissions.js","sourceRoot":"","sources":["../../src/harness/submissions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,QAAQ,EAA2B,MAAM,uBAAuB,CAAC;AACxF,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EACN,sBAAsB,EACtB,sBAAsB,GAGtB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAWhD,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EACN,WAAW,EACX,aAAa,EACb,cAAc,EACd,QAAQ,EACR,aAAa,EACb,OAAO,EACP,cAAc,EACd,eAAe,EAEf,eAAe,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKjD,kFAAkF;AAClF,MAAM,OAAO,WAAW;IACd,UAAU,CAAmB;IAC7B,QAAQ,CAAc;IACtB,QAAQ,CAAU;IAClB,IAAI,CAAe;IAC5B,mDAAmD;IAC1C,WAAW,CAAmB;IACvC,uEAAuE;IAC9D,OAAO,CAAa;IACpB,QAAQ,GAAG,IAAI,OAAO,EAAyC,CAAC;IACzE,OAAO,GAAG,KAAK,CAAC;IAEhB,YACC,OAAoB,EACpB,OAAgB,EAChB,GAAiB,EACjB,UAA4B,EAC5B,MAAkB,EAClB,SAAS,GAAqB,sBAAsB;QAEpD,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC;QAC9B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,OAAO,CAAC,cAAc,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;QACpE,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE;YAC3B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,iEAAiE;IACjE,KAAK,CAAC,MAAM,CAAC,cAA8B,EAAE,KAAsB,EAAE,OAAgB;QACpF,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;QAC5E,OAAO,sBAAsB,CAAC,MAAM,EAAE,CAAC,wBAAwB,CAAC,CAAC,CAChE,mBAAmB,EACnB;YACC,iBAAiB,EAAE,KAAK,CAAC,IAAI;YAC7B,iBAAiB,EAAE,cAAc;SACjC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;YACd,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO;gBAAE,IAAI,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;YACzE,IAAI,CAAC;gBACJ,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,UAAU,CACpD,CAAC,EAAE,EAAE,EAAE,CAAC,eAAe,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,EACzF,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAC5B,CAAC;gBACF,IAAI,CAAC,aAAa,CAAC,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;gBAC5D,OAAO,IAAI,gBAAgB,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YACvC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,iEAAiE;gBACjE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;gBACpC,MAAM,KAAK,CAAC;YACb,CAAC;QACF,CAAC,CACD,CAAC;IACH,CAAC;IAED,yDAAyD;IACzD,KAAK,CAAC,GAAG,CAAC,EAAgB,EAAE,OAAgB;QAC3C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;QAC3F,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,gBAAgB,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACjF,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAgB,EAAE,OAAgB;QAC9C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;QAC3F,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAAC;QAC7E,OAAO,MAAM,CAAC;IACf,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAgB,EAAE,OAAgB;QAC5C,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,gFAAgF;QAChF,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,IAAI,EAAE;YACvD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;YAC3D,IAAI,MAAM,KAAK,SAAS;gBAAE,MAAM,IAAI,KAAK,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAAC;YAC7E,IAAI,SAAS,CAAC,MAAM,CAAC;gBAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACnE,gFAAgF;YAChF,IAAI,IAAI,CAAC,OAAO;gBAAE,MAAM,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACnD,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC;QACpD,CAAC,CAAC,CAAC;QACH,OAAO,KAAK,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,kHAAkH;IAClH,KAAK,CAAC,EAAgB,EAAE,OAAgB,EAAE,cAA+B;QACxE,OAAO,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;YAC5C,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;YACvC,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,cAAc,KAAK,SAAS,IAAI,MAAM,CAAC,cAAc,KAAK,cAAc,CAAC,EAAE,CAAC;gBACxG,OAAO,WAAW,CAAC;YACpB,CAAC;YACD,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAChC,EAAE,CAAC,gBAAgB,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;gBACrE,MAAM,eAAe,CAAC,EAAE,EAAE,MAAM,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;gBACrD,OAAO,SAAS,CAAC;YAClB,CAAC;YACD,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,SAAS,CAAC;QAClE,CAAC,EAAE,OAAO,CAAC,CAAC;IACb,CAAC;IAED,QAAQ,CAAC,WAA8B;QACtC,KAAK,MAAM,MAAM,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;YAC1C,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC;gBAAE,SAAS;YACvE,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QACtD,CAAC;IACF,CAAC;CACD;AAED,MAAM,gBAAgB;IACZ,EAAE,CAAe;IACjB,YAAY,CAAc;IAEnC,YAAY,EAAgB,EAAE,WAAwB;QACrD,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;IACjC,CAAC;IAED,MAAM,CAAC,OAAgB;QACtB,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IACnD,CAAC;IAED,IAAI,CAAC,OAAgB;QACpB,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IACjD,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,OAAgB;QAC3B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QAC/D,IAAI,MAAM,KAAK,WAAW;YAAE,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,CAAC,EAAE,iBAAiB,CAAC,CAAC;QACpF,OAAO,MAAM,CAAC;IACf,CAAC;CACD;AAED,SAAS,SAAS,CAAC,MAAwB;IAC1C,OAAO,MAAM,CAAC,MAAM,KAAK,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,YAAY,CAAC;AACnE,CAAC;AAED,uHAAuH;AACvH,SAAS,eAAe,CAAC,KAAiB;IACzC,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IACvB,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7E,MAAM,IAAI,SAAS,CAClB,iFAAiF,MAAM,CAAC,IAAI,CAAC,EAAE,CAC/F,CAAC;IACH,CAAC;AACF,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACpC,EAAM,EACN,cAA8B,EAC9B,KAAsB,EACtB,GAAW,EACX,UAAsB,EACtB,IAAoB;IAEpB,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;QAAE,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACtF,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,mBAAmB,CAAC,cAAc,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;QAC/E,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC5B,IAAI,QAAQ,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,EAAE,CAAC;gBAClC,MAAM,IAAI,KAAK,CAAC,WAAW,KAAK,CAAC,SAAS,4CAA4C,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;YACxG,CAAC;YACD,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QACjG,CAAC;IACF,CAAC;IACD,MAAM,MAAM,GAAG,EAAE,cAAc,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;IACtG,IACC,KAAK,CAAC,IAAI,KAAK,OAAO;QACtB,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC;QAC1B,CAAC,cAAc,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,EAC9D,CAAC;QACF,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,WAAW,CAAC,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QAChE,MAAM,KAAK,GAAG,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,EAAW,CAAC;QACrF,OAAO,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACtE,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC;IACpC,IACC,KAAK,CAAC,IAAI,KAAK,OAAO;QACtB,KAAK,CAAC,QAAQ,KAAK,OAAO;QAC1B,IAAI,CAAC,GAAG,KAAK,SAAS;QACtB,IAAI,EAAE,WAAW,KAAK,SAAS,EAC9B,CAAC;QACF,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,GAAG,EAAE,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC9F,CAAC;IACD,IAAI,IAAI,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,QAAQ,KAAK,QAAQ;QAAE,MAAM,IAAI,gBAAgB,CAAC,cAAc,CAAC,CAAC;IAC9G,qGAAqG;IACrG,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,eAAe,CAAC,EAAE,EAAE,cAAc,EAAE,UAAU,CAAC,CAAC;IAC1F,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/D,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,EAAE,CAAC,gBAAgB,CAAC;YACxC,GAAG,MAAM;YACT,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,MAAM,EAAE,QAAQ;SAChB,CAAC,CAAC;QACH,wEAAwE;QACxE,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE;YAC5E,uBAAuB,EAAE,IAAI;SAC7B,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,CAAC,QAAQ,EAAE,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QAClF,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAmB,EAAE,CAAC,CAAC;aACrF,CAAC;YACL,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;YAC/D,MAAM,OAAO,GAAG,KAAsC,CAAC;YACvD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YACtF,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;QAC7C,CAAC;QACD,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;QAC5D,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;QAClE,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,QAAQ,CAAC,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QACtE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACpG,CAAC;IACD,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC5B,IAAI,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YACpC,MAAM,KAAK,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAW,CAAC;YACjE,OAAO;gBACN,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,gBAAgB,CAAC,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE;gBAC1E,MAAM,EAAE,MAAM;aACd,CAAC;QACH,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,WAAW,CAAC,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QAChE,MAAM,KAAK,GAAG,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,EAAW,CAAC;QACrF,OAAO,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACtE,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACrF,MAAM,KAAK,GAAG,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,EAAW,CAAC;IACvF,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAChD,MAAM,QAAQ,CAAC,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/C,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AACjC,CAAC","sourcesContent":["import { type Context, copyJson, type JsonRepresentation } from \"@earendil-works/chord\";\nimport { withTelemetry } from \"@earendil-works/chord/context\";\nimport {\n\tcreateTypedSpanStarter,\n\tNOOP_TELEMETRY_CONTEXT,\n\ttype TelemetryContext,\n\ttype TelemetrySpan,\n} from \"@earendil-works/pi-telemetry\";\nimport { ConversationBusy } from \"../errors.ts\";\nimport type { SessionImpl } from \"../session/session.ts\";\nimport type {\n\tCommitPublication,\n\tConversationId,\n\tJsonObject,\n\tStorage,\n\tSubmissionId,\n\tSubmissionRecord,\n\tTx,\n} from \"../types.ts\";\nimport { startRun } from \"./generation.ts\";\nimport {\n\tappendInput,\n\tapplyBoundary,\n\thasQueuedReset,\n\tInboxDoc,\n\tisDisplayOnly,\n\tisStale,\n\tpickInputEntry,\n\tprepareBoundary,\n\ttype QueueModes,\n\tremoveInboxItem,\n} from \"./inbox.ts\";\nimport { LiveDoc } from \"./live.ts\";\nimport { DURABLE_TELEMETRY_SCHEMA } from \"./telemetry.ts\";\nimport type { InputEntry, SettledSubmissionRecord, Submission, SubmissionDraft, UserInput } from \"./types.ts\";\nimport { closedError, Waiters } from \"./util.ts\";\n\ntype AbortResult = \"aborted\" | \"already_placed\" | \"settled\";\ntype Admission = { readonly id: SubmissionId; readonly status: \"queued\" | \"placed\" | \"done\" };\n\n/** Admission, waits, and withdrawal of the durable submissions of one Harness. */\nexport class Submissions {\n\treadonly #telemetry: TelemetryContext;\n\treadonly #session: SessionImpl;\n\treadonly #storage: Storage;\n\treadonly #now: () => number;\n\t/** Read at each admission, on the Session line. */\n\treadonly #queueModes: () => QueueModes;\n\t/** Enable task scheduling; submitting or waiting asks for progress. */\n\treadonly #resume: () => void;\n\treadonly #waiters = new Waiters<SubmissionId, SettledSubmissionRecord>();\n\t#closed = false;\n\n\tconstructor(\n\t\tsession: SessionImpl,\n\t\tstorage: Storage,\n\t\tnow: () => number,\n\t\tqueueModes: () => QueueModes,\n\t\tresume: () => void,\n\t\ttelemetry: TelemetryContext = NOOP_TELEMETRY_CONTEXT,\n\t) {\n\t\tthis.#telemetry = telemetry;\n\t\tthis.#session = session;\n\t\tthis.#storage = storage;\n\t\tthis.#now = now;\n\t\tthis.#queueModes = queueModes;\n\t\tthis.#resume = resume;\n\t\tsession.observeCommits((publication) => this.#observe(publication));\n\t\tsession.subscribeClose(() => {\n\t\t\tthis.#closed = true;\n\t\t\tthis.#waiters.rejectAll(closedError(this.#session));\n\t\t});\n\t}\n\n\t/** Admit a submission in one commit; see `admitSubmission()`. */\n\tasync submit(conversationId: ConversationId, draft: SubmissionDraft, context: Context): Promise<Submission> {\n\t\tthis.#resume();\n\t\tconst parent = draft.type === \"input\" ? this.#telemetry : context.telemetry;\n\t\treturn createTypedSpanStarter(parent, [DURABLE_TELEMETRY_SCHEMA])(\n\t\t\t\"pi.durable.submit\",\n\t\t\t{\n\t\t\t\t\"submission.type\": draft.type,\n\t\t\t\t\"conversation.id\": conversationId,\n\t\t\t},\n\t\t\tasync (span) => {\n\t\t\t\tif (draft.type === \"input\") span.addLink({ context: context.telemetry });\n\t\t\t\ttry {\n\t\t\t\t\tconst { id, status } = await this.#session.commitWith(\n\t\t\t\t\t\t(tx) => admitSubmission(tx, conversationId, draft, this.#now(), this.#queueModes(), span),\n\t\t\t\t\t\twithTelemetry(span, context),\n\t\t\t\t\t);\n\t\t\t\t\tspan.setAttributes({ \"submission.id\": id, result: status });\n\t\t\t\t\treturn new SubmissionHandle(id, this);\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Error text may contain caller content. Record only the status.\n\t\t\t\t\tspan.setStatus({ status: \"error\" });\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\t}\n\n\t/** Handle for an existing submission, or `undefined`. */\n\tasync get(id: SubmissionId, context: Context): Promise<Submission | undefined> {\n\t\tconst record = await this.#session.readOnLine(() => this.#storage.submission(id, context));\n\t\treturn record === undefined ? undefined : new SubmissionHandle(record.id, this);\n\t}\n\n\tasync status(id: SubmissionId, context: Context): Promise<SubmissionRecord> {\n\t\tconst record = await this.#session.readOnLine(() => this.#storage.submission(id, context));\n\t\tif (record === undefined) throw new Error(`Submission ${id} does not exist`);\n\t\treturn record;\n\t}\n\n\tasync wait(id: SubmissionId, context: Context): Promise<SettledSubmissionRecord> {\n\t\tthis.#resume();\n\t\t// Check and register on the line so no settling publication falls between them.\n\t\tconst found = await this.#session.readOnLine(async () => {\n\t\t\tconst record = await this.#storage.submission(id, context);\n\t\t\tif (record === undefined) throw new Error(`Submission ${id} does not exist`);\n\t\t\tif (isSettled(record)) return { promise: Promise.resolve(record) };\n\t\t\t// Close rejects registered waiters synchronously and may begin during the read.\n\t\t\tif (this.#closed) throw closedError(this.#session);\n\t\t\treturn { promise: this.#waiters.add(id, context) };\n\t\t});\n\t\treturn found.promise;\n\t}\n\n\t/** Withdraw a queued submission and remove its inbox item; placed inputs and settled submissions are reported. */\n\tabort(id: SubmissionId, context: Context, conversationId?: ConversationId): Promise<AbortResult | \"not_found\"> {\n\t\treturn this.#session.commitWith(async (tx) => {\n\t\t\tconst record = await tx.submission(id);\n\t\t\tif (record === undefined || (conversationId !== undefined && record.conversationId !== conversationId)) {\n\t\t\t\treturn \"not_found\";\n\t\t\t}\n\t\t\tif (record.status === \"queued\") {\n\t\t\t\ttx.settleSubmission(id, { status: \"unanswered\", reason: \"aborted\" });\n\t\t\t\tawait removeInboxItem(tx, record.conversationId, id);\n\t\t\t\treturn \"aborted\";\n\t\t\t}\n\t\t\treturn record.status === \"placed\" ? \"already_placed\" : \"settled\";\n\t\t}, context);\n\t}\n\n\t#observe(publication: CommitPublication): void {\n\t\tfor (const change of publication.changes) {\n\t\t\tif (change.type !== \"submission\" || !isSettled(change.value)) continue;\n\t\t\tthis.#waiters.resolve(change.value.id, change.value);\n\t\t}\n\t}\n}\n\nclass SubmissionHandle implements Submission {\n\treadonly id: SubmissionId;\n\treadonly #submissions: Submissions;\n\n\tconstructor(id: SubmissionId, submissions: Submissions) {\n\t\tthis.id = id;\n\t\tthis.#submissions = submissions;\n\t}\n\n\tstatus(context: Context): Promise<SubmissionRecord> {\n\t\treturn this.#submissions.status(this.id, context);\n\t}\n\n\twait(context: Context): Promise<SettledSubmissionRecord> {\n\t\treturn this.#submissions.wait(this.id, context);\n\t}\n\n\tasync abort(context: Context): Promise<AbortResult> {\n\t\tconst result = await this.#submissions.abort(this.id, context);\n\t\tif (result === \"not_found\") throw new Error(`Submission ${this.id} does not exist`);\n\t\treturn result;\n\t}\n}\n\nfunction isSettled(record: SubmissionRecord): record is SettledSubmissionRecord {\n\treturn record.status === \"done\" || record.status === \"unanswered\";\n}\n\n/** Reject an input entry an untyped caller got wrong: its kind must be a non-empty string outside the `pi.` prefix. */\nfunction checkInputEntry(entry: InputEntry): void {\n\tconst { kind } = entry;\n\tif (typeof kind !== \"string\" || kind.length === 0 || kind.startsWith(\"pi.\")) {\n\t\tthrow new TypeError(\n\t\t\t`An input entry's kind must be a non-empty string not starting with \"pi.\", got ${String(kind)}`,\n\t\t);\n\t}\n}\n\n/**\n * Admit a submission inside a commit (spec §6); `Conversation.submit()` and conversation-owned compactions share it. A\n * known request ID returns its existing submission without writing. A display-only write (only `kind` and `data`) is\n * appended and settled `done` at once, busy or not, as it changes nothing the model sees; only behind a queued reset it\n * queues, so it lands after the reset instead of before it in the context the reset ends. A busy conversation queues anything else in\n * `pi.inbox`, or rejects `whenBusy: \"reject\"` input with `ConversationBusy`. An idle conversation with queued items\n * queues it behind them and runs a final boundary. Otherwise idle input places its entry (`pi.user`, or the input's\n * own) and starts a run, and an idle write appends its entry and settles `done`, or `stale` when its head reaches\n * before the active range.\n */\nexport async function admitSubmission(\n\ttx: Tx,\n\tconversationId: ConversationId,\n\tdraft: SubmissionDraft,\n\tnow: number,\n\tqueueModes: QueueModes,\n\tspan?: TelemetrySpan,\n): Promise<Admission> {\n\tif (draft.type === \"input\" && draft.entry !== undefined) checkInputEntry(draft.entry);\n\tif (draft.requestId !== undefined) {\n\t\tconst existing = await tx.submissionByRequest(conversationId, draft.requestId);\n\t\tif (existing !== undefined) {\n\t\t\tif (existing.type !== draft.type) {\n\t\t\t\tthrow new Error(`Request ${draft.requestId} already identifies a submission of type ${existing.type}`);\n\t\t\t}\n\t\t\treturn { id: existing.id, status: existing.status === \"unanswered\" ? \"done\" : existing.status };\n\t\t}\n\t}\n\tconst fields = { conversationId, requestId: draft.requestId, trace: span?.propagation?.inject(span) };\n\tif (\n\t\tdraft.type === \"write\" &&\n\t\tisDisplayOnly(draft.entry) &&\n\t\t!hasQueuedReset((await tx.doc(InboxDoc, conversationId)).items)\n\t) {\n\t\tconst entry = await tx.appendEntry(conversationId, draft.entry);\n\t\tconst write = { ...fields, type: \"write\", status: \"done\", entry: entry.id } as const;\n\t\treturn { id: (await tx.createSubmission(write)).id, status: \"done\" };\n\t}\n\tconst live = await tx.doc(LiveDoc, conversationId);\n\tconst busy = live.run !== undefined;\n\tif (\n\t\tdraft.type === \"input\" &&\n\t\tdraft.whenBusy === \"steer\" &&\n\t\tlive.run !== undefined &&\n\t\tspan?.propagation !== undefined\n\t) {\n\t\tconst run = await tx.task(live.run.taskId);\n\t\tif (run?.trace !== undefined) span.addLink({ context: span.propagation.extract(run.trace) });\n\t}\n\tif (busy && draft.type === \"input\" && draft.whenBusy === \"reject\") throw new ConversationBusy(conversationId);\n\t// A boundary reads the table, so it is prepared before the first table write; a busy one needs none.\n\tconst boundary = busy ? undefined : await prepareBoundary(tx, conversationId, queueModes);\n\tif (boundary === undefined || boundary.inbox.items.length > 0) {\n\t\tconst { id } = await tx.createSubmission({\n\t\t\t...fields,\n\t\t\ttype: draft.type,\n\t\t\tstatus: \"queued\",\n\t\t});\n\t\t// Hosts may leave optional fields `undefined`; drafts take strict JSON.\n\t\tconst value = copyJson(draft.type === \"write\" ? draft.entry : draft.content, {\n\t\t\tomitUndefinedProperties: true,\n\t\t});\n\t\tconst items = (boundary?.inbox ?? (await tx.doc(InboxDoc, conversationId))).items;\n\t\tif (draft.type === \"write\") items.push({ id, mode: \"write\", entry: value as JsonObject });\n\t\telse {\n\t\t\tconst mode = draft.whenBusy === \"steer\" ? \"steer\" : \"followUp\";\n\t\t\tconst content = value as JsonRepresentation<UserInput>;\n\t\t\tconst entry = draft.entry === undefined ? {} : { entry: pickInputEntry(draft.entry) };\n\t\t\titems.push({ id, mode, content, ...entry });\n\t\t}\n\t\tif (boundary === undefined) return { id, status: \"queued\" };\n\t\tconst { users } = await applyBoundary(tx, boundary, \"final\", now);\n\t\tif (users.length > 0) await startRun(tx, conversationId, live, users);\n\t\treturn { id, status: draft.type === \"write\" ? \"done\" : users.at(-1) === id ? \"placed\" : \"queued\" };\n\t}\n\tif (draft.type === \"write\") {\n\t\tif (isStale(boundary, draft.entry)) {\n\t\t\tconst stale = { status: \"unanswered\", reason: \"stale\" } as const;\n\t\t\treturn {\n\t\t\t\tid: (await tx.createSubmission({ ...fields, type: \"write\", ...stale })).id,\n\t\t\t\tstatus: \"done\",\n\t\t\t};\n\t\t}\n\t\tconst entry = await tx.appendEntry(conversationId, draft.entry);\n\t\tconst write = { ...fields, type: \"write\", status: \"done\", entry: entry.id } as const;\n\t\treturn { id: (await tx.createSubmission(write)).id, status: \"done\" };\n\t}\n\tconst entry = await appendInput(tx, conversationId, draft.content, draft.entry, now);\n\tconst input = { ...fields, type: \"input\", status: \"placed\", entry: entry.id } as const;\n\tconst { id } = await tx.createSubmission(input);\n\tawait startRun(tx, conversationId, live, [id]);\n\treturn { id, status: \"placed\" };\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  import { type AttachedReplicatedState, type Context, type JsonValue } from "@earendil-works/chord";
2
2
  import { type SessionImpl } from "../session/session.ts";
3
3
  import type { ConversationId, JoinPolicy, Storage, TaskId, TaskOutcome, WatchHandle } from "../types.ts";
4
+ import { type BlockedReason, type BlockedSubject } from "./scheduler.ts";
4
5
  /** A live task's durable status without its checkpoint and outcome payloads (spec §9.5). */
5
6
  export type TaskGraphState = {
6
7
  readonly status: "pending" | "running";
@@ -11,6 +12,12 @@ export type TaskGraphState = {
11
12
  readonly on: readonly TaskId[];
12
13
  readonly policy: JoinPolicy;
13
14
  }
15
+ /** Asleep until the Harness clock reaches `until`. */
16
+ | {
17
+ readonly status: "sleeping";
18
+ readonly phase: string;
19
+ readonly until: number;
20
+ }
14
21
  /** Outcome held until its ordinary owned work drains. */
15
22
  | {
16
23
  readonly status: "completing";
@@ -25,6 +32,11 @@ export type TaskGraphNode = {
25
32
  readonly background: boolean;
26
33
  readonly abortRequested: boolean;
27
34
  readonly state: TaskGraphState;
35
+ /**
36
+ * Why it cannot run here, when it cannot: no registered definition takes it (`missing_task`, `task_too_old`,
37
+ * `migration_failed`), or the restart limit parked it (`restart_limit`). Follows registry changes.
38
+ */
39
+ readonly blocked?: BlockedReason;
28
40
  /** Conversations this task owns, in ID order. */
29
41
  readonly conversations: readonly ConversationId[];
30
42
  };
@@ -34,13 +46,18 @@ export type TaskGraph = {
34
46
  readonly tasks: Readonly<Record<string, TaskGraphNode>>;
35
47
  };
36
48
  export type TaskGraphWatch = WatchHandle<TaskGraph>;
49
+ /** What the graph asks the scheduler: a task's blocked reason, and when reasons may change without a commit. */
50
+ export type TaskGraphBlocking = {
51
+ readonly blocked: (record: BlockedSubject) => BlockedReason | undefined;
52
+ readonly subscribeBlocked: (listener: () => void) => () => void;
53
+ };
37
54
  /**
38
55
  * The Harness's task graph mount: built on the Session line by its first observer and dropped with its last. It
39
56
  * advances from the Session's commit publications, which are durable.
40
57
  */
41
58
  export declare class TaskGraphView {
42
59
  #private;
43
- constructor(session: SessionImpl, storage: Storage);
60
+ constructor(session: SessionImpl, storage: Storage, blocking: TaskGraphBlocking);
44
61
  /** A disposable read-only Chord state of the graph. */
45
62
  state(context: Context): Promise<AttachedReplicatedState<TaskGraph>>;
46
63
  /** A serialized exact-frame watch of the graph; cancelling `context` stops it. */
@@ -1 +1 @@
1
- {"version":3,"file":"task-graph.d.ts","sourceRoot":"","sources":["../../src/harness/task-graph.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,uBAAuB,EAAE,KAAK,OAAO,EAAE,KAAK,SAAS,EAAmB,MAAM,uBAAuB,CAAC;AAIpH,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,KAAK,EAEX,cAAc,EACd,UAAU,EACV,OAAO,EACP,MAAM,EACN,WAAW,EAEX,WAAW,EACX,MAAM,aAAa,CAAC;AAGrB,4FAA4F;AAC5F,MAAM,MAAM,cAAc,GACvB;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAClE;IACA,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;CAC3B;AACH,yDAAyD;GACvD;IAAE,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAA;CAAE,CAAC;AAEzF,MAAM,MAAM,aAAa,GAAG;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,wDAAwD;IACxD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,iDAAiD;IACjD,QAAQ,CAAC,aAAa,EAAE,SAAS,cAAc,EAAE,CAAC;CAClD,CAAC;AAEF,kDAAkD;AAClD,MAAM,MAAM,SAAS,GAAG;IACvB,gDAAgD;IAChD,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;AASpD;;;GAGG;AACH,qBAAa,aAAa;;IAMzB,YAAY,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAgBjD;IAED,uDAAuD;IACjD,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAWzE;IAED,kFAAkF;IAC5E,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAYrD;CAyCD"}
1
+ {"version":3,"file":"task-graph.d.ts","sourceRoot":"","sources":["../../src/harness/task-graph.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,uBAAuB,EAAE,KAAK,OAAO,EAAE,KAAK,SAAS,EAAmB,MAAM,uBAAuB,CAAC;AAIpH,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,KAAK,EAEX,cAAc,EACd,UAAU,EACV,OAAO,EACP,MAAM,EACN,WAAW,EAEX,WAAW,EACX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,cAAc,EAAkB,MAAM,gBAAgB,CAAC;AAGzF,4FAA4F;AAC5F,MAAM,MAAM,cAAc,GACvB;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAClE;IACA,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;CAC3B;AACH,sDAAsD;GACpD;IAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AACjF,yDAAyD;GACvD;IAAE,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAA;CAAE,CAAC;AAEzF,MAAM,MAAM,aAAa,GAAG;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,wDAAwD;IACxD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;IACjC,iDAAiD;IACjD,QAAQ,CAAC,aAAa,EAAE,SAAS,cAAc,EAAE,CAAC;CAClD,CAAC;AAEF,kDAAkD;AAClD,MAAM,MAAM,SAAS,GAAG;IACvB,gDAAgD;IAChD,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;AAWpD,gHAAgH;AAChH,MAAM,MAAM,iBAAiB,GAAG;IAC/B,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,aAAa,GAAG,SAAS,CAAC;IACxE,QAAQ,CAAC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;CAChE,CAAC;AAKF;;;GAGG;AACH,qBAAa,aAAa;;IAOzB,YAAY,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAmB9E;IAED,uDAAuD;IACjD,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAWzE;IAED,kFAAkF;IAC5E,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAYrD;CAiED"}
@@ -1,10 +1,11 @@
1
1
  import { replicatedState } from "@earendil-works/chord";
2
- import { withoutAbortSignal } from "@earendil-works/chord/context";
2
+ import { BACKGROUND_CONTEXT, withoutAbortSignal } from "@earendil-works/chord/context";
3
3
  import { applyImmutable } from "@earendil-works/chord/delta";
4
4
  import { CommittedStateSource, CommittedWatch } from "../session/observation.js";
5
5
  import { contained } from "../session/session.js";
6
+ import { blockedSubject } from "./scheduler.js";
6
7
  import { closedError, scanAll } from "./util.js";
7
- const LIVE_STATUSES = ["pending", "running", "waiting", "completing"];
8
+ const LIVE_STATUSES = ["pending", "running", "waiting", "sleeping", "completing"];
8
9
  const SCAN_PAGE_SIZE = 256;
9
10
  /**
10
11
  * The Harness's task graph mount: built on the Session line by its first observer and dropped with its last. It
@@ -13,15 +14,19 @@ const SCAN_PAGE_SIZE = 256;
13
14
  export class TaskGraphView {
14
15
  #session;
15
16
  #storage;
17
+ #blocking;
16
18
  #mount;
17
19
  #closed = false;
18
- constructor(session, storage) {
20
+ constructor(session, storage, blocking) {
19
21
  this.#session = session;
20
22
  this.#storage = storage;
23
+ this.#blocking = blocking;
21
24
  session.observeCommits((publication, context) => {
22
- if (this.#mount !== undefined)
23
- advance(this.#mount, publication, context, (error) => session.report(error));
25
+ if (this.#mount === undefined)
26
+ return;
27
+ advance(this.#mount, publication, context, blocking.blocked, (error) => session.report(error));
24
28
  });
29
+ blocking.subscribeBlocked(() => this.#refreshBlocked());
25
30
  session.subscribeClose(() => {
26
31
  this.#closed = true;
27
32
  for (const observer of [...(this.#mount?.observers ?? [])]) {
@@ -56,7 +61,7 @@ export class TaskGraphView {
56
61
  /** Register an observer created from the current revision, atomically on the Session line. */
57
62
  #attach(create, context) {
58
63
  return this.#session.readOnLine(async () => {
59
- const mount = this.#mount ?? { value: await this.#build(context), observers: new Set() };
64
+ const mount = this.#mount ?? { ...(await this.#build(context)), observers: new Set() };
60
65
  const detach = () => {
61
66
  mount.observers.delete(observer);
62
67
  if (mount.observers.size === 0 && this.#mount === mount)
@@ -72,8 +77,30 @@ export class TaskGraphView {
72
77
  return { observer, detach };
73
78
  });
74
79
  }
80
+ /** On the Session line: derive every node's blocked reason again, and publish the ones that changed. */
81
+ #refreshBlocked() {
82
+ if (this.#mount === undefined || this.#closed)
83
+ return;
84
+ this.#session
85
+ .readOnLine(async () => {
86
+ const mount = this.#mount;
87
+ if (mount === undefined)
88
+ return;
89
+ const ops = [];
90
+ for (const [key, record] of mount.records) {
91
+ const blocked = this.#blocking.blocked(record);
92
+ if (blocked === mount.value.tasks[key]?.blocked)
93
+ continue;
94
+ ops.push(blocked === undefined ? ["d", ["tasks", key, "blocked"]] : ["s", ["tasks", key, "blocked"], blocked]);
95
+ }
96
+ publish(mount, ops, BACKGROUND_CONTEXT, (error) => this.#session.report(error));
97
+ })
98
+ // Rejected only once the Session is closing or failed.
99
+ .catch(() => { });
100
+ }
75
101
  async #build(context) {
76
102
  const tasks = {};
103
+ const live = new Map();
77
104
  const records = [];
78
105
  for (const status of LIVE_STATUSES) {
79
106
  records.push(...(await scanAll((cursor) => this.#storage.scanTasks({ status }, SCAN_PAGE_SIZE, cursor, context))));
@@ -82,16 +109,18 @@ export class TaskGraphView {
82
109
  for (const record of records) {
83
110
  const owned = await scanAll((cursor) => this.#storage.scanConversations({ ownerTaskId: record.id }, SCAN_PAGE_SIZE, cursor, context));
84
111
  const conversations = owned.map((conversation) => conversation.id).sort((a, b) => a - b);
85
- tasks[String(record.id)] = nodeOf(record, conversations);
112
+ const subject = blockedSubject(record);
113
+ tasks[String(record.id)] = nodeOf(record, conversations, this.#blocking.blocked(subject));
114
+ live.set(String(record.id), subject);
86
115
  }
87
- return { tasks };
116
+ return { value: { tasks }, records: live };
88
117
  }
89
118
  }
90
119
  /**
91
120
  * Derive the mount's operations from one publication, apply them, and hand the revision to every observer; one that
92
121
  * throws is reported through `report`, and the others still get it.
93
122
  */
94
- function advance(mount, publication, context, report) {
123
+ function advance(mount, publication, context, blocked, report) {
95
124
  const ops = [];
96
125
  // Nodes this publication set or deleted, over the mount's value.
97
126
  const changed = new Map();
@@ -103,13 +132,16 @@ function advance(mount, publication, context, report) {
103
132
  const key = String(record.id);
104
133
  const previous = node(key);
105
134
  if (record.state.status === "terminal") {
135
+ mount.records.delete(key);
106
136
  if (previous === undefined)
107
137
  continue;
108
138
  ops.push(["d", ["tasks", key]]);
109
139
  changed.set(key, undefined);
110
140
  continue;
111
141
  }
112
- const next = nodeOf(record, previous?.conversations ?? []);
142
+ const subject = blockedSubject(record);
143
+ mount.records.set(key, subject);
144
+ const next = nodeOf(record, previous?.conversations ?? [], blocked(subject));
113
145
  if (previous !== undefined && JSON.stringify(next) === JSON.stringify(previous))
114
146
  continue;
115
147
  ops.push(["s", ["tasks", key], next]);
@@ -129,6 +161,10 @@ function advance(mount, publication, context, report) {
129
161
  const conversations = [...node(key).conversations, ...ids].sort((a, b) => a - b);
130
162
  ops.push(["s", ["tasks", key, "conversations"], conversations]);
131
163
  }
164
+ publish(mount, ops, context, report);
165
+ }
166
+ /** Apply `ops` to the mount and hand the revision to every observer; one that throws is reported. */
167
+ function publish(mount, ops, context, report) {
132
168
  if (ops.length === 0)
133
169
  return;
134
170
  mount.value = applyImmutable(mount.value, ops);
@@ -136,7 +172,7 @@ function advance(mount, publication, context, report) {
136
172
  for (const observer of [...mount.observers])
137
173
  contained(() => observer.advance(mount.value, ops, frameContext), report);
138
174
  }
139
- function nodeOf(record, conversations) {
175
+ function nodeOf(record, conversations, blocked) {
140
176
  return {
141
177
  id: record.id,
142
178
  kind: record.kind,
@@ -146,6 +182,7 @@ function nodeOf(record, conversations) {
146
182
  abortRequested: record.abortRequested,
147
183
  state: stateOf(record),
148
184
  conversations,
185
+ ...(blocked === undefined ? {} : { blocked }),
149
186
  };
150
187
  }
151
188
  function stateOf(record) {
@@ -156,6 +193,8 @@ function stateOf(record) {
156
193
  return { status: state.status, phase: phaseOf(state.checkpoint) };
157
194
  case "waiting":
158
195
  return { status: "waiting", phase: phaseOf(state.checkpoint), on: [...state.on], policy: state.policy };
196
+ case "sleeping":
197
+ return { status: "sleeping", phase: phaseOf(state.checkpoint), until: state.until };
159
198
  // Terminal records never reach here: they leave the graph.
160
199
  case "completing":
161
200
  case "terminal":
@@ -1 +1 @@
1
- {"version":3,"file":"task-graph.js","sourceRoot":"","sources":["../../src/harness/task-graph.ts"],"names":[],"mappings":"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","sourcesContent":["import { type AttachedReplicatedState, type Context, type JsonValue, replicatedState } from \"@earendil-works/chord\";\nimport { withoutAbortSignal } from \"@earendil-works/chord/context\";\nimport { applyImmutable, type Op } from \"@earendil-works/chord/delta\";\nimport { CommittedStateSource, CommittedWatch } from \"../session/observation.ts\";\nimport { contained, type SessionImpl } from \"../session/session.ts\";\nimport type {\n\tCommitPublication,\n\tConversationId,\n\tJoinPolicy,\n\tStorage,\n\tTaskId,\n\tTaskOutcome,\n\tTaskRecord,\n\tWatchHandle,\n} from \"../types.ts\";\nimport { closedError, scanAll } from \"./util.ts\";\n\n/** A live task's durable status without its checkpoint and outcome payloads (spec §9.5). */\nexport type TaskGraphState =\n\t| { readonly status: \"pending\" | \"running\"; readonly phase: string }\n\t| {\n\t\t\treadonly status: \"waiting\";\n\t\t\treadonly phase: string;\n\t\t\treadonly on: readonly TaskId[];\n\t\t\treadonly policy: JoinPolicy;\n\t }\n\t/** Outcome held until its ordinary owned work drains. */\n\t| { readonly status: \"completing\"; readonly outcome: TaskOutcome<JsonValue>[\"status\"] };\n\nexport type TaskGraphNode = {\n\treadonly id: TaskId;\n\treadonly kind: string;\n\treadonly conversationId: ConversationId;\n\t/** Owner task; absent for a conversation-owned task. */\n\treadonly owner?: TaskId;\n\treadonly background: boolean;\n\treadonly abortRequested: boolean;\n\treadonly state: TaskGraphState;\n\t/** Conversations this task owns, in ID order. */\n\treadonly conversations: readonly ConversationId[];\n};\n\n/** Every live task of the Session (spec §9.5). */\nexport type TaskGraph = {\n\t/** Every live task, keyed by its decimal ID. */\n\treadonly tasks: Readonly<Record<string, TaskGraphNode>>;\n};\n\nexport type TaskGraphWatch = WatchHandle<TaskGraph>;\n\ntype AnyTaskRecord = TaskRecord<JsonValue, JsonValue, JsonValue>;\ntype Observer = CommittedStateSource<TaskGraph> | CommittedWatch<TaskGraph>;\ntype Mount = { value: TaskGraph; readonly observers: Set<Observer> };\n\nconst LIVE_STATUSES = [\"pending\", \"running\", \"waiting\", \"completing\"] as const;\nconst SCAN_PAGE_SIZE = 256;\n\n/**\n * The Harness's task graph mount: built on the Session line by its first observer and dropped with its last. It\n * advances from the Session's commit publications, which are durable.\n */\nexport class TaskGraphView {\n\treadonly #session: SessionImpl;\n\treadonly #storage: Storage;\n\t#mount: Mount | undefined;\n\t#closed = false;\n\n\tconstructor(session: SessionImpl, storage: Storage) {\n\t\tthis.#session = session;\n\t\tthis.#storage = storage;\n\t\tsession.observeCommits((publication, context) => {\n\t\t\tif (this.#mount !== undefined) advance(this.#mount, publication, context, (error) => session.report(error));\n\t\t});\n\t\tsession.subscribeClose(() => {\n\t\t\tthis.#closed = true;\n\t\t\tfor (const observer of [...(this.#mount?.observers ?? [])]) {\n\t\t\t\tcontained(\n\t\t\t\t\t() => observer.closeSession(this.#session.failure),\n\t\t\t\t\t(error) => session.report(error),\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.#mount = undefined;\n\t\t});\n\t}\n\n\t/** A disposable read-only Chord state of the graph. */\n\tasync state(context: Context): Promise<AttachedReplicatedState<TaskGraph>> {\n\t\tconst { observer, detach } = await this.#attach(\n\t\t\t(value, release) => new CommittedStateSource<TaskGraph>(value, release),\n\t\t\tcontext,\n\t\t);\n\t\ttry {\n\t\t\treturn replicatedState(observer, { onError: (error) => this.#session.report(error) });\n\t\t} catch (error) {\n\t\t\tdetach();\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/** A serialized exact-frame watch of the graph; cancelling `context` stops it. */\n\tasync watch(context: Context): Promise<TaskGraphWatch> {\n\t\tconst { observer } = await this.#attach(\n\t\t\t(value, release) => new CommittedWatch<TaskGraph>(value, release, (error) => this.#session.report(error)),\n\t\t\tcontext,\n\t\t);\n\t\tconst signal = context.abortSignal;\n\t\tif (signal?.aborted) {\n\t\t\tobserver.cancel();\n\t\t\tthrow signal.reason;\n\t\t}\n\t\tif (signal !== undefined) observer.observeCancellation(signal);\n\t\treturn observer;\n\t}\n\n\t/** Register an observer created from the current revision, atomically on the Session line. */\n\t#attach<O extends Observer>(\n\t\tcreate: (value: TaskGraph, release: () => void) => O,\n\t\tcontext: Context,\n\t): Promise<{ observer: O; detach: () => void }> {\n\t\treturn this.#session.readOnLine(async () => {\n\t\t\tconst mount = this.#mount ?? { value: await this.#build(context), observers: new Set<Observer>() };\n\t\t\tconst detach = (): void => {\n\t\t\t\tmount.observers.delete(observer);\n\t\t\t\tif (mount.observers.size === 0 && this.#mount === mount) this.#mount = undefined;\n\t\t\t};\n\t\t\tconst observer = create(mount.value, detach);\n\t\t\t// Close or cancellation may begin while the mount builds; register nothing then.\n\t\t\tif (this.#closed) throw closedError(this.#session);\n\t\t\tcontext.abortSignal?.throwIfAborted();\n\t\t\tthis.#mount = mount;\n\t\t\tmount.observers.add(observer);\n\t\t\treturn { observer, detach };\n\t\t});\n\t}\n\n\tasync #build(context: Context): Promise<TaskGraph> {\n\t\tconst tasks: Record<string, TaskGraphNode> = {};\n\t\tconst records: AnyTaskRecord[] = [];\n\t\tfor (const status of LIVE_STATUSES) {\n\t\t\trecords.push(\n\t\t\t\t...(await scanAll((cursor) => this.#storage.scanTasks({ status }, SCAN_PAGE_SIZE, cursor, context))),\n\t\t\t);\n\t\t}\n\t\trecords.sort((a, b) => a.id - b.id);\n\t\tfor (const record of records) {\n\t\t\tconst owned = await scanAll((cursor) =>\n\t\t\t\tthis.#storage.scanConversations({ ownerTaskId: record.id }, SCAN_PAGE_SIZE, cursor, context),\n\t\t\t);\n\t\t\tconst conversations = owned.map((conversation) => conversation.id).sort((a, b) => a - b);\n\t\t\ttasks[String(record.id)] = nodeOf(record, conversations);\n\t\t}\n\t\treturn { tasks };\n\t}\n}\n\n/**\n * Derive the mount's operations from one publication, apply them, and hand the revision to every observer; one that\n * throws is reported through `report`, and the others still get it.\n */\nfunction advance(\n\tmount: Mount,\n\tpublication: CommitPublication,\n\tcontext: Context,\n\treport: (error: unknown) => void,\n): void {\n\tconst ops: Op[] = [];\n\t// Nodes this publication set or deleted, over the mount's value.\n\tconst changed = new Map<string, TaskGraphNode | undefined>();\n\tconst node = (key: string): TaskGraphNode | undefined =>\n\t\tchanged.has(key) ? changed.get(key) : mount.value.tasks[key];\n\tfor (const change of publication.changes) {\n\t\tif (change.type !== \"task\") continue;\n\t\tconst record = change.value;\n\t\tconst key = String(record.id);\n\t\tconst previous = node(key);\n\t\tif (record.state.status === \"terminal\") {\n\t\t\tif (previous === undefined) continue;\n\t\t\tops.push([\"d\", [\"tasks\", key]]);\n\t\t\tchanged.set(key, undefined);\n\t\t\tcontinue;\n\t\t}\n\t\tconst next = nodeOf(record, previous?.conversations ?? []);\n\t\tif (previous !== undefined && JSON.stringify(next) === JSON.stringify(previous)) continue;\n\t\tops.push([\"s\", [\"tasks\", key], next as unknown as JsonValue]);\n\t\tchanged.set(key, next);\n\t}\n\t// After the tasks, so a conversation created with its owner task in one commit finds the owner's node. Change order\n\t// within a publication is unspecified, so each owner's list is sorted again.\n\tconst created = new Map<string, ConversationId[]>();\n\tfor (const change of publication.changes) {\n\t\tif (change.type !== \"conversation\" || change.value.owner === undefined) continue;\n\t\tconst key = String(change.value.owner.taskId);\n\t\tif (node(key) !== undefined) created.set(key, [...(created.get(key) ?? []), change.value.id]);\n\t}\n\tfor (const [key, ids] of created) {\n\t\tconst conversations = [...node(key)!.conversations, ...ids].sort((a, b) => a - b);\n\t\tops.push([\"s\", [\"tasks\", key, \"conversations\"], conversations]);\n\t}\n\tif (ops.length === 0) return;\n\tmount.value = applyImmutable(mount.value, ops);\n\tconst frameContext = withoutAbortSignal(context);\n\tfor (const observer of [...mount.observers])\n\t\tcontained(() => observer.advance(mount.value, ops, frameContext), report);\n}\n\nfunction nodeOf(record: AnyTaskRecord, conversations: readonly ConversationId[]): TaskGraphNode {\n\treturn {\n\t\tid: record.id,\n\t\tkind: record.kind,\n\t\tconversationId: record.conversationId,\n\t\t...(record.owner === undefined ? {} : { owner: record.owner }),\n\t\tbackground: record.background,\n\t\tabortRequested: record.abortRequested,\n\t\tstate: stateOf(record),\n\t\tconversations,\n\t};\n}\n\nfunction stateOf(record: AnyTaskRecord): TaskGraphState {\n\tconst state = record.state;\n\tswitch (state.status) {\n\t\tcase \"pending\":\n\t\tcase \"running\":\n\t\t\treturn { status: state.status, phase: phaseOf(state.checkpoint) };\n\t\tcase \"waiting\":\n\t\t\treturn { status: \"waiting\", phase: phaseOf(state.checkpoint), on: [...state.on], policy: state.policy };\n\t\t// Terminal records never reach here: they leave the graph.\n\t\tcase \"completing\":\n\t\tcase \"terminal\":\n\t\t\treturn { status: \"completing\", outcome: state.outcome.status };\n\t}\n}\n\nfunction phaseOf(checkpoint: JsonValue): string {\n\treturn (checkpoint as { readonly phase: string }).phase;\n}\n"]}
1
+ {"version":3,"file":"task-graph.js","sourceRoot":"","sources":["../../src/harness/task-graph.ts"],"names":[],"mappings":"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","sourcesContent":["import { type AttachedReplicatedState, type Context, type JsonValue, replicatedState } from \"@earendil-works/chord\";\nimport { BACKGROUND_CONTEXT, withoutAbortSignal } from \"@earendil-works/chord/context\";\nimport { applyImmutable, type Op } from \"@earendil-works/chord/delta\";\nimport { CommittedStateSource, CommittedWatch } from \"../session/observation.ts\";\nimport { contained, type SessionImpl } from \"../session/session.ts\";\nimport type {\n\tCommitPublication,\n\tConversationId,\n\tJoinPolicy,\n\tStorage,\n\tTaskId,\n\tTaskOutcome,\n\tTaskRecord,\n\tWatchHandle,\n} from \"../types.ts\";\nimport { type BlockedReason, type BlockedSubject, blockedSubject } from \"./scheduler.ts\";\nimport { closedError, scanAll } from \"./util.ts\";\n\n/** A live task's durable status without its checkpoint and outcome payloads (spec §9.5). */\nexport type TaskGraphState =\n\t| { readonly status: \"pending\" | \"running\"; readonly phase: string }\n\t| {\n\t\t\treadonly status: \"waiting\";\n\t\t\treadonly phase: string;\n\t\t\treadonly on: readonly TaskId[];\n\t\t\treadonly policy: JoinPolicy;\n\t }\n\t/** Asleep until the Harness clock reaches `until`. */\n\t| { readonly status: \"sleeping\"; readonly phase: string; readonly until: number }\n\t/** Outcome held until its ordinary owned work drains. */\n\t| { readonly status: \"completing\"; readonly outcome: TaskOutcome<JsonValue>[\"status\"] };\n\nexport type TaskGraphNode = {\n\treadonly id: TaskId;\n\treadonly kind: string;\n\treadonly conversationId: ConversationId;\n\t/** Owner task; absent for a conversation-owned task. */\n\treadonly owner?: TaskId;\n\treadonly background: boolean;\n\treadonly abortRequested: boolean;\n\treadonly state: TaskGraphState;\n\t/**\n\t * Why it cannot run here, when it cannot: no registered definition takes it (`missing_task`, `task_too_old`,\n\t * `migration_failed`), or the restart limit parked it (`restart_limit`). Follows registry changes.\n\t */\n\treadonly blocked?: BlockedReason;\n\t/** Conversations this task owns, in ID order. */\n\treadonly conversations: readonly ConversationId[];\n};\n\n/** Every live task of the Session (spec §9.5). */\nexport type TaskGraph = {\n\t/** Every live task, keyed by its decimal ID. */\n\treadonly tasks: Readonly<Record<string, TaskGraphNode>>;\n};\n\nexport type TaskGraphWatch = WatchHandle<TaskGraph>;\n\ntype AnyTaskRecord = TaskRecord<JsonValue, JsonValue, JsonValue>;\ntype Observer = CommittedStateSource<TaskGraph> | CommittedWatch<TaskGraph>;\ntype Mount = {\n\tvalue: TaskGraph;\n\treadonly observers: Set<Observer>;\n\t/** What the blocked reason reads of each live task, so a registry change can derive it again without payloads. */\n\treadonly records: Map<string, BlockedSubject>;\n};\n\n/** What the graph asks the scheduler: a task's blocked reason, and when reasons may change without a commit. */\nexport type TaskGraphBlocking = {\n\treadonly blocked: (record: BlockedSubject) => BlockedReason | undefined;\n\treadonly subscribeBlocked: (listener: () => void) => () => void;\n};\n\nconst LIVE_STATUSES = [\"pending\", \"running\", \"waiting\", \"sleeping\", \"completing\"] as const;\nconst SCAN_PAGE_SIZE = 256;\n\n/**\n * The Harness's task graph mount: built on the Session line by its first observer and dropped with its last. It\n * advances from the Session's commit publications, which are durable.\n */\nexport class TaskGraphView {\n\treadonly #session: SessionImpl;\n\treadonly #storage: Storage;\n\treadonly #blocking: TaskGraphBlocking;\n\t#mount: Mount | undefined;\n\t#closed = false;\n\n\tconstructor(session: SessionImpl, storage: Storage, blocking: TaskGraphBlocking) {\n\t\tthis.#session = session;\n\t\tthis.#storage = storage;\n\t\tthis.#blocking = blocking;\n\t\tsession.observeCommits((publication, context) => {\n\t\t\tif (this.#mount === undefined) return;\n\t\t\tadvance(this.#mount, publication, context, blocking.blocked, (error) => session.report(error));\n\t\t});\n\t\tblocking.subscribeBlocked(() => this.#refreshBlocked());\n\t\tsession.subscribeClose(() => {\n\t\t\tthis.#closed = true;\n\t\t\tfor (const observer of [...(this.#mount?.observers ?? [])]) {\n\t\t\t\tcontained(\n\t\t\t\t\t() => observer.closeSession(this.#session.failure),\n\t\t\t\t\t(error) => session.report(error),\n\t\t\t\t);\n\t\t\t}\n\t\t\tthis.#mount = undefined;\n\t\t});\n\t}\n\n\t/** A disposable read-only Chord state of the graph. */\n\tasync state(context: Context): Promise<AttachedReplicatedState<TaskGraph>> {\n\t\tconst { observer, detach } = await this.#attach(\n\t\t\t(value, release) => new CommittedStateSource<TaskGraph>(value, release),\n\t\t\tcontext,\n\t\t);\n\t\ttry {\n\t\t\treturn replicatedState(observer, { onError: (error) => this.#session.report(error) });\n\t\t} catch (error) {\n\t\t\tdetach();\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/** A serialized exact-frame watch of the graph; cancelling `context` stops it. */\n\tasync watch(context: Context): Promise<TaskGraphWatch> {\n\t\tconst { observer } = await this.#attach(\n\t\t\t(value, release) => new CommittedWatch<TaskGraph>(value, release, (error) => this.#session.report(error)),\n\t\t\tcontext,\n\t\t);\n\t\tconst signal = context.abortSignal;\n\t\tif (signal?.aborted) {\n\t\t\tobserver.cancel();\n\t\t\tthrow signal.reason;\n\t\t}\n\t\tif (signal !== undefined) observer.observeCancellation(signal);\n\t\treturn observer;\n\t}\n\n\t/** Register an observer created from the current revision, atomically on the Session line. */\n\t#attach<O extends Observer>(\n\t\tcreate: (value: TaskGraph, release: () => void) => O,\n\t\tcontext: Context,\n\t): Promise<{ observer: O; detach: () => void }> {\n\t\treturn this.#session.readOnLine(async () => {\n\t\t\tconst mount = this.#mount ?? { ...(await this.#build(context)), observers: new Set<Observer>() };\n\t\t\tconst detach = (): void => {\n\t\t\t\tmount.observers.delete(observer);\n\t\t\t\tif (mount.observers.size === 0 && this.#mount === mount) this.#mount = undefined;\n\t\t\t};\n\t\t\tconst observer = create(mount.value, detach);\n\t\t\t// Close or cancellation may begin while the mount builds; register nothing then.\n\t\t\tif (this.#closed) throw closedError(this.#session);\n\t\t\tcontext.abortSignal?.throwIfAborted();\n\t\t\tthis.#mount = mount;\n\t\t\tmount.observers.add(observer);\n\t\t\treturn { observer, detach };\n\t\t});\n\t}\n\n\t/** On the Session line: derive every node's blocked reason again, and publish the ones that changed. */\n\t#refreshBlocked(): void {\n\t\tif (this.#mount === undefined || this.#closed) return;\n\t\tthis.#session\n\t\t\t.readOnLine(async () => {\n\t\t\t\tconst mount = this.#mount;\n\t\t\t\tif (mount === undefined) return;\n\t\t\t\tconst ops: Op[] = [];\n\t\t\t\tfor (const [key, record] of mount.records) {\n\t\t\t\t\tconst blocked = this.#blocking.blocked(record);\n\t\t\t\t\tif (blocked === mount.value.tasks[key]?.blocked) continue;\n\t\t\t\t\tops.push(\n\t\t\t\t\t\tblocked === undefined ? [\"d\", [\"tasks\", key, \"blocked\"]] : [\"s\", [\"tasks\", key, \"blocked\"], blocked],\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tpublish(mount, ops, BACKGROUND_CONTEXT, (error) => this.#session.report(error));\n\t\t\t})\n\t\t\t// Rejected only once the Session is closing or failed.\n\t\t\t.catch(() => {});\n\t}\n\n\tasync #build(context: Context): Promise<Omit<Mount, \"observers\">> {\n\t\tconst tasks: Record<string, TaskGraphNode> = {};\n\t\tconst live = new Map<string, BlockedSubject>();\n\t\tconst records: AnyTaskRecord[] = [];\n\t\tfor (const status of LIVE_STATUSES) {\n\t\t\trecords.push(\n\t\t\t\t...(await scanAll((cursor) => this.#storage.scanTasks({ status }, SCAN_PAGE_SIZE, cursor, context))),\n\t\t\t);\n\t\t}\n\t\trecords.sort((a, b) => a.id - b.id);\n\t\tfor (const record of records) {\n\t\t\tconst owned = await scanAll((cursor) =>\n\t\t\t\tthis.#storage.scanConversations({ ownerTaskId: record.id }, SCAN_PAGE_SIZE, cursor, context),\n\t\t\t);\n\t\t\tconst conversations = owned.map((conversation) => conversation.id).sort((a, b) => a - b);\n\t\t\tconst subject = blockedSubject(record);\n\t\t\ttasks[String(record.id)] = nodeOf(record, conversations, this.#blocking.blocked(subject));\n\t\t\tlive.set(String(record.id), subject);\n\t\t}\n\t\treturn { value: { tasks }, records: live };\n\t}\n}\n\n/**\n * Derive the mount's operations from one publication, apply them, and hand the revision to every observer; one that\n * throws is reported through `report`, and the others still get it.\n */\nfunction advance(\n\tmount: Mount,\n\tpublication: CommitPublication,\n\tcontext: Context,\n\tblocked: TaskGraphBlocking[\"blocked\"],\n\treport: (error: unknown) => void,\n): void {\n\tconst ops: Op[] = [];\n\t// Nodes this publication set or deleted, over the mount's value.\n\tconst changed = new Map<string, TaskGraphNode | undefined>();\n\tconst node = (key: string): TaskGraphNode | undefined =>\n\t\tchanged.has(key) ? changed.get(key) : mount.value.tasks[key];\n\tfor (const change of publication.changes) {\n\t\tif (change.type !== \"task\") continue;\n\t\tconst record = change.value;\n\t\tconst key = String(record.id);\n\t\tconst previous = node(key);\n\t\tif (record.state.status === \"terminal\") {\n\t\t\tmount.records.delete(key);\n\t\t\tif (previous === undefined) continue;\n\t\t\tops.push([\"d\", [\"tasks\", key]]);\n\t\t\tchanged.set(key, undefined);\n\t\t\tcontinue;\n\t\t}\n\t\tconst subject = blockedSubject(record);\n\t\tmount.records.set(key, subject);\n\t\tconst next = nodeOf(record, previous?.conversations ?? [], blocked(subject));\n\t\tif (previous !== undefined && JSON.stringify(next) === JSON.stringify(previous)) continue;\n\t\tops.push([\"s\", [\"tasks\", key], next as unknown as JsonValue]);\n\t\tchanged.set(key, next);\n\t}\n\t// After the tasks, so a conversation created with its owner task in one commit finds the owner's node. Change order\n\t// within a publication is unspecified, so each owner's list is sorted again.\n\tconst created = new Map<string, ConversationId[]>();\n\tfor (const change of publication.changes) {\n\t\tif (change.type !== \"conversation\" || change.value.owner === undefined) continue;\n\t\tconst key = String(change.value.owner.taskId);\n\t\tif (node(key) !== undefined) created.set(key, [...(created.get(key) ?? []), change.value.id]);\n\t}\n\tfor (const [key, ids] of created) {\n\t\tconst conversations = [...node(key)!.conversations, ...ids].sort((a, b) => a - b);\n\t\tops.push([\"s\", [\"tasks\", key, \"conversations\"], conversations]);\n\t}\n\tpublish(mount, ops, context, report);\n}\n\n/** Apply `ops` to the mount and hand the revision to every observer; one that throws is reported. */\nfunction publish(mount: Mount, ops: Op[], context: Context, report: (error: unknown) => void): void {\n\tif (ops.length === 0) return;\n\tmount.value = applyImmutable(mount.value, ops);\n\tconst frameContext = withoutAbortSignal(context);\n\tfor (const observer of [...mount.observers])\n\t\tcontained(() => observer.advance(mount.value, ops, frameContext), report);\n}\n\nfunction nodeOf(\n\trecord: AnyTaskRecord,\n\tconversations: readonly ConversationId[],\n\tblocked: BlockedReason | undefined,\n): TaskGraphNode {\n\treturn {\n\t\tid: record.id,\n\t\tkind: record.kind,\n\t\tconversationId: record.conversationId,\n\t\t...(record.owner === undefined ? {} : { owner: record.owner }),\n\t\tbackground: record.background,\n\t\tabortRequested: record.abortRequested,\n\t\tstate: stateOf(record),\n\t\tconversations,\n\t\t...(blocked === undefined ? {} : { blocked }),\n\t};\n}\n\nfunction stateOf(record: AnyTaskRecord): TaskGraphState {\n\tconst state = record.state;\n\tswitch (state.status) {\n\t\tcase \"pending\":\n\t\tcase \"running\":\n\t\t\treturn { status: state.status, phase: phaseOf(state.checkpoint) };\n\t\tcase \"waiting\":\n\t\t\treturn { status: \"waiting\", phase: phaseOf(state.checkpoint), on: [...state.on], policy: state.policy };\n\t\tcase \"sleeping\":\n\t\t\treturn { status: \"sleeping\", phase: phaseOf(state.checkpoint), until: state.until };\n\t\t// Terminal records never reach here: they leave the graph.\n\t\tcase \"completing\":\n\t\tcase \"terminal\":\n\t\t\treturn { status: \"completing\", outcome: state.outcome.status };\n\t}\n}\n\nfunction phaseOf(checkpoint: JsonValue): string {\n\treturn (checkpoint as { readonly phase: string }).phase;\n}\n"]}