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
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository observation contract shared by context recovery and workflow proof.
|
|
3
|
+
*
|
|
4
|
+
* This module has no runtime imports so read-only consumers (the Codec
|
|
5
|
+
* dashboard, proof readers) can use it without reaching the Git-running
|
|
6
|
+
* snapshot code in ./index.ts.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type RepoObservation = "ok" | "unavailable" | "partial";
|
|
10
|
+
|
|
11
|
+
export interface RepoSnapshot {
|
|
12
|
+
cwd: string;
|
|
13
|
+
observation: RepoObservation;
|
|
14
|
+
/** Bounded explanation of an unavailable or incomplete observation. */
|
|
15
|
+
observation_detail?: string;
|
|
16
|
+
root?: string;
|
|
17
|
+
branch?: string;
|
|
18
|
+
head?: string;
|
|
19
|
+
dirty_paths: string[];
|
|
20
|
+
dirty_paths_truncated?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Only explicitly successful observations can establish repository drift. */
|
|
24
|
+
export function observationsComparable(
|
|
25
|
+
a: { observation?: string },
|
|
26
|
+
b: { observation?: string },
|
|
27
|
+
): boolean {
|
|
28
|
+
return a.observation === "ok" && b.observation === "ok";
|
|
29
|
+
}
|
|
@@ -134,12 +134,12 @@ export function captureDiagnosticBundle(
|
|
|
134
134
|
const aggregateStats: SanitizationStats = { sanitized_value_count: 0, omitted_value_count: 0 };
|
|
135
135
|
const sources = [
|
|
136
136
|
...SOURCE_SPECS.map(([sourceKind, relativePath]) =>
|
|
137
|
-
readCapturedSource(repoRoot, sourceKind, relativePath
|
|
137
|
+
readCapturedSource(repoRoot, sourceKind, relativePath),
|
|
138
138
|
),
|
|
139
|
-
readShadowAdmissionSource(repoRoot, capturedAt
|
|
139
|
+
readShadowAdmissionSource(repoRoot, capturedAt),
|
|
140
140
|
];
|
|
141
141
|
const selection = diagnosticSelection(input, sources, now);
|
|
142
|
-
const
|
|
142
|
+
const rawObservations: DiagnosticObservations = {
|
|
143
143
|
schema_version: DIAGNOSTIC_INPUT_SCHEMA_VERSION,
|
|
144
144
|
captured_at: capturedAt,
|
|
145
145
|
selection,
|
|
@@ -156,7 +156,23 @@ export function captureDiagnosticBundle(
|
|
|
156
156
|
pressure_policy: PRESSURE_POLICY,
|
|
157
157
|
},
|
|
158
158
|
};
|
|
159
|
-
|
|
159
|
+
// Freeze the producer's actual output before sanitization. The replay must
|
|
160
|
+
// independently preserve that output, rather than share a transformed oracle.
|
|
161
|
+
const rawExpected = deriveCapturedExpected(rawObservations, thresholds);
|
|
162
|
+
const sanitizedExpected = sanitizeDiagnosticValue(JSON.parse(JSON.stringify(rawExpected)));
|
|
163
|
+
aggregateStats.sanitized_value_count += sanitizedExpected.stats.sanitized_value_count;
|
|
164
|
+
aggregateStats.omitted_value_count += sanitizedExpected.stats.omitted_value_count;
|
|
165
|
+
const observations: DiagnosticObservations = {
|
|
166
|
+
...rawObservations,
|
|
167
|
+
sources: rawObservations.sources.map((source) => {
|
|
168
|
+
if (source.value === undefined) return source;
|
|
169
|
+
const sanitized = sanitizeDiagnosticValue(source.value);
|
|
170
|
+
aggregateStats.sanitized_value_count += sanitized.stats.sanitized_value_count;
|
|
171
|
+
aggregateStats.omitted_value_count += sanitized.stats.omitted_value_count;
|
|
172
|
+
return { ...source, value: sanitized.value };
|
|
173
|
+
}),
|
|
174
|
+
};
|
|
175
|
+
const expected = sanitizedExpected.value as typeof rawExpected;
|
|
160
176
|
if (input.findingId && !expected.findings.some((finding) => finding.id === input.findingId)) {
|
|
161
177
|
throw new Error(
|
|
162
178
|
`finding "${input.findingId}" is not present in the captured findings projection`,
|
|
@@ -446,7 +462,6 @@ function readCapturedSource(
|
|
|
446
462
|
repoRoot: string,
|
|
447
463
|
sourceKind: string,
|
|
448
464
|
relativePath: string,
|
|
449
|
-
aggregateStats: SanitizationStats,
|
|
450
465
|
): DiagnosticCapturedSource {
|
|
451
466
|
const path = join(repoRoot, relativePath);
|
|
452
467
|
if (!existsSync(path)) {
|
|
@@ -462,10 +477,7 @@ function readCapturedSource(
|
|
|
462
477
|
: (() => {
|
|
463
478
|
throw new Error("source_is_not_regular_file");
|
|
464
479
|
})();
|
|
465
|
-
const
|
|
466
|
-
aggregateStats.sanitized_value_count += sanitized.stats.sanitized_value_count;
|
|
467
|
-
aggregateStats.omitted_value_count += sanitized.stats.omitted_value_count;
|
|
468
|
-
const value = sanitized.value;
|
|
480
|
+
const value = parsed;
|
|
469
481
|
const record = object(value);
|
|
470
482
|
return {
|
|
471
483
|
source_kind: sourceKind,
|
|
@@ -483,11 +495,7 @@ function readCapturedSource(
|
|
|
483
495
|
}
|
|
484
496
|
}
|
|
485
497
|
|
|
486
|
-
function readShadowAdmissionSource(
|
|
487
|
-
repoRoot: string,
|
|
488
|
-
capturedAt: string,
|
|
489
|
-
aggregateStats: SanitizationStats,
|
|
490
|
-
): DiagnosticCapturedSource {
|
|
498
|
+
function readShadowAdmissionSource(repoRoot: string, capturedAt: string): DiagnosticCapturedSource {
|
|
491
499
|
const sourceKind = "workflow.diagnostic-admission";
|
|
492
500
|
const workflowsRoot = join(repoRoot, ".harnery", "workflows");
|
|
493
501
|
if (!existsSync(workflowsRoot)) {
|
|
@@ -545,15 +553,12 @@ function readShadowAdmissionSource(
|
|
|
545
553
|
invalid_run_count: invalidCount,
|
|
546
554
|
records,
|
|
547
555
|
};
|
|
548
|
-
const sanitized = sanitizeDiagnosticValue(projection);
|
|
549
|
-
aggregateStats.sanitized_value_count += sanitized.stats.sanitized_value_count;
|
|
550
|
-
aggregateStats.omitted_value_count += sanitized.stats.omitted_value_count;
|
|
551
556
|
return {
|
|
552
557
|
source_kind: sourceKind,
|
|
553
558
|
capability: omittedCount > 0 || invalidCount > 0 ? "partial" : "supported",
|
|
554
559
|
schema_version: 1,
|
|
555
560
|
observed_at: records[0]?.ended_at as string | undefined,
|
|
556
|
-
value:
|
|
561
|
+
value: projection,
|
|
557
562
|
...(omittedCount > 0
|
|
558
563
|
? { reason_code: "bounded_workflow_window_truncated" }
|
|
559
564
|
: invalidCount > 0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
const HOME_PATH =
|
|
4
|
+
/(?:[A-Za-z]:[\\/]Users[\\/](?:[^\\/\r\n\0:"<>|]+(?=[\\/])|[^\\/\s\0:]+)|\/home\/[^/\s\0:]+|\/Users\/[^/\s\0:]+)/g;
|
|
5
|
+
|
|
6
|
+
/** One stable representation for private home paths in values and identity inputs. */
|
|
7
|
+
export function canonicalizeDiagnosticIdentity(value: string): string {
|
|
8
|
+
return value.replace(HOME_PATH, (home) => {
|
|
9
|
+
const digest = createHash("sha256")
|
|
10
|
+
.update("harnery-diagnostic-home-v1\0")
|
|
11
|
+
.update(home)
|
|
12
|
+
.digest("hex");
|
|
13
|
+
return `[REDACTED:home:${digest}]`;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -2,7 +2,6 @@ const SENSITIVE_KEY =
|
|
|
2
2
|
/(?:authorization|cookie|credential|password|passwd|private[_-]?key|secret|session[_-]?token|access[_-]?token|refresh[_-]?token|api[_-]?key)/i;
|
|
3
3
|
const OMITTED_CONTENT_KEY =
|
|
4
4
|
/(?:^|[_-])(?:environment|env|prompt|prompts|tool[_-]?payload|tool[_-]?result|transcript|transcripts)(?:$|[_-])/i;
|
|
5
|
-
const HOME_PATH = /(?:[A-Za-z]:\\Users\\[^\\\s]+|\/home\/[^/\s]+|\/Users\/[^/\s]+)/g;
|
|
6
5
|
const BEARER_VALUE = /\bBearer\s+[A-Za-z0-9._~+/-]+=*/gi;
|
|
7
6
|
const URL_CREDENTIALS = /([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/gi;
|
|
8
7
|
const PRIVATE_KEY = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g;
|
|
@@ -76,7 +75,8 @@ function sanitize(value: unknown, stats: SanitizationStats, depth: number): unkn
|
|
|
76
75
|
}
|
|
77
76
|
|
|
78
77
|
function sanitizeString(value: string, stats: SanitizationStats): string {
|
|
79
|
-
let next = value;
|
|
78
|
+
let next = canonicalizeDiagnosticIdentity(value);
|
|
79
|
+
if (next !== value) stats.sanitized_value_count += 1;
|
|
80
80
|
const replacements: Array<[RegExp, string | ((...args: string[]) => string)]> = [
|
|
81
81
|
[PRIVATE_KEY, "[REDACTED:private-key]"],
|
|
82
82
|
[BEARER_VALUE, "Bearer [REDACTED:token]"],
|
|
@@ -86,7 +86,6 @@ function sanitizeString(value: string, stats: SanitizationStats): string {
|
|
|
86
86
|
[SENSITIVE_ENV, "$1[REDACTED]"],
|
|
87
87
|
[SENSITIVE_ARGUMENT, "$1[REDACTED]"],
|
|
88
88
|
[URL_CREDENTIALS, (_match, scheme: string) => `${scheme}[REDACTED]@`],
|
|
89
|
-
[HOME_PATH, "[REDACTED:home]"],
|
|
90
89
|
];
|
|
91
90
|
for (const [pattern, replacement] of replacements) {
|
|
92
91
|
const replaced = next.replace(pattern, replacement as never);
|
|
@@ -95,3 +94,5 @@ function sanitizeString(value: string, stats: SanitizationStats): string {
|
|
|
95
94
|
}
|
|
96
95
|
return next;
|
|
97
96
|
}
|
|
97
|
+
|
|
98
|
+
import { canonicalizeDiagnosticIdentity } from "./path-identity.ts";
|
|
@@ -16,6 +16,7 @@ import { fsyncParentDirectory } from "../../workflow/durable-record.ts";
|
|
|
16
16
|
import { canonicalJsonV3, sha256V3 } from "./canonical.ts";
|
|
17
17
|
import type { EventV3 } from "./contract.ts";
|
|
18
18
|
import { liveGenesisIdV3 } from "./control.ts";
|
|
19
|
+
import { assertCurrentGenerationV3, withGenerationCustodyV3 } from "./generation-custody.ts";
|
|
19
20
|
import { assertEventV3 } from "./validate.ts";
|
|
20
21
|
import { ensureEventV3Layout, eventV3Paths, writeEventV3 } from "./writer.ts";
|
|
21
22
|
|
|
@@ -154,9 +155,32 @@ export function publishAuthorityTransactionV3(
|
|
|
154
155
|
coordRoot: string,
|
|
155
156
|
transaction: AuthorityTransactionV3,
|
|
156
157
|
fence: AuthorityEpochFenceV3 = {},
|
|
158
|
+
): AuthorityTransactionV3 {
|
|
159
|
+
return withGenerationCustodyV3(
|
|
160
|
+
coordRoot,
|
|
161
|
+
[transaction.actor_instance_id, transaction.subject_instance_id],
|
|
162
|
+
() => publishAuthorityTransactionWithCustodyV3(coordRoot, transaction, fence),
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function publishAuthorityTransactionWithCustodyV3(
|
|
167
|
+
coordRoot: string,
|
|
168
|
+
transaction: AuthorityTransactionV3,
|
|
169
|
+
fence: AuthorityEpochFenceV3,
|
|
157
170
|
): AuthorityTransactionV3 {
|
|
158
171
|
validateTransaction(transaction);
|
|
159
172
|
const paths = ensureEventV3Layout(coordRoot);
|
|
173
|
+
const committedPath = authorityCommittedPath(paths.authorityOutbox, transaction.transaction_id);
|
|
174
|
+
if (existsSync(committedPath)) {
|
|
175
|
+
if (
|
|
176
|
+
readAuthorityReceiptV3(committedPath).transaction_digest !==
|
|
177
|
+
sha256V3(`${canonicalJsonV3(transaction)}\n`)
|
|
178
|
+
) {
|
|
179
|
+
throw new Error("authority committed transaction conflicts with this transaction");
|
|
180
|
+
}
|
|
181
|
+
return transaction;
|
|
182
|
+
}
|
|
183
|
+
assertTransactionGeneration(coordRoot, transaction);
|
|
160
184
|
if (fence.expectedGenesisId && liveGenesisIdV3(coordRoot) !== fence.expectedGenesisId) {
|
|
161
185
|
throw new Error("authority transaction epoch was replaced before publication");
|
|
162
186
|
}
|
|
@@ -185,6 +209,26 @@ export function reconcileAuthorityTransactionV3(
|
|
|
185
209
|
transactionId: string,
|
|
186
210
|
reconciler: AuthorityReconcilerV3,
|
|
187
211
|
fence: AuthorityEpochFenceV3 = {},
|
|
212
|
+
): AuthorityReceiptV3 {
|
|
213
|
+
assertTransactionId(transactionId);
|
|
214
|
+
const paths = ensureEventV3Layout(coordRoot);
|
|
215
|
+
const committedPath = authorityCommittedPath(paths.authorityOutbox, transactionId);
|
|
216
|
+
if (existsSync(committedPath)) return readAuthorityReceiptV3(committedPath);
|
|
217
|
+
const transaction = readAuthorityTransactionV3(
|
|
218
|
+
authorityReadyPath(paths.authorityOutbox, transactionId),
|
|
219
|
+
);
|
|
220
|
+
return withGenerationCustodyV3(
|
|
221
|
+
coordRoot,
|
|
222
|
+
[transaction.actor_instance_id, transaction.subject_instance_id],
|
|
223
|
+
() => reconcileAuthorityTransactionWithCustodyV3(coordRoot, transactionId, reconciler, fence),
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function reconcileAuthorityTransactionWithCustodyV3(
|
|
228
|
+
coordRoot: string,
|
|
229
|
+
transactionId: string,
|
|
230
|
+
reconciler: AuthorityReconcilerV3,
|
|
231
|
+
fence: AuthorityEpochFenceV3,
|
|
188
232
|
): AuthorityReceiptV3 {
|
|
189
233
|
assertTransactionId(transactionId);
|
|
190
234
|
const paths = ensureEventV3Layout(coordRoot);
|
|
@@ -192,8 +236,10 @@ export function reconcileAuthorityTransactionV3(
|
|
|
192
236
|
if (existsSync(committedPath)) return readAuthorityReceiptV3(committedPath);
|
|
193
237
|
const readyPath = authorityReadyPath(paths.authorityOutbox, transactionId);
|
|
194
238
|
const transaction = readAuthorityTransactionV3(readyPath);
|
|
239
|
+
assertTransactionGeneration(coordRoot, transaction);
|
|
195
240
|
const current = reconciler.readStateDigest();
|
|
196
241
|
if (current === transaction.expected_prior_state_digest) {
|
|
242
|
+
assertTransactionGeneration(coordRoot, transaction);
|
|
197
243
|
reconciler.apply(transaction.mutation, transaction.transaction_id);
|
|
198
244
|
} else if (current !== transaction.desired_state_digest) {
|
|
199
245
|
throw new Error("authority transaction prior state conflicts with current state");
|
|
@@ -201,6 +247,7 @@ export function reconcileAuthorityTransactionV3(
|
|
|
201
247
|
if (reconciler.readStateDigest() !== transaction.desired_state_digest) {
|
|
202
248
|
throw new Error("authority transaction did not reach its declared desired state");
|
|
203
249
|
}
|
|
250
|
+
assertTransactionGeneration(coordRoot, transaction);
|
|
204
251
|
const event = JSON.parse(transaction.event_row) as unknown;
|
|
205
252
|
assertEventV3(event);
|
|
206
253
|
const durability = writeEventV3(coordRoot, event, {
|
|
@@ -230,6 +277,15 @@ export function reconcileAuthorityTransactionV3(
|
|
|
230
277
|
return receipt;
|
|
231
278
|
}
|
|
232
279
|
|
|
280
|
+
function assertTransactionGeneration(coordRoot: string, transaction: AuthorityTransactionV3): void {
|
|
281
|
+
const event = JSON.parse(transaction.event_row) as EventV3;
|
|
282
|
+
assertCurrentGenerationV3(
|
|
283
|
+
coordRoot,
|
|
284
|
+
transaction.actor_instance_id,
|
|
285
|
+
"generation_id" in event.scope ? event.scope.generation_id : undefined,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
233
289
|
export function listPendingAuthorityTransactionsV3(coordRoot: string): AuthorityTransactionV3[] {
|
|
234
290
|
const outbox = eventV3Paths(coordRoot).authorityOutbox;
|
|
235
291
|
if (!existsSync(outbox)) return [];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { hostname } from "node:os";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { acquireNoClobberLease } from "../../workflow/workspaces/leases.ts";
|
|
5
|
+
import { readHookProducerStateByInstanceV3 } from "./producers/recorder.ts";
|
|
6
|
+
|
|
7
|
+
const held = new Set<string>();
|
|
8
|
+
|
|
9
|
+
/** Serialize synchronous authority work with generation changes for these instances only. */
|
|
10
|
+
export function withGenerationCustodyV3<T>(
|
|
11
|
+
coordRoot: string,
|
|
12
|
+
instanceIds: readonly string[],
|
|
13
|
+
operation: () => T,
|
|
14
|
+
): T {
|
|
15
|
+
const ids = [...new Set(instanceIds)].sort();
|
|
16
|
+
for (const id of ids) {
|
|
17
|
+
if (!/^inst_[a-zA-Z0-9._-]{1,128}$/.test(id)) {
|
|
18
|
+
throw new Error("generation custody instance is invalid");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const enter = (index: number): T => {
|
|
22
|
+
const id = ids[index];
|
|
23
|
+
if (id === undefined) return operation();
|
|
24
|
+
const key = `${resolve(coordRoot)}\0${id}`;
|
|
25
|
+
if (held.has(key)) return enter(index + 1);
|
|
26
|
+
const authority = createHash("sha256").update(key).digest("hex");
|
|
27
|
+
const lease = acquireNoClobberLease({
|
|
28
|
+
// Custody spans epoch rotation; the ledger directory itself is archived.
|
|
29
|
+
path: join(coordRoot, ".harnery", "private", "event-v3-generation-custody", authority),
|
|
30
|
+
scope: "event-v3-generation-custody",
|
|
31
|
+
authoritySha256: authority,
|
|
32
|
+
// Expiry never permits stealing a live/unknown owner. A dead local process
|
|
33
|
+
// can be recovered immediately, including after a cache-handoff crash.
|
|
34
|
+
staleAfterMs: 1,
|
|
35
|
+
validateStaleOwner: (owner) =>
|
|
36
|
+
owner.host === hostname() && owner.authority_sha256 === authority,
|
|
37
|
+
});
|
|
38
|
+
held.add(key);
|
|
39
|
+
try {
|
|
40
|
+
return enter(index + 1);
|
|
41
|
+
} finally {
|
|
42
|
+
held.delete(key);
|
|
43
|
+
lease.release();
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
return enter(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The event already durably binds the generation; replay must not rebind its actor. */
|
|
50
|
+
export function assertCurrentGenerationV3(
|
|
51
|
+
coordRoot: string,
|
|
52
|
+
instanceId: string,
|
|
53
|
+
generationId: string | undefined,
|
|
54
|
+
): void {
|
|
55
|
+
const current = readHookProducerStateByInstanceV3(coordRoot, instanceId as `inst_${string}`);
|
|
56
|
+
if (!generationId || !current || current.generation_id !== generationId) {
|
|
57
|
+
throw new Error("authority_generation_changed");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -33,6 +33,7 @@ import { markObservedClockRegressionV3 } from "../clock-order.ts";
|
|
|
33
33
|
import type { EventV3 } from "../contract.ts";
|
|
34
34
|
import { type EventV3WriteMode, readEventV3ControlState } from "../control.ts";
|
|
35
35
|
import { fingerprintContextV3 } from "../fingerprint-keys.ts";
|
|
36
|
+
import { assertCurrentGenerationV3, withGenerationCustodyV3 } from "../generation-custody.ts";
|
|
36
37
|
import { clockIdV3, spanIdV3 } from "../ids.ts";
|
|
37
38
|
import { closeSpanStateV3, type OpenSpanStateV3, openSpanStateV3 } from "../span-state.ts";
|
|
38
39
|
import { EVENT_V3_LEDGER_RELATIVE_ROOT } from "../writer.ts";
|
|
@@ -103,6 +104,7 @@ export interface RecordCoordinationAuthorityV3Input<
|
|
|
103
104
|
bridge?: "codex-wsl";
|
|
104
105
|
expected_prior_state_digest: `sha256:${string}`;
|
|
105
106
|
desired_state_digest: `sha256:${string}`;
|
|
107
|
+
expected_generation_id?: `gen_${string}`;
|
|
106
108
|
monotonic_ns?: string;
|
|
107
109
|
observed_at?: string;
|
|
108
110
|
reconciler: AuthorityReconcilerV3;
|
|
@@ -129,6 +131,16 @@ export type RecordCoordinationAuthorityV3Result =
|
|
|
129
131
|
*/
|
|
130
132
|
export function recordCoordinationAuthorityV3<S extends CoordinationAuthoritySignalV3>(
|
|
131
133
|
input: RecordCoordinationAuthorityV3Input<S>,
|
|
134
|
+
): RecordCoordinationAuthorityV3Result {
|
|
135
|
+
return withGenerationCustodyV3(
|
|
136
|
+
input.coordRoot,
|
|
137
|
+
[input.actor_instance_id, input.subject_instance_id],
|
|
138
|
+
() => recordCoordinationAuthorityWithCustodyV3(input),
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function recordCoordinationAuthorityWithCustodyV3<S extends CoordinationAuthoritySignalV3>(
|
|
143
|
+
input: RecordCoordinationAuthorityV3Input<S>,
|
|
132
144
|
): RecordCoordinationAuthorityV3Result {
|
|
133
145
|
const control = readEventV3ControlState(input.coordRoot);
|
|
134
146
|
if (control.state !== input.mode) return { state: "gate_closed", reason: control.state };
|
|
@@ -141,6 +153,8 @@ export function recordCoordinationAuthorityV3<S extends CoordinationAuthoritySig
|
|
|
141
153
|
if (
|
|
142
154
|
!hook ||
|
|
143
155
|
hook.terminal ||
|
|
156
|
+
(input.expected_generation_id !== undefined &&
|
|
157
|
+
hook.generation_id !== input.expected_generation_id) ||
|
|
144
158
|
hook.instance_id !== input.actor_instance_id ||
|
|
145
159
|
!hook.last_event_id
|
|
146
160
|
) {
|
|
@@ -328,6 +342,7 @@ export function recordCoordinationAuthorityV3<S extends CoordinationAuthoritySig
|
|
|
328
342
|
mutation: normalized.mutation,
|
|
329
343
|
event: normalized.event,
|
|
330
344
|
});
|
|
345
|
+
assertCurrentGenerationV3(input.coordRoot, state.actor_instance_id, state.generation_id);
|
|
331
346
|
if (input.signal === "wait-ended" && waitId) delete state.open_waits[waitId];
|
|
332
347
|
state.pending = { source_id: sourceId, transaction };
|
|
333
348
|
publishCoordinationState(path, state);
|
|
@@ -43,6 +43,7 @@ import { markObservedClockRegressionV3 } from "../clock-order.ts";
|
|
|
43
43
|
import type { EventV3 } from "../contract.ts";
|
|
44
44
|
import { type EventV3WriteMode, readEventV3ControlState } from "../control.ts";
|
|
45
45
|
import { fingerprintContextV3 } from "../fingerprint-keys.ts";
|
|
46
|
+
import { withGenerationCustodyV3 } from "../generation-custody.ts";
|
|
46
47
|
import {
|
|
47
48
|
attestationIdV3,
|
|
48
49
|
clockIdV3,
|
|
@@ -658,6 +659,17 @@ function processHookSignalLocked(
|
|
|
658
659
|
input: RecordHookSignalV3Input,
|
|
659
660
|
sessionHash: `hid_${string}`,
|
|
660
661
|
path: string,
|
|
662
|
+
): RecordHookSignalV3Result {
|
|
663
|
+
return withGenerationCustodyV3(input.coordRoot, [input.instance_id], () =>
|
|
664
|
+
processHookSignalWithCustodyLocked(control, input, sessionHash, path),
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function processHookSignalWithCustodyLocked(
|
|
669
|
+
control: OpenHookControlStateV3,
|
|
670
|
+
input: RecordHookSignalV3Input,
|
|
671
|
+
sessionHash: `hid_${string}`,
|
|
672
|
+
path: string,
|
|
661
673
|
): RecordHookSignalV3Result {
|
|
662
674
|
const behavior = adapterBehavior(input.adapter);
|
|
663
675
|
const rootId = control.genesis.event.scope.root_id as `root_${string}`;
|
|
@@ -3209,6 +3221,14 @@ function suppressClosedSpanSignal(
|
|
|
3209
3221
|
*/
|
|
3210
3222
|
export function recordApprovedSessionEndV3(
|
|
3211
3223
|
input: RecordApprovedSessionEndV3Input,
|
|
3224
|
+
): RecordApprovedSessionEndV3Result {
|
|
3225
|
+
return withGenerationCustodyV3(input.coordRoot, [input.instance_id], () =>
|
|
3226
|
+
recordApprovedSessionEndWithCustodyV3(input),
|
|
3227
|
+
);
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
function recordApprovedSessionEndWithCustodyV3(
|
|
3231
|
+
input: RecordApprovedSessionEndV3Input,
|
|
3212
3232
|
): RecordApprovedSessionEndV3Result {
|
|
3213
3233
|
const control = readEventV3ControlState(input.coordRoot);
|
|
3214
3234
|
if (control.state !== input.mode) {
|
|
@@ -3556,77 +3576,81 @@ export function reanchorArchivedHookProducersV3(
|
|
|
3556
3576
|
for (const archived of listArchivedHookProducerStatesV3(input.archivedEpoch)) {
|
|
3557
3577
|
const previous = archived.state;
|
|
3558
3578
|
if (previous.terminal || !previous.started_event_id) continue;
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
try {
|
|
3565
|
-
if (existsSync(path)) {
|
|
3566
|
-
const current = readProducerState(path);
|
|
3567
|
-
if (current.epoch_genesis_id === genesisId) continue;
|
|
3579
|
+
withGenerationCustodyV3(input.coordRoot, [previous.instance_id], () => {
|
|
3580
|
+
const path = producerStatePath(input.coordRoot, previous.adapter, archived.sessionHash);
|
|
3581
|
+
const lease = acquireStateLeaseWithRetry(input.coordRoot, path, 40);
|
|
3582
|
+
if (!lease) {
|
|
3583
|
+
throw new Error(`event_v3_rotation_reanchor_busy:${previous.instance_id}`);
|
|
3568
3584
|
}
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
},
|
|
3610
|
-
};
|
|
3611
|
-
sessionStart.payload.resume = {
|
|
3612
|
-
state: "unknown",
|
|
3613
|
-
reason: "epoch_rotation_reanchor",
|
|
3614
|
-
};
|
|
3615
|
-
assertEventV3(sessionStart);
|
|
3616
|
-
commitEventLocked(rotationInput, state, path, sessionStart);
|
|
3617
|
-
sessions += 1;
|
|
3618
|
-
|
|
3619
|
-
if (previous.current_turn_id && previous.current_turn_span) {
|
|
3620
|
-
commitMidFlightTurnStart(rotationInput, state, path, rootId, {
|
|
3585
|
+
try {
|
|
3586
|
+
if (existsSync(path)) {
|
|
3587
|
+
const current = readProducerState(path);
|
|
3588
|
+
if (current.epoch_genesis_id === genesisId) return;
|
|
3589
|
+
}
|
|
3590
|
+
const sourceEvent = `${previous.adapter}.epoch-rotation-reanchor`;
|
|
3591
|
+
const rotationInput: RecordHookSignalV3Input = {
|
|
3592
|
+
coordRoot: input.coordRoot,
|
|
3593
|
+
mode: input.mode,
|
|
3594
|
+
signal: "session-start",
|
|
3595
|
+
payload: {
|
|
3596
|
+
raw: {},
|
|
3597
|
+
...(previous.current_native_turn_id
|
|
3598
|
+
? { turn_id: previous.current_native_turn_id }
|
|
3599
|
+
: {}),
|
|
3600
|
+
...(adapterBehavior(previous.adapter).tracksExecutionMode && previous.cursor_mode
|
|
3601
|
+
? { cursor_mode: previous.cursor_mode }
|
|
3602
|
+
: {}),
|
|
3603
|
+
},
|
|
3604
|
+
adapter: previous.adapter,
|
|
3605
|
+
instance_id: previous.instance_id,
|
|
3606
|
+
producer_id: "prd_agent-hook",
|
|
3607
|
+
build_id: input.build_id,
|
|
3608
|
+
platform: input.platform,
|
|
3609
|
+
observed_at: observedAt,
|
|
3610
|
+
writerOptions: { expectedGenesisId: genesisId },
|
|
3611
|
+
};
|
|
3612
|
+
const state = newProducerState(
|
|
3613
|
+
rotationInput,
|
|
3614
|
+
previous.session_id,
|
|
3615
|
+
epochId,
|
|
3616
|
+
boundaryEventId as `evt_${string}`,
|
|
3617
|
+
genesisId,
|
|
3618
|
+
);
|
|
3619
|
+
const sessionStart = buildMidFlightSessionStart(rotationInput, state, rootId);
|
|
3620
|
+
if (sessionStart.event_type !== "session.started") {
|
|
3621
|
+
throw new Error("rotation re-anchor session start could not be normalized");
|
|
3622
|
+
}
|
|
3623
|
+
sessionStart.provenance = {
|
|
3624
|
+
...sessionStart.provenance,
|
|
3621
3625
|
source_event: sourceEvent,
|
|
3626
|
+
attestation: "derived",
|
|
3622
3627
|
confidence: "high",
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3628
|
+
attribution: {
|
|
3629
|
+
method: "session_env",
|
|
3630
|
+
state: "verified",
|
|
3631
|
+
subject_instance_id: state.instance_id,
|
|
3632
|
+
},
|
|
3633
|
+
};
|
|
3634
|
+
sessionStart.payload.resume = {
|
|
3635
|
+
state: "unknown",
|
|
3636
|
+
reason: "epoch_rotation_reanchor",
|
|
3637
|
+
};
|
|
3638
|
+
assertEventV3(sessionStart);
|
|
3639
|
+
commitEventLocked(rotationInput, state, path, sessionStart);
|
|
3640
|
+
sessions += 1;
|
|
3641
|
+
|
|
3642
|
+
if (previous.current_turn_id && previous.current_turn_span) {
|
|
3643
|
+
commitMidFlightTurnStart(rotationInput, state, path, rootId, {
|
|
3644
|
+
source_event: sourceEvent,
|
|
3645
|
+
confidence: "high",
|
|
3646
|
+
attribution_method: "session_env",
|
|
3647
|
+
});
|
|
3648
|
+
turns += 1;
|
|
3649
|
+
}
|
|
3650
|
+
} finally {
|
|
3651
|
+
lease.release();
|
|
3626
3652
|
}
|
|
3627
|
-
}
|
|
3628
|
-
lease.release();
|
|
3629
|
-
}
|
|
3653
|
+
});
|
|
3630
3654
|
}
|
|
3631
3655
|
return { sessions, turns };
|
|
3632
3656
|
}
|
|
@@ -3864,7 +3888,9 @@ export function readJoinableHookProducerStateV3(
|
|
|
3864
3888
|
instanceId: `inst_${string}`,
|
|
3865
3889
|
): HookProducerStateV3 | undefined {
|
|
3866
3890
|
const direct = readHookProducerStateV3(coordRoot, adapter, nativeSessionId);
|
|
3867
|
-
|
|
3891
|
+
// A terminal native session belonging to this actor is stale authority,
|
|
3892
|
+
// not permission to adopt a replacement published under the same instance.
|
|
3893
|
+
if (direct?.instance_id === instanceId) return direct.terminal ? undefined : direct;
|
|
3868
3894
|
const byInstance = readHookProducerStateByInstanceV3(coordRoot, instanceId);
|
|
3869
3895
|
return byInstance?.adapter === adapter && !byInstance.terminal ? byInstance : undefined;
|
|
3870
3896
|
}
|
|
@@ -124,6 +124,7 @@ export async function runGovernorPlanner(
|
|
|
124
124
|
if (recovered) return recovered;
|
|
125
125
|
}
|
|
126
126
|
let proposalWritten = false;
|
|
127
|
+
let invalidProposal = false;
|
|
127
128
|
try {
|
|
128
129
|
const reviewRunId = reviewWorkflowRunId(request.id);
|
|
129
130
|
const resumingReview = Boolean(
|
|
@@ -153,14 +154,22 @@ export async function runGovernorPlanner(
|
|
|
153
154
|
onLog: input.onLog,
|
|
154
155
|
})
|
|
155
156
|
).result;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
157
|
+
let proposal: GovernorPlanProposal;
|
|
158
|
+
try {
|
|
159
|
+
proposal = normalizeProposal(
|
|
160
|
+
request.id,
|
|
161
|
+
rawProposal,
|
|
162
|
+
input.record,
|
|
163
|
+
history.materialized_work_ids.length,
|
|
164
|
+
request.trigger ?? "recovery",
|
|
165
|
+
reviewInput ? persistedProposalTimestamp(reviewInput.candidate) : undefined,
|
|
166
|
+
);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
// A fresh invalid proposal can consume a replan without aborting the run.
|
|
169
|
+
// Corrupt persisted review input still requires investigation.
|
|
170
|
+
invalidProposal = !reviewInput;
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
164
173
|
if (policy.review) {
|
|
165
174
|
if (proposal.decision === "attention") {
|
|
166
175
|
appendPlanEvent(coordRoot, input.record.intent.id, request.id, {
|
|
@@ -258,6 +267,9 @@ export async function runGovernorPlanner(
|
|
|
258
267
|
reason: bounded((error as Error).message, "plan failure", MAX_REASON),
|
|
259
268
|
...(failureClass ? { class: failureClass } : {}),
|
|
260
269
|
});
|
|
270
|
+
if (invalidProposal) {
|
|
271
|
+
return outcome(readGovernorPlan(coordRoot, input.record.intent.id, request.id));
|
|
272
|
+
}
|
|
261
273
|
throw error;
|
|
262
274
|
}
|
|
263
275
|
}
|