@ixo/decision-oracle 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +6 -3
  2. package/dist/cli/commands/init.d.ts +3 -1
  3. package/dist/cli/commands/init.d.ts.map +1 -1
  4. package/dist/cli/commands/init.js +42 -2
  5. package/dist/cli/commands/init.js.map +1 -1
  6. package/dist/cli/commands/oracle-init.d.ts.map +1 -1
  7. package/dist/cli/commands/oracle-init.js +13 -5
  8. package/dist/cli/commands/oracle-init.js.map +1 -1
  9. package/dist/cli/index.js +6 -3
  10. package/dist/cli/index.js.map +1 -1
  11. package/dist/cron/openhands.d.ts +9 -0
  12. package/dist/cron/openhands.d.ts.map +1 -0
  13. package/dist/cron/openhands.js +30 -0
  14. package/dist/cron/openhands.js.map +1 -0
  15. package/dist/cron/scan.d.ts.map +1 -1
  16. package/dist/cron/scan.js +3 -0
  17. package/dist/cron/scan.js.map +1 -1
  18. package/dist/db/schema.d.ts +158 -0
  19. package/dist/db/schema.d.ts.map +1 -1
  20. package/dist/db/schema.js +26 -0
  21. package/dist/db/schema.js.map +1 -1
  22. package/dist/define-oracle.d.ts +3 -0
  23. package/dist/define-oracle.d.ts.map +1 -1
  24. package/dist/define-oracle.js +13 -1
  25. package/dist/define-oracle.js.map +1 -1
  26. package/dist/env.d.ts +12 -0
  27. package/dist/env.d.ts.map +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/lib/ixo/blocksync/index.d.ts +4 -0
  31. package/dist/lib/ixo/blocksync/index.d.ts.map +1 -1
  32. package/dist/lib/ixo/blocksync/index.js +47 -1
  33. package/dist/lib/ixo/blocksync/index.js.map +1 -1
  34. package/dist/lib/ixo/blocksync/queries.d.ts +2 -0
  35. package/dist/lib/ixo/blocksync/queries.d.ts.map +1 -1
  36. package/dist/lib/ixo/blocksync/queries.js +17 -0
  37. package/dist/lib/ixo/blocksync/queries.js.map +1 -1
  38. package/dist/lib/ixo/chain.d.ts +1 -1
  39. package/dist/lib/ixo/chain.d.ts.map +1 -1
  40. package/dist/lib/openhands/backstop.d.ts +131 -0
  41. package/dist/lib/openhands/backstop.d.ts.map +1 -0
  42. package/dist/lib/openhands/backstop.js +513 -0
  43. package/dist/lib/openhands/backstop.js.map +1 -0
  44. package/dist/lib/openhands/chain.d.ts +218 -0
  45. package/dist/lib/openhands/chain.d.ts.map +1 -0
  46. package/dist/lib/openhands/chain.js +392 -0
  47. package/dist/lib/openhands/chain.js.map +1 -0
  48. package/dist/lib/openhands/options.d.ts +13 -0
  49. package/dist/lib/openhands/options.d.ts.map +1 -0
  50. package/dist/lib/openhands/options.js +28 -0
  51. package/dist/lib/openhands/options.js.map +1 -0
  52. package/dist/lib/openhands/routes.d.ts +73 -0
  53. package/dist/lib/openhands/routes.d.ts.map +1 -0
  54. package/dist/lib/openhands/routes.js +355 -0
  55. package/dist/lib/openhands/routes.js.map +1 -0
  56. package/dist/lib/openhands/store.d.ts +74 -0
  57. package/dist/lib/openhands/store.d.ts.map +1 -0
  58. package/dist/lib/openhands/store.js +148 -0
  59. package/dist/lib/openhands/store.js.map +1 -0
  60. package/dist/lib/review/reviewer-grant.js +1 -1
  61. package/dist/lib/review/reviewer-grant.js.map +1 -1
  62. package/dist/oracle.d.ts +52 -1
  63. package/dist/oracle.d.ts.map +1 -1
  64. package/dist/oracle.js +6 -0
  65. package/dist/oracle.js.map +1 -1
  66. package/dist/routes/v1/info.d.ts.map +1 -1
  67. package/dist/routes/v1/info.js +16 -1
  68. package/dist/routes/v1/info.js.map +1 -1
  69. package/dist/runtime.d.ts +6 -0
  70. package/dist/runtime.d.ts.map +1 -1
  71. package/dist/runtime.js +10 -0
  72. package/dist/runtime.js.map +1 -1
  73. package/dist/workflows/openhands-hire.d.ts +65 -0
  74. package/dist/workflows/openhands-hire.d.ts.map +1 -0
  75. package/dist/workflows/openhands-hire.js +203 -0
  76. package/dist/workflows/openhands-hire.js.map +1 -0
  77. package/docs/api.md +72 -7
  78. package/docs/guide.md +33 -0
  79. package/migrations/0025_openhands.sql +12 -0
  80. package/migrations/meta/0025_snapshot.json +2976 -0
  81. package/migrations/meta/_journal.json +7 -0
  82. package/package.json +1 -1
  83. package/skills/README.md +1 -1
  84. package/skills/add-openhands-support/SKILL.md +96 -86
  85. package/skills/create-oracle/SKILL.md +10 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/lib/openhands/options.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAa,KAAK,gBAAgB,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,mFAAmF;AACnF,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED,wBAAgB,gBAAgB,IAAI,gBAAgB,CAEnD;AAED,wGAAwG;AACxG,wBAAsB,WAAW,CAC/B,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,IAAI,CAAC,CAWf"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The provider's `openhands` options (oracle.ts `OpenhandsOptions`), read through the registry like
