# Paid-request flow verification report

Tested 2026-09-25 UTC against `https://explorer.imd.fun/request`, its same-origin `/api/requests/*` proxy, `https://api.imd.fun/requests/capabilities`, and `https://imd.fun/docs/`. This was read-only black-box testing: 23 quote requests, at least seven seconds apart, with generated bearer credentials. No signature, payment, transaction, wallet credential, publication, or other user's data was used.

## Plain-language summary

Job, Launch, and Oracle quoting worked in every page-supported variant tested. All successful quotes charged 0.5 IMD, expired exactly 600 seconds after issue, and said in the response that payment buys admission and does not guarantee a result. Request-key conflict protection and the documented size limits were enforced.

Release is presently broken at the quote step. The page's own placeholder and two progressively more explicit release descriptions all received the same `422 recheck_failed / needs_revision`. The response says to consult recheck questions, suggestions, review items, and prerequisites, but supplies none of them. The page consequently can only show an opaque serialized problem object, leaving a customer no path to correct the request.

Two additional customer-facing problems were verified: the page does not display the quote's “admission, not guaranteed result” terms before “Sign and pay,” and it withholds all product choices until a wallet is connected. The public API's validation responses also omit the documented actionable detail.

The supplied step-1 artifact `artifacts/findings.md` was absent, so there was no prior finding list to independently accept or reject. This report therefore contains only observations independently reproduced in this run. A real injected wallet and a rendered browser were unavailable; payment/signing was prohibited. The connected-wallet visual state and actual 390 px rendering are unanswered, though responsive CSS and server output were inspected.

## Verified findings

| ID | Severity | Title | Where |
|---|---|---|---|
| V-01 | high | Release cannot obtain a quote and gives no repair path | `POST https://explorer.imd.fun/api/requests/quote`, action `workflow.open` |
| V-02 | medium | “Admission, not guaranteed result” is not shown before payment | `https://explorer.imd.fun/request`, quoted-state panel |
| V-03 | low | Validation errors are bare and differ from the documented error contract | quote API proxy and paid-request docs |
| V-04 | low | A customer cannot inspect request types without connecting a wallet | initial state of `https://explorer.imd.fun/request` |
| V-05 | low | Useful API controls are fixed or omitted in the public form | connected form payload builder |

## Flow map

| Step | What the customer sees | What is sent | What came back |
|---|---|---|---|
| Load without wallet | “Connect the wallet that pays. It needs 0.5 IMD per request, and one approval the first time.” No Job/Launch/Release/Oracle form is rendered. | Initial HTML plus `GET /api/requests/capabilities` in the client. | Page `200`; direct API capabilities `200`, advertising four actions, 0.5 IMD and 600-second TTL. |
| Connect | The code rejects unsupported contract wallets, reads IMD balance/allowance, and offers a one-time 5 IMD approval if needed. | Wallet RPC plus Ethereum reads. | Not exercised: no wallet credentials and no transactions were allowed. |
| Describe | Job has six kinds; Launch has Contracts/hook; Release promises Sepolia deployment, IPFS site and GitHub source; Oracle exposes three answer types, two evidence types and three windows. | Local state only. | No server call yet. |
| Get a quote | Form disables after a request key is stored. | `POST /api/requests/quote` with `{requestKey, action, input}` and a generated 32-byte bearer token. Exact payload shapes appear below. | 14/17 matrix calls returned `201`; all three Release calls returned `422`. |
| Review quote | Price, payee, countdown, state “Quote ready,” and “Nothing is charged until you sign and pay.” | No write. | Successful quote includes 0.5 IMD, 600-second TTL and undisplayed terms `{purchase:"action-admission", resultGuaranteed:false}`. |
| Sign and pay | “Sign and pay”; code requests Permit2 payment and EIP-712 QuoteApproval signatures. | Would call order submit endpoints and wallet signing. | Not exercised by rule. |

### Exact page payload templates verified from the served client

- Job: `{"requestKey":"<UUID>","action":"job.open","input":{"objective":"<trimmed text>","skill":"<skill>","outputs":[...]}}`. The output is included for Image, Audio, and Video; Website sends no `ipfs` field.
- Launch: `{"requestKey":"<UUID>","action":"launch.open","input":{"objective":"<trimmed text>","shape":"chain","references":[...],"steps":[{"skill":"build-contract-project"},{"skill":"adversarial-review"}],"onchain":"evm_project|univ4_hook"}}`.
- Release: `{"requestKey":"<UUID>","action":"workflow.open","input":{"request":"<trimmed text>","context":"","draft":{"objective":"<first 8000 chars>","shape":"chain","onchain":"evm_project","github":true,"ipfs":true,"steps":[{"skill":"build-contract-project"},{"skill":"frontend-for-contract"},{"skill":"adversarial-review"}]},"permissions":{"github":true,"ipfs":true,"onchain":{"kind":"evm_project","chainId":11155111}}}}`.
- Oracle: `{"requestKey":"<UUID>","action":"oracle.request","input":{"v":1,"question":"<trimmed text>","chainId":1,"window":{"hours":24|168|720},"answerType":"bool|uint256|address","evidence":"panel|chain","panelSize":5,"quorum":4,"validForSeconds":86400}}`.

