@inflexa-ai/harness 0.43.1 → 0.45.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 (119) hide show
  1. package/dist/agents/report-session-agent.d.ts +1 -1
  2. package/dist/app/approve-plan.d.ts +48 -0
  3. package/dist/app/approve-plan.js +84 -0
  4. package/dist/app/data-profile-view.d.ts +1 -1
  5. package/dist/app/run-activity.js +4 -6
  6. package/dist/app/synthesize-run.js +1 -1
  7. package/dist/contracts/chat-events.d.ts +2 -0
  8. package/dist/contracts/chat-frame.d.ts +7 -0
  9. package/dist/contracts/chat-frame.js +18 -4
  10. package/dist/contracts/chat-parts.d.ts +73 -2
  11. package/dist/contracts/chat-parts.js +3 -1
  12. package/dist/contracts/index.d.ts +3 -1
  13. package/dist/contracts/index.js +1 -0
  14. package/dist/contracts/part-registry.js +3 -0
  15. package/dist/contracts/schemas/chat-events.d.ts +13 -0
  16. package/dist/contracts/schemas/chat-events.js +1 -0
  17. package/dist/contracts/schemas/chat-parts.d.ts +200 -15
  18. package/dist/contracts/schemas/chat-parts.js +63 -12
  19. package/dist/contracts/schemas/index.d.ts +1 -1
  20. package/dist/contracts/schemas/index.js +1 -1
  21. package/dist/contracts/subagent-activity.d.ts +12 -0
  22. package/dist/contracts/subagent-activity.js +76 -0
  23. package/dist/execution/artifact-metadata.js +1 -3
  24. package/dist/execution/post-step-pipeline.js +1 -1
  25. package/dist/execution/run-canceler.js +1 -1
  26. package/dist/execution/run-event-parts.d.ts +6 -5
  27. package/dist/execution/run-event-parts.js +6 -5
  28. package/dist/execution/run-event-stream.d.ts +18 -6
  29. package/dist/execution/run-event-stream.js +67 -17
  30. package/dist/execution/run-synthesis.d.ts +21 -0
  31. package/dist/execution/run-synthesis.js +77 -3
  32. package/dist/index.d.ts +24 -10
  33. package/dist/index.js +17 -3
  34. package/dist/lib/hooks.d.ts +1 -1
  35. package/dist/lib/metrics.d.ts +2 -2
  36. package/dist/loop/run-agent.js +1 -0
  37. package/dist/loop/types.d.ts +2 -0
  38. package/dist/memory/card-builders.d.ts +11 -0
  39. package/dist/memory/card-builders.js +35 -21
  40. package/dist/memory/conversation-display-recorder.d.ts +2 -0
  41. package/dist/memory/conversation-display-recorder.js +10 -1
  42. package/dist/memory/conversation-display-storage.d.ts +9 -2
  43. package/dist/memory/conversation-display-storage.js +18 -3
  44. package/dist/memory/thread-history.d.ts +8 -1
  45. package/dist/memory/thread-history.js +13 -3
  46. package/dist/memory/thread-store.d.ts +13 -0
  47. package/dist/memory/thread-store.js +15 -1
  48. package/dist/prompts/conversation.d.ts +1 -1
  49. package/dist/prompts/conversation.js +3 -1
  50. package/dist/provenance/seam.d.ts +16 -5
  51. package/dist/providers/ai-sdk.js +6 -4
  52. package/dist/reference-data/receipt.d.ts +16 -4
  53. package/dist/reference-data/receipt.js +20 -3
  54. package/dist/report-model/pin-snapshot.d.ts +1 -1
  55. package/dist/report-model/pin-snapshot.js +4 -52
  56. package/dist/runtime/assemble.d.ts +9 -2
  57. package/dist/runtime/assemble.js +13 -2
  58. package/dist/runtime/boot.d.ts +3 -0
  59. package/dist/runtime/boot.js +10 -1
  60. package/dist/sandbox/client.d.ts +1 -1
  61. package/dist/sandbox/create-sandbox.js +1 -1
  62. package/dist/sandbox/docker-client.js +1 -1
  63. package/dist/sandbox/k8s-client.js +5 -5
  64. package/dist/sandbox/sandbox-step-translate.d.ts +23 -0
  65. package/dist/sandbox/sandbox-step-translate.js +24 -0
  66. package/dist/schemas/artifact-manifest.d.ts +1 -1
  67. package/dist/schemas/artifact-manifest.js +11 -9
  68. package/dist/schemas/plan-schemas.d.ts +18 -18
  69. package/dist/schemas/run-synthesis.d.ts +2 -2
  70. package/dist/state/analyses-metrics.d.ts +79 -7
  71. package/dist/state/analyses-metrics.js +141 -6
  72. package/dist/state/artifacts.d.ts +50 -3
  73. package/dist/state/artifacts.js +67 -7
  74. package/dist/state/data-profile.d.ts +4 -2
  75. package/dist/state/data-profile.js +7 -5
  76. package/dist/state/index.d.ts +9 -8
  77. package/dist/state/index.js +5 -5
  78. package/dist/state/migrations/20261012120000_index_the_runs_by_start.d.ts +8 -0
  79. package/dist/state/migrations/20261012120000_index_the_runs_by_start.js +12 -0
  80. package/dist/state/migrations/20261013120000_index_the_ended_runs_and_the_recent_threads.d.ts +8 -0
  81. package/dist/state/migrations/20261013120000_index_the_ended_runs_and_the_recent_threads.js +13 -0
  82. package/dist/state/migrations/20261013180000_narrow_the_active_run_index.d.ts +8 -0
  83. package/dist/state/migrations/20261013180000_narrow_the_active_run_index.js +13 -0
  84. package/dist/state/migrations/20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.d.ts +8 -0
  85. package/dist/state/migrations/20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.js +13 -0
  86. package/dist/state/migrations/index.js +8 -0
  87. package/dist/state/plans.d.ts +21 -0
  88. package/dist/state/plans.js +25 -0
  89. package/dist/state/runs.d.ts +36 -5
  90. package/dist/state/runs.js +65 -19
  91. package/dist/state/schema.d.ts +2 -0
  92. package/dist/tasks/data-profile-activity.d.ts +7 -1
  93. package/dist/tasks/data-profile-activity.js +18 -9
  94. package/dist/tasks/data-profile.d.ts +15 -1
  95. package/dist/tasks/data-profile.js +18 -13
  96. package/dist/tools/approval/gateway.js +28 -5
  97. package/dist/tools/display/show-user.d.ts +1 -1
  98. package/dist/tools/execute-analysis.d.ts +51 -0
  99. package/dist/tools/execute-analysis.js +241 -109
  100. package/dist/tools/report-session/derive-table.js +1 -1
  101. package/dist/tools/report-session/examine-page.js +1 -1
  102. package/dist/tools/report-session/list-artifacts.js +2 -2
  103. package/dist/tools/report-session/preview-report.d.ts +1 -1
  104. package/dist/tools/report-session/preview-report.js +6 -6
  105. package/dist/tools/report-session/record-version.d.ts +1 -1
  106. package/dist/tools/report-session/record-version.js +4 -4
  107. package/dist/tools/research/inspect-data-profile.d.ts +1 -1
  108. package/dist/tools/research/inspect-run.d.ts +1 -1
  109. package/dist/tools/research/inspect-run.js +7 -7
  110. package/dist/tools/workspace/mutator.js +2 -2
  111. package/dist/workflows/execute-analysis.d.ts +4 -3
  112. package/dist/workflows/execute-analysis.js +38 -25
  113. package/dist/workflows/plan/graph.d.ts +16 -0
  114. package/dist/workflows/plan/graph.js +36 -0
  115. package/dist/workflows/sandbox-step.d.ts +2 -2
  116. package/dist/workflows/sandbox-step.js +16 -14
  117. package/dist/workspace/filesystem.js +18 -22
  118. package/dist/workspace/paths.d.ts +7 -4
  119. package/package.json +4 -4
