@ixo/oracle-runtime-workers 0.22.0 → 0.23.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 +15 -13
- package/package.json +10 -4
- package/src/action-proposals/policy.ts +54 -0
- package/src/action-proposals/reconcile.ts +87 -0
- package/src/action-proposals/store.ts +863 -0
- package/src/action-proposals/topic-budget.ts +84 -0
- package/src/action-proposals/topic-chain.ts +272 -0
- package/src/action-proposals/topic-effects.ts +44 -0
- package/src/action-proposals/topic-errors.ts +79 -0
- package/src/action-proposals/topic-history.ts +267 -0
- package/src/action-proposals/topic-matrix.ts +292 -0
- package/src/action-proposals/topic-prepare.ts +299 -0
- package/src/action-proposals/topic-store.ts +999 -0
- package/src/action-proposals/topic-verify.ts +279 -0
- package/src/action-proposals/verify.ts +223 -0
- package/src/channels/auth.ts +6 -1
- package/src/channels/contract.ts +4 -0
- package/src/channels/turns.ts +1 -0
- package/src/core/env.ts +89 -5
- package/src/core/main-agent-types.ts +8 -0
- package/src/core/main-agent.ts +2 -0
- package/src/core/runtime-context.ts +12 -0
- package/src/delivery/plan.ts +43 -2
- package/src/delivery/schema.ts +25 -1
- package/src/delivery/types.ts +13 -2
- package/src/do/ambient.ts +6 -0
- package/src/do/contracts.ts +39 -0
- package/src/do/user-oracle-do.ts +1253 -7
- package/src/hub-actions/client.ts +163 -0
- package/src/hub-actions/request.ts +110 -0
- package/src/hub-actions/store.ts +485 -0
- package/src/matrix/gateway-do.ts +176 -0
- package/src/matrix/reply-parts.ts +8 -2
- package/src/plugin-api/types.ts +54 -0
- package/src/plugins/action-proposals/action-proposals.plugin.ts +486 -0
- package/src/plugins/action-proposals/evidence.ts +67 -0
- package/src/plugins/hub-actions/hub-actions.plugin.ts +244 -0
- package/src/plugins/index.ts +6 -0
- package/src/shell/app.ts +195 -1
package/README.md
CHANGED
|
@@ -104,19 +104,21 @@ lifetime are in [operations](docs/operations.md).
|
|
|
104
104
|
|
|
105
105
|
## Documentation
|
|
106
106
|
|
|
107
|
-
| Page
|
|
108
|
-
|
|
|
109
|
-
| [architecture](docs/architecture.md)
|
|
110
|
-
| [node-parity](docs/node-parity.md)
|
|
111
|
-
| [configuration](docs/configuration.md)
|
|
112
|
-
| [operations](docs/operations.md)
|
|
113
|
-
| [chat-delivery](docs/chat-delivery.md)
|
|
114
|
-
| [channels](docs/channels.md)
|
|
115
|
-
| [frontend-bridge](docs/frontend-bridge.md)
|
|
116
|
-
| [pod-creator](docs/pod-creator.md)
|
|
117
|
-
| [ixo-transaction](docs/ixo-transaction.md)
|
|
118
|
-
| [
|
|
119
|
-
| [
|
|
107
|
+
| Page | What it covers |
|
|
108
|
+
| -------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
109
|
+
| [architecture](docs/architecture.md) | The objects, the gateway split, self-sovereign storage, cost model, workerd rules, tasks |
|
|
110
|
+
| [node-parity](docs/node-parity.md) | What is identical to the Node runtime, what differs, what is Workers-only, what is not ported |
|
|
111
|
+
| [configuration](docs/configuration.md) | Wrangler config, migrations, every env var and binding, `createOracleWorker` options, setup |
|
|
112
|
+
| [operations](docs/operations.md) | Routes, status fields, gateway and user-object lifecycle, the ChatGPT proxy, the runbook |
|
|
113
|
+
| [chat-delivery](docs/chat-delivery.md) | Chat replies on WhatsApp and Matrix: profiles, Reply Plans, artefacts and their links |
|
|
114
|
+
| [channels](docs/channels.md) | The IXO Channels ingress: authorization, retries, Reply Plans, Matrix continuity |
|
|
115
|
+
| [frontend-bridge](docs/frontend-bridge.md) | Browser tools and AG-UI actions over the socket: one invocation, one socket, unknown outcomes |
|
|
116
|
+
| [pod-creator](docs/pod-creator.md) | The POD Creator plugin: roles, state in `ctx.kv`, the create path and its launch gate |
|
|
117
|
+
| [ixo-transaction](docs/ixo-transaction.md) | The opt-in wallet-signing plugin: chain ids, signing outcomes, the mainnet receipt rule |
|
|
118
|
+
| [action-proposals](docs/action-proposals.md) | Action cards: a transfer prepared from chat, signed in the Portal, verified on chain, receipted |
|
|
119
|
+
| [topic-action-proposals](docs/topic-action-proposals.md) | Topic cards: discussion drafts and claim-access grants as multi-step journals with one-use permits |
|
|
120
|
+
| [testing](docs/testing.md) | Unit, harness e2e, the devnet feature matrix, stress |
|
|
121
|
+
| [load-tests](docs/load-tests.md) | Measurements and the findings that shaped the gateway |
|
|
120
122
|
|
|
121
123
|
## Status (September 2026)
|
|
122
124
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ixo/oracle-runtime-workers",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
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": {
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
"exports": {
|
|
20
20
|
".": "./src/index.ts",
|
|
21
21
|
"./gateway": "./src/gateway-worker.ts",
|
|
22
|
+
"./hub-actions/client": "./src/hub-actions/client.ts",
|
|
23
|
+
"./hub-actions/request": "./src/hub-actions/request.ts",
|
|
22
24
|
"./prompt": "./src/core/prompt-composer.ts"
|
|
23
25
|
},
|
|
24
26
|
"files": [
|
|
@@ -31,6 +33,7 @@
|
|
|
31
33
|
"!src/plugins/pod-creator/test-fixtures.ts"
|
|
32
34
|
],
|
|
33
35
|
"dependencies": {
|
|
36
|
+
"@ixo/action-protocol": "0.4.1",
|
|
34
37
|
"@blocknote/server-util": "0.29.1",
|
|
35
38
|
"@composio/core": "0.10.0",
|
|
36
39
|
"@composio/langchain": "0.9.2",
|
|
@@ -58,9 +61,12 @@
|
|
|
58
61
|
"wa-sqlite": "1.0.0",
|
|
59
62
|
"yjs": "13.6.32",
|
|
60
63
|
"zod": "^4.4.3",
|
|
61
|
-
"@ixo/
|
|
62
|
-
"
|
|
63
|
-
"@ixo/
|
|
64
|
+
"@ixo/impactxclient-sdk": "^3.0.0",
|
|
65
|
+
"protobufjs": "^7.6.5",
|
|
66
|
+
"@ixo/topic-protocol": "1.0.0-rc.8",
|
|
67
|
+
"@ixo/common": "1.7.1",
|
|
68
|
+
"@ixo/ixo-transaction": "0.2.0",
|
|
69
|
+
"@ixo/ucan": "2.3.0"
|
|
64
70
|
},
|
|
65
71
|
"devDependencies": {
|
|
66
72
|
"@cloudflare/vitest-pool-workers": "^0.22.0",
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ActionProposalSubject } from '@ixo/ixo-transaction/action-proposals';
|
|
2
|
+
import { ActionProposalConflict } from './store';
|
|
3
|
+
|
|
4
|
+
/** How long a prepared card can be claimed: the plugin's expiry and the owner object's ceiling. */
|
|
5
|
+
export const ACTION_PROPOSAL_TTL_MS = 15 * 60_000;
|
|
6
|
+
/** How far a card's `createdAt` may sit from the object's clock. */
|
|
7
|
+
export const ACTION_PROPOSAL_CLOCK_SKEW_MS = 5 * 60_000;
|
|
8
|
+
|
|
9
|
+
type Network = ActionProposalSubject['action']['network'];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* What the owner object requires of every card before it is stored, whichever
|
|
13
|
+
* plugin prepared it: the authenticated owner signs from their own account,
|
|
14
|
+
* on a non-mainnet chain the Worker is configured for and can verify against,
|
|
15
|
+
* stamped now, expiring inside the policy window. The contract schema already
|
|
16
|
+
* pins the memo to the card id and one transfer per card.
|
|
17
|
+
*/
|
|
18
|
+
export function assertActionProposalPolicy(
|
|
19
|
+
subject: ActionProposalSubject,
|
|
20
|
+
policy: {
|
|
21
|
+
ownerDid: string;
|
|
22
|
+
chainIds: Readonly<Record<Network, string>>;
|
|
23
|
+
rpc: Readonly<Record<Network, string | undefined>>;
|
|
24
|
+
now: number;
|
|
25
|
+
},
|
|
26
|
+
): void {
|
|
27
|
+
if (subject.ownerDid !== policy.ownerDid)
|
|
28
|
+
throw new ActionProposalConflict('Proposal owner mismatch');
|
|
29
|
+
if (subject.action.network === 'mainnet')
|
|
30
|
+
throw new ActionProposalConflict('Mainnet action proposals are disabled');
|
|
31
|
+
if (subject.action.signerAddress !== policy.ownerDid.replace(/^did:ixo:/, ''))
|
|
32
|
+
throw new ActionProposalConflict(
|
|
33
|
+
'The signer must be the authenticated account',
|
|
34
|
+
);
|
|
35
|
+
if (subject.action.chainId !== policy.chainIds[subject.action.network])
|
|
36
|
+
throw new ActionProposalConflict(
|
|
37
|
+
'Chain ID does not match the configured network',
|
|
38
|
+
);
|
|
39
|
+
// A card the Worker could never verify would sit `unknown` for ever.
|
|
40
|
+
if (!policy.rpc[subject.action.network])
|
|
41
|
+
throw new ActionProposalConflict(
|
|
42
|
+
`No chain endpoint is configured for ${subject.action.network}`,
|
|
43
|
+
);
|
|
44
|
+
const createdAt = Date.parse(subject.createdAt);
|
|
45
|
+
if (Math.abs(createdAt - policy.now) > ACTION_PROPOSAL_CLOCK_SKEW_MS)
|
|
46
|
+
throw new ActionProposalConflict('Proposal timestamp is not current');
|
|
47
|
+
if (
|
|
48
|
+
Date.parse(subject.expiresAt) >
|
|
49
|
+
policy.now + ACTION_PROPOSAL_TTL_MS + ACTION_PROPOSAL_CLOCK_SKEW_MS
|
|
50
|
+
)
|
|
51
|
+
throw new ActionProposalConflict(
|
|
52
|
+
'Proposal expiry exceeds the policy window',
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { ActionProposal } from '@ixo/ixo-transaction/action-proposals';
|
|
2
|
+
import {
|
|
3
|
+
TRANSACTION_CARD_BOUND_MISMATCH,
|
|
4
|
+
TRANSACTION_REJECTIONS,
|
|
5
|
+
type ActionProposalStore,
|
|
6
|
+
} from './store';
|
|
7
|
+
import { TRANSACTION_NOT_FOUND, verifyActionTransaction } from './verify';
|
|
8
|
+
export async function refreshActionProposal(
|
|
9
|
+
store: ActionProposalStore,
|
|
10
|
+
proposal: ActionProposal,
|
|
11
|
+
rpc: string | undefined,
|
|
12
|
+
fetcher: typeof fetch = fetch,
|
|
13
|
+
/** The caller's wall budget: an abort ends the lookup like an RPC outage. */
|
|
14
|
+
signal?: AbortSignal,
|
|
15
|
+
): Promise<{ proposal: ActionProposal; attempted: boolean }> {
|
|
16
|
+
if (
|
|
17
|
+
proposal.lifecycle.status !== 'unknown' ||
|
|
18
|
+
!proposal.lifecycle.transactionHash ||
|
|
19
|
+
proposal.lifecycle.executionBlockedAt
|
|
20
|
+
)
|
|
21
|
+
return { proposal, attempted: false };
|
|
22
|
+
if (!(await store.claimVerification(proposal.ownerDid, proposal.id)))
|
|
23
|
+
return { proposal, attempted: false };
|
|
24
|
+
if (!rpc) {
|
|
25
|
+
// No endpoint for this card's network (removed after the card was
|
|
26
|
+
// prepared; preparation refuses such a network): the cooldown slot is
|
|
27
|
+
// spent so the alarm backs off and stops instead of ticking every second.
|
|
28
|
+
console.warn(
|
|
29
|
+
`[action-proposals] verification of ${proposal.id} pending: no RPC configured for ${proposal.action.network}`,
|
|
30
|
+
);
|
|
31
|
+
return { proposal, attempted: false };
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const verified = await verifyActionTransaction(
|
|
35
|
+
proposal,
|
|
36
|
+
proposal.lifecycle.transactionHash,
|
|
37
|
+
rpc,
|
|
38
|
+
fetcher,
|
|
39
|
+
signal,
|
|
40
|
+
);
|
|
41
|
+
return {
|
|
42
|
+
proposal:
|
|
43
|
+
(await store.recordVerified(
|
|
44
|
+
proposal.ownerDid,
|
|
45
|
+
proposal.id,
|
|
46
|
+
verified,
|
|
47
|
+
)) ?? proposal,
|
|
48
|
+
attempted: true,
|
|
49
|
+
};
|
|
50
|
+
} catch (error) {
|
|
51
|
+
// Indexing lag, an RPC outage and a byte mismatch all leave the card
|
|
52
|
+
// `unknown`; the reason is for the operator, never for the user. A
|
|
53
|
+
// memo mismatch proves the hash is not this card's, so a later
|
|
54
|
+
// reconcile may replace it; a transaction with this card's memo and
|
|
55
|
+
// other messages is the owner's card-bound effect gone wrong, so the
|
|
56
|
+
// card is blocked with the hash listed.
|
|
57
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
58
|
+
console.warn(
|
|
59
|
+
`[action-proposals] verification of ${proposal.id} pending: ${message}`,
|
|
60
|
+
);
|
|
61
|
+
if (message === TRANSACTION_NOT_FOUND)
|
|
62
|
+
// Inside the grace window after the hash arrived this is the
|
|
63
|
+
// broadcast or the indexer still in flight, not a spent attempt.
|
|
64
|
+
await store.deferNotFound(
|
|
65
|
+
proposal.ownerDid,
|
|
66
|
+
proposal.id,
|
|
67
|
+
proposal.lifecycle.transactionHash,
|
|
68
|
+
);
|
|
69
|
+
else if (message === TRANSACTION_CARD_BOUND_MISMATCH)
|
|
70
|
+
await store.blockExecution(
|
|
71
|
+
proposal.ownerDid,
|
|
72
|
+
proposal.id,
|
|
73
|
+
message,
|
|
74
|
+
proposal.lifecycle.transactionHash,
|
|
75
|
+
);
|
|
76
|
+
else if (TRANSACTION_REJECTIONS.has(message))
|
|
77
|
+
await store.markTransactionRejected(
|
|
78
|
+
proposal.ownerDid,
|
|
79
|
+
proposal.id,
|
|
80
|
+
proposal.lifecycle.transactionHash,
|
|
81
|
+
);
|
|
82
|
+
return {
|
|
83
|
+
proposal: (await store.get(proposal.ownerDid, proposal.id)) ?? proposal,
|
|
84
|
+
attempted: true,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|