@muxoai/platform 0.0.0-stage → 1.2.0

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.
Files changed (173) hide show
  1. package/ARCHITECTURE.md +111 -0
  2. package/README.md +53 -2
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +527 -0
  5. package/dist/config.d.ts +20 -0
  6. package/dist/config.js +89 -0
  7. package/dist/engine.d.ts +128 -0
  8. package/dist/engine.js +709 -0
  9. package/dist/index.d.ts +7 -0
  10. package/dist/index.js +7 -0
  11. package/dist/mcp.d.ts +9 -0
  12. package/dist/mcp.js +401 -0
  13. package/dist/providers/composio-instant.d.ts +14 -0
  14. package/dist/providers/composio-instant.js +372 -0
  15. package/dist/providers/composio-sdk.d.ts +41 -0
  16. package/dist/providers/composio-sdk.js +514 -0
  17. package/dist/providers/microsoft-governance.d.ts +66 -0
  18. package/dist/providers/microsoft-governance.js +415 -0
  19. package/dist/providers/stripe-billing.d.ts +31 -0
  20. package/dist/providers/stripe-billing.js +168 -0
  21. package/dist/providers/stripe-projects.d.ts +53 -0
  22. package/dist/providers/stripe-projects.js +607 -0
  23. package/dist/providers/supermemory.d.ts +64 -0
  24. package/dist/providers/supermemory.js +224 -0
  25. package/dist/sdk.d.ts +115 -0
  26. package/dist/sdk.js +117 -0
  27. package/dist/server.d.ts +10 -0
  28. package/dist/server.js +816 -0
  29. package/dist/store.d.ts +200 -0
  30. package/dist/store.js +1260 -0
  31. package/dist/types.d.ts +273 -0
  32. package/dist/types.js +1 -0
  33. package/dist/ui/console-html.d.ts +2 -0
  34. package/dist/ui/console-html.js +2153 -0
  35. package/package.json +74 -4
  36. package/plugins/muxo/.app.json +8 -0
  37. package/plugins/muxo/.claude-plugin/plugin.json +16 -0
  38. package/plugins/muxo/.codex-plugin/plugin.json +51 -0
  39. package/plugins/muxo/.mcp.json +8 -0
  40. package/plugins/muxo/assets/logo.png +0 -0
  41. package/plugins/muxo/assets/logo.svg +6 -0
  42. package/plugins/muxo/hooks/hooks.json +27 -0
  43. package/plugins/muxo/hooks/session-start.sh +85 -0
  44. package/plugins/muxo/hooks/user-prompt-submit.sh +45 -0
  45. package/plugins/muxo/skills/muxo/SKILL.md +25 -0
  46. package/plugins/muxo/skills/muxo/references/cli.md +57 -0
  47. package/plugins/muxo/skills/muxo/references/mcp.md +25 -0
  48. package/src/catalogues/stripe-projects-catalog.json +9832 -0
  49. package/vendor/microsoft-governance/README.md +363 -0
  50. package/vendor/microsoft-governance/dist/audit.d.ts +26 -0
  51. package/vendor/microsoft-governance/dist/audit.d.ts.map +1 -0
  52. package/vendor/microsoft-governance/dist/audit.js +107 -0
  53. package/vendor/microsoft-governance/dist/audit.js.map +1 -0
  54. package/vendor/microsoft-governance/dist/cascade-containment.d.ts +84 -0
  55. package/vendor/microsoft-governance/dist/cascade-containment.d.ts.map +1 -0
  56. package/vendor/microsoft-governance/dist/cascade-containment.js +387 -0
  57. package/vendor/microsoft-governance/dist/cascade-containment.js.map +1 -0
  58. package/vendor/microsoft-governance/dist/client.d.ts +34 -0
  59. package/vendor/microsoft-governance/dist/client.d.ts.map +1 -0
  60. package/vendor/microsoft-governance/dist/client.js +112 -0
  61. package/vendor/microsoft-governance/dist/client.js.map +1 -0
  62. package/vendor/microsoft-governance/dist/context-poisoning.d.ts +58 -0
  63. package/vendor/microsoft-governance/dist/context-poisoning.d.ts.map +1 -0
  64. package/vendor/microsoft-governance/dist/context-poisoning.js +410 -0
  65. package/vendor/microsoft-governance/dist/context-poisoning.js.map +1 -0
  66. package/vendor/microsoft-governance/dist/credential-vault.d.ts +213 -0
  67. package/vendor/microsoft-governance/dist/credential-vault.d.ts.map +1 -0
  68. package/vendor/microsoft-governance/dist/credential-vault.js +567 -0
  69. package/vendor/microsoft-governance/dist/credential-vault.js.map +1 -0
  70. package/vendor/microsoft-governance/dist/discovery.d.ts +80 -0
  71. package/vendor/microsoft-governance/dist/discovery.d.ts.map +1 -0
  72. package/vendor/microsoft-governance/dist/discovery.js +404 -0
  73. package/vendor/microsoft-governance/dist/discovery.js.map +1 -0
  74. package/vendor/microsoft-governance/dist/encryption/channel.d.ts +29 -0
  75. package/vendor/microsoft-governance/dist/encryption/channel.d.ts.map +1 -0
  76. package/vendor/microsoft-governance/dist/encryption/channel.js +78 -0
  77. package/vendor/microsoft-governance/dist/encryption/channel.js.map +1 -0
  78. package/vendor/microsoft-governance/dist/encryption/index.d.ts +9 -0
  79. package/vendor/microsoft-governance/dist/encryption/index.d.ts.map +1 -0
  80. package/vendor/microsoft-governance/dist/encryption/index.js +23 -0
  81. package/vendor/microsoft-governance/dist/encryption/index.js.map +1 -0
  82. package/vendor/microsoft-governance/dist/encryption/mesh-client.d.ts +267 -0
  83. package/vendor/microsoft-governance/dist/encryption/mesh-client.d.ts.map +1 -0
  84. package/vendor/microsoft-governance/dist/encryption/mesh-client.js +1001 -0
  85. package/vendor/microsoft-governance/dist/encryption/mesh-client.js.map +1 -0
  86. package/vendor/microsoft-governance/dist/encryption/ratchet.d.ts +38 -0
  87. package/vendor/microsoft-governance/dist/encryption/ratchet.d.ts.map +1 -0
  88. package/vendor/microsoft-governance/dist/encryption/ratchet.js +223 -0
  89. package/vendor/microsoft-governance/dist/encryption/ratchet.js.map +1 -0
  90. package/vendor/microsoft-governance/dist/encryption/registry-client.d.ts +136 -0
  91. package/vendor/microsoft-governance/dist/encryption/registry-client.d.ts.map +1 -0
  92. package/vendor/microsoft-governance/dist/encryption/registry-client.js +369 -0
  93. package/vendor/microsoft-governance/dist/encryption/registry-client.js.map +1 -0
  94. package/vendor/microsoft-governance/dist/encryption/x3dh.d.ts +80 -0
  95. package/vendor/microsoft-governance/dist/encryption/x3dh.d.ts.map +1 -0
  96. package/vendor/microsoft-governance/dist/encryption/x3dh.js +238 -0
  97. package/vendor/microsoft-governance/dist/encryption/x3dh.js.map +1 -0
  98. package/vendor/microsoft-governance/dist/framework-adapter.d.ts +63 -0
  99. package/vendor/microsoft-governance/dist/framework-adapter.d.ts.map +1 -0
  100. package/vendor/microsoft-governance/dist/framework-adapter.js +253 -0
  101. package/vendor/microsoft-governance/dist/framework-adapter.js.map +1 -0
  102. package/vendor/microsoft-governance/dist/identity.d.ts +124 -0
  103. package/vendor/microsoft-governance/dist/identity.d.ts.map +1 -0
  104. package/vendor/microsoft-governance/dist/identity.js +446 -0
  105. package/vendor/microsoft-governance/dist/identity.js.map +1 -0
  106. package/vendor/microsoft-governance/dist/index.d.ts +39 -0
  107. package/vendor/microsoft-governance/dist/index.d.ts.map +1 -0
  108. package/vendor/microsoft-governance/dist/index.js +95 -0
  109. package/vendor/microsoft-governance/dist/index.js.map +1 -0
  110. package/vendor/microsoft-governance/dist/kill-switch.d.ts +22 -0
  111. package/vendor/microsoft-governance/dist/kill-switch.d.ts.map +1 -0
  112. package/vendor/microsoft-governance/dist/kill-switch.js +60 -0
  113. package/vendor/microsoft-governance/dist/kill-switch.js.map +1 -0
  114. package/vendor/microsoft-governance/dist/lifecycle.d.ts +50 -0
  115. package/vendor/microsoft-governance/dist/lifecycle.d.ts.map +1 -0
  116. package/vendor/microsoft-governance/dist/lifecycle.js +113 -0
  117. package/vendor/microsoft-governance/dist/lifecycle.js.map +1 -0
  118. package/vendor/microsoft-governance/dist/mcp.d.ts +41 -0
  119. package/vendor/microsoft-governance/dist/mcp.d.ts.map +1 -0
  120. package/vendor/microsoft-governance/dist/mcp.js +233 -0
  121. package/vendor/microsoft-governance/dist/mcp.js.map +1 -0
  122. package/vendor/microsoft-governance/dist/metrics.d.ts +123 -0
  123. package/vendor/microsoft-governance/dist/metrics.d.ts.map +1 -0
  124. package/vendor/microsoft-governance/dist/metrics.js +289 -0
  125. package/vendor/microsoft-governance/dist/metrics.js.map +1 -0
  126. package/vendor/microsoft-governance/dist/oci-manifest.d.ts +49 -0
  127. package/vendor/microsoft-governance/dist/oci-manifest.d.ts.map +1 -0
  128. package/vendor/microsoft-governance/dist/oci-manifest.js +182 -0
  129. package/vendor/microsoft-governance/dist/oci-manifest.js.map +1 -0
  130. package/vendor/microsoft-governance/dist/policy-backends/cedar.d.ts +15 -0
  131. package/vendor/microsoft-governance/dist/policy-backends/cedar.d.ts.map +1 -0
  132. package/vendor/microsoft-governance/dist/policy-backends/cedar.js +88 -0
  133. package/vendor/microsoft-governance/dist/policy-backends/cedar.js.map +1 -0
  134. package/vendor/microsoft-governance/dist/policy-backends/opa.d.ts +17 -0
  135. package/vendor/microsoft-governance/dist/policy-backends/opa.d.ts.map +1 -0
  136. package/vendor/microsoft-governance/dist/policy-backends/opa.js +74 -0
  137. package/vendor/microsoft-governance/dist/policy-backends/opa.js.map +1 -0
  138. package/vendor/microsoft-governance/dist/policy.d.ts +77 -0
  139. package/vendor/microsoft-governance/dist/policy.d.ts.map +1 -0
  140. package/vendor/microsoft-governance/dist/policy.js +760 -0
  141. package/vendor/microsoft-governance/dist/policy.js.map +1 -0
  142. package/vendor/microsoft-governance/dist/prompt-defense.d.ts +37 -0
  143. package/vendor/microsoft-governance/dist/prompt-defense.d.ts.map +1 -0
  144. package/vendor/microsoft-governance/dist/prompt-defense.js +229 -0
  145. package/vendor/microsoft-governance/dist/prompt-defense.js.map +1 -0
  146. package/vendor/microsoft-governance/dist/protocol-facets.d.ts +87 -0
  147. package/vendor/microsoft-governance/dist/protocol-facets.d.ts.map +1 -0
  148. package/vendor/microsoft-governance/dist/protocol-facets.js +728 -0
  149. package/vendor/microsoft-governance/dist/protocol-facets.js.map +1 -0
  150. package/vendor/microsoft-governance/dist/rings.d.ts +17 -0
  151. package/vendor/microsoft-governance/dist/rings.d.ts.map +1 -0
  152. package/vendor/microsoft-governance/dist/rings.js +61 -0
  153. package/vendor/microsoft-governance/dist/rings.js.map +1 -0
  154. package/vendor/microsoft-governance/dist/sandbox.d.ts +61 -0
  155. package/vendor/microsoft-governance/dist/sandbox.d.ts.map +1 -0
  156. package/vendor/microsoft-governance/dist/sandbox.js +199 -0
  157. package/vendor/microsoft-governance/dist/sandbox.js.map +1 -0
  158. package/vendor/microsoft-governance/dist/surface-parity.d.ts +45 -0
  159. package/vendor/microsoft-governance/dist/surface-parity.d.ts.map +1 -0
  160. package/vendor/microsoft-governance/dist/surface-parity.js +216 -0
  161. package/vendor/microsoft-governance/dist/surface-parity.js.map +1 -0
  162. package/vendor/microsoft-governance/dist/trust.d.ts +28 -0
  163. package/vendor/microsoft-governance/dist/trust.d.ts.map +1 -0
  164. package/vendor/microsoft-governance/dist/trust.js +209 -0
  165. package/vendor/microsoft-governance/dist/trust.js.map +1 -0
  166. package/vendor/microsoft-governance/dist/types.d.ts +422 -0
  167. package/vendor/microsoft-governance/dist/types.d.ts.map +1 -0
  168. package/vendor/microsoft-governance/dist/types.js +29 -0
  169. package/vendor/microsoft-governance/dist/types.js.map +1 -0
  170. package/vendor/microsoft-governance/dist/verify.d.ts +135 -0
  171. package/vendor/microsoft-governance/dist/verify.d.ts.map +1 -0
  172. package/vendor/microsoft-governance/dist/verify.js +356 -0
  173. package/vendor/microsoft-governance/dist/verify.js.map +1 -0
