Skip to content

Re-home the session & device lifecycle base contract onto the app overlay (canonical v1.18.29) - #819

Draft
aarontrowbridge wants to merge 11 commits into
mainfrom
rehome-client-session-contract
Draft

Re-home the session & device lifecycle base contract onto the app overlay (canonical v1.18.29)#819
aarontrowbridge wants to merge 11 commits into
mainfrom
rehome-client-session-contract

Conversation

@aarontrowbridge

Copy link
Copy Markdown
Member

Closes #817 (issue: #817). Spec of record: spec-20260905-045114-session-device-lifecycle — the Placement under harness-agnostic (amendment, revised same day) section and D2/D6 govern this port.

What this is

The session & device lifecycle base contract, ported from the held fork reference (harmoniqs/opencode#296, branch slice1-session-lifecycle) onto the app overlay at canonical opencode v1.18.29, with the three ADAPTATIONS the revised placement requires:

  • The currency token is CLIENT-DERIVED over the fetched projection — (count, max time_updated, sum time_updated) stamped with the server-reported version (from the health endpoint, exposed as serverSDK.version()). No server field; the fork's core/session/currency.ts + server-handler dependency is dropped entirely.
  • Project grouping is CLIENT-SIDE over directory/projectID from the session list the home index already returns — non-git homes become their own first-class groups; no server project rows, no boot-time backfill, no junk lint.
  • Parity is checked CLIENT-SIDE against the canonical release channel using the server-reported version, three-outcome record (parity-ok | parity-drift | channel-unreachable), fail-open; the fork-era server-side drift gate becomes a per-harness conformance suite fixture (D6's revised placement).

Commits (small, each green at its boundary)

Commit Behavior
honest states core sessionListState machine + the panel-reset scope classifier/touch-list (pure, unit-tested)
client-derived token deriveCurrencyToken + H4 properties: same-tick writes, archive churn, delete-then-touch, out-of-band writes all advance it by construction
snapshot self-heal persisted session:snapshot (accelerator, never authority), bootCurrencyDecision, hydration gated on sessions_fetched, verify+overwrite on every list response
honest states wired sessions_fetched flag in both load paths, dropdown renders loading-never-empty, command.panel.reset (18 locales)
cursor-only pagination home-session-index carried as a true overlay with the exhaustion-signal fix
first-class homes home-session-groups (path-keyed resolution, synthesized non-git homes, free re-key); home page threads listState
boot parity recordBootParity wired into the ConnectionGate health check
H1 harness headless boot fixture over the seeded hub's request log
conformance suite declared canonical semantics probed per harness; semantic drift red, additive field green

Green criteria (verified)

  • Extension vitest suite: 2373 passed; every new *_817.test.ts green (state machine, currency properties, snapshot decision, wiring pins, pagination, grouping, parity, H1, conformance).
  • app-bundle drift gate: PASS (overlay + manifest in sync, 589 files).
  • Materialized app tree (canonical v1.18.29 + overlay): typechecks clean except two pre-existing error classes, and the vite production build succeeds.

Not-green / honest caveats (pre-existing on origin/main, not touched here)

  • packages/app typecheck: server-session.ts(497) — upstream's createServerSession doesn't satisfy the overlay's SessionCache (diff_version required); and i18n/parity.test.ts — upstream's 46-locale set includes "am", not in the overlay's dict set. Both reproduce on a pristine origin/main materialization; noted for a follow-up (no drive-by fixes per discipline).
  • packages/extension vitest: test/terminal.test.ts (OPENCODE_DB/OPENCODE_CONFIG_DIR injection) fails identically on pristine origin/main — environment-dependent, pre-existing.
  • The in-browser H1 criterion (fresh_client_boot measured against a real hub's frontdoor log) is covered here at the harness level (H1 fixture + wiring pins); the live Fleet Doctor: incident-mode detection with preservation-first fixes #288-boot measurement needs a running hub and is out of this PR's hermetic reach.
  • fleet_overlay_unstaged_without_entitlement is out of scope (amicissimo#380, parallel session) per the issue.

Runner discipline

Tests run under vitest (extension suite) via relative imports of the overlay's pure modules + the repo's established source-pin idiom for Solid wiring; no bun test was used, no live chat DB or running server touched.

… classifier (D2 core)

Issue #817 — port of the held fork reference (harmoniqs/opencode#296) onto
the app overlay at canonical v1.18.29. The pure core of D2 (spec
spec-20260905-045114): sessionListState distinguishes 'not yet fetched'
(incident #293's invisible failure) from 'genuinely empty', and the reset
scope classifier + touch-list pin that 'Reset panel state' clears session
caches ONLY — workspace preferences and drafts survive recovery. Wiring into
the sync stores, dropdown, home, and the command lands next.
…he fetched projection (D2/H4)

Issue #817 — the revised placement: the fork's token was a server field
(core/src/session/currency.ts + the v2 response); the overlay port drops that
dependency entirely. deriveCurrencyToken recomputes (count, max time_updated,
sum time_updated) over the rows the list returns, stamped with the
server-reported version. The H4 properties are enforced client-side:
same-tick writes, archive churn, delete-then-touch pairs, and out-of-band
writes all advance the token by construction — derived, never hand-bumped.
…d flag, dropdown, in-product panel reset (D2)

Issue #817 — port of the fork's d4e3d64f82 adapted to the overlay's sync
shape: child stores carry a sessions_fetched flag set when a list fetch
resolves (both the cached-store early-return and the fetch path), the
sessions dropdown routes through sessionListState and renders loading —
never the empty state — while no fetch has resolved (#293's invisible
failure shape). 'Reset panel state' (command.panel.reset, all 18 overlay
locales) calls project.resetSessionCaches: in-memory session fields,
sessionTotal/status, and the loadSessions/activeSessions query keys only —
persisted workspace preferences are never touched, pinned by the wiring
test's no-persisted-write assertion.
…via the client-derived token (D2)

Issue #817 — port of the fork's 95a065f7ec adapted to the revised placement:
child stores persist a per-workspace 'session:snapshot' (render accelerator,
never an authority), hydrated only until the first real list fetch resolves
(sessions_fetched gate). Every list response verifies the snapshot via
bootCurrencyDecision over the CLIENT-derived token — count/max/sum of the
fetched rows stamped with the hub's self-reported version (resolved once per
server context through the health endpoint, exposed as serverSDK.version()).
A stale or tokenless snapshot — the #293 shape — is invalidated by the
overwrite, so recovery needs zero filesystem surgery. The panel reset
invalidates all snapshots too (session:snapshot is on the pinned touch-list).
…ss (D2/D4)

Issue #817 — port of the fork's 10b7e96e onto the overlay: home-session-index
is carried into the overlay (upstream-maintained true overlay, per the M2
pattern) with the exhaustion signal corrected — a page shorter than the
requested limit does NOT mean the store ended; only a missing continuation
cursor does. A hub capping page size below the request would otherwise
silently drop everything past its first short page. The carried module's
pure deps (session-trim, utils, path-key) ride along so the module stays
importable by the extension's vitest suite; utils' session-ui import becomes
type-only (used only in a return type). Pagination property tests ported:
desc-first recent tail, cursor-driven multi-page assembly, short-page
continuation, abort between pages.
…onest states on the home page (D1 + D2)

Issue #817 — the revised placement of D1: project grouping resolves
CLIENT-side over directory/projectID (data the global home index already
returns) — no server project rows, no boot-time backfill, no junk lint. The
old controller dropped every session whose directory resolved to no opened
project (the founding incident: 687 sessions in a non-git home, API-visible
but panel-invisible); resolution now keys on the WORKTREE PATH (the D1
identity — a stale projectID never steals a session), synthesizes a
first-class home for unmatched directories, and re-keys for free when a
non-git home later becomes a git repo (opened projects resolve first). The
per-project home scope keeps its deliberate filtering; the all-projects
scope drops nothing.

home-sessions-controller/home-sessions are carried into the overlay
(upstream-maintained true overlays) wired to the new pure module
(home-session-groups, unit-tested headless), and the home page routes
through sessionListState: while the index query has never succeeded the
view renders the skeleton, never the empty state.
…s release channel, fail-open (D3)

Issue #817 — the revised placement of D3: parity is checked CLIENT-side at
boot using the server-reported version (from the health check the app
already runs) against the canonical opencode release channel. Three
outcomes — parity-ok | parity-drift | channel-unreachable — and the check
fails OPEN on an unreachable channel while recording a DISTINCT outcome, so
an assertion that never ran is never mistaken for one that passed (the
lagging-hub incident). The record is logged, never a gate: enforcement of
upgrading stays advisory for solo users; the check is not.
…+ hub request log)

Issue #817 — port of the fork's 80a57acc8a adapted to the client-derived
currency: boots the client's session-list layer against a seeded hub and
asserts on the hub's request log — the same evidence that diagnosed #293.
Owns the D2 criteria's fixtures: fetch-before-first-render (the boot order
snapshot-hydrate → fetch-initiate → first-render, with the fetch initiated
unconditionally — a persisted snapshot is never an authority), stale-client
self-heal (seeded with the #293 webview-storage shape), tokenless-snapshot
invalidation, out-of-band projection change, and the honest states after a
resolved fetch.
…te becomes per-harness contract testing (D6)

Issue #817 — the revised placement: the fork's protocol-level drift gate
(packages/protocol session-list-semantics) becomes a CONFORMANCE suite the
client carries. The client's expectations of session-list query semantics
are declared (CANONICAL_SESSION_LIST_SEMANTICS: directory + parentID
scoping, desc order, archived excluded, roots-only on parentID null,
cursor-only pagination exhaustion) and probed against any harness's
session.list — canonical opencode today, Telaio tomorrow. A semantic change
without a companion update goes red (scoping/filtering/pagination drift
probes fail); an additive optional field with a base default goes green
(the D6 change policy, with the F5 additive-field registry declared). The
pagination probe takes the fixture's expected row count — no black-box
probe can distinguish a store that truly ended from a hub that stopped
early on a short page.
…iles; add the missed snapshot behavior test

The #817 overlay additions (session-currency, session-snapshot,
session-list-state, boot-parity, session-list-conformance,
home-session-groups, h1-client-boot, the carried true overlays) registered
via refresh_manifest.mjs against upstream v1.18.29; the boot-currency
decision's behavior suite (session_snapshot_817.test.ts) lands with it —
it was authored in the TDD loop but left unstaged at its commit boundary.
…ase.version, readonly projects, parity parts typing

Issue #817 — the app-tree typecheck (materialize + tsgo -b) caught three
gaps in the port: ServerSDKBase declares the version accessor the sync
consumes; home-session-groups takes a readonly project list; boot-parity's
log parts are typed as strings. Manifest + drift-report re-recorded for the
touched overlay files, and .materialized/ is gitignored — it is a local
build artifact and was never meant to ride a commit. The app package now
typechecks clean against upstream v1.18.29 except two PRE-EXISTING error
classes on origin/main (server-session.ts diff_version vs the overlay's
SessionCache; i18n/parity.test.ts's 'am' locale not in the overlay dict
set) — noted for a follow-up, not fixed here (no drive-bys). The vite
production build succeeds.
@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Re-home the session & device lifecycle base contract onto the app overlay (canonical v1.18.29)

1 participant