@mastra/factory 0.2.2-alpha.1 → 0.2.2-alpha.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.
Files changed (155) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/auth.js +401 -334
  3. package/dist/auth.js.map +1 -1
  4. package/dist/capabilities/connection.js +1 -1
  5. package/dist/capabilities/intake.js +1 -1
  6. package/dist/capabilities/version-control.js +1 -1
  7. package/dist/factory.js +441 -39622
  8. package/dist/factory.js.map +1 -1
  9. package/dist/index.js +2 -39633
  10. package/dist/integrations/base.js +1 -1
  11. package/dist/integrations/github/config.js +36 -22
  12. package/dist/integrations/github/config.js.map +1 -1
  13. package/dist/integrations/github/factory-session.js +33 -37
  14. package/dist/integrations/github/factory-session.js.map +1 -1
  15. package/dist/integrations/github/integration.js +1037 -3672
  16. package/dist/integrations/github/integration.js.map +1 -1
  17. package/dist/integrations/github/issue-triage.js +71 -71
  18. package/dist/integrations/github/issue-triage.js.map +1 -1
  19. package/dist/integrations/github/pat.js +47 -36
  20. package/dist/integrations/github/pat.js.map +1 -1
  21. package/dist/integrations/github/provenance.js +72 -101
  22. package/dist/integrations/github/provenance.js.map +1 -1
  23. package/dist/integrations/github/routes.js +1272 -1881
  24. package/dist/integrations/github/routes.js.map +1 -1
  25. package/dist/integrations/github/rules.js +203 -471
  26. package/dist/integrations/github/rules.js.map +1 -1
  27. package/dist/integrations/github/sandbox.js +400 -345
  28. package/dist/integrations/github/sandbox.js.map +1 -1
  29. package/dist/integrations/github/session-subscriptions.js +165 -223
  30. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  31. package/dist/integrations/github/subscriptions.js +43 -51
  32. package/dist/integrations/github/subscriptions.js.map +1 -1
  33. package/dist/integrations/github/token-refresh.js +15 -18
  34. package/dist/integrations/github/token-refresh.js.map +1 -1
  35. package/dist/integrations/github/webhook.js +359 -343
  36. package/dist/integrations/github/webhook.js.map +1 -1
  37. package/dist/integrations/linear/agent-tools.js +77 -100
  38. package/dist/integrations/linear/agent-tools.js.map +1 -1
  39. package/dist/integrations/linear/integration.js +611 -1309
  40. package/dist/integrations/linear/integration.js.map +1 -1
  41. package/dist/integrations/linear/routes.js +227 -230
  42. package/dist/integrations/linear/routes.js.map +1 -1
  43. package/dist/integrations/linear/rules.js +126 -346
  44. package/dist/integrations/linear/rules.js.map +1 -1
  45. package/dist/integrations/linear/storage.js +1 -1
  46. package/dist/integrations/platform/api-client.js +131 -135
  47. package/dist/integrations/platform/api-client.js.map +1 -1
  48. package/dist/integrations/platform/github/event-worker.js +275 -573
  49. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  50. package/dist/integrations/platform/github/integration.js +845 -4022
  51. package/dist/integrations/platform/github/integration.js.map +1 -1
  52. package/dist/integrations/platform/linear/integration.js +457 -1309
  53. package/dist/integrations/platform/linear/integration.js.map +1 -1
  54. package/dist/integrations/workos/integration.js +80 -85
  55. package/dist/integrations/workos/integration.js.map +1 -1
  56. package/dist/routes/config.d.ts.map +1 -1
  57. package/dist/routes/config.js +933 -912
  58. package/dist/routes/config.js.map +1 -1
  59. package/dist/routes/custom-provider-source.js +110 -93
  60. package/dist/routes/custom-provider-source.js.map +1 -1
  61. package/dist/routes/fs.js +401 -349
  62. package/dist/routes/fs.js.map +1 -1
  63. package/dist/routes/intake.js +165 -165
  64. package/dist/routes/intake.js.map +1 -1
  65. package/dist/routes/oauth.js +447 -25166
  66. package/dist/routes/oauth.js.map +1 -1
  67. package/dist/routes/projects.js +437 -376
  68. package/dist/routes/projects.js.map +1 -1
  69. package/dist/routes/provider-credentials.js +76 -58
  70. package/dist/routes/provider-credentials.js.map +1 -1
  71. package/dist/routes/route.js +12 -8
  72. package/dist/routes/route.js.map +1 -1
  73. package/dist/routes/skills.js +160 -173
  74. package/dist/routes/skills.js.map +1 -1
  75. package/dist/routes/surface.js +250 -28615
  76. package/dist/routes/surface.js.map +1 -1
  77. package/dist/routes/tenant-credentials.js +141 -166
  78. package/dist/routes/tenant-credentials.js.map +1 -1
  79. package/dist/routes/work-items.js +654 -862
  80. package/dist/routes/work-items.js.map +1 -1
  81. package/dist/rules/binding-context.js +23 -35
  82. package/dist/rules/binding-context.js.map +1 -1
  83. package/dist/rules/defaults.js +200 -309
  84. package/dist/rules/defaults.js.map +1 -1
  85. package/dist/rules/dispatcher.js +438 -683
  86. package/dist/rules/dispatcher.js.map +1 -1
  87. package/dist/rules/index.js +5 -599
  88. package/dist/rules/processor.js +344 -596
  89. package/dist/rules/processor.js.map +1 -1
  90. package/dist/rules/resolve.js +21 -18
  91. package/dist/rules/resolve.js.map +1 -1
  92. package/dist/rules/start-coordinator.js +168 -157
  93. package/dist/rules/start-coordinator.js.map +1 -1
  94. package/dist/rules/tools.js +50 -85
  95. package/dist/rules/tools.js.map +1 -1
  96. package/dist/rules/transition-service.js +183 -454
  97. package/dist/rules/transition-service.js.map +1 -1
  98. package/dist/rules/types.js +33 -29
  99. package/dist/rules/types.js.map +1 -1
  100. package/dist/rules/validation.js +266 -281
  101. package/dist/rules/validation.js.map +1 -1
  102. package/dist/sandbox/fleet.js +272 -250
  103. package/dist/sandbox/fleet.js.map +1 -1
  104. package/dist/sandbox/reattach.js +13 -6
  105. package/dist/sandbox/reattach.js.map +1 -1
  106. package/dist/server-error.js +12 -16
  107. package/dist/server-error.js.map +1 -1
  108. package/dist/skills/service.js +30 -35
  109. package/dist/skills/service.js.map +1 -1
  110. package/dist/spa-static.js +91 -65
  111. package/dist/spa-static.js.map +1 -1
  112. package/dist/state-signing.js +73 -44
  113. package/dist/state-signing.js.map +1 -1
  114. package/dist/storage/domains/audit/agent-audit.js +60 -59
  115. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  116. package/dist/storage/domains/audit/base.js +169 -112
  117. package/dist/storage/domains/audit/base.js.map +1 -1
  118. package/dist/storage/domains/audit/domain.js +155 -153
  119. package/dist/storage/domains/audit/domain.js.map +1 -1
  120. package/dist/storage/domains/credentials/base.js +259 -243
  121. package/dist/storage/domains/credentials/base.js.map +1 -1
  122. package/dist/storage/domains/custom-providers/base.js +138 -143
  123. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  124. package/dist/storage/domains/intake/base.js +75 -57
  125. package/dist/storage/domains/intake/base.js.map +1 -1
  126. package/dist/storage/domains/integrations/base.js +310 -245
  127. package/dist/storage/domains/integrations/base.js.map +1 -1
  128. package/dist/storage/domains/memory-settings/base.js +117 -102
  129. package/dist/storage/domains/memory-settings/base.js.map +1 -1
  130. package/dist/storage/domains/model-packs/base.js +89 -88
  131. package/dist/storage/domains/model-packs/base.js.map +1 -1
  132. package/dist/storage/domains/projects/base.js +97 -92
  133. package/dist/storage/domains/projects/base.js.map +1 -1
  134. package/dist/storage/domains/queue-health/base.js +111 -76
  135. package/dist/storage/domains/queue-health/base.js.map +1 -1
  136. package/dist/storage/domains/source-control/base.js +777 -706
  137. package/dist/storage/domains/source-control/base.js.map +1 -1
  138. package/dist/storage/domains/source-control/inmemory.js +322 -348
  139. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  140. package/dist/storage/domains/work-items/base.js +1266 -1148
  141. package/dist/storage/domains/work-items/base.js.map +1 -1
  142. package/dist/storage/domains/work-items/metrics.js +202 -184
  143. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  144. package/dist/timing.js +29 -22
  145. package/dist/timing.js.map +1 -1
  146. package/dist/workspace.js +236 -477
  147. package/dist/workspace.js.map +1 -1
  148. package/package.json +8 -8
  149. package/dist/capabilities/connection.js.map +0 -1
  150. package/dist/capabilities/intake.js.map +0 -1
  151. package/dist/capabilities/version-control.js.map +0 -1
  152. package/dist/index.js.map +0 -1
  153. package/dist/integrations/base.js.map +0 -1
  154. package/dist/integrations/linear/storage.js.map +0 -1
  155. package/dist/rules/index.js.map +0 -1
