@ixo/decision-oracle 0.2.17 → 0.2.19
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 +1 -1
- package/dist/cron/collection.d.ts +14 -1
- package/dist/cron/collection.d.ts.map +1 -1
- package/dist/cron/collection.js +62 -15
- package/dist/cron/collection.js.map +1 -1
- package/dist/cron/feed.d.ts +49 -0
- package/dist/cron/feed.d.ts.map +1 -0
- package/dist/cron/feed.js +170 -0
- package/dist/cron/feed.js.map +1 -0
- package/dist/cron/scan.d.ts.map +1 -1
- package/dist/cron/scan.js +24 -3
- package/dist/cron/scan.js.map +1 -1
- package/dist/db/schema.d.ts +68 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +18 -0
- package/dist/db/schema.js.map +1 -1
- package/dist/lib/ixo/blocksync/index.d.ts +12 -2
- package/dist/lib/ixo/blocksync/index.d.ts.map +1 -1
- package/dist/lib/ixo/blocksync/index.js +17 -2
- package/dist/lib/ixo/blocksync/index.js.map +1 -1
- package/dist/lib/ixo/blocksync/queries.d.ts +2 -1
- package/dist/lib/ixo/blocksync/queries.d.ts.map +1 -1
- package/dist/lib/ixo/blocksync/queries.js +17 -1
- package/dist/lib/ixo/blocksync/queries.js.map +1 -1
- package/dist/lib/ixo/blocksync/types.d.ts +10 -0
- package/dist/lib/ixo/blocksync/types.d.ts.map +1 -1
- package/dist/lib/ixo/blocksync/types.js +5 -0
- package/dist/lib/ixo/blocksync/types.js.map +1 -1
- package/dist/lib/ixo/chain-write.d.ts.map +1 -1
- package/dist/lib/ixo/chain-write.js +5 -1
- package/dist/lib/ixo/chain-write.js.map +1 -1
- package/dist/lib/openhands/backstop.d.ts +14 -5
- package/dist/lib/openhands/backstop.d.ts.map +1 -1
- package/dist/lib/openhands/backstop.js +22 -10
- package/dist/lib/openhands/backstop.js.map +1 -1
- package/dist/routes/v1/shared.d.ts +18 -0
- package/dist/routes/v1/shared.d.ts.map +1 -1
- package/dist/workflows/claim-ingest.d.ts.map +1 -1
- package/dist/workflows/claim-ingest.js +21 -6
- package/dist/workflows/claim-ingest.js.map +1 -1
- package/docs/api.md +22 -6
- package/docs/guide.md +11 -2
- package/migrations/0027_cron_state.sql +4 -0
- package/migrations/0028_next_scan_at.sql +1 -0
- package/migrations/meta/0027_snapshot.json +3007 -0
- package/migrations/meta/0028_snapshot.json +3014 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +1 -1
package/docs/api.md
CHANGED
|
@@ -258,7 +258,7 @@ and the database handle from it exactly as the cron does (`cron/scan.ts`'s `cron
|
|
|
258
258
|
| `broadcastAsOracle` | function | `(env, msgs, memo?) => Promise<{ code, transactionHash, rawLog? }>` — signs and broadcasts arbitrary messages as THIS oracle's account, the same sequence-managed signer the act lane uses (`getOracleSigner`, `lib/ixo/chain.ts`). Memo defaults to your oracle's own `name`. Does **not** inspect `res.code` — unlike `signEvaluation` this is a raw broadcast primitive, not a decision lane, so a non-zero code is yours to interpret. Throws `AppError.unavailable` when `ORACLE_SECP_MNEMONIC` is unset, before the signer is ever built | Composing your own `MsgExec` around kit-issued messages |
|
|
259
259
|
| `rehireOpenhands` | function | `(env, collectionId) => Promise<"started" \| "existing" \| "restarted" \| "throttled" \| "failed">` — starts the OpenHands hire for a collection, or reruns its ended instance (spending the collection's backstop restart budget: three a day). A rerun mints only when the chain says the grant is stale or below 50 verdicts | `openhands` on, and your own `act` policy counts verdicts and sees an ask running low before the backstop's next visit |
|
|
260
260
|
| `AppError` | class | The kit's typed error — `.notFound()`, `.forbidden()`, `.conflict()`, `.unavailable()`, `.internal()`, … (§13) | Catching/rethrowing what the four functions above throw, in your own error handling |
|
|
261
|
-
| `ChainWriteOutcome` | type | `"submitted" \| "skipped" \| "blocked" \| "held" \| "owner_submitted"` — what `signEvaluation` resolves to, and (alongside `"pending"`, the row's initial state before any write is attempted) the full `decisions.chainStatus` enum | Branching on the result, or reading `chainStatus` off `GET /v1/claims/:claimId` |
|
|
261
|
+
| `ChainWriteOutcome` | type | `"submitted" \| "skipped" \| "blocked" \| "held" \| "owner_submitted"` — what `signEvaluation` resolves to, and (alongside `"pending"`, the row's initial state before any write is attempted) the full `decisions.chainStatus` enum. A decision the act lane can never sign (an engine `unusable`, a `decided` review verdict) starts `"skipped"` instead; a `pending_review` starts `"pending"` and its fold acts | Branching on the result, or reading `chainStatus` off `GET /v1/claims/:claimId` |
|
|
262
262
|
| `RegisterInput` | type | `{ collectionId, principal: { did, address }, contactEmail?, delegationCar?, cursor? }`. No `contactEmail` sent and none stored ⇒ the row takes the `contactEmail` of the oldest-activated `active` Decision API source whose `companyEntityDid` is the collection's COMPANY as the engine derives it (`GET /v1/collections/:id/company`, UCAN `collection/company-read`: the collection's entity, or the org a deed is linked to both ways); a `disputed` company, a collection the engine does not know, or none such source ⇒ stays empty. Its `503` or any other failure never fails the register (logged `register.company-unavailable`, the row stays empty for a later register to fill). An explicit or stored contact is never overwritten. The SAME answer (one engine read per register) also links the collection to every `active` source of that company (`api_source_collections`, `linkedBy` = the company's entity DID, audit `source.collection_linked` by `system:company-link`; idempotent — an existing link is left alone); only the engine's `ok` links. The engine is asked only when no contact is sent or stored, or an `active` source is not yet linked to the collection. | Building the input to `registerCollection` |
|
|
263
263
|
| `WatchedCollection` | type | The `watched_collections` row `registerCollection` and `scanCollection` read and write | Typing a helper that takes one |
|
|
264
264
|
|
|
@@ -451,7 +451,10 @@ id = the collection id):
|
|
|
451
451
|
A **backstop** runs every five minutes so a lost webhook never strands an ask: it finds meta-grants
|
|
452
452
|
whose collections are not watched yet and hires them, re-mints a watched collection whose poster
|
|
453
453
|
raised the ceiling or whose quota fell below 50, and lifts an `engine-no-grant` pause once the
|
|
454
|
-
chain shows quota again (at least 5 min after the last read — the engine's grant cache).
|
|
454
|
+
chain shows quota again (at least 5 min after the last read — the engine's grant cache). Its
|
|
455
|
+
window reads only the watched collections that can need it — never read, hired or claimed in the
|
|
456
|
+
last day, quota below a top-up, or not read for a week — so a quiet ask costs one read a week (a
|
|
457
|
+
raised ceiling on one arrives by the hire webhook, or within that week). Each
|
|
455
458
|
collection gets at most three hire reruns a day from the backstop and three from the webhook, on
|
|
456
459
|
separate budgets. `POST /openhands/scan/<collectionId>` scans a watched collection at once (at
|
|
457
460
|
most once a minute; extra calls are scanned on the next minute tick). A scan that starts nothing —
|
|
@@ -1608,7 +1611,7 @@ once deployed. Every value is validated by Zod at isolate start, so a bad env fa
|
|
|
1608
1611
|
| `LOG_PRETTY` | `.dev.vars` only | `false` | Colorized local logs. Deployed envs must emit JSON — that is what Workers Logs ingests |
|
|
1609
1612
|
| `SERVICE_NAME` / `SERVICE_VERSION` | vars | `eval-oracle` / `0.0.0` | Log and `/health` identity |
|
|
1610
1613
|
| `CORS_ORIGINS` | vars | `*` | Comma-separated origins, or `*` |
|
|
1611
|
-
| `CRON_COLLECTIONS_PER_TICK` | vars | `25` | Watched collections visited per cron tick. A fleet larger than this rotates in across ticks, least-recently-scanned first
|
|
1614
|
+
| `CRON_COLLECTIONS_PER_TICK` | vars | `25` | Watched collections visited per cron tick. Only DUE rows rotate (`nextScanAt`): each visit parks a collection by what Blocksync says — closed, or ended more than a day ago, for a week; never claimed for a day; otherwise an hour (a full page stays due) — and a webhook scan or a claims-feed hit makes it due again. A fleet larger than this rotates in across ticks, least-recently-scanned first |
|
|
1612
1615
|
| `IXO_NETWORK` | vars | `devnet` | `devnet` \| `testnet` \| `mainnet` — supplies eight URLs below |
|
|
1613
1616
|
| `EVALUATION_DENOM` / `EVALUATION_MIN_AMOUNT` | vars | unset — **required when `IXO_NETWORK` is `mainnet`** | The evaluation fee for this network: never `upay` on an oracle IXO does not run (register refuses it `409 pay_requires_ixo_managed`; mainnet third parties charge USDC.inj `ibc/77C12480BD8C29BF0ACEC2143736C15BAF8CD232CA27BAE09336677B828955E6` or `uixo`). the denom a collection's `Payments.evaluation` must be in (matched exactly) and its minimum, an integer base-unit string. Set both or neither (one alone fails at isolate start). When set they replace `pricing.evaluation` at register and on `/v1/info`; a `pricing.byProtocol` entry still wins for its protocol. No default in code: on mainnet an unset pair fails config validation at isolate start, so a stand-in coin written in `defineOracle` can never be charged there. The template sets `uixo` / `1000000` on devnet and testnet and Noble USDC (`ibc/6BBE9BD4246F8E04948D5A4EEE7164B2630263B9EBB5E7DC5F0A46C62A2FF97B`, 6 decimals) / `1000000` on production |
|
|
1614
1617
|
| `BLOCKSYNC_GRAPHQL_URL` | vars | per network | Override only for a self-hosted indexer |
|
|
@@ -2401,11 +2404,11 @@ trigger is handled in its own `scheduled` before it delegates to the oracle's.
|
|
|
2401
2404
|
|
|
2402
2405
|
| Trigger | Tier | Jobs |
|
|
2403
2406
|
| --- | --- | --- |
|
|
2404
|
-
| `* * * * *` | minute | 1, 2, 4 (the resume) and 5 below; with `API_LANE` the lane's source resume, webhook deliveries and retries, review-batch emails and backlog gauges; with `openhands`, `openhands-scans` (the coalesced `/openhands/scan` calls and the scans that found nothing, three retries each, 50 a minute) |
|
|
2407
|
+
| `* * * * *` | minute | 1, 2, 4 (the resume) and 5 below; `claims-feed` (6) before the rotation `scan`; with `API_LANE` the lane's source resume, webhook deliveries and retries, review-batch emails and backlog gauges; with `openhands`, `openhands-scans` (the coalesced `/openhands/scan` calls and the scans that found nothing, three retries each, 50 a minute) |
|
|
2405
2408
|
| `*/5 * * * *` | five | `ai-cost-backfill`; with a facilitator, `payments-reconcile` and `balance-watch`; with `openhands`, `openhands-backstop` ("The OpenHands add-on", §2) |
|
|
2406
|
-
| `0 * * * *` | hour | 3 below; with `API_LANE` `retention` (housekeeping), `header-seal`, `review-billing-reminders`, `review-reminders`, `review-expiry`, `review-rooms`, `collection-links` |
|
|
2409
|
+
| `0 * * * *` | hour | `claims-replay` (6), 3 below; with `API_LANE` `retention` (housekeeping), `header-seal`, `review-billing-reminders`, `review-reminders`, `review-expiry`, `review-rooms`, `collection-links` |
|
|
2407
2410
|
|
|
2408
|
-
Sentry cron monitors: `oracle-scan` and (with `API_LANE`) `oracle-api-lane` per minute,
|
|
2411
|
+
Sentry cron monitors: `oracle-scan`, `oracle-claims-feed` and (with `API_LANE`) `oracle-api-lane` per minute,
|
|
2409
2412
|
`oracle-cron-five`, `oracle-cron-hour`.
|
|
2410
2413
|
|
|
2411
2414
|
1. `resumeEngineCredits` — while any collection is paused `engine-credits`, ask the engine's
|
|
@@ -2420,6 +2423,19 @@ Sentry cron monitors: `oracle-scan` and (with `API_LANE`) `oracle-api-lane` per
|
|
|
2420
2423
|
read. At most 20 such collections are probed per
|
|
2421
2424
|
tick, least recently probed first, 5 balance reads at a time; the resumes run one after another,
|
|
2422
2425
|
and a collection still unfunded moves to the back, so the rest are reached on later minutes.
|
|
2426
|
+
6. `claims-feed` — every minute, ONE chain-wide Blocksync read (`claims` by `submissionDate`,
|
|
2427
|
+
oldest first, 100 a page, at most 10 pages a tick) from the stored position minus a 2-minute
|
|
2428
|
+
overlap; only claims of an `active` watched collection start (the same unseen-ledger dedupe and
|
|
2429
|
+
idempotent instance ids as the rotation), and the position (`cron_state` `claims-feed`) moves only
|
|
2430
|
+
after their instances started. A tick that stops at its page cap continues from Blocksync's keyset
|
|
2431
|
+
cursor. The first run starts at now − 2 minutes (no backfill: a new collection's own first sweep
|
|
2432
|
+
reads its history). It also moves a collection's cursor (forward only) when that cursor lies
|
|
2433
|
+
inside the window the feed read; a NULL cursor is never moved. No active collection ⇒ no read.
|
|
2434
|
+
`claims-replay` (hourly) re-reads the slice that turned 24 h old (`claims-feed-replay`), so a
|
|
2435
|
+
claim indexed later than the overlap is still found within a day. The rotation (`scan`) keeps
|
|
2436
|
+
running beside it for first sweeps and as the backstop, over due rows only (`nextScanAt`,
|
|
2437
|
+
`CRON_COLLECTIONS_PER_TICK` in §11); a feed hit makes its collection due again, a paused one too,
|
|
2438
|
+
so the claims a pause held back are scanned as soon as it resumes.
|
|
2423
2439
|
3. `review-rooms` — hourly: the chain review rooms (20 a pass, least recently synced first) are
|
|
2424
2440
|
brought in line with their reviewer grants — an expired grant's reviewer kicked, a failed invite
|
|
2425
2441
|
retried.
|
package/docs/guide.md
CHANGED
|
@@ -1156,9 +1156,16 @@ keep all three, or that tier's jobs never run:
|
|
|
1156
1156
|
An unknown cron string is a `cron.unknown` log line and runs nothing. A provider that adds a
|
|
1157
1157
|
trigger of its own handles it in its own `scheduled` before delegating the rest to the oracle's.
|
|
1158
1158
|
|
|
1159
|
+
**The claims feed.** Every minute the oracle reads the chain's new claims ONCE, chain-wide, and
|
|
1160
|
+
starts the ones in collections it watches (`claims-feed`, [api.md](./api.md#cron-three-triggers)):
|
|
1161
|
+
a claim is usually picked up within a minute or two of landing, however many collections you
|
|
1162
|
+
watch. An hourly replay re-reads what turned 24 h old, for a claim Blocksync indexed late.
|
|
1163
|
+
|
|
1159
1164
|
**The cron tick.** `CRON_COLLECTIONS_PER_TICK` (default `25`, see [api.md §11](./api.md#11-configuration))
|
|
1160
1165
|
bounds how many watched collections one tick visits; a fleet larger than that rotates in over later
|
|
1161
|
-
ticks, least-recently-scanned first.
|
|
1166
|
+
ticks, least-recently-scanned first. The rotation visits a collection only when it is due: a closed
|
|
1167
|
+
or long-ended one weekly, one that never had a claim daily, the rest hourly — the claims feed is
|
|
1168
|
+
what finds new claims within a minute. Raise it for a large fleet. To act on one collection sooner
|
|
1162
1169
|
than the next tick — e.g. the instant your own webhook sees a submission — call
|
|
1163
1170
|
`scanCollection(env, collectionId)` from `@ixo/decision-oracle/runtime`, or hit
|
|
1164
1171
|
`POST /v1/collections/:collectionId/scan`; both run the exact same discover-and-start pass the tick
|
|
@@ -1173,7 +1180,9 @@ human review. Both are UCAN-guarded and gated to the collection's current chain
|
|
|
1173
1180
|
evaluate-grant holder — the registrant only while it still holds its grant — and the single-claim
|
|
1174
1181
|
read to the collection's live reviewers too, and to the claim's own submitter (its on-chain agent DID or
|
|
1175
1182
|
address, who sees the decision and its public receipt — which names who reviewed — but never the
|
|
1176
|
-
review's answers). `chainStatus` is `pending` / `submitted` / `owner_submitted` / `blocked` / `skipped
|
|
1183
|
+
review's answers). `chainStatus` is `pending` / `submitted` / `owner_submitted` / `blocked` / `skipped`
|
|
1184
|
+
(a decision nothing will ever sign: an engine `unusable`, or a `decided` review verdict with no review
|
|
1185
|
+
held for a person), or
|
|
1177
1186
|
**`held`** when your own `policies.act` hook chose to keep a decided verdict back instead of signing
|
|
1178
1187
|
it immediately — a payout you mean to pool across several claims, say (`ActContext`/`ActDecision`,
|
|
1179
1188
|
[api.md](./api.md#1-import-map)). `signEvaluation(env, claimId, amount?)` from
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE `watched_collections` ADD `next_scan_at` integer;
|