Skip to content

fix(opencode): support v2.0.x inline session_message schema #23

Closed

Description

@HarryHeYu

Problem

A real OpenCode install exposed a schema shape the Phase 3 adapter does not
recognise. The adapter was validated against upstream anomalyco/opencode
v2.2.x, whose message parts live in a separate session_part table. An
intermediate v2.0.x install instead stores message content inline in
session_message.data and has no session_part table at all.

voyager/adapters/opencode.py declares two shapes in SCHEMAS (v2 and
legacy), both of which require a parts table. detect_schema() therefore
matches neither, probe() returns ('unknown-schema', None), discover()
returns an empty source set, and no session from that install is indexed.
The failure itself is honest (unusable_sources() names the database instead of
silently reporting zero sessions or marking history SOURCE_MISSING), but the
provider is unusable on that real install.

The synthetic suite (27 tests) cannot catch this: its fixtures are built from
reviewable DDL modelled on the v2.2.x shape. This is exactly why
UNIT_VERIFIED on synthetic data must not be reported as LIVE_VERIFIED.

Goal

Support both real shapes, distinguished by structural schema features, never
by a version string or a hard-coded product version number:

  1. v2.0.x (inline) — session_v2 + session_message (content inline in
    data) + project, with no session_part.
  2. v2.2.x (parts) — the shape Phase 3 already implements
    (session_part with message_id, session_id, ordinal, type,
    call_id, data).

Detection must be based on actual tables and required columns (as
detect_schema() already does), plus the shape of session_message.data where
needed. Do not make probe() pass by simply dropping the session_part
requirement — the subsequent read path must actually parse the inline shape
correctly.

Required for the inline shape

  • Session discovery and stable identity (session_v2.id, title, directory,
    project association).
  • Message reading and role mapping from the type column (user,
    assistant, and provider-generated types such as model-switched / idle).
  • Content extraction from the inline data JSON (text plus any structured
    fields actually present); tool calls / tool results where the record carries
    them, and NOT_FOUND-style honest absence where it does not.
  • Timestamps (ms → s) and a stable per-session ordering (seq, with a
    deterministic tiebreak), so rescans are idempotent.
  • Provenance from structural fields only — the message type column and
    explicit source flags; never infer a human turn from text. Provider-generated
    messages must not be promoted to user.
  • Incremental sync and WAL: -wal / -shm remain in the discovered source
    set so a running agent's new turns trigger a rescan.
  • Honest degradation for unknown message types: record them without
    inventing content, roles or exit codes; do not fabricate a tool_result for a
    call that never completed.

Tests and isolation

  • Fully isolated HOME, USERPROFILE and XDG_DATA_HOME in every test that
    discovers databases.
  • A real database is used only to construct a consistent, isolated snapshot;
    all SQLite verification connections target the snapshot or a copy. No test
    connects to a developer's real profile, and no user message text or database
    path is committed into a fixture or report.
  • Keep all existing tests: legacy shape, v2.2.x parts shape, unknown schema,
    corrupt / absent databases, WAL discovery, SOURCE_MISSING retention,
    streaming equivalence.
  • Add an anonymised v2.0.x fixture (table/column names and JSON key shapes
    only, no real content) covering: schema discrimination, message extraction,
    tool calls, CJK text, empty fields, WAL-driven incremental rescan.
  • Address the discovery-path finding: _candidate_dbs() currently appends the
    default data-dir database even when an explicit VOYAGER_OPENCODE_DB /
    OPENCODE_DB override is set. Define and test a clear path priority so an
    operator-named database does not cause an unrequested probe of the default
    HOME database. At minimum, test isolation must be airtight; never silently
    access a real database the operator did not specify
    .

Acceptance (real-install Gate)

  • Gate A: adapter results compared row-for-row (structure and counts) against an
    independent read-only sqlite3 oracle on the isolated snapshot.
  • Gate B: before/after SHA-256 of the real .db / -wal / -shm, row counts,
    and file-set side effects.
  • Gate C: capability matrix declarations agree with real evidence.
  • Directed OpenCode tests, full Core suite, test_docs.py, and static checks.
  • Capabilities not observed on a real install stay explicitly NOT_VERIFIED —
    in particular native resume (the adapter denies it) and cross-agent
    continuation
    . Session discovery succeeding must not be reported as either.
  • LIVE_VERIFIED only for capabilities with real-install evidence and a
    recorded reason. Synthetic results stay UNIT_VERIFIED.