@@ -1,48 +1,77 @@
1
- // src/state-signing.ts
2
1
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
3
- var STATE_MAX_AGE_MS = 10 * 60 * 1e3;
2
+ //#region src/state-signing.ts
3
+ /**
4
+ * Shared OAuth/install `state` signing for web integrations.
5
+ *
6
+ * Both the GitHub and Linear OAuth flows round-trip a signed `state` value
7
+ * through the third party to bind the callback to the `(orgId, userId)` tenant
8
+ * that initiated it (CSRF protection + tenant routing). The signer is a system
9
+ * facility: `MastraFactory` creates ONE signer at boot and hands it to every
10
+ * registered integration through `IntegrationContext` (see
11
+ * `./factory-integration.ts`), so all integrations sign and verify with the
12
+ * same secret.
13
+ *
14
+ * Secret resolution happens in the factory, not here: explicit
15
+ * `config.stateSecret` → the GitHub integration's webhook secret → a
16
+ * per-process random secret. A random secret is NOT stable across replicas —
17
+ * a `state` signed by one replica cannot be verified by another — which is
18
+ * what the `stable` flag reports. The factory fails loud at boot when a
19
+ * registered integration requires a stable signer but only a random one is
20
+ * available.
21
+ *
22
+ * The wire format (base64url JSON payload + `.` + HMAC-SHA256 base64url
23
+ * signature) is unchanged from the previous `github/config.ts` implementation
24
+ * so in-flight OAuth states survive a deploy.
25
+ */
26
+ /** Signed `state` values expire after this window to bound the CSRF token. */
27
+ const STATE_MAX_AGE_MS = 600 * 1e3;
28
+ /**
29
+ * Create a state signer. With a `secret`, the signer is deployment-stable
30
+ * (`stable: true`); without one it falls back to a per-process random secret
31
+ * (`stable: false`).
32
+ */
4
33
  function createStateSigner(secret) {
5
- const stable = typeof secret === "string" && secret.length > 0;
6
- const key = stable ? secret : randomBytes(32).toString("hex");
7
- return {
8
- stable,
9
- sign(orgId, userId) {
10
- const payload = {
11
- orgId,
12
- userId,
13
- nonce: randomBytes(8).toString("hex"),
14
- issuedAt: Date.now()
15
- };
16
- const body = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
17
- const sig = createHmac("sha256", key).update(body).digest("base64url");
18
- return `${body}.${sig}`;
19
- },
20
- verify(state) {
21
- if (!state) return null;
22
- const dot = state.lastIndexOf(".");
23
- if (dot <= 0) return null;
24
- const body = state.slice(0, dot);
25
- const sig = state.slice(dot + 1);
26
- const expected = createHmac("sha256", key).update(body).digest("base64url");
27
- const sigBuf = Buffer.from(sig);
28
- const expectedBuf = Buffer.from(expected);
29
- if (sigBuf.length !== expectedBuf.length || !timingSafeEqual(sigBuf, expectedBuf)) {
30
- return null;
31
- }
32
- try {
33
- const parsed = JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
34
- if (typeof parsed.orgId !== "string" || typeof parsed.userId !== "string") return null;
35
- if (typeof parsed.issuedAt !== "number" || !Number.isFinite(parsed.issuedAt)) return null;
36
- const age = Date.now() - parsed.issuedAt;
37
- if (age < 0 || age > STATE_MAX_AGE_MS) return null;
38
- return { orgId: parsed.orgId, userId: parsed.userId };
39
- } catch {
40
- return null;
41
- }
42
- }
43
- };
34
+ const stable = typeof secret === "string" && secret.length > 0;
35
+ const key = stable ? secret : randomBytes(32).toString("hex");
36
+ return {
37
+ stable,
38
+ sign(orgId, userId) {
39
+ const payload = {
40
+ orgId,
41
+ userId,
42
+ nonce: randomBytes(8).toString("hex"),
43
+ issuedAt: Date.now()
44
+ };
45
+ const body = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
46
+ return `${body}.${createHmac("sha256", key).update(body).digest("base64url")}`;
47
+ },
48
+ verify(state) {
49
+ if (!state) return null;
50
+ const dot = state.lastIndexOf(".");
51
+ if (dot <= 0) return null;
52
+ const body = state.slice(0, dot);
53
+ const sig = state.slice(dot + 1);
54
+ const expected = createHmac("sha256", key).update(body).digest("base64url");
55
+ const sigBuf = Buffer.from(sig);
56
+ const expectedBuf = Buffer.from(expected);
57
+ if (sigBuf.length !== expectedBuf.length || !timingSafeEqual(sigBuf, expectedBuf)) return null;
58
+ try {
59
+ const parsed = JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
60
+ if (typeof parsed.orgId !== "string" || typeof parsed.userId !== "string") return null;
61
+ if (typeof parsed.issuedAt !== "number" || !Number.isFinite(parsed.issuedAt)) return null;
62
+ const age = Date.now() - parsed.issuedAt;
63
+ if (age < 0 || age > STATE_MAX_AGE_MS) return null;
64
+ return {
65
+ orgId: parsed.orgId,
66
+ userId: parsed.userId
67
+ };
68
+ } catch {
69
+ return null;
70
+ }
71
+ }
72
+ };
44
73
  }
