@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.
- package/CHANGELOG.md +36 -0
- package/dist/auth.d.ts +2 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +401 -314
- package/dist/auth.js.map +1 -1
- package/dist/capabilities/connection.js +1 -1
- package/dist/capabilities/intake.js +1 -1
- package/dist/capabilities/version-control.js +1 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +441 -39455
- package/dist/factory.js.map +1 -1
- package/dist/index.js +2 -39466
- package/dist/integrations/base.js +1 -1
- package/dist/integrations/github/config.js +36 -22
- package/dist/integrations/github/config.js.map +1 -1
- package/dist/integrations/github/factory-session.js +33 -37
- package/dist/integrations/github/factory-session.js.map +1 -1
- package/dist/integrations/github/integration.js +1037 -3624
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/issue-triage.js +71 -71
- package/dist/integrations/github/issue-triage.js.map +1 -1
- package/dist/integrations/github/pat.js +47 -36
- package/dist/integrations/github/pat.js.map +1 -1
- package/dist/integrations/github/provenance.js +72 -101
- package/dist/integrations/github/provenance.js.map +1 -1
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +1272 -1833
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/rules.js +203 -471
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +2 -0
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +401 -326
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/github/session-subscriptions.js +165 -223
- package/dist/integrations/github/session-subscriptions.js.map +1 -1
- package/dist/integrations/github/subscriptions.js +43 -51
- package/dist/integrations/github/subscriptions.js.map +1 -1
- package/dist/integrations/github/token-refresh.js +15 -18
- package/dist/integrations/github/token-refresh.js.map +1 -1
- package/dist/integrations/github/webhook.js +359 -343
- package/dist/integrations/github/webhook.js.map +1 -1
- package/dist/integrations/linear/agent-tools.js +77 -100
- package/dist/integrations/linear/agent-tools.js.map +1 -1
- package/dist/integrations/linear/integration.js +611 -1309
- package/dist/integrations/linear/integration.js.map +1 -1
- package/dist/integrations/linear/routes.js +227 -230
- package/dist/integrations/linear/routes.js.map +1 -1
- package/dist/integrations/linear/rules.js +126 -346
- package/dist/integrations/linear/rules.js.map +1 -1
- package/dist/integrations/linear/storage.js +1 -1
- package/dist/integrations/platform/api-client.js +131 -135
- package/dist/integrations/platform/api-client.js.map +1 -1
- package/dist/integrations/platform/github/event-worker.js +275 -573
- package/dist/integrations/platform/github/event-worker.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +845 -3942
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/linear/integration.js +457 -1309
- package/dist/integrations/platform/linear/integration.js.map +1 -1
- package/dist/integrations/workos/integration.js +80 -85
- package/dist/integrations/workos/integration.js.map +1 -1
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +933 -915
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/custom-provider-source.js +110 -93
- package/dist/routes/custom-provider-source.js.map +1 -1
- package/dist/routes/fs.js +401 -349
- package/dist/routes/fs.js.map +1 -1
- package/dist/routes/intake.js +165 -165
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/oauth.js +447 -25166
- package/dist/routes/oauth.js.map +1 -1
- package/dist/routes/projects.js +437 -376
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/provider-credentials.js +76 -58
- package/dist/routes/provider-credentials.js.map +1 -1
- package/dist/routes/route.js +12 -8
- package/dist/routes/route.js.map +1 -1
- package/dist/routes/skills.js +160 -173
- package/dist/routes/skills.js.map +1 -1
- package/dist/routes/surface.js +250 -28618
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/tenant-credentials.js +141 -166
- package/dist/routes/tenant-credentials.js.map +1 -1
- package/dist/routes/work-items.js +654 -862
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/binding-context.js +23 -35
- package/dist/rules/binding-context.js.map +1 -1
- package/dist/rules/defaults.js +200 -309
- package/dist/rules/defaults.js.map +1 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +438 -677
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/index.js +5 -599
- package/dist/rules/processor.js +344 -596
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/resolve.js +21 -18
- package/dist/rules/resolve.js.map +1 -1
- package/dist/rules/start-coordinator.js +168 -157
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/rules/tools.js +50 -85
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.js +183 -454
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.js +33 -29
- package/dist/rules/types.js.map +1 -1
- package/dist/rules/validation.js +266 -281
- package/dist/rules/validation.js.map +1 -1
- package/dist/sandbox/fleet.js +272 -250
- package/dist/sandbox/fleet.js.map +1 -1
- package/dist/sandbox/reattach.js +13 -6
- package/dist/sandbox/reattach.js.map +1 -1
- package/dist/server-error.js +12 -16
- package/dist/server-error.js.map +1 -1
- package/dist/skills/service.js +30 -35
- package/dist/skills/service.js.map +1 -1
- package/dist/spa-static.d.ts.map +1 -1
- package/dist/spa-static.js +91 -64
- package/dist/spa-static.js.map +1 -1
- package/dist/state-signing.js +73 -44
- package/dist/state-signing.js.map +1 -1
- package/dist/storage/domains/audit/agent-audit.js +60 -59
- package/dist/storage/domains/audit/agent-audit.js.map +1 -1
- package/dist/storage/domains/audit/base.js +169 -112
- package/dist/storage/domains/audit/base.js.map +1 -1
- package/dist/storage/domains/audit/domain.js +155 -153
- package/dist/storage/domains/audit/domain.js.map +1 -1
- package/dist/storage/domains/credentials/base.js +259 -243
- package/dist/storage/domains/credentials/base.js.map +1 -1
- package/dist/storage/domains/custom-providers/base.js +138 -143
- package/dist/storage/domains/custom-providers/base.js.map +1 -1
- package/dist/storage/domains/intake/base.js +75 -57
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/integrations/base.js +310 -245
- package/dist/storage/domains/integrations/base.js.map +1 -1
- package/dist/storage/domains/memory-settings/base.js +117 -102
- package/dist/storage/domains/memory-settings/base.js.map +1 -1
- package/dist/storage/domains/model-packs/base.js +89 -88
- package/dist/storage/domains/model-packs/base.js.map +1 -1
- package/dist/storage/domains/projects/base.js +97 -92
- package/dist/storage/domains/projects/base.js.map +1 -1
- package/dist/storage/domains/queue-health/base.js +111 -76
- package/dist/storage/domains/queue-health/base.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +9 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +777 -702
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +322 -344
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +1266 -1132
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/storage/domains/work-items/metrics.js +202 -184
- package/dist/storage/domains/work-items/metrics.js.map +1 -1
- package/dist/timing.d.ts +15 -0
- package/dist/timing.d.ts.map +1 -0
- package/dist/timing.js +34 -0
- package/dist/timing.js.map +1 -0
- package/dist/workspace.d.ts +2 -3
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +236 -438
- package/dist/workspace.js.map +1 -1
- package/factory-skills/factory-review/SKILL.md +9 -2
- package/package.json +9 -9
- package/dist/capabilities/connection.js.map +0 -1
- package/dist/capabilities/intake.js.map +0 -1
- package/dist/capabilities/version-control.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/integrations/base.js.map +0 -1
- package/dist/integrations/linear/storage.js.map +0 -1
- 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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
6
|
+
return c;
|
|
7
7
|
}
|
|
8
8
|
function parseActionsParam(raw) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
};
|
|
161
|
-
|
|
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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"}
|