@inflexa-ai/harness 0.31.0 → 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.
- package/dist/agents/conversation-agent.d.ts +7 -0
- package/dist/agents/conversation-agent.js +1 -0
- package/dist/agents/report-session-agent.d.ts +9 -0
- package/dist/agents/report-session-agent.js +10 -2
- package/dist/app/chat-turn.d.ts +11 -0
- package/dist/app/chat-turn.js +17 -1
- package/dist/app/spawn-report-session.d.ts +12 -0
- package/dist/app/spawn-report-session.js +21 -1
- package/dist/contracts/report-blocks.d.ts +8 -8
- package/dist/contracts/schemas/chat-parts.d.ts +6 -6
- package/dist/index.d.ts +4 -1
- package/dist/index.js +8 -0
- package/dist/prompts/briefing.d.ts +12 -1
- package/dist/prompts/briefing.js +27 -1
- package/dist/prompts/sandbox-standards.d.ts +1 -1
- package/dist/prompts/sandbox-standards.js +27 -0
- package/dist/provenance/seam.d.ts +203 -0
- package/dist/provenance/seam.js +71 -0
- package/dist/report-model/authoring-grammar.d.ts +2 -2
- package/dist/report-render/assets.d.ts +10 -0
- package/dist/report-render/assets.js +14 -0
- package/dist/report-render/design.js +256 -0
- package/dist/report-render/page.d.ts +75 -0
- package/dist/report-render/page.js +793 -1
- package/dist/report-render/provenance-data.d.ts +42 -0
- package/dist/report-render/provenance-data.js +62 -0
- package/dist/report-render/render.d.ts +20 -1
- package/dist/report-render/render.js +23 -10
- package/dist/report-render/types.d.ts +3 -0
- package/dist/report-render/views/chart-view.d.ts +5 -1
- package/dist/report-render/views/chart-view.js +6 -2
- package/dist/report-render/views/lineage.d.ts +93 -0
- package/dist/report-render/views/lineage.js +86 -0
- package/dist/report-render/views/page-view.d.ts +22 -1
- package/dist/report-render/views/page-view.js +12 -3
- package/dist/report-render/views/prose.d.ts +7 -1
- package/dist/report-render/views/prose.js +10 -6
- package/dist/report-render/views/references-view.d.ts +17 -1
- package/dist/report-render/views/references-view.js +12 -4
- package/dist/report-render/views/values.d.ts +17 -4
- package/dist/report-render/views/values.js +21 -8
- package/dist/runtime/assemble.d.ts +22 -0
- package/dist/runtime/assemble.js +4 -0
- package/dist/sandbox/k8s-client.d.ts +0 -5
- package/dist/sandbox/k8s-client.js +9 -115
- package/dist/sandbox/thread-env.d.ts +18 -12
- package/dist/sandbox/thread-env.js +36 -25
- package/dist/schemas/artifact-manifest.d.ts +4 -4
- package/dist/tools/display/show-user.d.ts +1 -1
- package/dist/tools/report-authoring/authoring-tools.d.ts +16 -1
- package/dist/tools/report-authoring/authoring-tools.js +58 -16
- package/dist/tools/report-session/derive-table.d.ts +3 -0
- package/dist/tools/report-session/derive-table.js +15 -0
- package/dist/tools/report-session/preview-report.d.ts +13 -4
- package/dist/tools/report-session/preview-report.js +23 -6
- package/dist/tools/report-session/record-version.d.ts +3 -0
- package/dist/tools/report-session/record-version.js +7 -3
- package/dist/tools/start-report-session.d.ts +7 -0
- package/dist/tools/start-report-session.js +1 -0
- package/dist/workflows/execute-analysis.d.ts +15 -69
- package/dist/workflows/execute-analysis.js +5 -4
- package/package.json +2 -1
|
@@ -8,9 +8,9 @@ import { z } from "zod";
|
|
|
8
8
|
/** Artifact type inferred from the step subdirectory. */
|
|
9
9
|
export declare const ArtifactTypeSchema: z.ZodEnum<{
|
|
10
10
|
output: "output";
|
|
11
|
-
script: "script";
|
|
12
|
-
figure: "figure";
|
|
13
11
|
log: "log";
|
|
12
|
+
figure: "figure";
|
|
13
|
+
script: "script";
|
|
14
14
|
notebook: "notebook";
|
|
15
15
|
}>;
|
|
16
16
|
export type ArtifactType = z.infer<typeof ArtifactTypeSchema>;
|
|
@@ -22,9 +22,9 @@ export declare const ArtifactManifestEntrySchema: z.ZodObject<{
|
|
|
22
22
|
size: z.ZodNumber;
|
|
23
23
|
type: z.ZodEnum<{
|
|
24
24
|
output: "output";
|
|
25
|
-
script: "script";
|
|
26
|
-
figure: "figure";
|
|
27
25
|
log: "log";
|
|
26
|
+
figure: "figure";
|
|
27
|
+
script: "script";
|
|
28
28
|
notebook: "notebook";
|
|
29
29
|
}>;
|
|
30
30
|
hash: z.ZodOptional<z.ZodString>;
|
|
@@ -23,7 +23,7 @@ type ShowUserOutput = {
|
|
|
23
23
|
id: string;
|
|
24
24
|
};
|
|
25
25
|
export declare const showUserTool: import("../define-tool.js").Tool<{
|
|
26
|
-
kind: "code" | "markdown" | "
|
|
26
|
+
kind: "code" | "markdown" | "table" | "echart" | "svg";
|
|
27
27
|
title?: string | undefined;
|
|
28
28
|
spec?: Record<string, unknown> | undefined;
|
|
29
29
|
dataPath?: string | undefined;
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
import { type Result } from "neverthrow";
|
|
20
20
|
import { z } from "zod";
|
|
21
21
|
import type { Scope } from "../../auth/types.js";
|
|
22
|
+
import type { Logger } from "../../lib/logger.js";
|
|
23
|
+
import { type ProvenanceSeam } from "../../provenance/seam.js";
|
|
22
24
|
import { type DraftRefusal } from "../../report-model/draft-operations.js";
|
|
23
25
|
import { type OutlineEntry, type ReadableBlock } from "../../report-model/draft-read.js";
|
|
24
26
|
import { type FinishResult } from "../../report-model/draft-finish.js";
|
|
@@ -289,12 +291,25 @@ export interface ReportAuthoringTools {
|
|
|
289
291
|
readonly read_block: Tool<ReadBlockInput, ReadBlockResult>;
|
|
290
292
|
readonly finish_draft: Tool<FinishDraftInput, FinishToolResult>;
|
|
291
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* The optional deps of the authoring surface. The gateway is the one mandatory part, thus it stays a
|
|
296
|
+
* parameter of its own and this bag holds what a composition can leave out.
|
|
297
|
+
*/
|
|
298
|
+
export interface ReportAuthoringToolDeps {
|
|
299
|
+
/** The provenance seam; an unbound session emit emits nothing and each tool acts the same. */
|
|
300
|
+
readonly provenance?: ProvenanceSeam;
|
|
301
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
302
|
+
readonly logger?: Logger;
|
|
303
|
+
}
|
|
292
304
|
/**
|
|
293
305
|
* Make the eight authoring tools over a session-state gateway.
|
|
294
306
|
*
|
|
295
307
|
* Each tool reads the thread id from the scope of the call, and it loads the state of that thread through
|
|
296
308
|
* the gateway. Thus one factory serves every thread, and two threads never share one draft. A mutation
|
|
297
309
|
* persists the new document before it reports `applied: true`, thus a reported landing is never lost.
|
|
310
|
+
*
|
|
311
|
+
* A landed mutation emits one observation. The emit sits after the persist, thus a refused operation and a
|
|
312
|
+
* failed persist each emit nothing.
|
|
298
313
|
*/
|
|
299
|
-
export declare function createReportAuthoringTools(gateway: ReportSessionStateGateway): ReportAuthoringTools;
|
|
314
|
+
export declare function createReportAuthoringTools(gateway: ReportSessionStateGateway, deps?: ReportAuthoringToolDeps): ReportAuthoringTools;
|
|
300
315
|
export {};
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
import { err, ok } from "neverthrow";
|
|
20
20
|
import { basename } from "node:path";
|
|
21
21
|
import { z } from "zod";
|
|
22
|
+
import { createNoopLogger } from "../../lib/console-logger.js";
|
|
22
23
|
import { capCodePoints, DETAIL_NEEDLE_MAX_LENGTH } from "../../loop/tool-detail.js";
|
|
24
|
+
import { bindSessionEmit } from "../../provenance/seam.js";
|
|
23
25
|
import { isSafeId } from "../../workspace/paths.js";
|
|
24
26
|
import { addBlock, changeBlock, locate, moveBlock, removeBlock, setTitle, } from "../../report-model/draft-operations.js";
|
|
25
27
|
import { buildOutline, childOutline, readBlock } from "../../report-model/draft-read.js";
|
|
@@ -299,6 +301,23 @@ function addedSubject(kind, block) {
|
|
|
299
301
|
function holderIdOf(document, blockId) {
|
|
300
302
|
return locate(document, blockId)?.parent?.id;
|
|
301
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* The event of one block act, or `undefined` when the document holds no block of that id.
|
|
306
|
+
*
|
|
307
|
+
* The kind rides beside the id, thus a reader of the record sees what the act touched and it needs no
|
|
308
|
+
* document of its own. The kind comes out of the document that holds the block after the act: the next
|
|
309
|
+
* document for an add, a change, and a move, and the previous one for a remove.
|
|
310
|
+
*
|
|
311
|
+
* A lookup of a landed operation always finds its block. The absent arm keeps the read total, and it drops
|
|
312
|
+
* the event rather than state a kind that no block carries.
|
|
313
|
+
*/
|
|
314
|
+
function blockActOf(type, scope, document, blockId) {
|
|
315
|
+
const kind = locate(document, blockId)?.block.kind;
|
|
316
|
+
if (kind === undefined) {
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
return { type, analysisId: scope.analysisId, threadId: scope.threadId, blockId, blockKind: kind };
|
|
320
|
+
}
|
|
302
321
|
/** Read the child order of one container out of a document. */
|
|
303
322
|
function containerIn(document, parentId) {
|
|
304
323
|
if (parentId === undefined) {
|
|
@@ -326,8 +345,12 @@ const AUTHORING_EXECUTION_MODE = "inline";
|
|
|
326
345
|
* Each tool reads the thread id from the scope of the call, and it loads the state of that thread through
|
|
327
346
|
* the gateway. Thus one factory serves every thread, and two threads never share one draft. A mutation
|
|
328
347
|
* persists the new document before it reports `applied: true`, thus a reported landing is never lost.
|
|
348
|
+
*
|
|
349
|
+
* A landed mutation emits one observation. The emit sits after the persist, thus a refused operation and a
|
|
350
|
+
* failed persist each emit nothing.
|
|
329
351
|
*/
|
|
330
|
-
export function createReportAuthoringTools(gateway) {
|
|
352
|
+
export function createReportAuthoringTools(gateway, deps = {}) {
|
|
353
|
+
const observe = bindSessionEmit(deps.provenance, (deps.logger ?? createNoopLogger()).named("report-authoring"));
|
|
331
354
|
/**
|
|
332
355
|
* Resolve the thread of the call, and load its state.
|
|
333
356
|
*
|
|
@@ -346,8 +369,13 @@ export function createReportAuthoringTools(gateway) {
|
|
|
346
369
|
*
|
|
347
370
|
* The persist is a compare-and-swap against the token that the load read. A concurrent turn that landed
|
|
348
371
|
* first turns the persist into a conflict, and the tool refuses with `stale-state`.
|
|
372
|
+
*
|
|
373
|
+
* `event` is the observation of the operation, and it emits after the persist landed. Thus one site owns
|
|
374
|
+
* the rule, and a refusal, a conflict, and a persist fault each emit nothing. It reads the two documents
|
|
375
|
+
* for the same reason that `holders` does: a removed block sits in the first alone, and an added block
|
|
376
|
+
* sits in the second alone. A caller that cannot name the block of its own operation gives no event.
|
|
349
377
|
*/
|
|
350
|
-
const land = async (threadId, token, previous, result, holders) => {
|
|
378
|
+
const land = async (threadId, token, previous, result, holders, event) => {
|
|
351
379
|
if (result.isErr()) {
|
|
352
380
|
return { applied: false, refusal: result.error };
|
|
353
381
|
}
|
|
@@ -362,6 +390,10 @@ export function createReportAuthoringTools(gateway) {
|
|
|
362
390
|
if (persisted.outcome === "failed") {
|
|
363
391
|
return { applied: false, refusal: { reason: "state-unavailable", detail: persisted.detail } };
|
|
364
392
|
}
|
|
393
|
+
const observed = event(previous, next);
|
|
394
|
+
if (observed !== undefined) {
|
|
395
|
+
observe(observed);
|
|
396
|
+
}
|
|
365
397
|
// A move inside one container names the same holder two times.
|
|
366
398
|
const unique = [...new Set(holders(previous, next))];
|
|
367
399
|
return { applied: true, changed: unique.map((parentId) => containerIn(next, parentId)) };
|
|
@@ -390,14 +422,17 @@ export function createReportAuthoringTools(gateway) {
|
|
|
390
422
|
if (opened.isErr()) {
|
|
391
423
|
return ok(refuse(opened.error));
|
|
392
424
|
}
|
|
393
|
-
const { threadId, state, token } = opened.value;
|
|
425
|
+
const { threadId, analysisId, state, token } = opened.value;
|
|
394
426
|
const destination = toDestination(input);
|
|
395
427
|
if (destination.isErr()) {
|
|
396
428
|
return ok(refuse(destination.error));
|
|
397
429
|
}
|
|
398
430
|
const block = decodeBlockPayload(input.block);
|
|
399
431
|
const addedId = readId(block);
|
|
400
|
-
return ok(await land(threadId, token, state.document, addBlock(state.document, { block, destination: destination.value }, state.snapshot), (_previous, next) => [addedId === undefined ? undefined : holderIdOf(next, addedId)]
|
|
432
|
+
return ok(await land(threadId, token, state.document, addBlock(state.document, { block, destination: destination.value }, state.snapshot), (_previous, next) => [addedId === undefined ? undefined : holderIdOf(next, addedId)],
|
|
433
|
+
// The grammar requires an id on the payload, thus a landed add always names one. A
|
|
434
|
+
// payload with no id reaches the core, which refuses it, and no event is due.
|
|
435
|
+
(_previous, next) => (addedId === undefined ? undefined : blockActOf("add-block", { analysisId, threadId }, next, addedId))));
|
|
401
436
|
},
|
|
402
437
|
});
|
|
403
438
|
const change_block = defineTool({
|
|
@@ -413,14 +448,15 @@ export function createReportAuthoringTools(gateway) {
|
|
|
413
448
|
if (opened.isErr()) {
|
|
414
449
|
return ok(refuse(opened.error));
|
|
415
450
|
}
|
|
416
|
-
const { threadId, state, token } = opened.value;
|
|
451
|
+
const { threadId, analysisId, state, token } = opened.value;
|
|
417
452
|
const operation = toChangeOperation(input);
|
|
418
453
|
if (operation.isErr()) {
|
|
419
454
|
return ok(refuse(operation.error));
|
|
420
455
|
}
|
|
421
|
-
return ok(await land(threadId, token, state.document, changeBlock(state.document, operation.value, state.snapshot), (_previous, next) => [
|
|
422
|
-
|
|
423
|
-
|
|
456
|
+
return ok(await land(threadId, token, state.document, changeBlock(state.document, operation.value, state.snapshot), (_previous, next) => [holderIdOf(next, input.targetId)],
|
|
457
|
+
// A change is permitted to change the kind, thus the event states the kind that the
|
|
458
|
+
// document holds after the act.
|
|
459
|
+
(_previous, next) => blockActOf("change-block", { analysisId, threadId }, next, input.targetId)));
|
|
424
460
|
},
|
|
425
461
|
});
|
|
426
462
|
const remove_block = defineTool({
|
|
@@ -434,10 +470,11 @@ export function createReportAuthoringTools(gateway) {
|
|
|
434
470
|
if (opened.isErr()) {
|
|
435
471
|
return ok(refuse(opened.error));
|
|
436
472
|
}
|
|
437
|
-
const { threadId, state, token } = opened.value;
|
|
438
|
-
return ok(await land(threadId, token, state.document, removeBlock(state.document, { targetId: input.targetId }, state.snapshot), (previous) => [
|
|
439
|
-
|
|
440
|
-
|
|
473
|
+
const { threadId, analysisId, state, token } = opened.value;
|
|
474
|
+
return ok(await land(threadId, token, state.document, removeBlock(state.document, { targetId: input.targetId }, state.snapshot), (previous) => [holderIdOf(previous, input.targetId)],
|
|
475
|
+
// The next document holds no removed block, thus the event states the kind that the
|
|
476
|
+
// previous one carried.
|
|
477
|
+
(previous) => blockActOf("remove-block", { analysisId, threadId }, previous, input.targetId)));
|
|
441
478
|
},
|
|
442
479
|
});
|
|
443
480
|
const move_block = defineTool({
|
|
@@ -452,12 +489,12 @@ export function createReportAuthoringTools(gateway) {
|
|
|
452
489
|
if (opened.isErr()) {
|
|
453
490
|
return ok(refuse(opened.error));
|
|
454
491
|
}
|
|
455
|
-
const { threadId, state, token } = opened.value;
|
|
492
|
+
const { threadId, analysisId, state, token } = opened.value;
|
|
456
493
|
const destination = toDestination(input);
|
|
457
494
|
if (destination.isErr()) {
|
|
458
495
|
return ok(refuse(destination.error));
|
|
459
496
|
}
|
|
460
|
-
return ok(await land(threadId, token, state.document, moveBlock(state.document, { targetId: input.targetId, destination: destination.value }, state.snapshot), (previous, next) => [holderIdOf(previous, input.targetId), holderIdOf(next, input.targetId)]));
|
|
497
|
+
return ok(await land(threadId, token, state.document, moveBlock(state.document, { targetId: input.targetId, destination: destination.value }, state.snapshot), (previous, next) => [holderIdOf(previous, input.targetId), holderIdOf(next, input.targetId)], (_previous, next) => blockActOf("move-block", { analysisId, threadId }, next, input.targetId)));
|
|
461
498
|
},
|
|
462
499
|
});
|
|
463
500
|
const set_title = defineTool({
|
|
@@ -473,8 +510,13 @@ export function createReportAuthoringTools(gateway) {
|
|
|
473
510
|
if (opened.isErr()) {
|
|
474
511
|
return ok(refuse(opened.error));
|
|
475
512
|
}
|
|
476
|
-
const { threadId, state, token } = opened.value;
|
|
477
|
-
return ok(await land(threadId, token, state.document, ok(setTitle(state.document, input.title)), () => [])
|
|
513
|
+
const { threadId, analysisId, state, token } = opened.value;
|
|
514
|
+
return ok(await land(threadId, token, state.document, ok(setTitle(state.document, input.title)), () => [], () => ({
|
|
515
|
+
type: "set-title",
|
|
516
|
+
analysisId,
|
|
517
|
+
threadId,
|
|
518
|
+
title: input.title,
|
|
519
|
+
})));
|
|
478
520
|
},
|
|
479
521
|
});
|
|
480
522
|
const read_outline = defineTool({
|
|
@@ -42,6 +42,7 @@ import type { DerivationSource, ReportSessionStateStore } from "../../state/repo
|
|
|
42
42
|
import { type ResolveWorkspaceRoot } from "../../workspace/paths.js";
|
|
43
43
|
import { type Tool } from "../define-tool.js";
|
|
44
44
|
import { type ReportSessionStateGateway, type SessionRefusal } from "../report-authoring/authoring-tools.js";
|
|
45
|
+
import { type ProvenanceSeam } from "../../provenance/seam.js";
|
|
45
46
|
/** The input of one derivation: the script, the declared pinned inputs, and the name of the output file. */
|
|
46
47
|
declare const deriveTableInput: z.ZodObject<{
|
|
47
48
|
script: z.ZodString;
|
|
@@ -114,6 +115,8 @@ export interface DeriveTableToolDeps {
|
|
|
114
115
|
readonly derivations: Pick<ReportSessionStateStore, "appendDerivation">;
|
|
115
116
|
readonly runDerivation?: DeriveTableRunner;
|
|
116
117
|
readonly runAuthorizer?: RunAuthorizer;
|
|
118
|
+
/** The provenance seam; an unbound session emit emits nothing and the derivation runs the same. */
|
|
119
|
+
readonly provenance?: ProvenanceSeam;
|
|
117
120
|
readonly logger?: Logger;
|
|
118
121
|
}
|
|
119
122
|
/** The synthetic run id and step id of one derivation. Both are constants, and neither names a run row. */
|
|
@@ -45,6 +45,7 @@ import { generateExecutionId } from "../../sandbox/execution-id.js";
|
|
|
45
45
|
import { isSafeId, reportSessionDerivedDir, toSandboxPath } from "../../workspace/paths.js";
|
|
46
46
|
import { defineTool } from "../define-tool.js";
|
|
47
47
|
import { openReportThread } from "../report-authoring/authoring-tools.js";
|
|
48
|
+
import { bindSessionEmit } from "../../provenance/seam.js";
|
|
48
49
|
import { readHeaderColumns } from "./list-artifacts.js";
|
|
49
50
|
/** The input of one derivation: the script, the declared pinned inputs, and the name of the output file. */
|
|
50
51
|
const deriveTableInput = z.object({
|
|
@@ -128,6 +129,7 @@ export function describeExecFailure(result) {
|
|
|
128
129
|
*/
|
|
129
130
|
export function createDeriveTableTool(deps) {
|
|
130
131
|
const logger = (deps.logger ?? createNoopLogger()).named("derive-table");
|
|
132
|
+
const observe = bindSessionEmit(deps.provenance, logger);
|
|
131
133
|
const { runDerivation, runAuthorizer } = deps;
|
|
132
134
|
return defineTool({
|
|
133
135
|
id: "derive_table",
|
|
@@ -252,6 +254,19 @@ export function createDeriveTableTool(deps) {
|
|
|
252
254
|
outputName: input.output,
|
|
253
255
|
logger,
|
|
254
256
|
});
|
|
257
|
+
// The record of the derivation lands before this point, thus the event states a table that
|
|
258
|
+
// the session already holds as evidence. Each other arm derived nothing.
|
|
259
|
+
if (result.outcome === "derived") {
|
|
260
|
+
observe({
|
|
261
|
+
type: "run-derivation",
|
|
262
|
+
analysisId,
|
|
263
|
+
threadId,
|
|
264
|
+
outputPath: result.path,
|
|
265
|
+
outputHash: result.hash,
|
|
266
|
+
scriptHash: result.scriptHash,
|
|
267
|
+
sources: result.sources,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
255
270
|
return ok(result);
|
|
256
271
|
}
|
|
257
272
|
finally {
|
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
* and renders the page.
|
|
8
8
|
*
|
|
9
9
|
* The renderer writes no file. It gives the page and the data asset of each table, and this tool stages
|
|
10
|
-
* them beside the figures.
|
|
11
|
-
* the
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* them beside the figures. The tool asks the optional provenance source for the frozen document of the
|
|
11
|
+
* analysis first, and the renderer turns the opaque text into data assets of the same kind. It also writes
|
|
12
|
+
* the script of each derivation that the document references, from the text of the durable record. The
|
|
13
|
+
* shipped libraries and fonts stage under `assets/deps/`, and each report-side file at the root of
|
|
14
|
+
* `assets/`. The stage is authoritative over that root: what this preview wrote stays, and each other file
|
|
15
|
+
* goes.
|
|
14
16
|
*
|
|
15
17
|
* The page and its staged assets land in `report-sessions/{threadId}/` under the workspace root. That
|
|
16
18
|
* namespace belongs to this path alone, thus the tool never writes under the old `previews/` or `reports/`
|
|
@@ -45,6 +47,7 @@ import { type BridgeMismatch } from "../../report-render/value-bridge.js";
|
|
|
45
47
|
import type { RenderProblem } from "../../report-render/types.js";
|
|
46
48
|
import { type Tool } from "../define-tool.js";
|
|
47
49
|
import { type ReportSessionStateGateway, type SessionRefusal } from "../report-authoring/authoring-tools.js";
|
|
50
|
+
import { type ProvenanceSeam } from "../../provenance/seam.js";
|
|
48
51
|
import { type MakeSessionPagePublisher } from "./session-page-publisher.js";
|
|
49
52
|
/** The empty input. The tool renders the current draft of the thread, thus it needs no field. */
|
|
50
53
|
declare const previewReportInput: z.ZodObject<{}, z.core.$strip>;
|
|
@@ -135,6 +138,12 @@ export interface PreviewReportToolDeps {
|
|
|
135
138
|
readonly resolveWorkspaceRoot: ResolveWorkspaceRoot;
|
|
136
139
|
readonly resolvePageAsset?: ResolvePageAsset;
|
|
137
140
|
readonly makeSessionPages?: MakeSessionPagePublisher;
|
|
141
|
+
/**
|
|
142
|
+
* The provenance seam. An unbound session emit emits nothing and the render runs the same. An unbound
|
|
143
|
+
* document read, and a read that holds no document for the analysis, both give absence, and the page
|
|
144
|
+
* then carries no provenance asset.
|
|
145
|
+
*/
|
|
146
|
+
readonly provenance?: ProvenanceSeam;
|
|
138
147
|
readonly logger?: Logger;
|
|
139
148
|
}
|
|
140
149
|
/**
|
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
* and renders the page.
|
|
8
8
|
*
|
|
9
9
|
* The renderer writes no file. It gives the page and the data asset of each table, and this tool stages
|
|
10
|
-
* them beside the figures.
|
|
11
|
-
* the
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* them beside the figures. The tool asks the optional provenance source for the frozen document of the
|
|
11
|
+
* analysis first, and the renderer turns the opaque text into data assets of the same kind. It also writes
|
|
12
|
+
* the script of each derivation that the document references, from the text of the durable record. The
|
|
13
|
+
* shipped libraries and fonts stage under `assets/deps/`, and each report-side file at the root of
|
|
14
|
+
* `assets/`. The stage is authoritative over that root: what this preview wrote stays, and each other file
|
|
15
|
+
* goes.
|
|
14
16
|
*
|
|
15
17
|
* The page and its staged assets land in `report-sessions/{threadId}/` under the workspace root. That
|
|
16
18
|
* namespace belongs to this path alone, thus the tool never writes under the old `previews/` or `reports/`
|
|
@@ -55,6 +57,7 @@ import { ASSETS_DIR, DEPS_DIR, derivationScriptName, PAGE_ASSETS, stagedSource,
|
|
|
55
57
|
import { renderReportPage } from "../../report-render/render.js";
|
|
56
58
|
import { defineTool } from "../define-tool.js";
|
|
57
59
|
import { openReportThread } from "../report-authoring/authoring-tools.js";
|
|
60
|
+
import { bindReadExport, bindSessionEmit } from "../../provenance/seam.js";
|
|
58
61
|
import { describeSessionPageMintFailure } from "./session-page-publisher.js";
|
|
59
62
|
/** The empty input. The tool renders the current draft of the thread, thus it needs no field. */
|
|
60
63
|
const previewReportInput = z.object({});
|
|
@@ -383,6 +386,8 @@ async function mintAccess(makeSessionPages, analysisId, threadId, auth, logger)
|
|
|
383
386
|
*/
|
|
384
387
|
export function createPreviewReportTool(deps) {
|
|
385
388
|
const logger = (deps.logger ?? createNoopLogger()).named("preview-report");
|
|
389
|
+
const observe = bindSessionEmit(deps.provenance, logger);
|
|
390
|
+
const readProvenance = bindReadExport(deps.provenance, logger);
|
|
386
391
|
const resolvePageAsset = deps.resolvePageAsset ?? resolvePageAssetFromInstallation;
|
|
387
392
|
return defineTool({
|
|
388
393
|
id: "preview_report",
|
|
@@ -438,7 +443,15 @@ export function createPreviewReportTool(deps) {
|
|
|
438
443
|
// relative links of its entry, thus the renderer states them and it stages nothing.
|
|
439
444
|
const used = referencedDerivations(document, derivations);
|
|
440
445
|
const sessionDir = reportSessionDir(threadId);
|
|
441
|
-
|
|
446
|
+
// The provenance is the frozen document of the analysis. It rides the render as opaque text, thus
|
|
447
|
+
// the renderer moves it into a data asset and no part of this path reads a field of it. Absence
|
|
448
|
+
// gives a page with no provenance asset, and each other part of the render is unchanged.
|
|
449
|
+
const provenance = await readProvenance(analysisId);
|
|
450
|
+
const rendered = renderReportPage(document, bridged.value, {
|
|
451
|
+
records: snapshot.citationRecords,
|
|
452
|
+
derivations: used.map((record) => chainOf(record, sessionDir)),
|
|
453
|
+
provenance,
|
|
454
|
+
});
|
|
442
455
|
if (rendered.isErr()) {
|
|
443
456
|
return ok({ outcome: "render-problems", problems: rendered.error });
|
|
444
457
|
}
|
|
@@ -471,7 +484,8 @@ export function createPreviewReportTool(deps) {
|
|
|
471
484
|
// The page shows the draft that the finish read. The stamp records that draft, thus the eyes
|
|
472
485
|
// copy this hash and the record compares against it. A failed stamp is a transient store fault,
|
|
473
486
|
// and the page stays on disk. The agent runs the preview again to stamp the marker.
|
|
474
|
-
const
|
|
487
|
+
const renderedHash = computeDraftHash(draft);
|
|
488
|
+
const stamped = await deps.gateway.stampRendered(threadId, renderedHash);
|
|
475
489
|
if (stamped.outcome !== "stamped") {
|
|
476
490
|
const detail = stamped.outcome === "failed" ? stamped.detail : "the session state row is absent";
|
|
477
491
|
logger.warn("the rendered hash did not stamp", { threadId, analysisId, detail });
|
|
@@ -483,6 +497,9 @@ export function createPreviewReportTool(deps) {
|
|
|
483
497
|
type: "data-report-rendered",
|
|
484
498
|
data: { id: randomUUID(), renderedAt: new Date().toISOString(), title: document.title },
|
|
485
499
|
});
|
|
500
|
+
// The event rides the rendered arm alone, the same as the part above. The hash is the hash of
|
|
501
|
+
// the draft that the page shows, thus a consumer ties the page to the document that made it.
|
|
502
|
+
observe({ type: "preview", analysisId, threadId, pagePath: written.value, documentHash: renderedHash });
|
|
486
503
|
const access = await mintAccess(deps.makeSessionPages, analysisId, threadId, ctx.session.auth, logger);
|
|
487
504
|
return ok({ outcome: "rendered", pagePath: written.value, ...(access !== undefined ? { access } : {}) });
|
|
488
505
|
},
|
|
@@ -39,6 +39,7 @@ import type { ReportVersionStore } from "../../state/report-versions.js";
|
|
|
39
39
|
import { type ResolveWorkspaceRoot } from "../../workspace/paths.js";
|
|
40
40
|
import { type Tool } from "../define-tool.js";
|
|
41
41
|
import { type ReportSessionStateGateway, type SessionRefusal } from "../report-authoring/authoring-tools.js";
|
|
42
|
+
import { type ProvenanceSeam } from "../../provenance/seam.js";
|
|
42
43
|
/** The empty input. The tool records the current draft of the thread, thus it needs no field. */
|
|
43
44
|
declare const recordVersionInput: z.ZodObject<{}, z.core.$strip>;
|
|
44
45
|
export type RecordVersionInput = z.infer<typeof recordVersionInput>;
|
|
@@ -101,6 +102,8 @@ export interface RecordVersionToolDeps {
|
|
|
101
102
|
analysisId: string;
|
|
102
103
|
auth: AuthContext;
|
|
103
104
|
}) => ReferenceResolver;
|
|
105
|
+
/** The provenance seam; an unbound session emit emits nothing and the record runs the same. */
|
|
106
|
+
readonly provenance?: ProvenanceSeam;
|
|
104
107
|
readonly logger?: Logger;
|
|
105
108
|
}
|
|
106
109
|
/**
|
|
@@ -42,6 +42,7 @@ import { validateReport } from "../../report-model/validate.js";
|
|
|
42
42
|
import { reportSessionDerivedDir, resolveWorkspacePath } from "../../workspace/paths.js";
|
|
43
43
|
import { defineTool } from "../define-tool.js";
|
|
44
44
|
import { openReportThread } from "../report-authoring/authoring-tools.js";
|
|
45
|
+
import { bindSessionEmit } from "../../provenance/seam.js";
|
|
45
46
|
/** The empty input. The tool records the current draft of the thread, thus it needs no field. */
|
|
46
47
|
const recordVersionInput = z.object({});
|
|
47
48
|
/** A short account of a record refusal, for the detail of a transient failure. */
|
|
@@ -122,6 +123,7 @@ async function pruneUnusedDerivations(args) {
|
|
|
122
123
|
*/
|
|
123
124
|
export function createRecordVersionTool(deps) {
|
|
124
125
|
const logger = (deps.logger ?? createNoopLogger()).named("record-report-version");
|
|
126
|
+
const observe = bindSessionEmit(deps.provenance, logger);
|
|
125
127
|
return defineTool({
|
|
126
128
|
id: "record_report_version",
|
|
127
129
|
description: "Record the current draft as one report version. The tool runs the whole gate first: it finishes the draft, " +
|
|
@@ -203,9 +205,11 @@ export function createRecordVersionTool(deps) {
|
|
|
203
205
|
parentSeq: thread.value.parentSeq,
|
|
204
206
|
});
|
|
205
207
|
if (recorded.isOk()) {
|
|
206
|
-
// The version stands from here
|
|
207
|
-
|
|
208
|
-
//
|
|
208
|
+
// The version stands from here, thus the event states a version that a reader can open.
|
|
209
|
+
observe({ type: "record-version", analysisId, threadId, versionId: recorded.value.versionId, replaced: recorded.value.outcome === "replaced" });
|
|
210
|
+
// The prune reclaims the bytes of each derivation that the recorded document ignores, and it
|
|
211
|
+
// decides nothing about the outcome. It runs on each record, thus an output that an amend
|
|
212
|
+
// unbound goes at the next record.
|
|
209
213
|
await pruneUnusedDerivations({
|
|
210
214
|
resolveWorkspaceRoot: deps.resolveWorkspaceRoot,
|
|
211
215
|
analysisId,
|
|
@@ -24,6 +24,7 @@ import type { AcquireEyes } from "../lib/eyes.js";
|
|
|
24
24
|
import type { Logger } from "../lib/logger.js";
|
|
25
25
|
import type { ThreadType } from "../memory/thread-store.js";
|
|
26
26
|
import { type Tool } from "./define-tool.js";
|
|
27
|
+
import type { ProvenanceSeam } from "../provenance/seam.js";
|
|
27
28
|
/**
|
|
28
29
|
* The brief of the ask, plus the one override of the advice.
|
|
29
30
|
*
|
|
@@ -101,6 +102,12 @@ export interface StartReportSessionToolDeps {
|
|
|
101
102
|
* carries no stale value of its own.
|
|
102
103
|
*/
|
|
103
104
|
readonly anchorSession?: (threadId: string) => Promise<EnsureSessionStateResult>;
|
|
105
|
+
/**
|
|
106
|
+
* The provenance seam of the composition. The tool hands it to the spawn,
|
|
107
|
+
* which emits the creation of the session at the moment that the child lands.
|
|
108
|
+
* The tool emits nothing of its own.
|
|
109
|
+
*/
|
|
110
|
+
readonly provenance?: ProvenanceSeam;
|
|
104
111
|
readonly logger?: Logger;
|
|
105
112
|
}
|
|
106
113
|
/**
|
|
@@ -111,6 +111,7 @@ export function createStartReportSessionTool(deps) {
|
|
|
111
111
|
chrome: deps.chrome,
|
|
112
112
|
...(deps.eyes ? { eyes: deps.eyes } : {}),
|
|
113
113
|
...(deps.anchorSession ? { anchorSession: deps.anchorSession } : {}),
|
|
114
|
+
...(deps.provenance ? { provenance: deps.provenance } : {}),
|
|
114
115
|
...(deps.logger ? { logger: deps.logger } : {}),
|
|
115
116
|
};
|
|
116
117
|
const spawn = createReportSessionSpawn(spawnDeps);
|
|
@@ -69,6 +69,7 @@ import type { SynthesisStatus, UpdateStepExecutionInput } from "../state/index.j
|
|
|
69
69
|
import type { AnalysisStep } from "../schemas/workflow-state.js";
|
|
70
70
|
import type { ChatProvider, EmbeddingProvider } from "../providers/types.js";
|
|
71
71
|
import type { BioToolKeys } from "../tools/bio/keys.js";
|
|
72
|
+
import type { ProvenanceSeam } from "../provenance/seam.js";
|
|
72
73
|
import type { RunCharge } from "../billing/run-charge.js";
|
|
73
74
|
import { type ResolveWorkspaceRoot } from "../workspace/paths.js";
|
|
74
75
|
import { type PlanStep } from "./execute-analysis-scheduler.js";
|
|
@@ -154,63 +155,6 @@ export interface ExecuteAnalysisResult {
|
|
|
154
155
|
readonly failedSteps: readonly string[];
|
|
155
156
|
readonly canceledSteps: readonly string[];
|
|
156
157
|
}
|
|
157
|
-
/**
|
|
158
|
-
* A run-lifecycle provenance observation handed to an optional host observer.
|
|
159
|
-
* Harness-owned plain union — the harness stays tsprov-free and bus-free; the
|
|
160
|
-
* host maps these execution facts onto its own ledger vocabulary.
|
|
161
|
-
*
|
|
162
|
-
* Every `atMs` is epoch milliseconds read via `await DBOS.now()`, a checkpointed
|
|
163
|
-
* step: a body re-executed by DBOS recovery reads the recorded value, so a
|
|
164
|
-
* re-emitted event carries the identical timestamp and merges on the host's
|
|
165
|
-
* ledger without a value conflict. Never source these from a wall clock
|
|
166
|
-
* (`Date.now()`) — that would diverge across replays and defeat the merge.
|
|
167
|
-
*
|
|
168
|
-
* `run_completed.durationMs` is the terminal `atMs` minus the `run_started`
|
|
169
|
-
* `atMs` (both `DBOS.now()` reads) — the true workflow-observed run span.
|
|
170
|
-
*
|
|
171
|
-
* `step_completed` fires once at EVERY scheduler-loop settlement — the only site
|
|
172
|
-
* that observes every executed step (registration sees only artifact-producing
|
|
173
|
-
* steps, and a child cannot observe its own parent-driven cancel). Steps that
|
|
174
|
-
* were never dispatched (dependents of a failed sibling) emit nothing by design;
|
|
175
|
-
* the run's terminal status carries that outcome. `status` maps the settlement
|
|
176
|
-
* outcome: `complete` → `"completed"`, `canceled` → `"canceled"`,
|
|
177
|
-
* `failed`/`blocked`/child-error → `"failed"`.
|
|
178
|
-
*
|
|
179
|
-
* `run_completed` fires at BOTH terminal boundaries (success and failure); the
|
|
180
|
-
* `status` field distinguishes them.
|
|
181
|
-
*/
|
|
182
|
-
export type RunProvenanceEvent = {
|
|
183
|
-
type: "run_started";
|
|
184
|
-
analysisId: string;
|
|
185
|
-
runId: string;
|
|
186
|
-
planSummary: string;
|
|
187
|
-
stepCount: number;
|
|
188
|
-
atMs: number;
|
|
189
|
-
} | {
|
|
190
|
-
type: "step_completed";
|
|
191
|
-
analysisId: string;
|
|
192
|
-
runId: string;
|
|
193
|
-
stepId: string;
|
|
194
|
-
/** Settlement outcome mapped to a terminal step status. */
|
|
195
|
-
status: "completed" | "failed" | "canceled";
|
|
196
|
-
/** The child's durable execution duration; absent when the child settled by throwing. */
|
|
197
|
-
durationMs?: number;
|
|
198
|
-
atMs: number;
|
|
199
|
-
} | {
|
|
200
|
-
type: "run_completed";
|
|
201
|
-
analysisId: string;
|
|
202
|
-
runId: string;
|
|
203
|
-
/**
|
|
204
|
-
* The body's terminal status. Both boundary sites resolve it through `deriveFinalStatus`,
|
|
205
|
-
* which records a budget pause as `"canceled"` — so `"suspended_insufficient_funds"` (a
|
|
206
|
-
* `RunStatus` member) is never emitted here and is deliberately absent from this narrower
|
|
207
|
-
* `ExecuteAnalysisFinalStatus`-minus-`"running"` set.
|
|
208
|
-
*/
|
|
209
|
-
status: Exclude<ExecuteAnalysisFinalStatus, "running">;
|
|
210
|
-
atMs: number;
|
|
211
|
-
/** `atMs − run_started.atMs`: the workflow-observed run span in ms. */
|
|
212
|
-
durationMs: number;
|
|
213
|
-
};
|
|
214
158
|
/**
|
|
215
159
|
* A step's in-run display state. Wider than the ledger's `StepExecutionStatus`: `queued` is a
|
|
216
160
|
* scheduler-only condition (dependency-satisfied but held for budget capacity) that never reaches a
|
|
@@ -291,9 +235,11 @@ export interface ExecuteAnalysisDeps {
|
|
|
291
235
|
*/
|
|
292
236
|
readonly usageRecorder?: UsageRecorder;
|
|
293
237
|
/**
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
238
|
+
* The provenance seam of the composition. The body reads its run emit member,
|
|
239
|
+
* and it reads no other member.
|
|
240
|
+
*
|
|
241
|
+
* The emit is deliberately invoked directly in the workflow body (NOT wrapped
|
|
242
|
+
* in `DBOS.runStep`) so that body re-execution on DBOS recovery re-fires it —
|
|
297
243
|
* a cached step would suppress the recovery re-emission, defeating the
|
|
298
244
|
* point. Idempotency is the consumer's job, achieved via the deterministic
|
|
299
245
|
* identifiers the events carry, not via step caching. Every call site is
|
|
@@ -303,19 +249,19 @@ export interface ExecuteAnalysisDeps {
|
|
|
303
249
|
* persisting realization needs its credential, scope, and identity.
|
|
304
250
|
* Implementations may ignore the parameter.
|
|
305
251
|
*/
|
|
306
|
-
readonly
|
|
252
|
+
readonly provenance?: ProvenanceSeam;
|
|
307
253
|
/**
|
|
308
254
|
* Optional, fire-and-forget observation of the run's live shape, for a host driving a UI.
|
|
309
255
|
*
|
|
310
|
-
* Independent of {@link
|
|
311
|
-
* the other, they share no payload, and supplying one does not imply the other.
|
|
312
|
-
* separately because provenance closes a signed, hash-chained record under a
|
|
313
|
-
* lock, while this is a lossy-tolerant display channel — coupling them would put
|
|
314
|
-
* behind the chain's write discipline and force provenance to carry step names and
|
|
315
|
-
* has no reason to record.
|
|
256
|
+
* Independent of the run emit of {@link provenance} in every direction: neither is implemented
|
|
257
|
+
* in terms of the other, they share no payload, and supplying one does not imply the other.
|
|
258
|
+
* They exist separately because provenance closes a signed, hash-chained record under a
|
|
259
|
+
* single-writer lock, while this is a lossy-tolerant display channel — coupling them would put
|
|
260
|
+
* a UI repaint behind the chain's write discipline and force provenance to carry step names and
|
|
261
|
+
* agent ids it has no reason to record.
|
|
316
262
|
*
|
|
317
|
-
* Invoked directly in the workflow body (NOT wrapped in `DBOS.runStep`) for the same reason
|
|
318
|
-
*
|
|
263
|
+
* Invoked directly in the workflow body (NOT wrapped in `DBOS.runStep`) for the same reason the
|
|
264
|
+
* run emit is: body re-execution on recovery must re-fire it, and a cached step would
|
|
319
265
|
* suppress that. Idempotency comes free for rendering because the payload is a whole-run
|
|
320
266
|
* snapshot; a host taking a DURABLE side effect on one must key it by run id and status itself.
|
|
321
267
|
*
|
|
@@ -88,14 +88,15 @@ function emitStreamPart(part) {
|
|
|
88
88
|
* host's own ledger, not here.
|
|
89
89
|
*/
|
|
90
90
|
function emitProvenanceGuarded(deps, event, session) {
|
|
91
|
-
const
|
|
92
|
-
if (!
|
|
91
|
+
const emit = deps.provenance?.emitRunEvent;
|
|
92
|
+
if (!emit)
|
|
93
93
|
return;
|
|
94
|
+
const logger = (deps.logger ?? createNoopLogger()).named("executeAnalysis");
|
|
94
95
|
try {
|
|
95
|
-
|
|
96
|
+
emit(event, session);
|
|
96
97
|
}
|
|
97
98
|
catch (err) {
|
|
98
|
-
logger.error("
|
|
99
|
+
logger.error("the run emit of the provenance seam threw", { runId: event.runId, event: event.type, ...logger.errorFields(err) });
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inflexa-ai/harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "Host-agnostic bioinformatics agent harness — hand-rolled agent loop, DBOS-durable workflows, sandboxed compute.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"@dbos-inc/dbos-sdk": "^4.23.6",
|
|
57
57
|
"@fontsource-variable/space-grotesk": "5.1.1",
|
|
58
58
|
"@fontsource/ibm-plex-mono": "5.1.1",
|
|
59
|
+
"@inflexa-ai/tsprov": "0.6.0",
|
|
59
60
|
"@kubernetes/client-node": "^1.4.0",
|
|
60
61
|
"@opentelemetry/api": "^1.9.1",
|
|
61
62
|
"@opentelemetry/core": "^2.9.0",
|