@@ -20,11 +20,12 @@ import { PART_REGISTRY } from "../contracts/part-registry.js";
20
20
  * newer than the reader.
21
21
  *
22
22
  * The registry is keyed by the discriminant alone, so this validates `type` and
23
- * trusts the producer for the payload. There is no per-part schema to validate
24
- * against: `contracts/chat-parts.ts` declares interfaces, not Zod objects. Both
25
- * ends of this stream are typed against that same union inside this package, so
26
- * the discriminant is the boundary that can actually drift (a producer emitting
27
- * something else entirely) and the one worth checking.
23
+ * trusts the producer for the payload. The part schemas in `contracts/schemas/`
24
+ * are for a consumer at the edge of the package, which parses what it receives.
25
+ * Both ends of this stream are typed against the same union inside this package,
26
+ * so the discriminant is the boundary that can drift (a producer that emits
27
+ * something else entirely), and a part that fails its schema still reaches the
28
+ * consumer for its own check.
28
29
  */
29
30
  export function parseRunEventPart(value) {
30
31
  if (typeof value !== "object" || value === null)
@@ -36,6 +36,11 @@ import type { ChatPart } from "../contracts/chat-parts.js";
36
36
  import { type DbError } from "../lib/db-result.js";
37
37
  import type { Logger } from "../lib/logger.js";
38
38
  import type { RunStatus } from "../state/schema.js";
39
+ /** The part of a run row that the terminal part is made from. */
40
+ export interface RunEventRunRead {
41
+ readonly status: RunStatus;
42
+ readonly error: string | null;
43
+ }
39
44
  /**
40
45
  * Construction-time dependencies. The pool is the application pool the run
41
46
  * ledger lives in; the logger is where every contained failure is reported, and
@@ -45,8 +50,13 @@ import type { RunStatus } from "../state/schema.js";
45
50
  export interface RunEventStreamDeps {
46
51
  readonly pool: Pool;
47
52
  readonly logger?: Logger;
48
- /** Reads the status of the run row, `null` when no row exists. Defaults to `queryRun` on `pool`. */
49
- readonly readRunStatus?: (runId: string) => ResultAsync<RunStatus | null, DbError>;
53
+ /** Reads the status and the error of the run row, `null` when no row exists. Defaults to `queryRun` on `pool`. */
54
+ readonly readRun?: (runId: string) => ResultAsync<RunEventRunRead | null, DbError>;
55
+ /**
56
+ * Reads the status of the parent workflow in the durability engine, `null` when no workflow exists. Defaults to
57
+ * `DBOS.getWorkflowStatus`.
58
+ */
59
+ readonly readWorkflowStatus?: (runId: string) => Promise<string | null>;
50
60
  /** The wait between two reads of the run row. Defaults to a timer. */
51
61
  readonly sleep?: (ms: number) => Promise<void>;
52
62
  }
