@mastra/factory 0.14.0-alpha.2 → 0.14.0-alpha.4

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 (62) hide show
  1. package/dist/factory.d.ts.map +1 -1
  2. package/dist/factory.js +8 -3
  3. package/dist/factory.js.map +1 -1
  4. package/dist/integrations/github/session-subscriptions.d.ts +0 -1
  5. package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
  6. package/dist/integrations/github/session-subscriptions.js +3 -16
  7. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  8. package/dist/integrations/workos/integration.js +1 -1
  9. package/dist/integrations/workos/integration.js.map +1 -1
  10. package/dist/routes/contracts.d.ts +2 -2
  11. package/dist/routes/surface.d.ts +2 -2
  12. package/dist/routes/surface.d.ts.map +1 -1
  13. package/dist/routes/surface.js +2 -1
  14. package/dist/routes/surface.js.map +1 -1
  15. package/dist/routes/work-items.d.ts.map +1 -1
  16. package/dist/routes/work-items.js +10 -87
  17. package/dist/routes/work-items.js.map +1 -1
  18. package/dist/rules/dispatcher.d.ts +2 -0
  19. package/dist/rules/dispatcher.d.ts.map +1 -1
  20. package/dist/rules/dispatcher.js +29 -0
  21. package/dist/rules/dispatcher.js.map +1 -1
  22. package/dist/rules/transition-service.d.ts +11 -0
  23. package/dist/rules/transition-service.d.ts.map +1 -1
  24. package/dist/rules/transition-service.js +72 -2
  25. package/dist/rules/transition-service.js.map +1 -1
  26. package/dist/session/run-audit.d.ts +49 -0
  27. package/dist/session/run-audit.d.ts.map +1 -0
  28. package/dist/session/run-audit.js +99 -0
  29. package/dist/session/run-audit.js.map +1 -0
  30. package/dist/session/shell-commands.d.ts +5 -0
  31. package/dist/session/shell-commands.d.ts.map +1 -0
  32. package/dist/session/shell-commands.js +25 -0
  33. package/dist/session/shell-commands.js.map +1 -0
  34. package/dist/storage/domains/audit/actions.d.ts +24 -0
  35. package/dist/storage/domains/audit/actions.d.ts.map +1 -0
  36. package/dist/storage/domains/audit/actions.js +67 -0
  37. package/dist/storage/domains/audit/actions.js.map +1 -0
  38. package/dist/storage/domains/audit/actors.d.ts +5 -0
  39. package/dist/storage/domains/audit/actors.d.ts.map +1 -0
  40. package/dist/storage/domains/audit/actors.js +25 -0
  41. package/dist/storage/domains/audit/actors.js.map +1 -0
  42. package/dist/storage/domains/audit/agent-audit.d.ts +5 -7
  43. package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -1
  44. package/dist/storage/domains/audit/agent-audit.js +33 -24
  45. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  46. package/dist/storage/domains/audit/base.d.ts +28 -33
  47. package/dist/storage/domains/audit/base.d.ts.map +1 -1
  48. package/dist/storage/domains/audit/base.js +58 -31
  49. package/dist/storage/domains/audit/base.js.map +1 -1
  50. package/dist/storage/domains/audit/domain.d.ts +12 -9
  51. package/dist/storage/domains/audit/domain.d.ts.map +1 -1
  52. package/dist/storage/domains/audit/domain.js +32 -48
  53. package/dist/storage/domains/audit/domain.js.map +1 -1
  54. package/dist/storage/domains/audit/wire.d.ts +24 -0
  55. package/dist/storage/domains/audit/wire.d.ts.map +1 -0
  56. package/dist/storage/domains/audit/wire.js +16 -0
  57. package/dist/storage/domains/audit/wire.js.map +1 -0
  58. package/dist/supervisor/write-tools.d.ts +2 -2
  59. package/dist/supervisor/write-tools.d.ts.map +1 -1
  60. package/dist/supervisor/write-tools.js +1 -17
  61. package/dist/supervisor/write-tools.js.map +1 -1
  62. package/package.json +7 -7
@@ -1,15 +1,10 @@
1
1
  import { getFactoryAuthUser } from "../../../auth.js";
2
+ import { auditActionsInNamespaces, isAuditNamespace } from "./actions.js";
3
+ import { isHumanActorId } from "./actors.js";
4
+ import { ACTOR_PROFILE_METADATA_KEY, auditActorProfile, auditAgentName } from "./base.js";
5
+ import { toWireAuditEvent } from "./wire.js";
2
6
  import { registerApiRoute } from "@mastra/core/server";
3
7
  //#region src/storage/domains/audit/domain.ts
4
- /**
5
- * Reserved metadata key on `AuditEventRow.metadata` that carries the acting
6
- * human user's display name and avatar captured from the auth context at
7
- * record time. Read back by `#resolveActorProfiles` so names/avatars work
8
- * for every auth provider without requiring an `IUserProvider.getUser(id)`
9
- * implementation — the Studio provider proxies through the shared API and
10
- * cannot resolve arbitrary users by id.
11
- */
12
- const ACTOR_PROFILE_METADATA_KEY = "__actorProfile";
13
8
  function readStoredActorProfile(metadata) {
14
9
  if (!metadata) return void 0;
15
10
  const raw = metadata[ACTOR_PROFILE_METADATA_KEY];
@@ -23,36 +18,15 @@ function readStoredActorProfile(metadata) {
23
18
  ...avatarUrl ? { avatarUrl } : {}
24
19
  };
25
20
  }
26
- function buildActorProfileMetadata(user) {
27
- const name = user.name?.trim() || user.email?.trim();
28
- const avatarUrl = user.avatarUrl?.trim();
29
- if (!name && !avatarUrl) return void 0;
30
- return {
31
- ...name ? { name } : {},
32
- ...avatarUrl ? { avatarUrl } : {}
33
- };
34
- }
35
21
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
36
- const MAX_ACTION_FILTERS = 16;
37
22
  const MAX_ACTOR_PROFILES = 100;
38
- const AUTOMATION_ACTORS = /* @__PURE__ */ new Set([
39
- "factory",
40
- "system",
41
- "automation",
42
- "factory-rule-dispatcher",
43
- "factory-tool-result-rule"
44
- ]);
45
23
  function loose(c) {
46
24
  return c;
47
25
  }
