hazo_audit 2.0.1 → 2.1.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/CHANGE_LOG.md +80 -0
- package/README.md +57 -1
- package/config/hazo_audit_config.ini.sample +28 -0
- package/db_setup_postgres.sql +76 -0
- package/db_setup_sqlite.sql +72 -0
- package/dist/client/AuditViewer.js +1 -1
- package/dist/client/FieldAuditIcon.d.ts.map +1 -1
- package/dist/client/FieldAuditIcon.js +3 -3
- package/dist/client/FieldAuditIcon.js.map +1 -1
- package/dist/client/FieldAuditTrail.d.ts.map +1 -1
- package/dist/client/FieldAuditTrail.js +72 -9
- package/dist/client/FieldAuditTrail.js.map +1 -1
- package/dist/client/RevertConfirmDialog.js +1 -1
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/config/hazo_audit_config.d.ts +99 -0
- package/dist/lib/config/hazo_audit_config.d.ts.map +1 -0
- package/dist/lib/config/hazo_audit_config.js +67 -0
- package/dist/lib/config/hazo_audit_config.js.map +1 -0
- package/dist/nextjs/index.d.ts +12 -0
- package/dist/nextjs/index.d.ts.map +1 -1
- package/dist/nextjs/index.js +307 -218
- package/dist/nextjs/index.js.map +1 -1
- package/dist/server/audited_crud.js +3 -3
- package/dist/server/audited_crud.js.map +1 -1
- package/dist/server/context.js +3 -3
- package/dist/server/context.js.map +1 -1
- package/dist/server/diff.js +11 -3
- package/dist/server/diff.js.map +1 -1
- package/dist/server/errors.d.ts +10 -1
- package/dist/server/errors.d.ts.map +1 -1
- package/dist/server/errors.js +16 -2
- package/dist/server/errors.js.map +1 -1
- package/dist/server/intent.js +3 -3
- package/dist/server/intent.js.map +1 -1
- package/dist/server/outbox.js +2 -2
- package/dist/server/outbox.js.map +1 -1
- package/dist/server/worker.d.ts +7 -7
- package/dist/server/worker.d.ts.map +1 -1
- package/dist/server/worker.js +60 -15
- package/dist/server/worker.js.map +1 -1
- package/dist/server/wrap.js +2 -2
- package/dist/server/wrap.js.map +1 -1
- package/dist/utils/logger.d.ts +25 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +42 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/uuid.d.ts +19 -0
- package/dist/utils/uuid.d.ts.map +1 -0
- package/dist/utils/uuid.js +22 -0
- package/dist/utils/uuid.js.map +1 -0
- package/package.json +36 -21
- package/dist/client/cn.d.ts +0 -3
- package/dist/client/cn.d.ts.map +0 -1
- package/dist/client/cn.js +0 -6
- package/dist/client/cn.js.map +0 -1
package/CHANGE_LOG.md
CHANGED
|
@@ -1,5 +1,85 @@
|
|
|
1
1
|
# Change log
|
|
2
2
|
|
|
3
|
+
## 2.1.0 — 2026-05-29
|
|
4
|
+
|
|
5
|
+
### Workspace v2 migration (Wave 2)
|
|
6
|
+
|
|
7
|
+
Standardisation onto the workspace foundation packages — adopt `hazo_core`,
|
|
8
|
+
align logging / errors / correlation IDs / config / UUIDs with the rest of
|
|
9
|
+
the ecosystem. No public API removed; the previous response shapes for
|
|
10
|
+
`/audit/events`, `reveal`, and `viewer` are preserved.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`hazo_core` peer dependency (`^1.0.0`).** Required; supplies
|
|
15
|
+
`HazoError`, `withContext`, `generateRequestId`, `createLogger`,
|
|
16
|
+
`loadConfig`.
|
|
17
|
+
- **`hazo_ui` peer dependency (`^3.0.1`, optional).** Client components now
|
|
18
|
+
import `cn` from `hazo_ui` — consumers that use `<FieldAuditIcon>` etc.
|
|
19
|
+
must install it. Server-only consumers can skip it.
|
|
20
|
+
- **Workspace-standard logger** at `src/utils/logger.ts` — `get_logger()` /
|
|
21
|
+
`set_logger()` default to `createLogger('hazo_audit')`. Worker + Next.js
|
|
22
|
+
routes emit structured event names (`worker.started`,
|
|
23
|
+
`outbox.row_drained`, `outbox.row_failed`, `events.reveal.served`, etc.).
|
|
24
|
+
- **`withContext` wrapping** around every drained outbox row in the worker
|
|
25
|
+
and every Next.js handler — correlation IDs from the producing write flow
|
|
26
|
+
through every nested DB call and `fetchWithRequestId`.
|
|
27
|
+
- **`HazoError` subclasses** — `RecordNotFoundError` now extends
|
|
28
|
+
`HazoNotFoundError` (`HAZO_AUDIT_RECORD_NOT_FOUND`, httpStatus 404). Route
|
|
29
|
+
handlers use a `handleError(logger, event, error)` helper that maps
|
|
30
|
+
`HazoError.is(err)` to the right HTTP status + log level.
|
|
31
|
+
- **`src/lib/config/hazo_audit_config.ts`** — workspace-standard INI
|
|
32
|
+
loader via `hazo_core.loadConfig` for `[general]` / `[log.overrides]` /
|
|
33
|
+
`[retention]` / `[worker]` sections. Coexists with
|
|
34
|
+
`src/server/config.ts::loadAuditConfig()`, which keeps owning the
|
|
35
|
+
package-specific `[capture]` and `[overrides.*]` rules.
|
|
36
|
+
- **`config/hazo_audit_config.ini.sample`** now documents the
|
|
37
|
+
`[general]` and `[log.overrides]` sections and `HAZO_AUDIT_<SECTION>_<KEY>`
|
|
38
|
+
env-var overrides.
|
|
39
|
+
- **Root `db_setup_postgres.sql` + `db_setup_sqlite.sql`** — idempotent,
|
|
40
|
+
consumer-facing schema setup (S6). The historical `migrations/001_init.sql`
|
|
41
|
+
is preserved.
|
|
42
|
+
- **`AGENTS.md`** at root + `src/`, and `design/feature_requests/INDEX.md`
|
|
43
|
+
skeleton (S13 + S16).
|
|
44
|
+
- **`/autotest` route** in the test-app powered by
|
|
45
|
+
`hazo_ui/test-harness` — workspace-standard browser scenarios
|
|
46
|
+
(`cn` re-export, `generateRequestId`, `HazoError` subclasses,
|
|
47
|
+
`withContext` / `getCorrelationId`).
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
|
|
51
|
+
- **`crypto.randomUUID()` removed** from all server modules in favour of
|
|
52
|
+
`src/utils/uuid.ts::generateUUID()` (wraps
|
|
53
|
+
`generateRequestId().slice(4)` from `hazo_core`).
|
|
54
|
+
- **`src/client/cn.ts` deleted** — components now import `cn` from
|
|
55
|
+
`hazo_ui`. The local `clsx + tailwind-merge` duplicate is gone.
|
|
56
|
+
- **Worker logger interface** narrowed to the workspace `Logger` type and
|
|
57
|
+
defaults to `get_logger()` when the consumer doesn't pass one.
|
|
58
|
+
- **test-app/package.json** uses `"*"` for workspace-local resolution of
|
|
59
|
+
`hazo_audit`, `hazo_connect`, `hazo_core`, `hazo_ui` (previously
|
|
60
|
+
`file:..`). `next.config.js` adds `hazo_core`, `hazo_logs`, `hazo_ui` to
|
|
61
|
+
`transpilePackages`.
|
|
62
|
+
- **`engines` block removed** from package.json (workspace standard).
|
|
63
|
+
- **Workspace registration** — `hazo_audit/test-app` is now listed in the
|
|
64
|
+
root `package.json` `workspaces` array.
|
|
65
|
+
|
|
66
|
+
## 2.0.2 — 2026-05-27
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- **`opLabels` prop** on `<FieldAuditTrail />` and `<FieldAuditIcon />` for
|
|
71
|
+
overriding the op badge text per app. Shape: `{ insert?, update?, delete? }`.
|
|
72
|
+
Exported alongside `DEFAULT_OP_LABELS` for apps that want to compose on top
|
|
73
|
+
of the defaults. Empty-string override falls back to the raw DB op so apps
|
|
74
|
+
can opt back into the legacy uppercase rendering per-op.
|
|
75
|
+
|
|
76
|
+
### Changed
|
|
77
|
+
|
|
78
|
+
- **Op badge defaults** — `INSERT` / `UPDATE` / `DELETE` are now rendered as
|
|
79
|
+
`Added` / `Changed` / `Removed` (action-oriented, past tense). This reads
|
|
80
|
+
naturally next to the actor line: `Added · Pubs Abayasiri`. The uppercase
|
|
81
|
+
text-transform on the badge was dropped so casing in `opLabels` is honoured.
|
|
82
|
+
|
|
3
83
|
## 2.0.1 — 2026-05-20
|
|
4
84
|
|
|
5
85
|
### Fixed
|
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ hazo_audit_field ◄──────────── /api/audit/events
|
|
|
72
72
|
|
|
73
73
|
| Import | What |
|
|
74
74
|
|---|---|
|
|
75
|
-
| `hazo_audit` | Client-safe types and permission constants (`AUDIT_VIEW_HISTORY`, etc.). |
|
|
75
|
+
| `hazo_audit` | Client-safe types and permission constants (`AUDIT_VIEW_HISTORY`, etc.). Also exports `OpLabels` interface and `DEFAULT_OP_LABELS` constant. |
|
|
76
76
|
| `hazo_audit/server` | `wrapWithAudit`, `startAuditWorker`, `runWithAuditContext`, `createAuditedCrudService`, `emitIntentEvent`. Server-only. |
|
|
77
77
|
| `hazo_audit/nextjs` | `createAuditEventsRoutes` — Next.js GET + reveal POST handlers. |
|
|
78
78
|
| `hazo_audit/client` | `<FieldAuditIcon />`, `<FieldAuditTrail />`. Safe in client components. |
|
|
@@ -172,6 +172,43 @@ await svc.updateById(folderId, patch, {
|
|
|
172
172
|
Direct Layer-1 intent emission (no piggyback). `withAudit(name, payload, fn)`
|
|
173
173
|
ergonomic is Phase 2.
|
|
174
174
|
|
|
175
|
+
### `OpLabels` / `DEFAULT_OP_LABELS`
|
|
176
|
+
|
|
177
|
+
Override the badge labels rendered on each event in `<FieldAuditTrail />`:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import { DEFAULT_OP_LABELS } from 'hazo_audit'
|
|
181
|
+
import { FieldAuditTrail } from 'hazo_audit/client'
|
|
182
|
+
|
|
183
|
+
// Keep defaults for insert/delete; rename update → "Edited"
|
|
184
|
+
<FieldAuditTrail
|
|
185
|
+
{...props}
|
|
186
|
+
opLabels={{ ...DEFAULT_OP_LABELS, update: 'Edited' }}
|
|
187
|
+
/>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Default labels: `insert → Added`, `update → Changed`, `delete → Removed`.
|
|
191
|
+
Pass an empty string for an op to fall back to the raw DB op.
|
|
192
|
+
|
|
193
|
+
### Intent labels (`intentLabels`)
|
|
194
|
+
|
|
195
|
+
When events are fired inside `auditIntent(adapter, name, callback)`, the
|
|
196
|
+
trail can annotate each event with a human-readable source label:
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
<FieldAuditTrail
|
|
200
|
+
{...props}
|
|
201
|
+
intentLabels={{
|
|
202
|
+
survey_prefill_key_info: 'auto-fill',
|
|
203
|
+
wp_pdf_prefill: 'PDF import',
|
|
204
|
+
}}
|
|
205
|
+
/>
|
|
206
|
+
// Renders: "Changed · Pubs Abayasiri (via auto-fill)"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The component fetches the correlated intent events automatically when
|
|
210
|
+
`intentLabels` is non-empty; no extra setup required.
|
|
211
|
+
|
|
175
212
|
### `createAuditEventsRoutes({ audit_adapter, getAuth })` → `{ GET, reveal: { POST } }`
|
|
176
213
|
|
|
177
214
|
Next.js route factory.
|
|
@@ -204,10 +241,29 @@ createAuditEventsRoutes({
|
|
|
204
241
|
Renders a clock icon that opens a popover with the field's audit trail.
|
|
205
242
|
Self-positioning, no Radix dep.
|
|
206
243
|
|
|
244
|
+
| Prop | Type | Default | Description |
|
|
245
|
+
|---|---|---|---|
|
|
246
|
+
| `subjectKind` | `string` | — | Required. Table-level subject kind (e.g. `"client_folder"`). |
|
|
247
|
+
| `subjectId` | `string` | — | Required. Record ID. |
|
|
248
|
+
| `fieldPath` | `string` | — | Field to scope the trail to. |
|
|
249
|
+
| `api_base` | `string` | `"/api/audit"` | Route prefix. |
|
|
250
|
+
| `opLabels` | `OpLabels` | `DEFAULT_OP_LABELS` | Override op badge text for individual ops. |
|
|
251
|
+
| `intentLabels` | `Record<string, string>` | — | Map of intent event names → display labels. Adds a "(via …)" annotation to events fired inside a named intent. |
|
|
252
|
+
|
|
207
253
|
### `<FieldAuditTrail subjectKind subjectId fieldPath? />`
|
|
208
254
|
|
|
209
255
|
Renders the trail directly (use in a sidebar, modal, etc.).
|
|
210
256
|
|
|
257
|
+
| Prop | Type | Default | Description |
|
|
258
|
+
|---|---|---|---|
|
|
259
|
+
| `subjectKind` | `string` | — | Required. |
|
|
260
|
+
| `subjectId` | `string` | — | Required. |
|
|
261
|
+
| `fieldPath` | `string` | — | Filter to one field. Omit for the full record trail. |
|
|
262
|
+
| `api_base` | `string` | `"/api/audit"` | Route prefix. |
|
|
263
|
+
| `opLabels` | `OpLabels` | `DEFAULT_OP_LABELS` | Override op badge text. See `OpLabels` + `DEFAULT_OP_LABELS`. |
|
|
264
|
+
| `intentLabels` | `Record<string, string>` | — | Annotate changes with their originating intent. Key = bare event name (`.completed`/`.failed` stripped). |
|
|
265
|
+
| `limit` | `number` | `50` | Max events to fetch. |
|
|
266
|
+
|
|
211
267
|
## Schema
|
|
212
268
|
|
|
213
269
|
Three tables (see `migrations/001_init.sql` for both Postgres + SQLite):
|
|
@@ -4,7 +4,35 @@
|
|
|
4
4
|
; and startAuditWorker() at startup. INI is the primary source of truth for
|
|
5
5
|
; capture rules; the only TypeScript-shaped escape hatch is the `resolvers`
|
|
6
6
|
; option on wrapWithAudit().
|
|
7
|
+
;
|
|
8
|
+
; Per hazo workspace standard S7, values may also be overridden via env vars:
|
|
9
|
+
; HAZO_AUDIT_<SECTION>_<KEY>=value
|
|
10
|
+
; e.g. HAZO_AUDIT_WORKER_POLL_MS=500
|
|
11
|
+
;
|
|
12
|
+
; Per D-019, a per-environment overlay file at
|
|
13
|
+
; config/hazo_audit_config.<HAZO_ENV>.ini
|
|
14
|
+
; is layered on top of this base file when HAZO_ENV is set.
|
|
15
|
+
|
|
16
|
+
; ============================================================================
|
|
17
|
+
; [general] - Workspace-standard general configuration
|
|
18
|
+
; ============================================================================
|
|
19
|
+
[general]
|
|
20
|
+
|
|
21
|
+
; Deployment environment label. Falls back to NODE_ENV, then 'development'.
|
|
22
|
+
; env = production
|
|
7
23
|
|
|
24
|
+
; ============================================================================
|
|
25
|
+
; [log.overrides] - Per-namespace log level overrides (workspace standard)
|
|
26
|
+
; Format: <namespace> = trace|debug|info|warn|error
|
|
27
|
+
; ============================================================================
|
|
28
|
+
[log.overrides]
|
|
29
|
+
|
|
30
|
+
; hazo_audit = info
|
|
31
|
+
; hazo_audit/worker = warn
|
|
32
|
+
|
|
33
|
+
; ============================================================================
|
|
34
|
+
; [capture] - Which tables to audit
|
|
35
|
+
; ============================================================================
|
|
8
36
|
[capture]
|
|
9
37
|
; Whitelist by prefix or exact name. At least one of include_* must match for
|
|
10
38
|
; a write to be captured (after passing exclusions).
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
-- hazo_audit PostgreSQL schema setup (idempotent)
|
|
2
|
+
--
|
|
3
|
+
-- Three tables:
|
|
4
|
+
-- hazo_audit_outbox : queue produced by wrapWithAudit, drained by the worker
|
|
5
|
+
-- hazo_audit_field : per-leaf field events the UI reads from
|
|
6
|
+
-- hazo_audit_intent : Layer-1 named events (e.g., 'audit_reveal')
|
|
7
|
+
|
|
8
|
+
CREATE TABLE IF NOT EXISTS hazo_audit_outbox (
|
|
9
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
10
|
+
scope_id UUID,
|
|
11
|
+
correlation_id UUID NOT NULL,
|
|
12
|
+
table_name TEXT NOT NULL,
|
|
13
|
+
subject_kind TEXT NOT NULL,
|
|
14
|
+
subject_id TEXT NOT NULL,
|
|
15
|
+
op TEXT NOT NULL CHECK (op IN ('insert','update','delete')),
|
|
16
|
+
before_row JSONB,
|
|
17
|
+
after_row JSONB,
|
|
18
|
+
actor_kind TEXT NOT NULL,
|
|
19
|
+
actor_user_id UUID,
|
|
20
|
+
actor_label TEXT,
|
|
21
|
+
intent_event TEXT,
|
|
22
|
+
intent_payload JSONB,
|
|
23
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
24
|
+
drained_at TIMESTAMPTZ,
|
|
25
|
+
attempt_count INTEGER NOT NULL DEFAULT 0,
|
|
26
|
+
last_attempt_at TIMESTAMPTZ,
|
|
27
|
+
failed_at TIMESTAMPTZ,
|
|
28
|
+
failure_reason TEXT
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
CREATE INDEX IF NOT EXISTS ix_hazo_audit_outbox_drain
|
|
32
|
+
ON hazo_audit_outbox (drained_at, failed_at, attempt_count, last_attempt_at)
|
|
33
|
+
WHERE drained_at IS NULL AND failed_at IS NULL;
|
|
34
|
+
|
|
35
|
+
CREATE INDEX IF NOT EXISTS ix_hazo_audit_outbox_chain
|
|
36
|
+
ON hazo_audit_outbox (subject_kind, subject_id, created_at DESC)
|
|
37
|
+
WHERE drained_at IS NOT NULL;
|
|
38
|
+
|
|
39
|
+
CREATE TABLE IF NOT EXISTS hazo_audit_field (
|
|
40
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
41
|
+
scope_id UUID,
|
|
42
|
+
correlation_id UUID NOT NULL,
|
|
43
|
+
subject_kind TEXT NOT NULL,
|
|
44
|
+
subject_id TEXT NOT NULL,
|
|
45
|
+
field_path TEXT NOT NULL,
|
|
46
|
+
op TEXT NOT NULL CHECK (op IN ('insert','update','delete')),
|
|
47
|
+
before_value JSONB,
|
|
48
|
+
after_value JSONB,
|
|
49
|
+
is_sensitive BOOLEAN NOT NULL DEFAULT FALSE,
|
|
50
|
+
actor_kind TEXT NOT NULL,
|
|
51
|
+
actor_user_id UUID,
|
|
52
|
+
actor_label TEXT,
|
|
53
|
+
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
CREATE INDEX IF NOT EXISTS ix_hazo_audit_field_subject
|
|
57
|
+
ON hazo_audit_field (subject_kind, subject_id, field_path, occurred_at DESC);
|
|
58
|
+
|
|
59
|
+
CREATE TABLE IF NOT EXISTS hazo_audit_intent (
|
|
60
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
61
|
+
scope_id UUID,
|
|
62
|
+
correlation_id UUID NOT NULL,
|
|
63
|
+
event_name TEXT NOT NULL,
|
|
64
|
+
payload JSONB,
|
|
65
|
+
subject_kind TEXT,
|
|
66
|
+
subject_id TEXT,
|
|
67
|
+
actor_kind TEXT NOT NULL,
|
|
68
|
+
actor_user_id UUID,
|
|
69
|
+
actor_label TEXT,
|
|
70
|
+
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
CREATE INDEX IF NOT EXISTS ix_hazo_audit_intent_event
|
|
74
|
+
ON hazo_audit_intent (event_name, occurred_at DESC);
|
|
75
|
+
|
|
76
|
+
NOTIFY pgrst, 'reload schema';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
-- hazo_audit SQLite schema setup (idempotent)
|
|
2
|
+
--
|
|
3
|
+
-- Three tables:
|
|
4
|
+
-- hazo_audit_outbox : queue produced by wrapWithAudit, drained by the worker
|
|
5
|
+
-- hazo_audit_field : per-leaf field events the UI reads from
|
|
6
|
+
-- hazo_audit_intent : Layer-1 named events (e.g., 'audit_reveal')
|
|
7
|
+
|
|
8
|
+
CREATE TABLE IF NOT EXISTS hazo_audit_outbox (
|
|
9
|
+
id TEXT PRIMARY KEY,
|
|
10
|
+
scope_id TEXT,
|
|
11
|
+
correlation_id TEXT NOT NULL,
|
|
12
|
+
table_name TEXT NOT NULL,
|
|
13
|
+
subject_kind TEXT NOT NULL,
|
|
14
|
+
subject_id TEXT NOT NULL,
|
|
15
|
+
op TEXT NOT NULL CHECK (op IN ('insert','update','delete')),
|
|
16
|
+
before_row TEXT,
|
|
17
|
+
after_row TEXT,
|
|
18
|
+
actor_kind TEXT NOT NULL,
|
|
19
|
+
actor_user_id TEXT,
|
|
20
|
+
actor_label TEXT,
|
|
21
|
+
intent_event TEXT,
|
|
22
|
+
intent_payload TEXT,
|
|
23
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
24
|
+
drained_at TEXT,
|
|
25
|
+
attempt_count INTEGER NOT NULL DEFAULT 0,
|
|
26
|
+
last_attempt_at TEXT,
|
|
27
|
+
failed_at TEXT,
|
|
28
|
+
failure_reason TEXT
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
CREATE INDEX IF NOT EXISTS ix_hazo_audit_outbox_drain
|
|
32
|
+
ON hazo_audit_outbox (drained_at, failed_at, attempt_count, last_attempt_at);
|
|
33
|
+
|
|
34
|
+
CREATE INDEX IF NOT EXISTS ix_hazo_audit_outbox_chain
|
|
35
|
+
ON hazo_audit_outbox (subject_kind, subject_id, created_at);
|
|
36
|
+
|
|
37
|
+
CREATE TABLE IF NOT EXISTS hazo_audit_field (
|
|
38
|
+
id TEXT PRIMARY KEY,
|
|
39
|
+
scope_id TEXT,
|
|
40
|
+
correlation_id TEXT NOT NULL,
|
|
41
|
+
subject_kind TEXT NOT NULL,
|
|
42
|
+
subject_id TEXT NOT NULL,
|
|
43
|
+
field_path TEXT NOT NULL,
|
|
44
|
+
op TEXT NOT NULL CHECK (op IN ('insert','update','delete')),
|
|
45
|
+
before_value TEXT,
|
|
46
|
+
after_value TEXT,
|
|
47
|
+
is_sensitive INTEGER NOT NULL DEFAULT 0,
|
|
48
|
+
actor_kind TEXT NOT NULL,
|
|
49
|
+
actor_user_id TEXT,
|
|
50
|
+
actor_label TEXT,
|
|
51
|
+
occurred_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
CREATE INDEX IF NOT EXISTS ix_hazo_audit_field_subject
|
|
55
|
+
ON hazo_audit_field (subject_kind, subject_id, field_path, occurred_at);
|
|
56
|
+
|
|
57
|
+
CREATE TABLE IF NOT EXISTS hazo_audit_intent (
|
|
58
|
+
id TEXT PRIMARY KEY,
|
|
59
|
+
scope_id TEXT,
|
|
60
|
+
correlation_id TEXT NOT NULL,
|
|
61
|
+
event_name TEXT NOT NULL,
|
|
62
|
+
payload TEXT,
|
|
63
|
+
subject_kind TEXT,
|
|
64
|
+
subject_id TEXT,
|
|
65
|
+
actor_kind TEXT NOT NULL,
|
|
66
|
+
actor_user_id TEXT,
|
|
67
|
+
actor_label TEXT,
|
|
68
|
+
occurred_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
CREATE INDEX IF NOT EXISTS ix_hazo_audit_intent_event
|
|
72
|
+
ON hazo_audit_intent (event_name, occurred_at);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useState } from 'react';
|
|
4
|
-
import { cn } from '
|
|
4
|
+
import { cn } from 'hazo_ui';
|
|
5
5
|
export function AuditViewer({ api_base = '/api/audit', initialFilters = {}, className, }) {
|
|
6
6
|
const [filters, setFilters] = useState(initialFilters);
|
|
7
7
|
const [events, setEvents] = useState([]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FieldAuditIcon.d.ts","sourceRoot":"","sources":["../../src/client/FieldAuditIcon.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,GAAG,CAAC,OAAO,
|
|
1
|
+
{"version":3,"file":"FieldAuditIcon.d.ts","sourceRoot":"","sources":["../../src/client/FieldAuditIcon.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,GAAG,CAAC,OAAO,CAmFtE"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from 'react';
|
|
4
|
-
import { cn } from '
|
|
4
|
+
import { cn } from 'hazo_ui';
|
|
5
5
|
import { FieldAuditTrail } from './FieldAuditTrail.js';
|
|
6
6
|
/**
|
|
7
7
|
* Small clock-icon button that opens a popover with the field's audit trail.
|
|
@@ -12,7 +12,7 @@ import { FieldAuditTrail } from './FieldAuditTrail.js';
|
|
|
12
12
|
* permission" hint.
|
|
13
13
|
*/
|
|
14
14
|
export function FieldAuditIcon(props) {
|
|
15
|
-
const { subjectKind, subjectId, fieldPath, api_base, className, label } = props;
|
|
15
|
+
const { subjectKind, subjectId, fieldPath, api_base, intentLabels, opLabels, className, label, } = props;
|
|
16
16
|
const [open, setOpen] = React.useState(false);
|
|
17
17
|
const rootRef = React.useRef(null);
|
|
18
18
|
React.useEffect(() => {
|
|
@@ -35,7 +35,7 @@ export function FieldAuditIcon(props) {
|
|
|
35
35
|
document.removeEventListener('keydown', onKey);
|
|
36
36
|
};
|
|
37
37
|
}, [open]);
|
|
38
|
-
return (_jsxs("div", { ref: rootRef, className: "relative inline-block", children: [_jsx("button", { type: "button", "aria-label": label ?? `View audit history for ${fieldPath}`, title: label ?? 'View audit history', onClick: () => setOpen((v) => !v), className: cn('inline-flex h-6 w-6 items-center justify-center rounded-full', 'text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700', 'focus:outline-none focus:ring-2 focus:ring-slate-400', 'dark:hover:bg-slate-800 dark:hover:text-slate-200', className), children: _jsx(ClockIcon, { className: "h-3.5 w-3.5" }) }), open && (_jsxs("div", { role: "dialog", "aria-label": "Audit history", className: cn('absolute right-0 z-50 mt-2 w-96 max-w-[calc(100vw-2rem)]', 'rounded-md border border-slate-200 bg-white p-3 shadow-lg', 'dark:border-slate-700 dark:bg-slate-900'), children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsxs("h3", { className: "text-sm font-semibold text-slate-900 dark:text-slate-100", children: ["History \u00B7 ", fieldPath] }), _jsx("button", { type: "button", "aria-label": "Close", onClick: () => setOpen(false), className: "text-slate-400 hover:text-slate-700 dark:hover:text-slate-200", children: _jsx(CloseIcon, { className: "h-4 w-4" }) })] }), _jsx(FieldAuditTrail, { subjectKind: subjectKind, subjectId: subjectId, fieldPath: fieldPath, api_base: api_base })] }))] }));
|
|
38
|
+
return (_jsxs("div", { ref: rootRef, className: "relative inline-block", children: [_jsx("button", { type: "button", "aria-label": label ?? `View audit history for ${fieldPath}`, title: label ?? 'View audit history', onClick: () => setOpen((v) => !v), className: cn('inline-flex h-6 w-6 items-center justify-center rounded-full', 'text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700', 'focus:outline-none focus:ring-2 focus:ring-slate-400', 'dark:hover:bg-slate-800 dark:hover:text-slate-200', className), children: _jsx(ClockIcon, { className: "h-3.5 w-3.5" }) }), open && (_jsxs("div", { role: "dialog", "aria-label": "Audit history", className: cn('absolute right-0 z-50 mt-2 w-96 max-w-[calc(100vw-2rem)]', 'rounded-md border border-slate-200 bg-white p-3 shadow-lg', 'dark:border-slate-700 dark:bg-slate-900'), children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsxs("h3", { className: "text-sm font-semibold text-slate-900 dark:text-slate-100", children: ["History \u00B7 ", fieldPath] }), _jsx("button", { type: "button", "aria-label": "Close", onClick: () => setOpen(false), className: "text-slate-400 hover:text-slate-700 dark:hover:text-slate-200", children: _jsx(CloseIcon, { className: "h-4 w-4" }) })] }), _jsx(FieldAuditTrail, { subjectKind: subjectKind, subjectId: subjectId, fieldPath: fieldPath, api_base: api_base, intentLabels: intentLabels, opLabels: opLabels })] }))] }));
|
|
39
39
|
}
|
|
40
40
|
function ClockIcon({ className }) {
|
|
41
41
|
return (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: className, "aria-hidden": true, children: [_jsx("circle", { cx: "12", cy: "12", r: "10" }), _jsx("polyline", { points: "12 6 12 12 16 14" })] }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FieldAuditIcon.js","sourceRoot":"","sources":["../../src/client/FieldAuditIcon.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,KAA0B;IACvD,MAAM,
|
|
1
|
+
{"version":3,"file":"FieldAuditIcon.js","sourceRoot":"","sources":["../../src/client/FieldAuditIcon.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FieldAuditTrail.d.ts","sourceRoot":"","sources":["../../src/client/FieldAuditTrail.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"FieldAuditTrail.d.ts","sourceRoot":"","sources":["../../src/client/FieldAuditTrail.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,GAAG,CAAC,OAAO,CAiNxE"}
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from 'react';
|
|
4
|
-
import { cn } from '
|
|
4
|
+
import { cn } from 'hazo_ui';
|
|
5
|
+
import { DEFAULT_OP_LABELS } from '../index.js';
|
|
5
6
|
/**
|
|
6
7
|
* Renders an ordered list of field audit events for a subject (optionally
|
|
7
8
|
* filtered to a single field_path). Sensitive rows show "[redacted]" with a
|
|
8
9
|
* "Reveal" button that calls /audit/events/[id]/reveal.
|
|
9
10
|
*/
|
|
10
11
|
export function FieldAuditTrail(props) {
|
|
11
|
-
const { subjectKind, subjectId, fieldPath, api_base = '/api/audit', limit = 50, className, onRevealed, } = props;
|
|
12
|
-
const
|
|
12
|
+
const { subjectKind, subjectId, fieldPath, api_base = '/api/audit', intentLabels, opLabels, limit = 50, className, onRevealed, } = props;
|
|
13
|
+
const wantIntent = !!intentLabels && Object.keys(intentLabels).length > 0;
|
|
14
|
+
const [state, setState] = React.useState({
|
|
15
|
+
loading: true,
|
|
16
|
+
events: [],
|
|
17
|
+
intentByCorrelation: new Map(),
|
|
18
|
+
revealed: {},
|
|
19
|
+
});
|
|
13
20
|
React.useEffect(() => {
|
|
14
21
|
let cancelled = false;
|
|
15
22
|
setState((s) => ({ ...s, loading: true, error: undefined }));
|
|
@@ -20,6 +27,8 @@ export function FieldAuditTrail(props) {
|
|
|
20
27
|
});
|
|
21
28
|
if (fieldPath)
|
|
22
29
|
params.set('field_path', fieldPath);
|
|
30
|
+
if (wantIntent)
|
|
31
|
+
params.set('include', 'intent');
|
|
23
32
|
fetch(`${api_base}/events?${params.toString()}`, { credentials: 'include' })
|
|
24
33
|
.then(async (r) => {
|
|
25
34
|
if (r.status === 403) {
|
|
@@ -37,14 +46,35 @@ export function FieldAuditTrail(props) {
|
|
|
37
46
|
setState({
|
|
38
47
|
loading: false,
|
|
39
48
|
events: [],
|
|
49
|
+
intentByCorrelation: new Map(),
|
|
40
50
|
revealed: {},
|
|
41
51
|
error: 'You do not have permission to view audit history.',
|
|
42
52
|
});
|
|
43
53
|
return;
|
|
44
54
|
}
|
|
55
|
+
// Build correlation_id → intent map. When multiple events share a
|
|
56
|
+
// correlation_id (e.g. `name` + `name.completed`), prefer the bare
|
|
57
|
+
// started event since `.completed`/`.failed` carry lifecycle status
|
|
58
|
+
// rather than the user-meaningful source label.
|
|
59
|
+
const intentMap = new Map();
|
|
60
|
+
for (const ie of data.intent_events ?? []) {
|
|
61
|
+
const existing = intentMap.get(ie.correlation_id);
|
|
62
|
+
if (!existing) {
|
|
63
|
+
intentMap.set(ie.correlation_id, ie);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const existingIsLifecycle = existing.event_name.endsWith('.completed') ||
|
|
67
|
+
existing.event_name.endsWith('.failed');
|
|
68
|
+
const candidateIsLifecycle = ie.event_name.endsWith('.completed') ||
|
|
69
|
+
ie.event_name.endsWith('.failed');
|
|
70
|
+
if (existingIsLifecycle && !candidateIsLifecycle) {
|
|
71
|
+
intentMap.set(ie.correlation_id, ie);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
45
74
|
setState({
|
|
46
75
|
loading: false,
|
|
47
76
|
events: data.field_events ?? [],
|
|
77
|
+
intentByCorrelation: intentMap,
|
|
48
78
|
revealed: {},
|
|
49
79
|
});
|
|
50
80
|
})
|
|
@@ -54,6 +84,7 @@ export function FieldAuditTrail(props) {
|
|
|
54
84
|
setState({
|
|
55
85
|
loading: false,
|
|
56
86
|
events: [],
|
|
87
|
+
intentByCorrelation: new Map(),
|
|
57
88
|
revealed: {},
|
|
58
89
|
error: err.message,
|
|
59
90
|
});
|
|
@@ -61,7 +92,7 @@ export function FieldAuditTrail(props) {
|
|
|
61
92
|
return () => {
|
|
62
93
|
cancelled = true;
|
|
63
94
|
};
|
|
64
|
-
}, [subjectKind, subjectId, fieldPath, api_base, limit]);
|
|
95
|
+
}, [subjectKind, subjectId, fieldPath, api_base, limit, wantIntent]);
|
|
65
96
|
async function handleReveal(id) {
|
|
66
97
|
try {
|
|
67
98
|
const r = await fetch(`${api_base}/events/${id}/reveal`, {
|
|
@@ -96,11 +127,43 @@ export function FieldAuditTrail(props) {
|
|
|
96
127
|
if (state.events.length === 0) {
|
|
97
128
|
return (_jsx("div", { className: cn('py-3 text-center text-xs text-slate-400', className), children: "No history yet." }));
|
|
98
129
|
}
|
|
99
|
-
return (_jsx("ol", { className: cn('space-y-2 text-sm', className), children: state.events.map((ev) =>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
130
|
+
return (_jsx("ol", { className: cn('space-y-2 text-sm', className), children: state.events.map((ev) => {
|
|
131
|
+
const sourceLabel = resolveIntentLabel(ev.correlation_id, state.intentByCorrelation, intentLabels);
|
|
132
|
+
const actor = ev.actor_label || ev.actor_user_id || ev.actor_kind;
|
|
133
|
+
return (_jsxs("li", { className: "rounded border border-slate-200 p-2 dark:border-slate-700", children: [_jsxs("div", { className: "mb-1 flex items-center justify-between gap-2 text-xs text-slate-500", children: [_jsxs("span", { children: [_jsx("span", { className: cn('mr-2 inline-block rounded px-1.5 py-0.5 text-[10px] font-semibold tracking-wide', ev.op === 'insert'
|
|
134
|
+
? 'bg-emerald-600 text-white dark:bg-emerald-500'
|
|
135
|
+
: ev.op === 'delete'
|
|
136
|
+
? 'bg-rose-600 text-white dark:bg-rose-500'
|
|
137
|
+
: 'bg-sky-600 text-white dark:bg-sky-500'), children: resolveOpLabel(ev.op, opLabels) }), actor, sourceLabel && (_jsxs("span", { className: "ml-1 text-slate-500 dark:text-slate-400", children: ["(via ", sourceLabel, ")"] }))] }), _jsx("time", { dateTime: ev.occurred_at, title: ev.occurred_at, className: "text-[11px] text-slate-400", children: formatDate(ev.occurred_at) })] }), !fieldPath && (_jsx("div", { className: "mb-1 font-mono text-xs text-slate-600 dark:text-slate-300", children: ev.field_path })), _jsx(ValueDiff, { event: ev, revealed: state.revealed[ev.id], onReveal: () => handleReveal(ev.id) })] }, ev.id));
|
|
138
|
+
}) }));
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Resolve the friendly badge label for a field event op. Consumer-supplied
|
|
142
|
+
* overrides win; otherwise the package defaults (Added / Changed / Removed)
|
|
143
|
+
* apply. An explicit empty-string override falls back to the raw DB op so
|
|
144
|
+
* apps can opt into the legacy uppercase rendering per-op.
|
|
145
|
+
*/
|
|
146
|
+
function resolveOpLabel(op, overrides) {
|
|
147
|
+
const override = overrides?.[op];
|
|
148
|
+
if (override !== undefined)
|
|
149
|
+
return override === '' ? op : override;
|
|
150
|
+
return DEFAULT_OP_LABELS[op];
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Look up an intent label for a field event by correlation_id. `.completed`
|
|
154
|
+
* and `.failed` suffixes are stripped so the app's `intentLabels` map only
|
|
155
|
+
* needs the bare event names.
|
|
156
|
+
*/
|
|
157
|
+
function resolveIntentLabel(correlation_id, intentByCorrelation, labels) {
|
|
158
|
+
if (!labels)
|
|
159
|
+
return undefined;
|
|
160
|
+
const intent = intentByCorrelation.get(correlation_id);
|
|
161
|
+
if (!intent)
|
|
162
|
+
return undefined;
|
|
163
|
+
const name = intent.event_name
|
|
164
|
+
.replace(/\.completed$/, '')
|
|
165
|
+
.replace(/\.failed$/, '');
|
|
166
|
+
return labels[name];
|
|
104
167
|
}
|
|
105
168
|
function ValueDiff({ event, revealed, onReveal, }) {
|
|
106
169
|
if (event.is_sensitive && !revealed) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FieldAuditTrail.js","sourceRoot":"","sources":["../../src/client/FieldAuditTrail.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAQ7B;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,KAA2B;IACzD,MAAM,EACJ,WAAW,EACX,SAAS,EACT,SAAS,EACT,QAAQ,GAAG,YAAY,EACvB,KAAK,GAAG,EAAE,EACV,SAAS,EACT,UAAU,GACX,GAAG,KAAK,CAAC;IAEV,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,KAAK,CAAC,QAAQ,
|
|
1
|
+
{"version":3,"file":"FieldAuditTrail.js","sourceRoot":"","sources":["../../src/client/FieldAuditTrail.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from 'react';
|
|
4
|
-
import { cn } from '
|
|
4
|
+
import { cn } from 'hazo_ui';
|
|
5
5
|
export function RevertConfirmDialog({ open, table, recordId, toTimestamp, onConfirm, onClose, className, }) {
|
|
6
6
|
const [reason, setReason] = useState('');
|
|
7
7
|
const [working, setWorking] = useState(false);
|