@ixo/decision-oracle 0.2.31 → 0.2.33

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/README.md CHANGED
@@ -43,9 +43,10 @@ publishing a protocol and its rubric, and for anyone who needs a claim judged th
43
43
  - **Be an OpenHands checker with one flag.** `init --openhands` wires the add-on: the OpenHands
44
44
  app's hire webhook makes your oracle authorize itself on each ask that picked it, register it and
45
45
  start judging, with a backstop that keeps every hired ask topped up.
46
- - **Drive all of it with the shipped Claude skills.** `init` copies eight skills into
46
+ - **Drive all of it with the shipped Claude skills.** `init` copies ten skills into
47
47
  `.claude/skills/`: `create-oracle`, `write-facts`, `create-rubric`, `publish-protocol`,
48
- `create-collection`, `decision-api-oracle`, `decision-api-client`, `add-openhands-support`.
48
+ `create-collection`, `decision-api-oracle`, `decision-api-client`, `add-openhands-support`,
49
+ `update-pricing`, `operate-oracle`.
49
50
 
50
51
  ## How it works
51
52
 
@@ -738,7 +739,7 @@ packages/eval-oracle/ THE package — @ixo/decision-oracle
738
739
  src/ the Worker: routes, workflows, the facts lane, the CLI
739
740
  src/facts/ src/testing/ the two node-safe public subpaths
740
741
  migrations/ the D1 migrations — shipped, never hand-written by a consumer
741
- skills/ the eight Claude skills `init` copies into a new project
742
+ skills/ the ten Claude skills `init` copies into a new project
742
743
  templates/project/ exactly what `decision-oracle init` generates
743
744
  dev/ engine-conformance harnesses (ours, not published)
744
745
  docs/ api.md (the API reference) + guide.md (the walkthrough)
@@ -786,6 +787,6 @@ symlinks are not packed). CI diffs them — change one, copy it to the other.
786
787
 
787
788
  ## Status
788
789
 
789
- Published on npm: `@ixo/decision-oracle` 0.2.31 and `@ixo/decision-oracle-client` 0.1.3.
790
+ Published on npm: `@ixo/decision-oracle` 0.2.33 and `@ixo/decision-oracle-client` 0.1.3.
790
791
 
791
792
  Apache License 2.0: `LICENSE` at the repo root and in each package.
package/docs/guide.md CHANGED
@@ -71,7 +71,7 @@ bun run cf-typegen
71
71
  | `wrangler.jsonc` | **yours to fill** | Bindings, vars, the cron, the custom domains. Binding and var **names are the package's contract** — never rename one |
72
72
  | `.dev.vars` | **yours, never committed** | Local secrets. `.dev.vars.example` lists every one with what it does |
73
73
  | `tsconfig.json`, `biome.json`, `vitest.config.ts`, `package.json` | generated | Scripts: `dev`, `deploy`, `deploy:devnet`, `deploy:testnet`, `deploy:production`, `build`, `cf-typegen`, `db:migrate`, `db:migrate:remote`, `test`, `typecheck`, `lint`, `check` |
74
- | `.claude/skills/` | shipped | The six skills below. Refresh after an upgrade: `bunx decision-oracle skills sync` |
74
+ | `.claude/skills/` | shipped | The ten skills in §14. Refresh after an upgrade: `bunx decision-oracle skills sync` |
75
75
  | `CLAUDE.md` | shipped | The two rules, and where the package docs live |
76
76
 
77
77
  Everything else — pulling claims, the engine handshake, the D1 ledgers, review folds, the chain
@@ -1652,7 +1652,7 @@ own retries, and a terminal failure clears it.
1652
1652
 
1653
1653
  ## 14. For AI agents working in a generated project
1654
1654
 
1655
- Six skills ship with the package and are copied into `.claude/skills/` by `init` (refresh with
1655
+ Ten skills ship with the package and are copied into `.claude/skills/` by `init` (refresh with
1656
1656
  `bunx decision-oracle skills sync`). Invoke the one that matches the job rather than improvising:
1657
1657
 
1658
1658
  | Job | Skill |
@@ -1663,6 +1663,10 @@ Six skills ship with the package and are copied into `.claude/skills/` by `init`
1663
1663
  | Anchor a claim form and rubric as an on-chain protocol entity | `publish-protocol` |
1664
1664
  | Create a deed and a claim collection, fund it, grant the roles | `create-collection` |
1665
1665
  | Turn on, deploy and verify the Decision API lane (`API_LANE`) — engine registration (payable at once on the Standard plan), operator sources and keys, verifying it | `decision-api-oracle` |
1666
+ | Call a Decision API oracle as a customer — test credentials, the SDK and CLI, MCP, paying | `decision-api-client` |
1667
+ | Make the oracle hireable from the Openhands app (`init --openhands`) | `add-openhands-support` |
1668
+ | Change what the oracle charges, and refresh the card apps show | `update-pricing` |
1669
+ | Run a deployed oracle — upgrades, pauses, renewals, reviews, alerts, admin top-ups | `operate-oracle` |
1666
1670
 
1667
1671
  And the three rules that matter more than any of them:
1668
1672
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ixo/decision-oracle",
3
- "version": "0.2.31",
3
+ "version": "0.2.33",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Run an IXO evaluation oracle: pull claims, produce facts with your handlers, let the decision engine decide, sign on chain",
6
6
  "keywords": [
package/skills/README.md CHANGED
@@ -11,11 +11,13 @@ installed package (leaving any skill of your own in that directory alone).
11
11
  | `create-rubric` | authoring a rubric for a claim form and validating it against the engine |
12
12
  | `publish-protocol` | anchoring a claim form and rubric on chain as a protocol entity |
13
13
  | `create-collection` | creating a claim collection an oracle can be hired against |
14
- | `decision-api-client` | testing a Decision API oracle from the customer side: devnet test credentials (source, keys, payment delegation), the SDK and CLI smoke, MCP from Claude Code, the docs agent test |
15
- | `decision-api-oracle` | turning on, deploying and verifying the Decision API lane — the shipment-delivery example (SwiftDrop Express) on devnet end to end: publish, identity, Cloudflare, deploy, engine registration (payable at once), operator, verify, emails |
14
+ | `decision-api-client` | calling a Decision API oracle from the customer side: test credentials (source, keys, payment delegation), the SDK and CLI, MCP from Claude Code, paying by card |
15
+ | `decision-api-oracle` | turning on, deploying and verifying the Decision API lane for your own oracle: protocol, identity, Cloudflare, deploy, engine registration (payable at once), operator, verify, emails, payments |
16
16
  | `add-openhands-support` | making an oracle hireable from the Openhands app (asks, the Yoma youth world): the OpenHands add-on (`init --openhands`), who pays for decisions, the account to register with, the logo and the picker's card |
17
+ | `update-pricing` | changing what a running oracle charges: the check fee, per-protocol fees, the Decision API price, and refreshing the Agent Card apps show |
18
+ | `operate-oracle` | looking after a deployed oracle: upgrades and migrations, pausing and resuming collections, renewals and key rotation, reviews and recovery, logs, status and alerts, topping up an admin account |
17
19
 
18
20
  `reference/engine-contract.md` (here, beside the skills) is the decision engine's contract —
19
- background for all eight, not an interface anyone codes against.
21
+ background for all ten, not an interface anyone codes against.
20
22
  `reference/linked-docs.md` is the linked-document rules: where an entity's documents are uploaded,
21
23
  each proof's format, and how to read one.
@@ -67,8 +67,9 @@ protocol.
67
67
  - the oracle address to grant Evaluate; the agent address(es) to grant Submit (comma-separated)
68
68
  — `--submit addr1,addr2` is also how to restrict who may claim, e.g. a payouts collection only
69
69
  two people may submit to
70
- - quota and window in days if the defaults (100, 90) aren't right — state them explicitly rather
71
- than silently applying them
70
+ - the collection's claim quota and window in days if the defaults (100, 90) aren't right —
71
+ state them explicitly rather than silently applying them. `--quota` is the COLLECTION's quota
72
+ only; the grants' own quota is fixed (step 5)
72
73
 
73
74
  2. **Log in once** if there is no session for this network yet:
74
75
  `bunx decision-oracle login --network devnet`.
@@ -96,9 +97,14 @@ protocol.
96
97
  [--max-custom-amount 0uixo] [--phase collection|fund|grants|all] [--yes]
97
98
  ```
98
99
 
99
- `--eval-denom` must equal the oracle's `EVALUATION_DENOM` — on mainnet the USDC ibc denom or
100
- `upay`, never `uixo`. `--eval-amount 0` writes an EMPTY evaluation leg (the chain refuses a zero
101
- coin): that is what a collection for a per-review-billed or 0-minimum oracle registers with.
100
+ `--eval-denom` must equal the oracle's `EVALUATION_DENOM` (read `GET <oracle>/v1/info`
101
+ `pricing`). A third-party oracle charges IXO (`uixo`, any network) or, on mainnet, USDC.inj
102
+ (`ibc/77C12480BD8C29BF0ACEC2143736C15BAF8CD232CA27BAE09336677B828955E6`). PAY (`upay`) is only
103
+ for oracles IXO runs: register refuses it on any other (`pay_requires_ixo_managed`).
104
+ `--eval-amount 0` writes an EMPTY evaluation leg (the chain refuses a zero coin): that is what a
105
+ collection for a per-review-billed or 0-minimum oracle registers with. `--fund` always sends
106
+ `uixo`: an admin that pays evaluations in USDC is topped up with
107
+ `bunx decision-oracle org fund --entity <deed did> --amount <whole USDC> --coin usdc --network mainnet`.
102
108
 
103
109
  `--max-custom-amount` is the evaluate grant's `MaxCustomAmount`, written explicitly (`0<eval
104
110
  denom>` by default). The chain holds EVERY non-empty evaluation amount to it (its denom must be
@@ -138,6 +144,20 @@ protocol.
138
144
  it is a bearer credential — and it expires on its own schedule (30 days by default), independent
139
145
  of the grants' 365-day expiry.
140
146
 
147
+ **The grants' limits.** Each grant expires after 365 days, and the evaluate grant's
148
+ `agentQuota` is hard-coded to 100 — `--quota` does not change it. Every `MsgEvaluateClaim` the
149
+ oracle signs uses one slot, and a claim that goes to a person uses TWO: the oracle signs it
150
+ FLAGGED while the person decides (it pays nothing), then signs the final verdict. So 100 slots
151
+ cover 100 decided claims, or 50 reviewed ones. When the grant runs out or expires, the engine
152
+ answers `NO_EVALUATE_GRANT` and the collection pauses `engine-no-grant`. Re-grant with the grants
153
+ phase alone (a new grant replaces the old one), then re-register to resume:
154
+
155
+ ```bash
156
+ bunx decision-oracle create-collection --entity <deed did> --protocol <protocol did> \
157
+ --phase grants --evaluate <oracle address> [--max-custom-amount <amount><denom>] [--network <n>] [--yes]
158
+ bunx decision-oracle register --collection <id>
159
+ ```
160
+
141
161
  The bot returns the claim's JSON re-ordered, so its bytes can't be CID-checked: a claim the oracle
142
162
  read only from the Matrix claim bot goes to the engine as `claimContent` with no `document`, and
143
163
  is decided like any other. For a payout protocol the oracle also looks past the bot to the IPFS
@@ -162,16 +182,18 @@ protocol.
162
182
  [--file <fileQuestion>=<path> …] [--yes]
163
183
  ```
164
184
 
165
- (claim-bot media upload → save into the deed's room → `MsgExec(MsgSubmitClaim)`. The
166
- passport-delivery example's `protocol/sample-claim.json` is a worked answers file.)
185
+ (claim-bot media upload → save into the deed's room → `MsgExec(MsgSubmitClaim)`.)
186
+ `answers.json` is a flat object of the claim form's answers keyed by question name, e.g.
187
+ `{ "orderId": "ORD-1", "deliveredAt": "2026-11-01T10:00:00Z" }`; each `--file` answers one file
188
+ question with a local file.
167
189
 
168
190
  7. **Report the run** — the output contract below.
169
191
 
170
192
  Payout ASKS (a deed or request with its organisation links and `#payoutTerms`) are posted only
171
- through OpenHands, not with these commands. A company paying people from its OWN collection runs
172
- its own self-hosted payouts facilitator instead: enable the collection there
173
- (`/v1/payouts/collections`) with a `fromDecision` rule on `$.<amount field>`, and name the same
174
- amount field and currency in the oracle's `payouts` config.
193
+ through OpenHands, not with these commands. A company that pays people from its OWN collection
194
+ names the amount field and currency per protocol in the oracle's `payouts` config; an approved
195
+ claim's receipt then carries the payment to make. Optional — see
196
+ `node_modules/@ixo/decision-oracle/docs/api.md` (`payouts`).
175
197
 
176
198
  ## Output contract
177
199
 
@@ -199,6 +221,8 @@ account, a phase that was dry-run but never confirmed, or a delegation nobody ha
199
221
  | Creating the collection before the deed links the protocol | run `create-deed --phase claims`; the picker reads `linkedClaim` → claims-config |
200
222
  | Granting Evaluate and expecting the oracle to pull claims | it also needs the claim-bot delegation (step 5) |
201
223
  | Leaving the admin account unfunded | every evaluation debits it, rejects included — pass `--fund` |
224
+ | Expecting `--quota` to size the evaluate grant | the grant's `agentQuota` is always 100 (a reviewed claim uses 2); re-run `--phase grants` when it runs out |
225
+ | `--eval-denom upay` for a third-party oracle | register refuses `pay_requires_ixo_managed` — use `uixo`, or USDC.inj on mainnet |
202
226
  | Guessing the evaluation amount | it must meet the oracle's `EVALUATION_MIN_AMOUNT` in its `EVALUATION_DENOM`, or register 402s |
203
227
  | `--eval-amount 0` expecting a zero coin | it writes an empty evaluation leg — right only for a per-review-billed or 0-minimum oracle |
204
228
  | Running with `--yes` first | dry-run, show the plan, then confirm |
@@ -58,7 +58,7 @@ explicitly — `0<--eval-denom>` unless the owner passes `--max-custom-amount <a
58
58
  chain checks EVERY non-empty evaluation amount against it (x/claims `IsCoinsInMaxConstraints`; the
59
59
  denom must be listed), and the act lane sends an ordinary claim's own coins by default, so claims
60
60
  that carry coins need a cap ≥ the largest claim amount in each denom — at `0` their approvals fail
61
- at MsgExec. It is also the owner's cap on any amount an evaluator sets (company authority D7); the
61
+ at MsgExec. It is also the owner's cap on any amount an evaluator sets; the
62
62
  oracle side's own switch is the owner's `operatorAmounts` (`PATCH /v1/collections/:id/settings`),
63
63
  honoured only while the owner who set it still owns the collection. All grant messages for one run
64
64
  broadcast as ONE tx. No addresses given ⇒
@@ -24,7 +24,8 @@ Write the rules claims are judged by → `create-rubric`. Put a claim form and i
24
24
  `publish-protocol`. Open a place for claims to be submitted and hire an oracle on it →
25
25
  `create-collection`. Let apps and agents ask the oracle for decisions over HTTP →
26
26
  `decision-api-oracle`; test that from the customer's side → `decision-api-client`. Be hireable from
27
- the Openhands app → `add-openhands-support`.
27
+ the Openhands app → `add-openhands-support`. Change what the oracle charges → `update-pricing`. Keep
28
+ a deployed oracle running (upgrades, pauses, renewals, alerts) → `operate-oracle`.
28
29
 
29
30
  **Words used throughout:**
30
31
 
@@ -42,7 +43,7 @@ the Openhands app → `add-openhands-support`.
42
43
  - **did:web** — this oracle's web identity, a name tied to its domain; other services check its
43
44
  signatures by fetching `https://<host>/.well-known/did.json`.
44
45
  - **Delegation** — a signed permission one party gives another, such as the owner letting the engine
45
- bill their subscription, or letting the oracle read their claims.
46
+ bill their engine credits, or letting the oracle read their claims.
46
47
 
47
48
  ## When to use
48
49
 
@@ -61,12 +62,23 @@ handlers themselves (`write-facts`, handed off to at step 3).
61
62
  bunx @ixo/decision-oracle init <dir> [--name <project-name>] [--openhands]
62
63
  cd <dir> && bun install
63
64
  bun run cf-typegen # after ANY wrangler.jsonc binding/var change, too
64
- wrangler d1 create <project-name> # paste the id into wrangler.jsonc
65
- wrangler kv namespace create <project>-cache # paste the id into wrangler.jsonc (KV names are per account)
66
65
  bun run db:migrate # the package's own migrations, applied to the local D1
67
66
  cp .dev.vars.example .dev.vars # git-ignored; secrets only
68
67
  ```
69
68
 
69
+ Local dev needs no Cloudflare resources: the placeholder ids at the top of `wrangler.jsonc` are fine
70
+ for `bun run dev` and the tests. Each env you deploy (`devnet`, `testnet`, `production` in the
71
+ template) needs its own D1 and KV, named exactly as `init` printed them, because that env's block
72
+ already names the database `<project-name>-<env>`:
73
+
74
+ ```bash
75
+ bunx wrangler d1 create <project-name>-<env> # → env.<env> d1_databases[0].database_id
76
+ bunx wrangler kv namespace create <project-name>-cache-<env> # → env.<env> kv_namespaces[0].id
77
+ ```
78
+
79
+ Paste each printed id over its placeholder in that env's block only (KV names are per account, so
80
+ two oracles in one account need two project names).
81
+
70
82
  AI checks run on the Cloudflare AI Gateway by default. `AI_GATEWAY_ID` (in `wrangler.jsonc`, every
71
83
  env) is the name of a gateway in the provider's own Cloudflare account: dashboard → AI → AI Gateway
72
84
  → create one and use its name. Third-party models through it need AI Gateway credits on that
@@ -154,6 +166,11 @@ signed-in AI app's own auto-approve limits (the hosted MCP's "Don't ask again")
154
166
  without a per-claim approval; `false` means every such submit waits for the user's approval of that
155
167
  claim. IXO's own oracles turn it on; anyone else decides it explicitly — ask.
156
168
 
169
+ Signing uses the collection's evaluate grant. Each `MsgEvaluateClaim` the oracle signs costs gas
170
+ (paid by `ORACLE_ADDRESS`, which `oracle init` keeps topped up) and one slot of the grant's quota. A
171
+ claim that goes to a person costs two: it is first signed FLAGGED (status 5, pays nothing) while the
172
+ person decides, then signed again with the final verdict after the review.
173
+
157
174
  ### 3. The handlers → the `write-facts` skill
158
175
 
159
176
  ```bash
@@ -187,13 +204,14 @@ table produced plus any validation issue (exit 1 if there are issues); `--previe
187
204
  engine's preview lane and prints the decision they would produce — nothing persisted, nothing billed,
188
205
  no grant needed. This is the first end-to-end proof.
189
206
 
190
- A flat answers file has no attachments, so any fact that reads one fails closed: in the passport
191
- example the AI check freezes as an `error` (its guard's path) rather than reaching the stock judge.
192
- Exercising that lane needs a claim file with an `attachments` entry pointing at a local photo.
207
+ A flat answers file has no attachments, so any fact that reads one fails closed: an AI check over a
208
+ photo freezes as an `error` (its guard's path) rather than reaching the stock judge. Exercising
209
+ that lane needs the envelope, with each file as `{ question, name, mediaType, path }` (`path`
210
+ relative to the claim file) — the `write-facts` skill shows one.
193
211
 
194
212
  ### 6. The owner's delegation (engine billing)
195
213
 
196
- The engine bills the oracle OWNER's credits, so the owner — whoever holds the IXO subscription —
214
+ The engine bills the oracle OWNER's credits, so the owner — the account that buys those credits —
197
215
  mints a delegation to the engine before register will work. **Do this step, don't just quote it.**
198
216
 
199
217
  ```bash
@@ -214,13 +232,14 @@ printf 'OWNER_DELEGATION_CAR="%s"\n' "$(cat owner-delegation.car)" >> .dev.vars
214
232
  before it lapses.
215
233
  - Only the human can do the login. Run it in the background so the loopback callback lands, and tell
216
234
  them to finish in the browser.
217
- - **The owner must hold an IXO subscription before `engine register`**, or it answers 402
218
- `NO_SUBSCRIPTION`. A subscription created by the subscriptions workflow (not the portal) also
219
- needs a Stripe customer: the owner calls the subscriptions API's `POST /api/v1/payments/intent`
220
- once (a SetupIntent — nothing is charged).
221
- - **A 402 at register is the owner's credits, not a bug**: `NO_SUBSCRIPTION` (none at all) or
222
- `INSUFFICIENT_CREDITS` (below one decision's price, carrying `manageSubscriptionUrl`). Send the
223
- owner to that portal URL to buy credits, then register again — nothing here can fake it.
235
+ - **The owner needs IXO decision-engine credits before `engine register`.** The engine bills
236
+ every decision to them. The owner buys credits in the Decision Console (devnet:
237
+ `https://decision-console.devnet.ixo.earth`) › Billing › Credits, signed in as the same account
238
+ that minted the delegation; the first purchase opens their credits account.
239
+ - **A 402 at register is the owner's credits, not a bug**: `NO_SUBSCRIPTION` (no credits account
240
+ yet) or `INSUFFICIENT_CREDITS` (below one decision's price; the engine adds a
241
+ `manageSubscriptionUrl`). `engine register` prints where to buy them (`<CONSOLE_URL>/billing/credits`
242
+ when `CONSOLE_URL` is set). Send the owner there, then register again — nothing here can fake it.
224
243
 
225
244
  ### 7. Run it locally — and leave it running
226
245
 
@@ -321,16 +340,45 @@ where a live token would have skipped (it mints a new Matrix device — not a ca
321
340
 
322
341
  ### 9. Deploy it
323
342
 
343
+ First write that env's secrets file, `.dev.vars.<env>` (git-ignored like `.dev.vars`). It is what
344
+ `--secrets-file` uploads and what `engine register --env-file` reads below. Never echo a value:
345
+
346
+ ```bash
347
+ {
348
+ grep -E '^(SERVICE_ED25519_MNEMONIC|ORACLE_SECP_MNEMONIC|ORACLE_MATRIX_ACCESS_TOKEN)=' .dev.vars
349
+ printf 'OWNER_DELEGATION_CAR="%s"\n' "$(cat owner-delegation.car)"
350
+ printf 'UNIQUE_HMAC_KEY="%s"\n' "$(openssl rand -base64 32)"
351
+ } > .dev.vars.<env> && chmod 600 .dev.vars.<env>
352
+ ```
353
+
354
+ What a deployed env needs:
355
+
356
+ | Secret | Needed |
357
+ | --- | --- |
358
+ | `SERVICE_ED25519_MNEMONIC` | always — the did:web key |
359
+ | `ORACLE_SECP_MNEMONIC` | always — signs `MsgEvaluateClaim` and the engine registration |
360
+ | `OWNER_DELEGATION_CAR` | always — register refuses without it. Mint it (step 6) with `--oracle did:web:<this env's host>`: a CAR minted for another host is refused `DELEGATION_NOT_FOR_ORACLE` |
361
+ | `UNIQUE_HMAC_KEY` | when a rubric declares `unique`, and always with `API_LANE` on. One per deployment, for life |
362
+ | `ORACLE_MATRIX_ACCESS_TOKEN` | for review cards. It belongs to one network's homeserver: copy it only into an env on the network step 8 ran on |
363
+ | `OPENROUTER_API_KEY` | only with `AI_PROVIDER=openrouter` (the AI Gateway default needs no secret) |
364
+ | `WEBHOOK_ENCRYPTION_KEY` | only with `API_LANE` on (`openssl rand -base64 32`, for life) |
365
+ | `SENTRY_DSN`, `OPS_ALERT_WEBHOOK_URL` | optional (`operate-oracle`) |
366
+
367
+ The env file must NOT carry a localhost `ORACLE_HOSTNAME` (that env's host comes from its
368
+ `wrangler.jsonc` vars). **Keep a private backup of every secret outside the repo** (a password
369
+ manager, or a 0600 file only the owner can read): Cloudflare never shows a secret again, and a lost
370
+ mnemonic or `UNIQUE_HMAC_KEY` cannot be recovered — a new one is a new identity or a blind
371
+ duplicate registry.
372
+
324
373
  ```bash
325
- bun run db:migrate:remote # BEFORE the first deploy of the default env; for a named env:
326
- # bunx wrangler d1 migrations apply DB --remote --env <env>
327
- bun run deploy:devnet # or deploy:testnet / deploy:production (`--env <env>`)
328
- wrangler secret put ORACLE_MATRIX_ACCESS_TOKEN # the value step 8 wrote into .dev.vars
374
+ bunx wrangler d1 migrations apply DB --remote --env <env> # BEFORE the first deploy
375
+ bunx wrangler deploy --env <env> --secrets-file .dev.vars.<env> # code + secrets in one version
376
+ bunx wrangler secret put ORACLE_MATRIX_ACCESS_TOKEN --env <env> # later, one secret at a time (e.g. after --relogin)
329
377
  ```
330
378
 
331
- Before deploying: the D1/KV ids for that env are real, that env's `ORACLE_HOSTNAME` is the host
379
+ Before deploying: the D1/KV ids for that env are real, and that env's `ORACLE_HOSTNAME` is the host
332
380
  it will be served from (its Custom Domain in `routes`, or, with no `routes`, its `workers.dev`
333
- host), and every secret is set with `wrangler secret put`. **Then run step 8 again with
381
+ host). Later deploys are `bun run deploy:<env>` (secrets persist). **Then run step 8 again with
334
382
  `--kit-url https://<the deployed host>` and `--yes`**: the `service` step re-points `#eval` at the
335
383
  real host and `profile` anchors the `#pro` it had to skip. `fund` may act again too — it is a floor
336
384
  check, so a wallet the first run's own `iid`/`key` transactions left below 1 IXO is topped back
@@ -352,7 +400,7 @@ failed check — `identity` means `ORACLE_ENTITY_DID` is missing from that env's
352
400
  bunx decision-oracle engine register --env <env> --env-file .dev.vars.<env>
353
401
  ```
354
402
 
355
- The chain lane proves its address lazily, on its first `decide`; until then the engine — and the
403
+ The `.dev.vars.<env>` file from the deploy step above supplies the keys. The chain lane proves its address lazily, on its first `decide`; until then the engine — and the
356
404
  Decision Console, which lists the engine's `GET /v1/oracles` — does not know this oracle exists.
357
405
  `billing: ok` is done; a 402 is the owner's credits (step 6).
358
406
 
@@ -394,7 +442,7 @@ the required fee (`pricing.byProtocol` for that collection's protocol, else the
394
442
  | Standing approvals | may a signed-in AI app's own auto-approve limits pay without a per-claim approval (hosted MCP) — IXO's own oracles say yes | `policies.allowStandingApprovals` (required) |
395
443
  | Custom lanes | per judgment-call name: do you control the endpoint / is a stock AI check the right judge | the `src/facts/` table (`write-facts`) |
396
444
  | Identity | a fresh wallet, or an existing mnemonic to derive from | `derive`, `.dev.vars` + `wrangler.jsonc` vars |
397
- | Owner | who owns this oracle — the AuthHub login whose subscription pays the engine | `login`, `delegation --for engine` |
445
+ | Owner | who owns this oracle — the AuthHub login whose engine credits pay for decisions | `login`, `delegation --for engine` |
398
446
  | Funding | nothing to ask — `oracle init` keeps the wallet topped up to 1 IXO for gas | `oracle init` (the `fund` step) |
399
447
  | Hosts | the did:web host per env (it IS the identity): a custom domain, or the free `workers.dev` host | `ORACLE_HOSTNAME`, `routes` (none for `workers.dev`) |
400
448
  | Contact | the address that gets pause / review notices | `register --contact-email` |
@@ -418,7 +466,7 @@ the required fee (`pricing.byProtocol` for that collection's protocol, else the
418
466
  | --- | --- |
419
467
  | Reading `rubric.json` to work out what to produce | read the manifest — `protocol review` / `manifest <did>` |
420
468
  | A stub that returns a fake `ok` so a run looks green | `fact.error("not implemented: <name>")` |
421
- | Register answers 402 and the agent retries | it is the owner's credits — send them to the printed portal URL, then re-run |
469
+ | Register answers 402 and the agent retries | it is the owner's credits — send them to the printed credits page, then re-run |
422
470
  | `.dev.vars` edited but the old value still in play | a running `wrangler dev` reloads source, not secrets — restart it |
423
471
  | `ORACLE_ADDRESS` typed by hand | must match the mnemonic exactly — asserted at first use |
424
472
  | Guessing `EVALUATION_MIN_AMOUNT` | ask what the collection's on-chain evaluation payment is (or will be) — set too high and register 402s |
@@ -467,6 +515,6 @@ restart, re-register. Restart both dev servers after any `.dev.vars` edit.
467
515
  The review output from step 2, the stock-vs-custom verdict and the handlers written (or stubbed,
468
516
  named), the `src/index.ts` config block, the `.dev.vars` / `wrangler.jsonc` lines to set (never
469
517
  committed, never printed), the `facts run --preview` decision, and — plainly — what the provider
470
- still owes: credits on the owner's subscription, the on-chain Evaluate grant (or the step-10
518
+ still owes: the owner's engine credits, the on-chain Evaluate grant (or the step-10
471
519
  self-hire), the deployment that replaces `localhost`, the `oracle init` re-run that re-points
472
520
  `#eval` at it, and any name still on a stub.
@@ -19,10 +19,11 @@ A source in billing mode `x402` answers a submit with `402`. The payer pays it w
19
19
  customer's **payment delegation**, a UCAN to the facilitator carrying a `billing/pay` cap. The
20
20
  price is held when the claim is accepted and paid only on the engine's receipt.
21
21
 
22
- The worked target is IXO's devnet example: SwiftDrop, at `https://shipment-delivery-oracle.devnet.ixo.earth`,
23
- protocol `did:ixo:entity:c2f5a188d28f3d4bf6351873d3d268ea`, price 60¢. Its `/demo/orders?route=<id>`
24
- issues a fresh courier route per id, with real order ids and delivery codes. Use a new route per
25
- test claim, so the duplicate lane never sees a repeat.
22
+ Point it at your own oracle, or at IXO's public demo: SwiftDrop, at
23
+ `https://shipment-delivery-oracle.devnet.ixo.earth`, protocol
24
+ `did:ixo:entity:c2f5a188d28f3d4bf6351873d3d268ea`, price 60¢. Its `/demo/orders?route=<id>` issues
25
+ a fresh courier route per id, with real order ids and delivery codes. Use a new route (or, on your
26
+ own oracle, new answers) per test claim, so the duplicate lane never sees a repeat.
26
27
 
27
28
  **Core principle: run it, don't recite it.** Every step below is an action you perform. A test
28
29
  claim spends real devnet PAY from the session's deed, so say how many claims you will send before
@@ -30,8 +31,9 @@ sending them.
30
31
 
31
32
  ## When to use
32
33
 
33
- - "Test the SDK / CLI / MCP against devnet", "does the paid flow still work", "mint a test key".
34
- - Before a client release, or after a facilitator or engine change the payer depends on.
34
+ - "Call this oracle from my app", "test the SDK / CLI / MCP against devnet", "does the paid flow
35
+ work", "mint a test key".
36
+ - Before you ship an integration, or after the oracle changes its price, protocols or plan.
35
37
  - Any symptom in the table at the end.
36
38
 
37
39
  Not for: standing up or deploying the oracle (`decision-api-oracle`), or the chain lane
@@ -143,25 +145,17 @@ the console) must make the next claim ask again. "Don't ask again" works only on
143
145
  `policies.allowStandingApprovals` is `true`; otherwise setting it is refused (`403`, reason
144
146
  `standing_approvals_off`) and every claim asks.
145
147
 
146
- ### 4. Card scenarios (devnet, Stripe test mode)
147
-
148
- Card needs an oracle whose protocol costs **≥ 50¢ per claim** (Stripe's minimum capture; the
149
- example is 60¢ for this) and whose plan accepts `card`. Which scenario runs is set on the
150
- **facilitator side**: the payer's default saved card and default way to pay. Saving a test card and
151
- completing 3-D Secure need the facilitator's Stripe test key, so that half belongs to whoever runs
152
- the facilitator. This half pays — an SDK submit with `prefer: "card"` (`"upay"` = PAY first; unset =
153
- the payer's console default), and after a 3-D Secure confirm a resume with the SAME offer and key
154
- (`{ idempotencyKey: err.idempotencyKey, resumePayment: err.paymentRequired }`):
155
-
156
- | Payer's default card (facilitator side) | Submit with | Expect |
157
- | --- | --- | --- |
158
- | `pm_card_authenticationRequired` | `prefer: "card"` | `CARD_AUTHENTICATION_REQUIRED` + `confirmUrl`; once confirmed, the resume → paid with the SAME PaymentIntent. Left unconfirmed, the facilitator cancels it ~17 min later, never charged |
159
- | `pm_card_chargeCustomerFail` | `prefer: "card"` | declined at authorize → paid with PAY for the same 402 |
160
- | `pm_card_createDispute` | `prefer: "card"`, then again once the dispute is open | paid by card; then `card_blocked` → PAY (no PaymentIntent created); after the dispute is won, card again |
161
- | `pm_card_visa`, preference `card` | no `prefer` | the SDK reads `/v1/payer/preferences` with the delegation → card |
162
-
163
- Every claim's record names the rail: `stripe-manual-capture` / `usd` / cents, or `ixo-pay` / `upay`.
164
- A run that answers `SPEND_CAP` has used up the delegation's `maxAmount`: mint a new one.
148
+ ### 4. Paying by card
149
+
150
+ Card is offered only when the protocol costs **≥ 50¢ per claim** (Stripe's minimum capture) and the
151
+ oracle's plan accepts `card`. The payer saves a card once in the Decision Console. An SDK submit
152
+ takes `prefer: "card"` (`"upay"` = PAY first; unset = the payer's console default). When the bank
153
+ asks for 3-D Secure the submit throws `CARD_AUTHENTICATION_REQUIRED` with a `confirmUrl`: confirm
154
+ there, then resume with the SAME offer and key
155
+ (`{ idempotencyKey: err.idempotencyKey, resumePayment: err.paymentRequired }`). Left unconfirmed,
156
+ the hold is cancelled and nothing is charged. Every claim's record names the rail:
157
+ `stripe-manual-capture` / `usd` / cents, or `ixo-pay` / `upay`. A run that answers `SPEND_CAP` has
158
+ used up the delegation's `maxAmount`: mint a new one.
165
159
 
166
160
  ## When something fails
167
161