harnery 0.44.0 → 0.45.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +44 -0
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +18 -0
- package/dist/core/agents/live-authority-v3.d.ts +0 -1
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +0 -11
- package/dist/core/agents/live-lifecycle-v3.d.ts +2 -4
- package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
- package/dist/core/agents/live-lifecycle-v3.js +7 -28
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +14 -25
- package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-writer.js +0 -5
- package/dist/core/agents/state/pidmap.d.ts.map +1 -1
- package/dist/core/agents/state/pidmap.js +0 -5
- package/dist/core/agents/state/proc-start.d.ts +5 -6
- package/dist/core/agents/state/proc-start.d.ts.map +1 -1
- package/dist/core/agents/state/proc-start.js +13 -9
- package/dist/core/agents/state/process-handle.d.ts +26 -0
- package/dist/core/agents/state/process-handle.d.ts.map +1 -0
- package/dist/core/agents/state/process-handle.js +121 -0
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +70 -180
- package/dist/core/artifacts/activity.d.ts +4 -1
- package/dist/core/artifacts/activity.d.ts.map +1 -1
- package/dist/core/artifacts/activity.js +8 -2
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +38 -12
- package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
- package/dist/core/events/v3/authority-outbox.js +0 -29
- package/dist/core/events/v3/producers/coordination-recorder.d.ts +0 -1
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/coordination-recorder.js +0 -7
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +64 -77
- package/dist/core/hooks/cli.js +78 -48
- package/dist/core/resources/service.d.ts +2 -16
- package/dist/core/resources/service.d.ts.map +1 -1
- package/dist/core/resources/service.js +1 -78
- package/dist/core/semantic/service.d.ts.map +1 -1
- package/dist/core/semantic/service.js +2 -9
- package/dist/core/servers/index.d.ts +4 -0
- package/dist/core/servers/index.d.ts.map +1 -1
- package/dist/core/servers/index.js +21 -3
- package/dist/core/storage/durable-history.js +56 -22
- package/dist/core/storage/segments.d.ts.map +1 -1
- package/dist/core/storage/segments.js +73 -17
- package/dist/core/supervisor/service.d.ts +2 -4
- package/dist/core/supervisor/service.d.ts.map +1 -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 +21 -65
- package/dist/core/workflow/index.d.ts +4 -2
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/index.js +2 -1
- package/dist/core/workflow/live-session-v3.d.ts +0 -6
- package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
- package/dist/core/workflow/live-session-v3.js +5 -126
- package/dist/core/workflow/proof.d.ts +4 -4
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +4 -12
- package/dist/core/workflow/recovery-audit.d.ts +30 -0
- package/dist/core/workflow/recovery-audit.d.ts.map +1 -0
- package/dist/core/workflow/recovery-audit.js +116 -0
- package/dist/core/workflow/run-state.d.ts +19 -2
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +113 -8
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +0 -19
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +0 -13
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +0 -19
- package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
- package/dist/core/workflow/spawn-opencode.js +0 -14
- package/dist/core/workflow/types.d.ts +11 -54
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.js +70 -14
- package/dist/core/workflow/workspaces/reclaim.d.ts +6 -1
- package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/recovery.d.ts +2 -0
- package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/recovery.js +24 -0
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +0 -4
- package/package.json +1 -1
- package/src/commands/workflow.ts +59 -0
- package/src/core/agents/coord-client.ts +20 -0
- package/src/core/agents/live-authority-v3.ts +0 -17
- package/src/core/agents/live-lifecycle-v3.ts +14 -52
- package/src/core/agents/state/heartbeat-writer.ts +12 -29
- package/src/core/agents/state/live-coordination-writer.ts +0 -29
- package/src/core/agents/state/pidmap.ts +0 -13
- package/src/core/agents/state/proc-start.ts +14 -11
- package/src/core/agents/state/process-handle.ts +165 -0
- package/src/core/agents/state/stale-sweep.ts +82 -247
- package/src/core/artifacts/activity.ts +9 -2
- package/src/core/artifacts/index.ts +45 -12
- package/src/core/events/v3/authority-outbox.ts +0 -56
- package/src/core/events/v3/producers/coordination-recorder.ts +0 -15
- package/src/core/events/v3/producers/recorder.ts +68 -94
- package/src/core/hooks/cli.ts +85 -47
- package/src/core/resources/service.ts +3 -88
- package/src/core/semantic/service.ts +2 -8
- package/src/core/servers/index.ts +30 -3
- package/src/core/storage/durable-history.ts +52 -20
- package/src/core/storage/segments.ts +79 -17
- package/src/core/supervisor/service.ts +2 -3
- package/src/core/work/state.ts +7 -7
- package/src/core/workflow/engine.ts +21 -73
- package/src/core/workflow/index.ts +12 -1
- package/src/core/workflow/live-session-v3.ts +5 -146
- package/src/core/workflow/proof.ts +5 -21
- package/src/core/workflow/recovery-audit.ts +181 -0
- package/src/core/workflow/run-state.ts +137 -8
- package/src/core/workflow/spawn-claude.ts +0 -18
- package/src/core/workflow/spawn-codex.ts +0 -13
- package/src/core/workflow/spawn-cursor.ts +0 -18
- package/src/core/workflow/spawn-opencode.ts +0 -14
- package/src/core/workflow/types.ts +11 -41
- package/src/core/workflow/workspaces/local-git.ts +86 -15
- package/src/core/workflow/workspaces/reclaim.ts +4 -0
- package/src/core/workflow/workspaces/recovery.ts +23 -0
- package/src/lib/identities/assume.ts +0 -4
- package/dist/core/agents/state/unchanged-file.d.ts +0 -44
- package/dist/core/agents/state/unchanged-file.d.ts.map +0 -1
- package/dist/core/agents/state/unchanged-file.js +0 -390
- package/dist/core/events/v3/generation-custody.d.ts +0 -5
- package/dist/core/events/v3/generation-custody.d.ts.map +0 -1
- package/dist/core/events/v3/generation-custody.js +0 -50
- package/dist/core/workflow/failure-evidence.d.ts +0 -16
- package/dist/core/workflow/failure-evidence.d.ts.map +0 -1
- package/dist/core/workflow/failure-evidence.js +0 -114
- package/src/core/agents/state/unchanged-file.ts +0 -456
- package/src/core/events/v3/generation-custody.ts +0 -59
- package/src/core/workflow/failure-evidence.ts +0 -141
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "node:fs";
|
|
10
10
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
11
11
|
import { stateDirMode, stateFileMode } from "../../storage/modes.ts";
|
|
12
|
+
import { beginWorkflowRecovery, validateWorkflowRecoveryAuthorization } from "../recovery-audit.ts";
|
|
12
13
|
import { acquireWorkflowResumeLease } from "../run-state.ts";
|
|
13
14
|
import { WorkspaceAttestationError } from "./attestation-error.ts";
|
|
14
15
|
import { cleanupWorkspace } from "./cleanup.ts";
|
|
@@ -53,7 +54,7 @@ import {
|
|
|
53
54
|
type WorkspaceReclaimPreview,
|
|
54
55
|
type WorkspaceReclaimResult,
|
|
55
56
|
} from "./reclaim.ts";
|
|
56
|
-
import { readWorkflowLiveness } from "./recovery.ts";
|
|
57
|
+
import { hasActiveWorkflowEvidence, readWorkflowLiveness } from "./recovery.ts";
|
|
57
58
|
import {
|
|
58
59
|
appendWorkspaceEvent,
|
|
59
60
|
fileSha256,
|
|
@@ -820,6 +821,7 @@ function reclaimPreview(
|
|
|
820
821
|
run_id: binding.run_id,
|
|
821
822
|
mode,
|
|
822
823
|
liveness: readWorkflowLiveness(coordRoot, binding.run_id),
|
|
824
|
+
evidence_sha256: reclaimEvidenceDigest(coordRoot, binding),
|
|
823
825
|
ownership: "unproven",
|
|
824
826
|
dirty_paths: null,
|
|
825
827
|
lease_holder: reclaimLeaseHolders(coordRoot, binding),
|
|
@@ -839,14 +841,45 @@ function reclaimPreview(
|
|
|
839
841
|
return preview;
|
|
840
842
|
}
|
|
841
843
|
|
|
842
|
-
function
|
|
844
|
+
function reclaimEvidenceDigest(coordRoot: string, binding: WorkspaceBinding): string {
|
|
845
|
+
const evidence = (name: string): string | null => {
|
|
846
|
+
try {
|
|
847
|
+
return readFileSync(join(coordRoot, ".harnery", "workflows", binding.run_id, name), "utf8");
|
|
848
|
+
} catch (error) {
|
|
849
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
|
|
850
|
+
throw error;
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
return stableDigest({
|
|
854
|
+
binding,
|
|
855
|
+
proof: evidence("proof.json"),
|
|
856
|
+
transcript: evidence("transcript.jsonl"),
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function assertReclaimLiveness(
|
|
861
|
+
coordRoot: string,
|
|
862
|
+
binding: WorkspaceBinding,
|
|
863
|
+
recovery?: WorkspaceReclaimOptions["unknownLiveness"],
|
|
864
|
+
): void {
|
|
843
865
|
const liveness = readWorkflowLiveness(coordRoot, binding.run_id);
|
|
866
|
+
if (liveness === "active" || (recovery && hasActiveWorkflowEvidence(coordRoot, binding.run_id))) {
|
|
867
|
+
throw new WorkflowReclaimError(
|
|
868
|
+
"workflow_reclaim_run_live",
|
|
869
|
+
`run ${binding.run_id} has active evidence; its workspace was left untouched`,
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
if (recovery && reclaimEvidenceDigest(coordRoot, binding) !== recovery.evidenceSha256) {
|
|
873
|
+
throw new WorkflowReclaimError(
|
|
874
|
+
"workflow_reclaim_recovery_mismatch",
|
|
875
|
+
"workflow reclaim requires the exact current preview evidence digest",
|
|
876
|
+
);
|
|
877
|
+
}
|
|
844
878
|
if (liveness === "inactive") return;
|
|
879
|
+
if (liveness === "unknown" && recovery) return;
|
|
845
880
|
throw new WorkflowReclaimError(
|
|
846
|
-
|
|
847
|
-
liveness
|
|
848
|
-
? `run ${binding.run_id} is active; its workspace was left untouched`
|
|
849
|
-
: `run ${binding.run_id} has missing, corrupt, or ambiguous liveness evidence; its workspace was left untouched`,
|
|
881
|
+
"workflow_reclaim_liveness_unknown",
|
|
882
|
+
`run ${binding.run_id} has missing, corrupt, or ambiguous liveness evidence; its workspace was left untouched`,
|
|
850
883
|
);
|
|
851
884
|
}
|
|
852
885
|
|
|
@@ -892,10 +925,11 @@ function reclaimAuthorityGuard(
|
|
|
892
925
|
binding: WorkspaceBinding,
|
|
893
926
|
claim: WorkspaceClaim,
|
|
894
927
|
reservation: WorkspaceReclaimReservation,
|
|
928
|
+
recovery?: WorkspaceReclaimOptions["unknownLiveness"],
|
|
895
929
|
): void {
|
|
896
930
|
try {
|
|
897
931
|
reservation.assertCurrent();
|
|
898
|
-
assertReclaimLiveness(coordRoot, binding);
|
|
932
|
+
assertReclaimLiveness(coordRoot, binding, recovery);
|
|
899
933
|
assertReclaimOwnership(coordRoot, binding);
|
|
900
934
|
assertPeerWorkspaceAuthority(coordRoot, claim);
|
|
901
935
|
} catch (error) {
|
|
@@ -914,10 +948,11 @@ async function reclaim(
|
|
|
914
948
|
throw new Error("unsupported workspace reclaim mode");
|
|
915
949
|
}
|
|
916
950
|
if (!options.yes) return reclaimPreview(coordRoot, binding, options.mode);
|
|
951
|
+
if (options.unknownLiveness) validateWorkflowRecoveryAuthorization(options.unknownLiveness);
|
|
917
952
|
|
|
918
953
|
// Liveness comes before terminal-proof cleanup helpers. A live run may not
|
|
919
954
|
// have a proof yet, and a missing proof must never be discovered after reset.
|
|
920
|
-
assertReclaimLiveness(coordRoot, binding);
|
|
955
|
+
assertReclaimLiveness(coordRoot, binding, options.unknownLiveness);
|
|
921
956
|
const claim = assertReclaimOwnership(coordRoot, binding);
|
|
922
957
|
let reservation: WorkspaceReclaimReservation;
|
|
923
958
|
try {
|
|
@@ -926,7 +961,7 @@ async function reclaim(
|
|
|
926
961
|
throw new WorkflowReclaimError("workflow_reclaim_lease_held", (error as Error).message);
|
|
927
962
|
}
|
|
928
963
|
try {
|
|
929
|
-
reclaimAuthorityGuard(coordRoot, binding, claim, reservation);
|
|
964
|
+
reclaimAuthorityGuard(coordRoot, binding, claim, reservation, options.unknownLiveness);
|
|
930
965
|
let releaseResume: () => void;
|
|
931
966
|
try {
|
|
932
967
|
releaseResume = acquireWorkflowResumeLease(coordRoot, binding.run_id);
|
|
@@ -953,9 +988,24 @@ async function reclaim(
|
|
|
953
988
|
throw new WorkflowReclaimError("workflow_reclaim_lease_held", (error as Error).message);
|
|
954
989
|
}
|
|
955
990
|
try {
|
|
956
|
-
assertReclaimLiveness(coordRoot, binding);
|
|
991
|
+
assertReclaimLiveness(coordRoot, binding, options.unknownLiveness);
|
|
957
992
|
assertReclaimOwnership(coordRoot, binding);
|
|
958
993
|
const preview = reclaimPreview(coordRoot, binding, options.mode);
|
|
994
|
+
const audit = options.unknownLiveness
|
|
995
|
+
? beginWorkflowRecovery(coordRoot, {
|
|
996
|
+
run_id: binding.run_id,
|
|
997
|
+
action: "reclaim_unknown_liveness",
|
|
998
|
+
actor: options.unknownLiveness.actor,
|
|
999
|
+
reason: options.unknownLiveness.reason,
|
|
1000
|
+
target_sha256: options.unknownLiveness.evidenceSha256,
|
|
1001
|
+
target: {
|
|
1002
|
+
binding_id: binding.binding_id,
|
|
1003
|
+
workspace_root: binding.workspace_root,
|
|
1004
|
+
mode: options.mode,
|
|
1005
|
+
liveness: preview.liveness,
|
|
1006
|
+
},
|
|
1007
|
+
})
|
|
1008
|
+
: undefined;
|
|
959
1009
|
let preparation: ReclaimPreparation | undefined;
|
|
960
1010
|
// Keep ordinary cleanup's terminal-evidence checks, intent and receipts.
|
|
961
1011
|
// Its provider callback uses the leases already held here instead of
|
|
@@ -967,12 +1017,28 @@ async function reclaim(
|
|
|
967
1017
|
provider: {
|
|
968
1018
|
...provider,
|
|
969
1019
|
cleanup: async (candidate, intent) => {
|
|
970
|
-
assertReclaimLiveness(coordRoot, candidate);
|
|
1020
|
+
assertReclaimLiveness(coordRoot, candidate, options.unknownLiveness);
|
|
971
1021
|
assertReclaimOwnership(coordRoot, candidate);
|
|
972
1022
|
return cleanupUnderLease(coordRoot, candidate, claim, intent, {
|
|
973
1023
|
mode: options.mode,
|
|
974
|
-
|
|
975
|
-
|
|
1024
|
+
recovery: options.unknownLiveness,
|
|
1025
|
+
beforeEffect: () => {
|
|
1026
|
+
audit?.assertCurrent();
|
|
1027
|
+
reclaimAuthorityGuard(
|
|
1028
|
+
coordRoot,
|
|
1029
|
+
candidate,
|
|
1030
|
+
claim,
|
|
1031
|
+
reservation,
|
|
1032
|
+
options.unknownLiveness,
|
|
1033
|
+
);
|
|
1034
|
+
},
|
|
1035
|
+
beforeRefEffect: () => {
|
|
1036
|
+
audit?.assertCurrent();
|
|
1037
|
+
reservation.assertCurrent();
|
|
1038
|
+
assertReclaimLiveness(coordRoot, candidate, options.unknownLiveness);
|
|
1039
|
+
requiredClaim(coordRoot, candidate);
|
|
1040
|
+
assertPeerWorkspaceAuthority(coordRoot, claim);
|
|
1041
|
+
},
|
|
976
1042
|
prepared: (value) => {
|
|
977
1043
|
preparation = value;
|
|
978
1044
|
},
|
|
@@ -991,6 +1057,8 @@ async function reclaim(
|
|
|
991
1057
|
"workspace cleanup preconditions could not be proved",
|
|
992
1058
|
);
|
|
993
1059
|
}
|
|
1060
|
+
if (cleanupResult.status === "released" || cleanupResult.status === "already_released")
|
|
1061
|
+
audit?.complete();
|
|
994
1062
|
return {
|
|
995
1063
|
...preview,
|
|
996
1064
|
action: "reclaimed",
|
|
@@ -1104,7 +1172,9 @@ function cleanupUnderLease(
|
|
|
1104
1172
|
intent: WorkspaceCleanupIntent,
|
|
1105
1173
|
reclaimRequest?: {
|
|
1106
1174
|
mode: ReclaimMode;
|
|
1175
|
+
recovery?: WorkspaceReclaimOptions["unknownLiveness"];
|
|
1107
1176
|
beforeEffect: () => void;
|
|
1177
|
+
beforeRefEffect: () => void;
|
|
1108
1178
|
prepared: (value: ReclaimPreparation) => void;
|
|
1109
1179
|
},
|
|
1110
1180
|
beforeRemoval?: () => void,
|
|
@@ -1152,7 +1222,7 @@ function cleanupUnderLease(
|
|
|
1152
1222
|
};
|
|
1153
1223
|
}
|
|
1154
1224
|
const liveness = readWorkflowLiveness(coordRoot, binding.run_id);
|
|
1155
|
-
if (liveness !== "inactive") {
|
|
1225
|
+
if (liveness !== "inactive" && !(liveness === "unknown" && reclaimRequest?.recovery)) {
|
|
1156
1226
|
return {
|
|
1157
1227
|
...base,
|
|
1158
1228
|
status: "blocked",
|
|
@@ -1245,7 +1315,7 @@ function cleanupUnderLease(
|
|
|
1245
1315
|
}
|
|
1246
1316
|
|
|
1247
1317
|
if (reclaimRequest) {
|
|
1248
|
-
assertReclaimLiveness(coordRoot, binding);
|
|
1318
|
+
assertReclaimLiveness(coordRoot, binding, reclaimRequest.recovery);
|
|
1249
1319
|
assertReclaimOwnership(coordRoot, binding);
|
|
1250
1320
|
revalidateClaimResources(claim, root);
|
|
1251
1321
|
reclaimRequest.prepared(
|
|
@@ -1293,6 +1363,7 @@ function cleanupUnderLease(
|
|
|
1293
1363
|
return { ...base, status: "blocked", reason: "workspace ref changed during cleanup" };
|
|
1294
1364
|
}
|
|
1295
1365
|
revalidateClaimResources(claim, root);
|
|
1366
|
+
reclaimRequest?.beforeRefEffect();
|
|
1296
1367
|
const deleted = gitMaybe(repository.source_root.realpath, [
|
|
1297
1368
|
"update-ref",
|
|
1298
1369
|
"-d",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { existsSync } from "node:fs";
|
|
19
|
+
import type { WorkflowRecoveryAuthorization } from "../recovery-audit.ts";
|
|
19
20
|
import { git, gitMaybe } from "./git.ts";
|
|
20
21
|
import type { WorkflowLiveness } from "./recovery.ts";
|
|
21
22
|
import type { WorkspaceCleanupAttempt, WorkspaceCleanupReceipt } from "./types.ts";
|
|
@@ -25,6 +26,7 @@ export type ReclaimMode = "salvage" | "discard";
|
|
|
25
26
|
export type WorkflowReclaimRefusalCode =
|
|
26
27
|
| "workflow_reclaim_run_live"
|
|
27
28
|
| "workflow_reclaim_liveness_unknown"
|
|
29
|
+
| "workflow_reclaim_recovery_mismatch"
|
|
28
30
|
| "workflow_reclaim_ownership_unproven"
|
|
29
31
|
| "workflow_reclaim_lease_held";
|
|
30
32
|
|
|
@@ -41,6 +43,7 @@ export class WorkflowReclaimError extends Error {
|
|
|
41
43
|
export interface WorkspaceReclaimOptions {
|
|
42
44
|
mode: ReclaimMode;
|
|
43
45
|
yes?: boolean;
|
|
46
|
+
unknownLiveness?: WorkflowRecoveryAuthorization & { evidenceSha256: string };
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
export interface ReclaimLeaseHolder {
|
|
@@ -56,6 +59,7 @@ export interface WorkspaceReclaimPreview {
|
|
|
56
59
|
run_id: string;
|
|
57
60
|
mode: ReclaimMode;
|
|
58
61
|
liveness: WorkflowLiveness;
|
|
62
|
+
evidence_sha256: string;
|
|
59
63
|
ownership: "ok" | "unproven";
|
|
60
64
|
ownership_detail?: string;
|
|
61
65
|
dirty_paths: string[] | null;
|
|
@@ -4,6 +4,29 @@ import { readWorkflowProof } from "../proof.ts";
|
|
|
4
4
|
|
|
5
5
|
export type WorkflowLiveness = "active" | "inactive" | "unknown";
|
|
6
6
|
|
|
7
|
+
/** Corruption must not hide a readable active marker from an audited override. */
|
|
8
|
+
export function hasActiveWorkflowEvidence(coordRoot: string, runId: string): boolean {
|
|
9
|
+
const path = join(resolve(coordRoot), ".harnery", "workflows", runId, "transcript.jsonl");
|
|
10
|
+
if (!existsSync(path)) return false;
|
|
11
|
+
let active = false;
|
|
12
|
+
for (const line of readFileSync(path, "utf8").split("\n")) {
|
|
13
|
+
try {
|
|
14
|
+
const event = JSON.parse(line);
|
|
15
|
+
if (event?.run_id !== runId) continue;
|
|
16
|
+
if (["run.start", "run.resume", "agent.start"].includes(event.event)) active = true;
|
|
17
|
+
if (
|
|
18
|
+
event.event === "run.parked" ||
|
|
19
|
+
(event.event === "run.end" && typeof event.ok === "boolean") ||
|
|
20
|
+
(event.event === "workspace.reattach.failed" && ["blocked", "lost"].includes(event.status))
|
|
21
|
+
)
|
|
22
|
+
active = false;
|
|
23
|
+
} catch {
|
|
24
|
+
/* Unreadable evidence cannot establish inactivity. */
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return active;
|
|
28
|
+
}
|
|
29
|
+
|
|
7
30
|
export function readWorkflowLiveness(coordRoot: string, runId: string): WorkflowLiveness {
|
|
8
31
|
const root = resolve(coordRoot);
|
|
9
32
|
const transcriptPath = join(root, ".harnery", "workflows", runId, "transcript.jsonl");
|
|
@@ -140,7 +140,6 @@ export function reclaimAbandonedLocalConflict(
|
|
|
140
140
|
let sessionId = conflict.instance_id;
|
|
141
141
|
let ageSecs: number | undefined;
|
|
142
142
|
const liveRow = readLiveCoordinationRow(coordRoot, conflict.instance_id);
|
|
143
|
-
let expectedGenerationId = liveRow?.v3_generation_id ?? null;
|
|
144
143
|
if (liveRow) {
|
|
145
144
|
adapter = adapterOf(liveRow.platform);
|
|
146
145
|
sessionId = liveRow.session_id || conflict.instance_id;
|
|
@@ -154,9 +153,7 @@ export function reclaimAbandonedLocalConflict(
|
|
|
154
153
|
// It is required to join the exact producer state when recording the sweep.
|
|
155
154
|
adapter = adapterOf(hb.platform);
|
|
156
155
|
sessionId = hb.session_id || conflict.instance_id;
|
|
157
|
-
expectedGenerationId = hb.v3_generation_id ?? null;
|
|
158
156
|
} catch {
|
|
159
|
-
expectedGenerationId = null;
|
|
160
157
|
/* content unreadable — still reclaim the file */
|
|
161
158
|
}
|
|
162
159
|
try {
|
|
@@ -171,7 +168,6 @@ export function reclaimAbandonedLocalConflict(
|
|
|
171
168
|
owner: conflict.instance_id,
|
|
172
169
|
nativeSessionId: sessionId,
|
|
173
170
|
adapter,
|
|
174
|
-
expectedGenerationId,
|
|
175
171
|
observation: "stale_heartbeat",
|
|
176
172
|
ageMs: Math.max(0, (ageSecs ?? 0) * 1_000),
|
|
177
173
|
});
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
export interface FileRevision {
|
|
2
|
-
readonly bytes: Buffer;
|
|
3
|
-
readonly dev: bigint;
|
|
4
|
-
readonly ino: bigint;
|
|
5
|
-
readonly size: bigint;
|
|
6
|
-
readonly mtimeNs: bigint;
|
|
7
|
-
readonly ctimeNs: bigint;
|
|
8
|
-
}
|
|
9
|
-
export type DeleteFileResult = {
|
|
10
|
-
state: "deleted" | "changed" | "missing" | "rejected";
|
|
11
|
-
} | {
|
|
12
|
-
state: "recovery-required";
|
|
13
|
-
recoveryPath: string;
|
|
14
|
-
error?: string;
|
|
15
|
-
} | {
|
|
16
|
-
state: "error";
|
|
17
|
-
recoveryPath?: string;
|
|
18
|
-
error: string;
|
|
19
|
-
};
|
|
20
|
-
export interface FileRecoveryResult {
|
|
21
|
-
state: "restored" | "retained" | "error";
|
|
22
|
-
recoveryPath: string;
|
|
23
|
-
sourcePath?: string;
|
|
24
|
-
error?: string;
|
|
25
|
-
}
|
|
26
|
-
/** A stable read of one regular file, including its exact bytes and revision. */
|
|
27
|
-
export declare function captureFileRevision(filePath: string): FileRevision | null;
|
|
28
|
-
export declare function fileRevisionMatches(filePath: string, expected: FileRevision): boolean;
|
|
29
|
-
/**
|
|
30
|
-
* Writers must publish by atomic rename, never by an open descriptor to the old
|
|
31
|
-
* inode. The private directory is outside their publication namespace. Moving
|
|
32
|
-
* the row gives deletion custody over that inode without owning its live name.
|
|
33
|
-
* The caller owns the state parent and keeps its topology stable. Its platform
|
|
34
|
-
* permissions define which principals can modify it; this protocol does not
|
|
35
|
-
* exclude principals already authorized to change that parent.
|
|
36
|
-
*/
|
|
37
|
-
export declare function deleteFileIfUnchanged(filePath: string, expected: FileRevision, validate: (pinned: FileRevision) => boolean): DeleteFileResult;
|
|
38
|
-
/**
|
|
39
|
-
* Recover same-host/platform/PID-namespace pins after process termination. File fsync
|
|
40
|
-
* protects manifest contents; directory entries are not fsynced, so this does
|
|
41
|
-
* not promise recovery after an OS crash or power loss.
|
|
42
|
-
*/
|
|
43
|
-
export declare function recoverInterruptedFileDeletions(directoryPath: string, validate: (sourcePath: string) => boolean): FileRecoveryResult[];
|
|
44
|
-
//# sourceMappingURL=unchanged-file.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"unchanged-file.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/unchanged-file.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAA;CAAE,GACzD;IAAE,KAAK,EAAE,mBAAmB,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACpE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7D,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,UAAU,GAAG,UAAU,GAAG,OAAO,CAAC;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAsED,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAoCzE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,OAAO,CAGrF;AAsGD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,GAC1C,gBAAgB,CAmFlB;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,CAC7C,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,GACxC,kBAAkB,EAAE,CA+FtB"}
|