Elnor Repo Reader

DOC2 ELNOR_FRESHNESS_MANAGER_ADDENDUM_v1_11_4 copy.md

Current Specs/DOC2/DOC2 ELNOR_FRESHNESS_MANAGER_ADDENDUM_v1_11_4 copy.md

Generated 2026-06-09T01:23:58.539Z from commit dbaa25962edc11ab30e8d4ca1715f9ae5bf77331. Worktree: clean.

Open text page · Open raw txt · Open path URL

# ELNOR Suite — Freshness Manager Addendum v1.11.4 (Patch-on-top)

Generated: 2026-02-23  
Status: Normative patch addendum — implement on top of the running repo

## 0) Spec pinning (non-negotiable)

Canonical spec files used (SHA256):

- ELNOR_CORE_SPEC_v1_11_2_CANONICAL.md  
  a1a23894dae7bbe93af868d02204896d887b2c2d1afd7b63a66a1c4468c81571
- Q_DASHBOARD_SPEC_v1_11_2_CANONICAL.md  
  f24788fbade9788a30df7e1d5546d14ac65e9464abd8cef84a9e4b4112405fa3
- ELNOR_BUILD_PROMPT_v1_11_2_WITH_UI.md  
  23065621fb9d28dbfad80318cec0975ef9959419955e12b3f5d85b797364aef0
- Q_UI_DESIGN_SPEC_v1_11_2_UPDATED.md  
  1d86f08acbd7e40aeffbd98d15d9936d8f1942740043f7a3ada2de2df123b555
- q-mockup-v11.7m.jsx  
  508aaef7022a73d5a92beb83791196a38e512799f5dc06463e4c3d0397691e77

## 1) Non-negotiables (binding)

- EC is the sole durable writer. All durable changes occur via append-only commands that EC validates and applies.
- Q never writes durable state. Q submits commands and renders state, with a unified approvals inbox.
- No drift. Use canonical specs + patch addenda only. No redesign. No “helpful” renaming. No new terminology.
- Local-first. Remote access via Tailscale; read-only by default; token auth with rotation/revocation.
- Emergency stop via canonical STOP request file (no changes in this addendum).
- Spec pinning required at top of addendum + readiness report (this section).

## 2) Problem

LLMs can be stale. The system does not automatically web-search for time-sensitive information (news/prices/weather/sports/legal updates/local rules/deadlines), causing stale answers and dead links.

## 3) Goal (Freshness Manager)

Add a robust freshness subsystem aligned to single-writer + taint rules + UI anti-drift:

- Model Capability Registry: knowledge_cutoff_date + tool support flags.
- Always-on Recency Layer: inject “today’s date/timezone” + model cutoff + freshness policy (bounded).
- Deterministic Recency Router (pure function) classifies when search is required.
- EC-level Search Executor (provider pluggable), so freshness works even if model cannot tool-call.
- Verified Facts Cache with TTL + verified_as_of; facts stored as durable EC artifacts.
- Staleness metadata: last_verified_at on derived facts; stale badges; “Verify now”.
- Q UI: freshness pill / stale banner + “Update with latest search”; show evidence + verified-as-of.
- Learning loop (approval-gated): log stale corrections + outcomes; propose standing rules; never auto-promote low-trust sources.

## 4) Hard budgets (defaults unless canonical specs override)

- Max 2 search attempts per user query.
- Max 10s per attempt (hard timeout).
- Max 5 sources fetched per attempt.
- Freshness context injection cap: 800–1200 tokens (default cap = 1000).
- No raw pages in prompts. Inject only summaries + evidence handles.
- TTL defaults by category must be explicit and stored on each record.

## 5) Durable artifacts (EC-owned)

All durable artifacts are EC-owned and stored under ELNOR_MEMORY.

### 5.1 Locations (canonical paths)

Add new canonical paths in `packages/contracts/src/canonical.ts`:

