harnery 0.43.0 → 0.44.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 +3 -3
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +41 -48
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +38 -11
- package/dist/commands/diagnostics.d.ts.map +1 -1
- package/dist/commands/diagnostics.js +10 -0
- package/dist/commands/doctor.d.ts +12 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +22 -7
- package/dist/commands/governor.js +22 -10
- package/dist/commands/inbox.d.ts +1 -1
- package/dist/commands/inbox.d.ts.map +1 -1
- package/dist/commands/inbox.js +55 -2
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +28 -4
- package/dist/commands/servers.d.ts.map +1 -1
- package/dist/commands/servers.js +16 -1
- package/dist/commands/supervisor.d.ts.map +1 -1
- package/dist/commands/supervisor.js +7 -2
- package/dist/commands/toc.d.ts +14 -0
- package/dist/commands/toc.d.ts.map +1 -1
- package/dist/commands/toc.js +7 -4
- package/dist/commands/workflow.js +24 -30
- package/dist/core/agents/live-authority-v3.d.ts +1 -0
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +11 -0
- package/dist/core/agents/live-lifecycle-v3.d.ts +4 -2
- package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
- package/dist/core/agents/live-lifecycle-v3.js +28 -7
- package/dist/core/agents/mailbox.d.ts +9 -8
- package/dist/core/agents/mailbox.d.ts.map +1 -1
- package/dist/core/agents/mailbox.js +24 -17
- package/dist/core/agents/render/prompt-context.d.ts +2 -0
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +8 -12
- package/dist/core/agents/render/session-context.d.ts +2 -8
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +7 -30
- package/dist/core/agents/rules/stop-hook.js +3 -3
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +25 -14
- package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-writer.js +5 -0
- package/dist/core/agents/state/pidmap.d.ts.map +1 -1
- package/dist/core/agents/state/pidmap.js +5 -0
- package/dist/core/agents/state/proc-start.d.ts +19 -0
- package/dist/core/agents/state/proc-start.d.ts.map +1 -1
- package/dist/core/agents/state/proc-start.js +41 -0
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +180 -70
- package/dist/core/agents/state/unchanged-file.d.ts +44 -0
- package/dist/core/agents/state/unchanged-file.d.ts.map +1 -0
- package/dist/core/agents/state/unchanged-file.js +390 -0
- package/dist/core/context/index.d.ts +22 -10
- package/dist/core/context/index.d.ts.map +1 -1
- package/dist/core/context/index.js +381 -119
- package/dist/core/context/observation.d.ts +26 -0
- package/dist/core/context/observation.d.ts.map +1 -0
- package/dist/core/context/observation.js +11 -0
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +25 -14
- package/dist/core/diagnostics/path-identity.d.ts +3 -0
- package/dist/core/diagnostics/path-identity.d.ts.map +1 -0
- package/dist/core/diagnostics/path-identity.js +12 -0
- package/dist/core/diagnostics/sanitize.d.ts.map +1 -1
- package/dist/core/diagnostics/sanitize.js +4 -3
- package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
- package/dist/core/events/v3/authority-outbox.js +29 -0
- package/dist/core/events/v3/generation-custody.d.ts +5 -0
- package/dist/core/events/v3/generation-custody.d.ts.map +1 -0
- package/dist/core/events/v3/generation-custody.js +50 -0
- package/dist/core/events/v3/producers/coordination-recorder.d.ts +1 -0
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/coordination-recorder.js +7 -0
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +77 -64
- package/dist/core/governor/planning.d.ts.map +1 -1
- package/dist/core/governor/planning.js +14 -1
- package/dist/core/governor/service-read.d.ts +10 -2
- package/dist/core/governor/service-read.d.ts.map +1 -1
- package/dist/core/governor/service-read.js +105 -22
- package/dist/core/governor/service.d.ts +20 -2
- package/dist/core/governor/service.d.ts.map +1 -1
- package/dist/core/governor/service.js +72 -47
- package/dist/core/hooks/cli.js +33 -5
- package/dist/core/inbox/contract.d.ts +2 -1
- package/dist/core/inbox/contract.d.ts.map +1 -1
- package/dist/core/inbox/index.d.ts +2 -0
- package/dist/core/inbox/index.d.ts.map +1 -1
- package/dist/core/inbox/index.js +2 -0
- package/dist/core/inbox/peer-messages.d.ts +37 -0
- package/dist/core/inbox/peer-messages.d.ts.map +1 -0
- package/dist/core/inbox/peer-messages.js +72 -0
- package/dist/core/inbox/service.d.ts +8 -1
- package/dist/core/inbox/service.d.ts.map +1 -1
- package/dist/core/inbox/service.js +67 -21
- package/dist/core/inbox/targets.d.ts +22 -0
- package/dist/core/inbox/targets.d.ts.map +1 -0
- package/dist/core/inbox/targets.js +108 -0
- package/dist/core/inbox/watcher.js +7 -3
- package/dist/core/journal/index.d.ts.map +1 -1
- package/dist/core/journal/index.js +17 -2
- package/dist/core/resources/contract.d.ts +3 -0
- package/dist/core/resources/contract.d.ts.map +1 -1
- package/dist/core/resources/service-status.d.ts +14 -1
- package/dist/core/resources/service-status.d.ts.map +1 -1
- package/dist/core/resources/service-status.js +23 -13
- package/dist/core/resources/service.d.ts +9 -1
- package/dist/core/resources/service.d.ts.map +1 -1
- package/dist/core/resources/service.js +47 -27
- package/dist/core/resources/status.d.ts +2 -5
- package/dist/core/resources/status.d.ts.map +1 -1
- package/dist/core/resources/status.js +36 -7
- package/dist/core/servers/index.d.ts +24 -13
- package/dist/core/servers/index.d.ts.map +1 -1
- package/dist/core/servers/index.js +132 -92
- package/dist/core/servers/tunnels.d.ts +3 -5
- package/dist/core/servers/tunnels.d.ts.map +1 -1
- package/dist/core/servers/tunnels.js +3 -9
- package/dist/core/supervisor/contract.d.ts +12 -0
- package/dist/core/supervisor/contract.d.ts.map +1 -1
- package/dist/core/supervisor/explanations.d.ts.map +1 -1
- package/dist/core/supervisor/explanations.js +2 -1
- package/dist/core/supervisor/findings.d.ts.map +1 -1
- package/dist/core/supervisor/findings.js +54 -9
- package/dist/core/supervisor/service.d.ts +9 -1
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +59 -29
- package/dist/core/supervisor/services.d.ts.map +1 -1
- package/dist/core/supervisor/services.js +22 -1
- package/dist/core/supervisor/status.d.ts +17 -2
- package/dist/core/supervisor/status.d.ts.map +1 -1
- package/dist/core/supervisor/status.js +70 -12
- package/dist/core/supervisor/timeline.d.ts.map +1 -1
- package/dist/core/supervisor/timeline.js +2 -1
- package/dist/core/work/state.js +7 -7
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +105 -32
- package/dist/core/workflow/failure-evidence.d.ts +16 -0
- package/dist/core/workflow/failure-evidence.d.ts.map +1 -0
- package/dist/core/workflow/failure-evidence.js +114 -0
- 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/live-session-v3.d.ts +6 -0
- package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
- package/dist/core/workflow/live-session-v3.js +126 -5
- package/dist/core/workflow/proof.d.ts +5 -5
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +115 -20
- package/dist/core/workflow/run-state.d.ts +3 -1
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +31 -27
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +19 -0
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +13 -0
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +19 -0
- package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
- package/dist/core/workflow/spawn-opencode.js +14 -0
- package/dist/core/workflow/types.d.ts +64 -17
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/index.d.ts +0 -1
- package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/index.js +0 -1
- package/dist/core/workflow/workspaces/leases.d.ts +3 -1
- package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.js +30 -16
- package/dist/core/workflow/workspaces/local-git.d.ts +6 -2
- package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.js +270 -6
- package/dist/core/workflow/workspaces/ownership.d.ts +11 -0
- package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -0
- package/dist/core/workflow/workspaces/ownership.js +194 -0
- package/dist/core/workflow/workspaces/reclaim.d.ts +35 -0
- package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/reclaim.js +14 -0
- package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/recovery.js +21 -13
- package/dist/core/workflow/workspaces/state.d.ts +1 -0
- package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.js +21 -1
- package/dist/lib/browser/windows-chrome.d.ts +100 -12
- package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
- package/dist/lib/browser/windows-chrome.js +262 -73
- package/dist/lib/docs-links.d.ts.map +1 -1
- package/dist/lib/docs-links.js +45 -2
- package/dist/lib/guarded-remove.d.ts +7 -0
- package/dist/lib/guarded-remove.d.ts.map +1 -1
- package/dist/lib/guarded-remove.js +106 -12
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +4 -0
- package/dist/lib/zip/index.d.ts +41 -0
- package/dist/lib/zip/index.d.ts.map +1 -0
- package/dist/lib/zip/index.js +398 -0
- package/package.json +7 -1
- package/src/commands/agents.ts +54 -63
- package/src/commands/browse.ts +50 -14
- package/src/commands/diagnostics.ts +10 -0
- package/src/commands/doctor.ts +26 -7
- package/src/commands/governor.ts +23 -10
- package/src/commands/inbox.ts +52 -2
- package/src/commands/rm.ts +43 -4
- package/src/commands/servers.ts +16 -4
- package/src/commands/supervisor.ts +7 -2
- package/src/commands/toc.ts +14 -6
- package/src/commands/workflow.ts +30 -30
- package/src/core/agents/live-authority-v3.ts +17 -0
- package/src/core/agents/live-lifecycle-v3.ts +52 -14
- package/src/core/agents/mailbox.ts +26 -17
- package/src/core/agents/render/prompt-context.ts +10 -12
- package/src/core/agents/render/session-context.ts +9 -32
- package/src/core/agents/rules/stop-hook.ts +3 -3
- package/src/core/agents/state/heartbeat-writer.ts +29 -12
- package/src/core/agents/state/live-coordination-writer.ts +29 -0
- package/src/core/agents/state/pidmap.ts +13 -0
- package/src/core/agents/state/proc-start.ts +57 -0
- package/src/core/agents/state/stale-sweep.ts +247 -82
- package/src/core/agents/state/unchanged-file.ts +456 -0
- package/src/core/context/index.ts +446 -136
- package/src/core/context/observation.ts +29 -0
- package/src/core/diagnostics/bundle.ts +23 -18
- package/src/core/diagnostics/path-identity.ts +15 -0
- package/src/core/diagnostics/sanitize.ts +4 -3
- package/src/core/events/v3/authority-outbox.ts +56 -0
- package/src/core/events/v3/generation-custody.ts +59 -0
- package/src/core/events/v3/producers/coordination-recorder.ts +15 -0
- package/src/core/events/v3/producers/recorder.ts +94 -68
- package/src/core/governor/planning.ts +20 -8
- package/src/core/governor/service-read.ts +128 -20
- package/src/core/governor/service.ts +119 -42
- package/src/core/hooks/cli.ts +33 -5
- package/src/core/inbox/contract.ts +2 -1
- package/src/core/inbox/index.ts +2 -0
- package/src/core/inbox/peer-messages.ts +94 -0
- package/src/core/inbox/service.ts +88 -22
- package/src/core/inbox/targets.ts +148 -0
- package/src/core/inbox/watcher.ts +8 -9
- package/src/core/journal/index.ts +19 -2
- package/src/core/resources/contract.ts +4 -0
- package/src/core/resources/service-status.ts +35 -12
- package/src/core/resources/service.ts +71 -24
- package/src/core/resources/status.ts +57 -8
- package/src/core/servers/index.ts +177 -91
- package/src/core/servers/tunnels.ts +3 -11
- package/src/core/supervisor/contract.ts +13 -0
- package/src/core/supervisor/explanations.ts +4 -1
- package/src/core/supervisor/findings.ts +68 -10
- package/src/core/supervisor/service.ts +88 -26
- package/src/core/supervisor/services.ts +26 -8
- package/src/core/supervisor/status.ts +89 -12
- package/src/core/supervisor/timeline.ts +2 -1
- package/src/core/work/state.ts +7 -7
- package/src/core/workflow/engine.ts +131 -49
- package/src/core/workflow/failure-evidence.ts +141 -0
- package/src/core/workflow/index.ts +0 -1
- package/src/core/workflow/live-session-v3.ts +146 -5
- package/src/core/workflow/proof.ts +134 -26
- package/src/core/workflow/run-state.ts +31 -29
- package/src/core/workflow/spawn-claude.ts +18 -0
- package/src/core/workflow/spawn-codex.ts +13 -0
- package/src/core/workflow/spawn-cursor.ts +18 -0
- package/src/core/workflow/spawn-opencode.ts +14 -0
- package/src/core/workflow/types.ts +51 -17
- package/src/core/workflow/workspaces/index.ts +0 -1
- package/src/core/workflow/workspaces/leases.ts +48 -17
- package/src/core/workflow/workspaces/local-git.ts +315 -5
- package/src/core/workflow/workspaces/ownership.ts +232 -0
- package/src/core/workflow/workspaces/reclaim.ts +58 -0
- package/src/core/workflow/workspaces/recovery.ts +25 -13
- package/src/core/workflow/workspaces/state.ts +20 -0
- package/src/lib/browser/windows-chrome.ts +346 -86
- package/src/lib/docs-links.ts +49 -2
- package/src/lib/guarded-remove.ts +132 -14
- package/src/lib/identities/assume.ts +4 -0
- package/src/lib/zip/index.ts +483 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import type
|
|
3
|
+
import { isAbsolute, join } from "node:path";
|
|
4
|
+
import { observationsComparable, type RepoSnapshot } from "../context/observation.ts";
|
|
5
5
|
import { DIAGNOSTIC_ADVICE_SCHEMA_VERSION } from "../diagnostics/contract.ts";
|
|
6
6
|
import type {
|
|
7
7
|
NormalizedPolicy,
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
import { policyDigest } from "../policy/index.ts";
|
|
13
13
|
import { isCanonicalWorkflowAttemptContext } from "./attempt-context.ts";
|
|
14
14
|
import { stableDigest, writeImmutableJson } from "./durable-record.ts";
|
|
15
|
+
import { failureEvidenceAllowsUncharged, isSpawnFailureEvidence } from "./failure-evidence.ts";
|
|
15
16
|
import { readWorkflowRunManifest } from "./run-state.ts";
|
|
16
17
|
import type {
|
|
17
18
|
AcceptanceCriterion,
|
|
@@ -242,17 +243,25 @@ export function digestResult(value: unknown, kind?: "text" | "json"): ResultDige
|
|
|
242
243
|
* attempt was informative about the work — charge it. This also keeps a
|
|
243
244
|
* resumed run whose earlier segment did real work from being written off by
|
|
244
245
|
* a later environment failure.
|
|
245
|
-
* 3.
|
|
246
|
-
*
|
|
247
|
-
* hard stop.
|
|
246
|
+
* 3. Every participant must have failed with positive no-effect evidence,
|
|
247
|
+
* observed absent output and no refusal. A class alone is insufficient.
|
|
248
|
+
* Among those eligible failures, environment is the hard stop.
|
|
248
249
|
* 4. Anything else is undefined ⇒ a charged work failure, exactly as today.
|
|
249
250
|
*/
|
|
250
251
|
export function deriveRunFailureClass(
|
|
251
252
|
status: "succeeded" | "failed",
|
|
252
|
-
agents: readonly Pick<WorkflowAgentProof, "status" | "class">[],
|
|
253
|
+
agents: readonly Pick<WorkflowAgentProof, "status" | "class" | "failure_evidence">[],
|
|
253
254
|
): SpawnFailureClass | undefined {
|
|
254
255
|
if (status !== "failed") return undefined;
|
|
255
|
-
if (
|
|
256
|
+
if (
|
|
257
|
+
agents.length === 0 ||
|
|
258
|
+
agents.some(
|
|
259
|
+
(agent) =>
|
|
260
|
+
agent.status !== "failed" ||
|
|
261
|
+
(agent.class !== "environment" && agent.class !== "upstream") ||
|
|
262
|
+
!failureEvidenceAllowsUncharged(agent.failure_evidence),
|
|
263
|
+
)
|
|
264
|
+
) {
|
|
256
265
|
return undefined;
|
|
257
266
|
}
|
|
258
267
|
const failed = agents.filter((agent) => agent.status === "failed");
|
|
@@ -385,6 +394,13 @@ export function readWorkflowProof(coordRoot: string, runId: string): WorkflowPro
|
|
|
385
394
|
proof.run.class !== "environment" &&
|
|
386
395
|
proof.run.class !== "upstream" &&
|
|
387
396
|
proof.run.class !== "decision") ||
|
|
397
|
+
!Array.isArray(proof.agents) ||
|
|
398
|
+
proof.agents.some(
|
|
399
|
+
(agent) =>
|
|
400
|
+
!agent ||
|
|
401
|
+
typeof agent !== "object" ||
|
|
402
|
+
(agent.failure_evidence !== undefined && !isSpawnFailureEvidence(agent.failure_evidence)),
|
|
403
|
+
) ||
|
|
388
404
|
(proof.run.work_context !== undefined &&
|
|
389
405
|
(!proof.run.work_item_id ||
|
|
390
406
|
proof.run.work_context.id !== proof.run.work_item_id ||
|
|
@@ -399,6 +415,22 @@ export function readWorkflowProof(coordRoot: string, runId: string): WorkflowPro
|
|
|
399
415
|
) {
|
|
400
416
|
throw new Error(`workflow proof at ${path} has an unsupported or mismatched schema`);
|
|
401
417
|
}
|
|
418
|
+
if (
|
|
419
|
+
repoComparisonProblem(proof.repository.before, proof.repository.after) !== undefined ||
|
|
420
|
+
proof.repository.before.dirty_paths_truncated ||
|
|
421
|
+
proof.repository.after.dirty_paths_truncated ||
|
|
422
|
+
!proof.repository.before.branch?.trim() ||
|
|
423
|
+
!proof.repository.after.branch?.trim() ||
|
|
424
|
+
!proof.repository.before.head?.trim() ||
|
|
425
|
+
!proof.repository.after.head?.trim()
|
|
426
|
+
) {
|
|
427
|
+
proof.repository = buildRepoEvidence(proof.repository.before, proof.repository.after);
|
|
428
|
+
proof.unknowns = proof.unknowns.filter((item) => item.code !== "repository_drift_incomplete");
|
|
429
|
+
proof.unknowns.push({
|
|
430
|
+
code: "repository_drift_incomplete",
|
|
431
|
+
message: proof.repository.drift.note!,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
402
434
|
const manifestPath = join(coordRoot, ".harnery", "workflows", runId, "run.json");
|
|
403
435
|
if (proof.execution === undefined && !existsSync(manifestPath)) return proof;
|
|
404
436
|
const manifest = readWorkflowRunManifest(coordRoot, runId);
|
|
@@ -512,13 +544,31 @@ export function renderWorkflowProof(proof: WorkflowProof): string {
|
|
|
512
544
|
: "admission: shadow not needed; no real child dispatch",
|
|
513
545
|
);
|
|
514
546
|
}
|
|
515
|
-
const
|
|
547
|
+
const comparable =
|
|
548
|
+
repoComparisonProblem(proof.repository.before, proof.repository.after) === undefined;
|
|
549
|
+
const repo =
|
|
550
|
+
comparable &&
|
|
551
|
+
!proof.repository.before.dirty_paths_truncated &&
|
|
552
|
+
!proof.repository.after.dirty_paths_truncated &&
|
|
553
|
+
proof.repository.before.branch?.trim() &&
|
|
554
|
+
proof.repository.after.branch?.trim() &&
|
|
555
|
+
proof.repository.before.head?.trim() &&
|
|
556
|
+
proof.repository.after.head?.trim()
|
|
557
|
+
? proof.repository
|
|
558
|
+
: buildRepoEvidence(proof.repository.before, proof.repository.after);
|
|
516
559
|
const drift = repo.drift;
|
|
517
|
-
|
|
518
|
-
`repository:
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
560
|
+
if (!comparable) {
|
|
561
|
+
lines.push(`repository: drift unknown. ${drift.note}`);
|
|
562
|
+
} else {
|
|
563
|
+
const paths =
|
|
564
|
+
drift.dirty_paths_added === null || drift.dirty_paths_cleared === null
|
|
565
|
+
? "dirty-path drift unknown"
|
|
566
|
+
: `${drift.dirty_paths_added.length} dirty added, ${drift.dirty_paths_cleared.length} cleared`;
|
|
567
|
+
lines.push(
|
|
568
|
+
`repository: branch ${repo.before.branch ?? "unknown"} -> ${repo.after.branch ?? "unknown"}; ` +
|
|
569
|
+
`HEAD ${short(repo.before.head)} -> ${short(repo.after.head)}; ${paths}`,
|
|
570
|
+
);
|
|
571
|
+
}
|
|
522
572
|
if (proof.unknowns.length > 0) {
|
|
523
573
|
lines.push(`unknowns: ${proof.unknowns.length}`);
|
|
524
574
|
for (const unknown of proof.unknowns) lines.push(` - ${unknown.message}`);
|
|
@@ -530,26 +580,56 @@ export function renderWorkflowProof(proof: WorkflowProof): string {
|
|
|
530
580
|
function buildRepoEvidence(beforeRaw: RepoSnapshot, afterRaw: RepoSnapshot): WorkflowRepoEvidence {
|
|
531
581
|
const before = normalizeRepoSnapshot(beforeRaw);
|
|
532
582
|
const after = normalizeRepoSnapshot(afterRaw);
|
|
583
|
+
const problem = repoComparisonProblem(before, after);
|
|
584
|
+
if (problem !== undefined) {
|
|
585
|
+
return {
|
|
586
|
+
source: "engine",
|
|
587
|
+
before,
|
|
588
|
+
after,
|
|
589
|
+
drift: {
|
|
590
|
+
branch_changed: null,
|
|
591
|
+
head_changed: null,
|
|
592
|
+
dirty_paths_added: null,
|
|
593
|
+
dirty_paths_cleared: null,
|
|
594
|
+
dirty_paths_retained: null,
|
|
595
|
+
incomplete: true,
|
|
596
|
+
note: `Repository drift could not be verified: ${problem}. Re-check before editing.`,
|
|
597
|
+
},
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
const pathsComparable = !before.dirty_paths_truncated && !after.dirty_paths_truncated;
|
|
601
|
+
const branchKnown = Boolean(before.branch && after.branch);
|
|
602
|
+
const headKnown = Boolean(before.head && after.head);
|
|
533
603
|
const beforeDirty = new Set(before.dirty_paths);
|
|
534
604
|
const afterDirty = new Set(after.dirty_paths);
|
|
535
605
|
const retained = after.dirty_paths.filter((path) => beforeDirty.has(path));
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
606
|
+
const notes = [
|
|
607
|
+
...(!pathsComparable
|
|
608
|
+
? ["Dirty-path drift is unknown because a snapshot list was truncated."]
|
|
609
|
+
: []),
|
|
610
|
+
...(!branchKnown ? ["Branch drift is unknown because a snapshot has no branch."] : []),
|
|
611
|
+
...(!headKnown ? ["HEAD drift is unknown because a snapshot has no HEAD."] : []),
|
|
612
|
+
...(pathsComparable && retained.length > 0
|
|
613
|
+
? ["Snapshots cannot prove whether retained dirty paths changed during the run."]
|
|
614
|
+
: []),
|
|
615
|
+
];
|
|
616
|
+
const incomplete = notes.length > 0;
|
|
539
617
|
return {
|
|
540
618
|
source: "engine",
|
|
541
619
|
before,
|
|
542
620
|
after,
|
|
543
621
|
drift: {
|
|
544
|
-
branch_changed: before.branch !== after.branch,
|
|
545
|
-
head_changed: before.head !== after.head,
|
|
546
|
-
dirty_paths_added:
|
|
547
|
-
|
|
548
|
-
|
|
622
|
+
branch_changed: branchKnown ? before.branch !== after.branch : null,
|
|
623
|
+
head_changed: headKnown ? before.head !== after.head : null,
|
|
624
|
+
dirty_paths_added: pathsComparable
|
|
625
|
+
? after.dirty_paths.filter((path) => !beforeDirty.has(path))
|
|
626
|
+
: null,
|
|
627
|
+
dirty_paths_cleared: pathsComparable
|
|
628
|
+
? before.dirty_paths.filter((path) => !afterDirty.has(path))
|
|
629
|
+
: null,
|
|
630
|
+
dirty_paths_retained: pathsComparable ? retained : null,
|
|
549
631
|
incomplete,
|
|
550
|
-
note: incomplete
|
|
551
|
-
? "Snapshots cannot prove whether retained dirty paths changed during the run, and truncated lists may omit paths."
|
|
552
|
-
: undefined,
|
|
632
|
+
note: incomplete ? notes.join(" ") : undefined,
|
|
553
633
|
},
|
|
554
634
|
};
|
|
555
635
|
}
|
|
@@ -557,14 +637,42 @@ function buildRepoEvidence(beforeRaw: RepoSnapshot, afterRaw: RepoSnapshot): Wor
|
|
|
557
637
|
function normalizeRepoSnapshot(snapshot: RepoSnapshot): WorkflowRepoSnapshot {
|
|
558
638
|
return {
|
|
559
639
|
cwd: snapshot.cwd,
|
|
640
|
+
observation: snapshot.observation ?? "unavailable",
|
|
641
|
+
observation_detail: snapshot.observation
|
|
642
|
+
? clippedOptional(snapshot.observation_detail, 500)
|
|
643
|
+
: "No repository observation was recorded.",
|
|
560
644
|
root: snapshot.root,
|
|
561
|
-
branch: snapshot.branch,
|
|
562
|
-
head: snapshot.head,
|
|
645
|
+
branch: snapshot.branch?.trim() || undefined,
|
|
646
|
+
head: snapshot.head?.trim() || undefined,
|
|
563
647
|
dirty_paths: snapshot.dirty_paths,
|
|
564
648
|
dirty_paths_truncated: snapshot.dirty_paths_truncated,
|
|
565
649
|
};
|
|
566
650
|
}
|
|
567
651
|
|
|
652
|
+
function snapshotFailure(label: string, snapshot: WorkflowRepoSnapshot): string {
|
|
653
|
+
return `${label} snapshot is ${snapshot.observation}${snapshot.observation_detail ? ` (${snapshot.observation_detail})` : ""}`;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function repoComparisonProblem(
|
|
657
|
+
before: WorkflowRepoSnapshot,
|
|
658
|
+
after: WorkflowRepoSnapshot,
|
|
659
|
+
): string | undefined {
|
|
660
|
+
if (!observationsComparable(before, after)) {
|
|
661
|
+
return [
|
|
662
|
+
...(before.observation !== "ok" ? [snapshotFailure("before", before)] : []),
|
|
663
|
+
...(after.observation !== "ok" ? [snapshotFailure("after", after)] : []),
|
|
664
|
+
].join("; ");
|
|
665
|
+
}
|
|
666
|
+
const unknownRoots = [
|
|
667
|
+
...(!before.root || !isAbsolute(before.root) ? ["before"] : []),
|
|
668
|
+
...(!after.root || !isAbsolute(after.root) ? ["after"] : []),
|
|
669
|
+
];
|
|
670
|
+
if (unknownRoots.length > 0)
|
|
671
|
+
return `${unknownRoots.join(" and ")} snapshot repository root is unknown`;
|
|
672
|
+
if (before.root !== after.root) return "snapshots name different repository roots";
|
|
673
|
+
return undefined;
|
|
674
|
+
}
|
|
675
|
+
|
|
568
676
|
function buildAdapterCoverage(
|
|
569
677
|
agents: WorkflowAgentProof[],
|
|
570
678
|
claims: Readonly<Record<string, AdapterEvidenceCapability | undefined>> | undefined,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import {
|
|
3
3
|
chmodSync,
|
|
4
4
|
closeSync,
|
|
@@ -47,7 +47,6 @@ import {
|
|
|
47
47
|
export const WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION = 2 as const;
|
|
48
48
|
|
|
49
49
|
const MANIFEST_LIMIT_BYTES = 256 * 1024;
|
|
50
|
-
const FOREIGN_LEASE_STALE_MS = 24 * 60 * 60 * 1_000;
|
|
51
50
|
|
|
52
51
|
export interface WorkflowRunManifest {
|
|
53
52
|
schema_version: typeof WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION;
|
|
@@ -144,6 +143,13 @@ export function readWorkflowRunManifest(coordRoot: string, runId: string): Workf
|
|
|
144
143
|
) {
|
|
145
144
|
throw new Error(`workflow run manifest at ${path} has an unsupported or mismatched schema`);
|
|
146
145
|
}
|
|
146
|
+
if (manifest.repository_before.observation === undefined) {
|
|
147
|
+
manifest.repository_before = {
|
|
148
|
+
...manifest.repository_before,
|
|
149
|
+
observation: "unavailable",
|
|
150
|
+
observation_detail: "No repository observation was recorded.",
|
|
151
|
+
};
|
|
152
|
+
}
|
|
147
153
|
assertWorkspaceManifestAuthority(manifest, readManifestWorkspaceRequest(coordRoot, runId));
|
|
148
154
|
return manifest;
|
|
149
155
|
}
|
|
@@ -197,18 +203,26 @@ export function workflowRunManifestPath(coordRoot: string, runId: string): strin
|
|
|
197
203
|
return join(coordRoot, ".harnery", "workflows", runId, "run.json");
|
|
198
204
|
}
|
|
199
205
|
|
|
200
|
-
/** Acquire an exclusive
|
|
206
|
+
/** Acquire an exclusive lease shared by resume and reclaim. Existing locks
|
|
207
|
+
* always refuse: a stale observation cannot authorize unlinking a new owner.
|
|
208
|
+
* Crash recovery requires an audited ownership check outside this operation. */
|
|
201
209
|
export function acquireWorkflowResumeLease(coordRoot: string, runId: string): () => void {
|
|
202
210
|
assertWorkflowRunId(runId);
|
|
203
211
|
const path = join(coordRoot, ".harnery", "workflows", runId, "resume.lock");
|
|
204
|
-
const owner = {
|
|
212
|
+
const owner = {
|
|
213
|
+
owner_id: randomUUID(),
|
|
214
|
+
pid: process.pid,
|
|
215
|
+
host: hostname(),
|
|
216
|
+
created_at: new Date().toISOString(),
|
|
217
|
+
};
|
|
218
|
+
const ownerBytes = `${JSON.stringify(owner)}\n`;
|
|
205
219
|
mkdirSync(dirname(path), { recursive: true });
|
|
206
220
|
|
|
207
221
|
const acquire = (): boolean => {
|
|
208
222
|
try {
|
|
209
223
|
const fd = openSync(path, "wx", stateFileMode());
|
|
210
224
|
try {
|
|
211
|
-
writeFileSync(fd,
|
|
225
|
+
writeFileSync(fd, ownerBytes, "utf8");
|
|
212
226
|
} finally {
|
|
213
227
|
closeSync(fd);
|
|
214
228
|
}
|
|
@@ -222,22 +236,17 @@ export function acquireWorkflowResumeLease(coordRoot: string, runId: string): ()
|
|
|
222
236
|
|
|
223
237
|
if (!acquire()) {
|
|
224
238
|
const existing = readResumeLease(path);
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
unlinkSync(path);
|
|
231
|
-
if (!acquire())
|
|
232
|
-
throw new Error(`workflow run ${runId} resume lease raced with another process`);
|
|
239
|
+
const holder = existing
|
|
240
|
+
? ` by pid ${existing.pid} on ${existing.host}; its liveness has not been verified`
|
|
241
|
+
: "; its owner could not be verified";
|
|
242
|
+
throw new Error(`workflow run ${runId} resume lease is already held${holder}`);
|
|
233
243
|
}
|
|
234
244
|
|
|
235
245
|
return () => {
|
|
236
246
|
try {
|
|
237
|
-
|
|
238
|
-
if (existing?.pid === owner.pid && existing.host === owner.host) unlinkSync(path);
|
|
247
|
+
if (readFileSync(path, "utf8") === ownerBytes) unlinkSync(path);
|
|
239
248
|
} catch {
|
|
240
|
-
//
|
|
249
|
+
// Leave an unreadable or replaced lock untouched.
|
|
241
250
|
}
|
|
242
251
|
};
|
|
243
252
|
}
|
|
@@ -257,19 +266,6 @@ function readResumeLease(path: string): { pid: number; host: string; created_at:
|
|
|
257
266
|
}
|
|
258
267
|
}
|
|
259
268
|
|
|
260
|
-
function resumeLeaseIsLive(lease: { pid: number; host: string; created_at: string }): boolean {
|
|
261
|
-
if (lease.host !== hostname()) {
|
|
262
|
-
const age = Date.now() - Date.parse(lease.created_at);
|
|
263
|
-
return Number.isFinite(age) && age < FOREIGN_LEASE_STALE_MS;
|
|
264
|
-
}
|
|
265
|
-
try {
|
|
266
|
-
process.kill(lease.pid, 0);
|
|
267
|
-
return true;
|
|
268
|
-
} catch (error) {
|
|
269
|
-
return (error as NodeJS.ErrnoException).code === "EPERM";
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
269
|
function validExecution(value: WorkflowRunManifest["execution"]): boolean {
|
|
274
270
|
if (!value || typeof value !== "object") return false;
|
|
275
271
|
return (
|
|
@@ -352,6 +348,12 @@ function validRepoSnapshot(value: RepoSnapshot): boolean {
|
|
|
352
348
|
return (
|
|
353
349
|
Boolean(value) &&
|
|
354
350
|
typeof value === "object" &&
|
|
351
|
+
(value.observation === undefined ||
|
|
352
|
+
value.observation === "ok" ||
|
|
353
|
+
value.observation === "partial" ||
|
|
354
|
+
value.observation === "unavailable") &&
|
|
355
|
+
(value.observation_detail === undefined ||
|
|
356
|
+
(typeof value.observation_detail === "string" && value.observation_detail.length <= 500)) &&
|
|
355
357
|
isAbsolute(value.cwd) &&
|
|
356
358
|
(value.root === undefined || isAbsolute(value.root)) &&
|
|
357
359
|
(value.branch === undefined || typeof value.branch === "string") &&
|
|
@@ -23,6 +23,7 @@ import { builtinAdapterProfile, validateAdapterEffort } from "../adapters/profil
|
|
|
23
23
|
import type { AdapterInvocation, AdapterRawResult } from "../adapters/types.ts";
|
|
24
24
|
import { notFoundError } from "./adapters.ts";
|
|
25
25
|
import { buildChildEnv } from "./child-env.ts";
|
|
26
|
+
import { observeSpawnFailure } from "./failure-evidence.ts";
|
|
26
27
|
import { resolveFullAccessArgv, resolveSandboxProjection } from "./sandbox-projection.ts";
|
|
27
28
|
import { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.ts";
|
|
28
29
|
import type { Spawner, SpawnRequest, SpawnResult } from "./types.ts";
|
|
@@ -66,6 +67,23 @@ export function buildClaudeInvocation(req: SpawnRequest): AdapterInvocation {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
export function normalizeClaudeResult(raw: AdapterRawResult): SpawnResult {
|
|
70
|
+
const result = normalizeClaudeTransportResult(raw);
|
|
71
|
+
if (result.ok) return result;
|
|
72
|
+
let text = raw.stdout.trim();
|
|
73
|
+
let source: "stdout" | "vendor_envelope" = "stdout";
|
|
74
|
+
try {
|
|
75
|
+
const envelope = JSON.parse(raw.stdout) as ClaudeEnvelope;
|
|
76
|
+
if (typeof envelope.result === "string" && envelope.result.length > 0) {
|
|
77
|
+
text = envelope.result;
|
|
78
|
+
source = "vendor_envelope";
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
// Retain unstructured output without treating it as native authority.
|
|
82
|
+
}
|
|
83
|
+
return { ...result, text, failureEvidence: observeSpawnFailure(raw, text, source) };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function normalizeClaudeTransportResult(raw: AdapterRawResult): SpawnResult {
|
|
69
87
|
if (raw.timedOut) {
|
|
70
88
|
return {
|
|
71
89
|
ok: false,
|
|
@@ -26,6 +26,7 @@ import { builtinAdapterProfile, validateAdapterEffort } from "../adapters/profil
|
|
|
26
26
|
import type { AdapterInvocation, AdapterRawResult } from "../adapters/types.ts";
|
|
27
27
|
import { notFoundError } from "./adapters.ts";
|
|
28
28
|
import { buildChildEnv } from "./child-env.ts";
|
|
29
|
+
import { observeSpawnFailure } from "./failure-evidence.ts";
|
|
29
30
|
import { resolveFullAccessArgv, resolveSandboxProjection } from "./sandbox-projection.ts";
|
|
30
31
|
import { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.ts";
|
|
31
32
|
import type { Spawner, SpawnRequest, SpawnResult } from "./types.ts";
|
|
@@ -78,6 +79,18 @@ export function buildCodexInvocation(req: SpawnRequest, resultFile?: string): Ad
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
export function normalizeCodexResult(raw: AdapterRawResult): SpawnResult {
|
|
82
|
+
const result = normalizeCodexTransportResult(raw);
|
|
83
|
+
if (result.ok) return result;
|
|
84
|
+
const finalText = raw.resultFileText?.trim();
|
|
85
|
+
const text = finalText || raw.stdout.trim();
|
|
86
|
+
return {
|
|
87
|
+
...result,
|
|
88
|
+
text,
|
|
89
|
+
failureEvidence: observeSpawnFailure(raw, text, finalText ? "final_message" : "stdout"),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function normalizeCodexTransportResult(raw: AdapterRawResult): SpawnResult {
|
|
81
94
|
if (raw.timedOut) {
|
|
82
95
|
return {
|
|
83
96
|
ok: false,
|
|
@@ -24,6 +24,7 @@ import { builtinAdapterProfile, validateAdapterEffort } from "../adapters/profil
|
|
|
24
24
|
import type { AdapterInvocation, AdapterRawResult } from "../adapters/types.ts";
|
|
25
25
|
import { notFoundError } from "./adapters.ts";
|
|
26
26
|
import { buildChildEnv } from "./child-env.ts";
|
|
27
|
+
import { observeSpawnFailure } from "./failure-evidence.ts";
|
|
27
28
|
import { resolveFullAccessArgv, resolveSandboxProjection } from "./sandbox-projection.ts";
|
|
28
29
|
import { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.ts";
|
|
29
30
|
import type { Spawner, SpawnRequest, SpawnResult } from "./types.ts";
|
|
@@ -78,6 +79,23 @@ export function buildCursorInvocation(req: SpawnRequest): AdapterInvocation {
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
export function normalizeCursorResult(raw: AdapterRawResult): SpawnResult {
|
|
82
|
+
const result = normalizeCursorTransportResult(raw);
|
|
83
|
+
if (result.ok) return result;
|
|
84
|
+
let text = raw.stdout;
|
|
85
|
+
let source: "stdout" | "vendor_envelope" = "stdout";
|
|
86
|
+
try {
|
|
87
|
+
const envelope = JSON.parse(raw.stdout) as CursorEnvelope;
|
|
88
|
+
if (typeof envelope.result === "string" && envelope.result.length > 0) {
|
|
89
|
+
text = envelope.result;
|
|
90
|
+
source = "vendor_envelope";
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
// Raw output remains evidence, not an inferred refusal or effect result.
|
|
94
|
+
}
|
|
95
|
+
return { ...result, text, failureEvidence: observeSpawnFailure(raw, text, source) };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function normalizeCursorTransportResult(raw: AdapterRawResult): SpawnResult {
|
|
81
99
|
if (raw.timedOut) {
|
|
82
100
|
return {
|
|
83
101
|
ok: false,
|
|
@@ -22,6 +22,7 @@ import { builtinAdapterProfile, validateAdapterEffort } from "../adapters/profil
|
|
|
22
22
|
import type { AdapterInvocation, AdapterRawResult } from "../adapters/types.ts";
|
|
23
23
|
import { notFoundError } from "./adapters.ts";
|
|
24
24
|
import { buildChildEnv } from "./child-env.ts";
|
|
25
|
+
import { observeSpawnFailure } from "./failure-evidence.ts";
|
|
25
26
|
import { resolveFullAccessArgv, resolveSandboxProjection } from "./sandbox-projection.ts";
|
|
26
27
|
import { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.ts";
|
|
27
28
|
import type { Spawner, SpawnRequest, SpawnResult } from "./types.ts";
|
|
@@ -88,6 +89,19 @@ export function buildOpenCodeInvocation(req: SpawnRequest): AdapterInvocation {
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
export function normalizeOpenCodeResult(raw: AdapterRawResult): SpawnResult {
|
|
92
|
+
const result = normalizeOpenCodeTransportResult(raw);
|
|
93
|
+
if (result.ok) return result;
|
|
94
|
+
const parsed = parseOpenCodeStream(raw.stdout);
|
|
95
|
+
const text = parsed.text || raw.stdout.trim();
|
|
96
|
+
return {
|
|
97
|
+
...result,
|
|
98
|
+
text,
|
|
99
|
+
sessionId: parsed.sessionId,
|
|
100
|
+
failureEvidence: observeSpawnFailure(raw, text, parsed.text ? "vendor_envelope" : "stdout"),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function normalizeOpenCodeTransportResult(raw: AdapterRawResult): SpawnResult {
|
|
91
105
|
if (raw.timedOut) {
|
|
92
106
|
return {
|
|
93
107
|
ok: false,
|
|
@@ -137,11 +137,12 @@ export interface WorkflowAgentProof {
|
|
|
137
137
|
session_id?: string;
|
|
138
138
|
result?: ResultDigest;
|
|
139
139
|
error?: string;
|
|
140
|
-
/**
|
|
141
|
-
*
|
|
142
|
-
* Absent ⇒ a work failure. Recorded even when a script's parallel() swallows
|
|
143
|
-
* the rejection, so the run-level class can still be derived from proof. */
|
|
140
|
+
/** Failed-spawn transport class. It is retained even when a script swallows
|
|
141
|
+
* the rejection; charging additionally requires validated failure evidence. */
|
|
144
142
|
class?: SpawnFailureClass;
|
|
143
|
+
/** Failure observations carried from the adapter. Missing observations are
|
|
144
|
+
* unknown and cannot authorize an uncharged attempt. */
|
|
145
|
+
failure_evidence?: SpawnFailureEvidence;
|
|
145
146
|
/** Which vendor sandbox mode this agent launched with and why (ADR 0192).
|
|
146
147
|
* Absent on a cached agent, which launched nothing. */
|
|
147
148
|
access?: WorkerAccessDecision;
|
|
@@ -149,6 +150,9 @@ export interface WorkflowAgentProof {
|
|
|
149
150
|
|
|
150
151
|
export interface WorkflowRepoSnapshot {
|
|
151
152
|
cwd: string;
|
|
153
|
+
observation: "ok" | "unavailable" | "partial";
|
|
154
|
+
/** Bounded explanation of an unavailable or incomplete observation. */
|
|
155
|
+
observation_detail?: string;
|
|
152
156
|
root?: string;
|
|
153
157
|
branch?: string;
|
|
154
158
|
head?: string;
|
|
@@ -161,12 +165,13 @@ export interface WorkflowRepoEvidence {
|
|
|
161
165
|
before: WorkflowRepoSnapshot;
|
|
162
166
|
after: WorkflowRepoSnapshot;
|
|
163
167
|
drift: {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
168
|
+
/** Null means the snapshots do not establish this comparison. */
|
|
169
|
+
branch_changed: boolean | null;
|
|
170
|
+
head_changed: boolean | null;
|
|
171
|
+
dirty_paths_added: string[] | null;
|
|
172
|
+
dirty_paths_cleared: string[] | null;
|
|
173
|
+
dirty_paths_retained: string[] | null;
|
|
174
|
+
/** True when an observation, comparison, or retained-path change is unknown. */
|
|
170
175
|
incomplete: boolean;
|
|
171
176
|
note?: string;
|
|
172
177
|
};
|
|
@@ -358,15 +363,16 @@ export interface WorkflowSpecialistProfile {
|
|
|
358
363
|
}
|
|
359
364
|
|
|
360
365
|
/**
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
* default and is charged against the attempt budget exactly as before.
|
|
366
|
+
* The positively identified transport cause of a failed spawn (ADR 0046).
|
|
367
|
+
* This class alone proves neither absence of effects nor retry eligibility.
|
|
364
368
|
*
|
|
365
369
|
* - `environment`: the run never started — a precondition was missing (the
|
|
366
|
-
* vendor binary was absent).
|
|
370
|
+
* vendor binary was absent). With positive no-effect/output evidence it is
|
|
371
|
+
* uncharged and not retried: retrying an unchanged
|
|
367
372
|
* environment cannot help, so the work item stops and names the precondition.
|
|
368
373
|
* - `upstream`: the vendor was reached and refused (5xx, 429, circuit open).
|
|
369
|
-
*
|
|
374
|
+
* Retry/free-attempt eligibility also requires positive no-effect evidence,
|
|
375
|
+
* observed absent output, and no refusal. Unsupported evidence stays unknown.
|
|
370
376
|
*/
|
|
371
377
|
export type SpawnFailureClass = "environment" | "upstream";
|
|
372
378
|
|
|
@@ -388,6 +394,30 @@ export type SpawnFailureClass = "environment" | "upstream";
|
|
|
388
394
|
*/
|
|
389
395
|
export type RunFailureClass = SpawnFailureClass | "decision";
|
|
390
396
|
|
|
397
|
+
/** Positive claims name the observation that supports them. A transport error
|
|
398
|
+
* or an empty reply is never evidence that no effects occurred. */
|
|
399
|
+
export type SpawnEffectEvidence =
|
|
400
|
+
| { state: "unknown" }
|
|
401
|
+
| { state: "none"; source: "spawn_errno"; reference: "ENOENT" }
|
|
402
|
+
| { state: "none" | "observed"; source: "adapter_evidence"; reference: string };
|
|
403
|
+
|
|
404
|
+
export type SpawnRefusalEvidence =
|
|
405
|
+
| { state: "unknown" }
|
|
406
|
+
| { state: "refused"; source: "vendor_envelope"; reference: string };
|
|
407
|
+
|
|
408
|
+
export type SpawnOutputSource = "final_message" | "vendor_envelope" | "stdout";
|
|
409
|
+
|
|
410
|
+
export type SpawnPartialOutputEvidence =
|
|
411
|
+
| { state: "unknown" }
|
|
412
|
+
| { state: "absent"; source: SpawnOutputSource }
|
|
413
|
+
| { state: "present"; source: SpawnOutputSource; bytes: number; sha256: string };
|
|
414
|
+
|
|
415
|
+
export interface SpawnFailureEvidence {
|
|
416
|
+
effect: SpawnEffectEvidence;
|
|
417
|
+
refusal: SpawnRefusalEvidence;
|
|
418
|
+
partial_output: SpawnPartialOutputEvidence;
|
|
419
|
+
}
|
|
420
|
+
|
|
391
421
|
/** What a spawn adapter returns for one subagent run. */
|
|
392
422
|
export interface SpawnResult {
|
|
393
423
|
ok: boolean;
|
|
@@ -399,9 +429,13 @@ export interface SpawnResult {
|
|
|
399
429
|
durationMs: number;
|
|
400
430
|
/** Populated when ok=false. */
|
|
401
431
|
error?: string;
|
|
402
|
-
/**
|
|
403
|
-
*
|
|
432
|
+
/** Transport classification when ok=false; never sufficient by itself to
|
|
433
|
+
* grant free work or automatic redispatch. */
|
|
404
434
|
class?: SpawnFailureClass;
|
|
435
|
+
/** Transport classification alone grants neither a retry nor free work.
|
|
436
|
+
* Absent evidence is explicitly unknown. Positive adapter_evidence claims
|
|
437
|
+
* require a documented producer observation, never an HTTP/text heuristic. */
|
|
438
|
+
failureEvidence?: SpawnFailureEvidence;
|
|
405
439
|
}
|
|
406
440
|
|
|
407
441
|
export interface SpawnRequest {
|
|
@@ -36,7 +36,6 @@ export {
|
|
|
36
36
|
probe as probeLocalGitWorktreeProvider,
|
|
37
37
|
} from "./local-git.ts";
|
|
38
38
|
export type { PrepareReclaimInput, ReclaimMode, ReclaimPreparation } from "./reclaim.ts";
|
|
39
|
-
export { prepareReclaim } from "./reclaim.ts";
|
|
40
39
|
export type {
|
|
41
40
|
AuthorizedIntegrationPlan,
|
|
42
41
|
AuthorizedProviderIntegrationInput,
|