@ixo/decision-oracle 0.2.11 → 0.2.13

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.
Files changed (83) hide show
  1. package/README.md +14 -13
  2. package/dist/cli/commands/derive.d.ts +6 -2
  3. package/dist/cli/commands/derive.d.ts.map +1 -1
  4. package/dist/cli/commands/derive.js +33 -3
  5. package/dist/cli/commands/derive.js.map +1 -1
  6. package/dist/cli/commands/engine.d.ts.map +1 -1
  7. package/dist/cli/commands/engine.js +9 -2
  8. package/dist/cli/commands/engine.js.map +1 -1
  9. package/dist/cli/commands/init.js +2 -2
  10. package/dist/cli/commands/oracle-init.d.ts.map +1 -1
  11. package/dist/cli/commands/oracle-init.js +2 -2
  12. package/dist/cli/commands/oracle-init.js.map +1 -1
  13. package/dist/cli/commands/reviewer-note.d.ts +11 -0
  14. package/dist/cli/commands/reviewer-note.d.ts.map +1 -0
  15. package/dist/cli/commands/reviewer-note.js +36 -0
  16. package/dist/cli/commands/reviewer-note.js.map +1 -0
  17. package/dist/cli/index.js +26 -85
  18. package/dist/cli/index.js.map +1 -1
  19. package/dist/cli/lib/usage.d.ts +11 -0
  20. package/dist/cli/lib/usage.d.ts.map +1 -0
  21. package/dist/cli/lib/usage.js +113 -0
  22. package/dist/cli/lib/usage.js.map +1 -0
  23. package/dist/config.d.ts +9 -0
  24. package/dist/config.d.ts.map +1 -1
  25. package/dist/config.js +15 -1
  26. package/dist/config.js.map +1 -1
  27. package/dist/db/schema.d.ts +20 -0
  28. package/dist/db/schema.d.ts.map +1 -1
  29. package/dist/db/schema.js +7 -0
  30. package/dist/db/schema.js.map +1 -1
  31. package/dist/lib/api-lane/retention.d.ts.map +1 -1
  32. package/dist/lib/api-lane/retention.js +5 -1
  33. package/dist/lib/api-lane/retention.js.map +1 -1
  34. package/dist/lib/claim-erase.d.ts.map +1 -1
  35. package/dist/lib/claim-erase.js +5 -1
  36. package/dist/lib/claim-erase.js.map +1 -1
  37. package/dist/lib/redact.d.ts.map +1 -1
  38. package/dist/lib/redact.js +1 -0
  39. package/dist/lib/redact.js.map +1 -1
  40. package/dist/lib/review/reviewer-note-prompt.d.ts +43 -0
  41. package/dist/lib/review/reviewer-note-prompt.d.ts.map +1 -0
  42. package/dist/lib/review/reviewer-note-prompt.js +106 -0
  43. package/dist/lib/review/reviewer-note-prompt.js.map +1 -0
  44. package/dist/lib/review/reviewer-note.d.ts +67 -0
  45. package/dist/lib/review/reviewer-note.d.ts.map +1 -0
  46. package/dist/lib/review/reviewer-note.js +138 -0
  47. package/dist/lib/review/reviewer-note.js.map +1 -0
  48. package/dist/middleware/capability.d.ts +1 -1
  49. package/dist/middleware/capability.d.ts.map +1 -1
  50. package/dist/middleware/capability.js +4 -0
  51. package/dist/middleware/capability.js.map +1 -1
  52. package/dist/middleware/error-handler.d.ts.map +1 -1
  53. package/dist/middleware/error-handler.js +11 -0
  54. package/dist/middleware/error-handler.js.map +1 -1
  55. package/dist/oracle.d.ts +1 -0
  56. package/dist/oracle.d.ts.map +1 -1
  57. package/dist/oracle.js +7 -0
  58. package/dist/oracle.js.map +1 -1
  59. package/dist/routes/v1/admin.d.ts.map +1 -1
  60. package/dist/routes/v1/admin.js +59 -0
  61. package/dist/routes/v1/admin.js.map +1 -1
  62. package/dist/routes/v1/claims.js +3 -0
  63. package/dist/routes/v1/claims.js.map +1 -1
  64. package/dist/routes/v1/reviews.js +2 -0
  65. package/dist/routes/v1/reviews.js.map +1 -1
  66. package/dist/workflows/claim-ingest.d.ts.map +1 -1
  67. package/dist/workflows/claim-ingest.js +23 -0
  68. package/dist/workflows/claim-ingest.js.map +1 -1
  69. package/docs/api.md +10 -3
  70. package/docs/guide.md +21 -9
  71. package/migrations/0026_reviewer_note.sql +1 -0
  72. package/migrations/meta/0026_snapshot.json +2983 -0
  73. package/migrations/meta/_journal.json +7 -0
  74. package/package.json +1 -1
  75. package/skills/create-oracle/SKILL.md +17 -13
  76. package/skills/create-rubric/SKILL.md +7 -0
  77. package/skills/create-rubric/reference/format-cheatsheet.md +20 -0
  78. package/skills/decision-api-oracle/SKILL.md +2 -2
  79. package/templates/project/README.md +4 -3
  80. package/templates/project/package.json +1 -0
  81. package/templates/project/src/facts/index.ts +6 -2
  82. package/templates/project/src/index.ts +3 -1
  83. package/templates/project/wrangler.jsonc +116 -11
