cool-workflow 0.1.79 → 0.1.81
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 +51 -3
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review-fast/app.json +64 -0
- package/apps/architecture-review-fast/workflow.js +153 -0
- 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/agent-config.js +21 -7
- package/dist/candidate-scoring.js +42 -22
- package/dist/capability-core.js +132 -17
- package/dist/capability-registry.js +138 -168
- package/dist/cli.js +97 -98
- package/dist/collaboration.js +5 -6
- package/dist/commit.js +20 -6
- package/dist/compare.js +18 -0
- package/dist/coordinator/classify.js +45 -0
- package/dist/coordinator/paths.js +42 -0
- package/dist/coordinator/util.js +129 -0
- package/dist/coordinator.js +127 -300
- package/dist/dispatch.js +35 -0
- package/dist/drive.js +79 -6
- package/dist/error-feedback.js +8 -4
- package/dist/evidence-reasoning.js +3 -3
- package/dist/execution-backend/agent.js +331 -0
- package/dist/execution-backend/probes.js +96 -0
- package/dist/execution-backend/util.js +47 -0
- package/dist/execution-backend.js +73 -421
- package/dist/mcp-server.js +79 -183
- package/dist/multi-agent/graph.js +84 -0
- package/dist/multi-agent/helpers.js +145 -0
- package/dist/multi-agent/paths.js +22 -0
- package/dist/multi-agent-eval/format.js +194 -0
- package/dist/multi-agent-eval/normalize.js +51 -0
- package/dist/multi-agent-eval.js +39 -244
- package/dist/multi-agent-host.js +0 -19
- package/dist/multi-agent.js +125 -314
- package/dist/node-snapshot.js +3 -3
- package/dist/observability/format.js +61 -0
- package/dist/observability/intake.js +98 -0
- package/dist/observability.js +14 -160
- package/dist/operator-ux/format.js +364 -0
- package/dist/operator-ux.js +22 -363
- package/dist/orchestrator/lifecycle-operations.js +2 -1
- package/dist/orchestrator/report.js +8 -0
- package/dist/orchestrator.js +26 -9
- package/dist/reclamation.js +26 -21
- package/dist/run-export.js +494 -25
- package/dist/run-registry/derive.js +172 -0
- package/dist/run-registry/format.js +124 -0
- package/dist/run-registry/gc.js +251 -0
- package/dist/run-registry/policy.js +16 -0
- package/dist/run-registry/queue.js +116 -0
- package/dist/run-registry.js +89 -597
- package/dist/run-state-schema.js +1 -0
- package/dist/sandbox-profile.js +43 -2
- package/dist/state-explosion/format.js +159 -0
- package/dist/state-explosion/helpers.js +82 -0
- package/dist/state-explosion.js +165 -304
- package/dist/state-node.js +19 -4
- package/dist/telemetry-attestation.js +55 -0
- package/dist/telemetry-demo.js +15 -3
- package/dist/telemetry-ledger.js +60 -15
- package/dist/topology.js +25 -8
- package/dist/triggers.js +33 -14
- package/dist/trust-audit.js +145 -33
- package/dist/version.js +1 -1
- package/dist/worker-isolation/helpers.js +51 -0
- package/dist/worker-isolation/paths.js +46 -0
- package/dist/worker-isolation.js +39 -115
- package/docs/agent-delegation-drive.7.md +71 -0
- package/docs/canonical-workflow-apps.7.md +37 -0
- package/docs/cli-mcp-parity.7.md +16 -0
- package/docs/contract-migration-tooling.7.md +6 -0
- package/docs/control-plane-scheduling.7.md +6 -0
- package/docs/dogfood/resume-drive-real-agent-2026-06-14.md +40 -0
- package/docs/durable-state-and-locking.7.md +8 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +6 -0
- package/docs/execution-backends.7.md +6 -0
- package/docs/index.md +2 -0
- package/docs/launch/demo.tape +28 -0
- package/docs/launch/launch-kit.md +96 -17
- package/docs/launch/pre-launch-checklist.md +53 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +8 -0
- package/docs/multi-agent-eval-replay-harness.7.md +6 -0
- package/docs/multi-agent-operator-ux.7.md +6 -0
- package/docs/multi-agent-trust-policy-audit.7.md +27 -0
- package/docs/node-snapshot-diff-replay.7.md +6 -0
- package/docs/observability-cost-accounting.7.md +6 -0
- package/docs/project-index.md +27 -6
- package/docs/real-execution-backends.7.md +6 -0
- package/docs/release-and-migration.7.md +8 -0
- package/docs/release-tooling.7.md +6 -0
- package/docs/routines.md +23 -0
- package/docs/run-registry-control-plane.7.md +89 -2
- package/docs/run-retention-reclamation.7.md +8 -0
- package/docs/source-context-profiles.7.md +119 -0
- package/docs/state-explosion-management.7.md +13 -0
- package/docs/team-collaboration.7.md +6 -0
- package/docs/trust-model.md +267 -0
- package/docs/unix-principles.md +49 -1
- package/docs/vendor-manifest-loadability.7.md +43 -0
- package/docs/web-desktop-workbench.7.md +6 -0
- package/manifest/plugin.manifest.json +1 -1
- package/manifest/source-context-profiles.json +142 -0
- package/package.json +4 -1
- package/scripts/agents/builtin-templates.json +7 -0
- package/scripts/agents/claude-p-agent.js +129 -43
- package/scripts/architecture-review-fast.js +362 -0
- package/scripts/bump-version.js +5 -10
- package/scripts/canonical-apps-list.js +64 -0
- package/scripts/canonical-apps.js +36 -4
- package/scripts/coverage-gate.js +211 -0
- package/scripts/dogfood-release.js +1 -1
- package/scripts/golden-path.js +4 -4
- package/scripts/parity-check.js +5 -0
- package/scripts/release-check.js +5 -1
- package/scripts/source-context.js +291 -0
- package/scripts/version-sync-check.js +5 -7
- package/skills/ci-triage/SKILL.md +50 -0
- package/skills/ci-triage/agents/openai.yaml +4 -0
- package/skills/cool-workflow/SKILL.md +4 -1
- package/skills/deploy-check/SKILL.md +55 -0
- package/skills/deploy-check/agents/openai.yaml +4 -0
- package/skills/design-qa/SKILL.md +49 -0
- package/skills/design-qa/agents/openai.yaml +4 -0
- package/skills/pr-review/SKILL.md +45 -0
- package/skills/pr-review/agents/openai.yaml +4 -0
- package/dist/capability-dispatcher.js +0 -86
package/dist/state-node.js
CHANGED
|
@@ -17,6 +17,8 @@ exports.artifactExists = artifactExists;
|
|
|
17
17
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
18
18
|
const node_path_1 = __importDefault(require("node:path"));
|
|
19
19
|
const state_1 = require("./state");
|
|
20
|
+
const execution_backend_1 = require("./execution-backend");
|
|
21
|
+
const telemetry_attestation_1 = require("./telemetry-attestation");
|
|
20
22
|
exports.STATE_NODE_SCHEMA_VERSION = 1;
|
|
21
23
|
exports.PIPELINE_CONTRACT_SCHEMA_VERSION = 1;
|
|
22
24
|
class PipelineContractError extends Error {
|
|
@@ -35,7 +37,7 @@ function createStateNode(input) {
|
|
|
35
37
|
const now = new Date().toISOString();
|
|
36
38
|
return {
|
|
37
39
|
schemaVersion: exports.STATE_NODE_SCHEMA_VERSION,
|
|
38
|
-
id: input.id || createNodeId(input
|
|
40
|
+
id: input.id || createNodeId(input),
|
|
39
41
|
kind: input.kind,
|
|
40
42
|
status: input.status || "pending",
|
|
41
43
|
loopStage: input.loopStage,
|
|
@@ -281,9 +283,22 @@ function contractError(code, message, options = {}) {
|
|
|
281
283
|
...options
|
|
282
284
|
});
|
|
283
285
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
286
|
+
// Deterministic id (FreeBSD-audit L12/L13): no wall-clock stamp, no PRNG suffix.
|
|
287
|
+
// Almost every node is created WITH an explicit, already-deterministic id
|
|
288
|
+
// (e.g. `${run.id}:result:${task.id}`); this fallback only fires for ad-hoc nodes
|
|
289
|
+
// minted without an id. We bind the id to a short sha256 of the node's stable
|
|
290
|
+
// content (kind + loopStage + canonical inputs/outputs/contract), so the same
|
|
291
|
+
// logical node yields a byte-identical id across runs and replay reaches the same
|
|
292
|
+
// fingerprint. Two nodes with identical content collapse to the same id by design.
|
|
293
|
+
function createNodeId(input) {
|
|
294
|
+
const digest = (0, execution_backend_1.sha256)((0, telemetry_attestation_1.stableStringify)({
|
|
295
|
+
kind: input.kind,
|
|
296
|
+
loopStage: input.loopStage,
|
|
297
|
+
contractId: input.contractId ?? null,
|
|
298
|
+
inputs: input.inputs ?? null,
|
|
299
|
+
outputs: input.outputs ?? null
|
|
300
|
+
}));
|
|
301
|
+
return `${input.kind}-${digest.replace("sha256:", "").slice(0, 16)}`;
|
|
287
302
|
}
|
|
288
303
|
function mergeById(existing, next) {
|
|
289
304
|
const values = [...existing];
|
|
@@ -33,6 +33,7 @@ exports.normalizeReportedUsage = normalizeReportedUsage;
|
|
|
33
33
|
exports.verifyTelemetryAttestation = verifyTelemetryAttestation;
|
|
34
34
|
exports.resolveTrustPublicKey = resolveTrustPublicKey;
|
|
35
35
|
exports.signTelemetry = signTelemetry;
|
|
36
|
+
exports.verifyTelemetrySignatures = verifyTelemetrySignatures;
|
|
36
37
|
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
37
38
|
/** Deterministic, key-sorted JSON so signer and verifier hash byte-identical
|
|
38
39
|
* input regardless of object key order. */
|
|
@@ -154,3 +155,57 @@ function signTelemetry(usage, privateKeyPem, ctx) {
|
|
|
154
155
|
const key = node_crypto_1.default.createPrivateKey(privateKeyPem);
|
|
155
156
|
return node_crypto_1.default.sign(null, payload, key).toString("base64");
|
|
156
157
|
}
|
|
158
|
+
function stableDigest(value) {
|
|
159
|
+
return `sha256:${node_crypto_1.default.createHash("sha256").update(stableStringify(value), "utf8").digest("hex")}`;
|
|
160
|
+
}
|
|
161
|
+
function verifyTelemetrySignatures(records, trustPublicKeyPem) {
|
|
162
|
+
const checks = [];
|
|
163
|
+
let checked = 0;
|
|
164
|
+
let reverified = 0;
|
|
165
|
+
let failed = 0;
|
|
166
|
+
for (let i = 0; i < records.length; i++) {
|
|
167
|
+
const record = records[i];
|
|
168
|
+
if (record.attestation !== "attested")
|
|
169
|
+
continue;
|
|
170
|
+
checked += 1;
|
|
171
|
+
if (!trustPublicKeyPem) {
|
|
172
|
+
checks.push({ name: `signature[${i}]`, pass: true, code: "signature-unchecked-no-key" });
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (!record.reportedUsage) {
|
|
176
|
+
// A claimed-`attested` record with no re-verifiable raw usage cannot be
|
|
177
|
+
// independently checked — fail closed rather than trust the stored verdict.
|
|
178
|
+
failed += 1;
|
|
179
|
+
checks.push({ name: `signature[${i}]`, pass: false, code: "telemetry-usage-unavailable" });
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (record.reportedUsageDigest !== stableDigest(record.reportedUsage)) {
|
|
183
|
+
// The raw usage is stored so a public-key verifier can re-run ed25519.
|
|
184
|
+
// The hash-chained record binds its digest; verify the two still match
|
|
185
|
+
// before trusting the raw payload for signature re-verification.
|
|
186
|
+
failed += 1;
|
|
187
|
+
checks.push({ name: `signature[${i}]`, pass: false, code: "telemetry-usage-digest-mismatch" });
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const result = verifyTelemetryAttestation(record.reportedUsage, record.usageSignature, trustPublicKeyPem, {
|
|
191
|
+
runId: record.runId,
|
|
192
|
+
taskId: record.taskId,
|
|
193
|
+
promptDigest: record.promptDigest
|
|
194
|
+
});
|
|
195
|
+
if (result.status === "attested") {
|
|
196
|
+
reverified += 1;
|
|
197
|
+
checks.push({ name: `signature[${i}]`, pass: true });
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
failed += 1;
|
|
201
|
+
checks.push({
|
|
202
|
+
name: `signature[${i}]`,
|
|
203
|
+
pass: false,
|
|
204
|
+
code: result.reason && result.reason.startsWith("trust key unreadable")
|
|
205
|
+
? "telemetry-pubkey-unreadable"
|
|
206
|
+
: "telemetry-signature-mismatch"
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return { keyProvided: Boolean(trustPublicKeyPem), checked, reverified, failed, checks };
|
|
211
|
+
}
|
package/dist/telemetry-demo.js
CHANGED
|
@@ -31,12 +31,24 @@ const telemetry_attestation_1 = require("./telemetry-attestation");
|
|
|
31
31
|
const execution_backend_1 = require("./execution-backend");
|
|
32
32
|
/** Human-facing render of `telemetry verify <run>`. */
|
|
33
33
|
function formatTelemetryVerify(r) {
|
|
34
|
-
|
|
34
|
+
const keyUnreadable = r.failedChecks.some((c) => c.code === "telemetry-pubkey-unreadable");
|
|
35
|
+
if (!r.present && !keyUnreadable)
|
|
35
36
|
return `telemetry: run ${r.runId} has no attestation ledger (nothing to verify)`;
|
|
36
|
-
const head = r.verified
|
|
37
|
+
const head = r.verified
|
|
38
|
+
? `✓ VERIFIED — ${r.records} record(s), chain intact, every hash recomputed independently`
|
|
39
|
+
: keyUnreadable
|
|
40
|
+
? `✗ VERIFICATION REFUSED — supplied public key was unreadable`
|
|
41
|
+
: `✗ TAMPERING DETECTED — ${r.failedChecks.length} check(s) failed`;
|
|
37
42
|
const tally = ` attested ${r.attested} · unattested ${r.unattested} · absent ${r.absent}`;
|
|
43
|
+
const sig = keyUnreadable
|
|
44
|
+
? `\n signatures: public key unreadable; ed25519 re-check refused`
|
|
45
|
+
: r.signatureKeyProvided
|
|
46
|
+
? `\n signatures: ${r.signaturesReverified}/${r.signaturesChecked} re-verified against the supplied public key${r.signaturesFailed ? ` · ${r.signaturesFailed} FAILED` : ""}`
|
|
47
|
+
: r.signaturesChecked
|
|
48
|
+
? `\n signatures: ${r.signaturesChecked} attested record(s) — chain-proven only; pass --pubkey to re-verify ed25519 offline`
|
|
49
|
+
: "";
|
|
38
50
|
const fails = r.failedChecks.length ? "\n" + r.failedChecks.map((c) => ` ✗ ${c.name} ${c.code || ""}`).join("\n") : "";
|
|
39
|
-
return `telemetry verify ${r.runId}\n${head}\n${tally}${fails}`;
|
|
51
|
+
return `telemetry verify ${r.runId}\n${head}\n${tally}${sig}${fails}`;
|
|
40
52
|
}
|
|
41
53
|
/** Human-facing render of `demo tamper` — the visible tamper-evidence proof. */
|
|
42
54
|
function formatTamperDemo(r) {
|
package/dist/telemetry-ledger.js
CHANGED
|
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
17
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.TELEMETRY_LEDGER_SCHEMA_VERSION = void 0;
|
|
20
|
+
exports.TelemetryLedgerCorruptError = exports.TELEMETRY_LEDGER_SCHEMA_VERSION = void 0;
|
|
21
21
|
exports.telemetryLedgerPath = telemetryLedgerPath;
|
|
22
22
|
exports.loadTelemetryLedger = loadTelemetryLedger;
|
|
23
23
|
exports.genesisPrevHash = genesisPrevHash;
|
|
@@ -34,19 +34,47 @@ exports.TELEMETRY_LEDGER_SCHEMA_VERSION = 1;
|
|
|
34
34
|
function telemetryLedgerPath(run) {
|
|
35
35
|
return node_path_1.default.join(run.paths.runDir, "telemetry.json");
|
|
36
36
|
}
|
|
37
|
-
/**
|
|
38
|
-
*
|
|
39
|
-
|
|
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) {
|
|
40
55
|
const file = telemetryLedgerPath(run);
|
|
41
56
|
if (!node_fs_1.default.existsSync(file))
|
|
42
|
-
return { schemaVersion: 1, runId: run.id, records: [] };
|
|
57
|
+
return { status: "absent", ledger: { schemaVersion: 1, runId: run.id, records: [] } };
|
|
58
|
+
let parsed;
|
|
43
59
|
try {
|
|
44
|
-
|
|
45
|
-
return { schemaVersion: 1, runId: run.id, records: Array.isArray(parsed.records) ? parsed.records : [] };
|
|
60
|
+
parsed = JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
|
|
46
61
|
}
|
|
47
62
|
catch {
|
|
48
|
-
return {
|
|
63
|
+
return { status: "corrupt", file };
|
|
64
|
+
}
|
|
65
|
+
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.records)) {
|
|
66
|
+
return { status: "corrupt", file };
|
|
49
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;
|
|
50
78
|
}
|
|
51
79
|
/** genesis prevHash for a run's chain (no prior record). */
|
|
52
80
|
function genesisPrevHash(runId) {
|
|
@@ -64,6 +92,7 @@ function recordHashInput(record) {
|
|
|
64
92
|
taskId: record.taskId,
|
|
65
93
|
promptDigest: record.promptDigest,
|
|
66
94
|
reportedUsageDigest: record.reportedUsageDigest,
|
|
95
|
+
...(record.reportedUsage !== undefined ? { reportedUsage: record.reportedUsage } : {}),
|
|
67
96
|
usageSignature: record.usageSignature || null,
|
|
68
97
|
attestation: record.attestation,
|
|
69
98
|
attestationReason: record.attestationReason || null,
|
|
@@ -78,11 +107,10 @@ function computeRecordHash(record) {
|
|
|
78
107
|
function reportedUsageDigest(usage) {
|
|
79
108
|
return (0, execution_backend_1.sha256)((0, telemetry_attestation_1.stableStringify)(usage ?? null));
|
|
80
109
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return `tel-${stamp}-${String(recordCounter).padStart(3, "0")}`;
|
|
110
|
+
function recordId(seq) {
|
|
111
|
+
// Deterministic (FreeBSD-audit L13): the chain POSITION, not a process-global
|
|
112
|
+
// counter or wall-clock stamp — recordId is bound into the recordHash chain.
|
|
113
|
+
return `tel-${String(seq).padStart(3, "0")}`;
|
|
86
114
|
}
|
|
87
115
|
/** Append one attestation record DURABLY to the append-only chain, linking it to
|
|
88
116
|
* the prior record (or genesis). Returns the committed record. */
|
|
@@ -93,12 +121,15 @@ function appendTelemetryAttestation(run, input) {
|
|
|
93
121
|
const base = {
|
|
94
122
|
schemaVersion: 1,
|
|
95
123
|
runId: run.id,
|
|
96
|
-
recordId: recordId(
|
|
124
|
+
recordId: recordId(ledger.records.length + 1),
|
|
97
125
|
recordedAt: now,
|
|
98
126
|
workerId: input.workerId,
|
|
99
127
|
taskId: input.taskId,
|
|
100
128
|
promptDigest: input.promptDigest,
|
|
101
129
|
reportedUsageDigest: reportedUsageDigest(input.reportedUsage),
|
|
130
|
+
// Store the raw usage verbatim, digest-bound, and hash-chained so the
|
|
131
|
+
// signature can be independently re-verified offline at `telemetry verify`.
|
|
132
|
+
...(input.reportedUsage ? { reportedUsage: input.reportedUsage } : {}),
|
|
102
133
|
usageSignature: input.usageSignature,
|
|
103
134
|
attestation: input.attestation,
|
|
104
135
|
attestationReason: input.attestationReason,
|
|
@@ -114,7 +145,21 @@ function appendTelemetryAttestation(run, input) {
|
|
|
114
145
|
* value — so an edited record/verdict is detected. An empty ledger verifies as
|
|
115
146
|
* present:false (nothing to prove), NOT a failure. */
|
|
116
147
|
function verifyTelemetryLedger(run) {
|
|
117
|
-
const
|
|
148
|
+
const state = readTelemetryLedgerState(run);
|
|
149
|
+
if (state.status === "corrupt") {
|
|
150
|
+
// Fail closed: a ledger that exists but cannot be parsed is indistinguishable
|
|
151
|
+
// from a truncated/forged one — report it, never green it.
|
|
152
|
+
return {
|
|
153
|
+
present: true,
|
|
154
|
+
verified: false,
|
|
155
|
+
records: [],
|
|
156
|
+
checks: [{ name: "ledger-load", pass: false, code: "telemetry-ledger-corrupt" }],
|
|
157
|
+
attested: 0,
|
|
158
|
+
unattested: 0,
|
|
159
|
+
absent: 0
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const records = state.ledger.records;
|
|
118
163
|
const checks = [];
|
|
119
164
|
const tally = { attested: 0, unattested: 0, absent: 0 };
|
|
120
165
|
for (const record of records)
|
package/dist/topology.js
CHANGED
|
@@ -30,7 +30,7 @@ exports.OFFICIAL_TOPOLOGIES = [
|
|
|
30
30
|
title: "Map-Reduce",
|
|
31
31
|
summary: "Fan out mapper roles, index mapper evidence on the blackboard, then reduce only after required evidence is present.",
|
|
32
32
|
roles: [
|
|
33
|
-
roleSpec("mapper", "Mapper", ["Produce an independent shard result and cite evidence."], ["mapper output artifact"], ["indexed mapper artifact"]),
|
|
33
|
+
roleSpec("mapper", "Mapper", ["Produce an independent shard result and cite evidence."], ["mapper output artifact"], ["indexed mapper artifact"], 2),
|
|
34
34
|
roleSpec("reducer", "Reducer", ["Synthesize mapper outputs only after fanin is verifier-ready."], ["reducer synthesis"], ["all mapper evidence"])
|
|
35
35
|
],
|
|
36
36
|
groups: [{ id: "map-reduce", title: "Map-Reduce Group", roleIds: ["mapper", "reducer"] }],
|
|
@@ -83,7 +83,7 @@ exports.OFFICIAL_TOPOLOGIES = [
|
|
|
83
83
|
title: "Judge Panel",
|
|
84
84
|
summary: "Collect independent judge outputs, aggregate scores, and select a panel decision with linked evidence.",
|
|
85
85
|
roles: [
|
|
86
|
-
roleSpec("judge", "Judge", ["Score candidates independently and cite evidence."], ["judge score artifact"], ["judge verdict"]),
|
|
86
|
+
roleSpec("judge", "Judge", ["Score candidates independently and cite evidence."], ["judge score artifact"], ["judge verdict"], 3),
|
|
87
87
|
roleSpec("panel-chair", "Panel Chair", ["Aggregate scores and write a panel decision."], ["panel decision"], ["judge evidence"])
|
|
88
88
|
],
|
|
89
89
|
groups: [{ id: "judge-panel", title: "Judge Panel Group", roleIds: ["judge", "panel-chair"] }],
|
|
@@ -211,7 +211,7 @@ function applyTopology(run, topologyId, input = {}) {
|
|
|
211
211
|
metadata: { topologyId: definition.id, topologyRunId: id }
|
|
212
212
|
});
|
|
213
213
|
const roleIds = [];
|
|
214
|
-
for (const role of materializedRoles(definition, input)) {
|
|
214
|
+
for (const role of materializedRoles(definition, withLegacyRoleCounts(input))) {
|
|
215
215
|
const record = (0, multi_agent_1.createAgentRole)(run, {
|
|
216
216
|
id: `${id}-${role.id}`,
|
|
217
217
|
multiAgentRunId: multiAgentRun.id,
|
|
@@ -387,7 +387,7 @@ function summarizeTopologies(run) {
|
|
|
387
387
|
runId: run.id,
|
|
388
388
|
totalRuns: state.runs.length,
|
|
389
389
|
runsByStatus: countBy(active, (record) => record.status),
|
|
390
|
-
officialTopologies:
|
|
390
|
+
officialTopologies: listTopologyDefinitions().map((definition) => definition.id),
|
|
391
391
|
active,
|
|
392
392
|
nextAction: active.find((record) => record.nextActions.length)?.nextActions[0] || `node scripts/cw.js topology apply ${run.id} map-reduce --task <task-id>`
|
|
393
393
|
};
|
|
@@ -422,11 +422,28 @@ function showTopologyRun(run, topologyRunId) {
|
|
|
422
422
|
throw new Error(`Unknown topology run id: ${topologyRunId}`);
|
|
423
423
|
return record;
|
|
424
424
|
}
|
|
425
|
+
/** Boundary adapter: fold the legacy id-keyed mapperCount/judgeCount flags into
|
|
426
|
+
* the uniform roleCounts map so materializedRoles can stay purely data-driven.
|
|
427
|
+
* An explicit roleCounts entry always wins; the judge floor (>= 2) preserves the
|
|
428
|
+
* prior clamp so a panel never collapses to a single judge. */
|
|
429
|
+
function withLegacyRoleCounts(input) {
|
|
430
|
+
const legacy = {
|
|
431
|
+
mapper: input.mapperCount === undefined ? undefined : Math.max(1, input.mapperCount),
|
|
432
|
+
judge: input.judgeCount === undefined ? undefined : Math.max(2, input.judgeCount)
|
|
433
|
+
};
|
|
434
|
+
const roleCounts = { ...input.roleCounts };
|
|
435
|
+
for (const [roleId, value] of Object.entries(legacy)) {
|
|
436
|
+
if (value !== undefined && roleCounts[roleId] === undefined)
|
|
437
|
+
roleCounts[roleId] = value;
|
|
438
|
+
}
|
|
439
|
+
return Object.keys(roleCounts).length ? { ...input, roleCounts } : input;
|
|
440
|
+
}
|
|
425
441
|
function materializedRoles(definition, input) {
|
|
426
|
-
const count = definition.id === "map-reduce" ? Math.max(1, input.mapperCount || 2) : definition.id === "judge-panel" ? Math.max(2, input.judgeCount || 3) : 1;
|
|
427
442
|
const roles = [];
|
|
428
443
|
for (const role of definition.roles) {
|
|
429
|
-
|
|
444
|
+
// Width is data-driven: a uniform per-role override, else the role's
|
|
445
|
+
// declared count, else a single instance. No topology-id/role-id branching.
|
|
446
|
+
const roleCount = Math.max(1, input.roleCounts?.[role.id] ?? role.count ?? 1);
|
|
430
447
|
if (roleCount > 1) {
|
|
431
448
|
for (let index = 1; index <= roleCount; index += 1)
|
|
432
449
|
roles.push(expandRole(role, `${role.id}-${index}`, `${role.title} ${index}`));
|
|
@@ -458,8 +475,8 @@ function appendTopologyNode(run, record, status) {
|
|
|
458
475
|
metadata: { topologyId: record.topologyId, topologyRunId: record.id }
|
|
459
476
|
}));
|
|
460
477
|
}
|
|
461
|
-
function roleSpec(id, title, responsibilities, expectedArtifacts, faninObligations) {
|
|
462
|
-
return { id, title, responsibilities, requiredEvidence: expectedArtifacts, expectedArtifacts, faninObligations };
|
|
478
|
+
function roleSpec(id, title, responsibilities, expectedArtifacts, faninObligations, count) {
|
|
479
|
+
return { id, title, responsibilities, requiredEvidence: expectedArtifacts, expectedArtifacts, faninObligations, ...(count !== undefined ? { count } : {}) };
|
|
463
480
|
}
|
|
464
481
|
function topicSpec(id, title, description) {
|
|
465
482
|
return { id, title, description };
|
package/dist/triggers.js
CHANGED
|
@@ -18,8 +18,14 @@ class RoutineTriggerBridge {
|
|
|
18
18
|
}
|
|
19
19
|
create(options) {
|
|
20
20
|
const now = new Date().toISOString();
|
|
21
|
+
const store = this.load();
|
|
22
|
+
// Monotonic id, NOT triggers.length: delete shrinks the collection, so a
|
|
23
|
+
// length-based seq would reuse a live id after delete+create (corrupting the
|
|
24
|
+
// append-only event/audit log). nextTriggerSeq only ever increments.
|
|
25
|
+
const seq = (store.nextTriggerSeq || 0) + 1;
|
|
26
|
+
store.nextTriggerSeq = seq;
|
|
21
27
|
const trigger = {
|
|
22
|
-
id: createTriggerId(normalizeKind(options.kind)),
|
|
28
|
+
id: createTriggerId(normalizeKind(options.kind), seq),
|
|
23
29
|
kind: normalizeKind(options.kind),
|
|
24
30
|
createdAt: now,
|
|
25
31
|
updatedAt: now,
|
|
@@ -30,7 +36,6 @@ class RoutineTriggerBridge {
|
|
|
30
36
|
match: parseJsonObject(options.match),
|
|
31
37
|
metadata: parseJsonObject(options.metadata)
|
|
32
38
|
};
|
|
33
|
-
const store = this.load();
|
|
34
39
|
store.triggers.push(trigger);
|
|
35
40
|
this.save(store);
|
|
36
41
|
return trigger;
|
|
@@ -50,9 +55,10 @@ class RoutineTriggerBridge {
|
|
|
50
55
|
const normalizedKind = normalizeKind(kind);
|
|
51
56
|
const store = this.load();
|
|
52
57
|
const now = new Date().toISOString();
|
|
58
|
+
const base = store.events.length;
|
|
53
59
|
const events = store.triggers
|
|
54
60
|
.filter((trigger) => trigger.kind === normalizedKind)
|
|
55
|
-
.map((trigger) => this.createEvent(trigger, payload, now));
|
|
61
|
+
.map((trigger, index) => this.createEvent(trigger, payload, now, base + index + 1));
|
|
56
62
|
store.events.push(...events);
|
|
57
63
|
this.save(store);
|
|
58
64
|
return events;
|
|
@@ -61,9 +67,9 @@ class RoutineTriggerBridge {
|
|
|
61
67
|
const store = this.load();
|
|
62
68
|
return triggerId ? store.events.filter((event) => event.triggerId === triggerId) : store.events;
|
|
63
69
|
}
|
|
64
|
-
createEvent(trigger, payload, receivedAt) {
|
|
70
|
+
createEvent(trigger, payload, receivedAt, seq) {
|
|
65
71
|
const matched = matches(trigger.match, payload);
|
|
66
|
-
const eventId = createEventId(trigger.kind);
|
|
72
|
+
const eventId = createEventId(trigger.kind, seq);
|
|
67
73
|
const payloadPath = node_path_1.default.join(this.payloadsDir, `${(0, state_1.safeFileName)(eventId)}.json`);
|
|
68
74
|
(0, state_1.writeJson)(payloadPath, {
|
|
69
75
|
schemaVersion: 1,
|
|
@@ -84,12 +90,21 @@ class RoutineTriggerBridge {
|
|
|
84
90
|
}
|
|
85
91
|
load() {
|
|
86
92
|
if (!node_fs_1.default.existsSync(this.storePath))
|
|
87
|
-
return { schemaVersion: 1, triggers: [], events: [] };
|
|
93
|
+
return { schemaVersion: 1, triggers: [], events: [], nextTriggerSeq: 0 };
|
|
88
94
|
const value = (0, state_1.readJson)(this.storePath);
|
|
95
|
+
const triggers = Array.isArray(value.triggers) ? value.triggers : [];
|
|
96
|
+
// Recover the monotonic sequence: max(persisted, highest existing id seq). The
|
|
97
|
+
// second term protects legacy stores (no nextTriggerSeq) and any store written
|
|
98
|
+
// before this field existed — a post-delete create can never reuse a live id.
|
|
99
|
+
const maxExisting = triggers.reduce((max, trigger) => {
|
|
100
|
+
const n = Number((String(trigger.id).match(/(\d+)$/) || [])[1] || 0);
|
|
101
|
+
return Number.isFinite(n) && n > max ? n : max;
|
|
102
|
+
}, 0);
|
|
89
103
|
return {
|
|
90
104
|
schemaVersion: 1,
|
|
91
|
-
triggers
|
|
92
|
-
events: Array.isArray(value.events) ? value.events : []
|
|
105
|
+
triggers,
|
|
106
|
+
events: Array.isArray(value.events) ? value.events : [],
|
|
107
|
+
nextTriggerSeq: Math.max(typeof value.nextTriggerSeq === "number" ? value.nextTriggerSeq : 0, maxExisting)
|
|
93
108
|
};
|
|
94
109
|
}
|
|
95
110
|
save(store) {
|
|
@@ -149,11 +164,15 @@ function stringOption(value) {
|
|
|
149
164
|
return undefined;
|
|
150
165
|
return String(value);
|
|
151
166
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
167
|
+
// Deterministic trigger id (FreeBSD-audit L12/L13): the trigger's POSITION in the
|
|
168
|
+
// append-only trigger store, qualified by kind. No wall-clock stamp, no PRNG suffix
|
|
169
|
+
// — registering the same triggers in the same order mints byte-identical ids.
|
|
170
|
+
function createTriggerId(kind, seq) {
|
|
171
|
+
return `${kind}-${String(seq).padStart(4, "0")}`;
|
|
155
172
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
173
|
+
// Deterministic event id (FreeBSD-audit L12/L13): the event's POSITION in the
|
|
174
|
+
// append-only event log (firing many triggers at once still yields a distinct,
|
|
175
|
+
// stable id per trigger). No clock, no PRNG.
|
|
176
|
+
function createEventId(kind, seq) {
|
|
177
|
+
return `event-${kind}-${String(seq).padStart(4, "0")}`;
|
|
159
178
|
}
|