@ixo/oracle-runtime-workers 0.34.1 → 0.35.1
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ixo/oracle-runtime-workers",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.1",
|
|
4
4
|
"description": "QiForge oracle runtime for Cloudflare Workers — Durable Object per user (SQLite in WASM over DO storage), Matrix E2EE gateway Durable Object, Hono HTTP/SSE shell.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"protobufjs": "^7.6.5",
|
|
68
68
|
"@ixo/topic-protocol": "1.0.0-rc.8",
|
|
69
69
|
"survey-core": "2.3.11",
|
|
70
|
-
"@ixo/common": "1.7.
|
|
70
|
+
"@ixo/common": "1.7.2",
|
|
71
71
|
"@ixo/ixo-transaction": "0.2.0",
|
|
72
72
|
"@ixo/ucan": "2.3.0"
|
|
73
73
|
},
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* (`GET /v1/entities`): one programme each, in the worker's order.
|
|
8
8
|
* 2. Every claim collection those entities own, from Blocksync.
|
|
9
9
|
* 3. Minus the collections admins hid per entity (`GET /v1/collections/<entity>`).
|
|
10
|
+
* 4. Plus the host's bot-only jobs ({@link BotOnlyJob}): test collections
|
|
11
|
+
* offered in chat but not on jambo.yoma.world, after Jambo's programmes.
|
|
10
12
|
*
|
|
11
13
|
* Jambo still lists collections that are not open (state PAUSED or CLOSED),
|
|
12
14
|
* have ended or are full; this does not, as the chain refuses a claim on
|
|
@@ -107,6 +109,23 @@ export interface Programme {
|
|
|
107
109
|
unavailable?: true;
|
|
108
110
|
}
|
|
109
111
|
|
|
112
|
+
/**
|
|
113
|
+
* A job the host offers in chat only, strictly for testing on a production
|
|
114
|
+
* collection: the test runs on mainnet with real cards and payments, while
|
|
115
|
+
* jambo.yoma.world, and so the partners' and opportunity providers' own
|
|
116
|
+
* listings and results, never show it. Not for real opportunities: those
|
|
117
|
+
* belong on Jambo's list. One collection of a programme's entity, shown
|
|
118
|
+
* whether or not Jambo lists the entity. Its programme comes after Jambo's
|
|
119
|
+
* (an entity Jambo lists keeps its place) and shows only its bot-only jobs;
|
|
120
|
+
* Jambo's hide list does not apply to it. The chain still decides whether it
|
|
121
|
+
* is open, so closing or pausing the collection ends the test.
|
|
122
|
+
*/
|
|
123
|
+
export interface BotOnlyJob {
|
|
124
|
+
/** The entity that owns the collection (`did:ixo:entity:…`). */
|
|
125
|
+
programme: string;
|
|
126
|
+
collectionId: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
110
129
|
export interface Catalog {
|
|
111
130
|
programmes: Programme[];
|
|
112
131
|
jobs: Job[];
|
|
@@ -473,23 +492,34 @@ export function resetCatalogCache(): void {
|
|
|
473
492
|
* (`untilAborted`). A complete list is kept five minutes; a failed build, or
|
|
474
493
|
* one with a piece missing (a hide list, a profile, a job's name), 30
|
|
475
494
|
* seconds, so a struggling upstream is not asked by every call and a gap
|
|
476
|
-
* does not last.
|
|
495
|
+
* does not last. `botOnly`: the host's bot-only jobs (malformed ones are
|
|
496
|
+
* left out).
|
|
477
497
|
*/
|
|
478
498
|
export async function loadCatalog(
|
|
479
499
|
call: ChainCall,
|
|
480
500
|
now = new Date(),
|
|
501
|
+
botOnly: readonly BotOnlyJob[] = [],
|
|
481
502
|
): Promise<Outcome<Catalog>> {
|
|
482
503
|
const base = JOB_LIST_URLS[call.network];
|
|
483
|
-
|
|
484
|
-
|
|
504
|
+
const extra = botOnly.filter(
|
|
505
|
+
(job) =>
|
|
506
|
+
ENTITY_DID.test(job.programme) && COLLECTION_ID.test(job.collectionId),
|
|
507
|
+
);
|
|
508
|
+
if (!base && !extra.length)
|
|
509
|
+
return { ok: true, value: { programmes: [], jobs: [] } };
|
|
510
|
+
const key = [
|
|
511
|
+
call.network,
|
|
512
|
+
...extra.map((job) => `${job.programme}/${job.collectionId}`),
|
|
513
|
+
].join('|');
|
|
514
|
+
let entry = listCache.get(key);
|
|
485
515
|
if (!entry || now.getTime() - entry.at >= entry.ttl) {
|
|
486
516
|
const started = {
|
|
487
517
|
at: now.getTime(),
|
|
488
518
|
ttl: LIST_TTL_MS,
|
|
489
|
-
pending: startBuild(call, base, now),
|
|
519
|
+
pending: startBuild(call, base, now, extra),
|
|
490
520
|
};
|
|
491
521
|
entry = started;
|
|
492
|
-
listCache.set(
|
|
522
|
+
listCache.set(key, started);
|
|
493
523
|
void started.pending.then((built) => {
|
|
494
524
|
if (!built.ok || built.value.incomplete) started.ttl = FAILED_TTL_MS;
|
|
495
525
|
});
|
|
@@ -500,8 +530,9 @@ export async function loadCatalog(
|
|
|
500
530
|
/** The build's reads: every caller's network and endpoints, but the build's own permits, deadline and abort. */
|
|
501
531
|
function startBuild(
|
|
502
532
|
caller: ChainCall,
|
|
503
|
-
base: string,
|
|
533
|
+
base: string | undefined,
|
|
504
534
|
now: Date,
|
|
535
|
+
botOnly: readonly BotOnlyJob[],
|
|
505
536
|
): Promise<Outcome<Catalog>> {
|
|
506
537
|
const controller = new AbortController();
|
|
507
538
|
const call: ChainCall = {
|
|
@@ -518,7 +549,7 @@ function startBuild(
|
|
|
518
549
|
() => controller.abort(new Error('catalog build deadline')),
|
|
519
550
|
TOOL_BUDGET_MS + 1_000,
|
|
520
551
|
);
|
|
521
|
-
return buildCatalog(call, base, now)
|
|
552
|
+
return buildCatalog(call, base, now, botOnly)
|
|
522
553
|
.catch((error: unknown): Outcome<Catalog> => {
|
|
523
554
|
console.warn(
|
|
524
555
|
`[yoma] catalog build failed: ${error instanceof Error ? error.name : 'unknown'}`,
|
|
@@ -593,24 +624,43 @@ async function openCollections(
|
|
|
593
624
|
|
|
594
625
|
async function buildCatalog(
|
|
595
626
|
call: ChainCall,
|
|
596
|
-
base: string,
|
|
627
|
+
base: string | undefined,
|
|
597
628
|
now: Date,
|
|
629
|
+
botOnly: readonly BotOnlyJob[],
|
|
598
630
|
): Promise<Outcome<Catalog>> {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
631
|
+
let jamboIds: string[] = [];
|
|
632
|
+
if (base) {
|
|
633
|
+
const listed = await getJson(
|
|
634
|
+
call,
|
|
635
|
+
'job-list',
|
|
636
|
+
`${base}/v1/entities`,
|
|
637
|
+
entityListSchema,
|
|
638
|
+
{
|
|
639
|
+
label: 'job list',
|
|
640
|
+
maxBytes: JOB_LIST_MAX_BYTES,
|
|
641
|
+
what: 'The Yoma job list',
|
|
642
|
+
},
|
|
643
|
+
);
|
|
644
|
+
if (!listed.ok) return listed;
|
|
645
|
+
jamboIds = [
|
|
646
|
+
...new Set(
|
|
647
|
+
listed.value.data.entities.filter((id) => ENTITY_DID.test(id)),
|
|
648
|
+
),
|
|
649
|
+
].slice(0, MAX_PROGRAMMES);
|
|
650
|
+
}
|
|
651
|
+
// Bot-only programmes after Jambo's; one Jambo lists keeps its place.
|
|
652
|
+
const onJambo = new Set(jamboIds);
|
|
611
653
|
const entityIds = [
|
|
612
|
-
...
|
|
613
|
-
|
|
654
|
+
...jamboIds,
|
|
655
|
+
...new Set(
|
|
656
|
+
botOnly.map((job) => job.programme).filter((id) => !onJambo.has(id)),
|
|
657
|
+
),
|
|
658
|
+
];
|
|
659
|
+
const botOnlyJobs = new Set(
|
|
660
|
+
botOnly.map((job) => `${job.programme}/${job.collectionId}`),
|
|
661
|
+
);
|
|
662
|
+
const isBotOnly = (c: { entity: string; id: string }) =>
|
|
663
|
+
botOnlyJobs.has(`${c.entity}/${c.id}`);
|
|
614
664
|
if (!entityIds.length)
|
|
615
665
|
return { ok: true, value: { programmes: [], jobs: [] } };
|
|
616
666
|
|
|
@@ -620,8 +670,9 @@ async function buildCatalog(
|
|
|
620
670
|
// list stands. A 404 is a programme with nothing hidden. Each hide list,
|
|
621
671
|
// profile and form keeps its own health mark here, so one 503 holds back
|
|
622
672
|
// only that address, after its own retry, not the rest of the build.
|
|
673
|
+
// A bot-only programme has no hide list: only its bot-only jobs show.
|
|
623
674
|
const [hidden, collections, programmeEntities] = await Promise.all([
|
|
624
|
-
mapLimit(
|
|
675
|
+
mapLimit(jamboIds, (id) =>
|
|
625
676
|
getJson(
|
|
626
677
|
call,
|
|
627
678
|
'job-list',
|
|
@@ -644,7 +695,7 @@ async function buildCatalog(
|
|
|
644
695
|
const hiddenBy = new Map<string, Set<string>>();
|
|
645
696
|
const unreadable = new Set<string>();
|
|
646
697
|
hidden.forEach((read, index) => {
|
|
647
|
-
const id =
|
|
698
|
+
const id = jamboIds[index]!;
|
|
648
699
|
if (read.ok) hiddenBy.set(id, new Set(read.value.data?.blacklist ?? []));
|
|
649
700
|
else if (read.failure.reason === 'not_found') hiddenBy.set(id, new Set());
|
|
650
701
|
else unreadable.add(id);
|
|
@@ -655,8 +706,10 @@ async function buildCatalog(
|
|
|
655
706
|
const allOpen = collections.value.rows
|
|
656
707
|
.filter(
|
|
657
708
|
(c) =>
|
|
658
|
-
|
|
659
|
-
|
|
709
|
+
(isBotOnly(c) ||
|
|
710
|
+
(onJambo.has(c.entity) &&
|
|
711
|
+
!unreadable.has(c.entity) &&
|
|
712
|
+
!hiddenBy.get(c.entity)?.has(c.id))) &&
|
|
660
713
|
isOpen(c, now),
|
|
661
714
|
)
|
|
662
715
|
.sort(
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
loadForm,
|
|
26
26
|
matchesQuery,
|
|
27
27
|
notOpen,
|
|
28
|
+
type BotOnlyJob,
|
|
28
29
|
type Catalog,
|
|
29
30
|
type FormKind,
|
|
30
31
|
type Job,
|
|
@@ -633,6 +634,8 @@ export interface YomaPluginOptions {
|
|
|
633
634
|
getClef?: () => ClefRunner | undefined;
|
|
634
635
|
/** Collections where a finished application gives permission at once, granted by the host. Absent: every job waits for acceptance. */
|
|
635
636
|
instantPermission?: InstantPermission;
|
|
637
|
+
/** Test collections offered in chat but not on jambo.yoma.world, so partners' and opportunity providers' listings are untouched (see {@link BotOnlyJob}). Testing only. */
|
|
638
|
+
botOnlyJobs?: readonly BotOnlyJob[];
|
|
636
639
|
}
|
|
637
640
|
|
|
638
641
|
const manifest: PluginManifest = {
|
|
@@ -805,7 +808,8 @@ export class YomaPlugin extends OraclePlugin {
|
|
|
805
808
|
if (!call) return NO_NETWORK;
|
|
806
809
|
// The list is read once for every request that needs it.
|
|
807
810
|
let catalog: Promise<Outcome<Catalog>> | undefined;
|
|
808
|
-
const getCatalog = () =>
|
|
811
|
+
const getCatalog = () =>
|
|
812
|
+
(catalog ??= loadCatalog(call, undefined, this.options.botOnlyJobs));
|
|
809
813
|
const results: Array<Record<string, unknown>> = [];
|
|
810
814
|
for (const request of input.requests) {
|
|
811
815
|
try {
|
|
@@ -1701,7 +1705,7 @@ export class YomaPlugin extends OraclePlugin {
|
|
|
1701
1705
|
}
|
|
1702
1706
|
let job: Job | undefined;
|
|
1703
1707
|
if (!draft) {
|
|
1704
|
-
const read = await loadCatalog(call);
|
|
1708
|
+
const read = await loadCatalog(call, undefined, this.options.botOnlyJobs);
|
|
1705
1709
|
if (!read.ok) return failed(read.failure);
|
|
1706
1710
|
job = read.value.jobs.find((j) => j.collectionId === input.collectionId);
|
|
1707
1711
|
if (!job) return failed(notOpen('job'));
|