- ELNOR_MEMORY/system/freshness/model_registry.json
- ELNOR_MEMORY/system/freshness/freshness_policy.json
- ELNOR_MEMORY/system/freshness/source_tiers.json
- ELNOR_MEMORY/system/freshness/search_runs.jsonl
- ELNOR_MEMORY/system/freshness/verified_facts.jsonl
- ELNOR_MEMORY/system/freshness/verified_facts_index.json

### 5.2 Records

#### SearchRunRecord (append-only JSONL)
Fields (minimum):
- id
- query, normalized_query
- router_decision (must_search / should_search / no_search)
- category
- retrieved_at, verified_as_of
- sources[]: url, title, tier, evidence_excerpt (capped), excerpt_hash
- result_summary (capped)
- ttl_days, expires_at
- status (ok/timeout/budget_exhausted/offline/error)

#### VerifiedFactRecord (append-only JSONL)
Fields (minimum):
- fact_id
- topic_key
- fact_text (short)
- fact_summary (short)
- verified_as_of
- sources[] with tier + capped snippet + snippet_hash
- ttl_days, expires_at
- confidence (high/medium/low)
- tags[]
- provenance (search_run_id, source_urls[])
- content_hash

#### Index (derived; EC only; atomic rewrite)
`verified_facts_index.json` maps:
- topic_key → latest_fact_id (prefer non-expired)
- fact_id → (optional) offsets/metadata (implementation choice)

## 6) Pure logic (Deterministic Recency Router + normalizer)

Implement in `packages/contracts/src/freshness.ts` (pure functions; testable; no I/O):

### 6.1 routeRecency(input) → { decision, category, reasons[] }

- MUST_SEARCH for:
  - explicit recency terms: today / yesterday / tomorrow / latest / current / right now / this week / as of / recently / breaking / update
  - inherently time-sensitive categories: news, sports schedules/scores, weather, prices/availability, current office-holders/leadership, software docs/version behavior, elections/voting procedures
  - legal_mode + local rules / filing requirements / deadlines / court calendars / recent case law
- SHOULD_SEARCH for:
  - “find/link/source” requests where user did not paste a URL
  - ambiguous “recent” without explicit timeframe
- NO_SEARCH otherwise (default)

Router must be a pure function and MUST NOT use an LLM.

### 6.2 normalizeTopicKey(text) → topic_key

Deterministic normalizer:
- lowercase, strip punctuation
- drop stopwords
- expand common abbreviations (FRCP/CCP/CPLR, SDNY/CDCA, etc.) where safe
- sort tokens
- topic_key = sha256(tokens.join(" "))

### 6.3 TTL defaults by category (stored per record)

Defaults (policy-configurable):
- news: 1 day
- sports: 1 day
- weather: 1 day
- prices/availability: 3 days
- software docs: 21 days
- office holders/leadership: 30 days
- legal local rules/procedures: 90 days
- statutes (text): 180 days
- evergreen/historical: null (no expiry)

## 7) EC changes (apps/ec-service)

### 7.1 Modify existing files (real repo paths)

- apps/ec-service/src/index.ts  
  Ensure freshness directories/files exist on startup; load policy/registry/tiers.
- apps/ec-service/src/fs-utils.ts  
  Add helpers: sha256(str|bytes), readJsonOrDefault(path, default), readJsonlTail(path, maxLines)
- apps/ec-service/src/server.ts  
  Add:
  - Command handlers (append-only + validated)
  - Read-only API endpoints for policy/registry/facts/runs
- apps/ec-service/src/event-bus.ts  
  Emit events for freshness verify actions (optional; keep compact)

### 7.2 Add new files

- apps/ec-service/src/freshness-redact.ts  
  Deterministic redaction for outbound queries (strip tokens/api keys/local paths).
- apps/ec-service/src/freshness-executor.ts  
  Search provider interface + default provider implementation; hard budgets enforced.
- apps/ec-service/src/freshness-cache.ts  
  Cache read/write helpers for SearchRun + VerifiedFact + derived index maintenance.

