@ixo/editor 6.30.3 → 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.
- package/dist/action-manifest.json +16745 -3218
- package/dist/{chunk-OAAEXL7Q.js → chunk-4475SO6R.js} +2 -2
- package/dist/{chunk-XYAKFAZ3.js → chunk-FQAKOGKS.js} +2626 -942
- package/dist/chunk-FQAKOGKS.js.map +1 -0
- package/dist/{chunk-LY32OATI.js → chunk-TBCPHCFA.js} +2704 -466
- package/dist/chunk-TBCPHCFA.js.map +1 -0
- package/dist/{store-whqrkEVn.d.ts → connections-B24zj0RI.d.ts} +340 -323
- package/dist/core/index.d.ts +314 -251
- package/dist/core/index.js +455 -2
- package/dist/core/index.js.map +1 -1
- package/dist/{graphql-client-BV2YEKAc.d.ts → graphql-client-BICFLCX8.d.ts} +6 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/mantine/index.d.ts +5 -5
- package/dist/mantine/index.js +2 -2
- package/dist/mantine/index.js.map +1 -1
- package/dist/{nodeContext-C-BsYuYi.d.ts → nodeContext-CV9D4s3X.d.ts} +1 -1
- package/dist/{index-CEIunGZv.d.ts → runtime-DFPayq89.d.ts} +9366 -8691
- package/package.json +7 -5
- package/style.css.d.ts +5 -5
- package/dist/chunk-LY32OATI.js.map +0 -1
- package/dist/chunk-XYAKFAZ3.js.map +0 -1
- /package/dist/{chunk-OAAEXL7Q.js.map → chunk-4475SO6R.js.map} +0 -0
|
@@ -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
|
|
552
|
-
|
|
553
|
-
"calendar.event/
|
|
554
|
-
"calendar.event/
|
|
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
|
|
682
|
-
|
|
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
|
-
|
|
685
|
-
return;
|
|
1397
|
+
definition.done = doneWhenCompleted;
|
|
686
1398
|
}
|
|
687
|
-
actions.set(definition.type,
|
|
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
|
|
788
|
-
const
|
|
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
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
if (action.
|
|
810
|
-
if (action.
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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
|
},
|
|
@@ -1013,6 +1795,10 @@ function buildServicesFromHandlers(handlers) {
|
|
|
1013
1795
|
// results, so we only forward calls here.
|
|
1014
1796
|
collectionUsers: handlers?.collectionUsers?.grant && handlers?.collectionUsers?.revoke && handlers?.collectionUsers?.list && handlers?.collectionUsers?.classifyAddress && handlers?.collectionUsers?.enumerateMembers ? {
|
|
1015
1797
|
grant: async (params) => handlers.collectionUsers.grant(params),
|
|
1798
|
+
// Optional (IXO-4396): only hosts that can broadcast every grant in
|
|
1799
|
+
// one transaction expose it. Left undefined, the dispatcher falls
|
|
1800
|
+
// back to one `grant` per grantee.
|
|
1801
|
+
grantMany: handlers.collectionUsers.grantMany ? async (params) => handlers.collectionUsers.grantMany(params) : void 0,
|
|
1016
1802
|
revoke: async (params) => handlers.collectionUsers.revoke(params),
|
|
1017
1803
|
list: async (params) => handlers.collectionUsers.list(params),
|
|
1018
1804
|
classifyAddress: async (params) => handlers.collectionUsers.classifyAddress(params),
|
|
@@ -2208,6 +2994,182 @@ for (const spec of ACTIONS) {
|
|
|
2208
2994
|
});
|
|
2209
2995
|
}
|
|
2210
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
|
+
|
|
2211
3173
|
// src/core/lib/actionRegistry/actions/governance/memberProposal.ts
|
|
2212
3174
|
var VALID_OPERATIONS = ["add", "remove", "update-weight"];
|
|
2213
3175
|
function defaultTitle(operation, count) {
|
|
@@ -2229,6 +3191,7 @@ registerAction({
|
|
|
2229
3191
|
done: doneWhenCompleted,
|
|
2230
3192
|
defaultRequiresConfirmation: true,
|
|
2231
3193
|
requiredCapability: "flow/block/execute",
|
|
3194
|
+
inputSchema: governanceInputSchema("qi/governance.member-proposal"),
|
|
2232
3195
|
outputSchema: [
|
|
2233
3196
|
{ path: "proposalId", displayName: "Proposal ID", type: "string", description: "The on-chain proposal identifier" },
|
|
2234
3197
|
{ path: "proposalTitle", displayName: "Proposal Title", type: "string", description: "The proposal name voters see on-chain" },
|
|
@@ -2329,6 +3292,7 @@ registerAction({
|
|
|
2329
3292
|
done: doneWhenCompleted,
|
|
2330
3293
|
defaultRequiresConfirmation: true,
|
|
2331
3294
|
requiredCapability: "flow/block/execute",
|
|
3295
|
+
inputSchema: governanceInputSchema("qi/governance.settings-proposal"),
|
|
2332
3296
|
outputSchema: [
|
|
2333
3297
|
{ path: "proposalId", displayName: "Proposal ID", type: "string", description: "The on-chain proposal identifier" },
|
|
2334
3298
|
{ path: "proposalTitle", displayName: "Proposal Title", type: "string", description: "The proposal name voters see on-chain" },
|
|
@@ -2410,70 +3374,6 @@ registerAction({
|
|
|
2410
3374
|
}
|
|
2411
3375
|
});
|
|
2412
3376
|
|
|
2413
|
-
// src/core/lib/actionRegistry/actions/governance/_shared.ts
|
|
2414
|
-
var STANDARD_OUTPUT_SCHEMA = [
|
|
2415
|
-
{ path: "proposalId", displayName: "Proposal ID", type: "string", description: "The on-chain proposal identifier" },
|
|
2416
|
-
{ path: "proposalTitle", displayName: "Proposal Title", type: "string", description: "The proposal name voters see on-chain" },
|
|
2417
|
-
{ path: "proposalDescription", displayName: "Proposal Description", type: "string", description: "The long-form description voters see on-chain" },
|
|
2418
|
-
{ path: "status", displayName: "Proposal Status", type: "string", description: "Current proposal status (open, passed, rejected, executed, etc.)" },
|
|
2419
|
-
{ path: "proposalContractAddress", displayName: "Proposal Contract Address", type: "string", description: "The proposal module contract address" },
|
|
2420
|
-
{ path: "coreAddress", displayName: "Core Address", type: "string", description: "The DAO core contract address" },
|
|
2421
|
-
{ path: "createdAt", displayName: "Created At", type: "string", description: "ISO timestamp of proposal creation" }
|
|
2422
|
-
];
|
|
2423
|
-
function registerGovernanceProposalAction(spec) {
|
|
2424
|
-
registerAction({
|
|
2425
|
-
type: spec.type,
|
|
2426
|
-
can: spec.can,
|
|
2427
|
-
sideEffect: true,
|
|
2428
|
-
proof: { fields: ["proposalId"] },
|
|
2429
|
-
done: doneWhenCompleted,
|
|
2430
|
-
defaultRequiresConfirmation: true,
|
|
2431
|
-
requiredCapability: "flow/block/execute",
|
|
2432
|
-
outputSchema: [...STANDARD_OUTPUT_SCHEMA, ...spec.extraOutputSchema || []],
|
|
2433
|
-
run: async (inputs, ctx) => {
|
|
2434
|
-
const handlers = ctx.handlers;
|
|
2435
|
-
if (!handlers) {
|
|
2436
|
-
throw new Error("Handlers not available");
|
|
2437
|
-
}
|
|
2438
|
-
if (!handlers.getPreProposalContractAddress || !handlers.getGroupContractAddress || !handlers.getProposalContractAddress || !handlers.createProposal) {
|
|
2439
|
-
throw new Error("Governance proposal handlers not available");
|
|
2440
|
-
}
|
|
2441
|
-
const coreAddress = String(inputs.coreAddress || "").trim();
|
|
2442
|
-
if (!coreAddress) throw new Error("coreAddress is required");
|
|
2443
|
-
const actions2 = spec.buildActions(inputs);
|
|
2444
|
-
if (!actions2.length) throw new Error("The proposal must contain at least one action");
|
|
2445
|
-
const title = String(inputs.title || "").trim() || spec.defaultTitle(inputs);
|
|
2446
|
-
const description = String(inputs.description || "").trim() || (spec.defaultDescription ? spec.defaultDescription(inputs) : title);
|
|
2447
|
-
const { preProposalContractAddress } = await handlers.getPreProposalContractAddress({ coreAddress });
|
|
2448
|
-
const { groupContractAddress } = await handlers.getGroupContractAddress({ coreAddress });
|
|
2449
|
-
const { proposalContractAddress } = await handlers.getProposalContractAddress({ coreAddress });
|
|
2450
|
-
const proposalId = await handlers.createProposal({
|
|
2451
|
-
preProposalContractAddress,
|
|
2452
|
-
title,
|
|
2453
|
-
description,
|
|
2454
|
-
actions: actions2,
|
|
2455
|
-
coreAddress,
|
|
2456
|
-
groupContractAddress
|
|
2457
|
-
});
|
|
2458
|
-
if (proposalId === void 0 || proposalId === null || String(proposalId).trim() === "") {
|
|
2459
|
-
throw new Error("Proposal creation returned no proposal id. Check the handler logs.");
|
|
2460
|
-
}
|
|
2461
|
-
return {
|
|
2462
|
-
output: {
|
|
2463
|
-
proposalId: String(proposalId),
|
|
2464
|
-
proposalTitle: title,
|
|
2465
|
-
proposalDescription: description,
|
|
2466
|
-
status: "open",
|
|
2467
|
-
proposalContractAddress: proposalContractAddress || "",
|
|
2468
|
-
coreAddress,
|
|
2469
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2470
|
-
...spec.buildExtraOutput ? spec.buildExtraOutput(inputs) : {}
|
|
2471
|
-
}
|
|
2472
|
-
};
|
|
2473
|
-
}
|
|
2474
|
-
});
|
|
2475
|
-
}
|
|
2476
|
-
|
|
2477
3377
|
// src/core/lib/actionRegistry/actions/governance/submissionConfigProposal.ts
|
|
2478
3378
|
var REFUND_POLICIES = ["always", "only_passed", "never"];
|
|
2479
3379
|
registerGovernanceProposalAction({
|
|
@@ -2535,6 +3435,7 @@ registerAction({
|
|
|
2535
3435
|
done: doneWhenCompleted,
|
|
2536
3436
|
defaultRequiresConfirmation: true,
|
|
2537
3437
|
requiredCapability: "flow/block/execute",
|
|
3438
|
+
inputSchema: governanceInputSchema("qi/governance.transaction.send-funds"),
|
|
2538
3439
|
outputSchema: [
|
|
2539
3440
|
{ path: "proposalId", displayName: "Proposal ID", type: "string", description: "The on-chain proposal identifier" },
|
|
2540
3441
|
{ path: "proposalTitle", displayName: "Proposal Title", type: "string", description: "The proposal name voters see on-chain" },
|
|
@@ -3454,66 +4355,157 @@ registerGovernanceProposalAction({
|
|
|
3454
4355
|
});
|
|
3455
4356
|
|
|
3456
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
|
+
}
|
|
3457
4406
|
registerAction({
|
|
3458
|
-
type: "qi/http.
|
|
3459
|
-
can: "http/
|
|
4407
|
+
type: "qi/http.fetch",
|
|
4408
|
+
can: "http/fetch",
|
|
3460
4409
|
sideEffect: false,
|
|
3461
|
-
proof: { fields: ["
|
|
4410
|
+
proof: { fields: ["responseDigest"] },
|
|
3462
4411
|
done: doneWhenCompleted,
|
|
3463
4412
|
defaultRequiresConfirmation: false,
|
|
3464
|
-
// HTTP request can be triggered as a listener — a human assignee is DM'd
|
|
3465
|
-
// to invoke it when the upstream event fires. See §3.6 of the
|
|
3466
|
-
// events-and-triggers plan.
|
|
3467
4413
|
eligibleForEventTrigger: true,
|
|
3468
4414
|
inputSchema: {
|
|
3469
4415
|
type: "object",
|
|
3470
|
-
required: [
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
url: { type: "string", description: "Alias for endpoint; the request URL. Either url or endpoint satisfies the URL requirement." },
|
|
3474
|
-
method: { type: "string", description: "HTTP method (GET, POST, etc.). Defaults to GET." },
|
|
3475
|
-
headers: { type: "object", description: "Request headers as a key/value map." },
|
|
3476
|
-
body: { type: "string", description: "Request body; sent for non-GET methods. Accepts a string or an object (serialized to JSON)." }
|
|
3477
|
-
}
|
|
4416
|
+
required: [],
|
|
4417
|
+
additionalProperties: false,
|
|
4418
|
+
properties: { ...INPUT_PROPERTIES, method: { type: "string", enum: ["GET", "HEAD"], default: "GET" } }
|
|
3478
4419
|
},
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
getMissingInputs: (inputs) => String(inputs.endpoint || inputs.url || "").trim() ? [] : ["endpoint"],
|
|
4420
|
+
getMissingInputs: missingEndpoint,
|
|
4421
|
+
outputSchema: OUTPUT_SCHEMA2,
|
|
3482
4422
|
run: async (inputs, ctx) => {
|
|
3483
|
-
const
|
|
3484
|
-
const
|
|
3485
|
-
if (
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
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);
|
|
3494
4436
|
return {
|
|
3495
4437
|
output: {
|
|
4438
|
+
requestId: result.requestId || responseDigest2,
|
|
3496
4439
|
status: result.status,
|
|
4440
|
+
responseDigest: responseDigest2,
|
|
3497
4441
|
data: result.data,
|
|
3498
|
-
response: JSON.stringify(result.data, null, 2)
|
|
4442
|
+
response: JSON.stringify(result.data, null, 2),
|
|
4443
|
+
traceReference: result.traceReference || ""
|
|
3499
4444
|
}
|
|
3500
4445
|
};
|
|
3501
4446
|
}
|
|
3502
|
-
const
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
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 {
|
|
3508
4453
|
}
|
|
3509
|
-
const
|
|
3510
|
-
const data = await response.json().catch(() => ({}));
|
|
4454
|
+
const responseDigest = sha256Digest(data);
|
|
3511
4455
|
return {
|
|
3512
4456
|
output: {
|
|
4457
|
+
requestId: responseDigest,
|
|
3513
4458
|
status: response.status,
|
|
4459
|
+
responseDigest,
|
|
3514
4460
|
data,
|
|
3515
|
-
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."
|
|
3516
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 || "" }
|
|
3517
4509
|
};
|
|
3518
4510
|
}
|
|
3519
4511
|
});
|
|
@@ -3598,7 +4590,7 @@ registerAction({
|
|
|
3598
4590
|
type: "qi/human.checkbox.set",
|
|
3599
4591
|
can: "human/checkbox",
|
|
3600
4592
|
sideEffect: true,
|
|
3601
|
-
proof: "
|
|
4593
|
+
proof: { fields: ["attestationId"] },
|
|
3602
4594
|
done: doneWhenCompleted,
|
|
3603
4595
|
defaultRequiresConfirmation: false,
|
|
3604
4596
|
requiredCapability: "flow/execute",
|
|
@@ -3609,9 +4601,17 @@ registerAction({
|
|
|
3609
4601
|
checked: { type: "boolean", description: "Whether the checkbox should be checked (defaults to true)." }
|
|
3610
4602
|
}
|
|
3611
4603
|
},
|
|
3612
|
-
|
|
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) => {
|
|
3613
4611
|
const checked = inputs.checked !== void 0 ? !!inputs.checked : true;
|
|
3614
|
-
|
|
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 } };
|
|
3615
4615
|
}
|
|
3616
4616
|
});
|
|
3617
4617
|
|
|
@@ -3641,7 +4641,7 @@ function registerFormSubmitAction(type, can) {
|
|
|
3641
4641
|
type,
|
|
3642
4642
|
can,
|
|
3643
4643
|
sideEffect: true,
|
|
3644
|
-
proof: "
|
|
4644
|
+
proof: { fields: ["submissionId"] },
|
|
3645
4645
|
done: doneWhenCompleted,
|
|
3646
4646
|
defaultRequiresConfirmation: false,
|
|
3647
4647
|
requiredCapability: "flow/execute",
|
|
@@ -3658,7 +4658,11 @@ function registerFormSubmitAction(type, can) {
|
|
|
3658
4658
|
},
|
|
3659
4659
|
outputSchema: [
|
|
3660
4660
|
{ path: "form.answers", displayName: "Form Answers JSON", type: "string", description: "JSON stringified form answers, matching form block runtime output." },
|
|
3661
|
-
{ 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" }
|
|
3662
4666
|
],
|
|
3663
4667
|
events: [
|
|
3664
4668
|
{
|
|
@@ -3669,15 +4673,22 @@ function registerFormSubmitAction(type, can) {
|
|
|
3669
4673
|
pendingDisplayFields: ["answers"]
|
|
3670
4674
|
}
|
|
3671
4675
|
],
|
|
3672
|
-
run: async (inputs) => {
|
|
4676
|
+
run: async (inputs, ctx) => {
|
|
3673
4677
|
const answers = normalizeAnswers(inputs.answers ?? inputs.form?.answers);
|
|
3674
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 });
|
|
3675
4682
|
return {
|
|
3676
4683
|
output: {
|
|
3677
4684
|
form: {
|
|
3678
4685
|
answers: answersJson
|
|
3679
4686
|
},
|
|
3680
|
-
answers
|
|
4687
|
+
answers,
|
|
4688
|
+
submissionId,
|
|
4689
|
+
answersDigest,
|
|
4690
|
+
submittedAt,
|
|
4691
|
+
submittedBy: ctx.actorDid
|
|
3681
4692
|
},
|
|
3682
4693
|
events: [{ name: "form.submitted", payload: { answers } }]
|
|
3683
4694
|
};
|
|
@@ -3706,20 +4717,39 @@ registerAction({
|
|
|
3706
4717
|
outputSchema: [
|
|
3707
4718
|
{ path: "runId", displayName: "Session run id", type: "string" },
|
|
3708
4719
|
{ path: "eventId", displayName: "Started event id", type: "string" },
|
|
3709
|
-
{ 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
|
+
}
|
|
3710
4735
|
],
|
|
3711
4736
|
run: async (inputs, ctx) => {
|
|
3712
4737
|
if (!ctx.services.flowRuns?.start) {
|
|
3713
4738
|
throw new Error("flowRuns.start handler not available");
|
|
3714
4739
|
}
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
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 || ""
|
|
3722
4751
|
};
|
|
4752
|
+
return { output, events: [{ name: "flow.run.started", payload: { runId: lifecycle.runId, topicBindingId: output.topicBindingId } }] };
|
|
3723
4753
|
}
|
|
3724
4754
|
});
|
|
3725
4755
|
registerAction({
|
|
@@ -3744,7 +4774,22 @@ registerAction({
|
|
|
3744
4774
|
{ path: "status", displayName: "Terminal status", type: "string" },
|
|
3745
4775
|
{ path: "eventId", displayName: "Terminal event id", type: "string" },
|
|
3746
4776
|
{ path: "closedAt", displayName: "Closed at", type: "number" },
|
|
3747
|
-
{ 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
|
+
}
|
|
3748
4793
|
],
|
|
3749
4794
|
run: async (inputs, ctx) => {
|
|
3750
4795
|
if (!ctx.sessionRunId) {
|
|
@@ -3754,24 +4799,30 @@ registerAction({
|
|
|
3754
4799
|
throw new Error("flowRuns lifecycle handler not available");
|
|
3755
4800
|
}
|
|
3756
4801
|
if (inputs.cancel === true) {
|
|
3757
|
-
|
|
3758
|
-
output: await ctx.services.flowRuns.cancel({
|
|
3759
|
-
actorDid: ctx.actorDid,
|
|
3760
|
-
flowId: ctx.flowId,
|
|
3761
|
-
flowUri: ctx.flowUri,
|
|
3762
|
-
runId: ctx.sessionRunId,
|
|
3763
|
-
...typeof inputs.reason === "string" && inputs.reason ? { reason: inputs.reason } : {}
|
|
3764
|
-
})
|
|
3765
|
-
};
|
|
3766
|
-
}
|
|
3767
|
-
return {
|
|
3768
|
-
output: await ctx.services.flowRuns.close({
|
|
4802
|
+
const lifecycle2 = await ctx.services.flowRuns.cancel({
|
|
3769
4803
|
actorDid: ctx.actorDid,
|
|
3770
4804
|
flowId: ctx.flowId,
|
|
3771
4805
|
flowUri: ctx.flowUri,
|
|
3772
4806
|
runId: ctx.sessionRunId,
|
|
3773
|
-
|
|
3774
|
-
})
|
|
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 } }]
|
|
3775
4826
|
};
|
|
3776
4827
|
}
|
|
3777
4828
|
});
|
|
@@ -3800,6 +4851,10 @@ registerAction({
|
|
|
3800
4851
|
replyTo: { type: "string", description: "Reply-to address." }
|
|
3801
4852
|
}
|
|
3802
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
|
+
],
|
|
3803
4858
|
run: async (inputs, ctx) => {
|
|
3804
4859
|
if (!ctx.services.notify) {
|
|
3805
4860
|
throw new Error("Notification service not configured");
|
|
@@ -4453,6 +5508,22 @@ registerAction({
|
|
|
4453
5508
|
};
|
|
4454
5509
|
return {
|
|
4455
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,
|
|
4456
5527
|
events: [
|
|
4457
5528
|
{
|
|
4458
5529
|
name: "submitted",
|
|
@@ -4807,7 +5878,7 @@ registerAction({
|
|
|
4807
5878
|
const flowId = String(ctx.flowId || ctx.flowUri || "flow");
|
|
4808
5879
|
const claimSnapshot = inputs.claimSnapshot && typeof inputs.claimSnapshot === "object" && !Array.isArray(inputs.claimSnapshot) ? inputs.claimSnapshot : void 0;
|
|
4809
5880
|
const surveyQuestions = Array.isArray(claimSnapshot?.surveyQuestions) ? claimSnapshot.surveyQuestions : Array.isArray(inputs?.surveyAnswersSchema) ? inputs.surveyAnswersSchema : [];
|
|
4810
|
-
const
|
|
5881
|
+
const idempotencyKey2 = buildXeroInvoiceWorkKey({ flowId, evaluationBlockId: ctx.nodeId, claimId });
|
|
4811
5882
|
const originalPayload = {
|
|
4812
5883
|
claim: { claimId, collectionId, deedDid },
|
|
4813
5884
|
surveyQuestions,
|
|
@@ -4823,11 +5894,11 @@ registerAction({
|
|
|
4823
5894
|
invoiceDefaults: buildXeroInvoiceDefaults(inputs.xeroInvoiceDefaults)
|
|
4824
5895
|
};
|
|
4825
5896
|
upsertXeroWorkItemForEditor(ctx.editor, {
|
|
4826
|
-
id:
|
|
5897
|
+
id: idempotencyKey2,
|
|
4827
5898
|
kind: "invoice.create",
|
|
4828
5899
|
status: "pending",
|
|
4829
5900
|
assignedBlockId: ctx.nodeId,
|
|
4830
|
-
idempotencyKey,
|
|
5901
|
+
idempotencyKey: idempotencyKey2,
|
|
4831
5902
|
source: {
|
|
4832
5903
|
claimId,
|
|
4833
5904
|
evaluationBlockId: ctx.nodeId,
|
|
@@ -4867,6 +5938,25 @@ registerAction({
|
|
|
4867
5938
|
};
|
|
4868
5939
|
return {
|
|
4869
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,
|
|
4870
5960
|
events: [{ name: eventName, payload: eventPayload }]
|
|
4871
5961
|
};
|
|
4872
5962
|
}
|
|
@@ -4949,7 +6039,23 @@ registerAction({
|
|
|
4949
6039
|
proposalContractAddress: proposalContractAddress || "",
|
|
4950
6040
|
coreAddress,
|
|
4951
6041
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4952
|
-
}
|
|
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
|
|
4953
6059
|
};
|
|
4954
6060
|
}
|
|
4955
6061
|
});
|
|
@@ -5004,13 +6110,30 @@ registerAction({
|
|
|
5004
6110
|
rationale: rationale || void 0,
|
|
5005
6111
|
proposalContractAddress
|
|
5006
6112
|
});
|
|
6113
|
+
const votedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5007
6114
|
return {
|
|
5008
6115
|
output: {
|
|
5009
6116
|
vote,
|
|
5010
6117
|
rationale: rationale || "",
|
|
5011
6118
|
proposalId: String(proposalId),
|
|
5012
|
-
votedAt
|
|
5013
|
-
}
|
|
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
|
|
5014
6137
|
};
|
|
5015
6138
|
}
|
|
5016
6139
|
});
|
|
@@ -6822,29 +7945,46 @@ registerAction({
|
|
|
6822
7945
|
|
|
6823
7946
|
// src/core/lib/actionRegistry/actions/oracle.ts
|
|
6824
7947
|
registerAction({
|
|
6825
|
-
type: "oracle",
|
|
6826
|
-
can: "oracle/
|
|
6827
|
-
sideEffect:
|
|
6828
|
-
proof: "
|
|
7948
|
+
type: "qi/oracle.invoke",
|
|
7949
|
+
can: "oracle/invoke",
|
|
7950
|
+
sideEffect: true,
|
|
7951
|
+
proof: { fields: ["resultDigest"] },
|
|
6829
7952
|
done: doneWhenCompleted,
|
|
6830
7953
|
defaultRequiresConfirmation: false,
|
|
6831
7954
|
inputSchema: {
|
|
6832
7955
|
type: "object",
|
|
6833
7956
|
required: ["prompt"],
|
|
6834
7957
|
properties: {
|
|
6835
|
-
prompt: { type: "string", description: "The prompt text sent to the
|
|
7958
|
+
prompt: { type: "string", description: "The prompt text sent to the Agent." }
|
|
6836
7959
|
}
|
|
6837
7960
|
},
|
|
6838
|
-
|
|
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
|
+
],
|
|
6839
7970
|
run: async (inputs, ctx) => {
|
|
6840
7971
|
const prompt = String(inputs.prompt || "").trim();
|
|
6841
7972
|
if (!prompt) throw new Error("prompt is required");
|
|
6842
7973
|
if (!ctx.handlers?.askCompanion) {
|
|
6843
7974
|
throw new Error("askCompanion handler is not available");
|
|
6844
7975
|
}
|
|
6845
|
-
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 : [];
|
|
6846
7980
|
return {
|
|
6847
|
-
output: {
|
|
7981
|
+
output: {
|
|
7982
|
+
prompt,
|
|
7983
|
+
sessionId: String(envelope.sessionId ?? envelope.runId ?? ""),
|
|
7984
|
+
result,
|
|
7985
|
+
resultDigest: sha256Digest(result),
|
|
7986
|
+
evidenceDigest: sha256Digest(evidence)
|
|
7987
|
+
}
|
|
6848
7988
|
};
|
|
6849
7989
|
}
|
|
6850
7990
|
});
|
|
@@ -6980,6 +8120,7 @@ registerAction({
|
|
|
6980
8120
|
});
|
|
6981
8121
|
|
|
6982
8122
|
// src/core/lib/actionRegistry/actions/walletFund.ts
|
|
8123
|
+
var DEFAULT_DENOM = "uixo";
|
|
6983
8124
|
registerAction({
|
|
6984
8125
|
type: "qi/wallet.fund",
|
|
6985
8126
|
can: "wallet/fund",
|
|
@@ -6992,20 +8133,37 @@ registerAction({
|
|
|
6992
8133
|
required: ["address"],
|
|
6993
8134
|
properties: {
|
|
6994
8135
|
address: { type: "string", description: "The IXO wallet address to fund." },
|
|
6995
|
-
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
|
+
}
|
|
6996
8142
|
}
|
|
6997
8143
|
},
|
|
6998
|
-
outputSchema: [
|
|
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
|
+
],
|
|
6999
8150
|
run: async (inputs, ctx) => {
|
|
7000
8151
|
if (!ctx.services.oracle?.fundWallet) {
|
|
7001
8152
|
throw new Error("oracle.fundWallet handler not available");
|
|
7002
8153
|
}
|
|
7003
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();
|
|
7004
8160
|
const result = await ctx.services.oracle.fundWallet({
|
|
7005
8161
|
address: inputs.address,
|
|
7006
|
-
amount
|
|
8162
|
+
amount,
|
|
8163
|
+
denom,
|
|
8164
|
+
...fromAddress ? { fromAddress } : {}
|
|
7007
8165
|
});
|
|
7008
|
-
return { output: result };
|
|
8166
|
+
return { output: { ...result, denom, amount: String(amount), fromAddress } };
|
|
7009
8167
|
}
|
|
7010
8168
|
});
|
|
7011
8169
|
|
|
@@ -7021,7 +8179,12 @@ registerAction({
|
|
|
7021
8179
|
type: "object",
|
|
7022
8180
|
required: [],
|
|
7023
8181
|
properties: {
|
|
7024
|
-
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
|
+
}
|
|
7025
8188
|
}
|
|
7026
8189
|
},
|
|
7027
8190
|
outputSchema: [
|
|
@@ -7029,7 +8192,8 @@ registerAction({
|
|
|
7029
8192
|
{ path: "did", displayName: "DID", type: "string", description: "The DID derived from the wallet address" },
|
|
7030
8193
|
{ path: "pubKey", displayName: "Public Key", type: "string", description: "The secp256k1 public key (hex)" },
|
|
7031
8194
|
{ path: "mnemonic", displayName: "Mnemonic", type: "string", description: "The BIP39 mnemonic seed phrase" },
|
|
7032
|
-
{ 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" }
|
|
7033
8197
|
],
|
|
7034
8198
|
run: async (inputs, ctx) => {
|
|
7035
8199
|
if (!ctx.services.oracle?.generateWallet) {
|
|
@@ -7042,9 +8206,13 @@ registerAction({
|
|
|
7042
8206
|
if (!walletResult?.address) {
|
|
7043
8207
|
throw new Error("generateWallet did not return an address");
|
|
7044
8208
|
}
|
|
8209
|
+
const denom = String(inputs.denom || "").trim() || "uixo";
|
|
8210
|
+
const fromAddress = String(inputs.fromAddress || "").trim();
|
|
7045
8211
|
const fundResult = await ctx.services.oracle.fundWallet({
|
|
7046
8212
|
address: walletResult.address,
|
|
7047
|
-
amount: inputs.amount || 25e4
|
|
8213
|
+
amount: inputs.amount || 25e4,
|
|
8214
|
+
denom,
|
|
8215
|
+
...fromAddress ? { fromAddress } : {}
|
|
7048
8216
|
});
|
|
7049
8217
|
if (!fundResult?.transactionHash) {
|
|
7050
8218
|
throw new Error("fundWallet did not return a transactionHash");
|
|
@@ -7055,7 +8223,8 @@ registerAction({
|
|
|
7055
8223
|
did: walletResult.did,
|
|
7056
8224
|
pubKey: walletResult.pubKey,
|
|
7057
8225
|
mnemonic: walletResult.mnemonic,
|
|
7058
|
-
transactionHash: fundResult.transactionHash
|
|
8226
|
+
transactionHash: fundResult.transactionHash,
|
|
8227
|
+
denom
|
|
7059
8228
|
}
|
|
7060
8229
|
};
|
|
7061
8230
|
}
|
|
@@ -8101,14 +9270,6 @@ var COLLECTION_CREATED_EVENT = {
|
|
|
8101
9270
|
pendingDisplayFields: ["collectionId", "entity"]
|
|
8102
9271
|
};
|
|
8103
9272
|
|
|
8104
|
-
// src/core/lib/actionRegistry/types.ts
|
|
8105
|
-
var CollectionStateEnum = /* @__PURE__ */ ((CollectionStateEnum2) => {
|
|
8106
|
-
CollectionStateEnum2[CollectionStateEnum2["OPEN"] = 0] = "OPEN";
|
|
8107
|
-
CollectionStateEnum2[CollectionStateEnum2["PAUSED"] = 1] = "PAUSED";
|
|
8108
|
-
CollectionStateEnum2[CollectionStateEnum2["CLOSED"] = 2] = "CLOSED";
|
|
8109
|
-
return CollectionStateEnum2;
|
|
8110
|
-
})(CollectionStateEnum || {});
|
|
8111
|
-
|
|
8112
9273
|
// src/core/lib/actionRegistry/actions/collection/collection.ts
|
|
8113
9274
|
function normalizeQuota(quota) {
|
|
8114
9275
|
if (quota === void 0 || quota === null) return void 0;
|
|
@@ -8428,12 +9589,19 @@ registerAction({
|
|
|
8428
9589
|
|
|
8429
9590
|
// src/core/lib/actionRegistry/actions/collectionUsers/index.ts
|
|
8430
9591
|
var COLLECTION_USERS_ACTION_TYPE = "qi/collection.users";
|
|
9592
|
+
var COLLECTION_USERS_MAX_GRANTS_PER_TX = 40;
|
|
9593
|
+
function collectionUsersBatchCount(count) {
|
|
9594
|
+
if (!Number.isFinite(count) || count <= 0) return 0;
|
|
9595
|
+
return Math.ceil(count / COLLECTION_USERS_MAX_GRANTS_PER_TX);
|
|
9596
|
+
}
|
|
8431
9597
|
var COLLECTION_USERS_OUTPUT_SCHEMA = [
|
|
9598
|
+
{ path: "operation", displayName: "Operation", type: "string", description: "Which op produced this output: add, revoke or list" },
|
|
8432
9599
|
{ path: "transactionHash", displayName: "Tx Hash", type: "string", description: "Broadcast transaction hash (add/revoke)" },
|
|
8433
9600
|
{ path: "grantedCount", displayName: "Granted Count", type: "number", description: "Number of grants broadcast (fan-out)" },
|
|
8434
9601
|
{ path: "role", displayName: "Role", type: "string", description: "submit or evaluate" },
|
|
8435
9602
|
{ path: "collectionId", displayName: "Collection ID", type: "string", description: "Target claim collection identifier" },
|
|
8436
9603
|
{ path: "granteeAddress", displayName: "Grantee Address", type: "string", description: "Address granted/revoked" },
|
|
9604
|
+
{ path: "granteeAddresses", displayName: "Grantee Addresses", type: "array", description: "Every address granted by a bulk/fan-out add" },
|
|
8437
9605
|
{
|
|
8438
9606
|
path: "grantees",
|
|
8439
9607
|
displayName: "Grantees",
|
|
@@ -8454,6 +9622,37 @@ function normalizeRole(value) {
|
|
|
8454
9622
|
if (normalized === "submit" || normalized === "evaluate") return normalized;
|
|
8455
9623
|
throw new Error('role must be either "submit" or "evaluate"');
|
|
8456
9624
|
}
|
|
9625
|
+
function resolveAddGrantees(inputs) {
|
|
9626
|
+
const kind = inputs.granteeKind || "user";
|
|
9627
|
+
let candidates;
|
|
9628
|
+
if (kind === "address-list") {
|
|
9629
|
+
candidates = Array.isArray(inputs.granteeAddresses) ? inputs.granteeAddresses.map((address) => String(address || "").trim()) : [];
|
|
9630
|
+
if (candidates.filter(Boolean).length === 0) {
|
|
9631
|
+
throw new Error("add (address-list): no accounts in the pasted list. Paste at least one valid ixo1\u2026 address.");
|
|
9632
|
+
}
|
|
9633
|
+
} else if (kind === "group-members") {
|
|
9634
|
+
candidates = Array.isArray(inputs.members) ? inputs.members.map((member) => String(member?.address || "").trim()) : [];
|
|
9635
|
+
if (candidates.filter(Boolean).length === 0) {
|
|
9636
|
+
throw new Error("add (group-members): no members resolved to grant to. Enumerate the group members before signing.");
|
|
9637
|
+
}
|
|
9638
|
+
} else {
|
|
9639
|
+
candidates = [String(inputs.granteeAddress || "").trim()];
|
|
9640
|
+
if (!candidates[0]) throw new Error("add: granteeAddress is required");
|
|
9641
|
+
}
|
|
9642
|
+
return Array.from(new Set(candidates.filter(Boolean)));
|
|
9643
|
+
}
|
|
9644
|
+
function batchFailurePrefix(completedBatches, totalBatches, granted, total) {
|
|
9645
|
+
if (totalBatches === 1) return `add: the grant of ${total} accounts was not broadcast.`;
|
|
9646
|
+
const remaining = total - granted;
|
|
9647
|
+
return `add: batch ${completedBatches + 1} of ${totalBatches} failed. ${granted} of ${total} accounts ARE granted and stay granted; re-run with the remaining ${remaining}.`;
|
|
9648
|
+
}
|
|
9649
|
+
function chunkGrantees(grantees, size = COLLECTION_USERS_MAX_GRANTS_PER_TX) {
|
|
9650
|
+
const batches = [];
|
|
9651
|
+
for (let i = 0; i < grantees.length; i += size) {
|
|
9652
|
+
batches.push(grantees.slice(i, i + size));
|
|
9653
|
+
}
|
|
9654
|
+
return batches;
|
|
9655
|
+
}
|
|
8457
9656
|
registerAction({
|
|
8458
9657
|
type: COLLECTION_USERS_ACTION_TYPE,
|
|
8459
9658
|
can: "collection/users",
|
|
@@ -8492,11 +9691,12 @@ registerAction({
|
|
|
8492
9691
|
deedDid: { type: "string", description: "Entity (deed) DID forwarded to the handler." },
|
|
8493
9692
|
granteeAddress: { type: "string", description: "The single grantee bech32 address for add/revoke." },
|
|
8494
9693
|
granteeDid: { type: "string", description: "Optional resolved grantee DID for display or audit." },
|
|
8495
|
-
granteeKind: { type: "string", description: "How to treat the add grantee: user, group-account, or
|
|
9694
|
+
granteeKind: { type: "string", description: "How to treat the add grantee: user, group-account, group-members, or address-list." },
|
|
8496
9695
|
agentQuota: { type: "string", description: "Remaining agent quota for add; 0 means unlimited." },
|
|
8497
9696
|
maxAmount: { type: "array", description: "Per-claim max amount cap for the add evaluate role." },
|
|
8498
9697
|
intentDurationNs: { type: "string", description: "Intent duration in nanoseconds for add, carried as a string." },
|
|
8499
|
-
members: { type: "array", description: "Resolved members to fan the grant out to when granteeKind is group-members." }
|
|
9698
|
+
members: { type: "array", description: "Resolved members to fan the grant out to when granteeKind is group-members." },
|
|
9699
|
+
granteeAddresses: { type: "array", description: "Accounts pasted in bulk to grant in one transaction when granteeKind is address-list." }
|
|
8500
9700
|
}
|
|
8501
9701
|
},
|
|
8502
9702
|
// Only the decision inputs are human-askable: `operation` selects the
|
|
@@ -8523,12 +9723,12 @@ registerAction({
|
|
|
8523
9723
|
if (operation === "list") {
|
|
8524
9724
|
if (!collectionId) throw new Error("list: collectionId is required");
|
|
8525
9725
|
if (!adminAddress) throw new Error("list: adminAddress (entity admin account) is required");
|
|
8526
|
-
const
|
|
8527
|
-
const
|
|
8528
|
-
if (!
|
|
9726
|
+
const result = await service.list({ granterAdminAddress: adminAddress, collectionId });
|
|
9727
|
+
const grantees2 = Array.isArray(result?.grantees) ? result.grantees : void 0;
|
|
9728
|
+
if (!grantees2) {
|
|
8529
9729
|
throw new Error("list: service returned no grantees array. Check the [collection:list] handler logs.");
|
|
8530
9730
|
}
|
|
8531
|
-
return { output: { grantees, collectionId } };
|
|
9731
|
+
return { output: { operation: "list", grantees: grantees2, collectionId } };
|
|
8532
9732
|
}
|
|
8533
9733
|
if (!collectionId) throw new Error(`${operation}: collectionId is required`);
|
|
8534
9734
|
if (!adminAddress) throw new Error(`${operation}: adminAddress (entity admin account) is required`);
|
|
@@ -8541,18 +9741,21 @@ registerAction({
|
|
|
8541
9741
|
throw new Error("Acting as a POD is not available: the host does not implement prepareGroupCollectionUserChange");
|
|
8542
9742
|
}
|
|
8543
9743
|
if (!deedDid) throw new Error(`${operation}: deedDid is required when acting as a POD`);
|
|
8544
|
-
|
|
8545
|
-
if (operation === "add"
|
|
8546
|
-
|
|
8547
|
-
if (
|
|
8548
|
-
|
|
9744
|
+
let grantees2;
|
|
9745
|
+
if (operation === "add") {
|
|
9746
|
+
grantees2 = resolveAddGrantees(inputs);
|
|
9747
|
+
if (grantees2.length > COLLECTION_USERS_MAX_GRANTS_PER_TX) {
|
|
9748
|
+
throw new Error(
|
|
9749
|
+
`add: ${grantees2.length} grantees exceed the ${COLLECTION_USERS_MAX_GRANTS_PER_TX} per proposal limit when acting as a POD (the proposal executes as one transaction). Grant them in groups of ${COLLECTION_USERS_MAX_GRANTS_PER_TX} or fewer.`
|
|
9750
|
+
);
|
|
9751
|
+
}
|
|
8549
9752
|
} else {
|
|
8550
|
-
const
|
|
8551
|
-
if (!
|
|
8552
|
-
|
|
9753
|
+
const granteeAddress = String(inputs.granteeAddress || "").trim();
|
|
9754
|
+
if (!granteeAddress) throw new Error(`${operation}: granteeAddress is required`);
|
|
9755
|
+
grantees2 = [granteeAddress];
|
|
8553
9756
|
}
|
|
8554
9757
|
const msgs = [];
|
|
8555
|
-
for (const
|
|
9758
|
+
for (const granteeAddress of grantees2) {
|
|
8556
9759
|
const prepared = await groupHandlers.prepareGroupCollectionUserChange({
|
|
8557
9760
|
groupAddress: coreAddress,
|
|
8558
9761
|
operation,
|
|
@@ -8560,7 +9763,7 @@ registerAction({
|
|
|
8560
9763
|
collectionId,
|
|
8561
9764
|
adminAddress,
|
|
8562
9765
|
role,
|
|
8563
|
-
granteeAddress
|
|
9766
|
+
granteeAddress,
|
|
8564
9767
|
quota: inputs.agentQuota !== void 0 && inputs.agentQuota !== null ? Number(inputs.agentQuota) : void 0,
|
|
8565
9768
|
maxAmount: Array.isArray(inputs.maxAmount) ? inputs.maxAmount : void 0
|
|
8566
9769
|
});
|
|
@@ -8572,69 +9775,95 @@ registerAction({
|
|
|
8572
9775
|
description,
|
|
8573
9776
|
msgs,
|
|
8574
9777
|
expectedOutput: {
|
|
9778
|
+
operation,
|
|
8575
9779
|
transactionHash: "",
|
|
8576
9780
|
transactionHashes: [],
|
|
8577
|
-
grantedCount: operation === "add" ?
|
|
9781
|
+
grantedCount: operation === "add" ? grantees2.length : 0,
|
|
8578
9782
|
role,
|
|
8579
9783
|
collectionId,
|
|
8580
|
-
granteeAddress:
|
|
9784
|
+
granteeAddress: grantees2.length === 1 ? grantees2[0] : void 0,
|
|
9785
|
+
granteeAddresses: operation === "add" ? grantees2 : void 0
|
|
8581
9786
|
}
|
|
8582
9787
|
});
|
|
8583
9788
|
}
|
|
8584
9789
|
if (operation === "revoke") {
|
|
8585
|
-
const
|
|
8586
|
-
if (!
|
|
8587
|
-
const
|
|
8588
|
-
const
|
|
8589
|
-
if (!
|
|
9790
|
+
const granteeAddress = String(inputs.granteeAddress || "").trim();
|
|
9791
|
+
if (!granteeAddress) throw new Error("revoke: granteeAddress is required");
|
|
9792
|
+
const result = await service.revoke({ granterAdminAddress: adminAddress, granteeAddress, collectionId, role });
|
|
9793
|
+
const transactionHash = String(result?.transactionHash || "").trim();
|
|
9794
|
+
if (!transactionHash) {
|
|
8590
9795
|
throw new Error("revoke: service returned no transactionHash. The revoke was not broadcast.");
|
|
8591
9796
|
}
|
|
8592
|
-
return { output: {
|
|
9797
|
+
return { output: { operation: "revoke", transactionHash, role, collectionId, granteeAddress } };
|
|
8593
9798
|
}
|
|
8594
|
-
const granteeKind = inputs.granteeKind || "user";
|
|
8595
9799
|
const agentQuota = inputs.agentQuota !== void 0 && inputs.agentQuota !== null ? String(inputs.agentQuota).trim() || void 0 : void 0;
|
|
8596
9800
|
const maxAmount = Array.isArray(inputs.maxAmount) ? inputs.maxAmount : void 0;
|
|
8597
9801
|
const intentDurationNs = String(inputs.intentDurationNs || "").trim() || void 0;
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
for (const
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
9802
|
+
const grantees = resolveAddGrantees(inputs);
|
|
9803
|
+
const grantConfig = { granterAdminAddress: adminAddress, collectionId, role, agentQuota, maxAmount, intentDurationNs, deedDid };
|
|
9804
|
+
if (grantees.length > 1 && typeof service.grantMany === "function") {
|
|
9805
|
+
const batches = chunkGrantees(grantees);
|
|
9806
|
+
const transactionHashes2 = [];
|
|
9807
|
+
let granted = 0;
|
|
9808
|
+
for (const batch of batches) {
|
|
9809
|
+
let result;
|
|
9810
|
+
try {
|
|
9811
|
+
result = await service.grantMany({ ...grantConfig, granteeAddresses: batch });
|
|
9812
|
+
} catch (error) {
|
|
9813
|
+
throw new Error(`${batchFailurePrefix(transactionHashes2.length, batches.length, granted, grantees.length)} ${error instanceof Error ? error.message : String(error)}`);
|
|
9814
|
+
}
|
|
9815
|
+
const hash = String(result?.transactionHash || "").trim();
|
|
8608
9816
|
if (!hash) {
|
|
8609
|
-
throw new Error(
|
|
9817
|
+
throw new Error(`${batchFailurePrefix(transactionHashes2.length, batches.length, granted, grantees.length)} The service returned no transactionHash.`);
|
|
8610
9818
|
}
|
|
8611
|
-
|
|
9819
|
+
transactionHashes2.push(hash);
|
|
9820
|
+
granted += batch.length;
|
|
8612
9821
|
}
|
|
8613
9822
|
return {
|
|
8614
9823
|
output: {
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
9824
|
+
operation: "add",
|
|
9825
|
+
transactionHash: transactionHashes2[transactionHashes2.length - 1],
|
|
9826
|
+
transactionHashes: transactionHashes2,
|
|
9827
|
+
grantedCount: grantees.length,
|
|
8618
9828
|
role,
|
|
8619
|
-
collectionId
|
|
9829
|
+
collectionId,
|
|
9830
|
+
granteeAddresses: grantees,
|
|
9831
|
+
batched: true,
|
|
9832
|
+
batchCount: batches.length
|
|
8620
9833
|
}
|
|
8621
9834
|
};
|
|
8622
9835
|
}
|
|
8623
|
-
const
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
9836
|
+
const transactionHashes = [];
|
|
9837
|
+
for (const granteeAddress of grantees) {
|
|
9838
|
+
let result;
|
|
9839
|
+
try {
|
|
9840
|
+
result = await service.grant({ ...grantConfig, granteeAddress });
|
|
9841
|
+
} catch (error) {
|
|
9842
|
+
const cause = error instanceof Error ? error.message : String(error);
|
|
9843
|
+
if (grantees.length === 1) throw error instanceof Error ? error : new Error(cause);
|
|
9844
|
+
throw new Error(
|
|
9845
|
+
`add: the grant to ${granteeAddress} failed. ${transactionHashes.length} of ${grantees.length} accounts ARE granted and stay granted; re-run with the rest. ${cause}`
|
|
9846
|
+
);
|
|
9847
|
+
}
|
|
9848
|
+
const hash = String(result?.transactionHash || "").trim();
|
|
9849
|
+
if (!hash) {
|
|
9850
|
+
throw new Error(
|
|
9851
|
+
grantees.length === 1 ? "add: service returned no transactionHash. The grant was not broadcast." : `add: the grant to ${granteeAddress} returned no transactionHash. ${transactionHashes.length} of ${grantees.length} accounts ARE granted and stay granted; re-run with the rest.`
|
|
9852
|
+
);
|
|
9853
|
+
}
|
|
9854
|
+
transactionHashes.push(hash);
|
|
8629
9855
|
}
|
|
8630
9856
|
return {
|
|
8631
9857
|
output: {
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
9858
|
+
operation: "add",
|
|
9859
|
+
transactionHash: transactionHashes[transactionHashes.length - 1],
|
|
9860
|
+
transactionHashes,
|
|
9861
|
+
grantedCount: transactionHashes.length,
|
|
8635
9862
|
role,
|
|
8636
9863
|
collectionId,
|
|
8637
|
-
granteeAddress
|
|
9864
|
+
granteeAddress: grantees.length === 1 ? grantees[0] : void 0,
|
|
9865
|
+
granteeAddresses: grantees,
|
|
9866
|
+
batched: false
|
|
8638
9867
|
}
|
|
8639
9868
|
};
|
|
8640
9869
|
}
|
|
@@ -8942,6 +10171,7 @@ async function runEvalRegister(inputs, ctx) {
|
|
|
8942
10171
|
const allowAiChecks = inputs.allowAiChecks !== false;
|
|
8943
10172
|
const allowImageChecks = allowAiChecks && inputs.allowImageChecks !== false;
|
|
8944
10173
|
const allowChainEvaluation = inputs.allowChainEvaluation !== false;
|
|
10174
|
+
const allowZeroPayoutApprovals = inputs.allowZeroPayoutApprovals === true;
|
|
8945
10175
|
if (!collectionId) throw new Error("collectionId is required");
|
|
8946
10176
|
if (!deedDid) throw new Error("deedDid (entity/deed DID) is required");
|
|
8947
10177
|
if (!ownerDid) throw new Error("ownerDid is required (pass it explicitly, or run as the collection owner)");
|
|
@@ -8980,7 +10210,7 @@ async function runEvalRegister(inputs, ctx) {
|
|
|
8980
10210
|
// (see above), and a host that copies `params` field-by-field would otherwise forward an
|
|
8981
10211
|
// explicit `undefined` as the erasing empty value.
|
|
8982
10212
|
...description !== void 0 ? { description } : {},
|
|
8983
|
-
settings: { allowAiChecks, allowImageChecks, allowChainEvaluation }
|
|
10213
|
+
settings: { allowAiChecks, allowImageChecks, allowChainEvaluation, allowZeroPayoutApprovals }
|
|
8984
10214
|
});
|
|
8985
10215
|
const registrationId = String(registration?.id || "").trim();
|
|
8986
10216
|
if (!registrationId) {
|
|
@@ -9053,8 +10283,12 @@ function canonicalJson(value) {
|
|
|
9053
10283
|
throw new Error(`canonicalJson: unsupported value of type ${typeof value}`);
|
|
9054
10284
|
}
|
|
9055
10285
|
|
|
10286
|
+
// src/core/lib/actionRegistry/actions/evalRubric/pathGrammar.ts
|
|
10287
|
+
var FORM_SEGMENT = "[A-Za-z0-9_-]+(?::[A-Za-z0-9_-]+)?";
|
|
10288
|
+
|
|
9056
10289
|
// src/core/lib/actionRegistry/actions/evalRubric/fieldCatalog.ts
|
|
9057
|
-
var SEGMENT =
|
|
10290
|
+
var SEGMENT = new RegExp(`^${FORM_SEGMENT}$`);
|
|
10291
|
+
var NAME_PATH = new RegExp(`^${FORM_SEGMENT}(?:\\.${FORM_SEGMENT})*$`);
|
|
9058
10292
|
function scalarKind(type, inputType) {
|
|
9059
10293
|
switch (type) {
|
|
9060
10294
|
case "text":
|
|
@@ -9137,7 +10371,7 @@ function extractRubricFieldCatalog(surveyTemplate, proof = "") {
|
|
|
9137
10371
|
}
|
|
9138
10372
|
const name = typeof el.name === "string" ? el.name.trim() : "";
|
|
9139
10373
|
const type = typeof el.type === "string" ? el.type : "";
|
|
9140
|
-
if (!name || !
|
|
10374
|
+
if (!name || !NAME_PATH.test(name) || seen2.has(name) || type === "html" || type === "expression") continue;
|
|
9141
10375
|
const field = extractQuestion(el, name, type);
|
|
9142
10376
|
if (!field) continue;
|
|
9143
10377
|
seen2.add(name);
|
|
@@ -9233,14 +10467,15 @@ function titleOf(el) {
|
|
|
9233
10467
|
return title || humanize2(typeof el.name === "string" ? el.name : "");
|
|
9234
10468
|
}
|
|
9235
10469
|
function humanize2(name) {
|
|
9236
|
-
|
|
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());
|
|
9237
10472
|
}
|
|
9238
10473
|
function stripHtml2(s) {
|
|
9239
10474
|
return s.replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim();
|
|
9240
10475
|
}
|
|
9241
10476
|
|
|
9242
10477
|
// src/core/lib/actionRegistry/actions/evalRubric/schemaGate.ts
|
|
9243
|
-
import
|
|
10478
|
+
import Ajv20202 from "ajv/dist/2020.js";
|
|
9244
10479
|
|
|
9245
10480
|
// src/core/lib/actionRegistry/actions/evalRubric/types.ts
|
|
9246
10481
|
var RUBRIC_CTX_TOKENS = [
|
|
@@ -9324,7 +10559,7 @@ async function getValidator(fetchSchema, evalEngineUrl) {
|
|
|
9324
10559
|
if (compiledValidator) return compiledValidator;
|
|
9325
10560
|
const schema = await fetchSchema(evalEngineUrl);
|
|
9326
10561
|
if (!schema || typeof schema !== "object") throw new Error("the rules service returned no schema");
|
|
9327
|
-
const validate = new
|
|
10562
|
+
const validate = new Ajv20202({ allErrors: true, strict: false }).compile(schema);
|
|
9328
10563
|
compiledValidator = validate;
|
|
9329
10564
|
return validate;
|
|
9330
10565
|
}
|
|
@@ -9422,7 +10657,7 @@ var ExpressionSyntaxError = class extends Error {
|
|
|
9422
10657
|
};
|
|
9423
10658
|
var IDENT = /[A-Za-z_][A-Za-z0-9_]*/y;
|
|
9424
10659
|
var NUMBER = /(?:\d+\.?\d*|\.\d+)/y;
|
|
9425
|
-
var FIELD_REF =
|
|
10660
|
+
var FIELD_REF = new RegExp(`\\$${FORM_SEGMENT}(?:\\.${FORM_SEGMENT}|\\[\\*\\])*`, "y");
|
|
9426
10661
|
var SIGIL_REF = /~[A-Za-z_][A-Za-z0-9_]*/y;
|
|
9427
10662
|
var CTX_REF = /ctx(?:\.[A-Za-z][A-Za-z0-9]*)+/y;
|
|
9428
10663
|
function parseExpression(src) {
|
|
@@ -9585,8 +10820,9 @@ var CTX_TOKEN_KIND = {
|
|
|
9585
10820
|
"ctx.submitter.priorApprovedCount": "number",
|
|
9586
10821
|
"ctx.collection.projectBoundary": "geo"
|
|
9587
10822
|
};
|
|
9588
|
-
var FIELD_REF2 =
|
|
9589
|
-
var ROW_REF =
|
|
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}`);
|
|
9590
10826
|
var DERIVED_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
9591
10827
|
var EXT_REF = /^ext\.([A-Za-z0-9_-]+)\.(valid|score|reason)$/;
|
|
9592
10828
|
var AI_REF = /^ai\.([A-Za-z0-9_-]+)\.(valid|reason)$/;
|
|
@@ -9618,7 +10854,7 @@ function validateRubric(body, catalog, authoringCatalog) {
|
|
|
9618
10854
|
error("RUB_SCHEMA_DRIFT", `'${ref}' no longer exists on the claim form (deleted or renamed since the rules were authored)`, path);
|
|
9619
10855
|
return void 0;
|
|
9620
10856
|
}
|
|
9621
|
-
const rootName =
|
|
10857
|
+
const rootName = FIELD_ROOT.exec(ref)?.[0] ?? ref;
|
|
9622
10858
|
if (ref !== rootName && fieldIndex.has(rootName)) {
|
|
9623
10859
|
error("RUB_FIELD_PATH", `'${ref}' does not address a row/column of ${rootName}`, path);
|
|
9624
10860
|
} else {
|
|
@@ -10691,6 +11927,11 @@ registerAction({
|
|
|
10691
11927
|
allowAiChecks: { type: "boolean", default: true, description: "Engine setting: allow paid AI checks for this collection." },
|
|
10692
11928
|
allowImageChecks: { type: "boolean", default: true, description: "Engine setting: allow fake-photo detection (needs AI checks on)." },
|
|
10693
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
|
+
},
|
|
10694
11935
|
evaluateMaxAmount: { type: "array", description: "Per-claim payout cap on the evaluate grant, base-unit coins in the owner's denoms." },
|
|
10695
11936
|
// ---- rules (qi/eval.rubric) ----
|
|
10696
11937
|
rubric: {
|
|
@@ -10863,6 +12104,27 @@ registerAction({
|
|
|
10863
12104
|
});
|
|
10864
12105
|
|
|
10865
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
|
+
}
|
|
10866
12128
|
function parseBoundConnection(raw) {
|
|
10867
12129
|
if (!raw || typeof raw !== "object") return null;
|
|
10868
12130
|
const c = raw;
|
|
@@ -10949,7 +12211,10 @@ async function executeDelegatedTool(ctx, opts) {
|
|
|
10949
12211
|
}
|
|
10950
12212
|
throw new Error(result.error || `${opts.toolkitLabel} action failed.`);
|
|
10951
12213
|
}
|
|
10952
|
-
return
|
|
12214
|
+
return {
|
|
12215
|
+
data: result.data ?? {},
|
|
12216
|
+
providerInvocationReceipt: result.providerInvocationReceipt
|
|
12217
|
+
};
|
|
10953
12218
|
}
|
|
10954
12219
|
|
|
10955
12220
|
// src/core/lib/actionRegistry/actions/gmail/emailSend.types.ts
|
|
@@ -10975,7 +12240,9 @@ var GMAIL_SEND_SCHEMA = {
|
|
|
10975
12240
|
var GMAIL_SEND_OUTPUT_SCHEMA = [
|
|
10976
12241
|
{ path: "messageId", displayName: "Message ID", type: "string", description: "Gmail id of the sent message" },
|
|
10977
12242
|
{ path: "threadId", displayName: "Thread ID", type: "string" },
|
|
10978
|
-
{ 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" }
|
|
10979
12246
|
];
|
|
10980
12247
|
|
|
10981
12248
|
// src/core/lib/actionRegistry/actions/gmail/emailSend.ts
|
|
@@ -10990,6 +12257,7 @@ registerAction({
|
|
|
10990
12257
|
requiredCapability: "flow/block/execute",
|
|
10991
12258
|
// Can be wired to another block's event (e.g. form submitted → send email).
|
|
10992
12259
|
eligibleForEventTrigger: true,
|
|
12260
|
+
inputSchema: delegatedToolInputSchema(GMAIL_SEND_SCHEMA),
|
|
10993
12261
|
// Mirrors executeDelegatedTool's gates: the bound connection plus the
|
|
10994
12262
|
// tool schema's required fields, so orchestrators ask before run() throws.
|
|
10995
12263
|
getMissingInputs: (inputs) => delegatedToolMissingInputs(GMAIL_SEND_SCHEMA, inputs),
|
|
@@ -11009,13 +12277,14 @@ registerAction({
|
|
|
11009
12277
|
run: async (inputs, ctx) => {
|
|
11010
12278
|
const parsed = parseDelegatedToolInputs(inputs);
|
|
11011
12279
|
const values = fieldValues(parsed);
|
|
11012
|
-
const
|
|
12280
|
+
const execution = await executeDelegatedTool(ctx, {
|
|
11013
12281
|
connection: parsed.connection,
|
|
11014
12282
|
schema: GMAIL_SEND_SCHEMA,
|
|
11015
12283
|
toolSlug: GMAIL_SEND_SLUG,
|
|
11016
12284
|
values,
|
|
11017
12285
|
toolkitLabel: "Gmail"
|
|
11018
12286
|
});
|
|
12287
|
+
const { data, providerInvocationReceipt } = execution;
|
|
11019
12288
|
const envelope = data.response_data ?? data;
|
|
11020
12289
|
const messageId = String(envelope.id ?? envelope.messageId ?? "");
|
|
11021
12290
|
const threadId = String(envelope.threadId ?? "");
|
|
@@ -11023,7 +12292,9 @@ registerAction({
|
|
|
11023
12292
|
output: {
|
|
11024
12293
|
messageId,
|
|
11025
12294
|
threadId,
|
|
11026
|
-
sentAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12295
|
+
sentAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12296
|
+
providerReceiptId: providerInvocationReceipt?.id || "",
|
|
12297
|
+
providerInvocationReceipt
|
|
11027
12298
|
},
|
|
11028
12299
|
events: messageId ? [{ name: GMAIL_SENT_EVENT, payload: { messageId, recipient_email: values.recipient_email ?? "" } }] : void 0
|
|
11029
12300
|
};
|
|
@@ -11053,7 +12324,9 @@ var OUTLOOK_SEND_SCHEMA = {
|
|
|
11053
12324
|
};
|
|
11054
12325
|
var OUTLOOK_SEND_OUTPUT_SCHEMA = [
|
|
11055
12326
|
{ path: "messageId", displayName: "Message ID", type: "string" },
|
|
11056
|
-
{ 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" }
|
|
11057
12330
|
];
|
|
11058
12331
|
|
|
11059
12332
|
// src/core/lib/actionRegistry/actions/outlook/emailSend.ts
|
|
@@ -11061,16 +12334,15 @@ registerAction({
|
|
|
11061
12334
|
type: "qi/outlook.email.send",
|
|
11062
12335
|
can: "outlook.email/send",
|
|
11063
12336
|
sideEffect: true,
|
|
11064
|
-
// Outlook
|
|
11065
|
-
//
|
|
11066
|
-
|
|
11067
|
-
// requiring messageId, which would push every id-less send to needs_verification.
|
|
11068
|
-
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"] },
|
|
11069
12340
|
done: doneWhenCompleted,
|
|
11070
12341
|
defaultRequiresConfirmation: true,
|
|
11071
12342
|
requiredCapability: "flow/block/execute",
|
|
11072
12343
|
// Can be wired to another block's event (e.g. form submitted → send email).
|
|
11073
12344
|
eligibleForEventTrigger: true,
|
|
12345
|
+
inputSchema: delegatedToolInputSchema(OUTLOOK_SEND_SCHEMA),
|
|
11074
12346
|
// Mirrors executeDelegatedTool's gates: the bound connection plus the
|
|
11075
12347
|
// tool schema's required fields, so orchestrators ask before run() throws.
|
|
11076
12348
|
getMissingInputs: (inputs) => delegatedToolMissingInputs(OUTLOOK_SEND_SCHEMA, inputs),
|
|
@@ -11090,184 +12362,962 @@ registerAction({
|
|
|
11090
12362
|
run: async (inputs, ctx) => {
|
|
11091
12363
|
const parsed = parseDelegatedToolInputs(inputs);
|
|
11092
12364
|
const values = fieldValues(parsed);
|
|
11093
|
-
const
|
|
12365
|
+
const execution = await executeDelegatedTool(ctx, {
|
|
11094
12366
|
connection: parsed.connection,
|
|
11095
12367
|
schema: OUTLOOK_SEND_SCHEMA,
|
|
11096
12368
|
toolSlug: OUTLOOK_SEND_SLUG,
|
|
11097
12369
|
values,
|
|
11098
12370
|
toolkitLabel: "Outlook"
|
|
11099
12371
|
});
|
|
11100
|
-
const
|
|
11101
|
-
const
|
|
12372
|
+
const { data, providerInvocationReceipt } = execution;
|
|
12373
|
+
const envelope = data.response_data ?? data;
|
|
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
|
+
}
|
|
12378
|
+
return {
|
|
12379
|
+
output: {
|
|
12380
|
+
messageId,
|
|
12381
|
+
sentAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12382
|
+
providerReceiptId: providerInvocationReceipt?.id || "",
|
|
12383
|
+
providerInvocationReceipt
|
|
12384
|
+
},
|
|
12385
|
+
// Outlook often returns no id, so emit unconditionally.
|
|
12386
|
+
events: [{ name: OUTLOOK_SENT_EVENT, payload: { messageId, to_email: values.to_email ?? "" } }]
|
|
12387
|
+
};
|
|
12388
|
+
}
|
|
12389
|
+
});
|
|
12390
|
+
|
|
12391
|
+
// src/core/lib/actionRegistry/actions/slack/messageSend.types.ts
|
|
12392
|
+
var SLACK_SEND_SLUG = "SLACK_CHAT_POST_MESSAGE";
|
|
12393
|
+
var SLACK_SENT_EVENT = "message.sent";
|
|
12394
|
+
var SLACK_SEND_SCHEMA = {
|
|
12395
|
+
slug: SLACK_SEND_SLUG,
|
|
12396
|
+
name: "Post Message",
|
|
12397
|
+
description: "Post a message to a Slack channel from the template author's Slack account.",
|
|
12398
|
+
parameters: {
|
|
12399
|
+
type: "object",
|
|
12400
|
+
required: ["channel"],
|
|
12401
|
+
properties: {
|
|
12402
|
+
channel: { type: "string", title: "Channel", description: "Channel ID or name, e.g. #general or C0123456." },
|
|
12403
|
+
markdown_text: {
|
|
12404
|
+
type: "string",
|
|
12405
|
+
title: "Message",
|
|
12406
|
+
description: "Message text in Slack markdown. Preferred over the deprecated plain text field."
|
|
12407
|
+
},
|
|
12408
|
+
thread_ts: { type: "string", title: "Thread", description: "Optional parent message timestamp to reply within a thread." }
|
|
12409
|
+
}
|
|
12410
|
+
}
|
|
12411
|
+
};
|
|
12412
|
+
var SLACK_SEND_OUTPUT_SCHEMA = [
|
|
12413
|
+
{ path: "messageTs", displayName: "Message ts", type: "string" },
|
|
12414
|
+
{ path: "channel", displayName: "Channel", type: "string" },
|
|
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" }
|
|
12418
|
+
];
|
|
12419
|
+
|
|
12420
|
+
// src/core/lib/actionRegistry/actions/slack/messageSend.ts
|
|
12421
|
+
registerAction({
|
|
12422
|
+
type: "qi/slack.message.send",
|
|
12423
|
+
can: "slack.message/send",
|
|
12424
|
+
sideEffect: true,
|
|
12425
|
+
// Proof of execution: Slack returns the posted message timestamp (ts).
|
|
12426
|
+
proof: { fields: ["messageTs"] },
|
|
12427
|
+
done: doneWhenCompleted,
|
|
12428
|
+
defaultRequiresConfirmation: true,
|
|
12429
|
+
requiredCapability: "flow/block/execute",
|
|
12430
|
+
// Can be wired to another block's event (e.g. form submitted → post message).
|
|
12431
|
+
eligibleForEventTrigger: true,
|
|
12432
|
+
inputSchema: delegatedToolInputSchema(SLACK_SEND_SCHEMA),
|
|
12433
|
+
// Mirrors executeDelegatedTool's gates: the bound connection plus the
|
|
12434
|
+
// tool schema's required fields, so orchestrators ask before run() throws.
|
|
12435
|
+
getMissingInputs: (inputs) => delegatedToolMissingInputs(SLACK_SEND_SCHEMA, inputs),
|
|
12436
|
+
outputSchema: SLACK_SEND_OUTPUT_SCHEMA,
|
|
12437
|
+
events: [
|
|
12438
|
+
{
|
|
12439
|
+
name: SLACK_SENT_EVENT,
|
|
12440
|
+
displayName: "Message posted",
|
|
12441
|
+
description: "Fired after the message is posted to Slack.",
|
|
12442
|
+
payloadSchema: [
|
|
12443
|
+
{ path: "messageTs", displayName: "Message ts", type: "string" },
|
|
12444
|
+
{ path: "channel", displayName: "Channel", type: "string" }
|
|
12445
|
+
],
|
|
12446
|
+
pendingDisplayFields: ["messageTs"]
|
|
12447
|
+
}
|
|
12448
|
+
],
|
|
12449
|
+
run: async (inputs, ctx) => {
|
|
12450
|
+
const parsed = parseDelegatedToolInputs(inputs);
|
|
12451
|
+
const values = fieldValues(parsed);
|
|
12452
|
+
const execution = await executeDelegatedTool(ctx, {
|
|
12453
|
+
connection: parsed.connection,
|
|
12454
|
+
schema: SLACK_SEND_SCHEMA,
|
|
12455
|
+
toolSlug: SLACK_SEND_SLUG,
|
|
12456
|
+
values,
|
|
12457
|
+
toolkitLabel: "Slack"
|
|
12458
|
+
});
|
|
12459
|
+
const { data, providerInvocationReceipt } = execution;
|
|
12460
|
+
const envelope = data.response_data ?? data;
|
|
12461
|
+
const messageTs = String(envelope.ts ?? "");
|
|
12462
|
+
const channel = String(envelope.channel ?? values.channel ?? "");
|
|
12463
|
+
return {
|
|
12464
|
+
output: {
|
|
12465
|
+
messageTs,
|
|
12466
|
+
channel,
|
|
12467
|
+
sentAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12468
|
+
providerReceiptId: providerInvocationReceipt?.id || "",
|
|
12469
|
+
providerInvocationReceipt
|
|
12470
|
+
},
|
|
12471
|
+
events: messageTs ? [{ name: SLACK_SENT_EVENT, payload: { messageTs, channel } }] : void 0
|
|
12472
|
+
};
|
|
12473
|
+
}
|
|
12474
|
+
});
|
|
12475
|
+
|
|
12476
|
+
// src/core/lib/actionRegistry/actions/googlecalendar/eventCreate.types.ts
|
|
12477
|
+
var GOOGLECALENDAR_CREATE_SLUG = "GOOGLECALENDAR_CREATE_EVENT";
|
|
12478
|
+
var GOOGLECALENDAR_CREATED_EVENT = "event.created";
|
|
12479
|
+
var GOOGLECALENDAR_CREATE_SCHEMA = {
|
|
12480
|
+
slug: GOOGLECALENDAR_CREATE_SLUG,
|
|
12481
|
+
name: "Create Event",
|
|
12482
|
+
description: "Create an event on the template author's Google Calendar.",
|
|
12483
|
+
parameters: {
|
|
12484
|
+
type: "object",
|
|
12485
|
+
required: ["start_datetime"],
|
|
12486
|
+
properties: {
|
|
12487
|
+
start_datetime: { type: "string", title: "Start time", description: "ISO 8601, e.g. 2026-05-12T09:00:00." },
|
|
12488
|
+
summary: { type: "string", title: "Title" },
|
|
12489
|
+
description: { type: "string", title: "Description" },
|
|
12490
|
+
location: { type: "string", title: "Location" },
|
|
12491
|
+
timezone: { type: "string", title: "Timezone", description: "IANA name, e.g. Europe/London." },
|
|
12492
|
+
attendees: { type: "array", title: "Attendees", description: "Comma-separated email addresses." },
|
|
12493
|
+
calendar_id: { type: "string", title: "Calendar", description: "Use 'primary' for the author's main calendar." },
|
|
12494
|
+
event_duration_minutes: { type: "number", title: "Duration (minutes)", description: "Defaults to the calendar default if blank." }
|
|
12495
|
+
}
|
|
12496
|
+
}
|
|
12497
|
+
};
|
|
12498
|
+
var GOOGLECALENDAR_CREATE_OUTPUT_SCHEMA = [
|
|
12499
|
+
{ path: "eventId", displayName: "Event ID", type: "string" },
|
|
12500
|
+
{ path: "htmlLink", displayName: "Event link", type: "string" },
|
|
12501
|
+
{ path: "summary", displayName: "Summary", 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" }
|
|
12505
|
+
];
|
|
12506
|
+
|
|
12507
|
+
// src/core/lib/actionRegistry/actions/googlecalendar/eventCreate.ts
|
|
12508
|
+
registerAction({
|
|
12509
|
+
type: "qi/googlecalendar.event.create",
|
|
12510
|
+
can: "googlecalendar.event/create",
|
|
12511
|
+
sideEffect: true,
|
|
12512
|
+
// Proof of execution: the created event's id. Matches qi/calendar.event.create.
|
|
12513
|
+
proof: { fields: ["eventId"] },
|
|
12514
|
+
done: doneWhenCompleted,
|
|
12515
|
+
defaultRequiresConfirmation: true,
|
|
12516
|
+
requiredCapability: "flow/block/execute",
|
|
12517
|
+
eligibleForEventTrigger: true,
|
|
12518
|
+
inputSchema: delegatedToolInputSchema(GOOGLECALENDAR_CREATE_SCHEMA),
|
|
12519
|
+
// Mirrors executeDelegatedTool's gates: the bound connection plus the
|
|
12520
|
+
// tool schema's required fields, so orchestrators ask before run() throws.
|
|
12521
|
+
getMissingInputs: (inputs) => delegatedToolMissingInputs(GOOGLECALENDAR_CREATE_SCHEMA, inputs),
|
|
12522
|
+
outputSchema: GOOGLECALENDAR_CREATE_OUTPUT_SCHEMA,
|
|
12523
|
+
events: [
|
|
12524
|
+
{
|
|
12525
|
+
name: GOOGLECALENDAR_CREATED_EVENT,
|
|
12526
|
+
displayName: "Calendar event created",
|
|
12527
|
+
description: "Fired after the event is created on the author\u2019s calendar.",
|
|
12528
|
+
payloadSchema: [
|
|
12529
|
+
{ path: "eventId", displayName: "Event ID", type: "string" },
|
|
12530
|
+
{ path: "htmlLink", displayName: "Event link", type: "string" },
|
|
12531
|
+
{ path: "summary", displayName: "Summary", type: "string" }
|
|
12532
|
+
],
|
|
12533
|
+
pendingDisplayFields: ["summary", "eventId"]
|
|
12534
|
+
}
|
|
12535
|
+
],
|
|
12536
|
+
run: async (inputs, ctx) => {
|
|
12537
|
+
const parsed = parseDelegatedToolInputs(inputs);
|
|
12538
|
+
const values = fieldValues(parsed);
|
|
12539
|
+
const execution = await executeDelegatedTool(ctx, {
|
|
12540
|
+
connection: parsed.connection,
|
|
12541
|
+
schema: GOOGLECALENDAR_CREATE_SCHEMA,
|
|
12542
|
+
toolSlug: GOOGLECALENDAR_CREATE_SLUG,
|
|
12543
|
+
values,
|
|
12544
|
+
toolkitLabel: "Google Calendar"
|
|
12545
|
+
});
|
|
12546
|
+
const { data, providerInvocationReceipt } = execution;
|
|
12547
|
+
const envelope = data.response_data ?? data;
|
|
12548
|
+
const eventId = String(envelope.id ?? "");
|
|
12549
|
+
const htmlLink = String(envelope.htmlLink ?? "");
|
|
12550
|
+
const summary = String(envelope.summary ?? values.summary ?? "");
|
|
12551
|
+
const start = envelope.start;
|
|
12552
|
+
const startIso = String(start?.dateTime ?? start?.date ?? values.start_datetime ?? "");
|
|
12553
|
+
return {
|
|
12554
|
+
output: { eventId, htmlLink, summary, startIso, providerReceiptId: providerInvocationReceipt?.id || "", providerInvocationReceipt },
|
|
12555
|
+
events: eventId ? [{ name: GOOGLECALENDAR_CREATED_EVENT, payload: { eventId, htmlLink, summary } }] : void 0
|
|
12556
|
+
};
|
|
12557
|
+
}
|
|
12558
|
+
});
|
|
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
|
+
});
|
|
11102
13112
|
return {
|
|
11103
|
-
|
|
11104
|
-
|
|
11105
|
-
|
|
11106
|
-
|
|
11107
|
-
|
|
11108
|
-
|
|
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
|
|
11109
13119
|
};
|
|
11110
13120
|
}
|
|
11111
13121
|
});
|
|
11112
|
-
|
|
11113
|
-
|
|
11114
|
-
|
|
11115
|
-
|
|
11116
|
-
|
|
11117
|
-
|
|
11118
|
-
|
|
11119
|
-
|
|
11120
|
-
|
|
11121
|
-
|
|
11122
|
-
required: ["channel"],
|
|
11123
|
-
properties: {
|
|
11124
|
-
channel: { type: "string", title: "Channel", description: "Channel ID or name, e.g. #general or C0123456." },
|
|
11125
|
-
markdown_text: {
|
|
11126
|
-
type: "string",
|
|
11127
|
-
title: "Message",
|
|
11128
|
-
description: "Message text in Slack markdown. Preferred over the deprecated plain text field."
|
|
11129
|
-
},
|
|
11130
|
-
thread_ts: { type: "string", title: "Thread", description: "Optional parent message timestamp to reply within a thread." }
|
|
11131
|
-
}
|
|
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 || "") });
|
|
11132
13132
|
}
|
|
11133
|
-
};
|
|
11134
|
-
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
|
|
11140
|
-
|
|
11141
|
-
|
|
11142
|
-
|
|
11143
|
-
|
|
11144
|
-
|
|
11145
|
-
|
|
11146
|
-
|
|
11147
|
-
|
|
11148
|
-
|
|
11149
|
-
|
|
11150
|
-
|
|
11151
|
-
|
|
11152
|
-
|
|
11153
|
-
// tool schema's required fields, so orchestrators ask before run() throws.
|
|
11154
|
-
getMissingInputs: (inputs) => delegatedToolMissingInputs(SLACK_SEND_SCHEMA, inputs),
|
|
11155
|
-
outputSchema: SLACK_SEND_OUTPUT_SCHEMA,
|
|
11156
|
-
events: [
|
|
11157
|
-
{
|
|
11158
|
-
name: SLACK_SENT_EVENT,
|
|
11159
|
-
displayName: "Message posted",
|
|
11160
|
-
description: "Fired after the message is posted to Slack.",
|
|
11161
|
-
payloadSchema: [
|
|
11162
|
-
{ path: "messageTs", displayName: "Message ts", type: "string" },
|
|
11163
|
-
{ path: "channel", displayName: "Channel", type: "string" }
|
|
11164
|
-
],
|
|
11165
|
-
pendingDisplayFields: ["messageTs"]
|
|
11166
|
-
}
|
|
11167
|
-
],
|
|
11168
|
-
run: async (inputs, ctx) => {
|
|
11169
|
-
const parsed = parseDelegatedToolInputs(inputs);
|
|
11170
|
-
const values = fieldValues(parsed);
|
|
11171
|
-
const data = await executeDelegatedTool(ctx, {
|
|
11172
|
-
connection: parsed.connection,
|
|
11173
|
-
schema: SLACK_SEND_SCHEMA,
|
|
11174
|
-
toolSlug: SLACK_SEND_SLUG,
|
|
11175
|
-
values,
|
|
11176
|
-
toolkitLabel: "Slack"
|
|
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
|
|
11177
13153
|
});
|
|
11178
|
-
const envelope = data.response_data ?? data;
|
|
11179
|
-
const messageTs = String(envelope.ts ?? "");
|
|
11180
|
-
const channel = String(envelope.channel ?? values.channel ?? "");
|
|
11181
13154
|
return {
|
|
11182
|
-
|
|
11183
|
-
|
|
11184
|
-
|
|
11185
|
-
|
|
11186
|
-
|
|
11187
|
-
events: messageTs ? [{ name: SLACK_SENT_EVENT, payload: { messageTs, channel } }] : void 0
|
|
13155
|
+
question: inputs.question,
|
|
13156
|
+
evidence: result.evidence,
|
|
13157
|
+
provenance: result.provenance,
|
|
13158
|
+
evidenceReferences: result.evidenceReferences,
|
|
13159
|
+
evidenceDigest: sha256Digest(result.evidence)
|
|
11188
13160
|
};
|
|
11189
13161
|
}
|
|
11190
13162
|
});
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
|
|
11194
|
-
|
|
11195
|
-
|
|
11196
|
-
|
|
11197
|
-
|
|
11198
|
-
|
|
11199
|
-
|
|
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: {
|
|
11200
13242
|
type: "object",
|
|
11201
|
-
required: ["
|
|
13243
|
+
required: ["approvedClaimReference", "amount", "asset", "recipient", "policyReference", "confirmationReference"],
|
|
13244
|
+
additionalProperties: false,
|
|
11202
13245
|
properties: {
|
|
11203
|
-
|
|
11204
|
-
|
|
11205
|
-
|
|
11206
|
-
|
|
11207
|
-
|
|
11208
|
-
|
|
11209
|
-
calendar_id: { type: "string", title: "Calendar", description: "Use 'primary' for the author's main calendar." },
|
|
11210
|
-
event_duration_minutes: { type: "number", title: "Duration (minutes)", description: "Defaults to the calendar default if blank." }
|
|
13246
|
+
approvedClaimReference: { type: "string" },
|
|
13247
|
+
amount: { type: "string" },
|
|
13248
|
+
asset: { type: "string" },
|
|
13249
|
+
recipient: { type: "string" },
|
|
13250
|
+
policyReference: { type: "string" },
|
|
13251
|
+
confirmationReference: { type: "string" }
|
|
11211
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
|
+
});
|
|
11212
13264
|
}
|
|
11213
|
-
};
|
|
11214
|
-
|
|
11215
|
-
|
|
11216
|
-
|
|
11217
|
-
|
|
11218
|
-
|
|
11219
|
-
|
|
11220
|
-
|
|
11221
|
-
|
|
11222
|
-
|
|
11223
|
-
|
|
11224
|
-
|
|
11225
|
-
|
|
11226
|
-
|
|
11227
|
-
|
|
11228
|
-
|
|
11229
|
-
|
|
11230
|
-
|
|
11231
|
-
eligibleForEventTrigger: true,
|
|
11232
|
-
// Mirrors executeDelegatedTool's gates: the bound connection plus the
|
|
11233
|
-
// tool schema's required fields, so orchestrators ask before run() throws.
|
|
11234
|
-
getMissingInputs: (inputs) => delegatedToolMissingInputs(GOOGLECALENDAR_CREATE_SCHEMA, inputs),
|
|
11235
|
-
outputSchema: GOOGLECALENDAR_CREATE_OUTPUT_SCHEMA,
|
|
11236
|
-
events: [
|
|
11237
|
-
{
|
|
11238
|
-
name: GOOGLECALENDAR_CREATED_EVENT,
|
|
11239
|
-
displayName: "Calendar event created",
|
|
11240
|
-
description: "Fired after the event is created on the author\u2019s calendar.",
|
|
11241
|
-
payloadSchema: [
|
|
11242
|
-
{ path: "eventId", displayName: "Event ID", type: "string" },
|
|
11243
|
-
{ path: "htmlLink", displayName: "Event link", type: "string" },
|
|
11244
|
-
{ path: "summary", displayName: "Summary", type: "string" }
|
|
11245
|
-
],
|
|
11246
|
-
pendingDisplayFields: ["summary", "eventId"]
|
|
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" }
|
|
11247
13283
|
}
|
|
11248
|
-
|
|
11249
|
-
|
|
11250
|
-
|
|
11251
|
-
const
|
|
11252
|
-
|
|
11253
|
-
|
|
11254
|
-
|
|
11255
|
-
|
|
11256
|
-
|
|
11257
|
-
toolkitLabel: "Google Calendar"
|
|
11258
|
-
});
|
|
11259
|
-
const envelope = data.response_data ?? data;
|
|
11260
|
-
const eventId = String(envelope.id ?? "");
|
|
11261
|
-
const htmlLink = String(envelope.htmlLink ?? "");
|
|
11262
|
-
const summary = String(envelope.summary ?? values.summary ?? "");
|
|
11263
|
-
const start = envelope.start;
|
|
11264
|
-
const startIso = String(start?.dateTime ?? start?.date ?? values.start_datetime ?? "");
|
|
11265
|
-
return {
|
|
11266
|
-
output: { eventId, htmlLink, summary, startIso },
|
|
11267
|
-
events: eventId ? [{ name: GOOGLECALENDAR_CREATED_EVENT, payload: { eventId, htmlLink, summary } }] : void 0
|
|
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")
|
|
11268
13293
|
};
|
|
13294
|
+
return { ...payload, ...await ctx.services.incidents.escalate(payload) };
|
|
11269
13295
|
}
|
|
11270
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
|
+
});
|
|
11271
13321
|
|
|
11272
13322
|
// src/core/lib/actionRegistry/actions/calendar/eventCreate.types.ts
|
|
11273
13323
|
var EMPTY = {
|
|
@@ -11322,7 +13372,13 @@ function parseAttendeesField(raw) {
|
|
|
11322
13372
|
// src/core/lib/actionRegistry/actions/calendar/eventCreate.ts
|
|
11323
13373
|
var CALENDAR_EVENT_CREATE_SLUG = "GOOGLECALENDAR_CREATE_EVENT";
|
|
11324
13374
|
registerAction({
|
|
11325
|
-
|
|
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",
|
|
11326
13382
|
can: "calendar.event/create",
|
|
11327
13383
|
sideEffect: true,
|
|
11328
13384
|
proof: { fields: ["eventId"] },
|
|
@@ -11431,7 +13487,9 @@ registerAction({
|
|
|
11431
13487
|
var CALENDAR_EVENT_UPDATE_SLUG = "GOOGLECALENDAR_UPDATE_EVENT";
|
|
11432
13488
|
var CALENDAR_EVENT_GET_SLUG = "GOOGLECALENDAR_EVENTS_GET";
|
|
11433
13489
|
registerAction({
|
|
11434
|
-
|
|
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",
|
|
11435
13493
|
can: "calendar.event/update",
|
|
11436
13494
|
sideEffect: true,
|
|
11437
13495
|
proof: { fields: ["eventId"] },
|
|
@@ -11542,7 +13600,9 @@ registerAction({
|
|
|
11542
13600
|
// src/core/lib/actionRegistry/actions/calendar/eventList.ts
|
|
11543
13601
|
var CALENDAR_EVENT_LIST_SLUG = "GOOGLECALENDAR_EVENTS_LIST";
|
|
11544
13602
|
registerAction({
|
|
11545
|
-
|
|
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",
|
|
11546
13606
|
can: "calendar.event/list",
|
|
11547
13607
|
sideEffect: false,
|
|
11548
13608
|
proof: "none",
|
|
@@ -12352,7 +14412,7 @@ registerDiffResolver("evaluateClaim", {
|
|
|
12352
14412
|
});
|
|
12353
14413
|
|
|
12354
14414
|
// src/core/lib/actionRegistry/actions/calendar/eventCreate.diff.ts
|
|
12355
|
-
registerDiffResolver("qi/
|
|
14415
|
+
registerDiffResolver("qi/googlecalendar.event.create-self", {
|
|
12356
14416
|
resolver: async (inputs, _ctx) => {
|
|
12357
14417
|
const attendees = parseAttendeesField(String(inputs.attendees || ""));
|
|
12358
14418
|
const calendarId = String(inputs.calendar_id || "").trim() || "primary";
|
|
@@ -12433,7 +14493,7 @@ registerDiffResolver("qi/calendar.event.create", {
|
|
|
12433
14493
|
});
|
|
12434
14494
|
|
|
12435
14495
|
// src/core/lib/actionRegistry/actions/calendar/eventUpdate.diff.ts
|
|
12436
|
-
registerDiffResolver("qi/
|
|
14496
|
+
registerDiffResolver("qi/googlecalendar.event.update-self", {
|
|
12437
14497
|
resolver: async (inputs, ctx) => {
|
|
12438
14498
|
const connection = inputs.connection || {};
|
|
12439
14499
|
const connectedAccountId = connection.connectedAccountId;
|
|
@@ -12689,14 +14749,79 @@ registerDiffResolver("qi/xero.payment.create", {
|
|
|
12689
14749
|
}
|
|
12690
14750
|
});
|
|
12691
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
|
+
|
|
12692
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
|
+
}
|
|
12693
14799
|
registerDiffResolver("qi/wallet.fund", {
|
|
12694
|
-
resolver: async (inputs,
|
|
14800
|
+
resolver: async (inputs, ctx) => {
|
|
12695
14801
|
const address = String(inputs.address || "").trim();
|
|
12696
14802
|
const amount = String(inputs.amount || "250000").trim();
|
|
12697
|
-
const
|
|
12698
|
-
const
|
|
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);
|
|
12699
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
|
+
},
|
|
12700
14825
|
{
|
|
12701
14826
|
key: "recipient",
|
|
12702
14827
|
label: "Recipient",
|
|
@@ -12707,17 +14832,10 @@ registerDiffResolver("qi/wallet.fund", {
|
|
|
12707
14832
|
{
|
|
12708
14833
|
key: "amount",
|
|
12709
14834
|
label: "Amount",
|
|
12710
|
-
before: "0
|
|
12711
|
-
after: `${
|
|
14835
|
+
before: "0",
|
|
14836
|
+
after: exponent === void 0 ? `${amount} ${denom}` : `${formatTokenAmount(amount, denom, exponent, symbol)} (${amount} ${denom})`,
|
|
12712
14837
|
changeType: "replace",
|
|
12713
14838
|
severity: "info"
|
|
12714
|
-
},
|
|
12715
|
-
{
|
|
12716
|
-
key: "network",
|
|
12717
|
-
label: "Network",
|
|
12718
|
-
before: network,
|
|
12719
|
-
after: network,
|
|
12720
|
-
changeType: "unchanged"
|
|
12721
14839
|
}
|
|
12722
14840
|
];
|
|
12723
14841
|
}
|
|
@@ -13023,6 +15141,7 @@ function roleLabel(value) {
|
|
|
13023
15141
|
var IXO_DENOM = "uixo";
|
|
13024
15142
|
var USDC_DENOM2 = "ibc/6BBE9BD4246F8E04948D5A4EEE7164B2630263B9EBB5E7DC5F0A46C62A2FF97B";
|
|
13025
15143
|
var DENOM_DECIMALS = 6;
|
|
15144
|
+
var MAX_LISTED_ADDRESSES = 5;
|
|
13026
15145
|
function formatCoin4(value) {
|
|
13027
15146
|
const coin = value;
|
|
13028
15147
|
const denomValue = String(coin?.denom || "");
|
|
@@ -13039,16 +15158,34 @@ function diffAdd(inputs) {
|
|
|
13039
15158
|
if (inputs.collectionId) {
|
|
13040
15159
|
results.push({ key: "collectionId", label: "Collection", before: null, after: String(inputs.collectionId), changeType: "add" });
|
|
13041
15160
|
}
|
|
13042
|
-
if (kind === "group-members") {
|
|
13043
|
-
const
|
|
15161
|
+
if (kind === "address-list" || kind === "group-members") {
|
|
15162
|
+
const addresses = kind === "address-list" ? (Array.isArray(inputs.granteeAddresses) ? inputs.granteeAddresses : []).map((a) => String(a || "").trim()).filter(Boolean) : (Array.isArray(inputs.members) ? inputs.members : []).map((m) => String(m?.address || "").trim()).filter(Boolean);
|
|
13044
15163
|
results.push({
|
|
13045
15164
|
key: "grantees",
|
|
13046
|
-
label: "Grantees (fan-out)",
|
|
15165
|
+
label: kind === "address-list" ? "Grantees (pasted list)" : "Grantees (fan-out)",
|
|
13047
15166
|
before: null,
|
|
13048
|
-
after: `${
|
|
15167
|
+
after: kind === "address-list" ? `${addresses.length} account${addresses.length === 1 ? "" : "s"}` : `${addresses.length} group member${addresses.length === 1 ? "" : "s"}`,
|
|
13049
15168
|
changeType: "add",
|
|
13050
15169
|
severity: "warning"
|
|
13051
15170
|
});
|
|
15171
|
+
if (addresses.length > 0) {
|
|
15172
|
+
results.push({
|
|
15173
|
+
key: "granteeAddresses",
|
|
15174
|
+
label: "Accounts",
|
|
15175
|
+
before: null,
|
|
15176
|
+
after: addresses.length > MAX_LISTED_ADDRESSES ? `${addresses.slice(0, MAX_LISTED_ADDRESSES).join(", ")} \u2026 +${addresses.length - MAX_LISTED_ADDRESSES} more` : addresses.join(", "),
|
|
15177
|
+
changeType: "add"
|
|
15178
|
+
});
|
|
15179
|
+
const batches = collectionUsersBatchCount(addresses.length);
|
|
15180
|
+
results.push({
|
|
15181
|
+
key: "transactions",
|
|
15182
|
+
label: "Transactions to sign",
|
|
15183
|
+
before: null,
|
|
15184
|
+
after: `${batches} (up to ${COLLECTION_USERS_MAX_GRANTS_PER_TX} grants each)`,
|
|
15185
|
+
changeType: "add",
|
|
15186
|
+
severity: batches > 1 ? "warning" : void 0
|
|
15187
|
+
});
|
|
15188
|
+
}
|
|
13052
15189
|
} else {
|
|
13053
15190
|
const label = kind === "group-account" ? "Grantee (group account)" : "Grantee";
|
|
13054
15191
|
results.push({ key: "grantee", label, before: null, after: String(inputs.granteeAddress || "\u2014"), changeType: "add" });
|
|
@@ -13211,7 +15348,7 @@ registerDiffResolver(EVAL_ENGINE_ACTION_TYPE, {
|
|
|
13211
15348
|
key: "decisions",
|
|
13212
15349
|
label: "Decisions",
|
|
13213
15350
|
before: null,
|
|
13214
|
-
after: inputs?.allowChainEvaluation
|
|
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",
|
|
13215
15352
|
changeType: "add"
|
|
13216
15353
|
}
|
|
13217
15354
|
];
|
|
@@ -13921,7 +16058,10 @@ function resolveReferencesDetailed(input, editorDocument, options = {}) {
|
|
|
13921
16058
|
}
|
|
13922
16059
|
if (warnContext && unresolved.length > 0) {
|
|
13923
16060
|
for (const entry of unresolved) {
|
|
13924
|
-
warnOnce(
|
|
16061
|
+
warnOnce(
|
|
16062
|
+
`ref-unresolved:${warnContext}:${entry.ref}`,
|
|
16063
|
+
`[flow-config] ${warnContext}: reference ${entry.ref} did not resolve (${entry.reason}); using fallback '${fallback}'`
|
|
16064
|
+
);
|
|
13925
16065
|
}
|
|
13926
16066
|
}
|
|
13927
16067
|
return { value: result, unresolved };
|
|
@@ -14759,8 +16899,8 @@ function fnv1a322(input, seed) {
|
|
|
14759
16899
|
}
|
|
14760
16900
|
function createRunEventIdempotencyKey(kind, ...identity) {
|
|
14761
16901
|
const canonical = JSON.stringify([kind, ...identity]);
|
|
14762
|
-
const
|
|
14763
|
-
return `v1:${kind.replace(/\./g, "_")}:${
|
|
16902
|
+
const digest2 = `${fnv1a322(canonical, 2166136261)}${fnv1a322(canonical, 2654435761)}`;
|
|
16903
|
+
return `v1:${kind.replace(/\./g, "_")}:${digest2}`;
|
|
14764
16904
|
}
|
|
14765
16905
|
function boundRunActionOutput(value) {
|
|
14766
16906
|
const byteLength = jsonByteLength(value);
|
|
@@ -17055,10 +19195,35 @@ async function executeActionBlock(params) {
|
|
|
17055
19195
|
let requestedAwaitingReadBack = false;
|
|
17056
19196
|
let proofFailureReason = null;
|
|
17057
19197
|
let proofFailureOutput;
|
|
19198
|
+
let topicRecords = [];
|
|
17058
19199
|
const startedAt = now();
|
|
17059
19200
|
const previousState = runtime.get(blockId);
|
|
17060
19201
|
const attempt = (previousState.attempt || 0) + 1;
|
|
17061
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
|
+
};
|
|
17062
19227
|
const timelineRequired = !!yDoc && !usesLegacyRuntimeCompatibility(yDoc);
|
|
17063
19228
|
if (sessionRunId && (eventLog || timelineRequired)) {
|
|
17064
19229
|
const startedLogged = await appendRunTimelineEvent({
|
|
@@ -17107,6 +19272,18 @@ async function executeActionBlock(params) {
|
|
|
17107
19272
|
executionId,
|
|
17108
19273
|
executionStartedAt: startedAt
|
|
17109
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 };
|
|
17110
19287
|
const outcome = await executeNode({
|
|
17111
19288
|
node: flowNode,
|
|
17112
19289
|
actorDid: params.actorDid,
|
|
@@ -17132,13 +19309,16 @@ async function executeActionBlock(params) {
|
|
|
17132
19309
|
nodeId: blockId,
|
|
17133
19310
|
flowNode,
|
|
17134
19311
|
runtime,
|
|
17135
|
-
services:
|
|
19312
|
+
services: actionServices,
|
|
17136
19313
|
handlers: params.handlers,
|
|
17137
19314
|
editor,
|
|
17138
19315
|
yDoc,
|
|
17139
|
-
pendingInvocation: inputBuild.pendingInvocation
|
|
19316
|
+
pendingInvocation: inputBuild.pendingInvocation,
|
|
19317
|
+
topic: params.topic,
|
|
19318
|
+
flowRevision: params.flowRevision
|
|
17140
19319
|
});
|
|
17141
19320
|
if (result.events?.length) events.push(...result.events);
|
|
19321
|
+
if (result.topicRecords?.length) topicRecords = result.topicRecords;
|
|
17142
19322
|
if (result.completion?.state === "awaiting_readback") {
|
|
17143
19323
|
requestedAwaitingReadBack = true;
|
|
17144
19324
|
rawReadBack = result.completion.readBack;
|
|
@@ -17201,7 +19381,12 @@ async function executeActionBlock(params) {
|
|
|
17201
19381
|
invocationCid: outcome.invocationCid,
|
|
17202
19382
|
capabilityId: outcome.capabilityId,
|
|
17203
19383
|
executionId,
|
|
17204
|
-
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
|
+
})
|
|
17205
19390
|
};
|
|
17206
19391
|
}
|
|
17207
19392
|
updateRuntimeFailure(runtime, blockId, message, now);
|
|
@@ -17226,6 +19411,10 @@ async function executeActionBlock(params) {
|
|
|
17226
19411
|
now
|
|
17227
19412
|
});
|
|
17228
19413
|
}
|
|
19414
|
+
const topicWriteBack2 = await recordTopicPhase(outcome.stage === "authorization" ? "rejected" : "failed", {
|
|
19415
|
+
error: { message },
|
|
19416
|
+
invocationReference: outcome.invocationCid || outcome.capabilityId
|
|
19417
|
+
});
|
|
17229
19418
|
return {
|
|
17230
19419
|
...buildFailureResult({
|
|
17231
19420
|
blockId,
|
|
@@ -17237,7 +19426,8 @@ async function executeActionBlock(params) {
|
|
|
17237
19426
|
invocationCid: outcome.invocationCid,
|
|
17238
19427
|
capabilityId: outcome.capabilityId,
|
|
17239
19428
|
executionId,
|
|
17240
|
-
runId: executionId
|
|
19429
|
+
runId: executionId,
|
|
19430
|
+
topicWriteBack: topicWriteBack2
|
|
17241
19431
|
};
|
|
17242
19432
|
}
|
|
17243
19433
|
const output = outcome.result?.payload || {};
|
|
@@ -17350,6 +19540,11 @@ async function executeActionBlock(params) {
|
|
|
17350
19540
|
});
|
|
17351
19541
|
}
|
|
17352
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
|
+
});
|
|
17353
19548
|
return {
|
|
17354
19549
|
success: !boundedOutput.exceeded,
|
|
17355
19550
|
stage: outcome.stage,
|
|
@@ -17367,7 +19562,8 @@ async function executeActionBlock(params) {
|
|
|
17367
19562
|
executionId,
|
|
17368
19563
|
pendingInvocationRemoved,
|
|
17369
19564
|
completionState,
|
|
17370
|
-
pendingInvocation: inputBuild.pendingInvocation
|
|
19565
|
+
pendingInvocation: inputBuild.pendingInvocation,
|
|
19566
|
+
topicWriteBack
|
|
17371
19567
|
};
|
|
17372
19568
|
}
|
|
17373
19569
|
|
|
@@ -18069,6 +20265,17 @@ function compileBlockProps(cap, registryType) {
|
|
|
18069
20265
|
var COMPILED_BLOCK_TYPE = "action";
|
|
18070
20266
|
|
|
18071
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
|
+
}
|
|
18072
20279
|
function compileBaseUcanFlow(plan, registry) {
|
|
18073
20280
|
if (!Array.isArray(plan.capabilities)) {
|
|
18074
20281
|
throw new Error("BaseUcanFlow.capabilities must be an array");
|
|
@@ -18166,6 +20373,21 @@ function compileBaseUcanFlow(plan, registry) {
|
|
|
18166
20373
|
});
|
|
18167
20374
|
}
|
|
18168
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
|
+
}
|
|
18169
20391
|
if (trigger.type === "block.event" || trigger.type === "block.event.all") {
|
|
18170
20392
|
const refs = collectOutputRefs(cap.nb || {});
|
|
18171
20393
|
for (const ref of refs) {
|
|
@@ -18883,7 +21105,8 @@ function readFlow() {
|
|
|
18883
21105
|
}
|
|
18884
21106
|
async function setupFlowFromBaseUcan(options) {
|
|
18885
21107
|
const { plan: rawPlan, roomId, matrixClient, creatorDid, docId, templateId, strategy = "full" } = options;
|
|
18886
|
-
const
|
|
21108
|
+
const identified = rawPlan.flowId ? rawPlan : { ...rawPlan, flowId: docId || roomId };
|
|
21109
|
+
const plan = templateId ? stripActiveScheduleBindings(identified) : identified;
|
|
18887
21110
|
const incomingCompiled = compileBaseUcanFlow(plan, { getActionByCan });
|
|
18888
21111
|
const { yDoc, provider } = await connectToRoom(roomId, matrixClient, { adoptRuns: true });
|
|
18889
21112
|
let finalCompiled;
|
|
@@ -20199,10 +22422,17 @@ export {
|
|
|
20199
22422
|
matrixUserIdToDid,
|
|
20200
22423
|
findOrCreateDMRoom,
|
|
20201
22424
|
sendDirectMessage,
|
|
22425
|
+
canonicalActionJson,
|
|
22426
|
+
sha256Digest,
|
|
22427
|
+
MAX_TOPIC_SEMANTIC_RECORDS_PER_RECEIPT,
|
|
22428
|
+
MAX_TOPIC_SEMANTIC_RECORD_BATCH_BYTES,
|
|
22429
|
+
validateTopicSemanticRecord,
|
|
22430
|
+
validateTopicSemanticRecordBatch,
|
|
20202
22431
|
canToType,
|
|
20203
22432
|
typeToCan,
|
|
20204
22433
|
getAllCanMappings,
|
|
20205
22434
|
warnOnce,
|
|
22435
|
+
getActionPresentation,
|
|
20206
22436
|
STEP_COMPLETED_EVENT_NAME,
|
|
20207
22437
|
STEP_COMPLETED_EVENT,
|
|
20208
22438
|
doneWhenCompleted,
|
|
@@ -20221,7 +22451,10 @@ export {
|
|
|
20221
22451
|
getActionByCan,
|
|
20222
22452
|
getEventsForBlock,
|
|
20223
22453
|
getOutputSchemaForBlock,
|
|
22454
|
+
ACTION_MANIFEST_VERSION,
|
|
22455
|
+
ACTION_REGISTRY_VERSION,
|
|
20224
22456
|
generateActionManifest,
|
|
22457
|
+
actionManifestIssues,
|
|
20225
22458
|
isBlankInputValue,
|
|
20226
22459
|
getMissingActionInputs,
|
|
20227
22460
|
SERVICE_VERBS,
|
|
@@ -20262,12 +22495,15 @@ export {
|
|
|
20262
22495
|
buildGovernanceGroupLinkedEntities,
|
|
20263
22496
|
tempDomainCreatorSurvey,
|
|
20264
22497
|
resolveEntityTypeFromSchema,
|
|
22498
|
+
COLLECTION_USERS_MAX_GRANTS_PER_TX,
|
|
22499
|
+
collectionUsersBatchCount,
|
|
20265
22500
|
REPEAT_SUBMISSIONS_OPTIONS,
|
|
20266
22501
|
DIFFERENT_WHEN_OPTIONS,
|
|
20267
22502
|
WHAT_IS_COLLECTED_MAX,
|
|
20268
22503
|
DIFFERENT_WHEN_OTHER_MAX,
|
|
20269
22504
|
normalizeDifferentWhen,
|
|
20270
22505
|
normalizeRepeatSubmissions,
|
|
22506
|
+
FORM_SEGMENT,
|
|
20271
22507
|
extractRubricFieldCatalog,
|
|
20272
22508
|
buildRubricEnvelope,
|
|
20273
22509
|
parsePublishedRubric,
|
|
@@ -20307,6 +22543,8 @@ export {
|
|
|
20307
22543
|
renderNumber,
|
|
20308
22544
|
formatCoin2 as formatCoin,
|
|
20309
22545
|
formatCoinAmount,
|
|
22546
|
+
toBaseUnits,
|
|
22547
|
+
formatTokenAmount,
|
|
20310
22548
|
DM_NOTIFICATIONS_MAP_KEY,
|
|
20311
22549
|
getDMNotificationState,
|
|
20312
22550
|
setDMNotificationRecord,
|
|
@@ -20509,4 +22747,4 @@ export {
|
|
|
20509
22747
|
executeQueuedFlowAgentCoreCommands,
|
|
20510
22748
|
FlowAgentService
|
|
20511
22749
|
};
|
|
20512
|
-
//# sourceMappingURL=chunk-
|
|
22750
|
+
//# sourceMappingURL=chunk-TBCPHCFA.js.map
|