harnery 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +27 -1
- package/dist/commands/harness.d.ts.map +1 -1
- package/dist/commands/harness.js +2 -0
- package/dist/commands/work.d.ts +2 -0
- package/dist/commands/work.d.ts.map +1 -1
- package/dist/commands/work.js +47 -4
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +64 -0
- package/dist/core/harnesses/attest-projection.d.ts +44 -0
- package/dist/core/harnesses/attest-projection.d.ts.map +1 -0
- package/dist/core/harnesses/attest-projection.js +113 -0
- package/dist/core/harnesses/attest.d.ts +9 -0
- package/dist/core/harnesses/attest.d.ts.map +1 -1
- package/dist/core/harnesses/attest.js +14 -1
- package/dist/core/harnesses/attestation.d.ts +1 -1
- package/dist/core/harnesses/attestation.d.ts.map +1 -1
- package/dist/core/harnesses/attestation.js +7 -1
- package/dist/core/harnesses/bench.d.ts.map +1 -1
- package/dist/core/harnesses/bench.js +13 -0
- package/dist/core/harnesses/profiles.d.ts +7 -0
- package/dist/core/harnesses/profiles.d.ts.map +1 -1
- package/dist/core/harnesses/profiles.js +9 -0
- package/dist/core/harnesses/types.d.ts +19 -1
- package/dist/core/harnesses/types.d.ts.map +1 -1
- package/dist/core/harnesses/types.js +1 -0
- package/dist/core/work/index.d.ts +1 -1
- package/dist/core/work/index.d.ts.map +1 -1
- package/dist/core/work/index.js +1 -1
- package/dist/core/work/runner.d.ts.map +1 -1
- package/dist/core/work/runner.js +8 -1
- package/dist/core/work/state.d.ts +24 -1
- package/dist/core/work/state.d.ts.map +1 -1
- package/dist/core/work/state.js +108 -14
- package/dist/core/workflow/approvals.d.ts.map +1 -1
- package/dist/core/workflow/approvals.js +3 -6
- package/dist/core/workflow/attempt-context.d.ts.map +1 -1
- package/dist/core/workflow/attempt-context.js +38 -1
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +61 -19
- package/dist/core/workflow/index.d.ts +1 -1
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/index.js +1 -1
- package/dist/core/workflow/journal.d.ts +30 -0
- package/dist/core/workflow/journal.d.ts.map +1 -0
- package/dist/core/workflow/journal.js +70 -0
- package/dist/core/workflow/proof.d.ts +3 -1
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +1 -0
- package/dist/core/workflow/sandbox-projection.d.ts +57 -0
- package/dist/core/workflow/sandbox-projection.d.ts.map +1 -0
- package/dist/core/workflow/sandbox-projection.js +95 -0
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +14 -1
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +18 -2
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +14 -1
- package/dist/core/workflow/types.d.ts +62 -0
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/index.d.ts +2 -0
- package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/index.js +1 -0
- package/dist/core/workflow/workspaces/inspect.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/inspect.js +14 -0
- package/dist/core/workflow/workspaces/reclaim.d.ts +50 -0
- package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -0
- package/dist/core/workflow/workspaces/reclaim.js +88 -0
- package/dist/core/workflow/workspaces/state.d.ts +1 -1
- package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.js +9 -30
- package/dist/lib/browser/client.d.ts +50 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +103 -0
- package/dist/lib/exec.d.ts +4 -0
- package/dist/lib/exec.d.ts.map +1 -1
- package/dist/lib/exec.js +6 -1
- package/dist/lib/tunnel/error-page.d.ts +15 -0
- package/dist/lib/tunnel/error-page.d.ts.map +1 -0
- package/dist/lib/tunnel/error-page.js +98 -0
- package/dist/lib/tunnel/gate.d.ts +1 -12
- package/dist/lib/tunnel/gate.d.ts.map +1 -1
- package/dist/lib/tunnel/gate.js +59 -4
- package/package.json +1 -1
- package/src/commands/browse.ts +33 -1
- package/src/commands/harness.ts +6 -0
- package/src/commands/work.ts +80 -13
- package/src/commands/workflow.ts +74 -0
- package/src/core/harnesses/attest-projection.ts +151 -0
- package/src/core/harnesses/attest.ts +23 -1
- package/src/core/harnesses/attestation.ts +7 -1
- package/src/core/harnesses/bench.ts +16 -0
- package/src/core/harnesses/profiles.ts +11 -0
- package/src/core/harnesses/types.ts +20 -0
- package/src/core/work/index.ts +1 -0
- package/src/core/work/runner.ts +8 -0
- package/src/core/work/state.ts +177 -27
- package/src/core/workflow/approvals.ts +3 -6
- package/src/core/workflow/attempt-context.ts +47 -1
- package/src/core/workflow/engine.ts +106 -33
- package/src/core/workflow/index.ts +1 -0
- package/src/core/workflow/journal.ts +90 -0
- package/src/core/workflow/proof.ts +4 -0
- package/src/core/workflow/sandbox-projection.ts +150 -0
- package/src/core/workflow/spawn-claude.ts +18 -1
- package/src/core/workflow/spawn-codex.ts +25 -2
- package/src/core/workflow/spawn-cursor.ts +18 -1
- package/src/core/workflow/types.ts +66 -0
- package/src/core/workflow/workspaces/index.ts +2 -0
- package/src/core/workflow/workspaces/inspect.ts +15 -0
- package/src/core/workflow/workspaces/reclaim.ts +109 -0
- package/src/core/workflow/workspaces/state.ts +13 -33
- package/src/lib/browser/client.ts +129 -0
- package/src/lib/exec.ts +10 -1
- package/src/lib/tunnel/error-page.ts +114 -0
- package/src/lib/tunnel/gate.ts +66 -3
|
@@ -3,7 +3,7 @@ import type { Spawner, SpawnRequest, SpawnResult } from "../workflow/types.js";
|
|
|
3
3
|
* repeated through closed unions in every consumer. */
|
|
4
4
|
export type HarnessId = string;
|
|
5
5
|
export type CapabilitySupport = "supported" | "partial" | "unsupported" | "unknown";
|
|
6
|
-
export declare const HARNESS_CAPABILITY_DIMENSIONS: readonly ["invocation", "modelSelection", "effortSelection", "maxTurns", "finalResult", "sessionId", "cost", "toolEvidence", "policyMapping", "interruption", "streaming", "steering", "resume", "images", "contextTelemetry", "preCompactionSignal", "postCompactionSignal", "compaction"];
|
|
6
|
+
export declare const HARNESS_CAPABILITY_DIMENSIONS: readonly ["invocation", "modelSelection", "effortSelection", "maxTurns", "finalResult", "sessionId", "cost", "toolEvidence", "policyMapping", "filesystemPolicyProjection", "interruption", "streaming", "steering", "resume", "images", "contextTelemetry", "preCompactionSignal", "postCompactionSignal", "compaction"];
|
|
7
7
|
export type HarnessCapabilityDimension = (typeof HARNESS_CAPABILITY_DIMENSIONS)[number];
|
|
8
8
|
export interface CapabilityClaim {
|
|
9
9
|
support: CapabilitySupport;
|
|
@@ -12,6 +12,16 @@ export interface CapabilityClaim {
|
|
|
12
12
|
note?: string;
|
|
13
13
|
}
|
|
14
14
|
export type HarnessCapabilities = Record<HarnessCapabilityDimension, CapabilityClaim>;
|
|
15
|
+
/** What one harness can represent of a filesystem-policy projection
|
|
16
|
+
* (ADR 0039). `null` means the harness does not distinguish that, which is a
|
|
17
|
+
* fact about the harness rather than something to paper over: a projection the
|
|
18
|
+
* adapter would silently drop must be refused instead. */
|
|
19
|
+
export interface HarnessSandboxProjection {
|
|
20
|
+
/** Native representation per canonical mode, or null when unrepresentable. */
|
|
21
|
+
modes: Record<"read-only" | "workspace-write", string | null>;
|
|
22
|
+
/** Whether the harness accepts an explicit writable-root set. */
|
|
23
|
+
writableRoots: boolean;
|
|
24
|
+
}
|
|
15
25
|
export interface HarnessProfile {
|
|
16
26
|
id: HarnessId;
|
|
17
27
|
displayName: string;
|
|
@@ -29,6 +39,9 @@ export interface HarnessProfile {
|
|
|
29
39
|
date: string;
|
|
30
40
|
version: string;
|
|
31
41
|
};
|
|
42
|
+
/** How this adapter projects host filesystem policy into the vendor's own
|
|
43
|
+
* sandbox (ADR 0039). Absent means it cannot project any of it. */
|
|
44
|
+
sandboxProjection?: HarnessSandboxProjection;
|
|
32
45
|
}
|
|
33
46
|
/** Fully planned child invocation. `resultFile` is used by adapters such as
|
|
34
47
|
* Codex that deliver the authoritative final answer out-of-band. */
|
|
@@ -43,6 +56,11 @@ export interface HarnessRawResult {
|
|
|
43
56
|
exitCode: number;
|
|
44
57
|
durationMs: number;
|
|
45
58
|
resultFileText?: string;
|
|
59
|
+
/** The host killed this child for exceeding its timeout. A vendor CLI that
|
|
60
|
+
* handles the signal cleanly still exits 0, so exit code alone cannot tell a
|
|
61
|
+
* kill from a finish; without this a timed-out child reads as a success with
|
|
62
|
+
* an empty answer. */
|
|
63
|
+
timedOut?: boolean;
|
|
46
64
|
}
|
|
47
65
|
export interface HarnessBenchFixture {
|
|
48
66
|
raw: HarnessRawResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/harnesses/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE/E;uDACuD;AACvD,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAAC;AAEpF,eAAO,MAAM,6BAA6B,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/harnesses/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE/E;uDACuD;AACvD,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAAC;AAEpF,eAAO,MAAM,6BAA6B,2TAoBhC,CAAC;AAEX,MAAM,MAAM,0BAA0B,GAAG,CAAC,OAAO,6BAA6B,CAAC,CAAC,MAAM,CAAC,CAAC;AAExF,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,iBAAiB,CAAC;IAC3B;iFAC6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,0BAA0B,EAAE,eAAe,CAAC,CAAC;AAEtF;;;0DAG0D;AAC1D,MAAM,WAAW,wBAAwB;IACvC,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC,WAAW,GAAG,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9D,iEAAiE;IACjE,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,SAAS,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,gBAAgB,CAAC;IAClC,SAAS,EAAE,UAAU,CAAC;IACtB,WAAW,EAAE,QAAQ,GAAG,KAAK,GAAG,OAAO,CAAC;IACxC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,YAAY,EAAE,mBAAmB,CAAC;IAClC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C;uEACmE;IACnE,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;CAC9C;AAED;oEACoE;AACpE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;0BAGsB;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,gBAAgB,CAAC;IACtB,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,MAAM,CAAC,GAAG;QAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED;;wDAEwD;AACxD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,cAAc,CAAC;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,eAAe,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,iBAAiB,CAAC;IACnF,eAAe,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,WAAW,CAAC;IACxD,OAAO,EAAE,mBAAmB,CAAC;CAC9B"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { type RunWorkItemInput, runWorkItem } from "./runner.js";
|
|
2
|
-
export { acceptWorkItem, assertWorkId, type CreateWorkItemInput, cancelWorkItem, createWorkItem, listWorkItems, newWorkId, readWorkItem, reconcileAllWorkItems, reconcileWorkItem, reopenWorkItem, WORK_EVENT_SCHEMA_VERSION, WORK_INTENT_SCHEMA_VERSION, type WorkAttempt, type WorkAttemptTrigger, type WorkEvent, type WorkEventType, type WorkIntent, type WorkNextAction, type WorkProjection, type WorkRecord, type WorkState, } from "./state.js";
|
|
2
|
+
export { acceptWorkItem, assertWorkId, type CreateWorkItemInput, cancelWorkItem, createWorkItem, listWorkItems, newWorkId, openOperatorFindings, readWorkItem, reconcileAllWorkItems, reconcileWorkItem, reopenWorkItem, WORK_EVENT_SCHEMA_VERSION, WORK_INTENT_SCHEMA_VERSION, type WorkAttempt, type WorkAttemptTrigger, type WorkEvent, type WorkEventType, type WorkIntent, type WorkNextAction, type WorkProjection, type WorkRecord, type WorkState, } from "./state.js";
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/work/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EACL,cAAc,EACd,YAAY,EACZ,KAAK,mBAAmB,EACxB,cAAc,EACd,cAAc,EACd,aAAa,EACb,SAAS,EACT,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,EACd,yBAAyB,EACzB,0BAA0B,EAC1B,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,SAAS,GACf,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/work/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EACL,cAAc,EACd,YAAY,EACZ,KAAK,mBAAmB,EACxB,cAAc,EACd,cAAc,EACd,aAAa,EACb,SAAS,EACT,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,EACd,yBAAyB,EACzB,0BAA0B,EAC1B,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,SAAS,GACf,MAAM,YAAY,CAAC"}
|
package/dist/core/work/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { runWorkItem } from "./runner.js";
|
|
2
|
-
export { acceptWorkItem, assertWorkId, cancelWorkItem, createWorkItem, listWorkItems, newWorkId, readWorkItem, reconcileAllWorkItems, reconcileWorkItem, reopenWorkItem, WORK_EVENT_SCHEMA_VERSION, WORK_INTENT_SCHEMA_VERSION, } from "./state.js";
|
|
2
|
+
export { acceptWorkItem, assertWorkId, cancelWorkItem, createWorkItem, listWorkItems, newWorkId, openOperatorFindings, readWorkItem, reconcileAllWorkItems, reconcileWorkItem, reopenWorkItem, WORK_EVENT_SCHEMA_VERSION, WORK_INTENT_SCHEMA_VERSION, } from "./state.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/core/work/runner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EACV,SAAS,EAGV,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/core/work/runner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EACV,SAAS,EAGV,MAAM,sBAAsB,CAAC;AAiB9B,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,IAAI,CACV,UAAU,EACV,WAAW,GAAG,OAAO,GAAG,aAAa,GAAG,YAAY,GAAG,aAAa,GAAG,gBAAgB,CACxF,CAAC;IACF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC,CA4F7E"}
|
package/dist/core/work/runner.js
CHANGED
|
@@ -2,7 +2,7 @@ import { resolve } from "node:path";
|
|
|
2
2
|
import { runWorkflow } from "../workflow/engine.js";
|
|
3
3
|
import { readWorkflowProof } from "../workflow/proof.js";
|
|
4
4
|
import { WORKFLOW_ATTEMPT_CONTEXT_SCHEMA_VERSION, WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION, } from "../workflow/types.js";
|
|
5
|
-
import { acquireWorkLease, appendReconcileIfChanged, appendWorkEvent, assertWorkflowUnchanged, boundedActor, newWorkflowRunId, readWorkItemIgnoringLease, } from "./state.js";
|
|
5
|
+
import { acquireWorkLease, appendReconcileIfChanged, appendWorkEvent, assertWorkflowUnchanged, boundedActor, newWorkflowRunId, openOperatorFindings, readWorkItemIgnoringLease, } from "./state.js";
|
|
6
6
|
export async function runWorkItem(input) {
|
|
7
7
|
const coordRoot = resolve(input.coordRoot);
|
|
8
8
|
const release = acquireWorkLease(coordRoot, input.workId);
|
|
@@ -49,11 +49,18 @@ export async function runWorkItem(input) {
|
|
|
49
49
|
}
|
|
50
50
|
prior = priorContext(coordRoot, latest);
|
|
51
51
|
}
|
|
52
|
+
const open = openOperatorFindings(coordRoot, input.workId);
|
|
53
|
+
const findings = open.findings.map((finding) => ({
|
|
54
|
+
id: finding.id,
|
|
55
|
+
actor: open.actor,
|
|
56
|
+
statement: finding.statement,
|
|
57
|
+
}));
|
|
52
58
|
const attemptContext = {
|
|
53
59
|
schema_version: WORKFLOW_ATTEMPT_CONTEXT_SCHEMA_VERSION,
|
|
54
60
|
number: attempt,
|
|
55
61
|
trigger,
|
|
56
62
|
...(prior ? { prior } : {}),
|
|
63
|
+
...(findings.length === 0 ? {} : { findings }),
|
|
57
64
|
};
|
|
58
65
|
const runId = newWorkflowRunId();
|
|
59
66
|
appendWorkEvent(coordRoot, input.workId, {
|
|
@@ -37,17 +37,32 @@ export interface WorkEvent {
|
|
|
37
37
|
proof_sha256?: string;
|
|
38
38
|
terminal_attestation_sha256?: string;
|
|
39
39
|
accepted_unknowns?: string[];
|
|
40
|
+
/** Operator corrections raised with a reopen. Authored by a human against a
|
|
41
|
+
* prior attempt, carried into the next attempt, and open until disposed. */
|
|
42
|
+
findings?: WorkOperatorFinding[];
|
|
43
|
+
/** How an operator disposed of each open finding at acceptance. */
|
|
44
|
+
dispositions?: WorkFindingDisposition[];
|
|
40
45
|
state?: WorkState;
|
|
41
46
|
next_action?: WorkNextAction;
|
|
42
47
|
}
|
|
48
|
+
export interface WorkOperatorFinding {
|
|
49
|
+
id: string;
|
|
50
|
+
statement: string;
|
|
51
|
+
}
|
|
52
|
+
export interface WorkFindingDisposition {
|
|
53
|
+
id: string;
|
|
54
|
+
outcome: "fixed" | "deferred";
|
|
55
|
+
reason?: string;
|
|
56
|
+
}
|
|
43
57
|
export interface WorkAttempt {
|
|
44
58
|
number: number;
|
|
45
59
|
run_id: string;
|
|
46
60
|
started_at: string;
|
|
47
61
|
trigger?: WorkAttemptTrigger;
|
|
48
|
-
status: "running" | "parked" | "succeeded" | "failed" | "lost";
|
|
62
|
+
status: "running" | "parked" | "succeeded" | "failed" | "lost" | "journal_unreadable";
|
|
49
63
|
approval_id?: string;
|
|
50
64
|
proof_path?: string;
|
|
65
|
+
journal_error?: string;
|
|
51
66
|
}
|
|
52
67
|
export interface WorkProjection {
|
|
53
68
|
id: string;
|
|
@@ -90,6 +105,7 @@ export declare function acceptWorkItem(coordRoot: string, workId: string, input?
|
|
|
90
105
|
actor?: string;
|
|
91
106
|
reason?: string;
|
|
92
107
|
acceptedUnknowns?: readonly string[];
|
|
108
|
+
dispositions?: readonly WorkFindingDisposition[];
|
|
93
109
|
}): WorkRecord;
|
|
94
110
|
export declare function cancelWorkItem(coordRoot: string, workId: string, input?: {
|
|
95
111
|
actor?: string;
|
|
@@ -98,7 +114,14 @@ export declare function cancelWorkItem(coordRoot: string, workId: string, input?
|
|
|
98
114
|
export declare function reopenWorkItem(coordRoot: string, workId: string, input?: {
|
|
99
115
|
actor?: string;
|
|
100
116
|
reason?: string;
|
|
117
|
+
findings?: readonly string[];
|
|
101
118
|
}): WorkRecord;
|
|
119
|
+
/** Findings raised by the most recent reopen that no later acceptance disposed
|
|
120
|
+
* of. Empty when the work was never reopened with findings. */
|
|
121
|
+
export declare function openOperatorFindings(coordRoot: string, workId: string): {
|
|
122
|
+
actor: string;
|
|
123
|
+
findings: WorkOperatorFinding[];
|
|
124
|
+
};
|
|
102
125
|
export declare function assertWorkId(workId: string): void;
|
|
103
126
|
export declare function newWorkId(): string;
|
|
104
127
|
export declare function newWorkflowRunId(): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/core/work/state.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/core/work/state.ts"],"names":[],"mappings":"AAuBA,eAAO,MAAM,0BAA0B,EAAG,CAAU,CAAC;AACrD,eAAO,MAAM,yBAAyB,EAAG,CAAU,CAAC;AAkBpD,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,OAAO,GACP,SAAS,GACT,mBAAmB,GACnB,WAAW,GACX,SAAS,GACT,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,MAAM,cAAc,GACtB,uBAAuB,GACvB,KAAK,GACL,cAAc,GACd,kBAAkB,GAClB,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,MAAM,CAAC;AAEX,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,OAAO,CAAC;AAErD,MAAM,WAAW,UAAU;IACzB,cAAc,EAAE,OAAO,0BAA0B,CAAC;IAClD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,UAAU,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GACrB,cAAc,GACd,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,GACjB,eAAe,GACf,gBAAgB,GAChB,eAAe,CAAC;AAEpB,MAAM,WAAW,SAAS;IACxB,cAAc,EAAE,OAAO,yBAAyB,CAAC;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,aAAa,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B;gFAC4E;IAC5E,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACjC,mEAAmE;IACnE,YAAY,CAAC,EAAE,sBAAsB,EAAE,CAAC;IACxC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,WAAW,CAAC,EAAE,cAAc,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,OAAO,GAAG,UAAU,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,MAAM,GAAG,oBAAoB,CAAC;IACtF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,cAAc,CAAC;IAC5B,uBAAuB,EAAE,MAAM,EAAE,CAAC;IAClC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,UAAU,EAAE,cAAc,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC9B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,UAAU,CA0DrE;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAI1E;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,EAAE,CAS7D;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,UAAU,CAmC/F;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE,CAIrF;AAED,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,GAAE;IACL,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,YAAY,CAAC,EAAE,SAAS,sBAAsB,EAAE,CAAC;CAC7C,GACL,UAAU,CAEZ;AAED,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9C,UAAU,CAQZ;AAED,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAO,GAC5E,UAAU,CAQZ;AAED;+DAC+D;AAC/D,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,mBAAmB,EAAE,CAAA;CAAE,CAepD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAkYD,kEAAkE;AAClE,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,MAAM,GACZ,IAAI,CA4BN;AAED,kEAAkE;AAClE,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,gBAAgB,GAAG,SAAS,GAAG,KAAK,GAAG,IAAI,CAAC,GAClE,SAAS,CAOX;AA2ED,kEAAkE;AAClE,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAIvF;AAoJD,kEAAkE;AAClE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAMhE;AAkBD,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,CAE9E;AAED,4CAA4C;AAC5C,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,CAOnF;AAiKD,kEAAkE;AAClE,wBAAgB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,SAAa,GAAG,MAAM,CAE1E"}
|
package/dist/core/work/state.js
CHANGED
|
@@ -4,6 +4,7 @@ import { hostname } from "node:os";
|
|
|
4
4
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
5
|
import { readWorkflowApproval } from "../workflow/approvals.js";
|
|
6
6
|
import { stableDigest } from "../workflow/durable-record.js";
|
|
7
|
+
import { WORKFLOW_JOURNAL_EVENT_BYTES, workflowJournalPath } from "../workflow/journal.js";
|
|
7
8
|
import { readWorkflowProof } from "../workflow/proof.js";
|
|
8
9
|
import { readWorkflowRunManifest, workflowScriptDigest } from "../workflow/run-state.js";
|
|
9
10
|
import { isWorkspaceBoundExecutionEvidence } from "../workflow/workspaces/validate.js";
|
|
@@ -142,6 +143,24 @@ export function cancelWorkItem(coordRoot, workId, input = {}) {
|
|
|
142
143
|
export function reopenWorkItem(coordRoot, workId, input = {}) {
|
|
143
144
|
return appendGovernanceEvent(coordRoot, workId, "work.reopened", ["blocked", "in_review", "succeeded", "cancelled"], input);
|
|
144
145
|
}
|
|
146
|
+
/** Findings raised by the most recent reopen that no later acceptance disposed
|
|
147
|
+
* of. Empty when the work was never reopened with findings. */
|
|
148
|
+
export function openOperatorFindings(coordRoot, workId) {
|
|
149
|
+
const events = readWorkEvents(coordRoot, workId);
|
|
150
|
+
let actor = "operator";
|
|
151
|
+
let open = [];
|
|
152
|
+
for (const event of events) {
|
|
153
|
+
if (event.event === "work.reopened" && event.findings?.length) {
|
|
154
|
+
actor = event.actor;
|
|
155
|
+
open = event.findings.map((finding) => ({ ...finding }));
|
|
156
|
+
}
|
|
157
|
+
if (event.event === "work.accepted" && event.dispositions?.length) {
|
|
158
|
+
const disposed = new Set(event.dispositions.map((entry) => entry.id));
|
|
159
|
+
open = open.filter((finding) => !disposed.has(finding.id));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return { actor, findings: open };
|
|
163
|
+
}
|
|
145
164
|
export function assertWorkId(workId) {
|
|
146
165
|
if (!WORK_ID.test(workId))
|
|
147
166
|
throw new Error(`invalid work id ${JSON.stringify(workId)}`);
|
|
@@ -248,6 +267,14 @@ function deriveWorkProjection(coordRoot, intent, events, ignoreLease = false) {
|
|
|
248
267
|
};
|
|
249
268
|
}
|
|
250
269
|
const attemptsRemaining = intent.max_attempts - attempts.length;
|
|
270
|
+
if (latest.status === "journal_unreadable") {
|
|
271
|
+
return {
|
|
272
|
+
...base,
|
|
273
|
+
state: "blocked",
|
|
274
|
+
reason: `workflow attempt ${latest.number} journal is unreadable: ${latest.journal_error ?? "unknown journal read error"}`,
|
|
275
|
+
next_action: attemptsRemaining > 0 ? "retry" : "none",
|
|
276
|
+
};
|
|
277
|
+
}
|
|
251
278
|
return {
|
|
252
279
|
...base,
|
|
253
280
|
state: "blocked",
|
|
@@ -309,22 +336,34 @@ function inspectAttempt(coordRoot, event, intent, priorRunId) {
|
|
|
309
336
|
: "failed";
|
|
310
337
|
return attempt;
|
|
311
338
|
}
|
|
312
|
-
const journalPath =
|
|
339
|
+
const journalPath = workflowJournalPath(coordRoot, runId);
|
|
313
340
|
if (existsSync(journalPath)) {
|
|
314
341
|
let parked;
|
|
315
342
|
let resumed = false;
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
parked
|
|
320
|
-
|
|
343
|
+
try {
|
|
344
|
+
for (const line of readBoundedLines(journalPath, MAX_EVENTS_BYTES, WORKFLOW_JOURNAL_EVENT_BYTES, "workflow journal")) {
|
|
345
|
+
const value = parseObject(line, `workflow run ${runId} journal`);
|
|
346
|
+
if (value.event === "run.parked" && typeof value.approval_id === "string") {
|
|
347
|
+
parked = value.approval_id;
|
|
348
|
+
resumed = false;
|
|
349
|
+
}
|
|
350
|
+
if (value.event === "run.resume")
|
|
351
|
+
resumed = true;
|
|
352
|
+
}
|
|
353
|
+
if (parked && !resumed) {
|
|
354
|
+
attempt.status = "parked";
|
|
355
|
+
attempt.approval_id = parked;
|
|
356
|
+
return attempt;
|
|
321
357
|
}
|
|
322
|
-
if (value.event === "run.resume")
|
|
323
|
-
resumed = true;
|
|
324
358
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
359
|
+
catch (error) {
|
|
360
|
+
if (workflowResumeLeaseIsLive(coordRoot, runId)) {
|
|
361
|
+
attempt.status = "running";
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
attempt.status = "journal_unreadable";
|
|
365
|
+
attempt.journal_error = boundedJournalError(error.message);
|
|
366
|
+
}
|
|
328
367
|
return attempt;
|
|
329
368
|
}
|
|
330
369
|
}
|
|
@@ -354,11 +393,17 @@ function appendGovernanceEvent(coordRoot, workId, event, allowed, input) {
|
|
|
354
393
|
const acceptance = event === "work.accepted"
|
|
355
394
|
? acceptanceAuthority(coordRoot, current, input.acceptedUnknowns ?? [])
|
|
356
395
|
: undefined;
|
|
396
|
+
const dispositions = event === "work.accepted"
|
|
397
|
+
? findingAuthority(coordRoot, workId, input.dispositions ?? [])
|
|
398
|
+
: undefined;
|
|
399
|
+
const findings = event === "work.reopened" ? normalizeRaisedFindings(input.findings) : undefined;
|
|
357
400
|
appendWorkEventUnderLease(coordRoot, workId, {
|
|
358
401
|
event,
|
|
359
402
|
actor: boundedActor(input.actor),
|
|
360
403
|
reason: boundedOptional(input.reason, "work reason", MAX_REASON),
|
|
361
404
|
...acceptance,
|
|
405
|
+
...(findings ? { findings } : {}),
|
|
406
|
+
...(dispositions ? { dispositions } : {}),
|
|
362
407
|
});
|
|
363
408
|
}
|
|
364
409
|
finally {
|
|
@@ -367,6 +412,51 @@ function appendGovernanceEvent(coordRoot, workId, event, allowed, input) {
|
|
|
367
412
|
}
|
|
368
413
|
return reconcileWorkItem(coordRoot, workId, input.actor);
|
|
369
414
|
}
|
|
415
|
+
/** Acceptance fails closed while an operator finding is open. The operator who
|
|
416
|
+
* raised it decides whether it was addressed; the team cannot mark its own. */
|
|
417
|
+
function findingAuthority(coordRoot, workId, dispositions) {
|
|
418
|
+
const open = openOperatorFindings(coordRoot, workId).findings;
|
|
419
|
+
if (open.length === 0) {
|
|
420
|
+
if (dispositions.length > 0) {
|
|
421
|
+
throw new Error(`work item ${workId} has no open operator finding to dispose`);
|
|
422
|
+
}
|
|
423
|
+
return [];
|
|
424
|
+
}
|
|
425
|
+
const byId = new Map(dispositions.map((entry) => [entry.id, entry]));
|
|
426
|
+
const undisposed = open.filter((finding) => !byId.has(finding.id));
|
|
427
|
+
if (undisposed.length > 0) {
|
|
428
|
+
throw new Error(`work item ${workId} cannot be accepted while operator findings are undisposed: ${undisposed
|
|
429
|
+
.map((finding) => finding.id)
|
|
430
|
+
.join(", ")}`);
|
|
431
|
+
}
|
|
432
|
+
const known = new Set(open.map((finding) => finding.id));
|
|
433
|
+
return dispositions.map((entry) => {
|
|
434
|
+
if (!known.has(entry.id)) {
|
|
435
|
+
throw new Error(`work item ${workId} has no open operator finding ${entry.id}`);
|
|
436
|
+
}
|
|
437
|
+
if (entry.outcome !== "fixed" && entry.outcome !== "deferred") {
|
|
438
|
+
throw new Error(`operator finding ${entry.id} disposition must be fixed or deferred`);
|
|
439
|
+
}
|
|
440
|
+
if (entry.outcome === "deferred" && !entry.reason?.trim()) {
|
|
441
|
+
throw new Error(`deferring operator finding ${entry.id} requires a reason`);
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
id: entry.id,
|
|
445
|
+
outcome: entry.outcome,
|
|
446
|
+
...(entry.reason ? { reason: boundedString(entry.reason, "disposition reason", 500) } : {}),
|
|
447
|
+
};
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
function normalizeRaisedFindings(statements) {
|
|
451
|
+
if (!statements || statements.length === 0)
|
|
452
|
+
return undefined;
|
|
453
|
+
if (statements.length > 20)
|
|
454
|
+
throw new Error("a reopen may raise at most 20 findings");
|
|
455
|
+
return statements.map((statement, index) => ({
|
|
456
|
+
id: `f${index + 1}`,
|
|
457
|
+
statement: boundedString(statement, `finding[${index}] statement`, 1_000),
|
|
458
|
+
}));
|
|
459
|
+
}
|
|
370
460
|
function acceptanceAuthority(coordRoot, current, acceptedUnknowns) {
|
|
371
461
|
const latest = current.projection.attempts.at(-1);
|
|
372
462
|
if (!latest?.proof_path)
|
|
@@ -484,7 +574,7 @@ function readWorkEvents(coordRoot, workId) {
|
|
|
484
574
|
const path = join(workDir(coordRoot, workId), "events.jsonl");
|
|
485
575
|
if (!existsSync(path))
|
|
486
576
|
return [];
|
|
487
|
-
const events = readBoundedLines(path, MAX_EVENTS_BYTES, `work item ${workId} events`).map((line, index) => {
|
|
577
|
+
const events = readBoundedLines(path, MAX_EVENTS_BYTES, MAX_EVENT_BYTES, `work item ${workId} events`).map((line, index) => {
|
|
488
578
|
const event = parseObject(line, `work item ${workId} event ${index + 1}`);
|
|
489
579
|
validateWorkEvent(event, workId, index + 1);
|
|
490
580
|
return event;
|
|
@@ -756,7 +846,7 @@ function writePrivateJson(path, value, maxBytes) {
|
|
|
756
846
|
writeFileSync(path, body, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
757
847
|
chmodSync(path, 0o600);
|
|
758
848
|
}
|
|
759
|
-
function readBoundedLines(path, maxBytes, field) {
|
|
849
|
+
function readBoundedLines(path, maxBytes, maxRecordBytes, field) {
|
|
760
850
|
const size = statSync(path).size;
|
|
761
851
|
if (size > maxBytes)
|
|
762
852
|
throw new Error(`${field} exceeds ${maxBytes} bytes`);
|
|
@@ -767,11 +857,15 @@ function readBoundedLines(path, maxBytes, field) {
|
|
|
767
857
|
if (lines.length > MAX_EVENTS)
|
|
768
858
|
throw new Error(`${field} exceeds ${MAX_EVENTS} records`);
|
|
769
859
|
for (const line of lines) {
|
|
770
|
-
if (Buffer.byteLength(line) >
|
|
860
|
+
if (Buffer.byteLength(line) > maxRecordBytes)
|
|
771
861
|
throw new Error(`${field} contains an oversized record`);
|
|
772
862
|
}
|
|
773
863
|
return lines;
|
|
774
864
|
}
|
|
865
|
+
function boundedJournalError(message) {
|
|
866
|
+
const normalized = message.trim() || "unknown journal read error";
|
|
867
|
+
return normalized.length > MAX_REASON ? normalized.slice(0, MAX_REASON) : normalized;
|
|
868
|
+
}
|
|
775
869
|
function parseObject(input, field) {
|
|
776
870
|
try {
|
|
777
871
|
const value = JSON.parse(input);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approvals.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/approvals.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"approvals.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/approvals.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { chmodSync, existsSync, linkSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
+
import { appendWorkflowJournalEvent } from "./journal.js";
|
|
4
5
|
export const WORKFLOW_APPROVAL_SCHEMA_VERSION = 1;
|
|
5
6
|
const RECORD_LIMIT_BYTES = 128 * 1024;
|
|
6
7
|
const RUN_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/;
|
|
@@ -136,19 +137,15 @@ function appendResolutionJournal(coordRoot, approval) {
|
|
|
136
137
|
const journalPath = join(coordRoot, ".harnery", "workflows", approval.request.run_id, "journal.jsonl");
|
|
137
138
|
if (!existsSync(journalPath) || !approval.decision)
|
|
138
139
|
return;
|
|
139
|
-
|
|
140
|
-
schema_version: 1,
|
|
141
|
-
run_id: approval.request.run_id,
|
|
140
|
+
appendWorkflowJournalEvent(coordRoot, approval.request.run_id, "approval.resolved", {
|
|
142
141
|
ts: approval.decision.decided_at,
|
|
143
|
-
event: "approval.resolved",
|
|
144
142
|
stage: "",
|
|
145
143
|
approval_id: approval.request.id,
|
|
146
144
|
decision_id: approval.request.decision_id,
|
|
147
145
|
verdict: approval.decision.verdict,
|
|
148
146
|
actor: approval.decision.actor,
|
|
149
147
|
reason: approval.decision.reason ?? null,
|
|
150
|
-
};
|
|
151
|
-
writeFileSync(journalPath, `${JSON.stringify(event)}\n`, { encoding: "utf8", flag: "a" });
|
|
148
|
+
});
|
|
152
149
|
}
|
|
153
150
|
function approvalRequestDigest(policySha256, request, evaluation) {
|
|
154
151
|
return createHash("sha256")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attempt-context.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/attempt-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,sBAAsB,
|
|
1
|
+
{"version":3,"file":"attempt-context.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/attempt-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,sBAAsB,EAKvB,MAAM,YAAY,CAAC;AAyBpB,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,sBAAsB,CAiCtF;AAmCD,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC,sBAAsB,CAAC,CAc7F;AAED,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,sBAAsB,CAOjG"}
|
|
@@ -5,7 +5,12 @@ const MAX_ATTEMPTS = 100;
|
|
|
5
5
|
const MAX_ERROR = 2_000;
|
|
6
6
|
const MAX_CRITERIA = 50;
|
|
7
7
|
const MAX_STATEMENT = 500;
|
|
8
|
-
const
|
|
8
|
+
const MAX_FINDINGS = 20;
|
|
9
|
+
const MAX_FINDING_STATEMENT = 1_000;
|
|
10
|
+
const MAX_FINDING_ACTOR = 100;
|
|
11
|
+
const FINDING_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
12
|
+
const CONTEXT_FIELDS = new Set(["schema_version", "number", "trigger", "prior", "findings"]);
|
|
13
|
+
const FINDING_FIELDS = new Set(["id", "actor", "statement"]);
|
|
9
14
|
const PRIOR_FIELDS = new Set(["run_id", "causes", "error", "acceptance", "unresolved"]);
|
|
10
15
|
const SUMMARY_FIELDS = new Set(["satisfied", "unsatisfied", "unknown", "total"]);
|
|
11
16
|
const CRITERION_FIELDS = new Set(["id", "statement", "status"]);
|
|
@@ -25,6 +30,7 @@ export function normalizeWorkflowAttemptContext(value) {
|
|
|
25
30
|
if (input.trigger !== "initial" && input.trigger !== "retry") {
|
|
26
31
|
throw new Error('workflow attempt context trigger must be "initial" or "retry"');
|
|
27
32
|
}
|
|
33
|
+
const findings = normalizeFindings(input.findings);
|
|
28
34
|
if (input.trigger === "initial") {
|
|
29
35
|
if (input.prior !== undefined) {
|
|
30
36
|
throw new Error("initial workflow attempt context cannot contain prior evidence");
|
|
@@ -33,6 +39,7 @@ export function normalizeWorkflowAttemptContext(value) {
|
|
|
33
39
|
schema_version: WORKFLOW_ATTEMPT_CONTEXT_SCHEMA_VERSION,
|
|
34
40
|
number,
|
|
35
41
|
trigger: "initial",
|
|
42
|
+
...(findings.length === 0 ? {} : { findings }),
|
|
36
43
|
};
|
|
37
44
|
}
|
|
38
45
|
if (number < 2)
|
|
@@ -45,8 +52,33 @@ export function normalizeWorkflowAttemptContext(value) {
|
|
|
45
52
|
number,
|
|
46
53
|
trigger: "retry",
|
|
47
54
|
prior: normalizePrior(input.prior),
|
|
55
|
+
...(findings.length === 0 ? {} : { findings }),
|
|
48
56
|
};
|
|
49
57
|
}
|
|
58
|
+
function normalizeFindings(value) {
|
|
59
|
+
if (value === undefined)
|
|
60
|
+
return [];
|
|
61
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_FINDINGS) {
|
|
62
|
+
throw new Error(`workflow attempt findings must contain 1 to ${MAX_FINDINGS} entries when present`);
|
|
63
|
+
}
|
|
64
|
+
const ids = new Set();
|
|
65
|
+
return value.map((candidate, index) => {
|
|
66
|
+
const input = objectValue(candidate, `workflow attempt finding[${index}]`);
|
|
67
|
+
rejectExtraFields(input, FINDING_FIELDS, `workflow attempt finding[${index}]`);
|
|
68
|
+
const id = requiredString(input.id, `workflow attempt finding[${index}].id`, 64);
|
|
69
|
+
if (!FINDING_ID.test(id)) {
|
|
70
|
+
throw new Error(`invalid workflow attempt finding id ${JSON.stringify(id)}`);
|
|
71
|
+
}
|
|
72
|
+
if (ids.has(id))
|
|
73
|
+
throw new Error(`duplicate workflow attempt finding id ${JSON.stringify(id)}`);
|
|
74
|
+
ids.add(id);
|
|
75
|
+
return {
|
|
76
|
+
id,
|
|
77
|
+
actor: requiredString(input.actor, `workflow attempt finding[${index}].actor`, MAX_FINDING_ACTOR),
|
|
78
|
+
statement: requiredString(input.statement, `workflow attempt finding[${index}].statement`, MAX_FINDING_STATEMENT),
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
}
|
|
50
82
|
export function freezeWorkflowAttemptContext(value) {
|
|
51
83
|
const normalized = normalizeWorkflowAttemptContext(value);
|
|
52
84
|
if (normalized.prior) {
|
|
@@ -57,6 +89,11 @@ export function freezeWorkflowAttemptContext(value) {
|
|
|
57
89
|
Object.freeze(normalized.prior.unresolved);
|
|
58
90
|
Object.freeze(normalized.prior);
|
|
59
91
|
}
|
|
92
|
+
if (normalized.findings) {
|
|
93
|
+
for (const finding of normalized.findings)
|
|
94
|
+
Object.freeze(finding);
|
|
95
|
+
Object.freeze(normalized.findings);
|
|
96
|
+
}
|
|
60
97
|
return Object.freeze(normalized);
|
|
61
98
|
}
|
|
62
99
|
export function isCanonicalWorkflowAttemptContext(value) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAyCH,OAAO,KAAK,EAEV,UAAU,EAGV,SAAS,EAYV,MAAM,YAAY,CAAC;AAsCpB,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAM/E;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAEjB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;CAOpF;AAED,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CA6E1F"}
|