cool-workflow 0.2.1 → 0.2.3
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/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 +29 -91
- package/dist/cli/entry.js +41 -1
- package/dist/cli/io.js +6 -2
- package/dist/cli/parseargv.js +6 -2
- package/dist/core/capability-data.js +271 -0
- package/dist/core/capability-table.js +16 -3504
- package/dist/core/format/completion.js +68 -0
- package/dist/core/format/help.js +50 -8
- package/dist/core/format/safe-json.js +73 -0
- package/dist/core/format/state-explosion-text.js +1 -1
- package/dist/core/multi-agent/candidate-scoring.js +5 -1
- package/dist/core/multi-agent/collaboration.js +6 -5
- package/dist/core/multi-agent/coordinator.js +9 -8
- package/dist/core/multi-agent/runtime.js +4 -4
- package/dist/core/multi-agent/topology.js +3 -3
- package/dist/core/pipeline/commit-gate.js +2 -1
- package/dist/core/pipeline/dispatch.js +18 -4
- package/dist/core/pipeline/drive-decide.js +2 -1
- package/dist/core/state/migrations.js +16 -1
- package/dist/core/state/state-explosion/digest.js +16 -15
- package/dist/core/state/state-explosion/graph.js +26 -19
- package/dist/core/state/state-explosion/helpers.js +2 -1
- package/dist/core/state/state-explosion/report.js +6 -6
- package/dist/core/trust/ledger.js +2 -1
- package/dist/core/types/execution-backend.js +18 -0
- package/dist/core/types/observability.js +7 -0
- package/dist/core/util/collate.js +23 -0
- package/dist/core/util/numeric-flag.js +40 -0
- package/dist/core/version.js +1 -1
- package/dist/mcp/server.js +99 -11
- package/dist/shell/audit-cli.js +99 -23
- package/dist/shell/commit-summary.js +2 -1
- package/dist/shell/coordinator-io.js +73 -13
- package/dist/shell/dispatch.js +1 -1
- package/dist/shell/doctor.js +80 -1
- package/dist/shell/drive.js +269 -59
- package/dist/shell/eval-text.js +2 -2
- package/dist/shell/evidence-reasoning.js +8 -7
- package/dist/shell/execution-backend/agent.js +20 -1
- package/dist/shell/execution-backend/container.js +4 -1
- package/dist/shell/execution-backend/local.js +4 -1
- package/dist/shell/execution-backend/registry.js +2 -1
- package/dist/shell/execution-backend/types.js +0 -9
- package/dist/shell/feedback-cli.js +6 -6
- package/dist/shell/fs-atomic.js +232 -30
- package/dist/shell/man-cli.js +6 -0
- package/dist/shell/metrics-cli.js +2 -1
- package/dist/shell/multi-agent-cli.js +367 -323
- package/dist/shell/multi-agent-host.js +9 -9
- package/dist/shell/multi-agent-operator-ux.js +82 -39
- package/dist/shell/node-store.js +10 -4
- package/dist/shell/observability.js +13 -12
- package/dist/shell/onramp.js +17 -1
- package/dist/shell/operator-ux-text.js +24 -23
- package/dist/shell/operator-ux.js +22 -20
- package/dist/shell/orchestrator.js +49 -38
- package/dist/shell/pipeline-cli.js +168 -111
- package/dist/shell/reclamation-io.js +16 -5
- package/dist/shell/registry-cli.js +19 -17
- package/dist/shell/remote-source.js +13 -8
- package/dist/shell/report-cli.js +45 -0
- package/dist/shell/report.js +4 -2
- package/dist/shell/run-registry-io.js +77 -19
- package/dist/shell/run-store.js +91 -2
- package/dist/shell/scheduling-io.js +12 -13
- package/dist/shell/state-cli.js +2 -7
- package/dist/shell/state-explosion-cli.js +19 -10
- package/dist/shell/topology-io.js +38 -6
- package/dist/shell/trust-audit.js +264 -23
- package/dist/shell/trust-policy-io.js +1 -1
- package/dist/shell/worker-cli.js +41 -29
- package/dist/shell/worker-isolation.js +34 -9
- package/dist/shell/workflow-app-loader.js +3 -2
- package/dist/wiring/capability-table/basics.js +91 -0
- package/dist/wiring/capability-table/exec-backend.js +171 -0
- package/dist/wiring/capability-table/index.js +42 -0
- package/dist/wiring/capability-table/multi-agent.js +630 -0
- package/dist/wiring/capability-table/parity.js +467 -0
- package/dist/wiring/capability-table/pipeline.js +292 -0
- package/dist/wiring/capability-table/registry-core.js +208 -0
- package/dist/wiring/capability-table/reporting.js +435 -0
- package/dist/wiring/capability-table/scheduling-registry.js +592 -0
- package/dist/wiring/capability-table/state.js +181 -0
- package/dist/wiring/capability-table/trust-ledger.js +158 -0
- package/dist/wiring/capability-table/workflow-apps.js +381 -0
- package/docs/agent-delegation-drive.7.md +4 -0
- package/docs/candidate-scoring.7.md +1 -1
- package/docs/canonical-workflow-apps.7.md +7 -7
- package/docs/cli-mcp-parity.7.md +13 -3
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +4 -0
- package/docs/coordinator-blackboard.7.md +17 -17
- package/docs/dogfood-one-real-repo.7.md +11 -11
- package/docs/durable-state-and-locking.7.md +24 -0
- package/docs/end-to-end-golden-path.7.md +14 -14
- package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
- package/docs/execution-backends.7.md +4 -0
- package/docs/getting-started.md +37 -37
- package/docs/index.md +1 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +21 -17
- package/docs/multi-agent-eval-replay-harness.7.md +17 -13
- package/docs/multi-agent-operator-ux.7.md +23 -19
- package/docs/multi-agent-runtime-core.7.md +22 -22
- package/docs/multi-agent-topologies.7.md +6 -6
- package/docs/multi-agent-trust-policy-audit.7.md +11 -11
- package/docs/node-snapshot-diff-replay.7.md +4 -0
- package/docs/observability-cost-accounting.7.md +4 -0
- package/docs/operator-ux.7.md +34 -34
- package/docs/pipeline-runner.7.md +4 -4
- package/docs/project-index.md +48 -5
- package/docs/real-execution-backends.7.md +4 -0
- package/docs/release-and-migration.7.md +5 -1
- package/docs/release-tooling.7.md +4 -0
- package/docs/routines.md +4 -4
- package/docs/run-registry-control-plane.7.md +23 -19
- package/docs/run-retention-reclamation.7.md +4 -0
- package/docs/scheduled-tasks.md +14 -14
- package/docs/security-trust-hardening.7.md +43 -13
- package/docs/state-explosion-management.7.md +14 -10
- package/docs/team-collaboration.7.md +4 -0
- package/docs/trust-audit-anchor.7.md +71 -0
- package/docs/unix-principles.md +3 -1
- package/docs/verifier-gated-commit.7.md +1 -1
- package/docs/web-desktop-workbench.7.md +4 -0
- package/docs/workflow-app-framework.7.md +13 -13
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +4 -2
- package/scripts/agents/claude-p-agent.js +2 -2
- package/scripts/block-unapproved-tag.sh +23 -2
- package/scripts/bump-version.js +24 -2
- package/scripts/canonical-apps.js +4 -4
- package/scripts/children/batch-delegate-child.js +52 -2
- package/scripts/dogfood-release.js +1 -1
- package/scripts/fake-date-for-reproduction.js +44 -0
- package/scripts/golden-path.js +4 -4
- package/scripts/purity-baseline.json +71 -0
- package/scripts/purity-gate.js +239 -0
- package/scripts/release-check.js +8 -1
- package/scripts/release-flow.js +57 -1
- package/scripts/verdict-keygen.js +83 -0
- package/scripts/verify-bump-reproduction.sh +148 -0
- package/scripts/verify-verdict-signature.js +61 -0
- package/scripts/version-sync-check.js +33 -12
- package/skills/cool-workflow/SKILL.md +9 -9
- package/skills/cool-workflow/references/commands.md +89 -88
- package/ui/workbench/app.css +37 -1
- package/ui/workbench/app.js +124 -6
- package/workflows/README.md +19 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Trust Audit Anchor
|
|
2
|
+
|
|
3
|
+
CW v0.2.1 adds the Trust Audit Anchor: a way to see when the END of a run's
|
|
4
|
+
trust-audit log was cut off. The hash chain in `audit/events.jsonl` lets
|
|
5
|
+
`cw audit verify` see an edited event, a removed middle event, a bad line, and
|
|
6
|
+
a mixed-era forgery. But one tamper shape gets past a pure chain walk: take
|
|
7
|
+
the last N lines off the file, and what is left is a shorter but fully
|
|
8
|
+
consistent chain — verify stays green. The anchor closes that hole, and it
|
|
9
|
+
does so without changing any old output byte.
|
|
10
|
+
|
|
11
|
+
## Design Discipline
|
|
12
|
+
|
|
13
|
+
- Mechanism, not policy: the kernel gives you two small parts — a read of the
|
|
14
|
+
chain head, and a check against a head you saved earlier. WHEN you save a
|
|
15
|
+
head (after a run, before you publish, at export time) is your policy.
|
|
16
|
+
- Fail-closed: a saved head that is not on the chain, or an event count that
|
|
17
|
+
comes up short, makes verify exit non-zero with the distinct check code
|
|
18
|
+
`trust-audit-truncated`. A bad `--expect-head` / `--expect-count` value is
|
|
19
|
+
an error, never a check silently made weaker.
|
|
20
|
+
- POLA: with no anchor flags, `cw audit verify` output is byte-for-byte what
|
|
21
|
+
it was before this feature (no `anchor` key, same checks, same exit rules).
|
|
22
|
+
- Reuse: the anchor rides on the existing eventHash chain — no new file, no
|
|
23
|
+
new state, no new hash form. `cw audit head` is a read-only projection.
|
|
24
|
+
- Parity: `audit.head` is on both front doors (`cw audit head` and the MCP
|
|
25
|
+
tool `cw_audit_head`); the anchor args are on both `cw audit verify` and
|
|
26
|
+
`cw_audit_verify` (`expectHead` / `expectCount`).
|
|
27
|
+
|
|
28
|
+
## CLI
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
node dist/cli.js audit head <run-id>
|
|
32
|
+
# -> { "schemaVersion": 1, "runId": "...", "eventCount": 87,
|
|
33
|
+
# "headHash": "sha256:..." }
|
|
34
|
+
|
|
35
|
+
node dist/cli.js audit verify <run-id> --expect-head <hash> --expect-count <n>
|
|
36
|
+
# green: verified true, "anchor": { ..., "satisfied": true }, exit 0
|
|
37
|
+
# cut tail: verified false, checks carry trust-audit-truncated, exit 1
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The head is the hash the NEXT appended event will link from: the last event's
|
|
41
|
+
`eventHash`, or the run's genesis hash when the log is empty. Save the pair
|
|
42
|
+
`{headHash, eventCount}` somewhere the log's writer cannot reach — a CI
|
|
43
|
+
variable, a note in your PR, the output of `cw run export` (the export
|
|
44
|
+
manifest hashes the event log bytes, so an export IS an anchor in file form).
|
|
45
|
+
|
|
46
|
+
## How the check works
|
|
47
|
+
|
|
48
|
+
`verifyTrustAudit` walks the chain as before, and keeps the trail of head
|
|
49
|
+
hashes it saw (genesis, then the hash after each event). With an anchor:
|
|
50
|
+
|
|
51
|
+
- `expectCount`: the walked log must have at least that many events. Fewer =
|
|
52
|
+
`trust-audit-truncated` (check name `anchor-count`).
|
|
53
|
+
- `expectHead`: the saved head must be ON the trail. A log that was cut and
|
|
54
|
+
then padded back with new events reaches the old count, but the new events
|
|
55
|
+
link from an earlier point — the old head is no longer on the trail, so
|
|
56
|
+
this still fails (check name `anchor-head`).
|
|
57
|
+
|
|
58
|
+
## Compatibility
|
|
59
|
+
|
|
60
|
+
Trust Audit Anchor is introduced in CW v0.2.1. Fields are additive and
|
|
61
|
+
optional; older run state loads unchanged. A plain `cw audit verify` keeps
|
|
62
|
+
its exact old output. The `anchor` key appears in the JSON only when the
|
|
63
|
+
caller passed an anchor flag.
|
|
64
|
+
|
|
65
|
+
## See Also
|
|
66
|
+
|
|
67
|
+
security-trust-hardening(7), cli-mcp-parity(7), report-verifiable-bundle(7)
|
|
68
|
+
|
|
69
|
+
0.2.2
|
|
70
|
+
|
|
71
|
+
0.2.3
|
package/docs/unix-principles.md
CHANGED
|
@@ -214,7 +214,9 @@ never in core. Core may send a vendor's stream on; it never reads one apart.
|
|
|
214
214
|
**Rule of Silence.** stdout is data, stderr is diagnostics, and a
|
|
215
215
|
non-interactive run says nothing on success. Anything friendly to a person is TTY-gated
|
|
216
216
|
and can be turned off; `--json` output is fixed and with nothing added so it goes together
|
|
217
|
-
in pipes.
|
|
217
|
+
in pipes. A broken pipe is not an error: when the reader at the other end
|
|
218
|
+
goes away early (`cw list --json | head -1`), the CLI stops quietly with
|
|
219
|
+
exit 0 — no stack trace, no error text (both `--json` and human output).
|
|
218
220
|
|
|
219
221
|
**Fail closed, conservative defaults.** Backends not yet configured probe as
|
|
220
222
|
`unverified`, telemetry that cannot be verified is made known loudly (or turned away in strict
|
|
@@ -114,7 +114,7 @@ commit-selection-not-verified
|
|
|
114
114
|
commit-verifier-linkage-mismatch
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
-
Use `cw
|
|
117
|
+
Use `cw feedback list <run-id>` and `cw node graph <run-id>` to look at
|
|
118
118
|
the failed transition.
|
|
119
119
|
|
|
120
120
|
## CANDIDATES
|
|
@@ -16,13 +16,13 @@ const {
|
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
cw app list
|
|
20
|
+
cw app show workflow-app-framework-demo
|
|
21
|
+
cw app validate apps/workflow-app-framework-demo/app.json
|
|
22
|
+
cw app show architecture-review
|
|
23
23
|
npm run canonical-apps
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
cw app init my-app --title "My App"
|
|
25
|
+
cw plan my-app --question "What should happen?"
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
## Description
|
|
@@ -170,7 +170,7 @@ App loading fails closed. CW checks:
|
|
|
170
170
|
- sandbox profile references on the app, workflow, and tasks
|
|
171
171
|
- compatibility limits against the current CW runtime
|
|
172
172
|
|
|
173
|
-
`cw
|
|
173
|
+
`cw app validate` prints a structured result. Apps that are not valid return nonzero:
|
|
174
174
|
|
|
175
175
|
```json
|
|
176
176
|
{
|
|
@@ -190,14 +190,14 @@ CW does not quietly change broken apps into workflows that can run.
|
|
|
190
190
|
## CLI
|
|
191
191
|
|
|
192
192
|
```bash
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
193
|
+
cw app list
|
|
194
|
+
cw app show <app-id>
|
|
195
|
+
cw app validate <path-or-app-id>
|
|
196
|
+
cw app init <app-id> --title "Title"
|
|
197
|
+
cw app package <app-id> --output app.cwapp.json
|
|
198
198
|
```
|
|
199
199
|
|
|
200
|
-
`cw
|
|
200
|
+
`cw list`, `cw init`, and `cw plan` still work the same way. `list` shows
|
|
201
201
|
legacy workflow files and first-class app directories. `plan` takes either
|
|
202
202
|
kind by id.
|
|
203
203
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"_comment": "SINGLE SOURCE OF TRUTH for every vendor manifest. Edit THIS file, then run `npm run gen:manifests`. Do NOT hand-edit the generated vendor manifests (.claude-plugin/, .codex-plugin/, .agents/, .mcp.json) — `npm run gen:manifests -- --check` (run by release:check) will fail if they drift from this source.",
|
|
3
3
|
"identity": {
|
|
4
4
|
"name": "cool-workflow",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.3",
|
|
6
6
|
"license": "BSD-2-Clause",
|
|
7
7
|
"homepage": "https://github.com/coo1white/cool-workflow",
|
|
8
8
|
"author": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cool-workflow",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"bin": {
|
|
5
5
|
"cool-workflow": "scripts/cw.js",
|
|
6
6
|
"cw": "scripts/cw.js"
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"build": "tsc -p tsconfig.json",
|
|
44
44
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
45
45
|
"dist:check": "node scripts/dist-drift-check.js",
|
|
46
|
+
"purity:check": "node scripts/purity-gate.js",
|
|
46
47
|
"golden-path": "node scripts/golden-path.js",
|
|
47
48
|
"dogfood:release": "node scripts/dogfood-release.js",
|
|
48
49
|
"canonical-apps": "node scripts/canonical-apps.js",
|
|
@@ -68,12 +69,13 @@
|
|
|
68
69
|
"test:fast": "npm run build --if-present && node dist/cli.js version > /dev/null && node test/run-all.js --concurrency auto",
|
|
69
70
|
"test:ci": "npm run build && node dist/cli.js version > /dev/null && node test/run-all.js --sample 55",
|
|
70
71
|
"test:coverage": "node dist/cli.js version > /dev/null && node scripts/coverage-gate.js --concurrency auto",
|
|
72
|
+
"test:unit": "node dist/cli.js version > /dev/null && node test/run-unit.js",
|
|
71
73
|
"eval:replay": "tsc -p tsconfig.json && node test/multi-agent-eval-replay-harness-smoke.js",
|
|
72
74
|
"ci": "npm run build && npm run check && npm run test && npm run release:check",
|
|
73
75
|
"validate:schema": "node scripts/validate-run-state-schema.js"
|
|
74
76
|
},
|
|
75
77
|
"devDependencies": {
|
|
76
|
-
"@types/node": "^
|
|
78
|
+
"@types/node": "^26.1.1",
|
|
77
79
|
"typescript": "^5.9.0"
|
|
78
80
|
},
|
|
79
81
|
"keywords": [
|
|
@@ -50,7 +50,7 @@ const traceEnabled = streamEnabled && Boolean(process.stderr.isTTY);
|
|
|
50
50
|
if (!streamEnabled) {
|
|
51
51
|
// Legacy default: --output-format json and verbatim stdout forwarding. This is
|
|
52
52
|
// the public wrapper contract existing users already scripted against.
|
|
53
|
-
const child = spawnSync("claude", ["-p", prompt, "--output-format", "json", "--allowedTools", "Read,Grep,Glob
|
|
53
|
+
const child = spawnSync("claude", ["-p", prompt, "--output-format", "json", "--allowedTools", "Read,Grep,Glob"], {
|
|
54
54
|
encoding: "utf8",
|
|
55
55
|
maxBuffer: 32 * 1024 * 1024,
|
|
56
56
|
shell: false
|
|
@@ -100,7 +100,7 @@ const transcriptPath = path.join(path.dirname(resultPath), "transcript.md");
|
|
|
100
100
|
// non-zero exit.
|
|
101
101
|
const child = spawn(
|
|
102
102
|
"claude",
|
|
103
|
-
["-p", prompt, "--output-format", "stream-json", "--verbose", "--allowedTools", "Read,Grep,Glob
|
|
103
|
+
["-p", prompt, "--output-format", "stream-json", "--verbose", "--allowedTools", "Read,Grep,Glob"],
|
|
104
104
|
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
105
105
|
);
|
|
106
106
|
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
# require BOTH markers for the current HEAD sha:
|
|
5
5
|
# .cw-release/gate-<sha>.ok (written by release-gate.sh)
|
|
6
6
|
# .cw-release/review-<sha>.verdict (written by the release-reviewer agent, must contain APPROVED)
|
|
7
|
+
# If .cw-release/verdict-signing.pub is committed (scripts/verdict-keygen.js),
|
|
8
|
+
# also requires a valid ed25519 signature on the verdict (its .sig sidecar) —
|
|
9
|
+
# opt-in, backward compatible with repos that haven't set up signing yet.
|
|
7
10
|
# Exit 2 blocks the tool call; stderr is fed back to the agent.
|
|
8
11
|
set -uo pipefail
|
|
9
12
|
|
|
@@ -16,8 +19,12 @@ INPUT="$(cat)"
|
|
|
16
19
|
CMD="$(printf '%s' "$INPUT" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{process.stdout.write(String(JSON.parse(s)?.tool_input?.command||""))}catch{process.stdout.write("")}})' 2>/dev/null)"
|
|
17
20
|
[[ -z "$CMD" ]] && exit 0
|
|
18
21
|
|
|
19
|
-
# Only care about tag creation / tag push
|
|
20
|
-
|
|
22
|
+
# Only care about tag creation / tag push. Widened from the original pattern,
|
|
23
|
+
# which missed the single most natural bypass form: `git push origin v0.2.3`
|
|
24
|
+
# (a bare tag-shaped ref, no --tags flag and no refs/tags/ prefix) matched
|
|
25
|
+
# neither side of the old alternation. Also now tolerates a global flag before
|
|
26
|
+
# the tag subcommand (`git -C dir tag ...`) and the `--annotate` long form.
|
|
27
|
+
if ! printf '%s' "$CMD" | grep -qE 'git(\s+-[A-Za-z]+\s+\S+)*\s+tag\s+(-a\s+|--annotate\s+)?v[0-9]|git\s+push\b.*(--tags|refs/tags|[[:space:]]v[0-9])'; then
|
|
21
28
|
exit 0
|
|
22
29
|
fi
|
|
23
30
|
|
|
@@ -36,4 +43,18 @@ if [[ ! -f "$VERDICT" ]] || ! grep -q '^APPROVED' "$VERDICT"; then
|
|
|
36
43
|
exit 2
|
|
37
44
|
fi
|
|
38
45
|
|
|
46
|
+
# Once .cw-release/verdict-signing.pub is committed (see scripts/verdict-keygen.js),
|
|
47
|
+
# also require a valid ed25519 signature on the verdict — closing the gap this grep
|
|
48
|
+
# alone can't: a plain APPROVED text match can't tell a real reviewer verdict from
|
|
49
|
+
# one typed by hand. Absent that public key, this block is a no-op (unchanged,
|
|
50
|
+
# grep-only behavior).
|
|
51
|
+
PUBKEY="$REPO_ROOT/.cw-release/verdict-signing.pub"
|
|
52
|
+
if [[ -f "$PUBKEY" ]]; then
|
|
53
|
+
SIG="$VERDICT.sig"
|
|
54
|
+
if [[ ! -f "$SIG" ]] || ! node "$REPO_ROOT/plugins/cool-workflow/scripts/verify-verdict-signature.js" "$VERDICT" "$SIG" "$PUBKEY" >/dev/null 2>&1; then
|
|
55
|
+
echo "BLOCKED: verdict for HEAD $SHA has no valid signature, but verdict-signing.pub is committed so one is required. Do not hand-write or hand-sign a verdict — obtain a real reviewer approval via release-flow.js with CW_RELEASE_VERDICT_PRIVKEY set." >&2
|
|
56
|
+
exit 2
|
|
57
|
+
fi
|
|
58
|
+
fi
|
|
59
|
+
|
|
39
60
|
exit 0
|
package/scripts/bump-version.js
CHANGED
|
@@ -46,6 +46,23 @@ function replaceFirstVersionField(absPath, next) {
|
|
|
46
46
|
return true;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function replaceLockfileVersions(absPath, next) {
|
|
50
|
+
// Move BOTH lockfile version fields with targeted string swaps, so the byte
|
|
51
|
+
// formatting npm wrote is kept as it is: the top-level "version" (the first
|
|
52
|
+
// one in the file) and the root-package entry `"": { "name": ..., "version": ... }`.
|
|
53
|
+
// Dependency entries are not touched — the second swap is keyed on the
|
|
54
|
+
// `"": {` root-package opening, which is present only once.
|
|
55
|
+
const text = fs.readFileSync(absPath, "utf8");
|
|
56
|
+
let updated = text.replace(/"version":\s*"[^"]*"/, `"version": "${next}"`);
|
|
57
|
+
updated = updated.replace(
|
|
58
|
+
/("":\s*\{\s*"name":\s*"[^"]*",\s*"version":\s*)"[^"]*"/,
|
|
59
|
+
`$1"${next}"`
|
|
60
|
+
);
|
|
61
|
+
if (updated === text) return false;
|
|
62
|
+
fs.writeFileSync(absPath, updated);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
49
66
|
function setNestedVersion(absPath, next) {
|
|
50
67
|
// For files where the first `"version"` is NOT the right one, parse + set.
|
|
51
68
|
const json = JSON.parse(fs.readFileSync(absPath, "utf8"));
|
|
@@ -75,9 +92,13 @@ function main() {
|
|
|
75
92
|
// 1. package.json (the single source of truth version:sync now reads from)
|
|
76
93
|
if (replaceFirstVersionField(path.join(pluginRoot, "package.json"), next)) note("package.json");
|
|
77
94
|
|
|
78
|
-
// 2. package-lock.json (
|
|
95
|
+
// 2. package-lock.json (tracked; only if present). The lockfile keeps the
|
|
96
|
+
// version in TWO places: the top-level "version" and the root-package
|
|
97
|
+
// entry packages[""].version. The old code moved only the first one, so
|
|
98
|
+
// the root-package entry kept an old version till the next `npm install`
|
|
99
|
+
// (the v0.1.97 drift seen after the v0.2.0/v0.2.1 cuts). Move both here.
|
|
79
100
|
const lock = path.join(pluginRoot, "package-lock.json");
|
|
80
|
-
if (fs.existsSync(lock) &&
|
|
101
|
+
if (fs.existsSync(lock) && replaceLockfileVersions(lock, next)) note("package-lock.json");
|
|
81
102
|
|
|
82
103
|
// 2b. Official MCP Registry server metadata (top-level server version + npm package version).
|
|
83
104
|
const serverJson = path.join(pluginRoot, "server.json");
|
|
@@ -218,6 +239,7 @@ function contentSurfaceFiles(next) {
|
|
|
218
239
|
{ path: "plugins/cool-workflow/docs/run-retention-reclamation.7.md", needle: next, desc: "run retention doc" },
|
|
219
240
|
{ path: "plugins/cool-workflow/docs/durable-state-and-locking.7.md", needle: next, desc: "durable state doc" },
|
|
220
241
|
{ path: "plugins/cool-workflow/docs/release-and-migration.7.md", needle: next, desc: "release & migration doc" },
|
|
242
|
+
{ path: "plugins/cool-workflow/docs/trust-audit-anchor.7.md", needle: next, desc: "trust audit anchor doc" },
|
|
221
243
|
];
|
|
222
244
|
}
|
|
223
245
|
|
|
@@ -83,7 +83,7 @@ const canonicalApps = [
|
|
|
83
83
|
"--source",
|
|
84
84
|
"plugins/cool-workflow/docs/workflow-app-framework.7.md",
|
|
85
85
|
"--scope",
|
|
86
|
-
"Cool Workflow v0.2.
|
|
86
|
+
"Cool Workflow v0.2.3",
|
|
87
87
|
"--freshness",
|
|
88
88
|
"as of release preparation"
|
|
89
89
|
]
|
|
@@ -117,14 +117,14 @@ function main() {
|
|
|
117
117
|
assert.ok(summary, `${app.id} must appear in app list`);
|
|
118
118
|
assert.equal(summary.sourceKind, "app-directory");
|
|
119
119
|
assert.equal(summary.legacy, false);
|
|
120
|
-
assert.equal(summary.version, "0.2.
|
|
120
|
+
assert.equal(summary.version, "0.2.3");
|
|
121
121
|
|
|
122
122
|
const validation = runJson(["app", "validate", manifestPath]);
|
|
123
123
|
assert.equal(validation.valid, true, `${app.id} manifest must validate`);
|
|
124
124
|
|
|
125
125
|
const shown = runJson(["app", "show", app.id]);
|
|
126
126
|
assert.equal(shown.app.id, app.id);
|
|
127
|
-
assert.equal(shown.app.version, "0.2.
|
|
127
|
+
assert.equal(shown.app.version, "0.2.3");
|
|
128
128
|
assert.ok(shown.app.metadata.canonical, `${app.id} must be marked canonical`);
|
|
129
129
|
assert.ok(shown.app.sandboxProfiles.length > 0, `${app.id} must declare sandbox profiles`);
|
|
130
130
|
assertTaskIdsUnique(shown);
|
|
@@ -135,7 +135,7 @@ function main() {
|
|
|
135
135
|
const plan = runJson(["plan", app.id, ...app.args(workspace)]);
|
|
136
136
|
const state = JSON.parse(fs.readFileSync(plan.statePath, "utf8"));
|
|
137
137
|
assert.equal(state.workflow.app.id, app.id);
|
|
138
|
-
assert.equal(state.workflow.app.version, "0.2.
|
|
138
|
+
assert.equal(state.workflow.app.version, "0.2.3");
|
|
139
139
|
assert.equal(state.workflow.app.metadata.canonical, true);
|
|
140
140
|
assert.ok(state.tasks.some((task) => task.requiresEvidence), `${app.id} plan must include evidence gates`);
|
|
141
141
|
assert.ok(state.tasks.every((task) => task.sandboxProfileId), `${app.id} plan must include sandbox hints`);
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
//
|
|
9
9
|
// Reads jobs JSON on stdin, spawns ALL concurrently (shell:false, inherited env —
|
|
10
10
|
// the agent's own credentials resolve; CW never reads them), per-job SIGTERM at
|
|
11
|
-
// timeoutMs + SIGKILL at +5s, caps each captured stdout at 32MB
|
|
11
|
+
// timeoutMs + SIGKILL at +5s, caps each captured stdout at 32MB RAW and each
|
|
12
|
+
// written NDJSON line at 33MB SERIALIZED (see LINE_CAP below). Streams ONE
|
|
12
13
|
// NDJSON line per job — `{i, spawnError?, exitCode, stdout}\n` — the INSTANT
|
|
13
14
|
// that job settles (not once at the end): the parent's spawnSync call has its
|
|
14
15
|
// own combined-output cap, so writing incrementally means a job whose line
|
|
@@ -21,6 +22,34 @@
|
|
|
21
22
|
//
|
|
22
23
|
// THE RED LINE: this child only `spawn`s the operator-resolved agent binary with
|
|
23
24
|
// shell:false. It imports NO model SDK and reads NO credentials.
|
|
25
|
+
//
|
|
26
|
+
// A direct kill of THIS process (e.g. the parent's spawnSync `timeout` option,
|
|
27
|
+
// or a signal that reaches this child but not the grandchildren it spawned)
|
|
28
|
+
// used to orphan every still-running job -- no SIGINT/SIGTERM handler existed
|
|
29
|
+
// here, so the kernel default (instant termination) left them running with no
|
|
30
|
+
// one left to reap them. Now the first stop signal forwards SIGTERM to every
|
|
31
|
+
// still-tracked child (same escalation shape the per-job timeout below
|
|
32
|
+
// already uses) and lets each one's own `close` handler settle it normally;
|
|
33
|
+
// a second signal escalates straight to SIGKILL and exits immediately.
|
|
34
|
+
const children = new Set();
|
|
35
|
+
function killAllChildren(signal) {
|
|
36
|
+
for (const child of children) {
|
|
37
|
+
try { child.kill(signal); } catch {}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
let stopSignalReceived = false;
|
|
41
|
+
function onStopSignal(signal) {
|
|
42
|
+
if (stopSignalReceived) {
|
|
43
|
+
killAllChildren("SIGKILL");
|
|
44
|
+
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
stopSignalReceived = true;
|
|
48
|
+
killAllChildren("SIGTERM");
|
|
49
|
+
setTimeout(() => killAllChildren("SIGKILL"), 5000).unref();
|
|
50
|
+
}
|
|
51
|
+
process.on("SIGINT", () => onStopSignal("SIGINT"));
|
|
52
|
+
process.on("SIGTERM", () => onStopSignal("SIGTERM"));
|
|
24
53
|
|
|
25
54
|
const { spawn } = require("node:child_process");
|
|
26
55
|
let raw = "";
|
|
@@ -37,6 +66,16 @@ process.stdin.on("end", () => {
|
|
|
37
66
|
}
|
|
38
67
|
if (!jobs.length) { process.stdout.write("[]"); return; }
|
|
39
68
|
const CAP = 32 * 1024 * 1024;
|
|
69
|
+
// The parent (runAgentBatchOutcomes) grants maxBuffer = 34MB PER JOB for
|
|
70
|
+
// the COMBINED NDJSON stream it captures from this child. CAP above bounds
|
|
71
|
+
// each job's RAW stdout bytes, but the line written by settle() is the
|
|
72
|
+
// ESCAPED serialization — JSON escaping grows bytes (quotes and
|
|
73
|
+
// backslashes 2x, control chars up to 6x as \uXXXX), so a raw-capped
|
|
74
|
+
// stdout could still serialize into a line far past the parent's per-job
|
|
75
|
+
// budget, push the combined stream over maxBuffer, and ENOBUFS the WHOLE
|
|
76
|
+
// batch. LINE_CAP bounds the SERIALIZED line itself, held under the
|
|
77
|
+
// parent's 34MB per-job grant with a small safety margin.
|
|
78
|
+
const LINE_CAP = 33 * 1024 * 1024;
|
|
40
79
|
jobs.forEach((job, i) => {
|
|
41
80
|
let stdout = "";
|
|
42
81
|
let stdoutBytes = 0;
|
|
@@ -45,7 +84,15 @@ process.stdin.on("end", () => {
|
|
|
45
84
|
const settle = (o) => {
|
|
46
85
|
if (settled) return;
|
|
47
86
|
settled = true;
|
|
48
|
-
|
|
87
|
+
let line = JSON.stringify({ i, ...o }) + "\n";
|
|
88
|
+
const lineBytes = Buffer.byteLength(line);
|
|
89
|
+
if (lineBytes > LINE_CAP) {
|
|
90
|
+
// Same fail-closed shape as the raw-cap path in the close handler
|
|
91
|
+
// below: capped output is never evidence, so drop it entirely and
|
|
92
|
+
// name the cap — never ship a line the parent's buffer cannot hold.
|
|
93
|
+
line = JSON.stringify({ i, spawnError: `serialized stdout line exceeded ${LINE_CAP} byte cap (${lineBytes} bytes)`, exitCode: null, stdout: "" }) + "\n";
|
|
94
|
+
}
|
|
95
|
+
process.stdout.write(line);
|
|
49
96
|
};
|
|
50
97
|
let child;
|
|
51
98
|
try {
|
|
@@ -54,6 +101,7 @@ process.stdin.on("end", () => {
|
|
|
54
101
|
settle({ spawnError: String((error && error.message) || error), exitCode: null, stdout: "" });
|
|
55
102
|
return;
|
|
56
103
|
}
|
|
104
|
+
children.add(child);
|
|
57
105
|
const term = setTimeout(() => { try { child.kill("SIGTERM"); } catch {} }, job.timeoutMs);
|
|
58
106
|
const kill = setTimeout(() => { try { child.kill("SIGKILL"); } catch {} }, job.timeoutMs + 5000);
|
|
59
107
|
child.stdout.on("data", (d) => {
|
|
@@ -71,10 +119,12 @@ process.stdin.on("end", () => {
|
|
|
71
119
|
child.stderr.on("data", () => {});
|
|
72
120
|
child.on("error", (error) => {
|
|
73
121
|
clearTimeout(term); clearTimeout(kill);
|
|
122
|
+
children.delete(child);
|
|
74
123
|
settle({ spawnError: String((error && error.message) || error), exitCode: null, stdout });
|
|
75
124
|
});
|
|
76
125
|
child.on("close", (code) => {
|
|
77
126
|
clearTimeout(term); clearTimeout(kill);
|
|
127
|
+
children.delete(child);
|
|
78
128
|
if (stdoutTruncated) {
|
|
79
129
|
settle({ spawnError: `stdout exceeded ${CAP} byte cap (${stdoutBytes} bytes)`, exitCode: null, stdout: "" });
|
|
80
130
|
return;
|
|
@@ -6,7 +6,7 @@ const fs = require("node:fs");
|
|
|
6
6
|
const path = require("node:path");
|
|
7
7
|
const { CoolWorkflowRunner } = require("../dist/shell/orchestrator.js");
|
|
8
8
|
|
|
9
|
-
const TARGET_VERSION = "0.2.
|
|
9
|
+
const TARGET_VERSION = "0.2.3";
|
|
10
10
|
const PREVIOUS_VERSION = "0.1.31";
|
|
11
11
|
const pluginRoot = path.resolve(__dirname, "..");
|
|
12
12
|
const repoRoot = path.resolve(pluginRoot, "..", "..");
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// fake-date-for-reproduction.js — a Node `--require` preload used ONLY by
|
|
4
|
+
// verify-bump-reproduction.sh. It pins `new Date()` (no-args construction)
|
|
5
|
+
// and `Date.now()` to a fixed instant, read from CW_FAKE_DATE (an ISO date or
|
|
6
|
+
// YYYY-MM-DD string). `new Date(explicit args)` is left untouched.
|
|
7
|
+
//
|
|
8
|
+
// WHY a runtime-level override, not an application-level env var: the
|
|
9
|
+
// scratch worktree checks out the APPROVED PARENT commit and runs ITS OWN
|
|
10
|
+
// copy of bump-version.js/sync-project-index.js — which, for every commit
|
|
11
|
+
// that predates this mechanism (i.e. every real release ever cut so far),
|
|
12
|
+
// has no idea any such override exists. An app-level env var a script has to
|
|
13
|
+
// explicitly read is a no-op against code that doesn't know to read it — it
|
|
14
|
+
// can only ever help releases cut AFTER the mechanism itself lands, making
|
|
15
|
+
// "reproduce a past release" true only by calendar coincidence (confirmed:
|
|
16
|
+
// re-running v0.2.0's real approved-parent/tagged pair on a day other than
|
|
17
|
+
// its actual cut day fails with a date-only diff in project-index.md).
|
|
18
|
+
// Intercepting the Date GLOBAL, before any application code runs, works
|
|
19
|
+
// identically regardless of which script version executes — old or new.
|
|
20
|
+
//
|
|
21
|
+
// Usage: NODE_OPTIONS="--require /path/to/this/file.js" CW_FAKE_DATE=2026-07-05 <command>
|
|
22
|
+
|
|
23
|
+
const pinned = process.env.CW_FAKE_DATE;
|
|
24
|
+
if (pinned) {
|
|
25
|
+
const fixedMs = new Date(pinned).getTime();
|
|
26
|
+
if (Number.isNaN(fixedMs)) {
|
|
27
|
+
process.stderr.write(`fake-date-for-reproduction: CW_FAKE_DATE is not a valid date: ${pinned}\n`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
const RealDate = Date;
|
|
31
|
+
class FakeDate extends RealDate {
|
|
32
|
+
constructor(...args) {
|
|
33
|
+
if (args.length === 0) {
|
|
34
|
+
super(fixedMs);
|
|
35
|
+
} else {
|
|
36
|
+
super(...args);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
static now() {
|
|
40
|
+
return fixedMs;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
global.Date = FakeDate;
|
|
44
|
+
}
|
package/scripts/golden-path.js
CHANGED
|
@@ -33,7 +33,7 @@ function main() {
|
|
|
33
33
|
const appValidation = runJson(["app", "validate", "end-to-end-golden-path"], pluginRoot);
|
|
34
34
|
assert.equal(appValidation.valid, true);
|
|
35
35
|
assert.equal(appValidation.summary.id, "end-to-end-golden-path");
|
|
36
|
-
assert.equal(appValidation.summary.version, "0.2.
|
|
36
|
+
assert.equal(appValidation.summary.version, "0.2.3");
|
|
37
37
|
|
|
38
38
|
const plan = runJson(
|
|
39
39
|
[
|
|
@@ -42,7 +42,7 @@ function main() {
|
|
|
42
42
|
"--repo",
|
|
43
43
|
tmp,
|
|
44
44
|
"--question",
|
|
45
|
-
"Prove the deterministic v0.2.
|
|
45
|
+
"Prove the deterministic v0.2.3 end-to-end golden path."
|
|
46
46
|
],
|
|
47
47
|
pluginRoot
|
|
48
48
|
);
|
|
@@ -52,7 +52,7 @@ function main() {
|
|
|
52
52
|
|
|
53
53
|
let state = readJson(plan.statePath);
|
|
54
54
|
assert.equal(state.workflow.app.id, "end-to-end-golden-path");
|
|
55
|
-
assert.equal(state.workflow.app.version, "0.2.
|
|
55
|
+
assert.equal(state.workflow.app.version, "0.2.3");
|
|
56
56
|
assert.equal(state.loopStage, "interpret");
|
|
57
57
|
|
|
58
58
|
const dispatch = runJson(["dispatch", plan.runId, "--limit", "1", "--sandbox", "readonly"], tmp);
|
|
@@ -195,7 +195,7 @@ function main() {
|
|
|
195
195
|
assert.equal(reportPath, plan.reportPath);
|
|
196
196
|
assert.ok(fs.existsSync(reportPath));
|
|
197
197
|
const report = fs.readFileSync(reportPath, "utf8");
|
|
198
|
-
assert.match(report, /Workflow App: end-to-end-golden-path@0\.2\.
|
|
198
|
+
assert.match(report, /Workflow App: end-to-end-golden-path@0\.2\.3/);
|
|
199
199
|
assert.match(report, /## Candidates/);
|
|
200
200
|
assert.match(report, /## Trust Audit/);
|
|
201
201
|
assert.match(report, /## Acceptance Rationale/);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"builtinViolations": {
|
|
3
|
+
"core/state/run-paths.ts": [
|
|
4
|
+
"node:fs"
|
|
5
|
+
],
|
|
6
|
+
"core/trust/telemetry-attestation.ts": [
|
|
7
|
+
"node:fs"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
"clockEnvCounts": {
|
|
11
|
+
"core/multi-agent/candidate-scoring.ts": {
|
|
12
|
+
"new Date()": 1
|
|
13
|
+
},
|
|
14
|
+
"core/pipeline/runner.ts": {
|
|
15
|
+
"new Date()": 1
|
|
16
|
+
},
|
|
17
|
+
"core/state/migrations.ts": {
|
|
18
|
+
"process.cwd()": 1
|
|
19
|
+
},
|
|
20
|
+
"core/state/state-explosion/digest.ts": {
|
|
21
|
+
"new Date()": 1
|
|
22
|
+
},
|
|
23
|
+
"core/state/state-explosion/graph.ts": {
|
|
24
|
+
"new Date()": 1
|
|
25
|
+
},
|
|
26
|
+
"core/state/state-explosion/report.ts": {
|
|
27
|
+
"new Date()": 1
|
|
28
|
+
},
|
|
29
|
+
"core/state/state-node.ts": {
|
|
30
|
+
"new Date()": 5
|
|
31
|
+
},
|
|
32
|
+
"core/trust/evidence-grounding.ts": {
|
|
33
|
+
"process.env": 3
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"layerViolations": {
|
|
37
|
+
"core/capability-table.ts": [
|
|
38
|
+
"../wiring/capability-table"
|
|
39
|
+
],
|
|
40
|
+
"wiring/capability-table/basics.ts": [
|
|
41
|
+
"../../cli/io"
|
|
42
|
+
],
|
|
43
|
+
"wiring/capability-table/exec-backend.ts": [
|
|
44
|
+
"../../cli/io"
|
|
45
|
+
],
|
|
46
|
+
"wiring/capability-table/multi-agent.ts": [
|
|
47
|
+
"../../cli/io"
|
|
48
|
+
],
|
|
49
|
+
"wiring/capability-table/pipeline.ts": [
|
|
50
|
+
"../../cli/io"
|
|
51
|
+
],
|
|
52
|
+
"wiring/capability-table/registry-core.ts": [
|
|
53
|
+
"../../cli/io"
|
|
54
|
+
],
|
|
55
|
+
"wiring/capability-table/reporting.ts": [
|
|
56
|
+
"../../cli/io"
|
|
57
|
+
],
|
|
58
|
+
"wiring/capability-table/scheduling-registry.ts": [
|
|
59
|
+
"../../cli/io"
|
|
60
|
+
],
|
|
61
|
+
"wiring/capability-table/state.ts": [
|
|
62
|
+
"../../cli/io"
|
|
63
|
+
],
|
|
64
|
+
"wiring/capability-table/trust-ledger.ts": [
|
|
65
|
+
"../../cli/io"
|
|
66
|
+
],
|
|
67
|
+
"wiring/capability-table/workflow-apps.ts": [
|
|
68
|
+
"../../cli/io"
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
}
|