## Findings in detail

### V-01 — Release cannot obtain a quote and gives no repair path

Severity: **high**. Real-world impact: the entire advertised Release product cannot progress to payment, and customers are not told how to make a request acceptable.

Reproduction, observed three times:

1. POST to `https://explorer.imd.fun/api/requests/quote` with a fresh 64-hex bearer token and the exact Release template above.
2. At `2026-09-25T07:38:17Z`, set `request` and `draft.objective` to the page placeholder: `A fixed-supply ERC-20 called Tip Vault, and a one-page site that shows its supply and a connected wallet balance.` Request key `91f613ad-e1dc-4412-b99e-ac06d7390880`.
3. At `2026-09-25T07:38:25Z`, use: `Release a fixed-supply ERC-20 named Harbor Token with symbol HBR, mint 1000000 tokens to the deployer, then build a one-page site showing total supply and the connected wallet balance.` Request key `c53a8927-0e18-40a1-8e01-85ca334e61a9`.
4. At `2026-09-25T07:38:33Z`, use: `Release a fixed-supply ERC-20 named Garden Credit with symbol GCR and 18 decimals. Mint 1000000 GCR to the deployer. Build a responsive website against the live deployment showing token name, symbol, total supply, contract address, connected wallet address and balance.` Request key `81cd425b-505f-4271-b2a3-563952fc8f06`.

Every response was HTTP 422 with exactly:

```json
{"error":"invalid_input","detail":"Rebuilt draft still needs attention: needs_revision. See recheck questions, suggestions, reviewItems and prerequisites.","problems":[{"code":"recheck_failed","detail":"Rebuilt draft still needs attention: needs_revision. See recheck questions, suggestions, reviewItems and prerequisites."}]}
```

The served page converts each object in `problems` with `JSON.stringify`, so the customer sees the raw object. The referenced questions/suggestions/review items/prerequisites are absent from the response.

Expected: the placeholder should quote, or the response/page should show concrete recheck questions and allow the customer to revise and retry.

Recommended fix: first repair the workflow evaluator/page-generated draft contract so the canonical placeholder reaches `201`. Independently return structured `questions`, `suggestions`, `reviewItems`, and `prerequisites` on failure; render those as labelled, actionable text and preserve editable input. Add an end-to-end test that submits the page placeholder through the proxy.

### V-02 — “Admission, not guaranteed result” is not shown before payment

Severity: **medium**. Real-world impact: a customer can sign a non-refundable paid admission request without being told in the payment UI that delivery is not guaranteed.

Reproduction:

1. At `2026-09-25T07:37:18Z`, POST a Job report quote with objective `Compare three public restaking protocols and summarize their documented slashing conditions.` and request key `57b29b17-5f8e-4996-82f4-56beaef3e4ee`.
2. Observe HTTP 201. The quote contains `"terms":{"purchase":"action-admission","resultGuaranteed":false}`, amount `500000000000000000`, decimals `18`, and `expiresAt-issuedAt = 600`.
3. Inspect the served quoted-state UI at `https://explorer.imd.fun/request`: it renders request, price, payee, countdown, state, and “Nothing is charged until you sign and pay,” then the “Sign and pay” button. It never renders `quote.terms` or equivalent wording.

Expected: immediately above the signing button, plain text should say that 0.5 IMD buys admission/attempt, not a guaranteed successful result, and explain the refusal/failure policy.

Recommended fix: render the server-returned terms next to price and require a short acknowledgement before enabling “Sign and pay.” Use the same wording for every action, with the Oracle-specific warning that panel disagreement may yield no answer.

### V-03 — Validation errors are bare and differ from the documented error contract

Severity: **low**. Real-world impact: API customers receive no field/path explanation and must guess how to repair invalid input.

Reproduction against the same proxy, each with a fresh key/token:

- `2026-09-25T07:40:21Z`: Job input `{"objective":"","skill":"research-report"}` → HTTP 400 `{"error":"invalid_request"}`.
- `2026-09-25T07:40:29Z`: Job objective of exactly 8,001 `x` characters → HTTP 400 `{"error":"invalid_request"}`.
- `2026-09-25T07:40:36Z`: Oracle question of exactly 2,001 `q` characters with otherwise valid page defaults → HTTP 400 `{"error":"invalid_request"}`.
- `2026-09-25T07:40:43Z`: 16,624-byte JSON body → HTTP 413 `{"error":"request_too_large"}`.

The docs describe `422 invalid_input` with `detail` and `problems`; the observed limit failures instead returned bare 400/413 errors. The browser prevents empty and over-field-limit submissions, so this primarily affects direct API users.

Expected: status and schema should match the documentation, and responses should name the invalid field and allowed limit.

Recommended fix: standardize errors, for example `422 {error:"invalid_input", problems:[{path:"input.objective", code:"too_big", max:8000}]}` and document the separate 413 body-size case if retained.

### V-04 — A customer cannot inspect request types without connecting a wallet

Severity: **low**. Real-world impact: wallet-cautious first-time visitors cannot evaluate what is being sold before granting site access to an account.

