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,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import type { CoordinationHealthSnapshot } from "../agents/health.ts";
|
|
3
|
+
import { canonicalizeDiagnosticIdentity } from "../diagnostics/path-identity.ts";
|
|
3
4
|
import type {
|
|
4
5
|
ResourceProcessGroup,
|
|
5
6
|
ResourceProcessSample,
|
|
@@ -96,15 +97,17 @@ export function updateSupervisorFindings(
|
|
|
96
97
|
const candidates = evaluateCandidates(input)
|
|
97
98
|
.sort((left, right) => severityRank(right.severity) - severityRank(left.severity))
|
|
98
99
|
.slice(0, SUPERVISOR_DIAGNOSTIC_LIMITS.max_active_findings);
|
|
99
|
-
const priorActive = new Map(previous.active.map((entry) => [entry
|
|
100
|
+
const priorActive = new Map(previous.active.map((entry) => [findingFingerprint(entry), entry]));
|
|
100
101
|
const current = new Set<string>();
|
|
101
102
|
const active: SupervisorFinding[] = [];
|
|
102
103
|
const transitions = new Map(previous.transitions.map((entry) => [entry.id, entry]));
|
|
103
104
|
|
|
104
|
-
for (const
|
|
105
|
-
const
|
|
106
|
-
current.add(
|
|
107
|
-
const prior = priorActive.get(
|
|
105
|
+
for (const observed of candidates) {
|
|
106
|
+
const identity = findingFingerprint(observed);
|
|
107
|
+
current.add(identity);
|
|
108
|
+
const prior = priorActive.get(identity) ?? recentResolved(previous, identity, observedAt);
|
|
109
|
+
const candidate = preserveEpisodeReferences(observed, prior);
|
|
110
|
+
const fingerprint = prior?.fingerprint ?? identity;
|
|
108
111
|
const openedAt = prior?.opened_at ?? candidate.primary_source.observed_at;
|
|
109
112
|
const occurrenceCount = prior
|
|
110
113
|
? prior.state === "resolved"
|
|
@@ -114,7 +117,7 @@ export function updateSupervisorFindings(
|
|
|
114
117
|
const peak = peakEvidence(prior, candidate.evidence, observedAt);
|
|
115
118
|
const finding: SupervisorFinding = {
|
|
116
119
|
schema_version: SUPERVISOR_FINDING_SCHEMA_VERSION,
|
|
117
|
-
id: deterministicId("find", `${fingerprint}\u0000${openedAt}`),
|
|
120
|
+
id: prior?.id ?? deterministicId("find", `${fingerprint}\u0000${openedAt}`),
|
|
118
121
|
fingerprint,
|
|
119
122
|
source_kind: candidate.source_kind,
|
|
120
123
|
finding_kind: candidate.finding_kind,
|
|
@@ -139,7 +142,7 @@ export function updateSupervisorFindings(
|
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
for (const prior of priorActive.values()) {
|
|
142
|
-
if (current.has(prior
|
|
145
|
+
if (current.has(findingFingerprint(prior))) continue;
|
|
143
146
|
transitions.set(prior.id, {
|
|
144
147
|
...prior,
|
|
145
148
|
state: "resolved",
|
|
@@ -859,7 +862,7 @@ function recentResolved(
|
|
|
859
862
|
return findings.transitions
|
|
860
863
|
.filter(
|
|
861
864
|
(entry) =>
|
|
862
|
-
entry
|
|
865
|
+
findingFingerprint(entry) === fingerprint &&
|
|
863
866
|
entry.state === "resolved" &&
|
|
864
867
|
entry.resolved_at !== undefined &&
|
|
865
868
|
observedMs - Date.parse(entry.resolved_at) >= 0 &&
|
|
@@ -898,7 +901,12 @@ function peakEvidence(
|
|
|
898
901
|
: {};
|
|
899
902
|
}
|
|
900
903
|
|
|
901
|
-
function findingFingerprint(
|
|
904
|
+
function findingFingerprint(
|
|
905
|
+
candidate: Pick<
|
|
906
|
+
Candidate,
|
|
907
|
+
"source_kind" | "finding_kind" | "scope_kind" | "scope_id" | "primary_source"
|
|
908
|
+
>,
|
|
909
|
+
): string {
|
|
902
910
|
return digest(
|
|
903
911
|
[
|
|
904
912
|
candidate.source_kind,
|
|
@@ -915,7 +923,57 @@ function deterministicId(prefix: string, value: string): string {
|
|
|
915
923
|
}
|
|
916
924
|
|
|
917
925
|
function digest(value: string): string {
|
|
918
|
-
return createHash("sha256").update(value).digest("hex");
|
|
926
|
+
return createHash("sha256").update(canonicalizeDiagnosticIdentity(value)).digest("hex");
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/** Keep opaque episode links when the same source observation is replayed privately. */
|
|
930
|
+
function preserveEpisodeReferences(candidate: Candidate, prior?: SupervisorFinding): Candidate {
|
|
931
|
+
if (!prior) return candidate;
|
|
932
|
+
const sources = [
|
|
933
|
+
prior.primary_source,
|
|
934
|
+
...prior.evidence.map((entry) => entry.source),
|
|
935
|
+
...(prior.workload_context ? [prior.workload_context.source] : []),
|
|
936
|
+
];
|
|
937
|
+
const byIdentity = new Map(sources.map((source) => [sourceIdentity(source), source]));
|
|
938
|
+
const retainSource = (source: SupervisorSourceReference): SupervisorSourceReference => {
|
|
939
|
+
const captured = byIdentity.get(sourceIdentity(source));
|
|
940
|
+
return captured ? { ...source, id: captured.id } : source;
|
|
941
|
+
};
|
|
942
|
+
return {
|
|
943
|
+
...candidate,
|
|
944
|
+
primary_source: retainSource(candidate.primary_source),
|
|
945
|
+
evidence: candidate.evidence.map((entry) => {
|
|
946
|
+
const captured = prior.evidence.find(
|
|
947
|
+
(previous) =>
|
|
948
|
+
sourceIdentity(previous.source) === sourceIdentity(entry.source) &&
|
|
949
|
+
previous.unit === entry.unit &&
|
|
950
|
+
previous.observed_value === entry.observed_value &&
|
|
951
|
+
canonicalizeDiagnosticIdentity(previous.summary) ===
|
|
952
|
+
canonicalizeDiagnosticIdentity(entry.summary),
|
|
953
|
+
);
|
|
954
|
+
return {
|
|
955
|
+
...entry,
|
|
956
|
+
...(captured ? { id: captured.id } : {}),
|
|
957
|
+
source: retainSource(entry.source),
|
|
958
|
+
};
|
|
959
|
+
}),
|
|
960
|
+
...(candidate.workload_context
|
|
961
|
+
? {
|
|
962
|
+
workload_context: {
|
|
963
|
+
...candidate.workload_context,
|
|
964
|
+
source: retainSource(candidate.workload_context.source),
|
|
965
|
+
},
|
|
966
|
+
}
|
|
967
|
+
: {}),
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
function sourceIdentity(source: SupervisorSourceReference): string {
|
|
972
|
+
return canonicalizeDiagnosticIdentity(
|
|
973
|
+
[source.source_kind, source.source_id, source.record_id ?? "", source.observed_at].join(
|
|
974
|
+
"\u0000",
|
|
975
|
+
),
|
|
976
|
+
);
|
|
919
977
|
}
|
|
920
978
|
|
|
921
979
|
function validFindings(value: SupervisorFindings | undefined): value is SupervisorFindings {
|
|
@@ -16,7 +16,12 @@ import { resolve } from "node:path";
|
|
|
16
16
|
import { performance } from "node:perf_hooks";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
18
|
import { collectCoordinationHealthSnapshot } from "../agents/health.ts";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
type ProcessGenerationVerdict,
|
|
21
|
+
processStartToken,
|
|
22
|
+
signalIfSameGeneration,
|
|
23
|
+
} from "../agents/state/proc-start.ts";
|
|
24
|
+
import { resolveBinName } from "../config.ts";
|
|
20
25
|
import { pressureHistoryFromSupervisor } from "../diagnostics/advice.ts";
|
|
21
26
|
import { assessPressure } from "../diagnostics/pressure.ts";
|
|
22
27
|
import { readEventV3ControlState } from "../events/v3/control.ts";
|
|
@@ -52,7 +57,12 @@ import type {
|
|
|
52
57
|
} from "./ledger-state.ts";
|
|
53
58
|
import { SupervisorLogCollector } from "./log-feed.ts";
|
|
54
59
|
import { collectServiceHealth } from "./services.ts";
|
|
55
|
-
import {
|
|
60
|
+
import {
|
|
61
|
+
installedSupervisorBuild,
|
|
62
|
+
readSupervisorStatus,
|
|
63
|
+
type SupervisorProcessDependencies,
|
|
64
|
+
supervisorProcessLiveness,
|
|
65
|
+
} from "./status.ts";
|
|
56
66
|
import {
|
|
57
67
|
readSupervisorFindings,
|
|
58
68
|
readSupervisorHistory,
|
|
@@ -97,15 +107,16 @@ export async function spawnSupervisor(
|
|
|
97
107
|
): Promise<SupervisorStatus> {
|
|
98
108
|
const coordRoot = resolve(coordRootRaw);
|
|
99
109
|
const current = readSupervisorStatus(coordRoot);
|
|
100
|
-
if (current.
|
|
110
|
+
if (current.liveness === "unknown") throw unknownOwnerError();
|
|
111
|
+
if (current.liveness === "running") {
|
|
101
112
|
throw new Error(`local supervisor is already running under pid ${current.record?.pid}`);
|
|
102
113
|
}
|
|
114
|
+
const paths = supervisorPaths(coordRoot);
|
|
115
|
+
assertLeaseAvailable(paths.lease);
|
|
103
116
|
await stopLegacyResourceObserver(coordRoot);
|
|
104
117
|
const intervalMs = normalizeInterval(options.intervalMs);
|
|
105
118
|
const idleExitMs = normalizeIdleExit(options.idleExitMs);
|
|
106
|
-
const paths = supervisorPaths(coordRoot);
|
|
107
119
|
mkdirSync(paths.root, { recursive: true, mode: stateDirMode() });
|
|
108
|
-
rmSync(paths.stop, { force: true });
|
|
109
120
|
const cliPath = fileURLToPath(
|
|
110
121
|
new URL(import.meta.url.endsWith(".ts") ? "../../cli.ts" : "../../cli.js", import.meta.url),
|
|
111
122
|
);
|
|
@@ -167,29 +178,57 @@ export async function ensureSupervisorRunning(coordRoot: string): Promise<Ensure
|
|
|
167
178
|
}
|
|
168
179
|
}
|
|
169
180
|
|
|
170
|
-
export
|
|
181
|
+
export interface SupervisorStopResult extends SupervisorStatus {
|
|
182
|
+
signal_sent: boolean;
|
|
183
|
+
signal_verdict: ProcessGenerationVerdict;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function requestSupervisorStop(
|
|
187
|
+
coordRootRaw: string,
|
|
188
|
+
dependencies: SupervisorProcessDependencies & {
|
|
189
|
+
kill?: (pid: number, signal: NodeJS.Signals | number) => unknown;
|
|
190
|
+
} = {},
|
|
191
|
+
): SupervisorStopResult {
|
|
171
192
|
const coordRoot = resolve(coordRootRaw);
|
|
172
193
|
const paths = supervisorPaths(coordRoot);
|
|
173
|
-
const status = readSupervisorStatus(coordRoot);
|
|
194
|
+
const status = readSupervisorStatus(coordRoot, Date.now(), dependencies);
|
|
174
195
|
mkdirSync(paths.root, { recursive: true, mode: stateDirMode() });
|
|
175
196
|
writePrivateJsonAtomic(paths.stop, {
|
|
176
197
|
requested_at: new Date().toISOString(),
|
|
177
198
|
requested_by_pid: process.pid,
|
|
178
199
|
});
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
200
|
+
const legacyPaths = resourcePaths(coordRoot);
|
|
201
|
+
if (existsSync(legacyPaths.service) || existsSync(legacyPaths.lease))
|
|
202
|
+
requestResourceServiceStop(coordRoot, dependencies);
|
|
203
|
+
let signal = { sent: false, verdict: status.generation };
|
|
204
|
+
if (
|
|
205
|
+
status.liveness === "running" &&
|
|
206
|
+
status.record?.host === hostname() &&
|
|
207
|
+
process.platform !== "win32"
|
|
208
|
+
) {
|
|
209
|
+
signal = signalIfSameGeneration(
|
|
210
|
+
status.record.pid,
|
|
211
|
+
status.record.start_token,
|
|
212
|
+
"SIGTERM",
|
|
213
|
+
dependencies,
|
|
214
|
+
);
|
|
185
215
|
}
|
|
186
|
-
return
|
|
216
|
+
return {
|
|
217
|
+
...readSupervisorStatus(coordRoot, Date.now(), dependencies),
|
|
218
|
+
signal_sent: signal.sent,
|
|
219
|
+
signal_verdict: signal.verdict,
|
|
220
|
+
};
|
|
187
221
|
}
|
|
188
222
|
|
|
189
223
|
export async function runSupervisor(
|
|
190
224
|
input: RunSupervisorInput,
|
|
191
225
|
): Promise<SupervisorServiceStatusRecord> {
|
|
192
226
|
const coordRoot = resolve(input.coordRoot);
|
|
227
|
+
const current = readSupervisorStatus(coordRoot);
|
|
228
|
+
if (current.liveness === "unknown") throw unknownOwnerError();
|
|
229
|
+
if (current.liveness === "running") {
|
|
230
|
+
throw new Error(`local supervisor is already running under pid ${current.record?.pid}`);
|
|
231
|
+
}
|
|
193
232
|
const paths = supervisorPaths(coordRoot);
|
|
194
233
|
const intervalMs = normalizeInterval(input.intervalMs);
|
|
195
234
|
const idleExitMs = normalizeIdleExit(input.idleExitMs);
|
|
@@ -204,6 +243,7 @@ export async function runSupervisor(
|
|
|
204
243
|
schema_version: SUPERVISOR_STATUS_SCHEMA_VERSION,
|
|
205
244
|
pid: process.pid,
|
|
206
245
|
...(startToken ? { start_token: startToken } : {}),
|
|
246
|
+
running_build: installedSupervisorBuild(),
|
|
207
247
|
host: hostname(),
|
|
208
248
|
nonce: lease.nonce,
|
|
209
249
|
state: "starting",
|
|
@@ -517,7 +557,9 @@ function acquireLease(coordRoot: string, now: Date): SupervisorLease {
|
|
|
517
557
|
} catch (error) {
|
|
518
558
|
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
519
559
|
const existing = readLease(paths.lease);
|
|
520
|
-
|
|
560
|
+
const liveness = existing ? leaseIsLive(existing) : "unknown";
|
|
561
|
+
if (liveness === "unknown") throw unknownOwnerError();
|
|
562
|
+
if (existing && liveness === "running") {
|
|
521
563
|
throw new Error(`local supervisor lease is held by pid ${existing.pid}`);
|
|
522
564
|
}
|
|
523
565
|
try {
|
|
@@ -554,7 +596,13 @@ function releaseLease(path: string, nonce: string): void {
|
|
|
554
596
|
function readLease(path: string): SupervisorLease | undefined {
|
|
555
597
|
try {
|
|
556
598
|
const value = JSON.parse(readFileSync(path, "utf8")) as SupervisorLease;
|
|
557
|
-
return value &&
|
|
599
|
+
return value &&
|
|
600
|
+
Number.isSafeInteger(value.pid) &&
|
|
601
|
+
value.pid > 0 &&
|
|
602
|
+
typeof value.host === "string" &&
|
|
603
|
+
value.host.length > 0 &&
|
|
604
|
+
typeof value.nonce === "string" &&
|
|
605
|
+
value.nonce.length > 0
|
|
558
606
|
? value
|
|
559
607
|
: undefined;
|
|
560
608
|
} catch {
|
|
@@ -562,22 +610,36 @@ function readLease(path: string): SupervisorLease | undefined {
|
|
|
562
610
|
}
|
|
563
611
|
}
|
|
564
612
|
|
|
565
|
-
function leaseIsLive(lease: SupervisorLease):
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
613
|
+
function leaseIsLive(lease: SupervisorLease): SupervisorStatus["liveness"] {
|
|
614
|
+
return supervisorProcessLiveness(lease).liveness;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function assertLeaseAvailable(path: string): void {
|
|
618
|
+
if (!existsSync(path)) return;
|
|
619
|
+
const lease = readLease(path);
|
|
620
|
+
const liveness = lease ? leaseIsLive(lease) : "unknown";
|
|
621
|
+
if (liveness === "unknown") throw unknownOwnerError();
|
|
622
|
+
if (liveness === "running")
|
|
623
|
+
throw new Error(`local supervisor lease is held by pid ${lease?.pid}`);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function unknownOwnerError(): Error {
|
|
627
|
+
return new Error(
|
|
628
|
+
`local supervisor process identity is unknown; run ${resolveBinName()} supervisor stop to write the durable stop request before starting again`,
|
|
629
|
+
);
|
|
573
630
|
}
|
|
574
631
|
|
|
575
632
|
async function stopLegacyResourceObserver(coordRoot: string): Promise<void> {
|
|
576
633
|
const legacy = readResourceServiceStatus(coordRoot);
|
|
577
|
-
if (
|
|
634
|
+
if (legacy.liveness === "stopped") return;
|
|
578
635
|
requestResourceServiceStop(coordRoot);
|
|
579
636
|
const deadline = Date.now() + 2_000;
|
|
580
|
-
while (readResourceServiceStatus(coordRoot).
|
|
637
|
+
while (readResourceServiceStatus(coordRoot).liveness !== "stopped" && Date.now() < deadline)
|
|
638
|
+
await delay(50);
|
|
639
|
+
if (readResourceServiceStatus(coordRoot).liveness !== "stopped")
|
|
640
|
+
throw new Error(
|
|
641
|
+
"legacy resource observer has not stopped after its durable stop request; its process identity may be unknown",
|
|
642
|
+
);
|
|
581
643
|
}
|
|
582
644
|
|
|
583
645
|
function log(
|
|
@@ -2,6 +2,7 @@ import { existsSync, readdirSync, readFileSync, rmSync, statSync } from "node:fs
|
|
|
2
2
|
import { hostname } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { checkPidToken, processStartToken } from "../agents/state/proc-start.ts";
|
|
5
|
+
import { readGovernorServiceStatus } from "../governor/service-read.ts";
|
|
5
6
|
import { writePrivateJsonAtomic } from "../storage/atomic-json.ts";
|
|
6
7
|
import {
|
|
7
8
|
type ObservedServiceHealth,
|
|
@@ -41,10 +42,7 @@ export function collectServiceHealth(
|
|
|
41
42
|
join(coordRoot, ".harnery", "semantic", "v2", "service.json"),
|
|
42
43
|
"semantic-reader",
|
|
43
44
|
);
|
|
44
|
-
const governor =
|
|
45
|
-
join(coordRoot, ".harnery", "governor-service", "service.json"),
|
|
46
|
-
"governor",
|
|
47
|
-
);
|
|
45
|
+
const governor = readGovernorHealth(coordRoot);
|
|
48
46
|
const relay = readPresenceRelay(join(coordRoot, ".harnery", "presence", "relay-daemon.json"));
|
|
49
47
|
const dashboard = consumers.find((consumer) => consumer.id === "dashboard");
|
|
50
48
|
const services: ObservedServiceHealth[] = [
|
|
@@ -118,10 +116,7 @@ interface DetachedServiceRecord {
|
|
|
118
116
|
heartbeat_at?: string;
|
|
119
117
|
}
|
|
120
118
|
|
|
121
|
-
function readDetachedService(
|
|
122
|
-
path: string,
|
|
123
|
-
id: "semantic-reader" | "governor",
|
|
124
|
-
): ObservedServiceHealth {
|
|
119
|
+
function readDetachedService(path: string, id: "semantic-reader"): ObservedServiceHealth {
|
|
125
120
|
const record = readBoundedJson<DetachedServiceRecord>(path);
|
|
126
121
|
if (!record || !Number.isSafeInteger(record.pid) || record.pid < 1) {
|
|
127
122
|
return { id, state: "stopped" };
|
|
@@ -150,6 +145,29 @@ function readDetachedService(
|
|
|
150
145
|
);
|
|
151
146
|
}
|
|
152
147
|
|
|
148
|
+
function readGovernorHealth(coordRoot: string): ObservedServiceHealth {
|
|
149
|
+
const status = readGovernorServiceStatus(coordRoot);
|
|
150
|
+
const state =
|
|
151
|
+
status.liveness === "unknown"
|
|
152
|
+
? "unknown"
|
|
153
|
+
: status.running
|
|
154
|
+
? "running"
|
|
155
|
+
: status.stale
|
|
156
|
+
? "stale"
|
|
157
|
+
: "stopped";
|
|
158
|
+
const reason =
|
|
159
|
+
state === "unknown"
|
|
160
|
+
? "governor-process-generation-unknown"
|
|
161
|
+
: status.generation === "different"
|
|
162
|
+
? "recorded-process-generation-different"
|
|
163
|
+
: status.stale
|
|
164
|
+
? "recorded-running-process-missing"
|
|
165
|
+
: undefined;
|
|
166
|
+
return status.record
|
|
167
|
+
? projection("governor", state, status.record, reason)
|
|
168
|
+
: { id: "governor", state, ...(reason ? { reason } : {}) };
|
|
169
|
+
}
|
|
170
|
+
|
|
153
171
|
function readPresenceRelay(path: string): ObservedServiceHealth {
|
|
154
172
|
const record = readBoundedJson<{ pid: number; started_at?: string }>(path);
|
|
155
173
|
if (!record || !Number.isSafeInteger(record.pid) || record.pid < 1) {
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
1
2
|
import { hostname } from "node:os";
|
|
2
|
-
import {
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
type ProcessGenerationVerdict,
|
|
6
|
+
verifyProcessGeneration,
|
|
7
|
+
} from "../agents/state/proc-start.ts";
|
|
8
|
+
import { currentHarneryRuntimeBuild, harneryPackageRoot } from "../events/v3/runtime-build.ts";
|
|
3
9
|
import {
|
|
4
10
|
SUPERVISOR_STATUS_SCHEMA_VERSION,
|
|
11
|
+
type SupervisorBuild,
|
|
5
12
|
type SupervisorServiceStatusRecord,
|
|
6
13
|
type SupervisorStatus,
|
|
7
14
|
} from "./contract.ts";
|
|
@@ -9,27 +16,71 @@ import { readSupervisorServiceRecord, supervisorPaths } from "./storage.ts";
|
|
|
9
16
|
|
|
10
17
|
const STALE_HEARTBEAT_MS = 15_000;
|
|
11
18
|
|
|
12
|
-
export
|
|
19
|
+
export interface SupervisorProcessDependencies {
|
|
20
|
+
readToken?: (pid: number) => string | null;
|
|
21
|
+
probePid?: (pid: number) => unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function installedSupervisorBuild(): SupervisorBuild {
|
|
25
|
+
let version = "unknown";
|
|
26
|
+
let revision = "unknown";
|
|
27
|
+
try {
|
|
28
|
+
const pkg = JSON.parse(readFileSync(join(harneryPackageRoot(), "package.json"), "utf8"));
|
|
29
|
+
if (typeof pkg.version === "string") version = pkg.version;
|
|
30
|
+
revision = currentHarneryRuntimeBuild();
|
|
31
|
+
} catch {
|
|
32
|
+
// Missing build evidence must still produce an inspectable status.
|
|
33
|
+
}
|
|
34
|
+
return { version, revision };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function readSupervisorStatus(
|
|
38
|
+
coordRoot: string,
|
|
39
|
+
nowMs = Date.now(),
|
|
40
|
+
dependencies: SupervisorProcessDependencies = {},
|
|
41
|
+
): SupervisorStatus {
|
|
13
42
|
const paths = supervisorPaths(coordRoot);
|
|
14
43
|
const record = readSupervisorServiceRecord(coordRoot);
|
|
44
|
+
const installedBuild = installedSupervisorBuild();
|
|
45
|
+
const runningBuild = validBuild(record?.running_build) ? record.running_build : null;
|
|
46
|
+
const restartNeeded =
|
|
47
|
+
!runningBuild ||
|
|
48
|
+
installedBuild.version === "unknown" ||
|
|
49
|
+
installedBuild.revision === "unknown" ||
|
|
50
|
+
runningBuild.version !== installedBuild.version ||
|
|
51
|
+
runningBuild.revision !== installedBuild.revision;
|
|
52
|
+
const build = {
|
|
53
|
+
running_build: runningBuild,
|
|
54
|
+
installed_build: installedBuild,
|
|
55
|
+
restart_needed: restartNeeded,
|
|
56
|
+
build_status: !runningBuild
|
|
57
|
+
? "running build unknown; restart needed"
|
|
58
|
+
: restartNeeded
|
|
59
|
+
? "restart needed"
|
|
60
|
+
: "current",
|
|
61
|
+
};
|
|
15
62
|
if (!validRecord(record)) {
|
|
16
63
|
return {
|
|
17
64
|
running: false,
|
|
18
65
|
stale: false,
|
|
66
|
+
generation: "unknown",
|
|
67
|
+
liveness: existsSync(paths.service) ? "unknown" : "stopped",
|
|
68
|
+
...build,
|
|
19
69
|
status_path: paths.service,
|
|
20
70
|
snapshot_path: paths.snapshot,
|
|
21
71
|
};
|
|
22
72
|
}
|
|
23
73
|
const heartbeatAt = Date.parse(record.heartbeat_at);
|
|
24
74
|
const stale = !Number.isFinite(heartbeatAt) || nowMs - heartbeatAt > STALE_HEARTBEAT_MS;
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
(record.state === "starting" || record.state === "running" || record.state === "stopping") &&
|
|
29
|
-
pidStillMatches(record.pid, record.start_token);
|
|
75
|
+
const owner = supervisorProcessLiveness(record, dependencies);
|
|
76
|
+
const liveness =
|
|
77
|
+
owner.generation === "same" && record.state === "stopped" ? "stopped" : owner.liveness;
|
|
30
78
|
return {
|
|
31
|
-
running,
|
|
79
|
+
running: liveness === "running" && !stale && record.state !== "stopped",
|
|
32
80
|
stale,
|
|
81
|
+
generation: owner.generation,
|
|
82
|
+
liveness,
|
|
83
|
+
...build,
|
|
33
84
|
record,
|
|
34
85
|
status_path: paths.service,
|
|
35
86
|
snapshot_path: paths.snapshot,
|
|
@@ -49,11 +100,37 @@ function validRecord(
|
|
|
49
100
|
);
|
|
50
101
|
}
|
|
51
102
|
|
|
52
|
-
|
|
103
|
+
/** A stale heartbeat does not prove that a process has exited. */
|
|
104
|
+
export function supervisorProcessLiveness(
|
|
105
|
+
owner: { pid: number; host: string; start_token?: string },
|
|
106
|
+
dependencies: SupervisorProcessDependencies = {},
|
|
107
|
+
): { liveness: SupervisorStatus["liveness"]; generation: ProcessGenerationVerdict } {
|
|
108
|
+
if (owner.host !== hostname()) return { liveness: "unknown", generation: "unknown" };
|
|
53
109
|
try {
|
|
54
|
-
process.kill(pid, 0);
|
|
110
|
+
(dependencies.probePid ?? ((pid) => process.kill(pid, 0)))(owner.pid);
|
|
55
111
|
} catch (error) {
|
|
56
|
-
|
|
112
|
+
return {
|
|
113
|
+
liveness: (error as NodeJS.ErrnoException).code === "ESRCH" ? "stopped" : "unknown",
|
|
114
|
+
generation: "unknown",
|
|
115
|
+
};
|
|
57
116
|
}
|
|
58
|
-
|
|
117
|
+
const generation = verifyProcessGeneration(owner.pid, owner.start_token, dependencies.readToken);
|
|
118
|
+
return {
|
|
119
|
+
generation,
|
|
120
|
+
liveness:
|
|
121
|
+
generation === "same" ? "running" : generation === "different" ? "stopped" : "unknown",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function validBuild(value: unknown): value is SupervisorBuild {
|
|
126
|
+
if (!value || typeof value !== "object") return false;
|
|
127
|
+
const build = value as Partial<SupervisorBuild>;
|
|
128
|
+
return (
|
|
129
|
+
typeof build.version === "string" &&
|
|
130
|
+
!!build.version &&
|
|
131
|
+
build.version !== "unknown" &&
|
|
132
|
+
typeof build.revision === "string" &&
|
|
133
|
+
!!build.revision &&
|
|
134
|
+
build.revision !== "unknown"
|
|
135
|
+
);
|
|
59
136
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { canonicalizeDiagnosticIdentity } from "../diagnostics/path-identity.ts";
|
|
2
3
|
import {
|
|
3
4
|
SUPERVISOR_DIAGNOSTIC_LIMITS,
|
|
4
5
|
SUPERVISOR_TIMELINE_SCHEMA_VERSION,
|
|
@@ -129,5 +130,5 @@ function clusterRelatedSources(
|
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
function stableId(prefix: string, value: string): string {
|
|
132
|
-
return `${prefix}_${createHash("sha256").update(value).digest("hex").slice(0, 24)}`;
|
|
133
|
+
return `${prefix}_${createHash("sha256").update(canonicalizeDiagnosticIdentity(value)).digest("hex").slice(0, 24)}`;
|
|
133
134
|
}
|
package/src/core/work/state.ts
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
import { stateDirMode, stateFileMode } from "../storage/modes.ts";
|
|
21
21
|
import { readWorkflowApproval } from "../workflow/approvals.ts";
|
|
22
22
|
import { stableDigest } from "../workflow/durable-record.ts";
|
|
23
|
-
import { readWorkflowProof } from "../workflow/proof.ts";
|
|
23
|
+
import { deriveRunFailureClass, readWorkflowProof } from "../workflow/proof.ts";
|
|
24
24
|
import { readWorkflowRunManifest, workflowScriptDigest } from "../workflow/run-state.ts";
|
|
25
25
|
import { WORKFLOW_TRANSCRIPT_EVENT_BYTES, workflowTranscriptPath } from "../workflow/transcript.ts";
|
|
26
26
|
import { isWorkspaceBoundExecutionEvidence } from "../workflow/workspaces/validate.ts";
|
|
@@ -718,14 +718,14 @@ function inspectAttempt(
|
|
|
718
718
|
proof.acceptance.summary.unknown === 0
|
|
719
719
|
? "succeeded"
|
|
720
720
|
: "failed";
|
|
721
|
-
//
|
|
722
|
-
//
|
|
723
|
-
//
|
|
721
|
+
// Re-derive no-effect eligibility from every participant rather than
|
|
722
|
+
// trusting a persisted transport class. Missing evidence stays charged.
|
|
723
|
+
// The script's explicit decision authority remains a separate path.
|
|
724
724
|
if (
|
|
725
725
|
attempt.status === "failed" &&
|
|
726
|
-
(proof.run.class === "
|
|
727
|
-
proof.run.class === "upstream"
|
|
728
|
-
|
|
726
|
+
(proof.run.class === "decision" ||
|
|
727
|
+
((proof.run.class === "environment" || proof.run.class === "upstream") &&
|
|
728
|
+
deriveRunFailureClass(proof.run.status, proof.agents) === proof.run.class))
|
|
729
729
|
) {
|
|
730
730
|
attempt.uncharged = proof.run.class;
|
|
731
731
|
if (proof.run.class === "decision" && typeof proof.run.decision_id === "string") {
|