@ixo/decision-oracle 0.2.25 → 0.2.27

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 (61) hide show
  1. package/README.md +6 -4
  2. package/dist/cli/commands/login.d.ts +2 -1
  3. package/dist/cli/commands/login.d.ts.map +1 -1
  4. package/dist/cli/commands/login.js +6 -2
  5. package/dist/cli/commands/login.js.map +1 -1
  6. package/dist/cli/lib/session.d.ts +5 -2
  7. package/dist/cli/lib/session.d.ts.map +1 -1
  8. package/dist/cli/lib/session.js +55 -5
  9. package/dist/cli/lib/session.js.map +1 -1
  10. package/dist/config.d.ts +3 -0
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +17 -0
  13. package/dist/config.js.map +1 -1
  14. package/dist/cron/payer-billing.d.ts.map +1 -1
  15. package/dist/cron/payer-billing.js +4 -0
  16. package/dist/cron/payer-billing.js.map +1 -1
  17. package/dist/cron/scan.d.ts +3 -1
  18. package/dist/cron/scan.d.ts.map +1 -1
  19. package/dist/cron/scan.js +36 -11
  20. package/dist/cron/scan.js.map +1 -1
  21. package/dist/env.d.ts +2 -0
  22. package/dist/env.d.ts.map +1 -1
  23. package/dist/lib/chain-lane.d.ts +17 -0
  24. package/dist/lib/chain-lane.d.ts.map +1 -0
  25. package/dist/lib/chain-lane.js +21 -0
  26. package/dist/lib/chain-lane.js.map +1 -0
  27. package/dist/lib/ixo/chain-write.d.ts.map +1 -1
  28. package/dist/lib/ixo/chain-write.js +6 -0
  29. package/dist/lib/ixo/chain-write.js.map +1 -1
  30. package/dist/lib/openhands/routes.d.ts +2 -0
  31. package/dist/lib/openhands/routes.d.ts.map +1 -1
  32. package/dist/lib/openhands/routes.js +8 -0
  33. package/dist/lib/openhands/routes.js.map +1 -1
  34. package/dist/lib/register.d.ts.map +1 -1
  35. package/dist/lib/register.js +5 -0
  36. package/dist/lib/register.js.map +1 -1
  37. package/dist/routes/v1/admin.d.ts.map +1 -1
  38. package/dist/routes/v1/admin.js +4 -0
  39. package/dist/routes/v1/admin.js.map +1 -1
  40. package/dist/routes/v1/collections.d.ts +5 -0
  41. package/dist/routes/v1/collections.d.ts.map +1 -1
  42. package/dist/routes/v1/collections.js +12 -0
  43. package/dist/routes/v1/collections.js.map +1 -1
  44. package/dist/routes/v1/info.js +4 -0
  45. package/dist/routes/v1/info.js.map +1 -1
  46. package/dist/runtime.d.ts +3 -2
  47. package/dist/runtime.d.ts.map +1 -1
  48. package/dist/runtime.js +7 -2
  49. package/dist/runtime.js.map +1 -1
  50. package/dist/workflows/claim-ingest.d.ts.map +1 -1
  51. package/dist/workflows/claim-ingest.js +8 -0
  52. package/dist/workflows/claim-ingest.js.map +1 -1
  53. package/dist/workflows/openhands-hire.d.ts.map +1 -1
  54. package/dist/workflows/openhands-hire.js +8 -0
  55. package/dist/workflows/openhands-hire.js.map +1 -1
  56. package/docs/api.md +21 -13
  57. package/docs/guide.md +56 -6
  58. package/package.json +1 -1
  59. package/skills/decision-api-oracle/SKILL.md +10 -0
  60. package/templates/project/.dev.vars.example +4 -0
  61. package/templates/project/wrangler.jsonc +8 -0
package/docs/guide.md CHANGED
@@ -1018,10 +1018,58 @@ Registration is one call with four gates, in this order:
1018
1018
  `company_unavailable` when the facilitator or the engine cannot say). Without one, the leg gate
