cool-workflow 0.1.97 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +11 -2
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/end-to-end-golden-path/app.json +1 -1
- package/apps/pr-review-fix-ci/app.json +1 -1
- package/apps/release-cut/app.json +1 -1
- package/apps/research-synthesis/app.json +1 -1
- package/dist/cli/dispatch.js +236 -0
- package/dist/cli/entry.js +120 -0
- package/dist/cli/io.js +21 -4
- package/dist/cli/parseargv.js +157 -0
- package/dist/cli.js +6 -33
- package/dist/core/capability-table.js +3534 -0
- package/dist/core/format/help.js +314 -0
- package/dist/{state-explosion/format.js → core/format/state-explosion-text.js} +10 -1
- package/dist/core/hash.js +137 -0
- package/dist/core/multi-agent/candidate-scoring.js +219 -0
- package/dist/core/multi-agent/collaboration.js +481 -0
- package/dist/core/multi-agent/coordinator.js +515 -0
- package/dist/core/multi-agent/eval-replay.js +306 -0
- package/dist/core/multi-agent/runtime.js +929 -0
- package/dist/core/multi-agent/topology.js +298 -0
- package/dist/core/multi-agent/trust-policy.js +197 -0
- package/dist/core/pipeline/commit-gate.js +320 -0
- package/dist/{pipeline-contract.js → core/pipeline/contract.js} +24 -37
- package/dist/core/pipeline/dispatch.js +103 -0
- package/dist/core/pipeline/drive-decide.js +227 -0
- package/dist/core/pipeline/error-feedback.js +161 -0
- package/dist/core/pipeline/loop-expansion.js +124 -0
- package/dist/{result-normalize.js → core/pipeline/result-normalize.js} +14 -37
- package/dist/core/pipeline/runner.js +230 -0
- package/dist/{contract-migration.js → core/state/contract-migration.js} +68 -92
- package/dist/{state-migrations.js → core/state/migrations.js} +103 -96
- package/dist/core/state/node-projection.js +95 -0
- package/dist/core/state/node-snapshot.js +230 -0
- package/dist/core/state/run-paths.js +93 -0
- package/dist/{schema-validate.js → core/state/schema-validate.js} +35 -28
- package/dist/core/state/schema.js +50 -0
- package/dist/core/state/state-explosion/digest.js +243 -0
- package/dist/core/state/state-explosion/graph.js +527 -0
- package/dist/{state-explosion → core/state/state-explosion}/helpers.js +34 -3
- package/dist/core/state/state-explosion/report.js +187 -0
- package/dist/{state-explosion → core/state/state-explosion}/size.js +25 -7
- package/dist/{state-node.js → core/state/state-node.js} +90 -113
- package/dist/core/state/types.js +19 -0
- package/dist/{validation.js → core/state/validation.js} +64 -131
- package/dist/core/trust/evidence-grounding.js +134 -0
- package/dist/core/trust/ledger.js +199 -0
- package/dist/{telemetry-attestation.js → core/trust/telemetry-attestation.js} +94 -68
- package/dist/core/trust/telemetry-ledger.js +127 -0
- package/dist/core/types.js +20 -0
- package/dist/core/version.js +16 -0
- package/dist/{workflow-app-framework.js → core/workflow-apps/app-schema.js} +337 -426
- package/dist/mcp/dispatch.js +104 -0
- package/dist/mcp/server.js +144 -0
- package/dist/mcp-server.js +6 -84
- package/dist/{agent-config.js → shell/agent-config.js} +118 -71
- package/dist/shell/app-run-cli.js +88 -0
- package/dist/shell/audit-cli.js +259 -0
- package/dist/shell/audit-provenance.js +83 -0
- package/dist/shell/candidate-scoring-io.js +459 -0
- package/dist/shell/collaboration-io.js +264 -0
- package/dist/shell/commit-summary.js +104 -0
- package/dist/shell/commit.js +290 -0
- package/dist/shell/coordinator-io.js +476 -0
- package/dist/shell/demo-cli.js +19 -0
- package/dist/shell/dispatch.js +162 -0
- package/dist/{doctor.js → shell/doctor.js} +123 -56
- package/dist/shell/drive.js +873 -0
- package/dist/shell/error-feedback-io.js +305 -0
- package/dist/shell/eval-io.js +473 -0
- package/dist/{multi-agent-eval/format.js → shell/eval-text.js} +78 -49
- package/dist/{evidence-reasoning.js → shell/evidence-reasoning.js} +124 -255
- package/dist/shell/exec-backend-cli.js +88 -0
- package/dist/shell/execution-backend/agent.js +475 -0
- package/dist/shell/execution-backend/ci.js +15 -0
- package/dist/shell/execution-backend/container.js +69 -0
- package/dist/shell/execution-backend/envelopes.js +55 -0
- package/dist/shell/execution-backend/local.js +113 -0
- package/dist/shell/execution-backend/probes.js +175 -0
- package/dist/shell/execution-backend/registry.js +402 -0
- package/dist/shell/execution-backend/remote.js +128 -0
- package/dist/shell/execution-backend/types.js +11 -0
- package/dist/shell/feedback-cli.js +81 -0
- package/dist/shell/feedback-operations.js +48 -0
- package/dist/shell/fs-atomic.js +276 -0
- package/dist/shell/harness.js +98 -0
- package/dist/shell/ledger-cli.js +212 -0
- package/dist/shell/ledger-io.js +169 -0
- package/dist/shell/man-cli.js +89 -0
- package/dist/shell/metrics-cli.js +98 -0
- package/dist/shell/multi-agent-cli.js +1002 -0
- package/dist/shell/multi-agent-host.js +563 -0
- package/dist/shell/multi-agent-io.js +387 -0
- package/dist/{multi-agent-operator-ux.js → shell/multi-agent-operator-ux.js} +234 -191
- package/dist/shell/node-store.js +124 -0
- package/dist/{observability/format.js → shell/observability-format.js} +7 -1
- package/dist/{observability/intake.js → shell/observability-intake.js} +79 -56
- package/dist/{observability.js → shell/observability.js} +159 -332
- package/dist/{onramp.js → shell/onramp.js} +11 -0
- package/dist/{operator-ux/format.js → shell/operator-ux-text.js} +250 -239
- package/dist/shell/operator-ux.js +431 -0
- package/dist/shell/orchestrator.js +231 -0
- package/dist/shell/pipeline-cli.js +667 -0
- package/dist/shell/pipeline.js +217 -0
- package/dist/shell/reclamation-io.js +1366 -0
- package/dist/shell/registry-cli.js +329 -0
- package/dist/{remote-source.js → shell/remote-source.js} +12 -5
- package/dist/shell/report-cli.js +101 -0
- package/dist/shell/report-view-cli.js +117 -0
- package/dist/{orchestrator → shell}/report.js +289 -282
- package/dist/shell/reporter.js +62 -0
- package/dist/shell/run-export-cli.js +106 -0
- package/dist/shell/run-export.js +680 -0
- package/dist/shell/run-registry-io.js +1014 -0
- package/dist/shell/run-store.js +164 -0
- package/dist/{sandbox-profile.js → shell/sandbox-profile.js} +134 -95
- package/dist/{scheduler.js → shell/scheduler-io.js} +248 -48
- package/dist/shell/scheduling-io.js +311 -0
- package/dist/shell/state-cli.js +181 -0
- package/dist/shell/state-explosion-cli.js +197 -0
- package/dist/shell/telemetry-cli.js +85 -0
- package/dist/{telemetry-demo.js → shell/telemetry-demo.js} +149 -119
- package/dist/shell/telemetry-ledger-io.js +132 -0
- package/dist/{term.js → shell/term.js} +36 -46
- package/dist/shell/topology-io.js +361 -0
- package/dist/shell/trust-audit.js +471 -0
- package/dist/{multi-agent-trust.js → shell/trust-policy-io.js} +67 -205
- package/dist/shell/verifier.js +48 -0
- package/dist/shell/workbench-host.js +250 -0
- package/dist/shell/workbench-text.js +18 -0
- package/dist/shell/workbench.js +175 -0
- package/dist/shell/worker-cli.js +124 -0
- package/dist/shell/worker-isolation.js +852 -0
- package/dist/shell/workflow-app-loader.js +650 -0
- package/docs/agent-delegation-drive.7.md +4 -0
- package/docs/cli-mcp-parity.7.md +284 -211
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +4 -0
- package/docs/cross-agent-ledger.7.md +217 -0
- package/docs/designs/handoff-ledger.md +145 -0
- package/docs/durable-state-and-locking.7.md +4 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
- package/docs/execution-backends.7.md +4 -0
- package/docs/handoff-setup.md +120 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
- package/docs/multi-agent-eval-replay-harness.7.md +4 -0
- package/docs/multi-agent-operator-ux.7.md +4 -0
- package/docs/node-snapshot-diff-replay.7.md +4 -0
- package/docs/observability-cost-accounting.7.md +4 -0
- package/docs/project-index.md +143 -71
- package/docs/real-execution-backends.7.md +4 -0
- package/docs/release-and-migration.7.md +4 -0
- package/docs/release-tooling.7.md +4 -0
- package/docs/run-registry-control-plane.7.md +4 -0
- package/docs/run-retention-reclamation.7.md +25 -0
- package/docs/state-explosion-management.7.md +4 -0
- package/docs/team-collaboration.7.md +4 -0
- package/docs/web-desktop-workbench.7.md +4 -0
- package/manifest/plugin.manifest.json +1 -1
- package/manifest/source-context-profiles.json +9 -13
- package/package.json +1 -1
- package/scripts/agents/codex-agent.js +34 -4
- package/scripts/agents/cw-attest-wrap.js +2 -2
- package/scripts/bump-version.js +4 -3
- package/scripts/canonical-apps.js +4 -4
- package/scripts/children/batch-delegate-child.js +30 -10
- package/scripts/dogfood-architecture-review.js +2 -3
- package/scripts/dogfood-release.js +3 -3
- package/scripts/gen-parity-doc.js +15 -2
- package/scripts/golden-path.js +4 -4
- package/scripts/onramp-check.js +1 -1
- package/scripts/parity-check.js +38 -21
- package/scripts/release-flow.js +9 -3
- package/scripts/sync-project-index.js +51 -27
- package/scripts/validate-run-state-schema.js +2 -2
- package/scripts/version-sync-check.js +30 -30
- package/dist/candidate-scoring.js +0 -729
- package/dist/capability-core.js +0 -1186
- package/dist/capability-registry.js +0 -879
- package/dist/cli/command-surface.js +0 -490
- package/dist/cli/format.js +0 -56
- package/dist/cli/handlers/audit.js +0 -82
- package/dist/cli/handlers/blackboard.js +0 -81
- package/dist/cli/handlers/candidate.js +0 -40
- package/dist/cli/handlers/clones.js +0 -34
- package/dist/cli/handlers/collaboration.js +0 -61
- package/dist/cli/handlers/eval.js +0 -40
- package/dist/cli/handlers/maintenance.js +0 -107
- package/dist/cli/handlers/multi-agent.js +0 -165
- package/dist/cli/handlers/node.js +0 -41
- package/dist/cli/handlers/operational.js +0 -155
- package/dist/cli/handlers/operator.js +0 -146
- package/dist/cli/handlers/registry.js +0 -68
- package/dist/cli/handlers/run.js +0 -153
- package/dist/cli/handlers/scheduling.js +0 -132
- package/dist/cli/handlers/workbench.js +0 -41
- package/dist/cli/handlers/worker.js +0 -45
- package/dist/cli/run-summary.js +0 -45
- package/dist/clones.js +0 -162
- package/dist/collaboration.js +0 -726
- package/dist/commit.js +0 -592
- package/dist/compare.js +0 -18
- package/dist/coordinator/classify.js +0 -45
- package/dist/coordinator/paths.js +0 -42
- package/dist/coordinator/util.js +0 -126
- package/dist/coordinator.js +0 -990
- package/dist/daemon.js +0 -44
- package/dist/dispatch.js +0 -250
- package/dist/drive.js +0 -827
- package/dist/error-feedback.js +0 -419
- package/dist/evidence-grounding.js +0 -184
- package/dist/execution-backend/agent.js +0 -294
- package/dist/execution-backend/probes.js +0 -112
- package/dist/execution-backend/util.js +0 -47
- package/dist/execution-backend.js +0 -961
- package/dist/gates.js +0 -48
- package/dist/harness.js +0 -61
- package/dist/loop-expansion.js +0 -60
- package/dist/mcp/tool-call.js +0 -434
- package/dist/mcp/tool-definitions.js +0 -1040
- package/dist/mcp-surface.js +0 -30
- package/dist/multi-agent/graph.js +0 -84
- package/dist/multi-agent/helpers.js +0 -141
- package/dist/multi-agent/ids.js +0 -20
- package/dist/multi-agent/paths.js +0 -22
- package/dist/multi-agent-eval/normalize.js +0 -51
- package/dist/multi-agent-eval.js +0 -678
- package/dist/multi-agent-host.js +0 -777
- package/dist/multi-agent.js +0 -984
- package/dist/node-projection.js +0 -59
- package/dist/node-snapshot.js +0 -260
- package/dist/operator-ux.js +0 -631
- package/dist/orchestrator/app-operations.js +0 -211
- package/dist/orchestrator/audit-operations.js +0 -182
- package/dist/orchestrator/candidate-operations.js +0 -117
- package/dist/orchestrator/cli-options.js +0 -294
- package/dist/orchestrator/collaboration-operations.js +0 -86
- package/dist/orchestrator/feedback-operations.js +0 -81
- package/dist/orchestrator/host-operations.js +0 -78
- package/dist/orchestrator/lifecycle-operations.js +0 -626
- package/dist/orchestrator/migration-operations.js +0 -44
- package/dist/orchestrator/multi-agent-operations.js +0 -362
- package/dist/orchestrator/topology-operations.js +0 -84
- package/dist/orchestrator.js +0 -917
- package/dist/pipeline-runner.js +0 -285
- package/dist/reclamation/hash.js +0 -72
- package/dist/reclamation.js +0 -812
- package/dist/reporter.js +0 -67
- package/dist/run-export.js +0 -784
- package/dist/run-registry/derive.js +0 -175
- package/dist/run-registry/format.js +0 -124
- package/dist/run-registry/gc.js +0 -251
- package/dist/run-registry/policy.js +0 -16
- package/dist/run-registry/queue.js +0 -115
- package/dist/run-registry.js +0 -850
- package/dist/run-state-schema.js +0 -68
- package/dist/scheduling.js +0 -184
- package/dist/state-explosion.js +0 -1014
- package/dist/state.js +0 -367
- package/dist/telemetry-ledger.js +0 -196
- package/dist/topology.js +0 -565
- package/dist/triggers.js +0 -184
- package/dist/trust-audit.js +0 -644
- package/dist/types/blackboard.js +0 -2
- package/dist/types/candidate.js +0 -2
- package/dist/types/collaboration.js +0 -2
- package/dist/types/core.js +0 -2
- package/dist/types/drive.js +0 -10
- package/dist/types/error-feedback.js +0 -2
- package/dist/types/evidence-reasoning.js +0 -2
- package/dist/types/execution-backend.js +0 -2
- package/dist/types/multi-agent.js +0 -2
- package/dist/types/observability.js +0 -2
- package/dist/types/pipeline.js +0 -2
- package/dist/types/reclamation.js +0 -8
- package/dist/types/report-bundle.js +0 -6
- package/dist/types/result.js +0 -2
- package/dist/types/run-registry.js +0 -2
- package/dist/types/run.js +0 -2
- package/dist/types/sandbox.js +0 -2
- package/dist/types/schedule.js +0 -2
- package/dist/types/state-node.js +0 -2
- package/dist/types/topology.js +0 -2
- package/dist/types/trust.js +0 -2
- package/dist/types/workbench.js +0 -2
- package/dist/types/worker.js +0 -2
- package/dist/types/workflow-app.js +0 -2
- package/dist/types.js +0 -44
- package/dist/util/fingerprint.js +0 -19
- package/dist/util/fingerprint.test.js +0 -27
- package/dist/verifier.js +0 -78
- package/dist/version.js +0 -8
- package/dist/workbench-host.js +0 -182
- package/dist/workbench.js +0 -192
- package/dist/worker-accept/acceptance.js +0 -114
- package/dist/worker-accept/blackboard-fanout.js +0 -80
- package/dist/worker-accept/blackboard-linkage.js +0 -19
- package/dist/worker-accept/context.js +0 -2
- package/dist/worker-accept/telemetry-ledger.js +0 -126
- package/dist/worker-accept/validation.js +0 -77
- package/dist/worker-accept/verifier-completion.js +0 -73
- package/dist/worker-isolation/helpers.js +0 -51
- package/dist/worker-isolation/paths.js +0 -46
- package/dist/worker-isolation.js +0 -656
- package/dist/workflow-api.js +0 -131
- /package/dist/{types → core/types}/boundary.js +0 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Cross-Agent Handoff Ledger
|
|
2
|
+
|
|
3
|
+
CW adds `cw ledger` — a way for two agents scoped to two separate repos to hand
|
|
4
|
+
each other a CHANGE PROPOSAL or a REVIEW VERDICT as verifiable data, not chat.
|
|
5
|
+
One side proposes or reviews; the other side verifies the entry fail-closed and
|
|
6
|
+
turns a proposal into a real pull request. Design notes:
|
|
7
|
+
[handoff-ledger](designs/handoff-ledger.md).
|
|
8
|
+
|
|
9
|
+
Each entry is a self-contained JSON object that carries its own sha256 content
|
|
10
|
+
digest. The producing side prints one; it reaches the other session by human
|
|
11
|
+
relay or a shared git repo (below); the consuming side runs `cw ledger verify`
|
|
12
|
+
(one entry) or `cw ledger list` (a whole directory) before acting. A tampered or
|
|
13
|
+
malformed entry is refused with a non-zero exit, so
|
|
14
|
+
`cw ledger verify <file> && open-pr` can never proceed on a lie.
|
|
15
|
+
|
|
16
|
+
Every verb is on both surfaces: the CLI (`cw ledger ...`) and MCP
|
|
17
|
+
(`cw_ledger_propose`, `cw_ledger_review`, `cw_ledger_verify`, `cw_ledger_list`),
|
|
18
|
+
so an agent can mint and check entries in-process, not only from a shell.
|
|
19
|
+
|
|
20
|
+
`cw ledger` is a NEW verb. It does not touch `cw handoff`, which is a separate
|
|
21
|
+
collaboration primitive (ownership transfer of a run/task) — see
|
|
22
|
+
[team-collaboration](team-collaboration.7.md).
|
|
23
|
+
|
|
24
|
+
## Why a ledger, not a shared folder
|
|
25
|
+
|
|
26
|
+
The two agents run as two separate cloud sessions. They share no filesystem, and
|
|
27
|
+
each is scoped to one repo at launch, so a local folder cannot be the channel.
|
|
28
|
+
The only medium both sides can durably reach is git/GitHub. `cw ledger` therefore
|
|
29
|
+
produces and consumes portable, self-verifying entries; how an entry crosses
|
|
30
|
+
(operator relay now, a shared handoff repo later) is transport, kept separate
|
|
31
|
+
from the verb.
|
|
32
|
+
|
|
33
|
+
## Mechanism vs policy
|
|
34
|
+
|
|
35
|
+
The MECHANISM is small and lives in the kernel (`src/ledger.ts`): build a
|
|
36
|
+
proposal or a review entry, seal it with a sha256 digest over its canonical
|
|
37
|
+
content, and verify that digest fail-closed. No run state, no network, no new
|
|
38
|
+
runtime dependency. The POLICY — which repos, who may propose, whether a verdict
|
|
39
|
+
blocks a merge — stays outside, in the operator's hands and the transport.
|
|
40
|
+
|
|
41
|
+
The digest is computed over a deterministic serialization (object keys sorted
|
|
42
|
+
recursively) of every field except `id` and `digest`, which are derived from it.
|
|
43
|
+
The `id` is content-addressed: `ldg-` + the first 16 hex chars of the digest.
|
|
44
|
+
|
|
45
|
+
## Commands
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
cw ledger propose --from <a> --to <b> --title <t> --rationale <r> \
|
|
49
|
+
[--files a.ts,b.ts] [--diff <patch>]
|
|
50
|
+
cw ledger review --from <a> --to <b> --target <proposal-id|pr-ref> \
|
|
51
|
+
--verdict <approved|rejected> [--findings "a,b"]
|
|
52
|
+
cw ledger verify [--file <path>] # else reads the entry from stdin
|
|
53
|
+
cw ledger apply [--file <path>] # verify a proposal, then print its diff
|
|
54
|
+
cw ledger list --dir <ledger-dir> [--dir <mirror-2> ...] # verify a dir (or union of mirrors)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
All write JSON to stdout (stdout is data). `propose` and `review` print a sealed
|
|
58
|
+
entry; `verify` prints a check report; `apply` prints a verify-plus-diff report;
|
|
59
|
+
`list` prints a per-entry report over a directory.
|
|
60
|
+
|
|
61
|
+
## Applying a proposal — fail-closed
|
|
62
|
+
|
|
63
|
+
A proposal carries a `suggestedDiff`, but a proposal never mutates the target
|
|
64
|
+
repo by itself: the write-capable side turns it into a real change. `cw ledger
|
|
65
|
+
apply` is the fail-closed bridge — it verifies the entry FIRST and only then
|
|
66
|
+
emits the diff, so an unverified patch can never reach `git`:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
cw ledger apply --file proposal.json | jq -r 'select(.ok).diff' | git apply -
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`apply` prints `{ ok, id, kind, diff }`. The `diff` is present **only** when
|
|
73
|
+
`ok` is `true` — a tampered entry (`ok:false`, the `verify` failure codes), a
|
|
74
|
+
review rather than a proposal (`ledger-not-a-proposal`), or a proposal with no
|
|
75
|
+
`suggestedDiff` (`ledger-empty-diff`) all yield `diff:null` and exit `1`. The
|
|
76
|
+
kernel never runs `git`; turning the verified diff into a commit stays the
|
|
77
|
+
operator's (or a wrapper's) step — mechanism, not policy.
|
|
78
|
+
|
|
79
|
+
## Git transport (T2a — shared handoff repo)
|
|
80
|
+
|
|
81
|
+
The two agents cannot share a local folder, but they can share a git repo both
|
|
82
|
+
are scoped to. The ledger rides on it with no git logic in the kernel — writing
|
|
83
|
+
is composition through files, and `git` is the operator's (or a wrapper's) step:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
# Producing side (cool-workflow), inside the shared repo's working tree:
|
|
87
|
+
cw ledger propose --from cool-workflow --to chime \
|
|
88
|
+
--title "Add retry" --rationale "flaky net" --files src/net.ts \
|
|
89
|
+
> ledger/$(cw ledger propose ... | jq -r .id).json # or any unique name
|
|
90
|
+
git add ledger/ && git commit -m "propose: add retry" && git push
|
|
91
|
+
|
|
92
|
+
# Consuming side (chime):
|
|
93
|
+
git pull
|
|
94
|
+
cw ledger list --dir ledger && echo "inbox verified — safe to act"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`cw ledger list` reads every `*.json` in the directory, verifies each entry, and
|
|
98
|
+
reports `allOk`. It is a **fail-closed inbox**: if any single entry is tampered,
|
|
99
|
+
malformed, or unreadable, `allOk` is `false` and the command exits `1`, so the
|
|
100
|
+
receiving side refuses the whole batch rather than acting on a mixed one.
|
|
101
|
+
|
|
102
|
+
### Inbox resolution — which proposals are still open
|
|
103
|
+
|
|
104
|
+
`cw ledger list` also derives a `resolution` summary so the inbox is
|
|
105
|
+
machine-actionable without opening each file. It pairs every proposal with the
|
|
106
|
+
review(s) whose `target` is that proposal's id and reports one of four states:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
"resolution": {
|
|
110
|
+
"proposals": [
|
|
111
|
+
{ "id": "ldg-1de7c92172af1871", "title": "Add retry", "resolution": "approved", "reviews": ["ldg-…"] },
|
|
112
|
+
{ "id": "ldg-…", "title": "Rename thing", "resolution": "pending", "reviews": [] }
|
|
113
|
+
],
|
|
114
|
+
"pending": 1, "approved": 1, "rejected": 0, "contested": 0
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
- `pending` — no verified review targets the proposal yet.
|
|
119
|
+
- `approved` / `rejected` — every verified review targeting it agrees.
|
|
120
|
+
- `contested` — verified reviews targeting it disagree (both an APPROVED and a
|
|
121
|
+
REJECTED exist); the ledger REPORTS the disagreement, it does not pick a
|
|
122
|
+
winner (mechanism, not policy — whether a verdict blocks a merge stays
|
|
123
|
+
outside).
|
|
124
|
+
|
|
125
|
+
Only **verified** entries take part: a tampered review can never resolve a
|
|
126
|
+
proposal, so a proposal answered only by a failing review stays `pending`
|
|
127
|
+
(fail-closed). The fields are additive — the existing `entries[]` / `allOk` /
|
|
128
|
+
`count` output is byte-unchanged (POLA), with each entry now also carrying its
|
|
129
|
+
`title` (proposals) or `target`/`verdict` (reviews). The same `resolution` rides
|
|
130
|
+
on the mirror-union output and on the `cw_ledger_list` MCP tool.
|
|
131
|
+
|
|
132
|
+
### Mirrors — union-verifying several directories
|
|
133
|
+
|
|
134
|
+
`--dir` is repeatable. With two or more, `cw ledger list` **union-verifies** the
|
|
135
|
+
directories as mirrors of one ledger (e.g. the same handoff repo cloned from a
|
|
136
|
+
GitHub remote and one or more self-hosted Gitea remotes in different places):
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
cw ledger list --dir gh/ledger --dir gitea-eu/ledger --dir gitea-asia/ledger
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The union is **conflict-free by construction**: entries are immutable and
|
|
143
|
+
content-addressed, so the same entry mirrored to several hosts collapses to one
|
|
144
|
+
result whose `dirs` records every mirror it was found in. It stays **fail-closed
|
|
145
|
+
across mirrors** — a tampered entry in ANY mirror sets `allOk:false` and exits
|
|
146
|
+
`1`. This is for redundancy and reachability, not load: the ledger's traffic is
|
|
147
|
+
tiny; multiple hosts guard against one being down or unreachable.
|
|
148
|
+
|
|
149
|
+
A single `--dir` keeps the original single-directory output (a `dir` field, no
|
|
150
|
+
`dirs`); two or more switch to the union shape (`dirs` plus a per-entry `dirs`).
|
|
151
|
+
The transport stays git-host-agnostic — adding a mirror is one more clone + one
|
|
152
|
+
more `--dir`, no code change.
|
|
153
|
+
|
|
154
|
+
## Entry shape
|
|
155
|
+
|
|
156
|
+
A proposal:
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
{
|
|
160
|
+
"kind": "proposal",
|
|
161
|
+
"schemaVersion": 1,
|
|
162
|
+
"from": "cool-workflow",
|
|
163
|
+
"to": "chime",
|
|
164
|
+
"title": "Add retry to the fetch path",
|
|
165
|
+
"rationale": "the network is flaky under load",
|
|
166
|
+
"targetFiles": ["src/net.ts"],
|
|
167
|
+
"suggestedDiff": "@@ ... @@",
|
|
168
|
+
"createdAt": "<iso>",
|
|
169
|
+
"id": "ldg-<16 hex>",
|
|
170
|
+
"digest": "sha256:<64 hex>"
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
A review is the same envelope with `kind: "review"`, plus `target` (the proposal
|
|
175
|
+
id or a PR ref), `verdict` (`APPROVED` | `REJECTED`), and `findings` (a list).
|
|
176
|
+
|
|
177
|
+
## Verification contract
|
|
178
|
+
|
|
179
|
+
`cw ledger verify` re-proves the entry and exits fail-closed:
|
|
180
|
+
|
|
181
|
+
- Not a JSON object, or non-JSON bytes → `ok:false`, code `ledger-bad-json` /
|
|
182
|
+
`ledger-not-object`.
|
|
183
|
+
- Unknown `kind`, wrong `schemaVersion`, missing digest, missing a required
|
|
184
|
+
field, or a bad `verdict` → `ok:false` with the matching code.
|
|
185
|
+
- Stored digest does not match a fresh digest of the content →
|
|
186
|
+
`ok:false`, code `ledger-digest-mismatch`.
|
|
187
|
+
- `id` is not the content-addressed id for the digest (spoofed or absent) →
|
|
188
|
+
`ok:false`, code `ledger-id-mismatch`. `id` is excluded from the digest, so it
|
|
189
|
+
is bound to the content by this check — a forged entry cannot set its `id` to
|
|
190
|
+
collide with a legitimate one and slip through the mirror-union de-duplication.
|
|
191
|
+
|
|
192
|
+
Any `ok:false` exits `1`. An intact entry exits `0` with `ok:true`.
|
|
193
|
+
|
|
194
|
+
## Example round-trip
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
# On the proposing side (cool-workflow):
|
|
198
|
+
cw ledger propose --from cool-workflow --to chime \
|
|
199
|
+
--title "Add retry" --rationale "flaky net" \
|
|
200
|
+
--files src/net.ts --diff "$(git diff)" > proposal.json
|
|
201
|
+
|
|
202
|
+
# The operator carries proposal.json to the chime session, which checks it:
|
|
203
|
+
cw ledger verify --file proposal.json && echo "safe to open a PR"
|
|
204
|
+
|
|
205
|
+
# The reviewing side hands a verdict back:
|
|
206
|
+
cw ledger review --from chime --to cool-workflow \
|
|
207
|
+
--target ldg-1de7c92172af1871 --verdict approved \
|
|
208
|
+
--findings "tests pass,scope ok" > verdict.json
|
|
209
|
+
cw ledger verify --file verdict.json
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Roadmap
|
|
213
|
+
|
|
214
|
+
Stage 1 shipped the CLI verbs (human relay). Stage 2 adds the MCP surface and
|
|
215
|
+
the git-as-ledger transport (`cw ledger list` over a shared repo). Still open:
|
|
216
|
+
the operator creates the shared handoff repo and scopes both agent environments
|
|
217
|
+
into it. See [handoff-ledger](designs/handoff-ledger.md).
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Design — Cross-agent handoff ledger
|
|
2
|
+
|
|
3
|
+
Status: DRAFT / proposal. Nothing here is built yet. This file ships no
|
|
4
|
+
behavior, no new command, no man-page contract, and changes no existing
|
|
5
|
+
output. It exists so two people (the operator and the reviewer agent) can
|
|
6
|
+
agree on the shape before any code is written.
|
|
7
|
+
|
|
8
|
+
North Star track: **Track B** (portable, verifiable state — the same
|
|
9
|
+
`run export` → `run restore` recovery story, now used as the channel
|
|
10
|
+
between two agents).
|
|
11
|
+
|
|
12
|
+
## Goal
|
|
13
|
+
|
|
14
|
+
Two agents work on two repositories:
|
|
15
|
+
|
|
16
|
+
- one agent scoped to repo **A** (for example `cool-workflow`),
|
|
17
|
+
- one agent scoped to repo **B** (for example `chime`).
|
|
18
|
+
|
|
19
|
+
The operator wants them to "share data, review each other, and each be
|
|
20
|
+
able to raise a pull request to the other". In plain terms:
|
|
21
|
+
|
|
22
|
+
- each side can hand the other a **change proposal**, and
|
|
23
|
+
- each side can hand the other a **review verdict** on a diff or PR,
|
|
24
|
+
- with saved, inspectable, fail-closed state — never a fabricated hand-off.
|
|
25
|
+
|
|
26
|
+
## The hard constraint (why the obvious design does not work)
|
|
27
|
+
|
|
28
|
+
The first idea is a shared local folder (for example `~/.chime/handoff/`)
|
|
29
|
+
that both agents read and append to. That only works when both agents run
|
|
30
|
+
on **one machine** with **one filesystem**.
|
|
31
|
+
|
|
32
|
+
In the operator's setup the two agents run as **two separate cloud
|
|
33
|
+
sessions**. Each session is a fresh, throwaway VM. Two facts follow, and
|
|
34
|
+
the design must respect both:
|
|
35
|
+
|
|
36
|
+
1. **No shared filesystem.** A file the B-agent writes to `~/.chime/handoff/`
|
|
37
|
+
in its VM is invisible to the A-agent's VM, and is gone when the session
|
|
38
|
+
ends. A local folder cannot be the channel.
|
|
39
|
+
2. **Single-repo scope.** Each session's GitHub reach is scoped to one repo
|
|
40
|
+
at launch (A-agent → repo A, B-agent → repo B). The A-agent cannot read
|
|
41
|
+
repo B through its GitHub tools, and the reverse is also true. Scope is
|
|
42
|
+
fixed at launch and cannot be widened mid-session.
|
|
43
|
+
|
|
44
|
+
The only medium both sessions can durably reach is **git / GitHub**. So the
|
|
45
|
+
ledger is a set of committed files, not a local folder — and the scope wall
|
|
46
|
+
means the hand-off still needs either a shared repo or a human relay for the
|
|
47
|
+
cross-repo step. This document is honest about that; it does not pretend the
|
|
48
|
+
wall is not there.
|
|
49
|
+
|
|
50
|
+
## What we reuse (no new trust machinery)
|
|
51
|
+
|
|
52
|
+
CW already has the parts this needs. The design adds a thin verb layer over
|
|
53
|
+
them; it invents no new crypto and no new state format.
|
|
54
|
+
|
|
55
|
+
- `run export` produces a **verifiable bundle** (file digests, telemetry
|
|
56
|
+
ledger, trust-audit hash chains).
|
|
57
|
+
- `run restore` **imports fail-closed**: it inspects first, refuses a corrupt
|
|
58
|
+
or tampered bundle without writing anything, and exits non-zero when the
|
|
59
|
+
chain does not verify. (`run import` is the exit-0 sibling; the hand-off
|
|
60
|
+
path must use the fail-closed `restore` contract.)
|
|
61
|
+
- `report verify` checks a run's evidence and citations.
|
|
62
|
+
|
|
63
|
+
A hand-off entry is therefore just a CW bundle. The receiving side trusts it
|
|
64
|
+
the same way it trusts any restored run: by verification, not by good faith.
|
|
65
|
+
|
|
66
|
+
## Two verbs
|
|
67
|
+
|
|
68
|
+
Both live under a single new `cw ledger` verb, so the existing surface is
|
|
69
|
+
untouched and the new behavior is opt-in (POLA). (The name `handoff` was already
|
|
70
|
+
taken by an unrelated collaboration primitive — ownership transfer of a run/task
|
|
71
|
+
— so the cross-agent verb is `ledger`, not `handoff`.) Stage 1 ships as
|
|
72
|
+
`cw ledger propose|review|verify`; see
|
|
73
|
+
[cross-agent-ledger](../cross-agent-ledger.7.md) for the contract.
|
|
74
|
+
|
|
75
|
+
- **`propose`** — the read-only side writes a structured *change proposal*
|
|
76
|
+
(title, rationale, target files, suggested diff) as a ledger entry. It does
|
|
77
|
+
**not** mutate the other repo. The write-capable side picks the entry up,
|
|
78
|
+
verifies it, and turns it into a **real GitHub pull request**.
|
|
79
|
+
- **`review`** — the reviewing side writes a structured *review verdict*
|
|
80
|
+
(`APPROVED` / `REJECTED`, findings, the diff or PR it judged) as a ledger
|
|
81
|
+
entry. The other side surfaces it and can act on it.
|
|
82
|
+
|
|
83
|
+
This keeps a read-only agent honest: it emits proposals and verdicts as
|
|
84
|
+
**data**, and the write-capable side is the only one that opens PRs. Neither
|
|
85
|
+
side has to be trusted to have mutated the other's code.
|
|
86
|
+
|
|
87
|
+
## Transports (how an entry actually crosses)
|
|
88
|
+
|
|
89
|
+
The verbs above produce and consume entries; the transport is how an entry
|
|
90
|
+
moves from one VM to the other. Two are in scope, smallest first.
|
|
91
|
+
|
|
92
|
+
- **T1 — Human relay (MVP, works today, zero infra).** The producing side
|
|
93
|
+
prints the entry (a verifiable bundle, or its safe text form) to stdout;
|
|
94
|
+
the operator carries it to the other session; the consuming side verifies
|
|
95
|
+
it fail-closed and acts (opens the PR, or records the verdict). This is
|
|
96
|
+
exactly the loop the operator is already running by hand. It needs no new
|
|
97
|
+
code beyond a stable print/parse shape.
|
|
98
|
+
- **T2 — Git-as-ledger.** Each entry is committed to a repo under a known
|
|
99
|
+
path (for example `handoff/<from>-<to>/<id>.bundle`). Because scope is
|
|
100
|
+
single-repo, this needs one of:
|
|
101
|
+
- **T2a — a shared handoff repo** both agents are scoped to (cleanest, but
|
|
102
|
+
the operator must create it and launch both sessions against it), or
|
|
103
|
+
- **T2b — each side writes to its own repo** and a bridge (the operator, or
|
|
104
|
+
a scheduled job that *is* scoped to both) moves entries across. The
|
|
105
|
+
cross-repo read cannot be automatic inside a single scoped session — this
|
|
106
|
+
is the scope wall, stated plainly, not a gap to be quietly filled.
|
|
107
|
+
|
|
108
|
+
## Fail-closed rules (non-negotiable)
|
|
109
|
+
|
|
110
|
+
- An entry that does not verify is **refused**, never acted on. No PR is
|
|
111
|
+
opened, no verdict is recorded, and the refusal is explicit on stderr with
|
|
112
|
+
a non-zero exit — the same contract as `run restore`.
|
|
113
|
+
- A proposal is a **suggestion only**. It never edits the target repo by
|
|
114
|
+
itself; a human-or-agent on the write side always makes the real PR, so the
|
|
115
|
+
read-only vow of the proposing side holds.
|
|
116
|
+
- stdout stays data (the entry / the machine result); stderr stays
|
|
117
|
+
diagnostics; a piped run is silent on success. `--json` is stable and
|
|
118
|
+
decoration-free.
|
|
119
|
+
|
|
120
|
+
## Non-goals / POLA
|
|
121
|
+
|
|
122
|
+
- No existing command, output byte, exit code, or file layout changes.
|
|
123
|
+
- No new runtime dependency (zero-dependency red line holds).
|
|
124
|
+
- No vendor-specific logic in core; the verbs move opaque bundles.
|
|
125
|
+
- Nothing ships until its own cycle lands with a test that fails before and
|
|
126
|
+
passes after, and a `docs/*.7.md` contract page — this design file is not
|
|
127
|
+
that contract and claims no shipped behavior.
|
|
128
|
+
|
|
129
|
+
## Suggested rollout (each stage its own reviewed cycle)
|
|
130
|
+
|
|
131
|
+
0. **This design doc** (no behavior). ← you are here.
|
|
132
|
+
1. **T1 human-relay shape** — a stable, documented print/parse form for a
|
|
133
|
+
proposal and a verdict, plus a smoke that round-trips one of each and
|
|
134
|
+
proves a tampered entry is refused.
|
|
135
|
+
2. **`cw ledger propose` / `review`** over `run export` / `restore`, with the
|
|
136
|
+
fail-closed refusal test.
|
|
137
|
+
3. **T2 git-ledger** (shared-repo first), then optionally a scoped bridge job
|
|
138
|
+
for T2b.
|
|
139
|
+
|
|
140
|
+
## Open decisions for the operator
|
|
141
|
+
|
|
142
|
+
- T2a (shared handoff repo) or T2b (own repos + bridge)? T2a is simpler and
|
|
143
|
+
should be the default unless a shared repo is not acceptable.
|
|
144
|
+
- Should a verdict be able to **block** a PR merge on the other side, or only
|
|
145
|
+
advise? Advise-only is the safer default and matches "review as data".
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Handoff ledger — shared-repo setup (T2a)
|
|
2
|
+
|
|
3
|
+
How to stand up the shared repo that carries `cw ledger` entries between two
|
|
4
|
+
agents scoped to two separate repos (e.g. `cool-workflow` and `chime`), each
|
|
5
|
+
running in its own cloud session. The verbs are documented in
|
|
6
|
+
[cross-agent-ledger](cross-agent-ledger.7.md); this is the operator runbook.
|
|
7
|
+
|
|
8
|
+
Examples are portable — replace `<owner>`, `<src-repo>`, and the paths with your
|
|
9
|
+
own, and keep tokens in environment variables, never in files or commit text.
|
|
10
|
+
|
|
11
|
+
## What only the operator can do
|
|
12
|
+
|
|
13
|
+
- A cool-workflow-scoped web session cannot create the shared repo — the GitHub
|
|
14
|
+
integration returns `403 Resource not accessible by integration` for any repo
|
|
15
|
+
outside its scope. Create it yourself.
|
|
16
|
+
- Scoping the two agent environments (or granting them git credentials) is a
|
|
17
|
+
Claude Code web-UI step; it cannot be done from inside a session.
|
|
18
|
+
|
|
19
|
+
## Choosing a host: GitHub vs self-hosted (Gitea)
|
|
20
|
+
|
|
21
|
+
The transport is git-host-agnostic — the kernel has no git logic, so any git
|
|
22
|
+
remote works. The choice is about reachability and operations, not code.
|
|
23
|
+
|
|
24
|
+
| | GitHub (private repo) | Self-hosted Gitea (your VPS) |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| Reachability from cloud sessions | github.com is in the default **Trusted** allowlist — works with no network-policy change | Your VPS host is **not** in the default allowlist — the environment's network access must be configured to permit it |
|
|
27
|
+
| Scope wall | The GitHub MCP scope is per-repo; the ledger uses plain git (not MCP) so it works, but it runs against the grain of the scoping model | Not a GitHub repo at all, so the GitHub scope wall does not apply |
|
|
28
|
+
| Limits / quota | Disable Actions on this repo (it needs no CI) so it burns no minutes; git push/pull is not API-rate-limited; ledger traffic is tiny | Fully self-controlled, unlimited |
|
|
29
|
+
| Operations | Managed, backed up, zero maintenance | You run it: uptime, backups, TLS cert, patching |
|
|
30
|
+
| Data location | GitHub's servers (private) | Your own hardware |
|
|
31
|
+
|
|
32
|
+
**Recommendation.** Start on **GitHub private** — it is reachable out of the box
|
|
33
|
+
and the quota worry is practically moot for tiny ledger traffic. Move to **Gitea**
|
|
34
|
+
if you want full self-hosting AND have confirmed the cloud environment can reach
|
|
35
|
+
your VPS through its network policy (the deciding prerequisite). Migrating later
|
|
36
|
+
is only a change of git remote — no code change.
|
|
37
|
+
|
|
38
|
+
## GitHub private — setup
|
|
39
|
+
|
|
40
|
+
1. **Token.** GitHub → Settings → Developer settings → Personal access tokens →
|
|
41
|
+
Fine-grained. Repository access: only `<owner>/handoff`. Permissions:
|
|
42
|
+
Contents = Read and write. Copy the token.
|
|
43
|
+
2. **Repo.** Create `<owner>/handoff`, private, initialized with a README. In
|
|
44
|
+
Settings → Actions → General, disable Actions (no CI needed → no minutes).
|
|
45
|
+
3. **Environments.** In each agent environment (both the `cool-workflow` and the
|
|
46
|
+
`chime` environment), add an environment variable `GH_TOKEN=<token>` (`.env`
|
|
47
|
+
format, no quotes). A new session is required for it to take effect.
|
|
48
|
+
4. **Optional** — put the clone in each environment's setup script so the ledger
|
|
49
|
+
is present at session start:
|
|
50
|
+
```bash
|
|
51
|
+
#!/bin/bash
|
|
52
|
+
git clone https://oauth2:${GH_TOKEN}@github.com/<owner>/handoff.git /home/user/handoff || true
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Gitea (self-hosted) — setup
|
|
56
|
+
|
|
57
|
+
Same shape, two extra prerequisites:
|
|
58
|
+
|
|
59
|
+
1. Serve Gitea over HTTPS with a valid certificate (e.g. Let's Encrypt) so the
|
|
60
|
+
cloud VM's git can verify it.
|
|
61
|
+
2. Configure the agent environment's **network access** to permit your VPS host —
|
|
62
|
+
the default Trusted allowlist does not include it. If the loop cannot reach
|
|
63
|
+
the VPS, it cannot run.
|
|
64
|
+
3. Create a Gitea access token, store it as an environment variable, and clone
|
|
65
|
+
with an authenticated remote (`https://<user>:${GIT_TOKEN}@<vps-host>/<owner>/handoff.git`).
|
|
66
|
+
|
|
67
|
+
## Directory convention
|
|
68
|
+
|
|
69
|
+
Entries live under `ledger/` in the shared repo, one file per entry named by its
|
|
70
|
+
id:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
handoff/
|
|
74
|
+
ledger/
|
|
75
|
+
ldg-1de7c92172af1871.json
|
|
76
|
+
ldg-2315e4b33b9a812f.json
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## The loop
|
|
80
|
+
|
|
81
|
+
Producing side (propose a change, hand it over):
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
entry=$(cw ledger propose --from cool-workflow --to chime \
|
|
85
|
+
--title "Add retry" --rationale "flaky net" \
|
|
86
|
+
--files src/net.ts --diff "$(git -C <src-repo> diff)")
|
|
87
|
+
id=$(printf '%s' "$entry" | jq -r .id)
|
|
88
|
+
printf '%s\n' "$entry" > /home/user/handoff/ledger/$id.json
|
|
89
|
+
git -C /home/user/handoff add ledger/$id.json
|
|
90
|
+
git -C /home/user/handoff commit -m "propose $id"
|
|
91
|
+
git -C /home/user/handoff push
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Note the single `cw ledger propose` call captured into `$entry` — calling it
|
|
95
|
+
twice would mint two different entries (each carries a fresh `createdAt`).
|
|
96
|
+
|
|
97
|
+
Consuming side (verify the inbox, then act or review back):
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
git -C /home/user/handoff pull
|
|
101
|
+
cw ledger list --dir /home/user/handoff/ledger && echo "inbox verified — safe to act"
|
|
102
|
+
|
|
103
|
+
# hand a verdict back:
|
|
104
|
+
entry=$(cw ledger review --from chime --to cool-workflow \
|
|
105
|
+
--target ldg-1de7c92172af1871 --verdict approved --findings "tests pass,scope ok")
|
|
106
|
+
id=$(printf '%s' "$entry" | jq -r .id)
|
|
107
|
+
printf '%s\n' "$entry" > /home/user/handoff/ledger/$id.json
|
|
108
|
+
git -C /home/user/handoff add ledger/$id.json
|
|
109
|
+
git -C /home/user/handoff commit -m "review $id"
|
|
110
|
+
git -C /home/user/handoff push
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Notes
|
|
114
|
+
|
|
115
|
+
- Keep private code out of a **public** handoff repo: omit `--diff` and reference
|
|
116
|
+
a commit/branch in the private source repo instead, so only metadata + a
|
|
117
|
+
pointer is exposed. On a private handoff repo, full diffs are fine.
|
|
118
|
+
- The other side may build entries without `cw` as long as they match the
|
|
119
|
+
digest/id rules in [cross-agent-ledger](cross-agent-ledger.7.md); otherwise
|
|
120
|
+
`cw ledger verify` refuses them with `ledger-digest-mismatch`.
|