@inflexa-ai/harness 0.44.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/approve-plan.d.ts +48 -0
- package/dist/app/approve-plan.js +84 -0
- package/dist/app/data-profile-view.d.ts +1 -1
- package/dist/app/run-activity.js +4 -6
- package/dist/contracts/chat-events.d.ts +2 -0
- package/dist/contracts/chat-frame.d.ts +7 -0
- package/dist/contracts/chat-frame.js +18 -4
- package/dist/contracts/chat-parts.d.ts +73 -2
- package/dist/contracts/chat-parts.js +3 -1
- package/dist/contracts/index.d.ts +3 -1
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/part-registry.js +3 -0
- package/dist/contracts/schemas/chat-events.d.ts +13 -0
- package/dist/contracts/schemas/chat-events.js +1 -0
- package/dist/contracts/schemas/chat-parts.d.ts +200 -15
- package/dist/contracts/schemas/chat-parts.js +63 -12
- package/dist/contracts/schemas/index.d.ts +1 -1
- package/dist/contracts/schemas/index.js +1 -1
- package/dist/contracts/subagent-activity.d.ts +12 -0
- package/dist/contracts/subagent-activity.js +76 -0
- package/dist/execution/artifact-metadata.js +1 -3
- package/dist/execution/run-canceler.js +1 -1
- package/dist/execution/run-event-parts.d.ts +6 -5
- package/dist/execution/run-event-parts.js +6 -5
- package/dist/execution/run-event-stream.d.ts +18 -6
- package/dist/execution/run-event-stream.js +67 -17
- package/dist/execution/run-synthesis.d.ts +21 -0
- package/dist/execution/run-synthesis.js +77 -3
- package/dist/index.d.ts +22 -8
- package/dist/index.js +16 -2
- package/dist/lib/hooks.d.ts +1 -1
- package/dist/lib/metrics.d.ts +2 -2
- package/dist/loop/run-agent.js +1 -0
- package/dist/loop/types.d.ts +2 -0
- package/dist/memory/card-builders.d.ts +11 -0
- package/dist/memory/card-builders.js +35 -21
- package/dist/memory/conversation-display-recorder.d.ts +2 -0
- package/dist/memory/conversation-display-recorder.js +10 -1
- package/dist/memory/conversation-display-storage.d.ts +9 -2
- package/dist/memory/conversation-display-storage.js +18 -3
- package/dist/memory/thread-history.d.ts +8 -1
- package/dist/memory/thread-history.js +13 -3
- package/dist/memory/thread-store.d.ts +13 -0
- package/dist/memory/thread-store.js +15 -1
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +3 -1
- package/dist/provenance/seam.d.ts +16 -5
- package/dist/providers/ai-sdk.js +6 -4
- package/dist/report-model/pin-snapshot.d.ts +1 -1
- package/dist/report-model/pin-snapshot.js +2 -50
- package/dist/runtime/assemble.d.ts +8 -1
- package/dist/runtime/assemble.js +11 -0
- package/dist/runtime/boot.d.ts +3 -0
- package/dist/runtime/boot.js +10 -1
- package/dist/sandbox/sandbox-step-translate.d.ts +23 -0
- package/dist/sandbox/sandbox-step-translate.js +24 -0
- package/dist/schemas/artifact-manifest.d.ts +1 -1
- package/dist/schemas/artifact-manifest.js +11 -9
- package/dist/schemas/plan-schemas.d.ts +18 -18
- package/dist/schemas/run-synthesis.d.ts +2 -2
- package/dist/state/analyses-metrics.d.ts +79 -7
- package/dist/state/analyses-metrics.js +141 -6
- package/dist/state/artifacts.d.ts +50 -3
- package/dist/state/artifacts.js +67 -7
- package/dist/state/data-profile.d.ts +4 -2
- package/dist/state/data-profile.js +7 -5
- package/dist/state/index.d.ts +9 -8
- package/dist/state/index.js +5 -5
- package/dist/state/migrations/20261012120000_index_the_runs_by_start.d.ts +8 -0
- package/dist/state/migrations/20261012120000_index_the_runs_by_start.js +12 -0
- package/dist/state/migrations/20261013120000_index_the_ended_runs_and_the_recent_threads.d.ts +8 -0
- package/dist/state/migrations/20261013120000_index_the_ended_runs_and_the_recent_threads.js +13 -0
- package/dist/state/migrations/20261013180000_narrow_the_active_run_index.d.ts +8 -0
- package/dist/state/migrations/20261013180000_narrow_the_active_run_index.js +13 -0
- package/dist/state/migrations/20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.d.ts +8 -0
- package/dist/state/migrations/20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.js +13 -0
- package/dist/state/migrations/index.js +8 -0
- package/dist/state/plans.d.ts +21 -0
- package/dist/state/plans.js +25 -0
- package/dist/state/runs.d.ts +36 -5
- package/dist/state/runs.js +65 -19
- package/dist/state/schema.d.ts +2 -0
- package/dist/tasks/data-profile-activity.d.ts +7 -1
- package/dist/tasks/data-profile-activity.js +18 -9
- package/dist/tasks/data-profile.d.ts +15 -1
- package/dist/tasks/data-profile.js +16 -11
- package/dist/tools/approval/gateway.js +28 -5
- package/dist/tools/display/show-user.d.ts +1 -1
- package/dist/tools/execute-analysis.d.ts +51 -0
- package/dist/tools/execute-analysis.js +241 -109
- package/dist/tools/research/inspect-data-profile.d.ts +1 -1
- package/dist/tools/research/inspect-run.d.ts +1 -1
- package/dist/tools/research/inspect-run.js +7 -7
- package/dist/workflows/execute-analysis.d.ts +4 -3
- package/dist/workflows/execute-analysis.js +35 -22
- package/dist/workflows/plan/graph.d.ts +16 -0
- package/dist/workflows/plan/graph.js +36 -0
- package/dist/workflows/sandbox-step.js +14 -12
- package/package.json +1 -1
|
@@ -144,7 +144,9 @@ export function recordDataProfileWorkflowId(pool, analysisId, workflowId) {
|
|
|
144
144
|
}
|
|
145
145
|
function terminalWriteOf(rows) {
|
|
146
146
|
const row = rows[0];
|
|
147
|
-
return row === undefined
|
|
147
|
+
return row === undefined
|
|
148
|
+
? { stamped: false }
|
|
149
|
+
: { stamped: true, startedAt: row.data_profile_started_at ?? null, completedAt: row.data_profile_completed_at };
|
|
148
150
|
}
|
|
149
151
|
/**
|
|
150
152
|
* Stamp a claimed-`running` row `completed` and record its result — a terminal
|
|
@@ -162,7 +164,7 @@ function terminalWriteOf(rows) {
|
|
|
162
164
|
* guard only ever refuses a row another writer already moved on from.
|
|
163
165
|
*
|
|
164
166
|
* `stamped: true` when this call stamped the row, with the `data_profile_started_at`
|
|
165
|
-
* of the claim it closed; `stamped: false` when the guard refused it (the row was
|
|
167
|
+
* of the claim it closed and the `data_profile_completed_at` it stamped; `stamped: false` when the guard refused it (the row was
|
|
166
168
|
* cleared/expired/replayed away) — a no-op the caller logs, not an error.
|
|
167
169
|
*/
|
|
168
170
|
export function completeDataProfile(pool, analysisId, result) {
|
|
@@ -173,7 +175,7 @@ export function completeDataProfile(pool, analysisId, result) {
|
|
|
173
175
|
SET data_profile_status = 'completed', data_profile_completed_at = $1,
|
|
174
176
|
data_profile_result = $2::jsonb
|
|
175
177
|
WHERE analysis_id = $3 AND data_profile_status = 'running'
|
|
176
|
-
RETURNING data_profile_started_at`,
|
|
178
|
+
RETURNING data_profile_started_at, data_profile_completed_at`,
|
|
177
179
|
values: [now, result ? JSON.stringify(result) : null, analysisId],
|
|
178
180
|
});
|
|
179
181
|
return terminalWriteOf(res.rows);
|
|
@@ -188,7 +190,7 @@ export function completeDataProfile(pool, analysisId, result) {
|
|
|
188
190
|
* prior profile survives the failure (the re-profile/retry route can keep serving it).
|
|
189
191
|
*
|
|
190
192
|
* `stamped: true` when this call stamped the row, with the `data_profile_started_at`
|
|
191
|
-
* of the claim it closed; `stamped: false` when the guard refused it — a logged
|
|
193
|
+
* of the claim it closed and the `data_profile_completed_at` it stamped; `stamped: false` when the guard refused it — a logged
|
|
192
194
|
* no-op, not an error.
|
|
193
195
|
*/
|
|
194
196
|
export function failDataProfile(pool, analysisId, error) {
|
|
@@ -199,7 +201,7 @@ export function failDataProfile(pool, analysisId, error) {
|
|
|
199
201
|
SET data_profile_status = 'failed', data_profile_error = $1,
|
|
200
202
|
data_profile_completed_at = $2
|
|
201
203
|
WHERE analysis_id = $3 AND data_profile_status = 'running'
|
|
202
|
-
RETURNING data_profile_started_at`,
|
|
204
|
+
RETURNING data_profile_started_at, data_profile_completed_at`,
|
|
203
205
|
values: [error, now, analysisId],
|
|
204
206
|
});
|
|
205
207
|
return terminalWriteOf(res.rows);
|
package/dist/state/index.d.ts
CHANGED
|
@@ -9,20 +9,21 @@
|
|
|
9
9
|
export { initCortexState } from "./init.js";
|
|
10
10
|
export type { Querier } from "./db.js";
|
|
11
11
|
export { upsertAnalysis, suspendAnalysis, resumeAnalysis, loadAnalysisStatus } from "./analyses.js";
|
|
12
|
-
export { upsertArtifact, upsertArtifacts, queryInputArtifacts, queryStepArtifactPaths, queryUnsyncedStepArtifacts, updateArtifactId, updateFileIds,
|
|
13
|
-
export type { RegisterArtifactInput, InputArtifactMeta, StepArtifactRef } from "./artifacts.js";
|
|
14
|
-
export { insertRun, reserveRunById, RunDedupCollisionError, RunIdentityCollisionError, updateRunStatus, markRunCanceledIfActive, promoteFailedToPartial, setRunMandate, // oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
15
|
-
setRunSynthesisOutcome, queryRun, queryActiveRun, queryNonTerminalRunsByAnalysis, queryRunsForInspection, queryRunsByAnalysis, queryRunsByThread, } from "./runs.js";
|
|
12
|
+
export { upsertArtifact, upsertArtifacts, queryInputArtifacts, queryStepArtifactPaths, queryUnsyncedStepArtifacts, updateArtifactId, updateFileIds, queryArtifactPage, countArtifactsByRuns, } from "./artifacts.js";
|
|
13
|
+
export type { RegisterArtifactInput, InputArtifactMeta, StepArtifactRef, ArtifactPage, ArtifactPageEntry, ArtifactPageQuery, ArtifactPageSort, RunArtifactCount, } from "./artifacts.js";
|
|
14
|
+
export { insertRun, reserveRunById, RunDedupCollisionError, RunIdentityCollisionError, updateRunStatus, reconcileOrphanedRuns, ORPHANED_RUN_GRACE_MS, markRunCanceledIfActive, promoteFailedToPartial, setRunMandate, // oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
15
|
+
setRunSynthesisOutcome, queryRun, queryActiveRun, queryNonTerminalRunsByAnalysis, queryRunsForInspection, queryRunsByAnalysis, queryRunsByThread, queryRunIdsByPlan, } from "./runs.js";
|
|
16
16
|
export type { InsertRunInput, RunPage, RunReservation } from "./runs.js";
|
|
17
17
|
export { insertStepExecution, seedStepExecutions, sweepPendingStepExecutions, updateStepExecution, queryStepsByRun } from "./step-executions.js";
|
|
18
18
|
export type { InsertStepExecutionInput, SeedStepExecutionRow, UpdateStepExecutionInput } from "./step-executions.js";
|
|
19
19
|
export { setSandboxRef, clearSandboxRef, reconcileReapedSandbox } from "./active-sandboxes.js";
|
|
20
|
-
export { insertPlan, upsertPlan, loadPlan } from "./plans.js";
|
|
21
|
-
export type { InsertPlanInput, UpsertPlanInput } from "./plans.js";
|
|
20
|
+
export { insertPlan, upsertPlan, loadPlan, loadPlanRecord } from "./plans.js";
|
|
21
|
+
export type { InsertPlanInput, UpsertPlanInput, PlanRecord } from "./plans.js";
|
|
22
22
|
export { tryStartDataProfile, tryRetryDataProfile, tryRerunDataProfile, completeDataProfile, failDataProfile, expireStaleDataProfile, reconcileOrphanedDataProfile, clearDataProfile, completeEmptyDataProfile, loadDataProfileStatus, loadSeedInputFileIds, recordDataProfileWorkflowId, } from "./data-profile.js";
|
|
23
23
|
export type { DataProfileStatus, DataProfileTerminalWrite, DataProfileResult, DataProfileChecked, DataProfileCompanionCompleteness, DataProfileDimension, DataProfileGroup, DataProfileGroupSlot, DataProfileFile, DataProfileMemberAnnotation, DataProfileObservation, DataProfilePartition, DataProfileProbeReport, DataProfileQualityAssessment, DataProfileQuarantine, DataProfileRecipeStep, DataProfileKind, DataProfileAxis, DataProfileInputSignature, DataProfileCoverage, } from "./data-profile.js";
|
|
24
|
-
export { queryRunCountsByAnalyses, queryThreadCountsByAnalyses, queryDataProfileStatusByAnalyses } from "./analyses-metrics.js";
|
|
24
|
+
export { queryRunCountsByAnalyses, queryThreadCountsByAnalyses, queryDataProfileStatusByAnalyses, queryLatestRunsByAnalyses, queryRunProgressByRuns, queryRunsEndedSince, } from "./analyses-metrics.js";
|
|
25
|
+
export type { DataProfileSummary, EndedRunSummary, LatestRunSummary, RunProgressSummary } from "./analyses-metrics.js";
|
|
25
26
|
export { createAnalysisPurge } from "./purge-analysis.js";
|
|
26
27
|
export type { AnalysisPurge, AnalysisPurgeDeps, AnalysisPurgeOutcome } from "./purge-analysis.js";
|
|
27
28
|
export { AnalysisStateRowSchema, ArtifactRole, ArtifactRowSchema, RunStatus, StepExecutionStatus, SynthesisStatus, CortexRunRowSchema, StepExecutionRowSchema, CortexPlanRowSchema, } from "./schema.js";
|
|
28
|
-
export type { AnalysisStateRow, ArtifactRow, CortexRunRow, StepExecutionRow, CortexPlanRow } from "./schema.js";
|
|
29
|
+
export type { AnalysisStateRow, ArtifactRow, CortexRunRow, StepExecutionRow, CortexPlanRow, TerminalRunStatus } from "./schema.js";
|
package/dist/state/index.js
CHANGED
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export { initCortexState } from "./init.js";
|
|
10
10
|
export { upsertAnalysis, suspendAnalysis, resumeAnalysis, loadAnalysisStatus } from "./analyses.js";
|
|
11
|
-
export { upsertArtifact, upsertArtifacts, queryInputArtifacts, queryStepArtifactPaths, queryUnsyncedStepArtifacts, updateArtifactId, updateFileIds,
|
|
12
|
-
export { insertRun, reserveRunById, RunDedupCollisionError, RunIdentityCollisionError, updateRunStatus, markRunCanceledIfActive, promoteFailedToPartial, setRunMandate, // oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
13
|
-
setRunSynthesisOutcome, queryRun, queryActiveRun, queryNonTerminalRunsByAnalysis, queryRunsForInspection, queryRunsByAnalysis, queryRunsByThread, } from "./runs.js";
|
|
11
|
+
export { upsertArtifact, upsertArtifacts, queryInputArtifacts, queryStepArtifactPaths, queryUnsyncedStepArtifacts, updateArtifactId, updateFileIds, queryArtifactPage, countArtifactsByRuns, } from "./artifacts.js";
|
|
12
|
+
export { insertRun, reserveRunById, RunDedupCollisionError, RunIdentityCollisionError, updateRunStatus, reconcileOrphanedRuns, ORPHANED_RUN_GRACE_MS, markRunCanceledIfActive, promoteFailedToPartial, setRunMandate, // oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
13
|
+
setRunSynthesisOutcome, queryRun, queryActiveRun, queryNonTerminalRunsByAnalysis, queryRunsForInspection, queryRunsByAnalysis, queryRunsByThread, queryRunIdsByPlan, } from "./runs.js";
|
|
14
14
|
export { insertStepExecution, seedStepExecutions, sweepPendingStepExecutions, updateStepExecution, queryStepsByRun } from "./step-executions.js";
|
|
15
15
|
export { setSandboxRef, clearSandboxRef, reconcileReapedSandbox } from "./active-sandboxes.js";
|
|
16
|
-
export { insertPlan, upsertPlan, loadPlan } from "./plans.js";
|
|
16
|
+
export { insertPlan, upsertPlan, loadPlan, loadPlanRecord } from "./plans.js";
|
|
17
17
|
export { tryStartDataProfile, tryRetryDataProfile, tryRerunDataProfile, completeDataProfile, failDataProfile, expireStaleDataProfile, reconcileOrphanedDataProfile, clearDataProfile, completeEmptyDataProfile, loadDataProfileStatus, loadSeedInputFileIds, recordDataProfileWorkflowId, } from "./data-profile.js";
|
|
18
|
-
export { queryRunCountsByAnalyses, queryThreadCountsByAnalyses, queryDataProfileStatusByAnalyses } from "./analyses-metrics.js";
|
|
18
|
+
export { queryRunCountsByAnalyses, queryThreadCountsByAnalyses, queryDataProfileStatusByAnalyses, queryLatestRunsByAnalyses, queryRunProgressByRuns, queryRunsEndedSince, } from "./analyses-metrics.js";
|
|
19
19
|
// Not per-entity: one analysis's whole Postgres footprint, reached from its id
|
|
20
20
|
// alone, with the workflow ledger behind an injected seam.
|
|
21
21
|
export { createAnalysisPurge } from "./purge-analysis.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Index the runs of an analysis by their start time, thus the read of the
|
|
3
|
+
* newest run of each analysis of a list reads one index entry for each
|
|
4
|
+
* analysis, and its cost does not grow with the run history. The index on
|
|
5
|
+
* `analysis_id` alone stays: a drop is a separate decision.
|
|
6
|
+
*/
|
|
7
|
+
import type { Migration } from "kysely/migration";
|
|
8
|
+
export declare const indexTheRunsByStart: Migration;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Index the runs of an analysis by their start time, thus the read of the
|
|
3
|
+
* newest run of each analysis of a list reads one index entry for each
|
|
4
|
+
* analysis, and its cost does not grow with the run history. The index on
|
|
5
|
+
* `analysis_id` alone stays: a drop is a separate decision.
|
|
6
|
+
*/
|
|
7
|
+
import { sql } from "kysely";
|
|
8
|
+
export const indexTheRunsByStart = {
|
|
9
|
+
async up(db) {
|
|
10
|
+
await sql `CREATE INDEX IF NOT EXISTS idx_cortex_runs_analysis_started ON cortex_runs(analysis_id, started_at DESC, run_id DESC)`.execute(db);
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Index the runs by their completion time and the live threads by their update
|
|
3
|
+
* time, thus the reads over each analysis walk an index and do not sort the
|
|
4
|
+
* whole history. The thread index is partial on the live rows, the predicate of
|
|
5
|
+
* the read.
|
|
6
|
+
*/
|
|
7
|
+
import type { Migration } from "kysely/migration";
|
|
8
|
+
export declare const indexTheEndedRunsAndTheRecentThreads: Migration;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Index the runs by their completion time and the live threads by their update
|
|
3
|
+
* time, thus the reads over each analysis walk an index and do not sort the
|
|
4
|
+
* whole history. The thread index is partial on the live rows, the predicate of
|
|
5
|
+
* the read.
|
|
6
|
+
*/
|
|
7
|
+
import { sql } from "kysely";
|
|
8
|
+
export const indexTheEndedRunsAndTheRecentThreads = {
|
|
9
|
+
async up(db) {
|
|
10
|
+
await sql `CREATE INDEX IF NOT EXISTS idx_cortex_runs_completed ON cortex_runs(completed_at DESC, run_id DESC)`.execute(db);
|
|
11
|
+
await sql `CREATE INDEX IF NOT EXISTS idx_cortex_analysis_threads_recent ON cortex_analysis_threads(updated_at DESC, thread_id) WHERE deleted_at IS NULL`.execute(db);
|
|
12
|
+
},
|
|
13
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrow the one-active-run index to the `running` status. A run that a host
|
|
3
|
+
* suspended is terminal, thus it no longer blocks a new run of its plan. A drop
|
|
4
|
+
* and a new index replace the index, because Postgres cannot change the
|
|
5
|
+
* predicate of an index.
|
|
6
|
+
*/
|
|
7
|
+
import type { Migration } from "kysely/migration";
|
|
8
|
+
export declare const narrowTheActiveRunIndex: Migration;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrow the one-active-run index to the `running` status. A run that a host
|
|
3
|
+
* suspended is terminal, thus it no longer blocks a new run of its plan. A drop
|
|
4
|
+
* and a new index replace the index, because Postgres cannot change the
|
|
5
|
+
* predicate of an index.
|
|
6
|
+
*/
|
|
7
|
+
import { sql } from "kysely";
|
|
8
|
+
export const narrowTheActiveRunIndex = {
|
|
9
|
+
async up(db) {
|
|
10
|
+
await sql `DROP INDEX IF EXISTS idx_cortex_runs_active_plan`.execute(db);
|
|
11
|
+
await sql `CREATE UNIQUE INDEX idx_cortex_runs_active_plan ON cortex_runs(analysis_id, plan_id) WHERE status = 'running'`.execute(db);
|
|
12
|
+
},
|
|
13
|
+
};
|
package/dist/state/migrations/20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Index the plans by their parent, thus the read of the plan that replaced a
|
|
3
|
+
* plan, and the key check when a plan row goes away, read one index entry.
|
|
4
|
+
* Index the runs by their plan, thus the read of the run ids of a plan reads
|
|
5
|
+
* only the runs of that plan.
|
|
6
|
+
*/
|
|
7
|
+
import type { Migration } from "kysely/migration";
|
|
8
|
+
export declare const indexThePlansByParentAndTheRunsByPlan: Migration;
|
package/dist/state/migrations/20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Index the plans by their parent, thus the read of the plan that replaced a
|
|
3
|
+
* plan, and the key check when a plan row goes away, read one index entry.
|
|
4
|
+
* Index the runs by their plan, thus the read of the run ids of a plan reads
|
|
5
|
+
* only the runs of that plan.
|
|
6
|
+
*/
|
|
7
|
+
import { sql } from "kysely";
|
|
8
|
+
export const indexThePlansByParentAndTheRunsByPlan = {
|
|
9
|
+
async up(db) {
|
|
10
|
+
await sql `CREATE INDEX IF NOT EXISTS idx_cortex_plans_parent ON cortex_plans(parent_plan_id)`.execute(db);
|
|
11
|
+
await sql `CREATE INDEX IF NOT EXISTS idx_cortex_runs_plan ON cortex_runs(analysis_id, plan_id, started_at DESC, run_id DESC)`.execute(db);
|
|
12
|
+
},
|
|
13
|
+
};
|
|
@@ -8,10 +8,18 @@
|
|
|
8
8
|
import { baseline } from "./20260927120000_baseline.js";
|
|
9
9
|
import { threadTitleSetByUser } from "./20260927180000_thread_title_set_by_user.js";
|
|
10
10
|
import { dropActiveExecTracking } from "./20261005120000_drop_the_active_exec_tracking.js";
|
|
11
|
+
import { indexTheRunsByStart } from "./20261012120000_index_the_runs_by_start.js";
|
|
12
|
+
import { indexTheEndedRunsAndTheRecentThreads } from "./20261013120000_index_the_ended_runs_and_the_recent_threads.js";
|
|
13
|
+
import { narrowTheActiveRunIndex } from "./20261013180000_narrow_the_active_run_index.js";
|
|
14
|
+
import { indexThePlansByParentAndTheRunsByPlan } from "./20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.js";
|
|
11
15
|
export function cortexMigrations(logger) {
|
|
12
16
|
return {
|
|
13
17
|
"20260927120000_baseline": baseline(logger),
|
|
14
18
|
"20260927180000_thread_title_set_by_user": threadTitleSetByUser,
|
|
15
19
|
"20261005120000_drop_the_active_exec_tracking": dropActiveExecTracking,
|
|
20
|
+
"20261012120000_index_the_runs_by_start": indexTheRunsByStart,
|
|
21
|
+
"20261013120000_index_the_ended_runs_and_the_recent_threads": indexTheEndedRunsAndTheRecentThreads,
|
|
22
|
+
"20261013180000_narrow_the_active_run_index": narrowTheActiveRunIndex,
|
|
23
|
+
"20261014120000_index_the_plans_by_parent_and_the_runs_by_plan": indexThePlansByParentAndTheRunsByPlan,
|
|
16
24
|
};
|
|
17
25
|
}
|
package/dist/state/plans.d.ts
CHANGED
|
@@ -54,3 +54,24 @@ export declare function upsertPlan(pool: Querier, input: UpsertPlanInput): Resul
|
|
|
54
54
|
export declare function loadPlan(pool: Querier, planId: string, opts: {
|
|
55
55
|
analysisId: string;
|
|
56
56
|
}): ResultAsync<unknown | null, DbError>;
|
|
57
|
+
/** A stored plan with its lineage, as {@link loadPlanRecord} reads it. */
|
|
58
|
+
export interface PlanRecord {
|
|
59
|
+
planId: string;
|
|
60
|
+
/** The stored document, as the store holds it. A reader parses it. */
|
|
61
|
+
plan: unknown;
|
|
62
|
+
parentPlanId: string | null;
|
|
63
|
+
/** The newest plan of the same analysis that names this plan as its parent. */
|
|
64
|
+
supersededBy: string | null;
|
|
65
|
+
createdAt: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Analysis-scoped read of one plan with its parent and the newest plan that
|
|
69
|
+
* replaced it, in one statement. Returns `ok(null)` for an unknown planId OR a
|
|
70
|
+
* planId of a different analysis, as {@link loadPlan} does.
|
|
71
|
+
*
|
|
72
|
+
* `created_at` is ISO 8601 text, thus the text order is the time order; the plan
|
|
73
|
+
* id breaks a tie so that the answer is stable.
|
|
74
|
+
*/
|
|
75
|
+
export declare function loadPlanRecord(pool: Querier, planId: string, opts: {
|
|
76
|
+
analysisId: string;
|
|
77
|
+
}): ResultAsync<PlanRecord | null, DbError>;
|
package/dist/state/plans.js
CHANGED
|
@@ -115,3 +115,28 @@ export function loadPlan(pool, planId, opts) {
|
|
|
115
115
|
return result.rows[0]?.plan ?? null;
|
|
116
116
|
});
|
|
117
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Analysis-scoped read of one plan with its parent and the newest plan that
|
|
120
|
+
* replaced it, in one statement. Returns `ok(null)` for an unknown planId OR a
|
|
121
|
+
* planId of a different analysis, as {@link loadPlan} does.
|
|
122
|
+
*
|
|
123
|
+
* `created_at` is ISO 8601 text, thus the text order is the time order; the plan
|
|
124
|
+
* id breaks a tie so that the answer is stable.
|
|
125
|
+
*/
|
|
126
|
+
export function loadPlanRecord(pool, planId, opts) {
|
|
127
|
+
return tryQuery("plans.loadPlanRecord", async () => {
|
|
128
|
+
const result = await pool.query({
|
|
129
|
+
text: `SELECT p.plan, p.parent_plan_id, p.created_at,
|
|
130
|
+
(SELECT c.plan_id FROM cortex_plans c
|
|
131
|
+
WHERE c.parent_plan_id = p.plan_id AND c.analysis_id = p.analysis_id
|
|
132
|
+
ORDER BY c.created_at DESC, c.plan_id DESC LIMIT 1) AS superseded_by
|
|
133
|
+
FROM cortex_plans p
|
|
134
|
+
WHERE p.plan_id = $1 AND p.analysis_id = $2`,
|
|
135
|
+
values: [planId, opts.analysisId],
|
|
136
|
+
});
|
|
137
|
+
const row = result.rows[0];
|
|
138
|
+
if (!row)
|
|
139
|
+
return null;
|
|
140
|
+
return { planId, plan: row.plan, parentPlanId: row.parent_plan_id, supersededBy: row.superseded_by, createdAt: row.created_at };
|
|
141
|
+
});
|
|
142
|
+
}
|
package/dist/state/runs.d.ts
CHANGED
|
@@ -77,11 +77,33 @@ export declare function queryActiveRun(pool: Querier, analysisId: string, planId
|
|
|
77
77
|
* analysis's live concurrency, not by its history, and a run leaves the set permanently when it
|
|
78
78
|
* finishes.
|
|
79
79
|
*
|
|
80
|
-
* The status set is the one {@link queryActiveRun} uses — non-terminal means
|
|
81
|
-
*
|
|
82
|
-
*
|
|
80
|
+
* The status set is the one {@link queryActiveRun} uses — non-terminal means `running`. Keep the two
|
|
81
|
+
* in step: a new non-terminal status added to one and not the other makes a live run invisible to
|
|
82
|
+
* exactly one of its readers.
|
|
83
83
|
*/
|
|
84
84
|
export declare function queryActiveRunsByAnalysis(pool: Querier, analysisId: string): ResultAsync<CortexRunRow[], DbError>;
|
|
85
|
+
/**
|
|
86
|
+
* How long a reserved run may wait for its workflow before {@link reconcileOrphanedRuns} fails it. A launch writes
|
|
87
|
+
* the workflow row seconds after the reservation, and the margin covers a slow authorization gate of a managed host.
|
|
88
|
+
*/
|
|
89
|
+
export declare const ORPHANED_RUN_GRACE_MS: number;
|
|
90
|
+
/**
|
|
91
|
+
* Fail each `running` run that started before the grace and that has no DBOS workflow row, and give the ids of
|
|
92
|
+
* the runs that it failed.
|
|
93
|
+
*
|
|
94
|
+
* A launch reserves the run row, then starts the workflow. A host that dies between the two, or a start that throws
|
|
95
|
+
* while the following status write also fails, leaves a `running` row that no workflow will ever settle. That row
|
|
96
|
+
* holds the active-plan slot, thus each later launch of the plan answers `started: false` for a run that never
|
|
97
|
+
* started. Recovery cannot help, because no workflow row exists to recover.
|
|
98
|
+
*
|
|
99
|
+
* Only a run with no workflow row at all is failed. A run whose workflow exists, also a terminal one, is the
|
|
100
|
+
* workflow's to settle: a failed mark could misname a run that completed. Call it after `DBOS.launch()`, thus a
|
|
101
|
+
* recovered workflow is visible to the predicate.
|
|
102
|
+
*/
|
|
103
|
+
export declare function reconcileOrphanedRuns(pool: Querier, opts?: {
|
|
104
|
+
now?: number;
|
|
105
|
+
graceMs?: number;
|
|
106
|
+
}): ResultAsync<string[], DbError>;
|
|
85
107
|
export declare function updateRunStatus(pool: Querier, runId: string, status: "running" | "completed" | "partial" | "failed" | "canceled" | "suspended_insufficient_funds", error?: string | null): ResultAsync<void, DbError>;
|
|
86
108
|
/**
|
|
87
109
|
* Conditionally transition an ACTIVE run to `canceled`, stamping `completed_at`
|
|
@@ -99,6 +121,16 @@ export declare function queryRunsByAnalysis(pool: Querier, analysisId: string, {
|
|
|
99
121
|
limit?: number;
|
|
100
122
|
offset?: number;
|
|
101
123
|
}): ResultAsync<CortexRunRow[], DbError>;
|
|
124
|
+
/**
|
|
125
|
+
* The run ids of one plan of one analysis, newest start first, at most `limit`.
|
|
126
|
+
* The run id breaks a tie of start times, highest first. A plan with no run gives
|
|
127
|
+
* an empty list.
|
|
128
|
+
*/
|
|
129
|
+
export declare function queryRunIdsByPlan(pool: Querier, opts: {
|
|
130
|
+
analysisId: string;
|
|
131
|
+
planId: string;
|
|
132
|
+
limit: number;
|
|
133
|
+
}): ResultAsync<string[], DbError>;
|
|
102
134
|
/**
|
|
103
135
|
* Read the analysis's non-terminal run activity without coupling it to a
|
|
104
136
|
* conversation thread. The total is counted before the detail limit so a
|
|
@@ -106,8 +138,7 @@ export declare function queryRunsByAnalysis(pool: Querier, analysisId: string, {
|
|
|
106
138
|
*/
|
|
107
139
|
export declare function queryNonTerminalRunsByAnalysis(pool: Querier, analysisId: string, limit?: number): ResultAsync<RunPage, DbError>;
|
|
108
140
|
/**
|
|
109
|
-
* Read one stable inspection page: running first, then
|
|
110
|
-
* terminal history. This is deliberately separate from
|
|
141
|
+
* Read one stable inspection page: running first, then each other run. This is deliberately separate from
|
|
111
142
|
* {@link queryRunsByAnalysis}, whose chronological order is an embedder/UI
|
|
112
143
|
* contract.
|
|
113
144
|
*/
|
package/dist/state/runs.js
CHANGED
|
@@ -132,7 +132,7 @@ export function queryActiveRun(pool, analysisId, planId) {
|
|
|
132
132
|
FROM cortex_runs
|
|
133
133
|
WHERE analysis_id = $1
|
|
134
134
|
AND plan_id IS NOT DISTINCT FROM $2
|
|
135
|
-
AND status
|
|
135
|
+
AND status = 'running'`,
|
|
136
136
|
values: [analysisId, planId],
|
|
137
137
|
});
|
|
138
138
|
const row = result.rows[0];
|
|
@@ -152,9 +152,9 @@ export function queryActiveRun(pool, analysisId, planId) {
|
|
|
152
152
|
* analysis's live concurrency, not by its history, and a run leaves the set permanently when it
|
|
153
153
|
* finishes.
|
|
154
154
|
*
|
|
155
|
-
* The status set is the one {@link queryActiveRun} uses — non-terminal means
|
|
156
|
-
*
|
|
157
|
-
*
|
|
155
|
+
* The status set is the one {@link queryActiveRun} uses — non-terminal means `running`. Keep the two
|
|
156
|
+
* in step: a new non-terminal status added to one and not the other makes a live run invisible to
|
|
157
|
+
* exactly one of its readers.
|
|
158
158
|
*/
|
|
159
159
|
export function queryActiveRunsByAnalysis(pool, analysisId) {
|
|
160
160
|
return tryQuery("runs.queryActiveRunsByAnalysis", async () => {
|
|
@@ -164,16 +164,52 @@ export function queryActiveRunsByAnalysis(pool, analysisId) {
|
|
|
164
164
|
mandate_jti, mandate_expires_at, plan_id -- oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
165
165
|
FROM cortex_runs
|
|
166
166
|
WHERE analysis_id = $1
|
|
167
|
-
AND status
|
|
167
|
+
AND status = 'running'
|
|
168
168
|
ORDER BY started_at DESC`,
|
|
169
169
|
values: [analysisId],
|
|
170
170
|
});
|
|
171
171
|
return result.rows.map(mapRunRow);
|
|
172
172
|
});
|
|
173
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* How long a reserved run may wait for its workflow before {@link reconcileOrphanedRuns} fails it. A launch writes
|
|
176
|
+
* the workflow row seconds after the reservation, and the margin covers a slow authorization gate of a managed host.
|
|
177
|
+
*/
|
|
178
|
+
export const ORPHANED_RUN_GRACE_MS = 5 * 60 * 1000;
|
|
179
|
+
/**
|
|
180
|
+
* Fail each `running` run that started before the grace and that has no DBOS workflow row, and give the ids of
|
|
181
|
+
* the runs that it failed.
|
|
182
|
+
*
|
|
183
|
+
* A launch reserves the run row, then starts the workflow. A host that dies between the two, or a start that throws
|
|
184
|
+
* while the following status write also fails, leaves a `running` row that no workflow will ever settle. That row
|
|
185
|
+
* holds the active-plan slot, thus each later launch of the plan answers `started: false` for a run that never
|
|
186
|
+
* started. Recovery cannot help, because no workflow row exists to recover.
|
|
187
|
+
*
|
|
188
|
+
* Only a run with no workflow row at all is failed. A run whose workflow exists, also a terminal one, is the
|
|
189
|
+
* workflow's to settle: a failed mark could misname a run that completed. Call it after `DBOS.launch()`, thus a
|
|
190
|
+
* recovered workflow is visible to the predicate.
|
|
191
|
+
*/
|
|
192
|
+
export function reconcileOrphanedRuns(pool, opts = {}) {
|
|
193
|
+
const now = opts.now ?? Date.now();
|
|
194
|
+
const before = new Date(now - (opts.graceMs ?? ORPHANED_RUN_GRACE_MS)).toISOString();
|
|
195
|
+
return tryMutation("runs.reconcileOrphanedRuns", async () => {
|
|
196
|
+
const result = await pool.query({
|
|
197
|
+
text: `UPDATE cortex_runs r
|
|
198
|
+
SET status = 'failed',
|
|
199
|
+
error = 'The workflow of the run never started.',
|
|
200
|
+
completed_at = $1
|
|
201
|
+
WHERE r.status = 'running'
|
|
202
|
+
AND r.started_at < $2
|
|
203
|
+
AND NOT EXISTS (SELECT 1 FROM dbos.workflow_status ws WHERE ws.workflow_uuid = r.run_id)
|
|
204
|
+
RETURNING r.run_id`,
|
|
205
|
+
values: [new Date(now).toISOString(), before],
|
|
206
|
+
});
|
|
207
|
+
return result.rows.map((row) => row.run_id);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
174
210
|
export function updateRunStatus(pool, runId, status, error) {
|
|
175
211
|
const now = new Date().toISOString();
|
|
176
|
-
const isTerminal = status === "completed" || status === "partial" || status === "failed" || status === "canceled";
|
|
212
|
+
const isTerminal = status === "completed" || status === "partial" || status === "failed" || status === "canceled" || status === "suspended_insufficient_funds";
|
|
177
213
|
return tryMutation("runs.updateRunStatus", async () => {
|
|
178
214
|
await pool.query({
|
|
179
215
|
text: `UPDATE cortex_runs
|
|
@@ -198,7 +234,7 @@ export function markRunCanceledIfActive(pool, runId, reason) {
|
|
|
198
234
|
const result = await pool.query({
|
|
199
235
|
text: `UPDATE cortex_runs
|
|
200
236
|
SET status = 'canceled', completed_at = $2, error = $3
|
|
201
|
-
WHERE run_id = $1 AND status
|
|
237
|
+
WHERE run_id = $1 AND status = 'running'`,
|
|
202
238
|
values: [runId, now, reason],
|
|
203
239
|
});
|
|
204
240
|
return (result.rowCount ?? 0) > 0;
|
|
@@ -242,6 +278,23 @@ export function queryRunsByAnalysis(pool, analysisId, { limit = 50, offset = 0 }
|
|
|
242
278
|
return result.rows.map(mapRunRow);
|
|
243
279
|
});
|
|
244
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* The run ids of one plan of one analysis, newest start first, at most `limit`.
|
|
283
|
+
* The run id breaks a tie of start times, highest first. A plan with no run gives
|
|
284
|
+
* an empty list.
|
|
285
|
+
*/
|
|
286
|
+
export function queryRunIdsByPlan(pool, opts) {
|
|
287
|
+
return tryQuery("runs.queryRunIdsByPlan", async () => {
|
|
288
|
+
const result = await pool.query({
|
|
289
|
+
text: `SELECT run_id FROM cortex_runs
|
|
290
|
+
WHERE analysis_id = $1 AND plan_id = $2
|
|
291
|
+
ORDER BY started_at DESC, run_id DESC
|
|
292
|
+
LIMIT $3`,
|
|
293
|
+
values: [opts.analysisId, opts.planId, opts.limit],
|
|
294
|
+
});
|
|
295
|
+
return result.rows.map((row) => row.run_id);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
245
298
|
/**
|
|
246
299
|
* Read the analysis's non-terminal run activity without coupling it to a
|
|
247
300
|
* conversation thread. The total is counted before the detail limit so a
|
|
@@ -253,7 +306,7 @@ export function queryNonTerminalRunsByAnalysis(pool, analysisId, limit = 20) {
|
|
|
253
306
|
text: `SELECT COUNT(*)::int AS total
|
|
254
307
|
FROM cortex_runs
|
|
255
308
|
WHERE analysis_id = $1
|
|
256
|
-
AND status
|
|
309
|
+
AND status = 'running'`,
|
|
257
310
|
values: [analysisId],
|
|
258
311
|
});
|
|
259
312
|
const rowsResult = await pool.query({
|
|
@@ -262,13 +315,8 @@ export function queryNonTerminalRunsByAnalysis(pool, analysisId, limit = 20) {
|
|
|
262
315
|
mandate_jti, mandate_expires_at, plan_id
|
|
263
316
|
FROM cortex_runs
|
|
264
317
|
WHERE analysis_id = $1
|
|
265
|
-
AND status
|
|
266
|
-
ORDER BY
|
|
267
|
-
WHEN 'running' THEN 0
|
|
268
|
-
WHEN 'suspended_insufficient_funds' THEN 1
|
|
269
|
-
ELSE 2
|
|
270
|
-
END,
|
|
271
|
-
started_at DESC,
|
|
318
|
+
AND status = 'running'
|
|
319
|
+
ORDER BY started_at DESC,
|
|
272
320
|
run_id DESC
|
|
273
321
|
LIMIT $2`,
|
|
274
322
|
values: [analysisId, limit],
|
|
@@ -280,8 +328,7 @@ export function queryNonTerminalRunsByAnalysis(pool, analysisId, limit = 20) {
|
|
|
280
328
|
});
|
|
281
329
|
}
|
|
282
330
|
/**
|
|
283
|
-
* Read one stable inspection page: running first, then
|
|
284
|
-
* terminal history. This is deliberately separate from
|
|
331
|
+
* Read one stable inspection page: running first, then each other run. This is deliberately separate from
|
|
285
332
|
* {@link queryRunsByAnalysis}, whose chronological order is an embedder/UI
|
|
286
333
|
* contract.
|
|
287
334
|
*/
|
|
@@ -299,8 +346,7 @@ export function queryRunsForInspection(pool, analysisId, { limit = 50, offset =
|
|
|
299
346
|
WHERE analysis_id = $1
|
|
300
347
|
ORDER BY CASE status
|
|
301
348
|
WHEN 'running' THEN 0
|
|
302
|
-
|
|
303
|
-
ELSE 2
|
|
349
|
+
ELSE 1
|
|
304
350
|
END,
|
|
305
351
|
started_at DESC,
|
|
306
352
|
run_id DESC
|
package/dist/state/schema.d.ts
CHANGED
|
@@ -54,6 +54,8 @@ export declare const RunStatus: z.ZodEnum<{
|
|
|
54
54
|
suspended_insufficient_funds: "suspended_insufficient_funds";
|
|
55
55
|
}>;
|
|
56
56
|
export type RunStatus = z.infer<typeof RunStatus>;
|
|
57
|
+
/** A status that a run keeps once it ended: every status but `running`. */
|
|
58
|
+
export type TerminalRunStatus = Exclude<RunStatus, "running">;
|
|
57
59
|
export declare const StepExecutionStatus: z.ZodEnum<{
|
|
58
60
|
failed: "failed";
|
|
59
61
|
pending: "pending";
|
|
@@ -103,5 +103,11 @@ export interface ProfileActivityFrame {
|
|
|
103
103
|
* A failing write is swallowed and logged, never propagated: observation is a
|
|
104
104
|
* diagnostic channel, and a dropped frame must not fail a profile that is otherwise
|
|
105
105
|
* succeeding. This mirrors the sandbox-step producer's stance on the same write.
|
|
106
|
+
*
|
|
107
|
+
* Each emission writes the reconciling activity part, so a late subscriber folds to the
|
|
108
|
+
* current activity, and then one `data-activity-entry`, so it also gets each earlier line.
|
|
109
|
+
* The two parts carry one time from `now`, which the body binds to the checkpointed clock of
|
|
110
|
+
* its workflow. The two writes fail alone. The sequence number of the entry advances before
|
|
111
|
+
* the clock read and the writes, thus a failure cannot shift a later id.
|
|
106
112
|
*/
|
|
107
|
-
export declare function createProfileActivityEmitter(write: ActivityWrite, frame: ProfileActivityFrame, logger: Logger): ProfileActivityEmitter;
|
|
113
|
+
export declare function createProfileActivityEmitter(write: ActivityWrite, frame: ProfileActivityFrame, logger: Logger, now: () => Promise<number>): ProfileActivityEmitter;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* anything that throws on the way there reaches the failure path having emitted
|
|
20
20
|
* no terminal at all.
|
|
21
21
|
*/
|
|
22
|
-
import {
|
|
22
|
+
import { activityEmission, activityForTool } from "../sandbox/sandbox-step-translate.js";
|
|
23
23
|
/**
|
|
24
24
|
* Build the profile's activity channel over `write`.
|
|
25
25
|
*
|
|
@@ -30,21 +30,30 @@ import { activityForTool, stepPartId } from "../sandbox/sandbox-step-translate.j
|
|
|
30
30
|
* A failing write is swallowed and logged, never propagated: observation is a
|
|
31
31
|
* diagnostic channel, and a dropped frame must not fail a profile that is otherwise
|
|
32
32
|
* succeeding. This mirrors the sandbox-step producer's stance on the same write.
|
|
33
|
+
*
|
|
34
|
+
* Each emission writes the reconciling activity part, so a late subscriber folds to the
|
|
35
|
+
* current activity, and then one `data-activity-entry`, so it also gets each earlier line.
|
|
36
|
+
* The two parts carry one time from `now`, which the body binds to the checkpointed clock of
|
|
37
|
+
* its workflow. The two writes fail alone. The sequence number of the entry advances before
|
|
38
|
+
* the clock read and the writes, thus a failure cannot shift a later id.
|
|
33
39
|
*/
|
|
34
|
-
export function createProfileActivityEmitter(write, frame, logger) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// accumulating them. Minted through the shared construction because that
|
|
38
|
-
// sameness across producers is what makes the fold correct.
|
|
39
|
-
const id = stepPartId("step-activity", frame.runId, frame.stepId);
|
|
40
|
-
const emit = async (phase, activity) => {
|
|
40
|
+
export function createProfileActivityEmitter(write, frame, logger, now) {
|
|
41
|
+
let seq = 0;
|
|
42
|
+
const writeAlone = async (part, phase) => {
|
|
41
43
|
try {
|
|
42
|
-
await write(
|
|
44
|
+
await write(part);
|
|
43
45
|
}
|
|
44
46
|
catch (err) {
|
|
45
47
|
logger.warn("run-event write failed (non-fatal)", { phase, ...logger.errorFields(err) });
|
|
46
48
|
}
|
|
47
49
|
};
|
|
50
|
+
const emit = async (phase, activity) => {
|
|
51
|
+
const position = seq++;
|
|
52
|
+
const at = new Date(await now()).toISOString();
|
|
53
|
+
const parts = activityEmission(frame, position, phase, activity, at);
|
|
54
|
+
await writeAlone(parts.activity, phase);
|
|
55
|
+
await writeAlone(parts.entry, phase);
|
|
56
|
+
};
|
|
48
57
|
return {
|
|
49
58
|
sandboxInit: () => emit("sandbox-init", "Starting sandbox"),
|
|
50
59
|
scanning: () => emit("executing", "Scanning input files"),
|
|
@@ -28,7 +28,7 @@ import type { SandboxAgentDeps } from "../agents/sandbox/shared.js";
|
|
|
28
28
|
import type { BioToolKeys } from "../tools/bio/keys.js";
|
|
29
29
|
import type { ToolOutputStore } from "../loop/tool-output.js";
|
|
30
30
|
import type { DbError } from "../lib/db-result.js";
|
|
31
|
-
import { type GateRefusal } from "../lib/hooks.js";
|
|
31
|
+
import { type GateRefusal, type NoticeFailure } from "../lib/hooks.js";
|
|
32
32
|
import type { Logger } from "../lib/logger.js";
|
|
33
33
|
import type { UsageRecorder } from "../billing/usage-recorder.js";
|
|
34
34
|
import type { ChatProvider, EmbeddingProvider } from "../providers/types.js";
|
|
@@ -38,6 +38,15 @@ import { type ResolveWorkspaceRoot } from "../workspace/paths.js";
|
|
|
38
38
|
import { type ProfileSubmission } from "../schemas/data-profile-schemas.js";
|
|
39
39
|
import { type ProfileResolution } from "./data-profile-resolve.js";
|
|
40
40
|
import { type DataProfileResult } from "../state/index.js";
|
|
41
|
+
/** The settle of one profile claim: the ledger row left `running`, and the terminal write stamped it. */
|
|
42
|
+
export interface DataProfileSettlement {
|
|
43
|
+
readonly analysisId: string;
|
|
44
|
+
readonly status: "completed" | "failed";
|
|
45
|
+
/** The `data_profile_completed_at` that the terminal write stamped, as an ISO 8601 string. */
|
|
46
|
+
readonly completedAt: string;
|
|
47
|
+
}
|
|
48
|
+
/** The notice of a profile settle. The workflow body calls it one time for each settled claim. */
|
|
49
|
+
export type NotifyDataProfileSettled = (settlement: DataProfileSettlement) => ResultAsync<void, NoticeFailure>;
|
|
41
50
|
/** The body's construction-time deps — closed over at registration. */
|
|
42
51
|
export interface DataProfileDeps extends EnvironmentStorePaths {
|
|
43
52
|
/** Operational logging seam; omitted falls back to no-op. */
|
|
@@ -73,6 +82,11 @@ export interface DataProfileDeps extends EnvironmentStorePaths {
|
|
|
73
82
|
* Unbound keeps the current shape: no link tool, and no error.
|
|
74
83
|
*/
|
|
75
84
|
readonly extendAnalysisFarm?: ExtendAnalysisFarm;
|
|
85
|
+
/**
|
|
86
|
+
* Notice of each profile settle, for the records of the embedder. Unbound, the
|
|
87
|
+
* body calls nothing. It is not a provenance hook.
|
|
88
|
+
*/
|
|
89
|
+
readonly notifyDataProfileSettled?: NotifyDataProfileSettled;
|
|
76
90
|
}
|
|
77
91
|
/**
|
|
78
92
|
* Workflow input. JSON-serialisable — DBOS persists it as the workflow's
|