cool-workflow 0.1.98 → 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} +2 -2
- package/dist/shell/report-cli.js +101 -0
- package/dist/shell/report-view-cli.js +117 -0
- package/dist/{orchestrator → shell}/report.js +289 -282
- package/dist/shell/reporter.js +62 -0
- package/dist/shell/run-export-cli.js +106 -0
- package/dist/shell/run-export.js +680 -0
- package/dist/shell/run-registry-io.js +1014 -0
- package/dist/shell/run-store.js +164 -0
- package/dist/{sandbox-profile.js → shell/sandbox-profile.js} +134 -95
- package/dist/{scheduler.js → shell/scheduler-io.js} +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 +2 -0
- package/docs/cli-mcp-parity.7.md +280 -219
- package/docs/contract-migration-tooling.7.md +2 -0
- package/docs/control-plane-scheduling.7.md +2 -0
- package/docs/durable-state-and-locking.7.md +2 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
- package/docs/execution-backends.7.md +2 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
- package/docs/multi-agent-eval-replay-harness.7.md +2 -0
- package/docs/multi-agent-operator-ux.7.md +2 -0
- package/docs/node-snapshot-diff-replay.7.md +2 -0
- package/docs/observability-cost-accounting.7.md +2 -0
- package/docs/project-index.md +132 -71
- package/docs/real-execution-backends.7.md +2 -0
- package/docs/release-and-migration.7.md +2 -0
- package/docs/release-tooling.7.md +2 -0
- package/docs/run-registry-control-plane.7.md +2 -0
- package/docs/run-retention-reclamation.7.md +23 -0
- package/docs/state-explosion-management.7.md +2 -0
- package/docs/team-collaboration.7.md +2 -0
- package/docs/web-desktop-workbench.7.md +2 -0
- package/manifest/plugin.manifest.json +1 -1
- package/manifest/source-context-profiles.json +9 -13
- package/package.json +1 -1
- 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/dogfood-architecture-review.js +2 -3
- package/scripts/dogfood-release.js +3 -3
- package/scripts/gen-parity-doc.js +15 -2
- package/scripts/golden-path.js +4 -4
- package/scripts/onramp-check.js +1 -1
- package/scripts/parity-check.js +38 -21
- package/scripts/release-flow.js +2 -2
- package/scripts/sync-project-index.js +51 -27
- package/scripts/validate-run-state-schema.js +2 -2
- package/scripts/version-sync-check.js +30 -30
- package/dist/candidate-scoring.js +0 -729
- package/dist/capability-core.js +0 -1189
- package/dist/capability-registry.js +0 -885
- package/dist/cli/command-surface.js +0 -494
- package/dist/cli/format.js +0 -56
- package/dist/cli/handlers/audit.js +0 -82
- package/dist/cli/handlers/blackboard.js +0 -81
- package/dist/cli/handlers/candidate.js +0 -40
- package/dist/cli/handlers/clones.js +0 -34
- package/dist/cli/handlers/collaboration.js +0 -61
- package/dist/cli/handlers/eval.js +0 -40
- package/dist/cli/handlers/ledger.js +0 -169
- package/dist/cli/handlers/maintenance.js +0 -107
- package/dist/cli/handlers/multi-agent.js +0 -165
- package/dist/cli/handlers/node.js +0 -41
- package/dist/cli/handlers/operational.js +0 -155
- package/dist/cli/handlers/operator.js +0 -146
- package/dist/cli/handlers/registry.js +0 -68
- package/dist/cli/handlers/run.js +0 -153
- package/dist/cli/handlers/scheduling.js +0 -132
- package/dist/cli/handlers/workbench.js +0 -41
- package/dist/cli/handlers/worker.js +0 -45
- package/dist/cli/run-summary.js +0 -45
- package/dist/clones.js +0 -162
- package/dist/collaboration.js +0 -726
- package/dist/commit.js +0 -592
- package/dist/compare.js +0 -18
- package/dist/coordinator/classify.js +0 -45
- package/dist/coordinator/paths.js +0 -42
- package/dist/coordinator/util.js +0 -126
- package/dist/coordinator.js +0 -990
- package/dist/daemon.js +0 -44
- package/dist/dispatch.js +0 -250
- package/dist/drive.js +0 -864
- package/dist/error-feedback.js +0 -419
- package/dist/evidence-grounding.js +0 -184
- package/dist/execution-backend/agent.js +0 -354
- package/dist/execution-backend/probes.js +0 -112
- package/dist/execution-backend/util.js +0 -47
- package/dist/execution-backend.js +0 -961
- package/dist/gates.js +0 -48
- package/dist/harness.js +0 -61
- package/dist/ledger.js +0 -313
- package/dist/loop-expansion.js +0 -60
- package/dist/mcp/tool-call.js +0 -470
- package/dist/mcp/tool-definitions.js +0 -1066
- package/dist/mcp-surface.js +0 -30
- package/dist/multi-agent/graph.js +0 -84
- package/dist/multi-agent/helpers.js +0 -141
- package/dist/multi-agent/ids.js +0 -20
- package/dist/multi-agent/paths.js +0 -22
- package/dist/multi-agent-eval/normalize.js +0 -51
- package/dist/multi-agent-eval.js +0 -678
- package/dist/multi-agent-host.js +0 -777
- package/dist/multi-agent.js +0 -984
- package/dist/node-projection.js +0 -59
- package/dist/node-snapshot.js +0 -260
- package/dist/operator-ux.js +0 -631
- package/dist/orchestrator/app-operations.js +0 -211
- package/dist/orchestrator/audit-operations.js +0 -182
- package/dist/orchestrator/candidate-operations.js +0 -117
- package/dist/orchestrator/cli-options.js +0 -294
- package/dist/orchestrator/collaboration-operations.js +0 -86
- package/dist/orchestrator/feedback-operations.js +0 -81
- package/dist/orchestrator/host-operations.js +0 -78
- package/dist/orchestrator/lifecycle-operations.js +0 -650
- package/dist/orchestrator/migration-operations.js +0 -44
- package/dist/orchestrator/multi-agent-operations.js +0 -362
- package/dist/orchestrator/topology-operations.js +0 -84
- package/dist/orchestrator.js +0 -925
- package/dist/pipeline-runner.js +0 -285
- package/dist/reclamation/hash.js +0 -72
- package/dist/reclamation.js +0 -812
- package/dist/reporter.js +0 -67
- package/dist/run-export.js +0 -815
- package/dist/run-registry/derive.js +0 -175
- package/dist/run-registry/format.js +0 -124
- package/dist/run-registry/gc.js +0 -251
- package/dist/run-registry/policy.js +0 -16
- package/dist/run-registry/queue.js +0 -115
- package/dist/run-registry.js +0 -850
- package/dist/run-state-schema.js +0 -68
- package/dist/scheduling.js +0 -184
- package/dist/state-explosion.js +0 -1014
- package/dist/state.js +0 -367
- package/dist/telemetry-ledger.js +0 -196
- package/dist/topology.js +0 -565
- package/dist/triggers.js +0 -184
- package/dist/trust-audit.js +0 -644
- package/dist/types/blackboard.js +0 -2
- package/dist/types/candidate.js +0 -2
- package/dist/types/collaboration.js +0 -2
- package/dist/types/core.js +0 -2
- package/dist/types/drive.js +0 -10
- package/dist/types/error-feedback.js +0 -2
- package/dist/types/evidence-reasoning.js +0 -2
- package/dist/types/execution-backend.js +0 -2
- package/dist/types/multi-agent.js +0 -2
- package/dist/types/observability.js +0 -2
- package/dist/types/pipeline.js +0 -2
- package/dist/types/reclamation.js +0 -8
- package/dist/types/report-bundle.js +0 -6
- package/dist/types/result.js +0 -2
- package/dist/types/run-registry.js +0 -2
- package/dist/types/run.js +0 -2
- package/dist/types/sandbox.js +0 -2
- package/dist/types/schedule.js +0 -2
- package/dist/types/state-node.js +0 -2
- package/dist/types/topology.js +0 -2
- package/dist/types/trust.js +0 -2
- package/dist/types/workbench.js +0 -2
- package/dist/types/worker.js +0 -2
- package/dist/types/workflow-app.js +0 -2
- package/dist/types.js +0 -44
- package/dist/util/fingerprint.js +0 -19
- package/dist/util/fingerprint.test.js +0 -27
- package/dist/verifier.js +0 -78
- package/dist/version.js +0 -8
- package/dist/workbench-host.js +0 -192
- package/dist/workbench.js +0 -192
- package/dist/worker-accept/acceptance.js +0 -114
- package/dist/worker-accept/blackboard-fanout.js +0 -80
- package/dist/worker-accept/blackboard-linkage.js +0 -19
- package/dist/worker-accept/context.js +0 -2
- package/dist/worker-accept/telemetry-ledger.js +0 -126
- package/dist/worker-accept/validation.js +0 -77
- package/dist/worker-accept/verifier-completion.js +0 -73
- package/dist/worker-isolation/helpers.js +0 -51
- package/dist/worker-isolation/paths.js +0 -46
- package/dist/worker-isolation.js +0 -656
- package/dist/workflow-api.js +0 -131
- /package/dist/{types → core/types}/boundary.js +0 -0
package/dist/gates.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// Shared, drift-proof gate primitives (F4). These two helpers were previously
|
|
3
|
-
// duplicated "in sync by comment" in commit.ts and candidate-scoring.ts — the
|
|
4
|
-
// no-false-green gate and the sandbox-profile lookup. A by-comment sync is a
|
|
5
|
-
// structural drift hazard: nothing prevents one copy from changing without the
|
|
6
|
-
// other, and the two gates MUST agree (selection + commit must reach the same
|
|
7
|
-
// empty-capture verdict). Extracting ONE implementation each, imported by both
|
|
8
|
-
// call sites, makes that drift impossible.
|
|
9
|
-
//
|
|
10
|
-
// Pure functions over PERSISTED state only — no clock, no randomness, no fs —
|
|
11
|
-
// so both selection and commit replays reach the same gate decision. Keep them
|
|
12
|
-
// fail-closed: when in doubt the caller must BLOCK, never silently pass.
|
|
13
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.emptyCaptureWarning = emptyCaptureWarning;
|
|
15
|
-
exports.sandboxProfileForCandidate = sandboxProfileForCandidate;
|
|
16
|
-
/** The HARD no-false-green gate (DIRECTION.md "ambiguity is a visible state").
|
|
17
|
-
* A verifier node is built FROM a result node; when that result captured no
|
|
18
|
-
* structured signal at all the result node carries a `metadata.captureWarning`
|
|
19
|
-
* marker (set in worker-isolation / lifecycle ingest via isEmptyCapture). The
|
|
20
|
-
* worker output is still ACCEPTED (a recorded warning, never a silent pass), but
|
|
21
|
-
* a verifier-GATED commit — and candidate SELECTION — must NOT be able to
|
|
22
|
-
* present that zero-evidence result as clean/green. We detect it here, reading
|
|
23
|
-
* ONLY persisted state (the source result node's metadata) — purely functional,
|
|
24
|
-
* no clock/ordering — so snapshot replay reaches the same gate decision.
|
|
25
|
-
* Returns the marker string, or undefined.
|
|
26
|
-
*
|
|
27
|
-
* Resolution trail: verifier node -> its input/parent result node. We look at
|
|
28
|
-
* `inputs.inputNodeId` (set by runPipelineStage) first, then fall back to the
|
|
29
|
-
* first parent, so it works regardless of which ingest path produced the node. */
|
|
30
|
-
function emptyCaptureWarning(run, verifierNode) {
|
|
31
|
-
const resultNodeId = (typeof verifierNode.inputs?.inputNodeId === "string" ? verifierNode.inputs.inputNodeId : undefined) ||
|
|
32
|
-
verifierNode.parents[0];
|
|
33
|
-
const resultNode = resultNodeId ? (run.nodes || []).find((node) => node.id === resultNodeId) : undefined;
|
|
34
|
-
const warning = resultNode?.metadata?.captureWarning;
|
|
35
|
-
return typeof warning === "string" && warning ? warning : undefined;
|
|
36
|
-
}
|
|
37
|
-
/** Resolve the sandbox profile that backed a candidate's acceptance: the
|
|
38
|
-
* worker's profile if the candidate has a worker, else the originating task's.
|
|
39
|
-
* Read by both the commit gate's acceptance rationale and selection. Accepts an
|
|
40
|
-
* optional candidate so the commit-side caller (which may not have resolved a
|
|
41
|
-
* candidate) can share the same lookup. Pure over persisted run state. */
|
|
42
|
-
function sandboxProfileForCandidate(run, candidate) {
|
|
43
|
-
const worker = candidate?.workerId ? (run.workers || []).find((entry) => entry.id === candidate.workerId) : undefined;
|
|
44
|
-
if (worker?.sandboxProfileId)
|
|
45
|
-
return worker.sandboxProfileId;
|
|
46
|
-
const task = candidate?.taskId ? (run.tasks || []).find((entry) => entry.id === candidate.taskId) : undefined;
|
|
47
|
-
return task?.sandboxProfileId;
|
|
48
|
-
}
|
package/dist/harness.js
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.writeTaskFiles = writeTaskFiles;
|
|
7
|
-
exports.renderTask = renderTask;
|
|
8
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
-
const state_1 = require("./state");
|
|
11
|
-
function writeTaskFiles(run) {
|
|
12
|
-
for (const task of run.tasks) {
|
|
13
|
-
const taskPath = node_path_1.default.join(run.paths.tasksDir, `${(0, state_1.safeFileName)(task.id)}.md`);
|
|
14
|
-
task.taskPath = taskPath;
|
|
15
|
-
node_fs_1.default.writeFileSync(taskPath, renderTask(run, task), "utf8");
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
function renderTask(run, task) {
|
|
19
|
-
return [
|
|
20
|
-
`# ${task.id}`,
|
|
21
|
-
"",
|
|
22
|
-
`- Workflow: ${run.workflow.title}`,
|
|
23
|
-
`- Run: ${run.id}`,
|
|
24
|
-
`- Phase: ${task.phase}`,
|
|
25
|
-
`- Kind: ${task.kind}`,
|
|
26
|
-
"",
|
|
27
|
-
"## Inputs",
|
|
28
|
-
"",
|
|
29
|
-
`- Repository: ${String(run.inputs.repo || run.cwd)}`,
|
|
30
|
-
`- Question: ${String(run.inputs.question || "")}`,
|
|
31
|
-
`- Invariants: ${formatInputList(run.inputs.invariant)}`,
|
|
32
|
-
"",
|
|
33
|
-
"## Task",
|
|
34
|
-
"",
|
|
35
|
-
task.prompt,
|
|
36
|
-
"",
|
|
37
|
-
"## Output Contract",
|
|
38
|
-
"",
|
|
39
|
-
"- Return a concise Markdown summary.",
|
|
40
|
-
"- Include concrete evidence paths and line numbers when applicable.",
|
|
41
|
-
"- Separate real, conditional, non-issue, and unknown findings when reviewing risk.",
|
|
42
|
-
"- For verification or verdict tasks, include a `cw:result` JSON fence with `findings` and `evidence`.",
|
|
43
|
-
"- Do not edit files unless the parent agent session explicitly assigned implementation work.",
|
|
44
|
-
"",
|
|
45
|
-
"```cw:result",
|
|
46
|
-
"{",
|
|
47
|
-
' "summary": "one sentence",',
|
|
48
|
-
' "findings": [',
|
|
49
|
-
' { "id": "finding-id", "classification": "real|conditional|non-issue|unknown", "severity": "P0|P1|P2|P3|none", "evidence": ["file-or-url:line"] }',
|
|
50
|
-
" ],",
|
|
51
|
-
' "evidence": ["file-or-url:line"]',
|
|
52
|
-
"}",
|
|
53
|
-
"```",
|
|
54
|
-
""
|
|
55
|
-
].join("\n");
|
|
56
|
-
}
|
|
57
|
-
function formatInputList(value) {
|
|
58
|
-
if (Array.isArray(value))
|
|
59
|
-
return value.join("; ");
|
|
60
|
-
return value ? String(value) : "";
|
|
61
|
-
}
|
package/dist/ledger.js
DELETED
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// Cross-agent handoff ledger — the core mechanism for two agents scoped to two
|
|
3
|
-
// separate repos to hand each other a CHANGE PROPOSAL or a REVIEW VERDICT as
|
|
4
|
-
// verifiable data, not chat. Design: docs/designs/handoff-ledger.md.
|
|
5
|
-
//
|
|
6
|
-
// Stage 1 (human-relay transport): a ledger entry is a self-contained JSON
|
|
7
|
-
// object carrying its own sha256 content digest. The producing side prints one;
|
|
8
|
-
// the operator carries it to the other session; the consuming side VERIFIES it
|
|
9
|
-
// fail-closed (a tampered or malformed entry is refused, never acted on) before
|
|
10
|
-
// turning a proposal into a real PR or recording a verdict.
|
|
11
|
-
//
|
|
12
|
-
// Zero-dependency (only node stdlib). `build*`/`verify*` are pure; the stage-2
|
|
13
|
-
// git transport adds `listLedgerEntries`, a READ-ONLY scan of a shared ledger
|
|
14
|
-
// directory (the working tree of a handoff repo) that verifies every entry
|
|
15
|
-
// fail-closed. No run state, no writes, no network.
|
|
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.computeLedgerDigest = computeLedgerDigest;
|
|
51
|
-
exports.buildLedgerProposal = buildLedgerProposal;
|
|
52
|
-
exports.buildLedgerReview = buildLedgerReview;
|
|
53
|
-
exports.verifyLedgerEntry = verifyLedgerEntry;
|
|
54
|
-
exports.applyLedgerProposal = applyLedgerProposal;
|
|
55
|
-
exports.listLedgerEntries = listLedgerEntries;
|
|
56
|
-
exports.unionLedgerEntries = unionLedgerEntries;
|
|
57
|
-
exports.resolveLedgerInbox = resolveLedgerInbox;
|
|
58
|
-
const crypto = __importStar(require("crypto"));
|
|
59
|
-
const fs = __importStar(require("fs"));
|
|
60
|
-
const path = __importStar(require("path"));
|
|
61
|
-
/** Deterministic JSON with recursively sorted object keys, so the digest is a
|
|
62
|
-
* function of content only — never key insertion order. */
|
|
63
|
-
function stableStringify(value) {
|
|
64
|
-
if (value === null || typeof value !== "object")
|
|
65
|
-
return JSON.stringify(value);
|
|
66
|
-
if (Array.isArray(value))
|
|
67
|
-
return `[${value.map(stableStringify).join(",")}]`;
|
|
68
|
-
const keys = Object.keys(value).sort();
|
|
69
|
-
const body = keys
|
|
70
|
-
.map((k) => `${JSON.stringify(k)}:${stableStringify(value[k])}`)
|
|
71
|
-
.join(",");
|
|
72
|
-
return `{${body}}`;
|
|
73
|
-
}
|
|
74
|
-
/** sha256 over the canonical content (every field except `id` and `digest`,
|
|
75
|
-
* which are derived FROM it). Returns the full `sha256:<hex>` form. */
|
|
76
|
-
function computeLedgerDigest(entry) {
|
|
77
|
-
const hash = crypto.createHash("sha256");
|
|
78
|
-
hash.update(stableStringify(entry));
|
|
79
|
-
return `sha256:${hash.digest("hex")}`;
|
|
80
|
-
}
|
|
81
|
-
/** Content-addressed id: `ldg-` + the first 16 hex chars of the digest. Two
|
|
82
|
-
* entries with the same content (and createdAt) get the same id. */
|
|
83
|
-
function deriveId(digest) {
|
|
84
|
-
return `ldg-${digest.replace(/^sha256:/, "").slice(0, 16)}`;
|
|
85
|
-
}
|
|
86
|
-
function seal(content) {
|
|
87
|
-
const digest = computeLedgerDigest(content);
|
|
88
|
-
return { ...content, id: deriveId(digest), digest };
|
|
89
|
-
}
|
|
90
|
-
function buildLedgerProposal(input) {
|
|
91
|
-
const content = {
|
|
92
|
-
kind: "proposal",
|
|
93
|
-
schemaVersion: 1,
|
|
94
|
-
from: input.from,
|
|
95
|
-
to: input.to,
|
|
96
|
-
title: input.title,
|
|
97
|
-
rationale: input.rationale,
|
|
98
|
-
targetFiles: [...input.targetFiles],
|
|
99
|
-
suggestedDiff: input.suggestedDiff || "",
|
|
100
|
-
createdAt: input.createdAt
|
|
101
|
-
};
|
|
102
|
-
return seal(content);
|
|
103
|
-
}
|
|
104
|
-
function buildLedgerReview(input) {
|
|
105
|
-
const content = {
|
|
106
|
-
kind: "review",
|
|
107
|
-
schemaVersion: 1,
|
|
108
|
-
from: input.from,
|
|
109
|
-
to: input.to,
|
|
110
|
-
target: input.target,
|
|
111
|
-
verdict: input.verdict,
|
|
112
|
-
findings: [...input.findings],
|
|
113
|
-
createdAt: input.createdAt
|
|
114
|
-
};
|
|
115
|
-
return seal(content);
|
|
116
|
-
}
|
|
117
|
-
function isRecord(value) {
|
|
118
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
119
|
-
}
|
|
120
|
-
const PROPOSAL_FIELDS = ["from", "to", "title", "rationale", "targetFiles", "suggestedDiff", "createdAt"];
|
|
121
|
-
const REVIEW_FIELDS = ["from", "to", "target", "verdict", "findings", "createdAt"];
|
|
122
|
-
/** Fail-closed verification. Any structural defect, unknown kind, or digest
|
|
123
|
-
* mismatch yields `ok:false` — the caller refuses to act on it. */
|
|
124
|
-
function verifyLedgerEntry(raw) {
|
|
125
|
-
const checks = [];
|
|
126
|
-
const fail = (name, code, detail) => {
|
|
127
|
-
checks.push({ name, pass: false, code, detail });
|
|
128
|
-
return {
|
|
129
|
-
ok: false,
|
|
130
|
-
id: isRecord(raw) && typeof raw.id === "string" ? raw.id : null,
|
|
131
|
-
kind: isRecord(raw) && typeof raw.kind === "string" ? raw.kind : null,
|
|
132
|
-
checks,
|
|
133
|
-
failedChecks: checks.filter((c) => !c.pass).map((c) => ({ name: c.name, code: c.code, detail: c.detail }))
|
|
134
|
-
};
|
|
135
|
-
};
|
|
136
|
-
if (!isRecord(raw))
|
|
137
|
-
return fail("structure", "ledger-not-object", "entry is not a JSON object");
|
|
138
|
-
checks.push({ name: "structure", pass: true });
|
|
139
|
-
const kind = raw.kind;
|
|
140
|
-
if (kind !== "proposal" && kind !== "review")
|
|
141
|
-
return fail("kind", "ledger-unknown-kind", `kind must be proposal|review, got ${JSON.stringify(kind)}`);
|
|
142
|
-
checks.push({ name: "kind", pass: true });
|
|
143
|
-
if (raw.schemaVersion !== 1)
|
|
144
|
-
return fail("schema", "ledger-bad-schema", `schemaVersion must be 1, got ${JSON.stringify(raw.schemaVersion)}`);
|
|
145
|
-
checks.push({ name: "schema", pass: true });
|
|
146
|
-
if (typeof raw.digest !== "string" || !raw.digest)
|
|
147
|
-
return fail("digest-present", "ledger-missing-digest", "digest is absent or not a string");
|
|
148
|
-
checks.push({ name: "digest-present", pass: true });
|
|
149
|
-
const fields = kind === "proposal" ? PROPOSAL_FIELDS : REVIEW_FIELDS;
|
|
150
|
-
const content = { kind, schemaVersion: 1 };
|
|
151
|
-
for (const field of fields) {
|
|
152
|
-
if (!(field in raw))
|
|
153
|
-
return fail("fields", "ledger-missing-field", `required field ${field} is absent`);
|
|
154
|
-
content[field] = raw[field];
|
|
155
|
-
}
|
|
156
|
-
if (kind === "review" && raw.verdict !== "APPROVED" && raw.verdict !== "REJECTED") {
|
|
157
|
-
return fail("verdict", "ledger-bad-verdict", `verdict must be APPROVED|REJECTED, got ${JSON.stringify(raw.verdict)}`);
|
|
158
|
-
}
|
|
159
|
-
checks.push({ name: "fields", pass: true });
|
|
160
|
-
const recomputed = computeLedgerDigest(content);
|
|
161
|
-
if (recomputed !== raw.digest) {
|
|
162
|
-
return fail("digest", "ledger-digest-mismatch", `stored digest does not match content (recomputed ${recomputed})`);
|
|
163
|
-
}
|
|
164
|
-
checks.push({ name: "digest", pass: true });
|
|
165
|
-
// Bind the id to the content: it MUST be the content-addressed id derived from
|
|
166
|
-
// the digest. Without this, `id` is a free, unverified field (it is excluded
|
|
167
|
-
// from the digest) — a forged entry could set `id` to collide with a legit
|
|
168
|
-
// one, and any id-keyed de-duplication (`cw ledger list` union) would silently
|
|
169
|
-
// drop one of them. Fail closed so a spoofed or absent id is refused, not
|
|
170
|
-
// trusted.
|
|
171
|
-
const expectedId = deriveId(raw.digest);
|
|
172
|
-
if (raw.id !== expectedId) {
|
|
173
|
-
return fail("id", "ledger-id-mismatch", `id ${JSON.stringify(raw.id)} is not the content-addressed id for this digest (expected ${expectedId})`);
|
|
174
|
-
}
|
|
175
|
-
checks.push({ name: "id", pass: true });
|
|
176
|
-
return { ok: true, id: expectedId, kind, checks, failedChecks: [] };
|
|
177
|
-
}
|
|
178
|
-
/** Fail-closed extraction of a proposal's `suggestedDiff` for `git apply`. The
|
|
179
|
-
* diff can ONLY escape after the entry verifies: a tampered entry, a review
|
|
180
|
-
* (not a proposal), or a proposal with no diff all yield `ok:false` and
|
|
181
|
-
* `diff:null`, so `cw ledger apply <file> | git apply` can never feed an
|
|
182
|
-
* unverified patch to git. The kernel never shells out to git — turning the
|
|
183
|
-
* diff into a patch stays the operator's step (mechanism, not policy). */
|
|
184
|
-
function applyLedgerProposal(raw) {
|
|
185
|
-
const verified = verifyLedgerEntry(raw);
|
|
186
|
-
if (!verified.ok) {
|
|
187
|
-
return { ok: false, id: verified.id, kind: verified.kind, diff: null, failedChecks: verified.failedChecks };
|
|
188
|
-
}
|
|
189
|
-
if (verified.kind !== "proposal") {
|
|
190
|
-
return { ok: false, id: verified.id, kind: verified.kind, diff: null, failedChecks: [{ name: "kind", code: "ledger-not-a-proposal", detail: "apply expects a proposal entry, not a review" }] };
|
|
191
|
-
}
|
|
192
|
-
const rec = isRecord(raw) ? raw : {};
|
|
193
|
-
const diff = typeof rec.suggestedDiff === "string" ? rec.suggestedDiff : "";
|
|
194
|
-
if (!diff) {
|
|
195
|
-
return { ok: false, id: verified.id, kind: verified.kind, diff: null, failedChecks: [{ name: "diff", code: "ledger-empty-diff", detail: "proposal carries no suggestedDiff to apply" }] };
|
|
196
|
-
}
|
|
197
|
-
return { ok: true, id: verified.id, kind: verified.kind, diff, failedChecks: [] };
|
|
198
|
-
}
|
|
199
|
-
/** Read every `*.json` in `dir`, verify each entry fail-closed, and report.
|
|
200
|
-
* `allOk` is false if any entry is tampered, malformed, or unreadable — so the
|
|
201
|
-
* receiving side refuses the whole inbox rather than acting on a mixed batch. */
|
|
202
|
-
function listLedgerEntries(dir) {
|
|
203
|
-
let names;
|
|
204
|
-
try {
|
|
205
|
-
names = fs.readdirSync(dir).filter((n) => n.endsWith(".json")).sort();
|
|
206
|
-
}
|
|
207
|
-
catch (error) {
|
|
208
|
-
const entry = { file: dir, id: null, kind: null, from: null, to: null, title: null, target: null, verdict: null, ok: false, failedChecks: [{ name: "dir", code: "ledger-dir-unreadable", detail: error.message }] };
|
|
209
|
-
return { dir, count: 0, allOk: false, entries: [entry], resolution: resolveLedgerInbox([entry]) };
|
|
210
|
-
}
|
|
211
|
-
const entries = names.map((name) => {
|
|
212
|
-
const file = path.join(dir, name);
|
|
213
|
-
let raw;
|
|
214
|
-
try {
|
|
215
|
-
const stat = fs.lstatSync(file);
|
|
216
|
-
if (!stat.isFile()) {
|
|
217
|
-
return { file: name, id: null, kind: null, from: null, to: null, title: null, target: null, verdict: null, ok: false, failedChecks: [{ name: "file", code: "ledger-entry-not-regular" }] };
|
|
218
|
-
}
|
|
219
|
-
raw = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
220
|
-
}
|
|
221
|
-
catch {
|
|
222
|
-
return { file: name, id: null, kind: null, from: null, to: null, title: null, target: null, verdict: null, ok: false, failedChecks: [{ name: "parse", code: "ledger-bad-json" }] };
|
|
223
|
-
}
|
|
224
|
-
const result = verifyLedgerEntry(raw);
|
|
225
|
-
const rec = isRecord(raw) ? raw : {};
|
|
226
|
-
return {
|
|
227
|
-
file: name,
|
|
228
|
-
id: result.id,
|
|
229
|
-
kind: result.kind,
|
|
230
|
-
from: typeof rec.from === "string" ? rec.from : null,
|
|
231
|
-
to: typeof rec.to === "string" ? rec.to : null,
|
|
232
|
-
title: typeof rec.title === "string" ? rec.title : null,
|
|
233
|
-
target: typeof rec.target === "string" ? rec.target : null,
|
|
234
|
-
verdict: typeof rec.verdict === "string" ? rec.verdict : null,
|
|
235
|
-
ok: result.ok,
|
|
236
|
-
failedChecks: result.failedChecks
|
|
237
|
-
};
|
|
238
|
-
});
|
|
239
|
-
return { dir, count: entries.length, allOk: entries.every((e) => e.ok), entries, resolution: resolveLedgerInbox(entries) };
|
|
240
|
-
}
|
|
241
|
-
/** Union-verify several mirror directories into ONE fail-closed inbox. Verified
|
|
242
|
-
* entries are de-duplicated by their content-addressed id (the same entry
|
|
243
|
-
* mirrored to N hosts collapses to one, recording every mirror it came from);
|
|
244
|
-
* failing entries are kept per-occurrence so every problem in every mirror is
|
|
245
|
-
* visible. `allOk` is false if ANY entry in ANY mirror does not verify — a
|
|
246
|
-
* tampered mirror fails the whole batch. Safe because entries are immutable and
|
|
247
|
-
* content-addressed, so a union is a conflict-free set-union, not a merge. */
|
|
248
|
-
function unionLedgerEntries(dirs) {
|
|
249
|
-
const byId = new Map();
|
|
250
|
-
const failures = [];
|
|
251
|
-
let allOk = true;
|
|
252
|
-
for (const dir of dirs) {
|
|
253
|
-
const listed = listLedgerEntries(dir);
|
|
254
|
-
if (!listed.allOk)
|
|
255
|
-
allOk = false;
|
|
256
|
-
for (const entry of listed.entries) {
|
|
257
|
-
if (entry.ok && entry.id) {
|
|
258
|
-
const existing = byId.get(entry.id);
|
|
259
|
-
if (existing) {
|
|
260
|
-
if (!existing.dirs.includes(dir))
|
|
261
|
-
existing.dirs.push(dir);
|
|
262
|
-
}
|
|
263
|
-
else {
|
|
264
|
-
byId.set(entry.id, { ...entry, dirs: [dir] });
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
failures.push({ ...entry, dirs: [dir] });
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
const entries = [...byId.values(), ...failures];
|
|
273
|
-
return { dirs, count: entries.length, allOk, entries, resolution: resolveLedgerInbox(entries) };
|
|
274
|
-
}
|
|
275
|
-
/** Derive a machine-actionable inbox summary: pair each proposal with the
|
|
276
|
-
* review(s) that target it and report whether it is pending, approved,
|
|
277
|
-
* rejected, or contested. Only VERIFIED entries take part — a tampered review
|
|
278
|
-
* must never resolve a proposal, so a proposal with only a failing review
|
|
279
|
-
* stays `pending` (fail-closed). Pure derivation over content-addressed
|
|
280
|
-
* entries: no git, no network, no policy (it reports the decision, it does not
|
|
281
|
-
* enforce one). */
|
|
282
|
-
function resolveLedgerInbox(entries) {
|
|
283
|
-
const verified = entries.filter((e) => e.ok);
|
|
284
|
-
const reviews = verified.filter((e) => e.kind === "review" && e.target);
|
|
285
|
-
const proposals = verified
|
|
286
|
-
.filter((e) => e.kind === "proposal" && e.id)
|
|
287
|
-
.map((p) => {
|
|
288
|
-
const answering = reviews.filter((r) => r.target === p.id);
|
|
289
|
-
const verdicts = new Set(answering.map((r) => r.verdict));
|
|
290
|
-
let resolution;
|
|
291
|
-
if (answering.length === 0)
|
|
292
|
-
resolution = "pending";
|
|
293
|
-
else if (verdicts.size > 1)
|
|
294
|
-
resolution = "contested";
|
|
295
|
-
else
|
|
296
|
-
resolution = verdicts.has("APPROVED") ? "approved" : "rejected";
|
|
297
|
-
return {
|
|
298
|
-
id: p.id,
|
|
299
|
-
title: p.title,
|
|
300
|
-
resolution,
|
|
301
|
-
reviews: answering.map((r) => r.id).sort()
|
|
302
|
-
};
|
|
303
|
-
})
|
|
304
|
-
.sort((a, b) => a.id.localeCompare(b.id));
|
|
305
|
-
const tally = (s) => proposals.filter((p) => p.resolution === s).length;
|
|
306
|
-
return {
|
|
307
|
-
proposals,
|
|
308
|
-
pending: tally("pending"),
|
|
309
|
-
approved: tally("approved"),
|
|
310
|
-
rejected: tally("rejected"),
|
|
311
|
-
contested: tally("contested")
|
|
312
|
-
};
|
|
313
|
-
}
|
package/dist/loop-expansion.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// Bounded dynamic control flow (#2) — a declarative LOOP phase whose tasks are a
|
|
3
|
-
// per-round TEMPLATE: after each round completes, a registered PURE predicate decides
|
|
4
|
-
// whether to run another round (a fresh phase appended after this one, with the same
|
|
5
|
-
// tasks under round-suffixed ids) or stop. Hard-capped at `maxRounds`.
|
|
6
|
-
//
|
|
7
|
-
// REPLAY-DETERMINISM: the predicate is a pure function of RECORDED round results +
|
|
8
|
-
// recorded usage — no IO/clock/random in its context by construction. The engine
|
|
9
|
-
// RECORDS each decision as a `loop-control` state node, so a replay walks the recorded
|
|
10
|
-
// round sequence; re-running the predicate is a verification cross-check, not the
|
|
11
|
-
// source of truth. Predicates are NAMED (registry refs), never inline closures —
|
|
12
|
-
// closures cannot be serialized into state or re-evaluated byte-identically on replay.
|
|
13
|
-
//
|
|
14
|
-
// The materialization (cloning tasks, appending phases, recording the loop-control
|
|
15
|
-
// node) lives in the orchestrator (maybeExpandLoop), which owns writeTaskFiles + the
|
|
16
|
-
// plan pipeline; THIS module is just the pure registry + the STATIC expansion bound.
|
|
17
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.registerLoopPredicate = registerLoopPredicate;
|
|
19
|
-
exports.getLoopPredicate = getLoopPredicate;
|
|
20
|
-
exports.hasLoopPredicate = hasLoopPredicate;
|
|
21
|
-
exports.maxLoopExpansion = maxLoopExpansion;
|
|
22
|
-
const REGISTRY = new Map();
|
|
23
|
-
/** Register a named pure loop predicate (apps register theirs at load; CW ships a few
|
|
24
|
-
* built-ins below). Re-registering a name overwrites it. */
|
|
25
|
-
function registerLoopPredicate(name, fn) {
|
|
26
|
-
REGISTRY.set(name, fn);
|
|
27
|
-
}
|
|
28
|
-
function getLoopPredicate(name) {
|
|
29
|
-
return REGISTRY.get(name);
|
|
30
|
-
}
|
|
31
|
-
function hasLoopPredicate(name) {
|
|
32
|
-
return REGISTRY.has(name);
|
|
33
|
-
}
|
|
34
|
-
// ---- Built-in predicates ---------------------------------------------------------
|
|
35
|
-
/** Stop once a round produced no findings (a convergence loop: keep going while the
|
|
36
|
-
* agent still reports findings; stop when a round comes back empty). */
|
|
37
|
-
registerLoopPredicate("no-new-findings", (ctx) => {
|
|
38
|
-
const empty = ctx.roundResults.every((r) => !r || !Array.isArray(r.findings) || r.findings.length === 0);
|
|
39
|
-
return empty
|
|
40
|
-
? { done: true, reason: "no-new-findings: the latest round produced no findings" }
|
|
41
|
-
: { done: false, reason: "no-new-findings: the latest round still has findings" };
|
|
42
|
-
});
|
|
43
|
-
/** Always stop after the first round (a degenerate loop ≈ a normal phase; useful as a
|
|
44
|
-
* default / for tests). */
|
|
45
|
-
registerLoopPredicate("single-round", () => ({ done: true, reason: "single-round: stop after one round" }));
|
|
46
|
-
/** Static worst-case number of EXTRA tasks a fully-expanded run could mint, derived
|
|
47
|
-
* purely from the workflow DECLARATION (every loop origin phase contributes
|
|
48
|
-
* (maxRounds-1) × its round-1 task count). Used to keep the drive's iteration bound
|
|
49
|
-
* safe as tasks grow, WITHOUT reading any runtime result — so the bound is itself
|
|
50
|
-
* replay-stable and the loop is provably bounded. Zero when there are no loop phases. */
|
|
51
|
-
function maxLoopExpansion(run) {
|
|
52
|
-
let extra = 0;
|
|
53
|
-
for (const phase of run.phases) {
|
|
54
|
-
if (phase.loop && typeof phase.loop.maxRounds === "number" && phase.loop.maxRounds > 1) {
|
|
55
|
-
const templateTaskCount = phase.taskIds.length;
|
|
56
|
-
extra += (phase.loop.maxRounds - 1) * templateTaskCount;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return extra;
|
|
60
|
-
}
|