@agentskit/harness 0.3.0 → 0.4.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.
@@ -0,0 +1,23 @@
1
+ {
2
+ "type": "agentskit-harness-compatibility-report",
3
+ "schemaVersion": 1,
4
+ "harnessVersion": "0.4.0",
5
+ "harnessSourceRevision": "a18b57d43f037b7a34b134e01baf913ae2341fd7",
6
+ "capturedAt": "2026-09-10T18:30:00Z",
7
+ "status": "blocked",
8
+ "components": [
9
+ { "id": "core", "package": "@agentskit/core", "version": "1.12.9", "revision": "d89a2d74108c29f2c6227948265449e985af3a0a", "status": "passed", "command": "pnpm --filter @agentskit/core test", "evidence": "34 files, 498 tests passed" },
10
+ { "id": "memory", "package": "@agentskit/memory", "version": "0.11.9", "revision": "d89a2d74108c29f2c6227948265449e985af3a0a", "status": "passed", "command": "pnpm --filter @agentskit/memory test", "evidence": "31 files, 228 tests passed" },
11
+ { "id": "eval", "package": "@agentskit/eval", "version": "0.6.5", "revision": "d89a2d74108c29f2c6227948265449e985af3a0a", "status": "passed", "command": "pnpm --filter @agentskit/eval test", "evidence": "9 files, 105 tests passed; build and bundler preparation passed" },
12
+ { "id": "doc-bridge", "package": "@agentskit/doc-bridge", "version": "1.7.45", "revision": "9f9e9ddd4f736981c2bea243fef33b68be2b5002", "status": "passed", "command": "pnpm build && pnpm test && pnpm typecheck", "evidence": "50 files, 351 tests passed; Cursor/Claude/Copilot/portable plugin contracts passed; typecheck passed" },
13
+ { "id": "code-review", "package": "@agentskit/code-review", "version": "0.30.8", "revision": "c993f9a9359157a767d738d60c30add2b02fd09a", "status": "failed", "command": "npm test; npm run quality:matrix -- --input <real-runner-artifact>", "evidence": "241 tests passed. Real run ceaf007a-ea6e-4dc9-a601-f816702b301c reached BLOCKED: detection 1/4, precision 1/4, batch-efficiency 1/4, integration 1/4; no comparable baseline." },
14
+ { "id": "adapter-boundary", "package": "@agentskit/harness", "version": "0.4.0", "revision": "a18b57d43f037b7a34b134e01baf913ae2341fd7", "status": "passed", "command": "pnpm test:adapters", "evidence": "covered by ak-verify run 1789063362018-21016-775q93" },
15
+ { "id": "runtime", "package": "@agentskit/harness", "version": "0.4.0", "revision": "a18b57d43f037b7a34b134e01baf913ae2341fd7", "status": "passed", "command": "pnpm test:runtime && pnpm test:runtime-attestation", "evidence": "covered by ak-verify run 1789063362018-21016-775q93" }
16
+ ],
17
+ "blockers": [
18
+ "code-review real-run quality matrix has no passing detection/precision/baseline evidence",
19
+ "harness-vs-no-Harness cohort is not yet comparable (10-task owner-approved cohort required)",
20
+ "published-registry smoke is pending the main-branch release"
21
+ ],
22
+ "baseline": { "path": "benchmarks/harness-0.4.0-baseline.json", "status": "not-run", "doNotInfer": true }
23
+ }
@@ -0,0 +1,22 @@
1
+ # Compatibility report
2
+
3
+ **Release:** `@agentskit/harness@0.4.0`
4
+ **Harness revision:** `a18b57d43f037b7a34b134e01baf913ae2341fd7`
5
+ **Captured:** 2026-09-10
6
+ **Decision:** BLOCKED (fail closed)
7
+
8
+ ## Passed boundaries
9
+
10
+ - `@agentskit/core@1.12.9` at `d89a2d7`: 498/498 tests.
11
+ - `@agentskit/memory@0.11.9` at `d89a2d7`: 228/228 tests.
12
+ - `@agentskit/eval@0.6.5` at `d89a2d7`: 105/105 tests, including build/bundler preparation.
13
+ - `@agentskit/doc-bridge@1.7.45` at `9f9e9d`: 351/351 tests plus plugin contracts and typecheck.
14
+ - Harness adapter and runtime boundaries: covered by `ak-verify` run `1789063362018-21016-775q93` (21/21 checks).
15
+
16
+ ## Blocked evidence
17
+
18
+ `@agentskit/code-review@0.30.8` passed its 241-test suite. A real fixed-provider run (`codex-cli`, `gpt-5.6-luna`, high, run `ceaf007a-ea6e-4dc9-a601-f816702b301c`) completed coverage, safety, reliability, memory, configuration, and actionability checks, but the quality matrix blocked on detection, precision, batch efficiency, integration, and missing comparable baselines. The result is retained as evidence, not promoted to a pass.
19
+
20
+ The 0.4.0 no-Harness comparison remains unavailable because the owner-approved ten-task cohort and matching criterion-level logs have not been captured. Registry smoke can only run after the main Trusted Publishing workflow publishes the package.
21
+
22
+ See [`migration.md`](migration.md), [`rollback.md`](rollback.md), and the machine-readable [`report.json`](report.json).
@@ -0,0 +1,8 @@
1
+ # Compatibility rollback rehearsal
2
+
3
+ Rollback is a clean checkout at the pinned `previousVersion`, followed by the
4
+ same adapter-boundary smoke and eval commands. Record the resulting revision,
5
+ commands, exit codes, and evidence digest in `compatibility/report.json`.
6
+
7
+ No external mutation is performed by the Harness. A missing rehearsal record
8
+ is `unknown` and blocks release rather than being inferred as a pass.
package/dist/cli.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { createHash, randomUUID, createPrivateKey, createPublicKey, sign, verify } from 'crypto';
3
- import { readFileSync, mkdirSync, existsSync, unlinkSync, openSync, writeFileSync, closeSync, writeSync, appendFileSync, mkdtempSync, renameSync, rmSync, readdirSync } from 'fs';
3
+ import { readFileSync, mkdirSync, existsSync, unlinkSync, openSync, writeFileSync, closeSync, readdirSync, writeSync, appendFileSync, mkdtempSync, renameSync, rmSync } from 'fs';
4
4
  import { Command } from 'commander';