@@ -183,6 +183,13 @@
183
183
  "when": 1792573809270,
184
184
  "tag": "0025_openhands",
185
185
  "breakpoints": true
186
+ },
187
+ {
188
+ "idx": 26,
189
+ "version": "6",
190
+ "when": 1792581171066,
191
+ "tag": "0026_reviewer_note",
192
+ "breakpoints": true
186
193
  }
187
194
  ]
188
195
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ixo/decision-oracle",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
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": [
@@ -62,7 +62,7 @@ bunx @ixo/decision-oracle init <dir> [--name <project-name>] [--openhands]
62
62
  cd <dir> && bun install
63
63
  bun run cf-typegen # after ANY wrangler.jsonc binding/var change, too
64
64
  wrangler d1 create <project-name> # paste the id into wrangler.jsonc
65
- wrangler kv namespace create CACHE_KV # 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
66
  bun run db:migrate # the package's own migrations, applied to the local D1
67
67
  cp .dev.vars.example .dev.vars # git-ignored; secrets only
68
68
  ```
@@ -77,19 +77,23 @@ every env) — then follow `add-openhands-support` for who pays, registering and
77
77
  ### 1. Identity — two keys
78
78
 
79
79
  ```bash
80
- bunx decision-oracle derive --new # run TWICE
80
+ bunx decision-oracle derive --new --write .dev.vars --as SERVICE_ED25519_MNEMONIC
81
+ bunx decision-oracle derive --new --write .dev.vars
81
82
  ```
82
83
 
83
- - Run one: take the mnemonic line and paste it into `.dev.vars` **relabelled** as
84
- `SERVICE_ED25519_MNEMONIC=` — `derive` always prints it as `ORACLE_SECP_MNEMONIC=`, so a verbatim
85
- paste silently sets the wrong key. This is the did:web key that signs every UCAN invocation this
86
- oracle mints and backs `/.well-known/did.json`; ignore the `ORACLE_*` lines this run prints.
87
- - Run two: the mnemonic is `ORACLE_SECP_MNEMONIC` (the chain wallet that signs `MsgEvaluateClaim`);
88
- the same run prints `ORACLE_ADDRESS`, `ORACLE_DID` and `ORACLE_MATRIX_USER_ID` — those three are
89
- **public vars**, so they go in `wrangler.jsonc` `vars`, not `.dev.vars`.
84
+ `--write` appends the key to the file (0600) and never prints it — never run `derive --new` without
85
+ it in an agent session, or the mnemonic lands in the transcript.
90
86
 
91
- Also set `ORACLE_HOSTNAME` (the host the did:web is served from — `localhost:8787` locally, and it
92
- must equal the Custom Domain in `routes` for any env you deploy), `IXO_NETWORK`, and
87
+ - The first is `SERVICE_ED25519_MNEMONIC`: the did:web key that signs every UCAN invocation this
88
+ oracle mints and backs `/.well-known/did.json`.
89
+ - The second is `ORACLE_SECP_MNEMONIC` (the chain wallet that signs `MsgEvaluateClaim`); it prints
90
+ `ORACLE_ADDRESS`, `ORACLE_DID` and `ORACLE_MATRIX_USER_ID` — **public vars**, so they go in
91
+ `wrangler.jsonc` `vars` (top level and every env), not `.dev.vars`.
92
+
93
+ `ORACLE_HOSTNAME` (the host the did:web is served from) is `localhost:8787` in `.dev.vars` for local
94
+ dev ONLY. Each deployed env's host lives in that env's `wrangler.jsonc` `vars` and must equal its
95
+ Custom Domain in `routes`; a per-env secrets file (`.dev.vars.<env>`) must NOT carry a localhost
96
+ `ORACLE_HOSTNAME`, or `engine register --env-file .dev.vars.<env>` registers localhost. Also put
93
97
  `UNIQUE_HMAC_KEY` in `.dev.vars` (any long random string; rotating it orphans every registered
94
98
  duplicate digest). `derive "<existing mnemonic>"` uses a wallet the provider already holds instead of
95
99
  minting one. `ORACLE_ADDRESS` is asserted against the mnemonic before the first proof or signature:
@@ -308,7 +312,7 @@ where a live token would have skipped (it mints a new Matrix device — not a ca
308
312
  ```bash
