@mastra/factory 0.2.2-alpha.0 → 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 (174) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/auth.d.ts +2 -2
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +401 -314
  5. package/dist/auth.js.map +1 -1
  6. package/dist/capabilities/connection.js +1 -1
  7. package/dist/capabilities/intake.js +1 -1
  8. package/dist/capabilities/version-control.js +1 -1
  9. package/dist/factory.d.ts.map +1 -1
  10. package/dist/factory.js +441 -39455
  11. package/dist/factory.js.map +1 -1
  12. package/dist/index.js +2 -39466
  13. package/dist/integrations/base.js +1 -1
  14. package/dist/integrations/github/config.js +36 -22
  15. package/dist/integrations/github/config.js.map +1 -1
  16. package/dist/integrations/github/factory-session.js +33 -37
  17. package/dist/integrations/github/factory-session.js.map +1 -1
  18. package/dist/integrations/github/integration.js +1037 -3624
  19. package/dist/integrations/github/integration.js.map +1 -1
  20. package/dist/integrations/github/issue-triage.js +71 -71
  21. package/dist/integrations/github/issue-triage.js.map +1 -1
  22. package/dist/integrations/github/pat.js +47 -36
  23. package/dist/integrations/github/pat.js.map +1 -1
  24. package/dist/integrations/github/provenance.js +72 -101
  25. package/dist/integrations/github/provenance.js.map +1 -1
  26. package/dist/integrations/github/routes.d.ts.map +1 -1
  27. package/dist/integrations/github/routes.js +1272 -1833
  28. package/dist/integrations/github/routes.js.map +1 -1
  29. package/dist/integrations/github/rules.js +203 -471
  30. package/dist/integrations/github/rules.js.map +1 -1
  31. package/dist/integrations/github/sandbox.d.ts +2 -0
  32. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  33. package/dist/integrations/github/sandbox.js +401 -326
  34. package/dist/integrations/github/sandbox.js.map +1 -1
  35. package/dist/integrations/github/session-subscriptions.js +165 -223
  36. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  37. package/dist/integrations/github/subscriptions.js +43 -51
  38. package/dist/integrations/github/subscriptions.js.map +1 -1
  39. package/dist/integrations/github/token-refresh.js +15 -18
  40. package/dist/integrations/github/token-refresh.js.map +1 -1
  41. package/dist/integrations/github/webhook.js +359 -343
  42. package/dist/integrations/github/webhook.js.map +1 -1
  43. package/dist/integrations/linear/agent-tools.js +77 -100
  44. package/dist/integrations/linear/agent-tools.js.map +1 -1
  45. package/dist/integrations/linear/integration.js +611 -1309
  46. package/dist/integrations/linear/integration.js.map +1 -1
  47. package/dist/integrations/linear/routes.js +227 -230
  48. package/dist/integrations/linear/routes.js.map +1 -1
  49. package/dist/integrations/linear/rules.js +126 -346
  50. package/dist/integrations/linear/rules.js.map +1 -1
  51. package/dist/integrations/linear/storage.js +1 -1
  52. package/dist/integrations/platform/api-client.js +131 -135
  53. package/dist/integrations/platform/api-client.js.map +1 -1
  54. package/dist/integrations/platform/github/event-worker.js +275 -573
  55. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  56. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  57. package/dist/integrations/platform/github/integration.js +845 -3942
  58. package/dist/integrations/platform/github/integration.js.map +1 -1
  59. package/dist/integrations/platform/linear/integration.js +457 -1309
  60. package/dist/integrations/platform/linear/integration.js.map +1 -1
  61. package/dist/integrations/workos/integration.js +80 -85
  62. package/dist/integrations/workos/integration.js.map +1 -1
  63. package/dist/routes/config.d.ts.map +1 -1
  64. package/dist/routes/config.js +933 -915
  65. package/dist/routes/config.js.map +1 -1
  66. package/dist/routes/custom-provider-source.js +110 -93
  67. package/dist/routes/custom-provider-source.js.map +1 -1
  68. package/dist/routes/fs.js +401 -349
  69. package/dist/routes/fs.js.map +1 -1
  70. package/dist/routes/intake.js +165 -165
  71. package/dist/routes/intake.js.map +1 -1
  72. package/dist/routes/oauth.js +447 -25166
  73. package/dist/routes/oauth.js.map +1 -1
  74. package/dist/routes/projects.js +437 -376
  75. package/dist/routes/projects.js.map +1 -1
  76. package/dist/routes/provider-credentials.js +76 -58
  77. package/dist/routes/provider-credentials.js.map +1 -1
  78. package/dist/routes/route.js +12 -8
  79. package/dist/routes/route.js.map +1 -1
  80. package/dist/routes/skills.js +160 -173
  81. package/dist/routes/skills.js.map +1 -1
  82. package/dist/routes/surface.js +250 -28618
  83. package/dist/routes/surface.js.map +1 -1
  84. package/dist/routes/tenant-credentials.js +141 -166
  85. package/dist/routes/tenant-credentials.js.map +1 -1
  86. package/dist/routes/work-items.js +654 -862
  87. package/dist/routes/work-items.js.map +1 -1
  88. package/dist/rules/binding-context.js +23 -35
  89. package/dist/rules/binding-context.js.map +1 -1
  90. package/dist/rules/defaults.js +200 -309
  91. package/dist/rules/defaults.js.map +1 -1
  92. package/dist/rules/dispatcher.d.ts.map +1 -1
  93. package/dist/rules/dispatcher.js +438 -677
  94. package/dist/rules/dispatcher.js.map +1 -1
  95. package/dist/rules/index.js +5 -599
  96. package/dist/rules/processor.js +344 -596
  97. package/dist/rules/processor.js.map +1 -1
  98. package/dist/rules/resolve.js +21 -18
  99. package/dist/rules/resolve.js.map +1 -1
  100. package/dist/rules/start-coordinator.js +168 -157
  101. package/dist/rules/start-coordinator.js.map +1 -1
  102. package/dist/rules/tools.js +50 -85
  103. package/dist/rules/tools.js.map +1 -1
  104. package/dist/rules/transition-service.js +183 -454
  105. package/dist/rules/transition-service.js.map +1 -1
  106. package/dist/rules/types.js +33 -29
  107. package/dist/rules/types.js.map +1 -1
  108. package/dist/rules/validation.js +266 -281
  109. package/dist/rules/validation.js.map +1 -1
  110. package/dist/sandbox/fleet.js +272 -250
  111. package/dist/sandbox/fleet.js.map +1 -1
  112. package/dist/sandbox/reattach.js +13 -6
  113. package/dist/sandbox/reattach.js.map +1 -1
  114. package/dist/server-error.js +12 -16
  115. package/dist/server-error.js.map +1 -1
  116. package/dist/skills/service.js +30 -35
  117. package/dist/skills/service.js.map +1 -1
  118. package/dist/spa-static.d.ts.map +1 -1
  119. package/dist/spa-static.js +91 -64
  120. package/dist/spa-static.js.map +1 -1
  121. package/dist/state-signing.js +73 -44
  122. package/dist/state-signing.js.map +1 -1
  123. package/dist/storage/domains/audit/agent-audit.js +60 -59
  124. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  125. package/dist/storage/domains/audit/base.js +169 -112
  126. package/dist/storage/domains/audit/base.js.map +1 -1
  127. package/dist/storage/domains/audit/domain.js +155 -153
  128. package/dist/storage/domains/audit/domain.js.map +1 -1
  129. package/dist/storage/domains/credentials/base.js +259 -243
  130. package/dist/storage/domains/credentials/base.js.map +1 -1
  131. package/dist/storage/domains/custom-providers/base.js +138 -143
  132. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  133. package/dist/storage/domains/intake/base.js +75 -57
  134. package/dist/storage/domains/intake/base.js.map +1 -1
  135. package/dist/storage/domains/integrations/base.js +310 -245
  136. package/dist/storage/domains/integrations/base.js.map +1 -1
  137. package/dist/storage/domains/memory-settings/base.js +117 -102
  138. package/dist/storage/domains/memory-settings/base.js.map +1 -1
  139. package/dist/storage/domains/model-packs/base.js +89 -88
  140. package/dist/storage/domains/model-packs/base.js.map +1 -1
  141. package/dist/storage/domains/projects/base.js +97 -92
  142. package/dist/storage/domains/projects/base.js.map +1 -1
  143. package/dist/storage/domains/queue-health/base.js +111 -76
  144. package/dist/storage/domains/queue-health/base.js.map +1 -1
  145. package/dist/storage/domains/source-control/base.d.ts +9 -0
  146. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  147. package/dist/storage/domains/source-control/base.js +777 -702
  148. package/dist/storage/domains/source-control/base.js.map +1 -1
  149. package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
  150. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  151. package/dist/storage/domains/source-control/inmemory.js +322 -344
  152. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  153. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  154. package/dist/storage/domains/work-items/base.js +1266 -1132
  155. package/dist/storage/domains/work-items/base.js.map +1 -1
  156. package/dist/storage/domains/work-items/metrics.js +202 -184
  157. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  158. package/dist/timing.d.ts +15 -0
  159. package/dist/timing.d.ts.map +1 -0
  160. package/dist/timing.js +34 -0
  161. package/dist/timing.js.map +1 -0
  162. package/dist/workspace.d.ts +2 -3
  163. package/dist/workspace.d.ts.map +1 -1
  164. package/dist/workspace.js +236 -438
  165. package/dist/workspace.js.map +1 -1
  166. package/factory-skills/factory-review/SKILL.md +9 -2
  167. package/package.json +9 -9
  168. package/dist/capabilities/connection.js.map +0 -1
  169. package/dist/capabilities/intake.js.map +0 -1
  170. package/dist/capabilities/version-control.js.map +0 -1
  171. package/dist/index.js.map +0 -1
  172. package/dist/integrations/base.js.map +0 -1
  173. package/dist/integrations/linear/storage.js.map +0 -1
  174. package/dist/rules/index.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/storage/domains/audit/base.ts"],"sourcesContent":["/**\n * Factory audit events domain — the append-only \"who did what, when\" trail\n * behind the software factory.\n *\n * One `audit_events` row records a single audited mutation (work-item change,\n * stage move, run start, worktree create/delete, git action, intake config\n * change). Rows are append-only: there is no update/delete API, and the table\n * is the local source of truth even when the WorkOS Audit Logs mirror is\n * unavailable.\n *\n * Tenancy is **org-first**, like `work_items`: events are scoped by `org_id`\n * and (usually) `factory_project_id`; `actor_id` records who acted but never\n * scopes reads.\n *\n * v1 action taxonomy (register these in the WorkOS dashboard under\n * Audit Logs → Events for the export mirror to accept them):\n * - factory.work_item.created\n * - factory.work_item.updated\n * - factory.work_item.stage_moved\n * - factory.work_item.deleted\n * - factory.run.started\n * - factory.worktree.created\n * - factory.worktree.deleted\n * - factory.triage.started\n * - factory.git.commit\n * - factory.git.push\n * - factory.git.pr_opened\n * - factory.intake.config_updated\n *\n * v1.1 adds agent-level actions (also register these in WorkOS):\n * - factory.agent.commit\n * - factory.agent.push\n * - factory.agent.pr_opened\n *\n * Agent events carry `actor_type = 'agent'` with `actor_id = 'agent:<threadId>'`\n * and `metadata.startedBy = <userId>` chaining accountability back to the human\n * whose message drove the run.\n */\n\nimport { FactoryStorageDomain } from '@mastra/core/storage';\nimport type { CollectionSchema, CollectionWhere, FactoryStorageOps } from '@mastra/core/storage';\n\n/** What an audit event acted on (WorkOS Audit Logs target shape). */\nexport interface AuditTarget {\n /** Target kind, e.g. 'work_item', 'worktree', 'issue', 'pull_request'. */\n type: string;\n /** Stable identifier of the target (row id, branch name, issue number...). */\n id: string;\n /** Human-readable label (work-item title, branch name...). */\n name?: string;\n}\n\n/** Who performed the audited action. */\nexport type AuditActorType = 'human' | 'agent';\n\n/** Request context captured alongside the event. */\nexport interface AuditContext {\n /** Client IP (first hop of `x-forwarded-for`) when available. */\n location?: string;\n /** Request `user-agent` header when available. */\n userAgent?: string;\n}\n\n/** One persisted audit event. */\nexport interface AuditEventRow {\n id: string;\n /** Owning WorkOS organization id — the trail is org-wide. */\n orgId: string;\n /** WorkOS user id of whoever performed the action, or `agent:<threadId>`. */\n actorId: string;\n /** Whether a human or an agent (inside a run) performed the action. */\n actorType: AuditActorType;\n /** Dot-namespaced action, e.g. 'factory.work_item.stage_moved'. */\n action: string;\n /** What was acted on. */\n targets: AuditTarget[];\n /** Bounded event summary — never full payloads, never secrets. */\n metadata: Record<string, unknown>;\n /** Factory project the event is scoped to; null for org-level events. */\n factoryProjectId: string | null;\n /** Project-repository link affected by a repository-specific action. */\n projectRepositoryId: string | null;\n /** Request context (`x-forwarded-for` / `user-agent`). */\n context: AuditContext;\n occurredAt: Date;\n}\n\nexport interface RecordAuditEventInput {\n orgId: string;\n actorId: string;\n /** Who performed the action; defaults to 'human'. */\n actorType?: AuditActorType;\n /** Dot-namespaced action, e.g. 'factory.work_item.stage_moved'. */\n action: string;\n targets: AuditTarget[];\n metadata?: Record<string, unknown>;\n factoryProjectId?: string;\n projectRepositoryId?: string;\n context?: AuditContext;\n occurredAt?: Date;\n}\n\n/** A fully-normalized event ready to persist (id assigned on insert). */\nexport type AuditEventInsert = Omit<AuditEventRow, 'id'>;\n\nexport interface ListAuditEventsInput {\n orgId: string;\n factoryProjectId?: string;\n /** Restrict to these actions (exact match). */\n actions?: string[];\n actorId?: string;\n /** Opaque cursor from a previous page (`nextCursor`). */\n before?: string;\n limit?: number;\n}\n\nexport interface AuditEventPage {\n events: AuditEventRow[];\n /** Pass back as `before` to fetch the next (older) page; absent at the end. */\n nextCursor?: string;\n}\n\n/** Metadata is a bounded summary — never full payloads, never secrets. */\nconst MAX_METADATA_JSON_LENGTH = 4096;\n\nconst DEFAULT_PAGE_SIZE = 50;\nconst MAX_PAGE_SIZE = 200;\n\n/** Truncate oversized metadata rather than dropping the whole event. */\nexport function boundAuditMetadata(metadata: Record<string, unknown> | undefined): Record<string, unknown> {\n if (!metadata) return {};\n try {\n if (JSON.stringify(metadata).length <= MAX_METADATA_JSON_LENGTH) return metadata;\n } catch {\n return { truncated: true };\n }\n return { truncated: true };\n}\n\n/** Normalize a requested page size to a finite integer in `[1, MAX_PAGE_SIZE]`. */\nexport function clampAuditLimit(limit: number | undefined): number {\n const normalized = typeof limit === 'number' && Number.isFinite(limit) ? Math.trunc(limit) : DEFAULT_PAGE_SIZE;\n return Math.min(Math.max(normalized, 1), MAX_PAGE_SIZE);\n}\n\n/** Encode the `(occurredAt, id)` keyset cursor of a row. */\nexport function encodeAuditCursor(row: AuditEventRow): string {\n return `${row.occurredAt.toISOString()}_${row.id}`;\n}\n\n/** Decode a cursor back into its `(occurredAt, id)` parts, or `undefined`. */\nexport function decodeAuditCursor(cursor: string): { occurredAt: Date; id: string } | undefined {\n const sep = cursor.lastIndexOf('_');\n if (sep <= 0) return undefined;\n const occurredAt = new Date(cursor.slice(0, sep));\n const id = cursor.slice(sep + 1);\n if (Number.isNaN(occurredAt.getTime()) || !id) return undefined;\n return { occurredAt, id };\n}\n\nexport const AUDIT_EVENTS_SCHEMA: CollectionSchema = {\n name: 'audit_events',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n actor_id: { type: 'text' },\n actor_type: { type: 'text', default: 'human' },\n action: { type: 'text' },\n targets: { type: 'json' },\n metadata: { type: 'json' },\n factory_project_id: { type: 'text', nullable: true },\n project_repository_id: { type: 'text', nullable: true },\n context: { type: 'json' },\n occurred_at: { type: 'timestamp' },\n },\n indexes: [\n { name: 'audit_events_org_occurred_idx', columns: ['org_id', 'occurred_at'] },\n { name: 'audit_events_org_project_occurred_idx', columns: ['org_id', 'factory_project_id', 'occurred_at'] },\n ],\n};\n\n/** Column shape of one `audit_events` row as returned by ops. */\ninterface AuditEventDbRow extends Record<string, unknown> {\n id: string;\n org_id: string;\n actor_id: string;\n actor_type: AuditActorType;\n action: string;\n targets: AuditTarget[];\n metadata: Record<string, unknown>;\n factory_project_id: string | null;\n project_repository_id: string | null;\n context: AuditContext;\n occurred_at: Date;\n}\n\nfunction toRow(db: AuditEventDbRow): AuditEventRow {\n return {\n id: db.id,\n orgId: db.org_id,\n actorId: db.actor_id,\n actorType: db.actor_type,\n action: db.action,\n targets: db.targets,\n metadata: db.metadata,\n factoryProjectId: db.factory_project_id,\n projectRepositoryId: db.project_repository_id,\n context: db.context,\n occurredAt: db.occurred_at,\n };\n}\n\n/**\n * Audit event storage, written once against the generic `FactoryStorageOps`\n * surface. `record()` normalizes inputs (defaults, metadata bounding);\n * `list()` is keyset-paginated on `(occurred_at, id)` newest-first.\n */\nexport class AuditStorage extends FactoryStorageDomain {\n constructor() {\n super('audit');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([AUDIT_EVENTS_SCHEMA]);\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('audit_events', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n /** Append one audit event. Throws on failure — swallow-on-failure lives in the caller. */\n async record(input: RecordAuditEventInput): Promise<AuditEventRow> {\n const inserted = await this.#db.insertOne<AuditEventDbRow>('audit_events', {\n org_id: input.orgId,\n actor_id: input.actorId,\n actor_type: input.actorType ?? 'human',\n action: input.action,\n targets: input.targets,\n metadata: boundAuditMetadata(input.metadata),\n factory_project_id: input.factoryProjectId ?? null,\n project_repository_id: input.projectRepositoryId ?? null,\n context: input.context ?? {},\n occurred_at: input.occurredAt ?? new Date(),\n });\n return toRow(inserted);\n }\n\n /** List an org's audit events newest-first with keyset pagination. */\n async list(input: ListAuditEventsInput): Promise<AuditEventPage> {\n const limit = clampAuditLimit(input.limit);\n\n const where: CollectionWhere = { org_id: input.orgId };\n if (input.factoryProjectId) where.factory_project_id = input.factoryProjectId;\n if (input.actions && input.actions.length > 0) where.action = { in: input.actions };\n if (input.actorId) where.actor_id = input.actorId;\n\n const cursor = input.before ? decodeAuditCursor(input.before) : undefined;\n\n const rows = await this.#db.findMany<AuditEventDbRow>('audit_events', where, {\n orderBy: [\n ['occurred_at', 'desc'],\n ['id', 'desc'],\n ],\n limit: limit + 1,\n ...(cursor ? { cursor: { values: [cursor.occurredAt, cursor.id] } } : {}),\n });\n\n const events = rows.slice(0, limit).map(toRow);\n const hasMore = rows.length > limit;\n const last = events[events.length - 1];\n return {\n events,\n ...(hasMore && last ? { nextCursor: encodeAuditCursor(last) } : {}),\n };\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"file":"base.js","names":["#db"],"sources":["../../../../src/storage/domains/audit/base.ts"],"sourcesContent":["/**\n * Factory audit events domain — the append-only \"who did what, when\" trail\n * behind the software factory.\n *\n * One `audit_events` row records a single audited mutation (work-item change,\n * stage move, run start, worktree create/delete, git action, intake config\n * change). Rows are append-only: there is no update/delete API, and the table\n * is the local source of truth even when the WorkOS Audit Logs mirror is\n * unavailable.\n *\n * Tenancy is **org-first**, like `work_items`: events are scoped by `org_id`\n * and (usually) `factory_project_id`; `actor_id` records who acted but never\n * scopes reads.\n *\n * v1 action taxonomy (register these in the WorkOS dashboard under\n * Audit Logs → Events for the export mirror to accept them):\n * - factory.work_item.created\n * - factory.work_item.updated\n * - factory.work_item.stage_moved\n * - factory.work_item.deleted\n * - factory.run.started\n * - factory.worktree.created\n * - factory.worktree.deleted\n * - factory.triage.started\n * - factory.git.commit\n * - factory.git.push\n * - factory.git.pr_opened\n * - factory.intake.config_updated\n *\n * v1.1 adds agent-level actions (also register these in WorkOS):\n * - factory.agent.commit\n * - factory.agent.push\n * - factory.agent.pr_opened\n *\n * Agent events carry `actor_type = 'agent'` with `actor_id = 'agent:<threadId>'`\n * and `metadata.startedBy = <userId>` chaining accountability back to the human\n * whose message drove the run.\n */\n\nimport { FactoryStorageDomain } from '@mastra/core/storage';\nimport type { CollectionSchema, CollectionWhere, FactoryStorageOps } from '@mastra/core/storage';\n\n/** What an audit event acted on (WorkOS Audit Logs target shape). */\nexport interface AuditTarget {\n /** Target kind, e.g. 'work_item', 'worktree', 'issue', 'pull_request'. */\n type: string;\n /** Stable identifier of the target (row id, branch name, issue number...). */\n id: string;\n /** Human-readable label (work-item title, branch name...). */\n name?: string;\n}\n\n/** Who performed the audited action. */\nexport type AuditActorType = 'human' | 'agent';\n\n/** Request context captured alongside the event. */\nexport interface AuditContext {\n /** Client IP (first hop of `x-forwarded-for`) when available. */\n location?: string;\n /** Request `user-agent` header when available. */\n userAgent?: string;\n}\n\n/** One persisted audit event. */\nexport interface AuditEventRow {\n id: string;\n /** Owning WorkOS organization id — the trail is org-wide. */\n orgId: string;\n /** WorkOS user id of whoever performed the action, or `agent:<threadId>`. */\n actorId: string;\n /** Whether a human or an agent (inside a run) performed the action. */\n actorType: AuditActorType;\n /** Dot-namespaced action, e.g. 'factory.work_item.stage_moved'. */\n action: string;\n /** What was acted on. */\n targets: AuditTarget[];\n /** Bounded event summary — never full payloads, never secrets. */\n metadata: Record<string, unknown>;\n /** Factory project the event is scoped to; null for org-level events. */\n factoryProjectId: string | null;\n /** Project-repository link affected by a repository-specific action. */\n projectRepositoryId: string | null;\n /** Request context (`x-forwarded-for` / `user-agent`). */\n context: AuditContext;\n occurredAt: Date;\n}\n\nexport interface RecordAuditEventInput {\n orgId: string;\n actorId: string;\n /** Who performed the action; defaults to 'human'. */\n actorType?: AuditActorType;\n /** Dot-namespaced action, e.g. 'factory.work_item.stage_moved'. */\n action: string;\n targets: AuditTarget[];\n metadata?: Record<string, unknown>;\n factoryProjectId?: string;\n projectRepositoryId?: string;\n context?: AuditContext;\n occurredAt?: Date;\n}\n\n/** A fully-normalized event ready to persist (id assigned on insert). */\nexport type AuditEventInsert = Omit<AuditEventRow, 'id'>;\n\nexport interface ListAuditEventsInput {\n orgId: string;\n factoryProjectId?: string;\n /** Restrict to these actions (exact match). */\n actions?: string[];\n actorId?: string;\n /** Opaque cursor from a previous page (`nextCursor`). */\n before?: string;\n limit?: number;\n}\n\nexport interface AuditEventPage {\n events: AuditEventRow[];\n /** Pass back as `before` to fetch the next (older) page; absent at the end. */\n nextCursor?: string;\n}\n\n/** Metadata is a bounded summary — never full payloads, never secrets. */\nconst MAX_METADATA_JSON_LENGTH = 4096;\n\nconst DEFAULT_PAGE_SIZE = 50;\nconst MAX_PAGE_SIZE = 200;\n\n/** Truncate oversized metadata rather than dropping the whole event. */\nexport function boundAuditMetadata(metadata: Record<string, unknown> | undefined): Record<string, unknown> {\n if (!metadata) return {};\n try {\n if (JSON.stringify(metadata).length <= MAX_METADATA_JSON_LENGTH) return metadata;\n } catch {\n return { truncated: true };\n }\n return { truncated: true };\n}\n\n/** Normalize a requested page size to a finite integer in `[1, MAX_PAGE_SIZE]`. */\nexport function clampAuditLimit(limit: number | undefined): number {\n const normalized = typeof limit === 'number' && Number.isFinite(limit) ? Math.trunc(limit) : DEFAULT_PAGE_SIZE;\n return Math.min(Math.max(normalized, 1), MAX_PAGE_SIZE);\n}\n\n/** Encode the `(occurredAt, id)` keyset cursor of a row. */\nexport function encodeAuditCursor(row: AuditEventRow): string {\n return `${row.occurredAt.toISOString()}_${row.id}`;\n}\n\n/** Decode a cursor back into its `(occurredAt, id)` parts, or `undefined`. */\nexport function decodeAuditCursor(cursor: string): { occurredAt: Date; id: string } | undefined {\n const sep = cursor.lastIndexOf('_');\n if (sep <= 0) return undefined;\n const occurredAt = new Date(cursor.slice(0, sep));\n const id = cursor.slice(sep + 1);\n if (Number.isNaN(occurredAt.getTime()) || !id) return undefined;\n return { occurredAt, id };\n}\n\nexport const AUDIT_EVENTS_SCHEMA: CollectionSchema = {\n name: 'audit_events',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n actor_id: { type: 'text' },\n actor_type: { type: 'text', default: 'human' },\n action: { type: 'text' },\n targets: { type: 'json' },\n metadata: { type: 'json' },\n factory_project_id: { type: 'text', nullable: true },\n project_repository_id: { type: 'text', nullable: true },\n context: { type: 'json' },\n occurred_at: { type: 'timestamp' },\n },\n indexes: [\n { name: 'audit_events_org_occurred_idx', columns: ['org_id', 'occurred_at'] },\n { name: 'audit_events_org_project_occurred_idx', columns: ['org_id', 'factory_project_id', 'occurred_at'] },\n ],\n};\n\n/** Column shape of one `audit_events` row as returned by ops. */\ninterface AuditEventDbRow extends Record<string, unknown> {\n id: string;\n org_id: string;\n actor_id: string;\n actor_type: AuditActorType;\n action: string;\n targets: AuditTarget[];\n metadata: Record<string, unknown>;\n factory_project_id: string | null;\n project_repository_id: string | null;\n context: AuditContext;\n occurred_at: Date;\n}\n\nfunction toRow(db: AuditEventDbRow): AuditEventRow {\n return {\n id: db.id,\n orgId: db.org_id,\n actorId: db.actor_id,\n actorType: db.actor_type,\n action: db.action,\n targets: db.targets,\n metadata: db.metadata,\n factoryProjectId: db.factory_project_id,\n projectRepositoryId: db.project_repository_id,\n context: db.context,\n occurredAt: db.occurred_at,\n };\n}\n\n/**\n * Audit event storage, written once against the generic `FactoryStorageOps`\n * surface. `record()` normalizes inputs (defaults, metadata bounding);\n * `list()` is keyset-paginated on `(occurred_at, id)` newest-first.\n */\nexport class AuditStorage extends FactoryStorageDomain {\n constructor() {\n super('audit');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([AUDIT_EVENTS_SCHEMA]);\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('audit_events', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n /** Append one audit event. Throws on failure — swallow-on-failure lives in the caller. */\n async record(input: RecordAuditEventInput): Promise<AuditEventRow> {\n const inserted = await this.#db.insertOne<AuditEventDbRow>('audit_events', {\n org_id: input.orgId,\n actor_id: input.actorId,\n actor_type: input.actorType ?? 'human',\n action: input.action,\n targets: input.targets,\n metadata: boundAuditMetadata(input.metadata),\n factory_project_id: input.factoryProjectId ?? null,\n project_repository_id: input.projectRepositoryId ?? null,\n context: input.context ?? {},\n occurred_at: input.occurredAt ?? new Date(),\n });\n return toRow(inserted);\n }\n\n /** List an org's audit events newest-first with keyset pagination. */\n async list(input: ListAuditEventsInput): Promise<AuditEventPage> {\n const limit = clampAuditLimit(input.limit);\n\n const where: CollectionWhere = { org_id: input.orgId };\n if (input.factoryProjectId) where.factory_project_id = input.factoryProjectId;\n if (input.actions && input.actions.length > 0) where.action = { in: input.actions };\n if (input.actorId) where.actor_id = input.actorId;\n\n const cursor = input.before ? decodeAuditCursor(input.before) : undefined;\n\n const rows = await this.#db.findMany<AuditEventDbRow>('audit_events', where, {\n orderBy: [\n ['occurred_at', 'desc'],\n ['id', 'desc'],\n ],\n limit: limit + 1,\n ...(cursor ? { cursor: { values: [cursor.occurredAt, cursor.id] } } : {}),\n });\n\n const events = rows.slice(0, limit).map(toRow);\n const hasMore = rows.length > limit;\n const last = events[events.length - 1];\n return {\n events,\n ...(hasMore && last ? { nextCursor: encodeAuditCursor(last) } : {}),\n };\n }\n}\n"],"mappings":"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"}
@@ -1,165 +1,167 @@
1
- // src/storage/domains/audit/domain.ts
2
1
  import { registerApiRoute } from "@mastra/core/server";
3
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4
- var MAX_ACTION_FILTERS = 16;
2
+ //#region src/storage/domains/audit/domain.ts
3
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4
+ const MAX_ACTION_FILTERS = 16;
5
5
  function loose(c) {
6
- return c;
6
+ return c;
7
7
  }
8
8
  function parseActionsParam(raw) {
9
- if (!raw) return void 0;
10
- const actions = raw.split(",").map((action) => action.trim()).filter(Boolean).slice(0, MAX_ACTION_FILTERS);
11
- return actions.length > 0 ? actions : void 0;
9
+ if (!raw) return void 0;
10
+ const actions = raw.split(",").map((action) => action.trim()).filter(Boolean).slice(0, MAX_ACTION_FILTERS);
11
+ return actions.length > 0 ? actions : void 0;
12
12
  }
13
13
  function parseLimitParam(raw) {
14
- if (!raw) return void 0;
15
- const limit = Number.parseInt(raw, 10);
16
- return Number.isFinite(limit) ? limit : void 0;
14
+ if (!raw) return void 0;
15
+ const limit = Number.parseInt(raw, 10);
16
+ return Number.isFinite(limit) ? limit : void 0;
17
17
  }
18
18
  function auditRequestContext(c) {
19
- const location = c.req.header("x-forwarded-for")?.split(",")[0]?.trim();
20
- const userAgent = c.req.header("user-agent");
21
- return {
22
- ...location ? { location } : {},
23
- ...userAgent ? { userAgent } : {}
24
- };
19
+ const location = c.req.header("x-forwarded-for")?.split(",")[0]?.trim();
20
+ const userAgent = c.req.header("user-agent");
21
+ return {
22
+ ...location ? { location } : {},
23
+ ...userAgent ? { userAgent } : {}
24
+ };
25
25
  }
26
+ /** Factory-owned audit behavior backed by the audit storage domain. */
26
27
  var AuditDomain = class {
27
- #auth;
28
- #audit;
29
- #projects;
30
- #sinks;
31
- #agentTenant;
32
- constructor({ auth, audit, projects, sinks = [], agentTenant }) {
33
- this.#auth = auth;
34
- this.#audit = audit;
35
- this.#projects = projects;
36
- this.#sinks = sinks;
37
- this.#agentTenant = agentTenant;
38
- const ids = /* @__PURE__ */ new Set();
39
- for (const sink of this.#sinks) {
40
- if (!sink.id) throw new Error("Audit integration id must not be empty");
41
- if (ids.has(sink.id)) throw new Error(`Duplicate audit integration id '${sink.id}'`);
42
- ids.add(sink.id);
43
- }
44
- }
45
- async record(input) {
46
- try {
47
- await this.#audit.ensureReady();
48
- const row = await this.#audit.record(input);
49
- for (const sink of this.#sinks) {
50
- if (!sink.audit) continue;
51
- void Promise.resolve().then(() => sink.audit?.({ event: row })).catch((err) => {
52
- console.warn("[Audit] Audit integration failed", {
53
- integration: sink.id,
54
- action: row.action,
55
- error: err instanceof Error ? err.message : String(err)
56
- });
57
- });
58
- }
59
- return row;
60
- } catch (err) {
61
- console.warn("[Audit] Failed to record audit event", {
62
- action: input.action,
63
- error: err instanceof Error ? err.message : String(err)
64
- });
65
- return null;
66
- }
67
- }
68
- async list(input) {
69
- await this.#audit.ensureReady();
70
- return this.#audit.list(input);
71
- }
72
- async emit({ context, input }) {
73
- try {
74
- const tenant = this.#auth.tenant(context);
75
- if (!tenant?.orgId) return;
76
- await this.record({
77
- orgId: tenant.orgId,
78
- actorId: tenant.userId,
79
- action: input.action,
80
- targets: input.targets,
81
- metadata: input.metadata,
82
- factoryProjectId: input.factoryProjectId,
83
- projectRepositoryId: input.projectRepositoryId,
84
- context: auditRequestContext(context)
85
- });
86
- } catch (err) {
87
- console.warn("[Audit] Failed to emit audit event", {
88
- action: input.action,
89
- error: err instanceof Error ? err.message : String(err)
90
- });
91
- }
92
- }
93
- async emitAgent({
94
- requestContext,
95
- input
96
- }) {
97
- try {
98
- const context = requestContext.get("controller");
99
- const tenant = this.#agentTenant?.(requestContext);
100
- const orgId = tenant?.orgId;
101
- const userId = tenant?.userId;
102
- const threadId = context?.threadId;
103
- const state = context?.getState();
104
- if (!orgId || !userId || !threadId || !state?.factoryProjectId) return;
105
- await this.record({
106
- orgId,
107
- actorId: `agent:${threadId}`,
108
- actorType: "agent",
109
- action: input.action,
110
- targets: input.targets,
111
- metadata: { ...input.metadata, startedBy: userId },
112
- factoryProjectId: state.factoryProjectId,
113
- projectRepositoryId: state.projectRepositoryId,
114
- context: {}
115
- });
116
- } catch (err) {
117
- console.warn("[Audit] Failed to emit agent audit event", {
118
- action: input.action,
119
- error: err instanceof Error ? err.message : String(err)
120
- });
121
- }
122
- }
123
- routes() {
124
- return [
125
- registerApiRoute("/web/factory/projects/:id/audit", {
126
- method: "GET",
127
- handler: async (cc) => {
128
- const c = loose(cc);
129
- const tenant = await this.#resolveTenant(c);
130
- if ("response" in tenant) return tenant.response;
131
- const projectId = c.req.param("id");
132
- if (!projectId || !UUID_RE.test(projectId)) return c.json({ error: "Project not found" }, 404);
133
- await this.#projects.ensureReady();
134
- const project = await this.#projects.get({ orgId: tenant.orgId, id: projectId });
135
- if (!project) return c.json({ error: "Project not found" }, 404);
136
- const page = await this.list({
137
- orgId: tenant.orgId,
138
- factoryProjectId: projectId,
139
- actions: parseActionsParam(c.req.query("actions")),
140
- actorId: c.req.query("actor") || void 0,
141
- before: c.req.query("before") || void 0,
142
- limit: parseLimitParam(c.req.query("limit"))
143
- });
144
- return c.json(page);
145
- }
146
- })
147
- ];
148
- }
149
- async #resolveTenant(c) {
150
- await this.#auth.ensureUser(c);
151
- const tenant = this.#auth.tenant(c);
152
- if (!tenant) return { response: c.json({ error: "unauthorized" }, 401) };
153
- if (!tenant.orgId) {
154
- return {
155
- response: c.json({ error: "organization_required", message: "The audit trail requires an organization." }, 403)
156
- };
157
- }
158
- return { orgId: tenant.orgId, userId: tenant.userId };
159
- }
160
- };
161
- export {
162
- AuditDomain,
163
- auditRequestContext
28
+ #auth;
29
+ #audit;
30
+ #projects;
31
+ #sinks;
32
+ #agentTenant;
33
+ constructor({ auth, audit, projects, sinks = [], agentTenant }) {
34
+ this.#auth = auth;
35
+ this.#audit = audit;
36
+ this.#projects = projects;
37
+ this.#sinks = sinks;
38
+ this.#agentTenant = agentTenant;
39
+ const ids = /* @__PURE__ */ new Set();
40
+ for (const sink of this.#sinks) {
41
+ if (!sink.id) throw new Error("Audit integration id must not be empty");
42
+ if (ids.has(sink.id)) throw new Error(`Duplicate audit integration id '${sink.id}'`);
43
+ ids.add(sink.id);
44
+ }
45
+ }
46
+ async record(input) {
47
+ try {
48
+ await this.#audit.ensureReady();
49
+ const row = await this.#audit.record(input);
50
+ for (const sink of this.#sinks) {
51
+ if (!sink.audit) continue;
52
+ Promise.resolve().then(() => sink.audit?.({ event: row })).catch((err) => {
53
+ console.warn("[Audit] Audit integration failed", {
54
+ integration: sink.id,
55
+ action: row.action,
56
+ error: err instanceof Error ? err.message : String(err)
57
+ });
58
+ });
59
+ }
60
+ return row;
61
+ } catch (err) {
62
+ console.warn("[Audit] Failed to record audit event", {
63
+ action: input.action,
64
+ error: err instanceof Error ? err.message : String(err)
65
+ });
66
+ return null;
67
+ }
68
+ }
69
+ async list(input) {
70
+ await this.#audit.ensureReady();
71
+ return this.#audit.list(input);
72
+ }
73
+ async emit({ context, input }) {
74
+ try {
75
+ const tenant = this.#auth.tenant(context);
76
+ if (!tenant?.orgId) return;
77
+ await this.record({
78
+ orgId: tenant.orgId,
79
+ actorId: tenant.userId,
80
+ action: input.action,
81
+ targets: input.targets,
82
+ metadata: input.metadata,
83
+ factoryProjectId: input.factoryProjectId,
84
+ projectRepositoryId: input.projectRepositoryId,
85
+ context: auditRequestContext(context)
86
+ });
87
+ } catch (err) {
88
+ console.warn("[Audit] Failed to emit audit event", {
89
+ action: input.action,
90
+ error: err instanceof Error ? err.message : String(err)
91
+ });
92
+ }
93
+ }
94
+ async emitAgent({ requestContext, input }) {
95
+ try {
96
+ const context = requestContext.get("controller");
97
+ const tenant = this.#agentTenant?.(requestContext);
98
+ const orgId = tenant?.orgId;
99
+ const userId = tenant?.userId;
100
+ const threadId = context?.threadId;
101
+ const state = context?.getState();
102
+ if (!orgId || !userId || !threadId || !state?.factoryProjectId) return;
103
+ await this.record({
104
+ orgId,
105
+ actorId: `agent:${threadId}`,
106
+ actorType: "agent",
107
+ action: input.action,
108
+ targets: input.targets,
109
+ metadata: {
110
+ ...input.metadata,
111
+ startedBy: userId
112
+ },
113
+ factoryProjectId: state.factoryProjectId,
114
+ projectRepositoryId: state.projectRepositoryId,
115
+ context: {}
116
+ });
117
+ } catch (err) {
118
+ console.warn("[Audit] Failed to emit agent audit event", {
119
+ action: input.action,
120
+ error: err instanceof Error ? err.message : String(err)
121
+ });
122
+ }
123
+ }
124
+ routes() {
125
+ return [registerApiRoute("/web/factory/projects/:id/audit", {
126
+ method: "GET",
127
+ handler: async (cc) => {
128
+ const c = loose(cc);
129
+ const tenant = await this.#resolveTenant(c);
130
+ if ("response" in tenant) return tenant.response;
131
+ const projectId = c.req.param("id");
132
+ if (!projectId || !UUID_RE.test(projectId)) return c.json({ error: "Project not found" }, 404);
133
+ await this.#projects.ensureReady();
134
+ if (!await this.#projects.get({
135
+ orgId: tenant.orgId,
136
+ id: projectId
137
+ })) return c.json({ error: "Project not found" }, 404);
138
+ const page = await this.list({
139
+ orgId: tenant.orgId,
140
+ factoryProjectId: projectId,
141
+ actions: parseActionsParam(c.req.query("actions")),
142
+ actorId: c.req.query("actor") || void 0,
143
+ before: c.req.query("before") || void 0,
144
+ limit: parseLimitParam(c.req.query("limit"))
145
+ });
146
+ return c.json(page);
147
+ }
148
+ })];
149
+ }
150
+ async #resolveTenant(c) {
151
+ await this.#auth.ensureUser(c);
152
+ const tenant = this.#auth.tenant(c);
153
+ if (!tenant) return { response: c.json({ error: "unauthorized" }, 401) };
154
+ if (!tenant.orgId) return { response: c.json({
155
+ error: "organization_required",
156
+ message: "The audit trail requires an organization."
157
+ }, 403) };
158
+ return {
159
+ orgId: tenant.orgId,
160
+ userId: tenant.userId
161
+ };
162
+ }
164
163
  };