5
5
  import { resolve, dirname, relative, basename, join, extname, sep } from 'path';
6
6
  import { execFile, spawn } from 'child_process';
7
7
  import { promisify } from 'util';
8
8
  import { tmpdir, cpus, loadavg, freemem, totalmem } from 'os';
9
9
 
10
- // src/constants.ts
10
+ // src/kernel/constants.ts
11
11
  var STATES = [
12
12
  "CLARIFYING",
13
13
  "PLANNED",
@@ -37,7 +37,7 @@ var LEGAL_TRANSITIONS = {
37
37
  var REAL_CATEGORIES = /* @__PURE__ */ new Set(["endpoint", "database", "cli", "mcp", "ui"]);
38
38
  var DECISIONS = /* @__PURE__ */ new Set(["approved", "approve", "yes", "ok", "rejected", "reject", "no"]);
39
39
 
40
- // src/errors.ts
40
+ // src/kernel/errors.ts
41
41
  var HarnessError = class extends Error {
42
42
  code;
43
43
  constructor(message, code = "HARNESS_ERROR") {
@@ -50,7 +50,7 @@ var fail = (message, code = "HARNESS_ERROR") => {
50
50
  throw new HarnessError(message, code);
51
51
  };
52
52
 
53
- // src/profiles.ts
53
+ // src/profiles/index.ts
54
54
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
55
55
  var record = (value, label) => {
56
56
  if (!isRecord(value)) fail(`${label} must be an object.`, "INVALID_CONFIG");
@@ -140,7 +140,7 @@ var cleanConfiguredArtifacts = (loaded) => {
140
140
  };
141
141
  var fileContents = (path) => readFileSync(path, "utf8");
142
142
 
143
- // src/types.ts
143
+ // src/kernel/types.ts
144
144
  var SURFACE_NAMES = ["logic", "endpoint", "database", "cli", "mcp", "ui", "docs"];
145
145
  var CHECK_CATEGORIES = ["build", "test", "lint", ...SURFACE_NAMES, "custom"];
146
146
  var RUN_STATES = [
@@ -157,7 +157,7 @@ var RUN_STATES = [
157
157
  "SUPERSEDED"
158
158
  ];
159
159
 
160
- // src/config.ts
160
+ // src/execution/config.ts
161
161
  var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
162
162
  var stringValue = (value, label) => {
163
163
  if (typeof value !== "string") return fail(`${label} is required.`, "INVALID_CONFIG");
@@ -271,7 +271,7 @@ var loadConfig = (configPath = ".codex/verification.json") => {
271
271
  return { absolute, root, stateDir, config, configHash: hashJson(config) };
272
272
  };
273
273
 
274
- // src/state-machine.ts
274
+ // src/kernel/state-machine.ts
275
275
  var transition = (run, to, reason, actor = "harness") => {
276
276
  if (!STATES.includes(to)) fail(`Unknown state ${to}.`, "INVALID_STATE");
277
277
  if (run.state !== to && !LEGAL_TRANSITIONS[run.state].some((state) => state === to)) fail(`Illegal transition ${run.state} -> ${to}.`, "INVALID_STATE");
@@ -287,7 +287,7 @@ var approvedDecision = (decision) => {
287
287
  };
288
288
  var HARNESS_EVENT_SCHEMA_VERSION = 1;
289
289
  var EVENT_LOG_GENESIS = "GENESIS";
290
- var HARNESS_EVENT_TYPES = ["run.created", "state.transitioned", "context.attached", "verification.completed", "approval.recorded", "authorization.recorded", "session.started", "session.resumed", "agent.turn.started", "policy.evaluated", "tool.approval.requested", "tool.approval.recorded", "tool.requested", "tool.execution.started", "tool.recovery.recorded", "tool.blocked", "tool.completed", "tool.failed", "session.ended"];
290
+ var HARNESS_EVENT_TYPES = ["run.created", "state.transitioned", "context.attached", "verification.completed", "artifact.recorded", "approval.recorded", "authorization.recorded", "session.started", "session.resumed", "agent.turn.started", "policy.evaluated", "tool.approval.requested", "tool.approval.recorded", "tool.requested", "tool.execution.started", "tool.recovery.recorded", "tool.blocked", "tool.completed", "tool.failed", "session.ended"];
291
291
  var SESSION_EVENT_TYPES = /* @__PURE__ */ new Set(["session.started", "session.resumed", "agent.turn.started", "policy.evaluated", "tool.approval.requested", "tool.approval.recorded", "tool.requested", "tool.execution.started", "tool.recovery.recorded", "tool.blocked", "tool.completed", "tool.failed", "session.ended"]);
292
292
  var eventPath = (stateDir, runId) => join(stateDir, "runs", runId, "events.ndjson");
293
293
  var lockPath = (stateDir, runId) => `${eventPath(stateDir, runId)}.lock`;
@@ -416,13 +416,16 @@ var recoverEventLogLock = ({ stateDir, runId, actor, maxAgeMs = 3e5 }) => {
416
416
  return fail("Event log lock owner is still alive.", "HARNESS_ERROR");
417
417
  };
418
418
 
419
- // src/plugins.ts
419
+ // src/kernel/plugins.ts
420
420
  var createPluginSlot = (id2) => {
421
421
  if (!id2.trim()) fail("Plugin slot id is required.", "INVALID_INPUT");
422
- return { id: id2 };
422
+ return { id: id2.trim() };
423
423
  };
424
424
 
425
- // src/context.ts
425
+ // src/kernel/adapter-contract.ts
426
+ var ASSURANCE_LEVELS = ["unverified", "contract-tested", "runtime-attested"];
427
+
428
+ // src/context/index.ts
426
429
  var hashContextSnapshot = ({ providerId, query, references, sourceHash: sourceHash2 }) => hashJson({ providerId, query, references, sourceHash: sourceHash2 });
427
430
  var hashContextSnapshots = (snapshots) => hashJson(snapshots.map(({ providerId, query, references, sourceHash: sourceHash2, snapshotHash }) => ({ providerId, query, references, sourceHash: sourceHash2, snapshotHash })));
428
431
  var record2 = (value, label) => {
@@ -445,17 +448,23 @@ var validateContextSnapshot = (value, index2 = 0) => {
445
448
  uri: requiredString(rawReference["uri"], `context snapshot ${index2}.references[${referenceIndex}].uri`),
446
449
  ...typeof rawReference["title"] === "string" ? { title: rawReference["title"] } : {},
447
450
  ...typeof rawReference["version"] === "string" ? { version: rawReference["version"] } : {},
448
- ...typeof rawReference["contentHash"] === "string" ? { contentHash: rawReference["contentHash"] } : {}
451
+ ...typeof rawReference["contentHash"] === "string" ? { contentHash: rawReference["contentHash"] } : {},
452
+ ...rawReference["relevance"] === void 0 ? {} : typeof rawReference["relevance"] === "number" && rawReference["relevance"] >= 0 && rawReference["relevance"] <= 1 ? { relevance: rawReference["relevance"] } : fail(`context snapshot ${index2}.references[${referenceIndex}].relevance must be between 0 and 1.`, "INVALID_INPUT")
449
453
  };
450
454
  });
451
455
  const scope = rawQuery["scope"] === void 0 ? void 0 : Array.isArray(rawQuery["scope"]) && rawQuery["scope"].every((item) => typeof item === "string") ? rawQuery["scope"] : fail(`context snapshot ${index2}.query.scope must be an array of strings.`, "INVALID_INPUT");
456
+ const assurance = raw["assurance"] === void 0 ? void 0 : ASSURANCE_LEVELS.includes(raw["assurance"]) ? raw["assurance"] : fail(`context snapshot ${index2}.assurance is invalid.`, "INVALID_INPUT");
457
+ const telemetry = raw["telemetry"] === void 0 ? void 0 : record2(raw["telemetry"], `context snapshot ${index2}.telemetry`);
458
+ if (telemetry && telemetry["status"] !== "measured" && telemetry["status"] !== "unknown") fail(`context snapshot ${index2}.telemetry.status is invalid.`, "INVALID_INPUT");
452
459
  const snapshot = {
453
460
  providerId: requiredString(raw["providerId"], `context snapshot ${index2}.providerId`),
454
461
  query: { query: requiredString(rawQuery["query"], `context snapshot ${index2}.query.query`), ...scope ? { scope } : {}, ...typeof rawQuery["sourceRevision"] === "string" ? { sourceRevision: rawQuery["sourceRevision"] } : {} },
455
462
  references,
456
463
  sourceHash: requiredString(raw["sourceHash"], `context snapshot ${index2}.sourceHash`),
457
464
  snapshotHash: requiredString(raw["snapshotHash"], `context snapshot ${index2}.snapshotHash`),
458
- resolvedAt: requiredString(raw["resolvedAt"], `context snapshot ${index2}.resolvedAt`)
465
+ resolvedAt: requiredString(raw["resolvedAt"], `context snapshot ${index2}.resolvedAt`),
466
+ ...assurance === void 0 ? {} : { assurance },
467
+ ...telemetry === void 0 ? {} : { telemetry }
459
468
  };
460
469
  if (snapshot.snapshotHash !== hashContextSnapshot(snapshot)) fail(`context snapshot ${index2}.snapshotHash does not match its contents.`, "INVALID_INPUT");
461
470
  return snapshot;
@@ -467,7 +476,7 @@ var readContextSnapshots = (path) => {
467
476
  var validateContextSnapshots = (snapshots) => snapshots.map((snapshot, index2) => validateContextSnapshot(snapshot, index2));
468
477
  createPluginSlot("context.provider");
469
478
 
470
- // src/runs.ts
479
+ // src/execution/runs.ts
471
480
  var now = () => (/* @__PURE__ */ new Date()).toISOString();
472
481
  var newRunId = () => `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
473
482
  var saveRun2 = (stateDir, run) => {
@@ -534,8 +543,8 @@ var validateEvidence = (root, check, evidence, outcomeIds) => {
534
543
  if (evidence.capability !== "real-browser") failures.push("UI evidence must declare capability real-browser");
535
544
  if (!Array.isArray(evidence.artifacts) || evidence.artifacts.length === 0) failures.push("UI evidence requires screenshot artifacts");
536
545
  }
537
- const artifacts = Array.isArray(evidence.artifacts) ? evidence.artifacts : [];
538
- for (const artifactValue of artifacts) {
546
+ const artifacts2 = Array.isArray(evidence.artifacts) ? evidence.artifacts : [];
547
+ for (const artifactValue of artifacts2) {
539
548
  if (!isRecord3(artifactValue) || typeof artifactValue["path"] !== "string" || typeof artifactValue["sha256"] !== "string") {
540
549
  failures.push("artifact requires string path and sha256");
541
550
  continue;
@@ -651,7 +660,7 @@ var createMachineMonitor = (sampleIntervalMs = 5e3, options2 = {}) => {
651
660
  };
652
661
  };
653
662
 
654
- // src/verification.ts
663
+ // src/execution/verification.ts
655
664
  var now2 = () => (/* @__PURE__ */ new Date()).toISOString();
656
665
  var requireRun = (run) => run ?? fail("No verification run exists.", "NO_RUN");
657
666
  var verificationProjection = (run) => ({ checks: run.checks, outcomes: run.outcomes, metrics: run.metrics });
@@ -796,10 +805,10 @@ var verifyRun = async ({ configPath }) => {
796
805
  const required8 = outcome.checks.filter((id2) => loaded.config.checks.find((check) => check.id === id2)?.required);
797
806
  return { ...outcome, status: required8.length === 0 ? "not-applicable" : required8.every((id2) => statuses.get(id2) === "passed") ? "passed" : "failed" };
798
807
  }), metrics: { totalDurationMs, wallDurationMs: Date.now() - verificationStarted, peakConcurrency: observedPeakConcurrency, budgetExceeded, machine: machineMonitor.stop() } };
799
- const digest3 = verificationDigest(current);
800
- current = { ...current, verificationDigest: digest3 };
808
+ const digest4 = verificationDigest(current);
809
+ current = { ...current, verificationDigest: digest4 };
801
810
  saveRun2(loaded.stateDir, current);
802
- new FileEventStore(loaded.stateDir).append({ runId: current.runId, sourceRevision: current.sourceRevision, configHash: current.configHash, type: "verification.completed", payload: { verificationDigest: digest3, checkCount: current.checks.length, outcomeCount: current.outcomes.length, totalDurationMs, budgetExceeded } });
811
+ new FileEventStore(loaded.stateDir).append({ runId: current.runId, sourceRevision: current.sourceRevision, configHash: current.configHash, type: "verification.completed", payload: { verificationDigest: digest4, checkCount: current.checks.length, outcomeCount: current.outcomes.length, totalDurationMs, budgetExceeded } });
803
812
  const automatic = allPassed && current.autonomy === "yolo" && !loaded.config.tracking.required && loaded.config.contract.ambiguities.length === 0;
804
813
  const nextState = allPassed ? automatic ? "COMPLETE" : "AWAITING_HUMAN_APPROVAL" : "BLOCKED";
805
814
  current = { ...transition(current, nextState, automatic ? "All applicable checks passed; YOLO policy permits automatic completion." : allPassed ? "All configured checks passed; human approval is required." : budgetExceeded ? "Verification budget was exceeded." : "A configured check failed or lacked structured evidence.", "harness") };
@@ -917,15 +926,17 @@ var createDocBridgeContextProvider = ({ root, indexPath = ".doc-bridge/index.jso
917
926
  id: "doc-bridge",
918
927
  version: "1.0.0",
919
928
  resolve: async (query) => {
929
+ const started = Date.now();
920
930
  const document = index(root, indexPath);
921
931
  const contentHash = sourceHash(document);
922
932
  const entries = Array.isArray(document.knowledge) ? document.knowledge.filter((value) => typeof value === "object" && value !== null && !Array.isArray(value)).filter((entry) => matches(entry, query)).sort((left, right) => String(left.id ?? "").localeCompare(String(right.id ?? ""))).slice(0, 8) : [];
923
- const references = entries.flatMap((entry) => typeof entry.id === "string" && typeof entry.path === "string" ? [{ id: entry.id, uri: `doc-bridge://${entry.path}`, ...typeof entry.title === "string" ? { title: entry.title } : {}, contentHash }] : []);
924
- return { providerId: "doc-bridge", query, references, sourceHash: contentHash, snapshotHash: hashContextSnapshot({ providerId: "doc-bridge", query, references, sourceHash: contentHash }), resolvedAt: (/* @__PURE__ */ new Date()).toISOString() };
933
+ const references = entries.flatMap((entry) => typeof entry.id === "string" && typeof entry.path === "string" ? [{ id: entry.id, uri: `doc-bridge://${entry.path}`, ...typeof entry.title === "string" ? { title: entry.title } : {}, contentHash: typeof entry.contentHash === "string" ? entry.contentHash : contentHash, relevance: 1 }] : []);
934
+ const telemetry = { status: "measured", durationMs: Date.now() - started, contextReferences: references.length, contextCostTokens: Math.max(1, Math.ceil(JSON.stringify(references).length / 4)) };
935
+ return { providerId: "doc-bridge", query, references, sourceHash: contentHash, snapshotHash: hashContextSnapshot({ providerId: "doc-bridge", query, references, sourceHash: contentHash }), resolvedAt: (/* @__PURE__ */ new Date()).toISOString(), assurance: "contract-tested", telemetry };
925
936
  }
926
937
  });
927
938
 
928
- // src/discovery.ts
939
+ // src/kernel/discovery.ts
929
940
  var required = (value, label) => {
930
941
  if (typeof value !== "string" || !value.trim()) fail(`${label} is required.`, "INVALID_INPUT");
931
942
  return value.trim();
@@ -987,7 +998,7 @@ var assessDiscovery = (input) => {
987
998
  return { ...base, digest: digest2(base) };
988
999
  };
989
1000
 
990
- // src/wip.ts
1001
+ // src/kernel/wip.ts
991
1002
  var WIP_STATES = ["ready", "implementing", "blocked", "awaiting-decision", "awaiting-acceptance", "done", "cancelled"];
992
1003
  var terminal = /* @__PURE__ */ new Set(["done", "cancelled"]);
993
1004
  var required2 = (value, label) => {
@@ -1019,7 +1030,7 @@ var assessWip = ({ entries, candidate, maxInFlight = 3 }) => {
1019
1030
  return { decision: "admit", inFlight, counts, reason: `WIP slot available (${inFlight.length}/${maxInFlight}).` };
1020
1031
  };
1021
1032
 
1022
- // src/experiment.ts
1033
+ // src/kernel/experiment.ts
1023
1034
  var required3 = (value, label) => {
1024
1035
  if (typeof value !== "string" || !value.trim()) fail(`${label} is required.`, "INVALID_INPUT");
1025
1036
  return value.trim();
@@ -1046,7 +1057,7 @@ var selectRuntime = (candidates) => {
1046
1057
  return { decision: "selected", selected, eligible, reason: "Selected by human minutes, duration, cost, then Orca tie-break." };
1047
1058
  };
1048
1059
 
1049
- // src/delivery.ts
1060
+ // src/delivery/index.ts
1050
1061
  var required4 = (value, label) => {
1051
1062
  if (typeof value !== "string" || !value.trim()) fail(`${label} is required.`, "INVALID_INPUT");
1052
1063
  return value.trim();
@@ -1141,7 +1152,7 @@ var assessAcceptance = ({ production, acceptanceRequired, accepted, notApplicabl
1141
1152
  return assessed("G5", "approved", acceptanceRequired ? [] : [`Acceptance is not applicable: ${notApplicableReason}.`], production.binding);
1142
1153
  };
1143
1154
 
1144
- // src/pilot.ts
1155
+ // src/kernel/pilot.ts
1145
1156
  var required5 = (value, label) => {
1146
1157
  if (typeof value !== "string" || !value.trim()) fail(`${label} is required.`, "INVALID_INPUT");
1147
1158
  return value.trim();
@@ -1216,9 +1227,131 @@ var assessImprovementCycle = (input) => {
1216
1227
  const reasons = complete ? ["All five cycle steps passed."] : iterations.length >= input.maxIterations ? ["Maximum cycle iterations reached; human adjustment is required."] : latest.adjustment ? ["A failed or blocked step remains; repeat with the recorded adjustment."] : ["A failed or blocked step remains; an explicit adjustment is required before repeating."];
1217
1228
  const decision = complete ? "complete" : iterations.length >= input.maxIterations || !latest.adjustment ? "blocked" : "repeat";
1218
1229
  const result = { type: "agentskit-harness-improvement-cycle", cycleId, decision, ...decision === "repeat" ? { nextIteration: latest.iteration + 1 } : {}, reasons, matrix };
1219
- const digest3 = createHash("sha256").update(JSON.stringify(result)).digest("hex");
1220
- return { ...result, digest: digest3 };
1230
+ const digest4 = createHash("sha256").update(JSON.stringify(result)).digest("hex");
1231
+ return { ...result, digest: digest4 };
1232
+ };
1233
+ var ARTIFACT_SCHEMA_VERSION = 1;
1234
+ var ARTIFACT_TYPES = ["plan", "finding", "decision", "repair", "blocker", "approval", "phase"];
1235
+ var text2 = (value, label) => {
1236
+ if (typeof value !== "string" || !value.trim()) return fail(`${label} is required.`, "INVALID_INPUT");
1237
+ return value.trim();
1238
+ };
1239
+ var digest3 = (value, label) => {
1240
+ const result = text2(value, label);
1241
+ if (!/^[a-f0-9]{64}$/.test(result)) fail(`${label} must be a lowercase SHA-256 digest.`, "INVALID_INPUT");
1242
+ return result;
1243
+ };
1244
+ var artifactId = (value) => {
1245
+ const result = text2(value, "Artifact artifactId");
1246
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(result)) fail("Artifact artifactId is invalid.", "INVALID_INPUT");
1247
+ return result;
1248
+ };
1249
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1250
+ var artifactBody = (artifact) => ({
1251
+ type: artifact.type,
1252
+ schemaVersion: artifact.schemaVersion,
1253
+ artifactId: artifact.artifactId,
1254
+ artifactType: artifact.artifactType,
1255
+ artifactVersion: artifact.artifactVersion,
1256
+ runId: artifact.runId,
1257
+ issueRef: artifact.issueRef,
1258
+ sourceRevision: artifact.sourceRevision,
1259
+ contractHash: artifact.contractHash,
1260
+ configHash: artifact.configHash,
1261
+ contextHash: artifact.contextHash,
1262
+ phase: artifact.phase,
1263
+ payload: artifact.payload,
1264
+ payloadHash: artifact.payloadHash
1265
+ });
1266
+ var expectedArtifactHash = (artifact) => hashJson(artifactBody(artifact));
1267
+ var validateArtifactEnvelope = (value) => {
1268
+ if (!isRecord4(value)) return fail("Artifact envelope must be an object.", "INVALID_INPUT");
1269
+ if (value["type"] !== "agentskit-harness-artifact" || value["schemaVersion"] !== ARTIFACT_SCHEMA_VERSION) fail("Artifact envelope type or schemaVersion is invalid.", "INVALID_INPUT");
1270
+ if (!ARTIFACT_TYPES.includes(value["artifactType"])) fail("Artifact artifactType is invalid.", "INVALID_INPUT");
1271
+ if (!Number.isInteger(value["artifactVersion"]) || value["artifactVersion"] < 1) fail("Artifact artifactVersion must be a positive integer.", "INVALID_INPUT");
1272
+ const createdAt = text2(value["createdAt"], "Artifact createdAt");
1273
+ if (!Number.isFinite(Date.parse(createdAt))) fail("Artifact createdAt must be a valid timestamp.", "INVALID_INPUT");
1274
+ const payloadHash = digest3(value["payloadHash"], "Artifact payloadHash");
1275
+ if (hashJson(value["payload"]) !== payloadHash) fail("Artifact payloadHash does not match payload.", "INVALID_INPUT");
1276
+ const artifact = {
1277
+ type: "agentskit-harness-artifact",
1278
+ schemaVersion: ARTIFACT_SCHEMA_VERSION,
1279
+ artifactId: artifactId(value["artifactId"]),
1280
+ artifactType: value["artifactType"],
1281
+ artifactVersion: value["artifactVersion"],
1282
+ runId: text2(value["runId"], "Artifact runId"),
1283
+ issueRef: text2(value["issueRef"], "Artifact issueRef"),
1284
+ sourceRevision: text2(value["sourceRevision"], "Artifact sourceRevision"),
1285
+ contractHash: digest3(value["contractHash"], "Artifact contractHash"),
1286
+ configHash: digest3(value["configHash"], "Artifact configHash"),
1287
+ contextHash: digest3(value["contextHash"], "Artifact contextHash"),
1288
+ phase: text2(value["phase"], "Artifact phase"),
1289
+ createdAt,
1290
+ payload: value["payload"],
1291
+ payloadHash
1292
+ };
1293
+ if (digest3(value["artifactHash"], "Artifact artifactHash") !== expectedArtifactHash(artifact)) fail("Artifact artifactHash does not match envelope.", "INVALID_INPUT");
1294
+ return { ...artifact, artifactHash: value["artifactHash"] };
1295
+ };
1296
+ var renderArtifactMarkdown = (artifact) => [
1297
+ `# ${artifact.artifactType} artifact ${artifact.artifactId}`,
1298
+ "",
1299
+ `- Schema: ${artifact.schemaVersion}`,
1300
+ `- Version: ${artifact.artifactVersion}`,
1301
+ `- Run: ${artifact.runId}`,
1302
+ `- Issue: ${artifact.issueRef}`,
1303
+ `- Phase: ${artifact.phase}`,
1304
+ `- Source revision: ${artifact.sourceRevision}`,
1305
+ `- Contract hash: ${artifact.contractHash}`,
1306
+ `- Configuration hash: ${artifact.configHash}`,
1307
+ `- Context hash: ${artifact.contextHash}`,
1308
+ `- Artifact hash: ${artifact.artifactHash}`,
1309
+ "",
1310
+ "## Payload",
1311
+ "",
1312
+ "```json",
1313
+ JSON.stringify(artifact.payload, null, 2),
1314
+ "```",
1315
+ ""
1316
+ ].join("\n");
1317
+ var artifactFilePath = (stateDir, runId, id2) => join(stateDir, "runs", runId, "artifacts", `${id2}.json`);
1318
+ var artifactMarkdownPath = (stateDir, runId, id2) => join(stateDir, "runs", runId, "artifacts", `${id2}.md`);
1319
+ var FileArtifactStore = class {
1320
+ constructor(stateDir) {
1321
+ this.stateDir = stateDir;
1322
+ }
1323
+ stateDir;
1324
+ write(input) {
1325
+ const artifact = validateArtifactEnvelope(input);
1326
+ const path = artifactFilePath(this.stateDir, artifact.runId, artifact.artifactId);
1327
+ mkdirSync(join(this.stateDir, "runs", artifact.runId, "artifacts"), { recursive: true });
1328
+ if (existsSync(path)) {
1329
+ const existing = validateArtifactEnvelope(JSON.parse(readFileSync(path, "utf8")));
1330
+ if (existing.artifactHash !== artifact.artifactHash) fail(`Artifact ${artifact.artifactId} already exists with different content.`, "HARNESS_ERROR");
1331
+ return existing;
1332
+ }
1333
+ writeFileSync(path, `${JSON.stringify(artifact, null, 2)}
1334
+ `, "utf8");
1335
+ writeFileSync(artifactMarkdownPath(this.stateDir, artifact.runId, artifact.artifactId), renderArtifactMarkdown(artifact), "utf8");
1336
+ new FileEventStore(this.stateDir).append({
1337
+ runId: artifact.runId,
1338
+ sourceRevision: artifact.sourceRevision,
1339
+ configHash: artifact.configHash,
1340
+ type: "artifact.recorded",
1341
+ payload: { artifactId: artifact.artifactId, artifactType: artifact.artifactType, artifactVersion: artifact.artifactVersion, artifactHash: artifact.artifactHash, phase: artifact.phase, representation: "json+markdown" }
1342
+ });
1343
+ return artifact;
1344
+ }
1345
+ read(runId, id2) {
1346
+ return validateArtifactEnvelope(JSON.parse(readFileSync(artifactFilePath(this.stateDir, runId, artifactId(id2)), "utf8")));
1347
+ }
1348
+ list(runId) {
1349
+ const directory = join(this.stateDir, "runs", runId, "artifacts");
1350
+ if (!existsSync(directory)) return [];
1351
+ return readdirSync(directory).filter((name) => name.endsWith(".json")).sort().map((name) => validateArtifactEnvelope(JSON.parse(readFileSync(join(directory, name), "utf8"))));
1352
+ }
1221
1353
  };
1354
+ var readArtifactFile = (path) => validateArtifactEnvelope(JSON.parse(readFileSync(path, "utf8")));
1222
1355
  var BENCHMARK_SCHEMA_VERSION = 1;
1223
1356
  var percentage = (part, total) => total ? Number((part / total).toFixed(4)) : null;
1224
1357
  var improvementRate = (baseline, current) => baseline === void 0 || current === void 0 || baseline === 0 ? null : Number(((baseline - current) / baseline).toFixed(4));
@@ -1504,10 +1637,10 @@ var createDispatchLedger = (stateDir) => {
1504
1637
  },
1505
1638
  recordDispatch: ({ lease, idempotencyKey, commandDigest }) => {
1506
1639
  const id2 = required6(idempotencyKey, "idempotencyKey");
1507
- const digest3 = required6(commandDigest, "commandDigest");
1640
+ const digest4 = required6(commandDigest, "commandDigest");
1508
1641
  const existing = records().find((record4) => record4.action === "dispatch" && record4.idempotencyKey === id2);
1509
1642
  if (existing) return { decision: "duplicate", record: existing };
1510
- const record3 = { ...lease, action: "dispatch", at: now3(), idempotencyKey: id2, commandDigest: digest3 };
1643
+ const record3 = { ...lease, action: "dispatch", at: now3(), idempotencyKey: id2, commandDigest: digest4 };
1511
1644
  append(record3);
1512
1645
  return { decision: "recorded", record: record3 };
1513
1646
  },
@@ -1566,9 +1699,9 @@ var planFilePreflight = (files, options2 = {}) => {
1566
1699
  return { files: unique2, codeFiles, testFiles, docsOnly, checks: docsOnly ? [] : ["lint", "typecheck", ...testFiles.length ? ["test"] : []] };
1567
1700
  };
1568
1701
 
1569
- // src/block.ts
1702
+ // src/kernel/block.ts
1570
1703
  var BLOCK_STATUSES = ["todo", "picked", "development", "validation", "pr-open", "merged", "post-merge", "done", "blocked", "scope-cut"];
1571
- var text2 = (value, label) => {
1704
+ var text3 = (value, label) => {
1572
1705
  return typeof value === "string" && value.trim() ? value.trim() : fail(`${label} must be a non-empty string.`, "INVALID_INPUT");
1573
1706
  };
1574
1707
  var list = (value, label) => {
@@ -1595,16 +1728,16 @@ var validateBlockManifest = (value) => {
1595
1728
  for (const key of ["maxMinutes", "maxAttempts"]) if (candidate[key] !== void 0 && (!Number.isInteger(candidate[key]) || candidate[key] < 1)) fail(`budget.${key} must be a positive integer.`, "INVALID_INPUT");
1596
1729
  budget = { ...candidate["maxMinutes"] === void 0 ? {} : { maxMinutes: candidate["maxMinutes"] }, ...candidate["maxAttempts"] === void 0 ? {} : { maxAttempts: candidate["maxAttempts"] } };
1597
1730
  }
1598
- return { schemaVersion: 1, id: text2(raw["id"], "id"), title: text2(raw["title"], "title"), tracker: text2(raw["tracker"], "tracker"), repository: text2(raw["repository"], "repository"), acceptanceCriteria: criteria, dependencies, wave, status: status2, ...budget ? { budget } : {}, ...raw["humanGates"] === void 0 ? {} : { humanGates: list(raw["humanGates"], "humanGates") }, ...raw["sourceHash"] === void 0 ? {} : { sourceHash: text2(raw["sourceHash"], "sourceHash") } };
1731
+ return { schemaVersion: 1, id: text3(raw["id"], "id"), title: text3(raw["title"], "title"), tracker: text3(raw["tracker"], "tracker"), repository: text3(raw["repository"], "repository"), acceptanceCriteria: criteria, dependencies, wave, status: status2, ...budget ? { budget } : {}, ...raw["humanGates"] === void 0 ? {} : { humanGates: list(raw["humanGates"], "humanGates") }, ...raw["sourceHash"] === void 0 ? {} : { sourceHash: text3(raw["sourceHash"], "sourceHash") } };
1599
1732
  };
1600
1733
  var assessBlock = (manifest, completedDependencies = []) => {
1601
1734
  const value = validateBlockManifest(manifest);
1602
- const completed = new Set(completedDependencies.map((item) => text2(item, "completedDependencies[]")));
1735
+ const completed = new Set(completedDependencies.map((item) => text3(item, "completedDependencies[]")));
1603
1736
  const blockers = value.dependencies.filter((dependency) => !completed.has(dependency));
1604
1737
  const next = blockers.length ? [`Complete dependencies: ${blockers.join(", ")}`] : value.status === "blocked" ? ["Resolve the recorded blocker before dispatch."] : ["Dispatch the block with the frozen acceptance criteria."];
1605
1738
  return { status: blockers.length || value.status === "blocked" ? "blocked" : "ready", manifestHash: hashJson(value), blockers, next };
1606
1739
  };
1607
- var text3 = (value, label) => {
1740
+ var text4 = (value, label) => {
1608
1741
  return typeof value === "string" && value.trim() ? value.trim() : fail(`${label} must be a non-empty string.`, "INVALID_INPUT");
1609
1742
  };
1610
1743
  var category = (heading) => {
@@ -1615,8 +1748,8 @@ var category = (heading) => {
1615
1748
  return "other";
1616
1749
  };
1617
1750
  var parseRetro = (markdown, source, recordedAt = (/* @__PURE__ */ new Date()).toISOString()) => {
1618
- const input = text3(markdown, "markdown");
1619
- const origin = text3(source, "source");
1751
+ const input = text4(markdown, "markdown");
1752
+ const origin = text4(source, "source");
1620
1753
  if (!Number.isFinite(Date.parse(recordedAt))) fail("recordedAt must be a valid timestamp.", "INVALID_INPUT");
1621
1754
  const records = [];
1622
1755
  let current = "other";
@@ -1635,7 +1768,7 @@ var parseRetro = (markdown, source, recordedAt = (/* @__PURE__ */ new Date()).to
1635
1768
  return records;
1636
1769
  };
1637
1770
 
1638
- // src/status.ts
1771
+ // src/kernel/status.ts
1639
1772
  var required7 = (value, label) => {
1640
1773
  return typeof value === "string" && value.trim() ? value.trim() : fail(`${label} must be a non-empty string.`, "INVALID_INPUT");
1641
1774
  };
@@ -1687,7 +1820,7 @@ var exportEvidenceBundle = async ({ configPath, runId, outputPath, privateKeyPat
1687
1820
  const loaded = loadConfig(configPath);
1688
1821
  const run = requireRun2(runId ? readJson(join(loaded.stateDir, "runs", runId, "run.json")) : loadLatestRun(loaded.stateDir));
1689
1822
  const reconciliation = await reconcileRun({ configPath, runId: run.runId });
1690
- const digest3 = run.verificationDigest ?? fail("Only a reconciled COMPLETE run can be exported.", "INVALID_STATE");
1823
+ const digest4 = run.verificationDigest ?? fail("Only a reconciled COMPLETE run can be exported.", "INVALID_STATE");
1691
1824
  if (reconciliation.state !== "COMPLETE") fail("Only a reconciled COMPLETE run can be exported.", "INVALID_STATE");
1692
1825
  const eventLog = new FileEventStore(loaded.stateDir);
1693
1826
  eventLog.read(run.runId);
@@ -1702,7 +1835,7 @@ var exportEvidenceBundle = async ({ configPath, runId, outputPath, privateKeyPat
1702
1835
  return bundleFile(loaded.stateDir, path);
1703
1836
  });
1704
1837
  const privateKey = createPrivateKey(readFileSync(privateKeyPath));
1705
- const unsigned = { type: "agentskit-harness-evidence-bundle", schemaVersion: EVIDENCE_BUNDLE_SCHEMA_VERSION, runId: run.runId, signerKeyId: keyId, sourceRevision: run.sourceRevision, configHash: run.configHash, contractHash: run.contractHash, verificationDigest: digest3, eventLog: eventVerification, files };
1838
+ const unsigned = { type: "agentskit-harness-evidence-bundle", schemaVersion: EVIDENCE_BUNDLE_SCHEMA_VERSION, runId: run.runId, signerKeyId: keyId, sourceRevision: run.sourceRevision, configHash: run.configHash, contractHash: run.contractHash, verificationDigest: digest4, eventLog: eventVerification, files };
1706
1839
  const payloadHash = sha256(JSON.stringify(unsigned));
1707
1840
  const publicKeyPem = createPublicKey(privateKey).export({ type: "spki", format: "pem" }).toString();
1708
1841
  const bundle = { ...unsigned, payloadHash, signature: { algorithm: "ed25519", keyId, publicKeyPem, signatureBase64: sign(null, Buffer.from(payloadHash), privateKey).toString("base64") } };
@@ -1816,6 +1949,17 @@ coordination.command("claim <input>").action((input) => {
1816
1949
  const loaded = loadConfig(options().config);
1817
1950
  print(createDispatchLedger(loaded.stateDir).claim(readJsonInput(input, "coordination identity")));
1818
1951
  });
1952
+ var artifacts = program.command("artifacts").description("Inspect versioned, provenance-bound run artifacts.");
1953
+ artifacts.command("inspect <path>").description("Validate and print one artifact as JSON or Markdown.").action((path) => {
1954
+ const artifact = readArtifactFile(path);
1955
+ print(options().json ? artifact : renderArtifactMarkdown(artifact));
1956
+ });
1957
+ artifacts.command("list [run-id]").description("List artifacts for the latest or selected run.").action((runId) => {
1958
+ const loaded = loadConfig(options().config);
1959
+ const run = runId ? { runId } : loadLatestRun(loaded.stateDir);
1960
+ print(new FileArtifactStore(loaded.stateDir).list(run?.runId ?? fail("No verification run exists.", "NO_RUN")));
1961
+ });
1962
+ artifacts.command("schema").description("Print the artifact schema version.").action(() => print({ schemaVersion: ARTIFACT_SCHEMA_VERSION, types: ["plan", "finding", "decision", "repair", "blocker", "approval", "phase"] }));
1819
1963
  program.command("start").description("Move a planned run into implementation.").action(() => print(startRun(loadConfig(options().config))));
1820
1964
  program.command("verify").description("Execute every configured check and record evidence.").action(async () => print(await verifyRun({ configPath: options().config })));
1821
1965
  program.command("run").description("Alias for verify, compatible with the common protocol.").action(async () => print(await verifyRun({ configPath: options().config })));