309
313
  bun run db:migrate:remote # BEFORE the first deploy of the default env; for a named env:
310
314
  # bunx wrangler d1 migrations apply DB --remote --env <env>
311
- bun run deploy # or deploy:testnet / deploy:production
315
+ bun run deploy:devnet # or deploy:testnet / deploy:production (`--env <env>`)
312
316
  wrangler secret put ORACLE_MATRIX_ACCESS_TOKEN # the value step 8 wrote into .dev.vars
313
317
  ```
314
318
 
@@ -332,7 +336,7 @@ failed check — `identity` means `ORACLE_ENTITY_DID` is missing from that env's
332
336
  **Then register with the engine — do it now, don't wait for the first claim:**
333
337
 
334
338
  ```bash
335
- bunx decision-oracle engine register --env <env> --env-file .dev.vars
339
+ bunx decision-oracle engine register --env <env> --env-file .dev.vars.<env>
336
340
  ```
337
341
 
338
342
  The chain lane proves its address lazily, on its first `decide`; until then the engine — and the
@@ -110,6 +110,13 @@ default here is the author's call — ask.
110
110
  approvals only.
111
111
  - Everything a rubric declares under `sources` / `aiChecks` becomes work for whatever oracle is
112
112
  hired: a name declared here is a fact someone has to produce. Don't declare one nobody can answer.
113
+ - **A person reads what an AI check says.** When a claim is held for review, the reviewer sees each
114
+ check's reason beside the answer. Write `aiChecks` instructions so the model reports what it SAW
115
+ in plain words ("the photo shows a cleared pavement on Main St"), not that a rule held ("the
116
+ answer is specific and not placeholder text"). Oracles that turn on `policies.reviewerNote` (on
117
+ by default for Openhands checkers) also give the reviewer a plain-words note of what the answer
118
+ claims and what no check could verify, so a ReviewTask's `prompt` should ask the one question the
119
+ person must decide, in plain words.
113
120
 
114
121
  ## Output contract
115
122
 
@@ -135,6 +135,26 @@ tasks downgrade approve/partial to review → verdict → action.
135
135
  "map": { "true": "approve", "false": "reject" } } ] } // EVERY possible answer mapped
136
136
  ```
137
137
 
138
+ `assignTo.role` is **routing, not access**: any non-empty name for the lane the review goes to (default
139
+ `"reviewer"`; who may answer is the review gate's business — the company's reviewer grants — never this
140
+ field). `assigneeDid` names ONE person instead; `route.matrixRoom` sends it to a room. Use one or the other.
141
+
142
+ **Claim-level task** — a `ReviewTask` with NO `forCriterion` that no `ManualScore` binds: the "a person
143
+ gives the final OK" question.
144
+
145
+ ```jsonc
146
+ { "@type": "ReviewTask", "id": "MED-RT-FINAL", "prompt": "Do you accept this delivery as done?",
147
+ "show": ["$deliveryPhoto", "$clinicId"], "answer": { "type": "boolean" },
148
+ "map": { "true": "approve", "false": "reject" } }
149
+ ```
150
+
151
+ - **When it is asked:** on the claim's first decision, whenever the verdict is `review` — beside anything
152
+ else pending (a held ManualScore, a duplicate hold, a gate with `onFail: "review"`, an errored
153
+ `onError: "review"` check). A claim the machine approves or rejects outright is never asked.
154
+ - **What the answer does:** its `map` IS the final verdict (several claim-level answers: reject beats
155
+ partial beats approve). It settles even a capped or errored-check hold — it is the final human authority.
156
+ - To make EVERY claim go to a person, pair it with a gate whose `onFail` is `"review"` and that always holds.
157
+
138
158
  ## Derived values
