sibyllabs Claude Opus 4.7 commited on
Commit
9bbb2ee
·
1 Parent(s): 1271f76

sibyl-plugin-schema: add 005_usage_log.sql migration

Browse files

Privacy-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;