@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.
@@ -499,6 +499,398 @@ async function sendDirectMessage(matrixClient, targetDid, message) {
499
499
  return { roomId };
500
500
  }
501
501
 
502
+ // src/core/lib/actionRegistry/digest.ts
503
+ function compareCodeUnits(left, right) {
504
+ return left < right ? -1 : left > right ? 1 : 0;
505
+ }
506
+ function canonicalActionJson(value) {
507
+ if (value === null) return "null";
508
+ if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
509
+ if (typeof value === "number") {
510
+ if (!Number.isFinite(value)) throw new Error("Action manifest cannot contain non-finite numbers");
511
+ return JSON.stringify(value);
512
+ }
513
+ if (Array.isArray(value)) return `[${value.map((item) => item === void 0 ? "null" : canonicalActionJson(item)).join(",")}]`;
514
+ if (typeof value === "object") {
515
+ const fields = Object.entries(value).filter(([, item]) => item !== void 0).sort(([a], [b]) => compareCodeUnits(a, b));
516
+ return `{${fields.map(([key, item]) => `${JSON.stringify(key)}:${canonicalActionJson(item)}`).join(",")}}`;
517
+ }
518
+ throw new Error(`Action manifest cannot contain ${typeof value}`);
519
+ }
520
+ var SHA256_K = new Uint32Array([
521
+ 1116352408,
522
+ 1899447441,
523
+ 3049323471,
524
+ 3921009573,
525
+ 961987163,
526
+ 1508970993,
527
+ 2453635748,
528
+ 2870763221,
529
+ 3624381080,
530
+ 310598401,
531
+ 607225278,
532
+ 1426881987,
533
+ 1925078388,
534
+ 2162078206,
535
+ 2614888103,
536
+ 3248222580,
537
+ 3835390401,
538
+ 4022224774,
539
+ 264347078,
540
+ 604807628,
541
+ 770255983,
542
+ 1249150122,
543
+ 1555081692,
544
+ 1996064986,
545
+ 2554220882,
546
+ 2821834349,
547
+ 2952996808,
548
+ 3210313671,
549
+ 3336571891,
550
+ 3584528711,
551
+ 113926993,
552
+ 338241895,
553
+ 666307205,
554
+ 773529912,
555
+ 1294757372,
556
+ 1396182291,
557
+ 1695183700,
558
+ 1986661051,
559
+ 2177026350,
560
+ 2456956037,
561
+ 2730485921,
562
+ 2820302411,
563
+ 3259730800,
564
+ 3345764771,
565
+ 3516065817,
566
+ 3600352804,
567
+ 4094571909,
568
+ 275423344,
569
+ 430227734,
570
+ 506948616,
571
+ 659060556,
572
+ 883997877,
573
+ 958139571,
574
+ 1322822218,
575
+ 1537002063,
576
+ 1747873779,
577
+ 1955562222,
578
+ 2024104815,
579
+ 2227730452,
580
+ 2361852424,
581
+ 2428436474,
582
+ 2756734187,
583
+ 3204031479,
584
+ 3329325298
585
+ ]);
586
+ function rotateRight(value, bits) {
587
+ return value >>> bits | value << 32 - bits;
588
+ }
589
+ function sha256Hex(text) {
590
+ const input = new TextEncoder().encode(text);
591
+ const bitLength = input.length * 8;
592
+ const paddedLength = Math.ceil((input.length + 9) / 64) * 64;
593
+ const bytes = new Uint8Array(paddedLength);
594
+ bytes.set(input);
595
+ bytes[input.length] = 128;
596
+ const view = new DataView(bytes.buffer);
597
+ view.setUint32(paddedLength - 8, Math.floor(bitLength / 4294967296), false);
598
+ view.setUint32(paddedLength - 4, bitLength >>> 0, false);
599
+ const state = new Uint32Array([1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]);
600
+ const words = new Uint32Array(64);
601
+ for (let offset = 0; offset < bytes.length; offset += 64) {
602
+ for (let index = 0; index < 16; index += 1) words[index] = view.getUint32(offset + index * 4, false);
603
+ for (let index = 16; index < 64; index += 1) {
604
+ const s0 = rotateRight(words[index - 15], 7) ^ rotateRight(words[index - 15], 18) ^ words[index - 15] >>> 3;
605
+ const s1 = rotateRight(words[index - 2], 17) ^ rotateRight(words[index - 2], 19) ^ words[index - 2] >>> 10;
606
+ words[index] = words[index - 16] + s0 + words[index - 7] + s1 >>> 0;
607
+ }
608
+ let a = state[0];
609
+ let b = state[1];
610
+ let c = state[2];
611
+ let d = state[3];
612
+ let e = state[4];
613
+ let f = state[5];
614
+ let g = state[6];
615
+ let h = state[7];
616
+ for (let index = 0; index < 64; index += 1) {
617
+ const sum1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
618
+ const choice = e & f ^ ~e & g;
619
+ const temp1 = h + sum1 + choice + SHA256_K[index] + words[index] >>> 0;
620
+ const sum0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
621
+ const majority = a & b ^ a & c ^ b & c;
622
+ const temp2 = sum0 + majority >>> 0;
623
+ h = g;
624
+ g = f;
625
+ f = e;
626
+ e = d + temp1 >>> 0;
627
+ d = c;
628
+ c = b;
629
+ b = a;
630
+ a = temp1 + temp2 >>> 0;
631
+ }
632
+ state[0] = state[0] + a >>> 0;
633
+ state[1] = state[1] + b >>> 0;
634
+ state[2] = state[2] + c >>> 0;
635
+ state[3] = state[3] + d >>> 0;
636
+ state[4] = state[4] + e >>> 0;
637
+ state[5] = state[5] + f >>> 0;
638
+ state[6] = state[6] + g >>> 0;
639
+ state[7] = state[7] + h >>> 0;
640
+ }
641
+ return Array.from(state, (word) => word.toString(16).padStart(8, "0")).join("");
642
+ }
643
+ function sha256Digest(value) {
644
+ return `sha256:${sha256Hex(canonicalActionJson(value))}`;
645
+ }
646
+
647
+ // src/core/lib/actionRegistry/topicSemanticRecords.ts
648
+ import Ajv2020 from "ajv/dist/2020.js";
649
+ var stringArray = { type: "array", items: { type: "string" } };
650
+ var digest = { type: "string", pattern: "^sha256:[a-f0-9]{64}$" };
651
+ var dateTime = {
652
+ type: "string",
653
+ pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})$"
654
+ };
655
+ var MAX_TOPIC_SEMANTIC_RECORDS_PER_RECEIPT = 25;
656
+ var MAX_TOPIC_SEMANTIC_RECORD_BATCH_BYTES = 32 * 1024;
657
+ function closed(required, properties) {
658
+ return { type: "object", required, additionalProperties: false, properties };
659
+ }
660
+ var TOPIC_SEMANTIC_RECORD_DEFINITIONS = [
661
+ {
662
+ type: "org.ixo.topic.claim-submission",
663
+ version: 1,
664
+ displayName: "Claim submitted",
665
+ description: "Records that a claim was submitted to a collection.",
666
+ valueSchema: closed(["claimId", "collectionId", "deedDid", "submittedByDid", "submittedAt", "transactionHash", "submissionDigest"], {
667
+ claimId: { type: "string" },
668
+ collectionId: { type: "string" },
669
+ deedDid: { type: "string" },
670
+ submittedByDid: { type: "string" },
671
+ submittedAt: dateTime,
672
+ transactionHash: { type: "string" },
673
+ submissionDigest: digest
674
+ })
675
+ },
676
+ {
677
+ type: "org.ixo.topic.claim-evaluation",
678
+ version: 1,
679
+ displayName: "Claim evaluated",
680
+ description: "Records the governed evaluation outcome for a claim.",
681
+ valueSchema: closed(["claimId", "collectionId", "deedDid", "decision", "evaluatedByDid", "evaluatedAt", "transactionHash", "evidenceDigest"], {
682
+ claimId: { type: "string" },
683
+ collectionId: { type: "string" },
684
+ deedDid: { type: "string" },
685
+ decision: { type: "string" },
686
+ evaluatedByDid: { type: "string" },
687
+ evaluatedAt: dateTime,
688
+ verificationProof: { type: "string" },
689
+ transactionHash: { type: "string" },
690
+ evidenceDigest: digest
691
+ })
692
+ },
693
+ {
694
+ type: "org.ixo.topic.proposal-receipt",
695
+ version: 1,
696
+ displayName: "Governance proposal update",
697
+ description: "Records the creation of, or a vote on, a governance proposal.",
698
+ valueSchema: closed(["event", "proposalId", "proposalContractAddress"], {
699
+ event: { type: "string", enum: ["created", "vote-cast"] },
700
+ actionType: { type: "string" },
701
+ proposalId: { type: "string" },
702
+ proposalContractAddress: { type: "string" },
703
+ coreAddress: { type: "string" },
704
+ title: { type: "string" },
705
+ proposalTitle: { type: "string" },
706
+ descriptionDigest: digest,
707
+ proposalDescriptionDigest: digest,
708
+ status: { type: "string" },
709
+ vote: { type: "string" },
710
+ rationaleDigest: digest,
711
+ actorDid: { type: "string" },
712
+ votedAt: dateTime,
713
+ createdAt: dateTime
714
+ })
715
+ },
716
+ {
717
+ type: "org.ixo.topic.work-event",
718
+ version: 1,
719
+ displayName: "Work update",
720
+ description: "Records an assignment, dispatch, checkpoint, submission, or acceptance of work.",
721
+ valueSchema: closed(["workId", "resourceType", "phase", "assigneeDid", "note", "artifactReferences", "evidenceReferences", "actorDid", "occurredAt"], {
722
+ workId: { type: "string" },
723
+ resourceType: { type: "string" },
724
+ phase: { type: "string", enum: ["requested", "dispatched", "in_progress", "ready_for_review", "completed"] },
725
+ assigneeDid: { type: "string" },
726
+ note: { type: "string" },
727
+ artifactReferences: stringArray,
728
+ evidenceReferences: stringArray,
729
+ actorDid: { type: "string" },
730
+ occurredAt: dateTime
731
+ })
732
+ },
733
+ {
734
+ type: "org.ixo.topic.agent-result",
735
+ version: 1,
736
+ displayName: "Agent result",
737
+ description: "Records a delegated agent result and its evidence references without exposing invocation inputs.",
738
+ valueSchema: closed(["sessionId", "result", "resultDigest", "evidenceReferences", "evidenceDigest", "providerReceiptReference"], {
739
+ sessionId: { type: "string" },
740
+ result: { description: "Provider-owned result extension point." },
741
+ resultDigest: digest,
742
+ evidenceReferences: stringArray,
743
+ evidenceDigest: digest,
744
+ providerReceiptReference: { type: "string" }
745
+ })
746
+ },
747
+ {
748
+ type: "org.ixo.topic.agent-cancellation",
749
+ version: 1,
750
+ displayName: "Agent cancelled",
751
+ description: "Records cancellation of a delegated agent session.",
752
+ valueSchema: closed(["sessionId", "status", "providerReceiptReference"], {
753
+ sessionId: { type: "string" },
754
+ status: { const: "cancelled" },
755
+ providerReceiptReference: { type: "string" }
756
+ })
757
+ },
758
+ {
759
+ type: "org.ixo.topic.evidence",
760
+ version: 1,
761
+ displayName: "Evidence collected",
762
+ description: "Records collected evidence, provenance, and stable evidence references.",
763
+ valueSchema: closed(["question", "evidence", "provenance", "evidenceReferences", "evidenceDigest"], {
764
+ question: { type: "string" },
765
+ evidence: { description: "Provider-owned evidence extension point." },
766
+ provenance: { description: "Provider-owned provenance extension point." },
767
+ evidenceReferences: stringArray,
768
+ evidenceDigest: digest
769
+ })
770
+ },
771
+ ...["proposed", "accepted"].map(
772
+ (status) => ({
773
+ type: `org.ixo.topic.${status}-answer`,
774
+ version: 1,
775
+ displayName: status === "accepted" ? "Answer accepted" : "Answer proposed",
776
+ description: status === "accepted" ? "Records an answer accepted by the stated authority." : "Records an answer proposed for review.",
777
+ valueSchema: closed(["answer", "status", "proposedAnswerRecordId", "authorityDid", "evidenceReferences", "limitations", "occurredAt"], {
778
+ answer: { type: "string" },
779
+ status: { const: status },
780
+ proposedAnswerRecordId: { type: "string" },
781
+ authorityDid: { type: "string" },
782
+ evidenceReferences: stringArray,
783
+ limitations: { type: "string" },
784
+ occurredAt: dateTime
785
+ })
786
+ })
787
+ ),
788
+ ...["assertion", "review"].map(
789
+ (kind) => ({
790
+ type: `org.ixo.topic.evaluation-${kind}`,
791
+ version: 1,
792
+ displayName: kind === "review" ? "Evaluation reviewed" : "Evaluation assertion",
793
+ description: kind === "review" ? "Records a signed human review of an evaluation assertion." : "Records a signed evaluation assertion.",
794
+ valueSchema: closed(
795
+ [kind === "review" ? "reviewId" : "assertionId", "providerResult", "methodologyRevision", "rubricRevision", "evaluatorDid", "evidenceReferences", "signature"],
796
+ {
797
+ assertionId: { type: "string" },
798
+ reviewId: { type: "string" },
799
+ providerResult: { type: "object", description: "Provider-owned evaluation result extension point.", additionalProperties: true },
800
+ methodologyRevision: { type: "string" },
801
+ rubricRevision: { type: "string" },
802
+ evaluatorDid: { type: "string" },
803
+ evidenceReferences: stringArray,
804
+ signature: { type: "string" }
805
+ }
806
+ )
807
+ })
808
+ ),
809
+ {
810
+ type: "org.ixo.topic.settlement-record",
811
+ version: 1,
812
+ displayName: "Settlement update",
813
+ description: "Records the provider reference and terminal status of an approved settlement execution.",
814
+ valueSchema: closed(["settlementId", "transactionReference", "providerReceiptReference", "status"], {
815
+ settlementId: { type: "string" },
816
+ transactionReference: { type: "string" },
817
+ providerReceiptReference: { type: "string" },
818
+ status: { type: "string", enum: ["submitted", "confirmed", "needs_verification"] }
819
+ })
820
+ },
821
+ {
822
+ type: "org.ixo.topic.incident-escalation",
823
+ version: 1,
824
+ displayName: "Incident escalated",
825
+ description: "Records that an incident was escalated to the stated recipients.",
826
+ valueSchema: closed(["severity", "affectedResources", "recipients", "evidenceReferences", "summary", "escalationId", "notifiedAt", "providerReceiptReferences"], {
827
+ severity: { type: "string", enum: ["low", "medium", "high", "critical"] },
828
+ affectedResources: stringArray,
829
+ recipients: stringArray,
830
+ evidenceReferences: stringArray,
831
+ summary: { type: "string" },
832
+ escalationId: { type: "string" },
833
+ notifiedAt: dateTime,
834
+ providerReceiptReferences: stringArray
835
+ })
836
+ },
837
+ {
838
+ type: "org.ixo.topic.incident-mitigation",
839
+ version: 1,
840
+ displayName: "Incident mitigation recorded",
841
+ description: "Records mitigation work for an incident without changing the incident lifecycle.",
842
+ valueSchema: closed(["mitigation", "affectedResources", "evidenceReferences", "recordedBy", "occurredAt"], {
843
+ mitigation: { type: "string" },
844
+ affectedResources: stringArray,
845
+ evidenceReferences: stringArray,
846
+ recordedBy: { type: "string" },
847
+ occurredAt: dateTime
848
+ })
849
+ }
850
+ ];
851
+ var definitionsByType = new Map(TOPIC_SEMANTIC_RECORD_DEFINITIONS.map((definition) => [definition.type, definition]));
852
+ var ajv = new Ajv2020({ allErrors: true, strict: false });
853
+ var validators = /* @__PURE__ */ new Map();
854
+ function getTopicSemanticRecordDefinitions(types) {
855
+ return types.map((type) => definitionsByType.get(type)).filter((definition) => !!definition);
856
+ }
857
+ function validateTopicSemanticRecord(record, topic) {
858
+ if (!record || typeof record !== "object" || Array.isArray(record)) return { valid: false, code: "INVALID_RECORD_ENVELOPE" };
859
+ const candidate = record;
860
+ const envelopeKeys = /* @__PURE__ */ new Set(["type", "id", "version", "value", "evidenceReferences"]);
861
+ if (Object.keys(candidate).some((key) => !envelopeKeys.has(key)) || typeof candidate.type !== "string" || typeof candidate.id !== "string" || !candidate.id || !Number.isInteger(candidate.version) || Number(candidate.version) < 1 || !candidate.value || typeof candidate.value !== "object" || Array.isArray(candidate.value)) {
862
+ return { valid: false, code: "INVALID_RECORD_ENVELOPE" };
863
+ }
864
+ if (candidate.evidenceReferences !== void 0 && (!Array.isArray(candidate.evidenceReferences) || candidate.evidenceReferences.some((item) => typeof item !== "string"))) {
865
+ return { valid: false, code: "INVALID_RECORD_ENVELOPE" };
866
+ }
867
+ const definition = topic.semanticRecordTypes.find((item) => item.type === candidate.type);
868
+ if (!definition) return { valid: false, code: "UNKNOWN_RECORD_TYPE" };
869
+ if (candidate.version !== definition.version) return { valid: false, code: "UNSUPPORTED_RECORD_VERSION" };
870
+ const validatorKey = `${definition.type}@${definition.version}:${sha256Digest(definition.valueSchema)}`;
871
+ let validate = validators.get(validatorKey);
872
+ if (!validate) {
873
+ validate = ajv.compile(definition.valueSchema);
874
+ validators.set(validatorKey, validate);
875
+ }
876
+ if (!validate(candidate.value)) return { valid: false, code: "INVALID_RECORD_VALUE", errors: validate.errors || void 0 };
877
+ return { valid: true, definition };
878
+ }
879
+ function validateTopicSemanticRecordBatch(records, topic) {
880
+ if (records.length > MAX_TOPIC_SEMANTIC_RECORDS_PER_RECEIPT) return { valid: false, code: "TOO_MANY_RECORDS" };
881
+ for (const record of records) {
882
+ const validation = validateTopicSemanticRecord(record, topic);
883
+ if (!validation.valid) return validation;
884
+ }
885
+ try {
886
+ const byteLength = new TextEncoder().encode(canonicalActionJson(records)).byteLength;
887
+ if (byteLength > MAX_TOPIC_SEMANTIC_RECORD_BATCH_BYTES) return { valid: false, code: "RECORD_BATCH_TOO_LARGE" };
888
+ } catch {
889
+ return { valid: false, code: "INVALID_RECORD_BATCH" };
890
+ }
891
+ return { valid: true };
892
+ }
893
+
502
894
  // src/core/lib/actionRegistry/canMapping.ts
