Changeset 0.15.0 (#71)

This commit is contained in:
2026-02-26 21:19:55 +00:00
parent e2149e249d
commit 2d79ff593b
35 changed files with 3218 additions and 504 deletions

View File

@@ -0,0 +1,106 @@
# v0.15.0 Phase 4 — Guard Rail + Integration Tests
## Files
```
server/compaction/compaction.go UPDATED — context budget guard rail
server/compaction/compaction_test.go NEW — scanner integration tests (DB required)
server/compaction/testmain_test.go NEW — TestMain for compaction package
server/database/seed_helpers.go NEW — SeedTestMessage, SeedTestMessages, SeedTestCursor
server/handlers/live_compaction_test.go NEW — e2e tests with Venice/Qwen3-4B
```
## Changes
### 1. Context Budget Guard Rail (`compaction.go`)
Added between prompt construction and LLM call. Prevents sending
truncated input to small utility models.
**How it works:**
1. Estimates total prompt tokens (system + conversation content)
2. Resolves the utility model's `max_context` from catalog
3. Reserves 20% for output (the summary itself)
4. If `estimated_prompt > max_context × 0.80`, returns `ErrContextBudget`
**Resolution chain** for utility model context:
- `Resolver.GetConfig()` → primary binding → `Catalog.GetByModelID()`
- Fallback: `Catalog.GetByModelIDAny()` (different provider)
- Hard fallback: `DefaultBudget` (128K)
The scanner's `doCompact()` already handles errors gracefully — a budget
error just logs a warning and retries next cycle (by which time the
conversation may have been manually compacted or the admin may have
upgraded the utility model).
**New export:** `ErrContextBudget` — callers can use `errors.Is()` to
distinguish budget failures from LLM errors.
### 2. Test Seed Helpers (`database/seed_helpers.go`)
New file in the `database` package alongside `testhelper.go`:
- `SeedTestMessage(t, channelID, parentID, role, content) → msgID`
- `SeedTestMessages(t, channelID, count, contentSize) → []msgIDs`
Creates a linear alternating user/assistant chain
- `SeedTestCursor(t, channelID, userID, leafID)`
Sets the active leaf for tree path resolution
### 3. Scanner Integration Tests (`compaction/compaction_test.go`)
All DB-dependent tests use `database.RequireTestDB(t)` and skip
gracefully when no DB is configured.
| Test | What it verifies |
|------|-----------------|
| `FindCandidates_ReturnsQualifying` | Channel with ≥10 msgs, ≥20K chars, proper age range appears |
| `FindCandidates_ExcludesTooFewMessages` | <10 messages → excluded |
| `FindCandidates_ExcludesTooRecent` | Updated just now → excluded (activity gap) |
| `FindCandidates_ExcludesArchived` | `is_archived=true` → excluded |
| `Cooldown` | Timestamps recorded and checked correctly |
| `InFlightDedup` | sync.Map prevents concurrent compaction of same channel |
| `ShouldCompact_ChannelOptOut` | `settings.auto_compaction=false` → rejected |
| `IsEnabled` | Reads `auto_compaction_enabled` from global_settings |
| `GetThreshold_Default` | Returns 0.70 when no override |
| `GetThreshold_GlobalOverride` | Reads from global_settings |
| `GetThreshold_ChannelOverride` | Channel setting takes precedence |
| `GetCooldownDuration_Default` | Returns 30min default |
| `GetCooldownDuration_Override` | Reads from global_settings |
| `GuardRailMath` | Validates token math for 32K vs 128K models |
### 4. Live E2E Tests (`handlers/live_compaction_test.go`)
Requires: `TEST_DATABASE_URL` + `VENICE_API_KEY`
| Test | What it verifies |
|------|-----------------|
| `CompactionFullPipeline` | Seeds 10 realistic messages → Compact() → verifies summary message in DB, metadata fields, cursor update, usage log entry |
| `CompactionContextBudgetGuardRail` | Seeds 160K chars of content → Compact() returns ErrContextBudget with 32K model |
## Running Tests
```bash
# Unit only (no DB)
cd server && go test ./compaction/ -run TestEstimate -v
# Integration (requires DB)
cd server && go test ./compaction/ -v
# Live e2e (requires DB + Venice key)
cd server && VENICE_API_KEY=... go test ./handlers/ -run TestLive_Compaction -v
```
## Surgical Edit
### `database/testhelper.go`
Add this import if not already present (the new `seed_helpers.go` file
is a separate file in the same package, so no edit needed to
`testhelper.go` itself):
```go
// No changes needed — seed_helpers.go is a new file in package database
```
The `strings` import in `seed_helpers.go` is used by `SeedTestMessages`
for `strings.Repeat`.