45
- export {
46
- createStateSigner
47
- };
74
+ //#endregion
75
+ export { createStateSigner };
76
+
48
77
  //# sourceMappingURL=state-signing.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/state-signing.ts"],"sourcesContent":["/**\n * Shared OAuth/install `state` signing for web integrations.\n *\n * Both the GitHub and Linear OAuth flows round-trip a signed `state` value\n * through the third party to bind the callback to the `(orgId, userId)` tenant\n * that initiated it (CSRF protection + tenant routing). The signer is a system\n * facility: `MastraFactory` creates ONE signer at boot and hands it to every\n * registered integration through `IntegrationContext` (see\n * `./factory-integration.ts`), so all integrations sign and verify with the\n * same secret.\n *\n * Secret resolution happens in the factory, not here: explicit\n * `config.stateSecret` → the GitHub integration's webhook secret → a\n * per-process random secret. A random secret is NOT stable across replicas —\n * a `state` signed by one replica cannot be verified by another — which is\n * what the `stable` flag reports. The factory fails loud at boot when a\n * registered integration requires a stable signer but only a random one is\n * available.\n *\n * The wire format (base64url JSON payload + `.` + HMAC-SHA256 base64url\n * signature) is unchanged from the previous `github/config.ts` implementation\n * so in-flight OAuth states survive a deploy.\n */\n\nimport { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';\n\n/** Verified `(orgId, userId)` tenant carried by a signed `state`. */\nexport interface StateTenant {\n orgId: string;\n userId: string;\n}\n\n/** Signs and verifies OAuth `state` values bound to a `(orgId, userId)` tenant. */\nexport interface StateSigner {\n /** Build a signed `state` bound to the tenant. */\n sign(orgId: string, userId: string): string;\n /** Verify a signed `state`; returns the bound tenant, or `null` if invalid. */\n verify(state: string | undefined): StateTenant | null;\n /**\n * True when the signer was built from an explicit deployment-stable secret.\n * False means a per-process random secret: fine for single-process/local\n * dev, broken for multi-replica deploys (see module docs).\n */\n readonly stable: boolean;\n}\n\ninterface StatePayload {\n orgId: string;\n userId: string;\n nonce: string;\n issuedAt: number;\n}\n\n/** Signed `state` values expire after this window to bound the CSRF token. */\nconst STATE_MAX_AGE_MS = 10 * 60 * 1000;\n\n/**\n * Create a state signer. With a `secret`, the signer is deployment-stable\n * (`stable: true`); without one it falls back to a per-process random secret\n * (`stable: false`).\n */\nexport function createStateSigner(secret?: string): StateSigner {\n const stable = typeof secret === 'string' && secret.length > 0;\n const key = stable ? secret : randomBytes(32).toString('hex');\n return {\n stable,\n sign(orgId: string, userId: string): string {\n const payload: StatePayload = {\n orgId,\n userId,\n nonce: randomBytes(8).toString('hex'),\n issuedAt: Date.now(),\n };\n const body = Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url');\n const sig = createHmac('sha256', key).update(body).digest('base64url');\n return `${body}.${sig}`;\n },\n verify(state: string | undefined): StateTenant | null {\n if (!state) return null;\n const dot = state.lastIndexOf('.');\n if (dot <= 0) return null;\n const body = state.slice(0, dot);\n const sig = state.slice(dot + 1);\n const expected = createHmac('sha256', key).update(body).digest('base64url');\n const sigBuf = Buffer.from(sig);\n const expectedBuf = Buffer.from(expected);\n if (sigBuf.length !== expectedBuf.length || !timingSafeEqual(sigBuf, expectedBuf)) {\n return null;\n }\n try {\n const parsed = JSON.parse(Buffer.from(body, 'base64url').toString('utf8')) as StatePayload;\n if (typeof parsed.orgId !== 'string' || typeof parsed.userId !== 'string') return null;\n if (typeof parsed.issuedAt !== 'number' || !Number.isFinite(parsed.issuedAt)) return null;\n const age = Date.now() - parsed.issuedAt;\n if (age < 0 || age > STATE_MAX_AGE_MS) return null;\n return { orgId: parsed.orgId, userId: parsed.userId };\n } catch {\n return null;\n }\n },\n };\n}\n"],"mappings":";AAwBA,SAAS,YAAY,aAAa,uBAAuB;AA8BzD,IAAM,mBAAmB,KAAK,KAAK;AAO5B,SAAS,kBAAkB,QAA8B;AAC9D,QAAM,SAAS,OAAO,WAAW,YAAY,OAAO,SAAS;AAC7D,QAAM,MAAM,SAAS,SAAS,YAAY,EAAE,EAAE,SAAS,KAAK;AAC5D,SAAO;AAAA,IACL;AAAA,IACA,KAAK,OAAe,QAAwB;AAC1C,YAAM,UAAwB;AAAA,QAC5B;AAAA,QACA;AAAA,QACA,OAAO,YAAY,CAAC,EAAE,SAAS,KAAK;AAAA,QACpC,UAAU,KAAK,IAAI;AAAA,MACrB;AACA,YAAM,OAAO,OAAO,KAAK,KAAK,UAAU,OAAO,GAAG,MAAM,EAAE,SAAS,WAAW;AAC9E,YAAM,MAAM,WAAW,UAAU,GAAG,EAAE,OAAO,IAAI,EAAE,OAAO,WAAW;AACrE,aAAO,GAAG,IAAI,IAAI,GAAG;AAAA,IACvB;AAAA,IACA,OAAO,OAA+C;AACpD,UAAI,CAAC,MAAO,QAAO;AACnB,YAAM,MAAM,MAAM,YAAY,GAAG;AACjC,UAAI,OAAO,EAAG,QAAO;AACrB,YAAM,OAAO,MAAM,MAAM,GAAG,GAAG;AAC/B,YAAM,MAAM,MAAM,MAAM,MAAM,CAAC;AAC/B,YAAM,WAAW,WAAW,UAAU,GAAG,EAAE,OAAO,IAAI,EAAE,OAAO,WAAW;AAC1E,YAAM,SAAS,OAAO,KAAK,GAAG;AAC9B,YAAM,cAAc,OAAO,KAAK,QAAQ;AACxC,UAAI,OAAO,WAAW,YAAY,UAAU,CAAC,gBAAgB,QAAQ,WAAW,GAAG;AACjF,eAAO;AAAA,MACT;AACA,UAAI;AACF,cAAM,SAAS,KAAK,MAAM,OAAO,KAAK,MAAM,WAAW,EAAE,SAAS,MAAM,CAAC;AACzE,YAAI,OAAO,OAAO,UAAU,YAAY,OAAO,OAAO,WAAW,SAAU,QAAO;AAClF,YAAI,OAAO,OAAO,aAAa,YAAY,CAAC,OAAO,SAAS,OAAO,QAAQ,EAAG,QAAO;AACrF,cAAM,MAAM,KAAK,IAAI,IAAI,OAAO;AAChC,YAAI,MAAM,KAAK,MAAM,iBAAkB,QAAO;AAC9C,eAAO,EAAE,OAAO,OAAO,OAAO,QAAQ,OAAO,OAAO;AAAA,MACtD,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"file":"state-signing.js","names":[],"sources":["../src/state-signing.ts"],"sourcesContent":["/**\n * Shared OAuth/install `state` signing for web integrations.\n *\n * Both the GitHub and Linear OAuth flows round-trip a signed `state` value\n * through the third party to bind the callback to the `(orgId, userId)` tenant\n * that initiated it (CSRF protection + tenant routing). The signer is a system\n * facility: `MastraFactory` creates ONE signer at boot and hands it to every\n * registered integration through `IntegrationContext` (see\n * `./factory-integration.ts`), so all integrations sign and verify with the\n * same secret.\n *\n * Secret resolution happens in the factory, not here: explicit\n * `config.stateSecret` → the GitHub integration's webhook secret → a\n * per-process random secret. A random secret is NOT stable across replicas —\n * a `state` signed by one replica cannot be verified by another — which is\n * what the `stable` flag reports. The factory fails loud at boot when a\n * registered integration requires a stable signer but only a random one is\n * available.\n *\n * The wire format (base64url JSON payload + `.` + HMAC-SHA256 base64url\n * signature) is unchanged from the previous `github/config.ts` implementation\n * so in-flight OAuth states survive a deploy.\n */\n\nimport { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';\n\n/** Verified `(orgId, userId)` tenant carried by a signed `state`. */\nexport interface StateTenant {\n orgId: string;\n userId: string;\n}\n\n/** Signs and verifies OAuth `state` values bound to a `(orgId, userId)` tenant. */\nexport interface StateSigner {\n /** Build a signed `state` bound to the tenant. */\n sign(orgId: string, userId: string): string;\n /** Verify a signed `state`; returns the bound tenant, or `null` if invalid. */\n verify(state: string | undefined): StateTenant | null;\n /**\n * True when the signer was built from an explicit deployment-stable secret.\n * False means a per-process random secret: fine for single-process/local\n * dev, broken for multi-replica deploys (see module docs).\n */\n readonly stable: boolean;\n}\n\ninterface StatePayload {\n orgId: string;\n userId: string;\n nonce: string;\n issuedAt: number;\n}\n\n/** Signed `state` values expire after this window to bound the CSRF token. */\nconst STATE_MAX_AGE_MS = 10 * 60 * 1000;\n\n/**\n * Create a state signer. With a `secret`, the signer is deployment-stable\n * (`stable: true`); without one it falls back to a per-process random secret\n * (`stable: false`).\n */\nexport function createStateSigner(secret?: string): StateSigner {\n const stable = typeof secret === 'string' && secret.length > 0;\n const key = stable ? secret : randomBytes(32).toString('hex');\n return {\n stable,\n sign(orgId: string, userId: string): string {\n const payload: StatePayload = {\n orgId,\n userId,\n nonce: randomBytes(8).toString('hex'),\n issuedAt: Date.now(),\n };\n const body = Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url');\n const sig = createHmac('sha256', key).update(body).digest('base64url');\n return `${body}.${sig}`;\n },\n verify(state: string | undefined): StateTenant | null {\n if (!state) return null;\n const dot = state.lastIndexOf('.');\n if (dot <= 0) return null;\n const body = state.slice(0, dot);\n const sig = state.slice(dot + 1);\n const expected = createHmac('sha256', key).update(body).digest('base64url');\n const sigBuf = Buffer.from(sig);\n const expectedBuf = Buffer.from(expected);\n if (sigBuf.length !== expectedBuf.length || !timingSafeEqual(sigBuf, expectedBuf)) {\n return null;\n }\n try {\n const parsed = JSON.parse(Buffer.from(body, 'base64url').toString('utf8')) as StatePayload;\n if (typeof parsed.orgId !== 'string' || typeof parsed.userId !== 'string') return null;\n if (typeof parsed.issuedAt !== 'number' || !Number.isFinite(parsed.issuedAt)) return null;\n const age = Date.now() - parsed.issuedAt;\n if (age < 0 || age > STATE_MAX_AGE_MS) return null;\n return { orgId: parsed.orgId, userId: parsed.userId };\n } catch {\n return null;\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAsDA,MAAM,mBAAmB,MAAU;;;;;;AAOnC,SAAgB,kBAAkB,QAA8B;CAC9D,MAAM,SAAS,OAAO,WAAW,YAAY,OAAO,SAAS;CAC7D,MAAM,MAAM,SAAS,SAAS,YAAY,EAAE,CAAC,CAAC,SAAS,KAAK;CAC5D,OAAO;EACL;EACA,KAAK,OAAe,QAAwB;GAC1C,MAAM,UAAwB;IAC5B;IACA;IACA,OAAO,YAAY,CAAC,CAAC,CAAC,SAAS,KAAK;IACpC,UAAU,KAAK,IAAI;GACrB;GACA,MAAM,OAAO,OAAO,KAAK,KAAK,UAAU,OAAO,GAAG,MAAM,CAAC,CAAC,SAAS,WAAW;GAE9E,OAAO,GAAG,KAAK,GADH,WAAW,UAAU,GAAG,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,WACtC;EACtB;EACA,OAAO,OAA+C;GACpD,IAAI,CAAC,OAAO,OAAO;GACnB,MAAM,MAAM,MAAM,YAAY,GAAG;GACjC,IAAI,OAAO,GAAG,OAAO;GACrB,MAAM,OAAO,MAAM,MAAM,GAAG,GAAG;GAC/B,MAAM,MAAM,MAAM,MAAM,MAAM,CAAC;GAC/B,MAAM,WAAW,WAAW,UAAU,GAAG,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,WAAW;GAC1E,MAAM,SAAS,OAAO,KAAK,GAAG;GAC9B,MAAM,cAAc,OAAO,KAAK,QAAQ;GACxC,IAAI,OAAO,WAAW,YAAY,UAAU,CAAC,gBAAgB,QAAQ,WAAW,GAC9E,OAAO;GAET,IAAI;IACF,MAAM,SAAS,KAAK,MAAM,OAAO,KAAK,MAAM,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC;IACzE,IAAI,OAAO,OAAO,UAAU,YAAY,OAAO,OAAO,WAAW,UAAU,OAAO;IAClF,IAAI,OAAO,OAAO,aAAa,YAAY,CAAC,OAAO,SAAS,OAAO,QAAQ,GAAG,OAAO;IACrF,MAAM,MAAM,KAAK,IAAI,IAAI,OAAO;IAChC,IAAI,MAAM,KAAK,MAAM,kBAAkB,OAAO;IAC9C,OAAO;KAAE,OAAO,OAAO;KAAO,QAAQ,OAAO;IAAO;GACtD,QAAQ;IACN,OAAO;GACT;EACF;CACF;AACF"}
