KB-7162
S170-FINAL KB Protection v2 Report
4 min read Revision 1
reportkb-protectionv2s170
S170-FINAL — KB Protection v2 Report
Date: 2026-04-04 | Session: S170-FINAL
Summary
Upgraded KB Protection from v1 (INSERT history, unbounded growth) to v2 (UPSERT history, bounded forever). Added PG permission guards. Fixed cron-env.sh permissions.
Changes
1. fn_kb_snapshot: INSERT -> UPSERT
- Before: Every UPDATE/DELETE appended a new history row -> unbounded growth
- After:
INSERT ON CONFLICT (document_key) DO UPDATE-> exactly 1 row per document, forever - 856 docs -> 856 history rows max, regardless of update frequency
2. UNIQUE constraint on kb_documents_history.document_key
uq_kb_history_document_keyadded- Enforces 1-row-per-doc invariant at PG kernel level
3. History deduplication (one-time)
- Before: 15 rows, 5 distinct keys
- After: 5 rows = 5 distinct keys
- Backup created, verified, then dropped
4. PG Permission guards
REVOKE DELETE, TRUNCATE ON kb_documents_history FROM incomexREVOKE DELETE, TRUNCATE ON kb_audit_log FROM incomex- UPSERT (INSERT ON CONFLICT UPDATE) unaffected — does not use DELETE
5. fn_kb_restore simplified
- Old: searched by revision parameter
- New: fetches the single row per document_key (revision param kept for compat, ignored)
6. dot-kb-verify.sh v2.0.0
- Added Check 7: Permission guard + history integrity + size monitor
- Permission drift -> CRITICAL alert
- Multi-row per doc -> WARN (should never happen with UPSERT)
7. cron-env.sh permissions
- Changed to 600 (root-only read)
Verification Results
| # | Check | Result |
|---|---|---|
| 1 | cron-env.sh perms | -rw------- root root |
| 2 | History 1 row/doc | 5 = 5 distinct |
| 3 | UNIQUE constraint | uq_kb_history_document_key |
| 4 | fn_kb_snapshot UPSERT | ON CONFLICT present |
| 5 | REVOKE applied | 0 DELETE/TRUNCATE grants |
| 6 | Direct DELETE blocked | ERROR permission denied |
| 7 | UPSERT e2e | 2 updates, still 1 history row |
| 8 | dot-kb-verify 7 checks | All 7 ran |
Architecture v2
UPDATE doc -> trg_kb_truncation_guard (warn if shrink >80%)
-> trg_kb_snapshot (UPSERT old content to history, 1 row/doc)
-> WRITE executes
-> trg_kb_audit (log metadata)
History: BOUNDED = 1 row per doc. Never grows beyond doc count.
Audit: metadata only, ~7MB/year at current rate.
Backup: nightly Google Drive (77MB, 14 retention).
Restore: fn_kb_restore(key) -> instant from history.
v1 vs v2
| Metric | v1 | v2 |
|---|---|---|
| History rows per doc | Unbounded | Exactly 1 |
| 856 docs x 100 updates/day | +85,600 rows/day | 856 rows forever |
| Cleanup needed | Yes (complex) | Never |
| DELETE permission | Required (for cleanup) | REVOKED |
| Permission drift detection | None | Check 7 CRITICAL |
DOT Status
- DOT_KB_PROTECT (B, event): v2 UPSERT — complete
- DOT_KB_VERIFY (A, cron): v2.0.0, 7 checks — complete
- DOT_KB_RESTORE (B, on-demand): simplified — complete
- Domain: kb (3 DOTs)
S170 Debt Status
- TD-KB-HIST-CLEANUP: CLOSED (not needed — UPSERT)
- TD-KB-AUDIT-CLEANUP: CLOSED (audit ~7MB/year)
- TD-KB-MEM-MONITOR: CLOSED (Check 6)
- cron-env permissions: CLOSED (chmod 600)
- S170 NOP = 0