sibyl-plugin-schema: add 005_usage_log.sql migration
Browse filesPrivacy-first per-request usage log for plugin server-side endpoints.
Hashed IPs, categorized UA, coarse outcome categories. 30d retention.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
sibyl-plugin-schema/005_usage_log.sql
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-- 005_usage_log.sql
|
| 2 |
+
--
|
| 3 |
+
-- Per-request usage log for the plugin server-side endpoints. Privacy-first:
|
| 4 |
+
-- * No raw IPs stored. IP is hashed with sha256(ip + account_id) so the same
|
| 5 |
+
-- wallet/user across requests is linkable for debugging, but cross-account
|
| 6 |
+
-- correlation is impossible and the raw IP cannot be recovered.
|
| 7 |
+
-- * No user-agent string stored. Only categorized fields: ua_class (cli/sdk/
|
| 8 |
+
-- browser/mcp/unknown) and ua_os (macos/linux/windows/unknown). No version
|
| 9 |
+
-- fingerprint.
|
| 10 |
+
-- * No request bodies. The bytes_proposed / bytes_current fields capture
|
| 11 |
+
-- cap-gate context only (sizes, not content).
|
| 12 |
+
-- * Coarse outcome categories: ok / cap_exceeded / rate_limited / auth_failed
|
| 13 |
+
-- / token_expired / not_found / dependency_failed. Surfaces what went wrong
|
| 14 |
+
-- without leaking specifics.
|
| 15 |
+
--
|
| 16 |
+
-- Retention: 30 days. Pruned daily by an admin cron or manual DELETE.
|
| 17 |
+
--
|
| 18 |
+
-- Burden: 1 INSERT per /access /check-write /heartbeat /bind /email-bind call.
|
| 19 |
+
-- Single small row (~150 bytes); indexed on the queries the dashboard needs.
|
| 20 |
+
|
| 21 |
+
BEGIN;
|
| 22 |
+
|
| 23 |
+
CREATE TABLE IF NOT EXISTS sibyl_plugin.usage_log (
|
| 24 |
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 25 |
+
account_id UUID REFERENCES sibyl_plugin.accounts(account_id) ON DELETE CASCADE,
|
| 26 |
+
ts TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
| 27 |
+
endpoint TEXT NOT NULL, -- '/access', '/check-write', '/heartbeat', '/bind', '/email-bind'
|
| 28 |
+
method TEXT NOT NULL, -- 'GET' or 'POST'
|
| 29 |
+
status INT NOT NULL, -- HTTP status code returned
|
| 30 |
+
duration_ms INT, -- server-side processing time
|
| 31 |
+
ip_hash TEXT, -- sha256(raw_ip + account_id), 64 hex chars; null if no account context
|
| 32 |
+
ua_class TEXT, -- 'cli' / 'sdk' / 'mcp' / 'browser' / 'unknown'
|
| 33 |
+
ua_os TEXT, -- 'macos' / 'linux' / 'windows' / 'unknown'
|
| 34 |
+
bytes_proposed INT, -- check-write only: proposed_delta_bytes
|
| 35 |
+
bytes_current INT, -- check-write only: current_size_bytes
|
| 36 |
+
outcome TEXT, -- 'ok' / 'cap_exceeded' / 'auth_failed' / etc.
|
| 37 |
+
|
| 38 |
+
CONSTRAINT usage_log_endpoint_len CHECK (length(endpoint) <= 100),
|
| 39 |
+
CONSTRAINT usage_log_method_len CHECK (length(method) <= 16),
|
| 40 |
+
CONSTRAINT usage_log_ua_class_len CHECK (ua_class IS NULL OR length(ua_class) <= 32),
|
| 41 |
+
CONSTRAINT usage_log_ua_os_len CHECK (ua_os IS NULL OR length(ua_os) <= 32),
|
| 42 |
+
CONSTRAINT usage_log_outcome_len CHECK (outcome IS NULL OR length(outcome) <= 64),
|
| 43 |
+
CONSTRAINT usage_log_iphash_len CHECK (ip_hash IS NULL OR length(ip_hash) = 64)
|
| 44 |
+
);
|
| 45 |
+
|
| 46 |
+
-- Hot paths the dashboard queries:
|
| 47 |
+
-- 1. Per-account rollup (drill-down)
|
| 48 |
+
CREATE INDEX IF NOT EXISTS usage_log_account_ts_idx
|
| 49 |
+
ON sibyl_plugin.usage_log (account_id, ts DESC);
|
| 50 |
+
|
| 51 |
+
-- 2. Time-window aggregates (overview)
|
| 52 |
+
CREATE INDEX IF NOT EXISTS usage_log_ts_idx
|
| 53 |
+
ON sibyl_plugin.usage_log (ts DESC);
|
| 54 |
+
|
| 55 |
+
-- 3. Endpoint distribution (overview)
|
| 56 |
+
CREATE INDEX IF NOT EXISTS usage_log_endpoint_ts_idx
|
| 57 |
+
ON sibyl_plugin.usage_log (endpoint, ts DESC);
|
| 58 |
+
|
| 59 |
+
-- Schema version bump
|
| 60 |
+
INSERT INTO sibyl_plugin.schema_version (version, applied_at, notes)
|
| 61 |
+
VALUES (
|
| 62 |
+
6,
|
| 63 |
+
NOW(),
|
| 64 |
+
'usage_log: per-request privacy-first server-side log. Hashed IP, categorized UA, coarse outcome. 30d retention.'
|
| 65 |
+
)
|
| 66 |
+
ON CONFLICT (version) DO NOTHING;
|
| 67 |
+
|
| 68 |
+
COMMIT;
|