@agent-native/core 0.71.0 → 0.72.0
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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +38 -0
- package/corpus/core/docs/content/actions.md +23 -0
- package/corpus/core/docs/content/audit-log.md +111 -0
- package/corpus/core/package.json +2 -1
- package/corpus/core/src/action.ts +80 -1
- package/corpus/core/src/agent/production-agent.ts +27 -0
- package/corpus/core/src/agent/run-store.ts +14 -0
- package/corpus/core/src/application-state/store.ts +5 -0
- package/corpus/core/src/audit/actions/get-audit-event.ts +23 -0
- package/corpus/core/src/audit/actions/list-audit-events.ts +65 -0
- package/corpus/core/src/audit/cleanup-job.ts +100 -0
- package/corpus/core/src/audit/config.ts +91 -0
- package/corpus/core/src/audit/index.ts +43 -0
- package/corpus/core/src/audit/record.ts +143 -0
- package/corpus/core/src/audit/redact.ts +109 -0
- package/corpus/core/src/audit/store.ts +244 -0
- package/corpus/core/src/audit/types.ts +125 -0
- package/corpus/core/src/chat-threads/store.ts +10 -0
- package/corpus/core/src/client/AssistantChat.tsx +6 -0
- package/corpus/core/src/client/chat/repo-helpers.ts +38 -0
- package/corpus/core/src/db/client.ts +4 -0
- package/corpus/core/src/db/widen-columns.ts +75 -0
- package/corpus/core/src/mcp/build-server.ts +1 -0
- package/corpus/core/src/oauth-tokens/store.ts +6 -0
- package/corpus/core/src/provider-api/custom-registry.ts +8 -0
- package/corpus/core/src/resources/store.ts +10 -0
- package/corpus/core/src/scripts/runner.ts +6 -3
- package/corpus/core/src/server/action-discovery.ts +6 -0
- package/corpus/core/src/server/action-routes.ts +1 -0
- package/corpus/core/src/server/auth.ts +5 -0
- package/corpus/core/src/server/core-routes-plugin.ts +14 -0
- package/corpus/core/src/server/security-headers.ts +9 -6
- package/corpus/core/src/settings/store.ts +6 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/audit-log/SKILL.md +93 -0
- package/corpus/core/src/usage/store.ts +6 -0
- package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +60 -6
- package/corpus/templates/calendar/changelog/2026-06-23-added-a-heads-up-explaining-google-s-app-not-verified-screen.md +6 -0
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +20 -43
- package/corpus/templates/clips/app/components/recorder/recording-toolbar.tsx +5 -1
- package/corpus/templates/clips/app/lib/countdown-audio-cue.ts +3 -51
- package/corpus/templates/clips/changelog/2026-06-23-public-clips-now-play-inline-in-slack-connect-a-workspac.md +6 -0
- package/corpus/templates/clips/chrome-extension/PERMISSIONS.md +72 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -10
- package/corpus/templates/clips/chrome-extension/src/background.ts +34 -1
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +138 -11
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +50 -33
- package/corpus/templates/clips/chrome-extension/src/overlay.css +11 -27
- package/corpus/templates/clips/chrome-extension/src/overlay.html +7 -1
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +33 -38
- package/corpus/templates/clips/chrome-extension/src/popup.ts +6 -24
- package/corpus/templates/clips/chrome-extension/vite.config.ts +5 -0
- package/corpus/templates/clips/server/lib/media-permissions.ts +5 -1
- package/corpus/templates/clips/shared/recording-audio.ts +62 -0
- package/corpus/templates/clips/shared/recording-core.ts +94 -0
- package/dist/action.d.ts +31 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +45 -1
- package/dist/action.js.map +1 -1
- package/dist/agent/context-xray/schema.d.ts +1 -1
- package/dist/agent/observational-memory/schema.d.ts +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +28 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +14 -0
- package/dist/agent/run-store.js.map +1 -1
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +5 -0
- package/dist/application-state/store.js.map +1 -1
- package/dist/audit/actions/get-audit-event.d.ts +11 -0
- package/dist/audit/actions/get-audit-event.d.ts.map +1 -0
- package/dist/audit/actions/get-audit-event.js +22 -0
- package/dist/audit/actions/get-audit-event.js.map +1 -0
- package/dist/audit/actions/list-audit-events.d.ts +22 -0
- package/dist/audit/actions/list-audit-events.d.ts.map +1 -0
- package/dist/audit/actions/list-audit-events.js +61 -0
- package/dist/audit/actions/list-audit-events.js.map +1 -0
- package/dist/audit/cleanup-job.d.ts +12 -0
- package/dist/audit/cleanup-job.d.ts.map +1 -0
- package/dist/audit/cleanup-job.js +93 -0
- package/dist/audit/cleanup-job.js.map +1 -0
- package/dist/audit/config.d.ts +30 -0
- package/dist/audit/config.d.ts.map +1 -0
- package/dist/audit/config.js +65 -0
- package/dist/audit/config.js.map +1 -0
- package/dist/audit/index.d.ts +13 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +6 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/record.d.ts +24 -0
- package/dist/audit/record.d.ts.map +1 -0
- package/dist/audit/record.js +106 -0
- package/dist/audit/record.js.map +1 -0
- package/dist/audit/redact.d.ts +28 -0
- package/dist/audit/redact.d.ts.map +1 -0
- package/dist/audit/redact.js +109 -0
- package/dist/audit/redact.js.map +1 -0
- package/dist/audit/store.d.ts +14 -0
- package/dist/audit/store.d.ts.map +1 -0
- package/dist/audit/store.js +219 -0
- package/dist/audit/store.js.map +1 -0
- package/dist/audit/types.d.ts +114 -0
- package/dist/audit/types.d.ts.map +1 -0
- package/dist/audit/types.js +15 -0
- package/dist/audit/types.js.map +1 -0
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +10 -0
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +6 -1
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/chat/repo-helpers.d.ts +16 -0
- package/dist/client/chat/repo-helpers.d.ts.map +1 -1
- package/dist/client/chat/repo-helpers.js +40 -0
- package/dist/client/chat/repo-helpers.js.map +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +3 -0
- package/dist/db/client.js.map +1 -1
- package/dist/db/widen-columns.d.ts +39 -0
- package/dist/db/widen-columns.d.ts.map +1 -0
- package/dist/db/widen-columns.js +73 -0
- package/dist/db/widen-columns.js.map +1 -0
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +1 -0
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/oauth-tokens/store.d.ts.map +1 -1
- package/dist/oauth-tokens/store.js +6 -0
- package/dist/oauth-tokens/store.js.map +1 -1
- package/dist/provider-api/custom-registry.d.ts.map +1 -1
- package/dist/provider-api/custom-registry.js +8 -0
- package/dist/provider-api/custom-registry.js.map +1 -1
- package/dist/resources/store.d.ts.map +1 -1
- package/dist/resources/store.js +9 -0
- package/dist/resources/store.js.map +1 -1
- package/dist/scripts/runner.js +4 -3
- package/dist/scripts/runner.js.map +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +6 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +1 -0
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +5 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +13 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/security-headers.d.ts +8 -5
- package/dist/server/security-headers.d.ts.map +1 -1
- package/dist/server/security-headers.js +9 -6
- package/dist/server/security-headers.js.map +1 -1
- package/dist/settings/store.d.ts.map +1 -1
- package/dist/settings/store.js +6 -0
- package/dist/settings/store.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/audit-log/SKILL.md +93 -0
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +5 -0
- package/dist/usage/store.js.map +1 -1
- package/docs/content/actions.md +23 -0
- package/docs/content/audit-log.md +111 -0
- package/package.json +2 -1
- package/src/templates/workspace-core/.agents/skills/audit-log/SKILL.md +93 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit-log retention job.
|
|
3
|
+
*
|
|
4
|
+
* Periodically purges `agent_audit_log` rows older than the configured horizon
|
|
5
|
+
* so the log doesn't grow unbounded. Retention is configurable via
|
|
6
|
+
* `AGENT_NATIVE_AUDIT_RETENTION_DAYS` (default: 365 days — audit trails are kept
|
|
7
|
+
* far longer than sampled traces). Setting it to `0` disables the purge (keep
|
|
8
|
+
* forever). Mirrors observability/cleanup-job.ts.
|
|
9
|
+
*
|
|
10
|
+
* Runs once on startup after a short delay, then on a 24-hour interval. Timers
|
|
11
|
+
* are unref'd so they never keep the process alive on their own.
|
|
12
|
+
*/
|
|
13
|
+
import { deleteOldAuditEvents } from "./store.js";
|
|
14
|
+
const DEFAULT_RETENTION_DAYS = 365;
|
|
15
|
+
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
16
|
+
const STARTUP_DELAY_MS = 5 * 60 * 1000;
|
|
17
|
+
let _cleanupTimer = null;
|
|
18
|
+
let _intervalTimer = null;
|
|
19
|
+
function resolveRetentionDays() {
|
|
20
|
+
const raw = process.env.AGENT_NATIVE_AUDIT_RETENTION_DAYS;
|
|
21
|
+
if (raw === undefined || raw === null || raw === "") {
|
|
22
|
+
return DEFAULT_RETENTION_DAYS;
|
|
23
|
+
}
|
|
24
|
+
const parsed = Number.parseInt(raw, 10);
|
|
25
|
+
if (!Number.isFinite(parsed) || parsed < 0)
|
|
26
|
+
return DEFAULT_RETENTION_DAYS;
|
|
27
|
+
return parsed;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Run the audit cleanup once. Returns the deleted row count, or null when
|
|
31
|
+
* retention is disabled (`AGENT_NATIVE_AUDIT_RETENTION_DAYS=0`).
|
|
32
|
+
*/
|
|
33
|
+
export async function runAuditCleanupOnce() {
|
|
34
|
+
const days = resolveRetentionDays();
|
|
35
|
+
if (days === 0)
|
|
36
|
+
return null;
|
|
37
|
+
const cutoff = Date.now() - days * ONE_DAY_MS;
|
|
38
|
+
return deleteOldAuditEvents(cutoff);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Start the recurring audit-cleanup job. Idempotent — calling more than once is
|
|
42
|
+
* a no-op while a previous schedule is active. Returns a stop function.
|
|
43
|
+
*/
|
|
44
|
+
export function startAuditCleanupJob() {
|
|
45
|
+
if (_cleanupTimer || _intervalTimer)
|
|
46
|
+
return stopAuditCleanupJob;
|
|
47
|
+
const days = resolveRetentionDays();
|
|
48
|
+
if (days === 0) {
|
|
49
|
+
if (process.env.DEBUG)
|
|
50
|
+
// eslint-disable-next-line no-console
|
|
51
|
+
console.log("[audit] Audit cleanup disabled (AGENT_NATIVE_AUDIT_RETENTION_DAYS=0)");
|
|
52
|
+
return () => { };
|
|
53
|
+
}
|
|
54
|
+
const tick = () => {
|
|
55
|
+
runAuditCleanupOnce()
|
|
56
|
+
.then((deleted) => {
|
|
57
|
+
if (deleted == null)
|
|
58
|
+
return;
|
|
59
|
+
if (process.env.DEBUG) {
|
|
60
|
+
// eslint-disable-next-line no-console
|
|
61
|
+
console.log(`[audit] Audit cleanup purged ${deleted} rows (retention=${days}d)`);
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
.catch((err) => {
|
|
65
|
+
// eslint-disable-next-line no-console
|
|
66
|
+
console.error("[audit] Audit cleanup failed:", err?.message ?? err);
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
_cleanupTimer = setTimeout(() => {
|
|
70
|
+
_cleanupTimer = null;
|
|
71
|
+
tick();
|
|
72
|
+
_intervalTimer = setInterval(tick, ONE_DAY_MS);
|
|
73
|
+
if (typeof _intervalTimer.unref === "function")
|
|
74
|
+
_intervalTimer.unref();
|
|
75
|
+
}, STARTUP_DELAY_MS);
|
|
76
|
+
if (typeof _cleanupTimer.unref === "function")
|
|
77
|
+
_cleanupTimer.unref();
|
|
78
|
+
if (process.env.DEBUG)
|
|
79
|
+
// eslint-disable-next-line no-console
|
|
80
|
+
console.log(`[audit] Audit cleanup scheduled (retention=${days}d, daily)`);
|
|
81
|
+
return stopAuditCleanupJob;
|
|
82
|
+
}
|
|
83
|
+
export function stopAuditCleanupJob() {
|
|
84
|
+
if (_cleanupTimer) {
|
|
85
|
+
clearTimeout(_cleanupTimer);
|
|
86
|
+
_cleanupTimer = null;
|
|
87
|
+
}
|
|
88
|
+
if (_intervalTimer) {
|
|
89
|
+
clearInterval(_intervalTimer);
|
|
90
|
+
_intervalTimer = null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=cleanup-job.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup-job.js","sourceRoot":"","sources":["../../src/audit/cleanup-job.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Audit-log retention job.\n *\n * Periodically purges `agent_audit_log` rows older than the configured horizon\n * so the log doesn't grow unbounded. Retention is configurable via\n * `AGENT_NATIVE_AUDIT_RETENTION_DAYS` (default: 365 days — audit trails are kept\n * far longer than sampled traces). Setting it to `0` disables the purge (keep\n * forever). Mirrors observability/cleanup-job.ts.\n *\n * Runs once on startup after a short delay, then on a 24-hour interval. Timers\n * are unref'd so they never keep the process alive on their own.\n */\nimport { deleteOldAuditEvents } from \"./store.js\";\n\nconst DEFAULT_RETENTION_DAYS = 365;\nconst ONE_DAY_MS = 24 * 60 * 60 * 1000;\nconst STARTUP_DELAY_MS = 5 * 60 * 1000;\n\nlet _cleanupTimer: NodeJS.Timeout | null = null;\nlet _intervalTimer: NodeJS.Timeout | null = null;\n\nfunction resolveRetentionDays(): number {\n const raw = process.env.AGENT_NATIVE_AUDIT_RETENTION_DAYS;\n if (raw === undefined || raw === null || raw === \"\") {\n return DEFAULT_RETENTION_DAYS;\n }\n const parsed = Number.parseInt(raw, 10);\n if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_RETENTION_DAYS;\n return parsed;\n}\n\n/**\n * Run the audit cleanup once. Returns the deleted row count, or null when\n * retention is disabled (`AGENT_NATIVE_AUDIT_RETENTION_DAYS=0`).\n */\nexport async function runAuditCleanupOnce(): Promise<number | null> {\n const days = resolveRetentionDays();\n if (days === 0) return null;\n const cutoff = Date.now() - days * ONE_DAY_MS;\n return deleteOldAuditEvents(cutoff);\n}\n\n/**\n * Start the recurring audit-cleanup job. Idempotent — calling more than once is\n * a no-op while a previous schedule is active. Returns a stop function.\n */\nexport function startAuditCleanupJob(): () => void {\n if (_cleanupTimer || _intervalTimer) return stopAuditCleanupJob;\n const days = resolveRetentionDays();\n if (days === 0) {\n if (process.env.DEBUG)\n // eslint-disable-next-line no-console\n console.log(\n \"[audit] Audit cleanup disabled (AGENT_NATIVE_AUDIT_RETENTION_DAYS=0)\",\n );\n return () => {};\n }\n\n const tick = () => {\n runAuditCleanupOnce()\n .then((deleted) => {\n if (deleted == null) return;\n if (process.env.DEBUG) {\n // eslint-disable-next-line no-console\n console.log(\n `[audit] Audit cleanup purged ${deleted} rows (retention=${days}d)`,\n );\n }\n })\n .catch((err) => {\n // eslint-disable-next-line no-console\n console.error(\"[audit] Audit cleanup failed:\", err?.message ?? err);\n });\n };\n\n _cleanupTimer = setTimeout(() => {\n _cleanupTimer = null;\n tick();\n _intervalTimer = setInterval(tick, ONE_DAY_MS);\n if (typeof _intervalTimer.unref === \"function\") _intervalTimer.unref();\n }, STARTUP_DELAY_MS);\n if (typeof _cleanupTimer.unref === \"function\") _cleanupTimer.unref();\n\n if (process.env.DEBUG)\n // eslint-disable-next-line no-console\n console.log(`[audit] Audit cleanup scheduled (retention=${days}d, daily)`);\n\n return stopAuditCleanupJob;\n}\n\nexport function stopAuditCleanupJob(): void {\n if (_cleanupTimer) {\n clearTimeout(_cleanupTimer);\n _cleanupTimer = null;\n }\n if (_intervalTimer) {\n clearInterval(_intervalTimer);\n _intervalTimer = null;\n }\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure (no-DB) audit configuration helpers.
|
|
3
|
+
*
|
|
4
|
+
* Kept free of any database / store import so `action.ts` can statically
|
|
5
|
+
* import them without pulling the DB client into every bundle that touches an
|
|
6
|
+
* action. The DB-touching recorder lives in `record.ts`, which `action.ts`
|
|
7
|
+
* loads lazily via dynamic import on first audited call.
|
|
8
|
+
*/
|
|
9
|
+
import type { ActionAuditConfig, AuditActorKind, AuditStatus } from "./types.js";
|
|
10
|
+
/** Normalize a raw `audit` option into a config object (or undefined). */
|
|
11
|
+
export declare function normalizeAuditConfig(raw: unknown): ActionAuditConfig | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Whether to attach the audit wrapper to an action at definition time. Decided
|
|
14
|
+
* from the action's resolved `readOnly` flag plus its config — the action's
|
|
15
|
+
* *name* isn't known here (it's the registry key), so name-based skipping
|
|
16
|
+
* happens later in `shouldRecordAudit`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveAuditAttach(config: ActionAuditConfig | undefined, readOnly: boolean | undefined): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Final, name-aware decision made at record time. Explicit `enabled: true`
|
|
21
|
+
* overrides the high-frequency denylist; otherwise denylisted names are
|
|
22
|
+
* dropped.
|
|
23
|
+
*/
|
|
24
|
+
export declare function shouldRecordAudit(config: ActionAuditConfig | undefined, actionName: string): boolean;
|
|
25
|
+
/** Derive the actor kind from the invocation surface + resolved identity. */
|
|
26
|
+
export declare function deriveActorKind(caller: string | undefined, actorEmail: string | undefined | null): AuditActorKind;
|
|
27
|
+
/** Whether the whole subsystem is disabled via env. */
|
|
28
|
+
export declare function isAuditDisabled(): boolean;
|
|
29
|
+
export type { AuditStatus };
|
|
30
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/audit/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,WAAW,EACZ,MAAM,YAAY,CAAC;AAwBpB,0EAA0E;AAC1E,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,OAAO,GACX,iBAAiB,GAAG,SAAS,CAG/B;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,iBAAiB,GAAG,SAAS,EACrC,QAAQ,EAAE,OAAO,GAAG,SAAS,GAC5B,OAAO,CAMT;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,iBAAiB,GAAG,SAAS,EACrC,UAAU,EAAE,MAAM,GACjB,OAAO,CAKT;AAED,6EAA6E;AAC7E,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GACpC,cAAc,CAGhB;AAED,uDAAuD;AACvD,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED,YAAY,EAAE,WAAW,EAAE,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-frequency / ephemeral framework actions that are not meaningful audit
|
|
3
|
+
* events. They mutate UI/agent-context state many times per session; auditing
|
|
4
|
+
* each one would flood the log without recording a real user-facing change.
|
|
5
|
+
*
|
|
6
|
+
* An action can still force itself on with `audit: { enabled: true }` —
|
|
7
|
+
* explicit config always wins over this denylist (see `shouldRecordAudit`).
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_SKIP_ACTIONS = new Set([
|
|
10
|
+
"context-pin",
|
|
11
|
+
"context-evict",
|
|
12
|
+
"context-restore",
|
|
13
|
+
"context-report",
|
|
14
|
+
"context-manifest-get",
|
|
15
|
+
"change-appearance",
|
|
16
|
+
"toggle-demo-mode",
|
|
17
|
+
]);
|
|
18
|
+
/** Name patterns for high-frequency state-sync actions, skipped by default. */
|
|
19
|
+
const DEFAULT_SKIP_PATTERN = /(application-state|app-state|set-state|view-screen|navigate|poll)/i;
|
|
20
|
+
/** Normalize a raw `audit` option into a config object (or undefined). */
|
|
21
|
+
export function normalizeAuditConfig(raw) {
|
|
22
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
23
|
+
return undefined;
|
|
24
|
+
return raw;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Whether to attach the audit wrapper to an action at definition time. Decided
|
|
28
|
+
* from the action's resolved `readOnly` flag plus its config — the action's
|
|
29
|
+
* *name* isn't known here (it's the registry key), so name-based skipping
|
|
30
|
+
* happens later in `shouldRecordAudit`.
|
|
31
|
+
*/
|
|
32
|
+
export function resolveAuditAttach(config, readOnly) {
|
|
33
|
+
if (config && typeof config.enabled === "boolean")
|
|
34
|
+
return config.enabled;
|
|
35
|
+
// Read-only actions are not audited unless they opt in via `onRead`.
|
|
36
|
+
if (readOnly === true)
|
|
37
|
+
return config?.onRead === true;
|
|
38
|
+
// Everything else mutates — audit by default.
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Final, name-aware decision made at record time. Explicit `enabled: true`
|
|
43
|
+
* overrides the high-frequency denylist; otherwise denylisted names are
|
|
44
|
+
* dropped.
|
|
45
|
+
*/
|
|
46
|
+
export function shouldRecordAudit(config, actionName) {
|
|
47
|
+
if (config && config.enabled === true)
|
|
48
|
+
return true;
|
|
49
|
+
if (DEFAULT_SKIP_ACTIONS.has(actionName))
|
|
50
|
+
return false;
|
|
51
|
+
if (DEFAULT_SKIP_PATTERN.test(actionName))
|
|
52
|
+
return false;
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
/** Derive the actor kind from the invocation surface + resolved identity. */
|
|
56
|
+
export function deriveActorKind(caller, actorEmail) {
|
|
57
|
+
if (caller === "tool")
|
|
58
|
+
return "agent";
|
|
59
|
+
return actorEmail ? "human" : "system";
|
|
60
|
+
}
|
|
61
|
+
/** Whether the whole subsystem is disabled via env. */
|
|
62
|
+
export function isAuditDisabled() {
|
|
63
|
+
return process.env.AGENT_NATIVE_AUDIT_ENABLED === "false";
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/audit/config.ts"],"names":[],"mappings":"AAcA;;;;;;;GAOG;AACH,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAS;IAC3C,aAAa;IACb,eAAe;IACf,iBAAiB;IACjB,gBAAgB;IAChB,sBAAsB;IACtB,mBAAmB;IACnB,kBAAkB;CACnB,CAAC,CAAC;AAEH,+EAA+E;AAC/E,MAAM,oBAAoB,GACxB,oEAAoE,CAAC;AAEvE,0EAA0E;AAC1E,MAAM,UAAU,oBAAoB,CAClC,GAAY;IAEZ,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAC5E,OAAO,GAAwB,CAAC;AAClC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAqC,EACrC,QAA6B;IAE7B,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC;IACzE,qEAAqE;IACrE,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,8CAA8C;IAC9C,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAqC,EACrC,UAAkB;IAElB,IAAI,MAAM,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACnD,IAAI,oBAAoB,CAAC,GAAG,CAAC,UAAU,CAAC;QAAE,OAAO,KAAK,CAAC;IACvD,IAAI,oBAAoB,CAAC,IAAI,CAAC,UAAU,CAAC;QAAE,OAAO,KAAK,CAAC;IACxD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,eAAe,CAC7B,MAA0B,EAC1B,UAAqC;IAErC,IAAI,MAAM,KAAK,MAAM;QAAE,OAAO,OAAO,CAAC;IACtC,OAAO,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;AACzC,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,eAAe;IAC7B,OAAO,OAAO,CAAC,GAAG,CAAC,0BAA0B,KAAK,OAAO,CAAC;AAC5D,CAAC","sourcesContent":["/**\n * Pure (no-DB) audit configuration helpers.\n *\n * Kept free of any database / store import so `action.ts` can statically\n * import them without pulling the DB client into every bundle that touches an\n * action. The DB-touching recorder lives in `record.ts`, which `action.ts`\n * loads lazily via dynamic import on first audited call.\n */\nimport type {\n ActionAuditConfig,\n AuditActorKind,\n AuditStatus,\n} from \"./types.js\";\n\n/**\n * High-frequency / ephemeral framework actions that are not meaningful audit\n * events. They mutate UI/agent-context state many times per session; auditing\n * each one would flood the log without recording a real user-facing change.\n *\n * An action can still force itself on with `audit: { enabled: true }` —\n * explicit config always wins over this denylist (see `shouldRecordAudit`).\n */\nconst DEFAULT_SKIP_ACTIONS = new Set<string>([\n \"context-pin\",\n \"context-evict\",\n \"context-restore\",\n \"context-report\",\n \"context-manifest-get\",\n \"change-appearance\",\n \"toggle-demo-mode\",\n]);\n\n/** Name patterns for high-frequency state-sync actions, skipped by default. */\nconst DEFAULT_SKIP_PATTERN =\n /(application-state|app-state|set-state|view-screen|navigate|poll)/i;\n\n/** Normalize a raw `audit` option into a config object (or undefined). */\nexport function normalizeAuditConfig(\n raw: unknown,\n): ActionAuditConfig | undefined {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return undefined;\n return raw as ActionAuditConfig;\n}\n\n/**\n * Whether to attach the audit wrapper to an action at definition time. Decided\n * from the action's resolved `readOnly` flag plus its config — the action's\n * *name* isn't known here (it's the registry key), so name-based skipping\n * happens later in `shouldRecordAudit`.\n */\nexport function resolveAuditAttach(\n config: ActionAuditConfig | undefined,\n readOnly: boolean | undefined,\n): boolean {\n if (config && typeof config.enabled === \"boolean\") return config.enabled;\n // Read-only actions are not audited unless they opt in via `onRead`.\n if (readOnly === true) return config?.onRead === true;\n // Everything else mutates — audit by default.\n return true;\n}\n\n/**\n * Final, name-aware decision made at record time. Explicit `enabled: true`\n * overrides the high-frequency denylist; otherwise denylisted names are\n * dropped.\n */\nexport function shouldRecordAudit(\n config: ActionAuditConfig | undefined,\n actionName: string,\n): boolean {\n if (config && config.enabled === true) return true;\n if (DEFAULT_SKIP_ACTIONS.has(actionName)) return false;\n if (DEFAULT_SKIP_PATTERN.test(actionName)) return false;\n return true;\n}\n\n/** Derive the actor kind from the invocation surface + resolved identity. */\nexport function deriveActorKind(\n caller: string | undefined,\n actorEmail: string | undefined | null,\n): AuditActorKind {\n if (caller === \"tool\") return \"agent\";\n return actorEmail ? \"human\" : \"system\";\n}\n\n/** Whether the whole subsystem is disabled via env. */\nexport function isAuditDisabled(): boolean {\n return process.env.AGENT_NATIVE_AUDIT_ENABLED === \"false\";\n}\n\nexport type { AuditStatus };\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework audit log — a durable, complete, access-scoped, append-only record
|
|
3
|
+
* of who mutated what app data, when, from where, and (for the agent) in which
|
|
4
|
+
* run. Distinct from observability (sampled telemetry) and tracking
|
|
5
|
+
* (fire-and-forget analytics). Capture is automatic at the `defineAction` seam.
|
|
6
|
+
*/
|
|
7
|
+
export type { ActionAuditConfig, AuditActorKind, AuditCallMeta, AuditEvent, AuditQueryFilters, AuditStatus, AuditTarget, AuditVisibility, } from "./types.js";
|
|
8
|
+
export { deriveActorKind, isAuditDisabled, normalizeAuditConfig, resolveAuditAttach, shouldRecordAudit, } from "./config.js";
|
|
9
|
+
export { redactArgsToJson } from "./redact.js";
|
|
10
|
+
export { ensureAuditTables, insertAuditEvent, queryAuditEvents, getAuditEventById, deleteOldAuditEvents, type AuditReadScope, } from "./store.js";
|
|
11
|
+
export { recordActionAudit } from "./record.js";
|
|
12
|
+
export { runAuditCleanupOnce, startAuditCleanupJob, stopAuditCleanupJob, } from "./cleanup-job.js";
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/audit/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,YAAY,EACV,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,UAAU,EACV,iBAAiB,EACjB,WAAW,EACX,WAAW,EACX,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { deriveActorKind, isAuditDisabled, normalizeAuditConfig, resolveAuditAttach, shouldRecordAudit, } from "./config.js";
|
|
2
|
+
export { redactArgsToJson } from "./redact.js";
|
|
3
|
+
export { ensureAuditTables, insertAuditEvent, queryAuditEvents, getAuditEventById, deleteOldAuditEvents, } from "./store.js";
|
|
4
|
+
export { recordActionAudit } from "./record.js";
|
|
5
|
+
export { runAuditCleanupOnce, startAuditCleanupJob, stopAuditCleanupJob, } from "./cleanup-job.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/audit/index.ts"],"names":[],"mappings":"AAiBA,OAAO,EACL,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,GAErB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC","sourcesContent":["/**\n * Framework audit log — a durable, complete, access-scoped, append-only record\n * of who mutated what app data, when, from where, and (for the agent) in which\n * run. Distinct from observability (sampled telemetry) and tracking\n * (fire-and-forget analytics). Capture is automatic at the `defineAction` seam.\n */\nexport type {\n ActionAuditConfig,\n AuditActorKind,\n AuditCallMeta,\n AuditEvent,\n AuditQueryFilters,\n AuditStatus,\n AuditTarget,\n AuditVisibility,\n} from \"./types.js\";\n\nexport {\n deriveActorKind,\n isAuditDisabled,\n normalizeAuditConfig,\n resolveAuditAttach,\n shouldRecordAudit,\n} from \"./config.js\";\n\nexport { redactArgsToJson } from \"./redact.js\";\n\nexport {\n ensureAuditTables,\n insertAuditEvent,\n queryAuditEvents,\n getAuditEventById,\n deleteOldAuditEvents,\n type AuditReadScope,\n} from \"./store.js\";\n\nexport { recordActionAudit } from \"./record.js\";\n\nexport {\n runAuditCleanupOnce,\n startAuditCleanupJob,\n stopAuditCleanupJob,\n} from \"./cleanup-job.js\";\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ActionAuditConfig, AuditStatus } from "./types.js";
|
|
2
|
+
/** Minimal view of the action run context the recorder needs. */
|
|
3
|
+
export interface AuditRunContextLike {
|
|
4
|
+
actionName?: string;
|
|
5
|
+
caller?: string;
|
|
6
|
+
userEmail?: string;
|
|
7
|
+
orgId?: string | null;
|
|
8
|
+
threadId?: string;
|
|
9
|
+
turnId?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface RecordActionAuditInput {
|
|
12
|
+
config: ActionAuditConfig | undefined;
|
|
13
|
+
args: unknown;
|
|
14
|
+
ctx: AuditRunContextLike | undefined;
|
|
15
|
+
status: AuditStatus;
|
|
16
|
+
result?: unknown;
|
|
17
|
+
error?: unknown;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Record one audit event. Resolves the actor, target, ownership (for scoped
|
|
21
|
+
* reads), and redacted inputs, then appends a row. Never throws.
|
|
22
|
+
*/
|
|
23
|
+
export declare function recordActionAudit(input: RecordActionAuditInput): Promise<void>;
|
|
24
|
+
//# sourceMappingURL=record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../src/audit/record.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACV,iBAAiB,EAGjB,WAAW,EAEZ,MAAM,YAAY,CAAC;AAEpB,iEAAiE;AACjE,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACtC,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACrC,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AA0CD;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAqDf"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit capture entry point, called from the `defineAction` audit wrapper after
|
|
3
|
+
* an action runs (success or error). Best-effort: any failure here is swallowed
|
|
4
|
+
* so auditing never breaks the action it observes.
|
|
5
|
+
*
|
|
6
|
+
* This module touches the DB (`store.js`), so `action.ts` loads it lazily via
|
|
7
|
+
* dynamic import on the first audited call — keeping the DB client out of every
|
|
8
|
+
* bundle that merely defines actions.
|
|
9
|
+
*/
|
|
10
|
+
import { deriveActorKind, isAuditDisabled, shouldRecordAudit, } from "./config.js";
|
|
11
|
+
import { redactArgsToJson } from "./redact.js";
|
|
12
|
+
import { insertAuditEvent } from "./store.js";
|
|
13
|
+
function errorCode(error) {
|
|
14
|
+
if (!error)
|
|
15
|
+
return null;
|
|
16
|
+
if (typeof error === "object") {
|
|
17
|
+
const e = error;
|
|
18
|
+
if (typeof e.errorCode === "string")
|
|
19
|
+
return e.errorCode;
|
|
20
|
+
if (typeof e.code === "string")
|
|
21
|
+
return e.code;
|
|
22
|
+
if (typeof e.name === "string")
|
|
23
|
+
return e.name;
|
|
24
|
+
}
|
|
25
|
+
return "error";
|
|
26
|
+
}
|
|
27
|
+
function safeTarget(config, args, result, meta) {
|
|
28
|
+
if (!config?.target)
|
|
29
|
+
return null;
|
|
30
|
+
try {
|
|
31
|
+
return config.target(args, result, meta) ?? null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function safeSummary(config, args, result, meta) {
|
|
38
|
+
if (!config?.summary)
|
|
39
|
+
return null;
|
|
40
|
+
try {
|
|
41
|
+
const s = config.summary(args, result, meta);
|
|
42
|
+
return typeof s === "string" ? s.slice(0, 500) : null;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Record one audit event. Resolves the actor, target, ownership (for scoped
|
|
50
|
+
* reads), and redacted inputs, then appends a row. Never throws.
|
|
51
|
+
*/
|
|
52
|
+
export async function recordActionAudit(input) {
|
|
53
|
+
try {
|
|
54
|
+
if (isAuditDisabled())
|
|
55
|
+
return;
|
|
56
|
+
const ctx = input.ctx;
|
|
57
|
+
const actionName = ctx?.actionName;
|
|
58
|
+
// No name → an internal/programmatic run() with no dispatch context. Skip
|
|
59
|
+
// rather than write a nameless row.
|
|
60
|
+
if (!actionName)
|
|
61
|
+
return;
|
|
62
|
+
if (!shouldRecordAudit(input.config, actionName))
|
|
63
|
+
return;
|
|
64
|
+
const caller = ctx?.caller ?? "http";
|
|
65
|
+
const actorEmail = ctx?.userEmail ?? null;
|
|
66
|
+
const meta = {
|
|
67
|
+
status: input.status,
|
|
68
|
+
caller,
|
|
69
|
+
userEmail: ctx?.userEmail,
|
|
70
|
+
orgId: ctx?.orgId ?? null,
|
|
71
|
+
};
|
|
72
|
+
const target = safeTarget(input.config, input.args, input.result, meta);
|
|
73
|
+
const summary = safeSummary(input.config, input.args, input.result, meta);
|
|
74
|
+
const recordInputs = input.config?.recordInputs !== false;
|
|
75
|
+
const inputJson = recordInputs ? redactArgsToJson(input.args) : null;
|
|
76
|
+
const event = {
|
|
77
|
+
id: crypto.randomUUID(),
|
|
78
|
+
createdAt: Date.now(),
|
|
79
|
+
action: actionName,
|
|
80
|
+
caller,
|
|
81
|
+
actorKind: deriveActorKind(caller, actorEmail),
|
|
82
|
+
actorEmail,
|
|
83
|
+
orgId: ctx?.orgId ?? null,
|
|
84
|
+
threadId: ctx?.threadId ?? null,
|
|
85
|
+
turnId: ctx?.turnId ?? null,
|
|
86
|
+
targetType: target?.type ?? null,
|
|
87
|
+
targetId: target?.id ?? null,
|
|
88
|
+
status: input.status,
|
|
89
|
+
summary,
|
|
90
|
+
input: inputJson,
|
|
91
|
+
errorCode: input.status === "error" ? errorCode(input.error) : null,
|
|
92
|
+
// Scope reads to the resource owner when the action declares one,
|
|
93
|
+
// otherwise to the actor (the common self-mutation case).
|
|
94
|
+
ownerEmail: target?.ownerEmail ?? actorEmail,
|
|
95
|
+
visibility: target?.visibility ?? "private",
|
|
96
|
+
};
|
|
97
|
+
// org_id used for scoping defaults to the target's, else the actor's org.
|
|
98
|
+
if (target?.orgId !== undefined)
|
|
99
|
+
event.orgId = target.orgId;
|
|
100
|
+
await insertAuditEvent(event);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Best-effort — auditing must never break the audited action.
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/audit/record.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Audit capture entry point, called from the `defineAction` audit wrapper after\n * an action runs (success or error). Best-effort: any failure here is swallowed\n * so auditing never breaks the action it observes.\n *\n * This module touches the DB (`store.js`), so `action.ts` loads it lazily via\n * dynamic import on the first audited call — keeping the DB client out of every\n * bundle that merely defines actions.\n */\nimport {\n deriveActorKind,\n isAuditDisabled,\n shouldRecordAudit,\n} from \"./config.js\";\nimport { redactArgsToJson } from \"./redact.js\";\nimport { insertAuditEvent } from \"./store.js\";\nimport type {\n ActionAuditConfig,\n AuditCallMeta,\n AuditEvent,\n AuditStatus,\n AuditTarget,\n} from \"./types.js\";\n\n/** Minimal view of the action run context the recorder needs. */\nexport interface AuditRunContextLike {\n actionName?: string;\n caller?: string;\n userEmail?: string;\n orgId?: string | null;\n threadId?: string;\n turnId?: string;\n}\n\nexport interface RecordActionAuditInput {\n config: ActionAuditConfig | undefined;\n args: unknown;\n ctx: AuditRunContextLike | undefined;\n status: AuditStatus;\n result?: unknown;\n error?: unknown;\n}\n\nfunction errorCode(error: unknown): string | null {\n if (!error) return null;\n if (typeof error === \"object\") {\n const e = error as { errorCode?: unknown; code?: unknown; name?: unknown };\n if (typeof e.errorCode === \"string\") return e.errorCode;\n if (typeof e.code === \"string\") return e.code;\n if (typeof e.name === \"string\") return e.name;\n }\n return \"error\";\n}\n\nfunction safeTarget(\n config: ActionAuditConfig | undefined,\n args: unknown,\n result: unknown,\n meta: AuditCallMeta,\n): AuditTarget | null {\n if (!config?.target) return null;\n try {\n return config.target(args, result, meta) ?? null;\n } catch {\n return null;\n }\n}\n\nfunction safeSummary(\n config: ActionAuditConfig | undefined,\n args: unknown,\n result: unknown,\n meta: AuditCallMeta,\n): string | null {\n if (!config?.summary) return null;\n try {\n const s = config.summary(args, result, meta);\n return typeof s === \"string\" ? s.slice(0, 500) : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Record one audit event. Resolves the actor, target, ownership (for scoped\n * reads), and redacted inputs, then appends a row. Never throws.\n */\nexport async function recordActionAudit(\n input: RecordActionAuditInput,\n): Promise<void> {\n try {\n if (isAuditDisabled()) return;\n const ctx = input.ctx;\n const actionName = ctx?.actionName;\n // No name → an internal/programmatic run() with no dispatch context. Skip\n // rather than write a nameless row.\n if (!actionName) return;\n if (!shouldRecordAudit(input.config, actionName)) return;\n\n const caller = ctx?.caller ?? \"http\";\n const actorEmail = ctx?.userEmail ?? null;\n const meta: AuditCallMeta = {\n status: input.status,\n caller,\n userEmail: ctx?.userEmail,\n orgId: ctx?.orgId ?? null,\n };\n\n const target = safeTarget(input.config, input.args, input.result, meta);\n const summary = safeSummary(input.config, input.args, input.result, meta);\n\n const recordInputs = input.config?.recordInputs !== false;\n const inputJson = recordInputs ? redactArgsToJson(input.args) : null;\n\n const event: AuditEvent = {\n id: crypto.randomUUID(),\n createdAt: Date.now(),\n action: actionName,\n caller,\n actorKind: deriveActorKind(caller, actorEmail),\n actorEmail,\n orgId: ctx?.orgId ?? null,\n threadId: ctx?.threadId ?? null,\n turnId: ctx?.turnId ?? null,\n targetType: target?.type ?? null,\n targetId: target?.id ?? null,\n status: input.status,\n summary,\n input: inputJson,\n errorCode: input.status === \"error\" ? errorCode(input.error) : null,\n // Scope reads to the resource owner when the action declares one,\n // otherwise to the actor (the common self-mutation case).\n ownerEmail: target?.ownerEmail ?? actorEmail,\n visibility: target?.visibility ?? \"private\",\n };\n // org_id used for scoping defaults to the target's, else the actor's org.\n if (target?.orgId !== undefined) event.orgId = target.orgId;\n\n await insertAuditEvent(event);\n } catch {\n // Best-effort — auditing must never break the audited action.\n }\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redaction for audit-captured arguments.
|
|
3
|
+
*
|
|
4
|
+
* The audit log must never become a secondary store of secrets. Before any
|
|
5
|
+
* call arguments are persisted we:
|
|
6
|
+
* - drop values under credential-looking keys (token, secret, password, …),
|
|
7
|
+
* - redact string values that look like bearer tokens / long opaque keys,
|
|
8
|
+
* - truncate oversized strings and cap the serialized payload size.
|
|
9
|
+
*
|
|
10
|
+
* This mirrors the framework's standing rule that credential-looking literals
|
|
11
|
+
* never land in source, logs, or fixtures.
|
|
12
|
+
*/
|
|
13
|
+
/** Heuristic: does a bare string value look like a secret? */
|
|
14
|
+
declare function looksSecret(value: string): boolean;
|
|
15
|
+
declare function redact(value: unknown, depth: number): unknown;
|
|
16
|
+
/**
|
|
17
|
+
* Redact and serialize call arguments to a capped JSON string, or `null` when
|
|
18
|
+
* there is nothing to record. Never throws.
|
|
19
|
+
*/
|
|
20
|
+
export declare function redactArgsToJson(args: unknown): string | null;
|
|
21
|
+
/** Exposed for tests. */
|
|
22
|
+
export declare const __test: {
|
|
23
|
+
looksSecret: typeof looksSecret;
|
|
24
|
+
redact: typeof redact;
|
|
25
|
+
SENSITIVE_KEY: RegExp;
|
|
26
|
+
};
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=redact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/audit/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAYH,8DAA8D;AAC9D,iBAAS,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAkB3C;AAUD,iBAAS,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CA0BtD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAqB7D;AAED,yBAAyB;AACzB,eAAO,MAAM,MAAM;;;;CAAyC,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redaction for audit-captured arguments.
|
|
3
|
+
*
|
|
4
|
+
* The audit log must never become a secondary store of secrets. Before any
|
|
5
|
+
* call arguments are persisted we:
|
|
6
|
+
* - drop values under credential-looking keys (token, secret, password, …),
|
|
7
|
+
* - redact string values that look like bearer tokens / long opaque keys,
|
|
8
|
+
* - truncate oversized strings and cap the serialized payload size.
|
|
9
|
+
*
|
|
10
|
+
* This mirrors the framework's standing rule that credential-looking literals
|
|
11
|
+
* never land in source, logs, or fixtures.
|
|
12
|
+
*/
|
|
13
|
+
const SENSITIVE_KEY = /(pass(word|phrase)?|secret|token|api[_-]?key|apikey|authorization|bearer|credential|cookie|session[_-]?(id|token)|private[_-]?key|client[_-]?secret|signing[_-]?secret|access[_-]?key|refresh[_-]?token|webhook[_-]?(url|secret))/i;
|
|
14
|
+
const REDACTED = "[redacted]";
|
|
15
|
+
const MAX_STRING = 2000;
|
|
16
|
+
const MAX_DEPTH = 6;
|
|
17
|
+
const MAX_KEYS = 100;
|
|
18
|
+
const MAX_ARRAY = 100;
|
|
19
|
+
const MAX_JSON = 8000;
|
|
20
|
+
/** Heuristic: does a bare string value look like a secret? */
|
|
21
|
+
function looksSecret(value) {
|
|
22
|
+
if (/^bearer\s+\S/i.test(value))
|
|
23
|
+
return true;
|
|
24
|
+
// Long, unbroken, high-entropy-ish opaque token (hex/base64url, no spaces).
|
|
25
|
+
if (value.length >= 32 && /^[A-Za-z0-9_\-+/=.]+$/.test(value))
|
|
26
|
+
return true;
|
|
27
|
+
// Common secret prefixes (Stripe, GitHub, OpenAI, Slack, AWS, …).
|
|
28
|
+
if (/^(sk|pk|rk|ghp|gho|xox[baprs]|AKIA|AIza|ya29)[-_]/i.test(value)) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
// Webhook URLs carry their secret in the path — redact regardless of the key
|
|
32
|
+
// they arrive under (e.g. a generic `value` field holding a Slack webhook).
|
|
33
|
+
if (/^https?:\/\/(hooks\.slack\.com\/|[^/]*\.webhook\.office\.com\/|(canary\.|ptb\.)?discord(app)?\.com\/api\/webhooks\/|hooks\.zapier\.com\/|maker\.ifttt\.com\/|discord\.com\/api\/webhooks\/)/i.test(value)) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
function redactString(value) {
|
|
39
|
+
if (looksSecret(value))
|
|
40
|
+
return REDACTED;
|
|
41
|
+
if (value.length > MAX_STRING) {
|
|
42
|
+
return `${value.slice(0, MAX_STRING)}…(${value.length - MAX_STRING} more chars)`;
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
function redact(value, depth) {
|
|
47
|
+
if (value == null)
|
|
48
|
+
return value;
|
|
49
|
+
if (typeof value === "string")
|
|
50
|
+
return redactString(value);
|
|
51
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
52
|
+
return value;
|
|
53
|
+
if (depth >= MAX_DEPTH)
|
|
54
|
+
return "[…]";
|
|
55
|
+
if (Array.isArray(value)) {
|
|
56
|
+
const out = value.slice(0, MAX_ARRAY).map((v) => redact(v, depth + 1));
|
|
57
|
+
if (value.length > MAX_ARRAY)
|
|
58
|
+
out.push(`…(${value.length - MAX_ARRAY} more)`);
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
if (typeof value === "object") {
|
|
62
|
+
const out = {};
|
|
63
|
+
let n = 0;
|
|
64
|
+
for (const [k, v] of Object.entries(value)) {
|
|
65
|
+
if (n >= MAX_KEYS) {
|
|
66
|
+
out["…"] = "(truncated)";
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
n += 1;
|
|
70
|
+
out[k] = SENSITIVE_KEY.test(k) ? REDACTED : redact(v, depth + 1);
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
// Functions, symbols, bigint, etc. — not serializable / not interesting.
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Redact and serialize call arguments to a capped JSON string, or `null` when
|
|
79
|
+
* there is nothing to record. Never throws.
|
|
80
|
+
*/
|
|
81
|
+
export function redactArgsToJson(args) {
|
|
82
|
+
try {
|
|
83
|
+
if (args == null)
|
|
84
|
+
return null;
|
|
85
|
+
const redacted = redact(args, 0);
|
|
86
|
+
if (redacted === undefined)
|
|
87
|
+
return null;
|
|
88
|
+
const json = JSON.stringify(redacted);
|
|
89
|
+
if (json == null)
|
|
90
|
+
return null;
|
|
91
|
+
if (json.length > MAX_JSON) {
|
|
92
|
+
// Slicing the serialized JSON would yield an unparseable string. Wrap a
|
|
93
|
+
// preview in a valid envelope so `get-audit-event` can always JSON.parse
|
|
94
|
+
// the stored `input`.
|
|
95
|
+
return JSON.stringify({
|
|
96
|
+
_auditTruncated: true,
|
|
97
|
+
originalBytes: json.length,
|
|
98
|
+
preview: json.slice(0, MAX_JSON),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return json;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Exposed for tests. */
|
|
108
|
+
export const __test = { looksSecret, redact, SENSITIVE_KEY };
|
|
109
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../../src/audit/redact.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Redaction for audit-captured arguments.\n *\n * The audit log must never become a secondary store of secrets. Before any\n * call arguments are persisted we:\n * - drop values under credential-looking keys (token, secret, password, …),\n * - redact string values that look like bearer tokens / long opaque keys,\n * - truncate oversized strings and cap the serialized payload size.\n *\n * This mirrors the framework's standing rule that credential-looking literals\n * never land in source, logs, or fixtures.\n */\n\nconst SENSITIVE_KEY =\n /(pass(word|phrase)?|secret|token|api[_-]?key|apikey|authorization|bearer|credential|cookie|session[_-]?(id|token)|private[_-]?key|client[_-]?secret|signing[_-]?secret|access[_-]?key|refresh[_-]?token|webhook[_-]?(url|secret))/i;\n\nconst REDACTED = \"[redacted]\";\nconst MAX_STRING = 2000;\nconst MAX_DEPTH = 6;\nconst MAX_KEYS = 100;\nconst MAX_ARRAY = 100;\nconst MAX_JSON = 8000;\n\n/** Heuristic: does a bare string value look like a secret? */\nfunction looksSecret(value: string): boolean {\n if (/^bearer\\s+\\S/i.test(value)) return true;\n // Long, unbroken, high-entropy-ish opaque token (hex/base64url, no spaces).\n if (value.length >= 32 && /^[A-Za-z0-9_\\-+/=.]+$/.test(value)) return true;\n // Common secret prefixes (Stripe, GitHub, OpenAI, Slack, AWS, …).\n if (/^(sk|pk|rk|ghp|gho|xox[baprs]|AKIA|AIza|ya29)[-_]/i.test(value)) {\n return true;\n }\n // Webhook URLs carry their secret in the path — redact regardless of the key\n // they arrive under (e.g. a generic `value` field holding a Slack webhook).\n if (\n /^https?:\\/\\/(hooks\\.slack\\.com\\/|[^/]*\\.webhook\\.office\\.com\\/|(canary\\.|ptb\\.)?discord(app)?\\.com\\/api\\/webhooks\\/|hooks\\.zapier\\.com\\/|maker\\.ifttt\\.com\\/|discord\\.com\\/api\\/webhooks\\/)/i.test(\n value,\n )\n ) {\n return true;\n }\n return false;\n}\n\nfunction redactString(value: string): string {\n if (looksSecret(value)) return REDACTED;\n if (value.length > MAX_STRING) {\n return `${value.slice(0, MAX_STRING)}…(${value.length - MAX_STRING} more chars)`;\n }\n return value;\n}\n\nfunction redact(value: unknown, depth: number): unknown {\n if (value == null) return value;\n if (typeof value === \"string\") return redactString(value);\n if (typeof value === \"number\" || typeof value === \"boolean\") return value;\n if (depth >= MAX_DEPTH) return \"[…]\";\n if (Array.isArray(value)) {\n const out = value.slice(0, MAX_ARRAY).map((v) => redact(v, depth + 1));\n if (value.length > MAX_ARRAY)\n out.push(`…(${value.length - MAX_ARRAY} more)`);\n return out;\n }\n if (typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n let n = 0;\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (n >= MAX_KEYS) {\n out[\"…\"] = \"(truncated)\";\n break;\n }\n n += 1;\n out[k] = SENSITIVE_KEY.test(k) ? REDACTED : redact(v, depth + 1);\n }\n return out;\n }\n // Functions, symbols, bigint, etc. — not serializable / not interesting.\n return undefined;\n}\n\n/**\n * Redact and serialize call arguments to a capped JSON string, or `null` when\n * there is nothing to record. Never throws.\n */\nexport function redactArgsToJson(args: unknown): string | null {\n try {\n if (args == null) return null;\n const redacted = redact(args, 0);\n if (redacted === undefined) return null;\n const json = JSON.stringify(redacted);\n if (json == null) return null;\n if (json.length > MAX_JSON) {\n // Slicing the serialized JSON would yield an unparseable string. Wrap a\n // preview in a valid envelope so `get-audit-event` can always JSON.parse\n // the stored `input`.\n return JSON.stringify({\n _auditTruncated: true,\n originalBytes: json.length,\n preview: json.slice(0, MAX_JSON),\n });\n }\n return json;\n } catch {\n return null;\n }\n}\n\n/** Exposed for tests. */\nexport const __test = { looksSecret, redact, SENSITIVE_KEY };\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AuditEvent, AuditQueryFilters } from "./types.js";
|
|
2
|
+
export declare function ensureAuditTables(): Promise<void>;
|
|
3
|
+
export declare function insertAuditEvent(event: AuditEvent): Promise<void>;
|
|
4
|
+
export interface AuditReadScope {
|
|
5
|
+
userEmail?: string;
|
|
6
|
+
orgId?: string | null;
|
|
7
|
+
}
|
|
8
|
+
export declare function queryAuditEvents(scope: AuditReadScope, filters?: AuditQueryFilters): Promise<AuditEvent[]>;
|
|
9
|
+
export declare function getAuditEventById(id: string, scope: AuditReadScope): Promise<AuditEvent | null>;
|
|
10
|
+
/** Purge audit rows older than `cutoffMs`. Returns the deleted row count. */
|
|
11
|
+
export declare function deleteOldAuditEvents(cutoffMs: number): Promise<number>;
|
|
12
|
+
/** Test-only: reset the cached init promise so a fresh DB re-creates tables. */
|
|
13
|
+
export declare function __resetAuditInitForTests(): void;
|
|
14
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/audit/store.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,UAAU,EACV,iBAAiB,EAElB,MAAM,YAAY,CAAC;AAIpB,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAmDvD;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BvE;AAwBD,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AA2CD,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,cAAc,EACrB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,UAAU,EAAE,CAAC,CAsCvB;AAED,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAW5B;AAED,6EAA6E;AAC7E,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ5E;AAED,gFAAgF;AAChF,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C"}
|