139
159
 
140
160
  ```jsonc
@@ -84,7 +84,7 @@ It must list `ext.orderKnown`, `ext.deliveryCodeValid`, `ext.cashReconciled`,
84
84
 
85
85
  ### 2. Identity
86
86
 
87
- Do `create-oracle` step 1 (two `derive --new` runs; relabel the first as `SERVICE_ED25519_MNEMONIC`;
87
+ Do `create-oracle` step 1 (two `derive --new --write` runs, the first `--as SERVICE_ED25519_MNEMONIC`;
88
88
  the second is `ORACLE_SECP_MNEMONIC`) and step 6 (the owner's engine delegation), with:
89
89
 
90
90
  - the three public lines (`ORACLE_ADDRESS`, `ORACLE_DID`, `ORACLE_MATRIX_USER_ID`) written into BOTH
@@ -125,7 +125,7 @@ deploy — `devnet` in the example, `testnet` / `production` in the template):
125
125
 
126
126
  ```bash
127
127
  bunx wrangler d1 create shipment-delivery-oracle-devnet # → env.<env> d1 database_id
128
- bunx wrangler kv namespace create CACHE_KV --env devnet # → env.<env> kv id
128
+ bunx wrangler kv namespace create <project>-cache-devnet # → env.<env> kv id
129
129
  bunx wrangler r2 bucket create shipment-delivery-oracle-uploads-devnet
130
130
  ```
131
131
 
@@ -30,7 +30,7 @@ Create the two bindings and paste their ids into `wrangler.jsonc` (once per env
30
30
 
31
31
  ```bash
32
32
  wrangler d1 create {{name}}
33
- wrangler kv namespace create CACHE_KV
33
+ wrangler kv namespace create {{name}}-cache
34
34
  bun run db:migrate # applies the package's migrations to the local D1
35
35
  ```
36
36
 
@@ -39,7 +39,8 @@ never write a migration.
39
39
 
40
40
  ## 3. Identity — two keys, two DIDs
41
41
 
42
- `bunx decision-oracle derive --new` prints a fresh mnemonic and the three public vars derived from it.
42
+ `bunx decision-oracle derive --new --write .dev.vars [--as SERVICE_ED25519_MNEMONIC]` writes a fresh
43
+ mnemonic into `.dev.vars` (never printed) and prints the three public vars derived from it.
43
44
  Secrets go in `.dev.vars` locally (`cp .dev.vars.example .dev.vars`) and via
44
45
  `wrangler secret put <NAME>` once deployed; their public halves are `vars` in `wrangler.jsonc`.
45
46
 
@@ -139,7 +140,7 @@ under `/v1` requires a UCAN invocation addressed to `did:web:<ORACLE_HOSTNAME>`.
139
140
  ## 7. Deploy and declare yourself on chain
140
141
 
141
142
  ```bash
