@ixo/editor 6.31.0 → 6.31.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.
@@ -14,8 +14,10 @@ import {
14
14
  swapFlowBlocks,
15
15
  updateNodeRuntime,
16
16
  upsertFlowParticipant
17
- } from "../chunk-S7CAGFE3.js";
17
+ } from "../chunk-4475SO6R.js";
18
18
  import {
19
+ ACTION_MANIFEST_VERSION,
20
+ ACTION_REGISTRY_VERSION,
19
21
  ExplicitRunRequiredError,
20
22
  FLOW_CONNECTIONS_MAP_KEY,
21
23
  FLOW_CONNECTION_BINDINGS_MAP_KEY,
@@ -26,6 +28,8 @@ import {
26
28
  LATEST_VERSION,
27
29
  LEGACY_RUN_STORAGE_VERSION,
28
30
  MAX_RUN_ACTION_OUTPUT_BYTES,
31
+ MAX_TOPIC_SEMANTIC_RECORDS_PER_RECEIPT,
32
+ MAX_TOPIC_SEMANTIC_RECORD_BATCH_BYTES,
29
33
  MULTI_RUN_STORAGE_VERSION,
30
34
  NODE_CONTEXT_MAP_NAME,
31
35
  NODE_CONTEXT_OVERRIDES_MAP_NAME,
@@ -48,6 +52,7 @@ import {
48
52
  XERO_WORK_ITEMS_MAP_NAME,
49
53
  XERO_WORK_TERMINAL_MAP_NAME,
50
54
  acquireFlowAgentLease,
55
+ actionManifestIssues,
51
56
  actionTypeToCan,
52
57
  adoptLegacyRuntime,
53
58
  appendAgentLedgerEvent,
@@ -70,6 +75,7 @@ import {
70
75
  canToActionType,
71
76
  canToType,
72
77
  cancelRun,
78
+ canonicalActionJson,
73
79
  capabilityPatternCoversCan,
74
80
  classifyBlockerCause,
75
81
  classifyNodeState,
@@ -123,6 +129,7 @@ import {
123
129
  getAction,
124
130
  getActionByCan,
125
131
  getActionForBlock,
132
+ getActionPresentation,
126
133
  getActiveEditor,
127
134
  getAliasEntries,
128
135
  getAllActions,
@@ -237,6 +244,7 @@ import {
237
244
  setMultiRunStorage,
238
245
  setTempUcanEnforcementDisabled,
239
246
  setupFlowFromBaseUcan,
247
+ sha256Digest,
240
248
  snapshotInputRefs,
241
249
  snapshotNode,
242
250
  startRun,
@@ -256,18 +264,441 @@ import {
256
264
  validateActionProof,
257
265
  validateAgentCommand,
258
266
  validateFlowAgentLease,
267
+ validateTopicSemanticRecord,
268
+ validateTopicSemanticRecordBatch,
259
269
  verifyCompletion,
260
270
  writeActionState,
261
271
  writeCompiledBlocksToFragment,
262
272
  writeRunRecordAndReconcile,
263
273
  xeroOracleActivityForBlock,
264
274
  xeroOracleActivityForBlockForEditor
265
- } from "../chunk-ZXNBOVAA.js";
275
+ } from "../chunk-TBCPHCFA.js";
266
276
  import {
267
277
  computeCID,
268
278
  computeJsonCID
269
279
  } from "../chunk-ZPMRM6DA.js";
270
280
 
281
+ // src/core/lib/topicActions/topicActionBridge.ts
282
+ var TopicRevisionConflictError = class extends Error {
283
+ constructor(currentRevision) {
284
+ super(`Topic revision conflict; current revision is ${currentRevision}`);
285
+ this.currentRevision = currentRevision;
286
+ this.name = "TopicRevisionConflictError";
287
+ }
288
+ };
289
+ function mergeOutboxProgress(current, next) {
290
+ return {
291
+ ...current,
292
+ ...next,
293
+ attempts: Math.max(current.attempts, next.attempts),
294
+ receiptDelivery: next.receiptDelivery ?? current.receiptDelivery,
295
+ noticeDelivery: next.noticeDelivery ?? current.noticeDelivery
296
+ };
297
+ }
298
+ var InMemoryTopicActionOutboxStore = class {
299
+ constructor() {
300
+ this.entries = /* @__PURE__ */ new Map();
301
+ this.delivered = /* @__PURE__ */ new Set();
302
+ this.sequences = /* @__PURE__ */ new Map();
303
+ }
304
+ async nextSequence(topicId, requestId) {
305
+ const key = `${topicId}|${requestId}`;
306
+ const sequence = (this.sequences.get(key) || 0) + 1;
307
+ this.sequences.set(key, sequence);
308
+ return sequence;
309
+ }
310
+ async enqueue(entry) {
311
+ if (this.entries.has(entry.deduplicationKey) || this.delivered.has(entry.deduplicationKey)) return "duplicate";
312
+ this.entries.set(entry.deduplicationKey, structuredClone(entry));
313
+ return "queued";
314
+ }
315
+ async pending() {
316
+ return [...this.entries.values()].map((entry) => structuredClone(entry));
317
+ }
318
+ async update(entry) {
319
+ if (this.delivered.has(entry.deduplicationKey)) return;
320
+ const current = this.entries.get(entry.deduplicationKey);
321
+ this.entries.set(entry.deduplicationKey, structuredClone(current ? mergeOutboxProgress(current, entry) : entry));
322
+ }
323
+ async markDelivered(deduplicationKey) {
324
+ this.entries.delete(deduplicationKey);
325
+ this.delivered.add(deduplicationKey);
326
+ }
327
+ async has(deduplicationKey) {
328
+ return this.entries.has(deduplicationKey) || this.delivered.has(deduplicationKey);
329
+ }
330
+ };
331
+ var YjsTopicActionOutboxStore = class {
332
+ constructor(yDoc) {
333
+ this.yDoc = yDoc;
334
+ this.entries = yDoc.getMap("topicActionReceiptOutbox");
335
+ this.delivered = yDoc.getMap("topicActionReceiptDelivered");
336
+ this.sequences = yDoc.getMap("topicActionReceiptSequences");
337
+ }
338
+ async nextSequence(topicId, requestId) {
339
+ const key = `${topicId}|${requestId}`;
340
+ let sequence = 0;
341
+ this.yDoc.transact(() => {
342
+ sequence = (this.sequences.get(key) || 0) + 1;
343
+ this.sequences.set(key, sequence);
344
+ }, "topic-action-sequence");
345
+ return sequence;
346
+ }
347
+ async enqueue(entry) {
348
+ if (this.entries.has(entry.deduplicationKey) || this.delivered.has(entry.deduplicationKey)) return "duplicate";
349
+ this.entries.set(entry.deduplicationKey, structuredClone(entry));
350
+ return "queued";
351
+ }
352
+ async pending() {
353
+ return [...this.entries.values()].filter((entry) => !this.delivered.has(entry.deduplicationKey)).map((entry) => structuredClone(entry));
354
+ }
355
+ async update(entry) {
356
+ this.yDoc.transact(() => {
357
+ if (this.delivered.has(entry.deduplicationKey)) return;
358
+ const current = this.entries.get(entry.deduplicationKey);
359
+ this.entries.set(entry.deduplicationKey, structuredClone(current ? mergeOutboxProgress(current, entry) : entry));
360
+ }, "topic-action-outbox-progress");
361
+ }
362
+ async markDelivered(deduplicationKey, operationId, topicRevision) {
363
+ this.yDoc.transact(() => {
364
+ this.entries.delete(deduplicationKey);
365
+ this.delivered.set(deduplicationKey, { operationId, topicRevision });
366
+ }, "topic-action-delivered");
367
+ }
368
+ async has(deduplicationKey) {
369
+ return this.entries.has(deduplicationKey) || this.delivered.has(deduplicationKey);
370
+ }
371
+ };
372
+ function readPath(value, path) {
373
+ return path.split(".").reduce((current, segment) => current && typeof current === "object" ? current[segment] : void 0, value);
374
+ }
375
+ function safeSummary(value, sensitivePaths) {
376
+ if (!value) return void 0;
377
+ const sensitiveRoots = new Set(sensitivePaths.map((path) => path.split(".")[0]));
378
+ const summary = {};
379
+ for (const key of Object.keys(value).sort()) {
380
+ if (Object.keys(summary).length >= 20 || sensitiveRoots.has(key)) continue;
381
+ const item = value[key];
382
+ if (typeof item === "string") summary[key] = item.length <= 240 ? item : `${item.slice(0, 237)}\u2026`;
383
+ else if (typeof item === "number" || typeof item === "boolean" || item === null) summary[key] = item;
384
+ else if (Array.isArray(item)) summary[key] = { count: item.length };
385
+ else if (item && typeof item === "object") summary[key] = { digest: sha256Digest(item) };
386
+ }
387
+ return Object.keys(summary).length > 0 ? summary : void 0;
388
+ }
389
+ function proofFor(action, output) {
390
+ if (!output || action.proof === "none") return void 0;
391
+ if ("validate" in action.proof) return { kind: "custom", digest: sha256Digest(output) };
392
+ const values = action.proof.fields.map((path) => readPath(output, path)).filter((value) => value !== void 0 && value !== null && value !== "");
393
+ const refs = values.filter((value) => typeof value === "string" && value.length <= 500);
394
+ return { kind: "fields", digest: sha256Digest(values), ...refs.length > 0 ? { refs: [...new Set(refs)].slice(0, 50) } : {} };
395
+ }
396
+ function localPhaseDeduplicationKey(receipt) {
397
+ return sha256Digest({
398
+ topicId: receipt.topicId,
399
+ requestId: receipt.requestId,
400
+ executionId: receipt.runtime.executionId,
401
+ status: receipt.status,
402
+ idempotencyKey: receipt.idempotencyKey
403
+ });
404
+ }
405
+ function externalReceiptDeduplicationKey(receipt) {
406
+ return sha256Digest({ topicId: receipt.topicId, requestId: receipt.requestId, receiptId: receipt.receiptId, sequence: receipt.sequence });
407
+ }
408
+ function topicSemanticRecordsDigest(records) {
409
+ return sha256Digest(records);
410
+ }
411
+ function validateSemanticRecords(action, records) {
412
+ if (!action.topic) throw new Error("Action has no Topic policy");
413
+ const result = validateTopicSemanticRecordBatch(records, action.topic);
414
+ if (!result.valid) throw new Error(`Invalid Topic semantic record (${result.code})`);
415
+ }
416
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed", "rejected", "cancelled", "needs_verification"]);
417
+ var MAX_RECEIPT_DELIVERY_ATTEMPTS = 3;
418
+ function statusWording(status) {
419
+ switch (status) {
420
+ case "succeeded":
421
+ return "completed successfully";
422
+ case "failed":
423
+ return "failed";
424
+ case "rejected":
425
+ return "was rejected";
426
+ case "cancelled":
427
+ return "was cancelled";
428
+ case "needs_verification":
429
+ return "completed and needs verification";
430
+ }
431
+ }
432
+ function topicActionThreadUpdateBody(action, status, semanticRecords) {
433
+ const recordText = semanticRecords.length > 0 ? ` ${semanticRecords.map((record) => `${record.displayName}: ${record.description}`).join(" ")}` : "";
434
+ return `${action.displayName} ${statusWording(status)}. ${action.description}${recordText} This update does not change the Topic, contract, outcome, claim, or settlement lifecycle.`;
435
+ }
436
+ function topicActionThreadUpdateId(input) {
437
+ return sha256Digest(input);
438
+ }
439
+ function buildTopicActionThreadUpdate(receipt, action, semanticRecords) {
440
+ if (!TERMINAL_STATUSES.has(receipt.status)) return void 0;
441
+ const status = receipt.status;
442
+ const definitions = new Map((action.topic?.semanticRecordTypes || []).map((definition) => [definition.type, definition]));
443
+ const presentedRecords = semanticRecords.map((record) => {
444
+ const definition = definitions.get(record.type);
445
+ return {
446
+ recordId: record.id,
447
+ type: record.type,
448
+ version: record.version,
449
+ displayName: definition.displayName,
450
+ description: definition.description
451
+ };
452
+ });
453
+ const body = topicActionThreadUpdateBody(action, status, presentedRecords);
454
+ const semanticRecordReferences = presentedRecords.map(({ recordId, type, version }) => ({ recordId, type, version }));
455
+ return {
456
+ version: 1,
457
+ updateId: topicActionThreadUpdateId({
458
+ topicId: receipt.topicId,
459
+ requestId: receipt.requestId,
460
+ receiptId: receipt.receiptId,
461
+ status,
462
+ semanticRecords: semanticRecordReferences
463
+ }),
464
+ topicId: receipt.topicId,
465
+ requestId: receipt.requestId,
466
+ receipt: { receiptId: receipt.receiptId },
467
+ action: { type: action.type, displayName: action.displayName, description: action.description },
468
+ status,
469
+ occurredAt: receipt.occurredAt,
470
+ semanticRecords: presentedRecords,
471
+ body,
472
+ lifecycleEffect: "none"
473
+ };
474
+ }
475
+ function topicActionThreadUpdateTransactionId(update) {
476
+ return `topic-action-update-${update.updateId.slice("sha256:".length)}`;
477
+ }
478
+ var TopicActionBridge = class {
479
+ constructor(sink, signer, store = new InMemoryTopicActionOutboxStore(), verifier, now = () => /* @__PURE__ */ new Date()) {
480
+ this.sink = sink;
481
+ this.signer = signer;
482
+ this.store = store;
483
+ this.verifier = verifier;
484
+ this.now = now;
485
+ this.deliveries = /* @__PURE__ */ new Map();
486
+ }
487
+ async recordFlowPhase(params) {
488
+ const action = getAction(params.actionType);
489
+ if (!action) return { state: "rejected", error: `Unknown Action '${params.actionType}'` };
490
+ const manifestEntry = generateActionManifest().actions.find((entry) => entry.type === action.type);
491
+ if (!manifestEntry) return { state: "rejected", error: `Action '${action.type}' is absent from Manifest v4` };
492
+ const baseKind = params.topic.kind.source === "standard" ? params.topic.kind.kind : params.topic.kind.baseKind;
493
+ if (!action.topic?.supportedBaseKinds.includes(baseKind)) return { state: "rejected", error: `Action '${action.type}' does not support Topic Kind '${baseKind}'` };
494
+ const semanticRecords = params.semanticRecords || [];
495
+ try {
496
+ validateSemanticRecords(action, semanticRecords);
497
+ } catch (error) {
498
+ return { state: "rejected", error: error instanceof Error ? error.message : "Invalid Topic semantic records" };
499
+ }
500
+ const stablePhaseKey = localPhaseDeduplicationKey({
501
+ topicId: params.topic.topicId,
502
+ requestId: params.topic.requestId,
503
+ status: params.status,
504
+ idempotencyKey: params.topic.idempotencyKey,
505
+ runtime: { executionId: params.executionId }
506
+ });
507
+ if (await this.store.has(stablePhaseKey)) return { state: "duplicate" };
508
+ const sequence = await this.store.nextSequence(params.topic.topicId, params.topic.requestId);
509
+ const occurredAt = params.occurredAt || this.now().toISOString();
510
+ const idempotencyKey = params.topic.idempotencyKey;
511
+ const inputSummary = safeSummary(params.input, action.sensitiveInputPaths || []);
512
+ const outputSummary = safeSummary(params.output, action.sensitiveOutputPaths || []);
513
+ const proof = proofFor(action, params.output);
514
+ const unsigned = {
515
+ version: 2,
516
+ receiptId: sha256Digest({ topicId: params.topic.topicId, requestId: params.topic.requestId, executionId: params.executionId, sequence, status: params.status }),
517
+ sequence,
518
+ requestId: params.topic.requestId,
519
+ topicId: params.topic.topicId,
520
+ topicRevision: params.topic.topicRevision,
521
+ action: { type: action.type, can: manifestEntry.can, registryVersion: ACTION_REGISTRY_VERSION, contractDigest: manifestEntry.contractDigest },
522
+ runtime: {
523
+ provider: params.topic.executorProvider,
524
+ ...params.topic.bindingId ? { bindingId: params.topic.bindingId } : {},
525
+ ...params.flowUri ? { flowUri: params.flowUri } : {},
526
+ ...params.sessionRunId ? { sessionRunId: params.sessionRunId } : {},
527
+ ...params.nodeId ? { nodeId: params.nodeId } : {},
528
+ executionId: params.executionId
529
+ },
530
+ actorDid: params.actorDid,
531
+ executorDid: params.executorDid,
532
+ status: params.status,
533
+ idempotencyKey,
534
+ occurredAt,
535
+ authorization: {
536
+ capability: params.topic.topicCapabilityReference,
537
+ ...params.invocationReference ? { invocation: params.invocationReference } : {},
538
+ ...params.topic.confirmationReference ? { confirmation: params.topic.confirmationReference } : {}
539
+ },
540
+ input: { digest: sha256Digest(params.input), ...inputSummary ? { summary: inputSummary } : {} },
541
+ semanticRecordsDigest: topicSemanticRecordsDigest(semanticRecords),
542
+ ...params.output ? { output: { digest: sha256Digest(params.output), ...outputSummary ? { summary: outputSummary } : {} } } : {},
543
+ ...proof ? { proof } : {},
544
+ ...params.evidenceReferences?.length ? { evidenceRefs: [...new Set(params.evidenceReferences)].slice(0, 100) } : {},
545
+ ...params.traceReference ? { traceRef: params.traceReference } : {},
546
+ ...params.error ? { error: { ...params.error.code ? { code: params.error.code.slice(0, 80) } : {}, message: params.error.message.slice(0, 2e3) } } : {}
547
+ };
548
+ const signed = await this.signer.sign(unsigned);
549
+ return this.queueAndFlush({ ...unsigned, ...signed }, action, semanticRecords, params.topic.topicRevision, stablePhaseKey);
550
+ }
551
+ async ingestExternalReceipt(receipt, semanticRecords = []) {
552
+ const action = getAction(receipt.action.type);
553
+ const manifest = generateActionManifest();
554
+ const manifestEntry = action && manifest.actions.find((entry) => entry.type === action.type);
555
+ if (!action || !manifestEntry) return { state: "rejected", error: "Receipt names an unknown Action" };
556
+ if (receipt.action.contractDigest !== manifestEntry.contractDigest || receipt.action.can !== manifestEntry.can || receipt.action.registryVersion !== manifest.registryVersion) {
557
+ return { state: "rejected", error: "Receipt Action contract does not match the live manifest" };
558
+ }
559
+ if (!this.verifier) return { state: "rejected", error: "No external receipt verifier is configured" };
560
+ const verification = await this.verifier.verify(receipt, {
561
+ audience: receipt.topicId,
562
+ capability: "topic/record-action",
563
+ actionType: receipt.action.type,
564
+ topicId: receipt.topicId
565
+ });
566
+ if (!verification.valid || verification.revoked)
567
+ return { state: "rejected", error: verification.reason || (verification.revoked ? "Receipt capability was revoked" : "Receipt signature is invalid") };
568
+ if (verification.audience && verification.audience !== receipt.topicId) return { state: "rejected", error: "Receipt has the wrong audience" };
569
+ if (verification.capabilities && !verification.capabilities.includes("topic/record-action"))
570
+ return { state: "rejected", error: "Receipt does not grant Topic receipt recording" };
571
+ if (verification.expiresAt && Date.parse(verification.expiresAt) <= this.now().getTime()) return { state: "rejected", error: "Receipt capability is expired" };
572
+ if (Date.parse(receipt.occurredAt) > this.now().getTime() + 5 * 6e4) return { state: "rejected", error: "Receipt occurrence time is too far in the future" };
573
+ try {
574
+ validateSemanticRecords(action, semanticRecords);
575
+ } catch (error) {
576
+ return { state: "rejected", error: error instanceof Error ? error.message : "Invalid Topic semantic records" };
577
+ }
578
+ if (receipt.semanticRecordsDigest !== topicSemanticRecordsDigest(semanticRecords)) {
579
+ return { state: "rejected", error: "Receipt semantic record digest does not match its records" };
580
+ }
581
+ return this.queueAndFlush(receipt, action, semanticRecords, receipt.topicRevision, externalReceiptDeduplicationKey(receipt));
582
+ }
583
+ async flush() {
584
+ const results = [];
585
+ for (const entry of await this.store.pending()) results.push(await this.deliver(entry));
586
+ return results;
587
+ }
588
+ async queueAndFlush(receipt, action, semanticRecords, writeRevision, key) {
589
+ if (await this.store.has(key)) return { state: "duplicate", receiptId: receipt.receiptId };
590
+ const queued = await this.store.enqueue({
591
+ deduplicationKey: key,
592
+ receipt,
593
+ semanticRecords,
594
+ writeRevision,
595
+ queuedAt: this.now().toISOString(),
596
+ attempts: 0,
597
+ threadUpdate: buildTopicActionThreadUpdate(receipt, action, semanticRecords)
598
+ });
599
+ if (queued === "duplicate") return { state: "duplicate", receiptId: receipt.receiptId };
600
+ const entry = (await this.store.pending()).find((candidate) => candidate.deduplicationKey === key);
601
+ return entry ? this.deliver(entry) : { state: "duplicate", receiptId: receipt.receiptId };
602
+ }
603
+ deliver(entry) {
604
+ const inFlight = this.deliveries.get(entry.deduplicationKey);
605
+ if (inFlight) return inFlight;
606
+ const delivery = this.deliverOnce(entry).finally(() => this.deliveries.delete(entry.deduplicationKey));
607
+ this.deliveries.set(entry.deduplicationKey, delivery);
608
+ return delivery;
609
+ }
610
+ async deliverOnce(entry) {
611
+ let current = { ...entry, attempts: entry.attempts + 1 };
612
+ try {
613
+ if (!current.receiptDelivery) {
614
+ const result = await this.sink.appendReceipt({ receipt: current.receipt, semanticRecords: current.semanticRecords, previousRevision: current.writeRevision });
615
+ current = {
616
+ ...current,
617
+ receiptDelivery: result,
618
+ writeRevision: result.topicRevision,
619
+ threadUpdate: current.threadUpdate ? { ...current.threadUpdate, receipt: { ...current.threadUpdate.receipt, operationId: result.operationId } } : void 0,
620
+ lastError: void 0
621
+ };
622
+ await this.store.update(current);
623
+ }
624
+ if (current.threadUpdate && !current.noticeDelivery) {
625
+ const notice = await this.sink.appendThreadUpdate({
626
+ update: current.threadUpdate,
627
+ transactionId: topicActionThreadUpdateTransactionId(current.threadUpdate)
628
+ });
629
+ current = { ...current, noticeDelivery: notice, lastError: void 0 };
630
+ await this.store.update(current);
631
+ }
632
+ const delivery = current.receiptDelivery;
633
+ await this.store.markDelivered(current.deduplicationKey, delivery.operationId, delivery.topicRevision);
634
+ return {
635
+ state: "delivered",
636
+ receiptId: current.receipt.receiptId,
637
+ operationId: delivery.operationId,
638
+ topicRevision: delivery.topicRevision,
639
+ ...current.noticeDelivery ? { threadEventId: current.noticeDelivery.eventId } : {}
640
+ };
641
+ } catch (error) {
642
+ if (!current.receiptDelivery && error instanceof TopicRevisionConflictError) {
643
+ current = { ...current, writeRevision: error.currentRevision, lastError: error.message };
644
+ await this.store.update(current);
645
+ if (current.attempts < MAX_RECEIPT_DELIVERY_ATTEMPTS) return this.deliverOnce(current);
646
+ }
647
+ current = { ...current, lastError: error instanceof Error ? error.message : "Topic write-back failed" };
648
+ await this.store.update(current);
649
+ return {
650
+ state: "queued",
651
+ receiptId: current.receipt.receiptId,
652
+ ...current.receiptDelivery ? { operationId: current.receiptDelivery.operationId, topicRevision: current.receiptDelivery.topicRevision } : {},
653
+ error: current.lastError
654
+ };
655
+ }
656
+ }
657
+ };
658
+ function topicActionReceiptSigningPayload(receipt) {
659
+ return canonicalActionJson(receipt);
660
+ }
661
+
662
+ // src/core/lib/topicActions/externalExecutorAdapter.ts
663
+ var ManifestBoundTopicExecutorAdapter = class {
664
+ constructor(provider, executor, bridge) {
665
+ this.provider = provider;
666
+ this.executor = executor;
667
+ this.bridge = bridge;
668
+ }
669
+ async execute(invocation) {
670
+ this.validateInvocation(invocation);
671
+ const result = await this.executor.invoke(structuredClone(invocation));
672
+ this.validateResult(invocation, result);
673
+ return this.bridge.ingestExternalReceipt(result.receipt, result.semanticRecords || []);
674
+ }
675
+ validateInvocation(invocation) {
676
+ if (invocation.version !== 1 || invocation.executorProvider !== this.provider) throw new Error(`Invocation is not addressed to ${this.provider}`);
677
+ if (!invocation.requestId || !invocation.idempotencyKey || !invocation.topicId || !invocation.topicRevision) throw new Error("External Topic invocation is incomplete");
678
+ if (!invocation.actorDid.startsWith("did:") || !invocation.executorDid.startsWith("did:")) throw new Error("External Topic invocation requires actor and executor DIDs");
679
+ if (!invocation.topicCapabilityReference || !invocation.input.digest.startsWith("sha256:")) throw new Error("External Topic invocation lacks capability or safe input digest");
680
+ const manifest = generateActionManifest();
681
+ const action = manifest.actions.find((entry) => entry.type === invocation.actionType);
682
+ if (!action || action.hidden || action.deprecated) throw new Error(`Action '${invocation.actionType}' is unavailable to external executors`);
683
+ if (action.contractDigest !== invocation.actionContractDigest) throw new Error(`Action '${invocation.actionType}' contract digest does not match Manifest v4`);
684
+ }
685
+ validateResult(invocation, result) {
686
+ const receipt = result.receipt;
687
+ if (receipt.topicId !== invocation.topicId || receipt.topicRevision !== invocation.topicRevision || receipt.requestId !== invocation.requestId || receipt.idempotencyKey !== invocation.idempotencyKey) {
688
+ throw new Error("External receipt does not correlate to its invocation");
689
+ }
690
+ if (receipt.runtime.provider !== this.provider || receipt.action.type !== invocation.actionType || receipt.action.contractDigest !== invocation.actionContractDigest) {
691
+ throw new Error("External receipt provider or Action contract does not match its invocation");
692
+ }
693
+ if (receipt.actorDid !== invocation.actorDid || receipt.executorDid !== invocation.executorDid)
694
+ throw new Error("External receipt actor or executor DID does not match its invocation");
695
+ if (receipt.input.digest !== invocation.input.digest) throw new Error("External receipt input digest does not match its invocation");
696
+ if (receipt.authorization.capability !== invocation.topicCapabilityReference) throw new Error("External receipt uses a different Topic capability reference");
697
+ }
698
+ };
699
+ var createQiForgeTopicExecutorAdapter = (executor, bridge) => new ManifestBoundTopicExecutorAdapter("qiforge", executor, bridge);
700
+ var createMcpTopicExecutorAdapter = (executor, bridge) => new ManifestBoundTopicExecutorAdapter("mcp", executor, bridge);
701
+
271
702
  // src/core/templates/oracleInitFlow.ts
272
703
  var oracleInitSurveySchema = {
273
704
  title: "Oracle Configuration",
@@ -648,6 +1079,8 @@ function createOracleInitFlowTemplate() {
648
1079
  return { metadata, nodes };
649
1080
  }
650
1081
  export {
1082
+ ACTION_MANIFEST_VERSION,
1083
+ ACTION_REGISTRY_VERSION,
651
1084
  ExplicitRunRequiredError,
652
1085
  FLOW_CONNECTIONS_MAP_KEY,
653
1086
  FLOW_CONNECTION_BINDINGS_MAP_KEY,
@@ -655,11 +1088,15 @@ export {
655
1088
  FlowAgentService,
656
1089
  FlowNotMigratedError,
657
1090
  INVOCATIONS_MAP_KEY,
1091
+ InMemoryTopicActionOutboxStore,
658
1092
  InvalidConditionConfigError,
659
1093
  LATEST_VERSION,
660
1094
  LEGACY_RUN_STORAGE_VERSION,
661
1095
  MAX_RUN_ACTION_OUTPUT_BYTES,
1096
+ MAX_TOPIC_SEMANTIC_RECORDS_PER_RECEIPT,
1097
+ MAX_TOPIC_SEMANTIC_RECORD_BATCH_BYTES,
662
1098
  MULTI_RUN_STORAGE_VERSION,
1099
+ ManifestBoundTopicExecutorAdapter,
663
1100
  NODE_CONTEXT_MAP_NAME,
664
1101
  NODE_CONTEXT_OVERRIDES_MAP_NAME,
665
1102
  PROOF_MISSING_CODE,
@@ -673,6 +1110,8 @@ export {
673
1110
  STEP_COMPLETED_EVENT,
674
1111
  STEP_COMPLETED_EVENT_NAME,
675
1112
  TerminalRunExecutionError,
1113
+ TopicActionBridge,
1114
+ TopicRevisionConflictError,
676
1115
  UnknownRunError,
677
1116
  VERSION_MANIFEST,
678
1117
  XERO_ORACLE_WORKING_STATUSES,
@@ -680,7 +1119,9 @@ export {
680
1119
  XERO_TOOLKIT,
681
1120
  XERO_WORK_ITEMS_MAP_NAME,
682
1121
  XERO_WORK_TERMINAL_MAP_NAME,
1122
+ YjsTopicActionOutboxStore,
683
1123
  acquireFlowAgentLease,
1124
+ actionManifestIssues,
684
1125
  actionTypeToCan,
685
1126
  addFlowNode,
686
1127
  adoptLegacyRuntime,
@@ -698,6 +1139,7 @@ export {
698
1139
  buildFlowNodeFromBlock,
699
1140
  buildRunDefinitionSnapshot,
700
1141
  buildServicesFromHandlers,
1142
+ buildTopicActionThreadUpdate,
701
1143
  buildXeroInvoiceWorkKey,
702
1144
  buildXeroPaymentWorkKey,
703
1145
  canMatches,
@@ -722,9 +1164,11 @@ export {
722
1164
  countPendingInvocations,
723
1165
  createAgentCommand,
724
1166
  createInvocationStore,
1167
+ createMcpTopicExecutorAdapter,
725
1168
  createMemoryInvocationStore,
726
1169
  createMemoryUcanDelegationStore,
727
1170
  createOracleInitFlowTemplate,
1171
+ createQiForgeTopicExecutorAdapter,
728
1172
  createReference,
729
1173
  createRunEventIdempotencyKey,
730
1174
  createRunId,
@@ -760,6 +1204,7 @@ export {
760
1204
  getAction,
761
1205
  getActionByCan,
762
1206
  getActionForBlock,
1207
+ getActionPresentation,
763
1208
  getActiveEditor,
764
1209
  getAliasEntries,
765
1210
  getAllActions,
@@ -885,6 +1330,7 @@ export {
885
1330
  setMultiRunStorage,
886
1331
  setTempUcanEnforcementDisabled,
887
1332
  setupFlowFromBaseUcan,
1333
+ sha256Digest,
888
1334
  snapshotInputRefs,
889
1335
  snapshotNode,
890
1336
  startRun,
@@ -893,6 +1339,11 @@ export {
893
1339
  tickFlowAgent,
894
1340
  toEvaluatorOperator,
895
1341
  toRunJsonValue,
1342
+ topicActionReceiptSigningPayload,
1343
+ topicActionThreadUpdateBody,
1344
+ topicActionThreadUpdateId,
1345
+ topicActionThreadUpdateTransactionId,
1346
+ topicSemanticRecordsDigest,
896
1347
  typeToCan,
897
1348
  updateAgentCommand,
898
1349
  updateNodeRuntime,
@@ -907,6 +1358,8 @@ export {
907
1358
  validateActionProof,
908
1359
  validateAgentCommand,
909
1360
  validateFlowAgentLease,
1361
+ validateTopicSemanticRecord,
1362
+ validateTopicSemanticRecordBatch,
910
1363
  verifyCompletion,
911
1364
  writeActionState,
912
1365
  writeCompiledBlocksToFragment,