503
895
  var CAN_TO_TYPE = {
504
896
  "flow/run.start": "qi/flow.run.start",
@@ -548,10 +940,11 @@ var CAN_TO_TYPE = {
548
940
  "outlook.email/send": "qi/outlook.email.send",
549
941
  "slack.message/send": "qi/slack.message.send",
550
942
  "googlecalendar.event/create": "qi/googlecalendar.event.create",
551
- // Calendar integration (self-connected)
552
- "calendar.event/create": "qi/calendar.event.create",
553
- "calendar.event/update": "qi/calendar.event.update",
554
- "calendar.event/list": "qi/calendar.event.list",
943
+ // Google Calendar, self-connected (renamed from qi/calendar.* — IXO-4420 §5).
944
+ // The cans keep their historical values so existing UCAN grants still match.
945
+ "calendar.event/create": "qi/googlecalendar.event.create-self",
946
+ "calendar.event/update": "qi/googlecalendar.event.update-self",
947
+ "calendar.event/list": "qi/googlecalendar.event.list-self",
555
948
  // Xero integration
556
949
  "xero.contact/create": "qi/xero.contact.create",
557
950
  "xero.invoice/create": "qi/xero.invoice.create",
@@ -580,6 +973,160 @@ function getAllCanMappings() {
580
973
  return Object.entries(CAN_TO_TYPE).map(([can, type]) => ({ can, type }));
581
974
  }
582
975
 
976
+ // src/core/lib/actionRegistry/presentation.ts
977
+ var PRESENTATION = {
978
+ oracle: { displayName: "Ask your Agent", description: "Send a prompt to the Personal Agent" },
979
+ "oracle.prompt": { displayName: "Oracle Prompt", description: "Send a prompt to the Personal Agent" },
980
+ "qi/bid.evaluate": { displayName: "Evaluate Bid", description: "Approve or reject a bid" },
981
+ "qi/bid.submit": { displayName: "Bid", description: "Submit a bid application" },
982
+ "qi/agent.cancel": { displayName: "Cancel Agent", description: "Cancel a delegated agent session" },
983
+ "qi/agent.invoke": { displayName: "Invoke Agent", description: "Delegate a task to an authorised agent runtime" },
984
+ "qi/answer.accept": { displayName: "Accept Answer", description: "Accept a proposed answer using the stated authority" },
985
+ "qi/answer.propose": { displayName: "Propose Answer", description: "Propose an answer for review in the Topic" },
986
+ "qi/blueprint.artifact-preview": { displayName: "Preview Blueprint Artifact", description: "Preview an authored Blueprint artifact before saving it" },
987
+ "qi/blueprint.artifact-save": { displayName: "Save Blueprint Artifact", description: "Save an authored artifact to the Blueprint workspace" },
988
+ "qi/blueprint.checklist-update": { displayName: "Update Blueprint Checklist", description: "Record progress against the Blueprint authoring checklist" },
989
+ "qi/blueprint.guided-authoring": { displayName: "Guide Blueprint Authoring", description: "Generate guided authoring input for the current Blueprint phase" },
990
+ "qi/blueprint.journey-resume": { displayName: "Resume Blueprint Journey", description: "Resume an existing Blueprint authoring journey" },
991
+ "qi/blueprint.phase-confirm": { displayName: "Confirm Blueprint Phase", description: "Confirm completion of the current Blueprint authoring phase" },
992
+ "qi/blueprint.publish": { displayName: "Publish Blueprint", description: "Publish an approved Blueprint release" },
993
+ "qi/blueprint.release-compile": { displayName: "Compile Blueprint Release", description: "Compile Blueprint artifacts into a release candidate" },
994
+ "qi/blueprint.review-respond": { displayName: "Respond to Blueprint Review", description: "Respond to review feedback on a Blueprint release" },
995
+ "qi/blueprint.review-submit": { displayName: "Submit Blueprint Review", description: "Submit a Blueprint release for review" },
996
+ "qi/blueprint.reviewer-assign": { displayName: "Assign Blueprint Reviewer", description: "Assign a reviewer to a Blueprint release" },
997
+ "qi/blueprint.workspace-start": { displayName: "Start Blueprint Workspace", description: "Create a workspace for a new Blueprint authoring journey" },
998
+ "qi/calendar.event.create": { displayName: "Create Calendar event", description: "Create an event on a connected Calendar" },
999
+ "qi/calendar.event.list": { displayName: "List Calendar events", description: "Fetch events from a connected Calendar" },
1000
+ "qi/calendar.event.update": { displayName: "Update Calendar event", description: "Replace an existing Calendar event" },
1001
+ "qi/carbon.harvest": { displayName: "Harvest Carbon", description: "Claim carbon credits into your wallet" },
1002
+ "qi/carbon.loadBatches": { displayName: "Load Carbon Batches", description: "Reconcile harvestable and retireable CARBON credits" },
1003
+ "qi/carbon.retire": { displayName: "Retire Carbon", description: "Permanently retire carbon credits to offset impact" },
1004
+ "qi/claim.evaluate": { displayName: "Evaluate Claim", description: "Approve or reject a claim" },
1005
+ "qi/claim.submit": { displayName: "Claim", description: "Submit a claim" },
1006
+ "qi/collection.lifecycle": { displayName: "Claim Collection", description: "Create and manage a claim collection lifecycle" },
1007
+ "qi/collection.create": { displayName: "Create Claim Collection", description: "Pin a protocol release and create its exact on-chain collection record" },
1008
+ "qi/collection.users": { displayName: "Collection Users", description: "Add, list and revoke claim collection contributors & evaluators" },
1009
+ "qi/credential.store": { displayName: "Store Credential", description: "Store a verifiable credential in Matrix room state" },
1010
+ "qi/domain.card-preview": { displayName: "Preview Domain Card", description: "Review an oracle-enriched domain card and approve it before signing" },
1011
+ "qi/domain.sign": { displayName: "Sign Domain", description: "Sign the domain card credential and create the entity on-chain" },
1012
+ "qi/email.send": { displayName: "Email", description: "Send an email to a user" },
1013
+ "qi/eval.connect": { displayName: "Connect Evaluation Service", description: "Authorize one evaluation service for one existing claim collection" },
1014
+ "qi/eval.engine": { displayName: "Evaluation Engine", description: "Enroll a collection and publish its claim-approval rules in one step" },
1015
+ "qi/evaluation.review": { displayName: "Review Evaluation", description: "Review and sign an evaluation assertion" },
1016
+ "qi/evaluation.run": { displayName: "Run Evaluation", description: "Run an evaluation against the selected methodology and rubric" },
1017
+ "qi/evidence.collect": { displayName: "Collect Evidence", description: "Collect evidence and provenance from approved sources" },
1018
+ "qi/flow.run.close": { displayName: "Close Flow Run", description: "Close the current Flow run with a terminal outcome" },
1019
+ "qi/flow.run.start": { displayName: "Start Flow Run", description: "Start a new governed Flow run" },
1020
+ "qi/entity.createOracle": { displayName: "Create Oracle Entity", description: "Create the oracle entity on-chain" },
1021
+ "qi/entity.transfer": { displayName: "Transfer Entity", description: "Transfer ownership of an entity to a new owner" },
1022
+ "qi/form.submit": { displayName: "Form Submit", description: "Submit a form response" },
1023
+ "qi/gmail.email.send": { displayName: "Send Gmail email", description: "Send an email from the template author's Gmail account" },
1024
+ "qi/googlecalendar.event.create": { displayName: "Create Calendar event (delegated)", description: "Create an event on the template author's Google Calendar" },
1025
+ "qi/googlecalendar.event.create-self": { displayName: "Create Calendar event", description: "Create an event on a connected Calendar" },
1026
+ "qi/googlecalendar.event.list-self": { displayName: "List Calendar events", description: "Fetch events from a connected Calendar" },
1027
+ "qi/googlecalendar.event.update-self": { displayName: "Update Calendar event", description: "Replace an existing Calendar event" },
1028
+ "qi/governance.authz.exec": { displayName: "Execute Authorized Action", description: "Propose executing a message the POD was authorized to run" },
1029
+ "qi/governance.authz.grant": { displayName: "Grant Authorization", description: "Propose granting an address authorization to act for the POD" },
1030
+ "qi/governance.authz.revoke": { displayName: "Revoke Authorization", description: "Propose revoking a previously granted authorization" },
1031
+ "qi/governance.chain-governance-vote": { displayName: "Chain Governance Vote", description: "Propose casting the POD's vote on a chain governance proposal" },
1032
+ "qi/governance.contract.execute": { displayName: "Execute Contract", description: "Propose executing a message on a smart contract as the DAO" },
1033
+ "qi/governance.contract.instantiate": { displayName: "Instantiate Contract", description: "Propose instantiating a new smart contract from a code id" },
1034
+ "qi/governance.contract.manage-cw20": { displayName: "Manage Token List", description: "Propose tracking or untracking a cw20 token in the DAO treasury" },
1035
+ "qi/governance.contract.migrate": { displayName: "Migrate Contract", description: "Propose migrating a smart contract the DAO administers to a new code id" },
1036
+ "qi/governance.contract.update-admin": { displayName: "Update Contract Admin", description: "Propose transferring admin rights over a smart contract to a new address" },
1037
+ "qi/governance.custom-message": { displayName: "Custom Message", description: "Propose executing a raw JSON cosmos message (advanced)" },
1038
+ "qi/governance.dao.accept-to-marketplace": { displayName: "Accept to Marketplace", description: "Propose marking an entity as verified on the marketplace" },
1039
+ "qi/governance.dao.admin-exec": { displayName: "DAO Admin Execute", description: "Propose executing admin messages on a SubDAO this POD administers" },
1040
+ "qi/governance.dao.create-entity": { displayName: "Create Entity", description: "Propose broadcasting a raw entity-creation message" },
1041
+ "qi/governance.dao.join": { displayName: "Join Entity", description: "Propose linking this POD as a member of another entity" },
1042
+ "qi/governance.dao.manage-storage": { displayName: "Manage Storage Items", description: "Propose setting or removing an item in the DAO's on-chain storage" },
1043
+ "qi/governance.dao.manage-subdaos": { displayName: "Manage SubDAOs", description: "Propose recognising or removing SubDAOs of this DAO" },
1044
+ "qi/governance.dao.update-info": { displayName: "Update DAO Info", description: "Propose replacing the DAO's name, description and image" },
1045
+ "qi/governance.member-proposal": { displayName: "Membership Proposal", description: "Propose adding/removing members or changing voting power" },
1046
+ "qi/governance.nft.burn": { displayName: "Burn NFT", description: "Propose permanently burning an NFT held by the treasury" },
1047
+ "qi/governance.nft.manage-collections": { displayName: "Manage NFT Collections", description: "Propose tracking or untracking an NFT collection in the treasury" },
1048
+ "qi/governance.nft.transfer": { displayName: "Transfer NFT", description: "Propose transferring an NFT from the treasury" },
1049
+ "qi/governance.settings-proposal": { displayName: "Governance Settings Proposal", description: "Propose new voting rules (period, quorum, thresholds)" },
1050
+ "qi/governance.staking.stake": { displayName: "Stake Treasury Tokens", description: "Propose staking treasury IXO with a validator \u2014 stake, unstake, restake or claim rewards" },
1051
+ "qi/governance.staking.stake-to-group": { displayName: "Stake to POD", description: "Propose staking treasury cw20 tokens into a POD's staking contract" },
1052
+ "qi/governance.submission-config-proposal": { displayName: "Proposal Submission Rules", description: "Propose changing who may submit proposals and the required deposit" },
1053
+ "qi/governance.transaction.mint": { displayName: "Mint Governance Tokens", description: "Propose minting new governance tokens to an address" },
1054
+ "qi/governance.transaction.perform-token-swap": { displayName: "Fund Token Swap", description: "Propose funding the POD\u2019s side of a token swap contract" },
1055
+ "qi/governance.transaction.send-funds": { displayName: "Send Funds", description: "Propose sending funds from the POD treasury" },
1056
+ "qi/governance.transaction.send-group-token": { displayName: "Send POD Tokens", description: "Propose transferring cw20 POD tokens from the treasury" },
1057
+ "qi/governance.transaction.withdraw-token-swap": { displayName: "Withdraw Token Swap", description: "Propose withdrawing the POD\u2019s funds from a token swap contract" },
1058
+ "qi/governance.validator.actions": { displayName: "Validator Actions", description: "Propose a validator operation with the POD's validator account" },
1059
+ "qi/http.request": { displayName: "HTTP Request", description: "Make an HTTP API request" },
1060
+ "qi/http.fetch": { displayName: "Fetch HTTP Resource", description: "Read data from an HTTP endpoint" },
1061
+ "qi/incident.escalate": { displayName: "Escalate Incident", description: "Notify the stated recipients about an incident escalation" },
1062
+ "qi/incident.mitigation.record": { displayName: "Record Incident Mitigation", description: "Record mitigation work and evidence for an incident" },
1063
+ "qi/human.checkbox.set": { displayName: "Checkbox Set", description: "Record a checkbox response" },
1064
+ "qi/human.form.submit": { displayName: "Human Form Submit", description: "Submit a human-completed form" },
1065
+ "qi/identity.create": { displayName: "Create Identity", description: "Create an IID document and Matrix account for a user" },
1066
+ "qi/iid.create": { displayName: "Create IID", description: "Create an IID document on-chain" },
1067
+ "qi/kyc.verify": { displayName: "Identity Verification (KYC)", description: "Verify your identity and save the issued credential to your Vault" },
1068
+ "qi/matrix.dm": { displayName: "Matrix DM", description: "Send a direct message via Matrix" },
1069
+ "qi/matrix.register": { displayName: "Register Matrix Account", description: "Create a Matrix account and access token" },
1070
+ "qi/notification.push": { displayName: "Push Notification", description: "Send a push notification" },
1071
+ "qi/oracle.configureOracle": { displayName: "Configure Oracle", description: "Store oracle secrets and configuration in one step" },
1072
+ "qi/oracle.contract": { displayName: "Create Oracle Contract", description: "Establish the user-oracle Matrix DM room" },
1073
+ "qi/oracle.deploy": { displayName: "Deploy Oracle", description: "Build, deploy, and start the oracle" },
1074
+ "qi/oracle.deploySetup": { displayName: "Set Up Oracle Deployment", description: "Build and prepare the oracle for deployment" },
1075
+ "qi/oracle.deployStart": { displayName: "Start Oracle Deployment", description: "Start the oracle deployment process" },
1076
+ "qi/oracle.invoke": { displayName: "Ask your Agent", description: "Send a prompt to the Personal Agent" },
1077
+ "qi/oracle.storeConfig": { displayName: "Store Oracle Config", description: "Store the oracle configuration in Matrix room state" },
1078
+ "qi/oracle.storeSecrets": { displayName: "Store Oracle Secrets", description: "Store oracle secrets in Matrix room state" },
1079
+ "qi/oracle.storeSecretsAndConfig": { displayName: "Store Oracle Secrets & Config", description: "Store oracle secrets and configuration in Matrix room state" },
1080
+ "qi/outlook.email.send": { displayName: "Send Outlook email", description: "Send an email from the template author's Outlook account" },
1081
+ "qi/pod.domain-indexer-lookup": { displayName: "Define Purpose", description: "Capture intent and find matching Blueprint candidates" },
1082
+ "qi/pod.domain-single-selection": { displayName: "Select Blueprint", description: "Choose a Blueprint protocol for the POD" },
1083
+ "qi/pod.entity-single-selection": { displayName: "Select Parent Organisation", description: "Pick a parent entity where you hold a role" },
1084
+ "qi/pod.governance-config": { displayName: "Configure Governance", description: "Set the governance group type and decision policy" },
1085
+ "qi/pod.list-domain-flows": { displayName: "Select Flow Templates", description: "Choose protocol flow templates to import" },
1086
+ "qi/pod.member-multi-select": { displayName: "Configure Membership", description: "Define POD members, roles, and voting power" },
1087
+ "qi/proposal.create": { displayName: "Create Proposal", description: "Create an on-chain governance proposal" },
1088
+ "qi/proposal.vote": { displayName: "Vote on Proposal", description: "Cast a vote on a governance proposal" },
1089
+ "qi/protocol.select": { displayName: "Select Protocol", description: "Select a protocol from a configured list" },
1090
+ "qi/sandbox.provision": { displayName: "Provision Sandbox", description: "Provision a sandbox environment for the oracle" },
1091
+ "qi/settlement.execute": { displayName: "Execute Settlement", description: "Execute an approved settlement using the configured provider" },
1092
+ "qi/slack.message.send": { displayName: "Post Slack message", description: "Post a message to a channel from the template author's Slack account" },
1093
+ "qi/topic.action.cancel": { displayName: "Cancel Topic Action", description: "Cancel an outstanding governed Action request" },
1094
+ "qi/topic.action.receipt.record": { displayName: "Record Action Receipt", description: "Record a signed Action receipt against its Topic request" },
1095
+ "qi/topic.action.request": { displayName: "Request Topic Action", description: "Request an Action against the current Topic revision" },
1096
+ "qi/topic.context.link": { displayName: "Link Topic Context", description: "Link a referenced resource or service to the Topic" },
1097
+ "qi/topic.contract.accept": { displayName: "Accept Topic Contract", description: "Accept the exact current Topic contract revision" },
1098
+ "qi/topic.decision.record": { displayName: "Record Topic Decision", description: "Record an authorised decision and its rationale" },
1099
+ "qi/topic.file.attach-reference": { displayName: "Attach File Reference", description: "Attach a pinned VFS file reference to the Topic" },
1100
+ "qi/topic.flow.bind": { displayName: "Bind Flow to Topic", description: "Bind a governed Flow revision to the Topic" },
1101
+ "qi/topic.flow.unbind": { displayName: "Unbind Flow from Topic", description: "Remove an existing Flow binding from the Topic" },
1102
+ "qi/topic.outcome.confirm": { displayName: "Confirm Topic Outcome", description: "Confirm a proposed outcome using the stated authority" },
1103
+ "qi/topic.outcome.propose": { displayName: "Propose Topic Outcome", description: "Propose an evidence-backed outcome for review" },
1104
+ "qi/topic.status.transition": { displayName: "Change Topic Status", description: "Move the Topic between permitted lifecycle statuses" },
1105
+ "qi/wallet.fund": { displayName: "Fund Wallet", description: "Fund a wallet with an on-chain transfer" },
1106
+ "qi/wallet.generate": { displayName: "Generate Wallet", description: "Generate an IXO wallet and DID" },
1107
+ "qi/wallet.generateAndFund": { displayName: "Generate & Fund Wallet", description: "Generate an IXO wallet and fund it on-chain" },
1108
+ "qi/work.accept": { displayName: "Accept Work", description: "Accept submitted work using the stated authority" },
1109
+ "qi/work.assign": { displayName: "Assign Work", description: "Assign a referenced unit of work" },
1110
+ "qi/work.checkpoint": { displayName: "Record Work Checkpoint", description: "Record progress and evidence for work in progress" },
1111
+ "qi/work.dispatch": { displayName: "Dispatch Work", description: "Dispatch assigned work to its assignee" },
1112
+ "qi/work.submit": { displayName: "Submit Work", description: "Submit completed work for review" },
1113
+ "qi/xero.contact.create": { displayName: "Create Xero contact", description: "Add a customer or supplier in Xero" },
1114
+ "qi/xero.invoice.create": { displayName: "Create Xero invoice", description: "Draft a new Xero invoice" },
1115
+ "qi/xero.invoice.list": { displayName: "List Xero invoices", description: "Fetch invoices from Xero" },
1116
+ "qi/xero.payment.create": { displayName: "Record Xero payment", description: "Settle a Xero bill \u2014 typically wired to capture an on-chain tx hash as the reference" }
1117
+ };
1118
+ function humanizeActionType(type) {
1119
+ const withoutNamespace = type.replace(/^qi\//, "").replace(/^oracle\.?/, "oracle ");
1120
+ const words = withoutNamespace.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[./_-]+/).filter(Boolean);
1121
+ return words.map((word) => /^(iid|kyc|dao|nft|http|dm|pod|cw20)$/i.test(word) ? word.toUpperCase() : word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1122
+ }
1123
+ function getActionPresentation(type) {
1124
+ const registered = PRESENTATION[type];
1125
+ if (registered) return registered;
1126
+ const displayName = humanizeActionType(type) || "Action";
1127
+ return { displayName, description: `Perform ${displayName.toLowerCase()} as part of this flow.` };
1128
+ }
1129
+
583
1130
  // src/core/lib/warnOnce.ts
584
1131
  var seen = /* @__PURE__ */ new Set();
585
1132
  function warnOnce(key, message) {
@@ -588,8 +1135,160 @@ function warnOnce(key, message) {
588
1135
  console.warn(message);
589
1136
  }
590
1137
 
1138
+ // src/core/lib/actionRegistry/types.ts
1139
+ var TOPIC_ACTION_BASE_KINDS = ["task", "agent_task", "proposal", "evaluation", "claims", "question", "discussion", "incident"];
1140
+ var CollectionStateEnum = /* @__PURE__ */ ((CollectionStateEnum2) => {
1141
+ CollectionStateEnum2[CollectionStateEnum2["OPEN"] = 0] = "OPEN";
1142
+ CollectionStateEnum2[CollectionStateEnum2["PAUSED"] = 1] = "PAUSED";
1143
+ CollectionStateEnum2[CollectionStateEnum2["CLOSED"] = 2] = "CLOSED";
1144
+ return CollectionStateEnum2;
1145
+ })(CollectionStateEnum || {});
1146
+
1147
+ // src/core/lib/actionRegistry/topicPolicy.ts
1148
+ var ALL_KINDS = [...TOPIC_ACTION_BASE_KINDS];
1149
+ var HUMAN_OWNED = [
1150
+ /^qi\/human\./,
1151
+ /^qi\/governance\./,
1152
+ /^qi\/proposal\./,
1153
+ /^qi\/(claim|bid)\.(submit|evaluate)$/,
1154
+ /^qi\/(domain\.sign|entity\.|iid\.|identity\.|kyc\.)/,
1155
+ /^qi\/(carbon\.(harvest|retire)|wallet\.fund)/,
1156
+ /^qi\/xero\.(invoice|payment)\.create$/,
1157
+ /^qi\/settlement\./,
1158
+ /^qi\/topic\.(flow\.(bind|unbind)|status\.transition|contract\.accept|outcome\.confirm)$/,
1159
+ /^qi\/work\.accept$/,
1160
+ /^qi\/answer\.accept$/
1161
+ ];
1162
+ var RESTRICTED = [/^qi\/oracle\.(configure|deploy|store)/i, /^qi\/(matrix\.register|sandbox\.provision|entity\.createOracle|wallet\.generate)/];
1163
+ function any(patterns, value) {
1164
+ return patterns.some((pattern) => pattern.test(value));
1165
+ }
1166
+ function requiredServices(type) {
1167
+ if (type.startsWith("qi/http.")) return ["http"];
1168
+ if (type === "qi/email.send") return ["email"];
1169
+ if (type === "qi/notification.push") return ["notifications"];
1170
+ if (/^qi\/(gmail|outlook|slack|googlecalendar|calendar|xero)\./.test(type)) return ["integrations"];
1171
+ if (/^qi\/flow\.run\./.test(type)) return ["flowRuns"];
1172
+ if (/^qi\/blueprint\./.test(type)) return ["blueprint"];
1173
+ if (/^qi\/bid\./.test(type)) return ["bid"];
1174
+ if (/^qi\/claim\./.test(type)) return ["claim"];
1175
+ if (type === "qi/collection.lifecycle") return ["collection"];
1176
+ if (type === "qi/collection.users") return ["collectionUsers"];
1177
+ if (/^qi\/(matrix\.dm|credential\.store)/.test(type)) return ["matrix"];
1178
+ if (/^qi\/(oracle\.|wallet\.|iid\.|matrix\.register|identity\.|entity\.createOracle|sandbox\.)/.test(type) || type === "qi/oracle.invoke") return ["oracle"];
1179
+ if (/^qi\/carbon\./.test(type)) return ["carbon"];
1180
+ if (/^qi\/entity\.transfer/.test(type)) return ["entity"];
1181
+ if (/^qi\/kyc\./.test(type)) return ["kyc"];
1182
+ if (/^qi\/eval\./.test(type)) return ["evalRegister", "rubric"];
1183
+ if (/^qi\/topic\./.test(type)) return ["topic"];
1184
+ if (/^qi\/agent\./.test(type)) return ["agents"];
1185
+ if (/^qi\/evidence\./.test(type)) return ["evidence"];
1186
+ if (/^qi\/evaluation\./.test(type)) return ["evaluations"];
1187
+ if (/^qi\/settlement\./.test(type)) return ["settlement"];
1188
+ if (/^qi\/incident\.escalate/.test(type)) return ["incidents"];
1189
+ if (/^qi\/(work|answer|incident\.mitigation)\./.test(type)) return ["topic"];
1190
+ if (/^qi\/(governance|proposal|domain\.)\./.test(type)) return ["portalHandlers"];
1191
+ return [];
1192
+ }
1193
+ function riskTier(action) {
1194
+ const type = action.type;
1195
+ if (/^qi\/(settlement\.execute|carbon\.retire)$/.test(type)) return "critical";
1196
+ if (/^qi\/(governance\.|claim\.evaluate|entity\.transfer|domain\.sign|xero\.payment\.create|wallet\.fund)/.test(type)) return "high";
1197
+ if (!action.sideEffect || /\.(list|loadBatches|card-preview|fetch)$/.test(type)) return "low";
1198
+ return "medium";
1199
+ }
1200
+ function sensitivePaths(type) {
1201
+ const input = [];
1202
+ const output = [];
1203
+ if (type.startsWith("qi/http.")) {
1204
+ input.push("headers.authorization", "headers.cookie", "headers.x-api-key", "body");
1205
+ output.push("data", "response");
1206
+ }
1207
+ if (/^qi\/(email|gmail|outlook|slack|notification|matrix\.dm)/.test(type)) {
1208
+ input.push("to", "cc", "bcc", "body", "template", "variables");
1209
+ output.push("providerResponse");
1210
+ }
1211
+ if (/^qi\/(gmail|outlook|slack|googlecalendar|calendar|xero)\./.test(type)) input.push("connection", "bindingId");
1212
+ if (/^qi\/(kyc|credential)\./.test(type)) {
1213
+ input.push("data", "credential");
1214
+ output.push("credential", "surveyAnswers");
1215
+ }
1216
+ if (/^qi\/(oracle\.|wallet\.|iid\.|matrix\.register|identity\.|entity\.createOracle|sandbox\.)/.test(type)) {
1217
+ input.push("mnemonic", "pin", "secrets", "config");
1218
+ output.push("mnemonic", "privateKey", "matrixAccessToken", "matrixPassword", "matrixRecoveryPhrase", "secrets");
1219
+ }
1220
+ if (type === "qi/oracle.invoke") {
1221
+ input.push("prompt");
1222
+ output.push("result");
1223
+ }
1224
+ if (/^qi\/(form|human\.form|claim|bid)\./.test(type)) {
1225
+ input.push("answers", "surveyAnswers", "surveyData");
1226
+ output.push("answers", "surveyAnswers", "surveyData");
1227
+ }
1228
+ return { sensitiveInputPaths: [...new Set(input)].sort(), sensitiveOutputPaths: [...new Set(output)].sort() };
1229
+ }
1230
+ function topicKindsAndRelevance(type) {
1231
+ if (any(RESTRICTED, type)) return { supportedBaseKinds: ["agent_task"], relevance: "restricted" };
1232
+ if (/^qi\/flow\.run\./.test(type)) return { supportedBaseKinds: ALL_KINDS, relevance: "recommended" };
1233
+ if (/^qi\/(oracle\.invoke|agent\.)/.test(type)) return { supportedBaseKinds: ["agent_task", "question", "task"], relevance: "recommended" };
1234
+ if (/^qi\/(governance\.|proposal\.|topic\.decision)/.test(type)) return { supportedBaseKinds: ["proposal", "discussion", "evaluation"], relevance: "recommended" };
1235
+ if (/^qi\/(eval\.|evaluation\.)/.test(type)) return { supportedBaseKinds: ["evaluation", "claims"], relevance: "recommended" };
1236
+ if (/^qi\/(claim\.|collection\.|settlement\.)/.test(type)) return { supportedBaseKinds: ["claims", "evaluation"], relevance: "recommended" };
1237
+ if (/^qi\/(work\.)/.test(type)) return { supportedBaseKinds: ["task", "discussion", "incident"], relevance: "recommended" };
1238
+ if (/^qi\/(evidence\.|answer\.)/.test(type)) return { supportedBaseKinds: ["question", "evaluation"], relevance: "recommended" };
1239
+ if (/^qi\/incident\./.test(type)) return { supportedBaseKinds: ["incident"], relevance: "recommended" };
1240
+ if (/^qi\/topic\./.test(type)) return { supportedBaseKinds: ALL_KINDS, relevance: "recommended" };
1241
+ if (/^qi\/(http\.|domain\.card-preview|calendar\.|googlecalendar\.)/.test(type))
1242
+ return { supportedBaseKinds: ["question", "evaluation", "agent_task", "task"], relevance: "contextual" };
1243
+ if (/^qi\/(email\.|gmail\.|outlook\.|slack\.|matrix\.dm|notification\.)/.test(type)) {
1244
+ return { supportedBaseKinds: ["task", "question", "discussion", "incident"], relevance: "contextual" };
1245
+ }
1246
+ return { supportedBaseKinds: ALL_KINDS, relevance: "contextual" };
1247
+ }
1248
+ function topicSemanticRecords(type) {
1249
+ if (/^qi\/(governance\.|proposal\.)/.test(type)) return ["org.ixo.topic.proposal-receipt"];
1250
+ if (/^qi\/claim\.submit/.test(type)) return ["org.ixo.topic.claim-submission"];
1251
+ if (/^qi\/claim\.evaluate/.test(type)) return ["org.ixo.topic.claim-evaluation"];
1252
+ if (/^qi\/evaluation\./.test(type)) return ["org.ixo.topic.evaluation-assertion"];
1253
+ return [];
1254
+ }
1255
+ function normalizeActionPolicy(action) {
1256
+ const sensitive = sensitivePaths(action.type);
1257
+ const topicSelection = topicKindsAndRelevance(action.type);
1258
+ const semanticRecords = topicSemanticRecords(action.type);
1259
+ const declaredTopic = action.topic;
1260
+ const sensitiveInputPaths = [.../* @__PURE__ */ new Set([...action.sensitiveInputPaths || [], ...sensitive.sensitiveInputPaths])].sort();
1261
+ const sensitiveOutputPaths = [.../* @__PURE__ */ new Set([...action.sensitiveOutputPaths || [], ...sensitive.sensitiveOutputPaths])].sort();
1262
+ return {
1263
+ executionOwner: action.executionOwner || (any(HUMAN_OWNED, action.type) ? "human" : "agent"),
1264
+ riskTier: action.riskTier || riskTier(action),
1265
+ requiredServices: [...new Set(action.requiredServices || requiredServices(action.type))].sort(),
1266
+ sensitiveInputPaths,
1267
+ sensitiveOutputPaths,
1268
+ topic: declaredTopic ? {
1269
+ ...declaredTopic,
1270
+ semanticRecordTypes: declaredTopic.semanticRecordTypes || getTopicSemanticRecordDefinitions(declaredTopic.permittedTopicRecordTypes),
1271
+ permittedTopicRecordTypes: (declaredTopic.semanticRecordTypes || getTopicSemanticRecordDefinitions(declaredTopic.permittedTopicRecordTypes)).map(
1272
+ (definition) => definition.type
1273
+ )
1274
+ } : {
1275
+ ...topicSelection,
1276
+ writeBackMode: semanticRecords.length > 0 ? "semantic-record" : "receipt-only",
1277
+ semanticRecordTypes: getTopicSemanticRecordDefinitions(semanticRecords),
1278
+ permittedTopicRecordTypes: semanticRecords,
1279
+ lifecycleEffect: "none",
1280
+ requiredTopicAbilities: ["topic/request-action", "topic/record-action"],
1281
+ redactionPolicy: { mode: "paths", sensitiveInputPaths, sensitiveOutputPaths }
1282
+ }
1283
+ };
1284
+ }
1285
+
591
1286
  // src/core/lib/actionRegistry/registry.ts
592
1287
  var actions = /* @__PURE__ */ new Map();
1288
+ var registryRevision = 0;
1289
+ function getRegistryRevision() {
1290
+ return registryRevision;
1291
+ }
593
1292
  var STEP_COMPLETED_EVENT_NAME = "step.completed";
594
1293
  var STEP_COMPLETED_EVENT = {
595
1294
  name: STEP_COMPLETED_EVENT_NAME,
@@ -604,6 +1303,7 @@ var neverDone = {
604
1303
  isDone: () => false
605
1304
  };
606
1305
  var ACTION_TYPE_ALIASES = {
1306
+ oracle: "qi/oracle.invoke",
607
1307
  bid: "qi/bid.submit",
608
1308
  claim: "qi/claim.submit",
609
1309
  evaluateBid: "qi/bid.evaluate",
@@ -627,7 +1327,16 @@ var ACTION_TYPE_ALIASES = {
627
1327
  MemberMultiSelect: "qi/pod.member-multi-select",
628
1328
  governanceConfig: "qi/pod.governance-config",
629
1329
  listDomainFlows: "qi/pod.list-domain-flows",
630
- "matrix.dm": "qi/matrix.dm"
1330
+ "matrix.dm": "qi/matrix.dm",
1331
+ // The qi/calendar.* namespace is retired permanently (IXO-4420 §5): these
1332
+ // blocks were always Google Calendar via Composio, misnamed as neutral.
1333
+ // Aliases keep every existing document loading; because resolveActionType
1334
+ // checks aliases before registered types, no new canonical action can ever
1335
+ // be registered under these names — the IXO-native calendar (M4) takes
1336
+ // qi/ixo.calendar.event.* instead.
1337
+ "qi/calendar.event.create": "qi/googlecalendar.event.create-self",
1338
+ "qi/calendar.event.update": "qi/googlecalendar.event.update-self",
1339
+ "qi/calendar.event.list": "qi/googlecalendar.event.list-self"
631
1340
  };
632
1341
  var aliases = new Map(Object.entries(ACTION_TYPE_ALIASES));
633
1342
  function resolveActionType(type) {
@@ -678,13 +1387,17 @@ function capabilityPatternCoversCan(pattern, can, options = {}) {
678
1387
  return false;
679
1388
  }
680
1389
  function registerAction(definition) {
681
- const normalized = definition.can ? { ...definition, can: normalizeCan(definition.can) } : definition;
682
- if (!normalized.done) {
1390
+ const presentation = getActionPresentation(definition.type);
1391
+ definition.displayName = definition.displayName?.trim() || presentation.displayName;
1392
+ definition.description = definition.description?.trim() || presentation.description;
1393
+ if (definition.can) definition.can = normalizeCan(definition.can);
1394
+ Object.assign(definition, normalizeActionPolicy(definition));
1395
+ if (!definition.done) {
683
1396
  warnOnce(`missing-done-contract:${definition.type}`, `[flow-config] action ${definition.type}: no done contract declared; defaulting to state === 'completed'`);
684
- actions.set(definition.type, { ...normalized, done: doneWhenCompleted });
685
- return;
1397
+ definition.done = doneWhenCompleted;
686
1398
  }
687
- actions.set(definition.type, normalized);
1399
+ actions.set(definition.type, definition);
1400
+ registryRevision += 1;
688
1401
  }
689
1402
  function getAction(type) {
690
1403
  return actions.get(resolveActionType(type));
@@ -770,6 +1483,8 @@ function normalizeInputs(inputs) {
770
1483
  }
771
1484
 
772
1485
  // src/core/lib/actionRegistry/manifest.ts
1486
+ var ACTION_MANIFEST_VERSION = "4";
1487
+ var ACTION_REGISTRY_VERSION = "6.32.0-topic-actions.3";
773
1488
  function serializeProof(action) {
774
1489
  const proof = action.proof;
775
1490
  if (proof === "none" || proof === void 0) return { kind: "none" };
@@ -784,42 +1499,106 @@ function serializeDone(action) {
784
1499
  cardinality: action.cardinality || "once"
785
1500
  };
786
1501
  }
787
- function generateActionManifest() {
788
- const aliasEntries = getAliasEntries();
1502
+ function serializeAction(action, aliases2) {
1503
+ const can = action.can || actionTypeToCan(action.type) || "";
1504
+ return {
1505
+ type: action.type,
1506
+ displayName: action.displayName,
1507
+ description: action.description,
1508
+ aliases: aliases2,
1509
+ can,
1510
+ effectiveCapability: {
1511
+ action: can,
1512
+ ...action.requiredCapability ? { flowExecution: action.requiredCapability } : {},
1513
+ topicWriteBack: [...action.topic?.requiredTopicAbilities || []].sort()
1514
+ },
1515
+ sideEffect: action.sideEffect,
1516
+ defaultRequiresConfirmation: action.defaultRequiresConfirmation,
1517
+ executionOwner: action.executionOwner || "agent",
1518
+ riskTier: action.riskTier || "medium",
1519
+ requiredServices: [...action.requiredServices || []].sort(),
1520
+ sensitiveInputPaths: [...action.sensitiveInputPaths || []].sort(),
1521
+ sensitiveOutputPaths: [...action.sensitiveOutputPaths || []].sort(),
1522
+ hidden: action.hiddenFromAuthoring === true,
1523
+ deprecated: action.deprecated === true,
1524
+ ...action.supersededBy ? { supersededBy: action.supersededBy } : {},
1525
+ proof: serializeProof(action),
1526
+ done: serializeDone(action),
1527
+ inputSchema: action.inputSchema || {},
1528
+ outputSchema: action.outputSchema || [],
1529
+ events: (action.events || []).map((event) => ({
1530
+ name: event.name,
1531
+ displayName: event.displayName,
1532
+ description: event.description,
1533
+ payloadSchema: event.payloadSchema
1534
+ })),
1535
+ hasDynamicEvents: !!action.getDynamicEvents,
1536
+ hasDynamicOutputSchema: !!action.getDynamicOutputSchema,
1537
+ eligibleForEventTrigger: !!action.eligibleForEventTrigger,
1538
+ eligibleForTimeTrigger: !!action.eligibleForTimeTrigger,
1539
+ ...action.scheduling ? { scheduling: action.scheduling } : {},
1540
+ hasCustomInputValidation: !!action.getMissingInputs,
1541
+ topic: action.topic
1542
+ };
1543
+ }
1544
+ function contractDigestPayload(entry) {
1545
+ const { displayName: _displayName, description: _description, topic, ...contract } = entry;
1546
+ const { semanticRecordTypes, ...legacyTopic } = topic;
1547
+ const semanticContracts = semanticRecordTypes.map(({ displayName: _recordDisplayName, description: _recordDescription, ...definition }) => definition);
1548
+ return {
1549
+ ...contract,
1550
+ topic: semanticContracts.length > 0 ? { ...legacyTopic, semanticRecordTypes: semanticContracts } : legacyTopic
1551
+ };
1552
+ }
1553
+ function buildActionManifest() {
789
1554
  const aliasesByType = /* @__PURE__ */ new Map();
790
- for (const [alias, canonical] of aliasEntries) {
791
- const list = aliasesByType.get(canonical) || [];
792
- list.push(alias);
793
- aliasesByType.set(canonical, list);
794
- }
795
- const actions2 = getAllActions().map((action) => {
796
- const entry = {
797
- type: action.type,
798
- aliases: (aliasesByType.get(action.type) || []).sort(),
799
- sideEffect: action.sideEffect,
800
- defaultRequiresConfirmation: action.defaultRequiresConfirmation,
801
- proof: serializeProof(action),
802
- done: serializeDone(action),
803
- hasDynamicEvents: !!action.getDynamicEvents,
804
- hasDynamicOutputSchema: !!action.getDynamicOutputSchema,
805
- eligibleForEventTrigger: !!action.eligibleForEventTrigger,
806
- hasCustomInputValidation: !!action.getMissingInputs
807
- };
808
- if (action.can) entry.can = action.can;
809
- if (action.requiredCapability) entry.requiredCapability = action.requiredCapability;
810
- if (action.inputSchema) entry.inputSchema = action.inputSchema;
811
- if (action.outputSchema) entry.outputSchema = action.outputSchema;
812
- if (action.events) {
813
- entry.events = action.events.map((event) => ({
814
- name: event.name,
815
- displayName: event.displayName,
816
- description: event.description,
817
- payloadSchema: event.payloadSchema
818
- }));
819
- }
820
- return entry;
821
- }).sort((a, b) => a.type.localeCompare(b.type));
822
- return { manifestVersion: "1", actions: actions2 };
1555
+ for (const [alias, canonical] of getAliasEntries()) {
1556
+ aliasesByType.set(canonical, [...aliasesByType.get(canonical) || [], alias]);
1557
+ }
1558
+ const actions2 = getAllActions().map((action) => serializeAction(action, [...aliasesByType.get(action.type) || []].sort(compareCodeUnits))).sort((a, b) => compareCodeUnits(a.type, b.type)).map((entry) => ({ ...entry, contractDigest: sha256Digest(contractDigestPayload(entry)) }));
1559
+ const payload = { manifestVersion: ACTION_MANIFEST_VERSION, registryVersion: ACTION_REGISTRY_VERSION, actions: actions2 };
1560
+ return { ...payload, manifestDigest: sha256Digest(payload) };
1561
+ }
1562
+ var cached;
1563
+ function generateActionManifest() {
1564
+ const revision = getRegistryRevision();
1565
+ if (cached && cached.revision === revision) return cached.manifest;
1566
+ const manifest = buildActionManifest();
1567
+ cached = { revision, manifest };
1568
+ return manifest;
1569
+ }
1570
+ function actionManifestIssues(manifest = generateActionManifest()) {
1571
+ const issues = [];
1572
+ for (const action of manifest.actions) {
1573
+ const publicAction = !action.hidden;
1574
+ if (!action.can) issues.push({ actionType: action.type, code: "MISSING_CAN", message: "Action has no canonical can ability." });
1575
+ if (!action.displayName.trim() || !action.description.trim() || action.displayName === action.type || action.description.includes(action.type) || /^Perform .* as part of this flow\.$/.test(action.description)) {
1576
+ issues.push({ actionType: action.type, code: "MISSING_PRESENTATION", message: "Action needs a non-technical display name and description." });
1577
+ }
1578
+ if (publicAction && Object.keys(action.inputSchema).length === 0) {
1579
+ issues.push({ actionType: action.type, code: "MISSING_INPUT_SCHEMA", message: "Public Action has no input schema." });
1580
+ }
1581
+ if (publicAction && action.outputSchema.length === 0 && !action.hasDynamicOutputSchema) {
1582
+ issues.push({ actionType: action.type, code: "MISSING_OUTPUT_SCHEMA", message: "Public Action has no output schema." });
1583
+ }
1584
+ if (action.sideEffect && action.proof.kind === "none") {
1585
+ issues.push({ actionType: action.type, code: "SIDE_EFFECT_WITHOUT_PROOF", message: "Side-effecting Action declares no proof." });
1586
+ }
1587
+ if (!action.topic) issues.push({ actionType: action.type, code: "MISSING_TOPIC_POLICY", message: "Action has no Topic compatibility policy." });
1588
+ else if (action.topic.lifecycleEffect !== "none" || action.topic.supportedBaseKinds.length === 0) {
1589
+ issues.push({ actionType: action.type, code: "INVALID_TOPIC_POLICY", message: "Topic policy must support a base Kind and cannot imply lifecycle effects." });
1590
+ }
1591
+ if (action.topic.semanticRecordTypes.some(
1592
+ (definition) => !definition.type || !definition.version || !definition.displayName.trim() || !definition.description.trim() || Object.keys(definition.valueSchema).length === 0
1593
+ ) || action.topic.permittedTopicRecordTypes.join("|") !== action.topic.semanticRecordTypes.map((definition) => definition.type).join("|")) {
1594
+ issues.push({
1595
+ actionType: action.type,
1596
+ code: "INVALID_SEMANTIC_RECORD_DEFINITION",
1597
+ message: "Topic semantic record definitions must be complete and determine the compatibility type list."
1598
+ });
1599
+ }
1600
+ }
1601
+ return issues;
823
1602
  }
824
1603
 
825
1604
  // src/core/lib/actionRegistry/inputRequirements.ts
@@ -938,7 +1717,8 @@ function buildServicesFromHandlers(handlers) {
938
1717
  request: async (params) => {
939
1718
  const fetchOptions = {
940
1719
  method: params.method,
941
- headers: { "Content-Type": "application/json", ...params.headers }
1720
+ headers: { "Content-Type": "application/json", ...params.headers },
1721
+ redirect: "error"
942
1722
  };
943
1723
  if (params.method !== "GET" && params.body) {
944
1724
  fetchOptions.body = typeof params.body === "string" ? params.body : JSON.stringify(params.body);
@@ -952,7 +1732,9 @@ function buildServicesFromHandlers(handlers) {
952
1732
  return {
953
1733
  status: res.status,
954
1734
  headers: responseHeaders,
955
- data
1735
+ data,
1736
+ responseDigest: sha256Digest(data),
1737
+ requestId: sha256Digest({ url: params.url, method: params.method, status: res.status, data })
956
1738
  };
957
1739
  }
958
1740
  },
@@ -2212,6 +2994,182 @@ for (const spec of ACTIONS) {
2212
2994
  });
2213
2995
  }
2214
2996
 
2997
+ // src/core/lib/actionRegistry/actions/governance/_shared.ts
2998
+ var GOVERNANCE_REQUIRED_FIELDS = {
2999
+ "qi/governance.authz.exec": ["authzExecActionType"],
3000
+ "qi/governance.authz.grant": ["grantee", "msgTypeUrl"],
3001
+ "qi/governance.authz.revoke": ["grantee", "msgTypeUrl"],
3002
+ "qi/governance.chain-governance-vote": ["proposalId", "vote"],
3003
+ "qi/governance.contract.execute": ["address", "message"],
3004
+ "qi/governance.contract.instantiate": ["codeId", "label", "message"],
3005
+ "qi/governance.contract.manage-cw20": ["adding", "address"],
3006
+ "qi/governance.contract.migrate": ["contract", "codeId", "msg"],
3007
+ "qi/governance.contract.update-admin": ["contract", "newAdmin"],
3008
+ "qi/governance.custom-message": ["message"],
3009
+ "qi/governance.dao.accept-to-marketplace": ["did", "relayerNodeAddress", "relayerNodeDid"],
3010
+ "qi/governance.dao.admin-exec": ["targetCoreAddress", "msgs"],
3011
+ "qi/governance.dao.create-entity": ["typeUrl", "value"],
3012
+ "qi/governance.dao.join": ["entityDid", "memberId"],
3013
+ "qi/governance.dao.manage-storage": ["setting", "key", "value"],
3014
+ "qi/governance.dao.manage-subdaos": [],
3015
+ "qi/governance.dao.update-info": ["name"],
3016
+ "qi/governance.member-proposal": ["operation", "members"],
3017
+ "qi/governance.nft.burn": ["collection", "tokenId"],
3018
+ "qi/governance.nft.manage-collections": ["adding", "address"],
3019
+ "qi/governance.nft.transfer": ["collection", "tokenId", "recipient"],
3020
+ "qi/governance.staking.stake": ["stakeType", "amount"],
3021
+ "qi/governance.staking.stake-to-group": ["tokenContract", "stakingContract", "amount"],
3022
+ "qi/governance.settings-proposal": ["votingPeriodHours", "quorumPercent", "thresholdPercent"],
3023
+ "qi/governance.submission-config-proposal": ["anyoneCanPropose", "depositRequired"],
3024
+ "qi/governance.transaction.mint": ["recipient", "amount"],
3025
+ "qi/governance.transaction.send-funds": ["recipient", "denom", "amount"],
3026
+ "qi/governance.transaction.perform-token-swap": ["tokenSwapContractAddress", "selfPartyType", "selfPartyDenomOrAddress", "selfPartyAmount"],
3027
+ "qi/governance.transaction.send-group-token": ["tokenContract", "recipient", "amount"],
3028
+ "qi/governance.transaction.withdraw-token-swap": ["tokenSwapContractAddress"],
3029
+ "qi/governance.validator.actions": ["validatorActionType"]
3030
+ };
3031
+ var GOVERNANCE_FIELDS = {
3032
+ "qi/governance.authz.exec": ["authzExecActionType", "delegatorAddress", "validatorAddress", "validatorDstAddress", "amount", "custom"],
3033
+ "qi/governance.authz.grant": ["grantee", "msgTypeUrl"],
3034
+ "qi/governance.authz.revoke": ["grantee", "msgTypeUrl"],
3035
+ "qi/governance.chain-governance-vote": ["proposalId", "vote"],
3036
+ "qi/governance.contract.execute": ["address", "message", "funds"],
3037
+ "qi/governance.contract.instantiate": ["codeId", "label", "admin", "message", "funds"],
3038
+ "qi/governance.contract.manage-cw20": ["adding", "address"],
3039
+ "qi/governance.contract.migrate": ["contract", "codeId", "msg"],
3040
+ "qi/governance.contract.update-admin": ["contract", "newAdmin"],
3041
+ "qi/governance.custom-message": ["message"],
3042
+ "qi/governance.dao.accept-to-marketplace": ["did", "relayerNodeAddress", "relayerNodeDid"],
3043
+ "qi/governance.dao.admin-exec": ["targetCoreAddress", "msgs"],
3044
+ "qi/governance.dao.create-entity": ["typeUrl", "value"],
3045
+ "qi/governance.dao.join": ["entityDid", "memberId"],
3046
+ "qi/governance.dao.manage-storage": ["setting", "key", "value"],
3047
+ "qi/governance.dao.manage-subdaos": ["toAdd", "toRemove"],
3048
+ "qi/governance.dao.update-info": ["name", "daoDescription", "imageUrl", "automaticallyAddCw20s", "automaticallyAddCw721s"],
3049
+ "qi/governance.member-proposal": ["operation", "members"],
3050
+ "qi/governance.nft.burn": ["collection", "tokenId"],
3051
+ "qi/governance.nft.manage-collections": ["adding", "address"],
3052
+ "qi/governance.nft.transfer": ["collection", "tokenId", "recipient", "executeSmartContract", "smartContractMsg"],
3053
+ "qi/governance.staking.stake": ["stakeType", "validator", "toValidator", "amount"],
3054
+ "qi/governance.staking.stake-to-group": ["tokenContract", "stakingContract", "amount"],
3055
+ "qi/governance.settings-proposal": ["votingPeriodHours", "quorumPercent", "thresholdPercent", "allowRevoting"],
3056
+ "qi/governance.submission-config-proposal": ["anyoneCanPropose", "depositRequired", "depositAmount", "depositRefundPolicy"],
3057
+ "qi/governance.transaction.mint": ["recipient", "amount"],
3058
+ "qi/governance.transaction.send-funds": ["recipient", "denom", "amount"],
3059
+ "qi/governance.transaction.perform-token-swap": ["tokenSwapContractAddress", "selfPartyType", "selfPartyDenomOrAddress", "selfPartyAmount"],
3060
+ "qi/governance.transaction.send-group-token": ["tokenContract", "recipient", "amount"],
3061
+ "qi/governance.transaction.withdraw-token-swap": ["tokenSwapContractAddress"],
3062
+ "qi/governance.validator.actions": ["validatorActionType", "createMsg", "editMsg"]
3063
+ };
3064
+ var BOOLEAN_FIELDS = /* @__PURE__ */ new Set(["adding", "automaticallyAddCw20s", "automaticallyAddCw721s", "executeSmartContract", "anyoneCanPropose", "depositRequired", "allowRevoting"]);
3065
+ var NUMBER_FIELDS = /* @__PURE__ */ new Set(["codeId", "vote", "votingPeriodHours", "quorumPercent", "thresholdPercent"]);
3066
+ var ARRAY_FIELDS = /* @__PURE__ */ new Set(["funds", "msgs", "toAdd", "toRemove", "members"]);
3067
+ var OBJECT_FIELDS = /* @__PURE__ */ new Set(["value"]);
3068
+ function governanceProperty(name) {
3069
+ if (BOOLEAN_FIELDS.has(name)) return { type: "boolean" };
3070
+ if (NUMBER_FIELDS.has(name)) return { type: "number" };
3071
+ if (ARRAY_FIELDS.has(name)) return { type: "array", items: {} };
3072
+ if (OBJECT_FIELDS.has(name)) return { type: "object" };
3073
+ return { type: "string" };
3074
+ }
3075
+ function governanceInputSchema(type, extraFields = {}, requiredOverride) {
3076
+ const properties = {
3077
+ coreAddress: { type: "string", description: "DAO core contract address." },
3078
+ title: { type: "string", description: "Proposal title voters see." },
3079
+ description: { type: "string", description: "Long-form proposal description voters see." }
3080
+ };
3081
+ for (const field of GOVERNANCE_FIELDS[type] || []) properties[field] = governanceProperty(field);
3082
+ Object.assign(properties, extraFields);
3083
+ return {
3084
+ type: "object",
3085
+ required: ["coreAddress", ...requiredOverride || GOVERNANCE_REQUIRED_FIELDS[type] || []],
3086
+ additionalProperties: false,
3087
+ properties
3088
+ };
3089
+ }
3090
+ var STANDARD_OUTPUT_SCHEMA = [
3091
+ { path: "proposalId", displayName: "Proposal ID", type: "string", description: "The on-chain proposal identifier" },
3092
+ { path: "proposalTitle", displayName: "Proposal Title", type: "string", description: "The proposal name voters see on-chain" },
3093
+ { path: "proposalDescription", displayName: "Proposal Description", type: "string", description: "The long-form description voters see on-chain" },
3094
+ { path: "status", displayName: "Proposal Status", type: "string", description: "Current proposal status (open, passed, rejected, executed, etc.)" },
3095
+ { path: "proposalContractAddress", displayName: "Proposal Contract Address", type: "string", description: "The proposal module contract address" },
3096
+ { path: "coreAddress", displayName: "Core Address", type: "string", description: "The DAO core contract address" },
3097
+ { path: "createdAt", displayName: "Created At", type: "string", description: "ISO timestamp of proposal creation" }
3098
+ ];
3099
+ function registerGovernanceProposalAction(spec) {
3100
+ registerAction({
3101
+ type: spec.type,
3102
+ can: spec.can,
3103
+ sideEffect: true,
3104
+ proof: { fields: ["proposalId"] },
3105
+ done: doneWhenCompleted,
3106
+ defaultRequiresConfirmation: true,
3107
+ requiredCapability: "flow/block/execute",
3108
+ inputSchema: governanceInputSchema(spec.type),
3109
+ outputSchema: [...STANDARD_OUTPUT_SCHEMA, ...spec.extraOutputSchema || []],
3110
+ run: async (inputs, ctx) => {
3111
+ const handlers = ctx.handlers;
3112
+ if (!handlers) {
3113
+ throw new Error("Handlers not available");
3114
+ }
3115
+ if (!handlers.getPreProposalContractAddress || !handlers.getGroupContractAddress || !handlers.getProposalContractAddress || !handlers.createProposal) {
3116
+ throw new Error("Governance proposal handlers not available");
3117
+ }
3118
+ const coreAddress = String(inputs.coreAddress || "").trim();
3119
+ if (!coreAddress) throw new Error("coreAddress is required");
3120
+ const actions2 = spec.buildActions(inputs);
3121
+ if (!actions2.length) throw new Error("The proposal must contain at least one action");
3122
+ const title = String(inputs.title || "").trim() || spec.defaultTitle(inputs);
3123
+ const description = String(inputs.description || "").trim() || (spec.defaultDescription ? spec.defaultDescription(inputs) : title);
3124
+ const { preProposalContractAddress } = await handlers.getPreProposalContractAddress({ coreAddress });
3125
+ const { groupContractAddress } = await handlers.getGroupContractAddress({ coreAddress });
3126
+ const { proposalContractAddress } = await handlers.getProposalContractAddress({ coreAddress });
3127
+ const proposalId = await handlers.createProposal({
3128
+ preProposalContractAddress,
3129
+ title,
3130
+ description,
3131
+ actions: actions2,
3132
+ coreAddress,
3133
+ groupContractAddress
3134
+ });
3135
+ if (proposalId === void 0 || proposalId === null || String(proposalId).trim() === "") {
3136
+ throw new Error("Proposal creation returned no proposal id. Check the handler logs.");
3137
+ }
3138
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
3139
+ const output = {
3140
+ proposalId: String(proposalId),
3141
+ proposalTitle: title,
3142
+ proposalDescription: description,
3143
+ status: "open",
3144
+ proposalContractAddress: proposalContractAddress || "",
3145
+ coreAddress,
3146
+ createdAt,
3147
+ ...spec.buildExtraOutput ? spec.buildExtraOutput(inputs) : {}
3148
+ };
3149
+ return {
3150
+ output,
3151
+ topicRecords: ctx.topic ? [
3152
+ {
3153
+ type: "org.ixo.topic.proposal-receipt",
3154
+ id: sha256Digest({ topicId: ctx.topic.topicId, proposalContractAddress, proposalId }),
3155
+ version: 1,
3156
+ value: {
3157
+ event: "created",
3158
+ actionType: spec.type,
3159
+ proposalId: String(proposalId),
3160
+ proposalContractAddress: proposalContractAddress || "",
3161
+ coreAddress,
3162
+ proposalTitle: title,
3163
+ proposalDescriptionDigest: sha256Digest(description),
3164
+ createdAt
3165
+ }
3166
+ }
3167
+ ] : void 0
3168
+ };
3169
+ }
3170
+ });
3171
+ }
3172
+
2215
3173
  // src/core/lib/actionRegistry/actions/governance/memberProposal.ts
2216
3174
  var VALID_OPERATIONS = ["add", "remove", "update-weight"];
2217
3175
  function defaultTitle(operation, count) {
@@ -2233,6 +3191,7 @@ registerAction({
2233
3191
  done: doneWhenCompleted,
2234
3192
  defaultRequiresConfirmation: true,
2235
3193
  requiredCapability: "flow/block/execute",
3194
+ inputSchema: governanceInputSchema("qi/governance.member-proposal"),
2236
3195
  outputSchema: [
2237
3196
  { path: "proposalId", displayName: "Proposal ID", type: "string", description: "The on-chain proposal identifier" },
2238
3197
  { path: "proposalTitle", displayName: "Proposal Title", type: "string", description: "The proposal name voters see on-chain" },
@@ -2333,6 +3292,7 @@ registerAction({
2333
3292
  done: doneWhenCompleted,
2334
3293
  defaultRequiresConfirmation: true,
2335
3294
  requiredCapability: "flow/block/execute",
3295
+ inputSchema: governanceInputSchema("qi/governance.settings-proposal"),
2336
3296
  outputSchema: [
2337
3297
  { path: "proposalId", displayName: "Proposal ID", type: "string", description: "The on-chain proposal identifier" },
2338
3298
  { path: "proposalTitle", displayName: "Proposal Title", type: "string", description: "The proposal name voters see on-chain" },
@@ -2414,70 +3374,6 @@ registerAction({
2414
3374
  }
2415
3375
  });
2416
3376
 
2417
- // src/core/lib/actionRegistry/actions/governance/_shared.ts
2418
- var STANDARD_OUTPUT_SCHEMA = [
2419
- { path: "proposalId", displayName: "Proposal ID", type: "string", description: "The on-chain proposal identifier" },
2420
- { path: "proposalTitle", displayName: "Proposal Title", type: "string", description: "The proposal name voters see on-chain" },
2421
- { path: "proposalDescription", displayName: "Proposal Description", type: "string", description: "The long-form description voters see on-chain" },
2422
- { path: "status", displayName: "Proposal Status", type: "string", description: "Current proposal status (open, passed, rejected, executed, etc.)" },
2423
- { path: "proposalContractAddress", displayName: "Proposal Contract Address", type: "string", description: "The proposal module contract address" },
2424
- { path: "coreAddress", displayName: "Core Address", type: "string", description: "The DAO core contract address" },
2425
- { path: "createdAt", displayName: "Created At", type: "string", description: "ISO timestamp of proposal creation" }
2426
- ];
2427
- function registerGovernanceProposalAction(spec) {
2428
- registerAction({
2429
- type: spec.type,
2430
- can: spec.can,
2431
- sideEffect: true,
2432
- proof: { fields: ["proposalId"] },
2433
- done: doneWhenCompleted,
2434
- defaultRequiresConfirmation: true,
2435
- requiredCapability: "flow/block/execute",
2436
- outputSchema: [...STANDARD_OUTPUT_SCHEMA, ...spec.extraOutputSchema || []],
2437
- run: async (inputs, ctx) => {
2438
- const handlers = ctx.handlers;
2439
- if (!handlers) {
2440
- throw new Error("Handlers not available");
2441
- }
2442
- if (!handlers.getPreProposalContractAddress || !handlers.getGroupContractAddress || !handlers.getProposalContractAddress || !handlers.createProposal) {
2443
- throw new Error("Governance proposal handlers not available");
2444
- }
2445
- const coreAddress = String(inputs.coreAddress || "").trim();
2446
- if (!coreAddress) throw new Error("coreAddress is required");
2447
- const actions2 = spec.buildActions(inputs);
2448
- if (!actions2.length) throw new Error("The proposal must contain at least one action");
2449
- const title = String(inputs.title || "").trim() || spec.defaultTitle(inputs);
2450
- const description = String(inputs.description || "").trim() || (spec.defaultDescription ? spec.defaultDescription(inputs) : title);
2451
- const { preProposalContractAddress } = await handlers.getPreProposalContractAddress({ coreAddress });
2452
- const { groupContractAddress } = await handlers.getGroupContractAddress({ coreAddress });
2453
- const { proposalContractAddress } = await handlers.getProposalContractAddress({ coreAddress });
2454
- const proposalId = await handlers.createProposal({
2455
- preProposalContractAddress,
2456
- title,
2457
- description,
2458
- actions: actions2,
2459
- coreAddress,
2460
- groupContractAddress
2461
- });
2462
- if (proposalId === void 0 || proposalId === null || String(proposalId).trim() === "") {
2463
- throw new Error("Proposal creation returned no proposal id. Check the handler logs.");
2464
- }
2465
- return {
2466
- output: {
2467
- proposalId: String(proposalId),
2468
- proposalTitle: title,
2469
- proposalDescription: description,
2470
- status: "open",
2471
- proposalContractAddress: proposalContractAddress || "",
2472
- coreAddress,
2473
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2474
- ...spec.buildExtraOutput ? spec.buildExtraOutput(inputs) : {}
2475
- }
2476
- };
2477
- }
2478
- });
2479
- }
2480
-
2481
3377
  // src/core/lib/actionRegistry/actions/governance/submissionConfigProposal.ts
2482
3378
  var REFUND_POLICIES = ["always", "only_passed", "never"];
2483
3379
  registerGovernanceProposalAction({
@@ -2539,6 +3435,7 @@ registerAction({
2539
3435
  done: doneWhenCompleted,
2540
3436
  defaultRequiresConfirmation: true,
2541
3437
  requiredCapability: "flow/block/execute",
3438
+ inputSchema: governanceInputSchema("qi/governance.transaction.send-funds"),
2542
3439
  outputSchema: [
2543
3440
  { path: "proposalId", displayName: "Proposal ID", type: "string", description: "The on-chain proposal identifier" },
2544
3441
  { path: "proposalTitle", displayName: "Proposal Title", type: "string", description: "The proposal name voters see on-chain" },
@@ -3458,66 +4355,157 @@ registerGovernanceProposalAction({
3458
4355
  });
3459
4356
 
3460
4357
  // src/core/lib/actionRegistry/actions/httpRequest.ts
4358
+ var SENSITIVE_HEADERS = /* @__PURE__ */ new Set(["authorization", "cookie", "proxy-authorization", "x-api-key"]);
4359
+ var MUTATING_METHODS = ["POST", "PUT", "PATCH", "DELETE"];
4360
+ var REQUEST_METHODS = [...MUTATING_METHODS, "GET", "HEAD"];
4361
+ function publicHttpUrl(raw) {
4362
+ const value = String(raw || "").trim();
4363
+ let url;
4364
+ try {
4365
+ url = new URL(value);
4366
+ } catch {
4367
+ throw new Error("HTTP endpoint must be an absolute URL");
4368
+ }
4369
+ if (url.protocol !== "https:") throw new Error("HTTP Actions require an HTTPS endpoint");
4370
+ if (url.username || url.password) throw new Error("Credentials must not be embedded in an HTTP endpoint");
4371
+ const host = url.hostname.toLowerCase().replace(/^\[|\]$/g, "");
4372
+ if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host === "::1" || host.startsWith("fc") || host.startsWith("fd") || host.startsWith("fe80:") || /^127\./.test(host) || /^10\./.test(host) || /^169\.254\./.test(host) || /^192\.168\./.test(host) || /^172\.(1[6-9]|2\d|3[01])\./.test(host) || host === "0.0.0.0") {
4373
+ throw new Error("HTTP endpoint must not resolve to a local, private, link-local, or metadata address");
4374
+ }
4375
+ return url.toString();
4376
+ }
4377
+ function safeHeaders(raw) {
4378
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
4379
+ const headers = {};
4380
+ for (const [key, value] of Object.entries(raw)) {
4381
+ if (SENSITIVE_HEADERS.has(key.toLowerCase())) {
4382
+ throw new Error(`Sensitive header '${key}' must be supplied through a host-managed credential binding, not Action input`);
4383
+ }
4384
+ headers[key] = String(value);
4385
+ }
4386
+ return headers;
4387
+ }
4388
+ var INPUT_PROPERTIES = {
4389
+ endpoint: { type: "string", format: "uri", description: "Public HTTPS request URL. Either endpoint or url is required." },
4390
+ url: { type: "string", format: "uri", description: "Legacy alias for endpoint." },
4391
+ method: { type: "string", description: "HTTP method." },
4392
+ headers: { type: "object", additionalProperties: { type: "string" }, description: "Non-secret request headers. Authorization and cookies are rejected." },
4393
+ body: { description: "Request body for a mutating HTTP request." }
4394
+ };
4395
+ var OUTPUT_SCHEMA2 = [
4396
+ { path: "requestId", displayName: "Request ID", type: "string", description: "Host invocation identifier." },
4397
+ { path: "status", displayName: "HTTP Status", type: "number" },
4398
+ { path: "responseDigest", displayName: "Response Digest", type: "string", description: "Digest of the response body." },
4399
+ { path: "data", displayName: "Response Data", type: "object", description: "Full result retained in the Flow timeline, not copied into Topic state." },
4400
+ { path: "response", displayName: "Response JSON", type: "string" },
4401
+ { path: "traceReference", displayName: "Trace Reference", type: "string" }
4402
+ ];
4403
+ function missingEndpoint(inputs) {
4404
+ return String(inputs.endpoint || inputs.url || "").trim() ? [] : ["endpoint"];
4405
+ }
3461
4406
  registerAction({
3462
- type: "qi/http.request",
3463
- can: "http/request",
4407
+ type: "qi/http.fetch",
4408
+ can: "http/fetch",
3464
4409
  sideEffect: false,
3465
- proof: { fields: ["status"] },
4410
+ proof: { fields: ["responseDigest"] },
3466
4411
  done: doneWhenCompleted,
3467
4412
  defaultRequiresConfirmation: false,
3468
- // HTTP request can be triggered as a listener — a human assignee is DM'd
3469
- // to invoke it when the upstream event fires. See §3.6 of the
3470
- // events-and-triggers plan.
3471
4413
  eligibleForEventTrigger: true,
3472
4414
  inputSchema: {
3473
4415
  type: "object",
3474
- required: ["endpoint"],
3475
- properties: {
3476
- endpoint: { type: "string", description: "The request URL. Either endpoint or its alias url must be provided." },
3477
- url: { type: "string", description: "Alias for endpoint; the request URL. Either url or endpoint satisfies the URL requirement." },
3478
- method: { type: "string", description: "HTTP method (GET, POST, etc.). Defaults to GET." },
3479
- headers: { type: "object", description: "Request headers as a key/value map." },
3480
- body: { type: "string", description: "Request body; sent for non-GET methods. Accepts a string or an object (serialized to JSON)." }
3481
- }
4416
+ required: [],
4417
+ additionalProperties: false,
4418
+ properties: { ...INPUT_PROPERTIES, method: { type: "string", enum: ["GET", "HEAD"], default: "GET" } }
3482
4419
  },
3483
- // run() accepts `endpoint` OR `url` — mirror the alias the schema's
3484
- // required array can't express.
3485
- getMissingInputs: (inputs) => String(inputs.endpoint || inputs.url || "").trim() ? [] : ["endpoint"],
4420
+ getMissingInputs: missingEndpoint,
4421
+ outputSchema: OUTPUT_SCHEMA2,
3486
4422
  run: async (inputs, ctx) => {
3487
- const endpoint = inputs.endpoint ?? inputs.url;
3488
- const url = typeof endpoint === "string" ? endpoint : "";
3489
- if (!url) {
3490
- throw new Error("HTTP request action requires an endpoint or url input");
3491
- }
3492
- const method = typeof inputs.method === "string" ? inputs.method : "GET";
3493
- const headers = inputs.headers && typeof inputs.headers === "object" && !Array.isArray(inputs.headers) ? inputs.headers : {};
3494
- const body = inputs.body;
3495
- const requestFn = ctx.services.http?.request;
3496
- if (requestFn) {
3497
- const result = await requestFn({ url, method, headers, body });
4423
+ const url = publicHttpUrl(inputs.endpoint ?? inputs.url);
4424
+ const method = String(inputs.method || "GET").toUpperCase();
4425
+ if (method !== "GET" && method !== "HEAD") throw new Error("qi/http.fetch permits GET or HEAD only");
4426
+ const headers = safeHeaders(inputs.headers);
4427
+ const service = ctx.services.http;
4428
+ if (service) {
4429
+ const result = await service.request({
4430
+ url,
4431
+ method,
4432
+ headers,
4433
+ security: { denyPrivateNetworks: true, maxRedirects: 0, stripSensitiveHeadersOnRedirect: true }
4434
+ });
4435
+ const responseDigest2 = result.responseDigest || sha256Digest(result.data);
3498
4436
  return {
3499
4437
  output: {
4438
+ requestId: result.requestId || responseDigest2,
3500
4439
  status: result.status,
4440
+ responseDigest: responseDigest2,
3501
4441
  data: result.data,
3502
- response: JSON.stringify(result.data, null, 2)
4442
+ response: JSON.stringify(result.data, null, 2),
4443
+ traceReference: result.traceReference || ""
3503
4444
  }
3504
4445
  };
3505
4446
  }
3506
- const fetchOptions = {
3507
- method,
3508
- headers: { "Content-Type": "application/json", ...headers }
3509
- };
3510
- if (method !== "GET" && body) {
3511
- fetchOptions.body = typeof body === "string" ? body : JSON.stringify(body);
4447
+ const response = await fetch(url, { method, headers, redirect: "error" });
4448
+ const text = method === "HEAD" ? "" : await response.text();
4449
+ let data = text;
4450
+ try {
4451
+ data = text ? JSON.parse(text) : {};
4452
+ } catch {
3512
4453
  }
3513
- const response = await fetch(url, fetchOptions);
3514
- const data = await response.json().catch(() => ({}));
4454
+ const responseDigest = sha256Digest(data);
3515
4455
  return {
3516
4456
  output: {
4457
+ requestId: responseDigest,
3517
4458
  status: response.status,
4459
+ responseDigest,
3518
4460
  data,
3519
- response: JSON.stringify(data, null, 2)
4461
+ response: typeof data === "string" ? data : JSON.stringify(data, null, 2),
4462
+ traceReference: ""
4463
+ }
4464
+ };
4465
+ }
4466
+ });
4467
+ registerAction({
4468
+ type: "qi/http.request",
4469
+ can: "http/request",
4470
+ sideEffect: true,
4471
+ proof: { fields: ["requestId"] },
4472
+ done: doneWhenCompleted,
4473
+ defaultRequiresConfirmation: true,
4474
+ requiredCapability: "flow/block/execute",
4475
+ eligibleForEventTrigger: true,
4476
+ inputSchema: {
4477
+ type: "object",
4478
+ required: [],
4479
+ additionalProperties: false,
4480
+ properties: {
4481
+ ...INPUT_PROPERTIES,
4482
+ method: {
4483
+ type: "string",
4484
+ enum: REQUEST_METHODS,
4485
+ default: "GET",
4486
+ description: "POST/PUT/PATCH/DELETE for mutations. GET/HEAD remain accepted for legacy Flows but use the same confirmation and receipt policy; new read Actions should use qi/http.fetch."
3520
4487
  }
4488
+ }
4489
+ },
4490
+ getMissingInputs: missingEndpoint,
4491
+ outputSchema: OUTPUT_SCHEMA2,
4492
+ run: async (inputs, ctx) => {
4493
+ const service = ctx.services.http;
4494
+ if (!service) throw new Error("Mutating HTTP requests require the host HTTP service; native fetch is not permitted");
4495
+ const url = publicHttpUrl(inputs.endpoint ?? inputs.url);
4496
+ const method = String(inputs.method || "GET").toUpperCase();
4497
+ if (!REQUEST_METHODS.includes(method)) throw new Error(`qi/http.request method must be one of ${REQUEST_METHODS.join(", ")}`);
4498
+ const result = await service.request({
4499
+ url,
4500
+ method,
4501
+ headers: safeHeaders(inputs.headers),
4502
+ body: inputs.body,
4503
+ security: { denyPrivateNetworks: true, maxRedirects: 0, stripSensitiveHeadersOnRedirect: true }
4504
+ });
4505
+ const responseDigest = result.responseDigest || sha256Digest(result.data);
4506
+ const requestId = result.requestId || sha256Digest({ url, method, status: result.status, responseDigest });
4507
+ return {
4508
+ output: { requestId, status: result.status, responseDigest, data: result.data, response: JSON.stringify(result.data, null, 2), traceReference: result.traceReference || "" }
3521
4509
  };
3522
4510
  }
3523
4511
  });
@@ -3602,7 +4590,7 @@ registerAction({
3602
4590
  type: "qi/human.checkbox.set",
3603
4591
  can: "human/checkbox",
3604
4592
  sideEffect: true,
3605
- proof: "none",
4593
+ proof: { fields: ["attestationId"] },
3606
4594
  done: doneWhenCompleted,
3607
4595
  defaultRequiresConfirmation: false,
3608
4596
  requiredCapability: "flow/execute",
@@ -3613,9 +4601,17 @@ registerAction({
3613
4601
  checked: { type: "boolean", description: "Whether the checkbox should be checked (defaults to true)." }
3614
4602
  }
3615
4603
  },
3616
- run: async (inputs) => {
4604
+ outputSchema: [
4605
+ { path: "checked", displayName: "Checked", type: "boolean" },
4606
+ { path: "attestationId", displayName: "Attestation ID", type: "string", description: "Proof identifier for the human checkbox attestation." },
4607
+ { path: "attestedAt", displayName: "Attested At", type: "string" },
4608
+ { path: "attestedBy", displayName: "Attested By", type: "string" }
4609
+ ],
4610
+ run: async (inputs, ctx) => {
3617
4611
  const checked = inputs.checked !== void 0 ? !!inputs.checked : true;
3618
- return { output: { checked } };
4612
+ const attestedAt = (/* @__PURE__ */ new Date()).toISOString();
4613
+ const attestationId = sha256Digest({ action: "qi/human.checkbox.set", checked, actorDid: ctx.actorDid, flowId: ctx.flowId, nodeId: ctx.nodeId, attestedAt });
4614
+ return { output: { checked, attestationId, attestedAt, attestedBy: ctx.actorDid } };
3619
4615
  }
3620
4616
  });
3621
4617
 
@@ -3645,7 +4641,7 @@ function registerFormSubmitAction(type, can) {
3645
4641
  type,
3646
4642
  can,
3647
4643
  sideEffect: true,
3648
- proof: "none",
4644
+ proof: { fields: ["submissionId"] },
3649
4645
  done: doneWhenCompleted,
3650
4646
  defaultRequiresConfirmation: false,
3651
4647
  requiredCapability: "flow/execute",
@@ -3662,7 +4658,11 @@ function registerFormSubmitAction(type, can) {
3662
4658
  },
3663
4659
  outputSchema: [
3664
4660
  { path: "form.answers", displayName: "Form Answers JSON", type: "string", description: "JSON stringified form answers, matching form block runtime output." },
3665
- { path: "answers", displayName: "Form Answers", type: "object", description: "Parsed form answers object for convenience." }
4661
+ { path: "answers", displayName: "Form Answers", type: "object", description: "Parsed form answers object for convenience." },
4662
+ { path: "submissionId", displayName: "Submission ID", type: "string", description: "Stable proof identifier for this submission." },
4663
+ { path: "answersDigest", displayName: "Answers Digest", type: "string", description: "Content digest; safe to place in a Topic receipt." },
4664
+ { path: "submittedAt", displayName: "Submitted At", type: "string" },
4665
+ { path: "submittedBy", displayName: "Submitted By", type: "string" }
3666
4666
  ],
3667
4667
  events: [
3668
4668
  {
@@ -3673,15 +4673,22 @@ function registerFormSubmitAction(type, can) {
3673
4673
  pendingDisplayFields: ["answers"]
3674
4674
  }
3675
4675
  ],
3676
- run: async (inputs) => {
4676
+ run: async (inputs, ctx) => {
3677
4677
  const answers = normalizeAnswers(inputs.answers ?? inputs.form?.answers);
3678
4678
  const answersJson = JSON.stringify(answers);
4679
+ const submittedAt = (/* @__PURE__ */ new Date()).toISOString();
4680
+ const answersDigest = sha256Digest(answers);
4681
+ const submissionId = sha256Digest({ type, flowId: ctx.flowId, sessionRunId: ctx.sessionRunId || "", nodeId: ctx.nodeId, submittedAt, answersDigest });
3679
4682
  return {
3680
4683
  output: {
3681
4684
  form: {
3682
4685
  answers: answersJson
3683
4686
  },
3684
- answers
4687
+ answers,
4688
+ submissionId,
4689
+ answersDigest,
4690
+ submittedAt,
4691
+ submittedBy: ctx.actorDid
3685
4692
  },
3686
4693
  events: [{ name: "form.submitted", payload: { answers } }]
3687
4694
  };
@@ -3710,20 +4717,39 @@ registerAction({
3710
4717
  outputSchema: [
3711
4718
  { path: "runId", displayName: "Session run id", type: "string" },
3712
4719
  { path: "eventId", displayName: "Started event id", type: "string" },
3713
- { path: "startedAt", displayName: "Started at", type: "number" }
4720
+ { path: "startedAt", displayName: "Started at", type: "number" },
4721
+ { path: "sessionId", displayName: "Session ID", type: "string" },
4722
+ { path: "flowRevision", displayName: "Flow revision", type: "string" },
4723
+ { path: "topicBindingId", displayName: "Topic binding ID", type: "string" }
4724
+ ],
4725
+ events: [
4726
+ {
4727
+ name: "flow.run.started",
4728
+ displayName: "Flow run started",
4729
+ description: "Emitted when a pinned Flow run starts; it does not change Topic status.",
4730
+ payloadSchema: [
4731
+ { path: "runId", displayName: "Run ID", type: "string" },
4732
+ { path: "topicBindingId", displayName: "Topic binding ID", type: "string" }
4733
+ ]
4734
+ }
3714
4735
  ],
3715
4736
  run: async (inputs, ctx) => {
3716
4737
  if (!ctx.services.flowRuns?.start) {
3717
4738
  throw new Error("flowRuns.start handler not available");
3718
4739
  }
3719
- return {
3720
- output: await ctx.services.flowRuns.start({
3721
- actorDid: ctx.actorDid,
3722
- flowId: ctx.flowId,
3723
- flowUri: ctx.flowUri,
3724
- ...typeof inputs.label === "string" && inputs.label ? { label: inputs.label } : {}
3725
- })
4740
+ const lifecycle = await ctx.services.flowRuns.start({
4741
+ actorDid: ctx.actorDid,
4742
+ flowId: ctx.flowId,
4743
+ flowUri: ctx.flowUri,
4744
+ ...typeof inputs.label === "string" && inputs.label ? { label: inputs.label } : {}
4745
+ });
4746
+ const output = {
4747
+ ...lifecycle,
4748
+ sessionId: lifecycle.runId,
4749
+ flowRevision: ctx.flowRevision || "",
4750
+ topicBindingId: ctx.topic?.bindingId || ""
3726
4751
  };
4752
+ return { output, events: [{ name: "flow.run.started", payload: { runId: lifecycle.runId, topicBindingId: output.topicBindingId } }] };
3727
4753
  }
3728
4754
  });
3729
4755
  registerAction({
@@ -3748,7 +4774,22 @@ registerAction({
3748
4774
  { path: "status", displayName: "Terminal status", type: "string" },
3749
4775
  { path: "eventId", displayName: "Terminal event id", type: "string" },
3750
4776
  { path: "closedAt", displayName: "Closed at", type: "number" },
3751
- { path: "cancelledAt", displayName: "Cancelled at", type: "number" }
4777
+ { path: "cancelledAt", displayName: "Cancelled at", type: "number" },
4778
+ { path: "runId", displayName: "Session run ID", type: "string" },
4779
+ { path: "flowRevision", displayName: "Flow revision", type: "string" },
4780
+ { path: "topicBindingId", displayName: "Topic binding ID", type: "string" }
4781
+ ],
4782
+ events: [
4783
+ {
4784
+ name: "flow.run.closed",
4785
+ displayName: "Flow run closed",
4786
+ description: "Emitted when the Flow run closes; Topic resolution remains an explicit, separate Action.",
4787
+ payloadSchema: [
4788
+ { path: "runId", displayName: "Run ID", type: "string" },
4789
+ { path: "status", displayName: "Status", type: "string" },
4790
+ { path: "topicBindingId", displayName: "Topic binding ID", type: "string" }
4791
+ ]
4792
+ }
3752
4793
  ],
3753
4794
  run: async (inputs, ctx) => {
3754
4795
  if (!ctx.sessionRunId) {
@@ -3758,24 +4799,30 @@ registerAction({
3758
4799
  throw new Error("flowRuns lifecycle handler not available");
3759
4800
  }
3760
4801
  if (inputs.cancel === true) {
3761
- return {
3762
- output: await ctx.services.flowRuns.cancel({
3763
- actorDid: ctx.actorDid,
3764
- flowId: ctx.flowId,
3765
- flowUri: ctx.flowUri,
3766
- runId: ctx.sessionRunId,
3767
- ...typeof inputs.reason === "string" && inputs.reason ? { reason: inputs.reason } : {}
3768
- })
3769
- };
3770
- }
3771
- return {
3772
- output: await ctx.services.flowRuns.close({
4802
+ const lifecycle2 = await ctx.services.flowRuns.cancel({
3773
4803
  actorDid: ctx.actorDid,
3774
4804
  flowId: ctx.flowId,
3775
4805
  flowUri: ctx.flowUri,
3776
4806
  runId: ctx.sessionRunId,
3777
- allowIncomplete: inputs.allowIncomplete === true
3778
- })
4807
+ ...typeof inputs.reason === "string" && inputs.reason ? { reason: inputs.reason } : {}
4808
+ });
4809
+ const output2 = { ...lifecycle2, runId: ctx.sessionRunId, flowRevision: ctx.flowRevision || "", topicBindingId: ctx.topic?.bindingId || "" };
4810
+ return {
4811
+ output: output2,
4812
+ events: [{ name: "flow.run.closed", payload: { runId: ctx.sessionRunId, status: lifecycle2.status, topicBindingId: output2.topicBindingId } }]
4813
+ };
4814
+ }
4815
+ const lifecycle = await ctx.services.flowRuns.close({
4816
+ actorDid: ctx.actorDid,
4817
+ flowId: ctx.flowId,
4818
+ flowUri: ctx.flowUri,
4819
+ runId: ctx.sessionRunId,
4820
+ allowIncomplete: inputs.allowIncomplete === true
4821
+ });
4822
+ const output = { ...lifecycle, runId: ctx.sessionRunId, flowRevision: ctx.flowRevision || "", topicBindingId: ctx.topic?.bindingId || "" };
4823
+ return {
4824
+ output,
4825
+ events: [{ name: "flow.run.closed", payload: { runId: ctx.sessionRunId, status: lifecycle.status, topicBindingId: output.topicBindingId } }]
3779
4826
  };
3780
4827
  }
3781
4828
  });
@@ -3804,6 +4851,10 @@ registerAction({
3804
4851
  replyTo: { type: "string", description: "Reply-to address." }
3805
4852
  }
3806
4853
  },
4854
+ outputSchema: [
4855
+ { path: "messageId", displayName: "Message ID", type: "string", description: "Provider or host notification identifier." },
4856
+ { path: "sentAt", displayName: "Sent At", type: "string", description: "ISO timestamp supplied by the provider or host." }
4857
+ ],
3807
4858
  run: async (inputs, ctx) => {
3808
4859
  if (!ctx.services.notify) {
3809
4860
  throw new Error("Notification service not configured");
@@ -4457,6 +5508,22 @@ registerAction({
4457
5508
  };
4458
5509
  return {
4459
5510
  output,
5511
+ topicRecords: ctx.topic ? [
5512
+ {
5513
+ type: "org.ixo.topic.claim-submission",
5514
+ id: sha256Digest({ topicId: ctx.topic.topicId, collectionId, claimId }),
5515
+ version: 1,
5516
+ value: {
5517
+ claimId,
5518
+ collectionId,
5519
+ deedDid,
5520
+ submittedByDid,
5521
+ submittedAt,
5522
+ transactionHash,
5523
+ submissionDigest: sha256Digest(surveyAnswers)
5524
+ }
5525
+ }
5526
+ ] : void 0,
4460
5527
  events: [
4461
5528
  {
4462
5529
  name: "submitted",
@@ -4811,7 +5878,7 @@ registerAction({
4811
5878
  const flowId = String(ctx.flowId || ctx.flowUri || "flow");
4812
5879
  const claimSnapshot = inputs.claimSnapshot && typeof inputs.claimSnapshot === "object" && !Array.isArray(inputs.claimSnapshot) ? inputs.claimSnapshot : void 0;
4813
5880
  const surveyQuestions = Array.isArray(claimSnapshot?.surveyQuestions) ? claimSnapshot.surveyQuestions : Array.isArray(inputs?.surveyAnswersSchema) ? inputs.surveyAnswersSchema : [];
4814
- const idempotencyKey = buildXeroInvoiceWorkKey({ flowId, evaluationBlockId: ctx.nodeId, claimId });
5881
+ const idempotencyKey2 = buildXeroInvoiceWorkKey({ flowId, evaluationBlockId: ctx.nodeId, claimId });
4815
5882
  const originalPayload = {
4816
5883
  claim: { claimId, collectionId, deedDid },
4817
5884
  surveyQuestions,
@@ -4827,11 +5894,11 @@ registerAction({
4827
5894
  invoiceDefaults: buildXeroInvoiceDefaults(inputs.xeroInvoiceDefaults)
4828
5895
  };
4829
5896
  upsertXeroWorkItemForEditor(ctx.editor, {
4830
- id: idempotencyKey,
5897
+ id: idempotencyKey2,
4831
5898
  kind: "invoice.create",
4832
5899
  status: "pending",
4833
5900
  assignedBlockId: ctx.nodeId,
4834
- idempotencyKey,
5901
+ idempotencyKey: idempotencyKey2,
4835
5902
  source: {
4836
5903
  claimId,
4837
5904
  evaluationBlockId: ctx.nodeId,
@@ -4871,6 +5938,25 @@ registerAction({
4871
5938
  };
4872
5939
  return {
4873
5940
  output,
5941
+ topicRecords: ctx.topic ? [
5942
+ {
5943
+ type: "org.ixo.topic.claim-evaluation",
5944
+ id: sha256Digest({ topicId: ctx.topic.topicId, collectionId, claimId, evaluatedAt, decision }),
5945
+ version: 1,
5946
+ value: {
5947
+ claimId,
5948
+ collectionId,
5949
+ deedDid,
5950
+ decision,
5951
+ evaluatedByDid,
5952
+ evaluatedAt,
5953
+ verificationProof,
5954
+ transactionHash,
5955
+ evidenceDigest: sha256Digest(surveyAnswers)
5956
+ },
5957
+ evidenceReferences: verificationProof ? [verificationProof] : []
5958
+ }
5959
+ ] : void 0,
4874
5960
  events: [{ name: eventName, payload: eventPayload }]
4875
5961
  };
4876
5962
  }
@@ -4953,7 +6039,23 @@ registerAction({
4953
6039
  proposalContractAddress: proposalContractAddress || "",
4954
6040
  coreAddress,
4955
6041
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
4956
- }
6042
+ },
6043
+ topicRecords: ctx.topic ? [
6044
+ {
6045
+ type: "org.ixo.topic.proposal-receipt",
6046
+ id: sha256Digest({ topicId: ctx.topic.topicId, proposalContractAddress, proposalId }),
6047
+ version: 1,
6048
+ value: {
6049
+ event: "created",
6050
+ proposalId: String(proposalId),
6051
+ proposalContractAddress: proposalContractAddress || "",
6052
+ coreAddress,
6053
+ title,
6054
+ descriptionDigest: sha256Digest(description),
6055
+ status: "open"
6056
+ }
6057
+ }
6058
+ ] : void 0
4957
6059
  };
4958
6060
  }
4959
6061
  });
@@ -5008,13 +6110,30 @@ registerAction({
5008
6110
  rationale: rationale || void 0,
5009
6111
  proposalContractAddress
5010
6112
  });
6113
+ const votedAt = (/* @__PURE__ */ new Date()).toISOString();
5011
6114
  return {
5012
6115
  output: {
5013
6116
  vote,
5014
6117
  rationale: rationale || "",
5015
6118
  proposalId: String(proposalId),
5016
- votedAt: (/* @__PURE__ */ new Date()).toISOString()
5017
- }
6119
+ votedAt
6120
+ },
6121
+ topicRecords: ctx.topic ? [
6122
+ {
6123
+ type: "org.ixo.topic.proposal-receipt",
6124
+ id: sha256Digest({ topicId: ctx.topic.topicId, proposalContractAddress, proposalId, actorDid: ctx.actorDid, votedAt }),
6125
+ version: 1,
6126
+ value: {
6127
+ event: "vote-cast",
6128
+ proposalId: String(proposalId),
6129
+ proposalContractAddress,
6130
+ vote,
6131
+ rationaleDigest: sha256Digest(rationale),
6132
+ actorDid: ctx.actorDid,
6133
+ votedAt
6134
+ }
6135
+ }
6136
+ ] : void 0
5018
6137
  };
5019
6138
  }
5020
6139
  });
@@ -6826,29 +7945,46 @@ registerAction({
6826
7945
 
6827
7946
  // src/core/lib/actionRegistry/actions/oracle.ts
6828
7947
  registerAction({
6829
- type: "oracle",
6830
- can: "oracle/query",
6831
- sideEffect: false,
6832
- proof: "none",
7948
+ type: "qi/oracle.invoke",
7949
+ can: "oracle/invoke",
7950
+ sideEffect: true,
7951
+ proof: { fields: ["resultDigest"] },
6833
7952
  done: doneWhenCompleted,
6834
7953
  defaultRequiresConfirmation: false,
6835
7954
  inputSchema: {
6836
7955
  type: "object",
6837
7956
  required: ["prompt"],
6838
7957
  properties: {
6839
- prompt: { type: "string", description: "The prompt text sent to the companion." }
7958
+ prompt: { type: "string", description: "The prompt text sent to the Agent." }
6840
7959
  }
6841
7960
  },
6842
- outputSchema: [{ path: "prompt", displayName: "Prompt", type: "string", description: "The prompt sent to the companion" }],
7961
+ sensitiveInputPaths: ["prompt"],
7962
+ sensitiveOutputPaths: ["result"],
7963
+ outputSchema: [
7964
+ { path: "prompt", displayName: "Prompt", type: "string", description: "Legacy Flow-timeline echo; redacted from Topic receipts." },
7965
+ { path: "sessionId", displayName: "Session ID", type: "string", description: "Private Oracle session identifier." },
7966
+ { path: "result", displayName: "Result", type: "object", description: "Full result retained in the Flow timeline." },
7967
+ { path: "resultDigest", displayName: "Result Digest", type: "string", description: "Content digest safe for a Topic receipt." },
7968
+ { path: "evidenceDigest", displayName: "Evidence Digest", type: "string", description: "Digest of evidence references returned by the Oracle." }
7969
+ ],
6843
7970
  run: async (inputs, ctx) => {
6844
7971
  const prompt = String(inputs.prompt || "").trim();
6845
7972
  if (!prompt) throw new Error("prompt is required");
6846
7973
  if (!ctx.handlers?.askCompanion) {
6847
7974
  throw new Error("askCompanion handler is not available");
6848
7975
  }
6849
- await ctx.handlers.askCompanion(prompt);
7976
+ const raw = await ctx.handlers.askCompanion(prompt);
7977
+ const envelope = raw && typeof raw === "object" ? raw : { result: raw };
7978
+ const result = envelope.result ?? envelope.response ?? envelope.message ?? raw ?? null;
7979
+ const evidence = Array.isArray(envelope.evidenceReferences) ? envelope.evidenceReferences : Array.isArray(envelope.evidence) ? envelope.evidence : [];
6850
7980
  return {
6851
- output: { prompt }
7981
+ output: {
7982
+ prompt,
7983
+ sessionId: String(envelope.sessionId ?? envelope.runId ?? ""),
7984
+ result,
7985
+ resultDigest: sha256Digest(result),
7986
+ evidenceDigest: sha256Digest(evidence)
7987
+ }
6852
7988
  };
6853
7989
  }
6854
7990
  });
@@ -6984,6 +8120,7 @@ registerAction({
6984
8120
  });
6985
8121
 
6986
8122
  // src/core/lib/actionRegistry/actions/walletFund.ts
8123
+ var DEFAULT_DENOM = "uixo";
6987
8124
  registerAction({
6988
8125
  type: "qi/wallet.fund",
6989
8126
  can: "wallet/fund",
@@ -6996,20 +8133,37 @@ registerAction({
6996
8133
  required: ["address"],
6997
8134
  properties: {
6998
8135
  address: { type: "string", description: "The IXO wallet address to fund." },
6999
- amount: { type: "number", description: "Funding amount in base units (defaults to 250000)." }
8136
+ amount: { type: "number", description: "Funding amount in the denom\u2019s base units (defaults to 250000)." },
8137
+ denom: { type: "string", description: "Base denom to send, e.g. uixo. Defaults to uixo." },
8138
+ fromAddress: {
8139
+ type: "string",
8140
+ description: "Wallet the tokens leave. Defaults to the signed-in user\u2019s wallet; any other address must be one they can act on."
8141
+ }
7000
8142
  }
7001
8143
  },
7002
- outputSchema: [{ path: "transactionHash", displayName: "Transaction Hash", type: "string", description: "The funding transaction hash" }],
8144
+ outputSchema: [
8145
+ { path: "transactionHash", displayName: "Transaction Hash", type: "string", description: "The funding transaction hash" },
8146
+ { path: "denom", displayName: "Denom", type: "string", description: "The base denom that was sent" },
8147
+ { path: "amount", displayName: "Amount", type: "string", description: "The amount sent, in the denom\u2019s base units" },
8148
+ { path: "fromAddress", displayName: "From Address", type: "string", description: "The wallet the tokens left (blank when the signer\u2019s own wallet)" }
8149
+ ],
7003
8150
  run: async (inputs, ctx) => {
7004
8151
  if (!ctx.services.oracle?.fundWallet) {
7005
8152
  throw new Error("oracle.fundWallet handler not available");
7006
8153
  }
7007
8154
  if (!inputs.address) throw new Error("address is required");
8155
+ const denom = String(inputs.denom || "").trim() || DEFAULT_DENOM;
8156
+ const amount = Number(inputs.amount) || 25e4;
8157
+ if (!Number.isFinite(amount) || amount <= 0) throw new Error("amount must be greater than 0");
8158
+ if (!Number.isInteger(amount)) throw new Error(`amount must be a whole number of ${denom} base units`);
8159
+ const fromAddress = String(inputs.fromAddress || "").trim();
7008
8160
  const result = await ctx.services.oracle.fundWallet({
7009
8161
  address: inputs.address,
7010
- amount: inputs.amount || 25e4
8162
+ amount,
8163
+ denom,
8164
+ ...fromAddress ? { fromAddress } : {}
7011
8165
  });
7012
- return { output: result };
8166
+ return { output: { ...result, denom, amount: String(amount), fromAddress } };
7013
8167
  }
7014
8168
  });
7015
8169
 
@@ -7025,7 +8179,12 @@ registerAction({
7025
8179
  type: "object",
7026
8180
  required: [],
7027
8181
  properties: {
7028
- amount: { type: "number", description: "Funding amount in base units for the generated wallet (defaults to 250000)." }
8182
+ amount: { type: "number", description: "Funding amount in base units for the generated wallet (defaults to 250000)." },
8183
+ denom: { type: "string", description: "Base denom to send, e.g. uixo. Defaults to uixo." },
8184
+ fromAddress: {
8185
+ type: "string",
8186
+ description: "Wallet the funding leaves. Defaults to the signed-in user\u2019s wallet; any other address must be one they can act on."
8187
+ }
7029
8188
  }
7030
8189
  },
7031
8190
  outputSchema: [
@@ -7033,7 +8192,8 @@ registerAction({
7033
8192
  { path: "did", displayName: "DID", type: "string", description: "The DID derived from the wallet address" },
7034
8193
  { path: "pubKey", displayName: "Public Key", type: "string", description: "The secp256k1 public key (hex)" },
7035
8194
  { path: "mnemonic", displayName: "Mnemonic", type: "string", description: "The BIP39 mnemonic seed phrase" },
7036
- { path: "transactionHash", displayName: "Transaction Hash", type: "string", description: "The funding transaction hash" }
8195
+ { path: "transactionHash", displayName: "Transaction Hash", type: "string", description: "The funding transaction hash" },
8196
+ { path: "denom", displayName: "Denom", type: "string", description: "The base denom that was sent" }
7037
8197
  ],
7038
8198
  run: async (inputs, ctx) => {
7039
8199
  if (!ctx.services.oracle?.generateWallet) {
@@ -7046,9 +8206,13 @@ registerAction({
7046
8206
  if (!walletResult?.address) {
7047
8207
  throw new Error("generateWallet did not return an address");
7048
8208
  }
8209
+ const denom = String(inputs.denom || "").trim() || "uixo";
8210
+ const fromAddress = String(inputs.fromAddress || "").trim();
7049
8211
  const fundResult = await ctx.services.oracle.fundWallet({
7050
8212
  address: walletResult.address,
7051
- amount: inputs.amount || 25e4
8213
+ amount: inputs.amount || 25e4,
8214
+ denom,
8215
+ ...fromAddress ? { fromAddress } : {}
7052
8216
  });
7053
8217
  if (!fundResult?.transactionHash) {
7054
8218
  throw new Error("fundWallet did not return a transactionHash");
@@ -7059,7 +8223,8 @@ registerAction({
7059
8223
  did: walletResult.did,
7060
8224
  pubKey: walletResult.pubKey,
7061
8225
  mnemonic: walletResult.mnemonic,
7062
- transactionHash: fundResult.transactionHash
8226
+ transactionHash: fundResult.transactionHash,
8227
+ denom
7063
8228
  }
7064
8229
  };
7065
8230
  }
@@ -8105,14 +9270,6 @@ var COLLECTION_CREATED_EVENT = {
8105
9270
  pendingDisplayFields: ["collectionId", "entity"]
8106
9271
  };
8107
9272
 
8108
- // src/core/lib/actionRegistry/types.ts
8109
- var CollectionStateEnum = /* @__PURE__ */ ((CollectionStateEnum2) => {
8110
- CollectionStateEnum2[CollectionStateEnum2["OPEN"] = 0] = "OPEN";
8111
- CollectionStateEnum2[CollectionStateEnum2["PAUSED"] = 1] = "PAUSED";
8112
- CollectionStateEnum2[CollectionStateEnum2["CLOSED"] = 2] = "CLOSED";
8113
- return CollectionStateEnum2;
8114
- })(CollectionStateEnum || {});
8115
-
8116
9273
  // src/core/lib/actionRegistry/actions/collection/collection.ts
8117
9274
  function normalizeQuota(quota) {
8118
9275
  if (quota === void 0 || quota === null) return void 0;
@@ -9014,6 +10171,7 @@ async function runEvalRegister(inputs, ctx) {
9014
10171
  const allowAiChecks = inputs.allowAiChecks !== false;
9015
10172
  const allowImageChecks = allowAiChecks && inputs.allowImageChecks !== false;
9016
10173
  const allowChainEvaluation = inputs.allowChainEvaluation !== false;
10174
+ const allowZeroPayoutApprovals = inputs.allowZeroPayoutApprovals === true;
9017
10175
  if (!collectionId) throw new Error("collectionId is required");
9018
10176
  if (!deedDid) throw new Error("deedDid (entity/deed DID) is required");
9019
10177
  if (!ownerDid) throw new Error("ownerDid is required (pass it explicitly, or run as the collection owner)");
@@ -9052,7 +10210,7 @@ async function runEvalRegister(inputs, ctx) {
9052
10210
  // (see above), and a host that copies `params` field-by-field would otherwise forward an
9053
10211
  // explicit `undefined` as the erasing empty value.
9054
10212
  ...description !== void 0 ? { description } : {},
9055
- settings: { allowAiChecks, allowImageChecks, allowChainEvaluation }
10213
+ settings: { allowAiChecks, allowImageChecks, allowChainEvaluation, allowZeroPayoutApprovals }
9056
10214
  });
9057
10215
  const registrationId = String(registration?.id || "").trim();
9058
10216
  if (!registrationId) {
@@ -9125,8 +10283,12 @@ function canonicalJson(value) {
9125
10283
  throw new Error(`canonicalJson: unsupported value of type ${typeof value}`);
9126
10284
  }
9127
10285
 
10286
+ // src/core/lib/actionRegistry/actions/evalRubric/pathGrammar.ts
10287
+ var FORM_SEGMENT = "[A-Za-z0-9_-]+(?::[A-Za-z0-9_-]+)?";
10288
+
9128
10289
  // src/core/lib/actionRegistry/actions/evalRubric/fieldCatalog.ts
9129
- var SEGMENT = /^[A-Za-z0-9_-]+$/;
10290
+ var SEGMENT = new RegExp(`^${FORM_SEGMENT}$`);
10291
+ var NAME_PATH = new RegExp(`^${FORM_SEGMENT}(?:\\.${FORM_SEGMENT})*$`);
9130
10292
  function scalarKind(type, inputType) {
9131
10293
  switch (type) {
9132
10294
  case "text":
@@ -9209,7 +10371,7 @@ function extractRubricFieldCatalog(surveyTemplate, proof = "") {
9209
10371
  }
9210
10372
  const name = typeof el.name === "string" ? el.name.trim() : "";
9211
10373
  const type = typeof el.type === "string" ? el.type : "";
9212
- if (!name || !SEGMENT.test(name) || seen2.has(name) || type === "html" || type === "expression") continue;
10374
+ if (!name || !NAME_PATH.test(name) || seen2.has(name) || type === "html" || type === "expression") continue;
9213
10375
  const field = extractQuestion(el, name, type);
9214
10376
  if (!field) continue;
9215
10377
  seen2.add(name);
@@ -9305,14 +10467,15 @@ function titleOf(el) {
9305
10467
  return title || humanize2(typeof el.name === "string" ? el.name : "");
9306
10468
  }
9307
10469
  function humanize2(name) {
9308
- return name.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
10470
+ const bare = name.split(".").map((seg) => seg.includes(":") ? seg.split(":").pop() : seg).join(".");
10471
+ return bare.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
9309
10472
  }
9310
10473
  function stripHtml2(s) {
9311
10474
  return s.replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim();
9312
10475
  }
9313
10476
 
9314
10477
  // src/core/lib/actionRegistry/actions/evalRubric/schemaGate.ts
9315
- import Ajv2020 from "ajv/dist/2020.js";
10478
+ import Ajv20202 from "ajv/dist/2020.js";
9316
10479
 
9317
10480
  // src/core/lib/actionRegistry/actions/evalRubric/types.ts
9318
10481
  var RUBRIC_CTX_TOKENS = [
@@ -9396,7 +10559,7 @@ async function getValidator(fetchSchema, evalEngineUrl) {
9396
10559
  if (compiledValidator) return compiledValidator;
9397
10560
  const schema = await fetchSchema(evalEngineUrl);
9398
10561
  if (!schema || typeof schema !== "object") throw new Error("the rules service returned no schema");
9399
- const validate = new Ajv2020({ allErrors: true, strict: false }).compile(schema);
10562
+ const validate = new Ajv20202({ allErrors: true, strict: false }).compile(schema);
9400
10563
  compiledValidator = validate;
9401
10564
  return validate;
9402
10565
  }
@@ -9494,7 +10657,7 @@ var ExpressionSyntaxError = class extends Error {
9494
10657
  };
9495
10658
  var IDENT = /[A-Za-z_][A-Za-z0-9_]*/y;
9496
10659
  var NUMBER = /(?:\d+\.?\d*|\.\d+)/y;
9497
- var FIELD_REF = /\$[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+|\[\*\])*/y;
10660
+ var FIELD_REF = new RegExp(`\\$${FORM_SEGMENT}(?:\\.${FORM_SEGMENT}|\\[\\*\\])*`, "y");
9498
10661
  var SIGIL_REF = /~[A-Za-z_][A-Za-z0-9_]*/y;
9499
10662
  var CTX_REF = /ctx(?:\.[A-Za-z][A-Za-z0-9]*)+/y;
9500
10663
  function parseExpression(src) {
@@ -9657,8 +10820,9 @@ var CTX_TOKEN_KIND = {
9657
10820
  "ctx.submitter.priorApprovedCount": "number",
9658
10821
  "ctx.collection.projectBoundary": "geo"
9659
10822
  };
9660
- var FIELD_REF2 = /^\$[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+|\[\*\])*$/;
9661
- var ROW_REF = /^\.[A-Za-z0-9_-]+$/;
10823
+ var FIELD_REF2 = new RegExp(`^\\$${FORM_SEGMENT}(?:\\.${FORM_SEGMENT}|\\[\\*\\])*$`);
10824
+ var ROW_REF = new RegExp(`^\\.${FORM_SEGMENT}$`);
10825
+ var FIELD_ROOT = new RegExp(`^\\$${FORM_SEGMENT}`);
9662
10826
  var DERIVED_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
9663
10827
  var EXT_REF = /^ext\.([A-Za-z0-9_-]+)\.(valid|score|reason)$/;
9664
10828
  var AI_REF = /^ai\.([A-Za-z0-9_-]+)\.(valid|reason)$/;
@@ -9690,7 +10854,7 @@ function validateRubric(body, catalog, authoringCatalog) {
9690
10854
  error("RUB_SCHEMA_DRIFT", `'${ref}' no longer exists on the claim form (deleted or renamed since the rules were authored)`, path);
9691
10855
  return void 0;
9692
10856
  }
9693
- const rootName = /^\$[A-Za-z0-9_-]+/.exec(ref)?.[0] ?? ref;
10857
+ const rootName = FIELD_ROOT.exec(ref)?.[0] ?? ref;
9694
10858
  if (ref !== rootName && fieldIndex.has(rootName)) {
9695
10859
  error("RUB_FIELD_PATH", `'${ref}' does not address a row/column of ${rootName}`, path);
9696
10860
  } else {
@@ -10763,6 +11927,11 @@ registerAction({
10763
11927
  allowAiChecks: { type: "boolean", default: true, description: "Engine setting: allow paid AI checks for this collection." },
10764
11928
  allowImageChecks: { type: "boolean", default: true, description: "Engine setting: allow fake-photo detection (needs AI checks on)." },
10765
11929
  allowChainEvaluation: { type: "boolean", default: true, description: "Engine setting: submit the decision on chain (releases payment); makes adminAddress required." },
11930
+ allowZeroPayoutApprovals: {
11931
+ type: "boolean",
11932
+ default: false,
11933
+ description: "Engine setting: approve on chain even when the approval pays nothing (attestation-only collections). Opt-in \u2014 otherwise such approvals wait for the owner."
11934
+ },
10766
11935
  evaluateMaxAmount: { type: "array", description: "Per-claim payout cap on the evaluate grant, base-unit coins in the owner's denoms." },
10767
11936
  // ---- rules (qi/eval.rubric) ----
10768
11937
  rubric: {
@@ -10935,6 +12104,27 @@ registerAction({
10935
12104
  });
10936
12105
 
10937
12106
  // src/core/lib/actionRegistry/actions/_shared/delegatedTool.ts
12107
+ function delegatedToolInputSchema(schema) {
12108
+ return {
12109
+ type: "object",
12110
+ required: ["connection", ...schema.parameters.required],
12111
+ additionalProperties: false,
12112
+ properties: {
12113
+ connection: {
12114
+ type: "object",
12115
+ required: ["bindingId", "connectedAccountId", "toolkit"],
12116
+ additionalProperties: false,
12117
+ properties: {
12118
+ bindingId: { type: "string", minLength: 1, description: "Opaque, server-side delegated credential binding." },
12119
+ connectedAccountId: { type: "string" },
12120
+ toolkit: { type: "string" },
12121
+ label: { type: ["string", "null"] }
12122
+ }
12123
+ },
12124
+ ...schema.parameters.properties
12125
+ }
12126
+ };
12127
+ }
10938
12128
  function parseBoundConnection(raw) {
10939
12129
  if (!raw || typeof raw !== "object") return null;
10940
12130
  const c = raw;
@@ -11021,7 +12211,10 @@ async function executeDelegatedTool(ctx, opts) {
11021
12211
  }
11022
12212
  throw new Error(result.error || `${opts.toolkitLabel} action failed.`);
11023
12213
  }
11024
- return result.data ?? {};
12214
+ return {
12215
+ data: result.data ?? {},
12216
+ providerInvocationReceipt: result.providerInvocationReceipt
12217
+ };
11025
12218
  }
11026
12219
 
11027
12220
  // src/core/lib/actionRegistry/actions/gmail/emailSend.types.ts
@@ -11047,7 +12240,9 @@ var GMAIL_SEND_SCHEMA = {
11047
12240
  var GMAIL_SEND_OUTPUT_SCHEMA = [
11048
12241
  { path: "messageId", displayName: "Message ID", type: "string", description: "Gmail id of the sent message" },
11049
12242
  { path: "threadId", displayName: "Thread ID", type: "string" },
11050
- { path: "sentAt", displayName: "Sent At", type: "string", description: "ISO timestamp when sent" }
12243
+ { path: "sentAt", displayName: "Sent At", type: "string", description: "ISO timestamp when sent" },
12244
+ { path: "providerReceiptId", displayName: "Provider Receipt ID", type: "string", description: "Signed integration-host invocation receipt identifier" },
12245
+ { path: "providerInvocationReceipt", displayName: "Provider Receipt", type: "object", description: "Signed integration-host invocation receipt" }
11051
12246
  ];
11052
12247
 
11053
12248
  // src/core/lib/actionRegistry/actions/gmail/emailSend.ts
@@ -11062,6 +12257,7 @@ registerAction({
11062
12257
  requiredCapability: "flow/block/execute",
11063
12258
  // Can be wired to another block's event (e.g. form submitted → send email).
11064
12259
  eligibleForEventTrigger: true,
12260
+ inputSchema: delegatedToolInputSchema(GMAIL_SEND_SCHEMA),
11065
12261
  // Mirrors executeDelegatedTool's gates: the bound connection plus the
11066
12262
  // tool schema's required fields, so orchestrators ask before run() throws.
11067
12263
  getMissingInputs: (inputs) => delegatedToolMissingInputs(GMAIL_SEND_SCHEMA, inputs),
@@ -11081,13 +12277,14 @@ registerAction({
11081
12277
  run: async (inputs, ctx) => {
11082
12278
  const parsed = parseDelegatedToolInputs(inputs);
11083
12279
  const values = fieldValues(parsed);
11084
- const data = await executeDelegatedTool(ctx, {
12280
+ const execution = await executeDelegatedTool(ctx, {
11085
12281
  connection: parsed.connection,
11086
12282
  schema: GMAIL_SEND_SCHEMA,
11087
12283
  toolSlug: GMAIL_SEND_SLUG,
11088
12284
  values,
11089
12285
  toolkitLabel: "Gmail"
11090
12286
  });
12287
+ const { data, providerInvocationReceipt } = execution;
11091
12288
  const envelope = data.response_data ?? data;
11092
12289
  const messageId = String(envelope.id ?? envelope.messageId ?? "");
11093
12290
  const threadId = String(envelope.threadId ?? "");
@@ -11095,7 +12292,9 @@ registerAction({
11095
12292
  output: {
11096
12293
  messageId,
11097
12294
  threadId,
11098
- sentAt: (/* @__PURE__ */ new Date()).toISOString()
12295
+ sentAt: (/* @__PURE__ */ new Date()).toISOString(),
12296
+ providerReceiptId: providerInvocationReceipt?.id || "",
12297
+ providerInvocationReceipt
11099
12298
  },
11100
12299
  events: messageId ? [{ name: GMAIL_SENT_EVENT, payload: { messageId, recipient_email: values.recipient_email ?? "" } }] : void 0
11101
12300
  };
@@ -11125,7 +12324,9 @@ var OUTLOOK_SEND_SCHEMA = {
11125
12324
  };
11126
12325
  var OUTLOOK_SEND_OUTPUT_SCHEMA = [
11127
12326
  { path: "messageId", displayName: "Message ID", type: "string" },
11128
- { path: "sentAt", displayName: "Sent At", type: "string", description: "ISO timestamp when sent" }
12327
+ { path: "sentAt", displayName: "Sent At", type: "string", description: "ISO timestamp when sent" },
12328
+ { path: "providerReceiptId", displayName: "Provider Receipt ID", type: "string", description: "Required signed proof when Outlook returns no message id" },
12329
+ { path: "providerInvocationReceipt", displayName: "Provider Receipt", type: "object", description: "Signed integration-host invocation receipt" }
11129
12330
  ];
11130
12331
 
11131
12332
  // src/core/lib/actionRegistry/actions/outlook/emailSend.ts
@@ -11133,16 +12334,15 @@ registerAction({
11133
12334
  type: "qi/outlook.email.send",
11134
12335
  can: "outlook.email/send",
11135
12336
  sideEffect: true,
11136
- // Outlook's send tool often returns no message id (see run() below), so there
11137
- // is no reliable output field to prove execution a successful tool call
11138
- // (run() throws on failure) is the signal. Declared 'none' rather than
11139
- // requiring messageId, which would push every id-less send to needs_verification.
11140
- proof: "none",
12337
+ // Outlook often returns no message id. The integration host must therefore
12338
+ // return a signed provider invocation receipt for proof of the side effect.
12339
+ proof: { fields: ["messageId", "providerReceiptId"] },
11141
12340
  done: doneWhenCompleted,
11142
12341
  defaultRequiresConfirmation: true,
11143
12342
  requiredCapability: "flow/block/execute",
11144
12343
  // Can be wired to another block's event (e.g. form submitted → send email).
11145
12344
  eligibleForEventTrigger: true,
12345
+ inputSchema: delegatedToolInputSchema(OUTLOOK_SEND_SCHEMA),
11146
12346
  // Mirrors executeDelegatedTool's gates: the bound connection plus the
11147
12347
  // tool schema's required fields, so orchestrators ask before run() throws.
11148
12348
  getMissingInputs: (inputs) => delegatedToolMissingInputs(OUTLOOK_SEND_SCHEMA, inputs),
@@ -11162,19 +12362,25 @@ registerAction({
11162
12362
  run: async (inputs, ctx) => {
11163
12363
  const parsed = parseDelegatedToolInputs(inputs);
11164
12364
  const values = fieldValues(parsed);
11165
- const data = await executeDelegatedTool(ctx, {
12365
+ const execution = await executeDelegatedTool(ctx, {
11166
12366
  connection: parsed.connection,
11167
12367
  schema: OUTLOOK_SEND_SCHEMA,
11168
12368
  toolSlug: OUTLOOK_SEND_SLUG,
11169
12369
  values,
11170
12370
  toolkitLabel: "Outlook"
11171
12371
  });
12372
+ const { data, providerInvocationReceipt } = execution;
11172
12373
  const envelope = data.response_data ?? data;
11173
12374
  const messageId = String(envelope.id ?? envelope.messageId ?? "");
12375
+ if (!messageId && !providerInvocationReceipt?.id) {
12376
+ throw new Error("Outlook returned no message id and the integration host returned no signed provider invocation receipt.");
12377
+ }
11174
12378
  return {
11175
12379
  output: {
11176
12380
  messageId,
11177
- sentAt: (/* @__PURE__ */ new Date()).toISOString()
12381
+ sentAt: (/* @__PURE__ */ new Date()).toISOString(),
12382
+ providerReceiptId: providerInvocationReceipt?.id || "",
12383
+ providerInvocationReceipt
11178
12384
  },
11179
12385
  // Outlook often returns no id, so emit unconditionally.
11180
12386
  events: [{ name: OUTLOOK_SENT_EVENT, payload: { messageId, to_email: values.to_email ?? "" } }]
@@ -11206,7 +12412,9 @@ var SLACK_SEND_SCHEMA = {
11206
12412
  var SLACK_SEND_OUTPUT_SCHEMA = [
11207
12413
  { path: "messageTs", displayName: "Message ts", type: "string" },
11208
12414
  { path: "channel", displayName: "Channel", type: "string" },
11209
- { path: "sentAt", displayName: "Sent At", type: "string", description: "ISO timestamp when sent" }
12415
+ { path: "sentAt", displayName: "Sent At", type: "string", description: "ISO timestamp when sent" },
12416
+ { path: "providerReceiptId", displayName: "Provider Receipt ID", type: "string", description: "Signed integration-host invocation receipt identifier" },
12417
+ { path: "providerInvocationReceipt", displayName: "Provider Receipt", type: "object", description: "Signed integration-host invocation receipt" }
11210
12418
  ];
11211
12419
 
11212
12420
  // src/core/lib/actionRegistry/actions/slack/messageSend.ts
@@ -11221,6 +12429,7 @@ registerAction({
11221
12429
  requiredCapability: "flow/block/execute",
11222
12430
  // Can be wired to another block's event (e.g. form submitted → post message).
11223
12431
  eligibleForEventTrigger: true,
12432
+ inputSchema: delegatedToolInputSchema(SLACK_SEND_SCHEMA),
11224
12433
  // Mirrors executeDelegatedTool's gates: the bound connection plus the
11225
12434
  // tool schema's required fields, so orchestrators ask before run() throws.
11226
12435
  getMissingInputs: (inputs) => delegatedToolMissingInputs(SLACK_SEND_SCHEMA, inputs),
@@ -11240,13 +12449,14 @@ registerAction({
11240
12449
  run: async (inputs, ctx) => {
11241
12450
  const parsed = parseDelegatedToolInputs(inputs);
11242
12451
  const values = fieldValues(parsed);
11243
- const data = await executeDelegatedTool(ctx, {
12452
+ const execution = await executeDelegatedTool(ctx, {
11244
12453
  connection: parsed.connection,
11245
12454
  schema: SLACK_SEND_SCHEMA,
11246
12455
  toolSlug: SLACK_SEND_SLUG,
11247
12456
  values,
11248
12457
  toolkitLabel: "Slack"
11249
12458
  });
12459
+ const { data, providerInvocationReceipt } = execution;
11250
12460
  const envelope = data.response_data ?? data;
11251
12461
  const messageTs = String(envelope.ts ?? "");
11252
12462
  const channel = String(envelope.channel ?? values.channel ?? "");
@@ -11254,7 +12464,9 @@ registerAction({
11254
12464
  output: {
11255
12465
  messageTs,
11256
12466
  channel,
11257
- sentAt: (/* @__PURE__ */ new Date()).toISOString()
12467
+ sentAt: (/* @__PURE__ */ new Date()).toISOString(),
12468
+ providerReceiptId: providerInvocationReceipt?.id || "",
12469
+ providerInvocationReceipt
11258
12470
  },
11259
12471
  events: messageTs ? [{ name: SLACK_SENT_EVENT, payload: { messageTs, channel } }] : void 0
11260
12472
  };
@@ -11287,7 +12499,9 @@ var GOOGLECALENDAR_CREATE_OUTPUT_SCHEMA = [
11287
12499
  { path: "eventId", displayName: "Event ID", type: "string" },
11288
12500
  { path: "htmlLink", displayName: "Event link", type: "string" },
11289
12501
  { path: "summary", displayName: "Summary", type: "string" },
11290
- { path: "startIso", displayName: "Start", type: "string" }
12502
+ { path: "startIso", displayName: "Start", type: "string" },
12503
+ { path: "providerReceiptId", displayName: "Provider Receipt ID", type: "string", description: "Signed integration-host invocation receipt identifier" },
12504
+ { path: "providerInvocationReceipt", displayName: "Provider Receipt", type: "object", description: "Signed integration-host invocation receipt" }
11291
12505
  ];
11292
12506
 
11293
12507
  // src/core/lib/actionRegistry/actions/googlecalendar/eventCreate.ts
@@ -11301,6 +12515,7 @@ registerAction({
11301
12515
  defaultRequiresConfirmation: true,
11302
12516
  requiredCapability: "flow/block/execute",
11303
12517
  eligibleForEventTrigger: true,
12518
+ inputSchema: delegatedToolInputSchema(GOOGLECALENDAR_CREATE_SCHEMA),
11304
12519
  // Mirrors executeDelegatedTool's gates: the bound connection plus the
11305
12520
  // tool schema's required fields, so orchestrators ask before run() throws.
11306
12521
  getMissingInputs: (inputs) => delegatedToolMissingInputs(GOOGLECALENDAR_CREATE_SCHEMA, inputs),
@@ -11321,13 +12536,14 @@ registerAction({
11321
12536
  run: async (inputs, ctx) => {
11322
12537
  const parsed = parseDelegatedToolInputs(inputs);
11323
12538
  const values = fieldValues(parsed);
11324
- const data = await executeDelegatedTool(ctx, {
12539
+ const execution = await executeDelegatedTool(ctx, {
11325
12540
  connection: parsed.connection,
11326
12541
  schema: GOOGLECALENDAR_CREATE_SCHEMA,
11327
12542
  toolSlug: GOOGLECALENDAR_CREATE_SLUG,
11328
12543
  values,
11329
12544
  toolkitLabel: "Google Calendar"
11330
12545
  });
12546
+ const { data, providerInvocationReceipt } = execution;
11331
12547
  const envelope = data.response_data ?? data;
11332
12548
  const eventId = String(envelope.id ?? "");
11333
12549
  const htmlLink = String(envelope.htmlLink ?? "");
@@ -11335,12 +12551,774 @@ registerAction({
11335
12551
  const start = envelope.start;
11336
12552
  const startIso = String(start?.dateTime ?? start?.date ?? values.start_datetime ?? "");
11337
12553
  return {
11338
- output: { eventId, htmlLink, summary, startIso },
12554
+ output: { eventId, htmlLink, summary, startIso, providerReceiptId: providerInvocationReceipt?.id || "", providerInvocationReceipt },
11339
12555
  events: eventId ? [{ name: GOOGLECALENDAR_CREATED_EVENT, payload: { eventId, htmlLink, summary } }] : void 0
11340
12556
  };
11341
12557
  }
11342
12558
  });
11343
12559
 
12560
+ // src/core/lib/actionRegistry/actions/topicActions.ts
12561
+ var ALL_KINDS2 = ["task", "agent_task", "proposal", "evaluation", "claims", "question", "discussion", "incident"];
12562
+ function topicMetadata(supportedBaseKinds, permittedTopicRecordTypes, requiredTopicAbilities, relevance = "recommended", sensitiveInputPaths = [], sensitiveOutputPaths = []) {
12563
+ const semanticRecordTypes = getTopicSemanticRecordDefinitions(permittedTopicRecordTypes);
12564
+ if (semanticRecordTypes.length !== permittedTopicRecordTypes.length) {
12565
+ throw new Error(`Topic Action metadata names a semantic record type without a complete definition`);
12566
+ }
12567
+ return {
12568
+ supportedBaseKinds,
12569
+ relevance,
12570
+ writeBackMode: permittedTopicRecordTypes.length > 0 ? "semantic-record" : "receipt-only",
12571
+ semanticRecordTypes,
12572
+ permittedTopicRecordTypes: semanticRecordTypes.map((definition) => definition.type),
12573
+ lifecycleEffect: "none",
12574
+ requiredTopicAbilities,
12575
+ redactionPolicy: { mode: "paths", sensitiveInputPaths, sensitiveOutputPaths }
12576
+ };
12577
+ }
12578
+ function requiredString(value, name) {
12579
+ const normalized = String(value || "").trim();
12580
+ if (!normalized) throw new Error(`${name} is required`);
12581
+ return normalized;
12582
+ }
12583
+ function requiredArray(value, name) {
12584
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) throw new Error(`${name} must be an array of strings`);
12585
+ return value.map(String);
12586
+ }
12587
+ function topicContext(ctx) {
12588
+ if (!ctx.topic) throw new Error("This Action requires a revision-bound Topic execution context");
12589
+ return ctx.topic;
12590
+ }
12591
+ function topicService(ctx) {
12592
+ topicContext(ctx);
12593
+ if (!ctx.services.topic) throw new Error("The host did not grant the capability-checked Topic service");
12594
+ return ctx.services.topic;
12595
+ }
12596
+ function idempotencyKey(actionType, inputs, ctx) {
12597
+ const explicit = String(inputs.idempotencyKey || "").trim();
12598
+ if (explicit) return explicit;
12599
+ const topic = topicContext(ctx);
12600
+ return sha256Digest({ actionType, topicId: topic.topicId, topicRevision: topic.topicRevision, requestId: topic.requestId, inputs });
12601
+ }
12602
+ function semanticRecord(type, value, ctx) {
12603
+ const topic = topicContext(ctx);
12604
+ const digest2 = sha256Digest(value);
12605
+ return {
12606
+ digest: digest2,
12607
+ record: {
12608
+ type,
12609
+ id: sha256Digest({ type, topicId: topic.topicId, requestId: topic.requestId, digest: digest2 }),
12610
+ version: 1,
12611
+ value,
12612
+ evidenceReferences: Array.isArray(value.evidenceReferences) ? value.evidenceReferences : void 0
12613
+ }
12614
+ };
12615
+ }
12616
+ function registerTopicOperation(spec) {
12617
+ registerAction({
12618
+ type: spec.type,
12619
+ can: spec.can,
12620
+ sideEffect: true,
12621
+ proof: { fields: ["operationId"] },
12622
+ done: doneWhenCompleted,
12623
+ defaultRequiresConfirmation: spec.confirmation === true,
12624
+ requiredCapability: "flow/block/execute",
12625
+ executionOwner: spec.owner || "agent",
12626
+ hiddenFromAuthoring: spec.hidden,
12627
+ riskTier: spec.confirmation ? "high" : "medium",
12628
+ requiredServices: ["topic"],
12629
+ topic: topicMetadata(spec.kinds || ALL_KINDS2, [], [spec.ability]),
12630
+ inputSchema: spec.inputSchema,
12631
+ outputSchema: [
12632
+ { path: "operationId", displayName: "Topic operation ID", type: "string" },
12633
+ { path: "topicRevision", displayName: "Topic revision", type: "string" },
12634
+ { path: "proofReference", displayName: "Operation proof", type: "string" }
12635
+ ],
12636
+ run: async (inputs, ctx) => {
12637
+ const service = topicService(ctx);
12638
+ const payload = await spec.buildPayload(inputs, ctx);
12639
+ return {
12640
+ output: await service.appendOperation({
12641
+ context: topicContext(ctx),
12642
+ actorDid: ctx.actorDid,
12643
+ operationType: spec.operationType,
12644
+ payload,
12645
+ idempotencyKey: idempotencyKey(spec.type, inputs, ctx)
12646
+ })
12647
+ };
12648
+ }
12649
+ });
12650
+ }
12651
+ registerTopicOperation({
12652
+ type: "qi/topic.flow.bind",
12653
+ can: "topic/flow.bind",
12654
+ operationType: "bind-flow",
12655
+ confirmation: true,
12656
+ owner: "human",
12657
+ ability: "topic/bind-flow",
12658
+ inputSchema: {
12659
+ type: "object",
12660
+ required: ["flowUri", "flowRevision", "flowDigest", "actionManifestDigest", "controllerDid", "role", "startPolicy", "triggerPolicy", "receiptPolicy"],
12661
+ additionalProperties: false,
12662
+ properties: {
12663
+ bindingId: { type: "string" },
12664
+ flowUri: { type: "string" },
12665
+ flowRevision: { type: "string" },
12666
+ flowDigest: { type: "string", pattern: "^sha256:" },
12667
+ actionManifestDigest: { type: "string", pattern: "^sha256:" },
12668
+ controllerDid: { type: "string", pattern: "^did:" },
12669
+ role: { type: "string", enum: ["primary", "supporting"] },
12670
+ startPolicy: { type: "string", enum: ["manual", "on-topic-active", "scheduled", "event"] },
12671
+ triggerPolicy: { type: "object" },
12672
+ receiptPolicy: { type: "string", enum: ["all", "terminal"] },
12673
+ capabilityReferences: { type: "array", minItems: 2, uniqueItems: true, items: { type: "string" } },
12674
+ idempotencyKey: { type: "string" }
12675
+ }
12676
+ },
12677
+ buildPayload: (inputs, ctx) => {
12678
+ const topic = topicContext(ctx);
12679
+ return {
12680
+ binding: {
12681
+ version: 1,
12682
+ bindingId: String(inputs.bindingId || sha256Digest({ topicId: topic.topicId, flowUri: inputs.flowUri, flowRevision: inputs.flowRevision })),
12683
+ topicId: topic.topicId,
12684
+ flowUri: requiredString(inputs.flowUri, "flowUri"),
12685
+ flowRevision: requiredString(inputs.flowRevision, "flowRevision"),
12686
+ flowDigest: requiredString(inputs.flowDigest, "flowDigest"),
12687
+ actionManifestDigest: requiredString(inputs.actionManifestDigest, "actionManifestDigest"),
12688
+ controllerDid: requiredString(inputs.controllerDid, "controllerDid"),
12689
+ role: requiredString(inputs.role, "role"),
12690
+ startPolicy: requiredString(inputs.startPolicy, "startPolicy"),
12691
+ triggerPolicy: inputs.triggerPolicy || {},
12692
+ receiptPolicy: requiredString(inputs.receiptPolicy, "receiptPolicy"),
12693
+ status: "active",
12694
+ capability: topic.topicCapabilityReference,
12695
+ capabilityReferences: Array.isArray(inputs.capabilityReferences) ? inputs.capabilityReferences.map(String) : [],
12696
+ createdBy: ctx.actorDid,
12697
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
12698
+ }
12699
+ };
12700
+ }
12701
+ });
12702
+ registerTopicOperation({
12703
+ type: "qi/topic.flow.unbind",
12704
+ can: "topic/flow.unbind",
12705
+ operationType: "unbind-flow",
12706
+ confirmation: true,
12707
+ owner: "human",
12708
+ ability: "topic/bind-flow",
12709
+ inputSchema: {
12710
+ type: "object",
12711
+ required: ["bindingId"],
12712
+ additionalProperties: false,
12713
+ properties: { bindingId: { type: "string" }, reason: { type: "string" }, idempotencyKey: { type: "string" } }
12714
+ },
12715
+ buildPayload: (inputs) => ({ bindingId: requiredString(inputs.bindingId, "bindingId"), reason: String(inputs.reason || "") })
12716
+ });
12717
+ registerTopicOperation({
12718
+ type: "qi/topic.action.request",
12719
+ can: "topic/action.request",
12720
+ operationType: "request-action",
12721
+ ability: "topic/request-action",
12722
+ inputSchema: {
12723
+ type: "object",
12724
+ required: ["actionType", "actionContractDigest", "inputDigest"],
12725
+ additionalProperties: false,
12726
+ properties: {
12727
+ actionType: { type: "string" },
12728
+ actionContractDigest: { type: "string", pattern: "^sha256:" },
12729
+ inputDigest: { type: "string", pattern: "^sha256:" },
12730
+ inputReference: { type: "string" },
12731
+ requestId: { type: "string" },
12732
+ safeInputSummary: { type: "object" },
12733
+ executorPreference: { type: "string", enum: ["qi-flow", "qiforge", "mcp"] },
12734
+ bindingId: { type: "string" },
12735
+ confirmationPolicy: { type: "string", enum: ["inherit", "required"] },
12736
+ idempotencyKey: { type: "string" }
12737
+ }
12738
+ },
12739
+ buildPayload: (inputs, ctx) => {
12740
+ const actionType = requiredString(inputs.actionType, "actionType");
12741
+ if (actionType === "qi/topic.action.request") throw new Error("A Topic Action request cannot recursively request itself");
12742
+ const target = getAction(actionType);
12743
+ if (!target) throw new Error(`Unknown Action type '${actionType}'`);
12744
+ const manifestEntry = generateActionManifest().actions.find((entry) => entry.type === target.type);
12745
+ const suppliedDigest = requiredString(inputs.actionContractDigest, "actionContractDigest");
12746
+ if (!manifestEntry || manifestEntry.contractDigest !== suppliedDigest) throw new Error("Action contract digest does not match the live registry");
12747
+ const kind = topicContext(ctx).kind;
12748
+ const baseKind = kind.source === "standard" ? kind.kind : kind.baseKind;
12749
+ if (!target.topic?.supportedBaseKinds.includes(baseKind)) throw new Error(`Action '${actionType}' does not support Topic base Kind '${baseKind}'`);
12750
+ const topic = topicContext(ctx);
12751
+ const inputDigest = requiredString(inputs.inputDigest, "inputDigest");
12752
+ const derived = (purpose) => sha256Digest({ purpose, parentRequestId: topic.requestId, topicId: topic.topicId, actionType: target.type, inputDigest });
12753
+ return {
12754
+ request: {
12755
+ version: 1,
12756
+ requestId: String(inputs.requestId || derived("topic-action-request")),
12757
+ topicId: topic.topicId,
12758
+ topicRevision: topic.topicRevision,
12759
+ actionType: target.type,
12760
+ actionContractDigest: suppliedDigest,
12761
+ executor: inputs.executorPreference || "qi-flow",
12762
+ inputs: { digest: inputDigest, ...inputs.inputReference ? { ref: String(inputs.inputReference) } : {} },
12763
+ requestedBy: ctx.actorDid,
12764
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
12765
+ idempotencyKey: String(inputs.idempotencyKey || derived("topic-action-idempotency")),
12766
+ confirmation: target.defaultRequiresConfirmation || inputs.confirmationPolicy === "required" ? "required" : topic.confirmationReference ? "confirmed" : "not-required",
12767
+ ...inputs.bindingId ? { flowBindingId: String(inputs.bindingId) } : {},
12768
+ capability: topic.topicCapabilityReference
12769
+ }
12770
+ };
12771
+ }
12772
+ });
12773
+ registerTopicOperation({
12774
+ type: "qi/topic.action.receipt.record",
12775
+ can: "topic/action.receipt.record",
12776
+ operationType: "record-action-receipt",
12777
+ ability: "topic/record-action",
12778
+ hidden: true,
12779
+ inputSchema: {
12780
+ type: "object",
12781
+ required: ["receipt"],
12782
+ additionalProperties: false,
12783
+ properties: { receipt: { type: "object" }, idempotencyKey: { type: "string" } }
12784
+ },
12785
+ buildPayload: (inputs, ctx) => {
12786
+ if (!inputs.receipt || typeof inputs.receipt !== "object" || Array.isArray(inputs.receipt)) throw new Error("receipt must be an ActionReceiptV2 object");
12787
+ const receipt = inputs.receipt;
12788
+ if (receipt.topicId !== topicContext(ctx).topicId) throw new Error("Receipt Topic does not match the execution context");
12789
+ if (receipt.version !== 2 || !receipt.signature || !receipt.issuerDid) throw new Error("Receipt requires a v2 issuer signature");
12790
+ const receiptAction = receipt.action;
12791
+ const action = getAction(String(receiptAction?.type || ""));
12792
+ const manifestEntry = action && generateActionManifest().actions.find((entry) => entry.type === action.type);
12793
+ if (!manifestEntry || manifestEntry.contractDigest !== receiptAction?.contractDigest) throw new Error("Receipt Action contract digest does not match the live registry");
12794
+ return { receipt };
12795
+ }
12796
+ });
12797
+ registerTopicOperation({
12798
+ type: "qi/topic.action.cancel",
12799
+ can: "topic/action.cancel",
12800
+ operationType: "cancel-action",
12801
+ ability: "topic/cancel-action",
12802
+ inputSchema: {
12803
+ type: "object",
12804
+ required: ["requestId", "reason"],
12805
+ additionalProperties: false,
12806
+ properties: { requestId: { type: "string" }, reason: { type: "string" }, idempotencyKey: { type: "string" } }
12807
+ },
12808
+ buildPayload: (inputs) => ({ requestId: requiredString(inputs.requestId, "requestId"), reason: requiredString(inputs.reason, "reason") })
12809
+ });
12810
+ registerTopicOperation({
12811
+ type: "qi/topic.status.transition",
12812
+ can: "topic/status.transition",
12813
+ operationType: "change-status",
12814
+ confirmation: true,
12815
+ owner: "human",
12816
+ ability: "topic/change-status",
12817
+ inputSchema: {
12818
+ type: "object",
12819
+ required: ["from", "to", "reason"],
12820
+ additionalProperties: false,
12821
+ properties: { from: { type: "string" }, to: { type: "string" }, reason: { type: "string" }, idempotencyKey: { type: "string" } }
12822
+ },
12823
+ buildPayload: async (inputs, ctx) => {
12824
+ const from = requiredString(inputs.from, "from");
12825
+ const to = requiredString(inputs.to, "to");
12826
+ if (to === "resolved") {
12827
+ const topic = topicContext(ctx);
12828
+ const projection = await topicService(ctx).readProjection?.({ topicId: topic.topicId, topicRevision: topic.topicRevision, requestId: topic.requestId });
12829
+ if (!projection) throw new Error("Resolution requires a current Topic projection so completion policy can be verified");
12830
+ const completion = projection.completion;
12831
+ const outcome = projection.outcome;
12832
+ if (completion?.requiresOutcomeRecord === true && !outcome?.outcomeRecordId) throw new Error("Topic policy requires an accepted outcome record before resolution");
12833
+ }
12834
+ return { from, to, reason: requiredString(inputs.reason, "reason") };
12835
+ }
12836
+ });
12837
+ registerTopicOperation({
12838
+ type: "qi/topic.contract.accept",
12839
+ can: "topic/contract.accept",
12840
+ operationType: "accept-contract",
12841
+ confirmation: true,
12842
+ owner: "human",
12843
+ ability: "topic/accept-contract",
12844
+ inputSchema: {
12845
+ type: "object",
12846
+ required: ["contractRevision", "contractDigest", "confirmationReference"],
12847
+ additionalProperties: false,
12848
+ properties: {
12849
+ contractRevision: { type: "string" },
12850
+ contractDigest: { type: "string", pattern: "^sha256:" },
12851
+ confirmationReference: { type: "string" },
12852
+ idempotencyKey: { type: "string" }
12853
+ }
12854
+ },
12855
+ buildPayload: (inputs, ctx) => {
12856
+ const revision = requiredString(inputs.contractRevision, "contractRevision");
12857
+ const digest2 = requiredString(inputs.contractDigest, "contractDigest");
12858
+ if (revision !== topicContext(ctx).contract.revision || digest2 !== topicContext(ctx).contract.digest)
12859
+ throw new Error("Contract acceptance must target the exact effective revision and digest");
12860
+ return { contractRevision: revision, contractDigest: digest2, confirmationReference: requiredString(inputs.confirmationReference, "confirmationReference") };
12861
+ }
12862
+ });
12863
+ registerTopicOperation({
12864
+ type: "qi/topic.outcome.propose",
12865
+ can: "topic/outcome.propose",
12866
+ operationType: "update-contract",
12867
+ ability: "topic/update-contract",
12868
+ inputSchema: {
12869
+ type: "object",
12870
+ required: ["statement"],
12871
+ additionalProperties: false,
12872
+ properties: { statement: { type: "string" }, evidenceReferences: { type: "array", items: { type: "string" } }, idempotencyKey: { type: "string" } }
12873
+ },
12874
+ buildPayload: (inputs) => ({
12875
+ patch: {
12876
+ outcome: {
12877
+ statement: requiredString(inputs.statement, "statement"),
12878
+ status: "proposed",
12879
+ evidenceReferences: requiredArray(inputs.evidenceReferences || [], "evidenceReferences")
12880
+ }
12881
+ }
12882
+ })
12883
+ });
12884
+ registerTopicOperation({
12885
+ type: "qi/topic.outcome.confirm",
12886
+ can: "topic/outcome.confirm",
12887
+ operationType: "update-contract",
12888
+ confirmation: true,
12889
+ owner: "human",
12890
+ ability: "topic/update-contract",
12891
+ inputSchema: {
12892
+ type: "object",
12893
+ required: ["proposedOutcomeRecordId", "confirmationAuthorityDid", "confirmationReference"],
12894
+ additionalProperties: false,
12895
+ properties: {
12896
+ proposedOutcomeRecordId: { type: "string" },
12897
+ confirmationAuthorityDid: { type: "string", pattern: "^did:" },
12898
+ confirmationReference: { type: "string" },
12899
+ idempotencyKey: { type: "string" }
12900
+ }
12901
+ },
12902
+ buildPayload: (inputs) => ({
12903
+ patch: {
12904
+ outcome: {
12905
+ status: "achieved",
12906
+ outcomeRecordId: requiredString(inputs.proposedOutcomeRecordId, "proposedOutcomeRecordId"),
12907
+ confirmedBy: requiredString(inputs.confirmationAuthorityDid, "confirmationAuthorityDid"),
12908
+ confirmationReference: requiredString(inputs.confirmationReference, "confirmationReference")
12909
+ }
12910
+ }
12911
+ })
12912
+ });
12913
+ registerTopicOperation({
12914
+ type: "qi/topic.decision.record",
12915
+ can: "topic/decision.record",
12916
+ operationType: "record-decision",
12917
+ ability: "topic/record-decision",
12918
+ inputSchema: {
12919
+ type: "object",
12920
+ required: ["decision", "authorityDid", "rationale"],
12921
+ additionalProperties: false,
12922
+ properties: {
12923
+ decision: { type: "string" },
12924
+ authorityDid: { type: "string", pattern: "^did:" },
12925
+ rationale: { type: "string" },
12926
+ alternatives: { type: "array", items: { type: "string" } },
12927
+ receiptReferences: { type: "array", items: { type: "string" } },
12928
+ evidenceReferences: { type: "array", items: { type: "string" } },
12929
+ idempotencyKey: { type: "string" }
12930
+ }
12931
+ },
12932
+ buildPayload: (inputs) => ({
12933
+ decision: requiredString(inputs.decision, "decision"),
12934
+ authorityDid: requiredString(inputs.authorityDid, "authorityDid"),
12935
+ rationale: requiredString(inputs.rationale, "rationale"),
12936
+ alternatives: requiredArray(inputs.alternatives || [], "alternatives"),
12937
+ receiptReferences: requiredArray(inputs.receiptReferences || [], "receiptReferences"),
12938
+ evidenceReferences: requiredArray(inputs.evidenceReferences || [], "evidenceReferences")
12939
+ })
12940
+ });
12941
+ registerTopicOperation({
12942
+ type: "qi/topic.context.link",
12943
+ can: "topic/context.link",
12944
+ operationType: "link-context",
12945
+ ability: "topic/link-context",
12946
+ inputSchema: {
12947
+ type: "object",
12948
+ required: ["contextType", "id"],
12949
+ additionalProperties: false,
12950
+ properties: {
12951
+ contextType: { type: "string", enum: ["ixo.resource", "ixo.flow", "matrix.conversation", "ixo.entity", "ixo.service"] },
12952
+ id: { type: "string" },
12953
+ label: { type: "string" },
12954
+ reference: { type: "string" },
12955
+ idempotencyKey: { type: "string" }
12956
+ }
12957
+ },
12958
+ buildPayload: (inputs) => ({
12959
+ type: requiredString(inputs.contextType, "contextType"),
12960
+ id: requiredString(inputs.id, "id"),
12961
+ label: String(inputs.label || ""),
12962
+ reference: String(inputs.reference || "")
12963
+ })
12964
+ });
12965
+ registerTopicOperation({
12966
+ type: "qi/topic.file.attach-reference",
12967
+ can: "topic/file.attach-reference",
12968
+ operationType: "attach-files",
12969
+ ability: "topic/attach-files",
12970
+ inputSchema: {
12971
+ type: "object",
12972
+ required: ["resource", "fileId", "version", "cid", "contentHash", "path", "name", "mimeType", "size"],
12973
+ additionalProperties: false,
12974
+ properties: {
12975
+ resource: { type: "string" },
12976
+ fileId: { type: "string" },
12977
+ version: { type: "number" },
12978
+ cid: { type: "string" },
12979
+ contentHash: { type: "string" },
12980
+ path: { type: "string" },
12981
+ name: { type: "string" },
12982
+ mimeType: { type: "string" },
12983
+ size: { type: "number" },
12984
+ idempotencyKey: { type: "string" }
12985
+ }
12986
+ },
12987
+ buildPayload: (inputs) => {
12988
+ if ("bytes" in inputs || "content" in inputs || "capability" in inputs)
12989
+ throw new Error("Topic file Actions accept pinned references only; bytes and access grants are forbidden");
12990
+ return {
12991
+ attachments: [
12992
+ {
12993
+ provider: "ixo.vfs",
12994
+ resource: inputs.resource,
12995
+ fileId: inputs.fileId,
12996
+ version: inputs.version,
12997
+ cid: inputs.cid,
12998
+ contentHash: inputs.contentHash,
12999
+ path: inputs.path,
13000
+ name: inputs.name,
13001
+ mimeType: inputs.mimeType,
13002
+ size: inputs.size
13003
+ }
13004
+ ]
13005
+ };
13006
+ }
13007
+ });
13008
+ function registerSemanticAction(spec) {
13009
+ registerAction({
13010
+ type: spec.type,
13011
+ can: spec.can,
13012
+ sideEffect: true,
13013
+ proof: { fields: ["recordDigest"] },
13014
+ done: doneWhenCompleted,
13015
+ defaultRequiresConfirmation: spec.confirmation === true,
13016
+ requiredCapability: "flow/block/execute",
13017
+ executionOwner: spec.owner || "agent",
13018
+ riskTier: spec.riskTier,
13019
+ requiredServices: spec.requiredServices || ["topic"],
13020
+ sensitiveInputPaths: spec.sensitiveInputPaths,
13021
+ sensitiveOutputPaths: spec.sensitiveOutputPaths,
13022
+ topic: topicMetadata(spec.kinds, [spec.recordType], ["topic/request-action", "topic/record-action"], "recommended", spec.sensitiveInputPaths, spec.sensitiveOutputPaths),
13023
+ inputSchema: spec.inputSchema,
13024
+ outputSchema: [
13025
+ { path: "recordId", displayName: "Semantic record ID", type: "string" },
13026
+ { path: "recordType", displayName: "Semantic record type", type: "string" },
13027
+ { path: "recordDigest", displayName: "Semantic record digest", type: "string" },
13028
+ { path: "record", displayName: "Semantic record", type: "object" }
13029
+ ],
13030
+ run: async (inputs, ctx) => {
13031
+ topicContext(ctx);
13032
+ const value = await spec.execute(inputs, ctx);
13033
+ const { record, digest: digest2 } = semanticRecord(spec.recordType, value, ctx);
13034
+ return { output: { recordId: record.id, recordType: record.type, recordDigest: digest2, record: value }, topicRecords: [record] };
13035
+ }
13036
+ });
13037
+ }
13038
+ var WORK_PHASES = {
13039
+ "qi/work.assign": "requested",
13040
+ "qi/work.dispatch": "dispatched",
13041
+ "qi/work.checkpoint": "in_progress",
13042
+ "qi/work.submit": "ready_for_review",
13043
+ "qi/work.accept": "completed"
13044
+ };
13045
+ for (const [type, phase] of Object.entries(WORK_PHASES)) {
13046
+ registerSemanticAction({
13047
+ type,
13048
+ can: type.replace("qi/", "").replace(".", "/"),
13049
+ kinds: ["task", "discussion", "incident"],
13050
+ recordType: "org.ixo.topic.work-event",
13051
+ confirmation: type === "qi/work.accept",
13052
+ owner: type === "qi/work.accept" ? "human" : "agent",
13053
+ inputSchema: {
13054
+ type: "object",
13055
+ required: ["workId", "resourceType"],
13056
+ additionalProperties: false,
13057
+ properties: {
13058
+ workId: { type: "string" },
13059
+ resourceType: { type: "string" },
13060
+ assigneeDid: { type: "string" },
13061
+ note: { type: "string" },
13062
+ artifactReferences: { type: "array", items: { type: "string" } },
13063
+ evidenceReferences: { type: "array", items: { type: "string" } }
13064
+ }
13065
+ },
13066
+ execute: (inputs, ctx) => ({
13067
+ workId: requiredString(inputs.workId, "workId"),
13068
+ resourceType: requiredString(inputs.resourceType, "resourceType"),
13069
+ phase,
13070
+ assigneeDid: String(inputs.assigneeDid || ""),
13071
+ note: String(inputs.note || ""),
13072
+ artifactReferences: requiredArray(inputs.artifactReferences || [], "artifactReferences"),
13073
+ evidenceReferences: requiredArray(inputs.evidenceReferences || [], "evidenceReferences"),
13074
+ actorDid: ctx.actorDid,
13075
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString()
13076
+ })
13077
+ });
13078
+ }
13079
+ registerSemanticAction({
13080
+ type: "qi/agent.invoke",
13081
+ can: "agent/invoke",
13082
+ kinds: ["agent_task", "question", "task"],
13083
+ recordType: "org.ixo.topic.agent-result",
13084
+ requiredServices: ["agents"],
13085
+ sensitiveInputPaths: ["prompt", "toolPolicy"],
13086
+ sensitiveOutputPaths: ["record.result"],
13087
+ inputSchema: {
13088
+ type: "object",
13089
+ required: ["agentDid", "capsuleReference", "toolPolicy"],
13090
+ additionalProperties: false,
13091
+ properties: {
13092
+ agentDid: { type: "string", pattern: "^did:" },
13093
+ capsuleReference: { type: "string" },
13094
+ toolPolicy: { type: "object" },
13095
+ budget: { type: "object" },
13096
+ deadline: { type: "string" },
13097
+ executorPreference: { type: "string", enum: ["qi-flow", "qiforge", "mcp"] },
13098
+ prompt: { type: "string" }
13099
+ }
13100
+ },
13101
+ execute: async (inputs, ctx) => {
13102
+ if (!ctx.services.agents) throw new Error("Agent runtime service is not configured");
13103
+ const result = await ctx.services.agents.invoke({
13104
+ agentDid: requiredString(inputs.agentDid, "agentDid"),
13105
+ capsuleReference: requiredString(inputs.capsuleReference, "capsuleReference"),
13106
+ toolPolicy: inputs.toolPolicy || {},
13107
+ budget: inputs.budget,
13108
+ deadline: inputs.deadline,
13109
+ executorPreference: inputs.executorPreference,
13110
+ prompt: inputs.prompt
13111
+ });
13112
+ return {
13113
+ sessionId: result.sessionId,
13114
+ result: result.result,
13115
+ resultDigest: sha256Digest(result.result),
13116
+ evidenceReferences: result.evidenceReferences || [],
13117
+ evidenceDigest: sha256Digest(result.evidenceReferences || []),
13118
+ providerReceiptReference: result.providerReceiptReference
13119
+ };
13120
+ }
13121
+ });
13122
+ registerSemanticAction({
13123
+ type: "qi/agent.cancel",
13124
+ can: "agent/cancel",
13125
+ kinds: ["agent_task", "question", "task"],
13126
+ recordType: "org.ixo.topic.agent-cancellation",
13127
+ requiredServices: ["agents"],
13128
+ inputSchema: { type: "object", required: ["sessionId"], additionalProperties: false, properties: { sessionId: { type: "string" }, reason: { type: "string" } } },
13129
+ execute: async (inputs, ctx) => {
13130
+ if (!ctx.services.agents) throw new Error("Agent runtime service is not configured");
13131
+ return ctx.services.agents.cancel({ sessionId: requiredString(inputs.sessionId, "sessionId"), reason: String(inputs.reason || "") });
13132
+ }
13133
+ });
13134
+ registerSemanticAction({
13135
+ type: "qi/evidence.collect",
13136
+ can: "evidence/collect",
13137
+ kinds: ["question", "evaluation"],
13138
+ recordType: "org.ixo.topic.evidence",
13139
+ requiredServices: ["evidence"],
13140
+ sensitiveOutputPaths: ["record.evidence"],
13141
+ inputSchema: {
13142
+ type: "object",
13143
+ required: ["question", "sourceReferences"],
13144
+ additionalProperties: false,
13145
+ properties: { question: { type: "string" }, sourceReferences: { type: "array", items: { type: "string" } }, constraints: { type: "object" } }
13146
+ },
13147
+ execute: async (inputs, ctx) => {
13148
+ if (!ctx.services.evidence) throw new Error("Evidence service is not configured");
13149
+ const result = await ctx.services.evidence.collect({
13150
+ question: requiredString(inputs.question, "question"),
13151
+ sourceReferences: requiredArray(inputs.sourceReferences, "sourceReferences"),
13152
+ constraints: inputs.constraints
13153
+ });
13154
+ return {
13155
+ question: inputs.question,
13156
+ evidence: result.evidence,
13157
+ provenance: result.provenance,
13158
+ evidenceReferences: result.evidenceReferences,
13159
+ evidenceDigest: sha256Digest(result.evidence)
13160
+ };
13161
+ }
13162
+ });
13163
+ for (const accepted of [false, true]) {
13164
+ registerSemanticAction({
13165
+ type: accepted ? "qi/answer.accept" : "qi/answer.propose",
13166
+ can: accepted ? "answer/accept" : "answer/propose",
13167
+ kinds: ["question"],
13168
+ recordType: accepted ? "org.ixo.topic.accepted-answer" : "org.ixo.topic.proposed-answer",
13169
+ confirmation: accepted,
13170
+ owner: accepted ? "human" : "agent",
13171
+ inputSchema: {
13172
+ type: "object",
13173
+ required: accepted ? ["answer", "acceptanceAuthorityDid", "proposedAnswerRecordId"] : ["answer"],
13174
+ additionalProperties: false,
13175
+ properties: {
13176
+ answer: { type: "string" },
13177
+ proposedAnswerRecordId: { type: "string" },
13178
+ acceptanceAuthorityDid: { type: "string", pattern: "^did:" },
13179
+ evidenceReferences: { type: "array", items: { type: "string" } },
13180
+ limitations: { type: "string" }
13181
+ }
13182
+ },
13183
+ execute: (inputs, ctx) => ({
13184
+ answer: requiredString(inputs.answer, "answer"),
13185
+ status: accepted ? "accepted" : "proposed",
13186
+ proposedAnswerRecordId: String(inputs.proposedAnswerRecordId || ""),
13187
+ authorityDid: accepted ? requiredString(inputs.acceptanceAuthorityDid, "acceptanceAuthorityDid") : ctx.actorDid,
13188
+ evidenceReferences: requiredArray(inputs.evidenceReferences || [], "evidenceReferences"),
13189
+ limitations: String(inputs.limitations || ""),
13190
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString()
13191
+ })
13192
+ });
13193
+ }
13194
+ for (const review of [false, true]) {
13195
+ registerSemanticAction({
13196
+ type: review ? "qi/evaluation.review" : "qi/evaluation.run",
13197
+ can: review ? "evaluation/review" : "evaluation/run",
13198
+ kinds: ["evaluation", "claims"],
13199
+ recordType: review ? "org.ixo.topic.evaluation-review" : "org.ixo.topic.evaluation-assertion",
13200
+ requiredServices: ["evaluations"],
13201
+ confirmation: review,
13202
+ owner: review ? "human" : "agent",
13203
+ inputSchema: {
13204
+ type: "object",
13205
+ required: review ? ["assertionReference", "methodologyRevision", "rubricRevision"] : ["subjectReference", "methodologyRevision", "rubricRevision"],
13206
+ additionalProperties: true,
13207
+ properties: {
13208
+ subjectReference: { type: "string" },
13209
+ assertionReference: { type: "string" },
13210
+ methodologyRevision: { type: "string" },
13211
+ rubricRevision: { type: "string" },
13212
+ evidenceReferences: { type: "array", items: { type: "string" } }
13213
+ }
13214
+ },
13215
+ execute: async (inputs, ctx) => {
13216
+ if (!ctx.services.evaluations) throw new Error("Evaluation runtime service is not configured");
13217
+ const result = review ? await ctx.services.evaluations.review(inputs) : await ctx.services.evaluations.run(inputs);
13218
+ const value = "assertion" in result ? result.assertion : result.review;
13219
+ return {
13220
+ providerResult: value,
13221
+ assertionId: "assertionId" in result ? result.assertionId : void 0,
13222
+ reviewId: "reviewId" in result ? result.reviewId : void 0,
13223
+ methodologyRevision: inputs.methodologyRevision,
13224
+ rubricRevision: inputs.rubricRevision,
13225
+ evaluatorDid: ctx.actorDid,
13226
+ evidenceReferences: result.evidenceReferences,
13227
+ signature: result.signature
13228
+ };
13229
+ }
13230
+ });
13231
+ }
13232
+ registerSemanticAction({
13233
+ type: "qi/settlement.execute",
13234
+ can: "settlement/execute",
13235
+ kinds: ["claims"],
13236
+ recordType: "org.ixo.topic.settlement-record",
13237
+ confirmation: true,
13238
+ owner: "human",
13239
+ riskTier: "critical",
13240
+ requiredServices: ["settlement"],
13241
+ inputSchema: {
13242
+ type: "object",
13243
+ required: ["approvedClaimReference", "amount", "asset", "recipient", "policyReference", "confirmationReference"],
13244
+ additionalProperties: false,
13245
+ properties: {
13246
+ approvedClaimReference: { type: "string" },
13247
+ amount: { type: "string" },
13248
+ asset: { type: "string" },
13249
+ recipient: { type: "string" },
13250
+ policyReference: { type: "string" },
13251
+ confirmationReference: { type: "string" }
13252
+ }
13253
+ },
13254
+ execute: async (inputs, ctx) => {
13255
+ if (!ctx.services.settlement) throw new Error("Settlement service is not configured");
13256
+ return ctx.services.settlement.execute({
13257
+ approvedClaimReference: requiredString(inputs.approvedClaimReference, "approvedClaimReference"),
13258
+ amount: requiredString(inputs.amount, "amount"),
13259
+ asset: requiredString(inputs.asset, "asset"),
13260
+ recipient: requiredString(inputs.recipient, "recipient"),
13261
+ policyReference: requiredString(inputs.policyReference, "policyReference"),
13262
+ confirmationReference: requiredString(inputs.confirmationReference, "confirmationReference")
13263
+ });
13264
+ }
13265
+ });
13266
+ registerSemanticAction({
13267
+ type: "qi/incident.escalate",
13268
+ can: "incident/escalate",
13269
+ kinds: ["incident"],
13270
+ recordType: "org.ixo.topic.incident-escalation",
13271
+ confirmation: true,
13272
+ requiredServices: ["incidents"],
13273
+ inputSchema: {
13274
+ type: "object",
13275
+ required: ["severity", "affectedResources", "recipients", "summary"],
13276
+ additionalProperties: false,
13277
+ properties: {
13278
+ severity: { type: "string", enum: ["low", "medium", "high", "critical"] },
13279
+ affectedResources: { type: "array", items: { type: "string" } },
13280
+ recipients: { type: "array", items: { type: "string" } },
13281
+ evidenceReferences: { type: "array", items: { type: "string" } },
13282
+ summary: { type: "string" }
13283
+ }
13284
+ },
13285
+ execute: async (inputs, ctx) => {
13286
+ if (!ctx.services.incidents) throw new Error("Incident service is not configured");
13287
+ const payload = {
13288
+ severity: requiredString(inputs.severity, "severity"),
13289
+ affectedResources: requiredArray(inputs.affectedResources, "affectedResources"),
13290
+ recipients: requiredArray(inputs.recipients, "recipients"),
13291
+ evidenceReferences: requiredArray(inputs.evidenceReferences || [], "evidenceReferences"),
13292
+ summary: requiredString(inputs.summary, "summary")
13293
+ };
13294
+ return { ...payload, ...await ctx.services.incidents.escalate(payload) };
13295
+ }
13296
+ });
13297
+ registerSemanticAction({
13298
+ type: "qi/incident.mitigation.record",
13299
+ can: "incident/mitigation.record",
13300
+ kinds: ["incident"],
13301
+ recordType: "org.ixo.topic.incident-mitigation",
13302
+ inputSchema: {
13303
+ type: "object",
13304
+ required: ["mitigation", "affectedResources"],
13305
+ additionalProperties: false,
13306
+ properties: {
13307
+ mitigation: { type: "string" },
13308
+ affectedResources: { type: "array", items: { type: "string" } },
13309
+ evidenceReferences: { type: "array", items: { type: "string" } },
13310
+ occurredAt: { type: "string" }
13311
+ }
13312
+ },
13313
+ execute: (inputs, ctx) => ({
13314
+ mitigation: requiredString(inputs.mitigation, "mitigation"),
13315
+ affectedResources: requiredArray(inputs.affectedResources, "affectedResources"),
13316
+ evidenceReferences: requiredArray(inputs.evidenceReferences || [], "evidenceReferences"),
13317
+ recordedBy: ctx.actorDid,
13318
+ occurredAt: String(inputs.occurredAt || (/* @__PURE__ */ new Date()).toISOString())
13319
+ })
13320
+ });
13321
+
11344
13322
  // src/core/lib/actionRegistry/actions/calendar/eventCreate.types.ts
11345
13323
  var EMPTY = {
11346
13324
  connection: null,
@@ -11394,7 +13372,13 @@ function parseAttendeesField(raw) {
11394
13372
  // src/core/lib/actionRegistry/actions/calendar/eventCreate.ts
11395
13373
  var CALENDAR_EVENT_CREATE_SLUG = "GOOGLECALENDAR_CREATE_EVENT";
11396
13374
  registerAction({
11397
- type: "qi/calendar.event.create",
13375
+ // Provider-explicit identity (IXO-4420 §5): this block is Google Calendar
13376
+ // via Composio with a per-runner pinned connection — `-self` distinguishes
13377
+ // it from the delegated `qi/googlecalendar.event.create`. The retired
13378
+ // `qi/calendar.event.create` name resolves here via ACTION_TYPE_ALIASES.
13379
+ // The `can` deliberately keeps its historical value so existing UCAN grants
13380
+ // keep matching; `qi/ixo.calendar.event.*` (M4) gets its own can namespace.
13381
+ type: "qi/googlecalendar.event.create-self",
11398
13382
  can: "calendar.event/create",
11399
13383
  sideEffect: true,
11400
13384
  proof: { fields: ["eventId"] },
@@ -11503,7 +13487,9 @@ registerAction({
11503
13487
  var CALENDAR_EVENT_UPDATE_SLUG = "GOOGLECALENDAR_UPDATE_EVENT";
11504
13488
  var CALENDAR_EVENT_GET_SLUG = "GOOGLECALENDAR_EVENTS_GET";
11505
13489
  registerAction({
11506
- type: "qi/calendar.event.update",
13490
+ // Provider-explicit identity (IXO-4420 §5); `qi/calendar.event.update` is
13491
+ // a permanent alias. `can` keeps its historical value — see eventCreate.ts.
13492
+ type: "qi/googlecalendar.event.update-self",
11507
13493
  can: "calendar.event/update",
11508
13494
  sideEffect: true,
11509
13495
  proof: { fields: ["eventId"] },
@@ -11614,7 +13600,9 @@ registerAction({
11614
13600
  // src/core/lib/actionRegistry/actions/calendar/eventList.ts
11615
13601
  var CALENDAR_EVENT_LIST_SLUG = "GOOGLECALENDAR_EVENTS_LIST";
11616
13602
  registerAction({
11617
- type: "qi/calendar.event.list",
13603
+ // Provider-explicit identity (IXO-4420 §5); `qi/calendar.event.list` is
13604
+ // a permanent alias. `can` keeps its historical value — see eventCreate.ts.
13605
+ type: "qi/googlecalendar.event.list-self",
11618
13606
  can: "calendar.event/list",
11619
13607
  sideEffect: false,
11620
13608
  proof: "none",
@@ -12424,7 +14412,7 @@ registerDiffResolver("evaluateClaim", {
12424
14412
  });
12425
14413
 
12426
14414
  // src/core/lib/actionRegistry/actions/calendar/eventCreate.diff.ts
12427
- registerDiffResolver("qi/calendar.event.create", {
14415
+ registerDiffResolver("qi/googlecalendar.event.create-self", {
12428
14416
  resolver: async (inputs, _ctx) => {
12429
14417
  const attendees = parseAttendeesField(String(inputs.attendees || ""));
12430
14418
  const calendarId = String(inputs.calendar_id || "").trim() || "primary";
@@ -12505,7 +14493,7 @@ registerDiffResolver("qi/calendar.event.create", {
12505
14493
  });
12506
14494
 
12507
14495
  // src/core/lib/actionRegistry/actions/calendar/eventUpdate.diff.ts
12508
- registerDiffResolver("qi/calendar.event.update", {
14496
+ registerDiffResolver("qi/googlecalendar.event.update-self", {
12509
14497
  resolver: async (inputs, ctx) => {
12510
14498
  const connection = inputs.connection || {};
12511
14499
  const connectedAccountId = connection.connectedAccountId;
@@ -12761,14 +14749,79 @@ registerDiffResolver("qi/xero.payment.create", {
12761
14749
  }
12762
14750
  });
12763
14751
 
14752
+ // src/core/utils/tokenAmount.ts
14753
+ var DECIMAL_RE = /^-?\d*(\.\d*)?$/;
14754
+ function toBaseUnits(displayAmount, exponent) {
14755
+ const raw = String(displayAmount ?? "").trim();
14756
+ if (!raw) return { ok: false, error: "Enter an amount" };
14757
+ if (!DECIMAL_RE.test(raw)) return { ok: false, error: `\u201C${raw}\u201D is not a valid amount` };
14758
+ if (raw.startsWith("-")) return { ok: false, error: "Amount must be greater than 0" };
14759
+ if (!Number.isInteger(exponent) || exponent < 0) return { ok: false, error: `Unknown decimals for this token` };
14760
+ const [whole = "", fraction = ""] = raw.split(".");
14761
+ if (fraction.length > exponent) {
14762
+ return {
14763
+ ok: false,
14764
+ error: exponent === 0 ? "This token has no decimal places \u2014 enter a whole number" : `This token has at most ${exponent} decimal places`
14765
+ };
14766
+ }
14767
+ const shifted = `${whole}${fraction.padEnd(exponent, "0")}`.replace(/^0+(?=\d)/, "");
14768
+ const value = shifted === "" ? "0" : shifted;
14769
+ if (value === "0") return { ok: false, error: "Amount must be greater than 0" };
14770
+ return { ok: true, value };
14771
+ }
14772
+ function toDisplayUnits(baseAmount, exponent) {
14773
+ const raw = String(baseAmount ?? "").trim();
14774
+ if (!raw || !DECIMAL_RE.test(raw)) return "0";
14775
+ if (!Number.isInteger(exponent) || exponent <= 0) return raw.replace(/\..*$/, "");
14776
+ const negative = raw.startsWith("-");
14777
+ const digits = (negative ? raw.slice(1) : raw).replace(/\..*$/, "").padStart(exponent + 1, "0");
14778
+ const whole = digits.slice(0, digits.length - exponent).replace(/^0+(?=\d)/, "");
14779
+ const fraction = digits.slice(digits.length - exponent).replace(/0+$/, "");
14780
+ return `${negative ? "-" : ""}${whole}${fraction ? `.${fraction}` : ""}`;
14781
+ }
14782
+ function formatTokenAmount(baseAmount, denom, exponent, symbol) {
14783
+ if (exponent === void 0 || exponent === null) return `${baseAmount} ${denom}`;
14784
+ return `${toDisplayUnits(baseAmount, exponent)} ${symbol || denom}`;
14785
+ }
14786
+
12764
14787
  // src/core/lib/actionRegistry/actions/walletFund.diff.ts
14788
+ var FALLBACK_DENOM = "uixo";
14789
+ async function describeToken(ctx, walletAddress, denom) {
14790
+ if (!walletAddress || !ctx.handlers?.getBalances) return {};
14791
+ try {
14792
+ const res = await ctx.handlers.getBalances(walletAddress);
14793
+ const match = (res?.data || []).find((b) => b.denom === denom);
14794
+ return { symbol: match?.tokenName, exponent: match?.exponent };
14795
+ } catch {
14796
+ return {};
14797
+ }
14798
+ }
12765
14799
  registerDiffResolver("qi/wallet.fund", {
12766
- resolver: async (inputs, _ctx) => {
14800
+ resolver: async (inputs, ctx) => {
12767
14801
  const address = String(inputs.address || "").trim();
12768
14802
  const amount = String(inputs.amount || "250000").trim();
12769
- const network = String(inputs.network || "devnet").trim();
12770
- const ixoAmount = (Number(amount) / 1e6).toFixed(6);
14803
+ const denom = String(inputs.denom || "").trim() || FALLBACK_DENOM;
14804
+ const fromAddress = String(inputs.fromAddress || "").trim();
14805
+ const signerAddress = (() => {
14806
+ try {
14807
+ return ctx.handlers?.getCurrentUser?.()?.address || "";
14808
+ } catch {
14809
+ return "";
14810
+ }
14811
+ })();
14812
+ const source = fromAddress || signerAddress;
14813
+ const { symbol, exponent } = await describeToken(ctx, source, denom);
12771
14814
  return [
14815
+ {
14816
+ key: "from",
14817
+ label: "From",
14818
+ before: "N/A",
14819
+ // Naming the mechanism matters: spending another wallet's tokens is an
14820
+ // authz exec, and the signer should see that before they slide.
14821
+ after: fromAddress ? `${fromAddress} (authorized send)` : source ? `${source} (your wallet)` : "Your wallet",
14822
+ changeType: "replace",
14823
+ severity: fromAddress ? "warning" : "info"
14824
+ },
12772
14825
  {
12773
14826
  key: "recipient",
12774
14827
  label: "Recipient",
@@ -12779,17 +14832,10 @@ registerDiffResolver("qi/wallet.fund", {
12779
14832
  {
12780
14833
  key: "amount",
12781
14834
  label: "Amount",
12782
- before: "0 IXO",
12783
- after: `${ixoAmount} IXO (${amount} uixo)`,
14835
+ before: "0",
14836
+ after: exponent === void 0 ? `${amount} ${denom}` : `${formatTokenAmount(amount, denom, exponent, symbol)} (${amount} ${denom})`,
12784
14837
  changeType: "replace",
12785
14838
  severity: "info"
12786
- },
12787
- {
12788
- key: "network",
12789
- label: "Network",
12790
- before: network,
12791
- after: network,
12792
- changeType: "unchanged"
12793
14839
  }
12794
14840
  ];
12795
14841
  }
@@ -13302,7 +15348,7 @@ registerDiffResolver(EVAL_ENGINE_ACTION_TYPE, {
13302
15348
  key: "decisions",
13303
15349
  label: "Decisions",
13304
15350
  before: null,
13305
- after: inputs?.allowChainEvaluation !== false ? "Submitted on chain, which releases payment \u2014 you grant the engine evaluator rights" : "Recorded only \u2014 nothing is submitted on chain",
15351
+ after: inputs?.allowChainEvaluation === false ? "Recorded only \u2014 nothing is submitted on chain" : inputs?.allowZeroPayoutApprovals === true ? "Submitted on chain, including approvals that pay nothing \u2014 you grant the engine evaluator rights" : "Submitted on chain, which releases payment \u2014 you grant the engine evaluator rights",
13306
15352
  changeType: "add"
13307
15353
  }
13308
15354
  ];
@@ -14012,7 +16058,10 @@ function resolveReferencesDetailed(input, editorDocument, options = {}) {
14012
16058
  }
14013
16059
  if (warnContext && unresolved.length > 0) {
14014
16060
  for (const entry of unresolved) {
14015
- warnOnce(`ref-unresolved:${warnContext}:${entry.ref}`, `[flow-config] ${warnContext}: reference ${entry.ref} did not resolve (${entry.reason}); using fallback '${fallback}'`);
16061
+ warnOnce(
16062
+ `ref-unresolved:${warnContext}:${entry.ref}`,
16063
+ `[flow-config] ${warnContext}: reference ${entry.ref} did not resolve (${entry.reason}); using fallback '${fallback}'`
16064
+ );
14016
16065
  }
14017
16066
  }
14018
16067
  return { value: result, unresolved };
@@ -14850,8 +16899,8 @@ function fnv1a322(input, seed) {
14850
16899
  }
14851
16900
  function createRunEventIdempotencyKey(kind, ...identity) {
14852
16901
  const canonical = JSON.stringify([kind, ...identity]);
14853
- const digest = `${fnv1a322(canonical, 2166136261)}${fnv1a322(canonical, 2654435761)}`;
14854
- return `v1:${kind.replace(/\./g, "_")}:${digest}`;
16902
+ const digest2 = `${fnv1a322(canonical, 2166136261)}${fnv1a322(canonical, 2654435761)}`;
16903
+ return `v1:${kind.replace(/\./g, "_")}:${digest2}`;
14855
16904
  }
14856
16905
  function boundRunActionOutput(value) {
14857
16906
  const byteLength = jsonByteLength(value);
@@ -17146,10 +19195,35 @@ async function executeActionBlock(params) {
17146
19195
  let requestedAwaitingReadBack = false;
17147
19196
  let proofFailureReason = null;
17148
19197
  let proofFailureOutput;
19198
+ let topicRecords = [];
17149
19199
  const startedAt = now();
17150
19200
  const previousState = runtime.get(blockId);
17151
19201
  const attempt = (previousState.attempt || 0) + 1;
17152
19202
  const executionId = makeExecutionId2(now);
19203
+ const recordTopicPhase = async (status, options = {}) => {
19204
+ if (!params.topic || !params.topicBridge) return void 0;
19205
+ try {
19206
+ return await params.topicBridge.recordFlowPhase({
19207
+ topic: params.topic,
19208
+ actionType,
19209
+ actorDid: params.actorDid,
19210
+ executorDid: params.executorDid || params.actorDid,
19211
+ executionId,
19212
+ status,
19213
+ input: inputBuild.inputs,
19214
+ output: options.output,
19215
+ semanticRecords: topicRecords,
19216
+ flowUri,
19217
+ sessionRunId,
19218
+ nodeId: blockId,
19219
+ invocationReference: options.invocationReference,
19220
+ traceReference: sessionRunId ? `${flowUri}/session/${sessionRunId}/execution/${executionId}` : `${flowUri}/execution/${executionId}`,
19221
+ error: options.error
19222
+ });
19223
+ } catch (error) {
19224
+ return { state: "queued", error: error instanceof Error ? error.message : "Topic receipt bridge failed" };
19225
+ }
19226
+ };
17153
19227
  const timelineRequired = !!yDoc && !usesLegacyRuntimeCompatibility(yDoc);
17154
19228
  if (sessionRunId && (eventLog || timelineRequired)) {
17155
19229
  const startedLogged = await appendRunTimelineEvent({
@@ -17198,6 +19272,18 @@ async function executeActionBlock(params) {
17198
19272
  executionId,
17199
19273
  executionStartedAt: startedAt
17200
19274
  });
19275
+ const runningWriteBack = await recordTopicPhase("running");
19276
+ if (runningWriteBack?.state === "rejected") {
19277
+ const message = runningWriteBack.error || "Topic Action execution was rejected by the receipt bridge.";
19278
+ updateRuntimeFailure(runtime, blockId, message, now);
19279
+ return {
19280
+ ...buildFailureResult({ blockId, actionType, stage: "authorization", error: message, pendingInvocation: inputBuild.pendingInvocation }),
19281
+ executionId,
19282
+ runId: executionId,
19283
+ topicWriteBack: runningWriteBack
19284
+ };
19285
+ }
19286
+ const actionServices = action.type.startsWith("qi/topic.") ? params.services || {} : { ...params.services || {}, topic: void 0 };
17201
19287
  const outcome = await executeNode({
17202
19288
  node: flowNode,
17203
19289
  actorDid: params.actorDid,
@@ -17223,13 +19309,16 @@ async function executeActionBlock(params) {
17223
19309
  nodeId: blockId,
17224
19310
  flowNode,
17225
19311
  runtime,
17226
- services: params.services || {},
19312
+ services: actionServices,
17227
19313
  handlers: params.handlers,
17228
19314
  editor,
17229
19315
  yDoc,
17230
- pendingInvocation: inputBuild.pendingInvocation
19316
+ pendingInvocation: inputBuild.pendingInvocation,
19317
+ topic: params.topic,
19318
+ flowRevision: params.flowRevision
17231
19319
  });
17232
19320
  if (result.events?.length) events.push(...result.events);
19321
+ if (result.topicRecords?.length) topicRecords = result.topicRecords;
17233
19322
  if (result.completion?.state === "awaiting_readback") {
17234
19323
  requestedAwaitingReadBack = true;
17235
19324
  rawReadBack = result.completion.readBack;
@@ -17292,7 +19381,12 @@ async function executeActionBlock(params) {
17292
19381
  invocationCid: outcome.invocationCid,
17293
19382
  capabilityId: outcome.capabilityId,
17294
19383
  executionId,
17295
- runId: executionId
19384
+ runId: executionId,
19385
+ topicWriteBack: await recordTopicPhase(proofFailureState === "needs_verification" ? "needs_verification" : "failed", {
19386
+ output: proofFailureOutput,
19387
+ error: { code: PROOF_MISSING_CODE, message },
19388
+ invocationReference: outcome.invocationCid || outcome.capabilityId
19389
+ })
17296
19390
  };
17297
19391
  }
17298
19392
  updateRuntimeFailure(runtime, blockId, message, now);
@@ -17317,6 +19411,10 @@ async function executeActionBlock(params) {
17317
19411
  now
17318
19412
  });
17319
19413
  }
19414
+ const topicWriteBack2 = await recordTopicPhase(outcome.stage === "authorization" ? "rejected" : "failed", {
19415
+ error: { message },
19416
+ invocationReference: outcome.invocationCid || outcome.capabilityId
19417
+ });
17320
19418
  return {
17321
19419
  ...buildFailureResult({
17322
19420
  blockId,
@@ -17328,7 +19426,8 @@ async function executeActionBlock(params) {
17328
19426
  invocationCid: outcome.invocationCid,
17329
19427
  capabilityId: outcome.capabilityId,
17330
19428
  executionId,
17331
- runId: executionId
19429
+ runId: executionId,
19430
+ topicWriteBack: topicWriteBack2
17332
19431
  };
17333
19432
  }
17334
19433
  const output = outcome.result?.payload || {};
@@ -17441,6 +19540,11 @@ async function executeActionBlock(params) {
17441
19540
  });
17442
19541
  }
17443
19542
  const pendingInvocationRemoved = completionState === "completed" ? cleanupCompletedPendingInvocation(yDoc, blockId, inputBuild.pendingInvocation, sessionRunId) : false;
19543
+ const topicWriteBack = await recordTopicPhase(completionState === "completed" ? "succeeded" : completionState === "needs_verification" ? "needs_verification" : "failed", {
19544
+ output,
19545
+ ...boundedOutput.exceeded ? { error: { code: "RUN_OUTPUT_TOO_LARGE", message: `Action output exceeded the ${MAX_RUN_ACTION_OUTPUT_BYTES / 1024} KiB run-head limit.` } } : {},
19546
+ invocationReference: outcome.invocationCid || outcome.capabilityId
19547
+ });
17444
19548
  return {
17445
19549
  success: !boundedOutput.exceeded,
17446
19550
  stage: outcome.stage,
@@ -17458,7 +19562,8 @@ async function executeActionBlock(params) {
17458
19562
  executionId,
17459
19563
  pendingInvocationRemoved,
17460
19564
  completionState,
17461
- pendingInvocation: inputBuild.pendingInvocation
19565
+ pendingInvocation: inputBuild.pendingInvocation,
19566
+ topicWriteBack
17462
19567
  };
17463
19568
  }
17464
19569
 
@@ -18160,6 +20265,17 @@ function compileBlockProps(cap, registryType) {
18160
20265
  var COMPILED_BLOCK_TYPE = "action";
18161
20266
 
18162
20267
  // src/core/lib/flowCompiler/compiler.ts
20268
+ function stripActiveScheduleBindings(plan) {
20269
+ let changed = false;
20270
+ const capabilities = plan.capabilities.map((cap) => {
20271
+ if (cap.trigger?.type !== "schedule") return cap;
20272
+ if (cap.trigger.scheduleRef === void 0 && cap.trigger.scheduleRevision === void 0) return cap;
20273
+ changed = true;
20274
+ const { scheduleRef: _ref, scheduleRevision: _rev, ...inactive } = cap.trigger;
20275
+ return { ...cap, trigger: inactive };
20276
+ });
20277
+ return changed ? { ...plan, capabilities } : plan;
20278
+ }
18163
20279
  function compileBaseUcanFlow(plan, registry) {
18164
20280
  if (!Array.isArray(plan.capabilities)) {
18165
20281
  throw new Error("BaseUcanFlow.capabilities must be an array");
@@ -18257,6 +20373,21 @@ function compileBaseUcanFlow(plan, registry) {
18257
20373
  });
18258
20374
  }
18259
20375
  }
20376
+ if (trigger.type === "schedule") {
20377
+ if (action.eligibleForTimeTrigger !== true) {
20378
+ throw new Error(
20379
+ `Block "${nodeId}" is configured with a schedule trigger, but its action type "${action.type}" is not marked eligibleForTimeTrigger. Set eligibleForTimeTrigger: true on the action definition or change the trigger.`
20380
+ );
20381
+ }
20382
+ if (trigger.sourceBlockId || trigger.sources || trigger.eventName) {
20383
+ throw new Error(
20384
+ `Block "${nodeId}" has a schedule trigger carrying event-trigger fields (sourceBlockId/eventName/sources). A schedule trigger holds only scheduleRef and scheduleRevision \u2014 timing lives in the referenced ScheduleSpec, never in the block.`
20385
+ );
20386
+ }
20387
+ if (trigger.scheduleRevision !== void 0 && (!Number.isInteger(trigger.scheduleRevision) || trigger.scheduleRevision < 1)) {
20388
+ throw new Error(`Block "${nodeId}" has a schedule trigger with an invalid scheduleRevision (must be an integer >= 1).`);
20389
+ }
20390
+ }
18260
20391
  if (trigger.type === "block.event" || trigger.type === "block.event.all") {
18261
20392
  const refs = collectOutputRefs(cap.nb || {});
18262
20393
  for (const ref of refs) {
@@ -18974,7 +21105,8 @@ function readFlow() {
18974
21105
  }
18975
21106
  async function setupFlowFromBaseUcan(options) {
18976
21107
  const { plan: rawPlan, roomId, matrixClient, creatorDid, docId, templateId, strategy = "full" } = options;
18977
- const plan = rawPlan.flowId ? rawPlan : { ...rawPlan, flowId: docId || roomId };
21108
+ const identified = rawPlan.flowId ? rawPlan : { ...rawPlan, flowId: docId || roomId };
21109
+ const plan = templateId ? stripActiveScheduleBindings(identified) : identified;
18978
21110
  const incomingCompiled = compileBaseUcanFlow(plan, { getActionByCan });
18979
21111
  const { yDoc, provider } = await connectToRoom(roomId, matrixClient, { adoptRuns: true });
18980
21112
  let finalCompiled;
@@ -20290,10 +22422,17 @@ export {
20290
22422
  matrixUserIdToDid,
20291
22423
  findOrCreateDMRoom,
20292
22424
  sendDirectMessage,
22425
+ canonicalActionJson,
22426
+ sha256Digest,
22427
+ MAX_TOPIC_SEMANTIC_RECORDS_PER_RECEIPT,
22428
+ MAX_TOPIC_SEMANTIC_RECORD_BATCH_BYTES,
22429
+ validateTopicSemanticRecord,
22430
+ validateTopicSemanticRecordBatch,
20293
22431
  canToType,
20294
22432
  typeToCan,
20295
22433
  getAllCanMappings,
20296
22434
  warnOnce,
22435
+ getActionPresentation,
20297
22436
  STEP_COMPLETED_EVENT_NAME,
20298
22437
  STEP_COMPLETED_EVENT,
20299
22438
  doneWhenCompleted,
@@ -20312,7 +22451,10 @@ export {
20312
22451
  getActionByCan,
20313
22452
  getEventsForBlock,
20314
22453
  getOutputSchemaForBlock,
22454
+ ACTION_MANIFEST_VERSION,
22455
+ ACTION_REGISTRY_VERSION,
20315
22456
  generateActionManifest,
22457
+ actionManifestIssues,
20316
22458
  isBlankInputValue,
20317
22459
  getMissingActionInputs,
20318
22460
  SERVICE_VERBS,
@@ -20361,6 +22503,7 @@ export {
20361
22503
  DIFFERENT_WHEN_OTHER_MAX,
20362
22504
  normalizeDifferentWhen,
20363
22505
  normalizeRepeatSubmissions,
22506
+ FORM_SEGMENT,
20364
22507
  extractRubricFieldCatalog,
20365
22508
  buildRubricEnvelope,
20366
22509
  parsePublishedRubric,
@@ -20400,6 +22543,8 @@ export {
20400
22543
  renderNumber,
20401
22544
  formatCoin2 as formatCoin,
20402
22545
  formatCoinAmount,
22546
+ toBaseUnits,
22547
+ formatTokenAmount,
20403
22548
  DM_NOTIFICATIONS_MAP_KEY,
20404
22549
  getDMNotificationState,
20405
22550
  setDMNotificationRecord,
@@ -20602,4 +22747,4 @@ export {
20602
22747
  executeQueuedFlowAgentCoreCommands,
20603
22748
  FlowAgentService
20604
22749
  };
20605
- //# sourceMappingURL=chunk-ZXNBOVAA.js.map
22750
+ //# sourceMappingURL=chunk-TBCPHCFA.js.map