@inflexa-ai/harness 0.44.0 → 0.45.1
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 +10 -0
- package/dist/agents/conversation-agent.js +19 -1
- package/dist/agents/sandbox/shared.d.ts +23 -2
- package/dist/agents/sandbox/shared.js +51 -18
- package/dist/agents/sandbox/translational-safety-agent.js +26 -0
- package/dist/agents/sandbox/types.d.ts +24 -1
- package/dist/agents/sandbox-catalog.d.ts +14 -0
- package/dist/agents/sandbox-catalog.js +19 -0
- package/dist/app/approve-plan.d.ts +51 -0
- package/dist/app/approve-plan.js +84 -0
- package/dist/app/chat-turn.js +6 -3
- package/dist/app/data-profile-view.d.ts +1 -1
- package/dist/app/run-activity.js +4 -6
- package/dist/config/environment-stores.d.ts +4 -2
- package/dist/contracts/chat-events.d.ts +14 -2
- package/dist/contracts/chat-events.js +1 -1
- package/dist/contracts/chat-frame.d.ts +7 -0
- package/dist/contracts/chat-frame.js +24 -6
- package/dist/contracts/chat-parts.d.ts +73 -2
- package/dist/contracts/chat-parts.js +3 -1
- package/dist/contracts/index.d.ts +4 -2
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/part-registry.js +3 -0
- package/dist/contracts/schemas/chat-events.d.ts +38 -0
- package/dist/contracts/schemas/chat-events.js +8 -0
- package/dist/contracts/schemas/chat-parts.d.ts +239 -15
- package/dist/contracts/schemas/chat-parts.js +80 -12
- package/dist/contracts/schemas/index.d.ts +2 -2
- package/dist/contracts/schemas/index.js +2 -2
- 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/dbos-workflow-purger.d.ts +2 -0
- package/dist/execution/dbos-workflow-purger.js +21 -14
- package/dist/execution/run-canceler.d.ts +33 -11
- package/dist/execution/run-canceler.js +130 -28
- package/dist/execution/run-end-record.d.ts +30 -0
- package/dist/execution/run-end-record.js +27 -0
- 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 +28 -10
- package/dist/index.js +26 -2
- package/dist/lib/hooks.d.ts +1 -1
- package/dist/lib/metrics.d.ts +2 -2
- package/dist/loop/call-guard.d.ts +50 -0
- package/dist/loop/call-guard.js +90 -0
- package/dist/loop/compaction.d.ts +5 -0
- package/dist/loop/compaction.js +17 -0
- package/dist/loop/run-agent.d.ts +9 -0
- package/dist/loop/run-agent.js +16 -1
- package/dist/loop/run-to-terminal.js +4 -1
- package/dist/loop/types.d.ts +8 -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 -2
- package/dist/memory/conversation-display-recorder.js +38 -4
- 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 +42 -8
- package/dist/memory/thread-history.js +164 -45
- package/dist/memory/thread-store.d.ts +13 -0
- package/dist/memory/thread-store.js +15 -1
- package/dist/prompts/briefing.d.ts +57 -3
- package/dist/prompts/briefing.js +129 -2
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +3 -1
- package/dist/prompts/planner.js +13 -2
- package/dist/provenance/seam.d.ts +39 -20
- package/dist/provenance/seam.js +7 -5
- package/dist/providers/ai-sdk.js +93 -11
- package/dist/providers/types.d.ts +6 -0
- package/dist/reference-data/catalog.js +141 -2
- package/dist/report-model/authoring-grammar.d.ts +26 -26
- 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/image-packages.d.ts +26 -4
- package/dist/sandbox/image-packages.js +18 -4
- 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/data-profile-schemas.d.ts +12 -12
- package/dist/schemas/plan-schemas.d.ts +75 -21
- package/dist/schemas/plan-schemas.js +1 -1
- package/dist/schemas/run-synthesis.d.ts +2 -2
- package/dist/schemas/workflow-state.d.ts +113 -0
- package/dist/schemas/workflow-state.js +54 -0
- package/dist/state/analyses-metrics.d.ts +79 -7
- package/dist/state/analyses-metrics.js +141 -6
- package/dist/state/artifacts.d.ts +66 -3
- package/dist/state/artifacts.js +85 -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 +79 -14
- package/dist/state/runs.js +150 -33
- package/dist/state/schema.d.ts +2 -0
- package/dist/state/step-executions.d.ts +9 -0
- package/dist/state/step-executions.js +22 -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/ad-hoc-router.d.ts +2 -0
- package/dist/tools/ad-hoc-router.js +18 -2
- package/dist/tools/approval/gateway.js +28 -5
- package/dist/tools/capability-data.d.ts +36 -0
- package/dist/tools/capability-data.js +73 -0
- package/dist/tools/display/show-user.d.ts +1 -1
- package/dist/tools/execute-analysis.d.ts +80 -1
- package/dist/tools/execute-analysis.js +352 -110
- package/dist/tools/knowledge/check.d.ts +60 -0
- package/dist/tools/knowledge/check.js +92 -0
- package/dist/tools/knowledge/client.d.ts +382 -0
- package/dist/tools/knowledge/client.js +302 -0
- package/dist/tools/knowledge/environment.d.ts +78 -0
- package/dist/tools/knowledge/environment.js +105 -0
- package/dist/tools/knowledge/index.d.ts +49 -0
- package/dist/tools/knowledge/index.js +70 -0
- package/dist/tools/knowledge/local-slots.d.ts +62 -0
- package/dist/tools/knowledge/local-slots.js +177 -0
- package/dist/tools/knowledge/pin.d.ts +14 -0
- package/dist/tools/knowledge/pin.js +17 -0
- package/dist/tools/knowledge/recommend.d.ts +92 -0
- package/dist/tools/knowledge/recommend.js +98 -0
- package/dist/tools/knowledge/situation.d.ts +143 -0
- package/dist/tools/knowledge/situation.js +164 -0
- package/dist/tools/knowledge/skeleton.d.ts +69 -0
- package/dist/tools/knowledge/skeleton.js +202 -0
- package/dist/tools/knowledge/template.d.ts +137 -0
- package/dist/tools/knowledge/template.js +267 -0
- package/dist/tools/research/generate-plan.d.ts +23 -0
- package/dist/tools/research/generate-plan.js +203 -12
- package/dist/tools/research/guarded-address.d.ts +20 -0
- package/dist/tools/research/guarded-address.js +148 -0
- 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/tools/research/searxng-engine.d.ts +40 -0
- package/dist/tools/research/searxng-engine.js +120 -0
- package/dist/tools/research/web-fetch.d.ts +124 -0
- package/dist/tools/research/web-fetch.js +627 -0
- package/dist/tools/research/web-search.d.ts +75 -0
- package/dist/tools/research/web-search.js +112 -0
- package/dist/tools/sandbox/list-available-packages.d.ts +7 -4
- package/dist/tools/sandbox/list-available-packages.js +12 -4
- package/dist/tools/sandbox/list-available-refs.d.ts +21 -0
- package/dist/tools/sandbox/list-available-refs.js +60 -3
- package/dist/tools/sandbox/report-blocker.js +5 -1
- package/dist/tools/terminal-text.d.ts +11 -0
- package/dist/tools/terminal-text.js +19 -0
- package/dist/tools/workspace/decision-record.d.ts +81 -0
- package/dist/tools/workspace/decision-record.js +117 -0
- package/dist/tools/workspace/edit-file.d.ts +3 -1
- package/dist/tools/workspace/edit-file.js +20 -2
- package/dist/tools/workspace/execute-command.d.ts +17 -0
- package/dist/tools/workspace/execute-command.js +28 -1
- package/dist/tools/workspace/index.d.ts +1 -0
- package/dist/tools/workspace/index.js +1 -0
- package/dist/tools/workspace/mutator.d.ts +1 -1
- package/dist/workflows/execute-analysis.d.ts +62 -21
- package/dist/workflows/execute-analysis.js +307 -79
- package/dist/workflows/plan/graph.d.ts +16 -0
- package/dist/workflows/plan/graph.js +36 -0
- package/dist/workflows/sandbox-step.d.ts +12 -0
- package/dist/workflows/sandbox-step.js +14 -12
- package/package.json +5 -1
|
@@ -6,6 +6,50 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
// ── Plan structures ─────────────────────────────────────────────────
|
|
9
|
+
/**
|
|
10
|
+
* One scientific setting of a grounded step: a parameter of the procedure
|
|
11
|
+
* that `knowledge_recommend` returned, with the procedure step it belongs to
|
|
12
|
+
* and the source of its value. The host binds the settings to the template
|
|
13
|
+
* of the step at dispatch, thus a template default never replaces a plan
|
|
14
|
+
* decision silently.
|
|
15
|
+
*/
|
|
16
|
+
export const GroundingSettingSchema = z.object({
|
|
17
|
+
step: z.string().describe("The procedure step the setting belongs to (for example `differential_expression` or `normalize`)."),
|
|
18
|
+
name: z.string().describe("The parameter name as the procedure names it (for example `alpha` or `lfc_shrink`)."),
|
|
19
|
+
value: z.union([z.string(), z.number(), z.boolean(), z.array(z.string())]).describe("The value the procedure sets."),
|
|
20
|
+
source: z.string().optional().describe("The source of the value (a doi or a document) when the procedure names one. Absent otherwise."),
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* The grounding of one method step: the claim identifiers and the snapshot
|
|
24
|
+
* digest that `knowledge_recommend` returned in this run, the template the
|
|
25
|
+
* step renders, the settings of the procedure, and a one-line reason.
|
|
26
|
+
* Optional on both step schemas, thus a stored plan and a plan made without
|
|
27
|
+
* the knowledge service load and validate as before. Phase 0 validates the
|
|
28
|
+
* shape only. An empty field is visible: the evaluation counts a method step
|
|
29
|
+
* without a value as ungrounded.
|
|
30
|
+
*/
|
|
31
|
+
export const GroundingSchema = z.object({
|
|
32
|
+
status: z
|
|
33
|
+
.enum(["grounded", "ungrounded", "flagged"])
|
|
34
|
+
.describe("`grounded` when the step follows a returned procedure; `ungrounded` when no rule covered it or the service did not answer; `flagged` when a rule changed the outcome of the step."),
|
|
35
|
+
snapshot: z.string().describe("The `snapshot.digest` that `knowledge_recommend` returned in this run, or `none`."),
|
|
36
|
+
claims: z
|
|
37
|
+
.array(z.string())
|
|
38
|
+
.describe("The claim identifiers of the step as `knowledge_recommend` returned them (for example `R-0031@e7d0`). Empty when ungrounded."),
|
|
39
|
+
template: z
|
|
40
|
+
.string()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("The template id with its version (for example `tpl-deseq2-two-group@1.0.0`) when the step renders a template. Absent otherwise."),
|
|
43
|
+
settings: z
|
|
44
|
+
.array(GroundingSettingSchema)
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("The scientific settings of the step: the parameters of the procedure steps the plan step holds, as `knowledge_recommend` returned them. " +
|
|
47
|
+
"Copy the `grounding.settings` of the skeleton step as it is, one entry per parameter, with its step, name, value, and source. " +
|
|
48
|
+
"Do not restate a setting as prose only, and do not change a value here: state a different value in the step and its reason. Absent when the step is ungrounded."),
|
|
49
|
+
reason: z
|
|
50
|
+
.string()
|
|
51
|
+
.describe("One line: the situation and the method with the rule that selects it, the side chosen for a disputed rule, or why the step is ungrounded or flagged."),
|
|
52
|
+
});
|
|
9
53
|
/**
|
|
10
54
|
* A step of an analysis plan. `PlanStepSchema` (schemas/plan-schemas.ts) narrows
|
|
11
55
|
* this into the planner's output contract and emits it as the `submit_plan`
|
|
@@ -52,8 +96,18 @@ export const AnalysisStepSchema = z.object({
|
|
|
52
96
|
})
|
|
53
97
|
.optional()
|
|
54
98
|
.describe("Resources the step needs. Ground the estimate in the actual data size — see the resource-estimation rules."),
|
|
99
|
+
grounding: GroundingSchema.optional().describe("The knowledge grounding of a method step: fill it from the answer of `knowledge_recommend` when that tool is attached. Omit it when no knowledge tool is attached."),
|
|
55
100
|
// Execution fields
|
|
56
101
|
agent: z.string().optional().describe("Assigned sandbox agent name from registry"),
|
|
102
|
+
// Optional on the persistence schema so a plan stored before this field still parses.
|
|
103
|
+
// `PlanStepSchema` re-requires it for planner output. The launch reads it with the agent id to
|
|
104
|
+
// resolve what reference data the capability declares — refer to `capabilityDataRequirement`.
|
|
105
|
+
capability: z
|
|
106
|
+
.string()
|
|
107
|
+
.optional()
|
|
108
|
+
.describe("The one capability phrase of the assigned agent that this step uses, copied EXACTLY from the agent catalog. " +
|
|
109
|
+
"One phrase, verbatim — never a paraphrase, never a list, never your own wording. " +
|
|
110
|
+
"The launch resolves the reference data that the capability declares before the run starts, so a reworded phrase loses that check and the step discovers an absent source itself."),
|
|
57
111
|
timeout: z.number().optional().describe("Execution timeout in seconds (falls back to image default)"),
|
|
58
112
|
maxSteps: z.number().describe("Max LLM agentic steps for sandbox agent"),
|
|
59
113
|
description: z.string().optional().describe("Human-readable description of what the step produces."),
|
|
@@ -1,11 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cross-entity aggregate reads
|
|
3
|
-
*
|
|
4
|
-
* the
|
|
2
|
+
* Cross-entity aggregate reads for a list of analyses. Each read takes the ids
|
|
3
|
+
* of a page and sends one statement, whatever the length of the list: an
|
|
4
|
+
* embedder builds the per-analysis summary of a list from these reads, never
|
|
5
|
+
* from one read for each analysis.
|
|
5
6
|
*/
|
|
6
7
|
import { type ResultAsync } from "neverthrow";
|
|
8
|
+
import type { DataProfileLifecycleStatus } from "../contracts/data-profile.js";
|
|
7
9
|
import { type DbError } from "../lib/db-result.js";
|
|
8
10
|
import type { Querier } from "./db.js";
|
|
11
|
+
import type { RunStatus, TerminalRunStatus } from "./schema.js";
|
|
12
|
+
/** The newest run of one analysis, with the title of its plan and the counts of its step rows. */
|
|
13
|
+
export interface LatestRunSummary {
|
|
14
|
+
readonly analysisId: string;
|
|
15
|
+
readonly runId: string;
|
|
16
|
+
readonly status: RunStatus;
|
|
17
|
+
readonly planTitle: string | null;
|
|
18
|
+
readonly doneSteps: number;
|
|
19
|
+
readonly totalSteps: number;
|
|
20
|
+
readonly startedAt: string;
|
|
21
|
+
readonly completedAt: string | null;
|
|
22
|
+
}
|
|
23
|
+
/** A run of a list of run ids, with the fields of the newest-run read and the thread of the run. */
|
|
24
|
+
export interface RunProgressSummary extends LatestRunSummary {
|
|
25
|
+
/** The thread of the run as the ledger stores it, or `null`. */
|
|
26
|
+
readonly threadId: string | null;
|
|
27
|
+
}
|
|
28
|
+
/** A terminal run that ended at or after a time. */
|
|
29
|
+
export interface EndedRunSummary {
|
|
30
|
+
readonly analysisId: string;
|
|
31
|
+
readonly runId: string;
|
|
32
|
+
readonly status: TerminalRunStatus;
|
|
33
|
+
readonly completedAt: string;
|
|
34
|
+
}
|
|
35
|
+
/** The data profile state of one analysis that has a profile status. */
|
|
36
|
+
export interface DataProfileSummary {
|
|
37
|
+
readonly analysisId: string;
|
|
38
|
+
readonly status: DataProfileLifecycleStatus;
|
|
39
|
+
readonly completedAt: string | null;
|
|
40
|
+
readonly profiledFiles: number | null;
|
|
41
|
+
}
|
|
9
42
|
export declare function queryRunCountsByAnalyses(pool: Querier, analysisIds: string[]): ResultAsync<Array<{
|
|
10
43
|
analysisId: string;
|
|
11
44
|
status: string;
|
|
@@ -15,7 +48,46 @@ export declare function queryThreadCountsByAnalyses(pool: Querier, analysisIds:
|
|
|
15
48
|
analysisId: string;
|
|
16
49
|
count: number;
|
|
17
50
|
}>, DbError>;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
51
|
+
/**
|
|
52
|
+
* The newest run of each analysis of a list, in one statement. An analysis with
|
|
53
|
+
* no run has no entry. The newest run has the latest `started_at`, and `run_id`
|
|
54
|
+
* breaks a tie in descending order. The `LATERAL` subquery reads
|
|
55
|
+
* `idx_cortex_runs_analysis_started`, thus one index entry for each analysis
|
|
56
|
+
* and not a sort of the run history.
|
|
57
|
+
*
|
|
58
|
+
* The plan joins on `plan_id` and `analysis_id`: a plan of a different analysis
|
|
59
|
+
* gives no title, the scope rule of `loadPlan`. The statement gives the title
|
|
60
|
+
* only when it is a string, and the row mapping trims it with `trim()`, the
|
|
61
|
+
* rule of the cli `planTitleOf`. A blank title or a title that is not a string
|
|
62
|
+
* reads as `null`. The step counts
|
|
63
|
+
* cover the ledger rows of the selected run only, the reserved synthesis row
|
|
64
|
+
* included.
|
|
65
|
+
*/
|
|
66
|
+
export declare function queryLatestRunsByAnalyses(pool: Querier, analysisIds: string[]): ResultAsync<LatestRunSummary[], DbError>;
|
|
67
|
+
/**
|
|
68
|
+
* The progress of each run of a list of run ids, in one statement. An id with
|
|
69
|
+
* no run row has no entry. The entries come newest first by `started_at`, and
|
|
70
|
+
* `run_id` breaks a tie in descending order. The plan title and the step counts
|
|
71
|
+
* follow the rules of `queryLatestRunsByAnalyses`, and each entry adds the
|
|
72
|
+
* thread of the run.
|
|
73
|
+
*/
|
|
74
|
+
export declare function queryRunProgressByRuns(pool: Querier, runIds: string[]): ResultAsync<RunProgressSummary[], DbError>;
|
|
75
|
+
/**
|
|
76
|
+
* The newest `limit` terminal runs of every analysis that ended at or after
|
|
77
|
+
* `since`, newest completion first, with `run_id` breaking a tie in descending
|
|
78
|
+
* order. `since` is in the form of `toISOString()`, the form of each
|
|
79
|
+
* `completed_at` stamp, thus the text comparison is the time comparison. The
|
|
80
|
+
* statement walks `idx_cortex_runs_completed`.
|
|
81
|
+
*/
|
|
82
|
+
export declare function queryRunsEndedSince(pool: Querier, input: {
|
|
83
|
+
since: string;
|
|
84
|
+
limit: number;
|
|
85
|
+
}): ResultAsync<EndedRunSummary[], DbError>;
|
|
86
|
+
/**
|
|
87
|
+
* The profile state of each analysis of a list, in one statement. An analysis
|
|
88
|
+
* with no ledger row, or with a cleared row whose status is null, has no entry:
|
|
89
|
+
* the same collapsed absence as `loadDataProfileStatus`. The statement selects
|
|
90
|
+
* only the three parts of the stored result that the file count reads, and the
|
|
91
|
+
* count itself is `profileDatasetFileCount`.
|
|
92
|
+
*/
|
|
93
|
+
export declare function queryDataProfileStatusByAnalyses(pool: Querier, analysisIds: string[]): ResultAsync<DataProfileSummary[], DbError>;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cross-entity aggregate reads
|
|
3
|
-
*
|
|
4
|
-
* the
|
|
2
|
+
* Cross-entity aggregate reads for a list of analyses. Each read takes the ids
|
|
3
|
+
* of a page and sends one statement, whatever the length of the list: an
|
|
4
|
+
* embedder builds the per-analysis summary of a list from these reads, never
|
|
5
|
+
* from one read for each analysis.
|
|
5
6
|
*/
|
|
6
7
|
import { okAsync } from "neverthrow";
|
|
8
|
+
import { profileDatasetFileCount } from "../app/data-profile-view.js";
|
|
7
9
|
import { tryQuery } from "../lib/db-result.js";
|
|
8
10
|
export function queryRunCountsByAnalyses(pool, analysisIds) {
|
|
9
11
|
if (analysisIds.length === 0)
|
|
@@ -41,19 +43,152 @@ export function queryThreadCountsByAnalyses(pool, analysisIds) {
|
|
|
41
43
|
}));
|
|
42
44
|
});
|
|
43
45
|
}
|
|
46
|
+
/** A plan title as text: trimmed with `trim()`, and `null` when it is blank. The SQL gives the title only when it is a string. */
|
|
47
|
+
function titleOf(title) {
|
|
48
|
+
const trimmed = title?.trim() ?? "";
|
|
49
|
+
return trimmed === "" ? null : trimmed;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The columns of a run summary over a run alias `r`, and the plan alias `p` and
|
|
53
|
+
* the step count alias `s` of `RUN_PROGRESS_JOINS`. The two reads of a run
|
|
54
|
+
* summary put these after their own selection of `r`, thus the title rule and
|
|
55
|
+
* the step count rule have one text.
|
|
56
|
+
*/
|
|
57
|
+
const RUN_PROGRESS_COLUMNS = `r.analysis_id, r.run_id, r.status, r.started_at, r.completed_at,
|
|
58
|
+
CASE WHEN jsonb_typeof(p.plan->'title') = 'string' THEN p.plan->>'title' END AS plan_title,
|
|
59
|
+
s.done_steps, s.total_steps`;
|
|
60
|
+
/** The plan join and the step count subquery for a run alias `r` that selects `analysis_id`, `run_id`, and `plan_id`. */
|
|
61
|
+
const RUN_PROGRESS_JOINS = `LEFT JOIN cortex_plans p ON p.plan_id = r.plan_id AND p.analysis_id = r.analysis_id
|
|
62
|
+
CROSS JOIN LATERAL (
|
|
63
|
+
SELECT COUNT(*)::int AS total_steps,
|
|
64
|
+
(COUNT(*) FILTER (WHERE status = 'completed'))::int AS done_steps
|
|
65
|
+
FROM cortex_step_executions
|
|
66
|
+
WHERE run_id = r.run_id
|
|
67
|
+
) s`;
|
|
68
|
+
function toRunSummary(row) {
|
|
69
|
+
return {
|
|
70
|
+
analysisId: row.analysis_id,
|
|
71
|
+
runId: row.run_id,
|
|
72
|
+
status: row.status,
|
|
73
|
+
planTitle: titleOf(row.plan_title),
|
|
74
|
+
doneSteps: row.done_steps,
|
|
75
|
+
totalSteps: row.total_steps,
|
|
76
|
+
startedAt: row.started_at,
|
|
77
|
+
completedAt: row.completed_at,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The newest run of each analysis of a list, in one statement. An analysis with
|
|
82
|
+
* no run has no entry. The newest run has the latest `started_at`, and `run_id`
|
|
83
|
+
* breaks a tie in descending order. The `LATERAL` subquery reads
|
|
84
|
+
* `idx_cortex_runs_analysis_started`, thus one index entry for each analysis
|
|
85
|
+
* and not a sort of the run history.
|
|
86
|
+
*
|
|
87
|
+
* The plan joins on `plan_id` and `analysis_id`: a plan of a different analysis
|
|
88
|
+
* gives no title, the scope rule of `loadPlan`. The statement gives the title
|
|
89
|
+
* only when it is a string, and the row mapping trims it with `trim()`, the
|
|
90
|
+
* rule of the cli `planTitleOf`. A blank title or a title that is not a string
|
|
91
|
+
* reads as `null`. The step counts
|
|
92
|
+
* cover the ledger rows of the selected run only, the reserved synthesis row
|
|
93
|
+
* included.
|
|
94
|
+
*/
|
|
95
|
+
export function queryLatestRunsByAnalyses(pool, analysisIds) {
|
|
96
|
+
if (analysisIds.length === 0)
|
|
97
|
+
return okAsync([]);
|
|
98
|
+
return tryQuery("analysesMetrics.queryLatestRunsByAnalyses", async () => {
|
|
99
|
+
const result = await pool.query({
|
|
100
|
+
text: `SELECT ${RUN_PROGRESS_COLUMNS}
|
|
101
|
+
FROM unnest($1::text[]) AS a(id)
|
|
102
|
+
CROSS JOIN LATERAL (
|
|
103
|
+
SELECT analysis_id, run_id, status, started_at, completed_at, plan_id
|
|
104
|
+
FROM cortex_runs
|
|
105
|
+
WHERE analysis_id = a.id
|
|
106
|
+
ORDER BY started_at DESC, run_id DESC
|
|
107
|
+
LIMIT 1
|
|
108
|
+
) r
|
|
109
|
+
${RUN_PROGRESS_JOINS}`,
|
|
110
|
+
values: [analysisIds],
|
|
111
|
+
});
|
|
112
|
+
return result.rows.map(toRunSummary);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The progress of each run of a list of run ids, in one statement. An id with
|
|
117
|
+
* no run row has no entry. The entries come newest first by `started_at`, and
|
|
118
|
+
* `run_id` breaks a tie in descending order. The plan title and the step counts
|
|
119
|
+
* follow the rules of `queryLatestRunsByAnalyses`, and each entry adds the
|
|
120
|
+
* thread of the run.
|
|
121
|
+
*/
|
|
122
|
+
export function queryRunProgressByRuns(pool, runIds) {
|
|
123
|
+
if (runIds.length === 0)
|
|
124
|
+
return okAsync([]);
|
|
125
|
+
return tryQuery("analysesMetrics.queryRunProgressByRuns", async () => {
|
|
126
|
+
const result = await pool.query({
|
|
127
|
+
text: `SELECT ${RUN_PROGRESS_COLUMNS}, r.thread_id
|
|
128
|
+
FROM cortex_runs r
|
|
129
|
+
${RUN_PROGRESS_JOINS}
|
|
130
|
+
WHERE r.run_id = ANY($1::text[])
|
|
131
|
+
ORDER BY r.started_at DESC, r.run_id DESC`,
|
|
132
|
+
values: [runIds],
|
|
133
|
+
});
|
|
134
|
+
return result.rows.map((row) => ({ ...toRunSummary(row), threadId: row.thread_id }));
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* The newest `limit` terminal runs of every analysis that ended at or after
|
|
139
|
+
* `since`, newest completion first, with `run_id` breaking a tie in descending
|
|
140
|
+
* order. `since` is in the form of `toISOString()`, the form of each
|
|
141
|
+
* `completed_at` stamp, thus the text comparison is the time comparison. The
|
|
142
|
+
* statement walks `idx_cortex_runs_completed`.
|
|
143
|
+
*/
|
|
144
|
+
export function queryRunsEndedSince(pool, input) {
|
|
145
|
+
return tryQuery("analysesMetrics.queryRunsEndedSince", async () => {
|
|
146
|
+
const result = await pool.query({
|
|
147
|
+
text: `SELECT analysis_id, run_id, status, completed_at
|
|
148
|
+
FROM cortex_runs
|
|
149
|
+
WHERE status IN ('completed', 'partial', 'failed', 'canceled', 'suspended_insufficient_funds')
|
|
150
|
+
AND completed_at >= $1
|
|
151
|
+
ORDER BY completed_at DESC, run_id DESC
|
|
152
|
+
LIMIT $2`,
|
|
153
|
+
values: [input.since, input.limit],
|
|
154
|
+
});
|
|
155
|
+
return result.rows.map((row) => ({
|
|
156
|
+
analysisId: row.analysis_id,
|
|
157
|
+
runId: row.run_id,
|
|
158
|
+
status: row.status,
|
|
159
|
+
completedAt: row.completed_at,
|
|
160
|
+
}));
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The profile state of each analysis of a list, in one statement. An analysis
|
|
165
|
+
* with no ledger row, or with a cleared row whose status is null, has no entry:
|
|
166
|
+
* the same collapsed absence as `loadDataProfileStatus`. The statement selects
|
|
167
|
+
* only the three parts of the stored result that the file count reads, and the
|
|
168
|
+
* count itself is `profileDatasetFileCount`.
|
|
169
|
+
*/
|
|
44
170
|
export function queryDataProfileStatusByAnalyses(pool, analysisIds) {
|
|
45
171
|
if (analysisIds.length === 0)
|
|
46
172
|
return okAsync([]);
|
|
47
173
|
return tryQuery("analysesMetrics.queryDataProfileStatusByAnalyses", async () => {
|
|
48
174
|
const result = await pool.query({
|
|
49
|
-
text: `SELECT analysis_id, data_profile_status
|
|
175
|
+
text: `SELECT analysis_id, data_profile_status, data_profile_completed_at,
|
|
176
|
+
data_profile_result->'partition' AS partition,
|
|
177
|
+
data_profile_result->'coverage' AS coverage,
|
|
178
|
+
data_profile_result->'kinds' AS kinds
|
|
50
179
|
FROM cortex_analysis_state
|
|
51
|
-
WHERE analysis_id = ANY($1::text[])`,
|
|
180
|
+
WHERE analysis_id = ANY($1::text[]) AND data_profile_status IS NOT NULL`,
|
|
52
181
|
values: [analysisIds],
|
|
53
182
|
});
|
|
54
183
|
return result.rows.map((r) => ({
|
|
55
184
|
analysisId: r.analysis_id,
|
|
56
|
-
status: r.data_profile_status
|
|
185
|
+
status: r.data_profile_status,
|
|
186
|
+
completedAt: r.data_profile_completed_at ?? null,
|
|
187
|
+
profiledFiles: profileDatasetFileCount({
|
|
188
|
+
...(r.partition === null ? {} : { partition: r.partition }),
|
|
189
|
+
...(r.coverage === null ? {} : { coverage: r.coverage }),
|
|
190
|
+
...(r.kinds === null ? {} : { kinds: r.kinds }),
|
|
191
|
+
}),
|
|
57
192
|
}));
|
|
58
193
|
});
|
|
59
194
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { ResultAsync } from "neverthrow";
|
|
6
6
|
import { type DbError } from "../lib/db-result.js";
|
|
7
7
|
import type { Querier } from "./db.js";
|
|
8
|
-
import type { ArtifactRow } from "./schema.js";
|
|
8
|
+
import type { ArtifactRole, ArtifactRow } from "./schema.js";
|
|
9
9
|
export interface RegisterArtifactInput {
|
|
10
10
|
resourceId: string;
|
|
11
11
|
path: string;
|
|
@@ -75,8 +75,71 @@ export interface AnalysisArtifactRef {
|
|
|
75
75
|
* false.
|
|
76
76
|
*/
|
|
77
77
|
export declare function queryAnalysisArtifacts(pool: Querier, analysisId: string): ResultAsync<AnalysisArtifactRef[], DbError>;
|
|
78
|
-
/**
|
|
79
|
-
export
|
|
78
|
+
/** The sort of {@link queryArtifactPage}: `path` is path order, `recent` is newest registration first. */
|
|
79
|
+
export type ArtifactPageSort = "path" | "recent";
|
|
80
|
+
/** The filters, the sort, and the window of {@link queryArtifactPage}. A filter that is absent selects each row. */
|
|
81
|
+
export interface ArtifactPageQuery {
|
|
82
|
+
runId?: string;
|
|
83
|
+
stepId?: string;
|
|
84
|
+
fileType?: string;
|
|
85
|
+
/** A part of the path, with no regard to case. Each character matches only itself. */
|
|
86
|
+
q?: string;
|
|
87
|
+
/** The whole path, with case. It selects the one row of that path. */
|
|
88
|
+
path?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Extensions in lower case, with no dot. A row is selected when the extension of its path is a member. A row with
|
|
91
|
+
* no extension never matches, and an empty list selects each row. The read compares the members as they are.
|
|
92
|
+
*/
|
|
93
|
+
extensions?: readonly string[];
|
|
94
|
+
sort: ArtifactPageSort;
|
|
95
|
+
limit: number;
|
|
96
|
+
offset: number;
|
|
97
|
+
}
|
|
98
|
+
/** One ledger row of {@link queryArtifactPage}. */
|
|
99
|
+
export interface ArtifactPageEntry {
|
|
100
|
+
/** Workspace-root-relative path (`runs/{runId}/{stepId}/output/de.csv`). */
|
|
101
|
+
path: string;
|
|
102
|
+
/** Bytes. */
|
|
103
|
+
size: number;
|
|
104
|
+
fileType: string | null;
|
|
105
|
+
role: ArtifactRole;
|
|
106
|
+
runId: string | null;
|
|
107
|
+
stepId: string | null;
|
|
108
|
+
createdAt: string;
|
|
109
|
+
}
|
|
110
|
+
/** The count of the selected rows that have one extension. */
|
|
111
|
+
export interface ArtifactExtensionCount {
|
|
112
|
+
/** The extension in lower case with no dot, or `null` for the rows that have no extension. */
|
|
113
|
+
extension: string | null;
|
|
114
|
+
count: number;
|
|
115
|
+
}
|
|
116
|
+
/** One page of the ledger, and the count of the rows that the filters select. */
|
|
117
|
+
export interface ArtifactPage {
|
|
118
|
+
entries: ArtifactPageEntry[];
|
|
119
|
+
total: number;
|
|
120
|
+
/** The selected rows by extension, in the order of the extension with `null` last. The counts sum to `total`. */
|
|
121
|
+
extensionCounts: ArtifactExtensionCount[];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* One page of the artifact ledger of an analysis, in two statements: the count of the rows that the filters
|
|
125
|
+
* select, grouped by extension, and the page. The count is a separate statement because a page past the end has no
|
|
126
|
+
* row to carry it. `total` is the sum of the groups.
|
|
127
|
+
*
|
|
128
|
+
* Every row of the analysis is a member, an `input` row and a row with `unrecoverable_at` included. The cost
|
|
129
|
+
* grows with the ledger of the one analysis, because the count reads each filtered row.
|
|
130
|
+
*/
|
|
131
|
+
export declare function queryArtifactPage(pool: Querier, analysisId: string, query: ArtifactPageQuery): ResultAsync<ArtifactPage, DbError>;
|
|
132
|
+
/** The step-output count of one run, from {@link countArtifactsByRuns}. */
|
|
133
|
+
export interface RunArtifactCount {
|
|
134
|
+
runId: string;
|
|
135
|
+
count: number;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* The step-output count of each run of a list, in one statement. A run with no step output has no entry,
|
|
139
|
+
* and a caller reads it as 0. An `input` row never counts. The run-completed card and a list of runs use
|
|
140
|
+
* this one rule.
|
|
141
|
+
*/
|
|
142
|
+
export declare function countArtifactsByRuns(pool: Querier, analysisId: string, runIds: readonly string[]): ResultAsync<RunArtifactCount[], DbError>;
|
|
80
143
|
export declare function updateArtifactId(pool: Querier, resourceId: string, path: string, artifactId: string, fileType?: string | null): ResultAsync<void, DbError>;
|
|
81
144
|
export declare function updateFileIds(pool: Querier, pairs: Array<{
|
|
82
145
|
artifactId: string;
|
package/dist/state/artifacts.js
CHANGED
|
@@ -179,16 +179,94 @@ export function queryAnalysisArtifacts(pool, analysisId) {
|
|
|
179
179
|
return result.rows.map((r) => ({ path: r.path, hash: r.hash, fileType: r.file_type ?? null }));
|
|
180
180
|
});
|
|
181
181
|
}
|
|
182
|
-
/**
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
/**
|
|
183
|
+
* The extension of `path` in lower case: the text after the last dot of the file name, which is the part after the
|
|
184
|
+
* last `/`. The pattern needs one character or more before that dot and one or more after it, thus a name with no dot,
|
|
185
|
+
* a name whose only dot is its first character, and a name that ends with a dot give `NULL`.
|
|
186
|
+
*/
|
|
187
|
+
const EXTENSION_SQL = `lower(substring(regexp_replace(path, '^.*/', '') from '^.+\\.([^.]+)$'))`;
|
|
188
|
+
/**
|
|
189
|
+
* One page of the artifact ledger of an analysis, in two statements: the count of the rows that the filters
|
|
190
|
+
* select, grouped by extension, and the page. The count is a separate statement because a page past the end has no
|
|
191
|
+
* row to carry it. `total` is the sum of the groups.
|
|
192
|
+
*
|
|
193
|
+
* Every row of the analysis is a member, an `input` row and a row with `unrecoverable_at` included. The cost
|
|
194
|
+
* grows with the ledger of the one analysis, because the count reads each filtered row.
|
|
195
|
+
*/
|
|
196
|
+
export function queryArtifactPage(pool, analysisId, query) {
|
|
197
|
+
return tryQuery("artifacts.queryArtifactPage", async () => {
|
|
198
|
+
const values = [analysisId];
|
|
199
|
+
const conditions = ["analysis_id = $1"];
|
|
200
|
+
const bind = (value) => {
|
|
201
|
+
values.push(value);
|
|
202
|
+
return `$${values.length}`;
|
|
203
|
+
};
|
|
204
|
+
if (query.runId !== undefined)
|
|
205
|
+
conditions.push(`source_run = ${bind(query.runId)}`);
|
|
206
|
+
if (query.stepId !== undefined)
|
|
207
|
+
conditions.push(`source_step = ${bind(query.stepId)}`);
|
|
208
|
+
if (query.fileType !== undefined)
|
|
209
|
+
conditions.push(`file_type = ${bind(query.fileType)}`);
|
|
210
|
+
// `LIKE` would turn `%` and `_` of the input into wildcards.
|
|
211
|
+
if (query.q !== undefined)
|
|
212
|
+
conditions.push(`strpos(lower(path), lower(${bind(query.q)}::text)) > 0`);
|
|
213
|
+
if (query.path !== undefined)
|
|
214
|
+
conditions.push(`path = ${bind(query.path)}`);
|
|
215
|
+
if (query.extensions !== undefined && query.extensions.length > 0) {
|
|
216
|
+
conditions.push(`${EXTENSION_SQL} = ANY(${bind([...query.extensions])}::text[])`);
|
|
217
|
+
}
|
|
218
|
+
const where = conditions.join(" AND ");
|
|
219
|
+
const countResult = await pool.query({
|
|
220
|
+
text: `SELECT ${EXTENSION_SQL} AS extension, COUNT(*)::int AS n
|
|
221
|
+
FROM cortex_artifacts
|
|
222
|
+
WHERE ${where}
|
|
223
|
+
GROUP BY 1
|
|
224
|
+
ORDER BY 1 ASC NULLS LAST`,
|
|
225
|
+
values,
|
|
226
|
+
});
|
|
227
|
+
const extensionCounts = countResult.rows.map((r) => ({ extension: r.extension ?? null, count: Number(r.n) }));
|
|
228
|
+
// `path` is unique in one analysis, thus it breaks each tie and two pages never overlap.
|
|
229
|
+
const order = query.sort === "recent" ? "created_at DESC, path ASC" : "path ASC";
|
|
230
|
+
const rowsResult = await pool.query({
|
|
231
|
+
text: `SELECT path, size, file_type, role, source_run, source_step, created_at
|
|
232
|
+
FROM cortex_artifacts
|
|
233
|
+
WHERE ${where}
|
|
234
|
+
ORDER BY ${order}
|
|
235
|
+
LIMIT ${bind(query.limit)} OFFSET ${bind(query.offset)}`,
|
|
236
|
+
values,
|
|
237
|
+
});
|
|
238
|
+
return {
|
|
239
|
+
entries: rowsResult.rows.map((r) => ({
|
|
240
|
+
path: r.path,
|
|
241
|
+
size: Number(r.size),
|
|
242
|
+
fileType: r.file_type ?? null,
|
|
243
|
+
role: r.role,
|
|
244
|
+
runId: r.source_run ?? null,
|
|
245
|
+
stepId: r.source_step ?? null,
|
|
246
|
+
createdAt: r.created_at,
|
|
247
|
+
})),
|
|
248
|
+
total: extensionCounts.reduce((sum, e) => sum + e.count, 0),
|
|
249
|
+
extensionCounts,
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* The step-output count of each run of a list, in one statement. A run with no step output has no entry,
|
|
255
|
+
* and a caller reads it as 0. An `input` row never counts. The run-completed card and a list of runs use
|
|
256
|
+
* this one rule.
|
|
257
|
+
*/
|
|
258
|
+
export function countArtifactsByRuns(pool, analysisId, runIds) {
|
|
259
|
+
return tryQuery("artifacts.countArtifactsByRuns", async () => {
|
|
260
|
+
if (runIds.length === 0)
|
|
261
|
+
return [];
|
|
185
262
|
const result = await pool.query({
|
|
186
|
-
text: `SELECT COUNT(*)::
|
|
263
|
+
text: `SELECT source_run, COUNT(*)::int AS n
|
|
187
264
|
FROM cortex_artifacts
|
|
188
|
-
WHERE analysis_id = $1 AND source_run = $2 AND role = 'step_output'
|
|
189
|
-
|
|
265
|
+
WHERE analysis_id = $1 AND source_run = ANY($2::text[]) AND role = 'step_output'
|
|
266
|
+
GROUP BY source_run`,
|
|
267
|
+
values: [analysisId, [...runIds]],
|
|
190
268
|
});
|
|
191
|
-
return
|
|
269
|
+
return result.rows.map((r) => ({ runId: r.source_run, count: Number(r.n) }));
|
|
192
270
|
});
|
|
193
271
|
}
|
|
194
272
|
export function updateArtifactId(pool, resourceId, path, artifactId, fileType) {
|
|
@@ -101,12 +101,14 @@ export declare function recordDataProfileWorkflowId(pool: Querier, analysisId: s
|
|
|
101
101
|
* write per `running` claim, so `stamped: true` is the exactly-once point of a
|
|
102
102
|
* profile's terminal transition. It carries the `data_profile_started_at` of the
|
|
103
103
|
* claim it closed, so the caller measures the profile from the ledger's own start.
|
|
104
|
+
* It also carries the `data_profile_completed_at` that the write stamped.
|
|
104
105
|
*/
|
|
105
106
|
export type DataProfileTerminalWrite = {
|
|
106
107
|
readonly stamped: false;
|
|
107
108
|
} | {
|
|
108
109
|
readonly stamped: true;
|
|
109
110
|
readonly startedAt: string | null;
|
|
111
|
+
readonly completedAt: string;
|
|
110
112
|
};
|
|
111
113
|
/**
|
|
112
114
|
* Stamp a claimed-`running` row `completed` and record its result — a terminal
|
|
@@ -124,7 +126,7 @@ export type DataProfileTerminalWrite = {
|
|
|
124
126
|
* guard only ever refuses a row another writer already moved on from.
|
|
125
127
|
*
|
|
126
128
|
* `stamped: true` when this call stamped the row, with the `data_profile_started_at`
|
|
127
|
-
* of the claim it closed; `stamped: false` when the guard refused it (the row was
|
|
129
|
+
* of the claim it closed and the `data_profile_completed_at` it stamped; `stamped: false` when the guard refused it (the row was
|
|
128
130
|
* cleared/expired/replayed away) — a no-op the caller logs, not an error.
|
|
129
131
|
*/
|
|
130
132
|
export declare function completeDataProfile(pool: Querier, analysisId: string, result?: DataProfileResult): ResultAsync<DataProfileTerminalWrite, DbError>;
|
|
@@ -137,7 +139,7 @@ export declare function completeDataProfile(pool: Querier, analysisId: string, r
|
|
|
137
139
|
* prior profile survives the failure (the re-profile/retry route can keep serving it).
|
|
138
140
|
*
|
|
139
141
|
* `stamped: true` when this call stamped the row, with the `data_profile_started_at`
|
|
140
|
-
* of the claim it closed; `stamped: false` when the guard refused it — a logged
|
|
142
|
+
* of the claim it closed and the `data_profile_completed_at` it stamped; `stamped: false` when the guard refused it — a logged
|
|
141
143
|
* no-op, not an error.
|
|
142
144
|
*/
|
|
143
145
|
export declare function failDataProfile(pool: Querier, analysisId: string, error: string): ResultAsync<DataProfileTerminalWrite, DbError>;
|
|
@@ -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, ArtifactExtensionCount, 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, countRunsByAnalysis, queryRunsByThread, queryRunIdsByPlan, queryNewestPlanRun, } 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";
|