3
+ * every other part of the config — and the one fail-soft way their `onQuota` hook is called.
4
+ */
5
+ import { getOracle } from "../../oracle.js";
6
+ /** Is this oracle an OpenHands checker (`openhands` present in `defineOracle`)? */
7
+ export function openhandsEnabled() {
8
+ return getOracle().config.openhands !== undefined;
9
+ }
10
+ export function openhandsOptions() {
11
+ return getOracle().config.openhands ?? {};
12
+ }
13
+ /** Tell the provider's `onQuota` what was just read. A throw there never fails the hire or the pass. */
14
+ export async function reportQuota(env, logger, quota) {
15
+ const hook = openhandsOptions().onQuota;
16
+ if (!hook)
17
+ return;
18
+ try {
19
+ await hook(quota, env);
20
+ }
21
+ catch (err) {
22
+ logger.warn("openhands.on-quota.failed", {
23
+ collectionId: quota.collectionId,
24
+ detail: err instanceof Error ? err.message : String(err),
25
+ });
26
+ }
27
+ }
28
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../../../src/lib/openhands/options.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,SAAS,EAA8C,MAAM,cAAc,CAAC;AAGrF,mFAAmF;AACnF,MAAM,UAAU,gBAAgB;IAC9B,OAAO,SAAS,EAAE,CAAC,MAAM,CAAC,SAAS,KAAK,SAAS,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,SAAS,EAAE,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC;AAC5C,CAAC;AAED,wGAAwG;AACxG,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,GAAc,EACd,MAAc,EACd,KAAqB;IAErB,MAAM,IAAI,GAAG,gBAAgB,EAAE,CAAC,OAAO,CAAC;IACxC,IAAI,CAAC,IAAI;QAAE,OAAO;IAClB,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CAAC,2BAA2B,EAAE;YACvC,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,MAAM,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACzD,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The two webhooks the OpenHands app calls, in front of the package's own app (define-oracle.ts):
3
+ *
4
+ * POST /openhands/hire/:collectionId — the ask just granted this oracle; start its hire
5
+ * POST /openhands/scan/:collectionId — a claim just landed; scan now, don't wait for the tick
6
+ *
7
+ * UNAUTHENTICATED on purpose: they only ask this oracle to look at the chain, so there is no secret
8
+ * on them and every caller is a stranger. What bounds a stranger is all here:
9
+ *
10
+ * - `OPENHANDS_RATE_LIMITER`, spent per caller address (an IPv6 caller by its /64) and per
11
+ * collection, failing CLOSED (503) where it is not bound outside development;
12
+ * - a collection id shaped like one (no leading zeros: one collection, one key, one instance id);
13
+ * - before any instance is created or restarted, a cheap check that the collection exists and its
14
+ * admin granted this oracle a live meta-grant — refused 404 / 409, only the 409 cached;
15
+ * - a healthy hire's answer cached for a minute, so replays cost one KV read;
16
+ * - an existing hire restarted ONLY when the chain says it must (unwatched, a stale ceiling, low
17
+ * quota) and only from the webhook's OWN restart budget — never the backstop's;
18
+ * - `/scan` at most once a minute per collection; a coalesced call marks the collection, and the
19
+ * minute tick scans it (`runDirtyScans`).
20
+ *
21
+ * What a stranger cannot do: make this oracle MINT where the chain does not say so (the hire signs
22
+ * nothing until it read a live meta-grant from the collection's own admin), un-pause a collection,
23
+ * or evaluate a claim it was not authorized for.
24
+ */
25
+ import type { OracleEnv } from "../../env.js";
26
+ import { type HireDeps, type WatchedRestart } from "./backstop.js";
27
+ import { type MetaGrant } from "./chain.js";
28
+ /** The paths `GET /v1/info` advertises (`openhands`) — the app calls these and nothing else. */
29
+ export declare const HIRE_PATH = "/openhands/hire/:collectionId";
30
+ export declare const SCAN_PATH = "/openhands/scan/:collectionId";
31
+ /** Twelve digits is a trillion collections; no leading zero, so `007` and `7` are not two keys. */
32
+ export declare const COLLECTION_ID_RE: RegExp;
33
+ /** How long a refused (409) hire is answered from `CACHE_KV` — the backstop's own period. */
34
+ export declare const HIRE_REFUSED_TTL_SEC = 600;
35
+ export declare const hireRefusedKey: (collectionId: string) => string;
36
+ /** How long a healthy hire is answered from `CACHE_KV` — the shortest TTL KV accepts. */
37
+ export declare const HIRE_HEALTHY_TTL_SEC = 60;
38
+ export declare const hireHealthyKey: (collectionId: string) => string;
39
+ /** One scan per collection per minute. */
40
+ export declare const SCAN_COALESCE_TTL_SEC = 60;
41
+ export declare const scanMarkerKey: (collectionId: string) => string;
42
+ /** Coalesced collections the minute tick re-scans. */
43
+ export declare const SCAN_DIRTY_PER_TICK = 20;
44
+ /**
45
+ * The rate-limit key for the caller: `cf-connecting-ip`, an IPv6 address cut to its /64 (one
46
+ * subscriber is handed a whole /64), an IPv4-mapped IPv6 address as its IPv4.
47
+ */
48
+ export declare function callerKey(request: Request): string;
49
+ /**
50
+ * What the hire lane reads before it creates or restarts anything — injected by tests. Every
51
+ * method THROWS when its source cannot answer; a throw is a 503, never a refusal, never cached.
52
+ */
53
+ export interface HireChain {
54
+ /** The collection's admin — Blocksync first, the chain when the index has not caught up. */
55
+ admin(collectionId: string): Promise<string | null>;
56
+ meta(admin: string, collectionId: string): Promise<MetaGrant | null>;
57
+ /** Why an ENDED hire must run again, or null when it need not. */
58
+ restartReason(collectionId: string, admin: string, meta: MetaGrant): Promise<WatchedRestart | "unwatched">;
59
+ }
60
+ export declare function liveHireChain(deps: HireDeps): HireChain;
61
+ export declare function hireDeps(env: OracleEnv): HireDeps;
62
+ /**
63
+ * Handle an OpenHands webhook, or `null` when the request is not one — the caller then falls
64
+ * through to the package's app. `chain` is injected by tests.
65
+ */
66
+ export declare function handleOpenhandsRoute(request: Request, env: OracleEnv, chain?: HireChain): Promise<Response | null>;
67
+ /**
68
+ * The minute half of `/scan` coalescing: scan the collections whose call was coalesced, then drop
69
+ * each marker if it still holds the value read (a call coalesced DURING the scan is scanned next
70
+ * minute). Every outcome drops it — a failed scan is the regular rotation's to find.
71
+ */
72
+ export declare function runDirtyScans(env: OracleEnv): Promise<number>;
73
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/lib/openhands/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAI3C,OAAO,EAEL,KAAK,QAAQ,EAGb,KAAK,cAAc,EAEpB,MAAM,YAAY,CAAC;AACpB,OAAO,EAGL,KAAK,SAAS,EAGf,MAAM,SAAS,CAAC;AAYjB,gGAAgG;AAChG,eAAO,MAAM,SAAS,kCAAkC,CAAC;AACzD,eAAO,MAAM,SAAS,kCAAkC,CAAC;AAGzD,mGAAmG;AACnG,eAAO,MAAM,gBAAgB,QAAuB,CAAC;AAErD,6FAA6F;AAC7F,eAAO,MAAM,oBAAoB,MAAM,CAAC;AACxC,eAAO,MAAM,cAAc,iBAAkB,MAAM,WAA6C,CAAC;AACjG,yFAAyF;AACzF,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,eAAO,MAAM,cAAc,iBAAkB,MAAM,WAA6C,CAAC;AACjG,0CAA0C;AAC1C,eAAO,MAAM,qBAAqB,KAAK,CAAC;AACxC,eAAO,MAAM,aAAa,iBAAkB,MAAM,WAAqC,CAAC;AACxF,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAUtC;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAKlD;AA+CD;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,4FAA4F;IAC5F,KAAK,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACpD,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IACrE,kEAAkE;IAClE,aAAa,CACX,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,SAAS,GACd,OAAO,CAAC,cAAc,GAAG,WAAW,CAAC,CAAC;CAC1C;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,CAkCvD;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,SAAS,GAAG,QAAQ,CAGjD;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,SAAS,EACd,KAAK,CAAC,EAAE,SAAS,GAChB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CA2B1B;AAgJD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAiBnE"}
@@ -0,0 +1,355 @@
1
+ /**
2
+ * The two webhooks the OpenHands app calls, in front of the package's own app (define-oracle.ts):
3
+ *
4
+ * POST /openhands/hire/:collectionId — the ask just granted this oracle; start its hire
5
+ * POST /openhands/scan/:collectionId — a claim just landed; scan now, don't wait for the tick
6
+ *
7
+ * UNAUTHENTICATED on purpose: they only ask this oracle to look at the chain, so there is no secret
8
+ * on them and every caller is a stranger. What bounds a stranger is all here:
9
+ *
10
+ * - `OPENHANDS_RATE_LIMITER`, spent per caller address (an IPv6 caller by its /64) and per
11
+ * collection, failing CLOSED (503) where it is not bound outside development;
12
+ * - a collection id shaped like one (no leading zeros: one collection, one key, one instance id);
13
+ * - before any instance is created or restarted, a cheap check that the collection exists and its
14
+ * admin granted this oracle a live meta-grant — refused 404 / 409, only the 409 cached;
15
+ * - a healthy hire's answer cached for a minute, so replays cost one KV read;
16
+ * - an existing hire restarted ONLY when the chain says it must (unwatched, a stale ceiling, low
17
+ * quota) and only from the webhook's OWN restart budget — never the backstop's;
18
+ * - `/scan` at most once a minute per collection; a coalesced call marks the collection, and the
19
+ * minute tick scans it (`runDirtyScans`).
20
+ *
21
+ * What a stranger cannot do: make this oracle MINT where the chain does not say so (the hire signs
22
+ * nothing until it read a live meta-grant from the collection's own admin), un-pause a collection,
23
+ * or evaluate a claim it was not authorized for.
24
+ */
25
+ import { loadConfig } from "../../config.js";
26
+ import { cronContext } from "../../cron/scan.js";
27
+ import { scanCollection } from "../../runtime.js";
28
+ import { AppError } from "../errors.js";
29
+ import { getCollection } from "../ixo/blocksync/index.js";
30
+ import { ENDED, hireWorkflow, isAlreadyExists, watchedRestartReason, } from "./backstop.js";
31
+ import { approvalDenomOf, collectionAdminOnChain, metaGrantFor, readGrantsFrom, } from "./chain.js";
32
+ import { openhandsOptions } from "./options.js";
33
+ import { claimRestart, deleteStateIf, dirtyScans, SCAN_DIRTY_PREFIX, scanDirtyName, webhookBudgetName, writeState, } from "./store.js";
34
+ /** The paths `GET /v1/info` advertises (`openhands`) — the app calls these and nothing else. */
35
+ export const HIRE_PATH = "/openhands/hire/:collectionId";
36
+ export const SCAN_PATH = "/openhands/scan/:collectionId";
37
+ const ROUTE = /^\/openhands\/(hire|scan)\/([^/]*)\/?$/;
38
+ /** Twelve digits is a trillion collections; no leading zero, so `007` and `7` are not two keys. */
39
+ export const COLLECTION_ID_RE = /^[1-9][0-9]{0,11}$/;
40
+ /** How long a refused (409) hire is answered from `CACHE_KV` — the backstop's own period. */
41
+ export const HIRE_REFUSED_TTL_SEC = 600;
42
+ export const hireRefusedKey = (collectionId) => `openhands:hire-refused:${collectionId}`;
43
+ /** How long a healthy hire is answered from `CACHE_KV` — the shortest TTL KV accepts. */
44
+ export const HIRE_HEALTHY_TTL_SEC = 60;
45
+ export const hireHealthyKey = (collectionId) => `openhands:hire-healthy:${collectionId}`;
46
+ /** One scan per collection per minute. */
47
+ export const SCAN_COALESCE_TTL_SEC = 60;
48
+ export const scanMarkerKey = (collectionId) => `openhands:scan:${collectionId}`;
49
+ /** Coalesced collections the minute tick re-scans. */
50
+ export const SCAN_DIRTY_PER_TICK = 20;
51
+ const asMessage = (err) => (err instanceof Error ? err.message : String(err));
52
+ const json = (status, body, headers = {}) => new Response(JSON.stringify(body), {
53
+ status,
54
+ headers: { "content-type": "application/json", ...headers },
55
+ });
56
+ /**
57
+ * The rate-limit key for the caller: `cf-connecting-ip`, an IPv6 address cut to its /64 (one
58
+ * subscriber is handed a whole /64), an IPv4-mapped IPv6 address as its IPv4.
59
+ */
60
+ export function callerKey(request) {
61
+ const raw = request.headers.get("cf-connecting-ip")?.trim() ?? "";
62
+ if (!raw)
63
+ return "unknown";
64
+ if (!raw.includes(":"))
65
+ return raw;
66
+ return ipv6Slash64(raw) ?? `unparsed:${raw.slice(0, 64)}`;
67
+ }
68
+ function ipv6Slash64(address) {
69
+ let a = address.toLowerCase();
70
+ const zone = a.indexOf("%");
71
+ if (zone >= 0)
72
+ a = a.slice(0, zone);
73
+ const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(a);
74
+ if (mapped?.[1])
75
+ return mapped[1];
76
+ const halves = a.split("::");
77
+ if (halves.length > 2)
78
+ return null;
79
+ const groupsOf = (part) => (part ? part.split(":") : []);
80
+ const head = groupsOf(halves[0]);
81
+ const tail = groupsOf(halves[1]);
82
+ const last = tail.length > 0 ? tail[tail.length - 1] : head[head.length - 1];
83
+ const tailWidth = last?.includes(".") ? 1 : 0;
84
+ const explicit = head.length + tail.length + tailWidth;
85
+ const groups = halves.length === 2
86
+ ? explicit <= 7
87
+ ? [...head, ...Array(8 - explicit).fill("0"), ...tail]
88
+ : null
89
+ : explicit === 8
90
+ ? head
91
+ : null;
92
+ if (!groups)
93
+ return null;
94
+ const prefix = groups.slice(0, 4);
95
+ if (!prefix.every((g) => /^[0-9a-f]{1,4}$/.test(g)))
96
+ return null;
97
+ return `${prefix.map((g) => Number.parseInt(g, 16).toString(16)).join(":")}::/64`;
98
+ }
99
+ async function spendLimit(env, key) {
100
+ const limiter = env.OPENHANDS_RATE_LIMITER;
101
+ if (!limiter) {
102
+ if (loadConfig(env).ENVIRONMENT === "development")
103
+ return "ok";
104
+ console.error("[openhands] OPENHANDS_RATE_LIMITER is not bound — refusing");
105
+ return "unavailable";
106
+ }
107
+ try {
108
+ return (await limiter.limit({ key })).success ? "ok" : "limited";
109
+ }
110
+ catch (err) {
111
+ console.error(`[openhands] rate limiter failed, refusing: ${asMessage(err)}`);
112
+ return "unavailable";
113
+ }
114
+ }
115
+ export function liveHireChain(deps) {
116
+ const { config, blocksync, db } = deps;
117
+ return {
118
+ admin: async (collectionId) => {
119
+ try {
120
+ const indexed = (await getCollection(blocksync, collectionId))?.admin;
121
+ if (indexed)
122
+ return indexed;
123
+ }
124
+ catch (err) {
125
+ // The chain is the authority the index copies; an index outage is no reason to refuse.
126
+ deps.logger.warn("openhands.hire.index-unreadable", {
127
+ collectionId,
128
+ detail: asMessage(err),
129
+ });
130
+ }
131
+ return collectionAdminOnChain(config, collectionId);
132
+ },
133
+ meta: (admin, collectionId) => metaGrantFor(config, admin, collectionId),
134
+ restartReason: async (collectionId, admin) => {
135
+ const watched = await db.query.watchedCollections.findFirst({
136
+ columns: { collectionId: true },
137
+ where: (t, { eq }) => eq(t.collectionId, collectionId),
138
+ });
139
+ if (!watched)
140
+ return "unwatched";
141
+ const [chain, collection] = await Promise.all([
142
+ readGrantsFrom(config, admin),
143
+ getCollection(blocksync, collectionId),
144
+ ]);
145
+ return watchedRestartReason(chain, { collectionId, admin, approvalDenom: approvalDenomOf(collection?.payments) }, openhandsOptions().maxCustomAmount);
146
+ },
147
+ };
148
+ }
149
+ export function hireDeps(env) {
150
+ const ctx = cronContext(env);
151
+ return { env, config: loadConfig(env), db: ctx.db, logger: ctx.logger, blocksync: ctx.deps };
152
+ }
153
+ /**
154
+ * Handle an OpenHands webhook, or `null` when the request is not one — the caller then falls
155
+ * through to the package's app. `chain` is injected by tests.
156
+ */
157
+ export async function handleOpenhandsRoute(request, env, chain) {
158
+ const match = ROUTE.exec(new URL(request.url).pathname);
159
+ if (!match)
160
+ return null;
161
+ const [, lane, rawId = ""] = match;
162
+ if (request.method !== "POST") {
163
+ return json(405, { error: "Use POST on this endpoint" }, { allow: "POST" });
164
+ }
165
+ if (!COLLECTION_ID_RE.test(rawId)) {
166
+ return json(400, {
167
+ error: `collectionId must be a decimal collection id without leading zeros, got ${rawId.slice(0, 32) || "''"}`,
168
+ });
169
+ }
170
+ // The address first: a caller over its own budget must not also spend the collection's.
171
+ let spent = await spendLimit(env, `ip:${callerKey(request)}`);
172
+ if (spent === "ok")
173
+ spent = await spendLimit(env, `${lane}:${rawId}`);
174
+ if (spent === "limited") {
175
+ return json(429, { error: "Too many requests — retry in a minute" }, { "retry-after": "60" });
176
+ }
177
+ if (spent === "unavailable") {
178
+ return json(503, { error: "rate limiter unavailable — retry later" }, { "retry-after": "60" });
179
+ }
180
+ if (lane === "hire" && !env.HIRE_WORKFLOW) {
181
+ console.error("[openhands] the HIRE_WORKFLOW binding is missing (wrangler.jsonc)");
182
+ return json(503, { error: "this oracle's hire workflow is not bound — retry later" });
183
+ }
184
+ const deps = hireDeps(env);
185
+ return lane === "hire" ? hire(deps, rawId, chain ?? liveHireChain(deps)) : scan(deps, rawId);
186
+ }
187
+ /**
188
+ * Start the one hire instance for this collection — or rerun the one holding its id, when and only
189
+ * when the chain says a rerun is needed. A poster calls again precisely when something changed (a
190
+ * raised ceiling, a re-grant); the chain read tells that apart from a stranger replaying it.
191
+ */
192
+ async function hire(deps, collectionId, chain) {
193
+ const { env, logger } = deps;
194
+ if (await kvHas(env, hireHealthyKey(collectionId))) {
195
+ return json(200, { started: false, existing: true, cached: true });
196
+ }
197
+ const workflow = hireWorkflow(env);
198
+ let instance = null;
199
+ try {
200
+ instance = await workflow.get(collectionId);
201
+ }
202
+ catch {
203
+ instance = null;
204
+ }
205
+ // A run in flight reaches the same place on its own: nothing to read, nothing to spend.
206
+ if (instance && !ENDED.has((await instance.status()).status)) {
207
+ return json(200, { started: false, existing: true });
208
+ }
209
+ const cached = await cachedRefusal(env, collectionId);
210
+ if (cached)
211
+ return json(409, { error: cached, cached: true });
212
+ let admin;
213
+ let meta;
214
+ try {
215
+ admin = await chain.admin(collectionId);
216
+ meta = admin ? await chain.meta(admin, collectionId) : null;
217
+ }
218
+ catch (err) {
219
+ logger.error("openhands.hire.precheck-unavailable", { collectionId, detail: asMessage(err) });
220
+ return json(503, { error: "could not read the collection or its grant — retry" });
221
+ }
222
+ // NOT cached: an ask posted seconds ago may not be on the index or the chain yet.
223
+ if (!admin)
224
+ return json(404, { error: `no collection #${collectionId}` });
225
+ if (!meta) {
226
+ const error = `collection #${collectionId}'s admin has granted this oracle no live meta-grant`;
227
+ await env.CACHE_KV.put(hireRefusedKey(collectionId), error, {
228
+ expirationTtl: HIRE_REFUSED_TTL_SEC,
229
+ }).catch(() => undefined);
230
+ logger.info("openhands.hire.no-grant", { collectionId });
231
+ return json(409, { error });
232
+ }
233
+ if (!instance) {
234
+ try {
235
+ await workflow.create({ id: collectionId, params: { collectionId } });
236
+ return json(202, { started: true });
237
+ }
238
+ catch (err) {
239
+ // Lost the race with the backstop or another webhook — the backstop owns what happens next.
240
+ if (isAlreadyExists(err))
241
+ return json(200, { started: false, existing: true });
242
+ logger.error("openhands.hire.unstartable", { collectionId, detail: asMessage(err) });
243
+ return json(503, { error: asMessage(err) });
244
+ }
245
+ }
246
+ let reason;
247
+ try {
248
+ reason = await chain.restartReason(collectionId, admin, meta);
249
+ }
250
+ catch (err) {
251
+ logger.error("openhands.hire.restart-check-unavailable", {
252
+ collectionId,
253
+ detail: asMessage(err),
254
+ });
255
+ return json(503, { error: "could not read whether this hire needs a rerun — retry" });
256
+ }
257
+ if (reason === null) {
258
+ await env.CACHE_KV.put(hireHealthyKey(collectionId), "1", {
259
+ expirationTtl: HIRE_HEALTHY_TTL_SEC,
260
+ }).catch(() => undefined);
261
+ return json(200, { started: false, existing: true });
262
+ }
263
+ // The webhook's OWN budget: anonymous POSTs never spend the restarts the backstop needs.
264
+ let won;
265
+ try {
266
+ won = !("denied" in (await claimRestart(deps.db, webhookBudgetName(collectionId), new Date())));
267
+ }
268
+ catch (err) {
269
+ logger.error("openhands.hire.budget-unreadable", { collectionId, detail: asMessage(err) });
270
+ return json(503, { error: `hire #${collectionId} could not be restarted` });
271
+ }
272
+ if (!won)
273
+ return json(200, { started: false, existing: true, throttled: true });
274
+ try {
275
+ await instance.restart();
276
+ }
277
+ catch (err) {
278
+ logger.error("openhands.hire.unrestartable", { collectionId, detail: asMessage(err) });
279
+ return json(503, { error: `hire #${collectionId} could not be restarted` });
280
+ }
281
+ logger.info("openhands.hire.restarted", { collectionId, by: "webhook", reason });
282
+ return json(200, { started: false, restarted: true, reason });
283
+ }
284
+ async function kvHas(env, key) {
285
+ try {
286
+ return (await env.CACHE_KV.get(key)) !== null;
287
+ }
288
+ catch {
289
+ return false;
290
+ }
291
+ }
292
+ async function cachedRefusal(env, collectionId) {
293
+ try {
294
+ return await env.CACHE_KV.get(hireRefusedKey(collectionId));
295
+ }
296
+ catch {
297
+ return null;
298
+ }
299
+ }
300
+ /**
301
+ * Run the per-collection discovery pass now — at most once a minute per collection. 202 = coalesced
302
+ * (the minute tick scans it); 404 = not watched, 409 = paused. KV is eventually consistent, so this
303
+ * is a cost brake, not an exact once.
304
+ */
305
+ async function scan(deps, collectionId) {
306
+ const { env, db, logger } = deps;
307
+ const marker = scanMarkerKey(collectionId);
308
+ try {
309
+ if (await env.CACHE_KV.get(marker)) {
310
+ await writeState(db, scanDirtyName(collectionId), new Date().toISOString()).catch((err) => logger.warn("openhands.scan.unmarked", { collectionId, detail: asMessage(err) }));
311
+ return json(202, { started: false, coalesced: true });
312
+ }
313
+ await env.CACHE_KV.put(marker, new Date().toISOString(), {
314
+ expirationTtl: SCAN_COALESCE_TTL_SEC,
315
+ });
316
+ }
317
+ catch (err) {
318
+ logger.warn("openhands.scan.uncoalesced", { collectionId, detail: asMessage(err) });
319
+ }
320
+ try {
321
+ return json(200, { started: await scanCollection(env, collectionId) });
322
+ }
323
+ catch (err) {
324
+ if (err instanceof AppError && (err.status === 404 || err.status === 409)) {
325
+ return json(err.status, { error: err.message });
326
+ }
327
+ logger.error("openhands.scan.failed", { collectionId, detail: asMessage(err) });
328
+ return json(503, { error: asMessage(err) });
329
+ }
330
+ }
331
+ /**
332
+ * The minute half of `/scan` coalescing: scan the collections whose call was coalesced, then drop
333
+ * each marker if it still holds the value read (a call coalesced DURING the scan is scanned next
334
+ * minute). Every outcome drops it — a failed scan is the regular rotation's to find.
335
+ */
336
+ export async function runDirtyScans(env) {
337
+ const { db, logger } = hireDeps(env);
338
+ const marked = await dirtyScans(db, SCAN_DIRTY_PER_TICK);
339
+ let scanned = 0;
340
+ for (const { name, value } of marked) {
341
+ const collectionId = name.slice(SCAN_DIRTY_PREFIX.length);
342
+ try {
343
+ await scanCollection(env, collectionId);
344
+ scanned += 1;
345
+ }
346
+ catch (err) {
347
+ if (!(err instanceof AppError && (err.status === 404 || err.status === 409))) {
348
+ logger.warn("openhands.scan.dirty-failed", { collectionId, detail: asMessage(err) });
349
+ }
350
+ }
351
+ await deleteStateIf(db, name, value);
352
+ }
353
+ return scanned;
354
+ }
355
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../../src/lib/openhands/routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Every statement the OpenHands add-on runs against its two tables (db/schema.ts `openhandsState`,
3
+ * `openhandsHires`) — each one statement, so two passes or a pass and a webhook never interleave a
4
+ * read and a write that matter.
5
+ */
6
+ import type { Database } from "../../db/client.js";
7
+ /** The rows the backstop resumes its rotating lanes from. */
8
+ export declare const WINDOW_CURSOR = "window";
9
+ export declare const PAUSED_CURSOR = "paused";
10
+ export declare const GRANTS_CURSOR = "grants";
11
+ /** One collection's restart budget — the backstop's and the webhook's, counted apart. */
12
+ export declare const restartBudgetName: (collectionId: string) => string;
13
+ export declare const webhookBudgetName: (collectionId: string) => string;
14
+ /** A collection whose `/openhands/scan` was coalesced, waiting for the minute tick. */
15
+ export declare const SCAN_DIRTY_PREFIX = "scan-dirty:";
16
+ export declare const scanDirtyName: (collectionId: string) => string;
17
+ export declare function readState(db: Database, name: string): Promise<string | null>;
18
+ export declare function writeState(db: Database, name: string, value: string): Promise<void>;
19
+ /** Delete a row only if it still holds `value` — a marker re-set meanwhile survives. */
20
+ export declare function deleteStateIf(db: Database, name: string, value: string): Promise<void>;
21
+ /** Restarts allowed per collection per window, per budget (backstop, webhook). */
22
+ export declare const MAX_RESTARTS = 3;
23
+ /** The window the count is kept over — it starts at the first restart, not at midnight. */
24
+ export declare const RESTART_WINDOW_MS = 86400000;
25
+ export interface RestartBudget {
26
+ allowed: boolean;
27
+ /** `<count>|<windowStartIso>`, or null when over budget. */
28
+ value: string | null;
29
+ }
30
+ /**
31
+ * The budget as a pure function of the stored row and the clock. A row that cannot be read opens a
32
+ * fresh window rather than blocking forever: a brake on a loop, not a ledger.
33
+ */
34
+ export declare function spendRestart(stored: string | null, now: Date): RestartBudget;
35
+ export type RestartClaim = {
36
+ slot: string;
37
+ } | {
38
+ denied: "exhausted" | "raced";
39
+ };
40
+ /**
41
+ * Spend one restart from budget `name`, BEFORE the restart (one that then fails still counts —
42
+ * a restart that keeps failing is what the budget is for). A compare-and-swap on the value read:
43
+ * two overlapping passes spend ONE slot between them, and the loser hears `raced`.
44
+ */
45
+ export declare function claimRestart(db: Database, name: string, now: Date): Promise<RestartClaim>;
46
+ /** The coalesced scans waiting, oldest first, at most `limit`. */
47
+ export declare function dirtyScans(db: Database, limit: number): Promise<{
48
+ name: string;
49
+ value: string;
50
+ }[]>;
51
+ /**
52
+ * Delete restart budgets whose window ended more than a day ago — written for every collection ever
53
+ * restarted, and `spendRestart` opens a fresh window for such a row anyway. ISO-8601 in UTC
54
+ * compares as text; a value that does not parse is left for a human.
55
+ */
56
+ export declare function pruneBudgets(db: Database, now: Date): Promise<number>;
57
+ /** What the backstop reads of one collection's `openhands_hires` row before it refreshes it. */
58
+ export interface HireState {
59
+ checkedAt: string;
60
+ endsAt: string | null;
61
+ }
62
+ /**
63
+ * Write what the chain says about one collection's quota. `endsAt` undefined leaves the stored
64
+ * value alone: the mint step does not read the end date the way the window does.
65
+ */
66
+ export declare function refreshHire(db: Database, row: {
67
+ collectionId: string;
68
+ remaining: number;
69
+ mintQuota: number;
70
+ now: Date;
71
+ endsAt?: string | null;
72
+ }): Promise<void>;
73
+ export declare function hireStates(db: Database, ids: readonly string[]): Promise<Map<string, HireState>>;
74
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/lib/openhands/store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAKhD,6DAA6D;AAC7D,eAAO,MAAM,aAAa,WAAW,CAAC;AACtC,eAAO,MAAM,aAAa,WAAW,CAAC;AACtC,eAAO,MAAM,aAAa,WAAW,CAAC;AAEtC,yFAAyF;AACzF,eAAO,MAAM,iBAAiB,iBAAkB,MAAM,WAA+B,CAAC;AACtF,eAAO,MAAM,iBAAiB,iBAAkB,MAAM,WAAuC,CAAC;AAE9F,uFAAuF;AACvF,eAAO,MAAM,iBAAiB,gBAAgB,CAAC;AAC/C,eAAO,MAAM,aAAa,iBAAkB,MAAM,WAA0C,CAAC;AAE7F,wBAAsB,SAAS,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMlF;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKzF;AAED,wFAAwF;AACxF,wBAAsB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI5F;AAED,kFAAkF;AAClF,eAAO,MAAM,YAAY,IAAI,CAAC;AAC9B,2FAA2F;AAC3F,eAAO,MAAM,iBAAiB,WAAS,CAAC;AAExC,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,4DAA4D;IAC5D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,aAAa,CAW5E;AAED,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAA;CAAE,CAAC;AAEhF;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC,CAc/F;AAED,kEAAkE;AAClE,wBAAsB,UAAU,CAC9B,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAO5C;AAKD;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAU3E;AAED,gGAAgG;AAChG,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,QAAQ,EACZ,GAAG,EAAE;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,IAAI,CAAC;IACV,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,GACA,OAAO,CAAC,IAAI,CAAC,CAgBf;AAKD,wBAAsB,UAAU,CAC9B,EAAE,EAAE,QAAQ,EACZ,GAAG,EAAE,SAAS,MAAM,EAAE,GACrB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAgBjC"}