1019
1019
  above stands, its refusal `402 no_review_billing` naming both ways in.
1020
1020
 
1021
- Every refusal carries a stable machine code in `details.reason` (`collection_not_found`,
1022
- `not_owner_or_grantee`, `not_hired`, `unsupported_protocol`, `evaluation_underpaid`,
1023
- `no_review_billing`, `insufficient_credits`, `company_unavailable`, `credits_unavailable`, …), so a
1024
- portal never matches a message.
1021
+ Every refusal carries a stable machine code in `details.reason` (`chain_lane_off`,
1022
+ `collection_not_found`, `not_owner_or_grantee`, `not_hired`, `unsupported_protocol`,
1023
+ `evaluation_underpaid`, `no_review_billing`, `insufficient_credits`, `company_unavailable`,
1024
+ `credits_unavailable`, …), so a portal never matches a message.
1025
+
1026
+ ### An API-only oracle: turning the chain lane off
1027
+
1028
+ An oracle that serves only the Decision API (`API_LANE` on) still runs the chain lane by default,
1029
+ and that is a door you may not want open: your protocol is public on chain, so anyone can create a
1030
+ claim collection on it, grant your oracle, and register it — at no fee at all where your `pricing`
1031
+ charges none (a `byProtocol` `minAmount: "0"`, or an empty evaluation leg). From then on every claim
1032
+ there is pulled, its facts produced and the engine asked to decide, on **your** engine credits, and
1033
+ its reviews land in your organisation's inbox.
1034
+
1035
+ Close it with one var in `wrangler.jsonc`, per env:
1036
+
1037
+ ```jsonc
1038
+ "CHAIN_LANE": "false", // needs "API_LANE": "true" — with no lane at all the worker refuses to start
1039
+ ```
1040
+
1041
+ Then:
1042
+
1043
+ - `POST /v1/collections/register` (and the `registerCollection` runtime export) answers
1044
+ `409 { reason: "chain_lane_off" }` before any of register's own reads, so nothing is stored;
1045
+ `decision-oracle register` prints that answer as it is.
1046
+ - `POST /v1/collections/:id/scan` (`decision-oracle scan`, the `scanCollection` export), a
1047
+ collection's resume (`POST /v1/collections/:id/resume` — a pause is still allowed), the
1048
+ operator's `POST /v1/admin/claims/:claimId/redeliver` and the `rehireOpenhands` export answer the
1049
+ same `409`, and so do the OpenHands webhooks (`{ error, reason: "chain_lane_off" }`).
1050
+ - The cron runs no chain job: no scan, no claims feed or replay, no chain-collection resume, no
1051
+ OpenHands hire. Each tier logs one `cron.chain-lane.off` line at debug, never one per collection.
1052
+ The Sentry monitors `oracle-scan` and `oracle-claims-feed` stop checking in, so close them if you
1053
+ had them.
1054
+ - A chain claim's run already in flight when you flip it (or retrying, or restarted by hand) stops
1055
+ before its pull: nothing is pulled, no AI is spent, the engine is not asked. The step retries on
1056
+ its backoff, so the run carries on once the lane is back on — within its ~4 hours of retries;
1057
+ past them it ends `failed` and is rerun like any failed claim. One that had already decided
1058
+ keeps its decision and receipt.
1059
+ - The act lane signs no `MsgEvaluateClaim`; a decided row is left as it was (`pending`, or
1060
+ `blocked`) for the owner to submit from the portal.
1061
+ - A reviewer's answer on a chain claim already waiting for review (`pending_review`) is still
1062
+ folded: the engine is asked to complete the review, and a collection billed per review charges
1063
+ its payer for it, so the decision is recorded for the owner. Only the signing is skipped.
1064
+ - `GET /v1/info` says `lanes: { chain: false, api: true }`, so a portal does not offer the oracle
1065
+ for a chain hire (the listing is cached 5 minutes, so a flip shows up to 5 minutes late).
1066
+
1067
+ The Decision API lane is untouched: sources, keys, submits, payments, payouts, reviews, webhooks
1068
+ and its cron share run exactly as before.
1069
+
1070
+ Collections you already watch are not deleted or changed — they are simply not scanned. Set
1071
+ `CHAIN_LANE` back to `"true"` (or remove it) and redeploy, and the next minute tick scans them from
1072
+ their cursors, the claims submitted meanwhile included, and the resumes run again.
1025
1073
 