164
+ //#endregion
165
+ export { AuditDomain, auditRequestContext };
166
+
165
167
  //# sourceMappingURL=domain.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/storage/domains/audit/domain.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type { RouteAuth } from '../../../routes/route.js';\nimport type { FactoryProjectsStorage } from '../projects/base.js';\nimport type {\n AuditContext,\n AuditEventPage,\n AuditEventRow,\n AuditStorage,\n AuditTarget,\n ListAuditEventsInput,\n RecordAuditEventInput,\n} from './base.js';\n\nexport interface EmitAuditInput {\n action: string;\n factoryProjectId?: string;\n projectRepositoryId?: string;\n targets: AuditTarget[];\n metadata?: Record<string, unknown>;\n}\n\nexport interface EmitAgentAuditInput {\n action: string;\n targets: AuditTarget[];\n metadata?: Record<string, unknown>;\n}\n\nexport interface AuditEmitter {\n emit(args: { context: Context; input: EmitAuditInput }): Promise<void>;\n}\n\nexport interface AuditAgentEmitter {\n emitAgent(args: { requestContext: RequestContext; input: EmitAgentAuditInput }): Promise<void>;\n}\n\n/** Best-effort destination for locally persisted audit events (e.g. an integration's audit log). */\nexport interface AuditSink {\n id: string;\n audit?(args: { event: AuditEventRow }): Promise<void>;\n}\n\ninterface FactorySessionState {\n factoryProjectId?: string;\n projectRepositoryId?: string;\n}\n\nexport interface AuditDomainOptions {\n auth: RouteAuth;\n /** Audit storage domain handle. */\n audit: AuditStorage;\n /** Projects domain handle, used to scope the audit trail route. */\n projects: FactoryProjectsStorage;\n /** Best-effort fan-out destinations notified after each recorded event. */\n sinks?: AuditSink[];\n /** Resolve the acting tenant for agent-emitted events from the request context. */\n agentTenant?: (requestContext: RequestContext) => { orgId?: string; userId?: string } | undefined;\n}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst MAX_ACTION_FILTERS = 16;\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nfunction parseActionsParam(raw: string | undefined): string[] | undefined {\n if (!raw) return undefined;\n const actions = raw\n .split(',')\n .map(action => action.trim())\n .filter(Boolean)\n .slice(0, MAX_ACTION_FILTERS);\n return actions.length > 0 ? actions : undefined;\n}\n\nfunction parseLimitParam(raw: string | undefined): number | undefined {\n if (!raw) return undefined;\n const limit = Number.parseInt(raw, 10);\n return Number.isFinite(limit) ? limit : undefined;\n}\n\nexport function auditRequestContext(c: Context): AuditContext {\n const location = c.req.header('x-forwarded-for')?.split(',')[0]?.trim();\n const userAgent = c.req.header('user-agent');\n return {\n ...(location ? { location } : {}),\n ...(userAgent ? { userAgent } : {}),\n };\n}\n\n/** Factory-owned audit behavior backed by the audit storage domain. */\nexport class AuditDomain implements AuditEmitter, AuditAgentEmitter {\n readonly #auth: RouteAuth;\n readonly #audit: AuditStorage;\n readonly #projects: FactoryProjectsStorage;\n readonly #sinks: AuditSink[];\n readonly #agentTenant: AuditDomainOptions['agentTenant'];\n\n constructor({ auth, audit, projects, sinks = [], agentTenant }: AuditDomainOptions) {\n this.#auth = auth;\n this.#audit = audit;\n this.#projects = projects;\n this.#sinks = sinks;\n this.#agentTenant = agentTenant;\n\n const ids = new Set<string>();\n for (const sink of this.#sinks) {\n if (!sink.id) throw new Error('Audit integration id must not be empty');\n if (ids.has(sink.id)) throw new Error(`Duplicate audit integration id '${sink.id}'`);\n ids.add(sink.id);\n }\n }\n\n async record(input: RecordAuditEventInput): Promise<AuditEventRow | null> {\n try {\n await this.#audit.ensureReady();\n const row = await this.#audit.record(input);\n for (const sink of this.#sinks) {\n if (!sink.audit) continue;\n void Promise.resolve()\n .then(() => sink.audit?.({ event: row }))\n .catch(err => {\n console.warn('[Audit] Audit integration failed', {\n integration: sink.id,\n action: row.action,\n error: err instanceof Error ? err.message : String(err),\n });\n });\n }\n return row;\n } catch (err) {\n console.warn('[Audit] Failed to record audit event', {\n action: input.action,\n error: err instanceof Error ? err.message : String(err),\n });\n return null;\n }\n }\n\n async list(input: ListAuditEventsInput): Promise<AuditEventPage> {\n await this.#audit.ensureReady();\n return this.#audit.list(input);\n }\n\n async emit({ context, input }: { context: Context; input: EmitAuditInput }): Promise<void> {\n try {\n const tenant = this.#auth.tenant(context);\n if (!tenant?.orgId) return;\n await this.record({\n orgId: tenant.orgId,\n actorId: tenant.userId,\n action: input.action,\n targets: input.targets,\n metadata: input.metadata,\n factoryProjectId: input.factoryProjectId,\n projectRepositoryId: input.projectRepositoryId,\n context: auditRequestContext(context),\n });\n } catch (err) {\n console.warn('[Audit] Failed to emit audit event', {\n action: input.action,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n async emitAgent({\n requestContext,\n input,\n }: {\n requestContext: RequestContext;\n input: EmitAgentAuditInput;\n }): Promise<void> {\n try {\n const context = requestContext.get('controller') as\n | AgentControllerRequestContext<FactorySessionState>\n | undefined;\n const tenant = this.#agentTenant?.(requestContext);\n const orgId = tenant?.orgId;\n const userId = tenant?.userId;\n const threadId = context?.threadId;\n const state = context?.getState();\n if (!orgId || !userId || !threadId || !state?.factoryProjectId) return;\n\n await this.record({\n orgId,\n actorId: `agent:${threadId}`,\n actorType: 'agent',\n action: input.action,\n targets: input.targets,\n metadata: { ...input.metadata, startedBy: userId },\n factoryProjectId: state.factoryProjectId,\n projectRepositoryId: state.projectRepositoryId,\n context: {},\n });\n } catch (err) {\n console.warn('[Audit] Failed to emit agent audit event', {\n action: input.action,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n routes(): ApiRoute[] {\n return [\n registerApiRoute('/web/factory/projects/:id/audit', {\n method: 'GET',\n handler: async cc => {\n const c = loose(cc);\n const tenant = await this.#resolveTenant(c);\n if ('response' in tenant) return tenant.response;\n\n const projectId = c.req.param('id');\n if (!projectId || !UUID_RE.test(projectId)) return c.json({ error: 'Project not found' }, 404);\n await this.#projects.ensureReady();\n const project = await this.#projects.get({ orgId: tenant.orgId, id: projectId });\n if (!project) return c.json({ error: 'Project not found' }, 404);\n\n const page = await this.list({\n orgId: tenant.orgId,\n factoryProjectId: projectId,\n actions: parseActionsParam(c.req.query('actions')),\n actorId: c.req.query('actor') || undefined,\n before: c.req.query('before') || undefined,\n limit: parseLimitParam(c.req.query('limit')),\n });\n return c.json(page);\n },\n }),\n ];\n }\n\n async #resolveTenant(c: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n await this.#auth.ensureUser(c);\n const tenant = this.#auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json({ error: 'organization_required', message: 'The audit trail requires an organization.' }, 403),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"file":"domain.js","names":["#auth","#audit","#projects","#sinks","#agentTenant","#resolveTenant"],"sources":["../../../../src/storage/domains/audit/domain.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type { RouteAuth } from '../../../routes/route.js';\nimport type { FactoryProjectsStorage } from '../projects/base.js';\nimport type {\n AuditContext,\n AuditEventPage,\n AuditEventRow,\n AuditStorage,\n AuditTarget,\n ListAuditEventsInput,\n RecordAuditEventInput,\n} from './base.js';\n\nexport interface EmitAuditInput {\n action: string;\n factoryProjectId?: string;\n projectRepositoryId?: string;\n targets: AuditTarget[];\n metadata?: Record<string, unknown>;\n}\n\nexport interface EmitAgentAuditInput {\n action: string;\n targets: AuditTarget[];\n metadata?: Record<string, unknown>;\n}\n\nexport interface AuditEmitter {\n emit(args: { context: Context; input: EmitAuditInput }): Promise<void>;\n}\n\nexport interface AuditAgentEmitter {\n emitAgent(args: { requestContext: RequestContext; input: EmitAgentAuditInput }): Promise<void>;\n}\n\n/** Best-effort destination for locally persisted audit events (e.g. an integration's audit log). */\nexport interface AuditSink {\n id: string;\n audit?(args: { event: AuditEventRow }): Promise<void>;\n}\n\ninterface FactorySessionState {\n factoryProjectId?: string;\n projectRepositoryId?: string;\n}\n\nexport interface AuditDomainOptions {\n auth: RouteAuth;\n /** Audit storage domain handle. */\n audit: AuditStorage;\n /** Projects domain handle, used to scope the audit trail route. */\n projects: FactoryProjectsStorage;\n /** Best-effort fan-out destinations notified after each recorded event. */\n sinks?: AuditSink[];\n /** Resolve the acting tenant for agent-emitted events from the request context. */\n agentTenant?: (requestContext: RequestContext) => { orgId?: string; userId?: string } | undefined;\n}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst MAX_ACTION_FILTERS = 16;\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nfunction parseActionsParam(raw: string | undefined): string[] | undefined {\n if (!raw) return undefined;\n const actions = raw\n .split(',')\n .map(action => action.trim())\n .filter(Boolean)\n .slice(0, MAX_ACTION_FILTERS);\n return actions.length > 0 ? actions : undefined;\n}\n\nfunction parseLimitParam(raw: string | undefined): number | undefined {\n if (!raw) return undefined;\n const limit = Number.parseInt(raw, 10);\n return Number.isFinite(limit) ? limit : undefined;\n}\n\nexport function auditRequestContext(c: Context): AuditContext {\n const location = c.req.header('x-forwarded-for')?.split(',')[0]?.trim();\n const userAgent = c.req.header('user-agent');\n return {\n ...(location ? { location } : {}),\n ...(userAgent ? { userAgent } : {}),\n };\n}\n\n/** Factory-owned audit behavior backed by the audit storage domain. */\nexport class AuditDomain implements AuditEmitter, AuditAgentEmitter {\n readonly #auth: RouteAuth;\n readonly #audit: AuditStorage;\n readonly #projects: FactoryProjectsStorage;\n readonly #sinks: AuditSink[];\n readonly #agentTenant: AuditDomainOptions['agentTenant'];\n\n constructor({ auth, audit, projects, sinks = [], agentTenant }: AuditDomainOptions) {\n this.#auth = auth;\n this.#audit = audit;\n this.#projects = projects;\n this.#sinks = sinks;\n this.#agentTenant = agentTenant;\n\n const ids = new Set<string>();\n for (const sink of this.#sinks) {\n if (!sink.id) throw new Error('Audit integration id must not be empty');\n if (ids.has(sink.id)) throw new Error(`Duplicate audit integration id '${sink.id}'`);\n ids.add(sink.id);\n }\n }\n\n async record(input: RecordAuditEventInput): Promise<AuditEventRow | null> {\n try {\n await this.#audit.ensureReady();\n const row = await this.#audit.record(input);\n for (const sink of this.#sinks) {\n if (!sink.audit) continue;\n void Promise.resolve()\n .then(() => sink.audit?.({ event: row }))\n .catch(err => {\n console.warn('[Audit] Audit integration failed', {\n integration: sink.id,\n action: row.action,\n error: err instanceof Error ? err.message : String(err),\n });\n });\n }\n return row;\n } catch (err) {\n console.warn('[Audit] Failed to record audit event', {\n action: input.action,\n error: err instanceof Error ? err.message : String(err),\n });\n return null;\n }\n }\n\n async list(input: ListAuditEventsInput): Promise<AuditEventPage> {\n await this.#audit.ensureReady();\n return this.#audit.list(input);\n }\n\n async emit({ context, input }: { context: Context; input: EmitAuditInput }): Promise<void> {\n try {\n const tenant = this.#auth.tenant(context);\n if (!tenant?.orgId) return;\n await this.record({\n orgId: tenant.orgId,\n actorId: tenant.userId,\n action: input.action,\n targets: input.targets,\n metadata: input.metadata,\n factoryProjectId: input.factoryProjectId,\n projectRepositoryId: input.projectRepositoryId,\n context: auditRequestContext(context),\n });\n } catch (err) {\n console.warn('[Audit] Failed to emit audit event', {\n action: input.action,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n async emitAgent({\n requestContext,\n input,\n }: {\n requestContext: RequestContext;\n input: EmitAgentAuditInput;\n }): Promise<void> {\n try {\n const context = requestContext.get('controller') as\n | AgentControllerRequestContext<FactorySessionState>\n | undefined;\n const tenant = this.#agentTenant?.(requestContext);\n const orgId = tenant?.orgId;\n const userId = tenant?.userId;\n const threadId = context?.threadId;\n const state = context?.getState();\n if (!orgId || !userId || !threadId || !state?.factoryProjectId) return;\n\n await this.record({\n orgId,\n actorId: `agent:${threadId}`,\n actorType: 'agent',\n action: input.action,\n targets: input.targets,\n metadata: { ...input.metadata, startedBy: userId },\n factoryProjectId: state.factoryProjectId,\n projectRepositoryId: state.projectRepositoryId,\n context: {},\n });\n } catch (err) {\n console.warn('[Audit] Failed to emit agent audit event', {\n action: input.action,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n routes(): ApiRoute[] {\n return [\n registerApiRoute('/web/factory/projects/:id/audit', {\n method: 'GET',\n handler: async cc => {\n const c = loose(cc);\n const tenant = await this.#resolveTenant(c);\n if ('response' in tenant) return tenant.response;\n\n const projectId = c.req.param('id');\n if (!projectId || !UUID_RE.test(projectId)) return c.json({ error: 'Project not found' }, 404);\n await this.#projects.ensureReady();\n const project = await this.#projects.get({ orgId: tenant.orgId, id: projectId });\n if (!project) return c.json({ error: 'Project not found' }, 404);\n\n const page = await this.list({\n orgId: tenant.orgId,\n factoryProjectId: projectId,\n actions: parseActionsParam(c.req.query('actions')),\n actorId: c.req.query('actor') || undefined,\n before: c.req.query('before') || undefined,\n limit: parseLimitParam(c.req.query('limit')),\n });\n return c.json(page);\n },\n }),\n ];\n }\n\n async #resolveTenant(c: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n await this.#auth.ensureUser(c);\n const tenant = this.#auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json({ error: 'organization_required', message: 'The audit trail requires an organization.' }, 403),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n}\n"],"mappings":"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"}