harnery 0.45.1 → 0.45.2
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 +6 -45
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +0 -18
- package/dist/core/agents/state/pidmap.js +2 -2
- package/dist/core/agents/state/proc-start.d.ts +20 -31
- package/dist/core/agents/state/proc-start.d.ts.map +1 -1
- package/dist/core/agents/state/proc-start.js +37 -70
- package/dist/core/agents/state/service-process.d.ts +61 -0
- package/dist/core/agents/state/service-process.d.ts.map +1 -0
- package/dist/core/agents/state/service-process.js +143 -0
- package/dist/core/context/index.d.ts +6 -4
- package/dist/core/context/index.d.ts.map +1 -1
- package/dist/core/context/index.js +194 -256
- package/dist/core/context/observation.d.ts +9 -9
- package/dist/core/context/observation.d.ts.map +1 -1
- package/dist/core/context/observation.js +37 -6
- package/dist/core/events/v3/runtime-build.d.ts +14 -0
- package/dist/core/events/v3/runtime-build.d.ts.map +1 -1
- package/dist/core/events/v3/runtime-build.js +44 -0
- package/dist/core/governor/index.d.ts +2 -1
- package/dist/core/governor/index.d.ts.map +1 -1
- package/dist/core/governor/index.js +2 -1
- package/dist/core/governor/service-read.d.ts +3 -10
- package/dist/core/governor/service-read.d.ts.map +1 -1
- package/dist/core/governor/service-read.js +10 -94
- package/dist/core/governor/service.d.ts +3 -7
- package/dist/core/governor/service.d.ts.map +1 -1
- package/dist/core/governor/service.js +17 -182
- package/dist/core/hooks/resolve/owner.js +2 -2
- package/dist/core/resources/sampler.js +2 -2
- package/dist/core/resources/service-status.d.ts +2 -14
- package/dist/core/resources/service-status.d.ts.map +1 -1
- package/dist/core/resources/service-status.js +3 -23
- package/dist/core/resources/service.d.ts +3 -7
- package/dist/core/resources/service.d.ts.map +1 -1
- package/dist/core/resources/service.js +12 -101
- package/dist/core/resources/status.d.ts.map +1 -1
- package/dist/core/resources/status.js +3 -2
- package/dist/core/semantic/service.d.ts.map +1 -1
- package/dist/core/semantic/service.js +9 -2
- package/dist/core/servers/index.d.ts +0 -2
- package/dist/core/servers/index.d.ts.map +1 -1
- package/dist/core/servers/index.js +3 -18
- package/dist/core/supervisor/service.d.ts +4 -7
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +23 -122
- package/dist/core/supervisor/services.js +2 -2
- package/dist/core/supervisor/status.d.ts +3 -17
- package/dist/core/supervisor/status.d.ts.map +1 -1
- package/dist/core/supervisor/status.js +7 -70
- package/dist/core/workflow/engine.js +2 -1
- package/dist/core/workflow/index.d.ts +3 -4
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/index.js +2 -2
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +30 -74
- package/dist/core/workflow/resume-lock.d.ts +17 -0
- package/dist/core/workflow/resume-lock.d.ts.map +1 -0
- package/dist/core/workflow/resume-lock.js +158 -0
- package/dist/core/workflow/run-state.d.ts +0 -21
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +3 -174
- package/dist/core/workflow/workspaces/leases.d.ts +4 -0
- package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.js +1 -1
- package/dist/core/workflow/workspaces/local-git.js +91 -210
- package/dist/core/workflow/workspaces/ownership.d.ts +0 -7
- package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/ownership.js +3 -130
- package/dist/core/workflow/workspaces/reclaim.d.ts +4 -7
- package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/reclaim.js +0 -4
- package/dist/core/workflow/workspaces/recovery.d.ts +1 -1
- package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/recovery.js +1 -1
- package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.js +1 -4
- package/package.json +1 -1
- package/src/commands/workflow.ts +8 -58
- package/src/core/agents/coord-client.ts +0 -20
- package/src/core/agents/state/pidmap.ts +2 -2
- package/src/core/agents/state/proc-start.ts +41 -69
- package/src/core/agents/state/service-process.ts +217 -0
- package/src/core/context/index.ts +207 -272
- package/src/core/context/observation.ts +40 -9
- package/src/core/events/v3/runtime-build.ts +62 -0
- package/src/core/governor/index.ts +1 -2
- package/src/core/governor/service-read.ts +15 -114
- package/src/core/governor/service.ts +36 -220
- package/src/core/hooks/resolve/owner.ts +2 -2
- package/src/core/resources/sampler.ts +3 -2
- package/src/core/resources/service-status.ts +6 -35
- package/src/core/resources/service.ts +23 -133
- package/src/core/resources/status.ts +3 -2
- package/src/core/semantic/service.ts +8 -2
- package/src/core/servers/index.ts +3 -23
- package/src/core/supervisor/service.ts +33 -155
- package/src/core/supervisor/services.ts +2 -2
- package/src/core/supervisor/status.ts +10 -85
- package/src/core/workflow/engine.ts +1 -1
- package/src/core/workflow/index.ts +2 -13
- package/src/core/workflow/proof.ts +35 -83
- package/src/core/workflow/resume-lock.ts +179 -0
- package/src/core/workflow/run-state.ts +3 -207
- package/src/core/workflow/workspaces/leases.ts +1 -1
- package/src/core/workflow/workspaces/local-git.ts +120 -239
- package/src/core/workflow/workspaces/ownership.ts +3 -158
- package/src/core/workflow/workspaces/reclaim.ts +3 -9
- package/src/core/workflow/workspaces/recovery.ts +1 -1
- package/src/core/workflow/workspaces/state.ts +1 -5
- package/dist/core/agents/state/process-handle.d.ts +0 -26
- package/dist/core/agents/state/process-handle.d.ts.map +0 -1
- package/dist/core/agents/state/process-handle.js +0 -121
- package/dist/core/workflow/recovery-audit.d.ts +0 -30
- package/dist/core/workflow/recovery-audit.d.ts.map +0 -1
- package/dist/core/workflow/recovery-audit.js +0 -116
- package/src/core/agents/state/process-handle.ts +0 -165
- package/src/core/workflow/recovery-audit.ts +0 -181
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/context/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/context/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,16 +6,15 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { spawnSync } from "node:child_process";
|
|
8
8
|
import { createHash, randomUUID } from "node:crypto";
|
|
9
|
-
import {
|
|
10
|
-
import { dirname,
|
|
9
|
+
import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
11
|
import { readLiveCoordinationRow } from "../agents/state/live-coordination-view.js";
|
|
12
12
|
import { resolveBinName } from "../config.js";
|
|
13
13
|
import { peerInbox } from "../inbox/peer-messages.js";
|
|
14
14
|
import { project } from "../inbox/service.js";
|
|
15
15
|
import { stateFileMode } from "../storage/modes.js";
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
export { observationsComparable } from "./observation.js";
|
|
16
|
+
import { repoComparisonProblems } from "./observation.js";
|
|
17
|
+
export { repoComparisonProblems } from "./observation.js";
|
|
19
18
|
export const CONTEXT_SCHEMA_VERSION = 1;
|
|
20
19
|
export const MAX_CAPSULE_BYTES = 32 * 1024;
|
|
21
20
|
const MAX_SNAPSHOT_PATHS = 15;
|
|
@@ -70,26 +69,24 @@ export function extractContextSample(payload, options) {
|
|
|
70
69
|
}
|
|
71
70
|
/** Persist a latest context sample. Identical measurements are a no-op. */
|
|
72
71
|
export function recordContextSample(coordRoot, instanceId, sample) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
:
|
|
78
|
-
|
|
79
|
-
generation: current?.generation ?? 0,
|
|
80
|
-
};
|
|
81
|
-
const changed = !sameMeasurement(state.latest_context, sample);
|
|
82
|
-
if (!changed)
|
|
83
|
-
return { state, changed: false };
|
|
84
|
-
const next = {
|
|
85
|
-
...state,
|
|
86
|
-
instance_id: instanceId,
|
|
87
|
-
latest_context: sample,
|
|
88
|
-
updated_at: sample.observed_at,
|
|
72
|
+
const current = readContextState(coordRoot, sample.session_id);
|
|
73
|
+
const state = current?.instance_id === instanceId
|
|
74
|
+
? current
|
|
75
|
+
: {
|
|
76
|
+
...newState({ sessionId: sample.session_id, instanceId, now: sample.observed_at }),
|
|
77
|
+
generation: current?.generation ?? 0,
|
|
89
78
|
};
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
79
|
+
const changed = !sameMeasurement(state.latest_context, sample);
|
|
80
|
+
if (!changed)
|
|
81
|
+
return { state, changed: false };
|
|
82
|
+
const next = {
|
|
83
|
+
...state,
|
|
84
|
+
instance_id: instanceId,
|
|
85
|
+
latest_context: sample,
|
|
86
|
+
updated_at: sample.observed_at,
|
|
87
|
+
};
|
|
88
|
+
writeState(coordRoot, next);
|
|
89
|
+
return { state: next, changed: true };
|
|
93
90
|
}
|
|
94
91
|
/**
|
|
95
92
|
* Snapshot external work state. Repeated PreCompact events reuse the existing
|
|
@@ -97,105 +94,100 @@ export function recordContextSample(coordRoot, instanceId, sample) {
|
|
|
97
94
|
* create a new generation.
|
|
98
95
|
*/
|
|
99
96
|
export function checkpointContext(coordRoot, input) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
:
|
|
106
|
-
|
|
107
|
-
|
|
97
|
+
const now = new Date().toISOString();
|
|
98
|
+
const previous = readContextState(coordRoot, input.sessionId);
|
|
99
|
+
const current = previous?.instance_id === input.instanceId
|
|
100
|
+
? previous
|
|
101
|
+
: {
|
|
102
|
+
...newState({ sessionId: input.sessionId, instanceId: input.instanceId, now }),
|
|
103
|
+
generation: previous?.generation ?? 0,
|
|
104
|
+
};
|
|
105
|
+
if (input.reason === "pre_compact" && current.phase === "checkpointed") {
|
|
106
|
+
const existing = readLatestCapsule(coordRoot, input.sessionId);
|
|
107
|
+
if (existing && capsuleMatchesState(current, existing, input) && current.latest_capsule) {
|
|
108
|
+
return {
|
|
109
|
+
capsule: existing,
|
|
110
|
+
path: join(coordRoot, ".harnery", current.latest_capsule),
|
|
111
|
+
state: current,
|
|
112
|
+
reused: true,
|
|
108
113
|
};
|
|
109
|
-
if (input.reason === "pre_compact" && current.phase === "checkpointed") {
|
|
110
|
-
const existing = readLatestCapsule(coordRoot, input.sessionId);
|
|
111
|
-
if (existing && capsuleMatchesState(current, existing, input) && current.latest_capsule) {
|
|
112
|
-
return {
|
|
113
|
-
capsule: existing,
|
|
114
|
-
path: join(coordRoot, ".harnery", current.latest_capsule),
|
|
115
|
-
state: current,
|
|
116
|
-
reused: true,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
114
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
adapter: input.adapter,
|
|
132
|
-
model: input.model ?? current.latest_context?.model ?? heartbeat?.model,
|
|
133
|
-
},
|
|
134
|
-
context: current.latest_context,
|
|
135
|
-
work: buildWorkSnapshot(heartbeat, input.continuationNote),
|
|
136
|
-
repo: snapshotRepo(input.cwd),
|
|
137
|
-
};
|
|
138
|
-
const relativePath = capsuleRelativePath(input.sessionId, generation);
|
|
139
|
-
const absolutePath = join(coordRoot, ".harnery", relativePath);
|
|
140
|
-
writeBoundedJson(absolutePath, capsule, MAX_CAPSULE_BYTES);
|
|
141
|
-
const next = {
|
|
142
|
-
...current,
|
|
115
|
+
}
|
|
116
|
+
const heartbeat = readHeartbeat(coordRoot, input.instanceId);
|
|
117
|
+
const generation = current.generation + 1;
|
|
118
|
+
const capsule = {
|
|
119
|
+
schema_version: CONTEXT_SCHEMA_VERSION,
|
|
120
|
+
capsule_id: randomUUID(),
|
|
121
|
+
generation,
|
|
122
|
+
created_at: now,
|
|
123
|
+
reason: input.reason,
|
|
124
|
+
session: {
|
|
125
|
+
session_id: input.sessionId,
|
|
143
126
|
instance_id: input.instanceId,
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
127
|
+
adapter: input.adapter,
|
|
128
|
+
model: input.model ?? current.latest_context?.model ?? heartbeat?.model,
|
|
129
|
+
},
|
|
130
|
+
context: current.latest_context,
|
|
131
|
+
work: buildWorkSnapshot(heartbeat, input.continuationNote),
|
|
132
|
+
repo: snapshotRepo(input.cwd),
|
|
133
|
+
};
|
|
134
|
+
const relativePath = capsuleRelativePath(input.sessionId, generation);
|
|
135
|
+
const absolutePath = join(coordRoot, ".harnery", relativePath);
|
|
136
|
+
writeBoundedJson(absolutePath, capsule, MAX_CAPSULE_BYTES);
|
|
137
|
+
const next = {
|
|
138
|
+
...current,
|
|
139
|
+
instance_id: input.instanceId,
|
|
140
|
+
phase: "checkpointed",
|
|
141
|
+
generation,
|
|
142
|
+
latest_capsule: relativePath,
|
|
143
|
+
updated_at: now,
|
|
144
|
+
recovered_at: undefined,
|
|
145
|
+
compaction_completed_at: undefined,
|
|
146
|
+
degraded_reason: undefined,
|
|
147
|
+
};
|
|
148
|
+
writeState(coordRoot, next);
|
|
149
|
+
return { capsule, path: absolutePath, state: next, reused: false };
|
|
155
150
|
}
|
|
156
151
|
/** Prepare a briefing without claiming that a adapter has accepted it yet. */
|
|
157
152
|
export function prepareContextRecovery(coordRoot, input) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
: "post-compaction signal arrived without a continuity checkpoint",
|
|
175
|
-
};
|
|
176
|
-
writeState(coordRoot, degraded);
|
|
177
|
-
return null;
|
|
178
|
-
}
|
|
179
|
-
const liveRepo = snapshotRepo(input.cwd);
|
|
180
|
-
const briefing = [
|
|
181
|
-
renderRecoveryBriefing(capsule, liveRepo),
|
|
182
|
-
renderRecoveryInput(coordRoot, input.instanceId),
|
|
183
|
-
]
|
|
184
|
-
.filter(Boolean)
|
|
185
|
-
.join("\n\n");
|
|
186
|
-
return {
|
|
187
|
-
state: current,
|
|
188
|
-
capsule,
|
|
189
|
-
briefing,
|
|
190
|
-
checkpoint: Object.freeze({
|
|
191
|
-
session_id: input.sessionId,
|
|
192
|
-
instance_id: input.instanceId,
|
|
193
|
-
generation: capsule.generation,
|
|
194
|
-
capsule_id: capsule.capsule_id,
|
|
195
|
-
capsule_sha256: capsuleDigest(capsule),
|
|
196
|
-
}),
|
|
153
|
+
const now = new Date().toISOString();
|
|
154
|
+
const current = readContextState(coordRoot, input.sessionId) ??
|
|
155
|
+
newState({ sessionId: input.sessionId, instanceId: input.instanceId, now });
|
|
156
|
+
// A reused provider ID does not transfer the prior task's context.
|
|
157
|
+
if (current.instance_id !== input.instanceId)
|
|
158
|
+
return null;
|
|
159
|
+
const capsule = readLatestCapsule(coordRoot, input.sessionId);
|
|
160
|
+
if (!capsule || !capsuleMatchesState(current, capsule, input)) {
|
|
161
|
+
const degraded = {
|
|
162
|
+
...current,
|
|
163
|
+
instance_id: input.instanceId,
|
|
164
|
+
phase: "degraded",
|
|
165
|
+
updated_at: now,
|
|
166
|
+
degraded_reason: capsule
|
|
167
|
+
? "continuity checkpoint does not match the requesting instance and state"
|
|
168
|
+
: "post-compaction signal arrived without a continuity checkpoint",
|
|
197
169
|
};
|
|
198
|
-
|
|
170
|
+
writeState(coordRoot, degraded);
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const liveRepo = snapshotRepo(input.cwd);
|
|
174
|
+
const briefing = [
|
|
175
|
+
renderRecoveryBriefing(capsule, liveRepo),
|
|
176
|
+
renderRecoveryInput(coordRoot, input.instanceId),
|
|
177
|
+
]
|
|
178
|
+
.filter(Boolean)
|
|
179
|
+
.join("\n\n");
|
|
180
|
+
return {
|
|
181
|
+
state: current,
|
|
182
|
+
capsule,
|
|
183
|
+
briefing,
|
|
184
|
+
checkpoint: Object.freeze({
|
|
185
|
+
session_id: input.sessionId,
|
|
186
|
+
instance_id: input.instanceId,
|
|
187
|
+
generation: capsule.generation,
|
|
188
|
+
capsule_id: capsule.capsule_id,
|
|
189
|
+
}),
|
|
190
|
+
};
|
|
199
191
|
}
|
|
200
192
|
/** Recovery lists input without surfacing it again or changing its receipts. */
|
|
201
193
|
function renderRecoveryInput(coordRoot, instanceId) {
|
|
@@ -244,66 +236,61 @@ function inputLine(messageId, status, body) {
|
|
|
244
236
|
}
|
|
245
237
|
/**
|
|
246
238
|
* Advance continuity only after the adapter accepted the prepared briefing.
|
|
247
|
-
*
|
|
248
|
-
*
|
|
239
|
+
* A foreign or superseded checkpoint refuses without writing, and repeating
|
|
240
|
+
* the same accepted checkpoint is an exact no-op. The write is a
|
|
241
|
+
* compare-and-write: if the state changed after it was read, for example
|
|
242
|
+
* because a newer checkpoint landed, completion refuses instead of
|
|
243
|
+
* overwriting it.
|
|
249
244
|
*/
|
|
250
245
|
export function completeContextRecovery(coordRoot, input) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
recovered_at: now,
|
|
280
|
-
degraded_reason: undefined,
|
|
281
|
-
};
|
|
282
|
-
writeState(coordRoot, recovered);
|
|
283
|
-
return recovered;
|
|
284
|
-
});
|
|
246
|
+
const path = statePath(coordRoot, input.sessionId);
|
|
247
|
+
const observed = readText(path);
|
|
248
|
+
if (observed === null)
|
|
249
|
+
return null;
|
|
250
|
+
const current = parseJson(observed);
|
|
251
|
+
const { checkpoint } = input;
|
|
252
|
+
if (!current ||
|
|
253
|
+
checkpoint.session_id !== input.sessionId ||
|
|
254
|
+
checkpoint.instance_id !== input.instanceId ||
|
|
255
|
+
checkpoint.generation !== current.generation ||
|
|
256
|
+
(current.phase !== "checkpointed" && current.phase !== "recovered"))
|
|
257
|
+
return null;
|
|
258
|
+
const capsule = readLatestCapsule(coordRoot, input.sessionId);
|
|
259
|
+
if (!capsule ||
|
|
260
|
+
!capsuleMatchesState(current, capsule, input) ||
|
|
261
|
+
capsule.capsule_id !== checkpoint.capsule_id)
|
|
262
|
+
return null;
|
|
263
|
+
if (current.phase === "recovered")
|
|
264
|
+
return current;
|
|
265
|
+
const now = new Date().toISOString();
|
|
266
|
+
const recovered = {
|
|
267
|
+
...current,
|
|
268
|
+
phase: "recovered",
|
|
269
|
+
updated_at: now,
|
|
270
|
+
recovered_at: now,
|
|
271
|
+
degraded_reason: undefined,
|
|
272
|
+
};
|
|
273
|
+
return writeBoundedJson(path, recovered, MAX_CAPSULE_BYTES, observed) ? recovered : null;
|
|
285
274
|
}
|
|
286
275
|
/** Record that the adapter, rather than Harnery, reported compaction complete. */
|
|
287
276
|
export function markContextCompactionCompleted(coordRoot, input) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return next;
|
|
306
|
-
});
|
|
277
|
+
const now = input.observedAt ?? new Date().toISOString();
|
|
278
|
+
const current = readContextState(coordRoot, input.sessionId) ??
|
|
279
|
+
newState({ sessionId: input.sessionId, instanceId: input.instanceId, now });
|
|
280
|
+
if (current.instance_id !== input.instanceId)
|
|
281
|
+
return current;
|
|
282
|
+
const next = {
|
|
283
|
+
...current,
|
|
284
|
+
instance_id: input.instanceId,
|
|
285
|
+
phase: current.latest_capsule ? current.phase : "degraded",
|
|
286
|
+
compaction_completed_at: now,
|
|
287
|
+
updated_at: now,
|
|
288
|
+
degraded_reason: current.latest_capsule
|
|
289
|
+
? current.degraded_reason
|
|
290
|
+
: "post-compaction signal arrived without a continuity checkpoint",
|
|
291
|
+
};
|
|
292
|
+
writeState(coordRoot, next);
|
|
293
|
+
return next;
|
|
307
294
|
}
|
|
308
295
|
/** Convenience API for callers whose injection is synchronous and guaranteed. */
|
|
309
296
|
export function recoverContext(coordRoot, input) {
|
|
@@ -337,25 +324,9 @@ export function readContextState(coordRoot, sessionId) {
|
|
|
337
324
|
}
|
|
338
325
|
export function readLatestCapsule(coordRoot, sessionId) {
|
|
339
326
|
const state = readContextState(coordRoot, sessionId);
|
|
340
|
-
if (
|
|
341
|
-
return null;
|
|
342
|
-
const capsule = readJson(join(coordRoot, ".harnery", state.latest_capsule));
|
|
343
|
-
if (!capsule ||
|
|
344
|
-
!record(capsule.repo) ||
|
|
345
|
-
!record(capsule.work) ||
|
|
346
|
-
typeof capsule.repo.cwd !== "string" ||
|
|
347
|
-
!Array.isArray(capsule.repo.dirty_paths) ||
|
|
348
|
-
!capsule.repo.dirty_paths.every((path) => typeof path === "string") ||
|
|
349
|
-
!Array.isArray(capsule.work.files_held) ||
|
|
350
|
-
!capsule.work.files_held.every((path) => typeof path === "string")) {
|
|
327
|
+
if (!state?.latest_capsule)
|
|
351
328
|
return null;
|
|
352
|
-
|
|
353
|
-
capsule.repo.observation = repoObservation(capsule.repo.observation);
|
|
354
|
-
capsule.repo.observation_detail = clampString(string(capsule.repo.observation_detail), MAX_OBSERVATION_DETAIL_CHARS);
|
|
355
|
-
capsule.repo.branch = string(capsule.repo.branch)?.trim() || undefined;
|
|
356
|
-
capsule.repo.head = string(capsule.repo.head)?.trim() || undefined;
|
|
357
|
-
capsule.repo.root = string(capsule.repo.root)?.trim() || undefined;
|
|
358
|
-
return capsule;
|
|
329
|
+
return readJson(join(coordRoot, ".harnery", state.latest_capsule));
|
|
359
330
|
}
|
|
360
331
|
export function renderRecoveryBriefing(capsule, liveRepo) {
|
|
361
332
|
const lines = [
|
|
@@ -369,44 +340,20 @@ export function renderRecoveryBriefing(capsule, liveRepo) {
|
|
|
369
340
|
if (capsule.work.files_held.length > 0) {
|
|
370
341
|
lines.push(`Files held: ${capsule.work.files_held.join(", ")}`);
|
|
371
342
|
}
|
|
372
|
-
const unknown = [
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
["current repository", liveRepo],
|
|
377
|
-
]) {
|
|
378
|
-
if (repo.observation !== "ok") {
|
|
379
|
-
const detail = clampString(string(repo.observation_detail), MAX_OBSERVATION_DETAIL_CHARS);
|
|
380
|
-
unknown.push(`${label}: ${repoObservation(repo.observation)}${detail ? ` (${detail})` : ""}`);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
if (capsule.repo.dirty_paths_truncated || liveRepo.dirty_paths_truncated) {
|
|
385
|
-
unknown.push("dirty path list is truncated");
|
|
386
|
-
}
|
|
387
|
-
if (!capsule.repo.root ||
|
|
388
|
-
!liveRepo.root ||
|
|
389
|
-
!isAbsolute(capsule.repo.root) ||
|
|
390
|
-
!isAbsolute(liveRepo.root)) {
|
|
391
|
-
unknown.push("repository root is unknown");
|
|
392
|
-
}
|
|
393
|
-
else if (capsule.repo.root !== liveRepo.root) {
|
|
394
|
-
unknown.push("repository root differs from the checkpoint");
|
|
395
|
-
}
|
|
396
|
-
if (!capsule.repo.branch?.trim() || !liveRepo.branch?.trim())
|
|
397
|
-
unknown.push("branch is unknown");
|
|
398
|
-
if (!capsule.repo.head?.trim() || !liveRepo.head?.trim())
|
|
399
|
-
unknown.push("HEAD is unknown");
|
|
343
|
+
const unknown = repoComparisonProblems(capsule.repo, liveRepo, [
|
|
344
|
+
"checkpoint",
|
|
345
|
+
"current repository",
|
|
346
|
+
]);
|
|
400
347
|
if (unknown.length > 0) {
|
|
401
348
|
lines.push(`Repository could not be verified against the checkpoint: ${unknown.join("; ")}. Re-check before editing.`);
|
|
402
349
|
return lines.join("\n");
|
|
403
350
|
}
|
|
404
351
|
const drift = [];
|
|
405
|
-
if (capsule.repo.branch
|
|
352
|
+
if (capsule.repo.branch !== liveRepo.branch) {
|
|
406
353
|
drift.push(`branch changed ${capsule.repo.branch} -> ${liveRepo.branch}`);
|
|
407
354
|
}
|
|
408
|
-
if (capsule.repo.head
|
|
409
|
-
drift.push(`HEAD changed ${capsule.repo.head
|
|
355
|
+
if (capsule.repo.head !== liveRepo.head) {
|
|
356
|
+
drift.push(`HEAD changed ${capsule.repo.head?.slice(0, 8)} -> ${liveRepo.head?.slice(0, 8)}`);
|
|
410
357
|
}
|
|
411
358
|
const beforeDirty = new Set(capsule.repo.dirty_paths);
|
|
412
359
|
const afterDirty = new Set(liveRepo.dirty_paths);
|
|
@@ -510,39 +457,13 @@ function newState(input) {
|
|
|
510
457
|
updated_at: input.now,
|
|
511
458
|
};
|
|
512
459
|
}
|
|
460
|
+
/** The capsule is the one the state names, written for the requesting task. */
|
|
513
461
|
function capsuleMatchesState(state, capsule, input) {
|
|
514
|
-
return (state.
|
|
515
|
-
|
|
516
|
-
capsule.session?.session_id === input.sessionId &&
|
|
517
|
-
capsule.session?.instance_id === input.instanceId &&
|
|
518
|
-
capsule.schema_version === CONTEXT_SCHEMA_VERSION &&
|
|
519
|
-
Number.isSafeInteger(capsule.generation) &&
|
|
520
|
-
capsule.generation > 0 &&
|
|
462
|
+
return (state.instance_id === input.instanceId &&
|
|
463
|
+
capsule.session.instance_id === input.instanceId &&
|
|
521
464
|
capsule.generation === state.generation &&
|
|
522
|
-
typeof capsule.capsule_id === "string" &&
|
|
523
|
-
capsule.capsule_id.length > 0 &&
|
|
524
465
|
state.latest_capsule === capsuleRelativePath(input.sessionId, capsule.generation));
|
|
525
466
|
}
|
|
526
|
-
function capsuleDigest(capsule) {
|
|
527
|
-
return createHash("sha256").update(JSON.stringify(capsule)).digest("hex");
|
|
528
|
-
}
|
|
529
|
-
/** Every state writer shares the same crash-recoverable session reservation. */
|
|
530
|
-
function withContextMutation(coordRoot, sessionId, action) {
|
|
531
|
-
const authority = createHash("sha256").update(sessionId).digest("hex");
|
|
532
|
-
const lease = acquireNoClobberLease({
|
|
533
|
-
path: join(contextDir(coordRoot, sessionId), "mutation.lease"),
|
|
534
|
-
scope: "context-continuity",
|
|
535
|
-
authoritySha256: authority,
|
|
536
|
-
staleAfterMs: 30_000,
|
|
537
|
-
validateStaleOwner: (owner) => owner.authority_sha256 === authority,
|
|
538
|
-
});
|
|
539
|
-
try {
|
|
540
|
-
return action();
|
|
541
|
-
}
|
|
542
|
-
finally {
|
|
543
|
-
lease.release();
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
467
|
function writeState(coordRoot, state) {
|
|
547
468
|
writeBoundedJson(statePath(coordRoot, state.session_id), state, MAX_CAPSULE_BYTES);
|
|
548
469
|
}
|
|
@@ -558,7 +479,12 @@ function contextDir(coordRoot, sessionId) {
|
|
|
558
479
|
function sessionKey(sessionId) {
|
|
559
480
|
return createHash("sha256").update(sessionId).digest("hex").slice(0, 24);
|
|
560
481
|
}
|
|
561
|
-
|
|
482
|
+
/**
|
|
483
|
+
* Atomically replace a record. With `expected`, the replacement happens only
|
|
484
|
+
* while the file still holds exactly those bytes, so a concurrent writer's
|
|
485
|
+
* newer record is never overwritten by a decision made on older state.
|
|
486
|
+
*/
|
|
487
|
+
function writeBoundedJson(path, value, maxBytes, expected) {
|
|
562
488
|
const text = `${JSON.stringify(value, null, 2)}\n`;
|
|
563
489
|
const bytes = Buffer.byteLength(text, "utf8");
|
|
564
490
|
if (bytes > maxBytes) {
|
|
@@ -567,18 +493,33 @@ function writeBoundedJson(path, value, maxBytes) {
|
|
|
567
493
|
mkdirSync(dirname(path), { recursive: true });
|
|
568
494
|
const temp = `${path}.tmp.${process.pid}.${randomUUID()}`;
|
|
569
495
|
writeFileSync(temp, text, { encoding: "utf8", mode: stateFileMode() });
|
|
496
|
+
if (expected !== undefined && readText(path) !== expected) {
|
|
497
|
+
unlinkSync(temp);
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
570
500
|
renameSync(temp, path);
|
|
501
|
+
return true;
|
|
571
502
|
}
|
|
572
|
-
function
|
|
573
|
-
|
|
503
|
+
function readText(path) {
|
|
504
|
+
try {
|
|
505
|
+
return readFileSync(path, "utf8");
|
|
506
|
+
}
|
|
507
|
+
catch {
|
|
574
508
|
return null;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function parseJson(text) {
|
|
575
512
|
try {
|
|
576
|
-
return JSON.parse(
|
|
513
|
+
return JSON.parse(text);
|
|
577
514
|
}
|
|
578
515
|
catch {
|
|
579
516
|
return null;
|
|
580
517
|
}
|
|
581
518
|
}
|
|
519
|
+
function readJson(path) {
|
|
520
|
+
const text = readText(path);
|
|
521
|
+
return text === null ? null : parseJson(text);
|
|
522
|
+
}
|
|
582
523
|
function git(cwd, args) {
|
|
583
524
|
try {
|
|
584
525
|
const result = spawnSync("git", args, { cwd, encoding: "utf8", timeout: 2_000 });
|
|
@@ -601,9 +542,6 @@ function git(cwd, args) {
|
|
|
601
542
|
return { ok: false, reason: "spawn_error" };
|
|
602
543
|
}
|
|
603
544
|
}
|
|
604
|
-
function repoObservation(value) {
|
|
605
|
-
return value === "ok" || value === "partial" ? value : "unavailable";
|
|
606
|
-
}
|
|
607
545
|
function sameMeasurement(a, b) {
|
|
608
546
|
if (!a)
|
|
609
547
|
return false;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Repository observation contract shared by context recovery and workflow proof.
|
|
3
3
|
*
|
|
4
|
-
* This module
|
|
5
|
-
* dashboard, proof readers) can use it without reaching the
|
|
6
|
-
* snapshot code in ./index.ts.
|
|
4
|
+
* This module runs no Git and touches no files, so read-only consumers (the
|
|
5
|
+
* Codec dashboard, proof readers) can use it without reaching the
|
|
6
|
+
* Git-running snapshot code in ./index.ts.
|
|
7
7
|
*/
|
|
8
8
|
export type RepoObservation = "ok" | "unavailable" | "partial";
|
|
9
9
|
export interface RepoSnapshot {
|
|
@@ -17,10 +17,10 @@ export interface RepoSnapshot {
|
|
|
17
17
|
dirty_paths: string[];
|
|
18
18
|
dirty_paths_truncated?: boolean;
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Every reason two snapshots cannot establish repository drift. Only an empty
|
|
22
|
+
* list lets a caller compare branch, HEAD and dirty paths; anything else
|
|
23
|
+
* leaves the whole comparison unknown.
|
|
24
|
+
*/
|
|
25
|
+
export declare function repoComparisonProblems(a: RepoSnapshot, b: RepoSnapshot, labels: readonly [string, string]): string[];
|
|
26
26
|
//# sourceMappingURL=observation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observation.d.ts","sourceRoot":"","sources":["../../../src/core/context/observation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"observation.d.ts","sourceRoot":"","sources":["../../../src/core/context/observation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,aAAa,GAAG,SAAS,CAAC;AAE/D,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,eAAe,CAAC;IAC7B,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,CAAC,EAAE,YAAY,EACf,CAAC,EAAE,YAAY,EACf,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,MAAM,EAAE,CA0BV"}
|