hazo_audit 1.0.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 +53 -0
- package/README.md +269 -0
- package/SETUP_CHECKLIST.md +186 -0
- package/bin/hazo-audit-migrate.js +169 -0
- package/config/hazo_audit_config.ini.sample +71 -0
- package/dist/client/FieldAuditIcon.d.ts +11 -0
- package/dist/client/FieldAuditIcon.d.ts.map +1 -0
- package/dist/client/FieldAuditIcon.js +46 -0
- package/dist/client/FieldAuditIcon.js.map +1 -0
- package/dist/client/FieldAuditTrail.d.ts +8 -0
- package/dist/client/FieldAuditTrail.d.ts.map +1 -0
- package/dist/client/FieldAuditTrail.js +136 -0
- package/dist/client/FieldAuditTrail.js.map +1 -0
- package/dist/client/cn.d.ts +3 -0
- package/dist/client/cn.d.ts.map +1 -0
- package/dist/client/cn.js +6 -0
- package/dist/client/cn.js.map +1 -0
- package/dist/client/index.d.ts +11 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +9 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/types.d.ts +19 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/nextjs/index.d.ts +78 -0
- package/dist/nextjs/index.d.ts.map +1 -0
- package/dist/nextjs/index.js +229 -0
- package/dist/nextjs/index.js.map +1 -0
- package/dist/server/audited_crud.d.ts +42 -0
- package/dist/server/audited_crud.d.ts.map +1 -0
- package/dist/server/audited_crud.js +57 -0
- package/dist/server/audited_crud.js.map +1 -0
- package/dist/server/config.d.ts +70 -0
- package/dist/server/config.d.ts.map +1 -0
- package/dist/server/config.js +203 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/context.d.ts +28 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/context.js +49 -0
- package/dist/server/context.js.map +1 -0
- package/dist/server/diff.d.ts +32 -0
- package/dist/server/diff.d.ts.map +1 -0
- package/dist/server/diff.js +152 -0
- package/dist/server/diff.js.map +1 -0
- package/dist/server/index.d.ts +23 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +26 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/outbox.d.ts +29 -0
- package/dist/server/outbox.d.ts.map +1 -0
- package/dist/server/outbox.js +53 -0
- package/dist/server/outbox.js.map +1 -0
- package/dist/server/worker.d.ts +43 -0
- package/dist/server/worker.d.ts.map +1 -0
- package/dist/server/worker.js +248 -0
- package/dist/server/worker.js.map +1 -0
- package/dist/server/wrap.d.ts +32 -0
- package/dist/server/wrap.d.ts.map +1 -0
- package/dist/server/wrap.js +232 -0
- package/dist/server/wrap.js.map +1 -0
- package/migrations/001_init.sql +149 -0
- package/package.json +101 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
; hazo_audit configuration
|
|
2
|
+
;
|
|
3
|
+
; Copy to `hazo_audit_config.ini` and customise. Loaded by wrapWithAudit()
|
|
4
|
+
; and startAuditWorker() at startup. INI is the primary source of truth for
|
|
5
|
+
; capture rules; the only TypeScript-shaped escape hatch is the `resolvers`
|
|
6
|
+
; option on wrapWithAudit().
|
|
7
|
+
|
|
8
|
+
[capture]
|
|
9
|
+
; Whitelist by prefix or exact name. At least one of include_* must match for
|
|
10
|
+
; a write to be captured (after passing exclusions).
|
|
11
|
+
include_table_prefixes = app_, hazo_
|
|
12
|
+
include_tables =
|
|
13
|
+
|
|
14
|
+
; Blacklist by prefix or exact name. Always wins over include rules.
|
|
15
|
+
exclude_table_prefixes = _
|
|
16
|
+
exclude_tables = hazo_chat_typing_indicators, hazo_worker_heartbeats
|
|
17
|
+
|
|
18
|
+
; Note: the audit pipeline tables themselves
|
|
19
|
+
; (hazo_audit_outbox, hazo_audit_field, hazo_audit_intent)
|
|
20
|
+
; are hardcoded as never-capture regardless of this config.
|
|
21
|
+
|
|
22
|
+
[retention]
|
|
23
|
+
; Days to keep successfully-drained outbox rows before the reaper deletes them.
|
|
24
|
+
outbox_drained_ttl_days = 30
|
|
25
|
+
; Days to keep dead-lettered (failed) outbox rows before reaping.
|
|
26
|
+
outbox_failed_ttl_days = 90
|
|
27
|
+
|
|
28
|
+
[worker]
|
|
29
|
+
poll_ms = 2000
|
|
30
|
+
max_attempts = 5
|
|
31
|
+
; Initial retry delay in ms; multiplied by 2 per attempt.
|
|
32
|
+
backoff_initial_ms = 500
|
|
33
|
+
; How often the reaper sweeps old outbox rows (ms).
|
|
34
|
+
reaper_interval_ms = 3600000
|
|
35
|
+
|
|
36
|
+
[defaults]
|
|
37
|
+
subject_id_column = id
|
|
38
|
+
|
|
39
|
+
; ============================================================================
|
|
40
|
+
; Per-table overrides
|
|
41
|
+
; ============================================================================
|
|
42
|
+
; Each [overrides.<table>] block customises capture behavior for one table.
|
|
43
|
+
;
|
|
44
|
+
; Keys:
|
|
45
|
+
; subject_kind logical kind name (default: table name)
|
|
46
|
+
; subject_id_column primary key column (default: from [defaults])
|
|
47
|
+
; sensitive_columns comma-separated list; values masked from /audit/events
|
|
48
|
+
; unless caller has view_sensitive_audit permission
|
|
49
|
+
; jsonb_columns columns to decompose into per-leaf field events
|
|
50
|
+
; jsonb_path_include prefixes to keep (after exclude); empty = keep all
|
|
51
|
+
; jsonb_path_exclude prefixes to drop from JSONB diff output
|
|
52
|
+
; resolver named resolver (from `resolvers: {...}` TS option)
|
|
53
|
+
; to canonicalise / categorise JSONB paths
|
|
54
|
+
; fanout_bulk_writes if true, emit one outbox row per affected row on
|
|
55
|
+
; bulk inserts / non-id updates (pre-write GET)
|
|
56
|
+
;
|
|
57
|
+
; Example: an `app_clients` table with masked SSN/DOB columns.
|
|
58
|
+
;
|
|
59
|
+
; [overrides.app_clients]
|
|
60
|
+
; subject_kind = client
|
|
61
|
+
; sensitive_columns = ssn, dob
|
|
62
|
+
;
|
|
63
|
+
; Example: a `app_client_folders` table whose `working_paper` JSONB column
|
|
64
|
+
; should be diffed but with internal `__prefill_data` paths suppressed.
|
|
65
|
+
; A named `wp` resolver (passed to wrapWithAudit) canonicalises paths.
|
|
66
|
+
;
|
|
67
|
+
; [overrides.app_client_folders]
|
|
68
|
+
; subject_kind = client_folder
|
|
69
|
+
; jsonb_columns = working_paper
|
|
70
|
+
; jsonb_path_exclude = working_paper.__prefill_data, working_paper.__reference_data
|
|
71
|
+
; resolver = wp
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FieldAuditIconProps } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Small clock-icon button that opens a popover with the field's audit trail.
|
|
4
|
+
*
|
|
5
|
+
* Self-positioning popover (no Radix/portal dep) — anchored to the icon with
|
|
6
|
+
* a click-outside listener. If the caller lacks `view_audit_history` the
|
|
7
|
+
* /audit/events call returns 403 and the popover renders a quiet "no
|
|
8
|
+
* permission" hint.
|
|
9
|
+
*/
|
|
10
|
+
export declare function FieldAuditIcon(props: FieldAuditIconProps): JSX.Element;
|
|
11
|
+
//# sourceMappingURL=FieldAuditIcon.d.ts.map
|
|
@@ -0,0 +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,CAwEtE"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { cn } from './cn.js';
|
|
5
|
+
import { FieldAuditTrail } from './FieldAuditTrail.js';
|
|
6
|
+
/**
|
|
7
|
+
* Small clock-icon button that opens a popover with the field's audit trail.
|
|
8
|
+
*
|
|
9
|
+
* Self-positioning popover (no Radix/portal dep) — anchored to the icon with
|
|
10
|
+
* a click-outside listener. If the caller lacks `view_audit_history` the
|
|
11
|
+
* /audit/events call returns 403 and the popover renders a quiet "no
|
|
12
|
+
* permission" hint.
|
|
13
|
+
*/
|
|
14
|
+
export function FieldAuditIcon(props) {
|
|
15
|
+
const { subjectKind, subjectId, fieldPath, api_base, className, label } = props;
|
|
16
|
+
const [open, setOpen] = React.useState(false);
|
|
17
|
+
const rootRef = React.useRef(null);
|
|
18
|
+
React.useEffect(() => {
|
|
19
|
+
if (!open)
|
|
20
|
+
return;
|
|
21
|
+
const onDocClick = (e) => {
|
|
22
|
+
if (!rootRef.current)
|
|
23
|
+
return;
|
|
24
|
+
if (!rootRef.current.contains(e.target))
|
|
25
|
+
setOpen(false);
|
|
26
|
+
};
|
|
27
|
+
const onKey = (e) => {
|
|
28
|
+
if (e.key === 'Escape')
|
|
29
|
+
setOpen(false);
|
|
30
|
+
};
|
|
31
|
+
document.addEventListener('mousedown', onDocClick);
|
|
32
|
+
document.addEventListener('keydown', onKey);
|
|
33
|
+
return () => {
|
|
34
|
+
document.removeEventListener('mousedown', onDocClick);
|
|
35
|
+
document.removeEventListener('keydown', onKey);
|
|
36
|
+
};
|
|
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 })] }))] }));
|
|
39
|
+
}
|
|
40
|
+
function ClockIcon({ className }) {
|
|
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" })] }));
|
|
42
|
+
}
|
|
43
|
+
function CloseIcon({ className }) {
|
|
44
|
+
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("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), _jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }));
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=FieldAuditIcon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldAuditIcon.js","sourceRoot":"","sources":["../../src/client/FieldAuditIcon.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FieldAuditTrailProps } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Renders an ordered list of field audit events for a subject (optionally
|
|
4
|
+
* filtered to a single field_path). Sensitive rows show "[redacted]" with a
|
|
5
|
+
* "Reveal" button that calls /audit/events/[id]/reveal.
|
|
6
|
+
*/
|
|
7
|
+
export declare function FieldAuditTrail(props: FieldAuditTrailProps): JSX.Element;
|
|
8
|
+
//# sourceMappingURL=FieldAuditTrail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldAuditTrail.d.ts","sourceRoot":"","sources":["../../src/client/FieldAuditTrail.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,GAAG,CAAC,OAAO,CA+JxE"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { cn } from './cn.js';
|
|
5
|
+
/**
|
|
6
|
+
* Renders an ordered list of field audit events for a subject (optionally
|
|
7
|
+
* filtered to a single field_path). Sensitive rows show "[redacted]" with a
|
|
8
|
+
* "Reveal" button that calls /audit/events/[id]/reveal.
|
|
9
|
+
*/
|
|
10
|
+
export function FieldAuditTrail(props) {
|
|
11
|
+
const { subjectKind, subjectId, fieldPath, api_base = '/api/audit', limit = 50, className, onRevealed, } = props;
|
|
12
|
+
const [state, setState] = React.useState({ loading: true, events: [], revealed: {} });
|
|
13
|
+
React.useEffect(() => {
|
|
14
|
+
let cancelled = false;
|
|
15
|
+
setState((s) => ({ ...s, loading: true, error: undefined }));
|
|
16
|
+
const params = new URLSearchParams({
|
|
17
|
+
subject_kind: subjectKind,
|
|
18
|
+
subject_id: subjectId,
|
|
19
|
+
limit: String(limit),
|
|
20
|
+
});
|
|
21
|
+
if (fieldPath)
|
|
22
|
+
params.set('field_path', fieldPath);
|
|
23
|
+
fetch(`${api_base}/events?${params.toString()}`, { credentials: 'include' })
|
|
24
|
+
.then(async (r) => {
|
|
25
|
+
if (r.status === 403) {
|
|
26
|
+
return { _forbidden: true };
|
|
27
|
+
}
|
|
28
|
+
if (!r.ok) {
|
|
29
|
+
throw new Error(`HTTP ${r.status}`);
|
|
30
|
+
}
|
|
31
|
+
return (await r.json());
|
|
32
|
+
})
|
|
33
|
+
.then((data) => {
|
|
34
|
+
if (cancelled)
|
|
35
|
+
return;
|
|
36
|
+
if ('_forbidden' in data) {
|
|
37
|
+
setState({
|
|
38
|
+
loading: false,
|
|
39
|
+
events: [],
|
|
40
|
+
revealed: {},
|
|
41
|
+
error: 'You do not have permission to view audit history.',
|
|
42
|
+
});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
setState({
|
|
46
|
+
loading: false,
|
|
47
|
+
events: data.field_events ?? [],
|
|
48
|
+
revealed: {},
|
|
49
|
+
});
|
|
50
|
+
})
|
|
51
|
+
.catch((err) => {
|
|
52
|
+
if (cancelled)
|
|
53
|
+
return;
|
|
54
|
+
setState({
|
|
55
|
+
loading: false,
|
|
56
|
+
events: [],
|
|
57
|
+
revealed: {},
|
|
58
|
+
error: err.message,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
return () => {
|
|
62
|
+
cancelled = true;
|
|
63
|
+
};
|
|
64
|
+
}, [subjectKind, subjectId, fieldPath, api_base, limit]);
|
|
65
|
+
async function handleReveal(id) {
|
|
66
|
+
try {
|
|
67
|
+
const r = await fetch(`${api_base}/events/${id}/reveal`, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
credentials: 'include',
|
|
70
|
+
});
|
|
71
|
+
if (!r.ok)
|
|
72
|
+
throw new Error(`HTTP ${r.status}`);
|
|
73
|
+
const body = (await r.json());
|
|
74
|
+
setState((s) => ({
|
|
75
|
+
...s,
|
|
76
|
+
revealed: {
|
|
77
|
+
...s.revealed,
|
|
78
|
+
[id]: { before: body.before_value, after: body.after_value },
|
|
79
|
+
},
|
|
80
|
+
}));
|
|
81
|
+
onRevealed?.(id, body.before_value, body.after_value);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
setState((s) => ({
|
|
85
|
+
...s,
|
|
86
|
+
error: `Reveal failed: ${err instanceof Error ? err.message : 'unknown'}`,
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (state.loading) {
|
|
91
|
+
return (_jsx("div", { className: cn('py-3 text-center text-xs text-slate-500', className), children: "Loading\u2026" }));
|
|
92
|
+
}
|
|
93
|
+
if (state.error) {
|
|
94
|
+
return (_jsx("div", { className: cn('py-3 text-center text-xs text-slate-500', className), children: state.error }));
|
|
95
|
+
}
|
|
96
|
+
if (state.events.length === 0) {
|
|
97
|
+
return (_jsx("div", { className: cn('py-3 text-center text-xs text-slate-400', className), children: "No history yet." }));
|
|
98
|
+
}
|
|
99
|
+
return (_jsx("ol", { className: cn('space-y-2 text-sm', className), children: state.events.map((ev) => (_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-medium uppercase', ev.op === 'insert'
|
|
100
|
+
? 'bg-emerald-100 text-emerald-700'
|
|
101
|
+
: ev.op === 'delete'
|
|
102
|
+
? 'bg-rose-100 text-rose-700'
|
|
103
|
+
: 'bg-sky-100 text-sky-700'), children: ev.op }), ev.actor_label || ev.actor_user_id || ev.actor_kind] }), _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))) }));
|
|
104
|
+
}
|
|
105
|
+
function ValueDiff({ event, revealed, onReveal, }) {
|
|
106
|
+
if (event.is_sensitive && !revealed) {
|
|
107
|
+
return (_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "italic text-slate-400", children: "[redacted]" }), _jsx("button", { type: "button", onClick: onReveal, className: "rounded border border-slate-300 px-2 py-0.5 text-[11px] text-slate-700 hover:bg-slate-50 dark:border-slate-600 dark:text-slate-200 dark:hover:bg-slate-800", children: "Reveal" })] }));
|
|
108
|
+
}
|
|
109
|
+
const before = revealed?.before ?? event.before_value;
|
|
110
|
+
const after = revealed?.after ?? event.after_value;
|
|
111
|
+
return (_jsxs("div", { className: "grid grid-cols-[auto_1fr] gap-x-2 gap-y-1 text-xs", children: [_jsx("span", { className: "text-slate-400", children: "before" }), _jsx("code", { className: "break-all text-slate-600 dark:text-slate-300", children: formatValue(before) }), _jsx("span", { className: "text-slate-400", children: "after" }), _jsx("code", { className: "break-all text-slate-900 dark:text-slate-100", children: formatValue(after) })] }));
|
|
112
|
+
}
|
|
113
|
+
function formatValue(v) {
|
|
114
|
+
if (v == null)
|
|
115
|
+
return '—';
|
|
116
|
+
if (typeof v === 'string')
|
|
117
|
+
return v;
|
|
118
|
+
try {
|
|
119
|
+
return JSON.stringify(v);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return String(v);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function formatDate(iso) {
|
|
126
|
+
try {
|
|
127
|
+
const d = new Date(iso);
|
|
128
|
+
if (Number.isNaN(d.getTime()))
|
|
129
|
+
return iso;
|
|
130
|
+
return d.toLocaleString();
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return iso;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=FieldAuditTrail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldAuditTrail.js","sourceRoot":"","sources":["../../src/client/FieldAuditTrail.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cn.d.ts","sourceRoot":"","sources":["../../src/client/cn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAG7C,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,CAElD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cn.js","sourceRoot":"","sources":["../../src/client/cn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,MAAM,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEzC,MAAM,UAAU,EAAE,CAAC,GAAG,MAAoB;IACxC,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hazo_audit/client — React UI primitives. Safe to import from any client
|
|
3
|
+
* component (no Node.js APIs, no hazo_connect, no hazo_auth).
|
|
4
|
+
*
|
|
5
|
+
* import { FieldAuditIcon, FieldAuditTrail } from 'hazo_audit/client'
|
|
6
|
+
*/
|
|
7
|
+
export { FieldAuditIcon } from './FieldAuditIcon.js';
|
|
8
|
+
export { FieldAuditTrail } from './FieldAuditTrail.js';
|
|
9
|
+
export type { FieldAuditIconProps, FieldAuditTrailProps } from './types.js';
|
|
10
|
+
export type { FieldAuditProps, FieldAuditEvent, IntentAuditEvent, AuditEventsResponse, RevealResponse, } from '../index.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAG5E,YAAY,EACV,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,cAAc,GACf,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hazo_audit/client — React UI primitives. Safe to import from any client
|
|
3
|
+
* component (no Node.js APIs, no hazo_connect, no hazo_auth).
|
|
4
|
+
*
|
|
5
|
+
* import { FieldAuditIcon, FieldAuditTrail } from 'hazo_audit/client'
|
|
6
|
+
*/
|
|
7
|
+
export { FieldAuditIcon } from './FieldAuditIcon.js';
|
|
8
|
+
export { FieldAuditTrail } from './FieldAuditTrail.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FieldAuditProps } from '../index.js';
|
|
2
|
+
export interface FieldAuditIconProps extends FieldAuditProps {
|
|
3
|
+
/** Specific field whose history to show. */
|
|
4
|
+
fieldPath: string;
|
|
5
|
+
/** Optional className applied to the trigger button. */
|
|
6
|
+
className?: string;
|
|
7
|
+
/** Optional label rendered as a tooltip / aria-label. */
|
|
8
|
+
label?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface FieldAuditTrailProps extends FieldAuditProps {
|
|
11
|
+
/** If set, restricts the trail to one field. Otherwise shows the subject's full trail. */
|
|
12
|
+
fieldPath?: string;
|
|
13
|
+
/** Maximum events to display. */
|
|
14
|
+
limit?: number;
|
|
15
|
+
className?: string;
|
|
16
|
+
/** Callback after a successful reveal; useful for live-updating the row. */
|
|
17
|
+
onRevealed?: (id: string, before: unknown, after: unknown) => void;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,MAAM,WAAW,mBAAoB,SAAQ,eAAe;IAC1D,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAqB,SAAQ,eAAe;IAC3D,0FAA0F;IAC1F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":""}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hazo_audit — client-safe entry point (types + permission constants).
|
|
3
|
+
*
|
|
4
|
+
* This file is safe to import from React client components. Server-only code
|
|
5
|
+
* (wrap, worker, AsyncLocalStorage, etc.) lives in `hazo_audit/server`.
|
|
6
|
+
*
|
|
7
|
+
* Multi-entry layout:
|
|
8
|
+
* import { AUDIT_VIEW_HISTORY } from 'hazo_audit'
|
|
9
|
+
* import { wrapWithAudit, ... } from 'hazo_audit/server'
|
|
10
|
+
* import { createAuditEventsRoutes } from 'hazo_audit/nextjs'
|
|
11
|
+
* import { FieldAuditIcon } from 'hazo_audit/client'
|
|
12
|
+
*/
|
|
13
|
+
export declare const AUDIT_VIEW_HISTORY: "view_audit_history";
|
|
14
|
+
export declare const AUDIT_VIEW_SENSITIVE: "view_sensitive_audit";
|
|
15
|
+
export declare const AUDIT_VIEW_ACROSS_SCOPES: "view_audit_across_scopes";
|
|
16
|
+
export type ActorKind = 'user' | 'system' | 'client' | 'super_admin' | 'background_job';
|
|
17
|
+
export interface ActorContext {
|
|
18
|
+
actor_kind: ActorKind;
|
|
19
|
+
actor_user_id?: string | null;
|
|
20
|
+
actor_label?: string | null;
|
|
21
|
+
scope_id?: string | null;
|
|
22
|
+
correlation_id?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface FieldAuditProps {
|
|
25
|
+
subjectKind: string;
|
|
26
|
+
subjectId: string;
|
|
27
|
+
/** Base path for audit routes. Defaults to `/api/audit`. */
|
|
28
|
+
api_base?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Canonicalises a JSONB path (e.g., `working_paper.sections.0.name`) into a
|
|
32
|
+
* stable, human-readable identifier. Apps register these by name in
|
|
33
|
+
* `wrapWithAudit({ resolvers: { wp: myResolver } })` and reference them from
|
|
34
|
+
* `[overrides.<table>] resolver = wp` in INI.
|
|
35
|
+
*/
|
|
36
|
+
export type PathResolver = (path: string) => string;
|
|
37
|
+
export interface FieldAuditEvent {
|
|
38
|
+
id: string;
|
|
39
|
+
scope_id: string | null;
|
|
40
|
+
correlation_id: string;
|
|
41
|
+
subject_kind: string;
|
|
42
|
+
subject_id: string;
|
|
43
|
+
field_path: string;
|
|
44
|
+
op: 'insert' | 'update' | 'delete';
|
|
45
|
+
before_value: unknown;
|
|
46
|
+
after_value: unknown;
|
|
47
|
+
is_sensitive: boolean;
|
|
48
|
+
actor_kind: ActorKind;
|
|
49
|
+
actor_user_id: string | null;
|
|
50
|
+
actor_label: string | null;
|
|
51
|
+
occurred_at: string;
|
|
52
|
+
}
|
|
53
|
+
export interface IntentAuditEvent {
|
|
54
|
+
id: string;
|
|
55
|
+
scope_id: string | null;
|
|
56
|
+
correlation_id: string;
|
|
57
|
+
event_name: string;
|
|
58
|
+
payload: unknown;
|
|
59
|
+
subject_kind: string | null;
|
|
60
|
+
subject_id: string | null;
|
|
61
|
+
actor_kind: ActorKind;
|
|
62
|
+
actor_user_id: string | null;
|
|
63
|
+
actor_label: string | null;
|
|
64
|
+
occurred_at: string;
|
|
65
|
+
}
|
|
66
|
+
export interface AuditEventsResponse {
|
|
67
|
+
field_events: FieldAuditEvent[];
|
|
68
|
+
intent_events?: IntentAuditEvent[];
|
|
69
|
+
next_cursor?: string | null;
|
|
70
|
+
}
|
|
71
|
+
export interface RevealResponse {
|
|
72
|
+
id: string;
|
|
73
|
+
before_value: unknown;
|
|
74
|
+
after_value: unknown;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,eAAO,MAAM,kBAAkB,EAAG,oBAA6B,CAAC;AAChE,eAAO,MAAM,oBAAoB,EAAG,sBAA+B,CAAC;AACpE,eAAO,MAAM,wBAAwB,EAAG,0BAAmC,CAAC;AAM5E,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,aAAa,GACb,gBAAgB,CAAC;AAErB,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,SAAS,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAMD,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;AAMpD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACnC,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,SAAS,CAAC;IACtB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,SAAS,CAAC;IACtB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,eAAe,EAAE,CAAC;IAChC,aAAa,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;CACtB"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hazo_audit — client-safe entry point (types + permission constants).
|
|
3
|
+
*
|
|
4
|
+
* This file is safe to import from React client components. Server-only code
|
|
5
|
+
* (wrap, worker, AsyncLocalStorage, etc.) lives in `hazo_audit/server`.
|
|
6
|
+
*
|
|
7
|
+
* Multi-entry layout:
|
|
8
|
+
* import { AUDIT_VIEW_HISTORY } from 'hazo_audit'
|
|
9
|
+
* import { wrapWithAudit, ... } from 'hazo_audit/server'
|
|
10
|
+
* import { createAuditEventsRoutes } from 'hazo_audit/nextjs'
|
|
11
|
+
* import { FieldAuditIcon } from 'hazo_audit/client'
|
|
12
|
+
*/
|
|
13
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
14
|
+
// Permission constants. These are the strings checked against the caller's
|
|
15
|
+
// `permissions[]` from hazo_auth. Apps can override per-route.
|
|
16
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
export const AUDIT_VIEW_HISTORY = 'view_audit_history';
|
|
18
|
+
export const AUDIT_VIEW_SENSITIVE = 'view_sensitive_audit';
|
|
19
|
+
export const AUDIT_VIEW_ACROSS_SCOPES = 'view_audit_across_scopes';
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,gFAAgF;AAChF,2EAA2E;AAC3E,+DAA+D;AAC/D,gFAAgF;AAEhF,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAA6B,CAAC;AAChE,MAAM,CAAC,MAAM,oBAAoB,GAAG,sBAA+B,CAAC;AACpE,MAAM,CAAC,MAAM,wBAAwB,GAAG,0BAAmC,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hazo_audit/nextjs — Next.js route handler factory for the audit API.
|
|
3
|
+
*
|
|
4
|
+
* The factory takes an injectable `getAuth(req)` callback so apps can plug
|
|
5
|
+
* their existing auth system (typically hazo_auth's hazo_get_tenant_auth)
|
|
6
|
+
* without a hard module dep at this layer.
|
|
7
|
+
*
|
|
8
|
+
* Mounting (Next.js App Router):
|
|
9
|
+
*
|
|
10
|
+
* // app/api/audit/events/route.ts
|
|
11
|
+
* import { createAuditEventsRoutes } from 'hazo_audit/nextjs'
|
|
12
|
+
* import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup'
|
|
13
|
+
* import { hazo_get_tenant_auth } from 'hazo_auth/lib/auth/hazo_get_tenant_auth.server'
|
|
14
|
+
*
|
|
15
|
+
* const audit = createAuditEventsRoutes({
|
|
16
|
+
* audit_adapter: await getHazoConnectSingleton(),
|
|
17
|
+
* getAuth: async (req) => {
|
|
18
|
+
* const a = await hazo_get_tenant_auth(req)
|
|
19
|
+
* if (!a.authenticated) return null
|
|
20
|
+
* return {
|
|
21
|
+
* user_id: a.user.id,
|
|
22
|
+
* scope_id: a.selected_scope_id,
|
|
23
|
+
* permissions: a.permissions,
|
|
24
|
+
* }
|
|
25
|
+
* },
|
|
26
|
+
* })
|
|
27
|
+
* export const GET = audit.GET
|
|
28
|
+
*
|
|
29
|
+
* // app/api/audit/events/[id]/reveal/route.ts
|
|
30
|
+
* export const POST = audit.reveal.POST
|
|
31
|
+
*/
|
|
32
|
+
import 'server-only';
|
|
33
|
+
import { NextResponse } from 'next/server';
|
|
34
|
+
/**
|
|
35
|
+
* Structural shape of the request object the route handlers and getAuth
|
|
36
|
+
* callback receive. Using a structural type (rather than `AuditRequest`)
|
|
37
|
+
* avoids nominal type clashes when the test/consumer app has a different
|
|
38
|
+
* next version than what hazo_audit was built against.
|
|
39
|
+
*/
|
|
40
|
+
export interface AuditRequest extends Request {
|
|
41
|
+
cookies?: {
|
|
42
|
+
get(name: string): {
|
|
43
|
+
value: string;
|
|
44
|
+
} | undefined;
|
|
45
|
+
};
|
|
46
|
+
nextUrl?: URL;
|
|
47
|
+
}
|
|
48
|
+
import type { HazoConnectAdapter } from 'hazo_connect';
|
|
49
|
+
export interface AuditAuth {
|
|
50
|
+
user_id: string;
|
|
51
|
+
scope_id: string | null;
|
|
52
|
+
permissions: string[];
|
|
53
|
+
/** Optional display label written to audit_reveal intent rows. */
|
|
54
|
+
user_label?: string | null;
|
|
55
|
+
}
|
|
56
|
+
export interface AuditEventsRoutesOptions {
|
|
57
|
+
audit_adapter: HazoConnectAdapter;
|
|
58
|
+
/** Resolve the caller's auth from the request. Return null = 401. */
|
|
59
|
+
getAuth: (req: AuditRequest) => Promise<AuditAuth | null> | AuditAuth | null;
|
|
60
|
+
history_permission?: string;
|
|
61
|
+
reveal_permission?: string;
|
|
62
|
+
cross_scope_permission?: string;
|
|
63
|
+
/** Default page size for list. Defaults to 50, capped at 200. */
|
|
64
|
+
default_limit?: number;
|
|
65
|
+
max_limit?: number;
|
|
66
|
+
}
|
|
67
|
+
type NextHandler = (req: AuditRequest, ctx?: {
|
|
68
|
+
params: Promise<Record<string, string>> | Record<string, string>;
|
|
69
|
+
}) => Promise<NextResponse>;
|
|
70
|
+
export interface AuditEventsRoutes {
|
|
71
|
+
GET: NextHandler;
|
|
72
|
+
reveal: {
|
|
73
|
+
POST: NextHandler;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export declare function createAuditEventsRoutes(opts: AuditEventsRoutesOptions): AuditEventsRoutes;
|
|
77
|
+
export {};
|
|
78
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/nextjs/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C;;;;;GAKG;AACH,MAAM,WAAW,YAAa,SAAQ,OAAO;IAC3C,OAAO,CAAC,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,GAAG,SAAS,CAAA;KAAE,CAAC;IAC/D,OAAO,CAAC,EAAE,GAAG,CAAC;CACf;AACD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAYvD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,kBAAkB,CAAC;IAClC,qEAAqE;IACrE,OAAO,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;IAC7E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,iEAAiE;IACjE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,KAAK,WAAW,GAAG,CACjB,GAAG,EAAE,YAAY,EACjB,GAAG,CAAC,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,KACvE,OAAO,CAAC,YAAY,CAAC,CAAC;AAE3B,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,WAAW,CAAC;IACjB,MAAM,EAAE;QAAE,IAAI,EAAE,WAAW,CAAA;KAAE,CAAC;CAC/B;AAuCD,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,wBAAwB,GAC7B,iBAAiB,CA4KnB"}
|