@@ -78,10 +88,12 @@ export interface RunEventStream {
78
88
  * aborts.
79
89
  *
80
90
  * A canceled workflow runs no more steps, thus it never writes its own
81
- * terminal part, and the canceler cannot write a stream. When the parent
82
- * stream ends with no `data-run-completed` or `data-run-failed` part and
83
- * the run row reads `canceled`, the last part delivered is a
84
- * `data-run-failed` part with `reason: "canceled"`.
91
+ * terminal part, and the canceler cannot write a stream. When every stream
92
+ * ends with no `data-run-completed` or `data-run-failed` part, the last
93
+ * part delivered is a `data-run-failed` part that the run row gives, or the
94
+ * status of the parent workflow when the row still reads `running`. A row
95
+ * that does not exist, a `completed` or `partial` row, an active workflow,
96
+ * and no workflow give no part.
85
97
  *
86
98
  * Parts from one workflow arrive in the order that workflow wrote them. No
87
99
  * order is promised ACROSS the parent and its children: those workflows
@@ -37,6 +37,28 @@ import { describeDbError } from "../lib/db-result.js";
37
37
  import { queryRun } from "../state/runs.js";
38
38
  import { queryStepsByRun } from "../state/step-executions.js";
39
39
  import { foldRunEventParts, parseRunEventPart } from "./run-event-parts.js";
40
+ const canceledPart = (runId) => ({ type: "data-run-failed", runId, error: "Run canceled", reason: "canceled" });
41
+ /** The terminal part that a settled run row gives, or `null` when the body of the run writes the part. */
42
+ function partOfRow(runId, row) {
43
+ switch (row.status) {
44
+ case "canceled":
45
+ return canceledPart(runId);
46
+ case "suspended_insufficient_funds":
47
+ return row.error === null
48
+ ? { type: "data-run-failed", runId, error: "Run suspended" }
49
+ : { type: "data-run-failed", runId, error: row.error, reason: row.error };
50
+ case "failed":
51
+ return { type: "data-run-failed", runId, error: row.error ?? "Run failed" };
52
+ case "completed":
53
+ case "partial":
54
+ case "running":
55
+ return null;
56
+ default: {
57
+ const _exhaustive = row.status;
58
+ throw new Error(`unhandled run status: ${String(_exhaustive)}`);
59
+ }
60
+ }
61
+ }
40
62
  /** Durable stream key every run producer writes its parts under. */
41
63
  const RUN_EVENT_STREAM_KEY = "events";
42
64
  /**
@@ -58,8 +80,8 @@ const CHILD_DISCOVERY_INTERVAL_MS = 1_000;
58
80
  * the stream can end before the row changes, and the later reads wait out that
59
81
  * window.
60
82
  */
61
- const CANCELED_ROW_READ_ATTEMPTS = 4;
62
- const CANCELED_ROW_READ_INTERVAL_MS = 500;
83
+ const TERMINAL_ROW_READ_ATTEMPTS = 4;
84
+ const TERMINAL_ROW_READ_INTERVAL_MS = 500;
63
85
  /**
64
86
  * Build the DBOS-backed realization of {@link RunEventStream}.
65
87
  *
@@ -72,7 +94,8 @@ const CANCELED_ROW_READ_INTERVAL_MS = 500;
72
94
  */
73
95
  export function createRunEventStream(deps) {
74
96
  const baseLogger = (deps.logger ?? createNoopLogger()).named("run-event-stream");
75
- const readRunStatus = deps.readRunStatus ?? ((runId) => queryRun(deps.pool, runId).map((row) => row?.status ?? null));
97
+ const readRun = deps.readRun ?? ((runId) => queryRun(deps.pool, runId).map((row) => (row === null ? null : { status: row.status, error: row.error })));
98
+ const readWorkflowStatus = deps.readWorkflowStatus ?? (async (runId) => (await DBOS.getWorkflowStatus(runId))?.status ?? null);
76
99
  const waitForRowRead = deps.sleep ?? sleep;
77
100
  return {
78
101
  async subscribe({ runId, onPart, signal }) {
@@ -210,37 +233,64 @@ export function createRunEventStream(deps) {
210
233
  await Promise.race([sleep(CHILD_DISCOVERY_INTERVAL_MS), aborted, parentRead]);
211
234
  }
212
235
  })();
213
- const canceledTerminalPart = async () => {
214
- for (let attempt = 1; attempt <= CANCELED_ROW_READ_ATTEMPTS; attempt++) {
215
- const read = await readRunStatus(runId);
236
+ const workflowTerminalPart = async () => {
237
+ let workflowStatus;
238
+ try {
239
+ workflowStatus = await readWorkflowStatus(runId);
240
+ }
241
+ catch (err) {
242
+ logger.warn("workflow status read failed", logger.errorFields(err));
243
+ return null;
244
+ }
245
+ switch (workflowStatus) {
246
+ case "CANCELLED":
247
+ return canceledPart(runId);
248
+ case "ERROR":
249
+ case "MAX_RECOVERY_ATTEMPTS_EXCEEDED":
250
+ case "SUCCESS":
251
+ return { type: "data-run-failed", runId, error: "Run ended without a terminal status" };
252
+ case "PENDING":
253
+ case "ENQUEUED":
254
+ case "DELAYED":
255
+ logger.warn("run streams ended while the workflow is active", { workflowStatus });
256
+ return null;
257
+ default:
258
+ // No workflow: the launch did not start it, or it never started. An unknown status gives no part too.
259
+ if (workflowStatus !== null)
260
+ logger.warn("unknown workflow status", { workflowStatus });
261
+ return null;
262
+ }
263
+ };
264
+ const missingTerminalPart = async () => {
265
+ let rowReadsRunning = false;
266
+ for (let attempt = 1; attempt <= TERMINAL_ROW_READ_ATTEMPTS; attempt++) {
267
+ const read = await readRun(runId);
268
+ rowReadsRunning = false;
216
269
  if (read.isErr()) {
217
270
  logger.warn("run row read failed", { err: describeDbError(read.error) });
218
271
  }
219
272
  else {
220
- const status = read.value;
221
- if (status === "canceled")
222
- return { type: "data-run-failed", runId, error: "Run canceled", reason: "canceled" };
223
273
  // The launch writes the row before the workflow starts, thus
224
274
  // no row means that the id is not an analysis run.
225
- if (status === null)
226
- return null;
227
- // Only an active row can still become `canceled`.
228
- if (status !== "running" && status !== "suspended_insufficient_funds")
275
+ if (read.value === null)
229
276
  return null;
277
+ if (read.value.status !== "running")
278
+ return partOfRow(runId, read.value);
279
+ rowReadsRunning = true;
230
280
  }
231
- if (attempt < CANCELED_ROW_READ_ATTEMPTS)
232
- await Promise.race([waitForRowRead(CANCELED_ROW_READ_INTERVAL_MS), aborted]);
281
+ if (attempt < TERMINAL_ROW_READ_ATTEMPTS)
282
+ await Promise.race([waitForRowRead(TERMINAL_ROW_READ_INTERVAL_MS), aborted]);
233
283
  if (signal.aborted)
234
284
  return null;
235
285
  }
236
- return null;
286
+ return rowReadsRunning ? workflowTerminalPart() : null;
237
287
  };
238
288
  const settled = (async () => {
239
289
  await discovery;
240
290
  await parentRead;
241
291
  await Promise.all(childReads);
242
292
  if (!sawTerminal && !signal.aborted) {
243
- const terminal = await canceledTerminalPart();
293
+ const terminal = await missingTerminalPart();
244
294
  if (terminal !== null)
245
295
  pending.push(terminal);
246
296
  }
@@ -24,6 +24,7 @@
24
24
  * `failed` part. We do not throw here for routine submitter rejections.
25
25
  */
26
26
  import { type RunSynthesis } from "../schemas/run-synthesis.js";
27
+ import type { RunSynthesisPart } from "../contracts/chat-parts.js";
27
28
  import type { StepSummary } from "../schemas/step-summary.js";
28
29
  import type { ToolOutputStore } from "../loop/tool-output.js";
29
30
  import type { ChatDataPart, EmitFn } from "../loop/types.js";
@@ -119,6 +120,26 @@ export interface PersistSynthesisInput {
119
120
  readonly synthesis: RunSynthesis;
120
121
  }
121
122
  export declare function persistSynthesis(args: PersistSynthesisInput): Promise<string>;
123
+ export interface ReadSynthesisInput {
124
+ /** Absolute host root of the analysis's workspace tree. */
125
+ readonly workspaceRoot: string;
126
+ readonly runId: string;
127
+ }
128
+ /**
129
+ * Read the text of a synthesis record under the cap, or give `undefined` when the bytes do not come back.
130
+ *
131
+ * The read takes a bounded byte window, thus a file of any size costs the cap. A run id that can escape
132
+ * the run tree, an absent file, a genuine read fault, and a file over the cap each give `undefined`,
133
+ * because a collection reports no error for one record.
134
+ */
135
+ export declare function readSynthesisText(args: ReadSynthesisInput): Promise<string | undefined>;
136
+ /**
137
+ * The validated synthesis record of a run as the flat `data-run-synthesis` part, the shape that the run
138
+ * stream carries for the same record. It gives `null` for each fault of {@link readSynthesisText}, for text
139
+ * that is not JSON, and for JSON that fails {@link RunSynthesisSchema}. The run row stays the authority on
140
+ * whether a synthesis exists, thus the read does not consult it.
141
+ */
142
+ export declare function readRunSynthesis(args: ReadSynthesisInput): Promise<RunSynthesisPart | null>;
122
143
  export declare function formatSynthesisEmbeddingText(synthesis: RunSynthesis): string;
123
144
  /**
124
145
  * Build the `data-run-synthesis` chat part the parent stream emits when the
@@ -23,16 +23,18 @@
23
23
  * resolves with `{ kind: "failed" }`, the wrapper emits a synthesis-progress
24
24
  * `failed` part. We do not throw here for routine submitter rejections.
25
25
  */
26
- import { readFile } from "node:fs/promises";
26
+ import { open, readFile } from "node:fs/promises";
27
27
  import { join } from "node:path";
28
28
  import { ok } from "neverthrow";
29
29
  import { z } from "zod";
30
30
  import { writeFileWithinRoot } from "../lib/fs-helpers.js";
31
+ import { tryFs } from "../lib/fs-result.js";
31
32
  import { hintForZodIssue } from "../lib/zod-issues.js";
32
33
  import { BiologicalThemeSchema, KeyReferenceSchema, RunSynthesisSchema, SynthesizedFindingSchema, } from "../schemas/run-synthesis.js";
33
34
  import { synthesisAgentPrompt } from "../prompts/synthesis-agent.js";
34
35
  import { composeSystemPrompt } from "../agents/system-prompt.js";
35
- import { runDir } from "../workspace/paths.js";
36
+ import { flattenDataPart } from "../contracts/chat-frame.js";
37
+ import { isSafeId, runDir } from "../workspace/paths.js";
36
38
  import { decodeObjectString } from "../lib/zod-issues.js";
37
39
  import { runToTerminal } from "../loop/run-to-terminal.js";
38
40
  import { passthroughStep } from "../loop/run-step.js";
@@ -435,15 +437,87 @@ export async function loadStepSummariesFromDisk(args) {
435
437
  }
436
438
  return out;
437
439
  }
440
+ /** The file name of the synthesis record inside the run directory. */
441
+ const SYNTHESIS_FILE = "synthesis.json";
442
+ /**
443
+ * The cap of one synthesis read. A synthesis record is small by construction, and the cap bounds the
444
+ * cost of a file that is not. A file over the cap gives no text, because a cut JSON text cannot parse.
445
+ */
446
+ const SYNTHESIS_CAP_BYTES = 1024 * 1024;
438
447
  export async function persistSynthesis(args) {
439
448
  // Confine to the run directory, not the whole workspace: synthesis.json sits
440
449
  // at runs/{runId}/, above any step's RW mount and never in the hard-linked
441
450
  // `data/` inputs.
442
451
  const runRoot = join(args.workspaceRoot, runDir(args.runId));
443
- const path = join(runRoot, "synthesis.json");
452
+ const path = join(runRoot, SYNTHESIS_FILE);
444
453
  await writeFileWithinRoot(runRoot, path, JSON.stringify(args.synthesis, null, 2));
445
454
  return path;
446
455
  }
456
+ /**
457
+ * Read the text of a synthesis record under the cap, or give `undefined` when the bytes do not come back.
458
+ *
459
+ * The read takes a bounded byte window, thus a file of any size costs the cap. A run id that can escape
460
+ * the run tree, an absent file, a genuine read fault, and a file over the cap each give `undefined`,
461
+ * because a collection reports no error for one record.
462
+ */
463
+ export async function readSynthesisText(args) {
464
+ // The path builder refuses an id that can escape the run tree with a throw. The guard keeps the read on
465
+ // the value channel.
466
+ if (!isSafeId(args.runId)) {
467
+ return undefined;
468
+ }
469
+ const absolute = join(args.workspaceRoot, runDir(args.runId), SYNTHESIS_FILE);
470
+ return tryFs("runSynthesis.readText", async () => {
471
+ const handle = await open(absolute, "r");
472
+ try {
473
+ // The window takes one byte more than the cap. A read that fills it states that the file
474
+ // holds more bytes than the cap admits, thus the two conditions stay apart.
475
+ const buffer = Buffer.alloc(SYNTHESIS_CAP_BYTES + 1);
476
+ // One read of a network-backed file can give fewer bytes than the window holds. Such a
477
+ // short read cuts the text in the middle of the JSON, and the record then parses to no
478
+ // key at all. Thus the loop reads again from the offset that the last read reached, and
479
+ // it stops when the window fills or when a read gives no byte.
480
+ let filled = 0;
481
+ while (filled < buffer.length) {
482
+ const { bytesRead } = await handle.read(buffer, filled, buffer.length - filled, filled);
483
+ if (bytesRead === 0) {
484
+ break;
485
+ }
486
+ filled += bytesRead;
487
+ }
488
+ return filled > SYNTHESIS_CAP_BYTES ? undefined : buffer.subarray(0, filled).toString("utf8");
489
+ }
490
+ finally {
491
+ await handle.close();
492
+ }
493
+ }, { path: absolute, onAbsent: () => undefined }).unwrapOr(undefined);
494
+ }
495
+ /**
496
+ * The validated synthesis record of a run as the flat `data-run-synthesis` part, the shape that the run
497
+ * stream carries for the same record. It gives `null` for each fault of {@link readSynthesisText}, for text
498
+ * that is not JSON, and for JSON that fails {@link RunSynthesisSchema}. The run row stays the authority on
499
+ * whether a synthesis exists, thus the read does not consult it.
500
+ */
501
+ export async function readRunSynthesis(args) {
502
+ const text = await readSynthesisText(args);
503
+ if (text === undefined) {
504
+ return null;
505
+ }
506
+ let json;
507
+ try {
508
+ json = JSON.parse(text);
509
+ }
510
+ catch {
511
+ return null;
512
+ }
513
+ const parsed = RunSynthesisSchema.safeParse(json);
514
+ if (!parsed.success) {
515
+ return null;
516
+ }
517
+ // The payload comes from a validated `RunSynthesis`, and the builder sets each field of the part contract.
518
+ // The tests parse the result with `RunSynthesisPartSchema`.
519
+ return flattenDataPart(buildRunSynthesisPart(args.runId, parsed.data));
520
+ }
447
521
  // ── Embedding text ──────────────────────────────────────────────────
448
522
  export function formatSynthesisEmbeddingText(synthesis) {
449
523
  return [
package/dist/index.d.ts CHANGED
@@ -16,6 +16,10 @@ export { resolveCompositionEyes } from "./runtime/assemble.js";
16
16
  export type { ThreadAgentResolver, UnregisteredThreadType } from "./runtime/assemble.js";
17
17
  export { createReportSessionSpawn } from "./app/spawn-report-session.js";
18
18
  export type { ReportSessionSpawn, ReportSessionSpawnDeps, ReportSessionPaging, SpawnRefusal, ReportBrief, ReportSessionDelta, NewestReportChild, } from "./app/spawn-report-session.js";
19
+ export { launchPlanRun } from "./tools/execute-analysis.js";
20
+ export type { LaunchPlanRunDeps, LaunchPlanRunRefusal, PlanLaunch } from "./tools/execute-analysis.js";
21
+ export { approvePlan } from "./app/approve-plan.js";
22
+ export type { ApprovePlanDeps, ApprovePlanInput, ApprovePlanRefusal, PlanApproval } from "./app/approve-plan.js";
19
23
  export type { EnsureSessionStateResult } from "./app/report-session-runtime.js";
20
24
  export { bootHarness } from "./runtime/boot.js";
21
25
  export type { BootHarnessDeps, BootedHarness } from "./runtime/boot.js";
@@ -85,7 +89,7 @@ export { createStreamingChat } from "./providers/streaming-chat.js";
85
89
  export { openChatTurn, prepareChatTurn, runChatTurn } from "./app/chat-turn.js";
86
90
  export type { ChatTurnOutcome, ChatTurnRan, ChatTurnRefusal, ChatTurnResult, ChatTurnSession, OpenChatTurn, OpenChatTurnParams, PrepareChatTurnDeps, PrepareChatTurnParams, PrepareChatTurnResult, RunChatTurnDeps, RunChatTurnParams, RunOpenChatTurnParams, } from "./app/chat-turn.js";
87
91
  export { createThreadStore } from "./memory/thread-store.js";
88
- export type { ThreadStore, Thread, ThreadType, ThreadInputError, CreateThreadInput, ListThreadsInput, ThreadPage } from "./memory/thread-store.js";
92
+ export type { ThreadStore, Thread, ThreadType, ThreadInputError, CreateThreadInput, ListThreadsInput, ListRecentThreadsInput, ThreadPage, } from "./memory/thread-store.js";
89
93
  export { createThreadHistory, conversationRecordTurn } from "./memory/thread-history.js";
90
94
  export type { ThreadHistory, StoredMessage, RetractOutcome, ConversationTurn, TurnWrite, TurnWriteResult, TurnClose, TurnStatus, StoredTurnRecord, } from "./memory/thread-history.js";
91
95
  export { syntheticUserMessage, syntheticRecordMessage, isSyntheticUserMessage, isSyntheticRecordMessage } from "./memory/ai-sdk-message-storage.js";
@@ -96,10 +100,12 @@ export type { ConversationDisplayMetadata, ConversationUIData, ConversationUIMes
96
100
  export { storedMessagesToChat } from "./memory/conversation-display-replay.js";
97
101
  export type { ChatEvent, ChatFrame, ChatPartFrame, TextDeltaEvent, IterationEvent, ToolStartedEvent, ToolFinishedEvent, ToolOutcome, ToolCallOutcome, ToolCallDetail, FinishEvent, ChatErrorEvent, } from "./contracts/chat-events.js";
98
102
  export { toChatFrame, applyChatFrame, checkChatPart } from "./contracts/chat-frame.js";
103
+ export { createSubAgentActivityTracker, SUBAGENT_ACTIVITY_MAX_ENTRIES } from "./contracts/subagent-activity.js";
104
+ export type { SubAgentActivityTracker } from "./contracts/subagent-activity.js";
99
105
  export type { TextPart, ToolCallPart, MessagePart, ChatMessage } from "./contracts/message.js";
100
106
  export type { TurnTerminal, ApplyFrameResult, ChatPartCheck } from "./contracts/chat-frame.js";
101
107
  export type { TokenUsageRollup } from "./contracts/usage.js";
102
- export type { ChatPart, PresentationPart, PresentationContent, PlanPart, RunCardPart, FileReferencePart, FileReferenceEntry, RunStartedPart, DagStatePart, DagStepState, StepStatus, StepActivityPart, StepPhase, StepFileTreePart, FileTreeEntry, StepOutputPart, StepOutputFile, StepSummaryPart, StepUsagePart, StepBlockedPart, RunSynthesisPart, SynthesizedFinding, BiologicalTheme, SynthesisProgressPart, SynthesisPhase, RunCompletedPart, RunCompletedFinding, RunFailedPart, CompactionPart, AskPart, ChildSessionStartedPart, ReportRenderedPart, } from "./contracts/chat-parts.js";
108
+ export type { ChatPart, PresentationPart, PresentationContent, PlanPart, RunCardPart, PlanApprovalPart, FileReferencePart, FileReferenceEntry, RunStartedPart, DagStatePart, DagStepState, PlanGraphStep, StepStatus, StepActivityPart, ActivityEntryPart, SubAgentActivityEntry, SubAgentActivityPart, StepPhase, StepFileTreePart, FileTreeEntry, StepOutputPart, StepOutputFile, StepSummaryPart, StepUsagePart, StepBlockedPart, RunSynthesisPart, FindingReference, SynthesizedFinding, BiologicalTheme, SynthesisProgressPart, SynthesisPhase, RunCompletedPart, RunCompletedFinding, RunFailedPart, CompactionPart, AskPart, ChildSessionStartedPart, ReportRenderedPart, } from "./contracts/chat-parts.js";
103
109
  export { PART_REGISTRY, isTransient, isReconciling, isSidebarPart } from "./contracts/part-registry.js";
104
110
  export type { ChatPartType, PartDescriptor, PartEmitter, PartConsumer } from "./contracts/part-registry.js";
105
111
  export { parseStructureUrl, alphafoldEntryUrl, alphafoldPredictionUrl } from "./contracts/structure-source.js";
@@ -115,7 +121,7 @@ export { createPool } from "./lib/storage.js";
115
121
  export type { PoolConfig } from "./lib/storage.js";
116
122
  export type { Pool } from "pg";
117
123
  export { registerDataProfileWorkflow, triggerDataProfile, runDataProfile, describeDataProfileStartError } from "./tasks/data-profile.js";
118
- export type { DataProfileDeps, DataProfileWorkflowInput, DataProfileTriggerDeps, DataProfileTriggerParams, DataProfileTriggerResult, DataProfileStartError, } from "./tasks/data-profile.js";
124
+ export type { DataProfileDeps, DataProfileWorkflowInput, DataProfileTriggerDeps, DataProfileTriggerParams, DataProfileTriggerResult, DataProfileStartError, DataProfileSettlement, NotifyDataProfileSettled, } from "./tasks/data-profile.js";
119
125
  /**
120
126
  * The synthetic run id the data-profile workflow stamps onto its own frame — the profile has no run
121
127
  * row, so this literal is the only thing that identifies its work as the profile's.
@@ -132,6 +138,12 @@ export { upsertAnalysis } from "./state/analyses.js";
132
138
  export { clearDataProfile, loadDataProfileStatus, tryRetryDataProfile, reconcileOrphanedDataProfile } from "./state/data-profile.js";
133
139
  export type { DataProfileAxis, DataProfileChecked, DataProfileCompanionCompleteness, DataProfileCoverage, DataProfileDimension, DataProfileFile, DataProfileGroup, DataProfileGroupSlot, DataProfileInputSignature, DataProfileKind, DataProfileLifecycleStatus, DataProfileMemberAnnotation, DataProfileObservation, DataProfileOrganism, DataProfilePartition, DataProfileProbeReport, DataProfileQualityAssessment, DataProfileQuarantine, DataProfileRecipeStep, DataProfileResult, DataProfileSubjectSource, } from "./contracts/data-profile.js";
134
140
  export type { DataProfileStatus } from "./state/data-profile.js";
141
+ export { queryLatestRunsByAnalyses, queryDataProfileStatusByAnalyses, queryRunProgressByRuns, queryRunsEndedSince } from "./state/analyses-metrics.js";
142
+ export type { DataProfileSummary, EndedRunSummary, LatestRunSummary, RunProgressSummary } from "./state/analyses-metrics.js";
143
+ export { queryArtifactPage, countArtifactsByRuns } from "./state/artifacts.js";
144
+ export type { ArtifactPage, ArtifactPageEntry, ArtifactPageQuery, ArtifactPageSort, RunArtifactCount } from "./state/artifacts.js";
145
+ export { readRunSynthesis } from "./execution/run-synthesis.js";
146
+ export type { ReadSynthesisInput } from "./execution/run-synthesis.js";
135
147
  export { DIMENSION_CATEGORIES, DIMENSION_CATEGORY_IDS, DIMENSION_PROBES, DIMENSION_PROBE_IDS, GROUP_CATEGORIES, GROUP_CATEGORY_IDS, GROUP_ROLES, GROUP_ROLE_IDS, PROBE_OUTCOME_IDS, dimensionCategoryEntry, dimensionScope, groupCategoryEntry, } from "./contracts/profile-vocabulary.js";
136
148
  export type { DimensionCategory, DimensionCategoryEntry, DimensionProbe, DimensionProbeEntry, DimensionScope, DimensionTreatment, GroupCategory, GroupCategoryEntry, GroupRole, GroupRoleEntry, ProbeOutcome, } from "./contracts/profile-vocabulary.js";
137
149
  export { dimensionCardinalities, profileCaveats, profileDatasetFileCount, profileDimensions, profileFileRecords, profileGroups, } from "./app/data-profile-view.js";
@@ -165,27 +177,29 @@ export type { ExecuteAnalysisDeps, ExecuteAnalysisInput, ExecuteAnalysisResult,
165
177
  export { registerSandboxStep } from "./workflows/sandbox-step.js";
166
178
  export type { SandboxStepDeps, SandboxStepInput, SandboxStepResult, SandboxAgentBuildContext } from "./workflows/sandbox-step.js";
167
179
  export { createRunEventStream } from "./execution/run-event-stream.js";
168
- export type { RunEventStream, RunEventStreamDeps, RunEventSubscribeOptions, RunEventPartHandler } from "./execution/run-event-stream.js";
180
+ export type { RunEventStream, RunEventStreamDeps, RunEventRunRead, RunEventSubscribeOptions, RunEventPartHandler } from "./execution/run-event-stream.js";
169
181
  export { pipeFoldedRunEvents } from "./execution/run-event-parts.js";
170
182
  export { createSandboxAgents, SANDBOX_AGENT_META } from "./agents/sandbox/index.js";
171
183
  export type { SandboxAgentDeps, SandboxStepCoords, AgentMeta } from "./agents/sandbox/index.js";
172
184
  export { AnalysisPlanSchema } from "./schemas/workflow-state.js";
173
185
  export type { AnalysisPlan, AnalysisStep } from "./schemas/workflow-state.js";
174
186
  export type { PlanStep } from "./workflows/execute-analysis-scheduler.js";
187
+ export { planGraphOf } from "./workflows/plan/graph.js";
188
+ export { planPartOf } from "./memory/card-builders.js";
175
189
  export { validatePlan } from "./schemas/validate-plan.js";
176
190
  export type { ValidatePlanOptions } from "./schemas/validate-plan.js";
177
191
  export type { ValidationResult } from "./schemas/validate-plan.js";
178
- export { upsertPlan, loadPlan } from "./state/plans.js";
179
- export type { UpsertPlanInput } from "./state/plans.js";
180
- export { insertRun, reserveRunById, queryActiveRun, queryActiveRunsByAnalysis, updateRunStatus, markRunCanceledIfActive, queryRun, queryRunsByAnalysis, RunDedupCollisionError, RunIdentityCollisionError, } from "./state/runs.js";
192
+ export { upsertPlan, loadPlan, loadPlanRecord } from "./state/plans.js";
193
+ export type { UpsertPlanInput, PlanRecord } from "./state/plans.js";
194
+ export { insertRun, reserveRunById, queryActiveRun, queryActiveRunsByAnalysis, updateRunStatus, markRunCanceledIfActive, queryRun, queryRunsByAnalysis, queryRunIdsByPlan, RunDedupCollisionError, RunIdentityCollisionError, } from "./state/runs.js";
181
195
  export type { InsertRunInput, RunReservation } from "./state/runs.js";
182
196
  export { queryStepsByRun } from "./state/step-executions.js";
183
- export type { CortexRunRow, StepExecutionRow, RunStatus } from "./state/schema.js";
197
+ export type { CortexRunRow, StepExecutionRow, RunStatus, TerminalRunStatus } from "./state/schema.js";
184
198
  export type { DbError } from "./lib/db-result.js";
185
199
  export { REFERENCE_DATA_CATALOG, REFERENCE_DATA_CATALOG_VERSION, ReferenceArtifactPathSchema, ReferenceArtifactSchema, ReferenceDataCatalogSchema, ReferenceDatasetSchema, ReferenceSha256Schema, UnknownReferenceDatasetError, referenceArtifactKey, resolveReferenceInstallPlan, } from "./reference-data/catalog.js";
186
200
  export type { ReferenceArtifact, ReferenceDataCatalog, ReferenceDataset, ReferenceInstallPlan, ReferenceInstallPlanDataset } from "./reference-data/catalog.js";
187
- export { REFERENCE_INSTALL_RECEIPT_VERSION, ReferenceInstallReceiptSchema, parseReferenceInstallReceipt } from "./reference-data/receipt.js";
188
- export type { ReferenceInstallReceipt, ReferenceReceiptArtifact } from "./reference-data/receipt.js";
201
+ export { REFERENCE_INSTALL_RECEIPT_VERSION, ReferenceInstallReceiptSchema, parseReferenceInstallReceipt, referenceReceiptContentDigest, } from "./reference-data/receipt.js";
202
+ export type { ReferenceInstallReceipt, ReferenceReceiptArtifact, ReferenceReceiptContent } from "./reference-data/receipt.js";
189
203
  export { runStepDir } from "./workspace/paths.js";
190
204
  export { reportSessionDir } from "./workspace/paths.js";
191
205
  export type { LogFields, LogLevel, Logger } from "./lib/logger.js";
package/dist/index.js CHANGED
@@ -22,6 +22,11 @@ export { resolveCompositionEyes } from "./runtime/assemble.js";
22
22
  // the report sessions of one analysis. An embedder reaches the listing for its
23
23
  // navigation surface, thus these names belong on the front door.
24
24
  export { createReportSessionSpawn } from "./app/spawn-report-session.js";
25
+ // Start the run of a stored plan, and approve a plan by its id. Each gives a `Result` and never throws, thus
26
+ // a host route calls them with no `try`. `CoreRuntime.approvePlan` binds the approval over the deps of the
27
+ // conversation agent.
28
+ export { launchPlanRun } from "./tools/execute-analysis.js";
29
+ export { approvePlan } from "./app/approve-plan.js";
25
30
  // The harness-owned boot sequence: ordered boot steps around `assembleCoreRuntime`
26
31
  // (skill validation → state init → connection budget → assemble → launch) + a
27
32
  // `shutdown` handle the embedder wires to its process signals.
@@ -156,6 +161,7 @@ export { envelopeDisplayMessages, parseStoredDisplayEnvelope } from "./memory/co
156
161
  // by dropping that part, so one stale row cannot fail a whole thread.
157
162
  export { storedMessagesToChat } from "./memory/conversation-display-replay.js";
158
163
  export { toChatFrame, applyChatFrame, checkChatPart } from "./contracts/chat-frame.js";
164
+ export { createSubAgentActivityTracker, SUBAGENT_ACTIVITY_MAX_ENTRIES } from "./contracts/subagent-activity.js";
159
165
  export { PART_REGISTRY, isTransient, isReconciling, isSidebarPart } from "./contracts/part-registry.js";
160
166
  // The source grammar of a `structure` presentation card — a host re-runs the same parse over a
161
167
  // persisted `url` before it fetches, so the rule lives in one place for both sides.
@@ -215,6 +221,12 @@ export { upsertAnalysis } from "./state/analyses.js";
215
221
  // analysis's input set empties (deferring on a running workflow), so the UI
216
222
  // stops advertising a profile that describes files the analysis no longer has.
217
223
  export { clearDataProfile, loadDataProfileStatus, tryRetryDataProfile, reconcileOrphanedDataProfile } from "./state/data-profile.js";
224
+ // The grouped reads that summarize a page of analyses: one statement for the whole list.
225
+ export { queryLatestRunsByAnalyses, queryDataProfileStatusByAnalyses, queryRunProgressByRuns, queryRunsEndedSince } from "./state/analyses-metrics.js";
226
+ // The artifact ledger reads of one analysis: one page with an exact total, and the step-output count of each run of a list in one statement.
227
+ export { queryArtifactPage, countArtifactsByRuns } from "./state/artifacts.js";
228
+ // The validated synthesis record of a run as the flat `data-run-synthesis` part of the run stream, or null.
229
+ export { readRunSynthesis } from "./execution/run-synthesis.js";
218
230
  // The controlled vocabulary the profiler draws from, as data. A consumer rendering a
219
231
  // stored group needs the category's meaning, and the package owns the catalogue — a host
220
232
  // does not extend it.
@@ -277,18 +289,20 @@ export { createSandboxAgents, SANDBOX_AGENT_META } from "./agents/sandbox/index.
277
289
  // an embedder supplies the plan's step DATA and the harness composes each step's
278
290
  // seed at dispatch, when the step's dependencies have actually produced something.
279
291
  export { AnalysisPlanSchema } from "./schemas/workflow-state.js";
292
+ export { planGraphOf } from "./workflows/plan/graph.js";
293
+ export { planPartOf } from "./memory/card-builders.js";
280
294
  export { validatePlan } from "./schemas/validate-plan.js";
281
295
  // Plan + run + step ledgers around a trigger. `upsertPlan` takes a
282
296
  // caller-derived id and inserts-if-absent (the deterministic-id intake path);
283
297
  // the run-state calls mirror the chat trigger's reserve → dedup → status flow.
284
- export { upsertPlan, loadPlan } from "./state/plans.js";
285
- export { insertRun, reserveRunById, queryActiveRun, queryActiveRunsByAnalysis, updateRunStatus, markRunCanceledIfActive, queryRun, queryRunsByAnalysis, RunDedupCollisionError, RunIdentityCollisionError, } from "./state/runs.js";
298
+ export { upsertPlan, loadPlan, loadPlanRecord } from "./state/plans.js";
299
+ export { insertRun, reserveRunById, queryActiveRun, queryActiveRunsByAnalysis, updateRunStatus, markRunCanceledIfActive, queryRun, queryRunsByAnalysis, queryRunIdsByPlan, RunDedupCollisionError, RunIdentityCollisionError, } from "./state/runs.js";
286
300
  export { queryStepsByRun } from "./state/step-executions.js";
287
301
  // Host-neutral reference-data catalog and shared installation receipts. Each
288
302
  // artifact names the third-party upstream it is fetched from; embedders own all
289
303
  // transfer/storage behavior but never choose a different source.
290
304
  export { REFERENCE_DATA_CATALOG, REFERENCE_DATA_CATALOG_VERSION, ReferenceArtifactPathSchema, ReferenceArtifactSchema, ReferenceDataCatalogSchema, ReferenceDatasetSchema, ReferenceSha256Schema, UnknownReferenceDatasetError, referenceArtifactKey, resolveReferenceInstallPlan, } from "./reference-data/catalog.js";
291
- export { REFERENCE_INSTALL_RECEIPT_VERSION, ReferenceInstallReceiptSchema, parseReferenceInstallReceipt } from "./reference-data/receipt.js";
305
+ export { REFERENCE_INSTALL_RECEIPT_VERSION, ReferenceInstallReceiptSchema, parseReferenceInstallReceipt, referenceReceiptContentDigest, } from "./reference-data/receipt.js";
292
306
  // Workspace-tree path convention — the root-relative step directory each
293
307
  // sandbox step confines its artifact writes to (join onto the resolved root).
294
308
  export { runStepDir } from "./workspace/paths.js";
@@ -29,7 +29,7 @@ export interface NoticeFailure {
29
29
  readonly reason: string;
30
30
  }
31
31
  export type GateName = "RunAuthorizer.authorize" | "RunCharge.open" | "ArtifactRegistry.register" | "resolveRequestHeaders" | "resolveSandboxLabels";
32
- export type NoticeName = "RunAuthorizer.revoke" | "RunAuthorizer.revokeByJti" | "RunCharge.close" | "ArtifactRegistry.sync" | "UsageRecorder.record";
32
+ export type NoticeName = "RunAuthorizer.revoke" | "RunAuthorizer.revokeByJti" | "RunCharge.close" | "ArtifactRegistry.sync" | "UsageRecorder.record" | "notifyDataProfileSettled";
33
33
  export type GateRefusal = {
34
34
  readonly kind: "failed";
35
35
  readonly gate: GateName;
@@ -32,8 +32,8 @@
32
32
  * on the no-op provider for the life of the process.
33
33
  */
34
34
  import type { StepExecutionStatus } from "../state/schema.js";
35
- /** Terminal status of a run. A budget pause records as `canceled`, the same as the run ledger. */
36
- export type RunOutcome = "completed" | "partial" | "failed" | "canceled";
35
+ /** Terminal status of a run, the same as the run ledger. */
36
+ export type RunOutcome = "completed" | "partial" | "failed" | "canceled" | "suspended_insufficient_funds";
37
37
  /** The workflow family a run belongs to. */
38
38
  export type RunWorkflow = "analysis" | "data_profile";
39
39
  /** Terminal status of an executed step. A blocker is a failure to deliver, so `blocked` maps here to `failed`. */
@@ -97,6 +97,7 @@ export function openLoop(agent, initial, session, opts, metricAgentId) {
97
97
  const source = {
98
98
  agentId: session.provenance.agentId ?? agent.id,
99
99
  callPath: session.provenance.callPath,
100
+ ...(opts.invocationId === undefined ? {} : { invocationId: opts.invocationId }),
100
101
  };
101
102
  const policy = opts.compaction;
102
103
  const viewOptions = { keepFirstTurn: policy?.keepFirstTurn === true };
@@ -46,6 +46,8 @@ export type RunStep = <T>(name: string, fn: () => Promise<T>) => Promise<T>;
46
46
  export interface EventSource {
47
47
  readonly agentId: string;
48
48
  readonly callPath: readonly string[];
49
+ /** The id of the tool call that runs this loop. For a data profile, the workflow id of its attempt. */
50
+ readonly invocationId?: string;
49
51
  }
50
52
  /**
51
53
  * Orchestration events the loop emits: iteration boundaries and tool-call
@@ -16,9 +16,12 @@
16
16
  import { type ResultAsync } from "neverthrow";
17
17
  import type { Pool } from "pg";
18
18
  import { type DbError } from "../lib/db-result.js";
19
+ import type { PlanPart } from "../contracts/chat-parts.js";
20
+ import { type PlanRecord } from "../state/index.js";
19
21
  export interface PlanCardData {
20
22
  id: string;
21
23
  planId: string;
24
+ parentPlanId?: string;
22
25
  title?: string;
23
26
  steps: unknown;
24
27
  analytical_narrative: unknown;
@@ -37,6 +40,14 @@ export interface RunCardData {
37
40
  title: string;
38
41
  stepCount: number;
39
42
  }
43
+ /** Build the `data-plan` payload of a stored plan record. `null` when the
44
+ * document no longer parses. An explicit `title` overrides the plan title. */
45
+ export declare function planCardOf(record: PlanRecord, title?: string): PlanCardData | null;
46
+ /** The `data-plan` part of a stored plan record, with the title of the plan. The
47
+ * part schema drops each key that it does not name, thus the part equals the
48
+ * stored card of a transcript. `null` when the document does not parse or the
49
+ * schema refuses the part. */
50
+ export declare function planPartOf(record: PlanRecord): PlanPart | null;
40
51
  /** Build the `data-plan` payload by loading the stored plan. `ok(null)` when
41
52
  * the plan is missing or no longer parses (absence is NOT an error) — the
42
53
  * caller falls back to a chip. */