1026
1074
  ### Getting hired on OpenHands
1027
1075
 
@@ -1381,7 +1429,8 @@ write, a webhook retry job) is still reported, codes and ids only — answers, f
1381
1429
  API lane's personal fields are redacted there and in every log line. `SENTRY_TRACES_SAMPLE_RATE`
1382
1430
  (default `0.1`) and `SENTRY_ENVIRONMENT` (default `ENVIRONMENT`) tune it. The cron triggers check in
1383
1431
  to Sentry cron monitors: `oracle-scan` and — only with `API_LANE` on — `oracle-api-lane` every
1384
- minute, `oracle-cron-five` every five minutes and `oracle-cron-hour` every hour; alert
1432
+ minute, `oracle-cron-five` every five minutes and `oracle-cron-hour` every hour (with `CHAIN_LANE`
1433
+ off, `oracle-scan` and `oracle-claims-feed` stop: §11, "An API-only oracle"); alert
1385
1434
  on a missed check-in and a stopped cron pages you. The conditions that need a person — a collection
1386
1435
  or API source paused (per reason), a webhook receiver that refused all 8 attempts, a paid claim whose
1387
1436
  settlement was lost or whose receipt the facilitator refused, a facilitator that refuses the
@@ -1548,7 +1597,8 @@ own retries, and a terminal failure clears it.
1548
1597
  | `401` from the claim bot, the UCAN Store or the VFS claims lane (`claim.store-denied`), on an invocation minted as `did:ixo:…` | That DID has no IID document, or the `SERVICE_ED25519_MNEMONIC` key is not a verification method on it — the resolver has nothing to check the signature against | Re-run `oracle init --protocol <protocol did> --kit-url https://<your host> --yes`: every step already done says `skip`, so only the missing `iid` / `key` writes anything. (`--protocol` and `--kit-url` are always required, and `--only <step>` without `--yes` is still just a dry run) |
1549
1598
  | `bunx @ixo/decision-oracle@<version>` says `No version matching … found (but package exists)` right after a release | bun's cached copy of the package list is older than the release | `bun pm cache rm`, then run it again — or `BUN_INSTALL_CACHE_DIR=$(mktemp -d) bunx @ixo/decision-oracle@<version> …` |
1550
1599
  | A protocol shows as "Unnamed" in the portal, the console or the Openhands app | It has no `#pro` profile: `publish-protocol` writes one only with `--name` | `publish-protocol --phase profile --entity <protocol did> --rubric <file> --form <file> --name "<name>" [--description "<text>"] --yes` |
1551
- | A service or test account cannot sign in through a browser | `login` is a browser flow | `login --session-file <session.json> --network <n>` saves a session in the shape `login` writes |
1600
+ | A service or test account cannot sign in through a browser | `login` is a browser flow | `login --session-file <session.json> --network <n>` saves a session in the shape `login` writes. Without a `sessionAuthenticatorId` it is a plain key account: its `sessionMnemonic` must derive `address` (checked before saving) and every chain command signs standard transactions with it. An AuthHub smart-account session keeps its `sessionAuthenticatorId` and signs through that authenticator |
1601
+ | A chain command fails `AuthHub session has no sessionAuthenticatorId and its sessionMnemonic does not derive ixo1…` | The saved session names no authenticator, so it is signed as a plain key account, but its mnemonic is not that address's key | For a plain account, save the mnemonic that derives `address`; for an AuthHub smart account, run `login` again (or put its `sessionAuthenticatorId` back in the session file) |
1552
1602
  | `MsgEvaluateClaim` fails with `insufficient funds` | The oracle's own wallet was never funded; it pays its own gas, and the AuthHub session's balance is a different account | `oracle init --protocol <protocol did> --kit-url https://<your host> --only fund --yes` (tops it back up to 1 IXO). `chainStatus` on `GET /v1/claims/:claimId` names the failure |
