cool-workflow 0.1.78
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 +20 -0
- package/.codex-plugin/mcp.json +10 -0
- package/.codex-plugin/plugin.json +38 -0
- package/.mcp.json +10 -0
- package/LICENSE +24 -0
- package/README.md +638 -0
- package/apps/architecture-review/app.json +51 -0
- package/apps/architecture-review/workflow.js +116 -0
- package/apps/end-to-end-golden-path/app.json +30 -0
- package/apps/end-to-end-golden-path/workflow.js +33 -0
- package/apps/pr-review-fix-ci/app.json +59 -0
- package/apps/pr-review-fix-ci/workflow.js +90 -0
- package/apps/release-cut/app.json +54 -0
- package/apps/release-cut/workflow.js +82 -0
- package/apps/research-synthesis/app.json +50 -0
- package/apps/research-synthesis/workflow.js +76 -0
- package/apps/workflow-app-framework-demo/app.json +29 -0
- package/apps/workflow-app-framework-demo/workflow.js +44 -0
- package/dist/agent-config.js +223 -0
- package/dist/candidate-scoring.js +715 -0
- package/dist/capability-core.js +630 -0
- package/dist/capability-dispatcher.js +86 -0
- package/dist/capability-registry.js +523 -0
- package/dist/cli.js +1276 -0
- package/dist/collaboration.js +727 -0
- package/dist/commit.js +570 -0
- package/dist/contract-migration.js +234 -0
- package/dist/coordinator.js +1163 -0
- package/dist/daemon.js +44 -0
- package/dist/dispatch.js +201 -0
- package/dist/drive.js +503 -0
- package/dist/error-feedback.js +415 -0
- package/dist/evidence-grounding.js +179 -0
- package/dist/evidence-reasoning.js +733 -0
- package/dist/execution-backend.js +1279 -0
- package/dist/harness.js +61 -0
- package/dist/mcp-server.js +1615 -0
- package/dist/multi-agent-eval.js +857 -0
- package/dist/multi-agent-host.js +764 -0
- package/dist/multi-agent-operator-ux.js +537 -0
- package/dist/multi-agent-trust.js +366 -0
- package/dist/multi-agent.js +1173 -0
- package/dist/node-snapshot.js +270 -0
- package/dist/observability.js +922 -0
- package/dist/operator-ux.js +971 -0
- package/dist/orchestrator/audit-operations.js +182 -0
- package/dist/orchestrator/candidate-operations.js +117 -0
- package/dist/orchestrator/cli-options.js +288 -0
- package/dist/orchestrator/collaboration-operations.js +86 -0
- package/dist/orchestrator/feedback-operations.js +81 -0
- package/dist/orchestrator/host-operations.js +78 -0
- package/dist/orchestrator/lifecycle-operations.js +462 -0
- package/dist/orchestrator/migration-operations.js +44 -0
- package/dist/orchestrator/multi-agent-operations.js +362 -0
- package/dist/orchestrator/report.js +369 -0
- package/dist/orchestrator/topology-operations.js +84 -0
- package/dist/orchestrator.js +874 -0
- package/dist/pipeline-contract.js +92 -0
- package/dist/pipeline-runner.js +285 -0
- package/dist/reclamation.js +882 -0
- package/dist/result-normalize.js +194 -0
- package/dist/run-export.js +64 -0
- package/dist/run-registry.js +1347 -0
- package/dist/run-state-schema.js +67 -0
- package/dist/sandbox-profile.js +471 -0
- package/dist/scheduler.js +266 -0
- package/dist/scheduling.js +184 -0
- package/dist/schema-validate.js +98 -0
- package/dist/state-explosion.js +1213 -0
- package/dist/state-migrations.js +463 -0
- package/dist/state-node.js +301 -0
- package/dist/state.js +308 -0
- package/dist/telemetry-attestation.js +156 -0
- package/dist/telemetry-ledger.js +145 -0
- package/dist/topology.js +527 -0
- package/dist/triggers.js +159 -0
- package/dist/trust-audit.js +475 -0
- package/dist/types/blackboard.js +2 -0
- package/dist/types/boundary.js +29 -0
- package/dist/types/candidate.js +2 -0
- package/dist/types/collaboration.js +2 -0
- package/dist/types/core.js +2 -0
- package/dist/types/drive.js +10 -0
- package/dist/types/error-feedback.js +2 -0
- package/dist/types/evidence-reasoning.js +2 -0
- package/dist/types/execution-backend.js +2 -0
- package/dist/types/multi-agent.js +2 -0
- package/dist/types/observability.js +2 -0
- package/dist/types/pipeline.js +2 -0
- package/dist/types/reclamation.js +8 -0
- package/dist/types/result.js +2 -0
- package/dist/types/run-registry.js +2 -0
- package/dist/types/run.js +2 -0
- package/dist/types/sandbox.js +2 -0
- package/dist/types/schedule.js +2 -0
- package/dist/types/state-node.js +2 -0
- package/dist/types/topology.js +2 -0
- package/dist/types/trust.js +2 -0
- package/dist/types/workbench.js +2 -0
- package/dist/types/worker.js +2 -0
- package/dist/types/workflow-app.js +2 -0
- package/dist/types.js +43 -0
- package/dist/verifier-registry.js +46 -0
- package/dist/verifier.js +78 -0
- package/dist/version.js +8 -0
- package/dist/workbench-host.js +172 -0
- package/dist/workbench.js +190 -0
- package/dist/worker-isolation.js +1028 -0
- package/dist/workflow-api.js +98 -0
- package/dist/workflow-app-framework.js +626 -0
- package/docs/agent-delegation-drive.7.md +190 -0
- package/docs/agent-framework.md +176 -0
- package/docs/candidate-scoring.7.md +106 -0
- package/docs/canonical-workflow-apps.7.md +137 -0
- package/docs/capability-topology-registry.7.md +168 -0
- package/docs/cli-mcp-parity.7.md +373 -0
- package/docs/contract-migration-tooling.7.md +123 -0
- package/docs/control-plane-scheduling.7.md +110 -0
- package/docs/coordinator-blackboard.7.md +183 -0
- package/docs/dogfood/architecture-review-cool-workflow.md +16 -0
- package/docs/dogfood-one-real-repo.7.md +168 -0
- package/docs/durable-state-and-locking.7.md +107 -0
- package/docs/end-to-end-golden-path.7.md +117 -0
- package/docs/error-feedback.7.md +153 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +270 -0
- package/docs/execution-backends.7.md +300 -0
- package/docs/getting-started.md +99 -0
- package/docs/index.md +41 -0
- package/docs/mcp-app-surface.7.md +235 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +265 -0
- package/docs/multi-agent-eval-replay-harness.7.md +302 -0
- package/docs/multi-agent-operator-ux.7.md +314 -0
- package/docs/multi-agent-runtime-core.7.md +231 -0
- package/docs/multi-agent-topologies.7.md +103 -0
- package/docs/multi-agent-trust-policy-audit.7.md +154 -0
- package/docs/node-snapshot-diff-replay.7.md +135 -0
- package/docs/observability-cost-accounting.7.md +194 -0
- package/docs/operator-ux.7.md +180 -0
- package/docs/pipeline-runner.7.md +136 -0
- package/docs/project-index.md +261 -0
- package/docs/real-execution-backends.7.md +142 -0
- package/docs/release-and-migration.7.md +280 -0
- package/docs/release-tooling.7.md +159 -0
- package/docs/routines.md +48 -0
- package/docs/run-registry-control-plane.7.md +312 -0
- package/docs/run-retention-reclamation.7.md +191 -0
- package/docs/sandbox-profiles.7.md +137 -0
- package/docs/scheduled-tasks.md +80 -0
- package/docs/security-trust-hardening.7.md +117 -0
- package/docs/state-explosion-management.7.md +264 -0
- package/docs/state-node.7.md +96 -0
- package/docs/team-collaboration.7.md +207 -0
- package/docs/unix-principles.md +192 -0
- package/docs/verifier-gated-commit.7.md +140 -0
- package/docs/web-desktop-workbench.7.md +215 -0
- package/docs/worker-isolation.7.md +167 -0
- package/docs/workflow-app-framework.7.md +274 -0
- package/manifest/README.md +43 -0
- package/manifest/plugin.manifest.json +316 -0
- package/manifest/pricing.policy.json +14 -0
- package/package.json +79 -0
- package/scripts/agents/claude-p-agent.js +104 -0
- package/scripts/agents/claude-p-agent.sh +9 -0
- package/scripts/agents/cw-attest-keygen.js +55 -0
- package/scripts/agents/cw-attest-wrap.js +143 -0
- package/scripts/block-unapproved-tag.sh +39 -0
- package/scripts/bump-version.js +249 -0
- package/scripts/canonical-apps.js +171 -0
- package/scripts/cw.js +4 -0
- package/scripts/dist-drift-check.js +79 -0
- package/scripts/dogfood-architecture-review.js +237 -0
- package/scripts/dogfood-release.js +624 -0
- package/scripts/forward-ref-docs.js +73 -0
- package/scripts/gen-manifests.js +232 -0
- package/scripts/golden-path.js +300 -0
- package/scripts/mcp-server.js +4 -0
- package/scripts/new-feature.js +121 -0
- package/scripts/parity-check.js +213 -0
- package/scripts/release-check.js +118 -0
- package/scripts/release-flow.js +272 -0
- package/scripts/release-gate.sh +85 -0
- package/scripts/sync-project-index.js +387 -0
- package/scripts/validate-run-state-schema.js +126 -0
- package/scripts/verify-container-selfref.js +64 -0
- package/scripts/version-sync-check.js +237 -0
- package/skills/cool-workflow/SKILL.md +162 -0
- package/skills/cool-workflow/references/commands.md +282 -0
- package/tsconfig.json +16 -0
- package/ui/workbench/app.css +76 -0
- package/ui/workbench/app.js +159 -0
- package/ui/workbench/index.html +32 -0
- package/workflows/architecture-review.workflow.js +84 -0
- package/workflows/research-synthesis.workflow.js +47 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Telemetry attestation (Track 1) — make "auditable" a FACT, not a claim.
|
|
3
|
+
//
|
|
4
|
+
// The agent self-reports its token usage on stdout (parseAgentReport). A control
|
|
5
|
+
// plane that records that number verbatim is recording a CLAIM: a tampered or
|
|
6
|
+
// fabricated usage survives untouched. This module is the verify gate that turns
|
|
7
|
+
// the claim into an attestation:
|
|
8
|
+
//
|
|
9
|
+
// - The EXECUTOR signs a canonical payload binding {usage, runId, taskId,
|
|
10
|
+
// promptDigest} with its private key (ed25519). The binding context is what
|
|
11
|
+
// stops one task's signature being replayed onto another.
|
|
12
|
+
// - CW VERIFIES that signature against an operator-provisioned PUBLIC key.
|
|
13
|
+
// CW holds ONLY the public key — it can verify, but can neither forge a
|
|
14
|
+
// signature nor (the red line) call a model to measure usage itself. A
|
|
15
|
+
// third-party auditor with the same public key can re-verify independently.
|
|
16
|
+
//
|
|
17
|
+
// HONEST CEILING [load-bearing]: a signature proves the usage came from the
|
|
18
|
+
// keyholder and was not tampered in transit — NON-REPUDIABLE ATTRIBUTION, not
|
|
19
|
+
// ground-truth measurement. A dishonest keyholder can still sign a lie, but the
|
|
20
|
+
// lie is now cryptographically bound to its signer. That is strictly stronger
|
|
21
|
+
// than self-signed sha256 (which any party can recompute) and is the most a
|
|
22
|
+
// delegating control-plane can claim without measuring (which it must not).
|
|
23
|
+
//
|
|
24
|
+
// Default is honest: no signature ⇒ `unattested`, no usage ⇒ `absent`. Usage is
|
|
25
|
+
// NEVER silently recorded as trusted.
|
|
26
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
27
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
|
+
};
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.stableStringify = stableStringify;
|
|
31
|
+
exports.canonicalTelemetryPayload = canonicalTelemetryPayload;
|
|
32
|
+
exports.normalizeReportedUsage = normalizeReportedUsage;
|
|
33
|
+
exports.verifyTelemetryAttestation = verifyTelemetryAttestation;
|
|
34
|
+
exports.resolveTrustPublicKey = resolveTrustPublicKey;
|
|
35
|
+
exports.signTelemetry = signTelemetry;
|
|
36
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
37
|
+
/** Deterministic, key-sorted JSON so signer and verifier hash byte-identical
|
|
38
|
+
* input regardless of object key order. */
|
|
39
|
+
function stableStringify(value) {
|
|
40
|
+
if (value === null || typeof value !== "object")
|
|
41
|
+
return JSON.stringify(value) ?? "null";
|
|
42
|
+
if (Array.isArray(value))
|
|
43
|
+
return `[${value.map(stableStringify).join(",")}]`;
|
|
44
|
+
const entries = Object.keys(value)
|
|
45
|
+
.sort()
|
|
46
|
+
.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`);
|
|
47
|
+
return `{${entries.join(",")}}`;
|
|
48
|
+
}
|
|
49
|
+
/** The exact bytes the executor signs and CW verifies. */
|
|
50
|
+
function canonicalTelemetryPayload(usage, ctx) {
|
|
51
|
+
return stableStringify({
|
|
52
|
+
usage: usage ?? null,
|
|
53
|
+
runId: ctx.runId,
|
|
54
|
+
taskId: ctx.taskId,
|
|
55
|
+
promptDigest: ctx.promptDigest
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function messageOf(error) {
|
|
59
|
+
return error instanceof Error ? error.message : String(error);
|
|
60
|
+
}
|
|
61
|
+
function numberAt(usage, ...keys) {
|
|
62
|
+
for (const key of keys) {
|
|
63
|
+
const value = usage[key];
|
|
64
|
+
const num = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
65
|
+
if (Number.isFinite(num))
|
|
66
|
+
return num;
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
/** Map the agent's free-form usage report onto UsageRecord token buckets, tolerating
|
|
71
|
+
* snake_case / camelCase variants (input_tokens vs inputTokens, etc.). CW never
|
|
72
|
+
* invents a number — an unreported bucket stays undefined, never zero. */
|
|
73
|
+
function normalizeReportedUsage(usage) {
|
|
74
|
+
if (!usage)
|
|
75
|
+
return {};
|
|
76
|
+
return {
|
|
77
|
+
inputTokens: numberAt(usage, "inputTokens", "input_tokens", "promptTokens", "prompt_tokens"),
|
|
78
|
+
outputTokens: numberAt(usage, "outputTokens", "output_tokens", "completionTokens", "completion_tokens"),
|
|
79
|
+
cacheReadTokens: numberAt(usage, "cacheReadTokens", "cache_read_tokens", "cacheReadInputTokens", "cache_read_input_tokens"),
|
|
80
|
+
cacheWriteTokens: numberAt(usage, "cacheWriteTokens", "cache_write_tokens", "cacheCreationInputTokens", "cache_creation_input_tokens"),
|
|
81
|
+
totalTokens: numberAt(usage, "totalTokens", "total_tokens")
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** Verify the agent's signed usage against the operator-provisioned public key.
|
|
85
|
+
* Pure + deterministic (no clock, no env). Returns a status, never throws. */
|
|
86
|
+
function verifyTelemetryAttestation(usage, signatureB64, trustPublicKeyPem, ctx) {
|
|
87
|
+
if (!usage || Object.keys(usage).length === 0) {
|
|
88
|
+
return { status: "absent", reason: "agent reported no usage" };
|
|
89
|
+
}
|
|
90
|
+
if (!signatureB64) {
|
|
91
|
+
return { status: "unattested", reason: "reported usage carries no signature" };
|
|
92
|
+
}
|
|
93
|
+
if (!trustPublicKeyPem) {
|
|
94
|
+
return { status: "unattested", reason: "no trust key configured (set agent attestPublicKey / CW_AGENT_ATTEST_PUBKEY)" };
|
|
95
|
+
}
|
|
96
|
+
let publicKey;
|
|
97
|
+
try {
|
|
98
|
+
publicKey = node_crypto_1.default.createPublicKey(trustPublicKeyPem);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
return { status: "unattested", reason: `trust key unreadable: ${messageOf(error)}` };
|
|
102
|
+
}
|
|
103
|
+
let signature;
|
|
104
|
+
try {
|
|
105
|
+
signature = Buffer.from(signatureB64, "base64");
|
|
106
|
+
if (signature.length === 0)
|
|
107
|
+
return { status: "unattested", reason: "signature is empty" };
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return { status: "unattested", reason: "signature is not valid base64" };
|
|
111
|
+
}
|
|
112
|
+
const payload = Buffer.from(canonicalTelemetryPayload(usage, ctx), "utf8");
|
|
113
|
+
let ok = false;
|
|
114
|
+
try {
|
|
115
|
+
ok = node_crypto_1.default.verify(null, payload, publicKey, signature);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
return { status: "unattested", reason: `verification error: ${messageOf(error)}` };
|
|
119
|
+
}
|
|
120
|
+
return ok
|
|
121
|
+
? { status: "attested", algorithm: "ed25519" }
|
|
122
|
+
: { status: "unattested", reason: "signature does not match reported usage (tampered, replayed, or wrong key)" };
|
|
123
|
+
}
|
|
124
|
+
/** Resolve a trust key from a config value that is EITHER an inline PEM or a path
|
|
125
|
+
* to a `.pem` file. Returns undefined when absent/unreadable (⇒ `unattested`,
|
|
126
|
+
* never a hard throw). CW only ever loads a PUBLIC key here. */
|
|
127
|
+
function resolveTrustPublicKey(value) {
|
|
128
|
+
if (!value)
|
|
129
|
+
return undefined;
|
|
130
|
+
const trimmed = value.trim();
|
|
131
|
+
if (!trimmed)
|
|
132
|
+
return undefined;
|
|
133
|
+
if (trimmed.includes("BEGIN") && trimmed.includes("KEY"))
|
|
134
|
+
return trimmed;
|
|
135
|
+
try {
|
|
136
|
+
// Lazy require so a bundle that never resolves a key path pays no fs cost.
|
|
137
|
+
const fs = require("node:fs");
|
|
138
|
+
if (fs.existsSync(trimmed))
|
|
139
|
+
return fs.readFileSync(trimmed, "utf8");
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
/* fall through to undefined */
|
|
143
|
+
}
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// EXECUTOR-SIDE HELPER — the CW RUNTIME NEVER CALLS THIS.
|
|
148
|
+
// Provided for signing wrappers around `claude -p`/endpoints and for tests. It
|
|
149
|
+
// touches a private key, never a model, so it does not cross the red line; it is
|
|
150
|
+
// kept here only so signer and verifier share one canonicalization.
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
function signTelemetry(usage, privateKeyPem, ctx) {
|
|
153
|
+
const payload = Buffer.from(canonicalTelemetryPayload(usage, ctx), "utf8");
|
|
154
|
+
const key = node_crypto_1.default.createPrivateKey(privateKeyPem);
|
|
155
|
+
return node_crypto_1.default.sign(null, payload, key).toString("base64");
|
|
156
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
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.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
|
+
/** Load the ledger; fail closed to an empty chain on a malformed overlay (a
|
|
38
|
+
* corrupt file must never brick the run — and an empty chain verifies as such). */
|
|
39
|
+
function loadTelemetryLedger(run) {
|
|
40
|
+
const file = telemetryLedgerPath(run);
|
|
41
|
+
if (!node_fs_1.default.existsSync(file))
|
|
42
|
+
return { schemaVersion: 1, runId: run.id, records: [] };
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
|
|
45
|
+
return { schemaVersion: 1, runId: run.id, records: Array.isArray(parsed.records) ? parsed.records : [] };
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return { schemaVersion: 1, runId: run.id, records: [] };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** genesis prevHash for a run's chain (no prior record). */
|
|
52
|
+
function genesisPrevHash(runId) {
|
|
53
|
+
return (0, execution_backend_1.sha256)(`cw-telemetry-ledger:${runId}`);
|
|
54
|
+
}
|
|
55
|
+
/** The canonical bytes a recordHash binds — every field except recordHash itself.
|
|
56
|
+
* Recomputed independently by verifyTelemetryLedger. */
|
|
57
|
+
function recordHashInput(record) {
|
|
58
|
+
return (0, telemetry_attestation_1.stableStringify)({
|
|
59
|
+
schemaVersion: record.schemaVersion,
|
|
60
|
+
runId: record.runId,
|
|
61
|
+
recordId: record.recordId,
|
|
62
|
+
recordedAt: record.recordedAt,
|
|
63
|
+
workerId: record.workerId,
|
|
64
|
+
taskId: record.taskId,
|
|
65
|
+
promptDigest: record.promptDigest,
|
|
66
|
+
reportedUsageDigest: record.reportedUsageDigest,
|
|
67
|
+
usageSignature: record.usageSignature || null,
|
|
68
|
+
attestation: record.attestation,
|
|
69
|
+
attestationReason: record.attestationReason || null,
|
|
70
|
+
prevHash: record.prevHash
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function computeRecordHash(record) {
|
|
74
|
+
return (0, execution_backend_1.sha256)(recordHashInput(record));
|
|
75
|
+
}
|
|
76
|
+
/** sha256 of the canonical reported usage (compact, chainable). Absent usage gets
|
|
77
|
+
* the digest of `null`, so "the agent reported nothing" is itself bound. */
|
|
78
|
+
function reportedUsageDigest(usage) {
|
|
79
|
+
return (0, execution_backend_1.sha256)((0, telemetry_attestation_1.stableStringify)(usage ?? null));
|
|
80
|
+
}
|
|
81
|
+
let recordCounter = 0;
|
|
82
|
+
function recordId(now) {
|
|
83
|
+
recordCounter += 1;
|
|
84
|
+
const stamp = now.replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
85
|
+
return `tel-${stamp}-${String(recordCounter).padStart(3, "0")}`;
|
|
86
|
+
}
|
|
87
|
+
/** Append one attestation record DURABLY to the append-only chain, linking it to
|
|
88
|
+
* the prior record (or genesis). Returns the committed record. */
|
|
89
|
+
function appendTelemetryAttestation(run, input) {
|
|
90
|
+
const ledger = loadTelemetryLedger(run);
|
|
91
|
+
const now = input.now || new Date().toISOString();
|
|
92
|
+
const prevHash = ledger.records.length ? ledger.records[ledger.records.length - 1].recordHash : genesisPrevHash(run.id);
|
|
93
|
+
const base = {
|
|
94
|
+
schemaVersion: 1,
|
|
95
|
+
runId: run.id,
|
|
96
|
+
recordId: recordId(now),
|
|
97
|
+
recordedAt: now,
|
|
98
|
+
workerId: input.workerId,
|
|
99
|
+
taskId: input.taskId,
|
|
100
|
+
promptDigest: input.promptDigest,
|
|
101
|
+
reportedUsageDigest: reportedUsageDigest(input.reportedUsage),
|
|
102
|
+
usageSignature: input.usageSignature,
|
|
103
|
+
attestation: input.attestation,
|
|
104
|
+
attestationReason: input.attestationReason,
|
|
105
|
+
prevHash
|
|
106
|
+
};
|
|
107
|
+
const record = { ...base, recordHash: computeRecordHash(base) };
|
|
108
|
+
ledger.records.push(record);
|
|
109
|
+
(0, state_1.writeJson)(telemetryLedgerPath(run), ledger, { durable: true });
|
|
110
|
+
return record;
|
|
111
|
+
}
|
|
112
|
+
/** Re-prove the whole telemetry chain for a run: prevHash linkage + per-record
|
|
113
|
+
* hash recompute. Recomputes every hash independently — never trusts the stored
|
|
114
|
+
* value — so an edited record/verdict is detected. An empty ledger verifies as
|
|
115
|
+
* present:false (nothing to prove), NOT a failure. */
|
|
116
|
+
function verifyTelemetryLedger(run) {
|
|
117
|
+
const records = loadTelemetryLedger(run).records;
|
|
118
|
+
const checks = [];
|
|
119
|
+
const tally = { attested: 0, unattested: 0, absent: 0 };
|
|
120
|
+
for (const record of records)
|
|
121
|
+
tally[record.attestation] += 1;
|
|
122
|
+
if (!records.length) {
|
|
123
|
+
return { present: false, verified: true, records, checks, ...tally };
|
|
124
|
+
}
|
|
125
|
+
// (a) chain linkage: genesis = sha256("cw-telemetry-ledger:"+runId).
|
|
126
|
+
let chainOk = true;
|
|
127
|
+
for (let i = 0; i < records.length; i++) {
|
|
128
|
+
const expectedPrev = i === 0 ? genesisPrevHash(run.id) : records[i - 1].recordHash;
|
|
129
|
+
const pass = records[i].prevHash === expectedPrev;
|
|
130
|
+
if (!pass)
|
|
131
|
+
chainOk = false;
|
|
132
|
+
checks.push({ name: `chain-link[${i}]`, pass, code: pass ? undefined : "telemetry-chain-broken" });
|
|
133
|
+
}
|
|
134
|
+
// (b) per-record independent hash recompute (digest integrity).
|
|
135
|
+
let digestsOk = true;
|
|
136
|
+
for (let i = 0; i < records.length; i++) {
|
|
137
|
+
const { recordHash, ...rest } = records[i];
|
|
138
|
+
const recomputed = computeRecordHash(rest);
|
|
139
|
+
const pass = recomputed === recordHash;
|
|
140
|
+
if (!pass)
|
|
141
|
+
digestsOk = false;
|
|
142
|
+
checks.push({ name: `record-hash[${i}]`, pass, code: pass ? undefined : "telemetry-digest-mismatch" });
|
|
143
|
+
}
|
|
144
|
+
return { present: true, verified: chainOk && digestsOk, records, checks, ...tally };
|
|
145
|
+
}
|