Blame
|
1 | --- |
||||||
| 2 | category: spec |
|||||||
| 3 | tags: [quota, disk-usage, plugin, plan] |
|||||||
| 4 | last_updated: 2026-03-17 |
|||||||
| 5 | confidence: high |
|||||||
| 6 | --- |
|||||||
| 7 | ||||||||
| 8 | # Per-Wiki Disk Usage Caps Plan |
|||||||
| 9 | ||||||||
| 10 | Quota enforcement code already exists in `resolver.py` (50MB limit, write rejection) but reads from `robot.db` where `disk_usage_bytes` is always 0. This plan wires up real measurements. |
|||||||
| 11 | ||||||||
| 12 | ## Architecture |
|||||||
| 13 | ||||||||
| 14 | - **Measurement:** `du -sb` on entire wiki directory (repo + wiki.db + FAISS index) |
|||||||
| 15 | - **Storage:** New `stats` table in per-wiki `wiki.db` (not robot.db) — resolver already operates per-wiki-DB |
|||||||
| 16 | - **Update timing:** Page count on every save/delete (via otterwiki plugin hook). Disk usage via cron only (15-min interval, acceptable lag). |
|||||||
| 17 | - **Enforcement:** Existing resolver code — reads stats from `wiki.db` after DB swap, strips WRITE permission or returns 413 for API requests. |
|||||||
| 18 | ||||||||
| 19 | ## Components |
|||||||
| 20 | ||||||||
| 21 | ### 1. `stats` table in `wiki.db` |
|||||||
| 22 | ||||||||
| 23 | ```sql |
|||||||
| 24 | CREATE TABLE IF NOT EXISTS stats ( |
|||||||
| 25 | name VARCHAR(64) PRIMARY KEY, |
|||||||
| 26 | value TEXT, |
|||||||
| 27 | updated_at DATETIME |
|||||||
| 28 | ); |
|||||||
| 29 | ``` |
|||||||
| 30 | ||||||||
| 31 | Keys: `page_count`, `disk_usage_bytes`. Seeded in `_init_wiki_db()`. |
|||||||
| 32 | ||||||||
| 33 | ### 2. New plugin: `otterwiki-wikistats` |
|||||||
| 34 | ||||||||
| 35 | Follows `otterwiki-api` pattern. Hooks: `page_saved`, `page_deleted` → update `page_count` via `storage.list()`. Disk usage left to cron. Must use `_state["storage"]` dict (not `self.storage`) for multi-tenant correctness — resolver patches this in `_swap_storage()`. |
|||||||
| 36 | ||||||||
| 37 | ### 3. Resolver changes |
|||||||
| 38 | ||||||||
| 39 | - Add `_get_wiki_stats(wiki_dir)` — reads `stats` table from `wiki.db` |
|||||||
| 40 | - Move quota check to after `_swap_database()` — read real disk_usage from `wiki.db` with fallback to `robot.db` |
|||||||
| 41 | - Add `otterwiki_wikistats._state["storage"]` patch in `_swap_storage()` |
|||||||
| 42 | - Seed `stats` table in `_init_wiki_db()` |
|||||||
| 43 | ||||||||
| 44 | ### 4. Cron backstop update |
|||||||
| 45 | ||||||||
| 46 | Update `wiki-quota.sh.j2` to write to each wiki's `wiki.db` stats table (primary) AND keep updating `robot.db` for the dashboard API. |
|||||||
| 47 | ||||||||
| 48 | ## User Experience |
|||||||
| 49 | ||||||||
| 50 | - **API/MCP:** HTTP 413 with `"Wiki quota exceeded (50MB limit)"` |
|||||||
| 51 | - **Web UI:** 403 on save (WRITE stripped). Generic permission denied — quota-specific message is a follow-up. |
|||||||
| 52 | - **Dashboard:** Reads `disk_usage_bytes` from `robot.db` via management API (15-min lag from cron). |
|||||||
| 53 | ||||||||
| 54 | ## Files to Modify |
|||||||
| 55 | ||||||||
| 56 | | Repo | File | Change | |
|||||||
| 57 | |---|---|---| |
|||||||
| 58 | | robot.wtf | `app/resolver.py` | `_get_wiki_stats()`, quota check after swap, stats table in init, storage patch | |
|||||||
| 59 | | robot.wtf | `ansible/roles/quota/templates/wiki-quota.sh.j2` | Write to wiki.db + robot.db | |
|||||||
| 60 | | robot.wtf | `ansible/roles/deploy/tasks/main.yml` | pip install otterwiki-wikistats | |
|||||||
| 61 | | new repo | `otterwiki-wikistats` | Plugin: setup, page_saved, page_deleted hooks | |
|||||||
| 62 | ||||||||
| 63 | ## Test Plan |
|||||||
| 64 | ||||||||
| 65 | 1. Quota read from wiki.db — over limit → WRITE stripped |
|||||||
| 66 | 2. Under limit → pass-through |
|||||||
| 67 | 3. API over-quota → 413 |
|||||||
| 68 | 4. Fallback to robot.db when stats table missing |
|||||||
| 69 | 5. Plugin: page count updated on save/delete |
|||||||
| 70 | 6. Plugin: uses `_state["storage"]` not stale reference |
|||||||
| 71 | ||||||||
| 72 | ## Open Questions |
|||||||
| 73 | ||||||||
| 74 | 1. Quota check must move to after `_swap_database()` — verify no regression on API 413 path |
|||||||
| 75 | 2. Cron user permissions on wiki.db files |
|||||||
| 76 | 3. Plugin `_state` dict convention — follow existing pattern from `otterwiki-api` |
|||||||
