@inflexa-ai/harness 0.31.1 → 0.32.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 (56) hide show
  1. package/dist/agents/conversation-agent.d.ts +7 -0
  2. package/dist/agents/conversation-agent.js +1 -0
  3. package/dist/agents/report-session-agent.d.ts +9 -0
  4. package/dist/agents/report-session-agent.js +10 -2
  5. package/dist/app/chat-turn.d.ts +11 -0
  6. package/dist/app/chat-turn.js +17 -1
  7. package/dist/app/spawn-report-session.d.ts +12 -0
  8. package/dist/app/spawn-report-session.js +21 -1
  9. package/dist/contracts/report-blocks.d.ts +8 -8
  10. package/dist/contracts/schemas/chat-parts.d.ts +6 -6
  11. package/dist/index.d.ts +4 -1
  12. package/dist/index.js +8 -0
  13. package/dist/provenance/seam.d.ts +203 -0
  14. package/dist/provenance/seam.js +71 -0
  15. package/dist/report-model/authoring-grammar.d.ts +2 -2
  16. package/dist/report-render/assets.d.ts +10 -0
  17. package/dist/report-render/assets.js +14 -0
  18. package/dist/report-render/design.js +256 -0
  19. package/dist/report-render/page.d.ts +75 -0
  20. package/dist/report-render/page.js +793 -1
  21. package/dist/report-render/provenance-data.d.ts +42 -0
  22. package/dist/report-render/provenance-data.js +62 -0
  23. package/dist/report-render/render.d.ts +20 -1
  24. package/dist/report-render/render.js +23 -10
  25. package/dist/report-render/types.d.ts +3 -0
  26. package/dist/report-render/views/chart-view.d.ts +5 -1
  27. package/dist/report-render/views/chart-view.js +6 -2
  28. package/dist/report-render/views/lineage.d.ts +93 -0
  29. package/dist/report-render/views/lineage.js +86 -0
  30. package/dist/report-render/views/page-view.d.ts +22 -1
  31. package/dist/report-render/views/page-view.js +12 -3
  32. package/dist/report-render/views/prose.d.ts +7 -1
  33. package/dist/report-render/views/prose.js +10 -6
  34. package/dist/report-render/views/references-view.d.ts +17 -1
  35. package/dist/report-render/views/references-view.js +12 -4
  36. package/dist/report-render/views/values.d.ts +17 -4
  37. package/dist/report-render/views/values.js +21 -8
  38. package/dist/runtime/assemble.d.ts +22 -0
  39. package/dist/runtime/assemble.js +4 -0
  40. package/dist/sandbox/k8s-client.d.ts +0 -5
  41. package/dist/sandbox/k8s-client.js +9 -115
  42. package/dist/schemas/artifact-manifest.d.ts +4 -4
  43. package/dist/tools/display/show-user.d.ts +1 -1
  44. package/dist/tools/report-authoring/authoring-tools.d.ts +16 -1
  45. package/dist/tools/report-authoring/authoring-tools.js +58 -16
  46. package/dist/tools/report-session/derive-table.d.ts +3 -0
  47. package/dist/tools/report-session/derive-table.js +15 -0
  48. package/dist/tools/report-session/preview-report.d.ts +13 -4
  49. package/dist/tools/report-session/preview-report.js +23 -6
  50. package/dist/tools/report-session/record-version.d.ts +3 -0
  51. package/dist/tools/report-session/record-version.js +7 -3
  52. package/dist/tools/start-report-session.d.ts +7 -0
  53. package/dist/tools/start-report-session.js +1 -0
  54. package/dist/workflows/execute-analysis.d.ts +15 -69
  55. package/dist/workflows/execute-analysis.js +5 -4
  56. package/package.json +2 -1
@@ -35,6 +35,7 @@ import { type BioToolKeys } from "../tools/bio/keys.js";
35
35
  import type { EnvironmentStorePaths } from "../config/environment-stores.js";
36
36
  import type { RunAuthorizer } from "../execution/run-authorizer.js";
37
37
  import type { RunLauncher } from "../execution/run-launcher.js";
38
+ import type { ProvenanceSeam } from "../provenance/seam.js";
38
39
  import type { Logger } from "../lib/logger.js";
39
40
  import type { UsageRecorder } from "../billing/usage-recorder.js";
40
41
  import type { CitationResolver } from "../citations/types.js";