142
- bun run deploy # or deploy:testnet / deploy:production
143
+ bun run deploy:devnet # or deploy:testnet / deploy:production
143
144
  ```
144
145
 
145
146
  The Custom Domain in `routes` must equal `ORACLE_HOSTNAME` — the did:web document is served from
@@ -5,6 +5,7 @@
5
5
  "scripts": {
6
6
  "dev": "wrangler dev --test-scheduled",
7
7
  "deploy": "wrangler deploy",
8
+ "deploy:devnet": "wrangler deploy --env devnet",
8
9
  "deploy:testnet": "wrangler deploy --env testnet",
9
10
  "deploy:production": "wrangler deploy --env production",
10
11
  "build": "wrangler deploy --dry-run --outdir dist",
@@ -3,7 +3,8 @@ import { defineFacts } from "@ixo/decision-oracle/facts";
3
3
  /**
4
4
  * How this oracle produces the facts a rubric asks for. List only the names you answer yourself;
5
5
  * every other declared source runs the stock lane (a UCAN-signed POST to the declared endpoint)
6
- * and every other AI check runs OpenRouter with the rubric's own instructions.
6
+ * and every other AI check runs the stock judge with the rubric's own instructions (the Cloudflare
7
+ * AI Gateway by default; OpenRouter with AI_PROVIDER=openrouter).
7
8
  *
8
9
  * bunx decision-oracle protocol review <protocol did> — what the rubric declares, and which names need a handler
9
10
  * bunx decision-oracle facts scaffold <protocol did> — stubs + tests for those names
@@ -12,7 +13,10 @@ export const facts = defineFacts({
12
13
  ext: {
13
14
  // deliveryConfirmed: async ({ send }, tools) => {
14
15
  // const res = await tools.http.json(`https://courier.example/deliveries/${send.reference}`);
15
- // return res.ok && res.body?.status === "delivered";
16
+ // // An outage is not a "no": a throw becomes an error fact with the rubric's onError
17
+ // // (`throw fact.retryLater(…)` instead retries the whole step later).
18
+ // if (!res.ok) throw new Error(`courier answered ${res.status ?? "nothing"}`);
19
+ // return res.body?.status === "delivered";
16
20
  // },
17
21
  },
18
22
  ai: {},
