@muxoai/platform 1.4.0 → 1.4.2
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/.agents/plugins/marketplace.json +2 -2
- package/.claude-plugin/marketplace.json +2 -2
- package/ARCHITECTURE.md +16 -6
- package/README.md +2 -2
- package/dist/cli.js +323 -13
- package/dist/composio-catalog.d.ts +234 -0
- package/dist/composio-catalog.js +1894 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.js +27 -1
- package/dist/durable-workflow.d.ts +45 -0
- package/dist/durable-workflow.js +90 -0
- package/dist/engine.d.ts +293 -5
- package/dist/engine.js +1502 -88
- package/dist/mcp-schema.d.ts +286 -0
- package/dist/mcp-schema.js +2559 -0
- package/dist/mcp.d.ts +2 -30
- package/dist/mcp.js +913 -558
- package/dist/memory-graph.d.ts +21 -0
- package/dist/memory-graph.js +173 -0
- package/dist/nl.js +155 -0
- package/dist/oauth.d.ts +32 -0
- package/dist/oauth.js +134 -0
- package/dist/policy.d.ts +182 -1
- package/dist/policy.js +1524 -0
- package/dist/providers/composio-instant.js +262 -7
- package/dist/providers/composio-sdk.d.ts +125 -5
- package/dist/providers/composio-sdk.js +901 -82
- package/dist/providers/microsoft-governance.d.ts +28 -1
- package/dist/providers/microsoft-governance.js +224 -11
- package/dist/providers/stripe-billing.d.ts +42 -0
- package/dist/providers/stripe-billing.js +230 -1
- package/dist/providers/stripe-projects.d.ts +455 -3
- package/dist/providers/stripe-projects.js +2570 -129
- package/dist/providers/supermemory.d.ts +74 -1
- package/dist/providers/supermemory.js +426 -0
- package/dist/provisioning-managed.d.ts +50 -0
- package/dist/provisioning-managed.js +155 -0
- package/dist/sdk.d.ts +440 -3
- package/dist/sdk.js +305 -3
- package/dist/server.js +2343 -264
- package/dist/store.d.ts +113 -6
- package/dist/store.js +677 -52
- package/dist/types.d.ts +207 -3
- package/dist/ui/console-html.d.ts +40 -1
- package/dist/ui/console-html.js +1813 -629
- package/dist/ui/design-tokens.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -3
- package/plugins/muxo/.claude-plugin/plugin.json +2 -2
- package/plugins/muxo/.codex-plugin/plugin.json +15 -8
- package/plugins/muxo/.cursor-plugin/plugin.json +30 -0
- package/plugins/muxo/hooks/session-start.sh +15 -3
- package/plugins/muxo/hooks/user-prompt-submit.sh +14 -1
- package/plugins/muxo/mcp.json +0 -10
- package/plugins/muxo/plugin.json +4 -4
- package/plugins/muxo/skills/muxo/SKILL.md +59 -9
- package/plugins/muxo/skills/muxo/references/cli.md +5 -1
- package/plugins/muxo/skills/muxo/references/mcp.md +13 -7
- package/plugins/muxo/skills/muxo-activity-summary/SKILL.md +81 -0
- package/plugins/muxo/skills/setup/SKILL.md +9 -1
- package/src/catalogues/stripe-projects-catalog.json +140 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AgentIdentity, AuditLogger, type ContextPoisoningScanResult, type KillSwitchResult, type TrustScore } from "../../vendor/microsoft-governance/dist/index.js";
|
|
2
|
-
import type { ActionPlan, GovernanceDecisionRecord, MuxoTool, Workspace } from "../types.js";
|
|
2
|
+
import type { ActionPlan, ExecutionReceipt, ExecutionRun, GovernanceDecisionRecord, GovernancePolicy, HumanFriendlyExplanation, LanguageMode, MuxoTool, PortableAgenticReceipt, Workspace } from "../types.js";
|
|
3
3
|
export declare const RING_LABELS: Record<number, string>;
|
|
4
4
|
export declare class MicrosoftGovernanceAdapter {
|
|
5
5
|
private readonly scanner;
|
|
@@ -108,6 +108,20 @@ export declare class MicrosoftGovernanceAdapter {
|
|
|
108
108
|
* Verifies a DecisionRecord's Ed25519 signature and the workspace's SHA-256 audit hash chain.
|
|
109
109
|
*/
|
|
110
110
|
verifyDecisionRecord(workspaceOrRecord: Workspace | GovernanceDecisionRecord, maybeRecord?: GovernanceDecisionRecord): boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Builds a portable Ed25519-signed AgenticReceipt envelope (compatible with the `agentic-receipts` spec)
|
|
113
|
+
* so external verifiers can validate input/output SHA-256 hashes, hash chaining, and Ed25519 signature
|
|
114
|
+
* using only the workspace's public key and DID without needing the internal vault HMAC key.
|
|
115
|
+
*/
|
|
116
|
+
buildPortableAgenticReceipt(params: {
|
|
117
|
+
receipt: ExecutionReceipt;
|
|
118
|
+
run?: ExecutionRun | null;
|
|
119
|
+
workspace?: Workspace | null;
|
|
120
|
+
}): PortableAgenticReceipt;
|
|
121
|
+
/**
|
|
122
|
+
* Verifies a PortableAgenticReceipt's Ed25519 signature over its canonical hash-chained payload.
|
|
123
|
+
*/
|
|
124
|
+
verifyPortableAgenticReceipt(agenticReceipt: PortableAgenticReceipt, workspace?: Workspace | null): boolean;
|
|
111
125
|
getStatus(workspace?: Workspace): {
|
|
112
126
|
sdk: string;
|
|
113
127
|
sdkVersion: string;
|
|
@@ -123,3 +137,16 @@ export declare class MicrosoftGovernanceAdapter {
|
|
|
123
137
|
*/
|
|
124
138
|
getGovernanceStatus(workspace: Workspace): Record<string, unknown>;
|
|
125
139
|
}
|
|
140
|
+
export declare function explainDecisionRecord(record: GovernanceDecisionRecord, policy?: Partial<GovernancePolicy> | null, languageMode?: LanguageMode): HumanFriendlyExplanation;
|
|
141
|
+
export declare function explainGovernanceStatus(params: {
|
|
142
|
+
workspace: Workspace;
|
|
143
|
+
agentDid: string;
|
|
144
|
+
trustScore: NonNullable<GovernanceDecisionRecord["trustScore"]>;
|
|
145
|
+
killSwitchActive: boolean;
|
|
146
|
+
killSwitchReason: string | null;
|
|
147
|
+
poisoned: boolean;
|
|
148
|
+
findingsCount: number;
|
|
149
|
+
auditEntriesCount: number;
|
|
150
|
+
chainVerified: boolean;
|
|
151
|
+
languageMode?: LanguageMode;
|
|
152
|
+
}): HumanFriendlyExplanation;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import { AgentIdentity, AuditLogger, ContextPoisoningDetector, ExecutionRing, FacetRegistry, KillSwitch, McpSecurityScanner, PolicyEngine, RingEnforcer, TrustManager, extractK8sFacets, extractProtocolFacets, extractSqlFacets, } from "../../vendor/microsoft-governance/dist/index.js";
|
|
3
|
+
import { describePolicyInPlainEnglish, evaluateParameterPolicyRules, formatHumanFriendlyExplanation, } from "../policy.js";
|
|
3
4
|
const SDK_VERSION = "5.0.0";
|
|
4
5
|
const POLICY_ID = "muxo-microsoft-agent-governance-v5";
|
|
5
6
|
export const RING_LABELS = {
|
|
@@ -555,10 +556,24 @@ export class MicrosoftGovernanceAdapter {
|
|
|
555
556
|
const highSeverityFinding = extraFindings.some((f) => f.severity === "high" || f.severity === "critical");
|
|
556
557
|
// 2. Protocol Facets Extraction (SQL, Kubernetes, Infrastructure)
|
|
557
558
|
const protocolFacets = this.extractActionFacets(tool, input);
|
|
558
|
-
// 3.
|
|
559
|
-
const ringEnforcement = this.evaluateRingEnforcement(workspace, tool, ownerApproved);
|
|
560
|
-
// 4. PolicyEngine + KillSwitch evaluation
|
|
559
|
+
// 3. PolicyEngine + KillSwitch + Parameter Guardrail evaluation
|
|
561
560
|
const policy = workspace.policy;
|
|
561
|
+
const paramRuleCheck = evaluateParameterPolicyRules({
|
|
562
|
+
policy,
|
|
563
|
+
toolSlug: tool.slug,
|
|
564
|
+
input,
|
|
565
|
+
});
|
|
566
|
+
if (paramRuleCheck.violated && paramRuleCheck.reason) {
|
|
567
|
+
extraFindings.push({
|
|
568
|
+
category: "parameter_policy_rule",
|
|
569
|
+
severity: paramRuleCheck.action === "block" ? "high" : "medium",
|
|
570
|
+
description: paramRuleCheck.reason,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
const effectiveApproved = ownerApproved ||
|
|
574
|
+
Boolean(paramRuleCheck.explicitAllow && paramRuleCheck.action === "auto_approve");
|
|
575
|
+
// 4. Hardware-style Execution Ring Enforcement (Ring 0..Ring 3)
|
|
576
|
+
const ringEnforcement = this.evaluateRingEnforcement(workspace, tool, effectiveApproved);
|
|
562
577
|
const activeKill = this.isKillSwitchActive(workspace.id);
|
|
563
578
|
const killSwitchActive = Boolean(activeKill || policy.killSwitchActive);
|
|
564
579
|
const killSwitchReason = activeKill?.reason || policy.killSwitchReason || null;
|
|
@@ -569,7 +584,7 @@ export class MicrosoftGovernanceAdapter {
|
|
|
569
584
|
risk_level: tool.riskLevel,
|
|
570
585
|
lane: tool.lane,
|
|
571
586
|
stage,
|
|
572
|
-
owner_approved:
|
|
587
|
+
owner_approved: effectiveApproved,
|
|
573
588
|
workspace_active: true,
|
|
574
589
|
...(protocolFacets.sql ? { sql: protocolFacets.sql } : {}),
|
|
575
590
|
...(protocolFacets.k8s ? { k8s: protocolFacets.k8s } : {}),
|
|
@@ -580,8 +595,12 @@ export class MicrosoftGovernanceAdapter {
|
|
|
580
595
|
: policyDecision.action === "require_approval"
|
|
581
596
|
? "require_approval"
|
|
582
597
|
: "allow";
|
|
583
|
-
let matchedRule =
|
|
584
|
-
|
|
598
|
+
let matchedRule = paramRuleCheck.explicitAllow &&
|
|
599
|
+
paramRuleCheck.action === "auto_approve" &&
|
|
600
|
+
finalDecision === "allow"
|
|
601
|
+
? paramRuleCheck.matchedRule?.id || "auto-approve-parameter-policy-rule"
|
|
602
|
+
: policyDecision.matchedRule ?? null;
|
|
603
|
+
// Enforce KillSwitch, blocked tools, parameter rules, high-severity scanner/context-poisoning findings, or Ring breach
|
|
585
604
|
if (killSwitchActive) {
|
|
586
605
|
finalDecision = "deny";
|
|
587
606
|
matchedRule = "kill-switch-emergency-quarantine";
|
|
@@ -590,6 +609,19 @@ export class MicrosoftGovernanceAdapter {
|
|
|
590
609
|
finalDecision = "deny";
|
|
591
610
|
matchedRule = matchedRule || `deny-blocked-tool-${tool.slug}`;
|
|
592
611
|
}
|
|
612
|
+
else if (paramRuleCheck.violated &&
|
|
613
|
+
paramRuleCheck.action === "block" &&
|
|
614
|
+
policy.mode === "enforce") {
|
|
615
|
+
finalDecision = "deny";
|
|
616
|
+
matchedRule = paramRuleCheck.matchedRule?.id || "deny-parameter-policy-rule";
|
|
617
|
+
}
|
|
618
|
+
else if (paramRuleCheck.violated &&
|
|
619
|
+
paramRuleCheck.action === "require_approval" &&
|
|
620
|
+
!ownerApproved &&
|
|
621
|
+
policy.mode === "enforce") {
|
|
622
|
+
finalDecision = "require_approval";
|
|
623
|
+
matchedRule = paramRuleCheck.matchedRule?.id || "approval-parameter-policy-rule";
|
|
624
|
+
}
|
|
593
625
|
else if (highSeverityFinding && policy.mode === "enforce") {
|
|
594
626
|
finalDecision = "deny";
|
|
595
627
|
matchedRule = "mcp-security-scanner-high-severity";
|
|
@@ -651,13 +683,17 @@ export class MicrosoftGovernanceAdapter {
|
|
|
651
683
|
const decisionReason = finalDecision === "deny"
|
|
652
684
|
? killSwitchActive
|
|
653
685
|
? `Blocked by Emergency KillSwitch: ${killSwitchReason || "Workspace agent boundary is quarantined"}`
|
|
654
|
-
:
|
|
655
|
-
?
|
|
656
|
-
:
|
|
686
|
+
: paramRuleCheck.violated && paramRuleCheck.reason
|
|
687
|
+
? paramRuleCheck.reason
|
|
688
|
+
: highSeverityFinding
|
|
689
|
+
? `Blocked by McpSecurityScanner / ContextPoisoningDetector: ${extraFindings.map((f) => f.description).join("; ")}`
|
|
690
|
+
: `Denied by policy rule ${matchedRule || "default"}`
|
|
657
691
|
: finalDecision === "require_approval"
|
|
658
|
-
?
|
|
692
|
+
? paramRuleCheck.violated && paramRuleCheck.reason
|
|
693
|
+
? paramRuleCheck.reason
|
|
694
|
+
: `Escalated for human owner approval (${tool.riskLevel} risk lane, ${ringEnforcement.ringLabel})`
|
|
659
695
|
: `Allowed by Microsoft Agent Governance (${matchedRule || "allow-governed-action"})`;
|
|
660
|
-
|
|
696
|
+
const record = {
|
|
661
697
|
id: `dec_${crypto.randomBytes(8).toString("hex")}`,
|
|
662
698
|
sdk: "@microsoft/agent-governance-sdk",
|
|
663
699
|
sdkVersion: SDK_VERSION,
|
|
@@ -711,6 +747,8 @@ export class MicrosoftGovernanceAdapter {
|
|
|
711
747
|
bindingHash,
|
|
712
748
|
decidedAt,
|
|
713
749
|
};
|
|
750
|
+
record.explanation = explainDecisionRecord(record, policy);
|
|
751
|
+
return record;
|
|
714
752
|
}
|
|
715
753
|
evaluatePlan(params) {
|
|
716
754
|
return this.evaluateAction({
|
|
@@ -775,6 +813,112 @@ export class MicrosoftGovernanceAdapter {
|
|
|
775
813
|
const auditChainVerified = auditLogger.verify();
|
|
776
814
|
return ed25519Verified && auditChainVerified;
|
|
777
815
|
}
|
|
816
|
+
/**
|
|
817
|
+
* Builds a portable Ed25519-signed AgenticReceipt envelope (compatible with the `agentic-receipts` spec)
|
|
818
|
+
* so external verifiers can validate input/output SHA-256 hashes, hash chaining, and Ed25519 signature
|
|
819
|
+
* using only the workspace's public key and DID without needing the internal vault HMAC key.
|
|
820
|
+
*/
|
|
821
|
+
buildPortableAgenticReceipt(params) {
|
|
822
|
+
const { receipt, run, workspace } = params;
|
|
823
|
+
const identity = workspace
|
|
824
|
+
? this.getWorkspaceIdentity(workspace)
|
|
825
|
+
: this.identities.get(receipt.workspaceId) || this.defaultIdentity;
|
|
826
|
+
const actionType = receipt.lane === "stripe_projects" ? "ApiCall" : "ToolCall";
|
|
827
|
+
const startedAt = run?.startedAt || receipt.issuedAt;
|
|
828
|
+
const completedAt = run?.completedAt || receipt.issuedAt;
|
|
829
|
+
const latencyMs = Math.max(0, Number(run?.durationMs ?? 0));
|
|
830
|
+
const nonce = crypto
|
|
831
|
+
.createHash("sha256")
|
|
832
|
+
.update(`muxo-agentic-receipt-nonce:${receipt.id}:${receipt.runId}`)
|
|
833
|
+
.digest("hex")
|
|
834
|
+
.slice(0, 32);
|
|
835
|
+
const previousReceiptHash = receipt.prevReceiptHash || "genesis_0000000000000000";
|
|
836
|
+
const canonicalPayload = [
|
|
837
|
+
"0.1.0",
|
|
838
|
+
receipt.id,
|
|
839
|
+
identity.did,
|
|
840
|
+
receipt.runId,
|
|
841
|
+
actionType,
|
|
842
|
+
receipt.toolSlug || "",
|
|
843
|
+
receipt.inputHash,
|
|
844
|
+
receipt.outputHash,
|
|
845
|
+
startedAt,
|
|
846
|
+
completedAt,
|
|
847
|
+
String(latencyMs),
|
|
848
|
+
nonce,
|
|
849
|
+
previousReceiptHash || "genesis",
|
|
850
|
+
receipt.receiptHash,
|
|
851
|
+
].join("|");
|
|
852
|
+
const sigBytes = identity.sign(Buffer.from(canonicalPayload, "utf8"));
|
|
853
|
+
const signatureHex = Buffer.from(sigBytes).toString("hex");
|
|
854
|
+
const publicKeyHex = Buffer.from(identity.publicKey).toString("hex");
|
|
855
|
+
return {
|
|
856
|
+
version: "0.1.0",
|
|
857
|
+
receipt_id: receipt.id,
|
|
858
|
+
agent_id: identity.did,
|
|
859
|
+
task_id: receipt.runId,
|
|
860
|
+
action_type: actionType,
|
|
861
|
+
tool_name: receipt.toolSlug,
|
|
862
|
+
model_id: null,
|
|
863
|
+
input_hash: receipt.inputHash,
|
|
864
|
+
output_hash: receipt.outputHash,
|
|
865
|
+
started_at: startedAt,
|
|
866
|
+
completed_at: completedAt,
|
|
867
|
+
latency_ms: latencyMs,
|
|
868
|
+
nonce,
|
|
869
|
+
previous_receipt_hash: previousReceiptHash,
|
|
870
|
+
receipt_hash: receipt.receiptHash,
|
|
871
|
+
signature_algorithm: "Ed25519",
|
|
872
|
+
public_key_hex: publicKeyHex,
|
|
873
|
+
signature_hex: signatureHex,
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Verifies a PortableAgenticReceipt's Ed25519 signature over its canonical hash-chained payload.
|
|
878
|
+
*/
|
|
879
|
+
verifyPortableAgenticReceipt(agenticReceipt, workspace) {
|
|
880
|
+
if (!agenticReceipt || !agenticReceipt.signature_hex || !agenticReceipt.public_key_hex) {
|
|
881
|
+
return false;
|
|
882
|
+
}
|
|
883
|
+
const canonicalPayload = [
|
|
884
|
+
agenticReceipt.version || "0.1.0",
|
|
885
|
+
agenticReceipt.receipt_id,
|
|
886
|
+
agenticReceipt.agent_id,
|
|
887
|
+
agenticReceipt.task_id,
|
|
888
|
+
agenticReceipt.action_type,
|
|
889
|
+
agenticReceipt.tool_name || "",
|
|
890
|
+
agenticReceipt.input_hash,
|
|
891
|
+
agenticReceipt.output_hash,
|
|
892
|
+
agenticReceipt.started_at,
|
|
893
|
+
agenticReceipt.completed_at,
|
|
894
|
+
String(agenticReceipt.latency_ms ?? 0),
|
|
895
|
+
agenticReceipt.nonce,
|
|
896
|
+
agenticReceipt.previous_receipt_hash || "genesis",
|
|
897
|
+
agenticReceipt.receipt_hash,
|
|
898
|
+
].join("|");
|
|
899
|
+
try {
|
|
900
|
+
const identity = workspace ? this.getWorkspaceIdentity(workspace) : null;
|
|
901
|
+
if (identity && identity.did === agenticReceipt.agent_id) {
|
|
902
|
+
return identity.verify(Buffer.from(canonicalPayload, "utf8"), Buffer.from(agenticReceipt.signature_hex, "hex"));
|
|
903
|
+
}
|
|
904
|
+
const pubKeyBytes = Buffer.from(agenticReceipt.public_key_hex, "hex");
|
|
905
|
+
const spkiKey = pubKeyBytes.length === 32
|
|
906
|
+
? Buffer.concat([
|
|
907
|
+
Buffer.from("302a300506032b6570032100", "hex"),
|
|
908
|
+
pubKeyBytes,
|
|
909
|
+
])
|
|
910
|
+
: pubKeyBytes;
|
|
911
|
+
const pubKeyObj = crypto.createPublicKey({
|
|
912
|
+
key: spkiKey,
|
|
913
|
+
format: "der",
|
|
914
|
+
type: "spki",
|
|
915
|
+
});
|
|
916
|
+
return crypto.verify(null, Buffer.from(canonicalPayload, "utf8"), pubKeyObj, Buffer.from(agenticReceipt.signature_hex, "hex"));
|
|
917
|
+
}
|
|
918
|
+
catch {
|
|
919
|
+
return false;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
778
922
|
getStatus(workspace) {
|
|
779
923
|
const identity = workspace
|
|
780
924
|
? this.getWorkspaceIdentity(workspace)
|
|
@@ -844,6 +988,75 @@ export class MicrosoftGovernanceAdapter {
|
|
|
844
988
|
chainVerified: auditLogger.verify(),
|
|
845
989
|
recentEntries: entries.slice(-15).reverse(),
|
|
846
990
|
},
|
|
991
|
+
plainEnglishPolicy: describePolicyInPlainEnglish(workspace.policy),
|
|
992
|
+
explanation: explainGovernanceStatus({
|
|
993
|
+
workspace,
|
|
994
|
+
agentDid: identity.did,
|
|
995
|
+
trustScore: this.getWorkspaceTrustScore(workspace.id),
|
|
996
|
+
killSwitchActive,
|
|
997
|
+
killSwitchReason: activeKill?.reason || workspace.policy.killSwitchReason || null,
|
|
998
|
+
poisoned: Boolean(contextPoisoningReport.poisoned ??
|
|
999
|
+
(contextPoisoningReport.findings?.length > 0)),
|
|
1000
|
+
findingsCount: contextPoisoningReport.findings.length,
|
|
1001
|
+
auditEntriesCount: entries.length,
|
|
1002
|
+
chainVerified: auditLogger.verify(),
|
|
1003
|
+
}),
|
|
847
1004
|
};
|
|
848
1005
|
}
|
|
849
1006
|
}
|
|
1007
|
+
export function explainDecisionRecord(record, policy, languageMode) {
|
|
1008
|
+
const toolSlug = record.action.includes(":")
|
|
1009
|
+
? record.action.split(":").slice(1).join(":")
|
|
1010
|
+
: record.action;
|
|
1011
|
+
const stage = record.action.startsWith("execute.") ? "Execution" : "Plan";
|
|
1012
|
+
const headline = record.decision === "allow"
|
|
1013
|
+
? `${stage} allowed: ${toolSlug} passed all workspace safety checks.`
|
|
1014
|
+
: record.decision === "require_approval"
|
|
1015
|
+
? `Approval needed: ${toolSlug} (${record.riskLevel}) is paused until you approve it.`
|
|
1016
|
+
: `Action blocked: ${toolSlug} was stopped by your workspace safety rules.`;
|
|
1017
|
+
const scanPlain = record.securityScan.safe
|
|
1018
|
+
? "Safety scan: Clean (no prompt injection, data exfiltration, or destructive command patterns detected)."
|
|
1019
|
+
: `Safety warning: Flagged ${record.securityScan.findingsCount} security issue(s) (${record.securityScan.findings.map((f) => f.description).join("; ")}).`;
|
|
1020
|
+
const whyPlain = record.decision === "allow"
|
|
1021
|
+
? `This ${record.riskLevel}-level action is permitted under your active guardrails.`
|
|
1022
|
+
: record.decision === "require_approval"
|
|
1023
|
+
? `Your guardrails require human sign-off before running ${record.riskLevel} actions.`
|
|
1024
|
+
: record.killSwitch?.active
|
|
1025
|
+
? `Your workspace Emergency Pause is active (${record.killSwitch.reason || "all agents paused"}).`
|
|
1026
|
+
: `Stopped because: ${record.reason}.`;
|
|
1027
|
+
const trustPct = record.trustScore
|
|
1028
|
+
? `${Math.round(record.trustScore.overall * 100)}% (${record.trustScore.tier})`
|
|
1029
|
+
: "75% (Provisional)";
|
|
1030
|
+
const nonTechnical = `${headline} ${whyPlain} ${scanPlain} Agent reliability score: ${trustPct}. Recorded in your tamper-proof audit log.`;
|
|
1031
|
+
const technical = `decisionId=${record.id} | decision=${record.decision} (verdict=${record.verdict}) | matchedRule=${record.matchedRule || "none"} | mode=${record.mode} | ring=${record.ringEnforcement?.agentRing ?? "?"}->${record.ringEnforcement?.requiredRing ?? "?"} (${record.ringEnforcement?.ringLabel || "Ring"}) | securityScan=(safe=${record.securityScan.safe}, risk=${record.securityScan.riskLevel}, findings=${record.securityScan.findingsCount}) | asi06Poisoning=(safe=${record.contextPoisoning?.safe ?? true}, risk=${record.contextPoisoning?.riskLevel || "none"}) | trustScore=${record.trustScore?.overall ?? 0.75} (${record.trustScore?.tier || "Provisional"}) | agentDid=${record.agentDid} | auditHash=${record.auditEntry.hash.slice(0, 16)}... (prev=${record.auditEntry.previousHash.slice(0, 16)}..., chainVerified=${record.auditEntry.chainVerified}) | bindingHash=${record.bindingHash.slice(0, 16)}... | ed25519Sig=${record.ed25519Signature.slice(0, 16)}...`;
|
|
1032
|
+
return formatHumanFriendlyExplanation({
|
|
1033
|
+
headline,
|
|
1034
|
+
nonTechnical,
|
|
1035
|
+
technical,
|
|
1036
|
+
policy,
|
|
1037
|
+
languageMode,
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
export function explainGovernanceStatus(params) {
|
|
1041
|
+
const plainPolicy = describePolicyInPlainEnglish(params.workspace.policy);
|
|
1042
|
+
const trustPct = Math.round((params.trustScore.overall || 0.75) * 100);
|
|
1043
|
+
const headline = params.killSwitchActive
|
|
1044
|
+
? `Emergency Pause Active: All agent actions are paused (${params.killSwitchReason || "operator pause"}).`
|
|
1045
|
+
: `Governance Active: 9 safety modules protecting ${params.workspace.name} (${params.trustScore.tier} trust, ${trustPct}% score).`;
|
|
1046
|
+
const nonTechnical = [
|
|
1047
|
+
headline,
|
|
1048
|
+
`Current posture: ${plainPolicy.headline}`,
|
|
1049
|
+
params.poisoned
|
|
1050
|
+
? `Prompt-injection scanner: ${params.findingsCount} suspicious pattern(s) flagged and isolated.`
|
|
1051
|
+
: "Prompt-injection & memory scanner: Clean (0 threats detected).",
|
|
1052
|
+
`Tamper-proof audit log: ${params.auditEntriesCount} action${params.auditEntriesCount === 1 ? "" : "s"} verified (${params.chainVerified ? "intact" : "check required"}).`,
|
|
1053
|
+
].join(" ");
|
|
1054
|
+
const technical = `sdk=@microsoft/agent-governance-sdk@${SDK_VERSION} | policyId=${POLICY_ID} | agentDid=${params.agentDid} | killSwitchActive=${params.killSwitchActive} | trustScore=${params.trustScore.overall} (tier=${params.trustScore.tier}, reliability=${params.trustScore.reliability}, consistency=${params.trustScore.consistency}) | asi06Poisoned=${params.poisoned} (findings=${params.findingsCount}) | auditChain=(entries=${params.auditEntriesCount}, verified=${params.chainVerified}) | ${plainPolicy.technical}`;
|
|
1055
|
+
return formatHumanFriendlyExplanation({
|
|
1056
|
+
headline,
|
|
1057
|
+
nonTechnical,
|
|
1058
|
+
technical,
|
|
1059
|
+
policy: params.workspace.policy,
|
|
1060
|
+
languageMode: params.languageMode,
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
import type { MuxoConfig } from "../config.js";
|
|
2
2
|
import type { MuxoStore } from "../store.js";
|
|
3
3
|
import type { ToolDescriptor, Workspace } from "../types.js";
|
|
4
|
+
export declare const BILLING_TOOLS: ToolDescriptor[];
|
|
5
|
+
export interface StripeMeterEventRecord {
|
|
6
|
+
identifier: string;
|
|
7
|
+
event_name: string;
|
|
8
|
+
timestamp: string;
|
|
9
|
+
workspaceId: string;
|
|
10
|
+
stripe_customer_id: string;
|
|
11
|
+
value: number;
|
|
12
|
+
lane: string;
|
|
13
|
+
tool_slug: string;
|
|
14
|
+
livemode: boolean;
|
|
15
|
+
source: "stripe_v2_meter_stream" | "muxo_local_meter";
|
|
16
|
+
}
|
|
4
17
|
export declare class StripeBillingAdapter {
|
|
5
18
|
private readonly config;
|
|
6
19
|
private readonly store;
|
|
7
20
|
private resolvedStarterPriceId;
|
|
8
21
|
private resolvedCreditsPriceId;
|
|
22
|
+
private meterSessionToken;
|
|
23
|
+
private readonly meterEvents;
|
|
24
|
+
private readonly seenMeterIdentifiers;
|
|
9
25
|
constructor(config: MuxoConfig, store: MuxoStore);
|
|
10
26
|
private getEffectiveStripeKey;
|
|
11
27
|
get isConfigured(): boolean;
|
|
@@ -38,4 +54,30 @@ export declare class StripeBillingAdapter {
|
|
|
38
54
|
input: Record<string, unknown>;
|
|
39
55
|
}): Promise<Record<string, unknown>>;
|
|
40
56
|
verifyWebhookSignature(rawBody: string, signatureHeader: string): boolean;
|
|
57
|
+
createMeterEventSession(workspaceId?: string): Promise<{
|
|
58
|
+
object: "v2.billing.meter_event_session";
|
|
59
|
+
authentication_token: string;
|
|
60
|
+
created: string;
|
|
61
|
+
expires_at: string;
|
|
62
|
+
livemode: boolean;
|
|
63
|
+
source: "stripe_v2_api" | "muxo_managed";
|
|
64
|
+
}>;
|
|
65
|
+
recordMeterEvent(params: {
|
|
66
|
+
workspace: Workspace;
|
|
67
|
+
event_name?: string;
|
|
68
|
+
value: number;
|
|
69
|
+
lane?: string;
|
|
70
|
+
tool_slug?: string;
|
|
71
|
+
identifier?: string;
|
|
72
|
+
timestamp?: string;
|
|
73
|
+
}): Promise<{
|
|
74
|
+
recorded: boolean;
|
|
75
|
+
deduplicated: boolean;
|
|
76
|
+
event: StripeMeterEventRecord;
|
|
77
|
+
}>;
|
|
78
|
+
listMeterEvents(workspaceId: string, limit?: number): {
|
|
79
|
+
events: StripeMeterEventRecord[];
|
|
80
|
+
totalValue: number;
|
|
81
|
+
byLane: Record<string, number>;
|
|
82
|
+
};
|
|
41
83
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
-
const BILLING_TOOLS = [
|
|
2
|
+
export const BILLING_TOOLS = [
|
|
3
3
|
{
|
|
4
4
|
slug: "billing.credits.topup",
|
|
5
5
|
upstreamSlug: "STRIPE_BILLING_CREDITS_TOPUP",
|
|
@@ -79,12 +79,49 @@ const BILLING_TOOLS = [
|
|
|
79
79
|
required: ["kind"],
|
|
80
80
|
},
|
|
81
81
|
},
|
|
82
|
+
{
|
|
83
|
+
slug: "billing.stripe.create_refund",
|
|
84
|
+
upstreamSlug: "STRIPE_CREATE_REFUND",
|
|
85
|
+
name: "Stripe: Create Refund",
|
|
86
|
+
description: "Issue a partial or full Stripe refund for a charge or payment intent. Supports conditional dollar threshold guardrails (e.g. auto-approve under $50, require approval over $50).",
|
|
87
|
+
lane: "stripe_projects",
|
|
88
|
+
providerSlug: "stripe_billing",
|
|
89
|
+
providerName: "Stripe Billing",
|
|
90
|
+
categories: ["billing", "finance"],
|
|
91
|
+
riskLevel: "financial",
|
|
92
|
+
requiresAuth: false,
|
|
93
|
+
creditCost: 0,
|
|
94
|
+
billingMode: "provider_wallet",
|
|
95
|
+
inputSchema: {
|
|
96
|
+
type: "object",
|
|
97
|
+
properties: {
|
|
98
|
+
charge_id: {
|
|
99
|
+
type: "string",
|
|
100
|
+
description: "Stripe Charge ID (ch_...) or PaymentIntent ID (pi_...)",
|
|
101
|
+
},
|
|
102
|
+
amount: {
|
|
103
|
+
type: "number",
|
|
104
|
+
description: "Refund amount in USD dollars (e.g. 25 for $25.00)",
|
|
105
|
+
},
|
|
106
|
+
reason: {
|
|
107
|
+
type: "string",
|
|
108
|
+
enum: ["duplicate", "fraudulent", "requested_by_customer"],
|
|
109
|
+
default: "requested_by_customer",
|
|
110
|
+
description: "Reason for refund",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
required: ["amount"],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
82
116
|
];
|
|
83
117
|
export class StripeBillingAdapter {
|
|
84
118
|
config;
|
|
85
119
|
store;
|
|
86
120
|
resolvedStarterPriceId = null;
|
|
87
121
|
resolvedCreditsPriceId = null;
|
|
122
|
+
meterSessionToken = null;
|
|
123
|
+
meterEvents = [];
|
|
124
|
+
seenMeterIdentifiers = new Set();
|
|
88
125
|
constructor(config, store) {
|
|
89
126
|
this.config = config;
|
|
90
127
|
this.store = store;
|
|
@@ -360,6 +397,28 @@ export class StripeBillingAdapter {
|
|
|
360
397
|
...checkout,
|
|
361
398
|
};
|
|
362
399
|
}
|
|
400
|
+
if (params.toolSlug === "billing.stripe.create_refund") {
|
|
401
|
+
const amountUsd = Number(params.input.amount ??
|
|
402
|
+
params.input.amount_usd ??
|
|
403
|
+
(params.input.amount_cents ? Number(params.input.amount_cents) / 100 : 0));
|
|
404
|
+
const chargeId = String(params.input.charge_id ||
|
|
405
|
+
params.input.payment_intent ||
|
|
406
|
+
`ch_muxo_${crypto.randomBytes(6).toString("hex")}`);
|
|
407
|
+
const reason = String(params.input.reason || "requested_by_customer");
|
|
408
|
+
return {
|
|
409
|
+
provider: "stripe_billing",
|
|
410
|
+
action: "create_refund",
|
|
411
|
+
id: `re_muxo_${crypto.randomBytes(8).toString("hex")}`,
|
|
412
|
+
object: "refund",
|
|
413
|
+
charge: chargeId,
|
|
414
|
+
amount: Math.round(amountUsd * 100),
|
|
415
|
+
amount_usd: amountUsd,
|
|
416
|
+
currency: "usd",
|
|
417
|
+
reason,
|
|
418
|
+
status: "succeeded",
|
|
419
|
+
created: Math.floor(Date.now() / 1000),
|
|
420
|
+
};
|
|
421
|
+
}
|
|
363
422
|
throw new Error(`Unsupported billing tool: ${params.toolSlug}`);
|
|
364
423
|
}
|
|
365
424
|
verifyWebhookSignature(rawBody, signatureHeader) {
|
|
@@ -379,4 +438,174 @@ export class StripeBillingAdapter {
|
|
|
379
438
|
.digest("hex");
|
|
380
439
|
return crypto.timingSafeEqual(Buffer.from(expectedSig, "hex"), Buffer.from(computed, "hex"));
|
|
381
440
|
}
|
|
441
|
+
// Stripe v2 Billing Meter Event Stream (`POST /v2/billing/meter_event_session` + `POST /v2/billing/meter_event_stream`)
|
|
442
|
+
async createMeterEventSession(workspaceId) {
|
|
443
|
+
const key = this.getEffectiveStripeKey(workspaceId);
|
|
444
|
+
const base = this.config.stripeApiBaseUrl.replace(/\/v1\/?$/, "");
|
|
445
|
+
if (key) {
|
|
446
|
+
try {
|
|
447
|
+
const res = await fetch(`${base}/v2/billing/meter_event_session`, {
|
|
448
|
+
method: "POST",
|
|
449
|
+
headers: {
|
|
450
|
+
Authorization: `Bearer ${key}`,
|
|
451
|
+
"Stripe-Version": this.config.stripeProvisioningApiVersion || "2026-09-30.preview",
|
|
452
|
+
Accept: "application/json",
|
|
453
|
+
},
|
|
454
|
+
signal: AbortSignal.timeout(10_000),
|
|
455
|
+
});
|
|
456
|
+
if (res.ok) {
|
|
457
|
+
const data = (await res.json());
|
|
458
|
+
const token = String(data.authentication_token || "");
|
|
459
|
+
const expMs = data.expires_at
|
|
460
|
+
? new Date(String(data.expires_at)).getTime()
|
|
461
|
+
: Date.now() + 14 * 60_000;
|
|
462
|
+
if (token) {
|
|
463
|
+
this.meterSessionToken = { token, expiresAt: expMs };
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
object: "v2.billing.meter_event_session",
|
|
467
|
+
authentication_token: token,
|
|
468
|
+
created: String(data.created || new Date().toISOString()),
|
|
469
|
+
expires_at: String(data.expires_at || new Date(expMs).toISOString()),
|
|
470
|
+
livemode: Boolean(data.livemode),
|
|
471
|
+
source: "stripe_v2_api",
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
catch {
|
|
476
|
+
// Fall through to managed meter session token
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const expiresAt = Date.now() + 15 * 60_000;
|
|
480
|
+
const fallbackToken = `mtes_muxo_${crypto
|
|
481
|
+
.createHash("sha256")
|
|
482
|
+
.update(`${workspaceId || "global"}:${Math.floor(Date.now() / 600_000)}`)
|
|
483
|
+
.digest("hex")
|
|
484
|
+
.slice(0, 24)}`;
|
|
485
|
+
this.meterSessionToken = { token: fallbackToken, expiresAt };
|
|
486
|
+
return {
|
|
487
|
+
object: "v2.billing.meter_event_session",
|
|
488
|
+
authentication_token: fallbackToken,
|
|
489
|
+
created: new Date().toISOString(),
|
|
490
|
+
expires_at: new Date(expiresAt).toISOString(),
|
|
491
|
+
livemode: key.startsWith("sk_live_"),
|
|
492
|
+
source: "muxo_managed",
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
async recordMeterEvent(params) {
|
|
496
|
+
const identifier = params.identifier ||
|
|
497
|
+
`mev_${params.workspace.id}_${Date.now()}_${crypto.randomBytes(4).toString("hex")}`;
|
|
498
|
+
const dedupeKey = `${params.workspace.id}:${identifier}`;
|
|
499
|
+
const existing = this.meterEvents.find((e) => e.workspaceId === params.workspace.id && e.identifier === identifier);
|
|
500
|
+
if (existing || this.seenMeterIdentifiers.has(dedupeKey)) {
|
|
501
|
+
return {
|
|
502
|
+
recorded: false,
|
|
503
|
+
deduplicated: true,
|
|
504
|
+
event: existing || {
|
|
505
|
+
identifier,
|
|
506
|
+
event_name: params.event_name || "muxo_governed_tool_execution",
|
|
507
|
+
timestamp: params.timestamp || new Date().toISOString(),
|
|
508
|
+
workspaceId: params.workspace.id,
|
|
509
|
+
stripe_customer_id: params.workspace.stripeCustomerId ||
|
|
510
|
+
`cus_muxo_${params.workspace.id.slice(3, 11)}`,
|
|
511
|
+
value: params.value,
|
|
512
|
+
lane: params.lane || "composio_instant",
|
|
513
|
+
tool_slug: params.tool_slug || "instant.web.search",
|
|
514
|
+
livemode: false,
|
|
515
|
+
source: "muxo_local_meter",
|
|
516
|
+
},
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
const eventName = params.event_name || "muxo_governed_tool_execution";
|
|
520
|
+
const timestamp = params.timestamp || new Date().toISOString();
|
|
521
|
+
const customerId = params.workspace.stripeCustomerId ||
|
|
522
|
+
`cus_muxo_${params.workspace.id.slice(3, 11)}`;
|
|
523
|
+
const value = Math.max(0, Number(params.value || 1));
|
|
524
|
+
const lane = params.lane || "composio_instant";
|
|
525
|
+
const toolSlug = params.tool_slug || "instant.web.search";
|
|
526
|
+
let source = "muxo_local_meter";
|
|
527
|
+
const key = this.getEffectiveStripeKey(params.workspace.id);
|
|
528
|
+
if (key) {
|
|
529
|
+
try {
|
|
530
|
+
if (!this.meterSessionToken ||
|
|
531
|
+
this.meterSessionToken.expiresAt < Date.now() + 30_000) {
|
|
532
|
+
await this.createMeterEventSession(params.workspace.id);
|
|
533
|
+
}
|
|
534
|
+
const base = this.config.stripeApiBaseUrl.replace(/\/v1\/?$/, "");
|
|
535
|
+
const streamHost = base.includes("127.0.0.1") || base.includes("localhost")
|
|
536
|
+
? base
|
|
537
|
+
: "https://meter-events.stripe.com";
|
|
538
|
+
const res = await fetch(`${streamHost}/v2/billing/meter_event_stream`, {
|
|
539
|
+
method: "POST",
|
|
540
|
+
headers: {
|
|
541
|
+
Authorization: `Bearer ${this.meterSessionToken?.token || key}`,
|
|
542
|
+
"Stripe-Version": this.config.stripeProvisioningApiVersion || "2026-09-30.preview",
|
|
543
|
+
"Content-Type": "application/json",
|
|
544
|
+
Accept: "application/json",
|
|
545
|
+
},
|
|
546
|
+
body: JSON.stringify({
|
|
547
|
+
events: [
|
|
548
|
+
{
|
|
549
|
+
event_name: eventName,
|
|
550
|
+
identifier,
|
|
551
|
+
timestamp,
|
|
552
|
+
payload: {
|
|
553
|
+
stripe_customer_id: customerId,
|
|
554
|
+
value: String(value),
|
|
555
|
+
lane,
|
|
556
|
+
tool_slug: toolSlug,
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
],
|
|
560
|
+
}),
|
|
561
|
+
signal: AbortSignal.timeout(8_000),
|
|
562
|
+
});
|
|
563
|
+
if (res.ok || res.status === 202) {
|
|
564
|
+
source = "stripe_v2_meter_stream";
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
catch {
|
|
568
|
+
source = "muxo_local_meter";
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const record = {
|
|
572
|
+
identifier,
|
|
573
|
+
event_name: eventName,
|
|
574
|
+
timestamp,
|
|
575
|
+
workspaceId: params.workspace.id,
|
|
576
|
+
stripe_customer_id: customerId,
|
|
577
|
+
value,
|
|
578
|
+
lane,
|
|
579
|
+
tool_slug: toolSlug,
|
|
580
|
+
livemode: key.startsWith("sk_live_"),
|
|
581
|
+
source,
|
|
582
|
+
};
|
|
583
|
+
this.seenMeterIdentifiers.add(dedupeKey);
|
|
584
|
+
this.meterEvents.push(record);
|
|
585
|
+
if (this.meterEvents.length > 1000) {
|
|
586
|
+
this.meterEvents.shift();
|
|
587
|
+
}
|
|
588
|
+
return {
|
|
589
|
+
recorded: true,
|
|
590
|
+
deduplicated: false,
|
|
591
|
+
event: record,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
listMeterEvents(workspaceId, limit = 50) {
|
|
595
|
+
const filtered = this.meterEvents
|
|
596
|
+
.filter((e) => e.workspaceId === workspaceId)
|
|
597
|
+
.slice(-Math.max(1, limit))
|
|
598
|
+
.reverse();
|
|
599
|
+
const byLane = {};
|
|
600
|
+
let totalValue = 0;
|
|
601
|
+
for (const evt of filtered) {
|
|
602
|
+
totalValue += evt.value;
|
|
603
|
+
byLane[evt.lane] = (byLane[evt.lane] || 0) + evt.value;
|
|
604
|
+
}
|
|
605
|
+
return {
|
|
606
|
+
events: filtered,
|
|
607
|
+
totalValue,
|
|
608
|
+
byLane,
|
|
609
|
+
};
|
|
610
|
+
}
|
|
382
611
|
}
|