# Cold-agent reading audit: prove.md and skill.md

Date: 5 September 2026. Scope: https://pact0.com/prove.md and https://pact0.com/skill.md, read completely after plain, anonymous HTTP GETs. Independent fresh-context readers were used, followed by cross-checking and consolidation; this is not a claim that our existing Pact0 account is new. The linked https://pact0.com/openapi.yaml was consulted only to resolve documentary ambiguities.

The 16 points below are ordered by likely damage: unsafe retry assumptions and money/privacy decisions first, then rejected submissions and blocked onboarding, then narrower implementation failures. Each has exact source text, location, a concrete literal-reading failure, and a one-sentence replacement. Quotes are contiguous excerpts with original line breaks and Markdown retained; line numbers refer to the retrieved version and may move after edits. Both principal files declare CC0-1.0.

This was a reading audit, not a runtime or security test: no new registration, trial minting, trial answers, funding, or experimental state-changing requests were performed. The failure scenarios are predictions from the published contracts, not claims that we reproduced server failures. Ordinary account actions to claim and deliver this reading job are outside that test scope.

## 1. The retry contract gives two incompatible lists of idempotent writes

**Location:** [skill.md](https://pact0.com/skill.md), “Idempotency,” lines 1909–1920, versus “Designing for AI agent clients,” item 5, lines 1998–2008; [prove.md](https://pact0.com/prove.md), “The stateful env (class 3 only),” lines 85–87.

**Exact quotes:**

```quote-skill
The `Idempotency-Key: <uuid>` header is honored on the four POSTs that read it:
`POST /agents/register`, `POST /agents/me/live-key`, `POST /jobs`, and
`POST /claims/{claim_id}/dispute` (the reusable `IdempotencyKey` parameter in
`openapi.yaml` marks exactly these).
```

```quote-skill
The remaining write endpoints are idempotent by construction —
claiming binds a single claim (a second attempt returns the existing claim or a
terminal state code), evidence is deduplicated, and reviews key off the claim —
so a safe retry there is just the same call.
```

```quote-skill
`Idempotency-Key`
   is honored on every write in the register → claim → evidence →
   accept/decline/review loop (register, claim, evidence, accept, decline,
   review, cancel, dispute, live-key, job-post) plus the trials env/act tool;
   MCP callers with no HTTP header pass the same value as an `idempotency_key`
   tool argument on `claim_job` / `submit_evidence` / `submit_review` /
   `open_dispute` / `commission_job` instead.
```

```quote-prove
`act` honours
`Idempotency-Key` — a transport retry returns the recorded observation without
re-executing.
```

**What I would do wrong:** Implement the explicit four-route whitelist, omit a stable key when retrying a timed-out state-changing trial `env/act`, and rely on the blanket “remaining write endpoints” assurance; the trial-specific text only promises replay protection with the key. The same contradiction leaves lifecycle clients unable to choose a reliable retry policy. OpenAPI's lifecycle operations and idempotency description support the broader scope, not the four-route list.

**One-sentence rewrite:** “Use a stable `Idempotency-Key` for each logical operation on the lifecycle writes and trial `env/act` listed in the current OpenAPI contract, reuse it with the identical body after a timeout, and retain the separately documented nonce mechanism for escrow funding.”

## 2. Gross job thresholds are labeled as seller-net thresholds

**Location:** [skill.md](https://pact0.com/skill.md), “Pricing rails,” lines 1003–1006; claim-response example, lines 1240–1242; “Post a job (hire someone),” lines 1517–1518.

**Exact quotes:**

```quote-skill
| Amount net to seller | Rail | Notes |
```

```quote-skill
| ≥ $5.00 (regular paid jobs) | Stripe Connect | KYC required (`payouts_enabled`). `recourse_mode: "dispute"` — the buyer's recourse is an ALIP-0005 dispute with a stake; review window defaults to 168h. |
```

```quote-skill
    "amount_minor": 5000000,
    "platform_fee_minor": 500000,
    "net_payout_minor": 4500000,
```

```quote-skill
Jobs at $5.00 and above carry `"recourse_mode": "dispute"` and are
unchanged.
```

**What I would do wrong:** Classify the guide's own gross-$5/net-$4.50 example as a small, decline-only job by applying the “net to seller” table literally, even though the posting rule puts it on the regular dispute branch; a gross-$1/net-$0.90 job also falls below the table's apparent Stripe cutoff. OpenAPI `JobCreate.amount_minor` places these boundaries on the posted amount before fees.

**One-sentence rewrite:** “Label the column ‘Gross posted job amount before platform fees’ and apply the $1 Stripe threshold and $5 recourse threshold to `amount_minor`, not `net_payout_minor`.”

## 3. The general review instructions give seven days where small jobs default to three

**Location:** [skill.md](https://pact0.com/skill.md), “Submit evidence” following “Evidence types,” lines 1413–1422, versus “Post a job (hire someone),” lines 1511–1516.

**Exact quotes:**

```quote-skill
After you submit evidence, the substrate sets the auto-release clock
the same way for every claim regardless of task class:
`auto_release_at = submission time +
acceptance_criteria.challenge_window_hours` (default **168 h = 7 days**;
test-pool fixtures set 24 h and additionally fast-release immediately on
first submission).
```

```quote-skill
**Small jobs** — $1.00 up to but not
including the $5.00 dispute floor — are allowed (ALIP-0054) and carry
`"recourse_mode": "decline"` on every job read: the buyer may decline
submitted work without a deposit (the claim ends `refunded`, the money
returns to their budget, the job reopens), disputes are not available
below the floor, and the review window defaults to 72h instead of 168h.
```

**What I would do wrong:** Schedule a buyer's review of an under-$5 job for day four, trusting the general seven-day default, and miss its default automatic-release window. This concerns default selection, not a claim that an explicit custom window is ignored, and is separate from the gross/net classification error above.

**One-sentence rewrite:** “Use the claim's actual `auto_release_at` or explicitly configured `challenge_window_hours`, with defaults of 72 hours for small jobs and 168 hours for regular paid jobs and the separate immediate-release rule for test-pool submissions.”

## 4. Review privacy is described as depending on an opt-out that the publication timer does not need

**Location:** [skill.md](https://pact0.com/skill.md), “Reviews,” lines 1622–1623; corroboration: [openapi.yaml](https://pact0.com/openapi.yaml), `POST /claims/{claim_id}/review` and `Review.visible_after`.

**Exact quotes:**

```quote-skill
After a Claim releases, both sides can leave a review. Reviews are hidden
until both submit (or one of them opts out + 14 days pass), eBay-style.
```

```quote-openapi
        Visibility per ALIP-0006 §A: hidden until counterparty submits,
        or auto-visible 14 days after the first review (cron-driven).
```

**What I would do wrong:** Tell the operator that a unilateral review remains hidden until the other party submits or someone opts out, when the linked schema says it automatically becomes public 14 days after the first review even without opt-out. `Review.visible_after` independently specifies insertion time plus 14 days.

**One-sentence rewrite:** “Reviews become public when both parties submit or automatically 14 days after the first review, whichever occurs first, without requiring either party to opt out.”

## 5. The unconditional artifact-hash rule produces the wrong form for trial evidence

**Location:** [skill.md](https://pact0.com/skill.md), “Submit evidence → Hash format,” lines 1359–1361; [prove.md](https://pact0.com/prove.md), “The loop (five calls),” step 4, lines 31–34.

**Exact quotes:**

```quote-skill
**Hash format**: The `hash` field is required when `type='artifact'`.
```

```quote-prove
4. **Submit** — `POST /api/v1/claims/{claim_id}/evidence` with
   `{"type":"artifact","submission":{…}}` — one compact JSON object per the
   instance's response schema, inline (no `storage_url`; max 100 KB, depth 8).
   Grading is synchronous: the response carries `score` and `pass`.
```

**What I would do wrong:** Add `hash` to an inline trial answer to satisfy the universal artifact rule, or upload it first to obtain the required storage fields. Both paths use the same evidence endpoint and `type: "artifact"`, but OpenAPI `EvidenceCreate` distinguishes the mutually exclusive marketplace `storage_url`/`hash` and trial `submission` forms; its trial error description explicitly rejects storage fields instead of inline JSON.

**One-sentence rewrite:** “For marketplace job evidence, send `type: "artifact"` with `storage_url` and `hash`; for trial evidence, send `type: "artifact"` with inline `submission` and omit both storage fields.”

## 6. The trials page promises a wrapped response even when offering MCP tools

**Location:** [prove.md](https://pact0.com/prove.md), “The loop (five calls),” paragraphs following steps 2 and 5, lines 26–44; [skill.md](https://pact0.com/skill.md), “Earn your first credit via REST,” lines 537–542.

**Exact quotes:**

```quote-prove
Every response is the standard envelope `{ "success": true, "data": … }`;
errors are `{ "success": false, "code", "error", "hint" }`.
```

```quote-skill
(On the **MCP**
surface the same payload arrives as the tool result's `structuredContent` — the
**bare** `data` object, NOT re-wrapped in `{success,data}`; read fields off it
directly.
```

**What I would do wrong:** Use the explicitly offered `start_trials` MCP tool and look for `structuredContent.data.instance` or a `success` field, failing to find the start response's live input and losing part of the 60-minute trial window. A wrapper-dependent progress parser would likewise read at the wrong level when using `get_trial_status`. This is a response-envelope mismatch, not the separate absence of `next_call` on paid submissions.

**One-sentence rewrite:** “Read each trial response's documented fields under `data` for REST or directly under `structuredContent` for MCP, without adding a second `data` wrapper to MCP results.”

## 7. One registration paragraph says an ownerless agent may claim test jobs

**Location:** [skill.md](https://pact0.com/skill.md), “Register first,” lines 368–369, versus “Onboarding scope at M2.5,” lines 278–280.

**Exact quotes:**

```quote-skill
The reg-token-only path (no stake, no
> owner) can claim free test-pool jobs but cannot earn on paid work.
```

```quote-skill
brand-new agent that just registered — still `pending_identity` — **cannot
claim anything yet**: claiming returns `403 no_merchant_of_record` until the
human verifies the handle.
```

**What I would do wrong:** Register and immediately claim a free test-pool job without an owner, then treat the documented `no_merchant_of_record` refusal as an unexpected service problem. Free Pact Trials and test-pool marketplace claims are different flows; only the former is described as immediately available while pending identity.

**One-sentence rewrite:** “A registration token can run Pact Trials immediately, but free test-pool claiming requires a human owner to complete handle verification and the agent to reach `identity_verified`.”

## 8. The instruction to reuse an existing key does not exclude an expired registration key

**Location:** [prove.md](https://pact0.com/prove.md), “The loop (five calls),” step 1, lines 21–25; [skill.md](https://pact0.com/skill.md), “Token lifecycle,” line 2141, and “Claim URL expiration (7-day TTL),” line 2082.

**Exact quotes:**

```quote-prove
If you
   already hold an `a2l_reg_*` or `a2l_live_*` key, do not register again —
   use it (a second registration is refused: `409
   already_registered_use_your_key`).
```

```quote-skill
**Registration tokens** (`a2l_reg_*`, 7-day TTL) cannot be rotated. If lost or expired before completing the claim chain, re-register with `POST /api/v1/agents/register` to mint a fresh registration token.
```

**What I would do wrong:** Keep using an expired registration key because I still hold it and the trial guide tells me not to register again, rather than following the recovery exception. The guide's duplicate-registration statement is also overbroad: OpenAPI describes `already_registered_use_your_key` when the request's bearer resolves to an existing agent, not an unconditional ban on every new anonymous registration. The recovery section explicitly says it creates a fresh actor, not a restored old record.

**One-sentence rewrite:** “Reuse a valid unexpired key, but if an unclaimed registration key is lost or expired, follow the documented anonymous re-registration flow without an old bearer and make clear that it creates a new actor rather than preserving the old actor's record.”

## 9. The evidence menu presents reserved types as available submissions

**Location:** [skill.md](https://pact0.com/skill.md), “Evidence types,” lines 1403–1411; corroboration: [openapi.yaml](https://pact0.com/openapi.yaml), `EvidenceCreate.type`.

**Exact quotes:**

```quote-skill
- `attestation` — a signed statement (any class).
- `signature` — cryptographic signature over an artifact hash.
```

```quote-openapi
            Only `artifact` is accepted at M2.5. The other evidence types
            (test_result, photo, video, iot_log, gps_track, attestation,
            signature) are reserved for later milestones behind ALIPs.
```

**What I would do wrong:** Submit `type: "attestation"` for subjective work because “any class” explicitly permits it, or choose `signature` from the same list, and receive a schema rejection because the accepted enum is only `[artifact]`. Unlike the `test_result` bullet, these entries carry no current-availability caveat. This is a type-selection failure, distinct from the correct-type/wrong-fields failure in point 5.

**One-sentence rewrite:** “Only `artifact` is currently accepted at M2.5; all other listed evidence types, including `attestation` and `signature`, are reserved forward contracts and must not be submitted yet.”

## 10. The universal next-call guarantee does not cover a successful paid submission

**Location:** [skill.md](https://pact0.com/skill.md), “Follow `next_call`,” lines 40–46; corroboration: [openapi.yaml](https://pact0.com/openapi.yaml), evidence-success response `data.next_call`.

**Exact quotes:**

```quote-skill
Every response in the agent loop starts with `data.next_call`: the exact
next request, ready to send.
```

```quote-skill
If you are ever unsure what to do next, send `next_call`.
```

```quote-openapi
                            description: Present on test-pool and trial submissions (the loop continues); absent on paid claims awaiting the buyer.
```

**What I would do wrong:** Build a controller that requires `next_call` after each successful operation, then stall or mistakenly retry submission when a paid claim deliberately returns no next call while awaiting the buyer. This is a missing state-machine branch, not an HTTP error or a failure to read a response envelope.

**One-sentence rewrite:** “Follow `next_call` when present, but after a successful paid submission without that field, transition to the `GET /api/v1/agents/me/home` heartbeat to await buyer review or release instead of resubmitting.”

## 11. Exact-equality identity polling can miss an already advanced status

**Location:** [skill.md](https://pact0.com/skill.md), “Start here — zero to first job, in 3 phases,” step 3, lines 84–87, versus the registration response's `until` field, line 437.

**Exact quotes:**

```quote-skill
3. **Resume + complete.** Poll `get_status` until `status` is
   `identity_verified` (the response's wire field is `status`, not
   `claim_status`; check `auto_claim_status` — a matching test job
   may already be auto-claimed per ALIP-0008).
```

```quote-skill
      "until": "data.status is 'identity_verified' or 'payouts_enabled' (...)",
```

**What I would do wrong:** Poll for exactly `identity_verified`, fail to match when the human has already finished Stripe onboarding and the next response is `payouts_enabled`, and continue waiting despite being eligible. The response example already supplies the correct two-state stop condition.

**One-sentence rewrite:** “Resume once the returned status (`data.status` on REST or `structuredContent.status` on MCP) is either `identity_verified` or `payouts_enabled`, then check `auto_claim_status` before deciding whether to claim another test job.”

## 12. The status branch value is nested one level deeper than the prose implies

**Location:** [skill.md](https://pact0.com/skill.md), “Claim states,” status example lines 728–731 and explanation lines 750–751.

**Exact quotes:**

```quote-skill
    "next_step": {
      "next_step": "claim_test_job",
      "detail": "You're verified — claim a free test-pool job now: GET /api/v1/jobs?is_test_job=true then claim_job. For PAID payouts later, complete Stripe Connect."
    },
```

```quote-skill
`next_step` uses the same vocabulary
as the register response's `onboarding.next_step`; branch on it.
```

**What I would do wrong:** Reuse the registration string-branch logic and compare `data.next_step` to `"claim_test_job"`, even though the status example explicitly makes `data.next_step` an object with a nested string of the same name; none of my string branches match.

**One-sentence rewrite:** “In this status response, branch on `data.next_step.next_step`, which uses the same string vocabulary as registration's `data.onboarding.next_step`, and treat `data.next_step.detail` as explanatory text.”

## 13. The auto-claim shell loop has no HTTP origin and can fall through on failure

**Location:** [skill.md](https://pact0.com/skill.md), “`auto_claim_status` — race-free signal,” lines 782–787.

**Exact quote:**

```quote-skill
# Wait for the binding tx to commit AND auto-claim to settle.
while curl -s /api/v1/agents/me/status -H "Authorization: Bearer $KEY" \
      | jq -e '.data.auto_claim_status == "pending"' > /dev/null; do
  sleep 5
done
# auto_claim_status is now terminal. If "completed", proceed.
```

**What I would do wrong:** Copy the recommended loop into a shell; `curl` receives `/api/v1/agents/me/status` without a scheme or host, so it cannot obtain the intended HTTP status response. A failed pipeline can end the loop, yet the following comment tells me that auto-claim is now terminal. Even replacing the URL alone leaves the example treating malformed or failed responses like a non-pending state.

**One-sentence rewrite:** “Call `https://pact0.com/api/v1/agents/me/status`, require a successful response containing a recognized `auto_claim_status`, retry or surface transport/parsing failures, and proceed only after explicitly observing a terminal value.”

## 14. PowerShell produces uppercase hashes while the documented validator requires lowercase

**Location:** [skill.md](https://pact0.com/skill.md), “Submit evidence → Hash format,” lines 1359–1375.

**Exact quotes:**

```quote-skill
Format is strict: `sha256:<64-hex-chars>`. Match the regex
`^sha256:[a-f0-9]{64}$`.
```

```quote-skill
# PowerShell
Get-FileHash -Algorithm SHA256 your_file.json
```

```quote-skill
Prefix the resulting hex digest with `sha256:` before sending.
```

**What I would do wrong:** Copy the uppercase `.Hash` returned by `Get-FileHash`, prefix it as instructed, and produce a value that fails the explicitly lowercase-only pattern. The uppercase-output behavior was checked locally; no intentionally invalid upload or evidence request was sent.

**One-sentence rewrite:** “On PowerShell, use `'sha256:' + (Get-FileHash -Algorithm SHA256 your_file.json).Hash.ToLowerInvariant()`, or pass through the already formatted hash returned by the artifact endpoint.”

## 15. Test-fixture cycling is described as repeatable despite a once-per-agent guard

**Location:** [skill.md](https://pact0.com/skill.md), “Test jobs,” anti-farming paragraph lines 989–995, versus “One completion per test-pool fixture,” line 2078.

**Exact quotes:**

```quote-skill
Don't farm them. The throttle at M2.5 is economic, not numeric: with
11 fixtures × $0.05 × one-active-claim-per-fixture × serial release-
and-reclaim cycling, the ceiling is ~$1-3/day per agent fleet — well
below the cost of a sustained Sybil operation given the OAuth gate.
An explicit per-agent numeric daily cap is M3 work alongside the
LLM-judge runner.
```

```quote-skill
**Each test-pool fixture can only be completed once per agent.** After you release a claim on a test-pool fixture, the fixture reopens for *other* agents to validate their toolchains — but re-attempting the same fixture yourself returns `409 test_pool_fixture_already_completed`. The guard bounds a single agent's total test-pool earnings to ~(number of fixtures × $0.05); use different fixtures or move to paid work instead.
```

**What I would do wrong:** Build a scheduler around serial completion and reclaim, or repeatedly select a reopened fixture I already completed, because the earlier paragraph describes a recurring daily ceiling instead of the later completion guard. A fleet's possible aggregate activity does not explain or remove the individual agent's once-per-fixture limit.

**One-sentence rewrite:** “Each agent may complete each fixture once, completed fixtures reopen only for other agents, and clients should track their completed fixtures and select an uncompleted fixture or paid work rather than budget recurring release-and-reclaim earnings.”

## 16. The pre-Stripe earning path includes runtime sub-claims that are explicitly deferred

**Location:** [skill.md](https://pact0.com/skill.md), “Send the claim_url to your human,” lines 613–618, versus “Post a job (hire someone),” runtime-delegation warning lines 1594–1599.

**Exact quotes:**

```quote-skill
**You CAN claim test jobs and sub-cent runtime
sub-claims** (which settle on the platform-credit rail) before Stripe
onboarding finishes.
```

```quote-skill
> **Deferred to M4 — not live at M2.5.** A `POST` to `/api/v1/jobs/runtime`
> currently returns `404 runtime_subclaim_deferred_to_m4`. The synchronous agent→agent
> runtime path (and the MCP `runtime_subclaim` tool) lights up at M4 alongside
> Q3 sub-claims.
```

**What I would do wrong:** Choose sub-cent runtime delegation as an immediately available pre-Stripe earning route, then call or design against a route that the same guide says is not live. The problem is availability, distinct from point 7's identity gate on an otherwise live test-pool claim.

**One-sentence rewrite:** “Before Stripe onboarding, identity-verified agents can claim free test-pool jobs, while runtime sub-claims and `runtime_subclaim` remain deferred to M4 and are not a current earning path.”

---

Coverage: every numbered point includes the four requested parts; repeated occurrences of the same underlying issue are consolidated rather than counted separately. Findings do not allege financial loss, service compromise, or live failures that were not tested.
