cool-workflow 0.1.97 → 0.2.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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +11 -2
- package/apps/architecture-review/app.json +1 -1
- 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 +3534 -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/{doctor.js → shell/doctor.js} +123 -56
- package/dist/shell/drive.js +873 -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 +475 -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 +98 -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 +329 -0
- package/dist/{remote-source.js → shell/remote-source.js} +12 -5
- 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} +248 -48
- package/dist/shell/scheduling-io.js +311 -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 +471 -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 +250 -0
- package/dist/shell/workbench-text.js +18 -0
- package/dist/shell/workbench.js +175 -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 +284 -211
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +4 -0
- package/docs/cross-agent-ledger.7.md +217 -0
- package/docs/designs/handoff-ledger.md +145 -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/handoff-setup.md +120 -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 +4 -0
- package/docs/project-index.md +143 -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/run-registry-control-plane.7.md +4 -0
- package/docs/run-retention-reclamation.7.md +25 -0
- package/docs/state-explosion-management.7.md +4 -0
- package/docs/team-collaboration.7.md +4 -0
- package/docs/web-desktop-workbench.7.md +4 -0
- package/manifest/plugin.manifest.json +1 -1
- package/manifest/source-context-profiles.json +9 -13
- package/package.json +1 -1
- package/scripts/agents/codex-agent.js +34 -4
- package/scripts/agents/cw-attest-wrap.js +2 -2
- package/scripts/bump-version.js +4 -3
- package/scripts/canonical-apps.js +4 -4
- package/scripts/children/batch-delegate-child.js +30 -10
- 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 +9 -3
- 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 -1186
- package/dist/capability-registry.js +0 -879
- package/dist/cli/command-surface.js +0 -490
- 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/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/drive.js +0 -827
- package/dist/error-feedback.js +0 -419
- package/dist/evidence-grounding.js +0 -184
- package/dist/execution-backend/agent.js +0 -294
- 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/loop-expansion.js +0 -60
- package/dist/mcp/tool-call.js +0 -434
- package/dist/mcp/tool-definitions.js +0 -1040
- 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 -626
- 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 -917
- 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 -784
- 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 -182
- 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
|
@@ -1,42 +1,64 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// shell/telemetry-demo.ts — `cw demo tamper|bundle`: the hermetic,
|
|
3
|
+
// offline trust proofs. Impure (fs, ephemeral keypair generation,
|
|
4
|
+
// tmpdir), so this lives in shell/ not core/.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// A) LEDGER layer — flip a recorded verdict on disk (unattested -> attested, the
|
|
11
|
-
// canonical "forge a green record" attack) -> verifyTelemetryLedger recomputes
|
|
12
|
-
// every hash independently, so the edited record's hash mismatches AND every
|
|
13
|
-
// record after it breaks the chain (cascade).
|
|
14
|
-
// B) SIGNATURE layer — inflate the reported tokens but keep the original ed25519
|
|
15
|
-
// signature -> verifyTelemetryAttestation rejects it ("signature does not match").
|
|
16
|
-
// C) RESULT layer — edit the agent's SIGNED FINDING after signing. The executor
|
|
17
|
-
// binds sha256(result.md) into the ed25519 payload; CW re-derives the digest
|
|
18
|
-
// from the result file at verify time, so an edited finding no longer joins the
|
|
19
|
-
// signature. This is the threat a usage-only signature never covered.
|
|
6
|
+
// MILESTONE 8. Byte-exact port of the old build's src/telemetry-demo.ts.
|
|
7
|
+
// Fully hermetic + deterministic: an EPHEMERAL ed25519 keypair, fixed
|
|
8
|
+
// hops/usages, a pinned `now`, no network, no model. Only the keypair
|
|
9
|
+
// itself varies per run and never leaves this module.
|
|
20
10
|
//
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
11
|
+
// Evidence: SPEC/ledger-trust.md "`cw demo tamper`", "`cw demo bundle`";
|
|
12
|
+
// plugins/cool-workflow/src/telemetry-demo.ts:1-424 (byte-exact source).
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
25
46
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
47
|
exports.formatTelemetryVerify = formatTelemetryVerify;
|
|
27
48
|
exports.formatTamperDemo = formatTamperDemo;
|
|
28
49
|
exports.runTamperDemo = runTamperDemo;
|
|
29
50
|
exports.runBundleDemo = runBundleDemo;
|
|
30
51
|
exports.formatBundleDemo = formatBundleDemo;
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const telemetry_attestation_1 = require("
|
|
52
|
+
const crypto = __importStar(require("node:crypto"));
|
|
53
|
+
const fs = __importStar(require("node:fs"));
|
|
54
|
+
const os = __importStar(require("node:os"));
|
|
55
|
+
const path = __importStar(require("node:path"));
|
|
56
|
+
const hash_1 = require("../core/hash");
|
|
57
|
+
const telemetry_attestation_1 = require("../core/trust/telemetry-attestation");
|
|
58
|
+
const telemetry_ledger_io_1 = require("./telemetry-ledger-io");
|
|
59
|
+
const run_paths_1 = require("../core/state/run-paths");
|
|
60
|
+
const run_store_1 = require("./run-store");
|
|
37
61
|
const run_export_1 = require("./run-export");
|
|
38
|
-
const state_1 = require("./state");
|
|
39
|
-
const execution_backend_1 = require("./execution-backend");
|
|
40
62
|
/** Human-facing render of `telemetry verify <run>`. */
|
|
41
63
|
function formatTelemetryVerify(r) {
|
|
42
64
|
const keyUnreadable = r.failedChecks.some((c) => c.code === "telemetry-pubkey-unreadable");
|
|
@@ -58,7 +80,7 @@ function formatTelemetryVerify(r) {
|
|
|
58
80
|
const fails = r.failedChecks.length ? "\n" + r.failedChecks.map((c) => ` ✗ ${c.name} ${c.code || ""}`).join("\n") : "";
|
|
59
81
|
return `telemetry verify ${r.runId}\n${head}\n${tally}${sig}${fails}`;
|
|
60
82
|
}
|
|
61
|
-
/** Human-facing render of `demo tamper
|
|
83
|
+
/** Human-facing render of `demo tamper`. */
|
|
62
84
|
function formatTamperDemo(r) {
|
|
63
85
|
const lines = [];
|
|
64
86
|
lines.push(`cw demo tamper — tamper-evidence proof (hermetic, ${r.trustKey} key)`);
|
|
@@ -78,214 +100,222 @@ function formatTamperDemo(r) {
|
|
|
78
100
|
: "VERDICT: PROOF FAILED ✗ — a tamper went undetected. This is a regression in the integrity guarantee.");
|
|
79
101
|
return lines.join("\n");
|
|
80
102
|
}
|
|
81
|
-
// Three hops with a deliberate mix: two signed/attested, one unattested —
|
|
82
|
-
// ledger-layer tamper can forge the unattested verdict into
|
|
83
|
-
// threat the ledger exists to catch).
|
|
103
|
+
// Three hops with a deliberate mix: two signed/attested, one unattested —
|
|
104
|
+
// so the ledger-layer tamper can forge the unattested verdict into
|
|
105
|
+
// "attested" (the exact threat the ledger exists to catch).
|
|
84
106
|
const HOPS = [
|
|
85
|
-
{ workerId: "w-map", taskId: "map:server-api", promptDigest: (0,
|
|
86
|
-
{ workerId: "w-assess", taskId: "assess:security", promptDigest: (0,
|
|
87
|
-
{ workerId: "w-verdict", taskId: "verdict:synthesis", promptDigest: (0,
|
|
107
|
+
{ workerId: "w-map", taskId: "map:server-api", promptDigest: (0, hash_1.sha256)("map:server-api"), usage: { input_tokens: 2117, output_tokens: 1911 }, attestation: "attested" },
|
|
108
|
+
{ workerId: "w-assess", taskId: "assess:security", promptDigest: (0, hash_1.sha256)("assess:security"), usage: { input_tokens: 1840, output_tokens: 1502 }, attestation: "unattested" },
|
|
109
|
+
{ workerId: "w-verdict", taskId: "verdict:synthesis", promptDigest: (0, hash_1.sha256)("verdict:synthesis"), usage: { input_tokens: 980, output_tokens: 770 }, attestation: "attested" },
|
|
88
110
|
];
|
|
89
111
|
const DEMO_NOW = "2026-01-01T00:00:00.000Z";
|
|
90
112
|
function failingChecks(checks) {
|
|
91
113
|
return checks.filter((c) => !c.pass).map((c) => `${c.name}: ${c.code}`);
|
|
92
114
|
}
|
|
93
|
-
/** Run the full tamper-evidence demonstration in a private tmpdir
|
|
94
|
-
* unless `keepDir` is set).
|
|
95
|
-
*
|
|
115
|
+
/** Run the full tamper-evidence demonstration in a private tmpdir
|
|
116
|
+
* (cleaned up unless `keepDir` is set). Deterministic except for the
|
|
117
|
+
* ephemeral keypair, which never leaves this function. */
|
|
96
118
|
function runTamperDemo(options = {}) {
|
|
97
|
-
const runDir = options.dir ||
|
|
98
|
-
|
|
119
|
+
const runDir = options.dir || fs.mkdtempSync(path.join(os.tmpdir(), "cw-tamper-demo-"));
|
|
120
|
+
fs.mkdirSync(runDir, { recursive: true });
|
|
99
121
|
const runId = "demo-tamper-run";
|
|
100
122
|
// Minimal run shape: the ledger API uses only id + paths.runDir.
|
|
101
123
|
const run = { id: runId, paths: { runDir } };
|
|
102
|
-
const { publicKey, privateKey } =
|
|
124
|
+
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
|
|
103
125
|
const publicKeyPem = publicKey.export({ type: "spki", format: "pem" }).toString();
|
|
104
126
|
const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" }).toString();
|
|
105
|
-
// 1. Build a REAL ledger through the production append API, signing
|
|
106
|
-
// attested hop's usage with the ephemeral key.
|
|
127
|
+
// 1. Build a REAL ledger through the production append API, signing
|
|
128
|
+
// each attested hop's usage with the ephemeral key.
|
|
107
129
|
const signed = [];
|
|
108
130
|
for (const hop of HOPS) {
|
|
109
131
|
const ctx = { runId, taskId: hop.taskId, promptDigest: hop.promptDigest };
|
|
110
132
|
const signature = hop.attestation === "attested" ? (0, telemetry_attestation_1.signTelemetry)(hop.usage, privateKeyPem, ctx) : undefined;
|
|
111
|
-
(0,
|
|
133
|
+
(0, telemetry_ledger_io_1.appendTelemetryAttestation)(run, {
|
|
112
134
|
workerId: hop.workerId,
|
|
113
135
|
taskId: hop.taskId,
|
|
114
136
|
promptDigest: hop.promptDigest,
|
|
115
137
|
reportedUsage: hop.usage,
|
|
116
138
|
usageSignature: signature,
|
|
117
139
|
attestation: hop.attestation,
|
|
118
|
-
now: DEMO_NOW
|
|
140
|
+
now: DEMO_NOW,
|
|
119
141
|
});
|
|
120
142
|
signed.push({ hop, signature });
|
|
121
143
|
}
|
|
122
|
-
// 2. Baseline: the clean ledger verifies, and every signed hop's
|
|
123
|
-
|
|
124
|
-
const
|
|
144
|
+
// 2. Baseline: the clean ledger verifies, and every signed hop's
|
|
145
|
+
// signature is valid.
|
|
146
|
+
const clean = (0, telemetry_ledger_io_1.verifyTelemetryLedger)(run);
|
|
147
|
+
const signaturesValid = signed.filter((s) => s.signature &&
|
|
148
|
+
(0, telemetry_attestation_1.verifyTelemetryAttestation)(s.hop.usage, s.signature, publicKeyPem, { runId, taskId: s.hop.taskId, promptDigest: s.hop.promptDigest }).status === "attested").length;
|
|
125
149
|
const baseline = { ledgerVerified: clean.verified, signaturesValid, records: clean.records.length };
|
|
126
150
|
const layers = [];
|
|
127
|
-
// 3a. LEDGER layer —
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const ledgerFile = (0, telemetry_ledger_1.telemetryLedgerPath)(run);
|
|
134
|
-
const ledgerJson = JSON.parse(node_fs_1.default.readFileSync(ledgerFile, "utf8"));
|
|
151
|
+
// 3a. LEDGER layer — flip record[1]'s verdict "unattested" -> "attested"
|
|
152
|
+
// AND recompute its recordHash to cover the edit, so the per-record
|
|
153
|
+
// digest check passes. The chain still catches it: record[2] was
|
|
154
|
+
// linked to the ORIGINAL record[1] hash, so chain-link[2] breaks.
|
|
155
|
+
const ledgerFile = (0, telemetry_ledger_io_1.telemetryLedgerPath)(run);
|
|
156
|
+
const ledgerJson = JSON.parse(fs.readFileSync(ledgerFile, "utf8"));
|
|
135
157
|
ledgerJson.records[1].attestation = "attested";
|
|
136
158
|
const { recordHash: _stale, ...rest1 } = ledgerJson.records[1];
|
|
137
|
-
ledgerJson.records[1].recordHash = (0,
|
|
138
|
-
|
|
139
|
-
const afterLedger = (0,
|
|
159
|
+
ledgerJson.records[1].recordHash = (0, telemetry_ledger_io_1.computeRecordHash)(rest1); // attacker re-seals the local hash
|
|
160
|
+
fs.writeFileSync(ledgerFile, JSON.stringify(ledgerJson, null, 2));
|
|
161
|
+
const afterLedger = (0, telemetry_ledger_io_1.verifyTelemetryLedger)(run);
|
|
140
162
|
layers.push({
|
|
141
163
|
layer: "ledger",
|
|
142
164
|
tamper: `forged record[1] verdict "unattested" -> "attested" AND recomputed its recordHash to cover the edit`,
|
|
143
165
|
before: { verified: clean.verified, detail: `${clean.records.length} records: chain intact, all hashes recompute` },
|
|
144
166
|
after: { verified: afterLedger.verified, detail: `the hash chain caught it: ${failingChecks(afterLedger.checks).join(", ")}` },
|
|
145
|
-
failures: failingChecks(afterLedger.checks)
|
|
167
|
+
failures: failingChecks(afterLedger.checks),
|
|
146
168
|
});
|
|
147
|
-
// 3b. SIGNATURE layer — inflate hop-0's reported output tokens, keep
|
|
148
|
-
//
|
|
169
|
+
// 3b. SIGNATURE layer — inflate hop-0's reported output tokens, keep
|
|
170
|
+
// the original signature.
|
|
149
171
|
const target = signed[0];
|
|
150
172
|
const inflated = { ...target.hop.usage, output_tokens: target.hop.usage.output_tokens * 10 };
|
|
151
173
|
const sigCheck = (0, telemetry_attestation_1.verifyTelemetryAttestation)(inflated, target.signature, publicKeyPem, {
|
|
152
174
|
runId,
|
|
153
175
|
taskId: target.hop.taskId,
|
|
154
|
-
promptDigest: target.hop.promptDigest
|
|
176
|
+
promptDigest: target.hop.promptDigest,
|
|
155
177
|
});
|
|
156
178
|
const sigCleanCheck = (0, telemetry_attestation_1.verifyTelemetryAttestation)(target.hop.usage, target.signature, publicKeyPem, {
|
|
157
179
|
runId,
|
|
158
180
|
taskId: target.hop.taskId,
|
|
159
|
-
promptDigest: target.hop.promptDigest
|
|
181
|
+
promptDigest: target.hop.promptDigest,
|
|
160
182
|
});
|
|
161
183
|
layers.push({
|
|
162
184
|
layer: "signature",
|
|
163
185
|
tamper: `inflated record[0] reported output_tokens ${target.hop.usage.output_tokens} -> ${inflated.output_tokens}, reused the original ed25519 signature`,
|
|
164
186
|
before: { verified: sigCleanCheck.status === "attested", detail: `signature verifies against the reported usage (${sigCleanCheck.algorithm || "ed25519"})` },
|
|
165
187
|
after: { verified: sigCheck.status === "attested", detail: sigCheck.reason || sigCheck.status },
|
|
166
|
-
failures: sigCheck.status === "attested" ? [] : [`signature: ${sigCheck.reason}`]
|
|
188
|
+
failures: sigCheck.status === "attested" ? [] : [`signature: ${sigCheck.reason}`],
|
|
167
189
|
});
|
|
168
|
-
// 3c. RESULT layer — the
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
// the digest from the result file at verify time. An edited finding hashes to a
|
|
172
|
-
// different digest, so the signature joins neither the 5-field nor the 4-field
|
|
173
|
-
// payload and the verify rejects it. A usage-only signature never covered this.
|
|
190
|
+
// 3c. RESULT layer — the agent's signed FINDING is edited after
|
|
191
|
+
// signing. CW re-derives the digest from the result text at verify
|
|
192
|
+
// time, so the edited finding no longer joins the signature.
|
|
174
193
|
const findingSigned = "Finding: auth bypass in login() — severity HIGH";
|
|
175
194
|
const findingEdited = findingSigned.replace("HIGH", "LOW");
|
|
176
195
|
const resultCtx = { runId, taskId: target.hop.taskId, promptDigest: target.hop.promptDigest };
|
|
177
|
-
const resultSignature = (0, telemetry_attestation_1.signTelemetry)(target.hop.usage, privateKeyPem, { ...resultCtx, resultDigest: (0,
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
// Tamper: CW re-derives the digest from the EDITED finding -> no longer the signed digest.
|
|
181
|
-
const resultCheck = (0, telemetry_attestation_1.verifyTelemetryAttestation)(target.hop.usage, resultSignature, publicKeyPem, { ...resultCtx, resultDigest: (0, execution_backend_1.sha256)(findingEdited) });
|
|
196
|
+
const resultSignature = (0, telemetry_attestation_1.signTelemetry)(target.hop.usage, privateKeyPem, { ...resultCtx, resultDigest: (0, hash_1.sha256)(findingSigned) });
|
|
197
|
+
const resultClean = (0, telemetry_attestation_1.verifyTelemetryAttestation)(target.hop.usage, resultSignature, publicKeyPem, { ...resultCtx, resultDigest: (0, hash_1.sha256)(findingSigned) });
|
|
198
|
+
const resultCheck = (0, telemetry_attestation_1.verifyTelemetryAttestation)(target.hop.usage, resultSignature, publicKeyPem, { ...resultCtx, resultDigest: (0, hash_1.sha256)(findingEdited) });
|
|
182
199
|
layers.push({
|
|
183
200
|
layer: "result",
|
|
184
201
|
tamper: `edited the agent's signed finding "severity HIGH" -> "severity LOW" after it was signed`,
|
|
185
202
|
before: {
|
|
186
203
|
verified: resultClean.status === "attested" && resultClean.coversResult === true,
|
|
187
|
-
detail: `the signed finding verifies — ed25519 binds usage + sha256(result), result-covering
|
|
204
|
+
detail: `the signed finding verifies — ed25519 binds usage + sha256(result), result-covering`,
|
|
188
205
|
},
|
|
189
206
|
after: { verified: resultCheck.status === "attested", detail: resultCheck.reason || resultCheck.status },
|
|
190
|
-
failures: resultCheck.status === "attested" ? [] : [`result: ${resultCheck.reason}`]
|
|
207
|
+
failures: resultCheck.status === "attested" ? [] : [`result: ${resultCheck.reason}`],
|
|
191
208
|
});
|
|
192
209
|
if (!options.keepDir && !options.dir)
|
|
193
|
-
|
|
210
|
+
fs.rmSync(runDir, { recursive: true, force: true });
|
|
194
211
|
const proven = baseline.ledgerVerified &&
|
|
195
212
|
baseline.signaturesValid === signed.filter((s) => s.signature).length &&
|
|
196
213
|
layers.every((l) => l.before.verified && !l.after.verified && l.failures.length > 0);
|
|
197
214
|
return { schemaVersion: 1, runId, workers: HOPS.length, trustKey: "ephemeral-ed25519", baseline, layers, proven };
|
|
198
215
|
}
|
|
199
216
|
function runBundleDemo(options = {}) {
|
|
200
|
-
const workdir = options.dir ||
|
|
201
|
-
|
|
217
|
+
const workdir = options.dir || fs.mkdtempSync(path.join(os.tmpdir(), "cw-bundle-demo-"));
|
|
218
|
+
fs.mkdirSync(workdir, { recursive: true });
|
|
202
219
|
const runId = "demo-bundle-run";
|
|
203
|
-
const runDir =
|
|
204
|
-
const paths = (0,
|
|
205
|
-
(0,
|
|
206
|
-
const { publicKey, privateKey } =
|
|
220
|
+
const runDir = path.join(workdir, ".cw", "runs", runId);
|
|
221
|
+
const paths = (0, run_paths_1.createRunPaths)(runDir);
|
|
222
|
+
(0, run_paths_1.ensureRunDirs)(paths);
|
|
223
|
+
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
|
|
207
224
|
const publicKeyPem = publicKey.export({ type: "spki", format: "pem" }).toString();
|
|
208
225
|
const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" }).toString();
|
|
209
|
-
// Build a real signed ledger + a cited report, the way an attested run
|
|
226
|
+
// Build a real signed ledger + a cited report, the way an attested run
|
|
227
|
+
// would.
|
|
210
228
|
const ledgerRun = { id: runId, paths };
|
|
211
229
|
for (const hop of HOPS) {
|
|
212
230
|
const ctx = { runId, taskId: hop.taskId, promptDigest: hop.promptDigest };
|
|
213
|
-
(0,
|
|
231
|
+
(0, telemetry_ledger_io_1.appendTelemetryAttestation)(ledgerRun, {
|
|
214
232
|
workerId: hop.workerId,
|
|
215
233
|
taskId: hop.taskId,
|
|
216
234
|
promptDigest: hop.promptDigest,
|
|
217
235
|
reportedUsage: hop.usage,
|
|
218
236
|
usageSignature: hop.attestation === "attested" ? (0, telemetry_attestation_1.signTelemetry)(hop.usage, privateKeyPem, ctx) : undefined,
|
|
219
237
|
attestation: hop.attestation,
|
|
220
|
-
now: DEMO_NOW
|
|
238
|
+
now: DEMO_NOW,
|
|
221
239
|
});
|
|
222
240
|
}
|
|
223
|
-
|
|
241
|
+
fs.writeFileSync(path.join(runDir, "report.md"), "# Architecture review\n\nRisk: src/server.js:18 — unauthenticated route.\n", "utf8");
|
|
224
242
|
const attestedCount = HOPS.filter((h) => h.attestation === "attested").length;
|
|
225
243
|
const fullRun = {
|
|
226
|
-
schemaVersion: 1,
|
|
244
|
+
schemaVersion: 1,
|
|
245
|
+
id: runId,
|
|
246
|
+
createdAt: DEMO_NOW,
|
|
247
|
+
updatedAt: DEMO_NOW,
|
|
248
|
+
cwd: workdir,
|
|
227
249
|
workflow: { id: "demo", title: "Demo", summary: "", limits: { maxAgents: 1, maxConcurrentAgents: 1 } },
|
|
228
|
-
inputs: { question: "what are the risks?" },
|
|
229
|
-
|
|
250
|
+
inputs: { question: "what are the risks?" },
|
|
251
|
+
loopStage: "interpret",
|
|
252
|
+
phases: [],
|
|
253
|
+
tasks: [],
|
|
254
|
+
dispatches: [],
|
|
255
|
+
commits: [],
|
|
256
|
+
paths,
|
|
257
|
+
nodes: [],
|
|
258
|
+
contracts: [],
|
|
259
|
+
};
|
|
260
|
+
(0, run_store_1.saveCheckpoint)(fullRun);
|
|
261
|
+
const ledgerFile = (0, telemetry_ledger_io_1.telemetryLedgerPath)(ledgerRun);
|
|
262
|
+
const cleanLedger = fs.readFileSync(ledgerFile, "utf8");
|
|
263
|
+
const exportSealed = (out) => {
|
|
264
|
+
(0, run_export_1.exportRun)(fullRun, out, { trustPublicKey: publicKeyPem });
|
|
230
265
|
};
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
const exportSealed = (out) => { (0, run_export_1.exportRun)(fullRun, out, { trustPublicKey: publicKeyPem }); };
|
|
235
|
-
// Baseline: a clean sealed bundle verifies offline; the embedded key reverifies
|
|
236
|
-
// every signed hop.
|
|
237
|
-
const cleanBundle = node_path_1.default.join(workdir, "clean.cwrun.json");
|
|
266
|
+
// Baseline: a clean sealed bundle verifies offline; the embedded key
|
|
267
|
+
// reverifies every signed hop.
|
|
268
|
+
const cleanBundle = path.join(workdir, "clean.cwrun.json");
|
|
238
269
|
exportSealed(cleanBundle);
|
|
239
270
|
const clean = (0, run_export_1.verifyReportBundle)(cleanBundle);
|
|
240
271
|
const baseline = { ok: clean.ok, telemetryVerified: clean.telemetryVerified, signaturesReverified: clean.signaturesReverified };
|
|
241
272
|
const layers = [];
|
|
242
|
-
// CHAIN forgery: flip record[1]'s verdict and reseal its recordHash;
|
|
243
|
-
// prevHash still points at the original hash, so the chain
|
|
244
|
-
// every archive file digest
|
|
245
|
-
// This is exactly what inspect-archive alone cannot catch.
|
|
273
|
+
// CHAIN forgery: flip record[1]'s verdict and reseal its recordHash;
|
|
274
|
+
// record[2]'s prevHash still points at the original hash, so the chain
|
|
275
|
+
// breaks — even though every archive file digest is valid.
|
|
246
276
|
{
|
|
247
277
|
const j = JSON.parse(cleanLedger);
|
|
248
278
|
j.records[1].attestation = "attested";
|
|
249
279
|
const { recordHash: _drop, ...rest } = j.records[1];
|
|
250
|
-
j.records[1].recordHash = (0,
|
|
251
|
-
|
|
252
|
-
const forged =
|
|
280
|
+
j.records[1].recordHash = (0, telemetry_ledger_io_1.computeRecordHash)(rest);
|
|
281
|
+
fs.writeFileSync(ledgerFile, JSON.stringify(j, null, 2));
|
|
282
|
+
const forged = path.join(workdir, "forged-chain.cwrun.json");
|
|
253
283
|
exportSealed(forged);
|
|
254
284
|
const after = (0, run_export_1.verifyReportBundle)(forged);
|
|
255
|
-
|
|
285
|
+
fs.writeFileSync(ledgerFile, cleanLedger);
|
|
256
286
|
layers.push({
|
|
257
287
|
layer: "chain",
|
|
258
288
|
tamper: `forged record[1] verdict "unattested" -> "attested" and resealed its recordHash; the archive's own file digests stay valid`,
|
|
259
289
|
before: { ok: clean.ok, detail: `${clean.signaturesReverified} signed hop(s) reverify; chain intact` },
|
|
260
290
|
after: { ok: after.ok, detail: after.telemetryVerified ? "telemetry chain still verified (UNDETECTED!)" : "the embedded hash chain broke at the next record" },
|
|
261
|
-
failures: after.ok ? [] : after.failedChecks.map((c) => `${c.name}: ${c.code}`)
|
|
291
|
+
failures: after.ok ? [] : after.failedChecks.map((c) => `${c.name}: ${c.code}`),
|
|
262
292
|
});
|
|
263
293
|
}
|
|
264
|
-
// SIGNATURE forgery: inflate the last attested hop's reported tokens
|
|
265
|
-
// usage digest + recordHash so the chain AND archive
|
|
266
|
-
//
|
|
294
|
+
// SIGNATURE forgery: inflate the last attested hop's reported tokens
|
|
295
|
+
// and reseal its usage digest + recordHash so the chain AND archive
|
|
296
|
+
// digests still verify; only the ed25519 signature no longer matches.
|
|
267
297
|
{
|
|
268
298
|
const j = JSON.parse(cleanLedger);
|
|
269
299
|
const idx = j.records.length - 1;
|
|
270
300
|
j.records[idx].reportedUsage = { ...j.records[idx].reportedUsage, output_tokens: j.records[idx].reportedUsage.output_tokens * 10 };
|
|
271
|
-
j.records[idx].reportedUsageDigest = (0,
|
|
301
|
+
j.records[idx].reportedUsageDigest = (0, telemetry_ledger_io_1.reportedUsageDigest)(j.records[idx].reportedUsage);
|
|
272
302
|
const { recordHash: _drop, ...rest } = j.records[idx];
|
|
273
|
-
j.records[idx].recordHash = (0,
|
|
274
|
-
|
|
275
|
-
const forged =
|
|
303
|
+
j.records[idx].recordHash = (0, telemetry_ledger_io_1.computeRecordHash)(rest);
|
|
304
|
+
fs.writeFileSync(ledgerFile, JSON.stringify(j, null, 2));
|
|
305
|
+
const forged = path.join(workdir, "forged-sig.cwrun.json");
|
|
276
306
|
exportSealed(forged);
|
|
277
307
|
const after = (0, run_export_1.verifyReportBundle)(forged);
|
|
278
|
-
|
|
308
|
+
fs.writeFileSync(ledgerFile, cleanLedger);
|
|
279
309
|
layers.push({
|
|
280
310
|
layer: "signature",
|
|
281
311
|
tamper: `inflated the last attested hop's output_tokens 10x and resealed its digest + recordHash; the chain stays valid`,
|
|
282
312
|
before: { ok: clean.ok, detail: `the embedded public key reverifies the original signature` },
|
|
283
313
|
after: { ok: after.ok, detail: after.signaturesFailed > 0 ? `${after.signaturesFailed} signature(s) failed ed25519 reverify` : "signature still verified (UNDETECTED!)" },
|
|
284
|
-
failures: after.ok ? [] : after.failedChecks.map((c) => `${c.name}: ${c.code}`)
|
|
314
|
+
failures: after.ok ? [] : after.failedChecks.map((c) => `${c.name}: ${c.code}`),
|
|
285
315
|
});
|
|
286
316
|
}
|
|
287
317
|
if (!options.keepDir && !options.dir)
|
|
288
|
-
|
|
318
|
+
fs.rmSync(workdir, { recursive: true, force: true });
|
|
289
319
|
const proven = baseline.ok &&
|
|
290
320
|
baseline.telemetryVerified &&
|
|
291
321
|
baseline.signaturesReverified === attestedCount &&
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// shell/telemetry-ledger-io.ts — telemetry ledger disk IO: path
|
|
3
|
+
// resolution, absent-vs-corrupt load, durable append. The pure verify
|
|
4
|
+
// math lives in core/trust/telemetry-ledger.ts.
|
|
5
|
+
//
|
|
6
|
+
// MILESTONE 8. Byte-exact port of the old build's src/telemetry-ledger.ts
|
|
7
|
+
// IO-touching half. CRITICAL invariant (v2/PLAN.md byte-compat item 12):
|
|
8
|
+
// an ABSENT telemetry.json is an empty, clean-verifying chain
|
|
9
|
+
// (`present:false`); a PRESENT but unparseable one is CORRUPT — reads
|
|
10
|
+
// report `telemetry-ledger-corrupt`, and append THROWS
|
|
11
|
+
// `TelemetryLedgerCorruptError`. It must never re-genesis over a
|
|
12
|
+
// poisoned file.
|
|
13
|
+
//
|
|
14
|
+
// Evidence: SPEC/ledger-trust.md "Absent vs corrupt telemetry ledger",
|
|
15
|
+
// invariant 3; plugins/cool-workflow/src/telemetry-ledger.ts:25-72,133-161.
|
|
16
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
+
}) : function(o, v) {
|
|
30
|
+
o["default"] = v;
|
|
31
|
+
});
|
|
32
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
33
|
+
var ownKeys = function(o) {
|
|
34
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
35
|
+
var ar = [];
|
|
36
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
37
|
+
return ar;
|
|
38
|
+
};
|
|
39
|
+
return ownKeys(o);
|
|
40
|
+
};
|
|
41
|
+
return function (mod) {
|
|
42
|
+
if (mod && mod.__esModule) return mod;
|
|
43
|
+
var result = {};
|
|
44
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
45
|
+
__setModuleDefault(result, mod);
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
})();
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.reportedUsageDigest = exports.computeRecordHash = exports.TELEMETRY_LEDGER_SCHEMA_VERSION = exports.TelemetryLedgerCorruptError = void 0;
|
|
51
|
+
exports.telemetryLedgerPath = telemetryLedgerPath;
|
|
52
|
+
exports.loadTelemetryLedger = loadTelemetryLedger;
|
|
53
|
+
exports.appendTelemetryAttestation = appendTelemetryAttestation;
|
|
54
|
+
exports.verifyTelemetryLedger = verifyTelemetryLedger;
|
|
55
|
+
const fs = __importStar(require("node:fs"));
|
|
56
|
+
const path = __importStar(require("node:path"));
|
|
57
|
+
const fs_atomic_1 = require("./fs-atomic");
|
|
58
|
+
const telemetry_ledger_1 = require("../core/trust/telemetry-ledger");
|
|
59
|
+
Object.defineProperty(exports, "computeRecordHash", { enumerable: true, get: function () { return telemetry_ledger_1.computeRecordHash; } });
|
|
60
|
+
Object.defineProperty(exports, "reportedUsageDigest", { enumerable: true, get: function () { return telemetry_ledger_1.reportedUsageDigest; } });
|
|
61
|
+
Object.defineProperty(exports, "TelemetryLedgerCorruptError", { enumerable: true, get: function () { return telemetry_ledger_1.TelemetryLedgerCorruptError; } });
|
|
62
|
+
Object.defineProperty(exports, "TELEMETRY_LEDGER_SCHEMA_VERSION", { enumerable: true, get: function () { return telemetry_ledger_1.TELEMETRY_LEDGER_SCHEMA_VERSION; } });
|
|
63
|
+
function telemetryLedgerPath(run) {
|
|
64
|
+
return path.join(run.paths.runDir, "telemetry.json");
|
|
65
|
+
}
|
|
66
|
+
/** Read the ledger, DISTINGUISHING absent (never written -> empty chain,
|
|
67
|
+
* fine) from corrupt (exists but unparseable/wrong shape -> fail
|
|
68
|
+
* closed). */
|
|
69
|
+
function readTelemetryLedgerState(run) {
|
|
70
|
+
const file = telemetryLedgerPath(run);
|
|
71
|
+
if (!fs.existsSync(file))
|
|
72
|
+
return { status: "absent", ledger: { schemaVersion: 1, runId: run.id, records: [] } };
|
|
73
|
+
let parsed;
|
|
74
|
+
try {
|
|
75
|
+
parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return { status: "corrupt", file };
|
|
79
|
+
}
|
|
80
|
+
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.records)) {
|
|
81
|
+
return { status: "corrupt", file };
|
|
82
|
+
}
|
|
83
|
+
return { status: "ok", ledger: { schemaVersion: 1, runId: run.id, records: parsed.records } };
|
|
84
|
+
}
|
|
85
|
+
/** Load the ledger for read/append. Absent -> empty chain. Corrupt ->
|
|
86
|
+
* THROWS, so an append can never silently re-genesis on a poisoned/
|
|
87
|
+
* edited file. */
|
|
88
|
+
function loadTelemetryLedger(run) {
|
|
89
|
+
const state = readTelemetryLedgerState(run);
|
|
90
|
+
if (state.status === "corrupt")
|
|
91
|
+
throw new telemetry_ledger_1.TelemetryLedgerCorruptError(state.file);
|
|
92
|
+
return state.ledger;
|
|
93
|
+
}
|
|
94
|
+
/** Append one attestation record DURABLY to the append-only chain,
|
|
95
|
+
* linking it to the prior record (or genesis). Returns the committed
|
|
96
|
+
* record. Throws `TelemetryLedgerCorruptError` if the file on disk is
|
|
97
|
+
* present but unparseable — never silently re-genesis. */
|
|
98
|
+
function appendTelemetryAttestation(run, input) {
|
|
99
|
+
const ledger = loadTelemetryLedger(run);
|
|
100
|
+
const now = input.now || new Date().toISOString();
|
|
101
|
+
const prevHash = ledger.records.length ? ledger.records[ledger.records.length - 1].recordHash : (0, telemetry_ledger_1.genesisPrevHash)(run.id);
|
|
102
|
+
const base = {
|
|
103
|
+
schemaVersion: 1,
|
|
104
|
+
runId: run.id,
|
|
105
|
+
recordId: (0, telemetry_ledger_1.recordId)(ledger.records.length + 1),
|
|
106
|
+
recordedAt: now,
|
|
107
|
+
workerId: input.workerId,
|
|
108
|
+
taskId: input.taskId,
|
|
109
|
+
promptDigest: input.promptDigest,
|
|
110
|
+
reportedUsageDigest: (0, telemetry_ledger_1.reportedUsageDigest)(input.reportedUsage),
|
|
111
|
+
...(input.reportedUsage ? { reportedUsage: input.reportedUsage } : {}),
|
|
112
|
+
usageSignature: input.usageSignature,
|
|
113
|
+
...(input.resultDigest ? { resultDigest: input.resultDigest } : {}),
|
|
114
|
+
attestation: input.attestation,
|
|
115
|
+
attestationReason: input.attestationReason,
|
|
116
|
+
prevHash,
|
|
117
|
+
};
|
|
118
|
+
const record = { ...base, recordHash: (0, telemetry_ledger_1.computeRecordHash)(base) };
|
|
119
|
+
ledger.records.push(record);
|
|
120
|
+
(0, fs_atomic_1.writeJson)(telemetryLedgerPath(run), ledger, { durable: true });
|
|
121
|
+
return record;
|
|
122
|
+
}
|
|
123
|
+
/** Re-prove the whole telemetry chain for a run, reading from disk.
|
|
124
|
+
* Absent -> present:false/verified:true (nothing to prove). Corrupt ->
|
|
125
|
+
* present:true/verified:false with the `telemetry-ledger-corrupt`
|
|
126
|
+
* check, never thrown. */
|
|
127
|
+
function verifyTelemetryLedger(run) {
|
|
128
|
+
const state = readTelemetryLedgerState(run);
|
|
129
|
+
if (state.status === "corrupt")
|
|
130
|
+
return (0, telemetry_ledger_1.corruptTelemetryLedgerVerification)();
|
|
131
|
+
return (0, telemetry_ledger_1.verifyTelemetryLedgerRecords)(run.id, state.ledger.records);
|
|
132
|
+
}
|