Scope / boundaries

  • Phase 3 design input: docs/audits/2026-11-opencode-adapter-design.md;
    implementation report: docs/audits/2026-11-opencode-phase3-report.md
    (both frozen — this issue does not rewrite their verification claims).
  • No change to Store, continuity, the bridge schema, any existing CLI
    contract, or any other provider adapter.
  • No database migration, no new runtime dependency (stdlib sqlite3 only).
  • Out of scope: Evidence Verification (#22 / dsh-sessionflow#9), native
    resume, channel-specific behaviour beyond the existing probe, release or tag
    changes.
  • Work happens on the integration branch; phase3-opencode and its backup ref
    are preserved. Merge requires all three Gates passing and maintainer review.

Verification level

Design/scope only: NOT_VERIFIED. Real-install evidence will be attached to
the PR, never to this issue as a claim.

Activity

  1. HarryHeYu commented on Nov 12, 2026

    @HarryHeYu
    OwnerAuthor

    Implemented and opened for review: PR #25 — feat(opencode): read-only SQLite adapter with v2.0.x inline schema support.

    Shape detection is structural (SCHEMAS + SCHEMA_FORBID): v2-inline requires session_v2 + session_message and forbids session_part, so a v2.2.x database still resolves to v2. No version string is consulted.

    Real-install gate on one v2.0.29 install (isolated copy; HOME/USERPROFILE/XDG_DATA_HOME redirected; every connection on the copy):

    • Gate A PASS — probe -> ok/v2-inline, 8/8 sessions, per-kind counts exactly match an independent read-only sqlite3 oracle (user 68, text 206, reasoning 2053, tool 2617, tool_result 2617), zero mismatches.
    • Gate B PASS — real .db / -wal / -shm SHA-256 and all row counts unchanged.
    • Gate C — no capability cell promoted; native resume stays NOT_FOUND, cross-agent continuation NOT_VERIFIED.

    Also fixed here: an explicit VOYAGER_OPENCODE_DB / OPENCODE_DB is now authoritative, so an operator-named database no longer causes an unrequested probe of the default HOME database.

    Directed: 36 opencode tests; with docs/isolation/consistency 105 passed / 3 skipped; check_consistency.py --strict clean. Report: docs/audits/2026-11-opencode-v20x-real-gate.md (no user text, paths or hashes). Not merged — left for maintainer review.

  2. HarryHeYu commented on Nov 12, 2026

    @HarryHeYu
    OwnerAuthor

    Final review passed — no blocking issues, CI 10/10 green at head 9603164 (PR MERGEABLE / CLEAN).

    Independent checks (27/27) covered: three-shape detection and priority (v2 wins when session_part exists), inline data parsing, event order/counts, tool call↔result pairing, WAL incremental rescan + idempotence, read-only connection and the documented -shm side effect, override path isolation, and unknown-schema / corrupt-JSON / unknown-type handling.

    One P1 found in review and fixed in 9603164: the capability-matrix comment still claimed "no real install was observed" and described only v2/legacy — corrected to three shapes plus one real v2.0.29 observation, states kept at UNIT_VERIFIED (no LIVE_VERIFIED).

    Gate scope is narrow and stated as such: one install, one version (v2.0.29), one platform (Windows). Native resume stays NOT_FOUND; cross-agent continuation NOT_VERIFIED. Not merged — merge is a separate explicit step, no release in this round.

  3. HarryHeYu commented on Nov 12, 2026

    @HarryHeYu
    OwnerAuthor

    Resolved by PR #25, squash-merged as ee9fd9e on 2026-11-12. main CI on that commit: success, 10/10 jobs (py3.10–3.13 × ubuntu/windows + core-install no-extras).

    Verified present on main after the merge:

    • voyager/adapters/opencode.py — v2-inline shape, SCHEMA_FORBID (a database carrying session_part still resolves to v2), _inline_parts(), _tool_exit() (reads state.metadata.exit, keeps 0), and the authoritative VOYAGER_OPENCODE_DB / OPENCODE_DB discovery priority.
    • voyager/capability_matrix.py — corrected wording: three shapes exercised synthetically, one real v2.0.29 install observed; all states stay UNIT_VERIFIED.
    • tests/test_opencode.py (36 tests) and tests/opencode_fixtures.py with the anonymised make_v20_inline / V20_INLINE_DDL fixture.

    Real-install gate on the one observed install (isolated copy): probe ok/v2-inline, 8/8 sessions, per-kind counts exactly matching an independent read-only sqlite3 oracle (user 68, text 206, reasoning 2053, tool 2617, tool_result 2617), zero mismatches; real .db / -wal / -shm hashes and all 20 table row counts unchanged. Report: docs/audits/2026-11-opencode-v20x-real-gate.md.

    Not promoted: native resume stays NOT_FOUND (adapter denies resume), cross-agent continuation NOT_VERIFIED, inline user.files NOT_VERIFIED (empty in observed data), multi-channel and non-Windows NOT_VERIFIED. Evidence covers one install, one version (v2.0.29), one platform (Windows).

    Closing as completed. No release or tag change was made.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingenhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions