@inflexa-ai/harness 0.14.1 → 0.15.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/README.md +2 -1
- package/dist/agents/conversation-agent.d.ts +7 -0
- package/dist/agents/conversation-agent.js +5 -3
- package/dist/app/synthesize-run.d.ts +3 -0
- package/dist/app/synthesize-run.js +2 -1
- package/dist/billing/noop-usage-recorder.d.ts +7 -0
- package/dist/billing/noop-usage-recorder.js +10 -0
- package/dist/billing/usage-recorder.d.ts +63 -0
- package/dist/billing/usage-recorder.js +15 -0
- package/dist/contracts/chat-events.d.ts +44 -3
- package/dist/contracts/chat-parts.d.ts +38 -2
- package/dist/contracts/chat-parts.js +1 -1
- package/dist/contracts/data-profile.d.ts +26 -0
- package/dist/contracts/data-profile.js +26 -0
- package/dist/contracts/index.d.ts +3 -1
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/message.d.ts +21 -1
- package/dist/contracts/part-registry.js +1 -0
- package/dist/contracts/schemas/chat-events.d.ts +48 -2
- package/dist/contracts/schemas/chat-events.js +8 -1
- package/dist/contracts/schemas/chat-parts.d.ts +46 -3
- package/dist/contracts/schemas/chat-parts.js +13 -0
- package/dist/contracts/schemas/index.d.ts +2 -1
- package/dist/contracts/schemas/index.js +2 -1
- package/dist/contracts/schemas/usage.d.ts +11 -0
- package/dist/contracts/schemas/usage.js +18 -0
- package/dist/contracts/usage.d.ts +51 -0
- package/dist/contracts/usage.js +41 -0
- package/dist/execution/artifact-metadata.d.ts +3 -0
- package/dist/execution/artifact-metadata.js +1 -0
- package/dist/execution/dbos-workflow-purger.d.ts +30 -0
- package/dist/execution/dbos-workflow-purger.js +193 -0
- package/dist/execution/post-step-pipeline.d.ts +3 -0
- package/dist/execution/post-step-pipeline.js +2 -0
- package/dist/execution/reconcile-manifest.js +28 -15
- package/dist/execution/report-runner.d.ts +16 -0
- package/dist/execution/report-runner.js +4 -0
- package/dist/execution/run-synthesis.d.ts +3 -0
- package/dist/execution/run-synthesis.js +2 -0
- package/dist/execution/step-summary.d.ts +3 -0
- package/dist/execution/step-summary.js +1 -0
- package/dist/execution/workflow-purger.d.ts +38 -0
- package/dist/execution/workflow-purger.js +24 -0
- package/dist/index.d.ts +25 -3
- package/dist/index.js +28 -1
- package/dist/lib/metrics.js +2 -1
- package/dist/loop/metrics.d.ts +18 -0
- package/dist/loop/metrics.js +18 -1
- package/dist/loop/run-agent.d.ts +35 -0
- package/dist/loop/run-agent.js +149 -23
- package/dist/loop/run-to-terminal.d.ts +5 -2
- package/dist/loop/run-to-terminal.js +36 -2
- package/dist/loop/tool-detail.d.ts +54 -0
- package/dist/loop/tool-detail.js +105 -0
- package/dist/loop/tool-outcome.d.ts +21 -0
- package/dist/loop/tool-outcome.js +25 -0
- package/dist/loop/types.d.ts +15 -1
- package/dist/memory/content-to-cortex.d.ts +28 -4
- package/dist/memory/content-to-cortex.js +70 -14
- package/dist/memory/thread-history.d.ts +21 -1
- package/dist/memory/thread-history.js +29 -7
- package/dist/memory/thread-store.d.ts +65 -8
- package/dist/memory/thread-store.js +81 -17
- package/dist/provenance/collector.d.ts +5 -3
- package/dist/provenance/collector.js +5 -3
- package/dist/providers/ai-sdk.d.ts +18 -0
- package/dist/providers/ai-sdk.js +129 -16
- package/dist/providers/anthropic.d.ts +2 -0
- package/dist/providers/anthropic.js +1 -0
- package/dist/providers/streaming-chat.js +5 -0
- package/dist/providers/types.d.ts +23 -0
- package/dist/providers/types.js +1 -0
- package/dist/runtime/assemble.d.ts +25 -8
- package/dist/runtime/assemble.js +7 -4
- package/dist/sandbox/sandbox-step-translate.d.ts +22 -1
- package/dist/sandbox/sandbox-step-translate.js +23 -36
- package/dist/schemas/run-synthesis.d.ts +2 -2
- package/dist/state/index.d.ts +2 -0
- package/dist/state/index.js +3 -0
- package/dist/state/init.js +28 -0
- package/dist/state/purge-analysis.d.ts +74 -0
- package/dist/state/purge-analysis.js +205 -0
- package/dist/state/vector-store.d.ts +6 -0
- package/dist/state/vector-store.js +22 -2
- package/dist/tasks/data-profile-activity.d.ts +14 -2
- package/dist/tasks/data-profile-activity.js +1 -1
- package/dist/tasks/data-profile.d.ts +3 -0
- package/dist/tasks/data-profile.js +9 -3
- package/dist/tools/bio/pubmed.js +15 -0
- package/dist/tools/bio/search-gene.js +12 -0
- package/dist/tools/define-tool.d.ts +41 -3
- package/dist/tools/define-tool.js +4 -1
- package/dist/tools/detail-resolver.d.ts +35 -0
- package/dist/tools/detail-resolver.js +39 -0
- package/dist/tools/execute-analysis.js +3 -0
- package/dist/tools/iterate-report.d.ts +3 -0
- package/dist/tools/iterate-report.js +3 -0
- package/dist/tools/memory/update-working-memory.js +8 -0
- package/dist/tools/research/generate-analogy-report.d.ts +3 -0
- package/dist/tools/research/generate-analogy-report.js +6 -0
- package/dist/tools/research/generate-plan.d.ts +3 -0
- package/dist/tools/research/generate-plan.js +6 -0
- package/dist/tools/research/inspect-run.js +4 -0
- package/dist/tools/research/literature-reviewer.d.ts +3 -0
- package/dist/tools/research/literature-reviewer.js +6 -0
- package/dist/tools/workspace/edit-file.js +2 -0
- package/dist/tools/workspace/execute-command.js +25 -0
- package/dist/tools/workspace/grep.js +3 -0
- package/dist/tools/workspace/list-files.js +2 -0
- package/dist/tools/workspace/read-file.js +7 -0
- package/dist/tools/workspace/workspace-search.js +1 -0
- package/dist/tools/workspace/write-file.js +3 -0
- package/dist/workflows/execute-analysis.d.ts +7 -0
- package/dist/workflows/execute-analysis.js +31 -2
- package/dist/workflows/execute-target-assessment.d.ts +3 -0
- package/dist/workflows/sandbox-step.d.ts +23 -0
- package/dist/workflows/sandbox-step.js +33 -2
- package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +3 -0
- package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.js +1 -0
- package/package.json +2 -2
|
@@ -104,8 +104,8 @@ export declare const FileReferencePartSchema: z.ZodObject<{
|
|
|
104
104
|
}, z.core.$strip>>;
|
|
105
105
|
}, z.core.$strip>;
|
|
106
106
|
export declare const AskStatusSchema: z.ZodEnum<{
|
|
107
|
-
pending: "pending";
|
|
108
107
|
aborted: "aborted";
|
|
108
|
+
pending: "pending";
|
|
109
109
|
resolved: "resolved";
|
|
110
110
|
rejected: "rejected";
|
|
111
111
|
expired: "expired";
|
|
@@ -117,8 +117,8 @@ export declare const AskPartSchema: z.ZodObject<{
|
|
|
117
117
|
command: z.ZodString;
|
|
118
118
|
detail: z.ZodOptional<z.ZodString>;
|
|
119
119
|
status: z.ZodEnum<{
|
|
120
|
-
pending: "pending";
|
|
121
120
|
aborted: "aborted";
|
|
121
|
+
pending: "pending";
|
|
122
122
|
resolved: "resolved";
|
|
123
123
|
rejected: "rejected";
|
|
124
124
|
expired: "expired";
|
|
@@ -279,6 +279,21 @@ export declare const StepSummaryPartSchema: z.ZodObject<{
|
|
|
279
279
|
agentId: z.ZodString;
|
|
280
280
|
markdown: z.ZodString;
|
|
281
281
|
}, z.core.$strip>;
|
|
282
|
+
export declare const StepUsagePartSchema: z.ZodObject<{
|
|
283
|
+
type: z.ZodLiteral<"data-step-usage">;
|
|
284
|
+
id: z.ZodString;
|
|
285
|
+
runId: z.ZodString;
|
|
286
|
+
stepId: z.ZodString;
|
|
287
|
+
agentId: z.ZodString;
|
|
288
|
+
modelId: z.ZodString;
|
|
289
|
+
usage: z.ZodObject<{
|
|
290
|
+
inputTokens: z.ZodOptional<z.ZodNumber>;
|
|
291
|
+
outputTokens: z.ZodOptional<z.ZodNumber>;
|
|
292
|
+
cacheCreationInputTokens: z.ZodOptional<z.ZodNumber>;
|
|
293
|
+
cacheReadInputTokens: z.ZodOptional<z.ZodNumber>;
|
|
294
|
+
reasoningTokens: z.ZodOptional<z.ZodNumber>;
|
|
295
|
+
}, z.core.$strip>;
|
|
296
|
+
}, z.core.$strip>;
|
|
282
297
|
export declare const StepBlockedPartSchema: z.ZodObject<{
|
|
283
298
|
type: z.ZodLiteral<"data-step-blocked">;
|
|
284
299
|
id: z.ZodString;
|
|
@@ -314,6 +329,13 @@ export declare const RunCompletedPartSchema: z.ZodObject<{
|
|
|
314
329
|
}>;
|
|
315
330
|
}, z.core.$strip>>;
|
|
316
331
|
note: z.ZodOptional<z.ZodString>;
|
|
332
|
+
usage: z.ZodOptional<z.ZodObject<{
|
|
333
|
+
inputTokens: z.ZodOptional<z.ZodNumber>;
|
|
334
|
+
outputTokens: z.ZodOptional<z.ZodNumber>;
|
|
335
|
+
cacheCreationInputTokens: z.ZodOptional<z.ZodNumber>;
|
|
336
|
+
cacheReadInputTokens: z.ZodOptional<z.ZodNumber>;
|
|
337
|
+
reasoningTokens: z.ZodOptional<z.ZodNumber>;
|
|
338
|
+
}, z.core.$strip>>;
|
|
317
339
|
}, z.core.$strip>;
|
|
318
340
|
export declare const SynthesizedFindingSchema: z.ZodObject<{
|
|
319
341
|
stepId: z.ZodString;
|
|
@@ -512,8 +534,8 @@ export declare const CortexChatPartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
|
|
|
512
534
|
command: z.ZodString;
|
|
513
535
|
detail: z.ZodOptional<z.ZodString>;
|
|
514
536
|
status: z.ZodEnum<{
|
|
515
|
-
pending: "pending";
|
|
516
537
|
aborted: "aborted";
|
|
538
|
+
pending: "pending";
|
|
517
539
|
resolved: "resolved";
|
|
518
540
|
rejected: "rejected";
|
|
519
541
|
expired: "expired";
|
|
@@ -607,6 +629,20 @@ export declare const CortexChatPartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
|
|
|
607
629
|
stepId: z.ZodString;
|
|
608
630
|
agentId: z.ZodString;
|
|
609
631
|
markdown: z.ZodString;
|
|
632
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
633
|
+
type: z.ZodLiteral<"data-step-usage">;
|
|
634
|
+
id: z.ZodString;
|
|
635
|
+
runId: z.ZodString;
|
|
636
|
+
stepId: z.ZodString;
|
|
637
|
+
agentId: z.ZodString;
|
|
638
|
+
modelId: z.ZodString;
|
|
639
|
+
usage: z.ZodObject<{
|
|
640
|
+
inputTokens: z.ZodOptional<z.ZodNumber>;
|
|
641
|
+
outputTokens: z.ZodOptional<z.ZodNumber>;
|
|
642
|
+
cacheCreationInputTokens: z.ZodOptional<z.ZodNumber>;
|
|
643
|
+
cacheReadInputTokens: z.ZodOptional<z.ZodNumber>;
|
|
644
|
+
reasoningTokens: z.ZodOptional<z.ZodNumber>;
|
|
645
|
+
}, z.core.$strip>;
|
|
610
646
|
}, z.core.$strip>, z.ZodObject<{
|
|
611
647
|
type: z.ZodLiteral<"data-step-blocked">;
|
|
612
648
|
id: z.ZodString;
|
|
@@ -691,6 +727,13 @@ export declare const CortexChatPartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
|
|
|
691
727
|
}>;
|
|
692
728
|
}, z.core.$strip>>;
|
|
693
729
|
note: z.ZodOptional<z.ZodString>;
|
|
730
|
+
usage: z.ZodOptional<z.ZodObject<{
|
|
731
|
+
inputTokens: z.ZodOptional<z.ZodNumber>;
|
|
732
|
+
outputTokens: z.ZodOptional<z.ZodNumber>;
|
|
733
|
+
cacheCreationInputTokens: z.ZodOptional<z.ZodNumber>;
|
|
734
|
+
cacheReadInputTokens: z.ZodOptional<z.ZodNumber>;
|
|
735
|
+
reasoningTokens: z.ZodOptional<z.ZodNumber>;
|
|
736
|
+
}, z.core.$strip>>;
|
|
694
737
|
}, z.core.$strip>, z.ZodObject<{
|
|
695
738
|
type: z.ZodLiteral<"data-run-failed">;
|
|
696
739
|
runId: z.ZodString;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Zod schemas for Cortex chat data parts — validation at boundaries.
|
|
3
3
|
*/
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { TokenUsageRollupSchema } from "./usage.js";
|
|
5
6
|
// ── Presentation ────────────────────────────────────────────────────
|
|
6
7
|
export const PlanStepSchema = z.object({
|
|
7
8
|
id: z.string(),
|
|
@@ -181,6 +182,16 @@ export const StepSummaryPartSchema = z.object({
|
|
|
181
182
|
agentId: z.string(),
|
|
182
183
|
markdown: z.string(),
|
|
183
184
|
});
|
|
185
|
+
// ── Step Usage ──────────────────────────────────────────────────────
|
|
186
|
+
export const StepUsagePartSchema = z.object({
|
|
187
|
+
type: z.literal("data-step-usage"),
|
|
188
|
+
id: z.string(),
|
|
189
|
+
runId: z.string(),
|
|
190
|
+
stepId: z.string(),
|
|
191
|
+
agentId: z.string(),
|
|
192
|
+
modelId: z.string(),
|
|
193
|
+
usage: TokenUsageRollupSchema,
|
|
194
|
+
});
|
|
184
195
|
export const StepBlockedPartSchema = z.object({
|
|
185
196
|
type: z.literal("data-step-blocked"),
|
|
186
197
|
id: z.string(),
|
|
@@ -203,6 +214,7 @@ export const RunCompletedPartSchema = z.object({
|
|
|
203
214
|
artifactCount: z.number(),
|
|
204
215
|
findings: z.array(RunCompletedFindingSchema),
|
|
205
216
|
note: z.string().optional(),
|
|
217
|
+
usage: TokenUsageRollupSchema.optional(),
|
|
206
218
|
});
|
|
207
219
|
// ── Run Synthesis ──────────────────────────────────────────────────
|
|
208
220
|
export const SynthesizedFindingSchema = z.object({
|
|
@@ -295,6 +307,7 @@ export const CortexChatPartSchema = z.discriminatedUnion("type", [
|
|
|
295
307
|
StepFileTreePartSchema,
|
|
296
308
|
StepOutputPartSchema,
|
|
297
309
|
StepSummaryPartSchema,
|
|
310
|
+
StepUsagePartSchema,
|
|
298
311
|
StepBlockedPartSchema,
|
|
299
312
|
RunSynthesisPartSchema,
|
|
300
313
|
SynthesisProgressPartSchema,
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export { PlanStepSchema, PresentationContentSchema, PresentationPartSchema, PlanPartSchema, RunCardPartSchema, FileReferenceEntrySchema, FileReferencePartSchema, RunStartedPartSchema, StepStatusSchema, DagStepStateSchema, DagStatePartSchema, StepPhaseSchema, StepActivityPartSchema, FileTreeEntrySchema, StepFileTreePartSchema, StepOutputFileSchema, StepOutputPartSchema, StepSummaryPartSchema, StepBlockedPartSchema, SynthesizedFindingSchema, BiologicalThemeSchema, RunSynthesisPartSchema, SynthesisPhaseSchema, SynthesisProgressPartSchema, RunCompletedFindingSchema, RunCompletedPartSchema, RunFailedPartSchema, PreviewPartSchema, DataPreviewFailedPartSchema, CortexChatPartSchema, } from "./chat-parts.js";
|
|
1
|
+
export { PlanStepSchema, PresentationContentSchema, PresentationPartSchema, PlanPartSchema, RunCardPartSchema, FileReferenceEntrySchema, FileReferencePartSchema, RunStartedPartSchema, StepStatusSchema, DagStepStateSchema, DagStatePartSchema, StepPhaseSchema, StepActivityPartSchema, FileTreeEntrySchema, StepFileTreePartSchema, StepOutputFileSchema, StepOutputPartSchema, StepSummaryPartSchema, StepUsagePartSchema, StepBlockedPartSchema, SynthesizedFindingSchema, BiologicalThemeSchema, RunSynthesisPartSchema, SynthesisPhaseSchema, SynthesisProgressPartSchema, RunCompletedFindingSchema, RunCompletedPartSchema, RunFailedPartSchema, PreviewPartSchema, DataPreviewFailedPartSchema, CortexChatPartSchema, } from "./chat-parts.js";
|
|
2
2
|
export { EventSourceSchema, TextDeltaEventSchema, ToolStartedEventSchema, ToolFinishedEventSchema, FinishEventSchema, ChatErrorEventSchema, CortexChatEventSchema, } from "./chat-events.js";
|
|
3
|
+
export { TokenUsageRollupSchema } from "./usage.js";
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export { PlanStepSchema, PresentationContentSchema, PresentationPartSchema, PlanPartSchema, RunCardPartSchema, FileReferenceEntrySchema, FileReferencePartSchema, RunStartedPartSchema, StepStatusSchema, DagStepStateSchema, DagStatePartSchema, StepPhaseSchema, StepActivityPartSchema, FileTreeEntrySchema, StepFileTreePartSchema, StepOutputFileSchema, StepOutputPartSchema, StepSummaryPartSchema, StepBlockedPartSchema, SynthesizedFindingSchema, BiologicalThemeSchema, RunSynthesisPartSchema, SynthesisPhaseSchema, SynthesisProgressPartSchema, RunCompletedFindingSchema, RunCompletedPartSchema, RunFailedPartSchema, PreviewPartSchema, DataPreviewFailedPartSchema, CortexChatPartSchema, } from "./chat-parts.js";
|
|
1
|
+
export { PlanStepSchema, PresentationContentSchema, PresentationPartSchema, PlanPartSchema, RunCardPartSchema, FileReferenceEntrySchema, FileReferencePartSchema, RunStartedPartSchema, StepStatusSchema, DagStepStateSchema, DagStatePartSchema, StepPhaseSchema, StepActivityPartSchema, FileTreeEntrySchema, StepFileTreePartSchema, StepOutputFileSchema, StepOutputPartSchema, StepSummaryPartSchema, StepUsagePartSchema, StepBlockedPartSchema, SynthesizedFindingSchema, BiologicalThemeSchema, RunSynthesisPartSchema, SynthesisPhaseSchema, SynthesisProgressPartSchema, RunCompletedFindingSchema, RunCompletedPartSchema, RunFailedPartSchema, PreviewPartSchema, DataPreviewFailedPartSchema, CortexChatPartSchema, } from "./chat-parts.js";
|
|
2
2
|
export { EventSourceSchema, TextDeltaEventSchema, ToolStartedEventSchema, ToolFinishedEventSchema, FinishEventSchema, ChatErrorEventSchema, CortexChatEventSchema, } from "./chat-events.js";
|
|
3
|
+
export { TokenUsageRollupSchema } from "./usage.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schema for the token-usage rollup — validation at boundaries.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
export declare const TokenUsageRollupSchema: z.ZodObject<{
|
|
6
|
+
inputTokens: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
outputTokens: z.ZodOptional<z.ZodNumber>;
|
|
8
|
+
cacheCreationInputTokens: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
cacheReadInputTokens: z.ZodOptional<z.ZodNumber>;
|
|
10
|
+
reasoningTokens: z.ZodOptional<z.ZodNumber>;
|
|
11
|
+
}, z.core.$strip>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schema for the token-usage rollup — validation at boundaries.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
/**
|
|
6
|
+
* One reported count. Token counts are whole tokens and cannot run negative, so
|
|
7
|
+
* the schema rejects fractions and negatives rather than passing them through as
|
|
8
|
+
* plain numbers. Optional throughout: absent means "not reported", never zero.
|
|
9
|
+
*/
|
|
10
|
+
const tokenCount = z.number().int().nonnegative().optional();
|
|
11
|
+
export const TokenUsageRollupSchema = z.object({
|
|
12
|
+
inputTokens: tokenCount,
|
|
13
|
+
outputTokens: tokenCount,
|
|
14
|
+
cacheCreationInputTokens: tokenCount,
|
|
15
|
+
cacheReadInputTokens: tokenCount,
|
|
16
|
+
reasoningTokens: tokenCount,
|
|
17
|
+
});
|
|
18
|
+
const _assertSchemaFields = true;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token-usage rollups on the Cortex wire vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* A rollup is the sum of what providers reported across some set of LLM calls
|
|
5
|
+
* — one agent loop, one whole turn, one run step. It is declared here, and not
|
|
6
|
+
* imported from the provider seam, because `contracts/` is the sole
|
|
7
|
+
* Cortex↔consumer wire contract and must stay free of the harness's own
|
|
8
|
+
* dependencies: a frontend importing these types should not inherit a
|
|
9
|
+
* typecheck dependency on the AI SDK.
|
|
10
|
+
*
|
|
11
|
+
* Every field is optional and absent means "not reported" — never zero. A set
|
|
12
|
+
* of calls that reported nothing carries no rollup at all rather than an
|
|
13
|
+
* all-zero one, so a consumer can tell "spent nothing" from "was told nothing".
|
|
14
|
+
*/
|
|
15
|
+
export interface TokenUsageRollup {
|
|
16
|
+
/** Total billed prefix, cache reads included. */
|
|
17
|
+
readonly inputTokens?: number;
|
|
18
|
+
readonly outputTokens?: number;
|
|
19
|
+
/** Prefix tokens written into the provider's prompt cache. */
|
|
20
|
+
readonly cacheCreationInputTokens?: number;
|
|
21
|
+
/** Prefix tokens served from the provider's prompt cache. */
|
|
22
|
+
readonly cacheReadInputTokens?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Exactly what the provider reported — never derived from, nor reconciled
|
|
25
|
+
* against, `outputTokens`. Whether reasoning tokens sit inside the output
|
|
26
|
+
* total varies by provider, so arithmetic between the two is a guess.
|
|
27
|
+
*/
|
|
28
|
+
readonly reasoningTokens?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The rollup's fields as values — the single enumeration a fold can iterate.
|
|
32
|
+
*
|
|
33
|
+
* This shape is declared four times over: here, on the provider seam's
|
|
34
|
+
* `ChatUsage`, on the loop's mutable `AgentRunUsage`, and on the Zod schema
|
|
35
|
+
* beside this file. Nothing but this list joins them, and it is deliberately
|
|
36
|
+
* the *only* thing that does: `contracts/` is the Cortex↔consumer wire
|
|
37
|
+
* vocabulary and must not import the loop, the providers, or the AI SDK, so a
|
|
38
|
+
* shared base type is not available to it. What is available is a
|
|
39
|
+
* dependency-free constant every side may import — the loop folds by iterating
|
|
40
|
+
* it (`loop/metrics.ts:addChatUsage`), and the declarations that cannot be
|
|
41
|
+
* expressed in terms of it are pinned to it by key-set assertions at their own
|
|
42
|
+
* sites, which is why adding a sixth count in one place fails `tsc` instead of
|
|
43
|
+
* silently going unfolded.
|
|
44
|
+
*
|
|
45
|
+
* `satisfies` rejects a name that is not a field; the assertion below rejects a
|
|
46
|
+
* field that is not named. Both directions are needed — either alone leaves one
|
|
47
|
+
* kind of drift compiling.
|
|
48
|
+
*/
|
|
49
|
+
export declare const TOKEN_USAGE_FIELDS: readonly ["inputTokens", "outputTokens", "cacheCreationInputTokens", "cacheReadInputTokens", "reasoningTokens"];
|
|
50
|
+
/** One token-usage field name. */
|
|
51
|
+
export type TokenUsageField = (typeof TOKEN_USAGE_FIELDS)[number];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token-usage rollups on the Cortex wire vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* A rollup is the sum of what providers reported across some set of LLM calls
|
|
5
|
+
* — one agent loop, one whole turn, one run step. It is declared here, and not
|
|
6
|
+
* imported from the provider seam, because `contracts/` is the sole
|
|
7
|
+
* Cortex↔consumer wire contract and must stay free of the harness's own
|
|
8
|
+
* dependencies: a frontend importing these types should not inherit a
|
|
9
|
+
* typecheck dependency on the AI SDK.
|
|
10
|
+
*
|
|
11
|
+
* Every field is optional and absent means "not reported" — never zero. A set
|
|
12
|
+
* of calls that reported nothing carries no rollup at all rather than an
|
|
13
|
+
* all-zero one, so a consumer can tell "spent nothing" from "was told nothing".
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The rollup's fields as values — the single enumeration a fold can iterate.
|
|
17
|
+
*
|
|
18
|
+
* This shape is declared four times over: here, on the provider seam's
|
|
19
|
+
* `ChatUsage`, on the loop's mutable `AgentRunUsage`, and on the Zod schema
|
|
20
|
+
* beside this file. Nothing but this list joins them, and it is deliberately
|
|
21
|
+
* the *only* thing that does: `contracts/` is the Cortex↔consumer wire
|
|
22
|
+
* vocabulary and must not import the loop, the providers, or the AI SDK, so a
|
|
23
|
+
* shared base type is not available to it. What is available is a
|
|
24
|
+
* dependency-free constant every side may import — the loop folds by iterating
|
|
25
|
+
* it (`loop/metrics.ts:addChatUsage`), and the declarations that cannot be
|
|
26
|
+
* expressed in terms of it are pinned to it by key-set assertions at their own
|
|
27
|
+
* sites, which is why adding a sixth count in one place fails `tsc` instead of
|
|
28
|
+
* silently going unfolded.
|
|
29
|
+
*
|
|
30
|
+
* `satisfies` rejects a name that is not a field; the assertion below rejects a
|
|
31
|
+
* field that is not named. Both directions are needed — either alone leaves one
|
|
32
|
+
* kind of drift compiling.
|
|
33
|
+
*/
|
|
34
|
+
export const TOKEN_USAGE_FIELDS = [
|
|
35
|
+
"inputTokens",
|
|
36
|
+
"outputTokens",
|
|
37
|
+
"cacheCreationInputTokens",
|
|
38
|
+
"cacheReadInputTokens",
|
|
39
|
+
"reasoningTokens",
|
|
40
|
+
];
|
|
41
|
+
const _assertFieldsComplete = true;
|
|
@@ -25,6 +25,7 @@ import type { LoopMessage } from "../loop/types.js";
|
|
|
25
25
|
import type { AgentChat } from "../providers/types.js";
|
|
26
26
|
import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
|
|
27
27
|
import type { Logger } from "../lib/logger.js";
|
|
28
|
+
import type { UsageRecorder } from "../billing/usage-recorder.js";
|
|
28
29
|
export interface ArtifactForMetadata {
|
|
29
30
|
/** Path used to update `cortex_artifacts` after metadata generation. */
|
|
30
31
|
readonly dbPath: string;
|
|
@@ -36,6 +37,8 @@ export interface ArtifactForMetadata {
|
|
|
36
37
|
export interface GenerateFileMetadataOptions {
|
|
37
38
|
/** Operational logging seam; omitted falls back to no-op. */
|
|
38
39
|
readonly logger?: Logger;
|
|
40
|
+
/** LLM usage-accounting seam for the describer loop; omitted falls back to the no-op recorder. */
|
|
41
|
+
readonly usageRecorder?: UsageRecorder;
|
|
39
42
|
readonly provider: AgentChat;
|
|
40
43
|
readonly session: AgentSession;
|
|
41
44
|
readonly artifacts: readonly ArtifactForMetadata[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DBOS realization of the `WorkflowPurger` seam — the single production adapter,
|
|
3
|
+
* shared by every embedder. Host-neutral: it references only DBOS, which the
|
|
4
|
+
* harness already depends on.
|
|
5
|
+
*
|
|
6
|
+
* It is built on `DBOSClient`, not the static `DBOS` facade. The facade's
|
|
7
|
+
* workflow-management calls require a launched engine and throw without one,
|
|
8
|
+
* which would make a purge impossible from exactly the process that needs it
|
|
9
|
+
* most: a headless command holding nothing but a pool. `DBOSClient` accepts a
|
|
10
|
+
* pre-built pool and needs no launch, so one realization serves a booted host and
|
|
11
|
+
* a headless one alike and no embedder has to choose between two purgers. The
|
|
12
|
+
* engine's system database is the same database as the application pool, so the
|
|
13
|
+
* injected pool already reaches the ledger.
|
|
14
|
+
*
|
|
15
|
+
* The engine owns the deletion itself, including the breadth-first walk over
|
|
16
|
+
* `parent_workflow_id` that reaches child step workflows. Its single
|
|
17
|
+
* `DELETE FROM dbos.workflow_status WHERE workflow_uuid = ANY(...)` is all the
|
|
18
|
+
* dependent tables need: step outputs, streams, inputs, events, notifications,
|
|
19
|
+
* and queue rows each hold an `ON DELETE CASCADE` foreign key to
|
|
20
|
+
* `workflow_status`, so they go with the status row.
|
|
21
|
+
*/
|
|
22
|
+
import type { Pool } from "pg";
|
|
23
|
+
import type { Logger } from "../lib/logger.js";
|
|
24
|
+
import type { WorkflowPurger } from "./workflow-purger.js";
|
|
25
|
+
export interface DbosWorkflowPurgerDeps {
|
|
26
|
+
/** Pool onto the engine's system database — the same database the application pool holds. */
|
|
27
|
+
readonly pool: Pool;
|
|
28
|
+
readonly logger?: Logger;
|
|
29
|
+
}
|
|
30
|
+
export declare function createDbosWorkflowPurger({ pool, logger: injected }: DbosWorkflowPurgerDeps): WorkflowPurger;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DBOS realization of the `WorkflowPurger` seam — the single production adapter,
|
|
3
|
+
* shared by every embedder. Host-neutral: it references only DBOS, which the
|
|
4
|
+
* harness already depends on.
|
|
5
|
+
*
|
|
6
|
+
* It is built on `DBOSClient`, not the static `DBOS` facade. The facade's
|
|
7
|
+
* workflow-management calls require a launched engine and throw without one,
|
|
8
|
+
* which would make a purge impossible from exactly the process that needs it
|
|
9
|
+
* most: a headless command holding nothing but a pool. `DBOSClient` accepts a
|
|
10
|
+
* pre-built pool and needs no launch, so one realization serves a booted host and
|
|
11
|
+
* a headless one alike and no embedder has to choose between two purgers. The
|
|
12
|
+
* engine's system database is the same database as the application pool, so the
|
|
13
|
+
* injected pool already reaches the ledger.
|
|
14
|
+
*
|
|
15
|
+
* The engine owns the deletion itself, including the breadth-first walk over
|
|
16
|
+
* `parent_workflow_id` that reaches child step workflows. Its single
|
|
17
|
+
* `DELETE FROM dbos.workflow_status WHERE workflow_uuid = ANY(...)` is all the
|
|
18
|
+
* dependent tables need: step outputs, streams, inputs, events, notifications,
|
|
19
|
+
* and queue rows each hold an `ON DELETE CASCADE` foreign key to
|
|
20
|
+
* `workflow_status`, so they go with the status row.
|
|
21
|
+
*/
|
|
22
|
+
import { DBOSClient } from "@dbos-inc/dbos-sdk";
|
|
23
|
+
import { errAsync, okAsync } from "neverthrow";
|
|
24
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
25
|
+
import { tryMutation, tryQuery } from "../lib/db-result.js";
|
|
26
|
+
/**
|
|
27
|
+
* The schema the engine keeps its ledger in. The raw SQL below names it literally,
|
|
28
|
+
* `DBOSClient` defaults to it, and the schema probe asks after this same name — the
|
|
29
|
+
* SDK accepts a `systemDatabaseSchemaName`, so moving a deployment's ledger has to
|
|
30
|
+
* move all three together.
|
|
31
|
+
*/
|
|
32
|
+
const SYSTEM_SCHEMA = "dbos";
|
|
33
|
+
/**
|
|
34
|
+
* SQLSTATE `undefined_table` — the answer both when the engine never created its
|
|
35
|
+
* schema and when `workflow_status` is gone from beneath a schema that exists. Only
|
|
36
|
+
* the first is nothing to purge, so the code on its own settles nothing.
|
|
37
|
+
*/
|
|
38
|
+
const UNDEFINED_TABLE = "42P01";
|
|
39
|
+
function isUndefinedTable(e) {
|
|
40
|
+
const cause = e.cause;
|
|
41
|
+
return cause !== null && typeof cause === "object" && "code" in cause && cause.code === UNDEFINED_TABLE;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Route the client's own diagnostics — pool errors and idle-client errors — onto
|
|
45
|
+
* the injected sink. Left unset, the SDK builds a console logger of its own, and
|
|
46
|
+
* a host whose UI owns stdout discards those records entirely.
|
|
47
|
+
*
|
|
48
|
+
* The SDK renders every entry to a string before delegating, and splits a failure
|
|
49
|
+
* across both parameters: the text arrives as the entry, the stack — with any
|
|
50
|
+
* `cause` chain already folded into it — as `metadata.stack`. So the entry is the
|
|
51
|
+
* record's message at every level, `error` included, and engine wording stays
|
|
52
|
+
* queryable in one place instead of the message at three levels and a field at the
|
|
53
|
+
* fourth. The stack rides beside it under the key `defaultErrorFields` uses.
|
|
54
|
+
* `errorFields` is not the tool here: it normalizes a thrown value, and an entry
|
|
55
|
+
* the SDK has already rendered takes its `String(...)` branch, which emits a bare
|
|
56
|
+
* message and drops the one stack the SDK bothered to hand over.
|
|
57
|
+
*/
|
|
58
|
+
function asEngineLogger(logger) {
|
|
59
|
+
const render = (entry) => (typeof entry === "string" ? entry : String(entry));
|
|
60
|
+
return {
|
|
61
|
+
debug: (entry) => logger.debug(render(entry)),
|
|
62
|
+
info: (entry) => logger.info(render(entry)),
|
|
63
|
+
warn: (entry) => logger.warn(render(entry)),
|
|
64
|
+
error: (entry, metadata) => logger.error(render(entry), metadata?.stack === undefined ? undefined : { stack: metadata.stack }),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export function createDbosWorkflowPurger({ pool, logger: injected }) {
|
|
68
|
+
const logger = (injected ?? createNoopLogger()).named("workflow-purger");
|
|
69
|
+
// One client per factory, not per call. Creating a client does no I/O, but the
|
|
70
|
+
// client registers `error` and `connect` handlers on the pool it is handed, so a
|
|
71
|
+
// client per purge would accumulate handler pairs on a long-lived pool. Its
|
|
72
|
+
// `destroy()` ends that pool, so a client over an injected pool must never be
|
|
73
|
+
// destroyed either — which leaves a per-call client nothing to release. Creation
|
|
74
|
+
// is lazy so building a purger stays free for a host that never purges.
|
|
75
|
+
let pending;
|
|
76
|
+
const engine = () => {
|
|
77
|
+
// `create` never initializes the system database, and initialization is the
|
|
78
|
+
// only path that reads the connection string once a pool is supplied — so the
|
|
79
|
+
// url is unreachable from this client rather than merely untaken, and there is
|
|
80
|
+
// none to thread through the seam.
|
|
81
|
+
pending ??= DBOSClient.create({ systemDatabaseUrl: "", systemDatabasePool: pool, logger: asEngineLogger(logger) });
|
|
82
|
+
return pending;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Whether the engine's schema exists at all — the only thing that separates a
|
|
86
|
+
* ledger the engine never created from one that is broken. It costs a round trip
|
|
87
|
+
* only on the failure path that consults it, and a positive answer is memoized for
|
|
88
|
+
* the purger's lifetime, since nothing drops the schema once the engine has made
|
|
89
|
+
* it. A negative answer is deliberately not memoized: a purger can be built before
|
|
90
|
+
* the launch that creates the schema, and caching absence would go on reading a
|
|
91
|
+
* ledger that has since broken as one that was never there.
|
|
92
|
+
*/
|
|
93
|
+
let schemaConfirmed = false;
|
|
94
|
+
const systemSchemaExists = () => {
|
|
95
|
+
if (schemaConfirmed)
|
|
96
|
+
return okAsync(true);
|
|
97
|
+
return tryQuery("workflowPurger.systemSchemaPresent", async () => {
|
|
98
|
+
const { rows } = await pool.query({
|
|
99
|
+
text: `SELECT EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = $1) AS present`,
|
|
100
|
+
values: [SYSTEM_SCHEMA],
|
|
101
|
+
});
|
|
102
|
+
return rows[0]?.present ?? false;
|
|
103
|
+
}).map((present) => {
|
|
104
|
+
schemaConfirmed = present;
|
|
105
|
+
return present;
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Recover a ledger the engine never created into `fallback`. A missing table
|
|
110
|
+
* beneath a schema that does exist stays on the error channel along with every
|
|
111
|
+
* other failure — an SDK rename, a manual drop, or a half-applied migration
|
|
112
|
+
* reaches the caller as one, rather than as a purge that reclaimed nothing.
|
|
113
|
+
*/
|
|
114
|
+
function whenLedgerAbsent(fallback) {
|
|
115
|
+
return (e) => isUndefinedTable(e) ? systemSchemaExists().andThen((present) => (present ? errAsync(e) : okAsync(fallback))) : errAsync(e);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* How many ledger rows the delete is about to take, read immediately before it,
|
|
119
|
+
* because the engine's delete reports no count of its own. This is a read only:
|
|
120
|
+
* the walk it mirrors selects the same set the delete targets — requested ids that
|
|
121
|
+
* exist, plus their descendants when asked — while the delete itself and its
|
|
122
|
+
* cascades stay engine-owned.
|
|
123
|
+
*/
|
|
124
|
+
const countDoomed = (workflowIds, includeDescendants) => tryQuery("workflowPurger.countDoomed", async () => {
|
|
125
|
+
const { rows } = await pool.query(includeDescendants
|
|
126
|
+
? {
|
|
127
|
+
// The non-recursive term unnests the requested ids rather than
|
|
128
|
+
// reading them out of the ledger, so a descendant is still
|
|
129
|
+
// reached when the id it descends from has already been
|
|
130
|
+
// removed; the outer count then keeps only rows that exist.
|
|
131
|
+
text: `WITH RECURSIVE doomed AS (
|
|
132
|
+
SELECT unnest($1::text[]) AS workflow_uuid
|
|
133
|
+
UNION
|
|
134
|
+
SELECT child.workflow_uuid
|
|
135
|
+
FROM dbos.workflow_status child
|
|
136
|
+
JOIN doomed ON child.parent_workflow_id = doomed.workflow_uuid
|
|
137
|
+
)
|
|
138
|
+
SELECT COUNT(*)::int AS total
|
|
139
|
+
FROM dbos.workflow_status
|
|
140
|
+
WHERE workflow_uuid IN (SELECT workflow_uuid FROM doomed)`,
|
|
141
|
+
values: [workflowIds],
|
|
142
|
+
}
|
|
143
|
+
: {
|
|
144
|
+
text: `SELECT COUNT(*)::int AS total FROM dbos.workflow_status WHERE workflow_uuid = ANY($1)`,
|
|
145
|
+
values: [workflowIds],
|
|
146
|
+
});
|
|
147
|
+
return rows[0]?.total ?? 0;
|
|
148
|
+
});
|
|
149
|
+
return {
|
|
150
|
+
findByIdPrefix(prefix) {
|
|
151
|
+
return tryQuery("workflowPurger.findByIdPrefix", async () => {
|
|
152
|
+
const { rows } = await pool.query({
|
|
153
|
+
// `starts_with` rather than a parameterized LIKE pattern: a namespace
|
|
154
|
+
// carrying `_` or `%` would match as a wildcard there and sweep in
|
|
155
|
+
// workflows belonging to a different analysis.
|
|
156
|
+
text: `SELECT workflow_uuid FROM dbos.workflow_status WHERE starts_with(workflow_uuid, $1)`,
|
|
157
|
+
values: [prefix],
|
|
158
|
+
});
|
|
159
|
+
return rows.map((row) => row.workflow_uuid);
|
|
160
|
+
}).orElse(whenLedgerAbsent([]));
|
|
161
|
+
},
|
|
162
|
+
cancel(workflowIds) {
|
|
163
|
+
if (workflowIds.length === 0)
|
|
164
|
+
return okAsync(undefined);
|
|
165
|
+
return tryMutation("workflowPurger.cancel", async () => {
|
|
166
|
+
const client = await engine();
|
|
167
|
+
// Descendants too: the delete side reaches them, so roots-only
|
|
168
|
+
// cancellation would leave child step executors writing behind it.
|
|
169
|
+
await client.cancelWorkflows([...workflowIds], { cancelChildren: true });
|
|
170
|
+
})
|
|
171
|
+
.map(() => {
|
|
172
|
+
logger.debug("cancelled workflows", { requested: workflowIds.length });
|
|
173
|
+
})
|
|
174
|
+
.orElse(whenLedgerAbsent(undefined));
|
|
175
|
+
},
|
|
176
|
+
deleteWorkflows(workflowIds, includeDescendants = false) {
|
|
177
|
+
if (workflowIds.length === 0)
|
|
178
|
+
return okAsync(0);
|
|
179
|
+
const ids = [...workflowIds];
|
|
180
|
+
return countDoomed(ids, includeDescendants)
|
|
181
|
+
.andThen((doomed) => tryMutation("workflowPurger.deleteWorkflows", async () => {
|
|
182
|
+
const client = await engine();
|
|
183
|
+
await client.deleteWorkflows(ids, includeDescendants);
|
|
184
|
+
return doomed;
|
|
185
|
+
}))
|
|
186
|
+
.map((deleted) => {
|
|
187
|
+
logger.debug("deleted workflows", { deleted, includeDescendants });
|
|
188
|
+
return deleted;
|
|
189
|
+
})
|
|
190
|
+
.orElse(whenLedgerAbsent(0));
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -17,6 +17,7 @@ import type { ResolveWorkspaceRoot } from "../workspace/paths.js";
|
|
|
17
17
|
import type { ArtifactManifestEntry } from "../schemas/artifact-manifest.js";
|
|
18
18
|
import type { StepSummary } from "../schemas/step-summary.js";
|
|
19
19
|
import type { Logger } from "../lib/logger.js";
|
|
20
|
+
import type { UsageRecorder } from "../billing/usage-recorder.js";
|
|
20
21
|
import type { ArtifactRegistry } from "./artifact-registry.js";
|
|
21
22
|
import { type FileMetadataEntry } from "./artifact-metadata.js";
|
|
22
23
|
import type { PostStepArtifacts, PostStepContext, StepOutputs } from "../workflows/sandbox-step.js";
|
|
@@ -28,6 +29,8 @@ export interface PostStepPipelineDeps {
|
|
|
28
29
|
readonly pool: Pool;
|
|
29
30
|
/** Operational logging seam; omitted falls back to no-op. */
|
|
30
31
|
readonly logger?: Logger;
|
|
32
|
+
/** LLM usage-accounting seam for the metadata + summary loops; omitted falls back to the no-op recorder. */
|
|
33
|
+
readonly usageRecorder?: UsageRecorder;
|
|
31
34
|
/** Non-streaming chat — the metadata + summary sub-agent loops. */
|
|
32
35
|
readonly provider: AgentChat;
|
|
33
36
|
/** Write-side embedder for the vector index. */
|
|
@@ -36,6 +36,7 @@ export async function generateStepFileMetadata(deps, postCtx, manifest) {
|
|
|
36
36
|
}));
|
|
37
37
|
const result = await coreGenerateFileMetadata({
|
|
38
38
|
provider: deps.provider,
|
|
39
|
+
usageRecorder: deps.usageRecorder,
|
|
39
40
|
session,
|
|
40
41
|
artifacts: artifactsForMeta,
|
|
41
42
|
resourceId: input.analysisId,
|
|
@@ -61,6 +62,7 @@ export async function generateStepSummaryAndWrite(deps, postCtx, manifest) {
|
|
|
61
62
|
const { input, session, transcript, writePrefix } = postCtx;
|
|
62
63
|
const summary = await coreGenerateStepSummary({
|
|
63
64
|
provider: deps.provider,
|
|
65
|
+
usageRecorder: deps.usageRecorder,
|
|
64
66
|
session,
|
|
65
67
|
modelId: deps.model,
|
|
66
68
|
messages: transcript,
|
|
@@ -87,13 +87,23 @@ export async function reconcileManifestWithDisk(input) {
|
|
|
87
87
|
*
|
|
88
88
|
* `artifacts`-source reads are the step's OWN files (mutating, dropped by the
|
|
89
89
|
* registration translator), so they are not attested and not hashed here.
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
90
|
+
*
|
|
91
|
+
* The invariant is that no hashless edge is ever registered; a ref that cannot
|
|
92
|
+
* be hashed is therefore dropped from lineage, whatever put it out of reach:
|
|
93
|
+
* a read resolving to a directory (an `ls` of a mount), one resolving OUTSIDE
|
|
94
|
+
* the analysis tree (the sandbox can legitimately open paths above its mount —
|
|
95
|
+
* `/{resourceId}/..` is the container root), and one naming a path that is not
|
|
96
|
+
* there at all. None of the three is drift, and failing the step over any of
|
|
97
|
+
* them killed real analyses in the field.
|
|
98
|
+
*
|
|
99
|
+
* A missing path in particular is far likelier to be a read that never happened
|
|
100
|
+
* than an artifact that vanished: the capture layers report *attempted* opens,
|
|
101
|
+
* not completed ones — the Python audit hook fires ahead of the open, R's
|
|
102
|
+
* `trace()` at call entry — so every failed open is reported exactly like a
|
|
103
|
+
* successful one. Nothing was consumed, so there is no edge to attest.
|
|
104
|
+
*
|
|
105
|
+
* Fail-fast is retained for a `stat` that fails any OTHER way: the file is there
|
|
106
|
+
* and cannot be read, which says something is wrong with the tree itself.
|
|
97
107
|
*/
|
|
98
108
|
async function fillInputHashesFromDisk(collector, workspaceRoot, resourceId, runId, stepId, agentId, logger) {
|
|
99
109
|
const resourceRoot = path.resolve(workspaceRoot);
|
|
@@ -102,12 +112,12 @@ async function fillInputHashesFromDisk(collector, workspaceRoot, resourceId, run
|
|
|
102
112
|
continue;
|
|
103
113
|
if (hasValidContentHash(ref.hash))
|
|
104
114
|
continue;
|
|
105
|
-
// Every
|
|
106
|
-
// logged
|
|
115
|
+
// Every drop is logged as it happens, and the one remaining throw is
|
|
116
|
+
// logged before it is raised. The thrown message reaches `failStep` as one
|
|
107
117
|
// opaque string and everything downstream of that is scrubbed, so naming
|
|
108
118
|
// the site and the offending ref HERE is the only account of why a step
|
|
109
|
-
// died —
|
|
110
|
-
// this input at all.
|
|
119
|
+
// died — or of which edge its lineage lost. The read's `source` in
|
|
120
|
+
// particular says whether the step declared this input at all.
|
|
111
121
|
const attestation = { path: ref.path, source: ref.source, refRunId: ref.runId, refStepId: ref.stepId };
|
|
112
122
|
// `ref.path` is the absolute container path `/{resourceId}/...`; strip the
|
|
113
123
|
// mount segment and map the tail onto the host workspace root. `path.join`
|
|
@@ -135,10 +145,13 @@ async function fillInputHashesFromDisk(collector, workspaceRoot, resourceId, run
|
|
|
135
145
|
}
|
|
136
146
|
catch (err) {
|
|
137
147
|
if (err.code === "ENOENT") {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
});
|
|
148
|
+
// Warn, not debug: a capture layer named a file this step never
|
|
149
|
+
// consumed, and the count of those is how a reader tells a noisy
|
|
150
|
+
// hook from an artifact that genuinely vanished under the step.
|
|
151
|
+
logger.warn("dropping input not present at reconcile from lineage", { ...attestation, hostPath, dropSite: "input-enoent" });
|
|
152
|
+
lineageInputDropped.add(1, { agent_id: agentId, step_id: stepId, reason: "missing" });
|
|
153
|
+
collector.dropInput(ref);
|
|
154
|
+
continue;
|
|
142
155
|
}
|
|
143
156
|
logger.error("cannot attest input — stat failed", { ...attestation, hostPath, throwSite: "input-stat", ...logger.errorFields(err) });
|
|
144
157
|
throw err;
|