@@ -1,65 +1,66 @@
1
- // src/storage/domains/audit/agent-audit.ts
2
- var GIT_COMMIT_RE = /(?:^|\n|;|&&|\|\|)\s*git\s+commit(?:\s|$)/;
3
- var GIT_PUSH_RE = /(?:^|\n|;|&&|\|\|)\s*git\s+push(?:\s|$)/;
1
+ //#region src/storage/domains/audit/agent-audit.ts
2
+ /** Match command-start positions while ignoring command text embedded in heredoc bodies. */
3
+ const GIT_COMMIT_RE = /(?:^|\n|;|&&|\|\|)\s*git\s+commit(?:\s|$)/;
4
+ const GIT_PUSH_RE = /(?:^|\n|;|&&|\|\|)\s*git\s+push(?:\s|$)/;
4
5
  function stripHeredocBodies(command) {
5
- const lines = command.split("\n");
6
- const executableLines = [];
7
- let delimiter;
8
- for (const line of lines) {
9
- if (delimiter) {
10
- if (line.trim() === delimiter) delimiter = void 0;
11
- continue;
12
- }
13
- executableLines.push(line);
14
- const heredoc = line.match(/<<-?\s*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\1/);
15
- delimiter = heredoc?.[2];
16
- }
17
- return executableLines.join("\n");
6
+ const lines = command.split("\n");
7
+ const executableLines = [];
8
+ let delimiter;
9
+ for (const line of lines) {
10
+ if (delimiter) {
11
+ if (line.trim() === delimiter) delimiter = void 0;
12
+ continue;
13
+ }
14
+ executableLines.push(line);
15
+ delimiter = line.match(/<<-?\s*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\1/)?.[2];
16
+ }
17
+ return executableLines.join("\n");
18
18
  }
