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