@@ -111,6 +112,12 @@ export interface ConversationAgentDeps extends EnvironmentStorePaths {
111
112
  * at a page reads that same answer.
112
113
  */
113
114
  readonly eyes?: AcquireEyes;
115
+ /**
116
+ * The provenance seam. `start_report_session` hands it to its spawn, which
117
+ * emits the creation of the session at the moment that the child lands. No
118
+ * other tool of this roster reads it.
119
+ */
120
+ readonly provenance?: ProvenanceSeam;
114
121
  /**
115
122
  * Host resource policy — per-step ceilings + machine budget. `generate_plan`
116
123
  * states the ceilings to the planner and validates against them;
@@ -122,6 +122,7 @@ export function createConversationAgent(deps) {
122
122
  chrome,
123
123
  anchorSession: deps.anchorReportSession,
124
124
  ...(deps.eyes ? { eyes: deps.eyes } : {}),
125
+ ...(deps.provenance ? { provenance: deps.provenance } : {}),
125
126
  ...(deps.logger ? { logger: deps.logger } : {}),
126
127
  }),
127
128
  // Display.
@@ -38,6 +38,7 @@ import type { ThreadStore } from "../memory/thread-store.js";
38
38
  import type { ReferenceResolver } from "../report-model/reference-resolver.js";
39
39
  import type { ReportVersionStore } from "../state/report-versions.js";
40
40
  import type { ReportSessionStateGateway } from "../tools/report-authoring/authoring-tools.js";
41
+ import type { ProvenanceSeam } from "../provenance/seam.js";
41
42
  import { type MakeSessionPagePublisher, type ResolvePageAsset, type ResolvePageUrl } from "../tools/report-session/index.js";
42
43
  /** Canonical agent id -- distinct from the id of the old templating agent. */
43
44
  export declare const REPORT_SESSION_AGENT_ID: "report-session";
@@ -114,6 +115,14 @@ export interface ReportSessionAgentDeps {
114
115
  * tool navigates through a `file://` URL of the page path.
115
116
  */
116
117
  readonly resolvePageUrl?: ResolvePageUrl;
118
+ /**
119
+ * The provenance seam -- the acts of the session as typed events, and the frozen document of the
120
+ * analysis as opaque bytes. The tools that change the draft, derive a table, preview the page, and
121
+ * record a version each emit through the session member, thus one bound member carries the whole
122
+ * session. The preview asks the read member before each render, and the page then carries the
123
+ * document. An unbound member emits nothing, or gives absence, and every tool acts the same.
124
+ */
125
+ readonly provenance?: ProvenanceSeam;
117
126
  /** Operational logging seam; omitted falls back to no-op. */
118
127
  readonly logger?: Logger;
119
128
  }
@@ -39,8 +39,11 @@ const REPORT_SESSION_MAX_ITERATIONS = 200;
39
39
  /** Build the report `AgentDefinition` with every tool bound to its deps. */
40
40
  export function createReportSessionAgent(deps) {
41
41
  const { model, pool, embedding, workspaceFs, gateway, resolveWorkspaceRoot, store, threads, chrome, eyes, makeResolver, resolvePageAsset, logger } = deps;
42
- const { derivations, runDerivation, runAuthorizer, makeSessionPages, resolvePageUrl } = deps;
43
- const authoring = createReportAuthoringTools(gateway);
42
+ const { derivations, runDerivation, runAuthorizer, makeSessionPages, resolvePageUrl, provenance } = deps;
43
+ // The seam rides into each tool that lands an act of the session, thus the composition binds it one
44
+ // time and no tool reads a second source.
45
+ const observation = provenance ? { provenance } : {};
46
+ const authoring = createReportAuthoringTools(gateway, { ...observation, ...(logger ? { logger } : {}) });
44
47
  const tools = [
45
48
  // The read surface toward the analysis. It roams the tree read-only.
46
49
  createReadFileTool(workspaceFs),
@@ -75,6 +78,7 @@ export function createReportSessionAgent(deps) {
75
78
  derivations,
76
79
  ...(runDerivation ? { runDerivation } : {}),
77
80
  ...(runAuthorizer ? { runAuthorizer } : {}),
81
+ ...observation,
78
82
  ...(logger ? { logger } : {}),
79
83
  }),
80
84
  // The render-and-preview tool. It resolves the page root per call.
@@ -84,6 +88,9 @@ export function createReportSessionAgent(deps) {
84
88
  ...(makeResolver ? { makeResolver } : {}),
85
89
  ...(resolvePageAsset ? { resolvePageAsset } : {}),
86
90
  ...(makeSessionPages ? { makeSessionPages } : {}),
91
+ // The preview is the one site that carries the document into the page, thus the read member
92
+ // reaches this tool alone.
93
+ ...observation,
87
94
  ...(logger ? { logger } : {}),
88
95
  }),
89
96
  // The eyes tool. It opens the rendered page in headless Chrome.
@@ -103,6 +110,7 @@ export function createReportSessionAgent(deps) {
103
110
  threads,
104
111
  resolveWorkspaceRoot,
105
112
  ...(makeResolver ? { makeResolver } : {}),
113
+ ...observation,
106
114
  ...(logger ? { logger } : {}),
107
115
  }),
108
116
  ];
@@ -16,10 +16,21 @@ import type { Pool } from "pg";
16
16
  import { type ThreadType } from "../memory/thread-store.js";
17
17
  import { type AssembledMessages } from "./message-assembly.js";
18
18
  import type { Logger } from "../lib/logger.js";