19
+ /** Parse the branch from a plain `git push <remote> <branch>` invocation. */
19
20
  function parsePushedBranch(command) {
20
- const match = command.match(
21
- /(?:^|\n|;|&&|\|\|)\s*git\s+push\s+(?:-[^\s]+\s+)*([^\s;&|-][^\s;&|]*)\s+([^\s;&|-][^\s;&|]*)/
22
- );
23
- return match?.[2];
21
+ return command.match(/(?:^|\n|;|&&|\|\|)\s*git\s+push\s+(?:-[^\s]+\s+)*([^\s;&|-][^\s;&|]*)\s+([^\s;&|-][^\s;&|]*)/)?.[2];
24
22
  }
25
- async function observeAgentGitAction({
26
- audit,
27
- toolContext
28
- }) {
29
- try {
30
- if (toolContext.toolName !== "execute_command" || toolContext.error) return;
31
- const rawCommand = toolContext.input?.command;
32
- if (typeof rawCommand !== "string") return;
33
- const command = stripHeredocBodies(rawCommand);
34
- const controller = toolContext.context.get("controller");
35
- const worktreePath = controller?.scope;
36
- if (GIT_COMMIT_RE.test(command)) {
37
- await audit.emitAgent({
38
- requestContext: toolContext.context,
39
- input: {
40
- action: "factory.agent.commit",
41
- targets: worktreePath ? [{ type: "worktree", id: worktreePath }] : []
42
- }
43
- });
44
- }
45
- if (GIT_PUSH_RE.test(command)) {
46
- const branch = parsePushedBranch(command);
47
- await audit.emitAgent({
48
- requestContext: toolContext.context,
49
- input: {
50
- action: "factory.agent.push",
51
- targets: worktreePath ? [{ type: "worktree", id: worktreePath }] : [],
52
- ...branch ? { metadata: { branch } } : {}
53
- }
54
- });
55
- }
56
- } catch (err) {
57
- console.warn("[Audit] Failed to observe agent git action", {
58
- error: err instanceof Error ? err.message : String(err)
59
- });
60
- }
23
+ /**
24
+ * Detect externally-visible git side effects in a completed tool call and
25
+ * record `factory.agent.*` audit events for them. One command can emit
26
+ * multiple events (`git commit && git push` emits both). Never throws.
27
+ */
28
+ async function observeAgentGitAction({ audit, toolContext }) {
29
+ try {
30
+ if (toolContext.toolName !== "execute_command" || toolContext.error) return;
31
+ const rawCommand = toolContext.input?.command;
32
+ if (typeof rawCommand !== "string") return;
33
+ const command = stripHeredocBodies(rawCommand);
34
+ const worktreePath = toolContext.context.get("controller")?.scope;
35
+ if (GIT_COMMIT_RE.test(command)) await audit.emitAgent({
36
+ requestContext: toolContext.context,
37
+ input: {
38
+ action: "factory.agent.commit",
39
+ targets: worktreePath ? [{
40
+ type: "worktree",
41
+ id: worktreePath
42
+ }] : []
43
+ }
44
+ });
45
+ if (GIT_PUSH_RE.test(command)) {
46
+ const branch = parsePushedBranch(command);
47
+ await audit.emitAgent({
48
+ requestContext: toolContext.context,
49
+ input: {
50
+ action: "factory.agent.push",
51
+ targets: worktreePath ? [{
52
+ type: "worktree",
53
+ id: worktreePath
54
+ }] : [],
55
+ ...branch ? { metadata: { branch } } : {}
56
+ }
57
+ });
58
+ }
59
+ } catch (err) {
60
+ console.warn("[Audit] Failed to observe agent git action", { error: err instanceof Error ? err.message : String(err) });
61
+ }
61
62
  }
62
- export {
63
- observeAgentGitAction
64
- };
63
+ //#endregion
64
+ export { observeAgentGitAction };
65
+
65
66
  //# sourceMappingURL=agent-audit.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/storage/domains/audit/agent-audit.ts"],"sourcesContent":["/**\n * Agent-level audit event detection (Audit v1.1).\n *\n * Git actions performed by agents inside runs never touch web routes, so this\n * observer detects externally-visible git side effects and delegates recording\n * to the factory-owned audit domain.\n */\n\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\n\nimport type { AuditAgentEmitter } from './domain.js';\n\ntype FactorySessionState = { factoryProjectId?: string; projectRepositoryId?: string };\n\ninterface ToolObserverContext {\n toolName: string;\n input: unknown;\n output?: unknown;\n error?: unknown;\n context: RequestContext;\n}\n\n/** Match command-start positions while ignoring command text embedded in heredoc bodies. */\nconst GIT_COMMIT_RE = /(?:^|\\n|;|&&|\\|\\|)\\s*git\\s+commit(?:\\s|$)/;\nconst GIT_PUSH_RE = /(?:^|\\n|;|&&|\\|\\|)\\s*git\\s+push(?:\\s|$)/;\n\nfunction stripHeredocBodies(command: string): string {\n const lines = command.split('\\n');\n const executableLines: string[] = [];\n let delimiter: string | undefined;\n\n for (const line of lines) {\n if (delimiter) {\n if (line.trim() === delimiter) delimiter = undefined;\n continue;\n }\n executableLines.push(line);\n const heredoc = line.match(/<<-?\\s*(['\"]?)([A-Za-z_][A-Za-z0-9_]*)\\1/);\n delimiter = heredoc?.[2];\n }\n\n return executableLines.join('\\n');\n}\n\n/** Parse the branch from a plain `git push <remote> <branch>` invocation. */\nfunction parsePushedBranch(command: string): string | undefined {\n const match = command.match(\n /(?:^|\\n|;|&&|\\|\\|)\\s*git\\s+push\\s+(?:-[^\\s]+\\s+)*([^\\s;&|-][^\\s;&|]*)\\s+([^\\s;&|-][^\\s;&|]*)/,\n );\n return match?.[2];\n}\n\n/**\n * Detect externally-visible git side effects in a completed tool call and\n * record `factory.agent.*` audit events for them. One command can emit\n * multiple events (`git commit && git push` emits both). Never throws.\n */\nexport async function observeAgentGitAction({\n audit,\n toolContext,\n}: {\n audit: AuditAgentEmitter;\n toolContext: ToolObserverContext;\n}): Promise<void> {\n try {\n if (toolContext.toolName !== 'execute_command' || toolContext.error) return;\n const rawCommand = (toolContext.input as { command?: unknown } | undefined)?.command;\n if (typeof rawCommand !== 'string') return;\n const command = stripHeredocBodies(rawCommand);\n\n const controller = toolContext.context.get('controller') as\n | AgentControllerRequestContext<FactorySessionState>\n | undefined;\n const worktreePath = controller?.scope;\n\n if (GIT_COMMIT_RE.test(command)) {\n await audit.emitAgent({\n requestContext: toolContext.context,\n input: {\n action: 'factory.agent.commit',\n targets: worktreePath ? [{ type: 'worktree', id: worktreePath }] : [],\n },\n });\n }\n\n if (GIT_PUSH_RE.test(command)) {\n const branch = parsePushedBranch(command);\n await audit.emitAgent({\n requestContext: toolContext.context,\n input: {\n action: 'factory.agent.push',\n targets: worktreePath ? [{ type: 'worktree', id: worktreePath }] : [],\n ...(branch ? { metadata: { branch } } : {}),\n },\n });\n }\n } catch (err) {\n console.warn('[Audit] Failed to observe agent git action', {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n}\n"],"mappings":";AAwBA,IAAM,gBAAgB;AACtB,IAAM,cAAc;AAEpB,SAAS,mBAAmB,SAAyB;AACnD,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,QAAM,kBAA4B,CAAC;AACnC,MAAI;AAEJ,aAAW,QAAQ,OAAO;AACxB,QAAI,WAAW;AACb,UAAI,KAAK,KAAK,MAAM,UAAW,aAAY;AAC3C;AAAA,IACF;AACA,oBAAgB,KAAK,IAAI;AACzB,UAAM,UAAU,KAAK,MAAM,0CAA0C;AACrE,gBAAY,UAAU,CAAC;AAAA,EACzB;AAEA,SAAO,gBAAgB,KAAK,IAAI;AAClC;AAGA,SAAS,kBAAkB,SAAqC;AAC9D,QAAM,QAAQ,QAAQ;AAAA,IACpB;AAAA,EACF;AACA,SAAO,QAAQ,CAAC;AAClB;AAOA,eAAsB,sBAAsB;AAAA,EAC1C;AAAA,EACA;AACF,GAGkB;AAChB,MAAI;AACF,QAAI,YAAY,aAAa,qBAAqB,YAAY,MAAO;AACrE,UAAM,aAAc,YAAY,OAA6C;AAC7E,QAAI,OAAO,eAAe,SAAU;AACpC,UAAM,UAAU,mBAAmB,UAAU;AAE7C,UAAM,aAAa,YAAY,QAAQ,IAAI,YAAY;AAGvD,UAAM,eAAe,YAAY;AAEjC,QAAI,cAAc,KAAK,OAAO,GAAG;AAC/B,YAAM,MAAM,UAAU;AAAA,QACpB,gBAAgB,YAAY;AAAA,QAC5B,OAAO;AAAA,UACL,QAAQ;AAAA,UACR,SAAS,eAAe,CAAC,EAAE,MAAM,YAAY,IAAI,aAAa,CAAC,IAAI,CAAC;AAAA,QACtE;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,YAAY,KAAK,OAAO,GAAG;AAC7B,YAAM,SAAS,kBAAkB,OAAO;AACxC,YAAM,MAAM,UAAU;AAAA,QACpB,gBAAgB,YAAY;AAAA,QAC5B,OAAO;AAAA,UACL,QAAQ;AAAA,UACR,SAAS,eAAe,CAAC,EAAE,MAAM,YAAY,IAAI,aAAa,CAAC,IAAI,CAAC;AAAA,UACpE,GAAI,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,QAC3C;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,SAAS,KAAK;AACZ,YAAQ,KAAK,8CAA8C;AAAA,MACzD,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,IACxD,CAAC;AAAA,EACH;AACF;","names":[]}
1
+ {"version":3,"file":"agent-audit.js","names":[],"sources":["../../../../src/storage/domains/audit/agent-audit.ts"],"sourcesContent":["/**\n * Agent-level audit event detection (Audit v1.1).\n *\n * Git actions performed by agents inside runs never touch web routes, so this\n * observer detects externally-visible git side effects and delegates recording\n * to the factory-owned audit domain.\n */\n\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\n\nimport type { AuditAgentEmitter } from './domain.js';\n\ntype FactorySessionState = { factoryProjectId?: string; projectRepositoryId?: string };\n\ninterface ToolObserverContext {\n toolName: string;\n input: unknown;\n output?: unknown;\n error?: unknown;\n context: RequestContext;\n}\n\n/** Match command-start positions while ignoring command text embedded in heredoc bodies. */\nconst GIT_COMMIT_RE = /(?:^|\\n|;|&&|\\|\\|)\\s*git\\s+commit(?:\\s|$)/;\nconst GIT_PUSH_RE = /(?:^|\\n|;|&&|\\|\\|)\\s*git\\s+push(?:\\s|$)/;\n\nfunction stripHeredocBodies(command: string): string {\n const lines = command.split('\\n');\n const executableLines: string[] = [];\n let delimiter: string | undefined;\n\n for (const line of lines) {\n if (delimiter) {\n if (line.trim() === delimiter) delimiter = undefined;\n continue;\n }\n executableLines.push(line);\n const heredoc = line.match(/<<-?\\s*(['\"]?)([A-Za-z_][A-Za-z0-9_]*)\\1/);\n delimiter = heredoc?.[2];\n }\n\n return executableLines.join('\\n');\n}\n\n/** Parse the branch from a plain `git push <remote> <branch>` invocation. */\nfunction parsePushedBranch(command: string): string | undefined {\n const match = command.match(\n /(?:^|\\n|;|&&|\\|\\|)\\s*git\\s+push\\s+(?:-[^\\s]+\\s+)*([^\\s;&|-][^\\s;&|]*)\\s+([^\\s;&|-][^\\s;&|]*)/,\n );\n return match?.[2];\n}\n\n/**\n * Detect externally-visible git side effects in a completed tool call and\n * record `factory.agent.*` audit events for them. One command can emit\n * multiple events (`git commit && git push` emits both). Never throws.\n */\nexport async function observeAgentGitAction({\n audit,\n toolContext,\n}: {\n audit: AuditAgentEmitter;\n toolContext: ToolObserverContext;\n}): Promise<void> {\n try {\n if (toolContext.toolName !== 'execute_command' || toolContext.error) return;\n const rawCommand = (toolContext.input as { command?: unknown } | undefined)?.command;\n if (typeof rawCommand !== 'string') return;\n const command = stripHeredocBodies(rawCommand);\n\n const controller = toolContext.context.get('controller') as\n | AgentControllerRequestContext<FactorySessionState>\n | undefined;\n const worktreePath = controller?.scope;\n\n if (GIT_COMMIT_RE.test(command)) {\n await audit.emitAgent({\n requestContext: toolContext.context,\n input: {\n action: 'factory.agent.commit',\n targets: worktreePath ? [{ type: 'worktree', id: worktreePath }] : [],\n },\n });\n }\n\n if (GIT_PUSH_RE.test(command)) {\n const branch = parsePushedBranch(command);\n await audit.emitAgent({\n requestContext: toolContext.context,\n input: {\n action: 'factory.agent.push',\n targets: worktreePath ? [{ type: 'worktree', id: worktreePath }] : [],\n ...(branch ? { metadata: { branch } } : {}),\n },\n });\n }\n } catch (err) {\n console.warn('[Audit] Failed to observe agent git action', {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n}\n"],"mappings":";;AAwBA,MAAM,gBAAgB;AACtB,MAAM,cAAc;AAEpB,SAAS,mBAAmB,SAAyB;CACnD,MAAM,QAAQ,QAAQ,MAAM,IAAI;CAChC,MAAM,kBAA4B,CAAC;CACnC,IAAI;CAEJ,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,WAAW;GACb,IAAI,KAAK,KAAK,MAAM,WAAW,YAAY,KAAA;GAC3C;EACF;EACA,gBAAgB,KAAK,IAAI;EAEzB,YADgB,KAAK,MAAM,0CACT,CAAC,GAAG;CACxB;CAEA,OAAO,gBAAgB,KAAK,IAAI;AAClC;;AAGA,SAAS,kBAAkB,SAAqC;CAI9D,OAHc,QAAQ,MACpB,8FAES,CAAC,GAAG;AACjB;;;;;;AAOA,eAAsB,sBAAsB,EAC1C,OACA,eAIgB;CAChB,IAAI;EACF,IAAI,YAAY,aAAa,qBAAqB,YAAY,OAAO;EACrE,MAAM,aAAc,YAAY,OAA6C;EAC7E,IAAI,OAAO,eAAe,UAAU;EACpC,MAAM,UAAU,mBAAmB,UAAU;EAK7C,MAAM,eAHa,YAAY,QAAQ,IAAI,YAGb,CAAC,EAAE;EAEjC,IAAI,cAAc,KAAK,OAAO,GAC5B,MAAM,MAAM,UAAU;GACpB,gBAAgB,YAAY;GAC5B,OAAO;IACL,QAAQ;IACR,SAAS,eAAe,CAAC;KAAE,MAAM;KAAY,IAAI;IAAa,CAAC,IAAI,CAAC;GACtE;EACF,CAAC;EAGH,IAAI,YAAY,KAAK,OAAO,GAAG;GAC7B,MAAM,SAAS,kBAAkB,OAAO;GACxC,MAAM,MAAM,UAAU;IACpB,gBAAgB,YAAY;IAC5B,OAAO;KACL,QAAQ;KACR,SAAS,eAAe,CAAC;MAAE,MAAM;MAAY,IAAI;KAAa,CAAC,IAAI,CAAC;KACpE,GAAI,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;IAC3C;GACF,CAAC;EACH;CACF,SAAS,KAAK;EACZ,QAAQ,KAAK,8CAA8C,EACzD,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EACxD,CAAC;CACH;AACF"}
@@ -1,127 +1,184 @@
1
- // src/storage/domains/audit/base.ts
2
1
  import { FactoryStorageDomain } from "@mastra/core/storage";
3
- var MAX_METADATA_JSON_LENGTH = 4096;
4
- var DEFAULT_PAGE_SIZE = 50;
5
- var MAX_PAGE_SIZE = 200;
2
+ //#region src/storage/domains/audit/base.ts
3
+ /**
4
+ * Factory audit events domain — the append-only "who did what, when" trail
5
+ * behind the software factory.
6
+ *
7
+ * One `audit_events` row records a single audited mutation (work-item change,
8
+ * stage move, run start, worktree create/delete, git action, intake config
9
+ * change). Rows are append-only: there is no update/delete API, and the table
10
+ * is the local source of truth even when the WorkOS Audit Logs mirror is
11
+ * unavailable.
12
+ *
13
+ * Tenancy is **org-first**, like `work_items`: events are scoped by `org_id`
14
+ * and (usually) `factory_project_id`; `actor_id` records who acted but never
15
+ * scopes reads.
16
+ *
17
+ * v1 action taxonomy (register these in the WorkOS dashboard under
18
+ * Audit Logs → Events for the export mirror to accept them):
19
+ * - factory.work_item.created
20
+ * - factory.work_item.updated
21
+ * - factory.work_item.stage_moved
22
+ * - factory.work_item.deleted
23
+ * - factory.run.started
24
+ * - factory.worktree.created
25
+ * - factory.worktree.deleted
26
+ * - factory.triage.started
27
+ * - factory.git.commit
28
+ * - factory.git.push
29
+ * - factory.git.pr_opened
30
+ * - factory.intake.config_updated
31
+ *
32
+ * v1.1 adds agent-level actions (also register these in WorkOS):
33
+ * - factory.agent.commit
34
+ * - factory.agent.push
35
+ * - factory.agent.pr_opened
36
+ *
37
+ * Agent events carry `actor_type = 'agent'` with `actor_id = 'agent:<threadId>'`
38
+ * and `metadata.startedBy = <userId>` chaining accountability back to the human
39
+ * whose message drove the run.
40
+ */
41
+ /** Metadata is a bounded summary — never full payloads, never secrets. */
42
+ const MAX_METADATA_JSON_LENGTH = 4096;
43
+ const DEFAULT_PAGE_SIZE = 50;
44
+ const MAX_PAGE_SIZE = 200;
45
+ /** Truncate oversized metadata rather than dropping the whole event. */
6
46
  function boundAuditMetadata(metadata) {
7
- if (!metadata) return {};
8
- try {
9
- if (JSON.stringify(metadata).length <= MAX_METADATA_JSON_LENGTH) return metadata;
10
- } catch {
11
- return { truncated: true };
12
- }
13
- return { truncated: true };
47
+ if (!metadata) return {};
48
+ try {
49
+ if (JSON.stringify(metadata).length <= MAX_METADATA_JSON_LENGTH) return metadata;
50
+ } catch {
51
+ return { truncated: true };
52
+ }
53
+ return { truncated: true };
14
54
  }
55
+ /** Normalize a requested page size to a finite integer in `[1, MAX_PAGE_SIZE]`. */
15
56
  function clampAuditLimit(limit) {
16
- const normalized = typeof limit === "number" && Number.isFinite(limit) ? Math.trunc(limit) : DEFAULT_PAGE_SIZE;
17
- return Math.min(Math.max(normalized, 1), MAX_PAGE_SIZE);
57
+ return Math.min(Math.max(typeof limit === "number" && Number.isFinite(limit) ? Math.trunc(limit) : DEFAULT_PAGE_SIZE, 1), MAX_PAGE_SIZE);
18
58
  }
59
+ /** Encode the `(occurredAt, id)` keyset cursor of a row. */
19
60
  function encodeAuditCursor(row) {
20
- return `${row.occurredAt.toISOString()}_${row.id}`;
61
+ return `${row.occurredAt.toISOString()}_${row.id}`;
21
62
  }
63
+ /** Decode a cursor back into its `(occurredAt, id)` parts, or `undefined`. */
22
64
  function decodeAuditCursor(cursor) {
23
- const sep = cursor.lastIndexOf("_");
24
- if (sep <= 0) return void 0;
25
- const occurredAt = new Date(cursor.slice(0, sep));
26
- const id = cursor.slice(sep + 1);
27
- if (Number.isNaN(occurredAt.getTime()) || !id) return void 0;
28
- return { occurredAt, id };
65
+ const sep = cursor.lastIndexOf("_");
66
+ if (sep <= 0) return void 0;
67
+ const occurredAt = new Date(cursor.slice(0, sep));
68
+ const id = cursor.slice(sep + 1);
69
+ if (Number.isNaN(occurredAt.getTime()) || !id) return void 0;
70
+ return {
71
+ occurredAt,
72
+ id
73
+ };
29
74
  }
30
- var AUDIT_EVENTS_SCHEMA = {
31
- name: "audit_events",
32
- columns: {
33
- id: { type: "uuid-pk" },
34
- org_id: { type: "text" },
35
- actor_id: { type: "text" },
36
- actor_type: { type: "text", default: "human" },
37
- action: { type: "text" },
38
- targets: { type: "json" },
39
- metadata: { type: "json" },
40
- factory_project_id: { type: "text", nullable: true },
41
- project_repository_id: { type: "text", nullable: true },
42
- context: { type: "json" },
43
- occurred_at: { type: "timestamp" }
44
- },
45
- indexes: [
46
- { name: "audit_events_org_occurred_idx", columns: ["org_id", "occurred_at"] },
47
- { name: "audit_events_org_project_occurred_idx", columns: ["org_id", "factory_project_id", "occurred_at"] }
48
- ]
75
+ const AUDIT_EVENTS_SCHEMA = {
76
+ name: "audit_events",
77
+ columns: {
78
+ id: { type: "uuid-pk" },
79
+ org_id: { type: "text" },
80
+ actor_id: { type: "text" },
81
+ actor_type: {
82
+ type: "text",
83
+ default: "human"
84
+ },
85
+ action: { type: "text" },
86
+ targets: { type: "json" },
87
+ metadata: { type: "json" },
88
+ factory_project_id: {
89
+ type: "text",
90
+ nullable: true
91
+ },
92
+ project_repository_id: {
93
+ type: "text",
94
+ nullable: true
95
+ },
96
+ context: { type: "json" },
97
+ occurred_at: { type: "timestamp" }
98
+ },
99
+ indexes: [{
100
+ name: "audit_events_org_occurred_idx",
101
+ columns: ["org_id", "occurred_at"]
102
+ }, {
103
+ name: "audit_events_org_project_occurred_idx",
104
+ columns: [
105
+ "org_id",
106
+ "factory_project_id",
107
+ "occurred_at"
108
+ ]
109
+ }]
49
110
  };
50
111
  function toRow(db) {
51
- return {
52
- id: db.id,
53
- orgId: db.org_id,
54
- actorId: db.actor_id,
55
- actorType: db.actor_type,
56
- action: db.action,
57
- targets: db.targets,
58
- metadata: db.metadata,
59
- factoryProjectId: db.factory_project_id,
60
- projectRepositoryId: db.project_repository_id,
61
- context: db.context,
62
- occurredAt: db.occurred_at
63
- };
112
+ return {
113
+ id: db.id,
114
+ orgId: db.org_id,
115
+ actorId: db.actor_id,
116
+ actorType: db.actor_type,
117
+ action: db.action,
118
+ targets: db.targets,
119
+ metadata: db.metadata,
120
+ factoryProjectId: db.factory_project_id,
121
+ projectRepositoryId: db.project_repository_id,
122
+ context: db.context,
123
+ occurredAt: db.occurred_at
124
+ };
64
125
  }
126
+ /**
127
+ * Audit event storage, written once against the generic `FactoryStorageOps`
128
+ * surface. `record()` normalizes inputs (defaults, metadata bounding);
129
+ * `list()` is keyset-paginated on `(occurred_at, id)` newest-first.
130
+ */
65
131
  var AuditStorage = class extends FactoryStorageDomain {
66
- constructor() {
67
- super("audit");
68
- }
69
- async init() {
70
- await this.ensureCollections([AUDIT_EVENTS_SCHEMA]);
71
- }
72
- async dangerouslyClearAll() {
73
- await this.ops.deleteMany("audit_events", {});
74
- }
75
- get #db() {
76
- return this.ops;
77
- }
78
- /** Append one audit event. Throws on failure — swallow-on-failure lives in the caller. */
79
- async record(input) {
80
- const inserted = await this.#db.insertOne("audit_events", {
81
- org_id: input.orgId,
82
- actor_id: input.actorId,
83
- actor_type: input.actorType ?? "human",
84
- action: input.action,
85
- targets: input.targets,
86
- metadata: boundAuditMetadata(input.metadata),
87
- factory_project_id: input.factoryProjectId ?? null,
88
- project_repository_id: input.projectRepositoryId ?? null,
89
- context: input.context ?? {},
90
- occurred_at: input.occurredAt ?? /* @__PURE__ */ new Date()
91
- });
92
- return toRow(inserted);
93
- }
94
- /** List an org's audit events newest-first with keyset pagination. */
95
- async list(input) {
96
- const limit = clampAuditLimit(input.limit);
97
- const where = { org_id: input.orgId };
98
- if (input.factoryProjectId) where.factory_project_id = input.factoryProjectId;
99
- if (input.actions && input.actions.length > 0) where.action = { in: input.actions };
100
- if (input.actorId) where.actor_id = input.actorId;
101
- const cursor = input.before ? decodeAuditCursor(input.before) : void 0;
102
- const rows = await this.#db.findMany("audit_events", where, {
103
- orderBy: [
104
- ["occurred_at", "desc"],
105
- ["id", "desc"]
106
- ],
107
- limit: limit + 1,
108
- ...cursor ? { cursor: { values: [cursor.occurredAt, cursor.id] } } : {}
109
- });
110
- const events = rows.slice(0, limit).map(toRow);
111
- const hasMore = rows.length > limit;
112
- const last = events[events.length - 1];
113
- return {
114
- events,
115
- ...hasMore && last ? { nextCursor: encodeAuditCursor(last) } : {}
116
- };
117
- }
118
- };
119
- export {
120
- AUDIT_EVENTS_SCHEMA,
121
- AuditStorage,
122
- boundAuditMetadata,
123
- clampAuditLimit,
124
- decodeAuditCursor,
125
- encodeAuditCursor
132
+ constructor() {
133
+ super("audit");
134
+ }
135
+ async init() {
136
+ await this.ensureCollections([AUDIT_EVENTS_SCHEMA]);
137
+ }
138
+ async dangerouslyClearAll() {
139
+ await this.ops.deleteMany("audit_events", {});
140
+ }
141
+ get #db() {
142
+ return this.ops;
143
+ }
144
+ /** Append one audit event. Throws on failure — swallow-on-failure lives in the caller. */
145
+ async record(input) {
146
+ return toRow(await this.#db.insertOne("audit_events", {
147
+ org_id: input.orgId,
148
+ actor_id: input.actorId,
149
+ actor_type: input.actorType ?? "human",
150
+ action: input.action,
151
+ targets: input.targets,
152
+ metadata: boundAuditMetadata(input.metadata),
153
+ factory_project_id: input.factoryProjectId ?? null,
154
+ project_repository_id: input.projectRepositoryId ?? null,
155
+ context: input.context ?? {},
156
+ occurred_at: input.occurredAt ?? /* @__PURE__ */ new Date()
157
+ }));
158
+ }
159
+ /** List an org's audit events newest-first with keyset pagination. */
160
+ async list(input) {
161
+ const limit = clampAuditLimit(input.limit);
162
+ const where = { org_id: input.orgId };
163
+ if (input.factoryProjectId) where.factory_project_id = input.factoryProjectId;
164
+ if (input.actions && input.actions.length > 0) where.action = { in: input.actions };
165
+ if (input.actorId) where.actor_id = input.actorId;
166
+ const cursor = input.before ? decodeAuditCursor(input.before) : void 0;
167
+ const rows = await this.#db.findMany("audit_events", where, {
168
+ orderBy: [["occurred_at", "desc"], ["id", "desc"]],
169
+ limit: limit + 1,
170
+ ...cursor ? { cursor: { values: [cursor.occurredAt, cursor.id] } } : {}
171
+ });
172
+ const events = rows.slice(0, limit).map(toRow);
173
+ const hasMore = rows.length > limit;
174
+ const last = events[events.length - 1];
175
+ return {
176
+ events,
177
+ ...hasMore && last ? { nextCursor: encodeAuditCursor(last) } : {}
178
+ };
179
+ }
126
180
  };
181
+ //#endregion
182
+ export { AUDIT_EVENTS_SCHEMA, AuditStorage, boundAuditMetadata, clampAuditLimit, decodeAuditCursor, encodeAuditCursor };
183
+
127
184
  //# sourceMappingURL=base.js.map