### 7.3 EC commands (via /api/commands)

Add command_type handlers in EC server:

- freshness_set_policy
- freshness_set_model_registry
- freshness_set_source_tiers
- freshness_verify_now (query + model_id; optional force_search)
- freshness_verify_fact (topic_key + claim_text + model_id)

All durability happens in EC:
- append SearchRun / VerifiedFact JSONL
- atomically rewrite index json
- record command_results

### 7.4 EC read-only endpoints (for Q rendering)

Add read-only endpoints:
- GET /api/freshness/policy
- GET /api/freshness/model-registry
- GET /api/freshness/verified-facts?topic_key=...
- GET /api/freshness/search-runs?id=... (or list/limit)

## 8) Q backend changes (apps/q-backend)

Q backend must not write any freshness durable state.

### 8.1 Modify files

- apps/q-backend/src/server.ts  
  Integrate freshness orchestration into the chat flow:
  - Always inject a tiny Temporal Context block.
  - Call router (pure) and, when needed, request EC freshness verification.
  - Inject only summary + evidence handles (no raw pages).
  - Attach FreshnessMeta to response payload for UI.

### 8.2 Add file

- apps/q-backend/src/freshness.ts  
  Glue module that:
  - calls routeRecency()
  - calls EC commands/endpoints
  - composes bounded freshness injection text

### 8.3 Verify Now endpoint

Add (or extend existing patterns) a route that triggers a forced verify:
- POST /api/freshness/verify-now { text, model_id, ... }
→ Q backend calls EC freshness_verify_now(force_search=true) and returns updated freshness evidence (and optionally triggers regenerate behavior per existing chat design).

## 9) Q frontend UI (apps/q-frontend)

### 9.1 Add components

- apps/q-frontend/src/components/FreshnessPill.tsx
- apps/q-frontend/src/components/StaleWarningBanner.tsx
- apps/q-frontend/src/components/FreshnessDetailsModal.tsx

### 9.2 Modify pages and API

- apps/q-frontend/src/api.ts  
  Add API calls for verify-now and evidence fetch.
- apps/q-frontend/src/pages/ChatsPage.tsx  
  Render pill per assistant message + stale banner when applicable.
- apps/q-frontend/src/pages/SettingsPage.tsx  
  Add “Knowledge Freshness” controls backed by EC commands:
  - auto_search_enabled
  - legal_research_mode
  - injection_token_cap (bounded)
  - provider selection (optional)

UI requirements:
- show verified-as-of date/time
- show “Verify now” for stale/unverified
- show evidence list (sources + capped snippets)
- do not redesign navigation or rename existing routes

## 10) Security & privacy

- Redact secrets/identifiers in outbound search queries (tokens, API keys, local paths).
- Prefer high-trust sources; allowlist/tier by category if possible.
- Never store secrets in caches.
- Never store raw HTML pages; store only capped excerpts + hashes + URLs/titles.
- No raw pages injected into prompts.

## 11) Acceptance tests (tests/)

Add tests under tests/ (vitest):

- tests/freshness/recency-router.test.ts
- tests/freshness/topic-key.test.ts
- tests/freshness/ttl.test.ts
- tests/freshness/cache.test.ts
- tests/freshness/redact.test.ts
- tests/freshness/executor.test.ts (mock fetch; enforce budgets)

Coverage must include:
- deterministic router classifications
- token/secret redaction
- TTL expiry behavior
- caching: hit vs miss
- hard caps: attempts, timeout, max sources
- injection size cap (approximate by bytes/chars)

## 12) Readiness report updates (BUILD_REPORT.md)

Append section “Freshness Manager v1.11.4” containing:
- the SHA256 pins (from §0)
- file inventory (modified/added)
- tests added + passing
- smoke checks:
  1) “weather in Los Angeles today”
  2) “latest Apple CEO” (time-sensitive office-holder)
  3) evergreen question (no search)
  4) force Verify Now on a stale answer