19
+ import { type ProvenanceSeam } from "../provenance/seam.js";
19
20
  export interface PrepareChatTurnDeps {
20
21
  /** Operational logging seam; omitted falls back to no-op. */
21
22
  readonly logger?: Logger;
22
23
  readonly pool: Pool;
24
+ /**
25
+ * The provenance seam. The turn reads its session emit member, and no other
26
+ * member. The turn holds the one site that writes the conversation thread of
27
+ * an analysis, thus it is the one site that can tell the embedder the true
28
+ * moment of that creation.
29
+ *
30
+ * The member is optional and fire-and-forget, the same as it is at each site
31
+ * of a report session.
32
+ */
33
+ readonly provenance?: ProvenanceSeam;
23
34
  }
24
35
  export interface PrepareChatTurnParams {
25
36
  readonly analysisId: string;
@@ -21,6 +21,7 @@ import { loadAnalysisStatus, queryNonTerminalRunsByAnalysis } from "../state/ind
21
21
  import { assembleMessages } from "./message-assembly.js";
22
22
  import { renderRunActivity, renderRunActivityUnavailable, RUN_ACTIVITY_DETAIL_LIMIT } from "./run-activity.js";
23
23
  import { createNoopLogger } from "../lib/console-logger.js";
24
+ import { bindSessionEmit } from "../provenance/seam.js";
24
25
  /**
25
26
  * Prepare one chat turn: resolve thread ownership, seed the title, load
26
27
  * analysis status, and assemble the message array. Input sanitization stays
@@ -29,6 +30,10 @@ import { createNoopLogger } from "../lib/console-logger.js";
29
30
  export async function prepareChatTurn(deps, params) {
30
31
  const { pool } = deps;
31
32
  const { analysisId, threadId, userInput } = params;
33
+ const logger = (deps.logger ?? createNoopLogger()).named("harness.chat");
34
+ // An unbound member gives a call that does nothing, thus the emit below needs
35
+ // no test of its own.
36
+ const observe = bindSessionEmit(deps.provenance, logger);
32
37
  // Ownership check before any read/write of the thread — a `threadId`
33
38
  // owned by a different analysis is indistinguishable from a missing one.
34
39
  const store = createThreadStore(pool);
@@ -54,13 +59,24 @@ export async function prepareChatTurn(deps, params) {
54
59
  title: deriveThreadTitle(userInput),
55
60
  }));
56
61
  threadType = created.threadType;
62
+ // The row that comes back is not proof that this call wrote it. The read
63
+ // above hides an archived thread, and the idempotent insert then reads
64
+ // that same row back. Thus a turn on an archived thread reaches here with
65
+ // a row that a different site created, and an emit would record a start
66
+ // that never happened.
67
+ //
68
+ // This site writes a live conversation thread and nothing else. An
69
+ // archived row, or a row of another kind, is therefore one that already
70
+ // existed. A root session has no parent, thus the event carries none.
71
+ if (created.deletedAt === null && created.threadType === "conversation") {
72
+ observe({ type: "create-session", analysisId, threadId, sessionKind: created.threadType });
73
+ }
57
74
  }
58
75
  else if (!existing.title || existing.title.length === 0) {
59
76
  unwrapOrThrow(await store.updateTitle(threadId, deriveThreadTitle(userInput)));
60
77
  }
61
78
  }
62
79
  catch (err) {
63
- const logger = (deps.logger ?? createNoopLogger()).named("harness.chat");
64
80
  logger.warn("title-seed failed (non-fatal)", logger.errorFields(err));
65
81
  }
66
82
  const analysisState = await loadAnalysisStatus(pool, analysisId).unwrapOr(null);
@@ -66,6 +66,7 @@ import type { AcquireEyes } from "../lib/eyes.js";
66
66
  import type { Logger } from "../lib/logger.js";
67
67
  import type { CapturePage } from "../lib/page-capture.js";
68
68
  import { type Thread, type ThreadInputError, type ThreadPage, type ThreadType } from "../memory/thread-store.js";
69
+ import { type ProvenanceSeam } from "../provenance/seam.js";
69
70
  import type { EnsureSessionStateResult } from "./report-session-runtime.js";
70
71
  /**
71
72
  * A spawn the operation refuses before it writes a row. Distinct from
@@ -192,6 +193,17 @@ export interface ReportSessionSpawnDeps {
192
193
  * is idempotent.
193
194
  */
194
195
  readonly anchorSession?: (threadId: string) => Promise<EnsureSessionStateResult>;
196
+ /**
197
+ * The provenance seam. The spawn reads its session emit member, and no other
198
+ * member. The spawn is the birth of the session, thus it is the one site that
199
+ * can tell the embedder the true moment of the ask. A consumer that waits for
200
+ * the first authoring act reads a later moment, and it reads nothing at all
201
+ * for a session that stays empty.
202
+ *
203
+ * The member is optional and fire-and-forget, the same as it is at each
204
+ * authoring site.
205
+ */
206
+ readonly provenance?: ProvenanceSeam;
195
207
  /** Operational logging seam. An omitted logger falls back to the no-op. */
196
208
  readonly logger?: Logger;
197
209
  }
