Repository navigation
[G1] Hydration reports capability "full" unconditionally for local sources; compute it from covered evidence kinds #169
Description
Part of #160 (group 1 — independent, small). This is a correctness bug in the current release, independent of the migration, and it blocks every parity issue from being verifiable through the public API.
Problem
build_result in crates/ai-hist/src/hydrate.rs:1642-1650 hard-codes:
capability: "full".to_string(),
discovery_state: "full".to_string(),
presence: "local".to_string(),So a Cursor, Grok, or OpenCode session that produced prompts only (no session_events, tool_calls, file_edits, usage) is reported to every consumer — CLI, TS SDK, MCP hydrate_session, and the SDK's merge ranking { full: 2, partial: 1, shallow_only: 0 } (sdk-ts/src/operations.ts:945) — as full. This is exactly the "well-formed answer computed over nothing" failure class from CLAUDE.md.
The correct rule already exists for the plugin path: source_intake::apply_normalized (crates/ai-hist/src/source_intake.rs:~270) sets full only when every kind in FULL_SESSION_KINDS (crates/ai-hist-core/src/source_evidence.rs:19: History, SessionEvent, ToolCall, FileEdit, Relationship) is in covered_kinds; otherwise partial. The local path never applies it.
Scope
- Define per-source declared coverage in the provider registry (
crates/ai-hist/src/sources.rs/discover.rsShallowSessionProvider): afn evidence_kinds(&self) -> &'static [EvidenceKind]that says which kinds the local parser can produce for that source (today: claude/codex = all five; cursor/grok/opencode =Historyonly; relay = none). This is a property of the provider, not of a database, matching howrelationship_capabilitiesis exposed. build_resultcomputescapability=fullif declared coverage ⊇FULL_SESSION_KINDS, elsepartial; anddiscovery_state= the value actually written to thesessionsrow (hydrate.rscurrently upgrades it separately — reuse that value instead of a literal).- Add a
coverage: Vec<EvidenceKind>field toHydrateSessionResult(andHydrateSessionResult.diagnosticsgainsHYDRATION_PARTIAL_COVERAGEnaming the missing kinds), so a consumer can tell which evidence is absent. BumpSESSION_HYDRATION_CONTRACT_VERSION(hydrate.rs:11) → 3; updatesdk-ts/src/contracts.ts:162-170,normalization.tsnormalizeHydration, the MCP tool description, anddocs/session-catalog.md. sessions.discovery_statesemantics: apartialhydration must not setdiscovery_state = 'full'in a way that makes the unchanged short-circuit (hydrate.rs:187-201) skip re-hydration after a later parser upgrade — verify theparser_versioncheck still forces a re-parse.- Each parity issue (Cursor, Grok, OpenCode) flips its provider's declared coverage as part of its own PR.
Acceptance
hydrateSession({source:'cursor'})on the current parser returnscapability: 'partial',coverage: ['history'], and aHYDRATION_PARTIAL_COVERAGEdiagnostic listingsession_event, tool_call, file_edit, relationship.hydrateSession({source:'claude'})still returnsfull.sdk-ts/src/session-evidence.test.ts/local-history-contract.test.tscover the new field and version.CHANGELOG.mdentry under Breaking: "hydration contract 3; local hydration no longer claimsfullfor prompt-only providers".
Out of scope
Making the prompt-only providers complete (their own issues).
Activity
feat(hydrate): Grok is `partial`, because Grok records no billing tokens
docs(grok): the four contracts round seven settled