@@ -10,7 +10,9 @@ const oracle = defineOracle({
10
10
  description: "Evaluates claims against the collection's rubric via the IXO decision engine.",
11
11
  // The protocol entity DIDs this oracle serves (GET /v1/info; enforced at register). [] ⇒ any.
12
12
  supportedProtocols: [],
13
- // The minimum on-chain evaluation payment a collection must carry (integer base units).
13
+ // The minimum on-chain evaluation payment a collection must carry (integer base units) — the one
14
+ // place to change it, except on mainnet, where wrangler.jsonc's EVALUATION_DENOM /
15
+ // EVALUATION_MIN_AMOUNT (required there, real USDC) replace it. Never PAY: only IXO-run oracles.
14
16
  pricing: { evaluation: { denom: "uixo", minAmount: "1000000" } },
15
17
  policies: {
16
18
  autoSign: true,
@@ -39,11 +39,11 @@
39
39
 
40
40
  // KV: caches only (UCAN principals, peer did:web documents, the engine-registered marker, chain
41
41
  // grants, the C2PA trust list). Nothing here is a source of truth — it can be emptied any time.
42
- // wrangler kv namespace create CACHE_KV
42
+ // wrangler kv namespace create {{name}}-cache
43
43
  "kv_namespaces": [
44
44
  {
45
45
  "binding": "CACHE_KV",
46
- "id": "<create with: wrangler kv namespace create CACHE_KV>"
46
+ "id": "<create with: wrangler kv namespace create {{name}}-cache>"
47
47
  }
48
48
  ],
49
49
 
@@ -133,6 +133,8 @@
133
133
  // Non-secret runtime config. Secrets go in .dev.vars locally (see .dev.vars.example) and via
134
134
  // `wrangler secret put <NAME>` once deployed — never here.
135
135
  "vars": {
136
+ // ENVIRONMENT is one of development | staging | production — anything else and the worker
137
+ // refuses its config (every request 500s, and the log names the var).
136
138
  "ENVIRONMENT": "development",
137
139
  "LOG_LEVEL": "debug",
138
140
  "SERVICE_NAME": "{{name}}",
@@ -148,12 +150,9 @@
148
150
  // store, VFS, IPFS gateway, RPC, portal) defaults from it — set one of those vars only to
149
151
  // override.
150
152
  "IXO_NETWORK": "devnet",
151
- // The coin (and minimum, integer base units) a collection's on-chain evaluation payment must
152
- // pay this oracle in — register refuses any other denom (402). It replaces `pricing.evaluation`
153
- // from `defineOracle`. devnet/testnet: a stand-in coin is fine (`uixo`). mainnet: REQUIRED,
154
- // and real USDC (see the production env) — the oracle refuses to start without it.
155
- "EVALUATION_DENOM": "uixo",
156
- "EVALUATION_MIN_AMOUNT": "1000000",
153
+ // The evaluation fee is `pricing.evaluation` in src/index.ts — the ONE place to change it off
154
+ // mainnet. EVALUATION_DENOM / EVALUATION_MIN_AMOUNT, when set in an env, REPLACE it there (the
155
+ // production env sets them: mainnet requires them, in real USDC).
157
156
 
158
157
  // THIS oracle's did:web host — the audience every inbound UCAN invocation must name, and the
159
158
  // issuer of every invocation this oracle mints. It must equal the Custom Domain in `routes`
@@ -226,6 +225,114 @@
226
225
  },
227
226
 
228
227
  "env": {
228
+ "devnet": {
229
+ // Without this the worker deploys as "<top-level name>-devnet" anyway — naming it here
230
+ // keeps the deployed name explicit and independent of the top-level one.
231
+ "name": "{{name}}-devnet",
232
+ // The Custom Domain IS the identity: it must equal ORACLE_HOSTNAME below. Declared here so
233
+ // the deploy attaches it (and its DNS record) instead of it being a dashboard step.
234
+ "routes": [{ "pattern": "<REPLACE: your devnet host>", "custom_domain": true }],
235
+ "vars": {
236
+ "ENVIRONMENT": "staging",
237
+ "LOG_LEVEL": "info",
238
+ "SERVICE_NAME": "{{name}}",
239
+ "SERVICE_VERSION": "0.1.0",
240
+ "CORS_ORIGINS": "*",
241
+ "CRON_COLLECTIONS_PER_TICK": "25",
242
+ "IXO_CLIENT_SEQUENCE_MIN_DELAY_MS": "1000",
243
+ "IXO_NETWORK": "devnet",
244
+ // No EVALUATION_DENOM / EVALUATION_MIN_AMOUNT: the fee is `pricing.evaluation` (src/index.ts).
245
+ "ORACLE_HOSTNAME": "<REPLACE: your devnet host>",
246
+ "ORACLE_DID": "did:ixo:<REPLACE_WITH_YOUR_ORACLE_ADDRESS>",
247
+ "ORACLE_ADDRESS": "<REPLACE_WITH_YOUR_ORACLE_ADDRESS>",
248
+ "ORACLE_MATRIX_USER_ID": "@did-ixo-<REPLACE_WITH_YOUR_ORACLE_ADDRESS>:devmx.ixo.earth",
249
+ "ORACLE_ENTITY_DID": "",
250
+ "AI_CHECK_MODEL": "openai/gpt-6-luna",
251
+ // AI provider (README "Configuration"): the AI Gateway by default — the `ai` binding plus the
252
+ // gateway's name; OpenRouter instead with AI_PROVIDER=openrouter + the OPENROUTER_API_KEY secret.
253
+ "AI_PROVIDER": "cloudflare",
254
+ // "AI_AUDIO_VIDEO": "false", // audio/video inputs — OpenRouter only
255
+ // "AI_GATEWAY_ID": "<your gateway>",
256
+ "C2PA_IMAGE_CHECK": "true",
257
+ "AUTO_SIGN": "true",
258
+ "API_LANE": "false",
259
+ "API_PAYMENTS": "false"
260
+ // "API_PRICE_USD_CENTS": "120",
261
+ // "X402_FACILITATOR_URL": "https://<facilitator host>",
262
+ // "X402_NETWORK": "cosmos:devnet-1",
263
+ // Where your earnings land (your own accounts; each offer's payTo). Unset ⇒ that way to pay is not offered.
264
+ // "EARNINGS_ADDRESS": "ixo1<your account>",
265
+ // "EARNINGS_BASE_WALLET": "0x<your Base wallet>",
266
+ // "BILLING_ENGINE_URL": "https://<billing-engine host>",
267
+ // "CONSOLE_URL": "https://<console host>",
268
+ // The OAuth server of /v1/mcp (MCP over OAuth); defaults per IXO_NETWORK — set for a local AuthHub.
269
+ // "AUTH_HUB_URL": "https://<authhub host>",
270
+ // "ORACLE_ADMIN_DIDS": "did:ixo:<admin>",
271
+ // "STATUS_READER_DIDS": "did:web:<monitor host>",
272
+
273
+ // "EMAIL_FROM_ADDRESS": "oracle@your-domain",
274
+ // "EMAIL_FROM_NAME": "Your Oracle",
275
+
276
+ // "SENTRY_TRACES_SAMPLE_RATE": "0.2",
277
+ // "SENTRY_ENVIRONMENT": "devnet"
278
+ },
279
+
280
+ // Optional Workers Traces for this env (billed per span from 2026-10-01): `observability` is
281
+ // inherited whole, so uncommenting this replaces the top-level block — traces 20%.
282
+ // "observability": {
283
+ // "enabled": true,
284
+ // "head_sampling_rate": 1,
285
+ // "traces": { "enabled": true, "head_sampling_rate": 0.2 },
286
+ // "logs": { "enabled": true }
287
+ // },
288
+
289
+ // Every binding and the `vars` block are redeclared per env — those inherit nothing from the
290
+ // top level (unlike `main`, `compatibility_date`, `triggers`, `migrations`, `observability`).
291
+ // wrangler d1 create {{name}}-devnet
292
+ "ai": { "binding": "AI" },
293
+ "d1_databases": [
294
+ {
295
+ "binding": "DB",
296
+ "database_name": "{{name}}-devnet",
297
+ "database_id": "<DEVNET_D1_ID>",
298
+ "migrations_dir": "node_modules/@ixo/decision-oracle/migrations"
299
+ }
300
+ ],
301
+ "kv_namespaces": [{ "binding": "CACHE_KV", "id": "<DEVNET_CACHE_KV_ID>" }],
302
+ "ratelimits": [
303
+ {
304
+ "name": "API_RATE_LIMITER",
305
+ "namespace_id": "1001",
306
+ "simple": { "limit": 100, "period": 60 }
307
+ },
308
+ {
309
+ "name": "API_KEY_RATE_LIMITER",
310
+ "namespace_id": "1002",
311
+ "simple": { "limit": 600, "period": 60 }
312
+ }
313
+ ],
314
+ // wrangler r2 bucket create {{name}}-uploads-devnet
315
+ // "r2_buckets": [{ "binding": "API_UPLOADS", "bucket_name": "{{name}}-uploads-devnet" }],
316
+ // wrangler r2 bucket create {{name}}-answers-devnet
317
+ // "r2_buckets": [{ "binding": "ANSWERS_ARCHIVE", "bucket_name": "{{name}}-answers-devnet" }],
318
+ // "analytics_engine_datasets": [{ "binding": "METRICS", "dataset": "eval_oracle_metrics_devnet" }],
319
+ "send_email": [{ "name": "EMAIL", "remote": true }],
320
+ "workflows": [
321
+ {
322
+ "name": "{{name}}-claim-ingest-devnet",
323
+ "binding": "CLAIM_WORKFLOW",
324
+ "class_name": "ClaimIngest"
325
+ },
326
+ {
327
+ "name": "{{name}}-review-complete-devnet",
328
+ "binding": "REVIEW_WORKFLOW",
329
+ "class_name": "ReviewComplete"
330
+ }
331
+ ],
332
+ "durable_objects": {
333
+ "bindings": [{ "name": "SEQUENCE_MANAGER", "class_name": "SequenceManagerDO" }]
334
+ }
335
+ },
229
336
  "testnet": {
230
337
  // Without this the worker deploys as "<top-level name>-testnet" anyway — naming it here
231
338
  // keeps the deployed name explicit and independent of the top-level one.
@@ -242,9 +349,7 @@
242
349
  "CRON_COLLECTIONS_PER_TICK": "25",
243
350
  "IXO_CLIENT_SEQUENCE_MIN_DELAY_MS": "1000",
244
351
  "IXO_NETWORK": "testnet",
245
- // Stand-in coin on testnet (owner ruling: any coin is fine off mainnet).
246
- "EVALUATION_DENOM": "uixo",
247
- "EVALUATION_MIN_AMOUNT": "1000000",
352
+ // No EVALUATION_DENOM / EVALUATION_MIN_AMOUNT: the fee is `pricing.evaluation` (src/index.ts).
248
353
  "ORACLE_HOSTNAME": "<REPLACE: your testnet host>",
249
354
  "ORACLE_DID": "did:ixo:<REPLACE_WITH_YOUR_ORACLE_ADDRESS>",
250
355
  "ORACLE_ADDRESS": "<REPLACE_WITH_YOUR_ORACLE_ADDRESS>",