1553
1603
  | `ORACLE_MATRIX_USER_ID (…) is not <network>'s MXID for did:ixo:…` | The MXID in `wrangler.jsonc` was hand-edited, or was derived for a different network's homeserver | Use the expected MXID the error prints — it is derived from `ORACLE_ADDRESS` and the network. Nothing is signed or written before this check |
1554
1604
  | `oracle init` says `.dev.vars`'s token belongs to someone else, or the homeserver did not answer for it | A stale or rotated `ORACLE_MATRIX_ACCESS_TOKEN`, or a homeserver blip — either way the answer is a fresh login | Let the `matrix` step run (or force it with `--relogin`); account creation underneath is idempotent, so the worst case is one extra Matrix device |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ixo/decision-oracle",
3
- "version": "0.2.25",
3
+ "version": "0.2.27",
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": [
@@ -148,6 +148,14 @@ courier page answer (`curl -s https://shipment-delivery-oracle.devnet.ixo.earth/
148
148
  Later deploys: `bunx wrangler deploy --env <env>` (secrets persist); one secret: `bunx wrangler
149
149
  secret put <NAME> --env <env>`. The did.json `id` must be `did:web:<host>`.
150
150
 
151
+ **API-only? Close the chain lane.** An oracle that serves only this lane still takes chain hires by
152
+ default — its protocol is public, so anyone can create a collection on it, hire the oracle (free
153
+ where `pricing` charges nothing) and spend the owner's engine credits. Ask the owner; if they want
154
+ API-only, set `"CHAIN_LANE": "false"` in that env's `vars` (needs `"API_LANE": "true"`), redeploy,
155
+ and check `curl -s https://<host>/v1/info` shows `"lanes":{"chain":false,"api":true}`. Register,
156
+ scan and the OpenHands webhooks then answer `409 chain_lane_off`; the API lane is untouched; setting
157
+ it back to `"true"` resumes any collections already watched (guide §11, "An API-only oracle").
158
+
151
159
  ### 6. Engine registration — payable at once
152
160
 
153
161
  ```bash
@@ -315,6 +323,8 @@ Decision Console shows the price and card eligibility read-only.
315
323
  | --- | --- |
316
324
  | Console / CLI 401 `UNAUTHORIZED` on a UCAN call | audience mismatch: the invocation names the did:web read from `<host>/.well-known/did.json` — `ORACLE_HOSTNAME` must equal the host the env is served from (its Custom Domain, or its `workers.dev` host), and `--kit` must be that host |
317
325
  | Worker 500s on every request after deploy; logs say `WEBHOOK_ENCRYPTION_KEY: required when API_LANE is on` | the secret is missing in that env — `bunx wrangler secret put WEBHOOK_ENCRYPTION_KEY --env <env>` (for a new deployment only; never replace an existing one) |
326
+ | Worker 500s on every request; logs say `CHAIN_LANE is off and API_LANE is not on` | an oracle with no lane — set `"API_LANE": "true"` in that env's `vars` (with its secrets), or remove `CHAIN_LANE` |
327
+ | `register` / `scan` / an OpenHands webhook answers 409 `chain_lane_off` | the oracle is API-only (`CHAIN_LANE` `"false"`) — by design; set it back to `"true"` and redeploy to take chain hires again |
318
328
  | `PROTOCOL_NOT_ALLOWED` on submit | the source's allow-list lacks it — empty means NONE; `api admin protocols <src> <did> --yes` |
319
329
  | 503 `SOURCE_PAUSED` `payments-disabled` | `API_PAYMENTS` is off and the source bills `x402` — turn payments on, or `api admin billing-mode <src> none` |
320
330
  | 402 again after a `PAYMENT-SIGNATURE` | the facilitator refused the payer: no deed / no `SendAuthorization` / not enough PAY — the fresh 402's error says which |
@@ -52,6 +52,10 @@ UNIQUE_HMAC_KEY="change-me-to-a-long-random-string"
52
52
  # openssl rand -base64 32
53
53
  # (deployed: wrangler secret put WEBHOOK_ENCRYPTION_KEY [--env testnet|production])
54
54
  # WEBHOOK_ENCRYPTION_KEY="change-me-to-a-long-random-string"
55
+ # An oracle that serves ONLY this lane turns the chain lane off with CHAIN_LANE "false" in
56
+ # wrangler.jsonc (on by default): nobody can then hire it on chain and spend your engine credits.
57
+ # Needs API_LANE on. Not a secret; for `wrangler dev` only:
58
+ # CHAIN_LANE="false"
55
59
  # Where your earnings land is NOT a secret: EARNINGS_ADDRESS (ixo1…, PAY and USDC on IXO) and
56
60
  # EARNINGS_BASE_WALLET (0x…, USDC on Base) live in wrangler.jsonc `vars`, per env. Each offer names
57
61
  # them as its payTo; a way to pay whose address is unset is not offered. For `wrangler dev` only:
@@ -193,6 +193,11 @@
193
193
  // Sign `MsgEvaluateClaim` on a terminal verdict automatically. "false" ⇒ decisions are
194
194
  // recorded and receipted, but a human submits them from the portal.
195
195
  "AUTO_SIGN": "true",
196
+ // The chain lane (collections hired on chain) is ON unless this says "false". Turn it off only
197
+ // for an oracle that serves the Decision API alone: your protocol is public on chain, so anyone
198
+ // could hire this oracle on a collection of their own and spend YOUR engine credits. Off ⇒
199
+ // register, scan and the hire webhooks answer 409 chain_lane_off; needs API_LANE "true".
200
+ // "CHAIN_LANE": "false",
196
201
 
197
202
  // The Decision API lane: paid, off-chain claims over HTTP at /v1/api. Dormant until API_LANE is
198
203
  // "true"; with API_PAYMENTS "false" new submits answer 503 payments-disabled, so nothing is
@@ -261,6 +266,7 @@
261
266
  // "AI_GATEWAY_ID": "<your gateway>",
262
267
  "C2PA_IMAGE_CHECK": "true",
263
268
  "AUTO_SIGN": "true",
269
+ // "CHAIN_LANE": "false", // API-only oracle: no chain hires (needs API_LANE "true")
264
270
  "API_LANE": "false",
265
271
  "API_PAYMENTS": "false"
266
272
  // "API_PRICE_USD_CENTS": "120",
@@ -371,6 +377,7 @@
371
377
  // "AI_GATEWAY_ID": "<your gateway>",
372
378
  "C2PA_IMAGE_CHECK": "true",
373
379
  "AUTO_SIGN": "true",
380
+ // "CHAIN_LANE": "false", // API-only oracle: no chain hires (needs API_LANE "true")
374
381
  "API_LANE": "false",
375
382
  "API_PAYMENTS": "false"
376
383
  // "API_PRICE_USD_CENTS": "120",
@@ -481,6 +488,7 @@
481
488
  // "AI_GATEWAY_ID": "<your gateway>",
482
489
  "C2PA_IMAGE_CHECK": "true",
483
490
  "AUTO_SIGN": "true",
491
+ // "CHAIN_LANE": "false", // API-only oracle: no chain hires (needs API_LANE "true")
484
492
  "API_LANE": "false",
485
493
  "API_PAYMENTS": "false"
486
494
  // "API_PRICE_USD_CENTS": "120",