cool-workflow 0.1.98 → 0.2.1
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +18 -7
- package/apps/architecture-review/app.json +2 -2
- package/apps/architecture-review/workflow.js +12 -12
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/end-to-end-golden-path/app.json +1 -1
- package/apps/pr-review-fix-ci/app.json +1 -1
- package/apps/release-cut/app.json +1 -1
- package/apps/research-synthesis/app.json +1 -1
- package/dist/cli/dispatch.js +236 -0
- package/dist/cli/entry.js +120 -0
- package/dist/cli/io.js +21 -4
- package/dist/cli/parseargv.js +157 -0
- package/dist/cli.js +6 -33
- package/dist/core/capability-table.js +3518 -0
- package/dist/core/format/help.js +314 -0
- package/dist/{state-explosion/format.js → core/format/state-explosion-text.js} +10 -1
- package/dist/core/hash.js +137 -0
- package/dist/core/multi-agent/candidate-scoring.js +219 -0
- package/dist/core/multi-agent/collaboration.js +481 -0
- package/dist/core/multi-agent/coordinator.js +515 -0
- package/dist/core/multi-agent/eval-replay.js +306 -0
- package/dist/core/multi-agent/runtime.js +929 -0
- package/dist/core/multi-agent/topology.js +298 -0
- package/dist/core/multi-agent/trust-policy.js +197 -0
- package/dist/core/pipeline/commit-gate.js +320 -0
- package/dist/{pipeline-contract.js → core/pipeline/contract.js} +24 -37
- package/dist/core/pipeline/dispatch.js +103 -0
- package/dist/core/pipeline/drive-decide.js +227 -0
- package/dist/core/pipeline/error-feedback.js +161 -0
- package/dist/core/pipeline/loop-expansion.js +124 -0
- package/dist/{result-normalize.js → core/pipeline/result-normalize.js} +14 -37
- package/dist/core/pipeline/runner.js +230 -0
- package/dist/{contract-migration.js → core/state/contract-migration.js} +68 -92
- package/dist/{state-migrations.js → core/state/migrations.js} +103 -96
- package/dist/core/state/node-projection.js +95 -0
- package/dist/core/state/node-snapshot.js +230 -0
- package/dist/core/state/run-paths.js +93 -0
- package/dist/{schema-validate.js → core/state/schema-validate.js} +35 -28
- package/dist/core/state/schema.js +50 -0
- package/dist/core/state/state-explosion/digest.js +243 -0
- package/dist/core/state/state-explosion/graph.js +527 -0
- package/dist/{state-explosion → core/state/state-explosion}/helpers.js +34 -3
- package/dist/core/state/state-explosion/report.js +187 -0
- package/dist/{state-explosion → core/state/state-explosion}/size.js +25 -7
- package/dist/{state-node.js → core/state/state-node.js} +90 -113
- package/dist/core/state/types.js +19 -0
- package/dist/{validation.js → core/state/validation.js} +64 -131
- package/dist/core/trust/evidence-grounding.js +134 -0
- package/dist/core/trust/ledger.js +199 -0
- package/dist/{telemetry-attestation.js → core/trust/telemetry-attestation.js} +94 -68
- package/dist/core/trust/telemetry-ledger.js +127 -0
- package/dist/core/types.js +20 -0
- package/dist/core/version.js +16 -0
- package/dist/{workflow-app-framework.js → core/workflow-apps/app-schema.js} +337 -426
- package/dist/mcp/dispatch.js +104 -0
- package/dist/mcp/server.js +144 -0
- package/dist/mcp-server.js +6 -84
- package/dist/{agent-config.js → shell/agent-config.js} +118 -71
- package/dist/shell/app-run-cli.js +88 -0
- package/dist/shell/audit-cli.js +259 -0
- package/dist/shell/audit-provenance.js +83 -0
- package/dist/shell/candidate-scoring-io.js +459 -0
- package/dist/shell/collaboration-io.js +264 -0
- package/dist/shell/commit-summary.js +104 -0
- package/dist/shell/commit.js +290 -0
- package/dist/shell/coordinator-io.js +476 -0
- package/dist/shell/demo-cli.js +19 -0
- package/dist/shell/dispatch.js +162 -0
- package/dist/shell/doctor.js +292 -0
- package/dist/shell/drive.js +885 -0
- package/dist/shell/error-feedback-io.js +305 -0
- package/dist/shell/eval-io.js +473 -0
- package/dist/{multi-agent-eval/format.js → shell/eval-text.js} +78 -49
- package/dist/{evidence-reasoning.js → shell/evidence-reasoning.js} +124 -255
- package/dist/shell/exec-backend-cli.js +88 -0
- package/dist/shell/execution-backend/agent.js +507 -0
- package/dist/shell/execution-backend/ci.js +15 -0
- package/dist/shell/execution-backend/container.js +69 -0
- package/dist/shell/execution-backend/envelopes.js +55 -0
- package/dist/shell/execution-backend/local.js +113 -0
- package/dist/shell/execution-backend/probes.js +175 -0
- package/dist/shell/execution-backend/registry.js +402 -0
- package/dist/shell/execution-backend/remote.js +128 -0
- package/dist/shell/execution-backend/types.js +11 -0
- package/dist/shell/feedback-cli.js +81 -0
- package/dist/shell/feedback-operations.js +48 -0
- package/dist/shell/fs-atomic.js +276 -0
- package/dist/shell/harness.js +98 -0
- package/dist/shell/ledger-cli.js +212 -0
- package/dist/shell/ledger-io.js +169 -0
- package/dist/shell/man-cli.js +89 -0
- package/dist/shell/metrics-cli.js +100 -0
- package/dist/shell/multi-agent-cli.js +1002 -0
- package/dist/shell/multi-agent-host.js +563 -0
- package/dist/shell/multi-agent-io.js +387 -0
- package/dist/{multi-agent-operator-ux.js → shell/multi-agent-operator-ux.js} +234 -191
- package/dist/shell/node-store.js +124 -0
- package/dist/{observability/format.js → shell/observability-format.js} +7 -1
- package/dist/{observability/intake.js → shell/observability-intake.js} +79 -56
- package/dist/{observability.js → shell/observability.js} +159 -332
- package/dist/{onramp.js → shell/onramp.js} +11 -0
- package/dist/{operator-ux/format.js → shell/operator-ux-text.js} +250 -239
- package/dist/shell/operator-ux.js +431 -0
- package/dist/shell/orchestrator.js +231 -0
- package/dist/shell/pipeline-cli.js +667 -0
- package/dist/shell/pipeline.js +217 -0
- package/dist/shell/reclamation-io.js +1366 -0
- package/dist/shell/registry-cli.js +344 -0
- package/dist/{remote-source.js → shell/remote-source.js} +2 -2
- package/dist/shell/report-cli.js +101 -0
- package/dist/shell/report-view-cli.js +117 -0
- package/dist/{orchestrator → shell}/report.js +289 -282
- package/dist/shell/reporter.js +62 -0
- package/dist/shell/run-export-cli.js +106 -0
- package/dist/shell/run-export.js +680 -0
- package/dist/shell/run-registry-io.js +1014 -0
- package/dist/shell/run-store.js +164 -0
- package/dist/{sandbox-profile.js → shell/sandbox-profile.js} +134 -95
- package/dist/{scheduler.js → shell/scheduler-io.js} +257 -48
- package/dist/shell/scheduling-io.js +321 -0
- package/dist/shell/state-cli.js +181 -0
- package/dist/shell/state-explosion-cli.js +197 -0
- package/dist/shell/telemetry-cli.js +85 -0
- package/dist/{telemetry-demo.js → shell/telemetry-demo.js} +149 -119
- package/dist/shell/telemetry-ledger-io.js +132 -0
- package/dist/{term.js → shell/term.js} +36 -46
- package/dist/shell/topology-io.js +361 -0
- package/dist/shell/trust-audit.js +472 -0
- package/dist/{multi-agent-trust.js → shell/trust-policy-io.js} +67 -205
- package/dist/shell/verifier.js +48 -0
- package/dist/shell/workbench-host.js +258 -0
- package/dist/shell/workbench-text.js +18 -0
- package/dist/shell/workbench.js +170 -0
- package/dist/shell/worker-cli.js +124 -0
- package/dist/shell/worker-isolation.js +852 -0
- package/dist/shell/workflow-app-loader.js +650 -0
- package/docs/agent-delegation-drive.7.md +4 -0
- package/docs/cli-mcp-parity.7.md +282 -219
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +4 -0
- package/docs/durable-state-and-locking.7.md +4 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
- package/docs/execution-backends.7.md +4 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
- package/docs/multi-agent-eval-replay-harness.7.md +4 -0
- package/docs/multi-agent-operator-ux.7.md +4 -0
- package/docs/node-snapshot-diff-replay.7.md +4 -0
- package/docs/observability-cost-accounting.7.md +6 -1
- package/docs/project-index.md +137 -71
- package/docs/real-execution-backends.7.md +4 -0
- package/docs/release-and-migration.7.md +4 -0
- package/docs/release-tooling.7.md +4 -0
- package/docs/remote-source-review.7.md +4 -4
- package/docs/report-verifiable-bundle.7.md +1 -1
- package/docs/run-registry-control-plane.7.md +4 -0
- package/docs/run-retention-reclamation.7.md +25 -0
- package/docs/security-trust-hardening.7.md +3 -1
- package/docs/state-explosion-management.7.md +4 -0
- package/docs/team-collaboration.7.md +4 -0
- package/docs/web-desktop-workbench.7.md +15 -1
- package/manifest/plugin.manifest.json +1 -1
- package/manifest/source-context-profiles.json +9 -13
- package/package.json +1 -1
- package/scripts/agents/agent-adapter-core.js +22 -2
- package/scripts/agents/claude-p-agent.js +8 -3
- package/scripts/agents/cw-attest-wrap.js +2 -2
- package/scripts/agents/gemini-agent.js +5 -1
- package/scripts/agents/opencode-agent.js +5 -1
- package/scripts/bump-version.js +4 -3
- package/scripts/canonical-apps.js +4 -4
- package/scripts/dogfood-architecture-review.js +2 -3
- package/scripts/dogfood-release.js +3 -3
- package/scripts/gen-parity-doc.js +15 -2
- package/scripts/golden-path.js +4 -4
- package/scripts/onramp-check.js +1 -1
- package/scripts/parity-check.js +38 -21
- package/scripts/release-flow.js +2 -2
- package/scripts/sync-project-index.js +51 -27
- package/scripts/validate-run-state-schema.js +2 -2
- package/scripts/version-sync-check.js +30 -30
- package/dist/candidate-scoring.js +0 -729
- package/dist/capability-core.js +0 -1189
- package/dist/capability-registry.js +0 -885
- package/dist/cli/command-surface.js +0 -494
- package/dist/cli/format.js +0 -56
- package/dist/cli/handlers/audit.js +0 -82
- package/dist/cli/handlers/blackboard.js +0 -81
- package/dist/cli/handlers/candidate.js +0 -40
- package/dist/cli/handlers/clones.js +0 -34
- package/dist/cli/handlers/collaboration.js +0 -61
- package/dist/cli/handlers/eval.js +0 -40
- package/dist/cli/handlers/ledger.js +0 -169
- package/dist/cli/handlers/maintenance.js +0 -107
- package/dist/cli/handlers/multi-agent.js +0 -165
- package/dist/cli/handlers/node.js +0 -41
- package/dist/cli/handlers/operational.js +0 -155
- package/dist/cli/handlers/operator.js +0 -146
- package/dist/cli/handlers/registry.js +0 -68
- package/dist/cli/handlers/run.js +0 -153
- package/dist/cli/handlers/scheduling.js +0 -132
- package/dist/cli/handlers/workbench.js +0 -41
- package/dist/cli/handlers/worker.js +0 -45
- package/dist/cli/run-summary.js +0 -45
- package/dist/clones.js +0 -162
- package/dist/collaboration.js +0 -726
- package/dist/commit.js +0 -592
- package/dist/compare.js +0 -18
- package/dist/coordinator/classify.js +0 -45
- package/dist/coordinator/paths.js +0 -42
- package/dist/coordinator/util.js +0 -126
- package/dist/coordinator.js +0 -990
- package/dist/daemon.js +0 -44
- package/dist/dispatch.js +0 -250
- package/dist/doctor.js +0 -212
- package/dist/drive.js +0 -864
- package/dist/error-feedback.js +0 -419
- package/dist/evidence-grounding.js +0 -184
- package/dist/execution-backend/agent.js +0 -354
- package/dist/execution-backend/probes.js +0 -112
- package/dist/execution-backend/util.js +0 -47
- package/dist/execution-backend.js +0 -961
- package/dist/gates.js +0 -48
- package/dist/harness.js +0 -61
- package/dist/ledger.js +0 -313
- package/dist/loop-expansion.js +0 -60
- package/dist/mcp/tool-call.js +0 -470
- package/dist/mcp/tool-definitions.js +0 -1066
- package/dist/mcp-surface.js +0 -30
- package/dist/multi-agent/graph.js +0 -84
- package/dist/multi-agent/helpers.js +0 -141
- package/dist/multi-agent/ids.js +0 -20
- package/dist/multi-agent/paths.js +0 -22
- package/dist/multi-agent-eval/normalize.js +0 -51
- package/dist/multi-agent-eval.js +0 -678
- package/dist/multi-agent-host.js +0 -777
- package/dist/multi-agent.js +0 -984
- package/dist/node-projection.js +0 -59
- package/dist/node-snapshot.js +0 -260
- package/dist/operator-ux.js +0 -631
- package/dist/orchestrator/app-operations.js +0 -211
- package/dist/orchestrator/audit-operations.js +0 -182
- package/dist/orchestrator/candidate-operations.js +0 -117
- package/dist/orchestrator/cli-options.js +0 -294
- package/dist/orchestrator/collaboration-operations.js +0 -86
- package/dist/orchestrator/feedback-operations.js +0 -81
- package/dist/orchestrator/host-operations.js +0 -78
- package/dist/orchestrator/lifecycle-operations.js +0 -650
- package/dist/orchestrator/migration-operations.js +0 -44
- package/dist/orchestrator/multi-agent-operations.js +0 -362
- package/dist/orchestrator/topology-operations.js +0 -84
- package/dist/orchestrator.js +0 -925
- package/dist/pipeline-runner.js +0 -285
- package/dist/reclamation/hash.js +0 -72
- package/dist/reclamation.js +0 -812
- package/dist/reporter.js +0 -67
- package/dist/run-export.js +0 -815
- package/dist/run-registry/derive.js +0 -175
- package/dist/run-registry/format.js +0 -124
- package/dist/run-registry/gc.js +0 -251
- package/dist/run-registry/policy.js +0 -16
- package/dist/run-registry/queue.js +0 -115
- package/dist/run-registry.js +0 -850
- package/dist/run-state-schema.js +0 -68
- package/dist/scheduling.js +0 -184
- package/dist/state-explosion.js +0 -1014
- package/dist/state.js +0 -367
- package/dist/telemetry-ledger.js +0 -196
- package/dist/topology.js +0 -565
- package/dist/triggers.js +0 -184
- package/dist/trust-audit.js +0 -644
- package/dist/types/blackboard.js +0 -2
- package/dist/types/candidate.js +0 -2
- package/dist/types/collaboration.js +0 -2
- package/dist/types/core.js +0 -2
- package/dist/types/drive.js +0 -10
- package/dist/types/error-feedback.js +0 -2
- package/dist/types/evidence-reasoning.js +0 -2
- package/dist/types/execution-backend.js +0 -2
- package/dist/types/multi-agent.js +0 -2
- package/dist/types/observability.js +0 -2
- package/dist/types/pipeline.js +0 -2
- package/dist/types/reclamation.js +0 -8
- package/dist/types/report-bundle.js +0 -6
- package/dist/types/result.js +0 -2
- package/dist/types/run-registry.js +0 -2
- package/dist/types/run.js +0 -2
- package/dist/types/sandbox.js +0 -2
- package/dist/types/schedule.js +0 -2
- package/dist/types/state-node.js +0 -2
- package/dist/types/topology.js +0 -2
- package/dist/types/trust.js +0 -2
- package/dist/types/workbench.js +0 -2
- package/dist/types/worker.js +0 -2
- package/dist/types/workflow-app.js +0 -2
- package/dist/types.js +0 -44
- package/dist/util/fingerprint.js +0 -19
- package/dist/util/fingerprint.test.js +0 -27
- package/dist/verifier.js +0 -78
- package/dist/version.js +0 -8
- package/dist/workbench-host.js +0 -192
- package/dist/workbench.js +0 -192
- package/dist/worker-accept/acceptance.js +0 -114
- package/dist/worker-accept/blackboard-fanout.js +0 -80
- package/dist/worker-accept/blackboard-linkage.js +0 -19
- package/dist/worker-accept/context.js +0 -2
- package/dist/worker-accept/telemetry-ledger.js +0 -126
- package/dist/worker-accept/validation.js +0 -77
- package/dist/worker-accept/verifier-completion.js +0 -73
- package/dist/worker-isolation/helpers.js +0 -51
- package/dist/worker-isolation/paths.js +0 -46
- package/dist/worker-isolation.js +0 -656
- package/dist/workflow-api.js +0 -131
- /package/dist/{types → core/types}/boundary.js +0 -0
package/dist/state.js
DELETED
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.CURRENT_RUN_STATE_SCHEMA_VERSION = void 0;
|
|
7
|
-
exports.createRunPaths = createRunPaths;
|
|
8
|
-
exports.ensureRunDirs = ensureRunDirs;
|
|
9
|
-
exports.loadRunFromCwd = loadRunFromCwd;
|
|
10
|
-
exports.loadRunStateFile = loadRunStateFile;
|
|
11
|
-
exports.checkRunStateFile = checkRunStateFile;
|
|
12
|
-
exports.migrateRunStateFile = migrateRunStateFile;
|
|
13
|
-
exports.saveCheckpoint = saveCheckpoint;
|
|
14
|
-
exports.compactCheckpoint = compactCheckpoint;
|
|
15
|
-
exports.readJson = readJson;
|
|
16
|
-
exports.writeJson = writeJson;
|
|
17
|
-
exports.durableAppendFileSync = durableAppendFileSync;
|
|
18
|
-
exports.realResolve = realResolve;
|
|
19
|
-
exports.isContainedPath = isContainedPath;
|
|
20
|
-
exports.withFileLock = withFileLock;
|
|
21
|
-
exports.safeFileName = safeFileName;
|
|
22
|
-
exports.assertSafeRunId = assertSafeRunId;
|
|
23
|
-
exports.hashArtifactFile = hashArtifactFile;
|
|
24
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
25
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
26
|
-
const state_migrations_1 = require("./state-migrations");
|
|
27
|
-
const version_1 = require("./version");
|
|
28
|
-
Object.defineProperty(exports, "CURRENT_RUN_STATE_SCHEMA_VERSION", { enumerable: true, get: function () { return version_1.CURRENT_RUN_STATE_SCHEMA_VERSION; } });
|
|
29
|
-
const execution_backend_1 = require("./execution-backend");
|
|
30
|
-
function createRunPaths(runDir) {
|
|
31
|
-
return {
|
|
32
|
-
runDir,
|
|
33
|
-
state: node_path_1.default.join(runDir, "state.json"),
|
|
34
|
-
report: node_path_1.default.join(runDir, "report.md"),
|
|
35
|
-
tasksDir: node_path_1.default.join(runDir, "tasks"),
|
|
36
|
-
resultsDir: node_path_1.default.join(runDir, "results"),
|
|
37
|
-
dispatchesDir: node_path_1.default.join(runDir, "dispatches"),
|
|
38
|
-
artifactsDir: node_path_1.default.join(runDir, "artifacts"),
|
|
39
|
-
commitsDir: node_path_1.default.join(runDir, "commits"),
|
|
40
|
-
stateNodesDir: node_path_1.default.join(runDir, "nodes"),
|
|
41
|
-
feedbackDir: node_path_1.default.join(runDir, "feedback"),
|
|
42
|
-
auditDir: node_path_1.default.join(runDir, "audit"),
|
|
43
|
-
workersDir: node_path_1.default.join(runDir, "workers"),
|
|
44
|
-
candidatesDir: node_path_1.default.join(runDir, "candidates"),
|
|
45
|
-
multiAgentDir: node_path_1.default.join(runDir, "multi-agent"),
|
|
46
|
-
blackboardDir: node_path_1.default.join(runDir, "blackboard"),
|
|
47
|
-
topologiesDir: node_path_1.default.join(runDir, "topologies")
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
function ensureRunDirs(paths) {
|
|
51
|
-
for (const dir of [
|
|
52
|
-
paths.runDir,
|
|
53
|
-
paths.tasksDir,
|
|
54
|
-
paths.resultsDir,
|
|
55
|
-
paths.dispatchesDir,
|
|
56
|
-
paths.artifactsDir,
|
|
57
|
-
paths.commitsDir,
|
|
58
|
-
paths.stateNodesDir,
|
|
59
|
-
paths.feedbackDir,
|
|
60
|
-
paths.auditDir || node_path_1.default.join(paths.runDir, "audit"),
|
|
61
|
-
paths.workersDir || node_path_1.default.join(paths.runDir, "workers"),
|
|
62
|
-
paths.candidatesDir || node_path_1.default.join(paths.runDir, "candidates"),
|
|
63
|
-
paths.multiAgentDir || node_path_1.default.join(paths.runDir, "multi-agent"),
|
|
64
|
-
paths.blackboardDir || node_path_1.default.join(paths.runDir, "blackboard"),
|
|
65
|
-
paths.topologiesDir || node_path_1.default.join(paths.runDir, "topologies")
|
|
66
|
-
]) {
|
|
67
|
-
node_fs_1.default.mkdirSync(dir, { recursive: true });
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
function loadRunFromCwd(runId, cwd = process.cwd()) {
|
|
71
|
-
if (!runId)
|
|
72
|
-
throw new Error("Missing run id");
|
|
73
|
-
assertSafeRunId(runId);
|
|
74
|
-
const statePath = node_path_1.default.join(cwd, ".cw", "runs", runId, "state.json");
|
|
75
|
-
const result = loadRunStateFile(statePath, { dryRun: true });
|
|
76
|
-
if (result.report.status === "unsupported") {
|
|
77
|
-
throw new Error(`Unsupported CW run state: ${result.report.errors.join("; ")}`);
|
|
78
|
-
}
|
|
79
|
-
return result.run;
|
|
80
|
-
}
|
|
81
|
-
function loadRunStateFile(statePath, options = {}) {
|
|
82
|
-
const result = (0, state_migrations_1.migrateRunState)(readJson(statePath), {
|
|
83
|
-
statePath,
|
|
84
|
-
dryRun: options.dryRun === undefined ? true : options.dryRun
|
|
85
|
-
});
|
|
86
|
-
if (result.report.status === "unsupported")
|
|
87
|
-
return result;
|
|
88
|
-
return result;
|
|
89
|
-
}
|
|
90
|
-
function checkRunStateFile(statePath) {
|
|
91
|
-
return loadRunStateFile(statePath, { dryRun: true });
|
|
92
|
-
}
|
|
93
|
-
function migrateRunStateFile(statePath, options = {}) {
|
|
94
|
-
const result = loadRunStateFile(statePath, { dryRun: !options.write });
|
|
95
|
-
if (result.report.status !== "unsupported" && options.write && result.report.writeRequired) {
|
|
96
|
-
writeJson(statePath, result.run);
|
|
97
|
-
}
|
|
98
|
-
return result;
|
|
99
|
-
}
|
|
100
|
-
function saveCheckpoint(run) {
|
|
101
|
-
run.updatedAt = new Date().toISOString();
|
|
102
|
-
// state.json is the single source of truth — write it DURABLY with a lock
|
|
103
|
-
// so concurrent processes never lose an update (v0.1.95).
|
|
104
|
-
withFileLock(run.paths.state, () => {
|
|
105
|
-
writeJson(run.paths.state, run, { durable: true });
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
/** Compact a run checkpoint by stripping empty optional arrays and null values
|
|
109
|
-
* that don't carry semantic meaning (v0.1.60). The normalization layer
|
|
110
|
-
* (normalizeRunState) backfills these on load, so stripping them saves disk
|
|
111
|
-
* without losing information. Returns the number of keys stripped. */
|
|
112
|
-
function compactCheckpoint(run) {
|
|
113
|
-
const optionalArrays = [
|
|
114
|
-
"nodes", "contracts", "feedback", "workers", "sandboxProfiles",
|
|
115
|
-
"candidates", "candidateSelections"
|
|
116
|
-
];
|
|
117
|
-
let stripped = 0;
|
|
118
|
-
const state = run;
|
|
119
|
-
for (const key of optionalArrays) {
|
|
120
|
-
if (Array.isArray(state[key]) && state[key].length === 0) {
|
|
121
|
-
delete state[key];
|
|
122
|
-
stripped++;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
if (stripped > 0)
|
|
126
|
-
saveCheckpoint(run);
|
|
127
|
-
return stripped;
|
|
128
|
-
}
|
|
129
|
-
function readJson(file) {
|
|
130
|
-
if (!node_fs_1.default.existsSync(file))
|
|
131
|
-
throw new Error(`File not found: ${file}`);
|
|
132
|
-
try {
|
|
133
|
-
return JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
137
|
-
throw new Error(`Invalid JSON in ${file}: ${message}`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
// ---------------------------------------------------------------------------
|
|
141
|
-
// Atomic, optionally-durable JSON write (v0.1.40, closes the prior P1).
|
|
142
|
-
//
|
|
143
|
-
// ORDER IS THE SAFETY PROPERTY: write to a unique temp file, then rename over the
|
|
144
|
-
// target. `rename(2)` is atomic on POSIX, so a crash/`ENOSPC` mid-write can never
|
|
145
|
-
// leave a truncated `state.json` that throws `Invalid JSON` on reload — a reader
|
|
146
|
-
// always sees EITHER the old bytes OR the new bytes, never a torn file. With
|
|
147
|
-
// `{ durable: true }` we additionally fsync the file (and best-effort the dir)
|
|
148
|
-
// before/after the rename so the bytes survive power loss — used for AUTHORITATIVE
|
|
149
|
-
// state (state.json, registry overlays, the scheduler store, reclaimed.json). The
|
|
150
|
-
// fsync is skipped for high-frequency derived/rebuildable writes so the atomic
|
|
151
|
-
// rename (the actual torn-write fix) stays cheap everywhere.
|
|
152
|
-
// ---------------------------------------------------------------------------
|
|
153
|
-
let atomicWriteCounter = 0;
|
|
154
|
-
function writeJson(file, value, options = {}) {
|
|
155
|
-
node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
|
|
156
|
-
const tmp = `${file}.tmp.${process.pid}.${atomicWriteCounter++}`;
|
|
157
|
-
const fd = node_fs_1.default.openSync(tmp, "w");
|
|
158
|
-
try {
|
|
159
|
-
node_fs_1.default.writeFileSync(fd, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
160
|
-
if (options.durable)
|
|
161
|
-
node_fs_1.default.fsyncSync(fd);
|
|
162
|
-
}
|
|
163
|
-
finally {
|
|
164
|
-
node_fs_1.default.closeSync(fd);
|
|
165
|
-
}
|
|
166
|
-
try {
|
|
167
|
-
node_fs_1.default.renameSync(tmp, file);
|
|
168
|
-
}
|
|
169
|
-
catch (error) {
|
|
170
|
-
try {
|
|
171
|
-
node_fs_1.default.rmSync(tmp, { force: true });
|
|
172
|
-
}
|
|
173
|
-
catch {
|
|
174
|
-
/* best-effort temp cleanup */
|
|
175
|
-
}
|
|
176
|
-
throw error;
|
|
177
|
-
}
|
|
178
|
-
if (options.durable) {
|
|
179
|
-
try {
|
|
180
|
-
const dirFd = node_fs_1.default.openSync(node_path_1.default.dirname(file), "r");
|
|
181
|
-
try {
|
|
182
|
-
node_fs_1.default.fsyncSync(dirFd);
|
|
183
|
-
}
|
|
184
|
-
finally {
|
|
185
|
-
node_fs_1.default.closeSync(dirFd);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
catch {
|
|
189
|
-
/* directory fsync is best-effort (not supported on every platform) */
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
// ---------------------------------------------------------------------------
|
|
194
|
-
// Durable append (v0.1.40 self-audit P1) — append a line and fsync it before
|
|
195
|
-
// returning. The trust-audit event log is the ONE artifact whose loss breaks
|
|
196
|
-
// audit-completeness/non-repudiation, so unlike high-frequency derived writes it
|
|
197
|
-
// must survive power loss. `appendFileSync` alone does NOT fsync, so a crash
|
|
198
|
-
// after it returns could drop the most recent event while durable state.json
|
|
199
|
-
// advanced past it. We open O_APPEND, write, fsync the fd, then close.
|
|
200
|
-
// ---------------------------------------------------------------------------
|
|
201
|
-
function durableAppendFileSync(file, data) {
|
|
202
|
-
node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
|
|
203
|
-
const fd = node_fs_1.default.openSync(file, "a");
|
|
204
|
-
try {
|
|
205
|
-
node_fs_1.default.writeFileSync(fd, data, "utf8");
|
|
206
|
-
node_fs_1.default.fsyncSync(fd);
|
|
207
|
-
}
|
|
208
|
-
finally {
|
|
209
|
-
node_fs_1.default.closeSync(fd);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
// ---------------------------------------------------------------------------
|
|
213
|
-
// Symlink-hardened path containment (v0.1.40 self-audit P1) — `path.resolve()`
|
|
214
|
-
// only normalizes `.`/`..` textually; it does NOT follow symlinks, so a planted
|
|
215
|
-
// symlink whose textual path sits "inside" an allowed root but whose real target
|
|
216
|
-
// escapes it would pass a `startsWith` containment check. `realResolve` resolves
|
|
217
|
-
// the deepest EXISTING ancestor with `realpathSync` (which follows symlinks) and
|
|
218
|
-
// re-joins the not-yet-created remainder, so a not-yet-created file is still
|
|
219
|
-
// pinned to its real parent. `isContainedPath` realpaths BOTH sides so the
|
|
220
|
-
// comparison stays consistent on platforms where the temp root itself is a
|
|
221
|
-
// symlink (e.g. macOS /tmp -> /private/tmp).
|
|
222
|
-
// ---------------------------------------------------------------------------
|
|
223
|
-
function realResolve(target) {
|
|
224
|
-
let current = node_path_1.default.resolve(target);
|
|
225
|
-
const tail = [];
|
|
226
|
-
// Walk up to the deepest existing ancestor, realpath it, then re-append the tail.
|
|
227
|
-
for (;;) {
|
|
228
|
-
try {
|
|
229
|
-
const real = node_fs_1.default.realpathSync.native ? node_fs_1.default.realpathSync.native(current) : node_fs_1.default.realpathSync(current);
|
|
230
|
-
return tail.length ? node_path_1.default.join(real, ...tail.reverse()) : real;
|
|
231
|
-
}
|
|
232
|
-
catch {
|
|
233
|
-
const parent = node_path_1.default.dirname(current);
|
|
234
|
-
if (parent === current)
|
|
235
|
-
return node_path_1.default.resolve(target); // reached root; nothing existed
|
|
236
|
-
tail.push(node_path_1.default.basename(current));
|
|
237
|
-
current = parent;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
function isContainedPath(candidate, allowed) {
|
|
242
|
-
const realCandidate = realResolve(candidate);
|
|
243
|
-
const realAllowed = realResolve(allowed);
|
|
244
|
-
return realCandidate === realAllowed || realCandidate.startsWith(realAllowed + node_path_1.default.sep);
|
|
245
|
-
}
|
|
246
|
-
// ---------------------------------------------------------------------------
|
|
247
|
-
// Portable advisory file lock (v0.1.40) — serialize cross-process read-modify-
|
|
248
|
-
// write on shared stores (home queue, scheduler store, archive overlay, the
|
|
249
|
-
// per-run reclamation chain) so a concurrent writer can never lose a record.
|
|
250
|
-
// O_EXCL (`wx`) is portable (no native flock); a stale holder is stolen so a
|
|
251
|
-
// crashed process can never wedge the store forever.
|
|
252
|
-
//
|
|
253
|
-
// v0.1.95: the lock mtime is refreshed immediately before fn() runs, and
|
|
254
|
-
// verified AFTER fn() returns — if another process stole the lock mid-operation
|
|
255
|
-
// (the stale check fired and it overwrote our lock), the holder refuses to
|
|
256
|
-
// release and throws. This guards long-running RMW operations (e.g. GC over a
|
|
257
|
-
// large run) against mid-operation theft.
|
|
258
|
-
// ---------------------------------------------------------------------------
|
|
259
|
-
const FILE_LOCK_STALE_MS = 30_000;
|
|
260
|
-
function sleepSync(ms) {
|
|
261
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
262
|
-
}
|
|
263
|
-
/** Run `fn` while holding an advisory lock for `targetPath`; always released. */
|
|
264
|
-
function withFileLock(targetPath, fn) {
|
|
265
|
-
const lock = `${targetPath}.lock`;
|
|
266
|
-
node_fs_1.default.mkdirSync(node_path_1.default.dirname(lock), { recursive: true });
|
|
267
|
-
const pid = String(process.pid);
|
|
268
|
-
let acquired = false;
|
|
269
|
-
for (let attempt = 0; attempt < 240 && !acquired; attempt++) {
|
|
270
|
-
try {
|
|
271
|
-
const fd = node_fs_1.default.openSync(lock, "wx");
|
|
272
|
-
node_fs_1.default.writeFileSync(fd, `${pid}@${new Date().toISOString()}\n`, "utf8");
|
|
273
|
-
node_fs_1.default.closeSync(fd);
|
|
274
|
-
acquired = true;
|
|
275
|
-
}
|
|
276
|
-
catch (error) {
|
|
277
|
-
if (!(error && typeof error === "object" && error.code === "EEXIST"))
|
|
278
|
-
throw error;
|
|
279
|
-
try {
|
|
280
|
-
if (Date.now() - node_fs_1.default.statSync(lock).mtimeMs > FILE_LOCK_STALE_MS) {
|
|
281
|
-
node_fs_1.default.rmSync(lock, { force: true });
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
catch {
|
|
286
|
-
continue;
|
|
287
|
-
}
|
|
288
|
-
sleepSync(25);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
if (!acquired)
|
|
292
|
-
throw new Error(`could not acquire file lock for ${targetPath}`);
|
|
293
|
-
// Refresh mtime right before the critical section
|
|
294
|
-
try {
|
|
295
|
-
node_fs_1.default.utimesSync(lock, new Date(), new Date());
|
|
296
|
-
}
|
|
297
|
-
catch { /* best-effort */ }
|
|
298
|
-
try {
|
|
299
|
-
const result = fn();
|
|
300
|
-
// Verify lock was not stolen during fn(). The lock content may have changed
|
|
301
|
-
// if another process opened it with wx after stealing it. If our PID is not
|
|
302
|
-
// in the lock file, the lock was stolen — do NOT release the stolen lock
|
|
303
|
-
// (the thief owns it now, and releasing would corrupt its operation).
|
|
304
|
-
try {
|
|
305
|
-
const current = node_fs_1.default.readFileSync(lock, "utf8");
|
|
306
|
-
if (!current.startsWith(pid + "@")) {
|
|
307
|
-
throw new Error(`File lock for ${targetPath} was stolen during the critical section ` +
|
|
308
|
-
`(lock now owned by another process). The operation may have lost ` +
|
|
309
|
-
`cross-process isolation — increase FILE_LOCK_STALE_MS or split the work.`);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
catch (checkError) {
|
|
313
|
-
if (checkError instanceof Error && checkError.message.includes("stolen"))
|
|
314
|
-
throw checkError;
|
|
315
|
-
/* lock vanished — another process already released it, nothing to clean up */
|
|
316
|
-
}
|
|
317
|
-
return result;
|
|
318
|
-
}
|
|
319
|
-
finally {
|
|
320
|
-
try {
|
|
321
|
-
// Only release if we still own the lock
|
|
322
|
-
const current = node_fs_1.default.readFileSync(lock, "utf8");
|
|
323
|
-
if (current.startsWith(pid + "@"))
|
|
324
|
-
node_fs_1.default.rmSync(lock, { force: true });
|
|
325
|
-
}
|
|
326
|
-
catch {
|
|
327
|
-
/* releasing a missing lock is fine */
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
function safeFileName(value) {
|
|
332
|
-
return String(value).replace(/[^a-zA-Z0-9_.:-]+/g, "_");
|
|
333
|
-
}
|
|
334
|
-
/** Refuse a run id that is not a single safe path segment, so an id taken from
|
|
335
|
-
* an untrusted source (an imported run archive / bundle) can never escape the
|
|
336
|
-
* runs directory via a separator or a `..`/`.` component (path traversal). A
|
|
337
|
-
* real run id is `${workflowId}-${stamp}-${suffix}` (createRunId), and a
|
|
338
|
-
* workflow id is alnum-bounded [a-z0-9.-] (validateWorkflowId) — all within
|
|
339
|
-
* [A-Za-z0-9._:-]. Sub-workflow ids also include `:` from the task id prefix.
|
|
340
|
-
* Because the charset already forbids any separator, the whole
|
|
341
|
-
* id is ONE path component, so the only values that could traverse are the
|
|
342
|
-
* components `.` and `..` themselves; an embedded `..` (e.g. a workflow id like
|
|
343
|
-
* `v1..2`) is a safe directory name and is allowed. A separator, an absolute
|
|
344
|
-
* path, or an empty value is refused, fail-closed. Returns the id on success so
|
|
345
|
-
* callers can use it inline. */
|
|
346
|
-
function assertSafeRunId(value, context = "run id") {
|
|
347
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
348
|
-
throw new Error(`Invalid ${context}: expected a non-empty string`);
|
|
349
|
-
}
|
|
350
|
-
if (!/^[A-Za-z0-9._:-]+$/.test(value) || value === "." || value === "..") {
|
|
351
|
-
throw new Error(`Unsafe ${context}: ${JSON.stringify(value)} must be a single path segment ([A-Za-z0-9._:-], not '.' or '..')`);
|
|
352
|
-
}
|
|
353
|
-
return value;
|
|
354
|
-
}
|
|
355
|
-
/** Compute and set SHA256 + sizeBytes on a StateArtifact from its file path
|
|
356
|
-
* (v0.1.73). Fails silently when the file doesn't exist — does not throw. */
|
|
357
|
-
function hashArtifactFile(artifact) {
|
|
358
|
-
try {
|
|
359
|
-
const content = node_fs_1.default.readFileSync(artifact.path, "utf8");
|
|
360
|
-
artifact.sha256 = (0, execution_backend_1.sha256)(content);
|
|
361
|
-
artifact.sizeBytes = Buffer.byteLength(content, "utf8");
|
|
362
|
-
}
|
|
363
|
-
catch {
|
|
364
|
-
/* file missing — silently skip */
|
|
365
|
-
}
|
|
366
|
-
return artifact;
|
|
367
|
-
}
|
package/dist/telemetry-ledger.js
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// Telemetry attestation ledger (Track 1) — make the RECORDED attestation itself
|
|
3
|
-
// tamper-evident, not just the in-flight signature.
|
|
4
|
-
//
|
|
5
|
-
// The executor's signature (telemetry-attestation.ts) proves the agent SAID a
|
|
6
|
-
// given usage. This ledger proves CW RECORDED exactly that and nobody edited the
|
|
7
|
-
// record afterward: an append-only, hash-chained overlay (`telemetry.json`, a
|
|
8
|
-
// runDir peer of reclaimed.json), one entry per agent hop. Each entry chains to
|
|
9
|
-
// the prior via prevHash; recordHash = sha256(canonical entry sans recordHash).
|
|
10
|
-
// Flip a recorded verdict (`unattested`→`attested`), or edit a recorded usage
|
|
11
|
-
// digest, and the chain no longer recomputes — `verifyTelemetryLedger` catches it.
|
|
12
|
-
//
|
|
13
|
-
// Same discipline as reclamation.ts's tombstone chain: APPEND-ONLY (never rewrite
|
|
14
|
-
// a prior entry), DURABLE (temp→fsync→rename via writeJson), and verify recomputes
|
|
15
|
-
// every hash independently — it never trusts the stored value.
|
|
16
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
-
};
|
|
19
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.TelemetryLedgerCorruptError = exports.TELEMETRY_LEDGER_SCHEMA_VERSION = void 0;
|
|
21
|
-
exports.telemetryLedgerPath = telemetryLedgerPath;
|
|
22
|
-
exports.loadTelemetryLedger = loadTelemetryLedger;
|
|
23
|
-
exports.genesisPrevHash = genesisPrevHash;
|
|
24
|
-
exports.computeRecordHash = computeRecordHash;
|
|
25
|
-
exports.reportedUsageDigest = reportedUsageDigest;
|
|
26
|
-
exports.appendTelemetryAttestation = appendTelemetryAttestation;
|
|
27
|
-
exports.verifyTelemetryLedger = verifyTelemetryLedger;
|
|
28
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
29
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
30
|
-
const state_1 = require("./state");
|
|
31
|
-
const execution_backend_1 = require("./execution-backend");
|
|
32
|
-
const telemetry_attestation_1 = require("./telemetry-attestation");
|
|
33
|
-
exports.TELEMETRY_LEDGER_SCHEMA_VERSION = 1;
|
|
34
|
-
function telemetryLedgerPath(run) {
|
|
35
|
-
return node_path_1.default.join(run.paths.runDir, "telemetry.json");
|
|
36
|
-
}
|
|
37
|
-
/** A telemetry ledger that EXISTS on disk but cannot be parsed (or whose shape is
|
|
38
|
-
* not a record array). This is exactly the corruption/truncation case the hash
|
|
39
|
-
* chain exists to catch — it must fail closed, never be silently treated as the
|
|
40
|
-
* "empty/absent" chain (which verifies as clean). */
|
|
41
|
-
class TelemetryLedgerCorruptError extends Error {
|
|
42
|
-
file;
|
|
43
|
-
constructor(file) {
|
|
44
|
-
super(`Telemetry ledger exists but is corrupt (unparseable): ${file}`);
|
|
45
|
-
this.name = "TelemetryLedgerCorruptError";
|
|
46
|
-
this.file = file;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
exports.TelemetryLedgerCorruptError = TelemetryLedgerCorruptError;
|
|
50
|
-
/** Read the ledger, DISTINGUISHING absent (never written -> empty chain, fine)
|
|
51
|
-
* from corrupt (exists but unparseable/wrong shape -> fail closed). Conflating
|
|
52
|
-
* the two was the bug that let a corrupt overlay verify green and let an append
|
|
53
|
-
* silently re-genesis on top of it, discarding history. */
|
|
54
|
-
function readTelemetryLedgerState(run) {
|
|
55
|
-
const file = telemetryLedgerPath(run);
|
|
56
|
-
if (!node_fs_1.default.existsSync(file))
|
|
57
|
-
return { status: "absent", ledger: { schemaVersion: 1, runId: run.id, records: [] } };
|
|
58
|
-
let parsed;
|
|
59
|
-
try {
|
|
60
|
-
parsed = JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
return { status: "corrupt", file };
|
|
64
|
-
}
|
|
65
|
-
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.records)) {
|
|
66
|
-
return { status: "corrupt", file };
|
|
67
|
-
}
|
|
68
|
-
return { status: "ok", ledger: { schemaVersion: 1, runId: run.id, records: parsed.records } };
|
|
69
|
-
}
|
|
70
|
-
/** Load the ledger for read/append. Absent -> empty chain. Corrupt -> THROWS, so
|
|
71
|
-
* an append can never silently re-genesis on a poisoned/edited file, and a read
|
|
72
|
-
* surfaces the corruption rather than swallowing it. */
|
|
73
|
-
function loadTelemetryLedger(run) {
|
|
74
|
-
const state = readTelemetryLedgerState(run);
|
|
75
|
-
if (state.status === "corrupt")
|
|
76
|
-
throw new TelemetryLedgerCorruptError(state.file);
|
|
77
|
-
return state.ledger;
|
|
78
|
-
}
|
|
79
|
-
/** genesis prevHash for a run's chain (no prior record). */
|
|
80
|
-
function genesisPrevHash(runId) {
|
|
81
|
-
return (0, execution_backend_1.sha256)(`cw-telemetry-ledger:${runId}`);
|
|
82
|
-
}
|
|
83
|
-
/** The canonical bytes a recordHash binds — every field except recordHash itself.
|
|
84
|
-
* Recomputed independently by verifyTelemetryLedger. */
|
|
85
|
-
function recordHashInput(record) {
|
|
86
|
-
return (0, telemetry_attestation_1.stableStringify)({
|
|
87
|
-
schemaVersion: record.schemaVersion,
|
|
88
|
-
runId: record.runId,
|
|
89
|
-
recordId: record.recordId,
|
|
90
|
-
recordedAt: record.recordedAt,
|
|
91
|
-
workerId: record.workerId,
|
|
92
|
-
taskId: record.taskId,
|
|
93
|
-
promptDigest: record.promptDigest,
|
|
94
|
-
reportedUsageDigest: record.reportedUsageDigest,
|
|
95
|
-
...(record.reportedUsage !== undefined ? { reportedUsage: record.reportedUsage } : {}),
|
|
96
|
-
usageSignature: record.usageSignature || null,
|
|
97
|
-
// Chain-bind resultDigest only when present, so a usage-only record's hash is
|
|
98
|
-
// byte-identical to a pre-result-coverage one (back-compat with old ledgers).
|
|
99
|
-
...(record.resultDigest !== undefined ? { resultDigest: record.resultDigest } : {}),
|
|
100
|
-
attestation: record.attestation,
|
|
101
|
-
attestationReason: record.attestationReason || null,
|
|
102
|
-
prevHash: record.prevHash
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
function computeRecordHash(record) {
|
|
106
|
-
return (0, execution_backend_1.sha256)(recordHashInput(record));
|
|
107
|
-
}
|
|
108
|
-
/** sha256 of the canonical reported usage (compact, chainable). Absent usage gets
|
|
109
|
-
* the digest of `null`, so "the agent reported nothing" is itself bound. */
|
|
110
|
-
function reportedUsageDigest(usage) {
|
|
111
|
-
return (0, execution_backend_1.sha256)((0, telemetry_attestation_1.stableStringify)(usage ?? null));
|
|
112
|
-
}
|
|
113
|
-
function recordId(seq) {
|
|
114
|
-
// Deterministic (FreeBSD-audit L13): the chain POSITION, not a process-global
|
|
115
|
-
// counter or wall-clock stamp — recordId is bound into the recordHash chain.
|
|
116
|
-
return `tel-${String(seq).padStart(3, "0")}`;
|
|
117
|
-
}
|
|
118
|
-
/** Append one attestation record DURABLY to the append-only chain, linking it to
|
|
119
|
-
* the prior record (or genesis). Returns the committed record. */
|
|
120
|
-
function appendTelemetryAttestation(run, input) {
|
|
121
|
-
const ledger = loadTelemetryLedger(run);
|
|
122
|
-
const now = input.now || new Date().toISOString();
|
|
123
|
-
const prevHash = ledger.records.length ? ledger.records[ledger.records.length - 1].recordHash : genesisPrevHash(run.id);
|
|
124
|
-
const base = {
|
|
125
|
-
schemaVersion: 1,
|
|
126
|
-
runId: run.id,
|
|
127
|
-
recordId: recordId(ledger.records.length + 1),
|
|
128
|
-
recordedAt: now,
|
|
129
|
-
workerId: input.workerId,
|
|
130
|
-
taskId: input.taskId,
|
|
131
|
-
promptDigest: input.promptDigest,
|
|
132
|
-
reportedUsageDigest: reportedUsageDigest(input.reportedUsage),
|
|
133
|
-
// Store the raw usage verbatim, digest-bound, and hash-chained so the
|
|
134
|
-
// signature can be independently re-verified offline at `telemetry verify`.
|
|
135
|
-
...(input.reportedUsage ? { reportedUsage: input.reportedUsage } : {}),
|
|
136
|
-
usageSignature: input.usageSignature,
|
|
137
|
-
// Present only for a result-bound signature, so usage-only records are
|
|
138
|
-
// byte-identical (and their recordHash unchanged) — back-compat.
|
|
139
|
-
...(input.resultDigest ? { resultDigest: input.resultDigest } : {}),
|
|
140
|
-
attestation: input.attestation,
|
|
141
|
-
attestationReason: input.attestationReason,
|
|
142
|
-
prevHash
|
|
143
|
-
};
|
|
144
|
-
const record = { ...base, recordHash: computeRecordHash(base) };
|
|
145
|
-
ledger.records.push(record);
|
|
146
|
-
(0, state_1.writeJson)(telemetryLedgerPath(run), ledger, { durable: true });
|
|
147
|
-
return record;
|
|
148
|
-
}
|
|
149
|
-
/** Re-prove the whole telemetry chain for a run: prevHash linkage + per-record
|
|
150
|
-
* hash recompute. Recomputes every hash independently — never trusts the stored
|
|
151
|
-
* value — so an edited record/verdict is detected. An empty ledger verifies as
|
|
152
|
-
* present:false (nothing to prove), NOT a failure. */
|
|
153
|
-
function verifyTelemetryLedger(run) {
|
|
154
|
-
const state = readTelemetryLedgerState(run);
|
|
155
|
-
if (state.status === "corrupt") {
|
|
156
|
-
// Fail closed: a ledger that exists but cannot be parsed is indistinguishable
|
|
157
|
-
// from a truncated/forged one — report it, never green it.
|
|
158
|
-
return {
|
|
159
|
-
present: true,
|
|
160
|
-
verified: false,
|
|
161
|
-
records: [],
|
|
162
|
-
checks: [{ name: "ledger-load", pass: false, code: "telemetry-ledger-corrupt" }],
|
|
163
|
-
attested: 0,
|
|
164
|
-
unattested: 0,
|
|
165
|
-
absent: 0
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
const records = state.ledger.records;
|
|
169
|
-
const checks = [];
|
|
170
|
-
const tally = { attested: 0, unattested: 0, absent: 0 };
|
|
171
|
-
for (const record of records)
|
|
172
|
-
tally[record.attestation] += 1;
|
|
173
|
-
if (!records.length) {
|
|
174
|
-
return { present: false, verified: true, records, checks, ...tally };
|
|
175
|
-
}
|
|
176
|
-
// (a) chain linkage: genesis = sha256("cw-telemetry-ledger:"+runId).
|
|
177
|
-
let chainOk = true;
|
|
178
|
-
for (let i = 0; i < records.length; i++) {
|
|
179
|
-
const expectedPrev = i === 0 ? genesisPrevHash(run.id) : records[i - 1].recordHash;
|
|
180
|
-
const pass = records[i].prevHash === expectedPrev;
|
|
181
|
-
if (!pass)
|
|
182
|
-
chainOk = false;
|
|
183
|
-
checks.push({ name: `chain-link[${i}]`, pass, code: pass ? undefined : "telemetry-chain-broken" });
|
|
184
|
-
}
|
|
185
|
-
// (b) per-record independent hash recompute (digest integrity).
|
|
186
|
-
let digestsOk = true;
|
|
187
|
-
for (let i = 0; i < records.length; i++) {
|
|
188
|
-
const { recordHash, ...rest } = records[i];
|
|
189
|
-
const recomputed = computeRecordHash(rest);
|
|
190
|
-
const pass = recomputed === recordHash;
|
|
191
|
-
if (!pass)
|
|
192
|
-
digestsOk = false;
|
|
193
|
-
checks.push({ name: `record-hash[${i}]`, pass, code: pass ? undefined : "telemetry-digest-mismatch" });
|
|
194
|
-
}
|
|
195
|
-
return { present: true, verified: chainOk && digestsOk, records, checks, ...tally };
|
|
196
|
-
}
|