@ixo/decision-oracle 0.0.0-stage → 0.2.0
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 +794 -2
- package/dist/act.d.ts +87 -0
- package/dist/act.d.ts.map +1 -0
- package/dist/act.js +20 -0
- package/dist/act.js.map +1 -0
- package/dist/app.d.ts +22 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +128 -0
- package/dist/app.js.map +1 -0
- package/dist/c2pa.wasm +0 -0
- package/dist/cli/commands/api.d.ts +50 -0
- package/dist/cli/commands/api.d.ts.map +1 -0
- package/dist/cli/commands/api.js +737 -0
- package/dist/cli/commands/api.js.map +1 -0
- package/dist/cli/commands/create-collection.d.ts +40 -0
- package/dist/cli/commands/create-collection.d.ts.map +1 -0
- package/dist/cli/commands/create-collection.js +318 -0
- package/dist/cli/commands/create-collection.js.map +1 -0
- package/dist/cli/commands/create-deed.d.ts +31 -0
- package/dist/cli/commands/create-deed.d.ts.map +1 -0
- package/dist/cli/commands/create-deed.js +208 -0
- package/dist/cli/commands/create-deed.js.map +1 -0
- package/dist/cli/commands/delegation.d.ts +26 -0
- package/dist/cli/commands/delegation.d.ts.map +1 -0
- package/dist/cli/commands/delegation.js +162 -0
- package/dist/cli/commands/delegation.js.map +1 -0
- package/dist/cli/commands/derive.d.ts +22 -0
- package/dist/cli/commands/derive.d.ts.map +1 -0
- package/dist/cli/commands/derive.js +53 -0
- package/dist/cli/commands/derive.js.map +1 -0
- package/dist/cli/commands/engine.d.ts +25 -0
- package/dist/cli/commands/engine.d.ts.map +1 -0
- package/dist/cli/commands/engine.js +107 -0
- package/dist/cli/commands/engine.js.map +1 -0
- package/dist/cli/commands/erase.d.ts +17 -0
- package/dist/cli/commands/erase.d.ts.map +1 -0
- package/dist/cli/commands/erase.js +117 -0
- package/dist/cli/commands/erase.js.map +1 -0
- package/dist/cli/commands/facts-run.d.ts +30 -0
- package/dist/cli/commands/facts-run.d.ts.map +1 -0
- package/dist/cli/commands/facts-run.js +409 -0
- package/dist/cli/commands/facts-run.js.map +1 -0
- package/dist/cli/commands/facts-scaffold.d.ts +22 -0
- package/dist/cli/commands/facts-scaffold.d.ts.map +1 -0
- package/dist/cli/commands/facts-scaffold.js +208 -0
- package/dist/cli/commands/facts-scaffold.js.map +1 -0
- package/dist/cli/commands/init.d.ts +18 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +34 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/login.d.ts +9 -0
- package/dist/cli/commands/login.d.ts.map +1 -0
- package/dist/cli/commands/login.js +16 -0
- package/dist/cli/commands/login.js.map +1 -0
- package/dist/cli/commands/logout.d.ts +10 -0
- package/dist/cli/commands/logout.d.ts.map +1 -0
- package/dist/cli/commands/logout.js +28 -0
- package/dist/cli/commands/logout.js.map +1 -0
- package/dist/cli/commands/manifest.d.ts +12 -0
- package/dist/cli/commands/manifest.d.ts.map +1 -0
- package/dist/cli/commands/manifest.js +48 -0
- package/dist/cli/commands/manifest.js.map +1 -0
- package/dist/cli/commands/oracle-init.d.ts +48 -0
- package/dist/cli/commands/oracle-init.d.ts.map +1 -0
- package/dist/cli/commands/oracle-init.js +1122 -0
- package/dist/cli/commands/oracle-init.js.map +1 -0
- package/dist/cli/commands/org.d.ts +28 -0
- package/dist/cli/commands/org.d.ts.map +1 -0
- package/dist/cli/commands/org.js +309 -0
- package/dist/cli/commands/org.js.map +1 -0
- package/dist/cli/commands/protocol-review.d.ts +20 -0
- package/dist/cli/commands/protocol-review.d.ts.map +1 -0
- package/dist/cli/commands/protocol-review.js +162 -0
- package/dist/cli/commands/protocol-review.js.map +1 -0
- package/dist/cli/commands/publish-protocol.d.ts +43 -0
- package/dist/cli/commands/publish-protocol.d.ts.map +1 -0
- package/dist/cli/commands/publish-protocol.js +345 -0
- package/dist/cli/commands/publish-protocol.js.map +1 -0
- package/dist/cli/commands/register.d.ts +16 -0
- package/dist/cli/commands/register.d.ts.map +1 -0
- package/dist/cli/commands/register.js +54 -0
- package/dist/cli/commands/register.js.map +1 -0
- package/dist/cli/commands/relink.d.ts +11 -0
- package/dist/cli/commands/relink.d.ts.map +1 -0
- package/dist/cli/commands/relink.js +36 -0
- package/dist/cli/commands/relink.js.map +1 -0
- package/dist/cli/commands/rubric-fetch-form.d.ts +16 -0
- package/dist/cli/commands/rubric-fetch-form.d.ts.map +1 -0
- package/dist/cli/commands/rubric-fetch-form.js +83 -0
- package/dist/cli/commands/rubric-fetch-form.js.map +1 -0
- package/dist/cli/commands/rubric-validate.d.ts +72 -0
- package/dist/cli/commands/rubric-validate.d.ts.map +1 -0
- package/dist/cli/commands/rubric-validate.js +163 -0
- package/dist/cli/commands/rubric-validate.js.map +1 -0
- package/dist/cli/commands/scan.d.ts +13 -0
- package/dist/cli/commands/scan.d.ts.map +1 -0
- package/dist/cli/commands/scan.js +39 -0
- package/dist/cli/commands/scan.js.map +1 -0
- package/dist/cli/commands/skills.d.ts +11 -0
- package/dist/cli/commands/skills.d.ts.map +1 -0
- package/dist/cli/commands/skills.js +19 -0
- package/dist/cli/commands/skills.js.map +1 -0
- package/dist/cli/commands/submit-claim.d.ts +25 -0
- package/dist/cli/commands/submit-claim.d.ts.map +1 -0
- package/dist/cli/commands/submit-claim.js +155 -0
- package/dist/cli/commands/submit-claim.js.map +1 -0
- package/dist/cli/index.d.ts +15 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +206 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/lib/agent-card-schema.d.ts +8 -0
- package/dist/cli/lib/agent-card-schema.d.ts.map +1 -0
- package/dist/cli/lib/agent-card-schema.js +169 -0
- package/dist/cli/lib/agent-card-schema.js.map +1 -0
- package/dist/cli/lib/ai-rest.d.ts +12 -0
- package/dist/cli/lib/ai-rest.d.ts.map +1 -0
- package/dist/cli/lib/ai-rest.js +36 -0
- package/dist/cli/lib/ai-rest.js.map +1 -0
- package/dist/cli/lib/args.d.ts +33 -0
- package/dist/cli/lib/args.d.ts.map +1 -0
- package/dist/cli/lib/args.js +24 -0
- package/dist/cli/lib/args.js.map +1 -0
- package/dist/cli/lib/authhub.d.ts +7 -0
- package/dist/cli/lib/authhub.d.ts.map +1 -0
- package/dist/cli/lib/authhub.js +210 -0
- package/dist/cli/lib/authhub.js.map +1 -0
- package/dist/cli/lib/chain.d.ts +264 -0
- package/dist/cli/lib/chain.d.ts.map +1 -0
- package/dist/cli/lib/chain.js +584 -0
- package/dist/cli/lib/chain.js.map +1 -0
- package/dist/cli/lib/devvars.d.ts +59 -0
- package/dist/cli/lib/devvars.d.ts.map +1 -0
- package/dist/cli/lib/devvars.js +206 -0
- package/dist/cli/lib/devvars.js.map +1 -0
- package/dist/cli/lib/engine.d.ts +54 -0
- package/dist/cli/lib/engine.d.ts.map +1 -0
- package/dist/cli/lib/engine.js +112 -0
- package/dist/cli/lib/engine.js.map +1 -0
- package/dist/cli/lib/identity-cards.d.ts +188 -0
- package/dist/cli/lib/identity-cards.d.ts.map +1 -0
- package/dist/cli/lib/identity-cards.js +261 -0
- package/dist/cli/lib/identity-cards.js.map +1 -0
- package/dist/cli/lib/matrix-account.d.ts +79 -0
- package/dist/cli/lib/matrix-account.d.ts.map +1 -0
- package/dist/cli/lib/matrix-account.js +228 -0
- package/dist/cli/lib/matrix-account.js.map +1 -0
- package/dist/cli/lib/oracle-account.d.ts +108 -0
- package/dist/cli/lib/oracle-account.d.ts.map +1 -0
- package/dist/cli/lib/oracle-account.js +207 -0
- package/dist/cli/lib/oracle-account.js.map +1 -0
- package/dist/cli/lib/project.d.ts +22 -0
- package/dist/cli/lib/project.d.ts.map +1 -0
- package/dist/cli/lib/project.js +62 -0
- package/dist/cli/lib/project.js.map +1 -0
- package/dist/cli/lib/session.d.ts +109 -0
- package/dist/cli/lib/session.d.ts.map +1 -0
- package/dist/cli/lib/session.js +354 -0
- package/dist/cli/lib/session.js.map +1 -0
- package/dist/cli/lib/workorder.d.ts +57 -0
- package/dist/cli/lib/workorder.d.ts.map +1 -0
- package/dist/cli/lib/workorder.js +101 -0
- package/dist/cli/lib/workorder.js.map +1 -0
- package/dist/config.d.ts +221 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +431 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +88 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +9 -0
- package/dist/context.js.map +1 -0
- package/dist/contracts/api.d.ts +3367 -0
- package/dist/contracts/api.d.ts.map +1 -0
- package/dist/contracts/api.js +1303 -0
- package/dist/contracts/api.js.map +1 -0
- package/dist/contracts/capabilities.d.ts +126 -0
- package/dist/contracts/capabilities.d.ts.map +1 -0
- package/dist/contracts/capabilities.js +164 -0
- package/dist/contracts/capabilities.js.map +1 -0
- package/dist/contracts/claim-source.d.ts +98 -0
- package/dist/contracts/claim-source.d.ts.map +1 -0
- package/dist/contracts/claim-source.js +17 -0
- package/dist/contracts/claim-source.js.map +1 -0
- package/dist/contracts/errors.d.ts +150 -0
- package/dist/contracts/errors.d.ts.map +1 -0
- package/dist/contracts/errors.js +121 -0
- package/dist/contracts/errors.js.map +1 -0
- package/dist/contracts/index.d.ts +15 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +11 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/payment-gate.d.ts +129 -0
- package/dist/contracts/payment-gate.d.ts.map +1 -0
- package/dist/contracts/payment-gate.js +24 -0
- package/dist/contracts/payment-gate.js.map +1 -0
- package/dist/contracts/start-api-claim.d.ts +90 -0
- package/dist/contracts/start-api-claim.d.ts.map +1 -0
- package/dist/contracts/start-api-claim.js +22 -0
- package/dist/contracts/start-api-claim.js.map +1 -0
- package/dist/contracts/webhooks.d.ts +1487 -0
- package/dist/contracts/webhooks.d.ts.map +1 -0
- package/dist/contracts/webhooks.js +71 -0
- package/dist/contracts/webhooks.js.map +1 -0
- package/dist/contracts/work-order.d.ts +29 -0
- package/dist/contracts/work-order.d.ts.map +1 -0
- package/dist/contracts/work-order.js +12 -0
- package/dist/contracts/work-order.js.map +1 -0
- package/dist/cron/ai-cost.d.ts +29 -0
- package/dist/cron/ai-cost.d.ts.map +1 -0
- package/dist/cron/ai-cost.js +80 -0
- package/dist/cron/ai-cost.js.map +1 -0
- package/dist/cron/api-lane.d.ts +39 -0
- package/dist/cron/api-lane.d.ts.map +1 -0
- package/dist/cron/api-lane.js +203 -0
- package/dist/cron/api-lane.js.map +1 -0
- package/dist/cron/backlog.d.ts +50 -0
- package/dist/cron/backlog.d.ts.map +1 -0
- package/dist/cron/backlog.js +115 -0
- package/dist/cron/backlog.js.map +1 -0
- package/dist/cron/billing.d.ts +11 -0
- package/dist/cron/billing.d.ts.map +1 -0
- package/dist/cron/billing.js +69 -0
- package/dist/cron/billing.js.map +1 -0
- package/dist/cron/collection.d.ts +50 -0
- package/dist/cron/collection.d.ts.map +1 -0
- package/dist/cron/collection.js +118 -0
- package/dist/cron/collection.js.map +1 -0
- package/dist/cron/funding.d.ts +16 -0
- package/dist/cron/funding.d.ts.map +1 -0
- package/dist/cron/funding.js +262 -0
- package/dist/cron/funding.js.map +1 -0
- package/dist/cron/payer-billing.d.ts +38 -0
- package/dist/cron/payer-billing.d.ts.map +1 -0
- package/dist/cron/payer-billing.js +119 -0
- package/dist/cron/payer-billing.js.map +1 -0
- package/dist/cron/scan.d.ts +43 -0
- package/dist/cron/scan.d.ts.map +1 -0
- package/dist/cron/scan.js +250 -0
- package/dist/cron/scan.js.map +1 -0
- package/dist/db/client.d.ts +14 -0
- package/dist/db/client.d.ts.map +1 -0
- package/dist/db/client.js +14 -0
- package/dist/db/client.js.map +1 -0
- package/dist/db/index.d.ts +7 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +7 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +6738 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +1433 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/define-oracle.d.ts +32 -0
- package/dist/define-oracle.d.ts.map +1 -0
- package/dist/define-oracle.js +80 -0
- package/dist/define-oracle.js.map +1 -0
- package/dist/dev-oracle.d.ts +9 -0
- package/dist/dev-oracle.d.ts.map +1 -0
- package/dist/dev-oracle.js +19 -0
- package/dist/dev-oracle.js.map +1 -0
- package/dist/dev-worker.d.ts +10 -0
- package/dist/dev-worker.d.ts.map +1 -0
- package/dist/dev-worker.js +12 -0
- package/dist/dev-worker.js.map +1 -0
- package/dist/env.d.ts +146 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +10 -0
- package/dist/env.js.map +1 -0
- package/dist/facts/exif.d.ts +43 -0
- package/dist/facts/exif.d.ts.map +1 -0
- package/dist/facts/exif.js +224 -0
- package/dist/facts/exif.js.map +1 -0
- package/dist/facts/fact.d.ts +62 -0
- package/dist/facts/fact.d.ts.map +1 -0
- package/dist/facts/fact.js +105 -0
- package/dist/facts/fact.js.map +1 -0
- package/dist/facts/geo.d.ts +55 -0
- package/dist/facts/geo.d.ts.map +1 -0
- package/dist/facts/geo.js +267 -0
- package/dist/facts/geo.js.map +1 -0
- package/dist/facts/index.d.ts +31 -0
- package/dist/facts/index.d.ts.map +1 -0
- package/dist/facts/index.js +27 -0
- package/dist/facts/index.js.map +1 -0
- package/dist/facts/linked-doc.d.ts +60 -0
- package/dist/facts/linked-doc.d.ts.map +1 -0
- package/dist/facts/linked-doc.js +101 -0
- package/dist/facts/linked-doc.js.map +1 -0
- package/dist/facts/types.d.ts +278 -0
- package/dist/facts/types.d.ts.map +1 -0
- package/dist/facts/types.js +19 -0
- package/dist/facts/types.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api-lane/accept.d.ts +29 -0
- package/dist/lib/api-lane/accept.d.ts.map +1 -0
- package/dist/lib/api-lane/accept.js +218 -0
- package/dist/lib/api-lane/accept.js.map +1 -0
- package/dist/lib/api-lane/claim-list.d.ts +44 -0
- package/dist/lib/api-lane/claim-list.d.ts.map +1 -0
- package/dist/lib/api-lane/claim-list.js +92 -0
- package/dist/lib/api-lane/claim-list.js.map +1 -0
- package/dist/lib/api-lane/company.d.ts +56 -0
- package/dist/lib/api-lane/company.d.ts.map +1 -0
- package/dist/lib/api-lane/company.js +103 -0
- package/dist/lib/api-lane/company.js.map +1 -0
- package/dist/lib/api-lane/events.d.ts +21 -0
- package/dist/lib/api-lane/events.d.ts.map +1 -0
- package/dist/lib/api-lane/events.js +111 -0
- package/dist/lib/api-lane/events.js.map +1 -0
- package/dist/lib/api-lane/ids.d.ts +39 -0
- package/dist/lib/api-lane/ids.d.ts.map +1 -0
- package/dist/lib/api-lane/ids.js +78 -0
- package/dist/lib/api-lane/ids.js.map +1 -0
- package/dist/lib/api-lane/index.d.ts +16 -0
- package/dist/lib/api-lane/index.d.ts.map +1 -0
- package/dist/lib/api-lane/index.js +16 -0
- package/dist/lib/api-lane/index.js.map +1 -0
- package/dist/lib/api-lane/linked.d.ts +264 -0
- package/dist/lib/api-lane/linked.d.ts.map +1 -0
- package/dist/lib/api-lane/linked.js +272 -0
- package/dist/lib/api-lane/linked.js.map +1 -0
- package/dist/lib/api-lane/media-type.d.ts +15 -0
- package/dist/lib/api-lane/media-type.d.ts.map +1 -0
- package/dist/lib/api-lane/media-type.js +73 -0
- package/dist/lib/api-lane/media-type.js.map +1 -0
- package/dist/lib/api-lane/member-emails.d.ts +83 -0
- package/dist/lib/api-lane/member-emails.d.ts.map +1 -0
- package/dist/lib/api-lane/member-emails.js +213 -0
- package/dist/lib/api-lane/member-emails.js.map +1 -0
- package/dist/lib/api-lane/members.d.ts +42 -0
- package/dist/lib/api-lane/members.d.ts.map +1 -0
- package/dist/lib/api-lane/members.js +75 -0
- package/dist/lib/api-lane/members.js.map +1 -0
- package/dist/lib/api-lane/notify.d.ts +47 -0
- package/dist/lib/api-lane/notify.d.ts.map +1 -0
- package/dist/lib/api-lane/notify.js +240 -0
- package/dist/lib/api-lane/notify.js.map +1 -0
- package/dist/lib/api-lane/pause.d.ts +41 -0
- package/dist/lib/api-lane/pause.d.ts.map +1 -0
- package/dist/lib/api-lane/pause.js +155 -0
- package/dist/lib/api-lane/pause.js.map +1 -0
- package/dist/lib/api-lane/record.d.ts +76 -0
- package/dist/lib/api-lane/record.d.ts.map +1 -0
- package/dist/lib/api-lane/record.js +337 -0
- package/dist/lib/api-lane/record.js.map +1 -0
- package/dist/lib/api-lane/retention.d.ts +43 -0
- package/dist/lib/api-lane/retention.d.ts.map +1 -0
- package/dist/lib/api-lane/retention.js +176 -0
- package/dist/lib/api-lane/retention.js.map +1 -0
- package/dist/lib/api-lane/review.d.ts +76 -0
- package/dist/lib/api-lane/review.d.ts.map +1 -0
- package/dist/lib/api-lane/review.js +355 -0
- package/dist/lib/api-lane/review.js.map +1 -0
- package/dist/lib/api-lane/reviewers.d.ts +109 -0
- package/dist/lib/api-lane/reviewers.d.ts.map +1 -0
- package/dist/lib/api-lane/reviewers.js +755 -0
- package/dist/lib/api-lane/reviewers.js.map +1 -0
- package/dist/lib/api-lane/source-delete.d.ts +16 -0
- package/dist/lib/api-lane/source-delete.d.ts.map +1 -0
- package/dist/lib/api-lane/source-delete.js +50 -0
- package/dist/lib/api-lane/source-delete.js.map +1 -0
- package/dist/lib/api-lane/source.d.ts +19 -0
- package/dist/lib/api-lane/source.d.ts.map +1 -0
- package/dist/lib/api-lane/source.js +226 -0
- package/dist/lib/api-lane/source.js.map +1 -0
- package/dist/lib/api-lane/submitter.d.ts +22 -0
- package/dist/lib/api-lane/submitter.d.ts.map +1 -0
- package/dist/lib/api-lane/submitter.js +34 -0
- package/dist/lib/api-lane/submitter.js.map +1 -0
- package/dist/lib/api-lane/timeline.d.ts +20 -0
- package/dist/lib/api-lane/timeline.d.ts.map +1 -0
- package/dist/lib/api-lane/timeline.js +23 -0
- package/dist/lib/api-lane/timeline.js.map +1 -0
- package/dist/lib/api-lane/webhook-crypto.d.ts +44 -0
- package/dist/lib/api-lane/webhook-crypto.d.ts.map +1 -0
- package/dist/lib/api-lane/webhook-crypto.js +103 -0
- package/dist/lib/api-lane/webhook-crypto.js.map +1 -0
- package/dist/lib/api-lane/webhooks.d.ts +117 -0
- package/dist/lib/api-lane/webhooks.d.ts.map +1 -0
- package/dist/lib/api-lane/webhooks.js +503 -0
- package/dist/lib/api-lane/webhooks.js.map +1 -0
- package/dist/lib/archive.d.ts +64 -0
- package/dist/lib/archive.d.ts.map +1 -0
- package/dist/lib/archive.js +305 -0
- package/dist/lib/archive.js.map +1 -0
- package/dist/lib/bech32.d.ts +8 -0
- package/dist/lib/bech32.d.ts.map +1 -0
- package/dist/lib/bech32.js +39 -0
- package/dist/lib/bech32.js.map +1 -0
- package/dist/lib/claim-erase.d.ts +22 -0
- package/dist/lib/claim-erase.d.ts.map +1 -0
- package/dist/lib/claim-erase.js +44 -0
- package/dist/lib/claim-erase.js.map +1 -0
- package/dist/lib/cursor.d.ts +11 -0
- package/dist/lib/cursor.d.ts.map +1 -0
- package/dist/lib/cursor.js +20 -0
- package/dist/lib/cursor.js.map +1 -0
- package/dist/lib/email/send.d.ts +43 -0
- package/dist/lib/email/send.d.ts.map +1 -0
- package/dist/lib/email/send.js +71 -0
- package/dist/lib/email/send.js.map +1 -0
- package/dist/lib/email/template.d.ts +217 -0
- package/dist/lib/email/template.d.ts.map +1 -0
- package/dist/lib/email/template.js +713 -0
- package/dist/lib/email/template.js.map +1 -0
- package/dist/lib/engine/adr36.d.ts +56 -0
- package/dist/lib/engine/adr36.d.ts.map +1 -0
- package/dist/lib/engine/adr36.js +76 -0
- package/dist/lib/engine/adr36.js.map +1 -0
- package/dist/lib/engine/client.d.ts +162 -0
- package/dist/lib/engine/client.d.ts.map +1 -0
- package/dist/lib/engine/client.js +473 -0
- package/dist/lib/engine/client.js.map +1 -0
- package/dist/lib/engine/types.d.ts +1356 -0
- package/dist/lib/engine/types.d.ts.map +1 -0
- package/dist/lib/engine/types.js +562 -0
- package/dist/lib/engine/types.js.map +1 -0
- package/dist/lib/errors.d.ts +73 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +96 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/facts/builder.d.ts +88 -0
- package/dist/lib/facts/builder.d.ts.map +1 -0
- package/dist/lib/facts/builder.js +239 -0
- package/dist/lib/facts/builder.js.map +1 -0
- package/dist/lib/facts/ctx.d.ts +34 -0
- package/dist/lib/facts/ctx.d.ts.map +1 -0
- package/dist/lib/facts/ctx.js +51 -0
- package/dist/lib/facts/ctx.js.map +1 -0
- package/dist/lib/facts/expected.d.ts +23 -0
- package/dist/lib/facts/expected.d.ts.map +1 -0
- package/dist/lib/facts/expected.js +16 -0
- package/dist/lib/facts/expected.js.map +1 -0
- package/dist/lib/facts/fact-cache.d.ts +469 -0
- package/dist/lib/facts/fact-cache.d.ts.map +1 -0
- package/dist/lib/facts/fact-cache.js +52 -0
- package/dist/lib/facts/fact-cache.js.map +1 -0
- package/dist/lib/facts/factset.d.ts +608 -0
- package/dist/lib/facts/factset.d.ts.map +1 -0
- package/dist/lib/facts/factset.js +226 -0
- package/dist/lib/facts/factset.js.map +1 -0
- package/dist/lib/facts/producer.d.ts +190 -0
- package/dist/lib/facts/producer.d.ts.map +1 -0
- package/dist/lib/facts/producer.js +15 -0
- package/dist/lib/facts/producer.js.map +1 -0
- package/dist/lib/facts/runner.d.ts +111 -0
- package/dist/lib/facts/runner.d.ts.map +1 -0
- package/dist/lib/facts/runner.js +438 -0
- package/dist/lib/facts/runner.js.map +1 -0
- package/dist/lib/facts/stock/ai-check.d.ts +100 -0
- package/dist/lib/facts/stock/ai-check.d.ts.map +1 -0
- package/dist/lib/facts/stock/ai-check.js +526 -0
- package/dist/lib/facts/stock/ai-check.js.map +1 -0
- package/dist/lib/facts/stock/ai-inputs.d.ts +104 -0
- package/dist/lib/facts/stock/ai-inputs.d.ts.map +1 -0
- package/dist/lib/facts/stock/ai-inputs.js +332 -0
- package/dist/lib/facts/stock/ai-inputs.js.map +1 -0
- package/dist/lib/facts/stock/ai-provider.d.ts +105 -0
- package/dist/lib/facts/stock/ai-provider.d.ts.map +1 -0
- package/dist/lib/facts/stock/ai-provider.js +250 -0
- package/dist/lib/facts/stock/ai-provider.js.map +1 -0
- package/dist/lib/facts/stock/ai-unique.d.ts +85 -0
- package/dist/lib/facts/stock/ai-unique.d.ts.map +1 -0
- package/dist/lib/facts/stock/ai-unique.js +208 -0
- package/dist/lib/facts/stock/ai-unique.js.map +1 -0
- package/dist/lib/facts/stock/archive.d.ts +63 -0
- package/dist/lib/facts/stock/archive.d.ts.map +1 -0
- package/dist/lib/facts/stock/archive.js +349 -0
- package/dist/lib/facts/stock/archive.js.map +1 -0
- package/dist/lib/facts/stock/c2pa-trust-list.d.ts +16 -0
- package/dist/lib/facts/stock/c2pa-trust-list.d.ts.map +1 -0
- package/dist/lib/facts/stock/c2pa-trust-list.js +637 -0
- package/dist/lib/facts/stock/c2pa-trust-list.js.map +1 -0
- package/dist/lib/facts/stock/c2pa.d.ts +56 -0
- package/dist/lib/facts/stock/c2pa.d.ts.map +1 -0
- package/dist/lib/facts/stock/c2pa.js +234 -0
- package/dist/lib/facts/stock/c2pa.js.map +1 -0
- package/dist/lib/facts/stock/call.d.ts +91 -0
- package/dist/lib/facts/stock/call.d.ts.map +1 -0
- package/dist/lib/facts/stock/call.js +281 -0
- package/dist/lib/facts/stock/call.js.map +1 -0
- package/dist/lib/facts/stock/capability.d.ts +39 -0
- package/dist/lib/facts/stock/capability.d.ts.map +1 -0
- package/dist/lib/facts/stock/capability.js +59 -0
- package/dist/lib/facts/stock/capability.js.map +1 -0
- package/dist/lib/facts/stock/ext-source.d.ts +51 -0
- package/dist/lib/facts/stock/ext-source.d.ts.map +1 -0
- package/dist/lib/facts/stock/ext-source.js +201 -0
- package/dist/lib/facts/stock/ext-source.js.map +1 -0
- package/dist/lib/facts/stock/frequency-fact.d.ts +59 -0
- package/dist/lib/facts/stock/frequency-fact.d.ts.map +1 -0
- package/dist/lib/facts/stock/frequency-fact.js +86 -0
- package/dist/lib/facts/stock/frequency-fact.js.map +1 -0
- package/dist/lib/facts/stock/frequency.d.ts +32 -0
- package/dist/lib/facts/stock/frequency.d.ts.map +1 -0
- package/dist/lib/facts/stock/frequency.js +48 -0
- package/dist/lib/facts/stock/frequency.js.map +1 -0
- package/dist/lib/facts/stock/index.d.ts +27 -0
- package/dist/lib/facts/stock/index.d.ts.map +1 -0
- package/dist/lib/facts/stock/index.js +28 -0
- package/dist/lib/facts/stock/index.js.map +1 -0
- package/dist/lib/facts/stock/normalize.d.ts +45 -0
- package/dist/lib/facts/stock/normalize.d.ts.map +1 -0
- package/dist/lib/facts/stock/normalize.js +135 -0
- package/dist/lib/facts/stock/normalize.js.map +1 -0
- package/dist/lib/facts/stock/phash.d.ts +55 -0
- package/dist/lib/facts/stock/phash.d.ts.map +1 -0
- package/dist/lib/facts/stock/phash.js +256 -0
- package/dist/lib/facts/stock/phash.js.map +1 -0
- package/dist/lib/facts/stock/rule-defaults.d.ts +72 -0
- package/dist/lib/facts/stock/rule-defaults.d.ts.map +1 -0
- package/dist/lib/facts/stock/rule-defaults.js +55 -0
- package/dist/lib/facts/stock/rule-defaults.js.map +1 -0
- package/dist/lib/facts/stock/send.d.ts +52 -0
- package/dist/lib/facts/stock/send.d.ts.map +1 -0
- package/dist/lib/facts/stock/send.js +148 -0
- package/dist/lib/facts/stock/send.js.map +1 -0
- package/dist/lib/facts/stock/unique-score.d.ts +138 -0
- package/dist/lib/facts/stock/unique-score.d.ts.map +1 -0
- package/dist/lib/facts/stock/unique-score.js +257 -0
- package/dist/lib/facts/stock/unique-score.js.map +1 -0
- package/dist/lib/facts/stock/unique-store.d.ts +420 -0
- package/dist/lib/facts/stock/unique-store.d.ts.map +1 -0
- package/dist/lib/facts/stock/unique-store.js +328 -0
- package/dist/lib/facts/stock/unique-store.js.map +1 -0
- package/dist/lib/facts/stock/unique.d.ts +35 -0
- package/dist/lib/facts/stock/unique.d.ts.map +1 -0
- package/dist/lib/facts/stock/unique.js +327 -0
- package/dist/lib/facts/stock/unique.js.map +1 -0
- package/dist/lib/facts/tools.d.ts +57 -0
- package/dist/lib/facts/tools.d.ts.map +1 -0
- package/dist/lib/facts/tools.js +325 -0
- package/dist/lib/facts/tools.js.map +1 -0
- package/dist/lib/facts/validate.d.ts +33 -0
- package/dist/lib/facts/validate.d.ts.map +1 -0
- package/dist/lib/facts/validate.js +137 -0
- package/dist/lib/facts/validate.js.map +1 -0
- package/dist/lib/hash.d.ts +44 -0
- package/dist/lib/hash.d.ts.map +1 -0
- package/dist/lib/hash.js +80 -0
- package/dist/lib/hash.js.map +1 -0
- package/dist/lib/http/client.d.ts +113 -0
- package/dist/lib/http/client.d.ts.map +1 -0
- package/dist/lib/http/client.js +330 -0
- package/dist/lib/http/client.js.map +1 -0
- package/dist/lib/ids.d.ts +17 -0
- package/dist/lib/ids.d.ts.map +1 -0
- package/dist/lib/ids.js +26 -0
- package/dist/lib/ids.js.map +1 -0
- package/dist/lib/ixo/attachments.d.ts +116 -0
- package/dist/lib/ixo/attachments.d.ts.map +1 -0
- package/dist/lib/ixo/attachments.js +290 -0
- package/dist/lib/ixo/attachments.js.map +1 -0
- package/dist/lib/ixo/authz.d.ts +40 -0
- package/dist/lib/ixo/authz.d.ts.map +1 -0
- package/dist/lib/ixo/authz.js +113 -0
- package/dist/lib/ixo/authz.js.map +1 -0
- package/dist/lib/ixo/blocksync/index.d.ts +90 -0
- package/dist/lib/ixo/blocksync/index.d.ts.map +1 -0
- package/dist/lib/ixo/blocksync/index.js +251 -0
- package/dist/lib/ixo/blocksync/index.js.map +1 -0
- package/dist/lib/ixo/blocksync/queries.d.ts +15 -0
- package/dist/lib/ixo/blocksync/queries.d.ts.map +1 -0
- package/dist/lib/ixo/blocksync/queries.js +144 -0
- package/dist/lib/ixo/blocksync/queries.js.map +1 -0
- package/dist/lib/ixo/blocksync/types.d.ts +184 -0
- package/dist/lib/ixo/blocksync/types.d.ts.map +1 -0
- package/dist/lib/ixo/blocksync/types.js +108 -0
- package/dist/lib/ixo/blocksync/types.js.map +1 -0
- package/dist/lib/ixo/cellnode.d.ts +35 -0
- package/dist/lib/ixo/cellnode.d.ts.map +1 -0
- package/dist/lib/ixo/cellnode.js +43 -0
- package/dist/lib/ixo/cellnode.js.map +1 -0
- package/dist/lib/ixo/chain-write.d.ts +130 -0
- package/dist/lib/ixo/chain-write.d.ts.map +1 -0
- package/dist/lib/ixo/chain-write.js +460 -0
- package/dist/lib/ixo/chain-write.js.map +1 -0
- package/dist/lib/ixo/chain.d.ts +1046 -0
- package/dist/lib/ixo/chain.d.ts.map +1 -0
- package/dist/lib/ixo/chain.js +279 -0
- package/dist/lib/ixo/chain.js.map +1 -0
- package/dist/lib/ixo/claim-bot.d.ts +49 -0
- package/dist/lib/ixo/claim-bot.d.ts.map +1 -0
- package/dist/lib/ixo/claim-bot.js +141 -0
- package/dist/lib/ixo/claim-bot.js.map +1 -0
- package/dist/lib/ixo/delegation-verify.d.ts +44 -0
- package/dist/lib/ixo/delegation-verify.d.ts.map +1 -0
- package/dist/lib/ixo/delegation-verify.js +101 -0
- package/dist/lib/ixo/delegation-verify.js.map +1 -0
- package/dist/lib/ixo/did.d.ts +13 -0
- package/dist/lib/ixo/did.d.ts.map +1 -0
- package/dist/lib/ixo/did.js +85 -0
- package/dist/lib/ixo/did.js.map +1 -0
- package/dist/lib/ixo/grant-chain.d.ts +55 -0
- package/dist/lib/ixo/grant-chain.d.ts.map +1 -0
- package/dist/lib/ixo/grant-chain.js +185 -0
- package/dist/lib/ixo/grant-chain.js.map +1 -0
- package/dist/lib/ixo/identity.d.ts +32 -0
- package/dist/lib/ixo/identity.d.ts.map +1 -0
- package/dist/lib/ixo/identity.js +104 -0
- package/dist/lib/ixo/identity.js.map +1 -0
- package/dist/lib/ixo/index.d.ts +118 -0
- package/dist/lib/ixo/index.d.ts.map +1 -0
- package/dist/lib/ixo/index.js +395 -0
- package/dist/lib/ixo/index.js.map +1 -0
- package/dist/lib/ixo/payment.d.ts +104 -0
- package/dist/lib/ixo/payment.d.ts.map +1 -0
- package/dist/lib/ixo/payment.js +136 -0
- package/dist/lib/ixo/payment.js.map +1 -0
- package/dist/lib/ixo/ucan-store.d.ts +30 -0
- package/dist/lib/ixo/ucan-store.d.ts.map +1 -0
- package/dist/lib/ixo/ucan-store.js +134 -0
- package/dist/lib/ixo/ucan-store.js.map +1 -0
- package/dist/lib/ixo/ucan.d.ts +96 -0
- package/dist/lib/ixo/ucan.d.ts.map +1 -0
- package/dist/lib/ixo/ucan.js +216 -0
- package/dist/lib/ixo/ucan.js.map +1 -0
- package/dist/lib/ixo/verified-read.d.ts +43 -0
- package/dist/lib/ixo/verified-read.d.ts.map +1 -0
- package/dist/lib/ixo/verified-read.js +73 -0
- package/dist/lib/ixo/verified-read.js.map +1 -0
- package/dist/lib/ixo/vfs.d.ts +27 -0
- package/dist/lib/ixo/vfs.d.ts.map +1 -0
- package/dist/lib/ixo/vfs.js +42 -0
- package/dist/lib/ixo/vfs.js.map +1 -0
- package/dist/lib/ixo/web-did.d.ts +7 -0
- package/dist/lib/ixo/web-did.d.ts.map +1 -0
- package/dist/lib/ixo/web-did.js +9 -0
- package/dist/lib/ixo/web-did.js.map +1 -0
- package/dist/lib/logger.d.ts +34 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +126 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/matrix/api.d.ts +57 -0
- package/dist/lib/matrix/api.d.ts.map +1 -0
- package/dist/lib/matrix/api.js +58 -0
- package/dist/lib/matrix/api.js.map +1 -0
- package/dist/lib/matrix/client.d.ts +41 -0
- package/dist/lib/matrix/client.d.ts.map +1 -0
- package/dist/lib/matrix/client.js +104 -0
- package/dist/lib/matrix/client.js.map +1 -0
- package/dist/lib/matrix/deliver.d.ts +224 -0
- package/dist/lib/matrix/deliver.d.ts.map +1 -0
- package/dist/lib/matrix/deliver.js +788 -0
- package/dist/lib/matrix/deliver.js.map +1 -0
- package/dist/lib/matrix/events.d.ts +114 -0
- package/dist/lib/matrix/events.d.ts.map +1 -0
- package/dist/lib/matrix/events.js +143 -0
- package/dist/lib/matrix/events.js.map +1 -0
- package/dist/lib/matrix/mxid.d.ts +24 -0
- package/dist/lib/matrix/mxid.d.ts.map +1 -0
- package/dist/lib/matrix/mxid.js +47 -0
- package/dist/lib/matrix/mxid.js.map +1 -0
- package/dist/lib/metrics.d.ts +105 -0
- package/dist/lib/metrics.d.ts.map +1 -0
- package/dist/lib/metrics.js +204 -0
- package/dist/lib/metrics.js.map +1 -0
- package/dist/lib/oauth/approvals.d.ts +160 -0
- package/dist/lib/oauth/approvals.d.ts.map +1 -0
- package/dist/lib/oauth/approvals.js +273 -0
- package/dist/lib/oauth/approvals.js.map +1 -0
- package/dist/lib/oauth/standing.d.ts +152 -0
- package/dist/lib/oauth/standing.d.ts.map +1 -0
- package/dist/lib/oauth/standing.js +299 -0
- package/dist/lib/oauth/standing.js.map +1 -0
- package/dist/lib/oauth/token.d.ts +60 -0
- package/dist/lib/oauth/token.d.ts.map +1 -0
- package/dist/lib/oauth/token.js +129 -0
- package/dist/lib/oauth/token.js.map +1 -0
- package/dist/lib/payments/extend.d.ts +42 -0
- package/dist/lib/payments/extend.d.ts.map +1 -0
- package/dist/lib/payments/extend.js +98 -0
- package/dist/lib/payments/extend.js.map +1 -0
- package/dist/lib/payments/facilitator.d.ts +220 -0
- package/dist/lib/payments/facilitator.d.ts.map +1 -0
- package/dist/lib/payments/facilitator.js +451 -0
- package/dist/lib/payments/facilitator.js.map +1 -0
- package/dist/lib/payments/gate.d.ts +207 -0
- package/dist/lib/payments/gate.d.ts.map +1 -0
- package/dist/lib/payments/gate.js +481 -0
- package/dist/lib/payments/gate.js.map +1 -0
- package/dist/lib/payments/header-seal.d.ts +33 -0
- package/dist/lib/payments/header-seal.d.ts.map +1 -0
- package/dist/lib/payments/header-seal.js +76 -0
- package/dist/lib/payments/header-seal.js.map +1 -0
- package/dist/lib/payments/index.d.ts +12 -0
- package/dist/lib/payments/index.d.ts.map +1 -0
- package/dist/lib/payments/index.js +11 -0
- package/dist/lib/payments/index.js.map +1 -0
- package/dist/lib/payments/price.d.ts +25 -0
- package/dist/lib/payments/price.d.ts.map +1 -0
- package/dist/lib/payments/price.js +43 -0
- package/dist/lib/payments/price.js.map +1 -0
- package/dist/lib/payments/reconcile.d.ts +77 -0
- package/dist/lib/payments/reconcile.d.ts.map +1 -0
- package/dist/lib/payments/reconcile.js +317 -0
- package/dist/lib/payments/reconcile.js.map +1 -0
- package/dist/lib/payments/refund.d.ts +34 -0
- package/dist/lib/payments/refund.d.ts.map +1 -0
- package/dist/lib/payments/refund.js +123 -0
- package/dist/lib/payments/refund.js.map +1 -0
- package/dist/lib/payments/review-billing.d.ts +222 -0
- package/dist/lib/payments/review-billing.d.ts.map +1 -0
- package/dist/lib/payments/review-billing.js +609 -0
- package/dist/lib/payments/review-billing.js.map +1 -0
- package/dist/lib/payments/settle.d.ts +44 -0
- package/dist/lib/payments/settle.d.ts.map +1 -0
- package/dist/lib/payments/settle.js +306 -0
- package/dist/lib/payments/settle.js.map +1 -0
- package/dist/lib/payments/terms.d.ts +27 -0
- package/dist/lib/payments/terms.d.ts.map +1 -0
- package/dist/lib/payments/terms.js +21 -0
- package/dist/lib/payments/terms.js.map +1 -0
- package/dist/lib/payouts.d.ts +20 -0
- package/dist/lib/payouts.d.ts.map +1 -0
- package/dist/lib/payouts.js +30 -0
- package/dist/lib/payouts.js.map +1 -0
- package/dist/lib/readiness.d.ts +42 -0
- package/dist/lib/readiness.d.ts.map +1 -0
- package/dist/lib/readiness.js +130 -0
- package/dist/lib/readiness.js.map +1 -0
- package/dist/lib/redact.d.ts +23 -0
- package/dist/lib/redact.d.ts.map +1 -0
- package/dist/lib/redact.js +67 -0
- package/dist/lib/redact.js.map +1 -0
- package/dist/lib/register.d.ts +85 -0
- package/dist/lib/register.d.ts.map +1 -0
- package/dist/lib/register.js +390 -0
- package/dist/lib/register.js.map +1 -0
- package/dist/lib/review/attestation.d.ts +92 -0
- package/dist/lib/review/attestation.d.ts.map +1 -0
- package/dist/lib/review/attestation.js +264 -0
- package/dist/lib/review/attestation.js.map +1 -0
- package/dist/lib/review/reviewer-grant.d.ts +146 -0
- package/dist/lib/review/reviewer-grant.d.ts.map +1 -0
- package/dist/lib/review/reviewer-grant.js +359 -0
- package/dist/lib/review/reviewer-grant.js.map +1 -0
- package/dist/lib/review/tasks.d.ts +18 -0
- package/dist/lib/review/tasks.d.ts.map +1 -0
- package/dist/lib/review/tasks.js +36 -0
- package/dist/lib/review/tasks.js.map +1 -0
- package/dist/lib/sentry.d.ts +89 -0
- package/dist/lib/sentry.d.ts.map +1 -0
- package/dist/lib/sentry.js +284 -0
- package/dist/lib/sentry.js.map +1 -0
- package/dist/lib/spans.d.ts +14 -0
- package/dist/lib/spans.d.ts.map +1 -0
- package/dist/lib/spans.js +28 -0
- package/dist/lib/spans.js.map +1 -0
- package/dist/lib/tracing.d.ts +31 -0
- package/dist/lib/tracing.d.ts.map +1 -0
- package/dist/lib/tracing.js +66 -0
- package/dist/lib/tracing.js.map +1 -0
- package/dist/lib/ttl-cache.d.ts +26 -0
- package/dist/lib/ttl-cache.d.ts.map +1 -0
- package/dist/lib/ttl-cache.js +61 -0
- package/dist/lib/ttl-cache.js.map +1 -0
- package/dist/lib/validator.d.ts +19 -0
- package/dist/lib/validator.d.ts.map +1 -0
- package/dist/lib/validator.js +32 -0
- package/dist/lib/validator.js.map +1 -0
- package/dist/lib/work-order/form.d.ts +40 -0
- package/dist/lib/work-order/form.d.ts.map +1 -0
- package/dist/lib/work-order/form.js +316 -0
- package/dist/lib/work-order/form.js.map +1 -0
- package/dist/lib/work-order/index.d.ts +20 -0
- package/dist/lib/work-order/index.d.ts.map +1 -0
- package/dist/lib/work-order/index.js +66 -0
- package/dist/lib/work-order/index.js.map +1 -0
- package/dist/middleware/api/audit.d.ts +9 -0
- package/dist/middleware/api/audit.d.ts.map +1 -0
- package/dist/middleware/api/audit.js +38 -0
- package/dist/middleware/api/audit.js.map +1 -0
- package/dist/middleware/api/auth.d.ts +29 -0
- package/dist/middleware/api/auth.d.ts.map +1 -0
- package/dist/middleware/api/auth.js +105 -0
- package/dist/middleware/api/auth.js.map +1 -0
- package/dist/middleware/api/console.d.ts +92 -0
- package/dist/middleware/api/console.d.ts.map +1 -0
- package/dist/middleware/api/console.js +446 -0
- package/dist/middleware/api/console.js.map +1 -0
- package/dist/middleware/api/errors.d.ts +48 -0
- package/dist/middleware/api/errors.d.ts.map +1 -0
- package/dist/middleware/api/errors.js +133 -0
- package/dist/middleware/api/errors.js.map +1 -0
- package/dist/middleware/api/idempotency.d.ts +23 -0
- package/dist/middleware/api/idempotency.d.ts.map +1 -0
- package/dist/middleware/api/idempotency.js +158 -0
- package/dist/middleware/api/idempotency.js.map +1 -0
- package/dist/middleware/api/keys.d.ts +14 -0
- package/dist/middleware/api/keys.d.ts.map +1 -0
- package/dist/middleware/api/keys.js +66 -0
- package/dist/middleware/api/keys.js.map +1 -0
- package/dist/middleware/api/membership.d.ts +32 -0
- package/dist/middleware/api/membership.d.ts.map +1 -0
- package/dist/middleware/api/membership.js +48 -0
- package/dist/middleware/api/membership.js.map +1 -0
- package/dist/middleware/api/oauth.d.ts +28 -0
- package/dist/middleware/api/oauth.d.ts.map +1 -0
- package/dist/middleware/api/oauth.js +109 -0
- package/dist/middleware/api/oauth.js.map +1 -0
- package/dist/middleware/api/rate-limit.d.ts +23 -0
- package/dist/middleware/api/rate-limit.d.ts.map +1 -0
- package/dist/middleware/api/rate-limit.js +70 -0
- package/dist/middleware/api/rate-limit.js.map +1 -0
- package/dist/middleware/api/types.d.ts +92 -0
- package/dist/middleware/api/types.d.ts.map +1 -0
- package/dist/middleware/api/types.js +7 -0
- package/dist/middleware/api/types.js.map +1 -0
- package/dist/middleware/api/wait.d.ts +21 -0
- package/dist/middleware/api/wait.d.ts.map +1 -0
- package/dist/middleware/api/wait.js +85 -0
- package/dist/middleware/api/wait.js.map +1 -0
- package/dist/middleware/capability.d.ts +32 -0
- package/dist/middleware/capability.d.ts.map +1 -0
- package/dist/middleware/capability.js +113 -0
- package/dist/middleware/capability.js.map +1 -0
- package/dist/middleware/database.d.ts +14 -0
- package/dist/middleware/database.d.ts.map +1 -0
- package/dist/middleware/database.js +18 -0
- package/dist/middleware/database.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +13 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +127 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/observability.d.ts +32 -0
- package/dist/middleware/observability.d.ts.map +1 -0
- package/dist/middleware/observability.js +134 -0
- package/dist/middleware/observability.js.map +1 -0
- package/dist/middleware/rate-limit.d.ts +16 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/middleware/rate-limit.js +56 -0
- package/dist/middleware/rate-limit.js.map +1 -0
- package/dist/middleware/ucan-auth.d.ts +39 -0
- package/dist/middleware/ucan-auth.d.ts.map +1 -0
- package/dist/middleware/ucan-auth.js +278 -0
- package/dist/middleware/ucan-auth.js.map +1 -0
- package/dist/networks.d.ts +34 -0
- package/dist/networks.d.ts.map +1 -0
- package/dist/networks.js +49 -0
- package/dist/networks.js.map +1 -0
- package/dist/oracle.d.ts +193 -0
- package/dist/oracle.d.ts.map +1 -0
- package/dist/oracle.js +245 -0
- package/dist/oracle.js.map +1 -0
- package/dist/routes/health.d.ts +16 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +29 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/v1/admin.d.ts +18 -0
- package/dist/routes/v1/admin.d.ts.map +1 -0
- package/dist/routes/v1/admin.js +101 -0
- package/dist/routes/v1/admin.js.map +1 -0
- package/dist/routes/v1/api/admin-claims.d.ts +14 -0
- package/dist/routes/v1/api/admin-claims.d.ts.map +1 -0
- package/dist/routes/v1/api/admin-claims.js +82 -0
- package/dist/routes/v1/api/admin-claims.js.map +1 -0
- package/dist/routes/v1/api/admin-facilitator.d.ts +20 -0
- package/dist/routes/v1/api/admin-facilitator.d.ts.map +1 -0
- package/dist/routes/v1/api/admin-facilitator.js +64 -0
- package/dist/routes/v1/api/admin-facilitator.js.map +1 -0
- package/dist/routes/v1/api/admin.d.ts +9 -0
- package/dist/routes/v1/api/admin.d.ts.map +1 -0
- package/dist/routes/v1/api/admin.js +110 -0
- package/dist/routes/v1/api/admin.js.map +1 -0
- package/dist/routes/v1/api/audit.d.ts +8 -0
- package/dist/routes/v1/api/audit.d.ts.map +1 -0
- package/dist/routes/v1/api/audit.js +56 -0
- package/dist/routes/v1/api/audit.js.map +1 -0
- package/dist/routes/v1/api/batch.d.ts +17 -0
- package/dist/routes/v1/api/batch.d.ts.map +1 -0
- package/dist/routes/v1/api/batch.js +192 -0
- package/dist/routes/v1/api/batch.js.map +1 -0
- package/dist/routes/v1/api/claims-read.d.ts +43 -0
- package/dist/routes/v1/api/claims-read.d.ts.map +1 -0
- package/dist/routes/v1/api/claims-read.js +123 -0
- package/dist/routes/v1/api/claims-read.js.map +1 -0
- package/dist/routes/v1/api/claims.d.ts +131 -0
- package/dist/routes/v1/api/claims.d.ts.map +1 -0
- package/dist/routes/v1/api/claims.js +407 -0
- package/dist/routes/v1/api/claims.js.map +1 -0
- package/dist/routes/v1/api/collections-link.d.ts +14 -0
- package/dist/routes/v1/api/collections-link.d.ts.map +1 -0
- package/dist/routes/v1/api/collections-link.js +97 -0
- package/dist/routes/v1/api/collections-link.js.map +1 -0
- package/dist/routes/v1/api/export.d.ts +48 -0
- package/dist/routes/v1/api/export.d.ts.map +1 -0
- package/dist/routes/v1/api/export.js +251 -0
- package/dist/routes/v1/api/export.js.map +1 -0
- package/dist/routes/v1/api/index.d.ts +16 -0
- package/dist/routes/v1/api/index.d.ts.map +1 -0
- package/dist/routes/v1/api/index.js +68 -0
- package/dist/routes/v1/api/index.js.map +1 -0
- package/dist/routes/v1/api/keys.d.ts +9 -0
- package/dist/routes/v1/api/keys.d.ts.map +1 -0
- package/dist/routes/v1/api/keys.js +95 -0
- package/dist/routes/v1/api/keys.js.map +1 -0
- package/dist/routes/v1/api/me.d.ts +21 -0
- package/dist/routes/v1/api/me.d.ts.map +1 -0
- package/dist/routes/v1/api/me.js +122 -0
- package/dist/routes/v1/api/me.js.map +1 -0
- package/dist/routes/v1/api/members.d.ts +17 -0
- package/dist/routes/v1/api/members.d.ts.map +1 -0
- package/dist/routes/v1/api/members.js +406 -0
- package/dist/routes/v1/api/members.js.map +1 -0
- package/dist/routes/v1/api/meta.d.ts +12 -0
- package/dist/routes/v1/api/meta.d.ts.map +1 -0
- package/dist/routes/v1/api/meta.js +59 -0
- package/dist/routes/v1/api/meta.js.map +1 -0
- package/dist/routes/v1/api/openapi.gen.d.ts +2 -0
- package/dist/routes/v1/api/openapi.gen.d.ts.map +1 -0
- package/dist/routes/v1/api/openapi.gen.js +7 -0
- package/dist/routes/v1/api/openapi.gen.js.map +1 -0
- package/dist/routes/v1/api/protocols.d.ts +22 -0
- package/dist/routes/v1/api/protocols.d.ts.map +1 -0
- package/dist/routes/v1/api/protocols.js +153 -0
- package/dist/routes/v1/api/protocols.js.map +1 -0
- package/dist/routes/v1/api/review-billing.d.ts +18 -0
- package/dist/routes/v1/api/review-billing.d.ts.map +1 -0
- package/dist/routes/v1/api/review-billing.js +196 -0
- package/dist/routes/v1/api/review-billing.js.map +1 -0
- package/dist/routes/v1/api/review.d.ts +30 -0
- package/dist/routes/v1/api/review.d.ts.map +1 -0
- package/dist/routes/v1/api/review.js +302 -0
- package/dist/routes/v1/api/review.js.map +1 -0
- package/dist/routes/v1/api/sources.d.ts +10 -0
- package/dist/routes/v1/api/sources.d.ts.map +1 -0
- package/dist/routes/v1/api/sources.js +293 -0
- package/dist/routes/v1/api/sources.js.map +1 -0
- package/dist/routes/v1/api/uploads.d.ts +37 -0
- package/dist/routes/v1/api/uploads.d.ts.map +1 -0
- package/dist/routes/v1/api/uploads.js +414 -0
- package/dist/routes/v1/api/uploads.js.map +1 -0
- package/dist/routes/v1/api/views.d.ts +18 -0
- package/dist/routes/v1/api/views.d.ts.map +1 -0
- package/dist/routes/v1/api/views.js +95 -0
- package/dist/routes/v1/api/views.js.map +1 -0
- package/dist/routes/v1/api/webhooks.d.ts +14 -0
- package/dist/routes/v1/api/webhooks.d.ts.map +1 -0
- package/dist/routes/v1/api/webhooks.js +282 -0
- package/dist/routes/v1/api/webhooks.js.map +1 -0
- package/dist/routes/v1/api/work-orders.d.ts +45 -0
- package/dist/routes/v1/api/work-orders.d.ts.map +1 -0
- package/dist/routes/v1/api/work-orders.js +171 -0
- package/dist/routes/v1/api/work-orders.js.map +1 -0
- package/dist/routes/v1/claims.d.ts +21 -0
- package/dist/routes/v1/claims.d.ts.map +1 -0
- package/dist/routes/v1/claims.js +153 -0
- package/dist/routes/v1/claims.js.map +1 -0
- package/dist/routes/v1/collections.d.ts +45 -0
- package/dist/routes/v1/collections.d.ts.map +1 -0
- package/dist/routes/v1/collections.js +201 -0
- package/dist/routes/v1/collections.js.map +1 -0
- package/dist/routes/v1/index.d.ts +13 -0
- package/dist/routes/v1/index.d.ts.map +1 -0
- package/dist/routes/v1/index.js +41 -0
- package/dist/routes/v1/index.js.map +1 -0
- package/dist/routes/v1/info.d.ts +14 -0
- package/dist/routes/v1/info.d.ts.map +1 -0
- package/dist/routes/v1/info.js +75 -0
- package/dist/routes/v1/info.js.map +1 -0
- package/dist/routes/v1/mcp/index.d.ts +33 -0
- package/dist/routes/v1/mcp/index.d.ts.map +1 -0
- package/dist/routes/v1/mcp/index.js +252 -0
- package/dist/routes/v1/mcp/index.js.map +1 -0
- package/dist/routes/v1/mcp/oauth.d.ts +27 -0
- package/dist/routes/v1/mcp/oauth.d.ts.map +1 -0
- package/dist/routes/v1/mcp/oauth.js +500 -0
- package/dist/routes/v1/mcp/oauth.js.map +1 -0
- package/dist/routes/v1/mcp/tools.d.ts +71 -0
- package/dist/routes/v1/mcp/tools.d.ts.map +1 -0
- package/dist/routes/v1/mcp/tools.js +432 -0
- package/dist/routes/v1/mcp/tools.js.map +1 -0
- package/dist/routes/v1/mcp/ui.d.ts +45 -0
- package/dist/routes/v1/mcp/ui.d.ts.map +1 -0
- package/dist/routes/v1/mcp/ui.js +454 -0
- package/dist/routes/v1/mcp/ui.js.map +1 -0
- package/dist/routes/v1/oauth.d.ts +29 -0
- package/dist/routes/v1/oauth.d.ts.map +1 -0
- package/dist/routes/v1/oauth.js +256 -0
- package/dist/routes/v1/oauth.js.map +1 -0
- package/dist/routes/v1/reviewers.d.ts +22 -0
- package/dist/routes/v1/reviewers.d.ts.map +1 -0
- package/dist/routes/v1/reviewers.js +165 -0
- package/dist/routes/v1/reviewers.js.map +1 -0
- package/dist/routes/v1/reviews.d.ts +38 -0
- package/dist/routes/v1/reviews.d.ts.map +1 -0
- package/dist/routes/v1/reviews.js +234 -0
- package/dist/routes/v1/reviews.js.map +1 -0
- package/dist/routes/v1/shared.d.ts +586 -0
- package/dist/routes/v1/shared.d.ts.map +1 -0
- package/dist/routes/v1/shared.js +229 -0
- package/dist/routes/v1/shared.js.map +1 -0
- package/dist/routes/well-known.d.ts +15 -0
- package/dist/routes/well-known.d.ts.map +1 -0
- package/dist/routes/well-known.js +35 -0
- package/dist/routes/well-known.js.map +1 -0
- package/dist/runtime.d.ts +100 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +144 -0
- package/dist/runtime.js.map +1 -0
- package/dist/testing/fake-tools.d.ts +104 -0
- package/dist/testing/fake-tools.d.ts.map +1 -0
- package/dist/testing/fake-tools.js +213 -0
- package/dist/testing/fake-tools.js.map +1 -0
- package/dist/testing/index.d.ts +54 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +104 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/samples.d.ts +70 -0
- package/dist/testing/samples.d.ts.map +1 -0
- package/dist/testing/samples.js +92 -0
- package/dist/testing/samples.js.map +1 -0
- package/dist/workflows/claim-ingest.d.ts +250 -0
- package/dist/workflows/claim-ingest.d.ts.map +1 -0
- package/dist/workflows/claim-ingest.js +1020 -0
- package/dist/workflows/claim-ingest.js.map +1 -0
- package/dist/workflows/claim-source.d.ts +25 -0
- package/dist/workflows/claim-source.d.ts.map +1 -0
- package/dist/workflows/claim-source.js +107 -0
- package/dist/workflows/claim-source.js.map +1 -0
- package/dist/workflows/review-complete.d.ts +82 -0
- package/dist/workflows/review-complete.d.ts.map +1 -0
- package/dist/workflows/review-complete.js +455 -0
- package/dist/workflows/review-complete.js.map +1 -0
- package/dist/workflows/steps.d.ts +125 -0
- package/dist/workflows/steps.d.ts.map +1 -0
- package/dist/workflows/steps.js +325 -0
- package/dist/workflows/steps.js.map +1 -0
- package/docs/api.md +2595 -0
- package/docs/guide.md +1525 -0
- package/migrations/0000_init.sql +108 -0
- package/migrations/0001_review_attestation.sql +1 -0
- package/migrations/0002_decisions_facts.sql +1 -0
- package/migrations/0003_api_lane.sql +171 -0
- package/migrations/0004_api_v1_1.sql +19 -0
- package/migrations/0005_member_notify.sql +3 -0
- package/migrations/0006_source_collections.sql +16 -0
- package/migrations/0007_api_settlement.sql +27 -0
- package/migrations/0008_review_extend.sql +1 -0
- package/migrations/0009_card_refunds.sql +4 -0
- package/migrations/0010_even_scorpion.sql +16 -0
- package/migrations/0011_mcp_oauth.sql +27 -0
- package/migrations/0012_mcp_standing.sql +21 -0
- package/migrations/0013_unknown_krista_starr.sql +2 -0
- package/migrations/0014_last_carnage.sql +14 -0
- package/migrations/0015_company_authority.sql +26 -0
- package/migrations/0016_owner_only_links.sql +6 -0
- package/migrations/0017_organisations.sql +35 -0
- package/migrations/0018_remove_app_signed.sql +4 -0
- package/migrations/0019_submitted_via.sql +2 -0
- package/migrations/0020_drop_decision_to_pay.sql +4 -0
- package/migrations/0021_manual_erase.sql +25 -0
- package/migrations/0022_errored_facts.sql +1 -0
- package/migrations/0023_review_billing.sql +20 -0
- package/migrations/0024_pending_task_details.sql +1 -0
- package/migrations/meta/0000_snapshot.json +707 -0
- package/migrations/meta/0001_snapshot.json +714 -0
- package/migrations/meta/0002_snapshot.json +721 -0
- package/migrations/meta/0003_snapshot.json +1845 -0
- package/migrations/meta/0004_snapshot.json +1969 -0
- package/migrations/meta/0005_snapshot.json +1991 -0
- package/migrations/meta/0006_snapshot.json +2097 -0
- package/migrations/meta/0007_snapshot.json +2128 -0
- package/migrations/meta/0008_snapshot.json +2133 -0
- package/migrations/meta/0009_snapshot.json +2161 -0
- package/migrations/meta/0010_snapshot.json +2265 -0
- package/migrations/meta/0011_snapshot.json +2448 -0
- package/migrations/meta/0012_snapshot.json +2577 -0
- package/migrations/meta/0013_snapshot.json +2594 -0
- package/migrations/meta/0014_snapshot.json +2602 -0
- package/migrations/meta/0015_snapshot.json +2778 -0
- package/migrations/meta/0016_snapshot.json +2778 -0
- package/migrations/meta/0017_snapshot.json +2792 -0
- package/migrations/meta/0018_snapshot.json +2778 -0
- package/migrations/meta/0019_snapshot.json +2792 -0
- package/migrations/meta/0020_snapshot.json +2785 -0
- package/migrations/meta/0021_snapshot.json +2762 -0
- package/migrations/meta/0022_snapshot.json +2769 -0
- package/migrations/meta/0023_snapshot.json +2900 -0
- package/migrations/meta/0024_snapshot.json +2907 -0
- package/migrations/meta/_journal.json +181 -0
- package/openapi/decision-api.yaml +12204 -0
- package/package.json +121 -4
- package/skills/README.md +19 -0
- package/skills/add-openhands-support/SKILL.md +113 -0
- package/skills/create-collection/SKILL.md +199 -0
- package/skills/create-collection/reference/claims-stack.md +106 -0
- package/skills/create-collection/reference/collection.md +111 -0
- package/skills/create-oracle/SKILL.md +400 -0
- package/skills/create-rubric/SKILL.md +130 -0
- package/skills/create-rubric/reference/format-cheatsheet.md +186 -0
- package/skills/create-rubric/templates/claim-form.example.json +60 -0
- package/skills/decision-api-client/SKILL.md +236 -0
- package/skills/decision-api-oracle/SKILL.md +336 -0
- package/skills/publish-protocol/SKILL.md +151 -0
- package/skills/publish-protocol/reference/publishing.md +116 -0
- package/skills/reference/engine-contract.md +222 -0
- package/skills/write-facts/SKILL.md +221 -0
- package/skills/write-facts/reference/examples/http-vendor.ts +88 -0
- package/skills/write-facts/reference/examples/price-matches-agreement.ts +67 -0
- package/skills/write-facts/reference/facts-api.md +232 -0
- package/templates/project/.dev.vars.example +93 -0
- package/templates/project/.editorconfig +9 -0
- package/templates/project/CLAUDE.md +45 -0
- package/templates/project/README.md +199 -0
- package/templates/project/biome.json +37 -0
- package/templates/project/gitignore +29 -0
- package/templates/project/package.json +32 -0
- package/templates/project/src/facts/index.ts +19 -0
- package/templates/project/src/index.ts +25 -0
- package/templates/project/test/api-lane.test.ts +57 -0
- package/templates/project/test/facts.test.ts +14 -0
- package/templates/project/tsconfig.json +25 -0
- package/templates/project/vitest.config.ts +13 -0
- package/templates/project/wrangler.jsonc +446 -0
package/docs/guide.md
ADDED
|
@@ -0,0 +1,1525 @@
|
|
|
1
|
+
# Building an evaluation oracle
|
|
2
|
+
|
|
3
|
+
A walk from an empty directory to an oracle that is hired, deciding, and signing on chain — and then
|
|
4
|
+
to running it. Every signature, default and cap this guide leans on is spelled out in
|
|
5
|
+
[`api.md`](./api.md); keep it open beside this.
|
|
6
|
+
|
|
7
|
+
**The one sentence that explains the whole shape:** the rubric asks for named facts, this oracle
|
|
8
|
+
produces them, and the decision engine decides. Your oracle never judges anything.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. The concepts, in the order you meet them
|
|
13
|
+
|
|
14
|
+
| Term | What it is |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| **Protocol** | An on-chain entity (`did:ixo:entity:…`) that *is* a claim type. It anchors two documents as linked resources: the claim form and the rubric |
|
|
17
|
+
| **Claim form** (`#vct`) | The SurveyJS template a submitter fills in. Its answers are the claim's `credentialSubject` — what you see as `claim.body` / `answers` |
|
|
18
|
+
| **Rubric** (`#rub`) | The commissioner's rule document: gates, scored criteria, the external sources and AI checks it needs, a duplicate rule, a submission-rate rule, human-review tasks. **You never read it.** Its id is the sha256 of its anchored bytes |
|
|
19
|
+
| **Manifest** (the work order) | What the engine's binder resolved that rubric to *want*, republished at `GET /v1/rubrics/manifest?protocol=<did>`: the `ext.<name>` and `ai.<name>` facts to produce, with each one's type and fallback, plus the fields, ctx tokens and derived values the rules read. This is the only work order there is |
|
|
20
|
+
| **Deed** | The domain entity people submit claims against. A collection hangs off a deed, never off the protocol |
|
|
21
|
+
| **Collection** | "Claims of protocol X against deed Y." It carries the on-chain `Payments.evaluation` — the coin that pays you — and its **admin account** pays the evaluation leg on every decision |
|
|
22
|
+
| **Grant** | An on-chain `EvaluateClaimAuthorization` naming your `ORACLE_ADDRESS`. Without it the chain refuses your `MsgEvaluateClaim`, so the register handshake insists on it |
|
|
23
|
+
| **The engine** | The IXO decision engine. It binds the rubric to the live form, decides from your facts, mints a signed receipt, and bills the collection owner's credits |
|
|
24
|
+
| **Credits** | The owner's IXO subscription balance, which the engine charges per decision. It finds that subscription through the delegation you post at register |
|
|
25
|
+
| **Fact** | One named answer: a boolean, an integer score in basis points, or an explicit "could not establish". The unit of work an oracle does |
|
|
26
|
+
|
|
27
|
+
### How a claim flows
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
cron tick → discover new claims on Blocksync → one ClaimIngest workflow per claim
|
|
31
|
+
pull the claim body from the first store that has it (VFS claims lane, VFS public copy,
|
|
32
|
+
Matrix claim bot, IPFS, Cellnode — all but the bot CID-verified) + attachment descriptors
|
|
33
|
+
facts read the manifest → resolve ctx (ONE clock read) → run your handlers → validate → FREEZE
|
|
34
|
+
decide POST the FactSet + the claim's content to the engine
|
|
35
|
+
→ { decision, resultDigest, receipt: { cid, jws } }
|
|
36
|
+
act on a terminal verdict, sign MsgEvaluateClaim with verification_proof = receipt.cid
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
After the freeze there is no network, no clock and no AI anywhere downstream. That is what makes a
|
|
40
|
+
decision replayable — and what lets a human review fold into the *same* decision instead of starting
|
|
41
|
+
a second one.
|
|
42
|
+
|
|
43
|
+
**The content binding.** Every decide and every fold also carries `claimContent`, so the engine can
|
|
44
|
+
check that the answers you froze are the claim's own (a hired oracle cannot change a company's
|
|
45
|
+
decision). For a chain claim that is its CID plus the EXACT bytes a CID-verified store served — the
|
|
46
|
+
bot's JSON comes back re-serialised, so a bot-only claim (or a document over 256 KiB) goes without
|
|
47
|
+
bytes; on a payouts protocol the pull asks the CID-verified stores behind the bot before it settles
|
|
48
|
+
for the bot. For an API claim it is the content you were sent, stored at accept. The engine
|
|
49
|
+
refusing it (`409 CONTENT_MISMATCH`, or `413 PAYLOAD_TOO_LARGE` — also said by the oracle itself
|
|
50
|
+
before a body over 6 MiB is sent) records a `content_mismatch` / `payload_too_large` decision row,
|
|
51
|
+
fails the claim and pages the operator (`engine.content-mismatch` / `engine.payload-too-large`); on
|
|
52
|
+
a fold it lands on the review's `foldError`. An API upload attachment names the `cid` its upload
|
|
53
|
+
returned, and accept refuses one that is not that upload's.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 2. The project after `init`
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
bunx @ixo/decision-oracle init my-oracle --name my-oracle
|
|
61
|
+
cd my-oracle && bun install
|
|
62
|
+
bun run cf-typegen
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
| File | Yours? | What it holds |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| `src/index.ts` | **yours** | One `defineOracle({...})` call — what the oracle *is* — plus the three mandatory re-exports |
|
|
68
|
+
| `src/facts/index.ts` | **yours** | `defineFacts({ ext, ai, groups?, unique?, frequency? })` — the handler table. This is the work |
|
|
69
|
+
| `src/facts/<name>.ts` | **yours** | One handler per fact name the stock lanes cannot answer |
|
|
70
|
+
| `test/facts.test.ts`, `test/facts/*.test.ts` | **yours** | Plain vitest in node, with `@ixo/decision-oracle/testing` |
|
|
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
|
+
| `.dev.vars` | **yours, never committed** | Local secrets. `.dev.vars.example` lists every one with what it does |
|
|
73
|
+
| `tsconfig.json`, `biome.json`, `vitest.config.ts`, `package.json` | generated | Scripts: `dev`, `deploy`, `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` |
|
|
75
|
+
| `CLAUDE.md` | shipped | The two rules, and where the package docs live |
|
|
76
|
+
|
|
77
|
+
Everything else — pulling claims, the engine handshake, the D1 ledgers, review folds, the chain
|
|
78
|
+
write, billing pauses, Matrix and email notices — is the package. See
|
|
79
|
+
[api.md §12](./api.md#12-what-the-package-does-at-runtime) before you build any of it yourself.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 3. Identities — two keys, two jobs
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
bunx decision-oracle derive --new # run this TWICE
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Run one** gives you the `did:web` key. Take the mnemonic line and paste it into `.dev.vars`
|
|
90
|
+
**relabelled** as `SERVICE_ED25519_MNEMONIC=` — `derive` always prints it as
|
|
91
|
+
`ORACLE_SECP_MNEMONIC=`, so a verbatim paste silently sets the wrong key. This one signs every UCAN
|
|
92
|
+
invocation the oracle mints (to the engine, to external sources, to the claim bot, to the UCAN
|
|
93
|
+
Store, to the VFS) and backs `/.well-known/did.json`. Ignore the `ORACLE_*` lines this run prints.
|
|
94
|
+
|
|
95
|
+
**Run two** gives you the chain account key. Its mnemonic is `ORACLE_SECP_MNEMONIC` (a secret), and
|
|
96
|
+
the same run prints `ORACLE_ADDRESS`, `ORACLE_DID` and `ORACLE_MATRIX_USER_ID` — those three are
|
|
97
|
+
**public config**, so they go in `wrangler.jsonc` `vars`. This key signs `MsgEvaluateClaim`, holds
|
|
98
|
+
the on-chain grant, and is the audience of the owner's claim-bot delegation. `ORACLE_ADDRESS` is
|
|
99
|
+
asserted against the mnemonic before the first signature: a hand-typed one that differs fails loud,
|
|
100
|
+
never signs as someone else.
|
|
101
|
+
|
|
102
|
+
Also set, in `wrangler.jsonc` `vars`: `IXO_NETWORK`, and `ORACLE_HOSTNAME` — the host your
|
|
103
|
+
`did:web` is served from (`localhost:8787` while developing). **It must equal the Custom Domain in
|
|
104
|
+
`routes` for any environment you deploy**, because callers resolve you by fetching
|
|
105
|
+
`https://<host>/.well-known/did.json`. And in `.dev.vars`: `UNIQUE_HMAC_KEY`, any long random
|
|
106
|
+
string, if the rubric you serve declares a `unique` rule.
|
|
107
|
+
|
|
108
|
+
Both of these keys are, at this point, only strings in your files. `decision-oracle oracle init` (§10)
|
|
109
|
+
is what makes them real on chain: it funds the wallet, creates the IID document for
|
|
110
|
+
`did:ixo:<ORACLE_ADDRESS>` and puts the `SERVICE_ED25519_MNEMONIC` public key on it as a
|
|
111
|
+
verification method — without which every invocation this oracle mints as its `did:ixo` (to the
|
|
112
|
+
claim bot, to the UCAN Store, to the VFS claims lane) is refused. The same command creates the
|
|
113
|
+
oracle's Matrix account and writes `ORACLE_MATRIX_ACCESS_TOKEN` into `.dev.vars` for you; it is not
|
|
114
|
+
a value you obtain yourself.
|
|
115
|
+
|
|
116
|
+
There is a third DID later: `ORACLE_ENTITY_DID`, the on-chain oracle *entity* from that same command.
|
|
117
|
+
Different thing, different owner (your AuthHub session), and the portal resolves your API from it.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 4. Read the work order
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
bunx decision-oracle protocol review <protocol did> [--network devnet|testnet|mainnet] [--json]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
It is a public read — no keys, no project, no identity needed. It prints:
|
|
128
|
+
|
|
129
|
+
- the claim-form **fields** the rules read, with the live form's kinds;
|
|
130
|
+
- the **ctx tokens** the rules name, and the **`~derived` values the engine computes** (with their
|
|
131
|
+
expressions);
|
|
132
|
+
- the **human-review tasks** a `pending_review` decision could name;
|
|
133
|
+
- then, per declared name: `ext.<name> stock` or `ext.<name> handler needed (<why>)`, with the
|
|
134
|
+
endpoint, audience, `expect`, `onError` and `send` map (or, for an AI check, the instructions);
|
|
135
|
+
- the `unique` / `frequency` rules and the attachment size limits — all stock, no handler;
|
|
136
|
+
- and a closing verdict naming exactly which names need a handler.
|
|
137
|
+
|
|
138
|
+
**If it comes back `unusable`, stop.** The engine could not bind that rubric to its claim form, so
|
|
139
|
+
there is nothing to answer yet — the rubric or its `#vct`/`#rub` anchoring has to be fixed first
|
|
140
|
+
(the `create-rubric` and `publish-protocol` skills). Exit code 2.
|
|
141
|
+
|
|
142
|
+
Now fill in `src/index.ts` from what you read — `supportedProtocols: ["<protocol did>"]`,
|
|
143
|
+
`pricing.evaluation` (the denom and minimum the collection's on-chain payment must carry), and the
|
|
144
|
+
three policies. Serving more than one protocol at different fees? `pricing.byProtocol` overrides
|
|
145
|
+
`pricing.evaluation` per protocol DID — `minAmount: "0"` accepts an empty `Payments.evaluation` leg
|
|
146
|
+
for a protocol whose collections aren't paying this oracle through that split (`docs/api.md`
|
|
147
|
+
§`OracleConfig`). Running the Decision API lane? Its price is a different thing — US dollars per
|
|
148
|
+
decision, in micro-USD: `pricing.api: { amountMicroUsd: 1_200_000, byProtocol?: { … } }`, served
|
|
149
|
+
publicly at `GET /v1/api/protocols/:did` and checked by the facilitator against every offer; a
|
|
150
|
+
protocol with no price answers `price: null` and is never offered. **Card needs at least 50¢ per
|
|
151
|
+
decision** (Stripe's minimum capture; the facilitator checks every claim on its own, so a batch
|
|
152
|
+
doesn't help): below that only PAY is offered. To change the price, edit `pricing.api` in
|
|
153
|
+
`src/oracle.ts` (or, with no `pricing.api` in code, the `API_PRICE_USD_CENTS` var) and redeploy.
|
|
154
|
+
If your rubric has a `frequency` rule, every Decision API claim needs a `submitter` (the person it
|
|
155
|
+
is about) or it is refused before any payment. The oracle never fills one: it is the caller's
|
|
156
|
+
`submitter`, else the answer the source's **organisation link** names (`payeeField`, e.g.
|
|
157
|
+
`courierId`) — an admin of the organisation sets that in the Decision Console, and the console's
|
|
158
|
+
sync mirrors it onto the source (`docs/api.md` "A source's organisation"). An AI app signed in as a
|
|
159
|
+
person may only name that person.
|
|
160
|
+
New offers use it at once; the facilitator's price cache catches up within about 5 minutes, and an
|
|
161
|
+
offer refused `price_mismatch` in that window is simply retried. The Decision Console shows the
|
|
162
|
+
price read-only, with whether card can be offered. Set `allowAiChecks` true iff anything asks for a model (an `ai` check, or a
|
|
163
|
+
`unique` rule with its judge on); `allowImageChecks` true iff a check reads a photo and you want
|
|
164
|
+
Content Credentials screened; `autoSign` true only when unattended chain writes are deliberate.
|
|
165
|
+
`allowStandingApprovals` (say it either way; IXO's own oracles turn it on) lets a signed-in AI app's auto-approve limits pay without
|
|
166
|
+
the user approving each claim. Off, the oracle refuses to store one (`403 standing_approvals_off`)
|
|
167
|
+
and never spends one it already holds — the hourly housekeeping sweep deletes any it stored while the
|
|
168
|
+
policy was on: every hosted-MCP submit waits for the user's approval of that claim.
|
|
169
|
+
|
|
170
|
+
**Per-review billing** (no setting of yours: IXO switches it on in your oracle's plan — the
|
|
171
|
+
engine's `GET /v1/oracles/me` `reviewBilling` — and the facilitator refuses a chain-lane
|
|
172
|
+
reservation otherwise) bills chain reviews per review instead of an escrowed evaluation fee: an
|
|
173
|
+
organisation's owner signs ONE standing x402 payment delegation for your oracle in the Decision
|
|
174
|
+
Console (or with `decision-oracle api review-billing enable --source <id>`, signed as the owner's login),
|
|
175
|
+
stored on the organisation's Decision API source (`PUT /v1/api/source/review-billing`),
|
|
176
|
+
and each claim of its collections then reserves your published `pricing.api` price before it is
|
|
177
|
+
decided and settles on the engine's receipt, from the owner's subscription credits. It needs
|
|
178
|
+
`API_LANE` and `API_PAYMENTS` on and a facilitator speaking x402-ixo-pay 0.7.0 (chain claim ids);
|
|
179
|
+
`GET /v1/info` tells posters which model you run (`reviewBilling.mode`: `x402` or `escrow`). The
|
|
180
|
+
delegation, like every verified payment header, is kept sealed under `WEBHOOK_ENCRYPTION_KEY`; its
|
|
181
|
+
payer is emailed 30 and 7 days before it ends, and once each when it has spent 80% and 95% of its
|
|
182
|
+
cap (a new delegation — a raised `--cap` — starts its own pair); both emails say how to raise it:
|
|
183
|
+
ask Claude Code to run `decision-oracle api review-billing enable --source <id> --cap <PAY>`, the OpenHands web app (as the organisation's owner), or the
|
|
184
|
+
Console's Pay for reviews tab (`CONSOLE_URL/billing/reviews`).
|
|
185
|
+
|
|
186
|
+
Where that money lands is yours to say, in `wrangler.jsonc` vars: `EARNINGS_ADDRESS` (your `ixo1…`
|
|
187
|
+
account, for PAY and USDC on IXO) and `EARNINGS_BASE_WALLET` (your `0x…` wallet, for USDC on Base).
|
|
188
|
+
Every offer names them as its `payTo`; IXO takes its share by your plan and never chooses your
|
|
189
|
+
payee. A way to pay whose address is unset is not offered — with no `EARNINGS_ADDRESS`, no PAY.
|
|
190
|
+
Which ways your offers carry is IXO's plan for your oracle (the engine's `GET /v1/oracles/me`
|
|
191
|
+
`accepts`, read every 5 minutes): PAY is for IXO-run oracles only, so an oracle whose plan takes
|
|
192
|
+
card and/or USDC on Base sends no PAY terms and needs no `EARNINGS_ADDRESS`; the facilitator lists
|
|
193
|
+
the card and USDC (`exact`) entries itself. A plan the oracle cannot read offers nothing (`503
|
|
194
|
+
payments-disabled`). `GET /v1/info` lists them as `earnings`. ("Payouts" below is only your company paying its people.)
|
|
195
|
+
|
|
196
|
+
### Payouts (S16)
|
|
197
|
+
|
|
198
|
+
If a protocol's claims carry an amount someone should be paid — a supplier invoice, an expense —
|
|
199
|
+
name where it is:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
defineOracle({
|
|
203
|
+
// …
|
|
204
|
+
payouts: {
|
|
205
|
+
"did:ixo:entity:…": {
|
|
206
|
+
amount: "$.invoice.total", // a decimal string or plain number in the claim's answers
|
|
207
|
+
currency: "EUR", // or { path: "$.invoice.currency" }, or { asset: "eip155:8453/erc20:0x…", decimals: 6 }
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Every claim of the protocol carries the block. Who is paid is the engine's to derive, never yours:
|
|
214
|
+
a chain claim's on-chain agent, an API claim's `submitter`, else the answer the source's company link
|
|
215
|
+
names (`payeeField`). A claim with no payee is still decided, its receipt a plain decision with no
|
|
216
|
+
payment binding (`decisionToPayRefused: "NO_PAYEE"`, logged `payout.refused`). One credential per
|
|
217
|
+
decision: when the engine approves such a claim, its **receipt** is also typed
|
|
218
|
+
**DecisionToPayCredential** and carries the payment binding for that amount and payee. It is the same
|
|
219
|
+
`decision.receipt` the claim record and the `claim.decided` / `claim.folded` webhooks already carry,
|
|
220
|
+
public at the engine's `/v1/receipts/:cid`, and your facilitator pays from the binding on it. A
|
|
221
|
+
claim with an unusable amount still gets one, declined with a reason code, and pays nothing. Use
|
|
222
|
+
**opaque** submitter ids: the public receipt binds the payee by that id, so never a name, email or
|
|
223
|
+
phone number. After erasure `submitter` reads null, but the receipt still binds the payee by that
|
|
224
|
+
opaque id — which is why it must never be personal data.
|
|
225
|
+
|
|
226
|
+
Who may approve, export or edit payout rules is not this oracle's to say: the payouts facilitator
|
|
227
|
+
holds each company's payout team itself (its admins are the controllers of the company entity, `GET
|
|
228
|
+
/v1/payouts/info`), and the Console manages that team there.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## 5. Stock or custom, per name
|
|
233
|
+
|
|
234
|
+
The stock lanes are not a fallback for the lazy — for most rubrics they are the right answer, and
|
|
235
|
+
every name you leave alone is a name that cannot have a bug.
|
|
236
|
+
|
|
237
|
+
| Lane | Stock covers it when… | Write a handler when… |
|
|
238
|
+
| --- | --- | --- |
|
|
239
|
+
| `ext.<name>` | The declared endpoint accepts a self-signed UCAN POST (the resolved `send` map plus the whole claim body under `claimBody`; audience the endpoint's own did:web, capability `ixo:external-source`) and answers the strict `{ valid: boolean }` / `{ score: 0..10000 }` contract. An outage (5xx, 429, timeout) retries the workflow step and is never frozen as a verdict; only a 4xx refusal or a malformed answer becomes an error fact | It is a vendor API with different auth, capability or response shape · it needs your own system, database or secret · its `send` map reads a `~derived` value, or its `audience` is not the endpoint's did:web (the stock lane always answers either `error`) |
|
|
240
|
+
| `ext.<name>` + a geo handler (`withinRadius`, `located`, `beforeAfter` from `@ixo/decision-oracle/facts`) | The rubric asks where or when a photo was taken. Missing GPS or capture time is an error (your `onError`), never `false`; the evidence grade (`camera` / `browser-reported` / `claim`) is in the reason. Send per-photo `capturedAt.{before,after}` from your form for phones that strip EXIF ([api.md](./api.md#geo-evidence-grades-and-capturedat)) | You need a different source ladder or a stricter grade |
|
|
241
|
+
| `ai.<name>` | A plain stock AI judgment against the rubric's own `instructions` is what the author meant. Evidence the model cannot read (too dark, blurred, cropped) is an error (your `onError`), never a failed rule; a before/after pair is judged knowing the work changes the subject | A different model or vision vendor is required · the inputs need re-slicing or a pre-check (a missing photo, an unreadable file) · `send` reads a `~derived` value |
|
|
242
|
+
| `unique` | Always — this oracle's own D1 registries, a keyed body digest, CID and perceptual-hash attachment lanes, a JS trigram near-text lane and the gray-zone same-event judge. Only claims registered earlier count, and a claim that never reached a decision (failed ingest, `invalid_facts`, a content refusal) never blocks a resubmission | Only a wholly different duplicate strategy, e.g. a vendor registry you already run. The `UniqueFact` shape is still fixed |
|
|
243
|
+
| `frequency` | Always — one Blocksync submission count over the claim's own UTC calendar window (a failed read retries the step, never freezes `unresolved`) | Same: only when the count lives somewhere else |
|
|
244
|
+
| `sizeProbes` | Always — the package probes the named roots itself | Never; not a handler's concern |
|
|
245
|
+
| `derived[]` | The **engine** computes these; no fact is ever produced for a `~name` | A `send` map naming one forces a handler on whichever `ext`/`ai` reads it. `manifest.derived[].expression` is there for exactly that |
|
|
246
|
+
|
|
247
|
+
**Upgrading an oracle that ran before the `void` registry token.** Claims that never reached a
|
|
248
|
+
decision (a facts-lane outcome, a content refusal, an `unusable` rubric, a failed ingest) used to
|
|
249
|
+
leave their duplicate-registry rows pending for ever, so an honest resubmission of the same
|
|
250
|
+
evidence read as their duplicate. New ones are voided as they happen. Run this once per oracle,
|
|
251
|
+
after deploying, to void the old ones (idempotent: it only touches rows still pending, and leaves
|
|
252
|
+
claims in flight or waiting for review alone):
|
|
253
|
+
|
|
254
|
+
```sh
|
|
255
|
+
wrangler d1 execute <your DB binding's database> --remote --command "UPDATE claim_body_digests SET final_verdict = 'void' WHERE final_verdict IS NULL AND (EXISTS (SELECT 1 FROM decisions d WHERE d.claim_id = claim_body_digests.claim_id AND d.status NOT IN ('decided', 'pending_review')) OR (NOT EXISTS (SELECT 1 FROM decisions d WHERE d.claim_id = claim_body_digests.claim_id) AND EXISTS (SELECT 1 FROM claim_ingests i WHERE i.claim_id = claim_body_digests.claim_id AND i.status = 'failed')));"
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Two judgment calls are the provider's to make, not yours to guess: *do you control that endpoint
|
|
259
|
+
(can you make it speak the stock contract)*, and *is a stock AI check the judge the author meant*.
|
|
260
|
+
|
|
261
|
+
Then scaffold:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
bunx decision-oracle facts scaffold <protocol did> [--all] [--dir src/facts]
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
One `src/facts/<name>.ts` per name that needs a handler, plus a test beside it; nothing existing is
|
|
268
|
+
overwritten, and you add each export to the table in `src/facts/index.ts` yourself. **Every stub
|
|
269
|
+
returns `fact.error("not implemented: <name>")`** — a visible failure the rubric routes with its own
|
|
270
|
+
`onError`. Leave it that way until the real call works. Never a placeholder `true`.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## 6. Writing the lanes
|
|
275
|
+
|
|
276
|
+
Every handler follows the same three-beat shape: **guard the inputs · make the one call · map only
|
|
277
|
+
what you understand.** Everything else is `fact.error(detail)`.
|
|
278
|
+
|
|
279
|
+
### An HTTP vendor source
|
|
280
|
+
|
|
281
|
+
Read `send` (not the raw body, and not a field the rubric never declared for this source). Call the
|
|
282
|
+
endpoint the rubric declares. Map the answers you recognise. Anything else is unanswered — and a
|
|
283
|
+
vendor outage is `retryLater`, not a verdict.
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
// src/facts/deliveryConfirmed.ts
|
|
287
|
+
import { type ExtHandler, fact } from "@ixo/decision-oracle/facts";
|
|
288
|
+
|
|
289
|
+
interface VendorDelivery {
|
|
290
|
+
status?: unknown;
|
|
291
|
+
signed_by?: unknown;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export const deliveryConfirmed: ExtHandler = async ({ send, expected }, tools) => {
|
|
295
|
+
const reference = send.reference;
|
|
296
|
+
if (typeof reference !== "string" || reference.length === 0) {
|
|
297
|
+
// Nothing to look up IS an answer about the world — a checked `false`, not a failure to check.
|
|
298
|
+
return fact.ok(false, { reason: "the claim carries no delivery reference" });
|
|
299
|
+
}
|
|
300
|
+
const { COURIER_API_KEY } = tools.env as typeof tools.env & { COURIER_API_KEY?: string };
|
|
301
|
+
if (!COURIER_API_KEY) return fact.error("COURIER_API_KEY is not configured on this oracle");
|
|
302
|
+
|
|
303
|
+
const res = await tools.http.json<VendorDelivery>(expected.endpoint, {
|
|
304
|
+
method: "POST",
|
|
305
|
+
headers: { authorization: `Bearer ${COURIER_API_KEY}` },
|
|
306
|
+
body: { reference },
|
|
307
|
+
timeoutMs: expected.timeoutMs ?? 5000,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
if (res.status === null || res.status >= 500) {
|
|
311
|
+
throw fact.retryLater(`courier API unavailable (status ${res.status ?? "none"})`);
|
|
312
|
+
}
|
|
313
|
+
if (res.status === 404) {
|
|
314
|
+
return fact.ok(false, {
|
|
315
|
+
reason: `courier has no delivery for ${reference}`,
|
|
316
|
+
transcript: res.transcript,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
if (!res.ok) return fact.error(`courier answered ${res.status}`, { transcript: res.transcript });
|
|
320
|
+
|
|
321
|
+
const status = res.body?.status;
|
|
322
|
+
if (typeof status !== "string") {
|
|
323
|
+
return fact.error("courier answered a body with no status field", {
|
|
324
|
+
transcript: res.transcript,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
if (status === "delivered") {
|
|
328
|
+
const signedBy = typeof res.body?.signed_by === "string" ? res.body.signed_by : "unknown";
|
|
329
|
+
return fact.ok(true, { reason: `courier recorded delivery, signed by ${signedBy}` });
|
|
330
|
+
}
|
|
331
|
+
if (status === "in_transit" || status === "returned" || status === "lost") {
|
|
332
|
+
return fact.ok(false, { reason: `courier status is ${status}` });
|
|
333
|
+
}
|
|
334
|
+
// A status this handler has never seen is NOT a "no" — it is an unanswered fact.
|
|
335
|
+
return fact.error(`unrecognised courier status: ${status}`, { transcript: res.transcript });
|
|
336
|
+
};
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Note `tools.http.json<T>` with the response type named — without the generic, `res.body` is
|
|
340
|
+
`unknown`. It never throws for an HTTP outcome, retries a 5xx / 408 / 429 or a network error three
|
|
341
|
+
times with backoff under a ~30 s budget (any other 4xx comes back after one call — the 404 above is
|
|
342
|
+
answered once), refuses a non-public host, and hands you a transcript with credentials taken out:
|
|
343
|
+
credential headers dropped (`authorization`, cookies, any name ending in `key` but
|
|
344
|
+
`idempotency-key`, or naming a token/secret/password/credential/signature), credential query values
|
|
345
|
+
masked (`?api_key=[redacted]`), and a response body over 64 KiB recorded as its size and sha256
|
|
346
|
+
(`docs/api.md`, `http.json`). Hand that transcript back on the fact: it is the "why did the engine
|
|
347
|
+
decide this" record.
|
|
348
|
+
|
|
349
|
+
If a source authenticates *you* rather than a bearer key, sign the call the way the stock lane
|
|
350
|
+
would:
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
const res = await tools.http.json(expected.endpoint, { method: "POST", ucan: true, body });
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
That token is from your oracle's did:web to the did:web of the host you call — bound to the URL, so
|
|
357
|
+
it can never be addressed anywhere else — with `{ can: "*", with: "ixo:external-source" }`. If your
|
|
358
|
+
API checks another capability, name it in code —
|
|
359
|
+
`{ ucan: { capability: { can: "shipments/read", with: "did:web:api.example" } } }`. Never
|
|
360
|
+
forward `expected.capability`: that is the rubric's, the protocol owner's words shared by every
|
|
361
|
+
oracle serving it, and what your key grants is yours alone. The stock lane ignores it (with an
|
|
362
|
+
`ext.source.capability-ignored` warning) and calls a source only when its `audience` is the
|
|
363
|
+
endpoint's own did:web; an IXO service capability (`ixo:eval-engine`, `ixo:ixo-pay-facilitator`, …)
|
|
364
|
+
is refused everywhere, as an `error` fact with the rubric's `onError`. The full wire shape — body, headers, token — is in `api.md`, "How an external
|
|
365
|
+
source is called".
|
|
366
|
+
|
|
367
|
+
A `score` source returns **integer basis points**, never a float, never a percentage — the engine
|
|
368
|
+
rejects a float and never clamps:
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
export const onTimeRate: ExtHandler = async ({ expected }, tools) => {
|
|
372
|
+
const res = await tools.http.json<{ on_time_pct?: unknown }>(expected.endpoint);
|
|
373
|
+
const pct = res.body?.on_time_pct;
|
|
374
|
+
if (!res.ok || typeof pct !== "number") return fact.error(`courier answered ${res.status}`);
|
|
375
|
+
return fact.ok(Math.round(pct * 100), { reason: `${pct}% on time` });
|
|
376
|
+
};
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### An AI check forwarded to the stock judge
|
|
380
|
+
|
|
381
|
+
The rubric's own `instructions` are the judge's rules, so there is usually nothing bespoke to judge
|
|
382
|
+
*with*. What a handler adds is the guard the model should never be asked to make:
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
// src/facts/handoverPhotoLooksReal.ts
|
|
386
|
+
import { type AiHandler, fact } from "@ixo/decision-oracle/facts";
|
|
387
|
+
|
|
388
|
+
export const handoverPhotoLooksReal: AiHandler = async (_input, tools) => {
|
|
389
|
+
if (tools.attachments.on("handoverPhoto").length === 0) {
|
|
390
|
+
// Never `false`: that would claim a model looked at a photo and found it fake.
|
|
391
|
+
return fact.error("the claim carries no handoverPhoto attachment");
|
|
392
|
+
}
|
|
393
|
+
return tools.stock();
|
|
394
|
+
};
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
`tools.stock()` is typed as *this lane's* fact (`AiFact` here, `ExtFact` in an ext handler), so
|
|
398
|
+
`return tools.stock()` needs no cast. And **never call the declared endpoint and `tools.stock()`
|
|
399
|
+
both** — that is two calls, and two charges, for one fact.
|
|
400
|
+
|
|
401
|
+
### An AI check with your own judge
|
|
402
|
+
|
|
403
|
+
```ts
|
|
404
|
+
export const photoShowsHandover: AiHandler = async ({ name, check }, tools) => {
|
|
405
|
+
const images = tools.ai.inputsFor(check).filter((entry) => entry.kind === "attachment");
|
|
406
|
+
if (images.length === 0) return fact.error(`${name}: the claim carries no image to judge`);
|
|
407
|
+
const { JUDGE_MODEL } = tools.env as typeof tools.env & { JUDGE_MODEL?: string };
|
|
408
|
+
return tools.ai.judge(
|
|
409
|
+
[...check.instructions, "The parcel must be in the recipient's hands, not on a doorstep."],
|
|
410
|
+
images,
|
|
411
|
+
JUDGE_MODEL ? { model: JUDGE_MODEL } : {},
|
|
412
|
+
);
|
|
413
|
+
};
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
`tools.ai.judge` is the same machinery the stock lane runs — the compliance prompt, strict
|
|
417
|
+
structured output, a verdict derived in code from the failed rules, the confidence floor (an answer
|
|
418
|
+
below it is re-sampled, up to three paid attempts; a 4xx from the provider is not retried), the
|
|
419
|
+
frozen transcript with token and cost accounting. It also honours the policy gate: with
|
|
420
|
+
`policies.allowAiChecks` off it returns `error / ai-checks-disabled` and makes no model call.
|
|
421
|
+
|
|
422
|
+
Images ride at the `AI_IMAGE_DETAIL` var — `high` by default, because a photo **of a screen** or a
|
|
423
|
+
generated image gives itself away only in fine detail (moiré, a pixel grid, garbled text) that `low`
|
|
424
|
+
downsamples out. `high` costs more image tokens per check; drop to `low` only for an oracle whose
|
|
425
|
+
rules never turn on it.
|
|
426
|
+
|
|
427
|
+
### Reading attachments, and image provenance
|
|
428
|
+
|
|
429
|
+
Attachments arrive as **descriptors** — the bytes stay in Matrix, content-addressed. Ask for them by
|
|
430
|
+
the root question name, and read them on demand:
|
|
431
|
+
|
|
432
|
+
```ts
|
|
433
|
+
export const photoIsOriginal: ExtHandler = async (_input, tools) => {
|
|
434
|
+
const [photo] = tools.attachments.on("handoverPhoto");
|
|
435
|
+
if (!photo) return fact.ok(false, { reason: "the claim carries no handover photo" });
|
|
436
|
+
const body = await tools.attachments.bytes(photo, { maxBytes: 5_000_000 });
|
|
437
|
+
if (!body.ok) return fact.error(`photo could not be read: ${body.reason}`);
|
|
438
|
+
|
|
439
|
+
const provenance = await tools.c2pa.screen(body.bytes, photo.mediaType);
|
|
440
|
+
// No manifest at all is the COMMON case (a screenshot strips it) — inconclusive, not a "no".
|
|
441
|
+
if (!provenance) return fact.ok(true, { reason: "no Content Credentials on the photo" });
|
|
442
|
+
// `trusted` is the only thing that makes the manifest's claims mean anything.
|
|
443
|
+
return fact.ok(!(provenance.trusted && provenance.aiGenerated), {
|
|
444
|
+
reason: provenance.trusted
|
|
445
|
+
? `Content Credentials signed by ${provenance.vendor}`
|
|
446
|
+
: "Content Credentials present but untrusted",
|
|
447
|
+
});
|
|
448
|
+
};
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
"Too large" and "could not be read" are different answers — the first is a file you can still
|
|
452
|
+
describe, the second taught you nothing.
|
|
453
|
+
|
|
454
|
+
### A `~derived` value the engine computes
|
|
455
|
+
|
|
456
|
+
The engine owns the expression grammar; this package carries no evaluator, so the stock lanes refuse
|
|
457
|
+
a `send` map that names one. A custom handler is not short-circuited: it gets the refs in
|
|
458
|
+
`unresolved` and the expressions on `manifest.derived`, and may reproduce the value itself.
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
import { type ExtHandler, fact, readAnswer } from "@ixo/decision-oracle/facts";
|
|
462
|
+
|
|
463
|
+
export const withinBudget: ExtHandler = ({ send, unresolved, answers, manifest }, tools) => {
|
|
464
|
+
const ref = unresolved[0];
|
|
465
|
+
// No `~derived` ref in this rubric's send map: `send` already carries everything.
|
|
466
|
+
if (ref === undefined) return typeof send.total === "number" && send.total <= 100_000;
|
|
467
|
+
|
|
468
|
+
const expression = manifest.derived.find((d) => `~${d.name}` === ref)?.expression;
|
|
469
|
+
if (expression !== "sum($lineItems[*].amount)") {
|
|
470
|
+
// Only ever reproduce an expression you recognise EXACTLY. Anything else is unanswered.
|
|
471
|
+
return fact.error(`cannot compute ${ref} (${expression ?? "no expression on the manifest"})`);
|
|
472
|
+
}
|
|
473
|
+
const cells = readAnswer(answers, "$lineItems[*].amount");
|
|
474
|
+
if (!Array.isArray(cells)) return fact.error(`${ref}: the claim carries no line items`);
|
|
475
|
+
const total = cells.reduce<number>((sum, v) => sum + (typeof v === "number" ? v : 0), 0);
|
|
476
|
+
tools.logger.info("computed the rubric's derived total", { ref, total });
|
|
477
|
+
return total <= 100_000;
|
|
478
|
+
};
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
`readAnswer` is the engine binder's own `$path` grammar: `.seg` steps into an object, `[*]` fans out
|
|
482
|
+
over an array (so a `[*]` ref resolves to an array of cell values), and a path that walks off the
|
|
483
|
+
data resolves to `undefined`.
|
|
484
|
+
|
|
485
|
+
### A fact read off the buyer statement
|
|
486
|
+
|
|
487
|
+
Guard first, then the **one** `tools.agreement()` call as late as possible — the engine records the
|
|
488
|
+
snapshot current at decision time on its receipt, so reading it last is what makes the receipt name
|
|
489
|
+
the version your facts used.
|
|
490
|
+
|
|
491
|
+
```ts
|
|
492
|
+
export const priceMatchesAgreement: ExtHandler = async ({ answers }, tools) => {
|
|
493
|
+
const { serviceId, priceUsd } = answers;
|
|
494
|
+
if (typeof serviceId !== "string" || typeof priceUsd !== "number") {
|
|
495
|
+
return fact.ok(false, { reason: "the claim carries no serviceId / priceUsd to compare" });
|
|
496
|
+
}
|
|
497
|
+
const agreement = await tools.agreement();
|
|
498
|
+
if (!agreement) return fact.error("no agreement on file for this claim's counterparty");
|
|
499
|
+
|
|
500
|
+
const services = agentServices(agreement.doc); // your own parser for the doc `kind` you support
|
|
501
|
+
if (!services) return fact.error(`agreement ${agreement.id} is not an agent-services/v1 document`);
|
|
502
|
+
const agreed = services.find((service) => service.id === serviceId);
|
|
503
|
+
if (!agreed) return fact.ok(false, { reason: `service ${serviceId} is not in the agreement` });
|
|
504
|
+
// Money is integers — whole cents, never float equality.
|
|
505
|
+
return fact.ok(cents(agreed.priceUsd) === cents(priceUsd), {
|
|
506
|
+
reason: `claimed $${priceUsd}, agreed $${agreed.priceUsd}`,
|
|
507
|
+
});
|
|
508
|
+
};
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
Note which failures are which: "the claim named a service the agreement does not list" is a real,
|
|
512
|
+
checked answer (`ok(false)`); "there is no agreement on file" or "this is a document shape I do not
|
|
513
|
+
understand" is an `error`. Neither is ever an invented `true`.
|
|
514
|
+
|
|
515
|
+
### A fact read off an entity's own document
|
|
516
|
+
|
|
517
|
+
An ask's terms (`#done`), its payout terms, a profile: documents an entity anchors as a
|
|
518
|
+
`LinkedResource`. Read them with `readLinkedDoc` from `@ixo/decision-oracle/facts`, never a bare
|
|
519
|
+
`fetch`. The `serviceEndpoint` may be the IXO Vault, the IXO IPFS gateway (or a bare `ipfs:<cid>`)
|
|
520
|
+
or, on older entities, Matrix media; `readLinkedDoc` reads all three and checks the bytes against
|
|
521
|
+
the resource's `proof`, so a store that serves the wrong bytes can never become your fact.
|
|
522
|
+
|
|
523
|
+
```ts
|
|
524
|
+
export const withinAskBudget: ExtHandler = async ({ send }, tools) => {
|
|
525
|
+
const { deedDid, amount } = send;
|
|
526
|
+
if (typeof deedDid !== "string" || typeof amount !== "number") {
|
|
527
|
+
return fact.error("the rubric's send map names no deed / amount");
|
|
528
|
+
}
|
|
529
|
+
const done = (await tools.chain.linkedResources(deedDid)).find((r) => r.id?.endsWith("#done"));
|
|
530
|
+
if (!done) return fact.error(`${deedDid} anchors no #done`);
|
|
531
|
+
const read = await readLinkedDoc(done, { ipfsGateway: tools.config.IPFS_GATEWAY_URL });
|
|
532
|
+
if (!read.ok) {
|
|
533
|
+
// A store that is down retries; bytes that are not the proof are never read as the terms.
|
|
534
|
+
if (read.retryable) throw fact.retryLater(read.message);
|
|
535
|
+
return fact.error(`#done of ${deedDid}: ${read.message}`);
|
|
536
|
+
}
|
|
537
|
+
const { maxAmount } = read.json as { maxAmount?: unknown };
|
|
538
|
+
if (typeof maxAmount !== "number") return fact.error(`#done of ${deedDid} sets no maxAmount`);
|
|
539
|
+
return fact.ok(amount <= maxAmount, { reason: `claimed ${amount}, the ask allows ${maxAmount}` });
|
|
540
|
+
};
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### A custom `unique` or `frequency`
|
|
544
|
+
|
|
545
|
+
Rare, and only when the answer genuinely lives elsewhere. Both replace their lane wholesale, so
|
|
546
|
+
`tools.stock()` throws in them by design, and both return the wire shape the engine reads.
|
|
547
|
+
|
|
548
|
+
```ts
|
|
549
|
+
import type { UniqueHandler } from "@ixo/decision-oracle/facts";
|
|
550
|
+
|
|
551
|
+
export const unique: UniqueHandler = async ({ rule, claim }, tools) => {
|
|
552
|
+
const res = await tools.http.json<{ digest?: unknown; scoreBps?: unknown }>(
|
|
553
|
+
"https://registry.example/duplicates",
|
|
554
|
+
{ method: "POST", body: { claimId: claim.claimId, collectionId: claim.collectionId } },
|
|
555
|
+
);
|
|
556
|
+
const digest = res.body?.digest;
|
|
557
|
+
if (!res.ok || typeof digest !== "string") {
|
|
558
|
+
// A registry we could not reach is never a pass.
|
|
559
|
+
return { ruleId: rule.id, digest: "registry-unavailable", scoreBps: 0, band: "review" };
|
|
560
|
+
}
|
|
561
|
+
const scoreBps = typeof res.body?.scoreBps === "number" ? res.body.scoreBps : 0;
|
|
562
|
+
return {
|
|
563
|
+
ruleId: rule.id,
|
|
564
|
+
digest,
|
|
565
|
+
scoreBps,
|
|
566
|
+
band:
|
|
567
|
+
scoreBps >= rule.thresholds.rejectAtBps
|
|
568
|
+
? "reject"
|
|
569
|
+
: scoreBps >= rule.thresholds.reviewAtBps
|
|
570
|
+
? "review"
|
|
571
|
+
: "pass",
|
|
572
|
+
vendor: "registry.example", // extra fields ride loose into the engine's trace
|
|
573
|
+
};
|
|
574
|
+
};
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
`rule` already carries the engine's own defaults (reject ≥ 9000 bps, review ≥ 5000 bps, and the
|
|
578
|
+
rest — see [api.md §4](./api.md#uniquehandler--the-duplicate-fact)). For `frequency`, remember the
|
|
579
|
+
count **includes** the claim under evaluation, windows are UTC calendar periods derived from the
|
|
580
|
+
claim's on-chain `submissionDate` (the rubric has no time-zone setting, so a "day" is a UTC day),
|
|
581
|
+
and an uncountable window is `unresolved: true` — its own state, which the engine fails closed
|
|
582
|
+
(a final reject) rather than reading as headroom. So keep `unresolved` for a deterministic miss (no
|
|
583
|
+
submitter, no usable date) and `throw fact.retryLater(...)` when the count could not be read: the
|
|
584
|
+
stock lane does exactly that, so a Blocksync outage retries the step instead of rejecting claims.
|
|
585
|
+
|
|
586
|
+
### The rules a handler must never break
|
|
587
|
+
|
|
588
|
+
1. **Never fake an ok.** A fact you could not establish is `fact.error(detail)`. Returning `false`
|
|
589
|
+
says "I checked, and it is false" — a different claim about the world, and the rubric may reject
|
|
590
|
+
on it. A stub is `fact.error("not implemented: <name>")`, visibly.
|
|
591
|
+
2. **Never choose `onError`.** There is no argument for it and no way to pass one: the runner stamps
|
|
592
|
+
the manifest's own value. The rubric's author already made that call.
|
|
593
|
+
3. **Never decide.** No thresholds, no gates, no rubric parsing, no reading `rubric.json`. The
|
|
594
|
+
manifest is the only work order and the engine is the only judge.
|
|
595
|
+
4. **`ctx.now`, never `Date.now()`.** The engine's date rules compare against the frozen instant; a
|
|
596
|
+
second clock read makes a fact that cannot be replayed.
|
|
597
|
+
5. **No concurrency of your own.** The runner already caps ext+ai at 4 in flight and caches every
|
|
598
|
+
answer, so a crashed step never re-buys a paid call.
|
|
599
|
+
6. **No secrets in code or commits.** A vendor key is a `.dev.vars` secret / `wrangler secret put`,
|
|
600
|
+
read as `tools.env.<NAME>`.
|
|
601
|
+
7. **A handler never imports the worker.** `@ixo/decision-oracle/facts` and `@ixo/decision-oracle/testing`
|
|
602
|
+
only — which is what keeps the tests plain node, fast and real.
|
|
603
|
+
|
|
604
|
+
---
|
|
605
|
+
|
|
606
|
+
## 7. Test them in node
|
|
607
|
+
|
|
608
|
+
```bash
|
|
609
|
+
bun run test # vitest, environment: node — no Worker, no pool, no network
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
`runFacts` is the same runner production uses, followed by the same `validateFacts` —
|
|
613
|
+
`issues: []` means the engine would accept this FactSet.
|
|
614
|
+
|
|
615
|
+
```ts
|
|
616
|
+
import { fakeTools, httpResponse, runFacts, sampleClaim, sampleManifest } from "@ixo/decision-oracle/testing";
|
|
617
|
+
import { describe, expect, it } from "vitest";
|
|
618
|
+
import { facts } from "../src/facts";
|
|
619
|
+
|
|
620
|
+
const manifest = sampleManifest({
|
|
621
|
+
ext: {
|
|
622
|
+
deliveryConfirmed: {
|
|
623
|
+
expect: "boolean",
|
|
624
|
+
onError: "review",
|
|
625
|
+
endpoint: "https://courier.example/deliveries",
|
|
626
|
+
send: { reference: "$reference" },
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
describe("ext.deliveryConfirmed", () => {
|
|
632
|
+
it("answers ok when the courier says delivered", async () => {
|
|
633
|
+
const out = await runFacts(facts, {
|
|
634
|
+
claim: sampleClaim({ reference: "ABC" }),
|
|
635
|
+
manifest,
|
|
636
|
+
tools: fakeTools({
|
|
637
|
+
env: { COURIER_API_KEY: "test-key" },
|
|
638
|
+
http: { "https://courier.example/deliveries": { status: "delivered" } },
|
|
639
|
+
}),
|
|
640
|
+
});
|
|
641
|
+
expect(out.issues).toEqual([]);
|
|
642
|
+
expect(out.ext.deliveryConfirmed).toMatchObject({ status: "ok", value: true });
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
it("answers error — never false — on a body it cannot read", async () => {
|
|
646
|
+
const out = await runFacts(facts, {
|
|
647
|
+
claim: sampleClaim({ reference: "ABC" }),
|
|
648
|
+
manifest,
|
|
649
|
+
tools: fakeTools({
|
|
650
|
+
env: { COURIER_API_KEY: "test-key" },
|
|
651
|
+
http: { "https://courier.example/deliveries": httpResponse(422, { detail: "bad ref" }) },
|
|
652
|
+
}),
|
|
653
|
+
});
|
|
654
|
+
expect(out.ext.deliveryConfirmed).toMatchObject({ status: "error", onError: "review" });
|
|
655
|
+
});
|
|
656
|
+
});
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
**A handler with only a happy-path test is not tested.** Cover the failure it will actually meet: an
|
|
660
|
+
unmatched URL in `fakeTools` is a network failure (`ok: false`, `status: null`); `httpResponse(503,
|
|
661
|
+
…)` is a bad status; a claim with no attachment exercises a guard. `fakeTools().logs` collects what
|
|
662
|
+
the handler logged. `sampleCtx({ now })` pins the clock. And there is no stock lane behind a test:
|
|
663
|
+
`tools.stock()` answers an error fact unless you pass `fakeTools({ stock })`, which is exactly how
|
|
664
|
+
you test a handler that forwards to it. Full option tables:
|
|
665
|
+
[api.md §10](./api.md#10-testing--ixodecision-oracletesting).
|
|
666
|
+
|
|
667
|
+
---
|
|
668
|
+
|
|
669
|
+
## 8. Dry-run against a real claim
|
|
670
|
+
|
|
671
|
+
```bash
|
|
672
|
+
bunx decision-oracle facts run --protocol <protocol did> --claim protocol/sample-claim.json \
|
|
673
|
+
[--collection <id>] [--agent <address>] [--preview] [--no-ai] [--facts src/facts/index.ts] [--json]
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
It reads your `.dev.vars`, pulls the **live** manifest, runs your table over that claim with **real**
|
|
677
|
+
tools (real HTTP, real UCAN signing, a real model call for `tools.ai.judge` — that one spends
|
|
678
|
+
money), runs the same `validateFacts`, and prints every fact, the run's AI spend (tokens per model;
|
|
679
|
+
USD where the provider reports it at call time — on the gateway it is in the gateway's logs) and any
|
|
680
|
+
issue. Exit 1 on issues, 2 on
|
|
681
|
+
an unusable rubric.
|
|
682
|
+
|
|
683
|
+
`--claim` takes either shape:
|
|
684
|
+
|
|
685
|
+
- a **flat answers object** — the same file `submit-claim --answers` posts, so a protocol's
|
|
686
|
+
`sample-claim.json` works as it stands;
|
|
687
|
+
- the **envelope** `{ answers, attachments: [{ question, name, mediaType, path }] }` when a claim
|
|
688
|
+
carries files. Each `path` is relative to the claim file, and the bytes ride as `data:` URLs, so
|
|
689
|
+
`tools.attachments.bytes()` behaves exactly as it does against Matrix — without the claim bot.
|
|
690
|
+
|
|
691
|
+
A flat file has no attachments, so a handler that reads one fails closed. That is the point: run the
|
|
692
|
+
passport example against its flat sample claim and the AI fact comes back `error` (the guard's path),
|
|
693
|
+
not a stock verdict.
|
|
694
|
+
|
|
695
|
+
Three things are not real off a Worker, and the command says so as it runs:
|
|
696
|
+
|
|
697
|
+
- **`ctx`** without `--collection`: the clock is the real one (a "not in the future" rule is judged
|
|
698
|
+
against the same instant the deployed oracle would freeze), but the collection window and
|
|
699
|
+
submission plane around it are a sample's empty ones. Pass `--collection <id>` for the real ones.
|
|
700
|
+
- **the stock `unique` lane**, which reads this oracle's D1 registries. It stands in with a
|
|
701
|
+
fail-closed placeholder — band `review`, score 0, a digest no body could have — so the rest of the
|
|
702
|
+
run still happens. A `facts.unique` handler of yours always wins over it.
|
|
703
|
+
- **C2PA provenance**, which cannot instantiate its wasm outside a Worker: inconclusive here, only
|
|
704
|
+
ever screened for real in the deployed oracle.
|
|
705
|
+
|
|
706
|
+
`--preview` then freezes the FactSet and asks the engine's preview route what it *would* decide —
|
|
707
|
+
nothing persisted, nothing billed, no grant needed. It needs `bun run dev` up first, because the
|
|
708
|
+
engine resolves your `did:web` by fetching `/.well-known/did.json` from `ORACLE_HOSTNAME`; the
|
|
709
|
+
command checks that and says so rather than letting the engine fail with an unrelated auth error.
|
|
710
|
+
`--preview` is skipped when there are validation issues — fix those first.
|
|
711
|
+
|
|
712
|
+
---
|
|
713
|
+
|
|
714
|
+
## 9. Run it locally
|
|
715
|
+
|
|
716
|
+
```bash
|
|
717
|
+
wrangler d1 create <project-name> # paste the id into wrangler.jsonc
|
|
718
|
+
wrangler kv namespace create CACHE_KV # same
|
|
719
|
+
bun run db:migrate # the package's own migrations, onto the local D1
|
|
720
|
+
bun run dev # wrangler dev --test-scheduled
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
Crons do not fire on a schedule locally — fire one by hand (each trigger runs its own tier, see
|
|
724
|
+
[§12](#12-operating-it)):
|
|
725
|
+
|
|
726
|
+
```bash
|
|
727
|
+
curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*" # minute: the scan, resumes, webhooks
|
|
728
|
+
curl "http://localhost:8787/__scheduled?cron=*/5+*+*+*+*" # five: AI cost, payment reconcile, balance watch
|
|
729
|
+
curl "http://localhost:8787/__scheduled?cron=0+*+*+*+*" # hour: housekeeping, reminders, rooms, link re-check
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
`GET /health` and `GET /.well-known/did.json` should answer immediately; `GET /v1/info` is your
|
|
733
|
+
public listing. The local D1 lives under `.wrangler/state` (there is no `db:reset` — delete the
|
|
734
|
+
directory).
|
|
735
|
+
|
|
736
|
+
Two CLI commands drive a running oracle as its owner, using your AuthHub session:
|
|
737
|
+
|
|
738
|
+
```bash
|
|
739
|
+
bunx decision-oracle register --collection <id> [--delegation <bot CAR>] [--contact-email <addr>] [--kit <url>]
|
|
740
|
+
bunx decision-oracle scan --collection <id> # discover + start now, not at the next tick
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
With the Decision API lane on (`API_LANE`), `decision-oracle api …` is its console from a terminal,
|
|
744
|
+
signed as the same session — an admin (controller) of a source's organisation, or one of the
|
|
745
|
+
oracle's admins named in `ORACLE_ADMIN_DIDS` (the oracle's operator's own people):
|
|
746
|
+
|
|
747
|
+
```bash
|
|
748
|
+
bunx decision-oracle org create --name "Acme" --yes # { entityDid, created } — the org entity, once (idempotent by name)
|
|
749
|
+
bunx decision-oracle api source create --name "Acme" --contact ops@acme.example --entity <org did> # secret once; pending-link
|
|
750
|
+
bunx decision-oracle api source link <sourceId> --yes # sign the org's link at the engine, sync → active
|
|
751
|
+
bunx decision-oracle org balance --entity <org did> # { adminAddress, balances }
|
|
752
|
+
bunx decision-oracle org fund --entity <org did> --amount 5 --yes # top its admin account up from your wallet (IXO; --coin usdc on mainnet)
|
|
753
|
+
bunx decision-oracle api key create --source <sourceId> --scopes claims:write,claims:read # the key, once
|
|
754
|
+
bunx decision-oracle api keys --source <sourceId>
|
|
755
|
+
bunx decision-oracle api key revoke --source <sourceId> <prefix> --grace-days 7 --yes
|
|
756
|
+
bunx decision-oracle api admin protocols <sourceId> <did,…> --yes # operator: the allow-list, whole (--none: nothing)
|
|
757
|
+
bunx decision-oracle api admin billing-mode <sourceId> none --yes # operator only, by design
|
|
758
|
+
bunx decision-oracle api review-billing enable --source <sourceId> --yes # default --cap 1000 PAY; "Pay for reviews", as the org's chain owner
|
|
759
|
+
bunx decision-oracle api review-billing status --source <sourceId>
|
|
760
|
+
bunx decision-oracle api review-billing disable --source <sourceId> --yes # drop it here + revoke it in the UCAN store
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
The oracle is `--kit <url>`, else `ORACLE_HOSTNAME` from `.dev.vars` (a project config that
|
|
764
|
+
does not load is reported var by var); the network is `--network`, else the project's
|
|
765
|
+
`IXO_NETWORK`, else devnet. `--json` prints the raw answer and nothing else on stdout — a gate's
|
|
766
|
+
plan goes to stderr, so a dry run under `--json` prints nothing there. `key revoke` takes the
|
|
767
|
+
prefix or the masked key a listing prints. Every write sends a fresh `Idempotency-Key`; a refusal
|
|
768
|
+
prints the error's `code`, `message` and `requestId` and exits 1. `decision-oracle api --help` lists
|
|
769
|
+
every subcommand.
|
|
770
|
+
|
|
771
|
+
Every source belongs to an **organisation**: its on-chain entity. A source is that organisation's
|
|
772
|
+
access to your oracle, added by one of its admins (the entity's controllers, read live on chain).
|
|
773
|
+
It starts `pending-link` — no claims, no keys, no invites, no webhooks, and its id in no log line —
|
|
774
|
+
until the admin signs the organisation's link in the Decision Console, or with `decision-oracle api
|
|
775
|
+
source link` (the link lives at the decision engine; the console, or the CLI, then calls the
|
|
776
|
+
oracle's sync). `org create` makes the organisation entity itself, the console's way, `org fund` sends IXO
|
|
777
|
+
(or mainnet USDC) from your wallet to its admin account (you must own or control it), and
|
|
778
|
+
`logout` forgets a network's saved session. From then on the entity's current
|
|
779
|
+
admins run the source — keys, members, settings — with no membership of their own; an admin
|
|
780
|
+
removed on chain loses it at once. Nothing to configure on the oracle; `docs/api.md` "A source's
|
|
781
|
+
organisation" has the rules. Members get one of five console roles: `owner`, `developer`,
|
|
782
|
+
`reviewer`, `viewer`, or `submitter` — staff who only file claims (`claims/submit` + `claims/read`:
|
|
783
|
+
submit, upload the files a claim attaches, read the decisions), e.g. through an AI app.
|
|
784
|
+
|
|
785
|
+
With the lane on, your oracle also hosts an MCP server at `/v1/mcp`, and AI apps (claude.ai,
|
|
786
|
+
ChatGPT, Claude Code) can connect to it by signing your customers in through IXO AuthHub, with no
|
|
787
|
+
API key to paste. You configure nothing for this: `AUTH_HUB_URL` defaults to the network's AuthHub,
|
|
788
|
+
and the oracle publishes `/.well-known/oauth-protected-resource/v1/mcp` for the apps to find it. Two
|
|
789
|
+
things to know as an operator:
|
|
790
|
+
|
|
791
|
+
- An app never pays on its token alone. Each claim it submits waits for the user to approve it on
|
|
792
|
+
AuthHub (`APPROVAL_REQUIRED`), and the payment is a delegation for that one claim at the price the
|
|
793
|
+
user saw. An app's grant also stops at $20.00 a day (`SPEND_CAP`).
|
|
794
|
+
- The app acts as the user on the sources whose organisation they are a current admin of (as
|
|
795
|
+
root, no membership needed) or they are a live member of (submitting needs `submitter` or
|
|
796
|
+
`owner`). Removing a member from a source, or an admin from the entity on chain, ends their apps'
|
|
797
|
+
access at once. The audit log names the app (actor kind `oauth`), and
|
|
798
|
+
the claim's `accepted` event says `via: oauth:<client_id>`.
|
|
799
|
+
- In apps that render MCP Apps (claude.ai, Claude desktop, ChatGPT, VS Code), `submit_claim` /
|
|
800
|
+
`get_decision` show a decision card, `verify_receipt` a receipt badge and `list_protocols` the
|
|
801
|
+
protocols or one work order instead of raw JSON (an approval ask is a normal result there, so its
|
|
802
|
+
card with the approve button shows);
|
|
803
|
+
Claude Code and other text clients get the same text as before.
|
|
804
|
+
|
|
805
|
+
`docs/api.md` §"Use with Claude Code and other MCP clients" has the full rules. For a local AuthHub,
|
|
806
|
+
set `AUTH_HUB_URL` in `.dev.vars`; its did:web is then the only caller the approvals back-channel
|
|
807
|
+
(`/v1/oauth/approvals/:approvalId`) accepts.
|
|
808
|
+
|
|
809
|
+
**A running `wrangler dev` reloads source, never `.dev.vars`.** Restart it after any secret or
|
|
810
|
+
hostname change, or a rebuilt claim silently runs against the old, empty secret.
|
|
811
|
+
|
|
812
|
+
---
|
|
813
|
+
|
|
814
|
+
## 10. Billing, provisioning on chain, and deploy
|
|
815
|
+
|
|
816
|
+
### The owner's delegation (do this before register)
|
|
817
|
+
|
|
818
|
+
The engine bills the **oracle owner's** credits per decision, and finds that subscription through a
|
|
819
|
+
UCAN delegation you post once, at register. Mint it as the owner:
|
|
820
|
+
|
|
821
|
+
```bash
|
|
822
|
+
bunx decision-oracle login --network <devnet|testnet|mainnet> # once per network; a browser login
|
|
823
|
+
bunx decision-oracle delegation --for engine \
|
|
824
|
+
--audience did:web:<engine host> --oracle did:web:<ORACLE_HOSTNAME> \
|
|
825
|
+
--out owner-delegation.car [--days 36500] --yes
|
|
826
|
+
printf 'OWNER_DELEGATION_CAR="%s"\n' "$(cat owner-delegation.car)" >> .dev.vars
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
The engine host is the one this oracle is configured against — the per-network default unless
|
|
830
|
+
`EVAL_ENGINE_HOSTNAME` overrides it. A `:` in a host is `%3A` inside a `did:web`. **Never print the
|
|
831
|
+
CAR**: it is a bearer credential. Deployed, it is
|
|
832
|
+
`wrangler secret put OWNER_DELEGATION_CAR < owner-delegation.car`. Its lifetime is 100 years by
|
|
833
|
+
default, because nothing renews it — the engine reads the expiry only at register. Shorten it with
|
|
834
|
+
`--days` only if you will re-mint **and** re-register before it lapses.
|
|
835
|
+
|
|
836
|
+
A `402` at register is not a bug: `NO_SUBSCRIPTION` (none at all) or `INSUFFICIENT_CREDITS` (below
|
|
837
|
+
one decision's price, carrying a `manageSubscriptionUrl`). The owner buys credits at that URL and
|
|
838
|
+
registers again. Nothing in the package can fake it.
|
|
839
|
+
|
|
840
|
+
### Provision the oracle on chain
|
|
841
|
+
|
|
842
|
+
A fresh `derive` identity exists only in your `.dev.vars`. On chain it has an **unfunded wallet**
|
|
843
|
+
(it cannot pay for a `MsgEvaluateClaim`), **no IID document** and **no Ed25519 verification method**
|
|
844
|
+
for `did:ixo:<ORACLE_ADDRESS>` — which is the DID every invocation to the claim bot, the UCAN
|
|
845
|
+
Store and the VFS claims lane is minted as, so all three refuse it — **no Matrix account** (so
|
|
846
|
+
`ORACLE_MATRIX_ACCESS_TOKEN` has nothing to hold and the review-card lane no-ops), and **no oracle
|
|
847
|
+
entity**, so nobody browsing the protocol can find you. One command fixes all of it:
|
|
848
|
+
|
|
849
|
+
```bash
|
|
850
|
+
bunx decision-oracle oracle init --protocol <protocol did> --kit-url http://localhost:8787 \
|
|
851
|
+
[--logo <url>] [--openhands [--price-pay <n>]] [--only <step>] [--relogin] [--network <n>] [--yes]
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
**A full on-chain identity is mandatory** for every oracle built with this kit — the entity, its
|
|
855
|
+
`#pro` profile, its `#dmn` domain card and its `#acard` Agent Card. `--logo <url>` is the one
|
|
856
|
+
optional input, and it is recommended: without it the domain card carries no logo and the portal
|
|
857
|
+
and the console show a placeholder (the run says so every time).
|
|
858
|
+
|
|
859
|
+
Thirteen steps, always in this order, each printing `do` or `skip (<why>)`:
|
|
860
|
+
|
|
861
|
+
**The wallet and the identity** — signed by the *oracle's own* wallet, except the funding.
|
|
862
|
+
|
|
863
|
+
1. `fund` — your AuthHub session tops `ORACLE_ADDRESS` up to 1 IXO (`1000000uixo`). That balance is
|
|
864
|
+
what pays the gas on every verdict this oracle signs. Skipped when the spendable balance is
|
|
865
|
+
already ≥ 1 IXO — a **floor check**, so a later run over a wallet that has since dipped below it
|
|
866
|
+
sends another full top-up, and this is the one step a re-run can legitimately repeat.
|
|
867
|
+
2. `iid` — `MsgCreateIidDocument` for `did:ixo:<ORACLE_ADDRESS>`, with a `#matrix` service pointing
|
|
868
|
+
at the network's homeserver. Only the oracle's own key may create its own DID document. Skipped
|
|
869
|
+
when the DID resolves.
|
|
870
|
+
3. `key` — `MsgAddVerification` puts the `SERVICE_ED25519_MNEMONIC` public key on that document
|
|
871
|
+
(`authentication` + `assertionMethod`). **This is the step that makes the pull lane work**: the
|
|
872
|
+
claim bot, the UCAN Store and the VFS claims lane resolve the DID and check the invocation's
|
|
873
|
+
signature against this key. Skipped when a verification method for that exact key is already
|
|
874
|
+
there.
|
|
875
|
+
4. `matrix` — creates the oracle's Matrix account (the rooms bot, answering a challenge your wallet
|
|
876
|
+
signs) if it does not exist, logs in, sets the display name from `GET <kit-url>/v1/info`, and
|
|
877
|
+
**writes `ORACLE_MATRIX_ACCESS_TOKEN` into `.dev.vars`** — never to the terminal. Skipped when
|
|
878
|
+
the file already holds a token whose `whoami` is `ORACLE_MATRIX_USER_ID`.
|
|
879
|
+
|
|
880
|
+
**The entity** — signed by your AuthHub session, which owns what it creates.
|
|
881
|
+
|
|
882
|
+
5. `entity` — `MsgCreateEntity`, carrying both services and the protocol edge in one transaction.
|
|
883
|
+
Skipped when **this oracle** already has an entity — found by the oracle, never by its owner,
|
|
884
|
+
because one AuthHub session may own several oracles' entities: `ORACLE_ENTITY_DID` when set
|
|
885
|
+
(read from `env.<--network>.vars`, else the top-level vars, `.dev.vars` winning; checked to be
|
|
886
|
+
an oracle entity the session owns AND bound to this oracle by one of the two links below — a pin
|
|
887
|
+
on another oracle's entity stops the run), else the session's oracle entity whose `#orz` grants
|
|
888
|
+
`ORACLE_ADDRESS` (so a moved host still finds it), else whose `#eval` is `--kit-url`. Two
|
|
889
|
+
matches, or an `#orz` that cannot be read, stop the run. The moment it creates the entity it
|
|
890
|
+
writes its DID into `.dev.vars` as `ORACLE_ENTITY_DID`, so a re-run after a later failure takes
|
|
891
|
+
the pin instead of minting a second entity while blocksync catches up.
|
|
892
|
+
6. `orz` — the `#orz` `oracleAuthZConfig` resource: the portal reads it to bridge the entity to the
|
|
893
|
+
`ORACLE_ADDRESS` it must grant. Skipped when `#orz` is on the entity — unless it is still on
|
|
894
|
+
Matrix media: a re-run moves any `#orz`, `#pro`, `#dmn` or `#acard` there onto the Vault (same
|
|
895
|
+
content, delete + add in one transaction).
|
|
896
|
+
7. `profile` — the `#pro` `Settings` resource, built from the running oracle's own `GET /v1/info`
|
|
897
|
+
(plus `--logo`). Skipped when `#pro` is there **or when the kit URL is not reachable** — a skip,
|
|
898
|
+
never a failure, so a not-yet-deployed oracle never blocks the run. The one exception: a
|
|
899
|
+
`--logo` that differs from the live profile's replaces `#pro` as a merge (only `logo` changes),
|
|
900
|
+
because the portal's avatar is read from it.
|
|
901
|
+
8. `domain` — the `#dmn` `domainCard` resource: the portal's `ixo:DomainCard` credential, built from
|
|
902
|
+
`GET /v1/info` (name, description), `url` = `--kit-url`, and `logo` / `image` only when `--logo`
|
|
903
|
+
is given; issued by your session. Like `#pro` and `#acard` it is stored on the IXO Vault under
|
|
904
|
+
your session's key (else the IXO IPFS gateway — the run says so), its public
|
|
905
|
+
`…/public/cid/<cid>` URL as `serviceEndpoint`, the bytes' `bafkrei…` CID as `proof`. This is
|
|
906
|
+
what the domain indexer, the portal and the console list you by. The kit owns only what it
|
|
907
|
+
writes — name, description, `url`, and the logo when `--logo` is given; skipped when those match
|
|
908
|
+
and the proof is the CID of the served bytes, or when the kit URL is not reachable. A card whose
|
|
909
|
+
owned fields drifted (a renamed oracle, a new host, a logo passed) — or whose proof is the old
|
|
910
|
+
sha256 hex — is deleted and re-added in one transaction, **merged onto the live card**: anything
|
|
911
|
+
you added by hand (a logo, images, keywords, a location) survives, and without `--logo` the live
|
|
912
|
+
logo stays. A card that cannot be read is left alone with a `⚠`, never replaced.
|
|
913
|
+
9. `acard` — the `#acard` `agentCard` resource: an `ixo:AgentCard` credential the portal's contract
|
|
914
|
+
flow reads: a `claim-evaluation` service when the kit publishes evaluation pricing (its fee for
|
|
915
|
+
`--protocol`, `byProtocol` first) and a `decision-api` service when its Decision API publishes a
|
|
916
|
+
price for `--protocol` (`GET /v1/api/protocols` — the same `publishedPrice` the facilitator
|
|
917
|
+
checks, `API_PRICE_USD_CENTS` included). A service with no price behind it is not carded. Your own
|
|
918
|
+
services survive a replace. Validated against the engine's Agent Card schema — the merged card,
|
|
919
|
+
before anything is uploaded; skipped and replaced by the same rule as `domain`.
|
|
920
|
+
To be listed as a checker in the Openhands app, pass `--openhands`: the card then also carries
|
|
921
|
+
a `check-openhands` service (the shape openhands-oracles writes), priced in PAY at your
|
|
922
|
+
per-review price — `/v1/info`'s `pricing.api` for `--protocol` (`byProtocol` first), else
|
|
923
|
+
`--price-pay <n>` (1 PAY = 1 USD; the run fails when neither gives one). The oracle must also be
|
|
924
|
+
registered with the engine. Without the flag the kit leaves `check-openhands` alone, so one
|
|
925
|
+
you added by hand survives every replace.
|
|
926
|
+
|
|
927
|
+
**The wiring** — also the session.
|
|
928
|
+
|
|
929
|
+
10. `linked` — the oracle → protocol `linkedEntity` edge.
|
|
930
|
+
11. `service` — `#eval` → `--kit-url`. An existing `#eval` pointing somewhere **else** is deleted
|
|
931
|
+
and re-added in one transaction, which is how a redeploy to a new host is fixed.
|
|
932
|
+
12. `backlink` — the protocol's edge back to you, which is what the marketplace filters on. If your
|
|
933
|
+
session does not own the protocol, it prints the exact edge to ask its owner for.
|
|
934
|
+
13. `index` — tells the network's domain indexer the entity exists, last so it reads the `#dmn`
|
|
935
|
+
just anchored. On `--yes` only (a dry run asks the indexer nothing) it is skipped when the
|
|
936
|
+
indexer's `GET /domain-cards/<did>` serves that card (`blockchain_version_id` = its proof) with
|
|
937
|
+
`status: completed`. A refusal or an unreachable indexer is a `⚠` line and nothing more.
|
|
938
|
+
|
|
939
|
+
**Dry-run, then `--yes`.** Without `--yes` the command signs, creates and writes nothing: every step
|
|
940
|
+
reads live state and prints the decision it would act on, so the plan you read is the plan that
|
|
941
|
+
executes. Read it, then re-run the identical line with `--yes`.
|
|
942
|
+
|
|
943
|
+
**Run it twice, and again after every redeploy.** Run it first against `http://localhost:8787` with
|
|
944
|
+
`bun run dev` up, so the Matrix token lands in `.dev.vars` before you deploy. Run it again with the
|
|
945
|
+
deployed host: `service` re-points `#eval`, `profile` fills in what it had to skip, and `domain` /
|
|
946
|
+
`acard` replace the cards that named the old host. `fund` is a
|
|
947
|
+
floor check rather than a once-only step: if the first run's own `iid`/`key` transactions left the
|
|
948
|
+
wallet below 1 IXO, it sends another full 1 IXO (not the shortfall) to top it back up. The
|
|
949
|
+
rest say `skip`. Nothing is derived from a marker this command wrote — every decision comes
|
|
950
|
+
from the chain, the homeserver and the entity themselves — so re-running after a partial failure, a
|
|
951
|
+
redeploy or a second person's attempt converges on the same state.
|
|
952
|
+
|
|
953
|
+
`--only <step>` runs exactly one of the thirteen step names above, for debugging a stuck run.
|
|
954
|
+
Only `iid`, `key` and `matrix` act as the oracle itself and need `ORACLE_SECP_MNEMONIC` /
|
|
955
|
+
`SERVICE_ED25519_MNEMONIC` (each stops naming the one it lacks); every other step signs with the
|
|
956
|
+
AuthHub session, so once your keys live only in the deployed worker's secrets you can still run,
|
|
957
|
+
say, `--only profile --yes` from a machine that holds just the session and `wrangler.jsonc`.
|
|
958
|
+
`--relogin` forces a fresh Matrix login where a live token would have skipped — it mints a new
|
|
959
|
+
Matrix device, so it is not a thing to run casually.
|
|
960
|
+
|
|
961
|
+
**Then put the entity DID it prints into `ORACLE_ENTITY_DID`** (a `wrangler.jsonc` var) and
|
|
962
|
+
redeploy. It is required for a deployed oracle: on any `ORACLE_HOSTNAME` that is not local
|
|
963
|
+
(`localhost`, `127.0.0.1`, `*.localhost`), `/health/ready` fails its `identity` check until it is
|
|
964
|
+
set, and `/v1/admin/status` says what to run. Unset, the Matrix review card also cannot resolve your API and a review can only be answered
|
|
965
|
+
from the portal's claim page — and every later `oracle init` uses it as the pin. The entity is owned by whoever logged in via AuthHub;
|
|
966
|
+
`ORACLE_ADDRESS` / `ORACLE_DID` remain the runtime identity that signs and holds grants. Keep
|
|
967
|
+
`supportedProtocols` in step with what you declared — nothing reconciles that drift.
|
|
968
|
+
|
|
969
|
+
### Deploy
|
|
970
|
+
|
|
971
|
+
```bash
|
|
972
|
+
bun run db:migrate:remote # BEFORE the first deploy of that environment
|
|
973
|
+
bun run deploy:testnet # or deploy / deploy:production
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
Before shipping an environment, check all four:
|
|
977
|
+
|
|
978
|
+
1. the **D1 and KV ids** in that env's block are real (named envs inherit nothing — every binding
|
|
979
|
+
and the whole `vars` block is redeclared per env);
|
|
980
|
+
2. the **Custom Domain** in `routes` equals that env's `ORACLE_HOSTNAME`, character for character —
|
|
981
|
+
callers resolve your `did:web` by fetching it;
|
|
982
|
+
3. every **secret** is set with `wrangler secret put` for that env — including
|
|
983
|
+
`ORACLE_MATRIX_ACCESS_TOKEN`, the one `oracle init` wrote into your local `.dev.vars`
|
|
984
|
+
(`wrangler secret put ORACLE_MATRIX_ACCESS_TOKEN`, then paste it);
|
|
985
|
+
4. `bun run cf-typegen` has been re-run since the last binding or var change.
|
|
986
|
+
|
|
987
|
+
Then run `oracle init` once more with the deployed host, to re-point `#eval` at it.
|
|
988
|
+
|
|
989
|
+
---
|
|
990
|
+
|
|
991
|
+
## 11. Getting hired
|
|
992
|
+
|
|
993
|
+
Registration is one call with four gates, in this order:
|
|
994
|
+
|
|
995
|
+
1. **Who** — the caller is the collection entity's chain owner, or holds an active on-chain
|
|
996
|
+
`EvaluateClaimAuthorization` for it (read uncached, because the portal grants and registers in
|
|
997
|
+
one flow).
|
|
998
|
+
2. **Hired** — *this* oracle's `ORACLE_ADDRESS` holds that same grant, or it could never sign
|
|
999
|
+
`MsgEvaluateClaim`. Otherwise `409`.
|
|
1000
|
+
3. **Protocol** — the collection's protocol is in your `supportedProtocols` (when that list is
|
|
1001
|
+
non-empty). Otherwise `409` — checked *before* payment, so a wrong-protocol collection hears
|
|
1002
|
+
"this oracle won't serve you" rather than being sent off to fix a payment that never mattered.
|
|
1003
|
+
4. **Paid** — the collection's on-chain `Payments.evaluation` is in your denom and at least your
|
|
1004
|
+
`minAmount`. Otherwise `402`. While per-review billing is on (IXO's plan), a collection whose company (the
|
|
1005
|
+
engine's answer) has a live review-billing delegation registers with an EMPTY evaluation leg
|
|
1006
|
+
instead — paid per review from the organisation owner's credits — and only once those credits
|
|
1007
|
+
cover one review (`402 insufficient_credits` otherwise; `503 credits_unavailable` /
|
|
1008
|
+
`company_unavailable` when the facilitator or the engine cannot say). Without one, the leg gate
|
|
1009
|
+
above stands, its refusal `402 no_review_billing` naming both ways in.
|
|
1010
|
+
|
|
1011
|
+
Every refusal carries a stable machine code in `details.reason` (`collection_not_found`,
|
|
1012
|
+
`not_owner_or_grantee`, `not_hired`, `unsupported_protocol`, `evaluation_underpaid`,
|
|
1013
|
+
`no_review_billing`, `insufficient_credits`, `company_unavailable`, `credits_unavailable`, …), so a
|
|
1014
|
+
portal never matches a message.
|
|
1015
|
+
|
|
1016
|
+
### Third-party oracle fees
|
|
1017
|
+
|
|
1018
|
+
An oracle that IXO does not run is paid on chain claims by the collection's on-chain **evaluation
|
|
1019
|
+
fee**: the `Payments.evaluation` coin, split 80/10/10 by the chain on every `MsgEvaluateClaim`. Gate 4
|
|
1020
|
+
holds a collection to your fee, matching the denom exactly. The coin differs per network:
|
|
1021
|
+
|
|
1022
|
+
- **mainnet** — real USDC: Noble USDC over IBC,
|
|
1023
|
+
`ibc/6BBE9BD4246F8E04948D5A4EEE7164B2630263B9EBB5E7DC5F0A46C62A2FF97B` (`transfer/channel-26/uusdc`,
|
|
1024
|
+
6 decimals, so `1000000` is 1 USDC);
|
|
1025
|
+
- **devnet and testnet** — any stand-in coin; the template uses `uixo`.
|
|
1026
|
+
|
|
1027
|
+
Set it per env in `wrangler.jsonc` with `EVALUATION_DENOM` and `EVALUATION_MIN_AMOUNT`. They replace
|
|
1028
|
+
`pricing.evaluation` from `defineOracle`, and `/v1/info` (and so `oracle init`'s Agent Card) shows the
|
|
1029
|
+
result. On mainnet they are **required**: without them the worker fails config validation at
|
|
1030
|
+
isolate start, so a stand-in written in code can never be charged there. Off mainnet they are
|
|
1031
|
+
optional, and a plain `pricing.evaluation` in code still works. A `pricing.byProtocol` entry still
|
|
1032
|
+
wins for its protocol. The collection's admin must still hold the evaluation fee plus the approval
|
|
1033
|
+
payment for every claim (`admin-unfunded`, §12). The coin you choose is the one the collection must
|
|
1034
|
+
be created with (`create-collection --eval-denom`).
|
|
1035
|
+
|
|
1036
|
+
The row's `ownerDid` is always the **chain owner** (never the registrant): the owner→oracle Matrix
|
|
1037
|
+
delegation the pull path needs is theirs to issue. The registrant is recorded beside it for the read
|
|
1038
|
+
gate. Re-registering is the "change my mind" path, and it also re-activates a paused collection.
|
|
1039
|
+
On a re-register only the chain owner or the current registrant may change `contactEmail` or
|
|
1040
|
+
resume a paused collection; any other grant holder gets `403` for either (their re-register still
|
|
1041
|
+
refreshes the fee and the delegation, and never touches the status). A `delegationCar` must be the
|
|
1042
|
+
owner's own root delegation (no proofs), **signed** by the owner's key — the Ed25519 key their
|
|
1043
|
+
`did:ixo` publishes on chain — or it is a `400`. The row keeps the owner it was registered with:
|
|
1044
|
+
after an entity transfer, the new owner re-registers to take over pause/resume.
|
|
1045
|
+
With no `contactEmail` sent and none on the row, register asks the engine for the collection's
|
|
1046
|
+
company (`GET /v1/collections/:id/company`) and defaults the contact to that company's `active`
|
|
1047
|
+
Decision API source (the oldest activated), so the collection notices reach someone. The company is
|
|
1048
|
+
the collection's entity, or, for a collection on a poster's deed, the org linked to that deed both
|
|
1049
|
+
ways. A `disputed` company, a collection the engine does not know, or an engine that cannot answer
|
|
1050
|
+
(its `503` while Blocksync is down, a network blip) leaves the contact empty and the register still
|
|
1051
|
+
succeeds (`register.company-unavailable` is logged); register again later to fill it. An explicit or
|
|
1052
|
+
stored contact is never overwritten.
|
|
1053
|
+
|
|
1054
|
+
The same answer — the engine is asked once per register — also **links** the collection to every
|
|
1055
|
+
`active` Decision API source of that company, so the organisation's reviewers see its chain claims
|
|
1056
|
+
in the Decision Console without a member linking each collection by hand (`POST
|
|
1057
|
+
/v1/api/source/collections`, which only the collection's chain owner can do). Only the engine's `ok`
|
|
1058
|
+
links; a `disputed` company, an unknown collection or an engine error links nothing and never fails
|
|
1059
|
+
the register. Such a link's `linkedBy` is the company's entity DID (no member made it), its audit row
|
|
1060
|
+
is `source.collection_linked` by `system:company-link`, and the hourly link re-check holds it to the
|
|
1061
|
+
engine's answer: it is dropped (`not_company`) once the engine names another company or `disputed`
|
|
1062
|
+
(or does not know the collection twice in a row). A member's link goes once its linker is confirmed
|
|
1063
|
+
no longer a member who may link or no longer the collection's chain owner. Only such a positive
|
|
1064
|
+
finding removes a link: a Blocksync or engine error, a timeout or a re-check that did not run keeps
|
|
1065
|
+
it, and the claim reads keep showing it (contracts 1.23.0; they used to hide a link not confirmed
|
|
1066
|
+
within 2 hours). The re-check runs on the hourly cron trigger.
|
|
1067
|
+
An existing link is left as it is. The engine is asked only when the answer could be used: no
|
|
1068
|
+
contact sent or stored, or an `active` source not yet linked to the collection.
|
|
1069
|
+
|
|
1070
|
+
Collections registered before register linked them get the same links from the operator's backfill:
|
|
1071
|
+
`bunx decision-oracle relink [--collection <id>]…` (`POST /v1/admin/collections/relink`, your DID in
|
|
1072
|
+
`ORACLE_ADMIN_DIDS`) — every watched collection, or those named, one engine read each; it prints each
|
|
1073
|
+
one's outcome and is safe to re-run.
|
|
1074
|
+
|
|
1075
|
+
**A real commissioner** grants you the Evaluate role on their collection (the portal's "Grant Role"
|
|
1076
|
+
flow) and registers it (the portal makes that call). Give them your `ORACLE_ADDRESS` and your host.
|
|
1077
|
+
|
|
1078
|
+
**To prove the loop yourself**, hire yourself with the same AuthHub session — the `create-collection`
|
|
1079
|
+
skill drives the whole sequence, and the two commands underneath it are:
|
|
1080
|
+
|
|
1081
|
+
```bash
|
|
1082
|
+
bunx decision-oracle create-deed --protocol <protocol did> --title "<a name>" [--yes]
|
|
1083
|
+
bunx decision-oracle create-collection --entity <deed did> --protocol <protocol did> \
|
|
1084
|
+
--eval-amount <base units> [--fund <base units>] [--evaluate <your ORACLE_ADDRESS>] [--yes]
|
|
1085
|
+
bunx decision-oracle register --collection <id> --delegation delegation.car
|
|
1086
|
+
bunx decision-oracle submit-claim --collection <id> --answers protocol/sample-claim.json --yes
|
|
1087
|
+
bunx decision-oracle scan --collection <id>
|
|
1088
|
+
```
|
|
1089
|
+
|
|
1090
|
+
Two things people get wrong here: a collection hangs off a **deed**, never off the protocol (the
|
|
1091
|
+
chain accepts `entity = protocol`, and then the portal says "No claim protocols are linked"); and
|
|
1092
|
+
the on-chain grant alone is not enough — without the owner→oracle **claim-bot** delegation
|
|
1093
|
+
(`delegation --for bot --audience did:ixo:<ORACLE_ADDRESS>`, passed as `--delegation`) the oracle
|
|
1094
|
+
holds the grant but still cannot pull claim bodies out of Matrix.
|
|
1095
|
+
|
|
1096
|
+
Everything under `create-deed` / `create-collection` signs **real** transactions and spends real
|
|
1097
|
+
funds. Dry-run first, read the plan, only then `--yes`. `--network` defaults to devnet; testnet and
|
|
1098
|
+
mainnet are always explicit.
|
|
1099
|
+
|
|
1100
|
+
---
|
|
1101
|
+
|
|
1102
|
+
## 12. Operating it
|
|
1103
|
+
|
|
1104
|
+
**The cron triggers.** Three, in `wrangler.jsonc` `triggers.crons`, each running only its own jobs —
|
|
1105
|
+
keep all three, or that tier's jobs never run:
|
|
1106
|
+
|
|
1107
|
+
| Trigger | Tier | Jobs |
|
|
1108
|
+
| --- | --- | --- |
|
|
1109
|
+
| `* * * * *` | minute | the claim scan; the engine-credits, admin-funds and payer-billing resumes; webhook deliveries and retries; review-batch emails; the backlog gauges |
|
|
1110
|
+
| `*/5 * * * *` | five | the AI-cost backfill; with a facilitator, the payment reconcile and the balance watch |
|
|
1111
|
+
| `0 * * * *` | hour | housekeeping; the payment-header seal; review and review-billing reminders; review expiry; review rooms (chain and API); the linked-collection re-check |
|
|
1112
|
+
|
|
1113
|
+
An unknown cron string is a `cron.unknown` log line and runs nothing. A provider that adds a
|
|
1114
|
+
trigger of its own handles it in its own `scheduled` before delegating the rest to the oracle's.
|
|
1115
|
+
|
|
1116
|
+
**The cron tick.** `CRON_COLLECTIONS_PER_TICK` (default `25`, see [api.md §11](./api.md#11-configuration))
|
|
1117
|
+
bounds how many watched collections one tick visits; a fleet larger than that rotates in over later
|
|
1118
|
+
ticks, least-recently-scanned first. Raise it for a large fleet. To act on one collection sooner
|
|
1119
|
+
than the next tick — e.g. the instant your own webhook sees a submission — call
|
|
1120
|
+
`scanCollection(env, collectionId)` from `@ixo/decision-oracle/runtime`, or hit
|
|
1121
|
+
`POST /v1/collections/:collectionId/scan`; both run the exact same discover-and-start pass the tick
|
|
1122
|
+
does, so a webhook and the cron racing on one claim can never double-start it.
|
|
1123
|
+
|
|
1124
|
+
**Watching a claim.** `GET /v1/claims?collectionId=<id>` lists newest-first with a keyset cursor;
|
|
1125
|
+
`GET /v1/claims/:claimId` is the full record — the ingest row, the decision (codes, scores,
|
|
1126
|
+
identifiers; never answer values), the receipt CID and JWS, the chain status and tx hash, and any
|
|
1127
|
+
human review. Both are UCAN-guarded and gated to the collection's current chain owner or a live
|
|
1128
|
+
evaluate-grant holder — the registrant only while it still holds its grant — and the single-claim
|
|
1129
|
+
read to the collection's live reviewers too, and to the claim's own submitter (its on-chain agent DID or
|
|
1130
|
+
address, who sees the decision and its public receipt — which names who reviewed — but never the
|
|
1131
|
+
review's answers). `chainStatus` is `pending` / `submitted` / `owner_submitted` / `blocked` / `skipped`, or
|
|
1132
|
+
**`held`** when your own `policies.act` hook chose to keep a decided verdict back instead of signing
|
|
1133
|
+
it immediately — a payout you mean to pool across several claims, say (`ActContext`/`ActDecision`,
|
|
1134
|
+
[api.md](./api.md#1-import-map)). `signEvaluation(env, claimId, amount?)` from
|
|
1135
|
+
`@ixo/decision-oracle/runtime` is the release pass that signs a `held` row later, for the amount you
|
|
1136
|
+
compute then. Any amount of yours — the hook's or the release pass's — stands only on a collection
|
|
1137
|
+
whose **owner** opted in: `PATCH /v1/collections/:id/settings` `{ operatorAmounts: "allowed" }`,
|
|
1138
|
+
callable by the collection's current chain owner alone. Tell that owner how you pick the amount
|
|
1139
|
+
with `policies.actSummary` (one plain sentence): `GET /v1/info` publishes it as
|
|
1140
|
+
`payouts.operatorAmount.summary`, next to `payouts.decisionToPay` (your `payouts` map), and the
|
|
1141
|
+
Portal shows both before the owner says yes. While it is `"off"` (the default) the lane
|
|
1142
|
+
pays its own default and logs `act.amount-ignored-not-opted-in`, so ask the owner first; the yes
|
|
1143
|
+
is that owner's, so a collection that changes hands is back to the default until the new owner
|
|
1144
|
+
says it again. `GET /v1/collections/:id/settings` (the owner again) answers what is set and
|
|
1145
|
+
whether it is in force now (`effective`), by the act lane's own reading. The chain caps every amount — the default too: the evaluate grant's
|
|
1146
|
+
`MaxCustomAmount`, which `create-collection` writes as `0<denom>` unless the owner passes
|
|
1147
|
+
`--max-custom-amount`, is checked against ANY non-empty evaluation amount (its denom must be
|
|
1148
|
+
listed), and the lane's default on an ordinary claim is the claim's own coins. So a collection
|
|
1149
|
+
whose claims carry coins needs a cap at least the largest claim amount in each denom, or its
|
|
1150
|
+
approvals fail at MsgExec; `0` fits only claims that carry none. `signEvaluation` is a provider-authority call, not just a release: it signs any deliverable row —
|
|
1151
|
+
`pending`, `blocked` or `held` — without consulting `policies.act`, so the hook that parked a claim
|
|
1152
|
+
never gets a second say over your own decision to sign it.
|
|
1153
|
+
|
|
1154
|
+
**Erasing a claim: the organisation's call, never the oracle's.** Your oracle never deletes an
|
|
1155
|
+
organisation's claim data on its own — no clock, no retention window, on either lane (owner,
|
|
1156
|
+
2026-10-11). A chain claim's answers shadow into two places your D1 holds: the fact transcripts on
|
|
1157
|
+
`decisions.facts` (an AI check's prompt, an external source's payload) and the duplicate registry's
|
|
1158
|
+
normalized text. The collection's **current chain owner** erases them with
|
|
1159
|
+
`DELETE /v1/claims/:claimId/body` (UCAN `claims/erase`; the enroller, grant holders and your own
|
|
1160
|
+
`ORACLE_ADMIN_DIDS` get `403`), or in bulk with `bunx decision-oracle erase --collection <id> --yes`
|
|
1161
|
+
signed as their session. The decision, its receipt and the digests stay, so the receipt still
|
|
1162
|
+
verifies and duplicates are still caught. An API source erases its claims with
|
|
1163
|
+
`DELETE /v1/api/claims/:claimId/body`, which also deletes the claim's uploads and empties its
|
|
1164
|
+
webhook payloads, and deletes an upload no claim uses with `DELETE /v1/api/uploads/:uploadId`.
|
|
1165
|
+
Either erase is `409` while the claim is still being decided; the CLI skips those and lists them.
|
|
1166
|
+
The hourly housekeeping only expires what is not the organisation's data —
|
|
1167
|
+
idempotency answers, OAuth approvals past their window, an export bundle after its day, a deleted
|
|
1168
|
+
source's files.
|
|
1169
|
+
|
|
1170
|
+
**Reviews.** A `review` verdict is a `pending_review` decision naming the tasks a human must answer.
|
|
1171
|
+
The oracle posts a card into the collection's Matrix review room and emails the registered contact.
|
|
1172
|
+
Answers come back to `POST /v1/reviews/:claimId` and must cover **exactly** the pending tasks; the
|
|
1173
|
+
`ReviewComplete` workflow then folds them into the *same* decision — a byte-identical re-pull, the
|
|
1174
|
+
frozen trace replayed, **no new AI calls and no new charges**. A re-POST before the fold replaces
|
|
1175
|
+
the answers and re-spawns (that is also how a `foldError` clears); after the fold it `409`s.
|
|
1176
|
+
|
|
1177
|
+
**Who answers: the company's reviewers.** Only a person the collection's company named may answer
|
|
1178
|
+
— never you (your identities are refused), never a holder of an Evaluate grant (that power already
|
|
1179
|
+
signs and pays; an evaluator does not also review), never the owner by being the owner. A current
|
|
1180
|
+
controller of the collection's entity grants a reviewer `claims/review` on the collection for at
|
|
1181
|
+
most 30 days, signed with their own `did:ixo` key (the Portal does this; by hand it is one
|
|
1182
|
+
`@ixo/ucan` delegation):
|
|
1183
|
+
|
|
1184
|
+
```ts
|
|
1185
|
+
import { createDelegation, serializeDelegation } from "@ixo/ucan";
|
|
1186
|
+
|
|
1187
|
+
const now = Math.floor(Date.now() / 1000);
|
|
1188
|
+
const grant = await createDelegation({
|
|
1189
|
+
issuer: controllerSigner, // a current controller of the collection's entity
|
|
1190
|
+
audience: "did:ixo:ixo1…", // the reviewer
|
|
1191
|
+
capabilities: [
|
|
1192
|
+
{ with: `ixo:oracle/did:web:${oracleHost}/collection/${collectionId}`, can: "claims/review" },
|
|
1193
|
+
],
|
|
1194
|
+
notBefore: now,
|
|
1195
|
+
expiration: now + 30 * 86_400, // at most 30 days after notBefore
|
|
1196
|
+
});
|
|
1197
|
+
// POST /v1/collections/<collectionId>/reviewers UCAN collections/reviewers-grant
|
|
1198
|
+
// { "delegation": await serializeDelegation(grant) }
|
|
1199
|
+
```
|
|
1200
|
+
|
|
1201
|
+
The oracle checks it the way it will check every answer (one link, the exact resource and ability,
|
|
1202
|
+
the window, a live controller as issuer, no oracle and no evaluator in it) and files it; the
|
|
1203
|
+
reviewer is invited into the review room at the next review request. The reviewer reads their own
|
|
1204
|
+
grant back from `GET /v1/collections/:id/reviewers` and sends it as `reviewerGrant` beside the
|
|
1205
|
+
answers and the attestation; the fold checks it again and forwards it, and the engine records the
|
|
1206
|
+
root in the receipt. `POST /v1/collections/:id/reviewers/:delegationCid/revoke` ends it at once: the
|
|
1207
|
+
reviewer is kicked from the room, and a pending answer under it fails its fold. So does a
|
|
1208
|
+
controller leaving the entity: their reviewers stop reading at once and leave the room at the next
|
|
1209
|
+
hourly pass. A grant that runs out is renewed by signing a new one. Send the CAR exactly as
|
|
1210
|
+
`serializeDelegation` wrote it (standard, padded base64), and never to the collection's owner —
|
|
1211
|
+
the owner already sits in the room.
|
|
1212
|
+
|
|
1213
|
+
**On the Decision API lane** (`POST /v1/api/claims/:claimId/review`) the reviewers are the source's
|
|
1214
|
+
console members, each signing with the `did:key` they registered — and the organisation's
|
|
1215
|
+
controllers, who need no membership. Every answer carries a `reviewerGrant` (the engine refuses
|
|
1216
|
+
the fold without one; the Decision Console signs it):
|
|
1217
|
+
|
|
1218
|
+
- a **member** sends two links: its current role grant from a controller (exactly the one the
|
|
1219
|
+
console stored for it — a `reviewer` or `developer` grant lasting at most 30 days), then its own
|
|
1220
|
+
grant from its `did:ixo` to its reviewer `did:key`, `claims/review` on
|
|
1221
|
+
`ixo:oracle/did:web:<oracleHost>/source/<sourceId>`, at most 30 days;
|
|
1222
|
+
- a **controller** answers in its own `did:ixo` (the attestation's `kid` a key on its DID
|
|
1223
|
+
document) and sends one delegation to ITSELF, `claims/review` on the source, signed with the same
|
|
1224
|
+
key, `notBefore` and `expiration` at most 30 days apart.
|
|
1225
|
+
|
|
1226
|
+
An answer without the grant is `400 REVIEWER_GRANT_INVALID`. The fold first re-checks the
|
|
1227
|
+
reviewer (a member revoked, narrowed or re-granted since, or a controller removed, is a `foldError`
|
|
1228
|
+
`REVIEWER_NOT_AUTHORIZED` and nothing is forwarded), then forwards the grant, and the engine
|
|
1229
|
+
re-checks, live, that its root still controls the organisation.
|
|
1230
|
+
|
|
1231
|
+
**Who is emailed about a claim waiting for review.** Every accepted member whose live grant carries
|
|
1232
|
+
`claims/review` over the claim's protocol, with an address and `notifyReviews` on, gets the
|
|
1233
|
+
review-needed email at once — at most one per member per source per 15 minutes: claims that arrive
|
|
1234
|
+
inside the window go out together as one email when it ends — and the 3- and 14-day reminders. The
|
|
1235
|
+
source's `contactEmail` gets the same three emails, on its own 15-minute window (an organisation
|
|
1236
|
+
admin needs no member row to hear of a claim), unless a member with that same address
|
|
1237
|
+
(case-insensitive) is already sent every claim it would be. The source's Matrix review room takes
|
|
1238
|
+
every review-capable member.
|
|
1239
|
+
|
|
1240
|
+
**A held duplicate shows what it matched.** A rubric's `unique` rule rejects at `rejectAtBps` and
|
|
1241
|
+
holds for review between `reviewAtBps` and it (the same file bytes again score 8800, so with the
|
|
1242
|
+
common 5000/9000 bands a reused photo is held, not rejected; the same identity key — or the same
|
|
1243
|
+
answers — from the SAME submitter scores 10000 and rejects, from another submitter 8800 and is
|
|
1244
|
+
held; on the Decision API lane the submitter is the claim's `submitter`). The pending task is
|
|
1245
|
+
`unique:<ruleId>`, and on the Decision API lane the reviewer view
|
|
1246
|
+
(`GET /v1/api/claims/:claimId/review`) carries `duplicates: [{ taskId, ruleId, score?, prior }]` —
|
|
1247
|
+
the engine's score in basis points and the earlier claim it matched: `prior.outcome` (this oracle's
|
|
1248
|
+
decision on it, `null` when none) and, only when the earlier claim is the same source's under the
|
|
1249
|
+
same protocol, `prior.claimId`, `prior.submittedAt` and `prior.files` (the shared files, else its
|
|
1250
|
+
images, signed like the view's own). Another organisation's claim never shows its id or files.
|
|
1251
|
+
Nothing to configure: the oracle stores the engine's `pendingTaskDetails` with the decision. The
|
|
1252
|
+
chain lane's `GET /v1/reviews/:claimId` does not carry it. Reference: `api.md`, "What a held
|
|
1253
|
+
duplicate matched".
|
|
1254
|
+
|
|
1255
|
+
**Pauses.** Seven reasons, five of which lift themselves:
|
|
1256
|
+
|
|
1257
|
+
| Reason | What happened | Who lifts it |
|
|
1258
|
+
| --- | --- | --- |
|
|
1259
|
+
| `engine-credits` | The owner's credits ran out — **every** watched collection pauses | The billing cron, once the engine says `ok` |
|
|
1260
|
+
| `admin-unfunded` | The collection's admin account cannot pay what a `MsgEvaluateClaim` draws from it — the evaluation payment, plus the approval payment on an ordinary approve (an intent claim's is escrowed) or the rejection payment on a reject; the chain fails the whole message unless every leg is covered. Decisions are recorded but parked `blocked` | The funding cron, once the balance covers the largest single parked delivery, per denom — and it re-delivers the parked decisions |
|
|
1261
|
+
| `engine-no-grant` | Your Evaluate authorization is gone | The owner grants again, then re-registers |
|
|
1262
|
+
| `rubric-unusable` | The anchored rubric no longer binds to its claim form. Every claim would be refused the same way, so a broken catalog entry is paid for at most once | Fix the rubric or `#vct`, then `POST /v1/collections/:id/resume` |
|
|
1263
|
+
| `payer-credits` | Per-review billing: the paying organisation's credits (or its send permission) did not cover a review's reservation | The payer cron, once a top-up covers a review — the claims that gave up meanwhile are restarted |
|
|
1264
|
+
| `oracle-plan` | Per-review billing: IXO's plan for your oracle no longer allows it (the facilitator or the engine said so). IXO is paged; the owner is not told | The payer cron, once IXO's plan allows it again |
|
|
1265
|
+
| `payer-delegation` | Per-review billing: nobody can pay — no live delegation, expired, used up, or signed by someone who no longer owns the organisation | A new delegation in the Decision Console: its `PUT` resumes the organisation's collections at once (the cron otherwise) |
|
|
1266
|
+
|
|
1267
|
+
Each pause tells the owner once, with the action to take (the `admin-unfunded` notice names the
|
|
1268
|
+
account and the full amount the failed claim needs — evaluation plus its verdict's payment, per
|
|
1269
|
+
denom), and each recovery sends the paired "resumed" notice. An operator's own `POST …/pause` has
|
|
1270
|
+
no reason and no cron ever lifts it. `POST …/pause` and `POST …/resume` take the
|
|
1271
|
+
chain owner, the registrant (while it still holds a live Evaluate grant) or an `ORACLE_ADMIN_DIDS`
|
|
1272
|
+
invoker; any other grant holder can read the collection but gets `403` here, so a competing oracle
|
|
1273
|
+
hired on it can neither stall it nor lift a stop.
|
|
1274
|
+
|
|
1275
|
+
**Notices are delivery, never judgement.** Matrix and email are both fail-soft and idempotent: an
|
|
1276
|
+
unset `ORACLE_MATRIX_ACCESS_TOKEN` or `EMAIL_FROM_ADDRESS` turns its lane into a logged skip, and no
|
|
1277
|
+
claim is ever blocked on one.
|
|
1278
|
+
|
|
1279
|
+
**A Decision API source that runs low is told.** Give a source a `refillFloorCents` and, with a
|
|
1280
|
+
facilitator configured, the balance watch (every 5 minutes) sends a `balance.low` webhook the
|
|
1281
|
+
first time the payer's balance drops below it, and emails the source's `contactEmail` once per UTC
|
|
1282
|
+
day while it stays below (a send that fails is retried on the next check): the balance against the floor in dollars, and an "Add credits" link to your console's
|
|
1283
|
+
Credits page (`CONSOLE_URL/credits`). Without `CONSOLE_URL` or a sender, the email is a logged skip.
|
|
1284
|
+
|
|
1285
|
+
**Uptime and status.** Point your uptime monitor (Sentry Uptime, or any HTTP checker, every
|
|
1286
|
+
minute) at `GET /health/ready`, not `/health`: `/health` only proves the worker boots, while
|
|
1287
|
+
`/health/ready` answers `200 { "ok": true }` when D1, KV and the engine (and, with `API_LANE` and
|
|
1288
|
+
`API_PAYMENTS` on, the facilitator) all answer within 2 s and a deployed oracle has its on-chain
|
|
1289
|
+
identity (`ORACLE_ENTITY_DID`, from `oracle init`) and an `OWNER_DELEGATION_CAR`, when set, that
|
|
1290
|
+
parses (`owner-delegation`: a broken one would refuse every review answer), and `503 { "ok": false }` otherwise. It names nothing else, is rate-limited per IP like the API, and is cached for 15 s, so a
|
|
1291
|
+
monitor cannot load your dependencies. To see *which* check failed, list your own `did:ixo` in `ORACLE_ADMIN_DIDS` and
|
|
1292
|
+
call `GET /v1/admin/status` with a UCAN from it: the per-check `{ ok, ms }` (a failed `identity` or `owner-delegation` adds a `detail` naming the command to run), the backlog (reviews
|
|
1293
|
+
pending, paused collections — and with `API_LANE` on paused sources and due webhooks — by reason)
|
|
1294
|
+
and the engine's billing status for this oracle (`needs_funds` means your credits ran out). To let
|
|
1295
|
+
a monitoring service read the same status — e.g. your decision engine's status board, identified by
|
|
1296
|
+
its DID — list its DID (any method, `did:web:…` included) in `STATUS_READER_DIDS`: it can read
|
|
1297
|
+
`GET /v1/admin/status` and nothing else. Never put a monitor in `ORACLE_ADMIN_DIDS` for this —
|
|
1298
|
+
operators also gate write routes. With both unset that route is `403` for everyone.
|
|
1299
|
+
|
|
1300
|
+
**Errors and alerts.** Both are yours to wire and both are optional — unset, the oracle only logs.
|
|
1301
|
+
`SENTRY_DSN` (a secret) turns on Sentry: every failure a lane swallows by design (a notify, a chain
|
|
1302
|
+
write, a webhook retry job) is still reported, codes and ids only — answers, facts, tokens and the
|
|
1303
|
+
API lane's personal fields are redacted there and in every log line. `SENTRY_TRACES_SAMPLE_RATE`
|
|
1304
|
+
(default `0.1`) and `SENTRY_ENVIRONMENT` (default `ENVIRONMENT`) tune it. The cron triggers check in
|
|
1305
|
+
to Sentry cron monitors: `oracle-scan` and — only with `API_LANE` on — `oracle-api-lane` every
|
|
1306
|
+
minute, `oracle-cron-five` every five minutes and `oracle-cron-hour` every hour; alert
|
|
1307
|
+
on a missed check-in and a stopped cron pages you. The conditions that need a person — a collection
|
|
1308
|
+
or API source paused (per reason), a webhook receiver that refused all 8 attempts, a paid claim whose
|
|
1309
|
+
settlement was lost or whose receipt the facilitator refused, a facilitator that refuses the
|
|
1310
|
+
oracle itself (not allow-listed, a clock) or its offers (`price_mismatch` / `plan_inactive` — every
|
|
1311
|
+
paid submit answers `503` until fixed), a paid review whose reservation could not be extended,
|
|
1312
|
+
a failing API-lane cron job or auto-resume — are each one Sentry issue, and, with
|
|
1313
|
+
`OPS_ALERT_WEBHOOK_URL` (a secret: a Slack-compatible incoming webhook) set, one Slack message per
|
|
1314
|
+
condition per 30 minutes (Sentry is deduped the same way).
|
|
1315
|
+
|
|
1316
|
+
**Sentry in ten minutes (one project is all you need).**
|
|
1317
|
+
|
|
1318
|
+
1. In Sentry create **one project** for your oracle (platform: Cloudflare Workers) and copy its
|
|
1319
|
+
**DSN** (Project Settings → Client Keys). A DSN can only send events to that project, nothing more.
|
|
1320
|
+
2. `wrangler secret put SENTRY_DSN [--env testnet|production]` for each environment you deploy.
|
|
1321
|
+
Optional: `wrangler secret put OPS_ALERT_WEBHOOK_URL` with a Slack incoming webhook, so pages
|
|
1322
|
+
also post straight to a channel.
|
|
1323
|
+
3. Deploy. The cron monitors (`oracle-scan`, `oracle-cron-five`, `oracle-cron-hour`, and
|
|
1324
|
+
`oracle-api-lane` with `API_LANE` on) appear by themselves on their first run.
|
|
1325
|
+
4. Add these alerts in Sentry (Alerts → Create Alert), each notifying you (email, or Slack if you
|
|
1326
|
+
installed Sentry's Slack integration):
|
|
1327
|
+
|
|
1328
|
+
| Alert | Type | Setting |
|
|
1329
|
+
| --- | --- | --- |
|
|
1330
|
+
| Something needs a person | Issue alert | *A new issue is created* **and** the event's tag `alert` is set — these are the pages listed above |
|
|
1331
|
+
| Any new error | Issue alert | *A new issue is created* (frequency: once per issue) |
|
|
1332
|
+
| A cron stopped | Cron monitor | on each monitor: notify on a missed or failed check-in |
|
|
1333
|
+
| The oracle is down | Uptime monitor | `GET https://<your oracle>/health/ready`, every 1 minute, alert after 3 failures |
|
|
1334
|
+
|
|
1335
|
+
That is the whole setup; the oracle decides what is page-worthy, you only choose where it goes.
|
|
1336
|
+
|
|
1337
|
+
**Metrics.** Every metric is a structured log line (`msg = "metric"`) in Workers Logs, always.
|
|
1338
|
+
For percentiles, bind `METRICS` to a Workers Analytics Engine dataset (`analytics_engine_datasets`
|
|
1339
|
+
in `wrangler.jsonc`, one dataset per environment — the template has it commented out); unbound,
|
|
1340
|
+
nothing breaks and the log lines stay at `info`, bound they drop to `debug` because the dataset
|
|
1341
|
+
carries them. Each data point has one fixed shape: `index1`/`blob1` the metric name, `blob2` op,
|
|
1342
|
+
`blob3` route, `blob4` status, `blob5` outcome, `blob6` network (`IXO_NETWORK`), `blob7` a
|
|
1343
|
+
low-volume id or `-`, `double1` the value, `double2` the milliseconds. The names: `http.request`
|
|
1344
|
+
(op = method, route = the endpoint pattern — never the raw path — or `unmatched`), `upstream` (op =
|
|
1345
|
+
the host, or `rpc` with the call name as route, for chain calls; outcome `ok` / `http_error` /
|
|
1346
|
+
`network_error` / `timeout`, or `error` for rpc), `cron.tick` (op = the job), `cache` (op = the
|
|
1347
|
+
cache; `hit` / `miss` / `error`), `errors` (status = the error code), plus the lanes' own counters.
|
|
1348
|
+
Weight by `_sample_interval` when you query: p95 is
|
|
1349
|
+
`quantileExactWeighted(0.95)(double2, _sample_interval)`, a count is
|
|
1350
|
+
`sum(_sample_interval * double1)`. At most 250 points per request, tick or workflow run.
|
|
1351
|
+
|
|
1352
|
+
The domain metrics, by the same slots (`-` = unused):
|
|
1353
|
+
|
|
1354
|
+
| Name | Type | op | route | status | outcome | id |
|
|
1355
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
1356
|
+
| `api.decision.latency` | timing — accept → decision row; **the SLO** | lane `api` / `chain` | - | `ai` / `no_ai` | verdict | protocol DID |
|
|
1357
|
+
| `engine.call` | timing | `decide`, `decision`, `review`, `manifest`, `me`, `register`, `agreements`, `report`, `preview`, `stats` | - | HTTP status or `network` | `ok`, the engine's error code (`unknown` if it is not a plain short code), or `unavailable` | - |
|
|
1358
|
+
| `claim.pull` | timing, per store read | `blocksync`, `vfs`, `vfs-public`, `matrix`, `ipfs`, `cellnode` | - | - | `ok` (found or a miss) / `error` | - |
|
|
1359
|
+
| `facts.handler` | timing, per handler call | `ext-source` / `ai-check` (stock), `custom`, `group`, `unique`, `frequency` | fact name (a group: its names; a rule: its id) | - | `ok` / `error` / `cached` | protocol DID |
|
|
1360
|
+
| `ai.tokens` | count, summed per model per facts build | model | `prompt` / `completion` | - | the provider (`cloudflare` / `openrouter`) | protocol DID |
|
|
1361
|
+
| `ai.cost_micro_usd` | count — OpenRouter: summed per model per facts build; Cloudflare AI Gateway: per call, when the `ai-cost-backfill` cron job (every fifth minute) reads the USD off the gateway's log | model | - | - | the provider | protocol DID (OpenRouter only) |
|
|
1362
|
+
| `workflow.step` | timing, the step's own work (a replayed step records nothing) | `claim-ingest` / `review-complete` | step name | attempt | `ok` / `error` | - |
|
|
1363
|
+
| `ingest.terminal` | count | lane | - | - | failure class (`invalid_facts`, `engine_rejected`, `body_mismatch`, `retries_exhausted`) | - |
|
|
1364
|
+
| `chain.broadcast` | timing | - | - | tx code, `-` if it threw | `ok` / `error` | - |
|
|
1365
|
+
| `webhook.post` | timing | - | - | HTTP status, `network`, or `redirects` | `delivered` / `failed` / `dead` | source id |
|
|
1366
|
+
| `webhook.dead` | count | - | - | - | - | source id |
|
|
1367
|
+
| `facilitator.call` | timing | `offers`, `verify`, `settle`, `settlement`, `extend`, `balances` | - | HTTP status or `network`; `settlement_pending` for PAY's normal settle | `ok`, `error`, or the refusal code (a `/verify` `invalidReason` or `/settle` `errorReason` too — both answer HTTP 200) | - |
|
|
1368
|
+
| `api.submit` | count, per claim taken | `sync` / `async` (a `Prefer: wait` above 0, or none / `wait=0`), `batch` | - | - | - | - |
|
|
1369
|
+
| `api.wait` | count, per wait asked (above 0) | `submit` / `get` | - | - | `hit` (the decision landed in time) / `miss` | - |
|
|
1370
|
+
| `api.402` | count, every 402 answered | `first_leg` (x402's opening `PAYMENT_REQUIRED`, no `PAYMENT-SIGNATURE` sent) / `refused` | - | `PAYMENT_REQUIRED` / `SPEND_CAP` / `INSUFFICIENT_CREDITS` | - | - |
|
|
1371
|
+
| `api.idempotent_replay` | count, per repeat answered | `submit` / `batch` (a claim's repeat), `other` (any other keyed route's stored answer) | - | - | - | - |
|
|
1372
|
+
| `api.rate_limited` | count, per key/source limit hit | - | the endpoint pattern | - | - | - |
|
|
1373
|
+
| `ucan.wildcard` | count, per chain-lane call still signing the legacy `can: "*"` | - | `<METHOD> <route pattern>` | - | - | - |
|
|
1374
|
+
|
|
1375
|
+
`api.decision.latency` starts at the API claim's `receivedAt`, or when a chain claim's ledger row
|
|
1376
|
+
was written; `ai` means the protocol's rubric declares AI checks. The backlog gauges are written
|
|
1377
|
+
once per minute tick: `review.pending` (decisions awaiting a reviewer) and `collections.paused`, and
|
|
1378
|
+
with `API_LANE` on `backlog.webhook.due`, `backlog.webhook.oldest_age_s` and `sources.paused` — and,
|
|
1379
|
+
with a facilitator configured, every 5 minutes after the payment reconcile,
|
|
1380
|
+
`backlog.charge.unsettled.oldest_age_s` (how long the oldest decided claim with a receipt has waited
|
|
1381
|
+
for the facilitator to accept it; alert above 10 minutes). The
|
|
1382
|
+
paused gauges have one point per reason that has any (outcome = the reason) plus an outcome `total`
|
|
1383
|
+
point that is always written.
|
|
1384
|
+
|
|
1385
|
+
**Correlation.** A claim accepted over HTTP starts its workflow with the request's `traceparent`
|
|
1386
|
+
and `requestId`; a claim the scan found, with the tick's trace. Every log line of the run carries
|
|
1387
|
+
that `traceId` plus `claimId`, `instanceId` and `workflow`, and the engine call sends the same
|
|
1388
|
+
`traceparent` — one `traceId` finds the accept, the run, the engine's decision and the run's
|
|
1389
|
+
inline webhook attempt together (a later retry from the cron logs under the tick's trace).
|
|
1390
|
+
|
|
1391
|
+
**Traces** (optional). Workers Traces show where a request's or workflow step's time goes: the
|
|
1392
|
+
platform records a span for every D1, KV, fetch and Durable Object call, and the facts runner adds
|
|
1393
|
+
one `facts.handler` span per handler call, tagged like the metric (`op`, `route`, `id`). They are
|
|
1394
|
+
off in the template: uncomment `observability.traces` in `wrangler.jsonc` (top level for devnet,
|
|
1395
|
+
and the commented per-env `observability` block for testnet and production — an env's block
|
|
1396
|
+
replaces the top-level one whole), and set `head_sampling_rate` to the share of requests to trace
|
|
1397
|
+
(the template suggests 1 / 0.2 / 0.05). From 2026-10-01 Cloudflare bills every span as one
|
|
1398
|
+
Workers Observability event, the same quota as log lines (Workers Paid: 20 million a month
|
|
1399
|
+
included, then $0.60 per million), so start low and check the volume after a week. Traces go to
|
|
1400
|
+
the Cloudflare dashboard; to also send them to an OpenTelemetry backend, create an OTLP
|
|
1401
|
+
destination in the dashboard (Workers Observability → Destinations) and name it in
|
|
1402
|
+
`observability.traces.destinations` — no code.
|
|
1403
|
+
|
|
1404
|
+
**The answer archive** (optional). Bind an R2 bucket as `ANSWERS_ARCHIVE` and the oracle keeps
|
|
1405
|
+
a copy of everything it evaluated — the ask, every answer, every photo — in your own account, so
|
|
1406
|
+
the data outlives the stores it was pulled from. No binding, no archive; there is no flag.
|
|
1407
|
+
|
|
1408
|
+
```jsonc
|
|
1409
|
+
// wrangler r2 bucket create my-oracle-answers (one bucket per env, redeclared in each env block)
|
|
1410
|
+
"r2_buckets": [{ "binding": "ANSWERS_ARCHIVE", "bucket_name": "my-oracle-answers" }]
|
|
1411
|
+
```
|
|
1412
|
+
|
|
1413
|
+
One folder per ask, named by the ask's id and its title (`<askId>` is the chain collection's id,
|
|
1414
|
+
or an API scope's `api:<sourceId>:<protocolDid>`; the slug is lowercase ASCII and dashes, at most
|
|
1415
|
+
60 characters; no title ⇒ the folder is `<askId>`). One oracle serves one network, so the network
|
|
1416
|
+
is not in the path:
|
|
1417
|
+
|
|
1418
|
+
```
|
|
1419
|
+
<askId>-<slug>/about.json what the ask was about, as people saw it: title, question,
|
|
1420
|
+
kind, place, reward, postedBy, opensAt/closesAt, askId,
|
|
1421
|
+
proof {entity, collection}
|
|
1422
|
+
<askId>-<slug>/<claimId>/answer.json the Q&A record: askId, question, answer {text, files,
|
|
1423
|
+
answeredAt, place}, verdict, why, checks, paid,
|
|
1424
|
+
answeredBy, receipt {cid}, archivedAt
|
|
1425
|
+
<askId>-<slug>/<claimId>/claim.json the claim body verbatim (a chain claim's exact stored bytes)
|
|
1426
|
+
<askId>-<slug>/<claimId>/<n>-<name> each attachment's bytes, unchanged (EXIF and GPS included)
|
|
1427
|
+
```
|
|
1428
|
+
|
|
1429
|
+
`about.json` is written once, by the ask's first archived claim, from the ask's own public
|
|
1430
|
+
documents: with no hook it carries the deed's `#pro` name as the title (else its `alsoKnownAs`), the
|
|
1431
|
+
collection's window as `opensAt`/`closesAt` and its approval payment as `reward`, and nothing else.
|
|
1432
|
+
Your `archive` hooks in `defineOracle` fill the rest ([api.md §2](./api.md#oracleconfig)):
|
|
1433
|
+
`describe(ask)` gets the collection, the deed's documents as they are (`#pro`, `#done`, … parsed)
|
|
1434
|
+
and `docsOf(did)` for another entity's, and returns any part of the view; `answer({ body })` returns
|
|
1435
|
+
the answer's `text` and exact `place`. These are the ask's own documents, never the rubric.
|
|
1436
|
+
|
|
1437
|
+
In `answer.json`, `why` is the engine's reason lines (else its explanation), `checks` one entry
|
|
1438
|
+
per frozen `ext`/`ai` fact (`pass`, `fail`, or `not-run` with the error), and `paid` is `no` for a
|
|
1439
|
+
rejection, `pending` until the act lane signs an approved verdict on chain, then `yes`. It is
|
|
1440
|
+
written inside the `evaluate` step once the decision has landed (both lanes), re-written by the
|
|
1441
|
+
review fold, and re-written with `paid: yes` after the chain write (an `archive-paid` step).
|
|
1442
|
+
Attachments are written once, byte for byte; one over 32 MB, or one that cannot be
|
|
1443
|
+
read, is listed under `answer.filesNotCopied` and not copied. Nothing is dropped: `claim.json` keeps
|
|
1444
|
+
the whole body. Every object carries R2 custom metadata — `askId`, `title` (≤ 100 characters),
|
|
1445
|
+
`kind`, `place` (its name), `oracle` (your `ORACLE_HOSTNAME`), and on a claim's objects `answeredAt`
|
|
1446
|
+
(the date) and, on `answer.json` and `claim.json`, `verdict` — so a listing with
|
|
1447
|
+
`include: ["customMetadata"]` reads an ask without opening it. Archiving is fail-soft — an R2
|
|
1448
|
+
failure is a Sentry issue and an `archive.failed` log line, never a failed or retried evaluation —
|
|
1449
|
+
so a claim whose archive failed simply stays out of it. There is no index: list the bucket by
|
|
1450
|
+
prefix to find an ask's answers.
|
|
1451
|
+
|
|
1452
|
+
**Privacy.** The archive holds what claimants sent: exact locations, times and photos. Keep the
|
|
1453
|
+
bucket private (no public bucket, no `r2.dev` URL, no custom domain), and grant its API tokens
|
|
1454
|
+
sparingly. It sits outside the oracle's own erasure: the Decision API lane's retention sweep does
|
|
1455
|
+
not reach it, so a deletion request is yours to honour by deleting the claim's folder
|
|
1456
|
+
(`<askId>-<slug>/<claimId>/`). The API lane's `submitter` is stored as its
|
|
1457
|
+
erasure-proof digest, never the raw value.
|
|
1458
|
+
|
|
1459
|
+
**Operator recovery** is the Cloudflare dashboard: rerun an errored `ClaimIngest` instance (its
|
|
1460
|
+
params are retained) and the ledger converges. The handlers run again: the fact cache covers a step's
|
|
1461
|
+
own retries, and a terminal failure clears it.
|
|
1462
|
+
|
|
1463
|
+
---
|
|
1464
|
+
|
|
1465
|
+
## 13. Troubleshooting
|
|
1466
|
+
|
|
1467
|
+
| Symptom | Cause | Fix |
|
|
1468
|
+
| --- | --- | --- |
|
|
1469
|
+
| `401` on any `/v1` call | The invocation's audience is not `did:web:<ORACLE_HOSTNAME>`, or `/.well-known/did.json` is not served from that host | Make `ORACLE_HOSTNAME`, the Custom Domain and the audience the same string. Check `SERVICE_ED25519_MNEMONIC` is set |
|
|
1470
|
+
| `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) |
|
|
1471
|
+
| `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 |
|
|
1472
|
+
| `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 |
|
|
1473
|
+
| `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 |
|
|
1474
|
+
| `⚠ domain indexer refused …` / `unreachable` at the end of `oracle init` | The domain indexer is a nice-to-have submission, not part of the chain state | Nothing — the exit code is untouched and the entity is already on chain. Re-run later if the portal listing looks stale |
|
|
1475
|
+
| The portal resolves your oracle at an old host | `#eval` still points where the last `oracle init` put it — a redeploy does not move it | Re-run `oracle init --protocol <protocol did> --kit-url https://<the new host> --yes`: the `service` step deletes and re-adds `#eval` in one transaction |
|
|
1476
|
+
| `402` at register | The **owner's** credits, not a bug: `NO_SUBSCRIPTION` or `INSUFFICIENT_CREDITS` (with a `manageSubscriptionUrl`) — or the collection's `Payments.evaluation` is below your `pricing.evaluation.minAmount` | Owner buys credits at the printed URL, or the collection's admin raises the evaluation payment. Then register again |
|
|
1477
|
+
| `403` at register | Gate 1: the caller is neither the collection entity's chain owner nor a holder of an active Evaluate grant | Register as the owner, or have them grant the caller first |
|
|
1478
|
+
| `409` at register | Gate 2 (this oracle holds no Evaluate grant) or gate 3 (the protocol is not in `supportedProtocols`) | Grant `ORACLE_ADDRESS` the Evaluate role; or add the protocol DID to `supportedProtocols` and redeploy |
|
|
1479
|
+
| `409` on `POST …/scan` | The collection is paused | Resume it — and read `pausedReason` first |
|
|
1480
|
+
| `409` on `POST /v1/reviews/:claimId` | Already folded | Read `GET /v1/reviews/:claimId` for the stored answers |
|
|
1481
|
+
| `400 REVIEWER_GRANT_INVALID` on `POST /v1/reviews/:claimId`, or as a `foldError` | No `reviewerGrant`, or one that is not the reviewer's, is past its window or over 30 days, or names another collection or ability | The reviewer fetches their grant from `GET /v1/collections/:id/reviewers` (or asks a controller for a new one) and answers again |
|
|
1482
|
+
| `403 REVIEWER_NOT_AUTHORIZED`, on the answer or as a `foldError` | The grant was revoked or never filed, its signer is no longer a controller of the collection's entity, the reviewer (or signer) holds an Evaluate grant on the collection, or either is this oracle's owner (`oracle-owner`: the account that signed `OWNER_DELEGATION_CAR`) | A current controller who neither evaluates nor owns the oracle grants a reviewer who does not evaluate; the reviewer answers again |
|
|
1483
|
+
| A decision comes back `unusable` | The engine cannot bind the rubric to the live claim form (no `#rub`, swapped bytes, a drifted `#vct`, an unbindable ref). Recorded once, then the collection pauses `rubric-unusable` | Fix the anchored rubric or form, then resume. `protocol review` prints the exact issues |
|
|
1484
|
+
| `invalid_facts` in the ledger, and the ingest failed loudly | Your table's answer misses the work order — see the `FACT_*` codes in [api.md §9](./api.md#coverage-factissue-codes) | `facts run` reproduces it locally; the issue names the path and the reason |
|
|
1485
|
+
| `content_mismatch` / `payload_too_large` in the ledger, the ingest failed, `engine.content-mismatch` paged | The engine would not bind the decision to the claim's content: the answers, attachments or agent you froze are not the claim's (`details.reason` is in the row's explanation), or the body is over its 6 MiB cap | Nothing to retry — the answers are the claim's or they are not. A mismatch on a chain claim is a bug on one side (the bytes are CID-verified): report it with the claim id. Never re-send edited answers |
|
|
1486
|
+
| `RUBRIC_PIN_MISMATCH` | The `#rub` was re-anchored between the manifest read and the decide call | Nothing to do — the workflow invalidates the cached manifest, re-reads and rebuilds once |
|
|
1487
|
+
| `RECEIPT_MISSING` on a failed ingest or as a `foldError`, `engine.receipt-missing` paged | The engine answered a final decision or fold without its receipt (its mint failed), and kept doing so through every retry. A receipt-less decision is never recorded: the receipt is the one credential the chain write and the payout spend, so each retry asks the engine again until its stored row comes back with the receipt | Report it to IXO with the claim id. Once the engine mints again, rerun the `ClaimIngest` instance or re-POST the review |
|
|
1488
|
+
| `NO_EVALUATE_GRANT` from the engine | You are not (or no longer) hired for that collection | The owner grants again; then re-register to clear the `engine-no-grant` pause |
|
|
1489
|
+
| `Invalid environment configuration: ORACLE_HOSTNAME: …` at boot | A required var is missing or malformed. Config is validated at isolate start, deliberately | The message names the var. Every var is documented in [api.md §11](./api.md#11-configuration) |
|
|
1490
|
+
| `tools.env: this build carries no worker env` | `tools.env` was read in a test or a `facts run` dry run, where there is no worker env | Pass `fakeTools({ env: { … } })` in tests; in a dry run, read the value from `tools.config` or guard on it |
|
|
1491
|
+
| `defineOracle() was already called` | Two different config objects in one isolate | There is one oracle per worker. In tests, go through a shared helper rather than calling `defineOracle` twice |
|
|
1492
|
+
| `UNIQUE_HMAC_KEY not configured` | The rubric declares a `unique` rule and the secret is unset | Set it — one long random string, **for the life of the oracle**: rotating it orphans every registered digest |
|
|
1493
|
+
| `AI binding / AI_GATEWAY_ID not configured` or `OPENROUTER_API_KEY not configured` | An AI check (or the duplicate judge) ran without its provider's pieces. Loud on purpose, so the step retries rather than freezing a verdict | Add the `ai` binding + `AI_GATEWAY_ID` (or set the key with `AI_PROVIDER=openrouter`), or turn `policies.allowAiChecks` off deliberately |
|
|
1494
|
+
| `ai.credits-exhausted` alert; AI facts erroring (`Insufficient AI Gateway credits`) | The Cloudflare account ran out of AI Gateway credits | Load credits: dashboard → AI → AI Gateway → Credits. Affected claims went to their `onError` |
|
|
1495
|
+
| Audio/video attachment judged as "content not sent: audio/video inputs are off" | `AI_AUDIO_VIDEO` is off (the default) | Set `AI_AUDIO_VIDEO=true` with `AI_PROVIDER=openrouter` — the gateway route cannot read them |
|
|
1496
|
+
| A `.dev.vars` edit made no difference | `wrangler dev` reloads source, never secrets | Restart it |
|
|
1497
|
+
| A wasm / bundle error on `deploy` or `dev` | Usually a stale `worker-configuration.d.ts`, or the package's wasm not copied after an install | `bun run cf-typegen`, then reinstall. `wrangler deploy --dry-run` (`bun run build`) reproduces a bundle failure without shipping |
|
|
1498
|
+
| `facts run` says a handler is `handler-unused` | The name in your table is not a name the manifest declares | Copy the name from `protocol review`, exactly |
|
|
1499
|
+
| `facts run --preview` says to start `bun run dev` | The engine resolves your `did:web` from `ORACLE_HOSTNAME` and found nothing there | Start the dev server (or point `ORACLE_HOSTNAME` at a reachable host) |
|
|
1500
|
+
| A decision is `decided` but never lands on chain | `policies.autoSign` or `AUTO_SIGN` is off · `ORACLE_SECP_MNEMONIC` is unset · `chainStatus` is `blocked` (no grant, no receipt CID, or an unfunded admin account) · `chainStatus` is `held` (your own `policies.act` hook parked it on purpose) | Read `chainStatus` / `chainError` on `GET /v1/claims/:claimId`. An unfunded admin account resumes and re-delivers by itself once topped up; a `held` row waits for your own `signEvaluation(env, claimId, amount?)` call |
|
|
1501
|
+
|
|
1502
|
+
---
|
|
1503
|
+
|
|
1504
|
+
## 14. For AI agents working in a generated project
|
|
1505
|
+
|
|
1506
|
+
Six skills ship with the package and are copied into `.claude/skills/` by `init` (refresh with
|
|
1507
|
+
`bunx decision-oracle skills sync`). Invoke the one that matches the job rather than improvising:
|
|
1508
|
+
|
|
1509
|
+
| Job | Skill |
|
|
1510
|
+
| --- | --- |
|
|
1511
|
+
| Set up, configure, deploy or get an oracle hired — the runbook this guide narrates | `create-oracle` |
|
|
1512
|
+
| Write or debug one fact handler; test with `runFacts`/`fakeTools`; chase a `FACT_MISSING` / `FACT_KIND` | `write-facts` |
|
|
1513
|
+
| Author the rubric a protocol will anchor | `create-rubric` |
|
|
1514
|
+
| Anchor a claim form and rubric as an on-chain protocol entity | `publish-protocol` |
|
|
1515
|
+
| Create a deed and a claim collection, fund it, grant the roles | `create-collection` |
|
|
1516
|
+
| 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` |
|
|
1517
|
+
|
|
1518
|
+
And the three rules that matter more than any of them:
|
|
1519
|
+
|
|
1520
|
+
1. **The manifest is the work order — never the rubric doc.** If `protocol review` says `unusable`,
|
|
1521
|
+
there is nothing to build against yet. Stop and say so.
|
|
1522
|
+
2. **Never invent a fact value.** A stub is a visible `fact.error("not implemented: <name>")`. A
|
|
1523
|
+
wrong value that passes shape validation is worse than a loud failure the rubric routes itself.
|
|
1524
|
+
3. **`bun run check` stays green** after any `src/` edit, and `bun run cf-typegen` runs after any
|
|
1525
|
+
`wrangler.jsonc` binding or var change.
|