@@ -65,6 +65,7 @@ import { createNoopLogger } from "../lib/console-logger.js";
65
65
  import { conversationRecordTurn, createThreadHistory } from "../memory/thread-history.js";
66
66
  import { createThreadStore } from "../memory/thread-store.js";
67
67
  import { createWorkingMemory } from "../memory/working-memory.js";
68
+ import { bindSessionEmit } from "../provenance/seam.js";
68
69
  const _assertDomainError = true;
69
70
  /**
70
71
  * Whether the composition gives a route to a look at the rendered page. The
@@ -165,6 +166,9 @@ export function createReportSessionSpawn(deps) {
165
166
  // The routes are fixed at construction, thus the gate reads one boolean and
166
167
  // never a live probe of the sidecar.
167
168
  const eyesAvailable = compositionHasEyes(deps);
169
+ // An unbound member gives a call that does nothing, thus the spawn emits at
170
+ // one site with no test of its own.
171
+ const observe = bindSessionEmit(deps.provenance, log);
168
172
  /**
169
173
  * Write the one seed message of the child and give the child back. The
170
174
  * message rides as a record, not as user input: nobody typed it in the child
@@ -250,7 +254,7 @@ export function createReportSessionSpawn(deps) {
250
254
  if (!eyesAvailable) {
251
255
  return errAsync({ type: "no_browser", op: OP, parentThreadId, detail: NO_BROWSER_DETAIL });
252
256
  }
253
- return store.getThread(parentThreadId).andThen((parent) => {
257
+ const spawned = store.getThread(parentThreadId).andThen((parent) => {
254
258
  // An absent parent and an archived one arrive the same way — `getThread`
255
259
  // filters the tombstone — and both refuse as `parent_not_found`.
256
260
  if (parent === null) {
@@ -288,6 +292,22 @@ export function createReportSessionSpawn(deps) {
288
292
  .andThen((child) => ResultAsync.fromSafePromise(pinSessionSnapshot(child))));
289
293
  });
290
294
  });
295
+ // The emit rides the ok channel alone, thus a refusal and a fault tell the
296
+ // embedder nothing. The child row is on disk at this point, and no
297
+ // authoring act of the session ran yet.
298
+ //
299
+ // The kind comes from the row and not from the input, because the insert
300
+ // is idempotent and the store tells a caller to read the type back.
301
+ return spawned.map((child) => {
302
+ observe({
303
+ type: "create-session",
304
+ analysisId: child.analysisId,
305
+ threadId: child.threadId,
306
+ sessionKind: child.threadType,
307
+ parentThreadId,
308
+ });
309
+ return child;
310
+ });
291
311
  }
292
312
  function listReportSessions(analysisId, paging = {}) {
293
313
  return store.listThreads({ analysisId, type: "report", ...paging });
@@ -200,15 +200,15 @@ export declare const ChartAxesSchema: z.ZodObject<{
200
200
  x: z.ZodOptional<z.ZodObject<{
201
201
  title: z.ZodOptional<z.ZodString>;
202
202
  scale: z.ZodOptional<z.ZodEnum<{
203
- log: "log";
204
203
  linear: "linear";
204
+ log: "log";
205
205
  }>>;
206
206
  }, z.core.$strict>>;
207
207
  y: z.ZodOptional<z.ZodObject<{
208
208
  title: z.ZodOptional<z.ZodString>;
209
209
  scale: z.ZodOptional<z.ZodEnum<{
210
- log: "log";
211
210
  linear: "linear";
211
+ log: "log";
212
212
  }>>;
213
213
  }, z.core.$strict>>;
214
214
  }, z.core.$strict>;
@@ -297,15 +297,15 @@ export declare const ChartCompositionSchema: z.ZodObject<{
297
297
  x: z.ZodOptional<z.ZodObject<{
298
298
  title: z.ZodOptional<z.ZodString>;
299
299
  scale: z.ZodOptional<z.ZodEnum<{
300
- log: "log";
301
300
  linear: "linear";
301
+ log: "log";
302
302
  }>>;
303
303
  }, z.core.$strict>>;
304
304
  y: z.ZodOptional<z.ZodObject<{
305
305
  title: z.ZodOptional<z.ZodString>;
306
306
  scale: z.ZodOptional<z.ZodEnum<{
307
- log: "log";
308
307
  linear: "linear";
308
+ log: "log";
309
309
  }>>;
310
310
  }, z.core.$strict>>;
311
311
  }, z.core.$strict>>;
@@ -724,15 +724,15 @@ export declare const ChartBlockSchema: z.ZodObject<{
724
724
  x: z.ZodOptional<z.ZodObject<{
725
725
  title: z.ZodOptional<z.ZodString>;
726
726
  scale: z.ZodOptional<z.ZodEnum<{
727
- log: "log";
728
727
  linear: "linear";
728
+ log: "log";
729
729
  }>>;
730
730
  }, z.core.$strict>>;
731
731
  y: z.ZodOptional<z.ZodObject<{
732
732
  title: z.ZodOptional<z.ZodString>;
733
733
  scale: z.ZodOptional<z.ZodEnum<{
734
- log: "log";
735
734
  linear: "linear";
735
+ log: "log";
736
736
  }>>;
737
737
  }, z.core.$strict>>;
738
738
  }, z.core.$strict>>;
@@ -1168,15 +1168,15 @@ export declare const ATOM_BLOCK_SCHEMAS: readonly [z.ZodObject<{
1168
1168
  x: z.ZodOptional<z.ZodObject<{
1169
1169
  title: z.ZodOptional<z.ZodString>;
1170
1170
  scale: z.ZodOptional<z.ZodEnum<{
1171
- log: "log";
1172
1171
  linear: "linear";
1172
+ log: "log";
1173
1173
  }>>;
1174
1174
  }, z.core.$strict>>;
1175
1175
  y: z.ZodOptional<z.ZodObject<{
1176
1176
  title: z.ZodOptional<z.ZodString>;
1177
1177
  scale: z.ZodOptional<z.ZodEnum<{
1178
- log: "log";
1179
1178
  linear: "linear";
1179
+ log: "log";
1180
1180
  }>>;
1181
1181
  }, z.core.$strict>>;
1182
1182
  }, z.core.$strict>>;
@@ -240,9 +240,9 @@ export declare const StepOutputFileSchema: z.ZodObject<{
240
240
  fileType: z.ZodEnum<{
241
241
  output: "output";
242
242
  summary: "summary";
243
- script: "script";
244
- figure: "figure";
245
243
  log: "log";
244
+ figure: "figure";
245
+ script: "script";
246
246
  notebook: "notebook";
247
247
  }>;
248
248
  description: z.ZodString;
@@ -259,9 +259,9 @@ export declare const StepOutputPartSchema: z.ZodObject<{
259
259
  fileType: z.ZodEnum<{
260
260
  output: "output";
261
261
  summary: "summary";
262
- script: "script";
263
- figure: "figure";
264
262
  log: "log";
263
+ figure: "figure";
264
+ script: "script";
265
265
  notebook: "notebook";
266
266
  }>;
267
267
  description: z.ZodString;
@@ -597,9 +597,9 @@ export declare const CortexChatPartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
597
597
  fileType: z.ZodEnum<{
598
598
  output: "output";
599
599
  summary: "summary";
600
- script: "script";
601
- figure: "figure";
602
600
  log: "log";
601
+ figure: "figure";
602
+ script: "script";
603
603
  notebook: "notebook";
604
604
  }>;
605
605
  description: z.ZodString;
package/dist/index.d.ts CHANGED
@@ -36,6 +36,9 @@ export type { ProvenanceCollector } from "./provenance/collector.js";
36
36
  export { UnavailableSessionPagePublisher } from "./tools/report-session/session-page-publisher.js";
37
37
  export type { MakeSessionPagePublisher, SessionPagePublisher, SessionPageMintResult } from "./tools/report-session/session-page-publisher.js";
38
38
  export type { ResolvePageUrl, SessionPageAccess } from "./tools/report-session/index.js";
39
+ export { bindReadExport, bindSessionEmit } from "./provenance/seam.js";
40
+ export type { ProvenanceExport, ProvenanceSeam, RunProvenanceEvent, SessionProvenanceEvent } from "./provenance/seam.js";
41
+ export type { DerivationSource } from "./state/report-session-state.js";
39
42
  export { createDbosRunLauncher } from "./execution/dbos-run-launcher.js";
40
43
  export type { RunLauncher, LaunchOptions } from "./execution/run-launcher.js";
41
44
  export { createDbosWorkflowPurger } from "./execution/dbos-workflow-purger.js";
@@ -140,7 +143,7 @@ export { deliverExecEvent, execEventTopic } from "./sandbox/deliver-exec-event.j
140
143
  export { ExecEventMessageSchema } from "./sandbox/types.js";
141
144
  export type { ExecEventMessage, DoneMarker } from "./sandbox/types.js";
142
145
  export { registerExecuteAnalysis } from "./workflows/execute-analysis.js";
143
- export type { ExecuteAnalysisDeps, ExecuteAnalysisInput, ExecuteAnalysisResult, RunObservation, RunProvenanceEvent, RunStepObservation, StepRuntimeStatus, } from "./workflows/execute-analysis.js";
146
+ export type { ExecuteAnalysisDeps, ExecuteAnalysisInput, ExecuteAnalysisResult, RunObservation, RunStepObservation, StepRuntimeStatus, } from "./workflows/execute-analysis.js";
144
147
  export { registerSandboxStep } from "./workflows/sandbox-step.js";
145
148
  export type { SandboxStepDeps, SandboxStepInput, SandboxStepResult, SandboxAgentBuildContext } from "./workflows/sandbox-step.js";
146
149
  export { DEFAULT_CLAIM_INVESTIGATION_CONFIG } from "./workflows/target-assessment/investigation/index.js";
package/dist/index.js CHANGED
@@ -47,6 +47,14 @@ export { createNoopArtifactRegistry } from "./execution/noop-artifact-registry.j
47
47
  // state. The URL seam of the eyes rides beside the publisher factory: a host whose
48
48
  // browser cannot reach the workspace tree binds a served URL for the look.
49
49
  export { UnavailableSessionPagePublisher } from "./tools/report-session/session-page-publisher.js";
50
+ // Seam: the one provenance surface. The embedder records the facts of a run and the acts of a
51
+ // session in the document of the analysis that it owns, and it gives the bytes of that document
52
+ // back for the report page. Thus the seam and its vocabulary belong on the front door: an embedder
53
+ // cannot type its realization without them. The two binders ride beside the types, because an
54
+ // embedder that calls `prepareChatTurn` or one tool factory directly binds the same total forms
55
+ // that the assembly binds. `DerivationSource` rides beside the event union, because the derivation
56
+ // event carries the chain of the derived table.
57
+ export { bindReadExport, bindSessionEmit } from "./provenance/seam.js";
50
58
  // Run launching.
51
59
  export { createDbosRunLauncher } from "./execution/dbos-run-launcher.js";
52
60
  // Seam: workflow-ledger reclamation — cancels and deletes an analysis's rows in
@@ -0,0 +1,203 @@
1
+ /**
2
+ * The provenance seam -- the one surface through which the harness tells an embedder what happened.
3
+ *
4
+ * Three duties answer one host concern. A durable run states the facts of its own lifecycle. A session
5
+ * states the acts that make a report. The report page needs the signed document of the analysis back, as
6
+ * bytes. The embedder holds that document, thus the harness declares the vocabulary and the seam alone,
7
+ * and it keeps no ledger of its own.
8
+ *
9
+ * One surface carries the three, and each member is optional alone. Thus a composition binds the members
10
+ * that it records, and the harness reads an absent member as absence and never as an error. The run emit
11
+ * takes the `RunSession` of the run, because a durable run carries one and a persisting realization needs
12
+ * its credential. A session act carries no session, thus the session emit takes the event alone.
13
+ *
14
+ * Both emit members are fire-and-forget. Neither gives a result, and the harness reads none. A site emits
15
+ * after its act lands, thus a refused act and a failed act each emit nothing and the record states what
16
+ * happened.
17
+ */
18
+ import type { RunSession } from "../auth/types.js";
19
+ import type { Logger } from "../lib/logger.js";
20
+ import type { ProvenanceExport } from "../report-render/provenance-data.js";
21
+ import type { AuthoringBlock } from "../report-model/authoring-grammar.js";
22
+ import type { ExecuteAnalysisFinalStatus } from "../workflows/execute-analysis.js";
23
+ export type { ProvenanceExport } from "../report-render/provenance-data.js";
24
+ /**
25
+ * A run-lifecycle provenance observation handed to an optional host observer.
26
+ * Harness-owned plain union — the harness stays tsprov-free and bus-free; the
27
+ * host maps these execution facts onto its own ledger vocabulary.
28
+ *
29
+ * Every `atMs` is epoch milliseconds read via `await DBOS.now()`, a checkpointed
30
+ * step: a body re-executed by DBOS recovery reads the recorded value, so a
31
+ * re-emitted event carries the identical timestamp and merges on the host's
32
+ * ledger without a value conflict. Never source these from a wall clock
33
+ * (`Date.now()`) — that would diverge across replays and defeat the merge.
34
+ *
35
+ * `run_completed.durationMs` is the terminal `atMs` minus the `run_started`
36
+ * `atMs` (both `DBOS.now()` reads) — the true workflow-observed run span.
37
+ *
38
+ * `step_completed` fires once at EVERY scheduler-loop settlement — the only site
39
+ * that observes every executed step (registration sees only artifact-producing
40
+ * steps, and a child cannot observe its own parent-driven cancel). Steps that
41
+ * were never dispatched (dependents of a failed sibling) emit nothing by design;
42
+ * the run's terminal status carries that outcome. `status` maps the settlement
43
+ * outcome: `complete` → `"completed"`, `canceled` → `"canceled"`,
44
+ * `failed`/`blocked`/child-error → `"failed"`.
45
+ *
46
+ * `run_completed` fires at BOTH terminal boundaries (success and failure); the
47
+ * `status` field distinguishes them.
48
+ */
49
+ export type RunProvenanceEvent = {
50
+ type: "run_started";
51
+ analysisId: string;
52
+ runId: string;
53
+ planSummary: string;
54
+ stepCount: number;
55
+ atMs: number;
56
+ } | {
57
+ type: "step_completed";
58
+ analysisId: string;
59
+ runId: string;
60
+ stepId: string;
61
+ /** Settlement outcome mapped to a terminal step status. */
62
+ status: "completed" | "failed" | "canceled";
63
+ /** The child's durable execution duration; absent when the child settled by throwing. */
64
+ durationMs?: number;
65
+ atMs: number;
66
+ } | {
67
+ type: "run_completed";
68
+ analysisId: string;
69
+ runId: string;
70
+ /**
71
+ * The body's terminal status. Both boundary sites resolve it through `deriveFinalStatus`,
72
+ * which records a budget pause as `"canceled"` — so `"suspended_insufficient_funds"` (a
73
+ * `RunStatus` member) is never emitted here and is deliberately absent from this narrower
74
+ * `ExecuteAnalysisFinalStatus`-minus-`"running"` set.
75
+ */
76
+ status: Exclude<ExecuteAnalysisFinalStatus, "running">;
77
+ atMs: number;
78
+ /** `atMs − run_started.atMs`: the workflow-observed run span in ms. */
79
+ durationMs: number;
80
+ };
81
+ /**
82
+ * One act on one block of a draft.
83
+ *
84
+ * The kind rides beside the id, because an id alone tells a reader nothing about what the act touched.
85
+ * The kind is the one that the document holds after the act: a change is permitted to change the kind,
86
+ * and a remove states the kind that the draft carried before it.
87
+ */
88
+ type BlockAct = {
89
+ analysisId: string;
90
+ threadId: string;
91
+ blockId: string;
92
+ blockKind: AuthoringBlock["kind"];
93
+ };
94
+ /**
95
+ * One observation of a session.
96
+ *
97
+ * The union names one event for each act: the start of a session, the four block operations, the title,
98
+ * the derivation, the preview, and the record. Each event carries the analysis and the thread, thus a
99
+ * consumer places it with no lookup. The title sits on the document, thus the title event names no block.
100
+ *
101
+ * Each member carries what its own site holds at the moment that the act lands, and nothing more. The
102
+ * derivation carries the chain of the table, because the site pins it and a consumer cannot rebuild it
103
+ * from the path. The preview carries the page and the hash of the draft that it shows, thus a consumer
104
+ * ties a page to the document that made it.
105
+ *
106
+ * The `create-session` event names the kind of the new session, and it names the parent thread of a child
107
+ * session. An analysis holds sessions of more than one kind, and the thread id alone says neither. Thus
108
+ * the document of the analysis tells the whole tree of the sessions, and a reader walks it with no second
109
+ * read of the seam. A root session carries no parent, thus `parentThreadId` is absent on it.
110
+ */
111
+ export type SessionProvenanceEvent = {
112
+ type: "create-session";
113
+ analysisId: string;
114
+ threadId: string;
115
+ sessionKind: "conversation" | "report";
116
+ parentThreadId?: string;
117
+ } | ({
118
+ type: "add-block";
119
+ } & BlockAct) | ({
120
+ type: "change-block";
121
+ } & BlockAct) | ({
122
+ type: "remove-block";
123
+ } & BlockAct) | ({
124
+ type: "move-block";
125
+ } & BlockAct) | {
126
+ type: "set-title";
127
+ analysisId: string;
128
+ threadId: string;
129
+ title: string;
130
+ } | {
131
+ type: "run-derivation";
132
+ analysisId: string;
133
+ threadId: string;
134
+ outputPath: string;
135
+ outputHash: string;
136
+ scriptHash: string;
137
+ sources: ReadonlyArray<{
138
+ path: string;
139
+ hash: string;
140
+ }>;
141
+ } | {
142
+ type: "preview";
143
+ analysisId: string;
144
+ threadId: string;
145
+ pagePath: string;
146
+ documentHash: string;
147
+ } | {
148
+ type: "record-version";
149
+ analysisId: string;
150
+ threadId: string;
151
+ versionId: string;
152
+ replaced: boolean;
153
+ };
154
+ /**
155
+ * The provenance seam of the harness. An embedder realizes the members that it records, and the harness
156
+ * never branches on which realization is bound.
157
+ */
158
+ export type ProvenanceSeam = {
159
+ /**
160
+ * The run engine emits the facts of one run here.
161
+ *
162
+ * Synchronous by signature (`void`, never a promise): a host that needs input and output dispatches
163
+ * the work rather than making a workflow body wait behind it. The `RunSession` rides from the durable
164
+ * workflow input, because a persisting realization needs the credential, the scope, and the identity
165
+ * of the run. A realization can ignore the parameter.
166
+ */
167
+ readonly emitRunEvent?: (event: RunProvenanceEvent, session: RunSession) => void;
168
+ /**
169
+ * A session emits one act here. A session act runs under no authorized run, thus this member takes
170
+ * the event alone. It is synchronous by signature for the same reason as the run emit.
171
+ */
172
+ readonly emitSessionEvent?: (event: SessionProvenanceEvent) => void;
173
+ /**
174
+ * The page staging reads the signed document of one analysis here.
175
+ *
176
+ * The member can be async, because a source can read a file or ask a service. It gives the bytes and
177
+ * the attestation, or `undefined`. Absence is a normal result, not a fault: the page then carries no
178
+ * provenance asset, and each other part of the render is what it was.
179
+ */
180
+ readonly readExport?: (analysisId: string) => ProvenanceExport | undefined | Promise<ProvenanceExport | undefined>;
181
+ };
182
+ /**
183
+ * Bind the optional session emit and the logger of one site into a total emit.
184
+ *
185
+ * An absent member gives a call that does nothing. Thus a site emits where its act lands with no test of
186
+ * its own, and absence stays a normal condition.
187
+ *
188
+ * The harness is host-agnostic, and it cannot assume that a host callback is total. Thus a throw of the
189
+ * realization reaches the log and stops there: an observation is a record of an act, and a defect in it
190
+ * must never undo the act that already landed.
191
+ */
192
+ export declare function bindSessionEmit(seam: ProvenanceSeam | undefined, logger: Logger): (event: SessionProvenanceEvent) => void;
193
+ /**
194
+ * Bind the optional document read and the logger of one site into a total read.
195
+ *
196
+ * An absent member gives a call that answers absence. Thus a site asks at its one place with no test of
197
+ * its own, and an unbound composition needs no branch.
198
+ *
199
+ * A throw of the realization reaches the log and becomes absence, for the same reason that a throw of an
200
+ * emit stops at the log: the provenance of the page is an addition to the report, and a defect in it must
201
+ * never cost the render.
202
+ */
203
+ export declare function bindReadExport(seam: ProvenanceSeam | undefined, logger: Logger): (analysisId: string) => Promise<ProvenanceExport | undefined>;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The provenance seam -- the one surface through which the harness tells an embedder what happened.
3
+ *
4
+ * Three duties answer one host concern. A durable run states the facts of its own lifecycle. A session
5
+ * states the acts that make a report. The report page needs the signed document of the analysis back, as
6
+ * bytes. The embedder holds that document, thus the harness declares the vocabulary and the seam alone,
7
+ * and it keeps no ledger of its own.
8
+ *
9
+ * One surface carries the three, and each member is optional alone. Thus a composition binds the members
10
+ * that it records, and the harness reads an absent member as absence and never as an error. The run emit
11
+ * takes the `RunSession` of the run, because a durable run carries one and a persisting realization needs
12
+ * its credential. A session act carries no session, thus the session emit takes the event alone.
13
+ *
14
+ * Both emit members are fire-and-forget. Neither gives a result, and the harness reads none. A site emits
15
+ * after its act lands, thus a refused act and a failed act each emit nothing and the record states what
16
+ * happened.
17
+ */
18
+ /**
19
+ * Bind the optional session emit and the logger of one site into a total emit.
20
+ *
21
+ * An absent member gives a call that does nothing. Thus a site emits where its act lands with no test of
22
+ * its own, and absence stays a normal condition.
23
+ *
24
+ * The harness is host-agnostic, and it cannot assume that a host callback is total. Thus a throw of the
25
+ * realization reaches the log and stops there: an observation is a record of an act, and a defect in it
26
+ * must never undo the act that already landed.
27
+ */
28
+ export function bindSessionEmit(seam, logger) {
29
+ const emit = seam?.emitSessionEvent;
30
+ if (emit === undefined) {
31
+ return () => undefined;
32
+ }
33
+ return (event) => {
34
+ try {
35
+ emit(event);
36
+ }
37
+ catch (cause) {
38
+ logger.error("the session emit of the provenance seam threw", {
39
+ analysisId: event.analysisId,
40
+ threadId: event.threadId,
41
+ event: event.type,
42
+ ...logger.errorFields(cause),
43
+ });
44
+ }
45
+ };
46
+ }
47
+ /**
48
+ * Bind the optional document read and the logger of one site into a total read.
49
+ *
50
+ * An absent member gives a call that answers absence. Thus a site asks at its one place with no test of
51
+ * its own, and an unbound composition needs no branch.
52
+ *
53
+ * A throw of the realization reaches the log and becomes absence, for the same reason that a throw of an
54
+ * emit stops at the log: the provenance of the page is an addition to the report, and a defect in it must
55
+ * never cost the render.
56
+ */
57
+ export function bindReadExport(seam, logger) {
58
+ const read = seam?.readExport;
59
+ if (read === undefined) {
60
+ return () => Promise.resolve(undefined);
61
+ }
62
+ return async (analysisId) => {
63
+ try {
64
+ return await read(analysisId);
65
+ }
66
+ catch (cause) {
67
+ logger.error("the document read of the provenance seam threw", { analysisId, ...logger.errorFields(cause) });
68
+ return undefined;
69
+ }
70
+ };
71
+ }