package/dist/store.js ADDED
@@ -0,0 +1,1260 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import crypto from "node:crypto";
3
+ const DEFAULT_POLICY = {
4
+ mode: "enforce",
5
+ requireApprovalFor: ["admin", "financial", "provisioning", "destructive"],
6
+ allowInstantWallet: true,
7
+ allowProvisioning: true,
8
+ maxCreditsPerRun: 250,
9
+ };
10
+ export class MuxoStore {
11
+ db;
12
+ vaultKey;
13
+ constructor(dbPath, vaultKey) {
14
+ this.db = new DatabaseSync(dbPath);
15
+ this.vaultKey = vaultKey;
16
+ this.initSchema();
17
+ }
18
+ initSchema() {
19
+ this.db.exec(`
20
+ PRAGMA journal_mode = WAL;
21
+ PRAGMA foreign_keys = ON;
22
+
23
+ CREATE TABLE IF NOT EXISTS users (
24
+ id TEXT PRIMARY KEY,
25
+ email TEXT NOT NULL UNIQUE,
26
+ name TEXT NOT NULL,
27
+ password_hash TEXT NOT NULL,
28
+ workspace_id TEXT NOT NULL,
29
+ role TEXT NOT NULL DEFAULT 'owner',
30
+ created_at TEXT NOT NULL
31
+ );
32
+
33
+ CREATE TABLE IF NOT EXISTS sessions (
34
+ token_hash TEXT PRIMARY KEY,
35
+ user_id TEXT NOT NULL,
36
+ workspace_id TEXT NOT NULL,
37
+ expires_at TEXT NOT NULL,
38
+ created_at TEXT NOT NULL
39
+ );
40
+
41
+ CREATE TABLE IF NOT EXISTS workspaces (
42
+ id TEXT PRIMARY KEY,
43
+ name TEXT NOT NULL,
44
+ slug TEXT NOT NULL UNIQUE,
45
+ owner_user_id TEXT NOT NULL,
46
+ tier TEXT NOT NULL DEFAULT 'free',
47
+ monthly_actions_limit INTEGER NOT NULL DEFAULT 1000,
48
+ monthly_actions_used INTEGER NOT NULL DEFAULT 0,
49
+ credits_balance INTEGER NOT NULL DEFAULT 100,
50
+ stripe_customer_id TEXT,
51
+ stripe_subscription_id TEXT,
52
+ projects_env_name TEXT NOT NULL,
53
+ policy_json TEXT NOT NULL,
54
+ created_at TEXT NOT NULL,
55
+ updated_at TEXT NOT NULL
56
+ );
57
+
58
+ CREATE TABLE IF NOT EXISTS api_keys (
59
+ id TEXT PRIMARY KEY,
60
+ workspace_id TEXT NOT NULL,
61
+ name TEXT NOT NULL,
62
+ key_prefix TEXT NOT NULL,
63
+ key_hash TEXT NOT NULL UNIQUE,
64
+ environment TEXT NOT NULL DEFAULT 'live',
65
+ scopes_json TEXT NOT NULL,
66
+ last_used_at TEXT,
67
+ revoked_at TEXT,
68
+ created_at TEXT NOT NULL
69
+ );
70
+
71
+ CREATE TABLE IF NOT EXISTS connections (
72
+ id TEXT PRIMARY KEY,
73
+ workspace_id TEXT NOT NULL,
74
+ lane TEXT NOT NULL,
75
+ provider_slug TEXT NOT NULL,
76
+ provider_name TEXT NOT NULL,
77
+ status TEXT NOT NULL,
78
+ external_account_id TEXT,
79
+ redirect_url TEXT,
80
+ metadata_json TEXT NOT NULL DEFAULT '{}',
81
+ updated_at TEXT NOT NULL,
82
+ created_at TEXT NOT NULL,
83
+ UNIQUE(workspace_id, lane, provider_slug)
84
+ );
85
+
86
+ CREATE TABLE IF NOT EXISTS resources (
87
+ id TEXT PRIMARY KEY,
88
+ workspace_id TEXT NOT NULL,
89
+ resource_name TEXT NOT NULL,
90
+ provider_slug TEXT NOT NULL,
91
+ provider_name TEXT NOT NULL,
92
+ service_slug TEXT NOT NULL,
93
+ plan_slug TEXT,
94
+ status TEXT NOT NULL,
95
+ env_var_keys_json TEXT NOT NULL DEFAULT '[]',
96
+ encrypted_secrets_hex TEXT,
97
+ external_resource_id TEXT,
98
+ metadata_json TEXT NOT NULL DEFAULT '{}',
99
+ created_at TEXT NOT NULL,
100
+ updated_at TEXT NOT NULL,
101
+ UNIQUE(workspace_id, resource_name)
102
+ );
103
+
104
+ CREATE TABLE IF NOT EXISTS plans (
105
+ id TEXT PRIMARY KEY,
106
+ workspace_id TEXT NOT NULL,
107
+ actor_id TEXT NOT NULL,
108
+ tool_slug TEXT NOT NULL,
109
+ upstream_slug TEXT NOT NULL,
110
+ lane TEXT NOT NULL,
111
+ risk_level TEXT NOT NULL,
112
+ input_json TEXT NOT NULL,
113
+ reason TEXT NOT NULL,
114
+ credit_cost INTEGER NOT NULL DEFAULT 0,
115
+ approval_status TEXT NOT NULL,
116
+ approved_by TEXT,
117
+ approved_at TEXT,
118
+ run_id TEXT,
119
+ decision_record_json TEXT,
120
+ memory_context_json TEXT,
121
+ created_at TEXT NOT NULL,
122
+ expires_at TEXT NOT NULL
123
+ );
124
+
125
+ CREATE TABLE IF NOT EXISTS runs (
126
+ id TEXT PRIMARY KEY,
127
+ plan_id TEXT NOT NULL,
128
+ workspace_id TEXT NOT NULL,
129
+ actor_id TEXT NOT NULL,
130
+ tool_slug TEXT NOT NULL,
131
+ lane TEXT NOT NULL,
132
+ risk_level TEXT NOT NULL,
133
+ status TEXT NOT NULL,
134
+ input_json TEXT NOT NULL,
135
+ output_json TEXT,
136
+ error TEXT,
137
+ actions_metered INTEGER NOT NULL DEFAULT 0,
138
+ credits_metered INTEGER NOT NULL DEFAULT 0,
139
+ duration_ms INTEGER,
140
+ receipt_id TEXT,
141
+ started_at TEXT NOT NULL,
142
+ completed_at TEXT
143
+ );
144
+
145
+ CREATE TABLE IF NOT EXISTS receipts (
146
+ id TEXT PRIMARY KEY,
147
+ run_id TEXT NOT NULL UNIQUE,
148
+ plan_id TEXT NOT NULL,
149
+ workspace_id TEXT NOT NULL,
150
+ actor_id TEXT NOT NULL,
151
+ tool_slug TEXT NOT NULL,
152
+ lane TEXT NOT NULL,
153
+ risk_level TEXT NOT NULL,
154
+ status TEXT NOT NULL,
155
+ actions_metered INTEGER NOT NULL,
156
+ credits_metered INTEGER NOT NULL,
157
+ input_hash TEXT NOT NULL,
158
+ output_hash TEXT NOT NULL,
159
+ prev_receipt_hash TEXT NOT NULL,
160
+ receipt_hash TEXT NOT NULL,
161
+ signature TEXT NOT NULL,
162
+ decision_record_json TEXT,
163
+ issued_at TEXT NOT NULL
164
+ );
165
+
166
+ CREATE TABLE IF NOT EXISTS memories (
167
+ id TEXT PRIMARY KEY,
168
+ workspace_id TEXT NOT NULL,
169
+ namespace TEXT NOT NULL,
170
+ document_id TEXT NOT NULL,
171
+ title TEXT NOT NULL,
172
+ content TEXT NOT NULL,
173
+ kind TEXT NOT NULL,
174
+ metadata_json TEXT NOT NULL DEFAULT '{}',
175
+ created_at TEXT NOT NULL
176
+ );
177
+
178
+ CREATE TABLE IF NOT EXISTS agent_claims (
179
+ claim_slug TEXT PRIMARY KEY,
180
+ workspace_id TEXT NOT NULL,
181
+ target_email TEXT NOT NULL,
182
+ status TEXT NOT NULL DEFAULT 'invited',
183
+ claimed_at TEXT,
184
+ expires_at TEXT NOT NULL,
185
+ created_at TEXT NOT NULL
186
+ );
187
+
188
+ CREATE TABLE IF NOT EXISTS ledger_entries (
189
+ id TEXT PRIMARY KEY,
190
+ workspace_id TEXT NOT NULL,
191
+ kind TEXT NOT NULL,
192
+ actions_delta INTEGER NOT NULL DEFAULT 0,
193
+ credits_delta INTEGER NOT NULL DEFAULT 0,
194
+ balance_after INTEGER NOT NULL DEFAULT 0,
195
+ reference_id TEXT,
196
+ description TEXT NOT NULL,
197
+ created_at TEXT NOT NULL
198
+ );
199
+
200
+ CREATE TABLE IF NOT EXISTS processed_stripe_events (
201
+ event_id TEXT PRIMARY KEY,
202
+ processed_at TEXT NOT NULL
203
+ );
204
+ `);
205
+ // Backwards-compatible column migrations for existing databases
206
+ for (const stmt of [
207
+ "ALTER TABLE plans ADD COLUMN decision_record_json TEXT",
208
+ "ALTER TABLE plans ADD COLUMN memory_context_json TEXT",
209
+ "ALTER TABLE receipts ADD COLUMN decision_record_json TEXT",
210
+ ]) {
211
+ try {
212
+ this.db.exec(stmt);
213
+ }
214
+ catch {
215
+ // Column already exists
216
+ }
217
+ }
218
+ }
219
+ // --- Encryption Vault (AES-256-GCM) ---
220
+ encryptSecrets(workspaceId, secrets) {
221
+ const iv = crypto.randomBytes(12);
222
+ const aad = Buffer.from(`muxo-vault:${workspaceId}`, "utf8");
223
+ const cipher = crypto.createCipheriv("aes-256-gcm", this.vaultKey, iv);
224
+ cipher.setAAD(aad);
225
+ const plaintext = Buffer.from(JSON.stringify(secrets), "utf8");
226
+ const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
227
+ const tag = cipher.getAuthTag();
228
+ return `${iv.toString("hex")}:${tag.toString("hex")}:${encrypted.toString("hex")}`;
229
+ }
230
+ decryptSecrets(workspaceId, payloadHex) {
231
+ if (!payloadHex)
232
+ return {};
233
+ const parts = payloadHex.split(":");
234
+ if (parts.length !== 3)
235
+ return {};
236
+ const [ivHex, tagHex, dataHex] = parts;
237
+ const iv = Buffer.from(ivHex, "hex");
238
+ const tag = Buffer.from(tagHex, "hex");
239
+ const encrypted = Buffer.from(dataHex, "hex");
240
+ const aad = Buffer.from(`muxo-vault:${workspaceId}`, "utf8");
241
+ const decipher = crypto.createDecipheriv("aes-256-gcm", this.vaultKey, iv);
242
+ decipher.setAAD(aad);
243
+ decipher.setAuthTag(tag);
244
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
245
+ return JSON.parse(decrypted.toString("utf8"));
246
+ }
247
+ // --- Bootstrap / Auth ---
248
+ ensureDefaultWorkspace() {
249
+ const existing = this.db
250
+ .prepare("SELECT id FROM workspaces ORDER BY created_at ASC LIMIT 1")
251
+ .get();
252
+ if (existing) {
253
+ const workspace = this.getWorkspace(existing.id);
254
+ const user = this.getUser(workspace.ownerUserId);
255
+ return { user, workspace };
256
+ }
257
+ const created = this.registerUser({
258
+ email: "founder@muxo.ai",
259
+ name: "Muxo Founder",
260
+ password: "muxo-demo-password",
261
+ workspaceName: "Muxo Default Workspace",
262
+ });
263
+ const apiKeyRes = this.createApiKey({
264
+ workspaceId: created.workspace.id,
265
+ name: "Default CLI & MCP Key",
266
+ environment: "live",
267
+ });
268
+ return {
269
+ user: created.user,
270
+ workspace: created.workspace,
271
+ defaultApiKey: apiKeyRes.secretKey,
272
+ };
273
+ }
274
+ hashPassword(password) {
275
+ const salt = "muxo-pw-salt-v1";
276
+ return crypto.scryptSync(password, salt, 32).toString("hex");
277
+ }
278
+ hashToken(token) {
279
+ return crypto.createHash("sha256").update(token).digest("hex");
280
+ }
281
+ registerUser(params) {
282
+ const now = new Date().toISOString();
283
+ const userId = `usr_${crypto.randomBytes(8).toString("hex")}`;
284
+ const workspaceId = `ws_${crypto.randomBytes(8).toString("hex")}`;
285
+ const slugBase = (params.workspaceName || params.name || "workspace")
286
+ .toLowerCase()
287
+ .replace(/[^a-z0-9]+/g, "-")
288
+ .replace(/^-|-$/g, "");
289
+ const slug = `${slugBase || "ws"}-${crypto.randomBytes(3).toString("hex")}`;
290
+ const projectsEnvName = `muxo_${workspaceId.slice(3, 11)}`;
291
+ const starterCredits = 250; // Initial trial credits for instant tools & exploration
292
+ this.db
293
+ .prepare(`INSERT INTO users (id, email, name, password_hash, workspace_id, role, created_at)
294
+ VALUES (?, ?, ?, ?, ?, 'owner', ?)`)
295
+ .run(userId, params.email.toLowerCase().trim(), params.name.trim(), this.hashPassword(params.password), workspaceId, now);
296
+ this.db
297
+ .prepare(`INSERT INTO workspaces (
298
+ id, name, slug, owner_user_id, tier,
299
+ monthly_actions_limit, monthly_actions_used, credits_balance,
300
+ stripe_customer_id, stripe_subscription_id, projects_env_name,
301
+ policy_json, created_at, updated_at
302
+ ) VALUES (?, ?, ?, ?, 'free', 1000, 0, ?, NULL, NULL, ?, ?, ?, ?)`)
303
+ .run(workspaceId, params.workspaceName?.trim() || `${params.name.trim()}'s Workspace`, slug, userId, starterCredits, projectsEnvName, JSON.stringify(DEFAULT_POLICY), now, now);
304
+ this.addLedgerEntry({
305
+ workspaceId,
306
+ kind: "plan_grant",
307
+ actionsDelta: 1000,
308
+ creditsDelta: starterCredits,
309
+ balanceAfter: starterCredits,
310
+ referenceId: workspaceId,
311
+ description: "Free Plan initial monthly allowance (1,000 actions + 250 trial credits)",
312
+ });
313
+ const sessionToken = this.createSession(userId, workspaceId);
314
+ return {
315
+ user: this.getUser(userId),
316
+ workspace: this.getWorkspace(workspaceId),
317
+ sessionToken,
318
+ };
319
+ }
320
+ loginUser(email, password) {
321
+ const row = this.db
322
+ .prepare("SELECT * FROM users WHERE email = ?")
323
+ .get(email.toLowerCase().trim());
324
+ if (!row)
325
+ return null;
326
+ if (row.password_hash !== this.hashPassword(password))
327
+ return null;
328
+ const user = this.mapUser(row);
329
+ const workspace = this.getWorkspace(user.workspaceId);
330
+ const sessionToken = this.createSession(user.id, workspace.id);
331
+ return { user, workspace, sessionToken };
332
+ }
333
+ createSession(userId, workspaceId) {
334
+ const rawToken = `mxs_${crypto.randomBytes(24).toString("hex")}`;
335
+ const now = new Date();
336
+ const expires = new Date(now.getTime() + 30 * 24 * 3600 * 1000);
337
+ this.db
338
+ .prepare(`INSERT INTO sessions (token_hash, user_id, workspace_id, expires_at, created_at)
339
+ VALUES (?, ?, ?, ?, ?)`)
340
+ .run(this.hashToken(rawToken), userId, workspaceId, expires.toISOString(), now.toISOString());
341
+ return rawToken;
342
+ }
343
+ resolveAuthToken(token) {
344
+ const trimmed = token.trim();
345
+ if (!trimmed)
346
+ return null;
347
+ if (trimmed.startsWith("mk_live_") || trimmed.startsWith("mk_test_")) {
348
+ const hash = this.hashToken(trimmed);
349
+ const row = this.db
350
+ .prepare("SELECT * FROM api_keys WHERE key_hash = ? AND revoked_at IS NULL")
351
+ .get(hash);
352
+ if (!row)
353
+ return null;
354
+ const now = new Date().toISOString();
355
+ this.db
356
+ .prepare("UPDATE api_keys SET last_used_at = ? WHERE id = ?")
357
+ .run(now, String(row.id));
358
+ const workspace = this.getWorkspace(String(row.workspace_id));
359
+ if (!workspace)
360
+ return null;
361
+ const user = this.getUser(workspace.ownerUserId);
362
+ if (!user)
363
+ return null;
364
+ return {
365
+ user,
366
+ workspace,
367
+ authType: "api_key",
368
+ actorId: `key:${String(row.id)}`,
369
+ };
370
+ }
371
+ const hash = this.hashToken(trimmed);
372
+ const sessionRow = this.db
373
+ .prepare("SELECT * FROM sessions WHERE token_hash = ?")
374
+ .get(hash);
375
+ if (!sessionRow)
376
+ return null;
377
+ if (new Date(String(sessionRow.expires_at)).getTime() < Date.now()) {
378
+ return null;
379
+ }
380
+ const user = this.getUser(String(sessionRow.user_id));
381
+ const workspace = this.getWorkspace(String(sessionRow.workspace_id));
382
+ if (!user || !workspace)
383
+ return null;
384
+ return {
385
+ user,
386
+ workspace,
387
+ authType: "session",
388
+ actorId: user.id,
389
+ };
390
+ }
391
+ getUser(userId) {
392
+ const row = this.db
393
+ .prepare("SELECT * FROM users WHERE id = ?")
394
+ .get(userId);
395
+ return row ? this.mapUser(row) : null;
396
+ }
397
+ getWorkspace(workspaceId) {
398
+ const row = this.db
399
+ .prepare("SELECT * FROM workspaces WHERE id = ?")
400
+ .get(workspaceId);
401
+ return row ? this.mapWorkspace(row) : null;
402
+ }
403
+ updateWorkspacePolicy(workspaceId, policy) {
404
+ const ws = this.getWorkspace(workspaceId);
405
+ if (!ws)
406
+ throw new Error(`Workspace ${workspaceId} not found`);
407
+ const merged = {
408
+ ...ws.policy,
409
+ ...policy,
410
+ };
411
+ const now = new Date().toISOString();
412
+ this.db
413
+ .prepare("UPDATE workspaces SET policy_json = ?, updated_at = ? WHERE id = ?")
414
+ .run(JSON.stringify(merged), now, workspaceId);
415
+ return this.getWorkspace(workspaceId);
416
+ }
417
+ // --- API Keys ---
418
+ createApiKey(params) {
419
+ const env = params.environment || "live";
420
+ const id = `key_${crypto.randomBytes(6).toString("hex")}`;
421
+ const rawSecret = `mk_${env}_${id.slice(4)}_${crypto.randomBytes(16).toString("hex")}`;
422
+ const keyPrefix = `${rawSecret.slice(0, 16)}...${rawSecret.slice(-4)}`;
423
+ const scopes = params.scopes || ["tools:read", "tools:execute", "resources:provision"];
424
+ const now = new Date().toISOString();
425
+ this.db
426
+ .prepare(`INSERT INTO api_keys (
427
+ id, workspace_id, name, key_prefix, key_hash, environment, scopes_json, created_at
428
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
429
+ .run(id, params.workspaceId, params.name, keyPrefix, this.hashToken(rawSecret), env, JSON.stringify(scopes), now);
430
+ return {
431
+ record: {
432
+ id,
433
+ workspaceId: params.workspaceId,
434
+ name: params.name,
435
+ keyPrefix,
436
+ environment: env,
437
+ scopes,
438
+ lastUsedAt: null,
439
+ revokedAt: null,
440
+ createdAt: now,
441
+ },
442
+ secretKey: rawSecret,
443
+ };
444
+ }
445
+ listApiKeys(workspaceId) {
446
+ const rows = this.db
447
+ .prepare("SELECT * FROM api_keys WHERE workspace_id = ? ORDER BY created_at DESC")
448
+ .all(workspaceId);
449
+ return rows.map((r) => ({
450
+ id: String(r.id),
451
+ workspaceId: String(r.workspace_id),
452
+ name: String(r.name),
453
+ keyPrefix: String(r.key_prefix),
454
+ environment: String(r.environment),
455
+ scopes: JSON.parse(String(r.scopes_json || "[]")),
456
+ lastUsedAt: r.last_used_at ? String(r.last_used_at) : null,
457
+ revokedAt: r.revoked_at ? String(r.revoked_at) : null,
458
+ createdAt: String(r.created_at),
459
+ }));
460
+ }
461
+ revokeApiKey(workspaceId, keyId) {
462
+ const now = new Date().toISOString();
463
+ const res = this.db
464
+ .prepare("UPDATE api_keys SET revoked_at = ? WHERE id = ? AND workspace_id = ?")
465
+ .run(now, keyId, workspaceId);
466
+ return res.changes > 0;
467
+ }
468
+ // --- Connections ---
469
+ upsertConnection(params) {
470
+ const now = new Date().toISOString();
471
+ const existing = this.db
472
+ .prepare("SELECT id FROM connections WHERE workspace_id = ? AND lane = ? AND provider_slug = ?")
473
+ .get(params.workspaceId, params.lane, params.providerSlug);
474
+ const id = existing?.id || `conn_${crypto.randomBytes(8).toString("hex")}`;
475
+ this.db
476
+ .prepare(`INSERT INTO connections (
477
+ id, workspace_id, lane, provider_slug, provider_name, status,
478
+ external_account_id, redirect_url, metadata_json, updated_at, created_at
479
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
480
+ ON CONFLICT(workspace_id, lane, provider_slug) DO UPDATE SET
481
+ provider_name = excluded.provider_name,
482
+ status = excluded.status,
483
+ external_account_id = COALESCE(excluded.external_account_id, connections.external_account_id),
484
+ redirect_url = CASE
485
+ WHEN excluded.status = 'active' THEN NULL
486
+ ELSE COALESCE(excluded.redirect_url, connections.redirect_url)
487
+ END,
488
+ metadata_json = excluded.metadata_json,
489
+ updated_at = excluded.updated_at`)
490
+ .run(id, params.workspaceId, params.lane, params.providerSlug, params.providerName, params.status, params.externalAccountId ?? null, params.redirectUrl ?? null, JSON.stringify(params.metadata || {}), now, now);
491
+ return this.getConnection(id);
492
+ }
493
+ getConnection(id) {
494
+ const row = this.db
495
+ .prepare("SELECT * FROM connections WHERE id = ?")
496
+ .get(id);
497
+ return row ? this.mapConnection(row) : null;
498
+ }
499
+ listConnections(workspaceId) {
500
+ const rows = this.db
501
+ .prepare("SELECT * FROM connections WHERE workspace_id = ? ORDER BY updated_at DESC")
502
+ .all(workspaceId);
503
+ return rows.map((r) => this.mapConnection(r));
504
+ }
505
+ // --- Provisioned Resources (Stripe Projects Vault) ---
506
+ upsertResource(params) {
507
+ const now = new Date().toISOString();
508
+ const existing = this.db
509
+ .prepare("SELECT id, env_var_keys_json, encrypted_secrets_hex FROM resources WHERE workspace_id = ? AND resource_name = ?")
510
+ .get(params.workspaceId, params.resourceName);
511
+ const id = existing?.id || `res_${crypto.randomBytes(8).toString("hex")}`;
512
+ let envVarKeys = existing
513
+ ? JSON.parse(existing.env_var_keys_json || "[]")
514
+ : [];
515
+ let encryptedHex = existing?.encrypted_secrets_hex || null;
516
+ if (params.secrets && Object.keys(params.secrets).length > 0) {
517
+ const mergedSecrets = {
518
+ ...this.decryptSecrets(params.workspaceId, encryptedHex),
519
+ ...params.secrets,
520
+ };
521
+ envVarKeys = Object.keys(mergedSecrets).sort();
522
+ encryptedHex = this.encryptSecrets(params.workspaceId, mergedSecrets);
523
+ }
524
+ this.db
525
+ .prepare(`INSERT INTO resources (
526
+ id, workspace_id, resource_name, provider_slug, provider_name,
527
+ service_slug, plan_slug, status, env_var_keys_json,
528
+ encrypted_secrets_hex, external_resource_id, metadata_json,
529
+ created_at, updated_at
530
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
531
+ ON CONFLICT(workspace_id, resource_name) DO UPDATE SET
532
+ status = excluded.status,
533
+ service_slug = excluded.service_slug,
534
+ plan_slug = COALESCE(excluded.plan_slug, resources.plan_slug),
535
+ env_var_keys_json = excluded.env_var_keys_json,
536
+ encrypted_secrets_hex = COALESCE(excluded.encrypted_secrets_hex, resources.encrypted_secrets_hex),
537
+ external_resource_id = COALESCE(excluded.external_resource_id, resources.external_resource_id),
538
+ metadata_json = excluded.metadata_json,
539
+ updated_at = excluded.updated_at`)
540
+ .run(id, params.workspaceId, params.resourceName, params.providerSlug, params.providerName, params.serviceSlug, params.planSlug ?? null, params.status, JSON.stringify(envVarKeys), encryptedHex, params.externalResourceId ?? null, JSON.stringify(params.metadata || {}), now, now);
541
+ return this.getResource(params.workspaceId, params.resourceName);
542
+ }
543
+ getResource(workspaceId, resourceNameOrId) {
544
+ const row = this.db
545
+ .prepare("SELECT * FROM resources WHERE workspace_id = ? AND (resource_name = ? OR id = ?)")
546
+ .get(workspaceId, resourceNameOrId, resourceNameOrId);
547
+ return row ? this.mapResource(row) : null;
548
+ }
549
+ listResources(workspaceId) {
550
+ const rows = this.db
551
+ .prepare("SELECT * FROM resources WHERE workspace_id = ? ORDER BY updated_at DESC")
552
+ .all(workspaceId);
553
+ return rows.map((r) => this.mapResource(r));
554
+ }
555
+ getDecryptedWorkspaceEnv(workspaceId) {
556
+ const rows = this.db
557
+ .prepare("SELECT encrypted_secrets_hex FROM resources WHERE workspace_id = ? AND status = 'active'")
558
+ .all(workspaceId);
559
+ const merged = {};
560
+ for (const row of rows) {
561
+ Object.assign(merged, this.decryptSecrets(workspaceId, row.encrypted_secrets_hex));
562
+ }
563
+ return merged;
564
+ }
565
+ // --- Plans ---
566
+ createPlan(params) {
567
+ const id = `plan_${crypto.randomBytes(8).toString("hex")}`;
568
+ const now = new Date();
569
+ const expires = new Date(now.getTime() + 60 * 60 * 1000);
570
+ this.db
571
+ .prepare(`INSERT INTO plans (
572
+ id, workspace_id, actor_id, tool_slug, upstream_slug, lane, risk_level,
573
+ input_json, reason, credit_cost, approval_status, approved_by, approved_at,
574
+ run_id, decision_record_json, memory_context_json, created_at, expires_at
575
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?)`)
576
+ .run(id, params.workspaceId, params.actorId, params.toolSlug, params.upstreamSlug, params.lane, params.riskLevel, JSON.stringify(params.input || {}), params.reason, params.creditCost, params.approvalStatus, params.approvalStatus === "auto_approved" ? "policy:auto" : null, params.approvalStatus === "auto_approved" ? now.toISOString() : null, params.decisionRecord ? JSON.stringify(params.decisionRecord) : null, params.memoryContext ? JSON.stringify(params.memoryContext) : null, now.toISOString(), expires.toISOString());
577
+ return this.getPlan(params.workspaceId, id);
578
+ }
579
+ getPlan(workspaceId, planId) {
580
+ const row = this.db
581
+ .prepare("SELECT * FROM plans WHERE id = ? AND workspace_id = ?")
582
+ .get(planId, workspaceId);
583
+ return row ? this.mapPlan(row) : null;
584
+ }
585
+ listPlans(workspaceId, limit = 50) {
586
+ const rows = this.db
587
+ .prepare("SELECT * FROM plans WHERE workspace_id = ? ORDER BY created_at DESC LIMIT ?")
588
+ .all(workspaceId, limit);
589
+ return rows.map((r) => this.mapPlan(r));
590
+ }
591
+ updatePlanApproval(params) {
592
+ const now = new Date().toISOString();
593
+ this.db
594
+ .prepare(`UPDATE plans
595
+ SET approval_status = ?, approved_by = ?, approved_at = ?
596
+ WHERE id = ? AND workspace_id = ?`)
597
+ .run(params.approvalStatus, params.approvedBy, now, params.planId, params.workspaceId);
598
+ return this.getPlan(params.workspaceId, params.planId);
599
+ }
600
+ // --- Runs & Cryptographic Receipts ---
601
+ createRun(plan, actorId) {
602
+ const id = `run_${crypto.randomBytes(8).toString("hex")}`;
603
+ const now = new Date().toISOString();
604
+ this.db
605
+ .prepare(`INSERT INTO runs (
606
+ id, plan_id, workspace_id, actor_id, tool_slug, lane, risk_level,
607
+ status, input_json, output_json, error, actions_metered, credits_metered,
608
+ duration_ms, receipt_id, started_at, completed_at
609
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, 'running', ?, NULL, NULL, 0, 0, NULL, NULL, ?, NULL)`)
610
+ .run(id, plan.id, plan.workspaceId, actorId, plan.toolSlug, plan.lane, plan.riskLevel, JSON.stringify(plan.input), now);
611
+ this.db
612
+ .prepare("UPDATE plans SET run_id = ?, approval_status = 'executed' WHERE id = ?")
613
+ .run(id, plan.id);
614
+ return this.getRun(plan.workspaceId, id);
615
+ }
616
+ finalizeRun(params) {
617
+ const run = this.getRun(params.workspaceId, params.runId);
618
+ if (!run)
619
+ throw new Error(`Run ${params.runId} not found`);
620
+ const now = new Date().toISOString();
621
+ if (params.actionsMetered > 0 || params.creditsMetered > 0) {
622
+ const ws = this.getWorkspace(params.workspaceId);
623
+ const newActionsUsed = ws.monthlyActionsUsed + params.actionsMetered;
624
+ const newCreditsBalance = Math.max(0, ws.creditsBalance - params.creditsMetered);
625
+ this.db
626
+ .prepare(`UPDATE workspaces
627
+ SET monthly_actions_used = ?, credits_balance = ?, updated_at = ?
628
+ WHERE id = ?`)
629
+ .run(newActionsUsed, newCreditsBalance, now, params.workspaceId);
630
+ const settlement = this.computeSupplierSettlement(run.lane, run.toolSlug, params.creditsMetered, "settled_by_muxo_master_wallet");
631
+ this.addLedgerEntry({
632
+ workspaceId: params.workspaceId,
633
+ kind: "run_deduction",
634
+ actionsDelta: -params.actionsMetered,
635
+ creditsDelta: -params.creditsMetered,
636
+ balanceAfter: newCreditsBalance,
637
+ referenceId: run.id,
638
+ description: `Executed ${run.toolSlug} (${run.lane}) — User paid Muxo ${params.creditsMetered} credits ($${(settlement.userRetailChargeCents / 100).toFixed(2)}) -> Muxo paid ${settlement.supplierName} ($${(settlement.supplierWholesalePayableCents / 100).toFixed(2)})`,
639
+ });
640
+ }
641
+ const receipt = this.issueReceipt({
642
+ run,
643
+ status: params.status,
644
+ output: params.output ?? null,
645
+ actionsMetered: params.actionsMetered,
646
+ creditsMetered: params.creditsMetered,
647
+ decisionRecord: params.decisionRecord ?? null,
648
+ issuedAt: now,
649
+ });
650
+ this.db
651
+ .prepare(`UPDATE runs
652
+ SET status = ?, output_json = ?, error = ?, actions_metered = ?,
653
+ credits_metered = ?, duration_ms = ?, receipt_id = ?, completed_at = ?
654
+ WHERE id = ? AND workspace_id = ?`)
655
+ .run(params.status, params.output ? JSON.stringify(params.output) : null, params.error ?? null, params.actionsMetered, params.creditsMetered, params.durationMs, receipt.id, now, params.runId, params.workspaceId);
656
+ return {
657
+ run: this.getRun(params.workspaceId, params.runId),
658
+ receipt,
659
+ };
660
+ }
661
+ issueReceipt(params) {
662
+ const id = `rcpt_${crypto.randomBytes(8).toString("hex")}`;
663
+ const lastReceipt = this.db
664
+ .prepare("SELECT receipt_hash FROM receipts WHERE workspace_id = ? ORDER BY issued_at DESC LIMIT 1")
665
+ .get(params.run.workspaceId);
666
+ const prevReceiptHash = lastReceipt?.receipt_hash || "genesis_0000000000000000";
667
+ const inputHash = crypto
668
+ .createHash("sha256")
669
+ .update(JSON.stringify(params.run.input || {}))
670
+ .digest("hex");
671
+ const outputHash = crypto
672
+ .createHash("sha256")
673
+ .update(JSON.stringify(params.output || {}))
674
+ .digest("hex");
675
+ const canonicalPayload = [
676
+ id,
677
+ params.run.id,
678
+ params.run.planId,
679
+ params.run.workspaceId,
680
+ params.run.actorId,
681
+ params.run.toolSlug,
682
+ params.run.lane,
683
+ params.run.riskLevel,
684
+ params.status,
685
+ String(params.actionsMetered),
686
+ String(params.creditsMetered),
687
+ inputHash,
688
+ outputHash,
689
+ prevReceiptHash,
690
+ params.issuedAt,
691
+ ].join("|");
692
+ const receiptHash = crypto.createHash("sha256").update(canonicalPayload).digest("hex");
693
+ const signature = crypto
694
+ .createHmac("sha256", this.vaultKey)
695
+ .update(receiptHash)
696
+ .digest("hex");
697
+ this.db
698
+ .prepare(`INSERT INTO receipts (
699
+ id, run_id, plan_id, workspace_id, actor_id, tool_slug, lane, risk_level,
700
+ status, actions_metered, credits_metered, input_hash, output_hash,
701
+ prev_receipt_hash, receipt_hash, signature, decision_record_json, issued_at
702
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
703
+ .run(id, params.run.id, params.run.planId, params.run.workspaceId, params.run.actorId, params.run.toolSlug, params.run.lane, params.run.riskLevel, params.status, params.actionsMetered, params.creditsMetered, inputHash, outputHash, prevReceiptHash, receiptHash, signature, params.decisionRecord ? JSON.stringify(params.decisionRecord) : null, params.issuedAt);
704
+ return this.getReceipt(params.run.workspaceId, id);
705
+ }
706
+ verifyReceipt(receipt) {
707
+ const canonicalPayload = [
708
+ receipt.id,
709
+ receipt.runId,
710
+ receipt.planId,
711
+ receipt.workspaceId,
712
+ receipt.actorId,
713
+ receipt.toolSlug,
714
+ receipt.lane,
715
+ receipt.riskLevel,
716
+ receipt.status,
717
+ String(receipt.actionsMetered),
718
+ String(receipt.creditsMetered),
719
+ receipt.inputHash,
720
+ receipt.outputHash,
721
+ receipt.prevReceiptHash,
722
+ receipt.issuedAt,
723
+ ].join("|");
724
+ const expectedHash = crypto.createHash("sha256").update(canonicalPayload).digest("hex");
725
+ if (expectedHash !== receipt.receiptHash)
726
+ return false;
727
+ const expectedSig = crypto
728
+ .createHmac("sha256", this.vaultKey)
729
+ .update(expectedHash)
730
+ .digest("hex");
731
+ return expectedSig === receipt.signature;
732
+ }
733
+ getRun(workspaceId, runId) {
734
+ const row = this.db
735
+ .prepare("SELECT * FROM runs WHERE id = ? AND workspace_id = ?")
736
+ .get(runId, workspaceId);
737
+ return row ? this.mapRun(row) : null;
738
+ }
739
+ listRuns(workspaceId, limit = 50) {
740
+ const rows = this.db
741
+ .prepare("SELECT * FROM runs WHERE workspace_id = ? ORDER BY started_at DESC LIMIT ?")
742
+ .all(workspaceId, limit);
743
+ return rows.map((r) => this.mapRun(r));
744
+ }
745
+ getReceipt(workspaceId, receiptIdOrRunId) {
746
+ const row = this.db
747
+ .prepare("SELECT * FROM receipts WHERE workspace_id = ? AND (id = ? OR run_id = ?)")
748
+ .get(workspaceId, receiptIdOrRunId, receiptIdOrRunId);
749
+ return row ? this.mapReceipt(row) : null;
750
+ }
751
+ listReceipts(workspaceId, limit = 50) {
752
+ const rows = this.db
753
+ .prepare("SELECT * FROM receipts WHERE workspace_id = ? ORDER BY issued_at DESC LIMIT ?")
754
+ .all(workspaceId, limit);
755
+ return rows.map((r) => this.mapReceipt(r));
756
+ }
757
+ // --- Billing & Credit Ledger ---
758
+ markStripeEventProcessed(eventId) {
759
+ const existing = this.db
760
+ .prepare("SELECT event_id FROM processed_stripe_events WHERE event_id = ?")
761
+ .get(eventId);
762
+ if (existing)
763
+ return false;
764
+ this.db
765
+ .prepare("INSERT INTO processed_stripe_events (event_id, processed_at) VALUES (?, ?)")
766
+ .run(eventId, new Date().toISOString());
767
+ return true;
768
+ }
769
+ upgradeWorkspaceToStarter(params) {
770
+ if (!this.markStripeEventProcessed(`upgrade:${params.referenceId}`)) {
771
+ return this.getWorkspace(params.workspaceId);
772
+ }
773
+ const ws = this.getWorkspace(params.workspaceId);
774
+ if (!ws)
775
+ throw new Error(`Workspace ${params.workspaceId} not found`);
776
+ const now = new Date().toISOString();
777
+ const newCredits = ws.creditsBalance + 1000;
778
+ this.db
779
+ .prepare(`UPDATE workspaces
780
+ SET tier = 'starter',
781
+ monthly_actions_limit = 5000,
782
+ credits_balance = ?,
783
+ stripe_customer_id = COALESCE(?, stripe_customer_id),
784
+ stripe_subscription_id = COALESCE(?, stripe_subscription_id),
785
+ updated_at = ?
786
+ WHERE id = ?`)
787
+ .run(newCredits, params.stripeCustomerId ?? null, params.stripeSubscriptionId ?? null, now, params.workspaceId);
788
+ this.addLedgerEntry({
789
+ workspaceId: params.workspaceId,
790
+ kind: "subscription_upgrade",
791
+ actionsDelta: 4000,
792
+ creditsDelta: 1000,
793
+ balanceAfter: newCredits,
794
+ referenceId: params.referenceId,
795
+ description: "Upgraded to Starter Plan ($29/mo — 5,000 actions/mo + 1,000 credits)",
796
+ });
797
+ return this.getWorkspace(params.workspaceId);
798
+ }
799
+ addWorkspaceCredits(params) {
800
+ if (!this.markStripeEventProcessed(`credits:${params.referenceId}`)) {
801
+ return this.getWorkspace(params.workspaceId);
802
+ }
803
+ const ws = this.getWorkspace(params.workspaceId);
804
+ if (!ws)
805
+ throw new Error(`Workspace ${params.workspaceId} not found`);
806
+ const now = new Date().toISOString();
807
+ const newCredits = ws.creditsBalance + params.credits;
808
+ this.db
809
+ .prepare("UPDATE workspaces SET credits_balance = ?, updated_at = ? WHERE id = ?")
810
+ .run(newCredits, now, params.workspaceId);
811
+ this.addLedgerEntry({
812
+ workspaceId: params.workspaceId,
813
+ kind: "credit_topup",
814
+ actionsDelta: 0,
815
+ creditsDelta: params.credits,
816
+ balanceAfter: newCredits,
817
+ referenceId: params.referenceId,
818
+ description: params.description,
819
+ });
820
+ return this.getWorkspace(params.workspaceId);
821
+ }
822
+ addLedgerEntry(params) {
823
+ const id = `led_${crypto.randomBytes(8).toString("hex")}`;
824
+ const now = new Date().toISOString();
825
+ this.db
826
+ .prepare(`INSERT INTO ledger_entries (
827
+ id, workspace_id, kind, actions_delta, credits_delta,
828
+ balance_after, reference_id, description, created_at
829
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
830
+ .run(id, params.workspaceId, params.kind, params.actionsDelta, params.creditsDelta, params.balanceAfter, params.referenceId, params.description, now);
831
+ return {
832
+ id,
833
+ workspaceId: params.workspaceId,
834
+ kind: params.kind,
835
+ actionsDelta: params.actionsDelta,
836
+ creditsDelta: params.creditsDelta,
837
+ balanceAfter: params.balanceAfter,
838
+ referenceId: params.referenceId,
839
+ description: params.description,
840
+ createdAt: now,
841
+ };
842
+ }
843
+ computeSupplierSettlement(lane, toolSlug, creditCost, settlementStatus = "settled_by_muxo_master_wallet") {
844
+ const userChargeCredits = Math.max(0, Number(creditCost || 0));
845
+ const userRetailChargeCents = Math.round(userChargeCredits * 100) / 100;
846
+ const supplierWholesalePayableCents = Math.round(userRetailChargeCents * 0.6 * 100) / 100;
847
+ const muxoNetMarginCents = Math.round((userRetailChargeCents - supplierWholesalePayableCents) * 100) / 100;
848
+ const upstreamProvider = toolSlug.split(".")[1] || lane;
849
+ const supplierMeta = {
850
+ composio_instant: {
851
+ supplierName: "Composio Instant Wholesale Wallet",
852
+ masterAccountRef: "muxo_master_composio_wallet",
853
+ },
854
+ composio_sdk: {
855
+ supplierName: "Composio Managed OAuth Gateway",
856
+ masterAccountRef: "muxo_master_composio_sdk_account",
857
+ },
858
+ stripe_projects: {
859
+ supplierName: "Stripe Projects Master Infrastructure Account",
860
+ masterAccountRef: "muxo_master_stripe_projects_account",
861
+ },
862
+ };
863
+ const meta = supplierMeta[lane] || supplierMeta.composio_instant;
864
+ return {
865
+ fundingMode: "muxo_funded",
866
+ supplier: lane,
867
+ supplierName: meta.supplierName,
868
+ upstreamProvider,
869
+ userChargeCredits,
870
+ userRetailChargeCents,
871
+ supplierWholesalePayableCents,
872
+ muxoNetMarginCents,
873
+ settlementStatus,
874
+ masterAccountRef: meta.masterAccountRef,
875
+ };
876
+ }
877
+ getSupplierSettlementSummary(workspaceId) {
878
+ const ws = this.getWorkspace(workspaceId);
879
+ const ledger = this.listLedgerEntries(workspaceId, 500);
880
+ const runs = this.listRuns(workspaceId, 500).filter((r) => r.status === "succeeded");
881
+ const totalCreditsPurchasedOrGranted = ledger
882
+ .filter((e) => e.creditsDelta > 0)
883
+ .reduce((acc, e) => acc + e.creditsDelta, 0);
884
+ const bySupplier = {
885
+ composio_instant: {
886
+ runsCount: 0,
887
+ creditsConsumed: 0,
888
+ userRetailSpendCents: 0,
889
+ supplierWholesalePaidCents: 0,
890
+ muxoNetMarginCents: 0,
891
+ },
892
+ composio_sdk: {
893
+ runsCount: 0,
894
+ creditsConsumed: 0,
895
+ userRetailSpendCents: 0,
896
+ supplierWholesalePaidCents: 0,
897
+ muxoNetMarginCents: 0,
898
+ },
899
+ stripe_projects: {
900
+ runsCount: 0,
901
+ creditsConsumed: 0,
902
+ userRetailSpendCents: 0,
903
+ supplierWholesalePaidCents: 0,
904
+ muxoNetMarginCents: 0,
905
+ },
906
+ };
907
+ let totalCreditsConsumed = 0;
908
+ let totalUserRetailSpendCents = 0;
909
+ let totalSupplierWholesalePaidCents = 0;
910
+ let totalMuxoNetMarginCents = 0;
911
+ for (const run of runs) {
912
+ const s = this.computeSupplierSettlement(run.lane, run.toolSlug, run.creditsMetered, "settled_by_muxo_master_wallet");
913
+ const bucket = bySupplier[run.lane] || bySupplier.composio_instant;
914
+ bucket.runsCount += 1;
915
+ bucket.creditsConsumed += s.userChargeCredits;
916
+ bucket.userRetailSpendCents =
917
+ Math.round((bucket.userRetailSpendCents + s.userRetailChargeCents) * 100) / 100;
918
+ bucket.supplierWholesalePaidCents =
919
+ Math.round((bucket.supplierWholesalePaidCents + s.supplierWholesalePayableCents) * 100) / 100;
920
+ bucket.muxoNetMarginCents =
921
+ Math.round((bucket.muxoNetMarginCents + s.muxoNetMarginCents) * 100) / 100;
922
+ totalCreditsConsumed += s.userChargeCredits;
923
+ totalUserRetailSpendCents =
924
+ Math.round((totalUserRetailSpendCents + s.userRetailChargeCents) * 100) / 100;
925
+ totalSupplierWholesalePaidCents =
926
+ Math.round((totalSupplierWholesalePaidCents + s.supplierWholesalePayableCents) * 100) / 100;
927
+ totalMuxoNetMarginCents =
928
+ Math.round((totalMuxoNetMarginCents + s.muxoNetMarginCents) * 100) / 100;
929
+ }
930
+ return {
931
+ workspaceId,
932
+ fundingModel: "user_pays_muxo_muxo_pays_suppliers",
933
+ creditPriceCents: 1,
934
+ totalCreditsPurchasedOrGranted,
935
+ totalCreditsConsumed,
936
+ currentCreditsBalance: ws?.creditsBalance ?? 0,
937
+ totalUserRetailSpendCents,
938
+ totalSupplierWholesalePaidCents,
939
+ totalMuxoNetMarginCents,
940
+ bySupplier,
941
+ };
942
+ }
943
+ listLedgerEntries(workspaceId, limit = 50) {
944
+ const rows = this.db
945
+ .prepare("SELECT * FROM ledger_entries WHERE workspace_id = ? ORDER BY created_at DESC LIMIT ?")
946
+ .all(workspaceId, limit);
947
+ return rows.map((r) => {
948
+ const kind = String(r.kind);
949
+ const refId = r.reference_id ? String(r.reference_id) : null;
950
+ const creditsDelta = Number(r.credits_delta);
951
+ let supplierSettlement = null;
952
+ if (kind === "run_deduction" && refId) {
953
+ const run = this.getRun(workspaceId, refId);
954
+ if (run) {
955
+ supplierSettlement = this.computeSupplierSettlement(run.lane, run.toolSlug, Math.abs(creditsDelta), "settled_by_muxo_master_wallet");
956
+ }
957
+ }
958
+ return {
959
+ id: String(r.id),
960
+ workspaceId: String(r.workspace_id),
961
+ kind,
962
+ actionsDelta: Number(r.actions_delta),
963
+ creditsDelta,
964
+ balanceAfter: Number(r.balance_after),
965
+ referenceId: refId,
966
+ description: String(r.description),
967
+ supplierSettlement,
968
+ createdAt: String(r.created_at),
969
+ };
970
+ });
971
+ }
972
+ // --- Supermemory Local Index ---
973
+ saveMemoryRecord(params) {
974
+ const id = `mem_${crypto.randomBytes(8).toString("hex")}`;
975
+ const now = new Date().toISOString();
976
+ this.db
977
+ .prepare(`INSERT INTO memories (
978
+ id, workspace_id, namespace, document_id, title, content, kind, metadata_json, created_at
979
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
980
+ .run(id, params.workspaceId, params.namespace, params.documentId, params.title, params.content, params.kind, JSON.stringify(params.metadata || {}), now);
981
+ return {
982
+ id,
983
+ workspaceId: params.workspaceId,
984
+ namespace: params.namespace,
985
+ documentId: params.documentId,
986
+ title: params.title,
987
+ content: params.content,
988
+ kind: params.kind,
989
+ metadata: params.metadata || {},
990
+ createdAt: now,
991
+ };
992
+ }
993
+ listMemoryRecords(workspaceId, limit = 30) {
994
+ const rows = this.db
995
+ .prepare("SELECT * FROM memories WHERE workspace_id = ? ORDER BY created_at DESC LIMIT ?")
996
+ .all(workspaceId, limit);
997
+ return rows.map((r) => ({
998
+ id: String(r.id),
999
+ workspaceId: String(r.workspace_id),
1000
+ namespace: String(r.namespace),
1001
+ documentId: String(r.document_id),
1002
+ title: String(r.title),
1003
+ content: String(r.content),
1004
+ kind: String(r.kind),
1005
+ metadata: JSON.parse(String(r.metadata_json || "{}")),
1006
+ createdAt: String(r.created_at),
1007
+ }));
1008
+ }
1009
+ deleteMemoryRecord(workspaceId, idOrDocId) {
1010
+ const row = this.db
1011
+ .prepare("SELECT * FROM memories WHERE workspace_id = ? AND (id = ? OR document_id = ?)")
1012
+ .get(workspaceId, idOrDocId, idOrDocId);
1013
+ if (!row)
1014
+ return null;
1015
+ this.db
1016
+ .prepare("DELETE FROM memories WHERE id = ? AND workspace_id = ?")
1017
+ .run(String(row.id), workspaceId);
1018
+ return {
1019
+ id: String(row.id),
1020
+ workspaceId: String(row.workspace_id),
1021
+ namespace: String(row.namespace),
1022
+ documentId: String(row.document_id),
1023
+ title: String(row.title),
1024
+ content: String(row.content),
1025
+ kind: String(row.kind),
1026
+ metadata: JSON.parse(String(row.metadata_json || "{}")),
1027
+ createdAt: String(row.created_at),
1028
+ };
1029
+ }
1030
+ // --- Autonomous Agent Onboarding (agents.composio.dev pattern) ---
1031
+ createAutonomousAgentSignup(params) {
1032
+ const suffix = crypto.randomBytes(4).toString("hex");
1033
+ const agentName = (params?.agentName || `Autonomous Agent ${suffix}`).trim();
1034
+ const email = `agent+${suffix}@agents.muxo.ai`;
1035
+ const password = crypto.randomBytes(16).toString("hex");
1036
+ const created = this.registerUser({
1037
+ email,
1038
+ name: agentName,
1039
+ password,
1040
+ workspaceName: `${agentName} Workspace`,
1041
+ });
1042
+ const keyRes = this.createApiKey({
1043
+ workspaceId: created.workspace.id,
1044
+ name: params?.harness
1045
+ ? `${params.harness} Autonomous Key`
1046
+ : "Autonomous Agent Key",
1047
+ environment: "live",
1048
+ });
1049
+ return {
1050
+ user: created.user,
1051
+ workspace: created.workspace,
1052
+ apiKey: keyRes.secretKey,
1053
+ apiKeyRecord: keyRes.record,
1054
+ };
1055
+ }
1056
+ getLatestClaimForWorkspace(workspaceId) {
1057
+ const row = this.db
1058
+ .prepare(`SELECT * FROM agent_claims
1059
+ WHERE workspace_id = ?
1060
+ ORDER BY created_at DESC
1061
+ LIMIT 1`)
1062
+ .get(workspaceId);
1063
+ if (!row)
1064
+ return null;
1065
+ return {
1066
+ claimSlug: String(row.claim_slug),
1067
+ targetEmail: String(row.target_email),
1068
+ status: String(row.status),
1069
+ claimedAt: row.claimed_at ? String(row.claimed_at) : null,
1070
+ expiresAt: String(row.expires_at),
1071
+ };
1072
+ }
1073
+ createAgentClaim(params) {
1074
+ const claimSlug = crypto.randomBytes(9).toString("base64url");
1075
+ const now = new Date();
1076
+ const expires = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
1077
+ this.db
1078
+ .prepare(`INSERT INTO agent_claims (
1079
+ claim_slug, workspace_id, target_email, status, claimed_at, expires_at, created_at
1080
+ ) VALUES (?, ?, ?, 'invited', NULL, ?, ?)`)
1081
+ .run(claimSlug, params.workspaceId, params.email.trim().toLowerCase(), expires.toISOString(), now.toISOString());
1082
+ return {
1083
+ claimSlug,
1084
+ claimUrl: `${params.baseUrl.replace(/\/$/, "")}/claim/${claimSlug}`,
1085
+ targetEmail: params.email.trim().toLowerCase(),
1086
+ status: "invited",
1087
+ expiresAt: expires.toISOString(),
1088
+ };
1089
+ }
1090
+ redeemAgentClaim(claimSlug) {
1091
+ const row = this.db
1092
+ .prepare("SELECT * FROM agent_claims WHERE claim_slug = ?")
1093
+ .get(claimSlug);
1094
+ if (!row)
1095
+ return null;
1096
+ const now = new Date().toISOString();
1097
+ const workspaceId = String(row.workspace_id);
1098
+ const targetEmail = String(row.target_email);
1099
+ this.db
1100
+ .prepare("UPDATE agent_claims SET status = 'claimed', claimed_at = ? WHERE claim_slug = ?")
1101
+ .run(now, claimSlug);
1102
+ const workspace = this.getWorkspace(workspaceId);
1103
+ if (!workspace)
1104
+ return null;
1105
+ return {
1106
+ claimSlug,
1107
+ workspace,
1108
+ targetEmail,
1109
+ status: "claimed",
1110
+ claimedAt: now,
1111
+ };
1112
+ }
1113
+ // --- Row Mappers ---
1114
+ mapUser(row) {
1115
+ return {
1116
+ id: String(row.id),
1117
+ email: String(row.email),
1118
+ name: String(row.name),
1119
+ workspaceId: String(row.workspace_id),
1120
+ role: String(row.role),
1121
+ createdAt: String(row.created_at),
1122
+ };
1123
+ }
1124
+ mapWorkspace(row) {
1125
+ return {
1126
+ id: String(row.id),
1127
+ name: String(row.name),
1128
+ slug: String(row.slug),
1129
+ ownerUserId: String(row.owner_user_id),
1130
+ tier: String(row.tier),
1131
+ monthlyActionsLimit: Number(row.monthly_actions_limit),
1132
+ monthlyActionsUsed: Number(row.monthly_actions_used),
1133
+ creditsBalance: Number(row.credits_balance),
1134
+ stripeCustomerId: row.stripe_customer_id ? String(row.stripe_customer_id) : null,
1135
+ stripeSubscriptionId: row.stripe_subscription_id
1136
+ ? String(row.stripe_subscription_id)
1137
+ : null,
1138
+ projectsEnvName: String(row.projects_env_name),
1139
+ policy: JSON.parse(String(row.policy_json || "{}")),
1140
+ createdAt: String(row.created_at),
1141
+ updatedAt: String(row.updated_at),
1142
+ };
1143
+ }
1144
+ mapConnection(row) {
1145
+ return {
1146
+ id: String(row.id),
1147
+ workspaceId: String(row.workspace_id),
1148
+ lane: String(row.lane),
1149
+ providerSlug: String(row.provider_slug),
1150
+ providerName: String(row.provider_name),
1151
+ status: String(row.status),
1152
+ externalAccountId: row.external_account_id ? String(row.external_account_id) : null,
1153
+ redirectUrl: row.redirect_url ? String(row.redirect_url) : null,
1154
+ metadata: JSON.parse(String(row.metadata_json || "{}")),
1155
+ updatedAt: String(row.updated_at),
1156
+ createdAt: String(row.created_at),
1157
+ };
1158
+ }
1159
+ mapResource(row) {
1160
+ return {
1161
+ id: String(row.id),
1162
+ workspaceId: String(row.workspace_id),
1163
+ resourceName: String(row.resource_name),
1164
+ providerSlug: String(row.provider_slug),
1165
+ providerName: String(row.provider_name),
1166
+ serviceSlug: String(row.service_slug),
1167
+ planSlug: row.plan_slug ? String(row.plan_slug) : null,
1168
+ status: String(row.status),
1169
+ envVarKeys: JSON.parse(String(row.env_var_keys_json || "[]")),
1170
+ externalResourceId: row.external_resource_id
1171
+ ? String(row.external_resource_id)
1172
+ : null,
1173
+ metadata: JSON.parse(String(row.metadata_json || "{}")),
1174
+ createdAt: String(row.created_at),
1175
+ updatedAt: String(row.updated_at),
1176
+ };
1177
+ }
1178
+ mapPlan(row) {
1179
+ const lane = String(row.lane);
1180
+ const toolSlug = String(row.tool_slug);
1181
+ const creditCost = Number(row.credit_cost);
1182
+ const approvalStatus = String(row.approval_status);
1183
+ return {
1184
+ id: String(row.id),
1185
+ workspaceId: String(row.workspace_id),
1186
+ actorId: String(row.actor_id),
1187
+ toolSlug,
1188
+ upstreamSlug: String(row.upstream_slug),
1189
+ lane,
1190
+ riskLevel: String(row.risk_level),
1191
+ input: JSON.parse(String(row.input_json || "{}")),
1192
+ reason: String(row.reason),
1193
+ creditCost,
1194
+ approvalStatus,
1195
+ approvedBy: row.approved_by ? String(row.approved_by) : null,
1196
+ approvedAt: row.approved_at ? String(row.approved_at) : null,
1197
+ runId: row.run_id ? String(row.run_id) : null,
1198
+ decisionRecord: row.decision_record_json
1199
+ ? JSON.parse(String(row.decision_record_json))
1200
+ : null,
1201
+ memoryContext: row.memory_context_json
1202
+ ? JSON.parse(String(row.memory_context_json))
1203
+ : null,
1204
+ supplierSettlement: this.computeSupplierSettlement(lane, toolSlug, creditCost, approvalStatus === "executed"
1205
+ ? "settled_by_muxo_master_wallet"
1206
+ : "planned_muxo_master_wallet"),
1207
+ createdAt: String(row.created_at),
1208
+ expiresAt: String(row.expires_at),
1209
+ };
1210
+ }
1211
+ mapRun(row) {
1212
+ return {
1213
+ id: String(row.id),
1214
+ planId: String(row.plan_id),
1215
+ workspaceId: String(row.workspace_id),
1216
+ actorId: String(row.actor_id),
1217
+ toolSlug: String(row.tool_slug),
1218
+ lane: String(row.lane),
1219
+ riskLevel: String(row.risk_level),
1220
+ status: String(row.status),
1221
+ input: JSON.parse(String(row.input_json || "{}")),
1222
+ output: row.output_json ? JSON.parse(String(row.output_json)) : null,
1223
+ error: row.error ? String(row.error) : null,
1224
+ actionsMetered: Number(row.actions_metered),
1225
+ creditsMetered: Number(row.credits_metered),
1226
+ durationMs: row.duration_ms !== null ? Number(row.duration_ms) : null,
1227
+ receiptId: row.receipt_id ? String(row.receipt_id) : null,
1228
+ startedAt: String(row.started_at),
1229
+ completedAt: row.completed_at ? String(row.completed_at) : null,
1230
+ };
1231
+ }
1232
+ mapReceipt(row) {
1233
+ const lane = String(row.lane);
1234
+ const toolSlug = String(row.tool_slug);
1235
+ const creditsMetered = Number(row.credits_metered);
1236
+ return {
1237
+ id: String(row.id),
1238
+ runId: String(row.run_id),
1239
+ planId: String(row.plan_id),
1240
+ workspaceId: String(row.workspace_id),
1241
+ actorId: String(row.actor_id),
1242
+ toolSlug,
1243
+ lane,
1244
+ riskLevel: String(row.risk_level),
1245
+ status: String(row.status),
1246
+ actionsMetered: Number(row.actions_metered),
1247
+ creditsMetered,
1248
+ inputHash: String(row.input_hash),
1249
+ outputHash: String(row.output_hash),
1250
+ prevReceiptHash: String(row.prev_receipt_hash),
1251
+ receiptHash: String(row.receipt_hash),
1252
+ signature: String(row.signature),
1253
+ decisionRecord: row.decision_record_json
1254
+ ? JSON.parse(String(row.decision_record_json))
1255
+ : null,
1256
+ supplierSettlement: this.computeSupplierSettlement(lane, toolSlug, creditsMetered, "settled_by_muxo_master_wallet"),
1257
+ issuedAt: String(row.issued_at),
1258
+ };
1259
+ }
1260
+ }