@venturekit-pro/audit 0.0.0-dev.20260602192622

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.
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Audit-event domain types.
3
+ *
4
+ * Mirrors `audit_events` table columns 1:1, with the caller-facing
5
+ * `record()` input shape derived from it. Keep these aligned with the
6
+ * SQL in `migrations/vk_audit_0001_init.sql`.
7
+ */
8
+ /**
9
+ * Actor that performed the action. Mirrors the Postgres
10
+ * `vk_audit_actor_type` enum.
11
+ *
12
+ * user — end-user via session token (Cognito sub, etc).
13
+ * service — internal service identity (workflow runner, worker).
14
+ * cron — scheduled-job Lambda.
15
+ * api_key — programmatic-access key (the actor id carries the
16
+ * key prefix; never the raw key).
17
+ * system — bootstrap / migration / cross-tenant maintenance.
18
+ */
19
+ export type AuditActorType = 'user' | 'service' | 'cron' | 'api_key' | 'system';
20
+ /**
21
+ * Outcome of the audited action. Mirrors the Postgres
22
+ * `vk_audit_status` enum and matches `venturekit-cms`'s legacy
23
+ * `job_status` shape so apps porting from `jobs_log` get a 1:1
24
+ * mapping.
25
+ */
26
+ export type AuditStatus = 'queued' | 'running' | 'succeeded' | 'failed' | 'skipped';
27
+ /**
28
+ * Compound identity of the actor.
29
+ */
30
+ export interface AuditActor {
31
+ type: AuditActorType;
32
+ /**
33
+ * Free-form id. User → auth sub. Service → service name. api_key →
34
+ * key prefix. cron → schedule rule name. Optional for system / bootstrap.
35
+ */
36
+ id?: string | null;
37
+ }
38
+ /**
39
+ * The thing the action was done to. Optional — events like
40
+ * `cron.tick` don't target a specific row.
41
+ */
42
+ export interface AuditTarget {
43
+ /** Logical type (e.g. `'blog_post'`, `'social_post'`, `'editorial_run'`). */
44
+ type: string;
45
+ /** Primary key of the target row. */
46
+ id: string;
47
+ /** Optional secondary identifier for human-readable refs (slug, label). */
48
+ slug?: string | null;
49
+ }
50
+ /**
51
+ * Caller-facing input to `record()`. Strictly typed on the in-args
52
+ * the package guarantees to preserve; opaque `payload` carries
53
+ * whatever else the event-kind needs.
54
+ *
55
+ * Strictly audit-scoped: WHO did WHAT, TO WHAT, WHEN, HOW IT WENT.
56
+ * Domain-specific fields (cost, tokens, payment amounts, …) live
57
+ * in their own packages' tables and reference this one via
58
+ * `correlationId`.
59
+ */
60
+ export interface AuditEventInput {
61
+ /** Tenant the event belongs to. Pass `null` only for platform-wide events. */
62
+ tenantId: string | null;
63
+ /** Who did the thing. */
64
+ actor: AuditActor;
65
+ /** Free-form, namespaced event kind (e.g. `'blog.save'`). */
66
+ kind: string;
67
+ /** Optional target row. */
68
+ target?: AuditTarget;
69
+ /** Outcome. Defaults to `'succeeded'`. */
70
+ status?: AuditStatus;
71
+ /** Free-form details. Caller's responsibility to keep small + PII-free. */
72
+ payload?: Record<string, unknown>;
73
+ /** Threads multiple events into one logical operation. */
74
+ correlationId?: string | null;
75
+ /** Optional idempotency token for safe retries. */
76
+ idempotencyKey?: string | null;
77
+ /** Failure summary when `status === 'failed'`. */
78
+ errorMessage?: string | null;
79
+ }
80
+ /**
81
+ * Persisted row shape. Mirrors `audit_events` columns post-mapping
82
+ * (snake_case → camelCase). Returned by `record()` and the query
83
+ * helpers.
84
+ */
85
+ export interface AuditEvent {
86
+ id: string;
87
+ tenantId: string | null;
88
+ actorType: AuditActorType;
89
+ actorId: string | null;
90
+ kind: string;
91
+ targetType: string | null;
92
+ targetId: string | null;
93
+ targetSlug: string | null;
94
+ status: AuditStatus;
95
+ payload: Record<string, unknown>;
96
+ correlationId: string | null;
97
+ idempotencyKey: string | null;
98
+ errorMessage: string | null;
99
+ createdAt: string;
100
+ }
101
+ /**
102
+ * Minimal querier shape — a function that runs a parameterized SQL
103
+ * statement and returns rows. Matches `@venturekit/data`'s `Querier`
104
+ * type so consumers can pass `query` directly. Kept structural so
105
+ * apps using a different Postgres driver can still inject their own
106
+ * adapter.
107
+ */
108
+ export type Querier = <T = Record<string, unknown>[]>(sql: string, params?: unknown[]) => Promise<T>;
109
+ /**
110
+ * Counters returned by `countEvents()` / `countEventsByKind()`. The
111
+ * audit package's only aggregation surface — anything more domain-
112
+ * specific (cost rollups, payment totals, token usage) lives in the
113
+ * package that owns those concerns.
114
+ */
115
+ export interface EventCount {
116
+ /** Number of events matching the filter. */
117
+ count: number;
118
+ /** Per-prefix split when caller passes `byKindPrefix`. */
119
+ byKindPrefix: Record<string, number>;
120
+ }
121
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,GACtB,MAAM,GACN,SAAS,GACT,MAAM,GACN,SAAS,GACT,QAAQ,CAAC;AAEb;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEpF;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,cAAc,CAAC;IACrB;;;OAGG;IACH,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,yBAAyB;IACzB,KAAK,EAAE,UAAU,CAAC;IAClB,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAGlC,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,mDAAmD;IACnD,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,cAAc,CAAC;IAC1B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAClD,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,OAAO,EAAE,KACf,OAAO,CAAC,CAAC,CAAC,CAAC;AAEhB;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC"}
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Audit-event domain types.
3
+ *
4
+ * Mirrors `audit_events` table columns 1:1, with the caller-facing
5
+ * `record()` input shape derived from it. Keep these aligned with the
6
+ * SQL in `migrations/vk_audit_0001_init.sql`.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@venturekit-pro/audit",
3
+ "version": "0.0.0-dev.20260602192622",
4
+ "description": "Append-only audit + cost-tracking log for VentureKit applications",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "src/migrations/*.sql"
11
+ ],
12
+ "vk": {
13
+ "migrations": "src/migrations"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/venturekit-dev/venturekit.private.git",
18
+ "directory": "packages/pro/audit"
19
+ },
20
+ "publishConfig": {
21
+ "registry": "https://registry.npmjs.org",
22
+ "access": "public"
23
+ },
24
+ "license": "SEE LICENSE IN ../LICENSE",
25
+ "licenseHeader": "VentureKit Pro Commercial License — production use requires a valid VentureKit Pro license key. See https://venturekit.dev/pricing.",
26
+ "exports": {
27
+ ".": {
28
+ "import": "./dist/index.js",
29
+ "types": "./dist/index.d.ts"
30
+ }
31
+ },
32
+ "keywords": [
33
+ "venturekit",
34
+ "saas",
35
+ "audit",
36
+ "cost-tracking",
37
+ "compliance"
38
+ ],
39
+ "dependencies": {
40
+ "@venturekit/core": "0.0.0-dev.20260602192622"
41
+ },
42
+ "peerDependencies": {
43
+ "@venturekit/data": "0.0.0-dev.20260602192622"
44
+ },
45
+ "peerDependenciesMeta": {
46
+ "@venturekit/data": {
47
+ "optional": true
48
+ }
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^25.6.0",
52
+ "@venturekit/data": "0.0.0-dev.20260602192622",
53
+ "typescript": "^5.3.0"
54
+ },
55
+ "scripts": {
56
+ "build": "tsc && node -e \"require('fs').cpSync('src/migrations','dist/migrations',{recursive:true})\"",
57
+ "dev": "tsc --watch",
58
+ "clean": "rm -rf dist"
59
+ }
60
+ }
@@ -0,0 +1,152 @@
1
+ -- @venturekit-pro/audit — initial schema.
2
+ --
3
+ -- One canonical, append-only `audit_events` table. Stores WHO did
4
+ -- WHAT, TO WHAT, WHEN, and HOW IT WENT. Nothing else.
5
+ --
6
+ -- **Strict scope.** The audit package does not know about LLM costs,
7
+ -- token counts, image generation, payment amounts, or any other
8
+ -- domain concern. Those live in their own packages / tables
9
+ -- (e.g. `@venturekit-pro/ai`'s `llm_cost_events`); audit events
10
+ -- merely reference them by `target_type` / `target_id` /
11
+ -- `correlation_id`.
12
+ --
13
+ -- Append-only by construction:
14
+ -- - No UPDATE / DELETE statements are issued by the package code.
15
+ -- - DB-level REVOKE on `audit_events` from the application role
16
+ -- at the bottom of this migration enforces it physically.
17
+ -- Privileged maintenance (retention pruning) runs as a
18
+ -- separate role.
19
+ --
20
+ -- Idempotency:
21
+ -- - `idempotency_key` is OPTIONAL on insert. When set, the
22
+ -- partial unique index makes a re-insert a no-op (caller
23
+ -- catches the duplicate-key error in `record()` and returns
24
+ -- the original row).
25
+
26
+ CREATE EXTENSION IF NOT EXISTS pgcrypto;
27
+
28
+ -- ─── Status enum ────────────────────────────────────────────────────
29
+ --
30
+ -- Outcome of an audited action. Mirrors the standard `job_status`
31
+ -- shape so callers can map 1:1.
32
+
33
+ DO $$
34
+ BEGIN
35
+ IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'vk_audit_status') THEN
36
+ CREATE TYPE vk_audit_status AS ENUM (
37
+ 'queued',
38
+ 'running',
39
+ 'succeeded',
40
+ 'failed',
41
+ 'skipped'
42
+ );
43
+ END IF;
44
+ END
45
+ $$;
46
+
47
+ DO $$
48
+ BEGIN
49
+ IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'vk_audit_actor_type') THEN
50
+ CREATE TYPE vk_audit_actor_type AS ENUM (
51
+ 'user', -- end-user via session token
52
+ 'service', -- internal service identity
53
+ 'cron', -- scheduled-job Lambda
54
+ 'api_key', -- programmatic-access key
55
+ 'system' -- bootstrap / migration / cross-tenant maintenance
56
+ );
57
+ END IF;
58
+ END
59
+ $$;
60
+
61
+ -- ─── audit_events ───────────────────────────────────────────────────
62
+
63
+ CREATE TABLE IF NOT EXISTS audit_events (
64
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
65
+ -- tenant_id is nullable so we can record platform-wide events
66
+ -- (e.g. cron sweeps that fan out across every tenant). Per-tenant
67
+ -- views filter on `tenant_id = $1`; the rare cross-tenant rows
68
+ -- show up only when querying with `tenant_id IS NULL`.
69
+ tenant_id uuid,
70
+ -- WHO did the thing.
71
+ actor_type vk_audit_actor_type NOT NULL,
72
+ -- Free-form actor id. For users: their auth id (e.g. Cognito sub).
73
+ -- For services: service name. For api_key: key prefix. For cron:
74
+ -- schedule rule name. NULL only for bootstrap / system rows.
75
+ actor_id text,
76
+ -- WHAT happened. Free-form, app-namespaced (e.g. `'blog.save'`,
77
+ -- `'order.refunded'`, `'security.login'`). The package never
78
+ -- enumerates these; the calling app owns its kind taxonomy.
79
+ kind text NOT NULL,
80
+ -- WHAT it was done to (optional). target_type + target_id together
81
+ -- identify the row this event mutated or read.
82
+ target_type text,
83
+ target_id text,
84
+ -- Secondary identifier for human-readable references (slug, label).
85
+ target_slug text,
86
+ -- Outcome of the action. Finalized rows are NEVER UPDATE'd; long
87
+ -- operations write a `running` row, then a separate `succeeded` /
88
+ -- `failed` / `skipped` row with the same `correlation_id`.
89
+ status vk_audit_status NOT NULL DEFAULT 'succeeded',
90
+ -- Free-form payload. Caller's responsibility to keep this small
91
+ -- (~< 32KB) and PII-free. Heavy artifacts go in S3 + are
92
+ -- referenced by URL/key.
93
+ payload jsonb NOT NULL DEFAULT '{}'::jsonb,
94
+
95
+ -- ─── Cross-event correlation ─────────────────────────────────────
96
+
97
+ -- Threads multiple events into one logical operation (a workflow
98
+ -- run, an HTTP request, a saga).
99
+ correlation_id text,
100
+ -- Optional idempotency token; supports safe retry of e.g. cron
101
+ -- ticks. NULL is allowed and skips the dedup check.
102
+ idempotency_key text,
103
+ -- Human-readable failure summary when `status = 'failed'`.
104
+ error_message text,
105
+
106
+ created_at timestamptz NOT NULL DEFAULT now()
107
+ );
108
+
109
+ -- ─── Indexes ────────────────────────────────────────────────────────
110
+
111
+ -- Main page query: "show me the last N events for this tenant".
112
+ CREATE INDEX IF NOT EXISTS audit_events_tenant_created_idx
113
+ ON audit_events (tenant_id, created_at DESC);
114
+
115
+ -- Filtered list: "show me the last N 'order.refunded' events".
116
+ CREATE INDEX IF NOT EXISTS audit_events_tenant_kind_idx
117
+ ON audit_events (tenant_id, kind, created_at DESC);
118
+
119
+ -- Per-entity history: "every event touching blog_post X".
120
+ CREATE INDEX IF NOT EXISTS audit_events_target_idx
121
+ ON audit_events (tenant_id, target_type, target_id, created_at DESC);
122
+
123
+ -- Correlation join: "every event in this workflow run".
124
+ CREATE INDEX IF NOT EXISTS audit_events_correlation_idx
125
+ ON audit_events (correlation_id, created_at DESC)
126
+ WHERE correlation_id IS NOT NULL;
127
+
128
+ -- Idempotency dedup. Partial because the vast majority of rows
129
+ -- have idempotency_key = NULL.
130
+ CREATE UNIQUE INDEX IF NOT EXISTS audit_events_idempotency_unique
131
+ ON audit_events (idempotency_key)
132
+ WHERE idempotency_key IS NOT NULL;
133
+
134
+ -- ─── Append-only enforcement at the storage layer ───────────────────
135
+ --
136
+ -- The application role can INSERT and SELECT but never UPDATE /
137
+ -- DELETE. A privileged maintenance role (whatever the app uses to
138
+ -- run migrations) retains full privileges, and is what retention
139
+ -- sweeps run as.
140
+ --
141
+ -- We don't know the application role's name at migration time — apps
142
+ -- vary (`venturekit_cms_app`, `myapp_runtime`, …). The REVOKE here
143
+ -- targets `PUBLIC` which covers the default no-role-set case AND
144
+ -- means the application role inherits the lockdown unless explicitly
145
+ -- granted UPDATE/DELETE. Apps that grant their runtime role
146
+ -- `ALL PRIVILEGES` on the schema must re-revoke after this migration.
147
+
148
+ REVOKE UPDATE, DELETE ON audit_events FROM PUBLIC;
149
+
150
+ -- A convenience GRANT so the app role still works for inserts and
151
+ -- reads when only PUBLIC's privileges were revoked.
152
+ GRANT SELECT, INSERT ON audit_events TO PUBLIC;