Reproduction:

1. At `2026-09-25T07:36:11Z`, GET `https://explorer.imd.fun/request` without wallet state.
2. Observe HTTP 200 and only the general description, 0.5 IMD price, approval note, and “Connect wallet” call to action. The component returns before rendering the four action tabs or their descriptions.

Expected: all product descriptions, limits, chain, deliverables, and terms should be browsable read-only; wallet connection should be necessary only to quote/pay or recover orders.

Recommended fix: render the form and explanatory cards in preview mode without an account, disable only “Get a quote,” and put “Connect wallet to quote” on that button.

390 px note: the CSS has a `max-width:640px` branch, makes action tabs horizontally scrollable, and changes six Job cards to two columns. Actual pixel rendering was not verified, so no visual defect is claimed.

### V-05 — Useful API controls are fixed or omitted in the public form

Severity: **low**. Real-world impact: customers cannot express documented publication and oracle-consensus preferences and may buy a materially different configuration than intended.

Reproduction:

1. Fetch `https://explorer.imd.fun/request` and its served request client at `2026-09-25T07:36–07:37Z`.
2. Compare the exact builders above with `https://imd.fun/docs/#job-body` and `#oracle-body`.
3. Observe: Job has no GitHub control; Website sends neither `ipfs` nor a site label; Oracle fixes `panelSize=5`, `quorum=4`, and `validForSeconds=86400`. Release fixes GitHub/IPFS true and Sepolia (`chainId=11155111`).

This is a verified omission, not proof the defaults are wrong. The page does clearly say Launch deploys on Sepolia, Release promises IPFS/GitHub, Website promises source/static export only, and Oracle states “Five agents answer and four must agree.”

Expected: expose the options likely to affect the purchased deliverable, or explicitly label fixed choices as defaults and link to the API for advanced configuration.

Recommended fix: add GitHub and IPFS/site-label controls for Job Website, panel size/quorum/validity controls for Oracle with constraints, and an “Advanced/API” link. Keep the current clear Sepolia disclosure.

## Successful and control checks

These are verified facts, not findings:

- Job Report, Website, Contracts, Image, Audio, and Video each returned HTTP 201 between `2026-09-25T07:37:18Z` and `07:37:55Z`.
- Launch Contracts and Uniswap v4 hook returned HTTP 201 at `07:38:02Z` and `07:38:10Z`.
- Oracle `bool`, `uint256`, and `address`, each with both `panel` and `chain` evidence, returned HTTP 201 between `07:38:42Z` and `07:39:21Z`.
- All inspected successful quotes had 0.5 IMD payment and a 600-second TTL.
- At `07:40:06Z`, a fresh request key returned 201; at `07:40:14Z`, the same key with a changed body returned HTTP 409 `{"error":"request_key_conflict"}` as documented.
- The served form uses `maxLength=8000` for Job/Launch, `maxLength=16000` for Release text, and `maxLength=2000` for Oracle questions; its empty-text checks are local and action-specific.
- Rate-limit behavior was intentionally not triggered. Quotes were spaced at least seven seconds apart.
- Expired-quote server behavior was not retested because waiting and then approaching submit behavior was unnecessary for verifying the available findings; the UI countdown and 600-second issued/expiry values were verified.

## Prioritised fixes

1. **Restore Release quoting and return/render the full recheck guidance** (V-01).
2. **Put admission/non-guarantee terms immediately beside “Sign and pay”** (V-02).
3. **Make validation responses actionable and align documentation/status codes** (V-03).
4. **Allow read-only product exploration before wallet connection** (V-04).
5. **Expose or clearly signpost publication and oracle-consensus controls** (V-05).

## Dropped findings

- **No step-1 findings could be reviewed:** `artifacts/findings.md` did not exist in the supplied working tree. Nothing has been silently promoted from earlier work.
- **“Website promises IPFS but does not request it” — dropped/refuted:** the Website card promises “A React site, its source and a static export,” not IPFS. The payload omits `ipfs`, consistently with that wording.
- **“Launch chain is unclear” — dropped/refuted:** the form explicitly says contracts are deployed on Sepolia; the server-provided chain is 11155111.
- **“Price or expiry is wrong” — dropped/refuted:** successful quotes showed 0.5 IMD and exactly 600 seconds, and the page renders both price and a countdown.
- **“Changed body with the same request key is accepted” — dropped/refuted:** the observed result was the expected HTTP 409 conflict.
- **390 px layout failure — dropped/unverified:** only responsive source/CSS could be checked; no rendered 390 px viewport was available, so claiming a visual failure would be unsupported.
- **Expired quote reuse defect — dropped/unverified:** no payment or submit call was allowed and the available findings did not justify waiting ten minutes solely to manufacture an expiry case.

## Uncertainty and unanswered questions

- Whether Release failure is caused by the evaluator, unavailable workflow prerequisites, or a mismatch in the page-generated draft is unknown from black-box evidence.
- Whether the payment/admission policy refunds a paid-but-refused request is not stated in the quoted page copy observed here.
- Actual wallet prompts, EIP-712 display quality, 390 px visual layout, post-payment admission, and recovery across browsers remain untested by design.
