@inflexa-ai/harness 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/dist/agents/conversation-agent.d.ts +3 -0
- package/dist/agents/conversation-agent.js +2 -2
- package/dist/agents/sandbox/shared.d.ts +3 -0
- package/dist/agents/sandbox/shared.js +2 -2
- package/dist/app/chat-turn.d.ts +3 -0
- package/dist/app/chat-turn.js +3 -1
- package/dist/app/synthesize-run.d.ts +3 -0
- package/dist/app/synthesize-run.js +3 -1
- package/dist/execution/artifact-metadata.d.ts +3 -0
- package/dist/execution/artifact-metadata.js +8 -2
- package/dist/execution/artifact-registration.d.ts +2 -1
- package/dist/execution/artifact-registration.js +8 -2
- package/dist/execution/ephemeral-runner.d.ts +3 -0
- package/dist/execution/ephemeral-runner.js +8 -4
- package/dist/execution/post-step-pipeline.d.ts +3 -0
- package/dist/execution/post-step-pipeline.js +24 -4
- package/dist/execution/reconcile-manifest.d.ts +7 -0
- package/dist/execution/reconcile-manifest.js +19 -5
- package/dist/execution/report-runner.d.ts +3 -0
- package/dist/execution/report-runner.js +4 -4
- package/dist/execution/step-summary.d.ts +3 -0
- package/dist/execution/step-summary.js +4 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -0
- package/dist/lib/chrome.d.ts +4 -1
- package/dist/lib/chrome.js +10 -7
- package/dist/lib/console-logger.d.ts +19 -0
- package/dist/lib/console-logger.js +62 -0
- package/dist/lib/logger.d.ts +88 -0
- package/dist/lib/logger.js +37 -0
- package/dist/lib/otel.d.ts +2 -1
- package/dist/lib/otel.js +8 -3
- package/dist/runtime/boot.d.ts +2 -2
- package/dist/runtime/boot.js +1 -1
- package/dist/runtime/connection-budget.d.ts +2 -2
- package/dist/runtime/connection-budget.js +5 -3
- package/dist/runtime/dbos.d.ts +7 -7
- package/dist/runtime/dbos.js +14 -11
- package/dist/runtime/shutdown.d.ts +2 -2
- package/dist/runtime/shutdown.js +4 -3
- package/dist/sandbox/create-sandbox.d.ts +2 -2
- package/dist/sandbox/docker-client.d.ts +2 -2
- package/dist/sandbox/docker-client.js +4 -1
- package/dist/sandbox/k8s-client.d.ts +3 -0
- package/dist/sandbox/k8s-client.js +5 -4
- package/dist/sandbox/notification-sweep.d.ts +3 -3
- package/dist/sandbox/notification-sweep.js +4 -2
- package/dist/sandbox/reaper.d.ts +3 -3
- package/dist/sandbox/reaper.js +7 -4
- package/dist/sandbox/watchdog.d.ts +3 -3
- package/dist/sandbox/watchdog.js +8 -2
- package/dist/state/init.d.ts +2 -1
- package/dist/state/init.js +5 -6
- package/dist/tasks/data-profile.d.ts +5 -0
- package/dist/tasks/data-profile.js +24 -17
- package/dist/tools/bio/search-dgidb.d.ts +11 -1
- package/dist/tools/bio/search-dgidb.js +92 -78
- package/dist/tools/iterate-report.d.ts +3 -0
- package/dist/tools/iterate-report.js +3 -1
- package/dist/tools/research/literature-reviewer.d.ts +3 -0
- package/dist/tools/research/literature-reviewer.js +2 -2
- package/dist/tools/workspace/execute-command.d.ts +3 -0
- package/dist/tools/workspace/execute-command.js +3 -1
- package/dist/workflows/execute-analysis.d.ts +3 -0
- package/dist/workflows/execute-analysis.js +11 -7
- package/dist/workflows/execute-target-assessment.d.ts +3 -0
- package/dist/workflows/execute-target-assessment.js +19 -15
- package/dist/workflows/sandbox-step.d.ts +9 -0
- package/dist/workflows/sandbox-step.js +39 -17
- package/dist/workflows/target-assessment/lib/llm-step.d.ts +3 -0
- package/dist/workflows/target-assessment/lib/llm-step.js +3 -1
- package/dist/workflows/target-assessment/lib/structured-llm.d.ts +3 -0
- package/dist/workflows/target-assessment/lib/structured-llm.js +1 -0
- package/dist/workflows/target-assessment/phase5-persist.d.ts +3 -0
- package/dist/workflows/target-assessment/phase5-persist.js +8 -3
- package/dist/workflows/target-assessment/progress.d.ts +2 -1
- package/dist/workflows/target-assessment/progress.js +4 -3
- package/dist/workspace/filesystem.js +163 -74
- package/package.json +95 -96
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
* effective capabilities.
|
|
30
30
|
*/
|
|
31
31
|
import Docker from "dockerode";
|
|
32
|
-
import type pino from "pino";
|
|
33
32
|
import { ResultAsync } from "neverthrow";
|
|
33
|
+
import type { Logger } from "../lib/logger.js";
|
|
34
34
|
import { type ResolveWorkspaceRoot } from "../workspace/paths.js";
|
|
35
35
|
import { type SandboxError } from "./sandbox-error.js";
|
|
36
36
|
import type { CreateSandboxMeta, ManagedSandbox, SandboxIdentity, SandboxLiveness, SandboxRef, SandboxTransport } from "./types.js";
|
|
@@ -62,7 +62,7 @@ export interface DockerClientConfig {
|
|
|
62
62
|
* vanished or went incomplete by sandbox-create time) is observable instead of a
|
|
63
63
|
* silent libs-mount drop. Matches the `reaper`/`watchdog` logger seam.
|
|
64
64
|
*/
|
|
65
|
-
logger?:
|
|
65
|
+
logger?: Logger;
|
|
66
66
|
/** Hook called after the registry row is written. */
|
|
67
67
|
registerSandbox: (meta: CreateSandboxMeta, ref: SandboxRef) => Promise<void>;
|
|
68
68
|
}
|
|
@@ -32,6 +32,7 @@ import { existsSync, statSync } from "node:fs";
|
|
|
32
32
|
import { join } from "node:path";
|
|
33
33
|
import Docker from "dockerode";
|
|
34
34
|
import { ResultAsync, err, ok } from "neverthrow";
|
|
35
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
35
36
|
import { stepWritePrefix } from "../workspace/paths.js";
|
|
36
37
|
import { trySandbox } from "./sandbox-error.js";
|
|
37
38
|
import { buildMountPlan } from "./mount-plan.js";
|
|
@@ -158,7 +159,9 @@ export function createDockerSandboxOps(config) {
|
|
|
158
159
|
// since an otherwise-silent libs-mount drop is invisible to operators.
|
|
159
160
|
const libsMounted = !!config.libStorePath && libStoreUsable(config.libStorePath);
|
|
160
161
|
if (config.libStorePath && !libsMounted) {
|
|
161
|
-
config.logger
|
|
162
|
+
(config.logger ?? createNoopLogger())
|
|
163
|
+
.named("docker-client")
|
|
164
|
+
.warn("lib store configured but `current` is missing or incomplete at sandbox creation — mounting no library store (sandbox degrades to available:false)", { libStorePath: config.libStorePath, sandboxId });
|
|
162
165
|
}
|
|
163
166
|
const plan = buildMountPlan(meta, {
|
|
164
167
|
libs: libsMounted,
|
|
@@ -16,6 +16,7 @@ import { ResultAsync } from "neverthrow";
|
|
|
16
16
|
import type { ResolveWorkspaceRoot } from "../workspace/paths.js";
|
|
17
17
|
import { type SandboxError } from "./sandbox-error.js";
|
|
18
18
|
import type { CreateSandboxMeta, ManagedSandbox, SandboxIdentity, SandboxLiveness, SandboxRef, SandboxTransport } from "./types.js";
|
|
19
|
+
import type { Logger } from "../lib/logger.js";
|
|
19
20
|
/**
|
|
20
21
|
* Coerce an arbitrary identifier into a valid K8s label value:
|
|
21
22
|
* ≤63 chars, `[A-Za-z0-9._-]` only, starting and ending alphanumeric.
|
|
@@ -27,6 +28,8 @@ import type { CreateSandboxMeta, ManagedSandbox, SandboxIdentity, SandboxLivenes
|
|
|
27
28
|
*/
|
|
28
29
|
export declare function sanitizeLabelValue(value: string): string;
|
|
29
30
|
export interface K8sClientConfig {
|
|
31
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
32
|
+
readonly logger?: Logger;
|
|
30
33
|
image: string;
|
|
31
34
|
cortexBaseUrl: string;
|
|
32
35
|
/**
|
|
@@ -16,6 +16,7 @@ import { BatchV1Api, CoreV1Api, KubeConfig } from "@kubernetes/client-node";
|
|
|
16
16
|
import { ResultAsync, err, ok } from "neverthrow";
|
|
17
17
|
import { trySandbox } from "./sandbox-error.js";
|
|
18
18
|
import { buildMountPlan, buildSessionSubPaths } from "./mount-plan.js";
|
|
19
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
19
20
|
/** Read the originating HTTP status off any `SandboxError` variant that carries one. */
|
|
20
21
|
function statusOf(e) {
|
|
21
22
|
return "status" in e ? e.status : undefined;
|
|
@@ -410,12 +411,12 @@ export function createK8sSandboxOps(config) {
|
|
|
410
411
|
cause,
|
|
411
412
|
}));
|
|
412
413
|
if (registered.isErr()) {
|
|
413
|
-
await cleanupFailedJob(batchApi, config.namespace, sandboxId);
|
|
414
|
+
await cleanupFailedJob(batchApi, config.namespace, sandboxId, config.logger ?? createNoopLogger());
|
|
414
415
|
return err(registered.error);
|
|
415
416
|
}
|
|
416
417
|
return ok(ref);
|
|
417
418
|
}
|
|
418
|
-
await cleanupFailedJob(batchApi, config.namespace, sandboxId);
|
|
419
|
+
await cleanupFailedJob(batchApi, config.namespace, sandboxId, config.logger ?? createNoopLogger());
|
|
419
420
|
return err(ready.error);
|
|
420
421
|
})());
|
|
421
422
|
},
|
|
@@ -480,9 +481,9 @@ export function createK8sSandboxOps(config) {
|
|
|
480
481
|
* matters; a delete error here is logged and swallowed so it never masks the
|
|
481
482
|
* create error returned to the caller.
|
|
482
483
|
*/
|
|
483
|
-
async function cleanupFailedJob(batchApi, namespace, sandboxId) {
|
|
484
|
+
async function cleanupFailedJob(batchApi, namespace, sandboxId, logger) {
|
|
484
485
|
const deleted = await deleteJobIgnoreMissing(batchApi, namespace, sandboxId);
|
|
485
486
|
if (deleted.isErr()) {
|
|
486
|
-
|
|
487
|
+
logger.named("k8s-client").error("failed to delete Job after startup failure", { sandboxId, err: deleted.error });
|
|
487
488
|
}
|
|
488
489
|
}
|
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
* injectable so tests can drive the sweep against a stub.
|
|
18
18
|
*/
|
|
19
19
|
import type { Pool } from "pg";
|
|
20
|
-
import type
|
|
20
|
+
import type { Logger } from "../lib/logger.js";
|
|
21
21
|
export interface SweepDeps {
|
|
22
22
|
/**
|
|
23
23
|
* Execute the bounded DELETE. Returns the row count cleared. Injected
|
|
24
24
|
* for tests; production wires the app pool.
|
|
25
25
|
*/
|
|
26
26
|
deleteStale: (limit: number) => Promise<number>;
|
|
27
|
-
logger?:
|
|
27
|
+
logger?: Logger;
|
|
28
28
|
}
|
|
29
29
|
export declare function sweepStaleNotifications(deps: SweepDeps): Promise<number>;
|
|
30
30
|
/**
|
|
@@ -35,7 +35,7 @@ export declare function sweepStaleNotifications(deps: SweepDeps): Promise<number
|
|
|
35
35
|
export declare function makeDefaultDeleteStale(pool: Pool): (limit: number) => Promise<number>;
|
|
36
36
|
export interface RegisterNotificationSweepDeps {
|
|
37
37
|
pool: Pool;
|
|
38
|
-
logger?:
|
|
38
|
+
logger?: Logger;
|
|
39
39
|
/** Override the DELETE driver — production defaults to the app pool. */
|
|
40
40
|
deleteStale?: (limit: number) => Promise<number>;
|
|
41
41
|
}
|
|
@@ -17,16 +17,18 @@
|
|
|
17
17
|
* injectable so tests can drive the sweep against a stub.
|
|
18
18
|
*/
|
|
19
19
|
import { DBOS } from "@dbos-inc/dbos-sdk";
|
|
20
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
20
21
|
const SWEEP_CRON = "0 */5 * * * *";
|
|
21
22
|
const DELETE_BATCH_LIMIT = 10_000;
|
|
22
23
|
export async function sweepStaleNotifications(deps) {
|
|
24
|
+
const logger = (deps.logger ?? createNoopLogger()).named("notification-sweep");
|
|
23
25
|
try {
|
|
24
26
|
const cleared = await deps.deleteStale(DELETE_BATCH_LIMIT);
|
|
25
|
-
|
|
27
|
+
logger.info("cleared stale notifications", { rowsCleared: cleared });
|
|
26
28
|
return cleared;
|
|
27
29
|
}
|
|
28
30
|
catch (err) {
|
|
29
|
-
|
|
31
|
+
logger.error("sweep failed", logger.errorFields(err));
|
|
30
32
|
throw err;
|
|
31
33
|
}
|
|
32
34
|
}
|
package/dist/sandbox/reaper.d.ts
CHANGED
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
* hoisted for unit testing without a DBOS runtime; `registerSandboxReaper` is
|
|
19
19
|
* the thin production wiring.
|
|
20
20
|
*/
|
|
21
|
-
import type pino from "pino";
|
|
22
21
|
import type { Pool } from "pg";
|
|
22
|
+
import type { Logger } from "../lib/logger.js";
|
|
23
23
|
import type { SandboxClient } from "./client.js";
|
|
24
24
|
import type { ManagedSandbox } from "./types.js";
|
|
25
25
|
export type ReapDecision = "leave" | "reap";
|
|
@@ -46,7 +46,7 @@ export interface ReaperDeps {
|
|
|
46
46
|
graceMs?: number;
|
|
47
47
|
/** Wall-clock ms; injected for tests. */
|
|
48
48
|
nowMs?: () => number;
|
|
49
|
-
logger?:
|
|
49
|
+
logger?: Logger;
|
|
50
50
|
}
|
|
51
51
|
export interface ReapSummary {
|
|
52
52
|
managedCount: number;
|
|
@@ -64,7 +64,7 @@ export interface RegisterReaperDeps {
|
|
|
64
64
|
pool: Pool;
|
|
65
65
|
sandboxClient: Pick<SandboxClient, "listManagedSandboxes" | "teardownById">;
|
|
66
66
|
graceMs?: number;
|
|
67
|
-
logger?:
|
|
67
|
+
logger?: Logger;
|
|
68
68
|
}
|
|
69
69
|
/** Production registration: a single (unsharded) `@DBOS.scheduled` workflow. */
|
|
70
70
|
export declare function registerSandboxReaper(deps: RegisterReaperDeps): void;
|
package/dist/sandbox/reaper.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* the thin production wiring.
|
|
20
20
|
*/
|
|
21
21
|
import { DBOS } from "@dbos-inc/dbos-sdk";
|
|
22
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
22
23
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
23
24
|
import { reconcileReapedSandbox } from "../state/active-sandboxes.js";
|
|
24
25
|
const REAPER_CRON = "0 */5 * * * *";
|
|
@@ -65,6 +66,7 @@ export function terminalStepStatus(workflowStatus) {
|
|
|
65
66
|
* failure on one machine is logged and skipped — the next sweep retries.
|
|
66
67
|
*/
|
|
67
68
|
export async function reapOnce(deps) {
|
|
69
|
+
const logger = (deps.logger ?? createNoopLogger()).named("sandbox-reaper");
|
|
68
70
|
const nowMs = (deps.nowMs ?? (() => Date.now()))();
|
|
69
71
|
const graceMs = deps.graceMs ?? DEFAULT_GRACE_MS;
|
|
70
72
|
const managed = await deps.sandboxClient.listManagedSandboxes();
|
|
@@ -85,10 +87,10 @@ export async function reapOnce(deps) {
|
|
|
85
87
|
rowsReconciled++;
|
|
86
88
|
}
|
|
87
89
|
catch (err) {
|
|
88
|
-
|
|
90
|
+
logger.warn("reap failed — skipping this round", {
|
|
89
91
|
sandboxId: sb.sandboxId,
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
+
...logger.errorFields(err),
|
|
93
|
+
});
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
const summary = {
|
|
@@ -97,7 +99,8 @@ export async function reapOnce(deps) {
|
|
|
97
99
|
rowsReconciled,
|
|
98
100
|
leftCount,
|
|
99
101
|
};
|
|
100
|
-
|
|
102
|
+
// Spread: an interface has no implicit index signature, so it needs widening to `LogFields`.
|
|
103
|
+
logger.info("sweep completed", { ...summary });
|
|
101
104
|
return summary;
|
|
102
105
|
}
|
|
103
106
|
/** Production registration: a single (unsharded) `@DBOS.scheduled` workflow. */
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
* without spinning a DBOS runtime; `registerWatchdog` is the thin
|
|
19
19
|
* production wiring.
|
|
20
20
|
*/
|
|
21
|
-
import type pino from "pino";
|
|
22
21
|
import type { ResultAsync } from "neverthrow";
|
|
22
|
+
import type { Logger } from "../lib/logger.js";
|
|
23
23
|
import type { DbError } from "../lib/db-result.js";
|
|
24
24
|
import type { ActiveSandboxRow } from "../state/index.js";
|
|
25
25
|
import type { SandboxClient } from "./client.js";
|
|
@@ -39,7 +39,7 @@ export interface CheckShardDeps {
|
|
|
39
39
|
* this is `DBOS.send`; injected here for testability.
|
|
40
40
|
*/
|
|
41
41
|
sendSynthetic: (workflowId: string, execId: string, failure: ExecResult, reason: string) => Promise<void>;
|
|
42
|
-
logger?:
|
|
42
|
+
logger?: Logger;
|
|
43
43
|
}
|
|
44
44
|
export interface CheckShardSummary {
|
|
45
45
|
activeCount: number;
|
|
@@ -61,7 +61,7 @@ export interface WatchdogDeps {
|
|
|
61
61
|
queryActiveSandboxes: () => ResultAsync<ActiveSandboxRow[], DbError>;
|
|
62
62
|
sandboxClient: SandboxClient;
|
|
63
63
|
shardCount?: number;
|
|
64
|
-
logger?:
|
|
64
|
+
logger?: Logger;
|
|
65
65
|
}
|
|
66
66
|
/**
|
|
67
67
|
* Production registration. Builds the parent + child registered workflows
|
package/dist/sandbox/watchdog.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { createHash } from "node:crypto";
|
|
22
22
|
import { DBOS } from "@dbos-inc/dbos-sdk";
|
|
23
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
23
24
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
24
25
|
import { workflowIdFromExec } from "./exec-id.js";
|
|
25
26
|
import { syntheticFailureReason, syntheticFailureResult } from "./liveness.js";
|
|
@@ -48,6 +49,7 @@ const ACTIVE_WORKFLOW_STATUSES = new Set(["PENDING", "ENQUEUED"]);
|
|
|
48
49
|
* notification.
|
|
49
50
|
*/
|
|
50
51
|
export async function checkShard(rows, deps) {
|
|
52
|
+
const logger = (deps.logger ?? createNoopLogger()).named("sandbox-watchdog");
|
|
51
53
|
let deadCount = 0;
|
|
52
54
|
let syntheticSends = 0;
|
|
53
55
|
let liveWorkflowsSkipped = 0;
|
|
@@ -64,7 +66,10 @@ export async function checkShard(rows, deps) {
|
|
|
64
66
|
liveness = await deps.isAlive(ref);
|
|
65
67
|
}
|
|
66
68
|
catch (err) {
|
|
67
|
-
|
|
69
|
+
logger.warn("isAlive threw — skipping this round", {
|
|
70
|
+
sandboxId: row.sandboxRef.sandboxId,
|
|
71
|
+
...logger.errorFields(err),
|
|
72
|
+
});
|
|
68
73
|
continue;
|
|
69
74
|
}
|
|
70
75
|
if (liveness.alive)
|
|
@@ -92,7 +97,8 @@ export async function checkShard(rows, deps) {
|
|
|
92
97
|
syntheticSends,
|
|
93
98
|
liveWorkflowsSkipped,
|
|
94
99
|
};
|
|
95
|
-
|
|
100
|
+
// Spread: an interface has no implicit index signature, so it needs widening to `LogFields`.
|
|
101
|
+
logger.info("shard check completed", { ...summary });
|
|
96
102
|
return summary;
|
|
97
103
|
}
|
|
98
104
|
/** Encodes the synthetic-failure marker on the per-exec topic envelope. */
|
package/dist/state/init.d.ts
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* simultaneous startups don't race on DDL.
|
|
8
8
|
*/
|
|
9
9
|
import type { Pool } from "pg";
|
|
10
|
+
import type { Logger } from "../lib/logger.js";
|
|
10
11
|
/**
|
|
11
12
|
* Initialize Cortex state tables. Safe to call on every startup and across
|
|
12
13
|
* concurrent replicas (advisory lock serializes the init flow).
|
|
13
14
|
*/
|
|
14
|
-
export declare function initCortexState(pool: Pool): Promise<void>;
|
|
15
|
+
export declare function initCortexState(pool: Pool, injected?: Logger): Promise<void>;
|
package/dist/state/init.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* simultaneous startups don't race on DDL.
|
|
8
8
|
*/
|
|
9
9
|
import { backfillAiSdkMessageEnvelopes } from "../memory/message-backfill.js";
|
|
10
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
10
11
|
const DDL = `
|
|
11
12
|
-- Analysis-level state (status, context, data-profile tracking, billing identity).
|
|
12
13
|
--
|
|
@@ -245,7 +246,8 @@ CREATE TABLE IF NOT EXISTS cortex_working_memory (
|
|
|
245
246
|
* Initialize Cortex state tables. Safe to call on every startup and across
|
|
246
247
|
* concurrent replicas (advisory lock serializes the init flow).
|
|
247
248
|
*/
|
|
248
|
-
export async function initCortexState(pool) {
|
|
249
|
+
export async function initCortexState(pool, injected) {
|
|
250
|
+
const logger = (injected ?? createNoopLogger()).named("cortex-state");
|
|
249
251
|
const client = await pool.connect();
|
|
250
252
|
try {
|
|
251
253
|
// Serialize concurrent replica startups — cheap, no extra tooling.
|
|
@@ -381,11 +383,8 @@ export async function initCortexState(pool) {
|
|
|
381
383
|
WITH (m = 16, ef_construction = 64)`);
|
|
382
384
|
}
|
|
383
385
|
catch (err) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
`(pgvector < 0.5.0 or operator class unavailable): ${msg}. ` +
|
|
387
|
-
`Cosine similarity queries will fall back to sequential scan. ` +
|
|
388
|
-
`Upgrade pgvector and re-run init to get the ANN index.`);
|
|
386
|
+
logger.warn("could not create HNSW index on cortex_regulatory_chunks.embedding (pgvector < 0.5.0 or operator class unavailable) — " +
|
|
387
|
+
"cosine similarity queries fall back to sequential scan; upgrade pgvector and re-run init to get the ANN index", logger.errorFields(err));
|
|
389
388
|
}
|
|
390
389
|
// No boot-time orphan sweep on cortex_runs / cortex_step_executions.
|
|
391
390
|
// Every running row is backed by a DBOS workflow (executeAnalysis,
|
|
@@ -21,6 +21,7 @@ import { type AuthContext, type RunSession } from "../auth/types.js";
|
|
|
21
21
|
import type { RunAuthorization, RunAuthorizer } from "../execution/run-authorizer.js";
|
|
22
22
|
import type { StagedInput } from "../execution/staged-input.js";
|
|
23
23
|
import type { BioToolKeys } from "../tools/bio/keys.js";
|
|
24
|
+
import type { Logger } from "../lib/logger.js";
|
|
24
25
|
import type { ChatProvider, EmbeddingProvider } from "../providers/types.js";
|
|
25
26
|
import type { SandboxClient } from "../sandbox/client.js";
|
|
26
27
|
import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
|
|
@@ -29,6 +30,8 @@ import { type ProfilerOutput } from "../schemas/data-profile-schemas.js";
|
|
|
29
30
|
import { type DataProfileInputFile, type DataProfileResult } from "../state/index.js";
|
|
30
31
|
/** The body's construction-time deps — closed over at registration. */
|
|
31
32
|
export interface DataProfileDeps {
|
|
33
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
34
|
+
readonly logger?: Logger;
|
|
32
35
|
readonly provider: ChatProvider;
|
|
33
36
|
readonly pool: Pool;
|
|
34
37
|
readonly sandboxClient: SandboxClient;
|
|
@@ -130,6 +133,8 @@ export type DataProfileTriggerResult = "started" | "restarted" | "already_runnin
|
|
|
130
133
|
* (`registerDataProfileWorkflow`); the route never holds them.
|
|
131
134
|
*/
|
|
132
135
|
export interface DataProfileTriggerDeps {
|
|
136
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
137
|
+
readonly logger?: Logger;
|
|
133
138
|
readonly pool: Pool;
|
|
134
139
|
readonly runAuthorizer: RunAuthorizer;
|
|
135
140
|
readonly workflow: (input: DataProfileWorkflowInput) => Promise<void>;
|
|
@@ -23,6 +23,7 @@ import { forSubAgent } from "../auth/types.js";
|
|
|
23
23
|
import { createDataProfilerAgent } from "../agents/sandbox/data-profiler.js";
|
|
24
24
|
import { runToTerminal } from "../loop/run-to-terminal.js";
|
|
25
25
|
import { durableStep } from "../loop/run-step.js";
|
|
26
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
26
27
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
27
28
|
import { defineTool } from "../tools/define-tool.js";
|
|
28
29
|
import { renderWorkspace } from "../prompts/briefing.js";
|
|
@@ -128,6 +129,7 @@ export function registerDataProfileWorkflow(deps) {
|
|
|
128
129
|
* 'failed' on error, and revokes the run authorization on every terminal path.
|
|
129
130
|
*/
|
|
130
131
|
export async function runDataProfileBody(input, deps) {
|
|
132
|
+
const logger = (deps.logger ?? createNoopLogger()).named("data-profile").with({ analysisId: input.analysisId });
|
|
131
133
|
// Ownership defaults to true for inputs persisted before the field existed
|
|
132
134
|
// (a #247 workflow recovered across this deploy): those were always
|
|
133
135
|
// Cortex-owned and must be revoked here.
|
|
@@ -138,9 +140,9 @@ export async function runDataProfileBody(input, deps) {
|
|
|
138
140
|
// embedder populated the tree and handed us this manifest in the workflow
|
|
139
141
|
// input. The body never downloads.
|
|
140
142
|
if (stagedInputs.length === 0) {
|
|
141
|
-
|
|
143
|
+
logger.warn("no input files staged");
|
|
142
144
|
if (!unwrapOrThrow(await completeDataProfile(deps.pool, analysisId)))
|
|
143
|
-
logTerminalNoop(analysisId, "completion");
|
|
145
|
+
logTerminalNoop(logger, analysisId, "completion");
|
|
144
146
|
await deps.runAuthorizer.revoke(authorization, "data-profile-completed");
|
|
145
147
|
return;
|
|
146
148
|
}
|
|
@@ -190,7 +192,7 @@ export async function runDataProfileBody(input, deps) {
|
|
|
190
192
|
``,
|
|
191
193
|
`All file paths in your response must be relative to the analysis root and must EXACTLY match the paths listed above.`,
|
|
192
194
|
].join("\n");
|
|
193
|
-
|
|
195
|
+
logger.info("starting sandbox", { executionId });
|
|
194
196
|
const sandbox = await deps.sandboxClient.createSandbox({
|
|
195
197
|
runId: DATA_PROFILE_RUN_LITERAL,
|
|
196
198
|
stepId: DATA_PROFILE_STEP_LITERAL,
|
|
@@ -310,13 +312,16 @@ export async function runDataProfileBody(input, deps) {
|
|
|
310
312
|
indexed++;
|
|
311
313
|
}
|
|
312
314
|
if (fallbackCount > 0) {
|
|
313
|
-
|
|
315
|
+
logger.warn("input file(s) used a deterministic fallback description — profiler omitted them", {
|
|
316
|
+
fallbackCount,
|
|
317
|
+
stagedCount: stagedInputs.length,
|
|
318
|
+
});
|
|
314
319
|
}
|
|
315
|
-
|
|
320
|
+
logger.info("indexed file(s)", { indexed });
|
|
316
321
|
// 5. Complete — store the FULL profiler finding plus the input snapshot for
|
|
317
322
|
// staleness detection. The scratch tree is gone; this row is all that survives.
|
|
318
323
|
if (!unwrapOrThrow(await completeDataProfile(deps.pool, analysisId, buildDataProfileResult(profilerData, stagedInputs, new Date().toISOString())))) {
|
|
319
|
-
logTerminalNoop(analysisId, "completion");
|
|
324
|
+
logTerminalNoop(logger, analysisId, "completion");
|
|
320
325
|
}
|
|
321
326
|
await deps.runAuthorizer.revoke(authorization, "data-profile-completed");
|
|
322
327
|
}
|
|
@@ -325,14 +330,14 @@ export async function runDataProfileBody(input, deps) {
|
|
|
325
330
|
await deps.sandboxClient.teardown(sandbox);
|
|
326
331
|
}
|
|
327
332
|
catch (teardownErr) {
|
|
328
|
-
|
|
333
|
+
logger.warn("teardown failed (non-fatal)", { executionId, ...logger.errorFields(teardownErr) });
|
|
329
334
|
}
|
|
330
335
|
}
|
|
331
336
|
}
|
|
332
337
|
catch (err) {
|
|
333
|
-
|
|
338
|
+
logger.error("profile failed", logger.errorFields(err));
|
|
334
339
|
if (!unwrapOrThrow(await failDataProfile(deps.pool, analysisId, profileFailureReason(err))))
|
|
335
|
-
logTerminalNoop(analysisId, "failure");
|
|
340
|
+
logTerminalNoop(logger, analysisId, "failure");
|
|
336
341
|
await deps.runAuthorizer.revoke(authorization, "data-profile-failed");
|
|
337
342
|
}
|
|
338
343
|
}
|
|
@@ -342,8 +347,8 @@ export async function runDataProfileBody(input, deps) {
|
|
|
342
347
|
* there is no `running` row to stamp. Not an error — the ledger correctly moved
|
|
343
348
|
* on; the workflow still revokes its own authorization on the same terminal path.
|
|
344
349
|
*/
|
|
345
|
-
function logTerminalNoop(analysisId, write) {
|
|
346
|
-
|
|
350
|
+
function logTerminalNoop(logger, analysisId, write) {
|
|
351
|
+
logger.warn("terminal write skipped: ledger row not running (cleared or expired concurrently)", { analysisId, write });
|
|
347
352
|
}
|
|
348
353
|
/**
|
|
349
354
|
* Resolve the `RunSession` the workflow runs under. Whether the authorization
|
|
@@ -409,6 +414,7 @@ async function startDataProfileWorkflow(deps, params) {
|
|
|
409
414
|
* Returns what happened, so the caller can surface it (e.g. in the seed response).
|
|
410
415
|
*/
|
|
411
416
|
export async function triggerDataProfile(deps, params) {
|
|
417
|
+
const logger = (deps.logger ?? createNoopLogger()).named("data-profile").with({ analysisId: params.analysisId });
|
|
412
418
|
const { analysisId } = params;
|
|
413
419
|
try {
|
|
414
420
|
// ADVISORY pre-check, not the enforcement — the claim CAS carries the same seed
|
|
@@ -420,7 +426,7 @@ export async function triggerDataProfile(deps, params) {
|
|
|
420
426
|
// a NULL-seed row (`loadDataProfileStatus` would hide the seed of a cleared row).
|
|
421
427
|
const seeded = unwrapOrThrow(await loadSeedInputFileIds(deps.pool, analysisId));
|
|
422
428
|
if (seeded === null || seeded.length === 0) {
|
|
423
|
-
|
|
429
|
+
logger.error("trigger rejected: no seeded input set (missing analysis row or caller skipped seeding)");
|
|
424
430
|
return "failed";
|
|
425
431
|
}
|
|
426
432
|
// The trigger validates the ledger seed above; it must also validate the manifest
|
|
@@ -429,7 +435,7 @@ export async function triggerDataProfile(deps, params) {
|
|
|
429
435
|
// NULL result while the seed still names files, an incoherence a staleness policy
|
|
430
436
|
// then loops on re-triggering. Refuse the divergence before any claim.
|
|
431
437
|
if (params.stagedInputs.length === 0) {
|
|
432
|
-
|
|
438
|
+
logger.error("trigger rejected: empty manifest dispatched against a non-empty seed", { seededCount: seeded.length });
|
|
433
439
|
return "failed";
|
|
434
440
|
}
|
|
435
441
|
const started = unwrapOrThrow(await tryStartDataProfile(deps.pool, analysisId));
|
|
@@ -448,7 +454,7 @@ export async function triggerDataProfile(deps, params) {
|
|
|
448
454
|
return "failed";
|
|
449
455
|
}
|
|
450
456
|
catch (err) {
|
|
451
|
-
|
|
457
|
+
logger.error("trigger error", logger.errorFields(err));
|
|
452
458
|
return "failed";
|
|
453
459
|
}
|
|
454
460
|
}
|
|
@@ -462,17 +468,18 @@ export async function triggerDataProfile(deps, params) {
|
|
|
462
468
|
* there is no further channel to report it on.
|
|
463
469
|
*/
|
|
464
470
|
async function compensateStartFailure(deps, analysisId, phase, err) {
|
|
465
|
-
|
|
471
|
+
const logger = (deps.logger ?? createNoopLogger()).named("data-profile").with({ analysisId });
|
|
472
|
+
logger.error("start failed", { phase, ...logger.errorFields(err) });
|
|
466
473
|
const failed = await failDataProfile(deps.pool, analysisId, profileFailureReason(err));
|
|
467
474
|
if (failed.isErr()) {
|
|
468
|
-
|
|
475
|
+
logger.error("failed to mark failed after a start error", { phase, err: failed.error });
|
|
469
476
|
}
|
|
470
477
|
else if (!failed.value) {
|
|
471
478
|
// The row this compensation was written to fail is no longer `running` — a
|
|
472
479
|
// concurrent clear/expire already moved it on, so the running-CAS refused the
|
|
473
480
|
// stamp. Nothing wedged at `running`, which is the outcome compensation exists
|
|
474
481
|
// to guarantee; just record the no-op.
|
|
475
|
-
logTerminalNoop(analysisId, "compensation");
|
|
482
|
+
logTerminalNoop(logger, analysisId, "compensation");
|
|
476
483
|
}
|
|
477
484
|
}
|
|
478
485
|
/**
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* - searchType="gene": input HUGO symbols, returns drugs interacting with each.
|
|
9
9
|
* - searchType="drug": input drug names/concept IDs, returns genes each acts on.
|
|
10
10
|
*/
|
|
11
|
+
import type { Logger } from "../../lib/logger.js";
|
|
11
12
|
interface Interaction {
|
|
12
13
|
geneName: string;
|
|
13
14
|
drugName: string;
|
|
@@ -26,7 +27,16 @@ interface Interaction {
|
|
|
26
27
|
value: string;
|
|
27
28
|
}[];
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
+
/**
|
|
31
|
+
* A factory rather than a bare const (unlike its bio-leaf siblings) because it
|
|
32
|
+
* logs: DGIdb answers GraphQL partial errors alongside usable data, and an
|
|
33
|
+
* operator debugging odd interaction results needs to see them. `ToolContext`
|
|
34
|
+
* carries no deps by design, so a tool that logs is dep-bearing — the same
|
|
35
|
+
* reason `createSearchClinvarTool` takes its key that way.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createSearchDgidbTool(deps?: {
|
|
38
|
+
readonly logger?: Logger;
|
|
39
|
+
}): import("../define-tool.js").Tool<{
|
|
30
40
|
query: string | string[];
|
|
31
41
|
searchType: "drug" | "gene";
|
|
32
42
|
minSources: number;
|