@inflexa-ai/harness 0.36.4 → 0.38.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 +1 -2
- package/dist/auth/local-run-authorizer.js +5 -4
- package/dist/auth/types.d.ts +28 -9
- package/dist/auth/types.js +5 -3
- package/dist/billing/noop-run-charge.js +3 -2
- package/dist/billing/noop-usage-recorder.js +2 -1
- package/dist/billing/run-charge.d.ts +18 -5
- package/dist/billing/run-charge.js +5 -1
- package/dist/billing/usage-recorder.d.ts +11 -9
- package/dist/billing/usage-recorder.js +2 -3
- package/dist/contracts/chat-events.d.ts +4 -1
- package/dist/contracts/chat-parts.d.ts +1 -1
- package/dist/contracts/message.d.ts +15 -0
- package/dist/contracts/schemas/chat-parts.d.ts +4 -4
- package/dist/execution/artifact-registration.d.ts +6 -1
- package/dist/execution/artifact-registration.js +12 -4
- package/dist/execution/artifact-registry.d.ts +10 -3
- package/dist/execution/noop-artifact-registry.js +3 -4
- package/dist/execution/post-step-pipeline.d.ts +14 -3
- package/dist/execution/post-step-pipeline.js +21 -8
- package/dist/execution/run-authorizer.d.ts +5 -3
- package/dist/execution/run-canceler.js +4 -20
- package/dist/index.d.ts +13 -7
- package/dist/index.js +12 -6
- package/dist/lib/hooks.d.ts +49 -0
- package/dist/lib/hooks.js +39 -0
- package/dist/lib/result.d.ts +22 -11
- package/dist/lib/result.js +24 -14
- package/dist/loop/run-agent.d.ts +2 -2
- package/dist/loop/run-agent.js +5 -4
- package/dist/memory/conversation-display-replay.js +14 -1
- package/dist/memory/thread-history.d.ts +24 -0
- package/dist/memory/thread-history.js +19 -6
- package/dist/provenance/seam.d.ts +1 -1
- package/dist/providers/ai-sdk.d.ts +43 -5
- package/dist/providers/ai-sdk.js +55 -52
- package/dist/providers/anthropic.d.ts +2 -3
- package/dist/providers/anthropic.js +2 -2
- package/dist/providers/embedding.d.ts +13 -7
- package/dist/providers/embedding.js +26 -6
- package/dist/providers/errors.d.ts +67 -37
- package/dist/providers/errors.js +57 -58
- package/dist/providers/request-headers.d.ts +18 -0
- package/dist/providers/request-headers.js +22 -0
- package/dist/report-model/production-resolver.d.ts +5 -2
- package/dist/report-model/production-resolver.js +12 -2
- package/dist/runtime/assemble.d.ts +3 -2
- package/dist/runtime/dbos.d.ts +4 -3
- package/dist/runtime/dbos.js +18 -3
- package/dist/runtime/result-serialization.d.ts +29 -0
- package/dist/runtime/result-serialization.js +45 -0
- package/dist/sandbox/client.d.ts +11 -3
- package/dist/sandbox/create-sandbox.d.ts +5 -2
- package/dist/sandbox/create-sandbox.js +51 -25
- package/dist/sandbox/docker-client.d.ts +4 -3
- package/dist/sandbox/docker-client.js +23 -31
- package/dist/sandbox/k8s-client.d.ts +4 -13
- package/dist/sandbox/k8s-client.js +28 -61
- package/dist/sandbox/labels.d.ts +34 -0
- package/dist/sandbox/labels.js +47 -0
- package/dist/sandbox/mount-plan.d.ts +3 -1
- package/dist/sandbox/mount-plan.js +9 -0
- package/dist/sandbox/sandbox-error.d.ts +36 -13
- package/dist/sandbox/sandbox-error.js +32 -17
- package/dist/sandbox/types.d.ts +13 -9
- package/dist/schemas/validate-plan.js +13 -0
- package/dist/schemas/workflow-state.d.ts +4 -4
- package/dist/state/analyses.d.ts +4 -4
- package/dist/state/analyses.js +4 -4
- package/dist/state/init.js +11 -0
- package/dist/state/schema.d.ts +5 -5
- package/dist/tasks/data-profile.d.ts +5 -8
- package/dist/tasks/data-profile.js +88 -43
- package/dist/tasks/derive-table-exec.d.ts +10 -6
- package/dist/tasks/derive-table-exec.js +17 -12
- package/dist/tasks/extract-values.d.ts +16 -10
- package/dist/tasks/extract-values.js +34 -18
- package/dist/tools/execute-analysis.js +22 -15
- package/dist/tools/report-session/derive-table.d.ts +9 -1
- package/dist/tools/report-session/derive-table.js +25 -22
- package/dist/tools/research/inspect-run.d.ts +1 -1
- package/dist/workflows/execute-analysis.d.ts +25 -16
- package/dist/workflows/execute-analysis.js +223 -216
- package/dist/workflows/metrics.d.ts +4 -3
- package/dist/workflows/metrics.js +4 -3
- package/dist/workflows/sandbox-step.d.ts +20 -29
- package/dist/workflows/sandbox-step.js +71 -127
- package/dist/workflows/suspension.d.ts +32 -0
- package/dist/workflows/suspension.js +38 -0
- package/package.json +1 -1
- package/dist/billing/noop-resolver.d.ts +0 -6
- package/dist/billing/noop-resolver.js +0 -7
- package/dist/billing/resolver.d.ts +0 -64
- package/dist/billing/resolver.js +0 -24
- package/dist/loop/budget-exceeded.d.ts +0 -38
- package/dist/loop/budget-exceeded.js +0 -88
package/README.md
CHANGED
|
@@ -35,7 +35,6 @@ import {
|
|
|
35
35
|
createConfiguredAiSdkProvider, // ChatProvider — any OpenAI-compatible endpoint
|
|
36
36
|
createEmbeddingProvider,
|
|
37
37
|
createLocalRunAuthorizer, // RunAuthorizer seam (local default)
|
|
38
|
-
createNoopBillingResolver, // ResolveBilling seam
|
|
39
38
|
createNoopRunCharge, // RunCharge seam
|
|
40
39
|
createNoopUsageRecorder, // UsageRecorder seam
|
|
41
40
|
createNoopArtifactRegistry, // ArtifactRegistry seam
|
|
@@ -49,7 +48,7 @@ import {
|
|
|
49
48
|
} from "@inflexa-ai/harness";
|
|
50
49
|
```
|
|
51
50
|
|
|
52
|
-
`assembleCoreRuntime` is the single composition point: it registers the durable workflows and builds the conversation agent over them. The harness declares **
|
|
51
|
+
`assembleCoreRuntime` is the single composition point: it registers the durable workflows and builds the conversation agent over them. The harness declares **four capability seams** an embedder wires — `RunAuthorizer`, `ArtifactRegistry`, `RunCharge`, `UsageRecorder` — plus the **shared `RunLauncher`** (one host-neutral realization, `createDbosRunLauncher`). Local, dependency-free realizations of all four ship from the barrel; an embedder constructs them (or its own) and passes them in. The harness only ever sees the interface. Two optional hooks go into the component that uses their values: `resolveRequestHeaders` into each provider, and `resolveSandboxLabels` into the sandbox client config.
|
|
53
52
|
|
|
54
53
|
## Logging
|
|
55
54
|
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
* forwarded untouched — local code never
|
|
8
8
|
* inspects it (no `getAuth`).
|
|
9
9
|
*/
|
|
10
|
+
import { okAsync } from "neverthrow";
|
|
10
11
|
export function createLocalRunAuthorizer() {
|
|
11
12
|
return {
|
|
12
|
-
|
|
13
|
+
authorize({ auth, scope, provenance, frame }) {
|
|
13
14
|
const runSession = {
|
|
14
15
|
identity: { user: "local" },
|
|
15
16
|
scope,
|
|
@@ -17,9 +18,9 @@ export function createLocalRunAuthorizer() {
|
|
|
17
18
|
runFrame: frame,
|
|
18
19
|
auth,
|
|
19
20
|
};
|
|
20
|
-
return { runSession, ownsMandate: false }; // oss-core-managed-ok
|
|
21
|
+
return okAsync({ runSession, ownsMandate: false }); // oss-core-managed-ok
|
|
21
22
|
},
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
revoke: () => okAsync(undefined),
|
|
24
|
+
revokeByJti: () => okAsync(undefined),
|
|
24
25
|
};
|
|
25
26
|
}
|
package/dist/auth/types.d.ts
CHANGED
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
* - `RunSession` — durable, JSON-serializable. Carries a `RunFrame`; minted
|
|
17
17
|
* only at the run-authorization seam.
|
|
18
18
|
*
|
|
19
|
-
* Neither bundle carries
|
|
20
|
-
*
|
|
19
|
+
* Neither bundle carries request headers — a provider gets them at the call
|
|
20
|
+
* site from the optional request headers hook of the host (see
|
|
21
|
+
* `providers/request-headers.ts`).
|
|
21
22
|
*/
|
|
22
23
|
/** Who the request is. Always complete. */
|
|
23
24
|
export interface Identity {
|
|
@@ -89,7 +90,7 @@ export interface AuthContext {
|
|
|
89
90
|
* consumer is the provider, which reads every field, so the conduit type is
|
|
90
91
|
* "all fields, generalized to what's read" — `credential` widened to the
|
|
91
92
|
* opaque `Credential` (nothing in the harness branches on the concrete shape),
|
|
92
|
-
* `runFrame` optional (only
|
|
93
|
+
* `runFrame` optional (only a host hook reads it).
|
|
93
94
|
*
|
|
94
95
|
* Both bundles satisfy this: a `RequestSession` (no RunFrame) and a
|
|
95
96
|
* `RunSession` (with RunFrame) are each structurally assignable, so the
|
|
@@ -130,16 +131,33 @@ export interface RunSession {
|
|
|
130
131
|
readonly auth: AuthContext;
|
|
131
132
|
}
|
|
132
133
|
/**
|
|
133
|
-
* The
|
|
134
|
-
*
|
|
135
|
-
* `RequestSession` emits no run/step tags.
|
|
134
|
+
* The session of a sandbox spawn. A sandbox takes its analysis id, its run id,
|
|
135
|
+
* and its step id from this session.
|
|
136
136
|
*/
|
|
137
|
-
export interface
|
|
137
|
+
export interface SpawnSession extends RunSession {
|
|
138
|
+
readonly runFrame: RunFrame & {
|
|
139
|
+
readonly stepId: string;
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The minimal session view that a host hook reads. Both bundles satisfy it;
|
|
144
|
+
* `runFrame` is present only on a `RunSession`, so a hook that gets a
|
|
145
|
+
* `RequestSession` sees no run or step.
|
|
146
|
+
*/
|
|
147
|
+
export interface HookSessionView {
|
|
138
148
|
readonly identity: Identity;
|
|
139
149
|
readonly scope: Scope;
|
|
140
150
|
readonly provenance: Provenance;
|
|
141
151
|
readonly runFrame?: RunFrame;
|
|
142
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* The session that the request headers hook gets: the hook view plus the
|
|
155
|
+
* opaque `auth`, which a host realization downcasts to reach its credential.
|
|
156
|
+
* `AgentSession` structurally satisfies it.
|
|
157
|
+
*/
|
|
158
|
+
export interface ResolvableSession extends HookSessionView {
|
|
159
|
+
readonly auth: AuthContext;
|
|
160
|
+
}
|
|
143
161
|
/**
|
|
144
162
|
* Derive a child session for a sub-agent call — provenance only. Sets the
|
|
145
163
|
* child `agentId` and appends it to `callPath`, leaving identity, scope, and
|
|
@@ -151,7 +169,8 @@ export declare function forSubAgent<S extends {
|
|
|
151
169
|
/**
|
|
152
170
|
* Derive a child `RunSession` for a sandbox step — pure value derivation.
|
|
153
171
|
* Sets `runFrame.stepId`; identity, scope, auth, and provenance are
|
|
154
|
-
* unchanged. Used by parents when computing child workflow input
|
|
172
|
+
* unchanged. Used by parents when computing child workflow input, and by a
|
|
173
|
+
* spawn path that names the step of its sandbox.
|
|
155
174
|
*/
|
|
156
|
-
export declare function forStep(parent: RunSession, stepId: string):
|
|
175
|
+
export declare function forStep(parent: RunSession, stepId: string): SpawnSession;
|
|
157
176
|
export {};
|
package/dist/auth/types.js
CHANGED
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
* - `RunSession` — durable, JSON-serializable. Carries a `RunFrame`; minted
|
|
17
17
|
* only at the run-authorization seam.
|
|
18
18
|
*
|
|
19
|
-
* Neither bundle carries
|
|
20
|
-
*
|
|
19
|
+
* Neither bundle carries request headers — a provider gets them at the call
|
|
20
|
+
* site from the optional request headers hook of the host (see
|
|
21
|
+
* `providers/request-headers.ts`).
|
|
21
22
|
*/
|
|
22
23
|
/** Derive the `{ resourceType, resourceId }` a scope resolves through: `{ "analysis", analysisId }`. */
|
|
23
24
|
export function scopeResource(scope) {
|
|
@@ -44,7 +45,8 @@ export function forSubAgent(session, agentId) {
|
|
|
44
45
|
/**
|
|
45
46
|
* Derive a child `RunSession` for a sandbox step — pure value derivation.
|
|
46
47
|
* Sets `runFrame.stepId`; identity, scope, auth, and provenance are
|
|
47
|
-
* unchanged. Used by parents when computing child workflow input
|
|
48
|
+
* unchanged. Used by parents when computing child workflow input, and by a
|
|
49
|
+
* spawn path that names the step of its sandbox.
|
|
48
50
|
*/
|
|
49
51
|
export function forStep(parent, stepId) {
|
|
50
52
|
return {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { okAsync } from "neverthrow";
|
|
1
2
|
/**
|
|
2
3
|
* Local/OSS run-charge seam: open and close are both no-ops. No external
|
|
3
4
|
* billing ledger in the cloud-free build.
|
|
4
5
|
*/
|
|
5
6
|
export function createNoopRunCharge() {
|
|
6
7
|
return {
|
|
7
|
-
open:
|
|
8
|
-
close:
|
|
8
|
+
open: () => okAsync(undefined),
|
|
9
|
+
close: () => okAsync(undefined),
|
|
9
10
|
};
|
|
10
11
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { okAsync } from "neverthrow";
|
|
1
2
|
/**
|
|
2
3
|
* Local/OSS usage recorder: drops every record. No ledger, no I/O — the
|
|
3
4
|
* default an embedder that wires nothing gets. Runs behave exactly as they did
|
|
@@ -5,6 +6,6 @@
|
|
|
5
6
|
*/
|
|
6
7
|
export function createNoopUsageRecorder() {
|
|
7
8
|
return {
|
|
8
|
-
record: () =>
|
|
9
|
+
record: () => okAsync(undefined),
|
|
9
10
|
};
|
|
10
11
|
}
|
|
@@ -2,23 +2,36 @@
|
|
|
2
2
|
* `RunCharge` — the run-level billing-bracket seam.
|
|
3
3
|
*
|
|
4
4
|
* `executeAnalysis` opens a running charge at init and closes it on the
|
|
5
|
-
* terminal path with
|
|
5
|
+
* terminal path with the outcome of the run. A managed embedder wires an
|
|
6
6
|
* external running-charge bracket; OSS wires a no-op. The harness only ever sees this
|
|
7
7
|
* interface — the body brackets the run, the embedder owns the ledger.
|
|
8
|
+
*
|
|
9
|
+
* `open` is a gate: an `err` ends the run before a step starts, or suspends it
|
|
10
|
+
* when the host asks. `close` is a notice: an `err` is logged, and the terminal
|
|
11
|
+
* status of the run does not change (see `lib/hooks.ts`).
|
|
8
12
|
*/
|
|
13
|
+
import type { ResultAsync } from "neverthrow";
|
|
9
14
|
import type { AgentSession } from "../auth/types.js";
|
|
15
|
+
import type { GateFailure, NoticeFailure } from "../lib/hooks.js";
|
|
16
|
+
/** A suspension carries the reason of the host, which the harness does not read. */
|
|
17
|
+
export type RunChargeOutcome = {
|
|
18
|
+
readonly kind: "ok" | "error" | "canceled";
|
|
19
|
+
} | {
|
|
20
|
+
readonly kind: "suspended";
|
|
21
|
+
readonly reason: string;
|
|
22
|
+
};
|
|
10
23
|
export interface RunCharge {
|
|
11
24
|
/** Open the run's running charge at init. */
|
|
12
25
|
open(args: {
|
|
13
26
|
analysisId: string;
|
|
14
27
|
runId: string;
|
|
15
28
|
session: AgentSession;
|
|
16
|
-
}):
|
|
17
|
-
/** Close the run's running charge with
|
|
29
|
+
}): ResultAsync<void, GateFailure>;
|
|
30
|
+
/** Close the run's running charge with the outcome of the run. */
|
|
18
31
|
close(args: {
|
|
19
32
|
analysisId: string;
|
|
20
33
|
runId: string;
|
|
21
|
-
|
|
34
|
+
outcome: RunChargeOutcome;
|
|
22
35
|
session: AgentSession;
|
|
23
|
-
}):
|
|
36
|
+
}): ResultAsync<void, NoticeFailure>;
|
|
24
37
|
}
|
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
* `RunCharge` — the run-level billing-bracket seam.
|
|
3
3
|
*
|
|
4
4
|
* `executeAnalysis` opens a running charge at init and closes it on the
|
|
5
|
-
* terminal path with
|
|
5
|
+
* terminal path with the outcome of the run. A managed embedder wires an
|
|
6
6
|
* external running-charge bracket; OSS wires a no-op. The harness only ever sees this
|
|
7
7
|
* interface — the body brackets the run, the embedder owns the ledger.
|
|
8
|
+
*
|
|
9
|
+
* `open` is a gate: an `err` ends the run before a step starts, or suspends it
|
|
10
|
+
* when the host asks. `close` is a notice: an `err` is logged, and the terminal
|
|
11
|
+
* status of the run does not change (see `lib/hooks.ts`).
|
|
8
12
|
*/
|
|
9
13
|
export {};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `UsageRecorder` — the per-call LLM usage-accounting seam.
|
|
3
3
|
*
|
|
4
|
-
* Sibling of `
|
|
5
|
-
*
|
|
6
|
-
* run for managed billing, and `UsageRecorder` streams the fine-grained token
|
|
4
|
+
* Sibling of `RunCharge`, and complementary to it: `RunCharge` brackets a run
|
|
5
|
+
* for managed billing, and `UsageRecorder` streams the fine-grained token
|
|
7
6
|
* telemetry every completed LLM call produces. The harness names no storage or
|
|
8
7
|
* display technology — it emits records; the embedder decides where they land.
|
|
9
8
|
*
|
|
@@ -12,7 +11,9 @@
|
|
|
12
11
|
* composition root and reaches every `runAgent` invocation through the deps
|
|
13
12
|
* bags. OSS default: `createNoopUsageRecorder` (`./noop-usage-recorder.ts`).
|
|
14
13
|
*/
|
|
14
|
+
import type { ResultAsync } from "neverthrow";
|
|
15
15
|
import type { Scope } from "../auth/types.js";
|
|
16
|
+
import type { NoticeFailure } from "../lib/hooks.js";
|
|
16
17
|
import type { ChatUsage } from "../providers/types.js";
|
|
17
18
|
/** One completed LLM call's token usage, with the attribution held at the call site. */
|
|
18
19
|
export interface LlmUsageRecord {
|
|
@@ -52,12 +53,13 @@ export interface LlmUsageRecord {
|
|
|
52
53
|
/**
|
|
53
54
|
* Records one `LlmUsageRecord` per completed LLM call.
|
|
54
55
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* `
|
|
59
|
-
*
|
|
56
|
+
* `record` is a notice (see `lib/hooks.ts`). It gives a failure as an `err`,
|
|
57
|
+
* never as a throw. The loop does not wait for the result and puts no `try`
|
|
58
|
+
* around the call. When the result arrives, the loop logs the reason of an
|
|
59
|
+
* `err`, and the run continues with no change. A recorder that blocks does not
|
|
60
|
+
* make a run slower, but a realization that throws before it returns a
|
|
61
|
+
* `ResultAsync` still fails the run it was only meant to observe.
|
|
60
62
|
*/
|
|
61
63
|
export interface UsageRecorder {
|
|
62
|
-
record(record: LlmUsageRecord): void
|
|
64
|
+
record(record: LlmUsageRecord): ResultAsync<void, NoticeFailure>;
|
|
63
65
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `UsageRecorder` — the per-call LLM usage-accounting seam.
|
|
3
3
|
*
|
|
4
|
-
* Sibling of `
|
|
5
|
-
*
|
|
6
|
-
* run for managed billing, and `UsageRecorder` streams the fine-grained token
|
|
4
|
+
* Sibling of `RunCharge`, and complementary to it: `RunCharge` brackets a run
|
|
5
|
+
* for managed billing, and `UsageRecorder` streams the fine-grained token
|
|
7
6
|
* telemetry every completed LLM call produces. The harness names no storage or
|
|
8
7
|
* display technology — it emits records; the embedder decides where they land.
|
|
9
8
|
*
|
|
@@ -124,7 +124,10 @@ export interface ChatErrorEvent {
|
|
|
124
124
|
type: "error";
|
|
125
125
|
/** Human-readable, scrubbed error message. */
|
|
126
126
|
message: string;
|
|
127
|
-
/**
|
|
127
|
+
/**
|
|
128
|
+
* Machine-readable failure code. For a turn that a `suspend` error failed, it
|
|
129
|
+
* is the suspend reason of the host, carried unread (`suspensionOfFailure`).
|
|
130
|
+
*/
|
|
128
131
|
reason?: string;
|
|
129
132
|
source: EventSource;
|
|
130
133
|
}
|
|
@@ -339,7 +339,7 @@ export interface RunFailedPart {
|
|
|
339
339
|
runId: string;
|
|
340
340
|
/** Scrubbed error message (no internal details). */
|
|
341
341
|
error: string;
|
|
342
|
-
/**
|
|
342
|
+
/** The suspend reason of the host, carried unread; present only when the run suspended. */
|
|
343
343
|
reason?: string;
|
|
344
344
|
}
|
|
345
345
|
/**
|
|
@@ -74,4 +74,19 @@ export interface CortexMessage {
|
|
|
74
74
|
* zero is a figure, thus it stays a value and it never reads as an absence.
|
|
75
75
|
*/
|
|
76
76
|
durationMs?: number;
|
|
77
|
+
/**
|
|
78
|
+
* Who sent this message — the identity the host named at the append site.
|
|
79
|
+
* Carried on a `user` message only: `role` already names the sender of a
|
|
80
|
+
* reply, and the harness holds no per-turn model name at the store boundary.
|
|
81
|
+
* Absent means that nothing recorded a sender.
|
|
82
|
+
*/
|
|
83
|
+
author?: string;
|
|
84
|
+
/**
|
|
85
|
+
* When the store wrote this message: the START time of the append
|
|
86
|
+
* transaction, thus every message of one append carries the same value and
|
|
87
|
+
* no order holds between two appends. An ISO 8601 string rather than a
|
|
88
|
+
* `Date`, because the wire shape is JSON. Absent means that no stored row
|
|
89
|
+
* backs the message — a live surface builds one before anything is written.
|
|
90
|
+
*/
|
|
91
|
+
createdAt?: string;
|
|
77
92
|
}
|
|
@@ -151,10 +151,10 @@ export declare const RunStartedPartSchema: z.ZodObject<{
|
|
|
151
151
|
stepCount: z.ZodNumber;
|
|
152
152
|
}, z.core.$strip>;
|
|
153
153
|
export declare const StepStatusSchema: z.ZodEnum<{
|
|
154
|
+
failed: "failed";
|
|
154
155
|
pending: "pending";
|
|
155
156
|
running: "running";
|
|
156
157
|
completed: "completed";
|
|
157
|
-
failed: "failed";
|
|
158
158
|
skipped: "skipped";
|
|
159
159
|
queued: "queued";
|
|
160
160
|
}>;
|
|
@@ -163,10 +163,10 @@ export declare const DagStepStateSchema: z.ZodObject<{
|
|
|
163
163
|
name: z.ZodString;
|
|
164
164
|
agent: z.ZodString;
|
|
165
165
|
status: z.ZodEnum<{
|
|
166
|
+
failed: "failed";
|
|
166
167
|
pending: "pending";
|
|
167
168
|
running: "running";
|
|
168
169
|
completed: "completed";
|
|
169
|
-
failed: "failed";
|
|
170
170
|
skipped: "skipped";
|
|
171
171
|
queued: "queued";
|
|
172
172
|
}>;
|
|
@@ -186,10 +186,10 @@ export declare const DagStatePartSchema: z.ZodObject<{
|
|
|
186
186
|
name: z.ZodString;
|
|
187
187
|
agent: z.ZodString;
|
|
188
188
|
status: z.ZodEnum<{
|
|
189
|
+
failed: "failed";
|
|
189
190
|
pending: "pending";
|
|
190
191
|
running: "running";
|
|
191
192
|
completed: "completed";
|
|
192
|
-
failed: "failed";
|
|
193
193
|
skipped: "skipped";
|
|
194
194
|
queued: "queued";
|
|
195
195
|
}>;
|
|
@@ -570,10 +570,10 @@ export declare const CortexChatPartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
|
|
|
570
570
|
name: z.ZodString;
|
|
571
571
|
agent: z.ZodString;
|
|
572
572
|
status: z.ZodEnum<{
|
|
573
|
+
failed: "failed";
|
|
573
574
|
pending: "pending";
|
|
574
575
|
running: "running";
|
|
575
576
|
completed: "completed";
|
|
576
|
-
failed: "failed";
|
|
577
577
|
skipped: "skipped";
|
|
578
578
|
queued: "queued";
|
|
579
579
|
}>;
|
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
* (an external provenance ledger, or a filesystem index) is a composition-root
|
|
10
10
|
* decision — this module never names one.
|
|
11
11
|
*/
|
|
12
|
+
import { type Result } from "neverthrow";
|
|
12
13
|
import type { Pool, PoolClient } from "pg";
|
|
13
14
|
import type { AgentSession } from "../auth/types.js";
|
|
15
|
+
import { type GateRefusal } from "../lib/hooks.js";
|
|
14
16
|
import type { Logger } from "../lib/logger.js";
|
|
15
17
|
import type { ArtifactRegistry, ArtifactRegistrationInput } from "./artifact-registry.js";
|
|
16
18
|
export type { ArtifactRegistrationInput };
|
|
@@ -33,5 +35,8 @@ export interface ArtifactRegistrationResult {
|
|
|
33
35
|
* Builds the `cortex_artifacts` rows from the manifest, upserts them, then
|
|
34
36
|
* delegates external provenance registration to the injected `ArtifactRegistry`
|
|
35
37
|
* and applies any returned external ids back onto the local rows.
|
|
38
|
+
*
|
|
39
|
+
* A refusal of `register` writes no external id: the upserted rows keep
|
|
40
|
+
* `artifact_id = NULL`.
|
|
36
41
|
*/
|
|
37
|
-
export declare function registerStepArtifacts(db: Pool | PoolClient, registry: ArtifactRegistry, input: ArtifactRegistrationInput, session: AgentSession, logger?: Logger): Promise<ArtifactRegistrationResult
|
|
42
|
+
export declare function registerStepArtifacts(db: Pool | PoolClient, registry: ArtifactRegistry, input: ArtifactRegistrationInput, session: AgentSession, logger?: Logger): Promise<Result<ArtifactRegistrationResult, GateRefusal>>;
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* (an external provenance ledger, or a filesystem index) is a composition-root
|
|
10
10
|
* decision — this module never names one.
|
|
11
11
|
*/
|
|
12
|
+
import { err, ok } from "neverthrow";
|
|
12
13
|
import { createNoopLogger } from "../lib/console-logger.js";
|
|
14
|
+
import { passGate } from "../lib/hooks.js";
|
|
13
15
|
import { upsertArtifacts, updateArtifactId } from "../state/index.js";
|
|
14
16
|
// ── Registration ─────────────────────────────────────────────────────
|
|
15
17
|
/**
|
|
@@ -18,6 +20,9 @@ import { upsertArtifacts, updateArtifactId } from "../state/index.js";
|
|
|
18
20
|
* Builds the `cortex_artifacts` rows from the manifest, upserts them, then
|
|
19
21
|
* delegates external provenance registration to the injected `ArtifactRegistry`
|
|
20
22
|
* and applies any returned external ids back onto the local rows.
|
|
23
|
+
*
|
|
24
|
+
* A refusal of `register` writes no external id: the upserted rows keep
|
|
25
|
+
* `artifact_id = NULL`.
|
|
21
26
|
*/
|
|
22
27
|
export async function registerStepArtifacts(db, registry, input, session,
|
|
23
28
|
// A parameter rather than a field on `ArtifactRegistrationInput`: that type is
|
|
@@ -27,7 +32,7 @@ logger = createNoopLogger()) {
|
|
|
27
32
|
const { resourceId, runId, stepId, artifacts } = input;
|
|
28
33
|
const log = logger.named("artifact-registration").with({ runId, stepId });
|
|
29
34
|
if (artifacts.length === 0) {
|
|
30
|
-
return { localCount: 0, externalRegistered: 0, externalFailed: 0, failureDetails: [] };
|
|
35
|
+
return ok({ localCount: 0, externalRegistered: 0, externalFailed: 0, failureDetails: [] });
|
|
31
36
|
}
|
|
32
37
|
const dbPathPrefix = `runs/${runId}/${stepId}/`;
|
|
33
38
|
const emptyHash = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
@@ -48,7 +53,10 @@ logger = createNoopLogger()) {
|
|
|
48
53
|
});
|
|
49
54
|
await upsertArtifacts(db, localEntries);
|
|
50
55
|
const localPaths = new Set(localEntries.map((e) => e.path));
|
|
51
|
-
const
|
|
56
|
+
const registered = await passGate("ArtifactRegistry.register", registry.register(input, session));
|
|
57
|
+
if (registered.isErr())
|
|
58
|
+
return err(registered.error);
|
|
59
|
+
const result = registered.value;
|
|
52
60
|
// The registry excluded these from `failed` by its own severity judgement, so
|
|
53
61
|
// this is their only record. Logged here rather than left to the registry:
|
|
54
62
|
// the seam is implementable by an embedder, and a rejection does not get to
|
|
@@ -67,10 +75,10 @@ logger = createNoopLogger()) {
|
|
|
67
75
|
await updateArtifactId(db, resourceId, reg.path, reg.externalId);
|
|
68
76
|
externalRegistered++;
|
|
69
77
|
}
|
|
70
|
-
return {
|
|
78
|
+
return ok({
|
|
71
79
|
localCount: localEntries.length,
|
|
72
80
|
externalRegistered,
|
|
73
81
|
externalFailed: result.failedCount,
|
|
74
82
|
failureDetails: result.failed,
|
|
75
|
-
};
|
|
83
|
+
});
|
|
76
84
|
}
|
|
@@ -14,9 +14,11 @@
|
|
|
14
14
|
* - `createNoopArtifactRegistry` (the harness, this dir) — registers nothing
|
|
15
15
|
* externally; the local `cortex_artifacts` ledger is the only record.
|
|
16
16
|
*/
|
|
17
|
+
import type { ResultAsync } from "neverthrow";
|
|
17
18
|
import type { ArtifactManifestEntry } from "../schemas/artifact-manifest.js";
|
|
18
19
|
import type { ProvenanceCollector } from "../provenance/collector.js";
|
|
19
20
|
import type { AgentSession } from "../auth/types.js";
|
|
21
|
+
import type { GateFailure, NoticeFailure } from "../lib/hooks.js";
|
|
20
22
|
/** High-level input for registering one step's artifacts. */
|
|
21
23
|
export interface ArtifactRegistrationInput {
|
|
22
24
|
/** Analysis resource ID. */
|
|
@@ -85,12 +87,17 @@ export interface ArtifactRegistry {
|
|
|
85
87
|
* Implementations MUST NOT touch the local `cortex_artifacts` ledger — that
|
|
86
88
|
* is the harness's responsibility, applied around this call. The `session` carries
|
|
87
89
|
* the run credential an adapter needs to address the external system.
|
|
90
|
+
*
|
|
91
|
+
* A partial outcome is an `ok`, with the rejections in `failed`.
|
|
88
92
|
*/
|
|
89
|
-
register(input: ArtifactRegistrationInput, session: AgentSession):
|
|
93
|
+
register(input: ArtifactRegistrationInput, session: AgentSession): ResultAsync<ExternalRegistrationResult, GateFailure>;
|
|
90
94
|
/**
|
|
91
95
|
* Push a step's registered artifacts to permanent storage. A no-op when the
|
|
92
96
|
* adapter's bytes already live locally; the managed adapter uploads them.
|
|
93
|
-
*
|
|
97
|
+
*
|
|
98
|
+
* A notice (see `lib/hooks.ts`): an `err` is logged and does not fail the
|
|
99
|
+
* step. The rows stay unsynced, thus a later sync of the step selects them
|
|
100
|
+
* again.
|
|
94
101
|
*/
|
|
95
|
-
sync(input: ArtifactSyncInput, session: AgentSession):
|
|
102
|
+
sync(input: ArtifactSyncInput, session: AgentSession): ResultAsync<void, NoticeFailure>;
|
|
96
103
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { okAsync } from "neverthrow";
|
|
1
2
|
/**
|
|
2
3
|
* Local/OSS artifact-registration seam: `register` records nothing externally
|
|
3
4
|
* and reports zero failures; `sync` is a no-op. The harness writes the local
|
|
@@ -9,9 +10,7 @@
|
|
|
9
10
|
*/
|
|
10
11
|
export function createNoopArtifactRegistry() {
|
|
11
12
|
return {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
},
|
|
15
|
-
async sync() { },
|
|
13
|
+
register: () => okAsync({ registered: [], failed: [], failedCount: 0 }),
|
|
14
|
+
sync: () => okAsync(undefined),
|
|
16
15
|
};
|
|
17
16
|
}
|
|
@@ -10,12 +10,14 @@
|
|
|
10
10
|
* the orchestration — which stage is `DBOS.runStep`-wrapped, which stage is
|
|
11
11
|
* fail-fast vs. best-effort. This module owns only the stage bodies.
|
|
12
12
|
*/
|
|
13
|
+
import { type Result } from "neverthrow";
|
|
13
14
|
import type { Pool } from "pg";
|
|
14
15
|
import type { AgentChat, EmbeddingProvider } from "../providers/types.js";
|
|
15
16
|
import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
|
|
16
17
|
import type { ResolveWorkspaceRoot } from "../workspace/paths.js";
|
|
17
18
|
import type { ArtifactManifestEntry } from "../schemas/artifact-manifest.js";
|
|
18
19
|
import type { StepSummary } from "../schemas/step-summary.js";
|
|
20
|
+
import type { GateRefusal } from "../lib/hooks.js";
|
|
19
21
|
import type { Logger } from "../lib/logger.js";
|
|
20
22
|
import type { UsageRecorder } from "../billing/usage-recorder.js";
|
|
21
23
|
import type { ArtifactRegistry } from "./artifact-registry.js";
|
|
@@ -53,17 +55,26 @@ export declare function generateStepFileMetadata(deps: PostStepPipelineDeps, pos
|
|
|
53
55
|
* still returned for vector indexing.
|
|
54
56
|
*/
|
|
55
57
|
export declare function generateStepSummaryAndWrite(deps: PostStepPipelineDeps, postCtx: PostStepContext, manifest: readonly ArtifactManifestEntry[]): Promise<StepSummary | undefined>;
|
|
58
|
+
export type StepRegistrationFailure = {
|
|
59
|
+
readonly kind: "refused";
|
|
60
|
+
readonly refusal: GateRefusal;
|
|
61
|
+
} | {
|
|
62
|
+
readonly kind: "rejected";
|
|
63
|
+
readonly message: string;
|
|
64
|
+
};
|
|
65
|
+
export declare function describeStepRegistrationFailure(failure: StepRegistrationFailure): string;
|
|
56
66
|
/**
|
|
57
67
|
* Reconcile the manifest against disk (drop phantoms, rehash) and register the
|
|
58
68
|
* survivors with the local ledger + the injected `ArtifactRegistry`. Fail-fast
|
|
59
69
|
* (see the artifact-manifest spec): `externalFailed` counts only the terminal
|
|
60
70
|
* rejections — a rejected output whose bytes exist nowhere but the step tree,
|
|
61
71
|
* plus anything rejected as a consequence of one — so a non-zero count means
|
|
62
|
-
* real outputs went unregistered, and
|
|
63
|
-
* OSS filesystem registry returns `externalFailed: 0`, so it never trips).
|
|
72
|
+
* real outputs went unregistered, and the `err` carries the per-file detail (the
|
|
73
|
+
* OSS filesystem registry returns `externalFailed: 0`, so it never trips). A
|
|
74
|
+
* refusal of the `register` gate is the other `err`.
|
|
64
75
|
* Returns the reconciled manifest.
|
|
65
76
|
*/
|
|
66
|
-
export declare function reconcileAndRegisterStepArtifacts(deps: PostStepPipelineDeps, postCtx: PostStepContext, manifest: readonly ArtifactManifestEntry[]): Promise<readonly ArtifactManifestEntry[]
|
|
77
|
+
export declare function reconcileAndRegisterStepArtifacts(deps: PostStepPipelineDeps, postCtx: PostStepContext, manifest: readonly ArtifactManifestEntry[]): Promise<Result<readonly ArtifactManifestEntry[], StepRegistrationFailure>>;
|
|
67
78
|
/**
|
|
68
79
|
* Vector-index the threaded file descriptions + the step summary into the
|
|
69
80
|
* per-analysis pgvector store. Best-effort (see the artifact-manifest spec):
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* fail-fast vs. best-effort. This module owns only the stage bodies.
|
|
12
12
|
*/
|
|
13
13
|
import { join } from "node:path";
|
|
14
|
+
import { err, ok } from "neverthrow";
|
|
14
15
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
15
16
|
import { createNoopLogger } from "../lib/console-logger.js";
|
|
16
17
|
import { writeFileWithinRoot } from "../lib/fs-helpers.js";
|
|
@@ -92,14 +93,18 @@ export async function generateStepSummaryAndWrite(deps, postCtx, manifest) {
|
|
|
92
93
|
}
|
|
93
94
|
return summary;
|
|
94
95
|
}
|
|
96
|
+
export function describeStepRegistrationFailure(failure) {
|
|
97
|
+
return failure.kind === "refused" ? `the artifact registry refused the registration: ${failure.refusal.reason}` : failure.message;
|
|
98
|
+
}
|
|
95
99
|
/**
|
|
96
100
|
* Reconcile the manifest against disk (drop phantoms, rehash) and register the
|
|
97
101
|
* survivors with the local ledger + the injected `ArtifactRegistry`. Fail-fast
|
|
98
102
|
* (see the artifact-manifest spec): `externalFailed` counts only the terminal
|
|
99
103
|
* rejections — a rejected output whose bytes exist nowhere but the step tree,
|
|
100
104
|
* plus anything rejected as a consequence of one — so a non-zero count means
|
|
101
|
-
* real outputs went unregistered, and
|
|
102
|
-
* OSS filesystem registry returns `externalFailed: 0`, so it never trips).
|
|
105
|
+
* real outputs went unregistered, and the `err` carries the per-file detail (the
|
|
106
|
+
* OSS filesystem registry returns `externalFailed: 0`, so it never trips). A
|
|
107
|
+
* refusal of the `register` gate is the other `err`.
|
|
103
108
|
* Returns the reconciled manifest.
|
|
104
109
|
*/
|
|
105
110
|
export async function reconcileAndRegisterStepArtifacts(deps, postCtx, manifest) {
|
|
@@ -115,14 +120,17 @@ export async function reconcileAndRegisterStepArtifacts(deps, postCtx, manifest)
|
|
|
115
120
|
...(deps.logger ? { logger: deps.logger } : {}),
|
|
116
121
|
});
|
|
117
122
|
if (reconciled.manifest.length === 0)
|
|
118
|
-
return reconciled.manifest;
|
|
119
|
-
const
|
|
123
|
+
return ok(reconciled.manifest);
|
|
124
|
+
const registered = await registerStepArtifacts(deps.pool, deps.artifactRegistry, {
|
|
120
125
|
resourceId: input.analysisId,
|
|
121
126
|
runId: input.runId,
|
|
122
127
|
stepId: input.stepId,
|
|
123
128
|
artifacts: reconciled.manifest,
|
|
124
129
|
collector: lineageCollector,
|
|
125
130
|
}, session, deps.logger);
|
|
131
|
+
if (registered.isErr())
|
|
132
|
+
return err({ kind: "refused", refusal: registered.error });
|
|
133
|
+
const reg = registered.value;
|
|
126
134
|
if (reg.externalFailed > 0) {
|
|
127
135
|
// The registry commits per leaf and per activity, so accepted and rejected
|
|
128
136
|
// rows arriving together is ordinary and `externalRegistered` is what
|
|
@@ -137,7 +145,7 @@ export async function reconcileAndRegisterStepArtifacts(deps, postCtx, manifest)
|
|
|
137
145
|
`${reg.externalFailed} terminal rejection(s), ${reg.externalRegistered}/${reg.localCount} local artifact(s) registered` +
|
|
138
146
|
(noneRegistered ? " (nothing in this batch registered)" : "") +
|
|
139
147
|
(detail ? `\n ${detail}` : "");
|
|
140
|
-
// Logged as fields as well as
|
|
148
|
+
// Logged as fields as well as returned: the failure reaches `failStep` as one
|
|
141
149
|
// opaque string, so the per-path rejections are only queryable from here.
|
|
142
150
|
// This is what distinguishes a registry rejection from the attestation
|
|
143
151
|
// throws in `reconcileManifestWithDisk` when a step dies.
|
|
@@ -150,9 +158,9 @@ export async function reconcileAndRegisterStepArtifacts(deps, postCtx, manifest)
|
|
|
150
158
|
noneRegistered,
|
|
151
159
|
failures: reg.failureDetails,
|
|
152
160
|
});
|
|
153
|
-
|
|
161
|
+
return err({ kind: "rejected", message: msg });
|
|
154
162
|
}
|
|
155
|
-
return reconciled.manifest;
|
|
163
|
+
return ok(reconciled.manifest);
|
|
156
164
|
}
|
|
157
165
|
/**
|
|
158
166
|
* Vector-index the threaded file descriptions + the step summary into the
|
|
@@ -212,7 +220,12 @@ export async function vectorIndexStepOutputs(deps, postCtx, artifacts) {
|
|
|
212
220
|
let indexed = 0;
|
|
213
221
|
let failed = 0;
|
|
214
222
|
for (const entry of liveMetadata) {
|
|
215
|
-
const ok = await indexOne(`/${input.analysisId}/${entry.dbPath}`, entry.description, {
|
|
223
|
+
const ok = await indexOne(`/${input.analysisId}/${entry.dbPath}`, entry.description, {
|
|
224
|
+
text: entry.description,
|
|
225
|
+
type: "output",
|
|
226
|
+
...entry.metadata,
|
|
227
|
+
path: entry.dbPath,
|
|
228
|
+
});
|
|
216
229
|
if (ok)
|
|
217
230
|
indexed++;
|
|
218
231
|
else
|