48
- function parseActionsParam(raw) {
26
+ /** The actions a `namespaces=` query names: none when it names only unknown ones, `undefined` when it names nothing. */
27
+ function actionsInRequestedNamespaces(raw) {
49
28
  if (!raw) return void 0;
50
- const actions = raw.split(",").map((action) => action.trim()).filter(Boolean).slice(0, MAX_ACTION_FILTERS);
51
- return actions.length > 0 ? actions : void 0;
52
- }
53
- function isHumanActorId(actorId) {
54
- if (!actorId) return false;
55
- return !AUTOMATION_ACTORS.has(actorId) && !actorId.startsWith("agent:") && !actorId.startsWith("github:");
29
+ return auditActionsInNamespaces(raw.split(",").map((namespace) => namespace.trim()).filter(isAuditNamespace));
56
30
  }
57
31
  function parseActorIdsParam(raw) {
58
32
  if (!raw) return [];
@@ -71,6 +45,13 @@ function auditRequestContext(c) {
71
45
  ...userAgent ? { userAgent } : {}
72
46
  };
73
47
  }
48
+ /** Who a browser request is and where it came from: the pair every funnel row carries. */
49
+ function auditRequestOrigin(c) {
50
+ return {
51
+ actorProfile: auditActorProfile(getFactoryAuthUser(c)),
52
+ context: auditRequestContext(c)
53
+ };
54
+ }
74
55
  /** Factory-owned audit behavior backed by the audit storage domain. */
75
56
  var AuditDomain = class {
76
57
  #auth;
@@ -96,7 +77,11 @@ var AuditDomain = class {
96
77
  async record(input) {
97
78
  try {
98
79
  await this.#audit.ensureReady();
99
- const row = await this.#audit.record(input);
80
+ const { event: row, created } = input.idempotencyKey ? await this.#audit.recordOnce(input) : {
81
+ event: await this.#audit.record(input),
82
+ created: true
83
+ };
84
+ if (!created) return row;
100
85
  for (const sink of this.#sinks) {
101
86
  if (!sink.audit) continue;
102
87
  Promise.resolve().then(() => sink.audit?.({ event: row })).catch((err) => {
@@ -124,18 +109,13 @@ var AuditDomain = class {
124
109
  try {
125
110
  const tenant = this.#auth.tenant(context);
126
111
  if (!tenant?.orgId) return;
127
- const user = getFactoryAuthUser(context);
128
- const actorProfile = user ? buildActorProfileMetadata(user) : void 0;
129
- const metadata = actorProfile ? {
130
- ...input.metadata,
131
- [ACTOR_PROFILE_METADATA_KEY]: actorProfile
132
- } : input.metadata;
133
112
  await this.record({
134
113
  orgId: tenant.orgId,
135
114
  actorId: tenant.userId,
115
+ actorProfile: auditActorProfile(getFactoryAuthUser(context)),
136
116
  action: input.action,
137
117
  targets: input.targets,
138
- metadata,
118
+ metadata: input.metadata,
139
119
  factoryProjectId: input.factoryProjectId,
140
120
  projectRepositoryId: input.projectRepositoryId,
141
121
  context: auditRequestContext(context)
@@ -167,7 +147,7 @@ var AuditDomain = class {
167
147
  metadata: {
168
148
  ...input.metadata,
169
149
  startedBy: userId,
170
- ...modeId ? { agentName: `${modeId} agent` } : {},
150
+ ...modeId ? { agentName: auditAgentName(modeId) } : {},
171
151
  ...modelId ? { modelId } : {}
172
152
  },
173
153
  factoryProjectId: state.factoryProjectId,
@@ -230,19 +210,23 @@ var AuditDomain = class {
230
210
  orgId: tenant.orgId,
231
211
  id: projectId
232
212
  })) return c.json({ error: "Project not found" }, 404);
213
+ const actions = actionsInRequestedNamespaces(c.req.query("namespaces"));
214
+ if (actions?.length === 0) return c.json({ error: "unknown_namespaces" }, 400);
233
215
  const page = await this.list({
234
216
  orgId: tenant.orgId,
235
217
  factoryProjectId: projectId,
236
- actions: parseActionsParam(c.req.query("actions")),
218
+ actions,
237
219
  actorId: c.req.query("actor") || void 0,
238
220
  before: c.req.query("before") || void 0,
239
221
  limit: parseLimitParam(c.req.query("limit"))
240
222
  });
241
223
  const actors = await this.#resolveActorProfiles(page.events, parseActorIdsParam(c.req.query("actorIds")));
242
- return c.json({
243
- ...page,
244
- actors
245
- });
224
+ const body = {
225
+ events: page.events.map(toWireAuditEvent),
226
+ actors,
227
+ ...page.nextCursor ? { nextCursor: page.nextCursor } : {}
228
+ };
229
+ return c.json(body);
246
230
  }
247
231
  })];
248
232
  }
@@ -261,6 +245,6 @@ var AuditDomain = class {
261
245
  }
262
246
  };
263
247
  //#endregion
264
- export { AuditDomain, auditRequestContext };
248
+ export { AuditDomain, auditRequestContext, auditRequestOrigin };
265
249
 
266
250
  //# sourceMappingURL=domain.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"domain.js","names":["#auth","#audit","#projects","#users","#sinks","#agentTenant","#resolveActorProfiles","#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, IUserProvider } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { getFactoryAuthUser } from '../../../auth.js';\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 AuditActorProfile {\n id: string;\n name: string;\n avatarUrl?: string;\n}\n\n/**\n * Reserved metadata key on `AuditEventRow.metadata` that carries the acting\n * human user's display name and avatar captured from the auth context at\n * record time. Read back by `#resolveActorProfiles` so names/avatars work\n * for every auth provider without requiring an `IUserProvider.getUser(id)`\n * implementation — the Studio provider proxies through the shared API and\n * cannot resolve arbitrary users by id.\n */\nconst ACTOR_PROFILE_METADATA_KEY = '__actorProfile';\n\ninterface StoredActorProfile {\n name?: string;\n avatarUrl?: string;\n}\n\nfunction readStoredActorProfile(metadata: Record<string, unknown> | undefined): StoredActorProfile | undefined {\n if (!metadata) return undefined;\n const raw = metadata[ACTOR_PROFILE_METADATA_KEY];\n if (!raw || typeof raw !== 'object') return undefined;\n const profile = raw as Record<string, unknown>;\n const name = typeof profile.name === 'string' && profile.name.trim() ? profile.name.trim() : undefined;\n const avatarUrl =\n typeof profile.avatarUrl === 'string' && profile.avatarUrl.trim() ? profile.avatarUrl.trim() : undefined;\n if (!name && !avatarUrl) return undefined;\n return { ...(name ? { name } : {}), ...(avatarUrl ? { avatarUrl } : {}) };\n}\n\nfunction buildActorProfileMetadata(user: {\n name?: string;\n email?: string;\n avatarUrl?: string;\n}): StoredActorProfile | undefined {\n const name = user.name?.trim() || user.email?.trim();\n const avatarUrl = user.avatarUrl?.trim();\n if (!name && !avatarUrl) return undefined;\n return { ...(name ? { name } : {}), ...(avatarUrl ? { avatarUrl } : {}) };\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 /** Resolve persisted human actor ids to display names and profile images. */\n users?: Pick<IUserProvider, 'getUser' | 'getUsers'>;\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;\nconst MAX_ACTOR_PROFILES = 100;\nconst AUTOMATION_ACTORS = new Set([\n 'factory',\n 'system',\n 'automation',\n 'factory-rule-dispatcher',\n 'factory-tool-result-rule',\n]);\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 isHumanActorId(actorId: string | undefined): actorId is string {\n if (!actorId) return false;\n return !AUTOMATION_ACTORS.has(actorId) && !actorId.startsWith('agent:') && !actorId.startsWith('github:');\n}\n\nfunction parseActorIdsParam(raw: string | undefined): string[] {\n if (!raw) return [];\n return [\n ...new Set(\n raw\n .split(',')\n .map(actorId => actorId.trim())\n .filter(isHumanActorId),\n ),\n ].slice(0, MAX_ACTOR_PROFILES);\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 #users: AuditDomainOptions['users'];\n readonly #sinks: AuditSink[];\n readonly #agentTenant: AuditDomainOptions['agentTenant'];\n\n constructor({ auth, audit, projects, users, sinks = [], agentTenant }: AuditDomainOptions) {\n this.#auth = auth;\n this.#audit = audit;\n this.#projects = projects;\n this.#users = users;\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 const user = getFactoryAuthUser(context);\n const actorProfile = user ? buildActorProfileMetadata(user) : undefined;\n const metadata = actorProfile\n ? { ...input.metadata, [ACTOR_PROFILE_METADATA_KEY]: actorProfile }\n : input.metadata;\n await this.record({\n orgId: tenant.orgId,\n actorId: tenant.userId,\n action: input.action,\n targets: input.targets,\n 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 const modeId = context.session.modeId?.trim();\n const modelId = context.session.modelId?.trim();\n await this.record({\n orgId,\n actorId: `agent:${threadId}`,\n actorType: 'agent',\n action: input.action,\n targets: input.targets,\n metadata: {\n ...input.metadata,\n startedBy: userId,\n ...(modeId ? { agentName: `${modeId} agent` } : {}),\n ...(modelId ? { modelId } : {}),\n },\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 async #resolveActorProfiles(\n events: AuditEventRow[],\n requestedActorIds: string[] = [],\n ): Promise<Record<string, AuditActorProfile>> {\n const humanActorIds = [\n ...new Set(\n [\n ...requestedActorIds,\n ...events.filter(event => event.actorType === 'human').map(event => event.actorId),\n ].filter(isHumanActorId),\n ),\n ].slice(0, MAX_ACTOR_PROFILES);\n if (humanActorIds.length === 0) return {};\n\n // Prefer the profile stamped into event metadata at record time: it works\n // regardless of the auth provider's ability to resolve a user by id\n // (MastraAuthStudio's getUser() always returns null, WorkOS looks up by\n // id but every provider stamps here uniformly).\n const profiles: Record<string, AuditActorProfile> = {};\n for (const event of events) {\n if (event.actorType !== 'human') continue;\n if (!isHumanActorId(event.actorId)) continue;\n if (profiles[event.actorId]) continue;\n const stored = readStoredActorProfile(event.metadata);\n if (!stored?.name) continue;\n profiles[event.actorId] = {\n id: event.actorId,\n name: stored.name,\n ...(stored.avatarUrl ? { avatarUrl: stored.avatarUrl } : {}),\n };\n }\n\n const unresolved = humanActorIds.filter(actorId => !profiles[actorId]);\n if (unresolved.length === 0 || !this.#users) return profiles;\n\n try {\n const users = this.#users.getUsers\n ? await this.#users.getUsers(unresolved)\n : await Promise.all(unresolved.map(actorId => this.#users?.getUser(actorId) ?? null));\n for (const [index, user] of users.entries()) {\n if (!user) continue;\n const name = user.name?.trim() || user.email?.trim() || user.id;\n const actorId = unresolved[index] ?? user.id;\n profiles[actorId] = {\n id: user.id,\n name,\n ...(user.avatarUrl ? { avatarUrl: user.avatarUrl } : {}),\n };\n }\n } catch (err) {\n console.warn('[Audit] Failed to resolve audit actor profiles', {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n return profiles;\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 const actors = await this.#resolveActorProfiles(page.events, parseActorIdsParam(c.req.query('actorIds')));\n return c.json({ ...page, actors });\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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"}
1
+ {"version":3,"file":"domain.js","names":["#auth","#audit","#projects","#users","#sinks","#agentTenant","#resolveActorProfiles","#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, IUserProvider } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { getFactoryAuthUser } from '../../../auth.js';\nimport type { RouteAuth } from '../../../routes/route.js';\nimport type { FactoryProjectsStorage } from '../projects/base.js';\nimport { auditActionsInNamespaces, isAuditNamespace } from './actions.js';\nimport type { AuditAction } from './actions.js';\nimport { isHumanActorId } from './actors.js';\nimport { ACTOR_PROFILE_METADATA_KEY, auditActorProfile, auditAgentName } from './base.js';\nimport type {\n AuditActorProfileInput,\n AuditContext,\n AuditEventPage,\n AuditEventRow,\n AuditStorage,\n AuditTarget,\n ListAuditEventsInput,\n RecordAuditEventInput,\n} from './base.js';\nimport { toWireAuditEvent } from './wire.js';\nimport type { WireAuditActor, WireAuditPage } from './wire.js';\n\nexport interface EmitAuditInput {\n action: AuditAction;\n factoryProjectId?: string;\n projectRepositoryId?: string;\n targets: AuditTarget[];\n metadata?: Record<string, unknown>;\n}\n\nexport interface EmitAgentAuditInput {\n action: AuditAction;\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/** Records with an explicit actor, for the paths that have no request: rule transitions, run starts, run ends, supervisor tools. */\nexport type AuditRecorder = Pick<AuditDomain, 'record'>;\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\nfunction readStoredActorProfile(metadata: Record<string, unknown> | undefined): AuditActorProfileInput | undefined {\n if (!metadata) return undefined;\n const raw = metadata[ACTOR_PROFILE_METADATA_KEY];\n if (!raw || typeof raw !== 'object') return undefined;\n const profile = raw as Record<string, unknown>;\n const name = typeof profile.name === 'string' && profile.name.trim() ? profile.name.trim() : undefined;\n const avatarUrl =\n typeof profile.avatarUrl === 'string' && profile.avatarUrl.trim() ? profile.avatarUrl.trim() : undefined;\n if (!name && !avatarUrl) return undefined;\n return { ...(name ? { name } : {}), ...(avatarUrl ? { avatarUrl } : {}) };\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 /** Resolve persisted human actor ids to display names and profile images. */\n users?: Pick<IUserProvider, 'getUser' | 'getUsers'>;\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_ACTOR_PROFILES = 100;\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\n/** The actions a `namespaces=` query names: none when it names only unknown ones, `undefined` when it names nothing. */\nfunction actionsInRequestedNamespaces(raw: string | undefined): string[] | undefined {\n if (!raw) return undefined;\n return auditActionsInNamespaces(\n raw\n .split(',')\n .map(namespace => namespace.trim())\n .filter(isAuditNamespace),\n );\n}\n\nfunction parseActorIdsParam(raw: string | undefined): string[] {\n if (!raw) return [];\n return [\n ...new Set(\n raw\n .split(',')\n .map(actorId => actorId.trim())\n .filter(isHumanActorId),\n ),\n ].slice(0, MAX_ACTOR_PROFILES);\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/** Who a browser request is and where it came from: the pair every funnel row carries. */\nexport function auditRequestOrigin(c: Context): {\n actorProfile: AuditActorProfileInput | undefined;\n context: AuditContext;\n} {\n return { actorProfile: auditActorProfile(getFactoryAuthUser(c)), context: auditRequestContext(c) };\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 #users: AuditDomainOptions['users'];\n readonly #sinks: AuditSink[];\n readonly #agentTenant: AuditDomainOptions['agentTenant'];\n\n constructor({ auth, audit, projects, users, sinks = [], agentTenant }: AuditDomainOptions) {\n this.#auth = auth;\n this.#audit = audit;\n this.#projects = projects;\n this.#users = users;\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<AuditAction>): Promise<AuditEventRow | null> {\n try {\n await this.#audit.ensureReady();\n const { event: row, created } = input.idempotencyKey\n ? await this.#audit.recordOnce(input)\n : { event: await this.#audit.record(input), created: true };\n if (!created) return row;\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 actorProfile: auditActorProfile(getFactoryAuthUser(context)),\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 const modeId = context.session.modeId?.trim();\n const modelId = context.session.modelId?.trim();\n await this.record({\n orgId,\n actorId: `agent:${threadId}`,\n actorType: 'agent',\n action: input.action,\n targets: input.targets,\n metadata: {\n ...input.metadata,\n startedBy: userId,\n ...(modeId ? { agentName: auditAgentName(modeId) } : {}),\n ...(modelId ? { modelId } : {}),\n },\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 async #resolveActorProfiles(\n events: AuditEventRow[],\n requestedActorIds: string[] = [],\n ): Promise<Record<string, WireAuditActor>> {\n const humanActorIds = [\n ...new Set(\n [\n ...requestedActorIds,\n ...events.filter(event => event.actorType === 'human').map(event => event.actorId),\n ].filter(isHumanActorId),\n ),\n ].slice(0, MAX_ACTOR_PROFILES);\n if (humanActorIds.length === 0) return {};\n\n // Prefer the profile stamped into event metadata at record time: it works\n // regardless of the auth provider's ability to resolve a user by id\n // (MastraAuthStudio's getUser() always returns null, WorkOS looks up by\n // id but every provider stamps here uniformly).\n const profiles: Record<string, WireAuditActor> = {};\n for (const event of events) {\n if (event.actorType !== 'human') continue;\n if (!isHumanActorId(event.actorId)) continue;\n if (profiles[event.actorId]) continue;\n const stored = readStoredActorProfile(event.metadata);\n if (!stored?.name) continue;\n profiles[event.actorId] = {\n id: event.actorId,\n name: stored.name,\n ...(stored.avatarUrl ? { avatarUrl: stored.avatarUrl } : {}),\n };\n }\n\n const unresolved = humanActorIds.filter(actorId => !profiles[actorId]);\n if (unresolved.length === 0 || !this.#users) return profiles;\n\n try {\n const users = this.#users.getUsers\n ? await this.#users.getUsers(unresolved)\n : await Promise.all(unresolved.map(actorId => this.#users?.getUser(actorId) ?? null));\n for (const [index, user] of users.entries()) {\n if (!user) continue;\n const name = user.name?.trim() || user.email?.trim() || user.id;\n const actorId = unresolved[index] ?? user.id;\n profiles[actorId] = {\n id: user.id,\n name,\n ...(user.avatarUrl ? { avatarUrl: user.avatarUrl } : {}),\n };\n }\n } catch (err) {\n console.warn('[Audit] Failed to resolve audit actor profiles', {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n return profiles;\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 actions = actionsInRequestedNamespaces(c.req.query('namespaces'));\n if (actions?.length === 0) return c.json({ error: 'unknown_namespaces' }, 400);\n\n const page = await this.list({\n orgId: tenant.orgId,\n factoryProjectId: projectId,\n actions,\n actorId: c.req.query('actor') || undefined,\n before: c.req.query('before') || undefined,\n limit: parseLimitParam(c.req.query('limit')),\n });\n const actors = await this.#resolveActorProfiles(page.events, parseActorIdsParam(c.req.query('actorIds')));\n const body: WireAuditPage = {\n events: page.events.map(toWireAuditEvent),\n actors,\n ...(page.nextCursor ? { nextCursor: page.nextCursor } : {}),\n };\n return c.json(body);\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":";;;;;;;AA8DA,SAAS,uBAAuB,UAAmF;CACjH,IAAI,CAAC,UAAU,OAAO,KAAA;CACtB,MAAM,MAAM,SAAS;CACrB,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU,OAAO,KAAA;CAC5C,MAAM,UAAU;CAChB,MAAM,OAAO,OAAO,QAAQ,SAAS,YAAY,QAAQ,KAAK,KAAK,IAAI,QAAQ,KAAK,KAAK,IAAI,KAAA;CAC7F,MAAM,YACJ,OAAO,QAAQ,cAAc,YAAY,QAAQ,UAAU,KAAK,IAAI,QAAQ,UAAU,KAAK,IAAI,KAAA;CACjG,IAAI,CAAC,QAAQ,CAAC,WAAW,OAAO,KAAA;CAChC,OAAO;EAAE,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;EAAI,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;CAAG;AAC1E;AAgBA,MAAM,UAAU;AAChB,MAAM,qBAAqB;AAE3B,SAAS,MAAM,GAAqB;CAClC,OAAO;AACT;;AAGA,SAAS,6BAA6B,KAA+C;CACnF,IAAI,CAAC,KAAK,OAAO,KAAA;CACjB,OAAO,yBACL,IACG,MAAM,GAAG,CAAC,CACV,KAAI,cAAa,UAAU,KAAK,CAAC,CAAC,CAClC,OAAO,gBAAgB,CAC5B;AACF;AAEA,SAAS,mBAAmB,KAAmC;CAC7D,IAAI,CAAC,KAAK,OAAO,CAAC;CAClB,OAAO,CACL,GAAG,IAAI,IACL,IACG,MAAM,GAAG,CAAC,CACV,KAAI,YAAW,QAAQ,KAAK,CAAC,CAAC,CAC9B,OAAO,cAAc,CAC1B,CACF,CAAC,CAAC,MAAM,GAAG,kBAAkB;AAC/B;AAEA,SAAS,gBAAgB,KAA6C;CACpE,IAAI,CAAC,KAAK,OAAO,KAAA;CACjB,MAAM,QAAQ,OAAO,SAAS,KAAK,EAAE;CACrC,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ,KAAA;AAC1C;AAEA,SAAgB,oBAAoB,GAA0B;CAC5D,MAAM,WAAW,EAAE,IAAI,OAAO,iBAAiB,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK;CACtE,MAAM,YAAY,EAAE,IAAI,OAAO,YAAY;CAC3C,OAAO;EACL,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;EAC/B,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;CACnC;AACF;;AAGA,SAAgB,mBAAmB,GAGjC;CACA,OAAO;EAAE,cAAc,kBAAkB,mBAAmB,CAAC,CAAC;EAAG,SAAS,oBAAoB,CAAC;CAAE;AACnG;;AAGA,IAAa,cAAb,MAAoE;CAClE;CACA;CACA;CACA;CACA;CACA;CAEA,YAAY,EAAE,MAAM,OAAO,UAAU,OAAO,QAAQ,CAAC,GAAG,eAAmC;EACzF,KAAKA,QAAQ;EACb,KAAKC,SAAS;EACd,KAAKC,YAAY;EACjB,KAAKC,SAAS;EACd,KAAKC,SAAS;EACd,KAAKC,eAAe;EAEpB,MAAM,sBAAM,IAAI,IAAY;EAC5B,KAAK,MAAM,QAAQ,KAAKD,QAAQ;GAC9B,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,MAAM,wCAAwC;GACtE,IAAI,IAAI,IAAI,KAAK,EAAE,GAAG,MAAM,IAAI,MAAM,mCAAmC,KAAK,GAAG,EAAE;GACnF,IAAI,IAAI,KAAK,EAAE;EACjB;CACF;CAEA,MAAM,OAAO,OAA0E;EACrF,IAAI;GACF,MAAM,KAAKH,OAAO,YAAY;GAC9B,MAAM,EAAE,OAAO,KAAK,YAAY,MAAM,iBAClC,MAAM,KAAKA,OAAO,WAAW,KAAK,IAClC;IAAE,OAAO,MAAM,KAAKA,OAAO,OAAO,KAAK;IAAG,SAAS;GAAK;GAC5D,IAAI,CAAC,SAAS,OAAO;GACrB,KAAK,MAAM,QAAQ,KAAKG,QAAQ;IAC9B,IAAI,CAAC,KAAK,OAAO;IACjB,QAAa,QAAQ,CAAC,CACnB,WAAW,KAAK,QAAQ,EAAE,OAAO,IAAI,CAAC,CAAC,CAAC,CACxC,OAAM,QAAO;KACZ,QAAQ,KAAK,oCAAoC;MAC/C,aAAa,KAAK;MAClB,QAAQ,IAAI;MACZ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;KACxD,CAAC;IACH,CAAC;GACL;GACA,OAAO;EACT,SAAS,KAAK;GACZ,QAAQ,KAAK,wCAAwC;IACnD,QAAQ,MAAM;IACd,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GACxD,CAAC;GACD,OAAO;EACT;CACF;CAEA,MAAM,KAAK,OAAsD;EAC/D,MAAM,KAAKH,OAAO,YAAY;EAC9B,OAAO,KAAKA,OAAO,KAAK,KAAK;CAC/B;CAEA,MAAM,KAAK,EAAE,SAAS,SAAqE;EACzF,IAAI;GACF,MAAM,SAAS,KAAKD,MAAM,OAAO,OAAO;GACxC,IAAI,CAAC,QAAQ,OAAO;GACpB,MAAM,KAAK,OAAO;IAChB,OAAO,OAAO;IACd,SAAS,OAAO;IAChB,cAAc,kBAAkB,mBAAmB,OAAO,CAAC;IAC3D,QAAQ,MAAM;IACd,SAAS,MAAM;IACf,UAAU,MAAM;IAChB,kBAAkB,MAAM;IACxB,qBAAqB,MAAM;IAC3B,SAAS,oBAAoB,OAAO;GACtC,CAAC;EACH,SAAS,KAAK;GACZ,QAAQ,KAAK,sCAAsC;IACjD,QAAQ,MAAM;IACd,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GACxD,CAAC;EACH;CACF;CAEA,MAAM,UAAU,EACd,gBACA,SAIgB;EAChB,IAAI;GACF,MAAM,UAAU,eAAe,IAAI,YAAY;GAG/C,MAAM,SAAS,KAAKK,eAAe,cAAc;GACjD,MAAM,QAAQ,QAAQ;GACtB,MAAM,SAAS,QAAQ;GACvB,MAAM,WAAW,SAAS;GAC1B,MAAM,QAAQ,SAAS,SAAS;GAChC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,YAAY,CAAC,OAAO,kBAAkB;GAEhE,MAAM,SAAS,QAAQ,QAAQ,QAAQ,KAAK;GAC5C,MAAM,UAAU,QAAQ,QAAQ,SAAS,KAAK;GAC9C,MAAM,KAAK,OAAO;IAChB;IACA,SAAS,SAAS;IAClB,WAAW;IACX,QAAQ,MAAM;IACd,SAAS,MAAM;IACf,UAAU;KACR,GAAG,MAAM;KACT,WAAW;KACX,GAAI,SAAS,EAAE,WAAW,eAAe,MAAM,EAAE,IAAI,CAAC;KACtD,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;IAC/B;IACA,kBAAkB,MAAM;IACxB,qBAAqB,MAAM;IAC3B,SAAS,CAAC;GACZ,CAAC;EACH,SAAS,KAAK;GACZ,QAAQ,KAAK,4CAA4C;IACvD,QAAQ,MAAM;IACd,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GACxD,CAAC;EACH;CACF;CAEA,MAAMC,sBACJ,QACA,oBAA8B,CAAC,GACU;EACzC,MAAM,gBAAgB,CACpB,GAAG,IAAI,IACL,CACE,GAAG,mBACH,GAAG,OAAO,QAAO,UAAS,MAAM,cAAc,OAAO,CAAC,CAAC,KAAI,UAAS,MAAM,OAAO,CACnF,CAAC,CAAC,OAAO,cAAc,CACzB,CACF,CAAC,CAAC,MAAM,GAAG,kBAAkB;EAC7B,IAAI,cAAc,WAAW,GAAG,OAAO,CAAC;EAMxC,MAAM,WAA2C,CAAC;EAClD,KAAK,MAAM,SAAS,QAAQ;GAC1B,IAAI,MAAM,cAAc,SAAS;GACjC,IAAI,CAAC,eAAe,MAAM,OAAO,GAAG;GACpC,IAAI,SAAS,MAAM,UAAU;GAC7B,MAAM,SAAS,uBAAuB,MAAM,QAAQ;GACpD,IAAI,CAAC,QAAQ,MAAM;GACnB,SAAS,MAAM,WAAW;IACxB,IAAI,MAAM;IACV,MAAM,OAAO;IACb,GAAI,OAAO,YAAY,EAAE,WAAW,OAAO,UAAU,IAAI,CAAC;GAC5D;EACF;EAEA,MAAM,aAAa,cAAc,QAAO,YAAW,CAAC,SAAS,QAAQ;EACrE,IAAI,WAAW,WAAW,KAAK,CAAC,KAAKH,QAAQ,OAAO;EAEpD,IAAI;GACF,MAAM,QAAQ,KAAKA,OAAO,WACtB,MAAM,KAAKA,OAAO,SAAS,UAAU,IACrC,MAAM,QAAQ,IAAI,WAAW,KAAI,YAAW,KAAKA,QAAQ,QAAQ,OAAO,KAAK,IAAI,CAAC;GACtF,KAAK,MAAM,CAAC,OAAO,SAAS,MAAM,QAAQ,GAAG;IAC3C,IAAI,CAAC,MAAM;IACX,MAAM,OAAO,KAAK,MAAM,KAAK,KAAK,KAAK,OAAO,KAAK,KAAK,KAAK;IAC7D,MAAM,UAAU,WAAW,UAAU,KAAK;IAC1C,SAAS,WAAW;KAClB,IAAI,KAAK;KACT;KACA,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;IACxD;GACF;EACF,SAAS,KAAK;GACZ,QAAQ,KAAK,kDAAkD,EAC7D,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EACxD,CAAC;EACH;EACA,OAAO;CACT;CAEA,SAAqB;EACnB,OAAO,CACL,iBAAiB,mCAAmC;GAClD,QAAQ;GACR,SAAS,OAAM,OAAM;IACnB,MAAM,IAAI,MAAM,EAAE;IAClB,MAAM,SAAS,MAAM,KAAKI,eAAe,CAAC;IAC1C,IAAI,cAAc,QAAQ,OAAO,OAAO;IAExC,MAAM,YAAY,EAAE,IAAI,MAAM,IAAI;IAClC,IAAI,CAAC,aAAa,CAAC,QAAQ,KAAK,SAAS,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;IAC7F,MAAM,KAAKL,UAAU,YAAY;IAEjC,IAAI,CAAC,MADiB,KAAKA,UAAU,IAAI;KAAE,OAAO,OAAO;KAAO,IAAI;IAAU,CAAC,GACjE,OAAO,EAAE,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;IAE/D,MAAM,UAAU,6BAA6B,EAAE,IAAI,MAAM,YAAY,CAAC;IACtE,IAAI,SAAS,WAAW,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,qBAAqB,GAAG,GAAG;IAE7E,MAAM,OAAO,MAAM,KAAK,KAAK;KAC3B,OAAO,OAAO;KACd,kBAAkB;KAClB;KACA,SAAS,EAAE,IAAI,MAAM,OAAO,KAAK,KAAA;KACjC,QAAQ,EAAE,IAAI,MAAM,QAAQ,KAAK,KAAA;KACjC,OAAO,gBAAgB,EAAE,IAAI,MAAM,OAAO,CAAC;IAC7C,CAAC;IACD,MAAM,SAAS,MAAM,KAAKI,sBAAsB,KAAK,QAAQ,mBAAmB,EAAE,IAAI,MAAM,UAAU,CAAC,CAAC;IACxG,MAAM,OAAsB;KAC1B,QAAQ,KAAK,OAAO,IAAI,gBAAgB;KACxC;KACA,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;IAC3D;IACA,OAAO,EAAE,KAAK,IAAI;GACpB;EACF,CAAC,CACH;CACF;CAEA,MAAMC,eAAe,GAAiF;EACpG,MAAM,KAAKP,MAAM,WAAW,CAAC;EAC7B,MAAM,SAAS,KAAKA,MAAM,OAAO,CAAC;EAClC,IAAI,CAAC,QAAQ,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,eAAe,GAAG,GAAG,EAAE;EACvE,IAAI,CAAC,OAAO,OACV,OAAO,EACL,UAAU,EAAE,KAAK;GAAE,OAAO;GAAyB,SAAS;EAA4C,GAAG,GAAG,EAChH;EAEF,OAAO;GAAE,OAAO,OAAO;GAAO,QAAQ,OAAO;EAAO;CACtD;AACF"}
@@ -0,0 +1,24 @@
1
+ import type { AuditActorType } from './actors.js';
2
+ import type { AuditEventRow, AuditTarget } from './base.js';
3
+ export interface WireAuditActor {
4
+ id: string;
5
+ name: string;
6
+ avatarUrl?: string;
7
+ }
8
+ /** An `AuditEventRow` as the list route sends it: the date as ISO text, tenancy and request context left behind. */
9
+ export interface WireAuditEvent {
10
+ id: string;
11
+ actorId: string;
12
+ actorType: AuditActorType;
13
+ action: string;
14
+ targets: AuditTarget[];
15
+ metadata: Record<string, unknown>;
16
+ occurredAt: string;
17
+ }
18
+ export interface WireAuditPage {
19
+ events: WireAuditEvent[];
20
+ actors: Record<string, WireAuditActor>;
21
+ nextCursor?: string;
22
+ }
23
+ export declare function toWireAuditEvent(row: AuditEventRow): WireAuditEvent;
24
+ //# sourceMappingURL=wire.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wire.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/audit/wire.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE5D,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,oHAAoH;AACpH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,cAAc,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAUnE"}
@@ -0,0 +1,16 @@
1
+ //#region src/storage/domains/audit/wire.ts
2
+ function toWireAuditEvent(row) {
3
+ return {
4
+ id: row.id,
5
+ actorId: row.actorId,
6
+ actorType: row.actorType,
7
+ action: row.action,
8
+ targets: row.targets,
9
+ metadata: row.metadata,
10
+ occurredAt: row.occurredAt.toISOString()
11
+ };
12
+ }
13
+ //#endregion
14
+ export { toWireAuditEvent };
15
+
16
+ //# sourceMappingURL=wire.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wire.js","names":[],"sources":["../../../../src/storage/domains/audit/wire.ts"],"sourcesContent":["import type { AuditActorType } from './actors.js';\nimport type { AuditEventRow, AuditTarget } from './base.js';\n\nexport interface WireAuditActor {\n id: string;\n name: string;\n avatarUrl?: string;\n}\n\n/** An `AuditEventRow` as the list route sends it: the date as ISO text, tenancy and request context left behind. */\nexport interface WireAuditEvent {\n id: string;\n actorId: string;\n actorType: AuditActorType;\n action: string;\n targets: AuditTarget[];\n metadata: Record<string, unknown>;\n occurredAt: string;\n}\n\nexport interface WireAuditPage {\n events: WireAuditEvent[];\n actors: Record<string, WireAuditActor>;\n nextCursor?: string;\n}\n\nexport function toWireAuditEvent(row: AuditEventRow): WireAuditEvent {\n return {\n id: row.id,\n actorId: row.actorId,\n actorType: row.actorType,\n action: row.action,\n targets: row.targets,\n metadata: row.metadata,\n occurredAt: row.occurredAt.toISOString(),\n };\n}\n"],"mappings":";AA0BA,SAAgB,iBAAiB,KAAoC;CACnE,OAAO;EACL,IAAI,IAAI;EACR,SAAS,IAAI;EACb,WAAW,IAAI;EACf,QAAQ,IAAI;EACZ,SAAS,IAAI;EACb,UAAU,IAAI;EACd,YAAY,IAAI,WAAW,YAAY;CACzC;AACF"}
@@ -1,13 +1,13 @@
1
1
  import type { IntegrationTools } from '../integrations/base.js';
2
2
  import type { FactoryTransitionService } from '../rules/transition-service.js';
3
- import type { AuditStorage } from '../storage/domains/audit/base.js';
3
+ import type { AuditRecorder } from '../storage/domains/audit/domain.js';
4
4
  import type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';
5
5
  import type { SupervisorScope } from './read-tools.js';
6
6
  interface SupervisorWriteDependencies {
7
7
  scope: SupervisorScope;
8
8
  userId: string;
9
9
  workItems: WorkItemsStorage;
10
- audit: AuditStorage;
10
+ audit: AuditRecorder;
11
11
  transitionService: FactoryTransitionService;
12
12
  reconcileAcceptanceLabels?: (input: {
13
13
  orgId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"write-tools.d.ts","sourceRoot":"","sources":["../../src/supervisor/write-tools.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAE/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC3F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,UAAU,2BAA2B;IACnC,KAAK,EAAE,eAAe,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,gBAAgB,CAAC;IAC5B,KAAK,EAAE,YAAY,CAAC;IACpB,iBAAiB,EAAE,wBAAwB,CAAC;IAC5C,yBAAyB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACpG,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,wBAAgB,iCAAiC,CAAC,IAAI,EAAE,2BAA2B,GAAG,gBAAgB,CA+LrG"}
1
+ {"version":3,"file":"write-tools.d.ts","sourceRoot":"","sources":["../../src/supervisor/write-tools.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAG/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC3F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,UAAU,2BAA2B;IACnC,KAAK,EAAE,eAAe,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,gBAAgB,CAAC;IAC5B,KAAK,EAAE,aAAa,CAAC;IACrB,iBAAiB,EAAE,wBAAwB,CAAC;IAC5C,yBAAyB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACpG,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,wBAAgB,iCAAiC,CAAC,IAAI,EAAE,2BAA2B,GAAG,gBAAgB,CA4LrG"}
@@ -95,8 +95,7 @@ function createFactorySupervisorWriteTools(deps) {
95
95
  id: workItemId
96
96
  });
97
97
  if (!item || item.factoryProjectId !== deps.scope.factoryProjectId) throw new Error("Work item not found.");
98
- const from = item.stages.length === 1 ? item.stages[0] : void 0;
99
- if (!from) throw new Error("The work item does not have one valid Factory stage.");
98
+ if (!(item.stages.length === 1 ? item.stages[0] : void 0)) throw new Error("The work item does not have one valid Factory stage.");
100
99
  const result = await deps.transitionService.transition({
101
100
  orgId: deps.scope.orgId,
102
101
  factoryProjectId: deps.scope.factoryProjectId,
@@ -114,21 +113,6 @@ function createFactorySupervisorWriteTools(deps) {
114
113
  },
115
114
  cause: "supervisor"
116
115
  });
117
- await audit(result.status === "accepted" ? "factory.work_item.stage_moved" : "factory.work_item.transition_rejected", {
118
- type: "work_item",
119
- id: workItemId
120
- }, result.status === "accepted" ? {
121
- from,
122
- to: result.stage,
123
- revision: result.revision,
124
- transitionId: result.transitionId
125
- } : {
126
- from,
127
- to: stage,
128
- code: result.code,
129
- reason: result.reason,
130
- transitionId: result.transitionId
131
- });
132
116
  if (result.status !== "accepted") throw new Error(`The transition was rejected (${result.code}): ${result.reason}`);
133
117
  return result;
134
118
  }
@@ -1 +1 @@
1
- {"version":3,"file":"write-tools.js","names":[],"sources":["../../src/supervisor/write-tools.ts"],"sourcesContent":["import { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport type { IntegrationTools } from '../integrations/base.js';\nimport type { FactoryTransitionService } from '../rules/transition-service.js';\nimport { BOARD_IDENTIFIER_RE, MAX_BOARD_IDENTIFIER_LENGTH } from '../rules/validation.js';\nimport type { AuditStorage } from '../storage/domains/audit/base.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { SupervisorScope } from './read-tools.js';\n\ninterface SupervisorWriteDependencies {\n scope: SupervisorScope;\n userId: string;\n workItems: WorkItemsStorage;\n audit: AuditStorage;\n transitionService: FactoryTransitionService;\n reconcileAcceptanceLabels?: (input: { orgId: string; factoryProjectId: string; item: WorkItemRow }) => Promise<void>;\n signalSession?: (input: { sessionId: string; message: string; userId: string }) => Promise<unknown>;\n now?: () => Date;\n}\n\nexport function createFactorySupervisorWriteTools(deps: SupervisorWriteDependencies): IntegrationTools {\n const now = deps.now ?? (() => new Date());\n const audit = async (action: string, target: { type: string; id: string }, metadata: Record<string, unknown> = {}) =>\n deps.audit.record({\n orgId: deps.scope.orgId,\n actorId: deps.userId,\n actorType: 'human',\n action,\n targets: [target],\n factoryProjectId: deps.scope.factoryProjectId,\n metadata: { ...metadata, cause: 'supervisor' },\n occurredAt: now(),\n });\n\n return {\n factory_retry_decision: createTool({\n id: 'factory_retry_decision',\n description: 'Retry one failed Factory decision after the person confirms the repair.',\n inputSchema: z.object({ decisionId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ decisionId }) => {\n const decision = await deps.workItems.retryDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n );\n if (!decision) throw new Error('The decision is not failed, no longer exists, or belongs to another factory.');\n await audit(\n 'factory.run.retry',\n { type: 'rule_decision', id: decisionId },\n { workItemId: decision.workItemId },\n );\n return { decisionId, status: decision.status, workItemId: decision.workItemId };\n },\n }),\n factory_dismiss_decision: createTool({\n id: 'factory_dismiss_decision',\n description: 'Dismiss one parked Factory proposal after the person confirms it is no longer wanted.',\n inputSchema: z.object({ decisionId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ decisionId }) => {\n const decision = await deps.workItems.dismissDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n );\n if (!decision)\n throw new Error('The decision is not proposed, no longer exists, or belongs to another factory.');\n await audit(\n 'factory.run.dismissed',\n { type: 'rule_decision', id: decisionId },\n { workItemId: decision.workItemId },\n );\n return { decisionId, status: decision.status, workItemId: decision.workItemId };\n },\n }),\n factory_resolve_proposal: createTool({\n id: 'factory_resolve_proposal',\n description: 'Approve or dismiss one proposed Factory decision after the person confirms the choice.',\n inputSchema: z.object({ decisionId: z.string().min(1), resolution: z.enum(['approve', 'dismiss']) }),\n requireApproval: true,\n execute: async ({ decisionId, resolution }) => {\n const decision =\n resolution === 'approve'\n ? await deps.workItems.approveDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n deps.userId,\n )\n : await deps.workItems.dismissDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n );\n if (!decision) throw new Error('The proposal is no longer open or belongs to another factory.');\n await audit(\n resolution === 'approve' ? 'factory.run.approved' : 'factory.run.dismissed',\n {\n type: 'rule_decision',\n id: decisionId,\n },\n { workItemId: decision.workItemId },\n );\n return { decisionId, resolution, status: decision.status, workItemId: decision.workItemId };\n },\n }),\n factory_transition_work_item: createTool({\n id: 'factory_transition_work_item',\n description: 'Move or accept one Factory work item after the person confirms the destination stage.',\n inputSchema: z.object({\n workItemId: z.string().min(1),\n stage: z.string().max(MAX_BOARD_IDENTIFIER_LENGTH).regex(BOARD_IDENTIFIER_RE),\n }),\n requireApproval: true,\n execute: async ({ workItemId, stage }) => {\n const item = await deps.workItems.get({ orgId: deps.scope.orgId, id: workItemId });\n if (!item || item.factoryProjectId !== deps.scope.factoryProjectId) throw new Error('Work item not found.');\n const from = item.stages.length === 1 ? item.stages[0] : undefined;\n if (!from) throw new Error('The work item does not have one valid Factory stage.');\n const result = await deps.transitionService.transition({\n orgId: deps.scope.orgId,\n factoryProjectId: deps.scope.factoryProjectId,\n workItemId,\n board: boardForWorkItem(item),\n stage,\n expectedRevision: item.revision,\n actor: { type: 'human', id: deps.userId },\n ingress: { type: 'human', identity: `supervisor:${deps.userId}:${workItemId}:${item.revision}:${stage}` },\n cause: 'supervisor',\n });\n await audit(\n result.status === 'accepted' ? 'factory.work_item.stage_moved' : 'factory.work_item.transition_rejected',\n { type: 'work_item', id: workItemId },\n result.status === 'accepted'\n ? { from, to: result.stage, revision: result.revision, transitionId: result.transitionId }\n : { from, to: stage, code: result.code, reason: result.reason, transitionId: result.transitionId },\n );\n if (result.status !== 'accepted') {\n throw new Error(`The transition was rejected (${result.code}): ${result.reason}`);\n }\n return result;\n },\n }),\n factory_reconcile_labels: createTool({\n id: 'factory_reconcile_labels',\n description: 'Reconcile stale acceptance labels for one accepted work item after the person confirms the repair.',\n inputSchema: z.object({ workItemId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ workItemId }) => {\n if (!deps.reconcileAcceptanceLabels) throw new Error('Acceptance-label reconciliation is unavailable.');\n const item = await deps.workItems.get({ orgId: deps.scope.orgId, id: workItemId });\n if (!item || item.factoryProjectId !== deps.scope.factoryProjectId) throw new Error('Work item not found.');\n if (!item.acceptedAt) throw new Error('The work item has not been accepted.');\n await deps.reconcileAcceptanceLabels({ ...deps.scope, item });\n await audit('factory.work_item.labels_reconciled', { type: 'work_item', id: workItemId });\n return { workItemId, reconciled: true };\n },\n }),\n factory_revoke_binding: createTool({\n id: 'factory_revoke_binding',\n description: 'Revoke an orphaned or stale run binding after a health finding and person confirmation.',\n inputSchema: z.object({ bindingId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ bindingId }) => {\n const binding = await deps.workItems.revokeRunBinding({\n orgId: deps.scope.orgId,\n factoryProjectId: deps.scope.factoryProjectId,\n bindingId,\n revokedAt: now(),\n });\n if (!binding) throw new Error('The binding is not active, no longer exists, or belongs to another factory.');\n await audit(\n 'factory.intake.binding_updated',\n { type: 'run_binding', id: bindingId },\n {\n workItemId: binding.workItemId,\n role: binding.role,\n status: 'revoked',\n },\n );\n return { bindingId, status: binding.status, workItemId: binding.workItemId, role: binding.role };\n },\n }),\n factory_signal_session: createTool({\n id: 'factory_signal_session',\n description: 'Send bounded guidance to a worker session after the person confirms the exact message.',\n inputSchema: z.object({ sessionId: z.string().min(1), message: z.string().trim().min(1).max(2000) }),\n requireApproval: true,\n execute: async ({ sessionId, message }) => {\n if (!deps.signalSession) throw new Error('Worker session signaling is unavailable.');\n const bindings = await deps.workItems.listRunBindings(deps.scope.orgId, deps.scope.factoryProjectId);\n const binding = bindings.find(row => row.sessionId === sessionId);\n if (!binding) throw new Error('The session does not belong to this factory.');\n await deps.signalSession({ sessionId, message, userId: deps.userId });\n await audit(\n 'factory.agent.signaled',\n { type: 'factory_session', id: sessionId },\n {\n workItemId: binding.workItemId,\n role: binding.role,\n },\n );\n return { sessionId, delivered: true, workItemId: binding.workItemId, role: binding.role };\n },\n }),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"write-tools.js","names":[],"sources":["../../src/supervisor/write-tools.ts"],"sourcesContent":["import { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\nimport { boardForWorkItem } from '../boards/index.js';\nimport type { IntegrationTools } from '../integrations/base.js';\nimport type { FactoryTransitionService } from '../rules/transition-service.js';\nimport { BOARD_IDENTIFIER_RE, MAX_BOARD_IDENTIFIER_LENGTH } from '../rules/validation.js';\nimport type { AuditAction } from '../storage/domains/audit/actions.js';\nimport type { AuditRecorder } from '../storage/domains/audit/domain.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { SupervisorScope } from './read-tools.js';\n\ninterface SupervisorWriteDependencies {\n scope: SupervisorScope;\n userId: string;\n workItems: WorkItemsStorage;\n audit: AuditRecorder;\n transitionService: FactoryTransitionService;\n reconcileAcceptanceLabels?: (input: { orgId: string; factoryProjectId: string; item: WorkItemRow }) => Promise<void>;\n signalSession?: (input: { sessionId: string; message: string; userId: string }) => Promise<unknown>;\n now?: () => Date;\n}\n\nexport function createFactorySupervisorWriteTools(deps: SupervisorWriteDependencies): IntegrationTools {\n const now = deps.now ?? (() => new Date());\n const audit = async (\n action: AuditAction,\n target: { type: string; id: string },\n metadata: Record<string, unknown> = {},\n ) =>\n deps.audit.record({\n orgId: deps.scope.orgId,\n actorId: deps.userId,\n actorType: 'human',\n action,\n targets: [target],\n factoryProjectId: deps.scope.factoryProjectId,\n metadata: { ...metadata, cause: 'supervisor' },\n occurredAt: now(),\n });\n\n return {\n factory_retry_decision: createTool({\n id: 'factory_retry_decision',\n description: 'Retry one failed Factory decision after the person confirms the repair.',\n inputSchema: z.object({ decisionId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ decisionId }) => {\n const decision = await deps.workItems.retryDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n );\n if (!decision) throw new Error('The decision is not failed, no longer exists, or belongs to another factory.');\n await audit(\n 'factory.run.retry',\n { type: 'rule_decision', id: decisionId },\n { workItemId: decision.workItemId },\n );\n return { decisionId, status: decision.status, workItemId: decision.workItemId };\n },\n }),\n factory_dismiss_decision: createTool({\n id: 'factory_dismiss_decision',\n description: 'Dismiss one parked Factory proposal after the person confirms it is no longer wanted.',\n inputSchema: z.object({ decisionId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ decisionId }) => {\n const decision = await deps.workItems.dismissDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n );\n if (!decision)\n throw new Error('The decision is not proposed, no longer exists, or belongs to another factory.');\n await audit(\n 'factory.run.dismissed',\n { type: 'rule_decision', id: decisionId },\n { workItemId: decision.workItemId },\n );\n return { decisionId, status: decision.status, workItemId: decision.workItemId };\n },\n }),\n factory_resolve_proposal: createTool({\n id: 'factory_resolve_proposal',\n description: 'Approve or dismiss one proposed Factory decision after the person confirms the choice.',\n inputSchema: z.object({ decisionId: z.string().min(1), resolution: z.enum(['approve', 'dismiss']) }),\n requireApproval: true,\n execute: async ({ decisionId, resolution }) => {\n const decision =\n resolution === 'approve'\n ? await deps.workItems.approveDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n deps.userId,\n )\n : await deps.workItems.dismissDeferredDecision(\n deps.scope.orgId,\n deps.scope.factoryProjectId,\n decisionId,\n now(),\n );\n if (!decision) throw new Error('The proposal is no longer open or belongs to another factory.');\n await audit(\n resolution === 'approve' ? 'factory.run.approved' : 'factory.run.dismissed',\n {\n type: 'rule_decision',\n id: decisionId,\n },\n { workItemId: decision.workItemId },\n );\n return { decisionId, resolution, status: decision.status, workItemId: decision.workItemId };\n },\n }),\n factory_transition_work_item: createTool({\n id: 'factory_transition_work_item',\n description: 'Move or accept one Factory work item after the person confirms the destination stage.',\n inputSchema: z.object({\n workItemId: z.string().min(1),\n stage: z.string().max(MAX_BOARD_IDENTIFIER_LENGTH).regex(BOARD_IDENTIFIER_RE),\n }),\n requireApproval: true,\n execute: async ({ workItemId, stage }) => {\n const item = await deps.workItems.get({ orgId: deps.scope.orgId, id: workItemId });\n if (!item || item.factoryProjectId !== deps.scope.factoryProjectId) throw new Error('Work item not found.');\n const from = item.stages.length === 1 ? item.stages[0] : undefined;\n if (!from) throw new Error('The work item does not have one valid Factory stage.');\n const result = await deps.transitionService.transition({\n orgId: deps.scope.orgId,\n factoryProjectId: deps.scope.factoryProjectId,\n workItemId,\n board: boardForWorkItem(item),\n stage,\n expectedRevision: item.revision,\n actor: { type: 'human', id: deps.userId },\n ingress: { type: 'human', identity: `supervisor:${deps.userId}:${workItemId}:${item.revision}:${stage}` },\n cause: 'supervisor',\n });\n if (result.status !== 'accepted') {\n throw new Error(`The transition was rejected (${result.code}): ${result.reason}`);\n }\n return result;\n },\n }),\n factory_reconcile_labels: createTool({\n id: 'factory_reconcile_labels',\n description: 'Reconcile stale acceptance labels for one accepted work item after the person confirms the repair.',\n inputSchema: z.object({ workItemId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ workItemId }) => {\n if (!deps.reconcileAcceptanceLabels) throw new Error('Acceptance-label reconciliation is unavailable.');\n const item = await deps.workItems.get({ orgId: deps.scope.orgId, id: workItemId });\n if (!item || item.factoryProjectId !== deps.scope.factoryProjectId) throw new Error('Work item not found.');\n if (!item.acceptedAt) throw new Error('The work item has not been accepted.');\n await deps.reconcileAcceptanceLabels({ ...deps.scope, item });\n await audit('factory.work_item.labels_reconciled', { type: 'work_item', id: workItemId });\n return { workItemId, reconciled: true };\n },\n }),\n factory_revoke_binding: createTool({\n id: 'factory_revoke_binding',\n description: 'Revoke an orphaned or stale run binding after a health finding and person confirmation.',\n inputSchema: z.object({ bindingId: z.string().min(1) }),\n requireApproval: true,\n execute: async ({ bindingId }) => {\n const binding = await deps.workItems.revokeRunBinding({\n orgId: deps.scope.orgId,\n factoryProjectId: deps.scope.factoryProjectId,\n bindingId,\n revokedAt: now(),\n });\n if (!binding) throw new Error('The binding is not active, no longer exists, or belongs to another factory.');\n await audit(\n 'factory.intake.binding_updated',\n { type: 'run_binding', id: bindingId },\n {\n workItemId: binding.workItemId,\n role: binding.role,\n status: 'revoked',\n },\n );\n return { bindingId, status: binding.status, workItemId: binding.workItemId, role: binding.role };\n },\n }),\n factory_signal_session: createTool({\n id: 'factory_signal_session',\n description: 'Send bounded guidance to a worker session after the person confirms the exact message.',\n inputSchema: z.object({ sessionId: z.string().min(1), message: z.string().trim().min(1).max(2000) }),\n requireApproval: true,\n execute: async ({ sessionId, message }) => {\n if (!deps.signalSession) throw new Error('Worker session signaling is unavailable.');\n const bindings = await deps.workItems.listRunBindings(deps.scope.orgId, deps.scope.factoryProjectId);\n const binding = bindings.find(row => row.sessionId === sessionId);\n if (!binding) throw new Error('The session does not belong to this factory.');\n await deps.signalSession({ sessionId, message, userId: deps.userId });\n await audit(\n 'factory.agent.signaled',\n { type: 'factory_session', id: sessionId },\n {\n workItemId: binding.workItemId,\n role: binding.role,\n },\n );\n return { sessionId, delivered: true, workItemId: binding.workItemId, role: binding.role };\n },\n }),\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/factory",
3
- "version": "0.14.0-alpha.2",
3
+ "version": "0.14.0-alpha.4",
4
4
  "description": "Mastra Software Factory module: the server core behind the Mastra Software Factory — storage domains, integrations, and surfaces for agent-powered software delivery",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -57,11 +57,11 @@
57
57
  "hono": "^4.12.8",
58
58
  "posthog-node": "^5.46.1",
59
59
  "zod": "^4.3.6",
60
- "@mastra/auth-workos": "1.6.5",
61
60
  "@mastra/auth-studio": "1.3.5",
61
+ "@mastra/auth-workos": "1.6.5",
62
+ "@mastra/code-sdk": "1.7.1-alpha.4",
62
63
  "@mastra/slack": "1.6.3",
63
- "@mastra/code-sdk": "1.7.1-alpha.2",
64
- "@mastra/core": "1.66.0-alpha.2"
64
+ "@mastra/core": "1.66.0-alpha.4"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@types/node": "22.20.1",
@@ -70,11 +70,11 @@
70
70
  "typescript": "^6.0.3",
71
71
  "typescript-eslint": "^8.57.0",
72
72
  "vitest": "4.1.10",
73
- "@internal/lint": "0.0.131",
74
73
  "@mastra/libsql": "1.22.5-alpha.1",
75
- "@mastra/pg": "1.24.0-alpha.2",
74
+ "@mastra/pg": "1.24.0-alpha.3",
76
75
  "@internal/types-builder": "0.0.106",
77
- "@internal/workspace": "0.0.3"
76
+ "@internal/workspace": "0.0.3",
77
+ "@internal/lint": "0.0.131"
78
78
  },
79
79
  "engines": {
80
80
  "node": ">=22.19.0"