@mastra/factory 0.14.0-alpha.2 → 0.14.0-alpha.3
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/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +8 -3
- package/dist/factory.js.map +1 -1
- package/dist/integrations/github/session-subscriptions.d.ts +0 -1
- package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
- package/dist/integrations/github/session-subscriptions.js +3 -16
- package/dist/integrations/github/session-subscriptions.js.map +1 -1
- package/dist/integrations/workos/integration.js +1 -1
- package/dist/integrations/workos/integration.js.map +1 -1
- package/dist/routes/contracts.d.ts +2 -2
- package/dist/routes/surface.d.ts +2 -2
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +2 -1
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +10 -87
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/dispatcher.d.ts +2 -0
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +29 -0
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/transition-service.d.ts +11 -0
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +72 -2
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/session/run-audit.d.ts +49 -0
- package/dist/session/run-audit.d.ts.map +1 -0
- package/dist/session/run-audit.js +99 -0
- package/dist/session/run-audit.js.map +1 -0
- package/dist/session/shell-commands.d.ts +5 -0
- package/dist/session/shell-commands.d.ts.map +1 -0
- package/dist/session/shell-commands.js +25 -0
- package/dist/session/shell-commands.js.map +1 -0
- package/dist/storage/domains/audit/actions.d.ts +24 -0
- package/dist/storage/domains/audit/actions.d.ts.map +1 -0
- package/dist/storage/domains/audit/actions.js +67 -0
- package/dist/storage/domains/audit/actions.js.map +1 -0
- package/dist/storage/domains/audit/actors.d.ts +5 -0
- package/dist/storage/domains/audit/actors.d.ts.map +1 -0
- package/dist/storage/domains/audit/actors.js +25 -0
- package/dist/storage/domains/audit/actors.js.map +1 -0
- package/dist/storage/domains/audit/agent-audit.d.ts +5 -7
- package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -1
- package/dist/storage/domains/audit/agent-audit.js +33 -24
- package/dist/storage/domains/audit/agent-audit.js.map +1 -1
- package/dist/storage/domains/audit/base.d.ts +28 -33
- package/dist/storage/domains/audit/base.d.ts.map +1 -1
- package/dist/storage/domains/audit/base.js +58 -31
- package/dist/storage/domains/audit/base.js.map +1 -1
- package/dist/storage/domains/audit/domain.d.ts +12 -9
- package/dist/storage/domains/audit/domain.d.ts.map +1 -1
- package/dist/storage/domains/audit/domain.js +32 -48
- package/dist/storage/domains/audit/domain.js.map +1 -1
- package/dist/storage/domains/audit/wire.d.ts +24 -0
- package/dist/storage/domains/audit/wire.d.ts.map +1 -0
- package/dist/storage/domains/audit/wire.js +16 -0
- package/dist/storage/domains/audit/wire.js.map +1 -0
- package/dist/supervisor/write-tools.d.ts +2 -2
- package/dist/supervisor/write-tools.d.ts.map +1 -1
- package/dist/supervisor/write-tools.js +1 -17
- package/dist/supervisor/write-tools.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,28 +1,27 @@
|
|
|
1
|
+
import { executableCommand, runsPullRequestCreate } from "../../../session/shell-commands.js";
|
|
1
2
|
//#region src/storage/domains/audit/agent-audit.ts
|
|
2
3
|
/** Match command-start positions while ignoring command text embedded in heredoc bodies. */
|
|
3
4
|
const GIT_COMMIT_RE = /(?:^|\n|;|&&|\|\|)\s*git\s+commit(?:\s|$)/;
|
|
4
5
|
const GIT_PUSH_RE = /(?:^|\n|;|&&|\|\|)\s*git\s+push(?:\s|$)/;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
return executableLines.join("\n");
|
|
6
|
+
/** `gh pr create` prints the new pull request URL alone on the last line, and nothing else does. */
|
|
7
|
+
const CREATED_PULL_REQUEST_URL_RE = /^https:\/\/\S+\/pull\/\d+$/;
|
|
8
|
+
/**
|
|
9
|
+
* The pull request `gh pr create` actually opened, or nothing. A command that
|
|
10
|
+
* printed no URL created no pull request: `--dry-run` prints a preview, `--web`
|
|
11
|
+
* prints a `/compare/` link, and a failure prints its exit code last.
|
|
12
|
+
*/
|
|
13
|
+
function createdPullRequestUrl(output) {
|
|
14
|
+
if (typeof output !== "string") return void 0;
|
|
15
|
+
const lastLine = output.trimEnd().split("\n").at(-1)?.trim();
|
|
16
|
+
return lastLine !== void 0 && CREATED_PULL_REQUEST_URL_RE.test(lastLine) ? lastLine : void 0;
|
|
18
17
|
}
|
|
19
18
|
/** Parse the branch from a plain `git push <remote> <branch>` invocation. */
|
|
20
19
|
function parsePushedBranch(command) {
|
|
21
20
|
return command.match(/(?:^|\n|;|&&|\|\|)\s*git\s+push\s+(?:-[^\s]+\s+)*([^\s;&|-][^\s;&|]*)\s+([^\s;&|-][^\s;&|]*)/)?.[2];
|
|
22
21
|
}
|
|
23
22
|
/**
|
|
24
|
-
* Detect externally-visible git side effects in a completed tool
|
|
25
|
-
* record `factory.agent.*` audit events for them. One command can emit
|
|
23
|
+
* Detect externally-visible git and GitHub side effects in a completed tool
|
|
24
|
+
* call and record `factory.agent.*` audit events for them. One command can emit
|
|
26
25
|
* multiple events (`git commit && git push` emits both). Never throws.
|
|
27
26
|
*/
|
|
28
27
|
async function observeAgentGitAction({ audit, toolContext }) {
|
|
@@ -30,16 +29,17 @@ async function observeAgentGitAction({ audit, toolContext }) {
|
|
|
30
29
|
if (toolContext.toolName !== "execute_command" || toolContext.error) return;
|
|
31
30
|
const rawCommand = toolContext.input?.command;
|
|
32
31
|
if (typeof rawCommand !== "string") return;
|
|
33
|
-
const command =
|
|
32
|
+
const command = executableCommand(rawCommand);
|
|
34
33
|
const worktreePath = toolContext.context.get("controller")?.scope;
|
|
34
|
+
const targets = worktreePath ? [{
|
|
35
|
+
type: "worktree",
|
|
36
|
+
id: worktreePath
|
|
37
|
+
}] : [];
|
|
35
38
|
if (GIT_COMMIT_RE.test(command)) await audit.emitAgent({
|
|
36
39
|
requestContext: toolContext.context,
|
|
37
40
|
input: {
|
|
38
41
|
action: "factory.agent.commit",
|
|
39
|
-
targets
|
|
40
|
-
type: "worktree",
|
|
41
|
-
id: worktreePath
|
|
42
|
-
}] : []
|
|
42
|
+
targets
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
45
|
if (GIT_PUSH_RE.test(command)) {
|
|
@@ -48,14 +48,23 @@ async function observeAgentGitAction({ audit, toolContext }) {
|
|
|
48
48
|
requestContext: toolContext.context,
|
|
49
49
|
input: {
|
|
50
50
|
action: "factory.agent.push",
|
|
51
|
-
targets
|
|
52
|
-
type: "worktree",
|
|
53
|
-
id: worktreePath
|
|
54
|
-
}] : [],
|
|
51
|
+
targets,
|
|
55
52
|
...branch ? { metadata: { branch } } : {}
|
|
56
53
|
}
|
|
57
54
|
});
|
|
58
55
|
}
|
|
56
|
+
const pullRequestUrl = runsPullRequestCreate(rawCommand) ? createdPullRequestUrl(toolContext.output) : void 0;
|
|
57
|
+
if (pullRequestUrl) await audit.emitAgent({
|
|
58
|
+
requestContext: toolContext.context,
|
|
59
|
+
input: {
|
|
60
|
+
action: "factory.agent.pr_opened",
|
|
61
|
+
targets: [{
|
|
62
|
+
type: "pull_request",
|
|
63
|
+
id: pullRequestUrl
|
|
64
|
+
}, ...targets],
|
|
65
|
+
metadata: { url: pullRequestUrl }
|
|
66
|
+
}
|
|
67
|
+
});
|
|
59
68
|
} catch (err) {
|
|
60
69
|
console.warn("[Audit] Failed to observe agent git action", { error: err instanceof Error ? err.message : String(err) });
|
|
61
70
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-audit.js","names":[],"sources":["../../../../src/storage/domains/audit/agent-audit.ts"],"sourcesContent":["/**\n *
|
|
1
|
+
{"version":3,"file":"agent-audit.js","names":[],"sources":["../../../../src/storage/domains/audit/agent-audit.ts"],"sourcesContent":["/**\n * Git and GitHub actions performed by agents inside runs never touch web routes,\n * so this observer detects their externally-visible side effects in the command\n * and delegates recording to the factory-owned audit domain.\n */\n\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\n\nimport { executableCommand, runsPullRequestCreate } from '../../../session/shell-commands.js';\nimport type { AuditAgentEmitter } from './domain.js';\n\ntype FactorySessionState = { factoryProjectId?: string; projectRepositoryId?: string };\n\ninterface ToolObserverContext {\n toolName: string;\n input: unknown;\n output?: unknown;\n error?: unknown;\n context: RequestContext;\n}\n\n/** Match command-start positions while ignoring command text embedded in heredoc bodies. */\nconst GIT_COMMIT_RE = /(?:^|\\n|;|&&|\\|\\|)\\s*git\\s+commit(?:\\s|$)/;\nconst GIT_PUSH_RE = /(?:^|\\n|;|&&|\\|\\|)\\s*git\\s+push(?:\\s|$)/;\n/** `gh pr create` prints the new pull request URL alone on the last line, and nothing else does. */\nconst CREATED_PULL_REQUEST_URL_RE = /^https:\\/\\/\\S+\\/pull\\/\\d+$/;\n\n/**\n * The pull request `gh pr create` actually opened, or nothing. A command that\n * printed no URL created no pull request: `--dry-run` prints a preview, `--web`\n * prints a `/compare/` link, and a failure prints its exit code last.\n */\nfunction createdPullRequestUrl(output: unknown): string | undefined {\n if (typeof output !== 'string') return undefined;\n const lastLine = output.trimEnd().split('\\n').at(-1)?.trim();\n return lastLine !== undefined && CREATED_PULL_REQUEST_URL_RE.test(lastLine) ? lastLine : undefined;\n}\n\n/** Parse the branch from a plain `git push <remote> <branch>` invocation. */\nfunction parsePushedBranch(command: string): string | undefined {\n const match = command.match(\n /(?:^|\\n|;|&&|\\|\\|)\\s*git\\s+push\\s+(?:-[^\\s]+\\s+)*([^\\s;&|-][^\\s;&|]*)\\s+([^\\s;&|-][^\\s;&|]*)/,\n );\n return match?.[2];\n}\n\n/**\n * Detect externally-visible git and GitHub side effects in a completed tool\n * call and record `factory.agent.*` audit events for them. One command can emit\n * multiple events (`git commit && git push` emits both). Never throws.\n */\nexport async function observeAgentGitAction({\n audit,\n toolContext,\n}: {\n audit: AuditAgentEmitter;\n toolContext: ToolObserverContext;\n}): Promise<void> {\n try {\n if (toolContext.toolName !== 'execute_command' || toolContext.error) return;\n const rawCommand = (toolContext.input as { command?: unknown } | undefined)?.command;\n if (typeof rawCommand !== 'string') return;\n const command = executableCommand(rawCommand);\n\n const controller = toolContext.context.get('controller') as\n | AgentControllerRequestContext<FactorySessionState>\n | undefined;\n const worktreePath = controller?.scope;\n const targets = worktreePath ? [{ type: 'worktree', id: worktreePath }] : [];\n\n if (GIT_COMMIT_RE.test(command)) {\n await audit.emitAgent({\n requestContext: toolContext.context,\n input: { action: 'factory.agent.commit', targets },\n });\n }\n\n if (GIT_PUSH_RE.test(command)) {\n const branch = parsePushedBranch(command);\n await audit.emitAgent({\n requestContext: toolContext.context,\n input: { action: 'factory.agent.push', targets, ...(branch ? { metadata: { branch } } : {}) },\n });\n }\n\n const pullRequestUrl = runsPullRequestCreate(rawCommand) ? createdPullRequestUrl(toolContext.output) : undefined;\n if (pullRequestUrl) {\n await audit.emitAgent({\n requestContext: toolContext.context,\n input: {\n action: 'factory.agent.pr_opened',\n targets: [{ type: 'pull_request', id: pullRequestUrl }, ...targets],\n metadata: { url: pullRequestUrl },\n },\n });\n }\n } catch (err) {\n console.warn('[Audit] Failed to observe agent git action', {\n error: err instanceof Error ? err.message : String(err),\n });\n }\n}\n"],"mappings":";;;AAuBA,MAAM,gBAAgB;AACtB,MAAM,cAAc;;AAEpB,MAAM,8BAA8B;;;;;;AAOpC,SAAS,sBAAsB,QAAqC;CAClE,IAAI,OAAO,WAAW,UAAU,OAAO,KAAA;CACvC,MAAM,WAAW,OAAO,QAAQ,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK;CAC3D,OAAO,aAAa,KAAA,KAAa,4BAA4B,KAAK,QAAQ,IAAI,WAAW,KAAA;AAC3F;;AAGA,SAAS,kBAAkB,SAAqC;CAI9D,OAHc,QAAQ,MACpB,8FAES,CAAC,GAAG;AACjB;;;;;;AAOA,eAAsB,sBAAsB,EAC1C,OACA,eAIgB;CAChB,IAAI;EACF,IAAI,YAAY,aAAa,qBAAqB,YAAY,OAAO;EACrE,MAAM,aAAc,YAAY,OAA6C;EAC7E,IAAI,OAAO,eAAe,UAAU;EACpC,MAAM,UAAU,kBAAkB,UAAU;EAK5C,MAAM,eAHa,YAAY,QAAQ,IAAI,YAGb,CAAC,EAAE;EACjC,MAAM,UAAU,eAAe,CAAC;GAAE,MAAM;GAAY,IAAI;EAAa,CAAC,IAAI,CAAC;EAE3E,IAAI,cAAc,KAAK,OAAO,GAC5B,MAAM,MAAM,UAAU;GACpB,gBAAgB,YAAY;GAC5B,OAAO;IAAE,QAAQ;IAAwB;GAAQ;EACnD,CAAC;EAGH,IAAI,YAAY,KAAK,OAAO,GAAG;GAC7B,MAAM,SAAS,kBAAkB,OAAO;GACxC,MAAM,MAAM,UAAU;IACpB,gBAAgB,YAAY;IAC5B,OAAO;KAAE,QAAQ;KAAsB;KAAS,GAAI,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;IAAG;GAC9F,CAAC;EACH;EAEA,MAAM,iBAAiB,sBAAsB,UAAU,IAAI,sBAAsB,YAAY,MAAM,IAAI,KAAA;EACvG,IAAI,gBACF,MAAM,MAAM,UAAU;GACpB,gBAAgB,YAAY;GAC5B,OAAO;IACL,QAAQ;IACR,SAAS,CAAC;KAAE,MAAM;KAAgB,IAAI;IAAe,GAAG,GAAG,OAAO;IAClE,UAAU,EAAE,KAAK,eAAe;GAClC;EACF,CAAC;CAEL,SAAS,KAAK;EACZ,QAAQ,KAAK,8CAA8C,EACzD,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EACxD,CAAC;CACH;AACF"}
|
|
@@ -2,41 +2,20 @@
|
|
|
2
2
|
* Factory audit events domain — the append-only "who did what, when" trail
|
|
3
3
|
* behind the software factory.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* unavailable.
|
|
5
|
+
* Rows are append-only: there is no update/delete API, and the table is the
|
|
6
|
+
* local source of truth even when the WorkOS Audit Logs mirror is unavailable.
|
|
7
|
+
* Tenancy is org-first, like `work_items`: `actor_id` records who acted but
|
|
8
|
+
* never scopes reads.
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
* and (usually) `factory_project_id`; `actor_id` records who acted but never
|
|
13
|
-
* scopes reads.
|
|
14
|
-
*
|
|
15
|
-
* v1 action taxonomy (register these in the WorkOS dashboard under
|
|
16
|
-
* Audit Logs → Events for the export mirror to accept them):
|
|
17
|
-
* - factory.work_item.created
|
|
18
|
-
* - factory.work_item.updated
|
|
19
|
-
* - factory.work_item.stage_moved
|
|
20
|
-
* - factory.work_item.deleted
|
|
21
|
-
* - factory.run.started
|
|
22
|
-
* - factory.worktree.created
|
|
23
|
-
* - factory.worktree.deleted
|
|
24
|
-
* - factory.git.commit
|
|
25
|
-
* - factory.git.push
|
|
26
|
-
* - factory.git.pr_opened
|
|
27
|
-
* - factory.intake.config_updated
|
|
28
|
-
*
|
|
29
|
-
* v1.1 adds agent-level actions (also register these in WorkOS):
|
|
30
|
-
* - factory.agent.commit
|
|
31
|
-
* - factory.agent.push
|
|
32
|
-
* - factory.agent.pr_opened
|
|
10
|
+
* The actions the trail holds are listed in `./actions.ts`.
|
|
33
11
|
*
|
|
34
12
|
* Agent events carry `actor_type = 'agent'` with `actor_id = 'agent:<threadId>'`
|
|
35
13
|
* and `metadata.startedBy = <userId>` chaining accountability back to the human
|
|
36
|
-
* whose message drove the run.
|
|
14
|
+
* whose message drove the run. Rule-driven events carry `actor_type = 'system'`.
|
|
37
15
|
*/
|
|
38
16
|
import { FactoryStorageDomain } from '@mastra/core/storage';
|
|
39
17
|
import type { CollectionSchema } from '@mastra/core/storage';
|
|
18
|
+
import type { AuditActorType } from './actors.js';
|
|
40
19
|
/** What an audit event acted on (WorkOS Audit Logs target shape). */
|
|
41
20
|
export interface AuditTarget {
|
|
42
21
|
/** Target kind, e.g. 'work_item', 'worktree', 'issue', 'pull_request'. */
|
|
@@ -46,8 +25,19 @@ export interface AuditTarget {
|
|
|
46
25
|
/** Human-readable label (work-item title, branch name...). */
|
|
47
26
|
name?: string;
|
|
48
27
|
}
|
|
49
|
-
|
|
50
|
-
|
|
28
|
+
export type { AuditActorType } from './actors.js';
|
|
29
|
+
/** Display name and avatar of a human actor, stamped at record time because MastraAuthStudio cannot resolve users by id. */
|
|
30
|
+
export interface AuditActorProfileInput {
|
|
31
|
+
name?: string;
|
|
32
|
+
avatarUrl?: string;
|
|
33
|
+
}
|
|
34
|
+
export declare const ACTOR_PROFILE_METADATA_KEY = "__actorProfile";
|
|
35
|
+
export declare function auditAgentName(modeId: string): string;
|
|
36
|
+
export declare function auditActorProfile(user: {
|
|
37
|
+
name?: string;
|
|
38
|
+
email?: string;
|
|
39
|
+
avatarUrl?: string;
|
|
40
|
+
} | undefined): AuditActorProfileInput | undefined;
|
|
51
41
|
/** Request context captured alongside the event. */
|
|
52
42
|
export interface AuditContext {
|
|
53
43
|
/** Client IP (first hop of `x-forwarded-for`) when available. */
|
|
@@ -78,13 +68,14 @@ export interface AuditEventRow {
|
|
|
78
68
|
context: AuditContext;
|
|
79
69
|
occurredAt: Date;
|
|
80
70
|
}
|
|
81
|
-
export interface RecordAuditEventInput {
|
|
71
|
+
export interface RecordAuditEventInput<Action extends string = string> {
|
|
72
|
+
idempotencyKey?: string;
|
|
82
73
|
orgId: string;
|
|
83
74
|
actorId: string;
|
|
84
75
|
/** Who performed the action; defaults to 'human'. */
|
|
85
76
|
actorType?: AuditActorType;
|
|
86
|
-
|
|
87
|
-
action:
|
|
77
|
+
actorProfile?: AuditActorProfileInput;
|
|
78
|
+
action: Action;
|
|
88
79
|
targets: AuditTarget[];
|
|
89
80
|
metadata?: Record<string, unknown>;
|
|
90
81
|
factoryProjectId?: string;
|
|
@@ -133,6 +124,10 @@ export declare class AuditStorage extends FactoryStorageDomain {
|
|
|
133
124
|
dangerouslyClearAll(): Promise<void>;
|
|
134
125
|
/** Append one audit event. Throws on failure — swallow-on-failure lives in the caller. */
|
|
135
126
|
record(input: RecordAuditEventInput): Promise<AuditEventRow>;
|
|
127
|
+
recordOnce(input: RecordAuditEventInput): Promise<{
|
|
128
|
+
event: AuditEventRow;
|
|
129
|
+
created: boolean;
|
|
130
|
+
}>;
|
|
136
131
|
/** List an org's audit events newest-first with keyset pagination. */
|
|
137
132
|
list(input: ListAuditEventsInput): Promise<AuditEventPage>;
|
|
138
133
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/audit/base.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/audit/base.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,42 +1,34 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
import { FactoryStorageDomain, UniqueViolationError } from "@mastra/core/storage";
|
|
2
3
|
//#region src/storage/domains/audit/base.ts
|
|
3
4
|
/**
|
|
4
5
|
* Factory audit events domain — the append-only "who did what, when" trail
|
|
5
6
|
* behind the software factory.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* unavailable.
|
|
8
|
+
* Rows are append-only: there is no update/delete API, and the table is the
|
|
9
|
+
* local source of truth even when the WorkOS Audit Logs mirror is unavailable.
|
|
10
|
+
* Tenancy is org-first, like `work_items`: `actor_id` records who acted but
|
|
11
|
+
* never scopes reads.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
* and (usually) `factory_project_id`; `actor_id` records who acted but never
|
|
15
|
-
* scopes reads.
|
|
16
|
-
*
|
|
17
|
-
* v1 action taxonomy (register these in the WorkOS dashboard under
|
|
18
|
-
* Audit Logs → Events for the export mirror to accept them):
|
|
19
|
-
* - factory.work_item.created
|
|
20
|
-
* - factory.work_item.updated
|
|
21
|
-
* - factory.work_item.stage_moved
|
|
22
|
-
* - factory.work_item.deleted
|
|
23
|
-
* - factory.run.started
|
|
24
|
-
* - factory.worktree.created
|
|
25
|
-
* - factory.worktree.deleted
|
|
26
|
-
* - factory.git.commit
|
|
27
|
-
* - factory.git.push
|
|
28
|
-
* - factory.git.pr_opened
|
|
29
|
-
* - factory.intake.config_updated
|
|
30
|
-
*
|
|
31
|
-
* v1.1 adds agent-level actions (also register these in WorkOS):
|
|
32
|
-
* - factory.agent.commit
|
|
33
|
-
* - factory.agent.push
|
|
34
|
-
* - factory.agent.pr_opened
|
|
13
|
+
* The actions the trail holds are listed in `./actions.ts`.
|
|
35
14
|
*
|
|
36
15
|
* Agent events carry `actor_type = 'agent'` with `actor_id = 'agent:<threadId>'`
|
|
37
16
|
* and `metadata.startedBy = <userId>` chaining accountability back to the human
|
|
38
|
-
* whose message drove the run.
|
|
17
|
+
* whose message drove the run. Rule-driven events carry `actor_type = 'system'`.
|
|
39
18
|
*/
|
|
19
|
+
const ACTOR_PROFILE_METADATA_KEY = "__actorProfile";
|
|
20
|
+
function auditAgentName(modeId) {
|
|
21
|
+
return `${modeId} agent`;
|
|
22
|
+
}
|
|
23
|
+
function auditActorProfile(user) {
|
|
24
|
+
const name = user?.name?.trim() || user?.email?.trim();
|
|
25
|
+
const avatarUrl = user?.avatarUrl?.trim();
|
|
26
|
+
if (!name && !avatarUrl) return void 0;
|
|
27
|
+
return {
|
|
28
|
+
...name ? { name } : {},
|
|
29
|
+
...avatarUrl ? { avatarUrl } : {}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
40
32
|
/** Metadata is a bounded summary — never full payloads, never secrets. */
|
|
41
33
|
const MAX_METADATA_JSON_LENGTH = 4096;
|
|
42
34
|
const DEFAULT_PAGE_SIZE = 50;
|
|
@@ -142,13 +134,48 @@ var AuditStorage = class extends FactoryStorageDomain {
|
|
|
142
134
|
}
|
|
143
135
|
/** Append one audit event. Throws on failure — swallow-on-failure lives in the caller. */
|
|
144
136
|
async record(input) {
|
|
137
|
+
if (input.idempotencyKey) return (await this.recordOnce(input)).event;
|
|
138
|
+
return this.#insert(input);
|
|
139
|
+
}
|
|
140
|
+
async recordOnce(input) {
|
|
141
|
+
if (!input.idempotencyKey) throw new Error("An audit idempotency key is required");
|
|
142
|
+
const hash = createHash("sha256").update(JSON.stringify([
|
|
143
|
+
input.orgId,
|
|
144
|
+
input.factoryProjectId ?? null,
|
|
145
|
+
input.action,
|
|
146
|
+
input.idempotencyKey
|
|
147
|
+
])).digest("hex");
|
|
148
|
+
const id = `${hash.slice(0, 8)}-${hash.slice(8, 12)}-8${hash.slice(13, 16)}-a${hash.slice(17, 20)}-${hash.slice(20, 32)}`;
|
|
149
|
+
try {
|
|
150
|
+
return {
|
|
151
|
+
event: await this.#insert(input, id),
|
|
152
|
+
created: true
|
|
153
|
+
};
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (!(error instanceof UniqueViolationError)) throw error;
|
|
156
|
+
const existing = await this.#db.findOne("audit_events", {
|
|
157
|
+
id,
|
|
158
|
+
org_id: input.orgId
|
|
159
|
+
});
|
|
160
|
+
if (!existing) throw error;
|
|
161
|
+
return {
|
|
162
|
+
event: toRow(existing),
|
|
163
|
+
created: false
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async #insert(input, id) {
|
|
145
168
|
return toRow(await this.#db.insertOne("audit_events", {
|
|
169
|
+
...id ? { id } : {},
|
|
146
170
|
org_id: input.orgId,
|
|
147
171
|
actor_id: input.actorId,
|
|
148
172
|
actor_type: input.actorType ?? "human",
|
|
149
173
|
action: input.action,
|
|
150
174
|
targets: input.targets,
|
|
151
|
-
metadata: boundAuditMetadata(input.
|
|
175
|
+
metadata: boundAuditMetadata(input.actorProfile ? {
|
|
176
|
+
...input.metadata,
|
|
177
|
+
[ACTOR_PROFILE_METADATA_KEY]: input.actorProfile
|
|
178
|
+
} : input.metadata),
|
|
152
179
|
factory_project_id: input.factoryProjectId ?? null,
|
|
153
180
|
project_repository_id: input.projectRepositoryId ?? null,
|
|
154
181
|
context: input.context ?? {},
|
|
@@ -178,6 +205,6 @@ var AuditStorage = class extends FactoryStorageDomain {
|
|
|
178
205
|
}
|
|
179
206
|
};
|
|
180
207
|
//#endregion
|
|
181
|
-
export { AUDIT_EVENTS_SCHEMA, AuditStorage, boundAuditMetadata, clampAuditLimit, decodeAuditCursor, encodeAuditCursor };
|
|
208
|
+
export { ACTOR_PROFILE_METADATA_KEY, AUDIT_EVENTS_SCHEMA, AuditStorage, auditActorProfile, auditAgentName, boundAuditMetadata, clampAuditLimit, decodeAuditCursor, encodeAuditCursor };
|
|
182
209
|
|
|
183
210
|
//# sourceMappingURL=base.js.map
|
|
@@ -1 +1 @@
|
|
|
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.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
|
+
{"version":3,"file":"base.js","names":["#db","#insert"],"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 * Rows are append-only: there is no update/delete API, and the table is the\n * local source of truth even when the WorkOS Audit Logs mirror is unavailable.\n * Tenancy is org-first, like `work_items`: `actor_id` records who acted but\n * never scopes reads.\n *\n * The actions the trail holds are listed in `./actions.ts`.\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. Rule-driven events carry `actor_type = 'system'`.\n */\n\nimport { createHash } from 'node:crypto';\n\nimport { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, CollectionWhere, FactoryStorageOps } from '@mastra/core/storage';\n\nimport type { AuditActorType } from './actors.js';\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\nexport type { AuditActorType } from './actors.js';\n\n/** Display name and avatar of a human actor, stamped at record time because MastraAuthStudio cannot resolve users by id. */\nexport interface AuditActorProfileInput {\n name?: string;\n avatarUrl?: string;\n}\n\nexport const ACTOR_PROFILE_METADATA_KEY = '__actorProfile';\n\nexport function auditAgentName(modeId: string): string {\n return `${modeId} agent`;\n}\n\nexport function auditActorProfile(\n user: { name?: string; email?: string; avatarUrl?: string } | undefined,\n): AuditActorProfileInput | 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\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<Action extends string = string> {\n idempotencyKey?: string;\n orgId: string;\n actorId: string;\n /** Who performed the action; defaults to 'human'. */\n actorType?: AuditActorType;\n actorProfile?: AuditActorProfileInput;\n action: Action;\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 if (input.idempotencyKey) return (await this.recordOnce(input)).event;\n return this.#insert(input);\n }\n\n async recordOnce(input: RecordAuditEventInput): Promise<{ event: AuditEventRow; created: boolean }> {\n if (!input.idempotencyKey) throw new Error('An audit idempotency key is required');\n const hash = createHash('sha256')\n .update(JSON.stringify([input.orgId, input.factoryProjectId ?? null, input.action, input.idempotencyKey]))\n .digest('hex');\n const id = `${hash.slice(0, 8)}-${hash.slice(8, 12)}-8${hash.slice(13, 16)}-a${hash.slice(17, 20)}-${hash.slice(20, 32)}`;\n try {\n return { event: await this.#insert(input, id), created: true };\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n const existing = await this.#db.findOne<AuditEventDbRow>('audit_events', { id, org_id: input.orgId });\n if (!existing) throw error;\n return { event: toRow(existing), created: false };\n }\n }\n\n async #insert(input: RecordAuditEventInput, id?: string): Promise<AuditEventRow> {\n const inserted = await this.#db.insertOne<AuditEventDbRow>('audit_events', {\n ...(id ? { id } : {}),\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(\n input.actorProfile ? { ...input.metadata, [ACTOR_PROFILE_METADATA_KEY]: input.actorProfile } : input.metadata,\n ),\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":";;;;;;;;;;;;;;;;;;AAyCA,MAAa,6BAA6B;AAE1C,SAAgB,eAAe,QAAwB;CACrD,OAAO,GAAG,OAAO;AACnB;AAEA,SAAgB,kBACd,MACoC;CACpC,MAAM,OAAO,MAAM,MAAM,KAAK,KAAK,MAAM,OAAO,KAAK;CACrD,MAAM,YAAY,MAAM,WAAW,KAAK;CACxC,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;;AAuEA,MAAM,2BAA2B;AAEjC,MAAM,oBAAoB;AAC1B,MAAM,gBAAgB;;AAGtB,SAAgB,mBAAmB,UAAwE;CACzG,IAAI,CAAC,UAAU,OAAO,CAAC;CACvB,IAAI;EACF,IAAI,KAAK,UAAU,QAAQ,CAAC,CAAC,UAAU,0BAA0B,OAAO;CAC1E,QAAQ;EACN,OAAO,EAAE,WAAW,KAAK;CAC3B;CACA,OAAO,EAAE,WAAW,KAAK;AAC3B;;AAGA,SAAgB,gBAAgB,OAAmC;CAEjE,OAAO,KAAK,IAAI,KAAK,IADF,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,KAAK,MAAM,KAAK,IAAI,mBACxD,CAAC,GAAG,aAAa;AACxD;;AAGA,SAAgB,kBAAkB,KAA4B;CAC5D,OAAO,GAAG,IAAI,WAAW,YAAY,EAAE,GAAG,IAAI;AAChD;;AAGA,SAAgB,kBAAkB,QAA8D;CAC9F,MAAM,MAAM,OAAO,YAAY,GAAG;CAClC,IAAI,OAAO,GAAG,OAAO,KAAA;CACrB,MAAM,aAAa,IAAI,KAAK,OAAO,MAAM,GAAG,GAAG,CAAC;CAChD,MAAM,KAAK,OAAO,MAAM,MAAM,CAAC;CAC/B,IAAI,OAAO,MAAM,WAAW,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,KAAA;CACtD,OAAO;EAAE;EAAY;CAAG;AAC1B;AAEA,MAAa,sBAAwC;CACnD,MAAM;CACN,SAAS;EACP,IAAI,EAAE,MAAM,UAAU;EACtB,QAAQ,EAAE,MAAM,OAAO;EACvB,UAAU,EAAE,MAAM,OAAO;EACzB,YAAY;GAAE,MAAM;GAAQ,SAAS;EAAQ;EAC7C,QAAQ,EAAE,MAAM,OAAO;EACvB,SAAS,EAAE,MAAM,OAAO;EACxB,UAAU,EAAE,MAAM,OAAO;EACzB,oBAAoB;GAAE,MAAM;GAAQ,UAAU;EAAK;EACnD,uBAAuB;GAAE,MAAM;GAAQ,UAAU;EAAK;EACtD,SAAS,EAAE,MAAM,OAAO;EACxB,aAAa,EAAE,MAAM,YAAY;CACnC;CACA,SAAS,CACP;EAAE,MAAM;EAAiC,SAAS,CAAC,UAAU,aAAa;CAAE,GAC5E;EAAE,MAAM;EAAyC,SAAS;GAAC;GAAU;GAAsB;EAAa;CAAE,CAC5G;AACF;AAiBA,SAAS,MAAM,IAAoC;CACjD,OAAO;EACL,IAAI,GAAG;EACP,OAAO,GAAG;EACV,SAAS,GAAG;EACZ,WAAW,GAAG;EACd,QAAQ,GAAG;EACX,SAAS,GAAG;EACZ,UAAU,GAAG;EACb,kBAAkB,GAAG;EACrB,qBAAqB,GAAG;EACxB,SAAS,GAAG;EACZ,YAAY,GAAG;CACjB;AACF;;;;;;AAOA,IAAa,eAAb,cAAkC,qBAAqB;CACrD,cAAc;EACZ,MAAM,OAAO;CACf;CAEA,MAAM,OAAsB;EAC1B,MAAM,KAAK,kBAAkB,CAAC,mBAAmB,CAAC;CACpD;CAEA,MAAM,sBAAqC;EACzC,MAAM,KAAK,IAAI,WAAW,gBAAgB,CAAC,CAAC;CAC9C;CAEA,IAAIA,MAAyB;EAC3B,OAAO,KAAK;CACd;;CAGA,MAAM,OAAO,OAAsD;EACjE,IAAI,MAAM,gBAAgB,QAAQ,MAAM,KAAK,WAAW,KAAK,EAAA,CAAG;EAChE,OAAO,KAAKC,QAAQ,KAAK;CAC3B;CAEA,MAAM,WAAW,OAAmF;EAClG,IAAI,CAAC,MAAM,gBAAgB,MAAM,IAAI,MAAM,sCAAsC;EACjF,MAAM,OAAO,WAAW,QAAQ,CAAC,CAC9B,OAAO,KAAK,UAAU;GAAC,MAAM;GAAO,MAAM,oBAAoB;GAAM,MAAM;GAAQ,MAAM;EAAc,CAAC,CAAC,CAAC,CACzG,OAAO,KAAK;EACf,MAAM,KAAK,GAAG,KAAK,MAAM,GAAG,CAAC,EAAE,GAAG,KAAK,MAAM,GAAG,EAAE,EAAE,IAAI,KAAK,MAAM,IAAI,EAAE,EAAE,IAAI,KAAK,MAAM,IAAI,EAAE,EAAE,GAAG,KAAK,MAAM,IAAI,EAAE;EACtH,IAAI;GACF,OAAO;IAAE,OAAO,MAAM,KAAKA,QAAQ,OAAO,EAAE;IAAG,SAAS;GAAK;EAC/D,SAAS,OAAO;GACd,IAAI,EAAE,iBAAiB,uBAAuB,MAAM;GACpD,MAAM,WAAW,MAAM,KAAKD,IAAI,QAAyB,gBAAgB;IAAE;IAAI,QAAQ,MAAM;GAAM,CAAC;GACpG,IAAI,CAAC,UAAU,MAAM;GACrB,OAAO;IAAE,OAAO,MAAM,QAAQ;IAAG,SAAS;GAAM;EAClD;CACF;CAEA,MAAMC,QAAQ,OAA8B,IAAqC;EAgB/E,OAAO,MAAM,MAfU,KAAKD,IAAI,UAA2B,gBAAgB;GACzE,GAAI,KAAK,EAAE,GAAG,IAAI,CAAC;GACnB,QAAQ,MAAM;GACd,UAAU,MAAM;GAChB,YAAY,MAAM,aAAa;GAC/B,QAAQ,MAAM;GACd,SAAS,MAAM;GACf,UAAU,mBACR,MAAM,eAAe;IAAE,GAAG,MAAM;KAAW,6BAA6B,MAAM;GAAa,IAAI,MAAM,QACvG;GACA,oBAAoB,MAAM,oBAAoB;GAC9C,uBAAuB,MAAM,uBAAuB;GACpD,SAAS,MAAM,WAAW,CAAC;GAC3B,aAAa,MAAM,8BAAc,IAAI,KAAK;EAC5C,CAAC,CACoB;CACvB;;CAGA,MAAM,KAAK,OAAsD;EAC/D,MAAM,QAAQ,gBAAgB,MAAM,KAAK;EAEzC,MAAM,QAAyB,EAAE,QAAQ,MAAM,MAAM;EACrD,IAAI,MAAM,kBAAkB,MAAM,qBAAqB,MAAM;EAC7D,IAAI,MAAM,WAAW,MAAM,QAAQ,SAAS,GAAG,MAAM,SAAS,EAAE,IAAI,MAAM,QAAQ;EAClF,IAAI,MAAM,SAAS,MAAM,WAAW,MAAM;EAE1C,MAAM,SAAS,MAAM,SAAS,kBAAkB,MAAM,MAAM,IAAI,KAAA;EAEhE,MAAM,OAAO,MAAM,KAAKA,IAAI,SAA0B,gBAAgB,OAAO;GAC3E,SAAS,CACP,CAAC,eAAe,MAAM,GACtB,CAAC,MAAM,MAAM,CACf;GACA,OAAO,QAAQ;GACf,GAAI,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,OAAO,YAAY,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC;EACzE,CAAC;EAED,MAAM,SAAS,KAAK,MAAM,GAAG,KAAK,CAAC,CAAC,IAAI,KAAK;EAC7C,MAAM,UAAU,KAAK,SAAS;EAC9B,MAAM,OAAO,OAAO,OAAO,SAAS;EACpC,OAAO;GACL;GACA,GAAI,WAAW,OAAO,EAAE,YAAY,kBAAkB,IAAI,EAAE,IAAI,CAAC;EACnE;CACF;AACF"}
|
|
@@ -3,16 +3,17 @@ import type { ApiRoute, IUserProvider } from '@mastra/core/server';
|
|
|
3
3
|
import type { Context } from 'hono';
|
|
4
4
|
import type { RouteAuth } from '../../../routes/route.js';
|
|
5
5
|
import type { FactoryProjectsStorage } from '../projects/base.js';
|
|
6
|
-
import type {
|
|
6
|
+
import type { AuditAction } from './actions.js';
|
|
7
|
+
import type { AuditActorProfileInput, AuditContext, AuditEventPage, AuditEventRow, AuditStorage, AuditTarget, ListAuditEventsInput, RecordAuditEventInput } from './base.js';
|
|
7
8
|
export interface EmitAuditInput {
|
|
8
|
-
action:
|
|
9
|
+
action: AuditAction;
|
|
9
10
|
factoryProjectId?: string;
|
|
10
11
|
projectRepositoryId?: string;
|
|
11
12
|
targets: AuditTarget[];
|
|
12
13
|
metadata?: Record<string, unknown>;
|
|
13
14
|
}
|
|
14
15
|
export interface EmitAgentAuditInput {
|
|
15
|
-
action:
|
|
16
|
+
action: AuditAction;
|
|
16
17
|
targets: AuditTarget[];
|
|
17
18
|
metadata?: Record<string, unknown>;
|
|
18
19
|
}
|
|
@@ -28,6 +29,8 @@ export interface AuditAgentEmitter {
|
|
|
28
29
|
input: EmitAgentAuditInput;
|
|
29
30
|
}): Promise<void>;
|
|
30
31
|
}
|
|
32
|
+
/** Records with an explicit actor, for the paths that have no request: rule transitions, run starts, run ends, supervisor tools. */
|
|
33
|
+
export type AuditRecorder = Pick<AuditDomain, 'record'>;
|
|
31
34
|
/** Best-effort destination for locally persisted audit events (e.g. an integration's audit log). */
|
|
32
35
|
export interface AuditSink {
|
|
33
36
|
id: string;
|
|
@@ -35,11 +38,6 @@ export interface AuditSink {
|
|
|
35
38
|
event: AuditEventRow;
|
|
36
39
|
}): Promise<void>;
|
|
37
40
|
}
|
|
38
|
-
export interface AuditActorProfile {
|
|
39
|
-
id: string;
|
|
40
|
-
name: string;
|
|
41
|
-
avatarUrl?: string;
|
|
42
|
-
}
|
|
43
41
|
export interface AuditDomainOptions {
|
|
44
42
|
auth: RouteAuth;
|
|
45
43
|
/** Audit storage domain handle. */
|
|
@@ -57,11 +55,16 @@ export interface AuditDomainOptions {
|
|
|
57
55
|
} | undefined;
|
|
58
56
|
}
|
|
59
57
|
export declare function auditRequestContext(c: Context): AuditContext;
|
|
58
|
+
/** Who a browser request is and where it came from: the pair every funnel row carries. */
|
|
59
|
+
export declare function auditRequestOrigin(c: Context): {
|
|
60
|
+
actorProfile: AuditActorProfileInput | undefined;
|
|
61
|
+
context: AuditContext;
|
|
62
|
+
};
|
|
60
63
|
/** Factory-owned audit behavior backed by the audit storage domain. */
|
|
61
64
|
export declare class AuditDomain implements AuditEmitter, AuditAgentEmitter {
|
|
62
65
|
#private;
|
|
63
66
|
constructor({ auth, audit, projects, users, sinks, agentTenant }: AuditDomainOptions);
|
|
64
|
-
record(input: RecordAuditEventInput): Promise<AuditEventRow | null>;
|
|
67
|
+
record(input: RecordAuditEventInput<AuditAction>): Promise<AuditEventRow | null>;
|
|
65
68
|
list(input: ListAuditEventsInput): Promise<AuditEventPage>;
|
|
66
69
|
emit({ context, input }: {
|
|
67
70
|
context: Context;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/audit/domain.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEnE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAGpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/audit/domain.ts"],"names":[],"mappings":"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"}
|