harnery 0.37.0 → 0.38.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/dist/commander.d.ts +9 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +7 -2
- package/dist/commands/admission.d.ts +21 -0
- package/dist/commands/admission.d.ts.map +1 -0
- package/dist/commands/admission.js +565 -0
- package/dist/commands/agents.d.ts +7 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +61 -1
- package/dist/commands/artifacts.d.ts.map +1 -1
- package/dist/commands/artifacts.js +48 -2
- package/dist/commands/browse-ai.d.ts +2 -2
- package/dist/commands/browse-ai.d.ts.map +1 -1
- package/dist/commands/browse-ai.js +6 -4
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +349 -21
- package/dist/commands/fetch.js +1 -0
- package/dist/commands/qa-record.d.ts +144 -0
- package/dist/commands/qa-record.d.ts.map +1 -0
- package/dist/commands/qa-record.js +0 -0
- package/dist/commands/qa-run.d.ts +7 -3
- package/dist/commands/qa-run.d.ts.map +1 -1
- package/dist/commands/qa-run.js +268 -13
- package/dist/commands/qa-status.d.ts +71 -0
- package/dist/commands/qa-status.d.ts.map +1 -0
- package/dist/commands/qa-status.js +490 -0
- package/dist/commands/qa-verify.d.ts +40 -0
- package/dist/commands/qa-verify.d.ts.map +1 -0
- package/dist/commands/qa-verify.js +180 -0
- package/dist/commands/review-pack.d.ts +4 -0
- package/dist/commands/review-pack.d.ts.map +1 -0
- package/dist/commands/review-pack.js +1001 -0
- package/dist/core/agents/qa-signal.d.ts +111 -0
- package/dist/core/agents/qa-signal.d.ts.map +1 -0
- package/dist/core/agents/qa-signal.js +231 -0
- package/dist/core/agents/session-name-display.d.ts +20 -5
- package/dist/core/agents/session-name-display.d.ts.map +1 -1
- package/dist/core/agents/session-name-display.js +67 -7
- package/dist/core/agents/state/heartbeat-reader.d.ts +7 -0
- package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.d.ts +11 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +17 -0
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +1 -0
- package/dist/core/agents/state/live-coordination-writer.js +5 -0
- package/dist/core/artifacts/constants.d.ts +1 -1
- package/dist/core/artifacts/constants.js +1 -1
- package/dist/core/artifacts/index.d.ts +57 -6
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +265 -10
- package/dist/core/config.d.ts +8 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +16 -0
- package/dist/core/diagnostics/bundle.d.ts +16 -0
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +101 -7
- package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
- package/dist/core/events/v3/bootstrap.js +10 -0
- package/dist/core/events/v3/coordination-view.d.ts +3 -0
- package/dist/core/events/v3/coordination-view.d.ts.map +1 -1
- package/dist/core/events/v3/coordination-view.js +69 -8
- package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
- package/dist/core/events/v3/producers/intake.js +9 -2
- package/dist/core/events/v3/producers/recorder.d.ts +23 -0
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +264 -18
- package/dist/core/hooks/cli.js +120 -27
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +10 -3
- package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
- package/dist/core/hooks/session-name-presence.js +4 -1
- package/dist/core/qa-artifacts.d.ts +20 -0
- package/dist/core/qa-artifacts.d.ts.map +1 -0
- package/dist/core/qa-artifacts.js +110 -0
- package/dist/core/resources/contract.d.ts +6 -0
- package/dist/core/resources/contract.d.ts.map +1 -1
- package/dist/core/resources/sampler.d.ts +7 -0
- package/dist/core/resources/sampler.d.ts.map +1 -1
- package/dist/core/resources/sampler.js +106 -5
- package/dist/lib/admission.d.ts +71 -0
- package/dist/lib/admission.d.ts.map +1 -0
- package/dist/lib/admission.js +264 -0
- package/dist/lib/agent-browser/client.d.ts +1 -1
- package/dist/lib/agent-browser/client.d.ts.map +1 -1
- package/dist/lib/agent-browser/client.js +1 -5
- package/dist/lib/browser/capture-fidelity.d.ts +39 -0
- package/dist/lib/browser/capture-fidelity.d.ts.map +1 -0
- package/dist/lib/browser/capture-fidelity.js +84 -0
- package/dist/lib/browser/client.d.ts +41 -1
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +167 -9
- package/dist/lib/browser/critique.d.ts +38 -1
- package/dist/lib/browser/critique.d.ts.map +1 -1
- package/dist/lib/browser/critique.js +34 -6
- package/dist/lib/browser/index.d.ts +4 -2
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +2 -0
- package/dist/lib/browser/page-review-judge.d.ts +64 -0
- package/dist/lib/browser/page-review-judge.d.ts.map +1 -0
- package/dist/lib/browser/page-review-judge.js +270 -0
- package/dist/lib/browser/page-review-pack.d.ts +613 -0
- package/dist/lib/browser/page-review-pack.d.ts.map +1 -0
- package/dist/lib/browser/page-review-pack.js +1751 -0
- package/dist/lib/browser/qa-run-contracts.d.ts +214 -10
- package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
- package/dist/lib/browser/qa-run-contracts.js +136 -1
- package/dist/lib/browser/qa-run.d.ts +100 -10
- package/dist/lib/browser/qa-run.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.js +768 -169
- package/dist/lib/browser/request-diagnostics.d.ts +13 -0
- package/dist/lib/browser/request-diagnostics.d.ts.map +1 -0
- package/dist/lib/browser/request-diagnostics.js +18 -0
- package/dist/lib/browser/tiling.d.ts +19 -0
- package/dist/lib/browser/tiling.d.ts.map +1 -1
- package/dist/lib/browser/tiling.js +28 -0
- package/dist/lib/cookies/client.d.ts +9 -0
- package/dist/lib/cookies/client.d.ts.map +1 -1
- package/dist/lib/cookies/client.js +197 -44
- package/dist/lib/cookies/extra.d.ts +18 -0
- package/dist/lib/cookies/extra.d.ts.map +1 -0
- package/dist/lib/cookies/extra.js +14 -0
- package/dist/lib/cookies/index.d.ts +2 -1
- package/dist/lib/cookies/index.d.ts.map +1 -1
- package/dist/lib/cookies/index.js +2 -1
- package/dist/lib/durable-job.d.ts +124 -0
- package/dist/lib/durable-job.d.ts.map +1 -0
- package/dist/lib/durable-job.js +296 -0
- package/dist/lib/http/client.d.ts +7 -1
- package/dist/lib/http/client.d.ts.map +1 -1
- package/dist/lib/http/client.js +2 -0
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +5 -2
- package/package.json +8 -2
- package/src/commander.ts +50 -2
- package/src/commands/admission.ts +699 -0
- package/src/commands/agents.ts +87 -1
- package/src/commands/artifacts.ts +97 -21
- package/src/commands/browse-ai.ts +10 -5
- package/src/commands/browse.ts +481 -20
- package/src/commands/fetch.ts +1 -0
- package/src/commands/qa-record.ts +682 -0
- package/src/commands/qa-run.ts +335 -16
- package/src/commands/qa-status.ts +608 -0
- package/src/commands/qa-verify.ts +238 -0
- package/src/commands/review-pack.ts +1281 -0
- package/src/core/agents/qa-signal.ts +261 -0
- package/src/core/agents/session-name-display.ts +78 -7
- package/src/core/agents/state/heartbeat-reader.ts +7 -0
- package/src/core/agents/state/heartbeat-writer.ts +23 -0
- package/src/core/agents/state/live-coordination-view.ts +1 -0
- package/src/core/agents/state/live-coordination-writer.ts +5 -0
- package/src/core/artifacts/constants.ts +1 -1
- package/src/core/artifacts/index.ts +370 -21
- package/src/core/config.ts +23 -0
- package/src/core/diagnostics/bundle.ts +119 -11
- package/src/core/events/v3/bootstrap.ts +10 -0
- package/src/core/events/v3/coordination-view.ts +100 -11
- package/src/core/events/v3/producers/intake.ts +9 -2
- package/src/core/events/v3/producers/recorder.ts +312 -18
- package/src/core/hooks/cli.ts +140 -32
- package/src/core/hooks/resolve/transcript.ts +10 -3
- package/src/core/hooks/session-name-presence.ts +6 -1
- package/src/core/qa-artifacts.ts +126 -0
- package/src/core/resources/contract.ts +7 -0
- package/src/core/resources/sampler.ts +138 -6
- package/src/lib/admission.ts +347 -0
- package/src/lib/agent-browser/client.ts +2 -10
- package/src/lib/browser/capture-fidelity.ts +98 -0
- package/src/lib/browser/client.ts +206 -10
- package/src/lib/browser/critique.ts +62 -7
- package/src/lib/browser/index.ts +36 -0
- package/src/lib/browser/page-review-judge.ts +360 -0
- package/src/lib/browser/page-review-pack.ts +2384 -0
- package/src/lib/browser/qa-run-contracts.ts +366 -3
- package/src/lib/browser/qa-run.ts +868 -190
- package/src/lib/browser/request-diagnostics.ts +27 -0
- package/src/lib/browser/tiling.ts +32 -0
- package/src/lib/cookies/client.ts +228 -42
- package/src/lib/cookies/extra.ts +28 -0
- package/src/lib/cookies/index.ts +2 -0
- package/src/lib/durable-job.ts +407 -0
- package/src/lib/http/client.ts +13 -1
- package/src/lib/instructions/templates.ts +5 -2
|
@@ -8,16 +8,20 @@ import {
|
|
|
8
8
|
realpathSync,
|
|
9
9
|
} from "node:fs";
|
|
10
10
|
import { dirname, join, resolve, sep } from "node:path";
|
|
11
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
ArtifactActor,
|
|
13
|
+
ArtifactInventoryEntry,
|
|
14
|
+
ArtifactManifestV2,
|
|
15
|
+
} from "../artifacts/index.ts";
|
|
12
16
|
import {
|
|
13
17
|
ARTIFACT_MANIFEST,
|
|
14
18
|
artifactsRoot,
|
|
15
19
|
configuredArtifactRetentionDays,
|
|
16
20
|
createArtifact,
|
|
17
21
|
inventoryArtifacts,
|
|
22
|
+
parseArtifactManifest,
|
|
18
23
|
releaseArtifact,
|
|
19
24
|
resolveArtifactRef,
|
|
20
|
-
showArtifact,
|
|
21
25
|
} from "../artifacts/index.ts";
|
|
22
26
|
import { currentHarneryRuntimeBuild } from "../events/v3/runtime-build.ts";
|
|
23
27
|
import { writePrivateJsonAtomic } from "../storage/atomic-json.ts";
|
|
@@ -104,6 +108,17 @@ export interface DiagnosticBundleListRow {
|
|
|
104
108
|
error?: string;
|
|
105
109
|
}
|
|
106
110
|
|
|
111
|
+
export interface DiagnosticBundleCandidateRow {
|
|
112
|
+
artifact_id: string;
|
|
113
|
+
created_at: string;
|
|
114
|
+
expires_at: string | null;
|
|
115
|
+
classification: ArtifactInventoryEntry["classification"];
|
|
116
|
+
selectable: boolean;
|
|
117
|
+
captured_at?: string;
|
|
118
|
+
finding_id?: string;
|
|
119
|
+
error?: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
107
122
|
export function captureDiagnosticBundle(
|
|
108
123
|
repoRootRaw: string,
|
|
109
124
|
input: CaptureDiagnosticBundleInput = {},
|
|
@@ -210,10 +225,10 @@ export function captureDiagnosticBundle(
|
|
|
210
225
|
return { path: created.path, manifest, summary };
|
|
211
226
|
}
|
|
212
227
|
|
|
213
|
-
|
|
228
|
+
function openDiagnosticBundleManifest(
|
|
214
229
|
repoRootRaw: string,
|
|
215
230
|
ref: string,
|
|
216
|
-
):
|
|
231
|
+
): { artifactPath: string; manifest: DiagnosticBundleManifest } {
|
|
217
232
|
const repoRoot = resolve(repoRootRaw);
|
|
218
233
|
const artifactPath = resolveArtifactRef(repoRoot, ref);
|
|
219
234
|
const managedRoot = realpathSync(artifactsRoot(repoRoot));
|
|
@@ -225,21 +240,28 @@ export function validateDiagnosticBundle(
|
|
|
225
240
|
throw new Error("diagnostic bundle is outside the managed artifact root");
|
|
226
241
|
}
|
|
227
242
|
assertBundleTree(artifactPath);
|
|
228
|
-
const
|
|
243
|
+
const artifactManifest = readCandidateArtifactManifest(artifactPath);
|
|
244
|
+
const classification = candidateClassification(artifactManifest);
|
|
229
245
|
if (
|
|
230
246
|
!["managed-active", "managed-current", "managed-expired", "managed-tracked"].includes(
|
|
231
|
-
|
|
247
|
+
classification,
|
|
232
248
|
)
|
|
233
249
|
) {
|
|
234
|
-
throw new Error(
|
|
235
|
-
`diagnostic bundle has invalid managed classification ${shown.entry.classification}`,
|
|
236
|
-
);
|
|
250
|
+
throw new Error(`diagnostic bundle has invalid managed classification ${classification}`);
|
|
237
251
|
}
|
|
238
|
-
if (
|
|
252
|
+
if (artifactManifest.slug !== "diagnostic-bundle") {
|
|
239
253
|
throw new Error("artifact is not a diagnostic bundle");
|
|
240
254
|
}
|
|
241
255
|
const manifest = readJson<DiagnosticBundleManifest>(artifactPath, "diagnostic-manifest.json");
|
|
242
|
-
validateManifest(manifest,
|
|
256
|
+
validateManifest(manifest, artifactManifest.artifact_id);
|
|
257
|
+
return { artifactPath, manifest };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function validateDiagnosticBundle(
|
|
261
|
+
repoRootRaw: string,
|
|
262
|
+
ref: string,
|
|
263
|
+
): ValidatedDiagnosticBundle {
|
|
264
|
+
const { artifactPath, manifest } = openDiagnosticBundleManifest(repoRootRaw, ref);
|
|
243
265
|
let totalBytes = 0;
|
|
244
266
|
for (const file of manifest.files) {
|
|
245
267
|
const bytes = readValidatedFile(artifactPath, file.path);
|
|
@@ -305,6 +327,92 @@ export function listDiagnosticBundles(repoRoot: string): DiagnosticBundleListRow
|
|
|
305
327
|
});
|
|
306
328
|
}
|
|
307
329
|
|
|
330
|
+
/**
|
|
331
|
+
* Lists managed bundle candidates without opening their bounded payloads.
|
|
332
|
+
* Selection is not authority: every show, replay, and comparison operation
|
|
333
|
+
* still performs complete digest and schema validation before returning data.
|
|
334
|
+
*/
|
|
335
|
+
export function listDiagnosticBundleCandidates(repoRoot: string): DiagnosticBundleCandidateRow[] {
|
|
336
|
+
const root = artifactsRoot(repoRoot);
|
|
337
|
+
if (!existsSync(root)) return [];
|
|
338
|
+
const rows: DiagnosticBundleCandidateRow[] = [];
|
|
339
|
+
for (const name of readdirSync(root).sort()) {
|
|
340
|
+
const artifactPath = join(root, name);
|
|
341
|
+
let artifactManifest: ArtifactManifestV2;
|
|
342
|
+
try {
|
|
343
|
+
const stat = lstatSync(artifactPath);
|
|
344
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
|
|
345
|
+
artifactManifest = readCandidateArtifactManifest(artifactPath);
|
|
346
|
+
if (artifactManifest.slug !== "diagnostic-bundle") continue;
|
|
347
|
+
assertBundleTree(artifactPath);
|
|
348
|
+
const manifest = readJson<DiagnosticBundleManifest>(artifactPath, "diagnostic-manifest.json");
|
|
349
|
+
validateManifest(manifest, artifactManifest.artifact_id);
|
|
350
|
+
rows.push({
|
|
351
|
+
artifact_id: artifactManifest.artifact_id,
|
|
352
|
+
created_at: artifactManifest.created_at,
|
|
353
|
+
expires_at: artifactManifest.retention.expires_at,
|
|
354
|
+
classification: candidateClassification(artifactManifest),
|
|
355
|
+
selectable: true,
|
|
356
|
+
captured_at: manifest.captured_at,
|
|
357
|
+
finding_id: manifest.finding_id,
|
|
358
|
+
});
|
|
359
|
+
} catch (error) {
|
|
360
|
+
if (!existsSync(join(artifactPath, "diagnostic-manifest.json"))) continue;
|
|
361
|
+
const fallbackId = candidateArtifactId(artifactPath) ?? name;
|
|
362
|
+
rows.push({
|
|
363
|
+
artifact_id: fallbackId,
|
|
364
|
+
created_at: "",
|
|
365
|
+
expires_at: null,
|
|
366
|
+
classification: "invalid-manifest",
|
|
367
|
+
selectable: false,
|
|
368
|
+
error: error instanceof Error ? error.message : String(error),
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const counts = new Map<string, number>();
|
|
373
|
+
for (const row of rows) counts.set(row.artifact_id, (counts.get(row.artifact_id) ?? 0) + 1);
|
|
374
|
+
return rows.map((row) =>
|
|
375
|
+
counts.get(row.artifact_id) === 1
|
|
376
|
+
? row
|
|
377
|
+
: { ...row, selectable: false, error: "duplicate managed artifact id" },
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function readCandidateArtifactManifest(artifactPath: string): ArtifactManifestV2 {
|
|
382
|
+
const manifestPath = join(artifactPath, ARTIFACT_MANIFEST);
|
|
383
|
+
const stat = lstatSync(manifestPath);
|
|
384
|
+
if (stat.isSymbolicLink() || !stat.isFile() || stat.size > 64 * 1024) {
|
|
385
|
+
throw new Error("artifact manifest is not a bounded regular file");
|
|
386
|
+
}
|
|
387
|
+
let manifest: Partial<ArtifactManifestV2>;
|
|
388
|
+
try {
|
|
389
|
+
manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as Partial<ArtifactManifestV2>;
|
|
390
|
+
} catch {
|
|
391
|
+
throw new Error("artifact manifest is not valid JSON");
|
|
392
|
+
}
|
|
393
|
+
const parsed = parseArtifactManifest(manifest);
|
|
394
|
+
if (!parsed.ok) throw new Error(parsed.reason);
|
|
395
|
+
return parsed.manifest;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function candidateArtifactId(artifactPath: string): string | undefined {
|
|
399
|
+
try {
|
|
400
|
+
return readCandidateArtifactManifest(artifactPath).artifact_id;
|
|
401
|
+
} catch {
|
|
402
|
+
return undefined;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function candidateClassification(
|
|
407
|
+
manifest: ArtifactManifestV2,
|
|
408
|
+
): ArtifactInventoryEntry["classification"] {
|
|
409
|
+
if (manifest.holds.length > 0) return "managed-held";
|
|
410
|
+
if (!manifest.released_at) return "managed-active";
|
|
411
|
+
return Date.parse(manifest.retention.expires_at) <= Date.now()
|
|
412
|
+
? "managed-expired"
|
|
413
|
+
: "managed-current";
|
|
414
|
+
}
|
|
415
|
+
|
|
308
416
|
export function replayDiagnosticBundle(
|
|
309
417
|
repoRoot: string,
|
|
310
418
|
ref: string,
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
import { repairEventV3ControlPair } from "./control-writer.ts";
|
|
31
31
|
import { loadOrCreateFingerprintKeyStoreV3 } from "./fingerprint-keys.ts";
|
|
32
32
|
import { EVENT_V3_SCHEMA_DIGEST } from "./generated.ts";
|
|
33
|
+
import { reanchorArchivedHookProducersV3 } from "./producers/recorder.ts";
|
|
33
34
|
import { resolveEventLedgerRotateActiveBytesV3 } from "./rotation-config.ts";
|
|
34
35
|
import { currentHarneryRuntimeBuild, liveEventV3BuildId, livePlatformV3 } from "./runtime-build.ts";
|
|
35
36
|
import { drainReadyEventsV3 } from "./writer.ts";
|
|
@@ -210,6 +211,15 @@ export function rotateOversizedEventLedgerV3(
|
|
|
210
211
|
approvalRecordId: "harnery-runtime-v3-size-rotation",
|
|
211
212
|
forceNewEpoch: true,
|
|
212
213
|
});
|
|
214
|
+
if (initialized.archived_epoch) {
|
|
215
|
+
reanchorArchivedHookProducersV3({
|
|
216
|
+
coordRoot: root,
|
|
217
|
+
archivedEpoch: initialized.archived_epoch,
|
|
218
|
+
mode: initialized.control.state,
|
|
219
|
+
build_id: liveEventV3BuildId(currentHarneryRuntimeBuild()),
|
|
220
|
+
platform: livePlatformV3(),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
213
223
|
return {
|
|
214
224
|
state: "rotated",
|
|
215
225
|
active_bytes: activeBytes,
|
|
@@ -52,8 +52,11 @@ export interface CoordinationViewV3 {
|
|
|
52
52
|
contract_major: 2;
|
|
53
53
|
reducer_build_id: typeof EVENT_V3_SAFETY_REDUCER_BUILD;
|
|
54
54
|
source_complete: boolean;
|
|
55
|
+
/** True when no unscoped authority diagnostic can affect every generation. */
|
|
55
56
|
authority_safe: boolean;
|
|
56
57
|
diagnostics: SafetyProjectionDiagnosticV3[];
|
|
58
|
+
global_diagnostics: SafetyProjectionDiagnosticV3[];
|
|
59
|
+
diagnostics_by_generation: Record<string, SafetyProjectionDiagnosticV3[]>;
|
|
57
60
|
instances: Record<string, CoordinationGenerationViewV3>;
|
|
58
61
|
terminal_generations: Record<string, CoordinationGenerationViewV3>;
|
|
59
62
|
delegations: SafetyProjectionV3["delegations"];
|
|
@@ -95,7 +98,9 @@ export function projectCoordinationViewV3(read: ReadLedgerV3Result): Coordinatio
|
|
|
95
98
|
const safety = reduceSafetyProjectionV3(read);
|
|
96
99
|
const starts = new Map<string, Extract<EventV3, { event_type: "session.started" }>>();
|
|
97
100
|
const eventGeneration = new Map<string, string>();
|
|
101
|
+
const eventTypes = new Map<string, EventV3["event_type"]>();
|
|
98
102
|
for (const { event } of read.events) {
|
|
103
|
+
eventTypes.set(event.event_id, event.event_type);
|
|
99
104
|
const generationId = "generation_id" in event.scope ? event.scope.generation_id : undefined;
|
|
100
105
|
if (generationId !== undefined) {
|
|
101
106
|
eventGeneration.set(event.event_id, generationId);
|
|
@@ -105,6 +110,15 @@ export function projectCoordinationViewV3(read: ReadLedgerV3Result): Coordinatio
|
|
|
105
110
|
}
|
|
106
111
|
}
|
|
107
112
|
|
|
113
|
+
const { globalDiagnostics, diagnosticsByGeneration } = partitionCoordinationDiagnosticsV3(
|
|
114
|
+
safety,
|
|
115
|
+
eventGeneration,
|
|
116
|
+
eventTypes,
|
|
117
|
+
);
|
|
118
|
+
const globalAuthoritySafe = !globalDiagnostics.some(
|
|
119
|
+
(diagnostic) => diagnostic.authority_blocking,
|
|
120
|
+
);
|
|
121
|
+
|
|
108
122
|
const instances: Record<string, CoordinationGenerationViewV3> = {};
|
|
109
123
|
const terminalGenerations: Record<string, CoordinationGenerationViewV3> = {};
|
|
110
124
|
const states = Object.values(safety.generations).sort((left, right) =>
|
|
@@ -113,14 +127,7 @@ export function projectCoordinationViewV3(read: ReadLedgerV3Result): Coordinatio
|
|
|
113
127
|
for (const state of states) {
|
|
114
128
|
const started = starts.get(state.generation_id);
|
|
115
129
|
if (!started) continue;
|
|
116
|
-
const diagnostics =
|
|
117
|
-
(diagnostic) =>
|
|
118
|
-
diagnostic.generation_id === state.generation_id ||
|
|
119
|
-
diagnostic.subject_instance_id === state.instance_id ||
|
|
120
|
-
(diagnostic.event_id !== undefined &&
|
|
121
|
-
eventGeneration.get(diagnostic.event_id) === state.generation_id) ||
|
|
122
|
-
diagnostic.code === "ledger_incomplete",
|
|
123
|
-
);
|
|
130
|
+
const diagnostics = diagnosticsByGeneration[state.generation_id] ?? [];
|
|
124
131
|
const delegation = Object.values(safety.delegations).find(
|
|
125
132
|
(candidate) => candidate.child_generation_id === state.generation_id,
|
|
126
133
|
);
|
|
@@ -173,8 +180,11 @@ export function projectCoordinationViewV3(read: ReadLedgerV3Result): Coordinatio
|
|
|
173
180
|
last_context_event_id: state.last_context_event_id,
|
|
174
181
|
provisional_termination: state.provisional_termination,
|
|
175
182
|
terminal: state.terminal,
|
|
176
|
-
evidence_complete:
|
|
177
|
-
authority_eligible:
|
|
183
|
+
evidence_complete: globalDiagnostics.length === 0 && diagnostics.length === 0,
|
|
184
|
+
authority_eligible:
|
|
185
|
+
globalAuthoritySafe &&
|
|
186
|
+
!diagnostics.some((diagnostic) => diagnostic.authority_blocking) &&
|
|
187
|
+
state.phase === "live",
|
|
178
188
|
};
|
|
179
189
|
if (state.phase === "terminal") {
|
|
180
190
|
terminalGenerations[state.generation_id] = view;
|
|
@@ -188,8 +198,10 @@ export function projectCoordinationViewV3(read: ReadLedgerV3Result): Coordinatio
|
|
|
188
198
|
contract_major: 2,
|
|
189
199
|
reducer_build_id: EVENT_V3_SAFETY_REDUCER_BUILD,
|
|
190
200
|
source_complete: read.complete,
|
|
191
|
-
authority_safe:
|
|
201
|
+
authority_safe: globalAuthoritySafe,
|
|
192
202
|
diagnostics: safety.diagnostics,
|
|
203
|
+
global_diagnostics: globalDiagnostics,
|
|
204
|
+
diagnostics_by_generation: diagnosticsByGeneration,
|
|
193
205
|
instances,
|
|
194
206
|
terminal_generations: terminalGenerations,
|
|
195
207
|
delegations: safety.delegations,
|
|
@@ -200,6 +212,83 @@ export function projectCoordinationViewV3(read: ReadLedgerV3Result): Coordinatio
|
|
|
200
212
|
return view;
|
|
201
213
|
}
|
|
202
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Partition reducer findings at the narrowest authority boundary that can be
|
|
217
|
+
* proven from canonical event attribution. A diagnostic stays global when it
|
|
218
|
+
* concerns shared claim/decision state, lacks a live generation witness, or
|
|
219
|
+
* came from the canonical reader. Reader failures deliberately remain global:
|
|
220
|
+
* an incomplete read cannot produce the append checkpoint that proves causal
|
|
221
|
+
* parents and attestations for later writes.
|
|
222
|
+
*/
|
|
223
|
+
function partitionCoordinationDiagnosticsV3(
|
|
224
|
+
safety: SafetyProjectionV3,
|
|
225
|
+
eventGeneration: ReadonlyMap<string, string>,
|
|
226
|
+
eventTypes: ReadonlyMap<string, EventV3["event_type"]>,
|
|
227
|
+
): {
|
|
228
|
+
globalDiagnostics: SafetyProjectionDiagnosticV3[];
|
|
229
|
+
diagnosticsByGeneration: Record<string, SafetyProjectionDiagnosticV3[]>;
|
|
230
|
+
} {
|
|
231
|
+
const globalDiagnostics: SafetyProjectionDiagnosticV3[] = [];
|
|
232
|
+
const diagnosticsByGeneration: Record<string, SafetyProjectionDiagnosticV3[]> = {};
|
|
233
|
+
for (const diagnostic of safety.diagnostics) {
|
|
234
|
+
const generationIds = diagnosticGenerationIdsV3(diagnostic, safety, eventGeneration);
|
|
235
|
+
if (
|
|
236
|
+
generationIds.length === 0 ||
|
|
237
|
+
diagnosticRequiresGlobalAuthorityV3(diagnostic, eventTypes.get(diagnostic.event_id ?? ""))
|
|
238
|
+
) {
|
|
239
|
+
globalDiagnostics.push(diagnostic);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
for (const generationId of generationIds) {
|
|
243
|
+
const generationDiagnostics = diagnosticsByGeneration[generationId] ?? [];
|
|
244
|
+
generationDiagnostics.push(diagnostic);
|
|
245
|
+
diagnosticsByGeneration[generationId] = generationDiagnostics;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return { globalDiagnostics, diagnosticsByGeneration };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function diagnosticGenerationIdsV3(
|
|
252
|
+
diagnostic: SafetyProjectionDiagnosticV3,
|
|
253
|
+
safety: SafetyProjectionV3,
|
|
254
|
+
eventGeneration: ReadonlyMap<string, string>,
|
|
255
|
+
): string[] {
|
|
256
|
+
const exact = new Set<string>();
|
|
257
|
+
if (diagnostic.generation_id && safety.generations[diagnostic.generation_id]) {
|
|
258
|
+
exact.add(diagnostic.generation_id);
|
|
259
|
+
}
|
|
260
|
+
const eventGenerationId = diagnostic.event_id
|
|
261
|
+
? eventGeneration.get(diagnostic.event_id)
|
|
262
|
+
: undefined;
|
|
263
|
+
if (eventGenerationId && safety.generations[eventGenerationId]) exact.add(eventGenerationId);
|
|
264
|
+
if (exact.size > 0) return [...exact].sort();
|
|
265
|
+
|
|
266
|
+
const subjectGenerationId = diagnostic.subject_instance_id
|
|
267
|
+
? safety.current_generation_by_instance[diagnostic.subject_instance_id]
|
|
268
|
+
: undefined;
|
|
269
|
+
return subjectGenerationId && safety.generations[subjectGenerationId]
|
|
270
|
+
? [subjectGenerationId]
|
|
271
|
+
: [];
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function diagnosticRequiresGlobalAuthorityV3(
|
|
275
|
+
diagnostic: SafetyProjectionDiagnosticV3,
|
|
276
|
+
eventType: EventV3["event_type"] | undefined,
|
|
277
|
+
): boolean {
|
|
278
|
+
if (diagnostic.code === "ledger_incomplete") return true;
|
|
279
|
+
if (diagnostic.code === "decision_prior_mismatch") return true;
|
|
280
|
+
if (
|
|
281
|
+
diagnostic.code === "claim_acquire_conflict" ||
|
|
282
|
+
diagnostic.code === "claim_release_without_acquire"
|
|
283
|
+
) {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
return (
|
|
287
|
+
diagnostic.code === "authority_attribution_unverified" &&
|
|
288
|
+
(eventType === "decision.state_changed" || eventType === "coord.claim_changed")
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
203
292
|
/** Refuse authority decisions while still allowing callers to render diagnostics. */
|
|
204
293
|
export function requireAuthoritySafeCoordinationViewV3(
|
|
205
294
|
view: CoordinationViewV3,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
unlinkSync,
|
|
13
13
|
writeFileSync,
|
|
14
14
|
} from "node:fs";
|
|
15
|
-
import { join, resolve } from "node:path";
|
|
15
|
+
import { dirname, join, resolve } from "node:path";
|
|
16
16
|
import type { Adapter } from "../../../adapter.ts";
|
|
17
17
|
import type { ParsedPayload } from "../../../hooks/adapter/parse.ts";
|
|
18
18
|
import { fsyncParentDirectory } from "../../../workflow/durable-record.ts";
|
|
@@ -224,7 +224,14 @@ export function listHookIntakeRecordsV3(directory: string): HookIntakeEntryV3[]
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
export function removeIntakeRecordV3(path: string): void {
|
|
227
|
-
if (existsSync(path))
|
|
227
|
+
if (existsSync(path)) {
|
|
228
|
+
unlinkSync(path);
|
|
229
|
+
} else if (!existsSync(dirname(path))) {
|
|
230
|
+
// Epoch rotation atomically moves the complete intake tree into the
|
|
231
|
+
// archive. A producer that was already draining the old path has nothing
|
|
232
|
+
// left to remove or fsync in the successor.
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
228
235
|
fsyncParentDirectory(path);
|
|
229
236
|
}
|
|
230
237
|
|