@ixo/oracle-runtime-workers 0.41.3 → 0.42.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.
@@ -1,5 +1,7 @@
1
+ import { ITrxMsgSchema } from '@ixo/ixo-transaction';
1
2
  import { z } from 'zod';
2
3
  import type { RuntimeContext, UserKvSurface } from '../../plugin-api/types';
4
+ import type { SentPodBatch } from './chain-gateway';
3
5
 
4
6
  /**
5
7
  * Per-(user, thread) state machine for the create path's propose → approve →
@@ -16,9 +18,13 @@ import type { RuntimeContext, UserKvSurface } from '../../plugin-api/types';
16
18
  * message after seeing the batch before it can be approved, so a single model
17
19
  * turn — steered, say, by injected text in the brief or a section — cannot run
18
20
  * prepare → approve → sign on its own.
21
+ *
22
+ * After the commit it keeps what was sent for signing (confirmation checks
23
+ * the transaction against it), the POD confirmed from it, and the one domain
24
+ * card step that may follow.
19
25
  */
20
26
  export interface CreateSessionStore {
21
- /** Record a freshly prepared batch; any prior approval is superseded. */
27
+ /** Record a freshly prepared batch; anything before it in the thread is superseded. */
22
28
  prepared(
23
29
  userDid: string,
24
30
  threadId: string,
@@ -37,15 +43,57 @@ export interface CreateSessionStore {
37
43
  requestId: string,
38
44
  ): Promise<ApproveOutcome>;
39
45
  /**
40
- * Spend the approval for a sign dispatch. Returns true only when `blobId`
41
- * is the approved pending batch; the approval is cleared when it matches, so
42
- * a second dispatch needs a fresh approve.
46
+ * Spend the approval for a sign dispatch of `sent`. Returns true only when
47
+ * `blobId` is the approved pending batch; the approval is cleared when it
48
+ * matches (a second dispatch needs a fresh approve) and `sent` is recorded
49
+ * as what the wallet was asked to sign.
43
50
  */
44
- consume(userDid: string, threadId: string, blobId: string): Promise<boolean>;
45
- /** Drop the session (after on-chain confirmation). */
51
+ consume(
52
+ userDid: string,
53
+ threadId: string,
54
+ blobId: string,
55
+ sent?: SentPodBatch,
56
+ ): Promise<boolean>;
57
+ /** The thread's approval, what its last sign request carried, and the POD confirmed from it. */
58
+ read(
59
+ userDid: string,
60
+ threadId: string,
61
+ ): Promise<CreateSessionView | undefined>;
62
+ /** Record the POD confirmed from the sent batch (no-op when nothing was sent). */
63
+ confirmed(
64
+ userDid: string,
65
+ threadId: string,
66
+ pod: { podDid: string; txHash: string },
67
+ ): Promise<void>;
68
+ /**
69
+ * Claim the confirmed POD's one domain card step. `no-pod` when no POD
70
+ * was confirmed in the thread, `claimed` when the card is being added or
71
+ * was added already.
72
+ */
73
+ claimCard(userDid: string, threadId: string): Promise<CardClaim>;
74
+ /** End a claimed card step: `added` keeps it closed, `released` lets it run again. */
75
+ settleCard(
76
+ userDid: string,
77
+ threadId: string,
78
+ outcome: 'added' | 'released',
79
+ ): Promise<void>;
80
+ /** Drop the session (the design was restarted). */
46
81
  clear(userDid: string, threadId: string): Promise<void>;
47
82
  }
48
83
 
84
+ /** The session as the create tools read it. */
85
+ export interface CreateSessionView {
86
+ /** The prepared batch while it holds an approval not spent yet. */
87
+ approvedBlobId?: string;
88
+ sent?: SentPodBatch;
89
+ confirmed?: { podDid: string; txHash: string };
90
+ }
91
+
92
+ export type CardClaim =
93
+ | { status: 'ok'; podDid: string; sent: SentPodBatch }
94
+ | { status: 'no-pod' }
95
+ | { status: 'claimed'; state: 'sending' | 'added' };
96
+
49
97
  /** Resolves the create-session store for one request (it lives in the user's database). */
50
98
  export type CreateSessionStoreFor = (ctx: RuntimeContext) => CreateSessionStore;
51
99
 
@@ -54,11 +102,25 @@ export type ApproveOutcome = 'approved' | 'not-prepared' | 'same-request';
54
102
  /** `ctx.kv` namespace holding one create session per (user, thread). */
55
103
  export const CREATE_SESSION_NAMESPACE = 'pod-creator/create-sessions';
56
104
 
105
+ const sentSchema = z.object({
106
+ messages: z.array(ITrxMsgSchema),
107
+ profile: z.object({
108
+ name: z.string(),
109
+ description: z.string().optional(),
110
+ }),
111
+ });
112
+
57
113
  const createSessionSchema = z.object({
58
114
  preparedBlobId: z.string(),
59
115
  /** The request (turn) that prepared the batch. */
60
116
  preparedRequestId: z.string(),
61
117
  approved: z.boolean(),
118
+ /** What the last sign request of the prepared batch carried. */
119
+ sent: sentSchema.optional(),
120
+ /** The POD confirmed from `sent`. */
121
+ confirmed: z.object({ podDid: z.string(), txHash: z.string() }).optional(),
122
+ /** The confirmed POD's domain card: being added (claimed), or added. */
123
+ card: z.enum(['sending', 'added']).optional(),
62
124
  });
63
125
 
64
126
  type CreateSession = z.infer<typeof createSessionSchema>;
@@ -156,6 +218,7 @@ export class KvCreateSessionStore implements CreateSessionStore {
156
218
  userDid: string,
157
219
  threadId: string,
158
220
  blobId: string,
221
+ sent?: SentPodBatch,
159
222
  ): Promise<boolean> {
160
223
  let spent = false;
161
224
  await this.kv.update(
@@ -167,13 +230,102 @@ export class KvCreateSessionStore implements CreateSessionStore {
167
230
  session !== undefined &&
168
231
  session.preparedBlobId === blobId &&
169
232
  session.approved;
170
- return session && spent ? { ...session, approved: false } : session;
233
+ if (!session || !spent) return session;
234
+ return {
235
+ ...session,
236
+ approved: false,
237
+ ...(sent !== undefined ? { sent } : {}),
238
+ };
171
239
  },
172
240
  this.writeOptions,
173
241
  );
174
242
  return spent;
175
243
  }
176
244
 
245
+ async read(
246
+ userDid: string,
247
+ threadId: string,
248
+ ): Promise<CreateSessionView | undefined> {
249
+ const session = parseSession(
250
+ await this.kv.get(CREATE_SESSION_NAMESPACE, key(userDid, threadId)),
251
+ );
252
+ if (!session) return undefined;
253
+ return {
254
+ ...(session.approved ? { approvedBlobId: session.preparedBlobId } : {}),
255
+ ...(session.sent ? { sent: session.sent } : {}),
256
+ ...(session.confirmed ? { confirmed: session.confirmed } : {}),
257
+ };
258
+ }
259
+
260
+ async confirmed(
261
+ userDid: string,
262
+ threadId: string,
263
+ pod: { podDid: string; txHash: string },
264
+ ): Promise<void> {
265
+ await this.kv.update(
266
+ CREATE_SESSION_NAMESPACE,
267
+ key(userDid, threadId),
268
+ (current) => {
269
+ const session = parseSession(current);
270
+ if (!session?.sent) return session;
271
+ // The card belongs to the POD: another confirmed POD starts afresh.
272
+ const samePod = session.confirmed?.podDid === pod.podDid;
273
+ const { card: _card, ...rest } = session;
274
+ return {
275
+ ...(samePod ? session : rest),
276
+ confirmed: { podDid: pod.podDid, txHash: pod.txHash },
277
+ };
278
+ },
279
+ this.writeOptions,
280
+ );
281
+ }
282
+
283
+ async claimCard(userDid: string, threadId: string): Promise<CardClaim> {
284
+ let claim: CardClaim = { status: 'no-pod' };
285
+ await this.kv.update(
286
+ CREATE_SESSION_NAMESPACE,
287
+ key(userDid, threadId),
288
+ (current) => {
289
+ const session = parseSession(current);
290
+ if (!session?.confirmed || !session.sent) {
291
+ claim = { status: 'no-pod' };
292
+ return session;
293
+ }
294
+ if (session.card) {
295
+ claim = { status: 'claimed', state: session.card };
296
+ return session;
297
+ }
298
+ claim = {
299
+ status: 'ok',
300
+ podDid: session.confirmed.podDid,
301
+ sent: session.sent,
302
+ };
303
+ return { ...session, card: 'sending' };
304
+ },
305
+ this.writeOptions,
306
+ );
307
+ return claim;
308
+ }
309
+
310
+ async settleCard(
311
+ userDid: string,
312
+ threadId: string,
313
+ outcome: 'added' | 'released',
314
+ ): Promise<void> {
315
+ await this.kv.update(
316
+ CREATE_SESSION_NAMESPACE,
317
+ key(userDid, threadId),
318
+ (current) => {
319
+ const session = parseSession(current);
320
+ if (!session || session.card !== 'sending') return session;
321
+ if (outcome === 'added') return { ...session, card: 'added' };
322
+ const { card: _card, ...rest } = session;
323
+ return rest;
324
+ },
325
+ this.writeOptions,
326
+ );
327
+ }
328
+
177
329
  async clear(userDid: string, threadId: string): Promise<void> {
178
330
  await this.kv.delete(CREATE_SESSION_NAMESPACE, key(userDid, threadId));
179
331
  }