Skip to content

[G1] Hydration reports capability "full" unconditionally for local sources; compute it from covered evidence kinds #169

Closed

Description

@willwashburn

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

  1. Define per-source declared coverage in the provider registry (crates/ai-hist/src/sources.rs / discover.rs ShallowSessionProvider): a fn 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 = History only; relay = none). This is a property of the provider, not of a database, matching how relationship_capabilities is exposed.
  2. build_result computes capability = full if declared coverage ⊇ FULL_SESSION_KINDS, else partial; and discovery_state = the value actually written to the sessions row (hydrate.rs currently upgrades it separately — reuse that value instead of a literal).
  3. Add a coverage: Vec<EvidenceKind> field to HydrateSessionResult (and HydrateSessionResult.diagnostics gains HYDRATION_PARTIAL_COVERAGE naming the missing kinds), so a consumer can tell which evidence is absent. Bump SESSION_HYDRATION_CONTRACT_VERSION (hydrate.rs:11) → 3; update sdk-ts/src/contracts.ts:162-170, normalization.ts normalizeHydration, the MCP tool description, and docs/session-catalog.md.
  4. sessions.discovery_state semantics: a partial hydration must not set discovery_state = 'full' in a way that makes the unchanged short-circuit (hydrate.rs:187-201) skip re-hydration after a later parser upgrade — verify the parser_version check still forces a re-parse.
  5. 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 returns capability: 'partial', coverage: ['history'], and a HYDRATION_PARTIAL_COVERAGE diagnostic listing session_event, tool_call, file_edit, relationship.
  • hydrateSession({source:'claude'}) still returns full.
  • sdk-ts/src/session-evidence.test.ts / local-history-contract.test.ts cover the new field and version.
  • CHANGELOG.md entry under Breaking: "hydration contract 3; local hydration no longer claims full for prompt-only providers".

Out of scope

Making the prompt-only providers complete (their own issues).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions