cascrow-cli 1.3.0 → 3.0.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/CHANGELOG.md +30 -0
- package/README.md +49 -32
- package/builder-agent.js +146 -49
- package/index.js +619 -123
- package/package.json +2 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 3.0.0 — unreleased (owner publishes)
|
|
4
|
+
|
|
5
|
+
- **`cascrow join` says what the builder can do now**: a $0 verification contract is active at once; a contract with an amount waits for the payer's MilestoneEscrowV3 offer, accepted with your own key (`npx cascrow-signer@0.1.0 accept --milestone <id>`). The JSON carries the server's `status` and `activated`. The builder agent prints the same.
|
|
6
|
+
- **`cascrow review --decision APPROVE`** says "approved and completed (a verification-only contract: no money moves)" and prints the contract status; the "escrow is being released" line and the "Release tx" it never received are gone.
|
|
7
|
+
- **New outcome `NOT_COMPLETABLE_WITHOUT_ESCROW`** at `cascrow verify --proof` (stream), `cascrow verify --contract` (the one-shot submit: the server's 409, with its hint) and `cascrow status` (the new `notCompletableWithoutEscrow` field ends the wait): the panel approved a milestone that carries an amount without a MilestoneEscrowV3 escrow — nothing completed it, no money moved, contact support.
|
|
8
|
+
- **Breaking: `releaseHoldUntil` / `release_hold_until` are gone** from `verify` and `status` output; a bare `VERIFIED` no longer promises a retried release. `passed` on the one-shot `verify --contract` is `verdict === "approved"`.
|
|
9
|
+
- **Breaking: `cascrow fund` is removed** (cascrow Phase 6 — the v2 RLUSD escrow is gone). It now only prints why: a contract with `--amount 0` is active by itself once the builder has joined, and a milestone with an amount is a MilestoneEscrowV3 escrow in native XRP — `cascrow v3 offer`, the builder accepts (`cascrow v3 sign accept`), and the payer funds with one transaction from their own machine (`cascrow v3 sign fund`). Nothing is sent.
|
|
10
|
+
- The `escrow-fund` refusal and the help text no longer point at `escrow-prepare` / `escrow/sync` (both removed); `--amount` is described as the switch to the MilestoneEscrowV3 offer flow.
|
|
11
|
+
- A COMPLETED milestone no longer reads "funds released": the line says whether no money was held (verification only) or a MilestoneEscrowV3 escrow paid out on chain. `review` / `review-queue` describe a verdict that completes or hands back a verification-only milestone and moves no money.
|
|
12
|
+
- The builder agent (`builder-agent.js`) waits for the milestone to be active rather than "funded", and its demo deliverable describes the product as it is now.
|
|
13
|
+
|
|
14
|
+
## 2.1.0 — unreleased (owner publishes)
|
|
15
|
+
|
|
16
|
+
- `cascrow v3 offer | acceptance | accept-submit | prepare | report-tx | attestation | claims` — the MilestoneEscrowV3 remote calls. `cascrow v3 sign <command…>` runs the PINNED local signer (`npx cascrow-signer@0.1.0`) with this terminal attached; the CLI itself stays dependency-free and never sees a key.
|
|
17
|
+
- `cascrow fund` refuses a MilestoneEscrowV3 milestone with the way to fund it; every V3 refusal code prints what can be done about it; a V3 verification reads as a recommendation that moves no money.
|
|
18
|
+
|
|
19
|
+
## 2.0.0 — unreleased (owner publishes)
|
|
20
|
+
|
|
21
|
+
- **Breaking: `cascrow escrow-fund --private-key` is removed.** It sent your EVM private key to cascrow, which signed the deposit server-side; cascrow no longer accepts, stores or signs with anyone's key. Fund a v2 escrow by signing locally: `GET /api/agent/escrow-prepare` returns the two unsigned transactions, you sign and broadcast them with your own wallet, then `POST /api/escrow/sync`. Funding a MilestoneEscrowV3 contract from an API client is not available yet. **If you ever passed a private key to this command, treat that key as exposed and move its funds to a new wallet.**
|
|
22
|
+
- `cascrow register` no longer receives a generated wallet: registration returns `wallet: null` and a note on how to link your own (`GET /api/user/wallet/challenge` → sign → `POST /api/user/wallet`).
|
|
23
|
+
|
|
24
|
+
## 1.4.0 — 2026-09-25
|
|
25
|
+
|
|
26
|
+
- New `cascrow status --proof <id> [--wait]` — show, or wait for, a proof's verification result. `submit` now points to it: verification starts by itself after an upload.
|
|
27
|
+
- `cascrow verify --proof <id>` on a proof whose verification is already running waits for that run instead of failing.
|
|
28
|
+
- `cascrow create` takes `--criteria`, `--criteria-file`, `--checklist-file` and `--review-mode` — the bar the AI panel judges against, kept out of the title (which is printed on the certificate).
|
|
29
|
+
- Every API call has a deadline and survives a non-JSON answer; errors end with the API's stable code in brackets, e.g. `[RATE_LIMITED]`.
|
|
30
|
+
- `status` never reports a released milestone as closed.
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# cascrow-cli
|
|
2
2
|
|
|
3
|
-
Command-line interface for [Cascrow](https://cascrow.com) — trust infrastructure for the agent economy (AI verification, on-chain reputation, milestone escrow
|
|
3
|
+
Command-line interface for [Cascrow](https://cascrow.com) — trust infrastructure for the agent economy (AI verification, on-chain reputation, and a non-custodial milestone escrow in native XRP), for shells, CI pipelines, and subprocesses.
|
|
4
4
|
|
|
5
|
-
AI agents and their operators use Cascrow to create milestone-based contracts, submit proof of completion, and get verified by a **5-model AI majority vote
|
|
5
|
+
AI agents and their operators use Cascrow to create milestone-based contracts, submit proof of completion, and get verified by a **5-model AI majority vote**, anchored on the XRP Ledger. For paid work the AI verdict is a signed, non-binding recommendation: the money sits in a MilestoneEscrowV3 smart contract that only the parties (or an arbiter they chose) can move — cascrow holds no key and never signs.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -51,14 +51,16 @@ is never anchored.
|
|
|
51
51
|
| `register` | Create an agent account, get an API key instantly | No |
|
|
52
52
|
| `verify` | Submit proof and get an AI verdict in one call (recommended) | Yes |
|
|
53
53
|
| `create` | Create a milestone contract | Yes |
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `submit` | Upload proof text (returns `proofId` for the legacy verify flow) | Yes |
|
|
54
|
+
| `submit` | Upload proof text — verification starts by itself; returns `proofId` | Yes |
|
|
55
|
+
| `status` | Show, or `--wait` for, the verification result of a proof | Yes |
|
|
57
56
|
| `get` | Get contract status and milestones | Yes |
|
|
58
57
|
| `join` | Join a contract as Builder via invite code | Yes |
|
|
59
58
|
| `handoff` | Send a contract invite to a Builder agent by Agent ID | Yes |
|
|
60
59
|
| `check-invites` | Check pending contract invites for this agent | Yes |
|
|
61
60
|
| `review-queue` | As funder: list milestones still waiting for your verdict | Yes |
|
|
61
|
+
| `v3-claims` | As payer: list MilestoneEscrowV3 claims you can still object to on chain | Yes |
|
|
62
|
+
| `v3 offer \| acceptance \| accept-submit \| prepare \| report-tx \| attestation \| claims` | MilestoneEscrowV3, remote half — cascrow prepares and reads, you sign | Yes |
|
|
63
|
+
| `v3 sign <command…>` | Runs the pinned local signer, `npx cascrow-signer@0.1.0 <command…>` — your key stays on your machine | Signer's own |
|
|
62
64
|
| `review` | As funder: approve or reject a milestone under manual review | Yes |
|
|
63
65
|
| `me` | Print this agent's ID | Yes |
|
|
64
66
|
|
|
@@ -84,7 +86,7 @@ Exactly one of `--pr`, `--code`, or `--image` is required alongside `--task`.
|
|
|
84
86
|
--key-name <name> optional label for the API key (default: cli)
|
|
85
87
|
```
|
|
86
88
|
|
|
87
|
-
### `verify` [one-shot — recommended for
|
|
89
|
+
### `verify` [one-shot — recommended for text proof]
|
|
88
90
|
|
|
89
91
|
```
|
|
90
92
|
--contract <id> required contract ID
|
|
@@ -97,35 +99,43 @@ Exactly one of `--pr`, `--code`, or `--image` is required alongside `--task`.
|
|
|
97
99
|
### `create`
|
|
98
100
|
|
|
99
101
|
```
|
|
100
|
-
--title <title> required milestone title
|
|
102
|
+
--title <title> required short milestone title (printed on the certificate)
|
|
101
103
|
--days <n> optional deadline days from now (default: 7)
|
|
102
|
-
--amount <usd> optional
|
|
104
|
+
--amount <usd> optional default 0 = verification-only (active once the builder joins);
|
|
105
|
+
above 0 = a MilestoneEscrowV3 escrow in native XRP (see below)
|
|
106
|
+
--criteria <text> optional what the AI panel checks the proof against (max 4000 chars)
|
|
107
|
+
--criteria-file <path> optional the same, read from a file
|
|
108
|
+
--checklist-file <path> optional JSON array of { title, severity?, id? } (max 20), scored item by item on text proofs
|
|
109
|
+
--review-mode <mode> optional AUTO | MANUAL | MANUAL_AUTO — what happens when the panel is unsure (60–85%)
|
|
103
110
|
```
|
|
104
111
|
|
|
105
|
-
|
|
112
|
+
Keep acceptance criteria out of `--title`: the title is printed on the public
|
|
113
|
+
certificate, and the panel judges the proof against `--criteria`.
|
|
106
114
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
--milestone <id> optional defaults to first available
|
|
110
|
-
```
|
|
115
|
+
`cascrow fund` was removed in 3.0.0: a verification-only contract needs no funding,
|
|
116
|
+
and a paid milestone is funded by the payer's own transaction (`cascrow v3 sign fund`).
|
|
111
117
|
|
|
112
|
-
### `
|
|
118
|
+
### `submit` [or use `verify` for text proof in one call]
|
|
113
119
|
|
|
114
120
|
```
|
|
115
|
-
--
|
|
116
|
-
--
|
|
117
|
-
--
|
|
118
|
-
--milestone <id> optional
|
|
121
|
+
--milestone <id> required
|
|
122
|
+
--proof <text> required
|
|
123
|
+
--file <name> optional filename (default: proof-report.txt)
|
|
119
124
|
```
|
|
120
125
|
|
|
121
|
-
|
|
126
|
+
Verification starts by itself after the upload — wait for it with `status`, don't
|
|
127
|
+
start it again with `verify --proof` (that would only wait for the same run).
|
|
128
|
+
|
|
129
|
+
### `status`
|
|
122
130
|
|
|
123
131
|
```
|
|
124
|
-
--
|
|
125
|
-
--
|
|
126
|
-
--file <name> optional filename (default: proof-report.txt)
|
|
132
|
+
--proof <id> required
|
|
133
|
+
--wait optional wait up to ~2 min; stops early on 401/403/404 or an interrupted run
|
|
127
134
|
```
|
|
128
135
|
|
|
136
|
+
Errors end with the API's stable code in brackets, e.g. `[RATE_LIMITED]` — branch
|
|
137
|
+
on that, not on the wording.
|
|
138
|
+
|
|
129
139
|
### `get`
|
|
130
140
|
|
|
131
141
|
```
|
|
@@ -154,16 +164,16 @@ No options.
|
|
|
154
164
|
### `review-queue`
|
|
155
165
|
|
|
156
166
|
No options. Lists the contracts on which you, as funder, still owe a manual
|
|
157
|
-
verdict — the pull behind the
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
167
|
+
verdict on a verification-only contract — the pull behind the
|
|
168
|
+
`manual_review.required` webhook. A webhook nobody received is a decision nobody
|
|
169
|
+
makes; cascrow completes an undecided milestone when its review window closes —
|
|
170
|
+
48 hours under review mode `MANUAL_AUTO` or `AUTO`, 14 days otherwise. A verdict completes or hands back the milestone and moves no money.
|
|
161
171
|
|
|
162
172
|
### `review`
|
|
163
173
|
|
|
164
174
|
```
|
|
165
175
|
--contract <id> required from `cascrow review-queue`
|
|
166
|
-
--decision <APPROVE|REJECT> required APPROVE
|
|
176
|
+
--decision <APPROVE|REJECT> required APPROVE completes the milestone; REJECT returns
|
|
167
177
|
the milestone to the builder, who can resubmit
|
|
168
178
|
--milestone <id> required once more than one milestone of the contract is
|
|
169
179
|
pending review; `review-queue` names it
|
|
@@ -203,18 +213,25 @@ Five AI models vote in parallel on submitted proof:
|
|
|
203
213
|
|
|
204
214
|
| Confidence | Outcome |
|
|
205
215
|
|---|---|
|
|
206
|
-
| > 85% + 3/5 YES | **VERIFIED** —
|
|
216
|
+
| > 85% + 3/5 YES | **VERIFIED** — a verification-only milestone completes; for a MilestoneEscrowV3 milestone it is a recommendation, and the money moves only by the parties' own on-chain calls |
|
|
207
217
|
| 60–85% | **PENDING_REVIEW** — manual review triggered |
|
|
208
218
|
| < 60% | **REJECTED** — resubmit with stronger proof |
|
|
209
219
|
|
|
210
|
-
## Example:
|
|
220
|
+
## Example: Paid milestone (MilestoneEscrowV3, native XRP, testnet)
|
|
211
221
|
|
|
212
222
|
```bash
|
|
213
|
-
cascrow create --title "Deploy API" --amount 100 --days 14
|
|
214
|
-
cascrow
|
|
223
|
+
cascrow create --title "Deploy API" --amount 100 --days 14 # as payer
|
|
224
|
+
cascrow v3 offer --milestone <id> --review-period-sec 259200 # terms frozen at an XRP/USD rate
|
|
225
|
+
cascrow v3 sign accept --milestone <id> --network testnet # as builder: check + sign locally
|
|
226
|
+
cascrow v3 sign fund --milestone <id> --network testnet # as payer: ONE transaction from your machine
|
|
215
227
|
cascrow verify --contract <contractId> --proof "API live at https://api.example.com, all endpoints responding"
|
|
228
|
+
# The verdict is a recommendation. The builder claims on chain; the payer releases or objects.
|
|
216
229
|
```
|
|
217
230
|
|
|
231
|
+
`cascrow v3 sign …` runs the pinned local signer (`npx cascrow-signer@0.1.0`), which keeps
|
|
232
|
+
your key on your machine. This CLI never takes a private key (`escrow-fund`, which did, was
|
|
233
|
+
removed in 2.0.0 — if you ever passed a key to it, treat that key as exposed and move its funds).
|
|
234
|
+
|
|
218
235
|
## Example: Agent-to-Agent Handoff
|
|
219
236
|
|
|
220
237
|
```bash
|
|
@@ -231,7 +248,7 @@ cascrow join --invite <code>
|
|
|
231
248
|
|
|
232
249
|
| Chain | Purpose |
|
|
233
250
|
|---|---|
|
|
234
|
-
| XRPL EVM Sidechain (Chain ID 1449000) |
|
|
251
|
+
| XRPL EVM Sidechain (Chain ID 1449000, testnet) | MilestoneEscrowV3 — non-custodial escrow in native XRP, no platform role |
|
|
235
252
|
| XRP Ledger Mainnet | NFT completion certificates + audit trail |
|
|
236
253
|
|
|
237
254
|
## Links
|
package/builder-agent.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Cascrow Builder Agent
|
|
4
4
|
* Polls for pending contract invites, then automatically:
|
|
5
|
-
* joins →
|
|
5
|
+
* joins → waits until the milestone is active → submits proof → triggers AI verification
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const BASE_URL = process.env.CASCROW_BASE_URL ?? "https://cascrow.com";
|
|
@@ -15,24 +15,57 @@ if (!API_KEY) {
|
|
|
15
15
|
process.exit(1);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
// Every call reads the body as text and parses it in a try (an HTML 502 used to
|
|
19
|
+
// crash the agent as "Unexpected token '<'"), carries a deadline over the body
|
|
20
|
+
// read, and throws an Error with `status` and `code` (T11-A4). Upload's deadline
|
|
21
|
+
// mirrors UPLOAD_CLIENT_ABORT_MS in src/lib/latency-budget.ts.
|
|
22
|
+
const UPLOAD_CLIENT_ABORT_MS = 120_000;
|
|
23
|
+
const REQUEST_CLIENT_ABORT_MS = 300_000;
|
|
24
|
+
|
|
25
|
+
async function readJsonSafe(res) {
|
|
26
|
+
const text = await res.text();
|
|
27
|
+
if (!text) return {};
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(text);
|
|
30
|
+
} catch {
|
|
31
|
+
return { error: `HTTP ${res.status} (non-JSON response from server)`, code: "UPSTREAM_NON_JSON" };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function fetchJson(method, path, { json, form, deadlineMs = REQUEST_CLIENT_ABORT_MS } = {}) {
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
const timer = setTimeout(() => controller.abort(), deadlineMs);
|
|
38
|
+
let res;
|
|
39
|
+
let data;
|
|
40
|
+
try {
|
|
41
|
+
res = await fetch(`${BASE_URL}${path}`, {
|
|
42
|
+
method,
|
|
43
|
+
headers: { "Authorization": `Bearer ${API_KEY}`, ...(json !== undefined ? { "Content-Type": "application/json" } : {}) },
|
|
44
|
+
...(json !== undefined ? { body: JSON.stringify(json) } : form !== undefined ? { body: form } : {}),
|
|
45
|
+
signal: controller.signal,
|
|
46
|
+
});
|
|
47
|
+
data = await readJsonSafe(res);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
if (e && e.name === "AbortError") {
|
|
50
|
+
throw Object.assign(new Error(`No answer from ${path} within ${Math.round(deadlineMs / 1000)} s [CLIENT_TIMEOUT]`), { status: 0, code: "CLIENT_TIMEOUT" });
|
|
51
|
+
}
|
|
52
|
+
throw e;
|
|
53
|
+
} finally {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
}
|
|
56
|
+
if (!res.ok || data.code === "UPSTREAM_NON_JSON") {
|
|
57
|
+
const code = typeof data.code === "string" ? data.code : null;
|
|
58
|
+
throw Object.assign(new Error(`${data.error ?? `HTTP ${res.status}`}${code ? ` [${code}]` : ""}`), { status: res.status, code });
|
|
59
|
+
}
|
|
26
60
|
return data;
|
|
27
61
|
}
|
|
28
62
|
|
|
63
|
+
async function apiPost(path, body) {
|
|
64
|
+
return fetchJson("POST", path, { json: body });
|
|
65
|
+
}
|
|
66
|
+
|
|
29
67
|
async function apiGet(path) {
|
|
30
|
-
|
|
31
|
-
headers: { "Authorization": `Bearer ${API_KEY}` },
|
|
32
|
-
});
|
|
33
|
-
const data = await res.json();
|
|
34
|
-
if (!res.ok) throw new Error(data.error ?? `HTTP ${res.status}`);
|
|
35
|
-
return data;
|
|
68
|
+
return fetchJson("GET", path);
|
|
36
69
|
}
|
|
37
70
|
|
|
38
71
|
async function uploadProof(milestoneId, content) {
|
|
@@ -40,14 +73,7 @@ async function uploadProof(milestoneId, content) {
|
|
|
40
73
|
const form = new FormData();
|
|
41
74
|
form.append("file", blob, "proof-report.txt");
|
|
42
75
|
form.append("milestoneId", milestoneId);
|
|
43
|
-
|
|
44
|
-
method: "POST",
|
|
45
|
-
headers: { "Authorization": `Bearer ${API_KEY}` },
|
|
46
|
-
body: form,
|
|
47
|
-
});
|
|
48
|
-
const data = await res.json();
|
|
49
|
-
if (!res.ok) throw new Error(data.error ?? `HTTP ${res.status}`);
|
|
50
|
-
return data;
|
|
76
|
+
return fetchJson("POST", "/api/proof/upload", { form, deadlineMs: UPLOAD_CLIENT_ABORT_MS });
|
|
51
77
|
}
|
|
52
78
|
|
|
53
79
|
// SEC-073. The FIFTH client door of `/api/verify`, and until 2026-09-22 the only
|
|
@@ -81,8 +107,8 @@ async function streamVerify(proofId) {
|
|
|
81
107
|
clearTimeout(timeout);
|
|
82
108
|
// SEC-073. Now that the deadline is live it can actually fire, and a raw
|
|
83
109
|
// `DOMException: This operation was aborted` names no remedy — worse, per
|
|
84
|
-
// the tail arithmetic in latency-budget.ts the abort can land AFTER
|
|
85
|
-
//
|
|
110
|
+
// the tail arithmetic in latency-budget.ts the abort can land AFTER the
|
|
111
|
+
// verdict was written, so "it failed" may be false.
|
|
86
112
|
if (e && e.name === "AbortError") {
|
|
87
113
|
throw new Error(
|
|
88
114
|
"Verification is still running on the server — this client stopped waiting, it did not fail. " +
|
|
@@ -93,8 +119,16 @@ async function streamVerify(proofId) {
|
|
|
93
119
|
}
|
|
94
120
|
if (!res.ok) {
|
|
95
121
|
clearTimeout(timeout);
|
|
96
|
-
const data = await res
|
|
97
|
-
|
|
122
|
+
const data = await readJsonSafe(res);
|
|
123
|
+
// T01-8. Upload starts verification by itself, and this agent then starts it
|
|
124
|
+
// again — so losing the claim to a RUNNING run is the normal case, not a failure.
|
|
125
|
+
// The caller's polling path waits for that run; `alreadyRunning` sends it there.
|
|
126
|
+
// Never read this 409's Retry-After as a wait: it is the ~1 h takeover cutoff.
|
|
127
|
+
if (res.status === 409 && data.code === "VERIFICATION_IN_PROGRESS" && data.phase === "running") {
|
|
128
|
+
throw Object.assign(new Error("A verification of this proof is already running (the one upload started)."), { alreadyRunning: true });
|
|
129
|
+
}
|
|
130
|
+
const code = typeof data.code === "string" ? data.code : null;
|
|
131
|
+
throw Object.assign(new Error(`${data.error ?? `HTTP ${res.status}`}${code ? ` [${code}]` : ""}`), { status: res.status, code });
|
|
98
132
|
}
|
|
99
133
|
if (!res.body) {
|
|
100
134
|
clearTimeout(timeout);
|
|
@@ -161,32 +195,40 @@ async function handleInvite(invite) {
|
|
|
161
195
|
console.log("🔗 Joining contract...");
|
|
162
196
|
const joined = await apiPost("/api/contracts/join", { inviteCode: invite.inviteCode });
|
|
163
197
|
console.log(` ✓ Joined. Contract ID: ${joined.contractId}`);
|
|
198
|
+
// The join's own activation result: a $0 contract is active now; a money contract
|
|
199
|
+
// waits for the payer's MilestoneEscrowV3 offer, which this agent must accept with
|
|
200
|
+
// its OWN key — cascrow never signs for it.
|
|
201
|
+
if (joined.activated === true) console.log(" ✓ Verification-only contract — active now.");
|
|
202
|
+
else if (joined.activated === false)
|
|
203
|
+
console.log(" ⏳ Paid contract — waiting for the payer's MilestoneEscrowV3 offer. Accept it with your own key: npx cascrow-signer@0.1.0 accept --milestone <milestoneId>.");
|
|
164
204
|
|
|
165
|
-
// 2. Wait
|
|
166
|
-
|
|
205
|
+
// 2. Wait until the milestone is active. A verification-only ($0) contract is active
|
|
206
|
+
// as soon as the builder has joined; a paid one once the payer has funded its
|
|
207
|
+
// MilestoneEscrowV3 escrow from their own wallet. Either way the status is FUNDED.
|
|
208
|
+
console.log("⏳ Waiting for the milestone to be active...");
|
|
167
209
|
let fundedMilestoneId = null;
|
|
168
210
|
for (let i = 0; i < 40; i++) {
|
|
169
211
|
const contract = await apiGet(`/api/contracts/${joined.contractId}`);
|
|
170
212
|
const funded = (contract.milestones ?? []).find((m) => m.status === "FUNDED");
|
|
171
213
|
if (funded) {
|
|
172
214
|
fundedMilestoneId = funded.id;
|
|
173
|
-
console.log(` ✓ Milestone
|
|
215
|
+
console.log(` ✓ Milestone active. ID: ${fundedMilestoneId}`);
|
|
174
216
|
break;
|
|
175
217
|
}
|
|
176
218
|
await new Promise((r) => setTimeout(r, 3000));
|
|
177
219
|
}
|
|
178
|
-
if (!fundedMilestoneId) throw new Error("Timed out waiting for milestone to be
|
|
220
|
+
if (!fundedMilestoneId) throw new Error("Timed out waiting for the milestone to be active");
|
|
179
221
|
|
|
180
222
|
// 3. Do the work — generate proof based on the task message
|
|
181
223
|
console.log("🤖 Performing task...");
|
|
182
224
|
const task = invite.message ?? "Complete the assigned milestone";
|
|
183
|
-
const bio = `
|
|
225
|
+
const bio = `cascrow is trust infrastructure for the agent economy. It checks whether delivered work meets the bar that was agreed for it, and it keeps a verifiable record of the answer.
|
|
184
226
|
|
|
185
|
-
|
|
227
|
+
Every submission is judged in parallel by five AI models from different vendors — Claude, GPT-4o, Gemini, Mistral and Cerebras — and three approvals make a YES. Each verdict is anchored on the XRP Ledger, completed milestones earn an NFT certificate, and the results build a portable on-chain reputation for the builder.
|
|
186
228
|
|
|
187
|
-
|
|
229
|
+
For paid work, cascrow supports a non-custodial escrow in native XRP: the funds sit in a smart contract that only the two parties, or an arbiter they chose, can move. The AI verdict is a signed recommendation; cascrow never holds funds or keys.
|
|
188
230
|
|
|
189
|
-
|
|
231
|
+
Agents use a REST API, an MCP server and a CLI to create contracts, submit proof and read reputation programmatically. Live at cascrow.com.`;
|
|
190
232
|
|
|
191
233
|
const proof = `PROOF OF COMPLETION
|
|
192
234
|
═══════════════════════════════════════
|
|
@@ -196,13 +238,13 @@ Contract ID: ${invite.contractId}
|
|
|
196
238
|
Milestone ID: ${fundedMilestoneId}
|
|
197
239
|
Completed at: ${new Date().toISOString()}
|
|
198
240
|
|
|
199
|
-
DELIVERABLE (
|
|
241
|
+
DELIVERABLE (${bio.split(/\s+/).filter(Boolean).length} words):
|
|
200
242
|
${bio}
|
|
201
243
|
|
|
202
244
|
VERIFICATION CRITERIA MET:
|
|
203
245
|
✓ Professional third-person tone throughout
|
|
204
|
-
✓
|
|
205
|
-
✓ Clearly explains what
|
|
246
|
+
✓ ${bio.split(/\s+/).filter(Boolean).length} words — within the 120–160 word range
|
|
247
|
+
✓ Clearly explains what cascrow does (AI verification, on-chain reputation, non-custodial escrow)
|
|
206
248
|
✓ Highlights AI verification mechanism (5-model majority vote, named models)
|
|
207
249
|
✓ Addresses agent economy angle (autonomous agents, programmatic interfaces)
|
|
208
250
|
✓ No placeholder text
|
|
@@ -224,7 +266,15 @@ VERIFICATION CRITERIA MET:
|
|
|
224
266
|
// Stream disconnected before complete event (e.g. Vercel 150s timeout).
|
|
225
267
|
// The verification is still running server-side — just poll for the result,
|
|
226
268
|
// do NOT call streamVerify again (would trigger duplicate AI verification).
|
|
227
|
-
|
|
269
|
+
// Two ways here, and the loop below serves both: the stream dropped mid-run, or
|
|
270
|
+
// it never opened because upload's own run already held the claim (T01-8).
|
|
271
|
+
if (streamErr && streamErr.alreadyRunning) {
|
|
272
|
+
console.log(` ℹ️ Verification was already running (upload starts it) — waiting for its result...`);
|
|
273
|
+
} else if (streamErr && (streamErr.status === 401 || streamErr.status === 403 || streamErr.status === 404)) {
|
|
274
|
+
throw streamErr;
|
|
275
|
+
} else {
|
|
276
|
+
console.log(` ⚠️ Stream ended early, polling for result...`);
|
|
277
|
+
}
|
|
228
278
|
// SEC-076. This polled `contract.status`, and that column answers a different
|
|
229
279
|
// question than the one asked here. It is a single slot with nine writers:
|
|
230
280
|
// funding a SIBLING milestone writes `contractStatusAfterFunding(...)` over
|
|
@@ -240,13 +290,33 @@ VERIFICATION CRITERIA MET:
|
|
|
240
290
|
// funded a specific one, so the honest read is that row. The contract-level
|
|
241
291
|
// read is kept only for the legacy population that has no milestone rows at
|
|
242
292
|
// all (proofs written via the contractId-only path).
|
|
243
|
-
|
|
293
|
+
//
|
|
294
|
+
// EXPIRED is terminal too. Since 2026-09-23 `/api/verify` no longer reopens a
|
|
295
|
+
// milestone that went EXPIRED while its panel ran, so without it this loop polled a
|
|
296
|
+
// closed row for five minutes and then threw "Timed out" — and it must never be
|
|
297
|
+
// mapped to a YES: the milestone moved no funds through this verification.
|
|
298
|
+
const TERMINAL_PROOF_STATES = ["VERIFIED", "COMPLETED", "REJECTED", "PENDING_REVIEW", "EXPIRED"];
|
|
244
299
|
for (let i = 0; i < 60; i++) {
|
|
245
300
|
await new Promise((r) => setTimeout(r, 5000));
|
|
246
|
-
|
|
301
|
+
let contract;
|
|
302
|
+
try {
|
|
303
|
+
contract = await apiGet(`/api/contracts/${joined.contractId}`);
|
|
304
|
+
} catch (e) {
|
|
305
|
+
// 401/403/404 do not change by waiting; anything else is transient. One
|
|
306
|
+
// dropped poll used to end the whole run.
|
|
307
|
+
if (e && (e.status === 401 || e.status === 403 || e.status === 404)) throw e;
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
247
310
|
const mine = (contract.milestones ?? []).find((m) => m.id === fundedMilestoneId);
|
|
248
311
|
const observed = mine ? mine.status : (contract.milestones ?? []).length === 0 ? contract.status : null;
|
|
249
312
|
if (observed && TERMINAL_PROOF_STATES.includes(observed)) {
|
|
313
|
+
if (observed === "EXPIRED") {
|
|
314
|
+
// No verdict is read on this path: the stream that carried it dropped, and
|
|
315
|
+
// the row says only that it is closed.
|
|
316
|
+
result = { decision: null, confidence: 0, action: "EXPIRED", milestoneClosed: true };
|
|
317
|
+
console.log(` ✓ Got result via polling: ${observed}`);
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
250
320
|
const decision = ["VERIFIED", "COMPLETED"].includes(observed) ? "YES" : "NO";
|
|
251
321
|
result = { decision, confidence: 0, action: observed };
|
|
252
322
|
console.log(` ✓ Got result via polling: ${observed}`);
|
|
@@ -269,20 +339,47 @@ VERIFICATION CRITERIA MET:
|
|
|
269
339
|
const released = result.action === "COMPLETED";
|
|
270
340
|
const passed = released;
|
|
271
341
|
console.log(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
272
|
-
if (result.action === "
|
|
273
|
-
|
|
342
|
+
if (result.action === "V3_RECOMMENDATION") {
|
|
343
|
+
// MilestoneEscrowV3: a recommendation that moves nothing — asked first, so no arm
|
|
344
|
+
// below can read it as a payout or a rejection. The claim is judged by the
|
|
345
|
+
// SUBMISSION's verdict, never by `decision` (the panel on this one file).
|
|
346
|
+
const verdict = result.submissionVerdict === "YES" || result.submissionVerdict === "NO" ? result.submissionVerdict : "PENDING";
|
|
347
|
+
console.log(
|
|
348
|
+
`🧭 AI RECOMMENDATION ${verdict} on a MilestoneEscrowV3 submission — this verdict moves NO money. ` +
|
|
349
|
+
(verdict === "YES"
|
|
350
|
+
? `Claim on chain with proofHash ${result.manifestHash ?? "(the upload's manifestHash)"} from your own key (npx cascrow-signer submit-claim).`
|
|
351
|
+
: verdict === "NO"
|
|
352
|
+
? `Not claim-ready: the payer can object to a claim made with this evidence.`
|
|
353
|
+
: `The submission's verdict is still pending; poll the proof status.`)
|
|
354
|
+
);
|
|
355
|
+
} else if (result.milestoneClosed === true && result.action === "EXPIRED") {
|
|
356
|
+
// `GET /api/contracts/{id}` does not return the milestone's `terminalReason`, so this
|
|
357
|
+
// cannot say whether the funder was refunded or the builder was paid on-chain, and
|
|
358
|
+
// does not guess.
|
|
359
|
+
console.log(
|
|
360
|
+
`⛔ CLOSED — the milestone was closed (EXPIRED) while the panel ran or after it decided, so this verification moved no funds. ` +
|
|
361
|
+
`The reason is not available here — check the contract page before assuming where the funds are. Do not resubmit to it.`
|
|
362
|
+
);
|
|
363
|
+
} else if (result.action === "SUPERSEDED") {
|
|
364
|
+
console.log(
|
|
365
|
+
result.settledAs
|
|
366
|
+
? `ℹ️ SUPERSEDED — this milestone was closed (${result.settledAs}) before this verification could settle it; it changed nothing and moved no funds. Do not resubmit to it.`
|
|
367
|
+
: `ℹ️ SUPERSEDED — another proof on this milestone was decided first; this changed nothing.`
|
|
368
|
+
);
|
|
274
369
|
} else if (released) {
|
|
275
370
|
console.log(
|
|
276
371
|
result.alreadyReleased === true
|
|
277
|
-
? `✅ COMPLETED —
|
|
278
|
-
: `✅ COMPLETED — ${result.confidence}% confidence
|
|
372
|
+
? `✅ COMPLETED — another request completed this milestone first; no money was held (verification-only contract). Do not resubmit.`
|
|
373
|
+
: `✅ COMPLETED — ${result.confidence}% confidence; no money was held (verification-only contract).`
|
|
279
374
|
);
|
|
280
375
|
} else if (result.complianceBlocked === true) {
|
|
281
376
|
console.log(`⏸️ VERIFIED — payout on hold pending compliance review. No funds moved.`);
|
|
282
|
-
} else if (result.
|
|
283
|
-
|
|
377
|
+
} else if (result.action === "NOT_COMPLETABLE_WITHOUT_ESCROW") {
|
|
378
|
+
// Approved, but the milestone carries an amount without a MilestoneEscrowV3 escrow,
|
|
379
|
+
// so nothing completed it. Do not resubmit: the next verdict meets the same row.
|
|
380
|
+
console.log(`⛔ NOT COMPLETED — ${result.remedy ?? "contact support@cascrow.com; do not resubmit."} ${result.error ?? ""}`.trim());
|
|
284
381
|
} else if (result.action === "VERIFIED") {
|
|
285
|
-
console.log(`⚠️ VERIFIED — approved, but the
|
|
382
|
+
console.log(`⚠️ VERIFIED — approved, but the milestone was NOT completed. No money moved; if it stays VERIFIED, contact support@cascrow.com.`);
|
|
286
383
|
} else if (result.timedOut === true) {
|
|
287
384
|
console.log(`⏳ PENDING_REVIEW — the panel did not answer (too few models). NOT a rejection; a human decides.`);
|
|
288
385
|
} else if (result.action === "PENDING_REVIEW") {
|
|
@@ -292,7 +389,7 @@ VERIFICATION CRITERIA MET:
|
|
|
292
389
|
} else {
|
|
293
390
|
console.log(`⚠️ Outcome not recognised by this version (action: ${result.action}). No conclusion drawn.`);
|
|
294
391
|
}
|
|
295
|
-
console.log(` Decision: ${result.decision}`);
|
|
392
|
+
console.log(` Decision: ${result.decision ?? "none read (the stream dropped before it)"}`);
|
|
296
393
|
console.log(` View contract: ${BASE_URL}/contract/${invite.contractId}`);
|
|
297
394
|
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
|
|
298
395
|
}
|