Skip to content

Integrate disposable SQLite catalogs (session/history/usage/task) / 整合可丢弃 SQLite 目录投影(会话/历史/用量/任务) - #8257

Open
SivanCola wants to merge 66 commits into
esengine:main-v2from
SivanCola:feature/sqlite-catalogs-integration
Open

Integrate disposable SQLite catalogs (session/history/usage/task) / 整合可丢弃 SQLite 目录投影(会话/历史/用量/任务)#8257
SivanCola wants to merge 66 commits into
esengine:main-v2from
SivanCola:feature/sqlite-catalogs-integration

Conversation

@SivanCola

@SivanCola SivanCola commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Integration PR that lands the multi-domain disposable SQLite catalog stack on the latest main-v2:

Source Domain What is kept
#8186 Session Catalog Non-blocking desktop session discovery, hardened projectiondb lifecycle (corruption-only quarantine, atomic rebuild, empty-cache memory fallback, repair drain, scan resume), ProjectTree shell + request sequencing
#8217 History Search Catalog Token-only FTS5 projection, non-blocking persist observer, paged Desktop history manager, history tool partial results, CLI doctor/reindex
#8218 Usage Catalog Idempotent usage rollups under cache, stats writer hooks, CLI doctor/reindex
#8219 Task Catalog Cross-project task center projection, Desktop task APIs, FileStore remains authoritative, CLI doctor/reindex

Shared rules across all four domains:

  • Authoritative data stays on disk as JSONL / event logs / .meta / task snapshots / stats files.
  • SQLite lives only under config.CacheDir() (session-catalog, history-search, usage-catalog, task-catalog) and can be deleted/rebuilt safely.
  • Startup, create-session, add-project, and quit never wait on full historical scans.

Consolidation notes

Kept / adapted

Conflict resolution preferred the #8186 hardened open/rebuild/repair paths when foundation files overlapped, then re-applied domain-specific registration (history + usage + task) and combined flush/register on desktop shutdown/startup.

Follow-up hardening on this integration head:

  • Windows-safe SQLite file URIs and RequireDisk rebuild opens.
  • Empty CacheDir forces in-memory projections for all four domains.
  • History flush drains pending work; directory signature skips unchanged rescans; tool_output is not FTS-indexed by default.

Reviewed but not adopted as separate stacks

The original stacked heads of #8217/#8218/#8219 (pre-#8186-fix foundation) are superseded by this integration branch rather than merged independently onto main-v2.

Issues

Refs #8152

Supersedes / consolidates: #8186, #8217, #8218, #8219

Documentation impact

Documentation-impact: updated - session/history/usage/task catalog docs and CLI doctor/reindex surfaces

Cache impact

Cache-impact: low - history tool Name/Description/Schema bytes are unchanged; only the search backend switches to an indexed projection. Managed plugin auto-migration from the session-catalog foundation can still cause a one-time tool-list change when an old invalid plugin is upgraded into a loaded tool set.
Cache-guard: existing internal/history package tests plus go test ./internal/history ./internal/boot exercise tool registration; schema text matches pre-PR history tool schema.
System-prompt-review: Reviewed — no provider-visible system prompt, memory prefix, output style, or skill-index change; boot only wires session persist observer + indexed history tool backend with unchanged history tool schema.

Compatibility

Format Behavior
Session JSONL / events / .meta Unchanged authority
Task FileStore snapshots / events Unchanged authority
Stats JSONL Unchanged authority; usage SQLite is a rollup
SQLite projections Disposable; missing/corrupt → rebuild or memory fallback
Wails Additive paged history/task/session APIs; legacy wrappers retained where present

Verification

  • go test ./internal/projectiondb ./internal/sessioncatalog ./internal/historycatalog ./internal/usagecatalog ./internal/taskcatalog ./internal/stats ./internal/history ./internal/cli
  • go test -race ./internal/projectiondb ./internal/sessioncatalog ./internal/historycatalog
  • go run ./tools/repolint
  • cd desktop && go test -run 'Catalog|History|TaskCatalog|SessionCatalog|TopicMigration' .

Test plan

  • Desktop cold start with a large session directory: project shells appear without multi-minute stall; quit works while indexing continues
  • History manager search returns partial results while indexing, then refreshes on history-index:changed-v1
  • Usage stats rollups after a few turns; reasonix catalogs reindex usage rebuilds without deleting stats files
  • Task center lists cross-project tasks; delete/archive remains FileStore-authoritative
  • reasonix doctor catalogs --json reports all four projections
  • reasonix sessions reindex succeeds on Windows and leaves transcripts untouched
  • CI green on this head

Problem:
Desktop startup and project-tree queries could synchronously migrate and decode legacy session JSONL, blocking navigation, creation, and shutdown.

Root cause:
Session discovery, metadata repair, controller admission, and plugin startup shared UI-critical paths and broad lifecycle locks.

Fix:
- add a disposable SQLite session catalog with incremental reconciliation, repair checkpoints, corruption recovery, and memory fallback
- expose revisioned cursor pagination and migrate the desktop project tree to lazy loading
- move controller construction outside lifecycle admission and isolate incompatible plugin startup
- add session doctor and reindex commands while preserving authoritative JSONL and metadata
- retain CGO-free builds with platform-specific fallbacks

Verification:
- go test ./...
- cd desktop && go test ./...
- go test -race ./internal/sessioncatalog ./internal/agent
- cd desktop && go test -race .
- go vet ./... in root and desktop
- cd desktop/frontend && pnpm test:all && pnpm build
- CGO_ENABLED=0 cross-builds for supported macOS, Windows, and Linux targets
Reason:
The official base advanced by six plan-contract commits after the session catalog implementation began.

Resolution:
Merge the current official main-v2 without rewriting the reviewed feature commit.

Verification:
- go test ./... passed at the repository root after the merge
- go test ./... passed in desktop after the merge
- the feature commit retains focused race, frontend, vet, and cross-build evidence
Problem:
The initial session catalog implementation exceeded incremental repository size and complexity budgets in several existing owner files, and Desktop CI reported stale helpers plus two modernization findings.

Root cause:
Catalog lifecycle, runtime projection, frontend topic presentation, mock bindings, plugin compatibility, and CLI wiring were colocated with already-large modules. Removing the eager project tree also left legacy runtime-tree helpers unused.

Fix:
- split catalog lifecycle from runtime projection and metadata mapping
- extract frontend catalog contracts, mock bridge, topic presentation, and count formatting
- isolate plugin compatibility, session projection, and CLI completion wiring
- remove obsolete eager-tree helpers and address CI modernization findings
- preserve behavior without changing repolint baselines

Verification:
- go run ./tools/repolint
- golangci-lint v2.12.2: 0 issues
- go test affected root packages
- focused Desktop catalog and workspace regression tests
- pnpm test:all
- pnpm build
Bring the session catalog prerequisite onto the current upstream base before publishing the History, Usage, and Task follow-up branches.

# Conflicts:
#	desktop/frontend/scripts/check-bundle-budget.mjs
Problem: Each disposable catalog would otherwise duplicate SQLite migration, integrity, quarantine, permission, and in-memory fallback behavior.

Root cause: The original session catalog owned database lifecycle policy together with session-specific schema and reconciliation logic.

Fix: Extract the reusable lifecycle into internal/projectiondb, migrate the session catalog to it, and add schema-v3 keyset session pagination for follow-up consumers.

Verification: Covered by projectiondb lifecycle tests and session catalog migration, pagination, cursor, and compatibility tests.
Problem: Agent history search and the Desktop history manager scaled with the total transcript set and could synchronously decode session files.

Root cause: Search, metadata listing, and context retrieval shared filesystem-scanning code instead of a disposable query projection.

Fix: Add token-only FTS5 indexing with resumable reconciliation, post-commit persistence hints, indexed Agent search, keyset Desktop pagination, runtime overlays, progress reporting, diagnostics, and bilingual storage documentation.

Verification: Covered by tokenizer/search compatibility, append/rewrite reconciliation, stale cursor, corrupt source, Wails array, and warm-query benchmark tests; provider-visible history tool schema bytes remain unchanged.
Problem: Users and operators need a discoverable cache location, diagnostic command, and safe rebuild path for indexed history.

Fix: Link the bilingual history catalog guide from CLI and storage references, document its cache path, and ratchet repolint to the History-only branch shape.

Verification: repolint baseline generation and git diff --check passed.
Problem: Adding the catalogs dispatcher without a matching shell-completion root caused the dispatch coverage test to fail and hid reindex flags from users.

Root cause: Catalog subcommands were registered only for execution, while the completion registry remained a separate static list.

Fix: Carry completion flags in each catalog registration and build the catalogs reindex completion tree from the same registry without increasing lint debt.

Verification: go test ./internal/cli and repolint passed.
Problem: The History bridge adds one KiB of always-available typed contracts and exceeded the prerequisite branch's exact raw bundle ceiling by 0.9 KiB.

Fix: Increase only the raw initial JavaScript/CSS allowance from 2,209 to 2,210 KiB while preserving all gzip and largest-chunk gates.

Verification: The production bundle measured 2,209.9 KiB raw and all bundle checks passed.
Problem: Catalog-backed compatibility listing lost legacy filename-only recovery markers, and old Desktop fixtures invoked listing without installing the startup-owned catalog.

Root cause: SessionMeta projection trusted only the persisted recovered flag, while tests still assumed ListSessions could synchronously scan JSONL when the catalog pointer was nil.

Fix: Conservatively infer legacy recovery names from catalog metadata and migrate the affected fixtures to an explicit in-memory catalog without restoring any synchronous scan fallback.

Verification: The three ownership/recovery regressions, go test ./internal/cli, bundle budgets, and repolint passed.
Problem: Each disposable catalog would otherwise duplicate SQLite migration, integrity, quarantine, permission, and in-memory fallback behavior.

Root cause: The original session catalog owned database lifecycle policy together with session-specific schema and reconciliation logic.

Fix: Extract the reusable lifecycle into internal/projectiondb, migrate the session catalog to it, and add schema-v3 keyset session pagination for follow-up consumers.

Verification: Covered by projectiondb lifecycle tests and session catalog migration, pagination, cursor, and compatibility tests.
Problem: Long-range usage queries repeatedly decoded every daily JSONL record, making the statistics page increasingly expensive.

Root cause: The authoritative append log had no offset ledger or derived aggregation layer, so exact queries always rescanned source files.

Fix: Add a disposable usage catalog with path-offset idempotency, line-hash rewrite detection, tail reconciliation, daily rollups, exact JSONL fallback for incomplete ranges, bounded flush integration, diagnostics, and bilingual documentation.

Verification: Covered by duplicate receipt, tail append, file rewrite, corrupt-line, legacy request, SQL-versus-JSONL parity, and million-record warm-query benchmark tests.
Problem: The catalogs dispatcher was absent from shell completion and did not expose the Usage reindex flags.

Root cause: Catalog execution and completion used separate static registries.

Fix: Build the catalogs reindex completion tree from the same domain registration and publish the Usage --json contract without increasing completion-function size.

Verification: go test ./internal/cli passed.
Problem: Operators need a discoverable cache path, diagnostic command, exact-fallback guarantee, and safe rebuild path for usage rollups.

Fix: Link the bilingual Usage catalog guide from CLI and storage references and ratchet repolint to the Usage-only branch shape.

Verification: repolint baseline generation and git diff --check passed.
Problem: Each disposable catalog would otherwise duplicate SQLite migration, integrity, quarantine, permission, and in-memory fallback behavior.

Root cause: The original session catalog owned database lifecycle policy together with session-specific schema and reconciliation logic.

Fix: Extract the reusable lifecycle into internal/projectiondb, migrate the session catalog to it, and add schema-v3 keyset session pagination for follow-up consumers.

Verification: Covered by projectiondb lifecycle tests and session catalog migration, pagination, cursor, and compatibility tests.
Problem: Task Monitor could only scan the active project synchronously and could not page or safely route controls across projects.

Root cause: Task snapshots and event logs had no disposable query projection, while control APIs implicitly depended on the active tab instead of a stable project identity.

Fix: Add an observed authoritative FileStore, resumable snapshot and lazy event indexing, SHA-256 project-key routing, runtime overlays, paged Wails APIs, a cross-project Task Center, diagnostics, and bilingual documentation.

Verification: Covered by lock-boundary notifications, corrupt snapshot/event isolation, event-tail pagination, lease reconciliation, stale cursor, project-key action routing, Wails arrays, and 100,000-task warm-page benchmark tests.
Problem: the isolated Task Catalog branch lacked shell completion metadata and only flushed its shared projection during desktop shutdown, allowing late reconcile goroutines to outlive the shutdown boundary.

Root cause: the task implementation was originally layered on the integrated History branch, so domain-specific lifecycle and documentation details were lost when it was split into an independently reviewable branch.

Fix: register task reindex completions, document the task cache and recovery commands, gate reconcile scheduling during close, wait for scheduled workers, close the shared catalog within the 250ms desktop drain, and account for the measured 1 KiB frontend bundle increase.

Verification: focused Desktop tests, frontend production build and bundle budgets, repository lint, core package tests, and race tests for projectiondb, taskcatalog, taskmonitor, and control passed.
# Conflicts:
#	desktop/frontend/scripts/check-bundle-budget.mjs
# Conflicts:
#	desktop/frontend/scripts/check-bundle-budget.mjs
Problem: the latest main-v2 transcript-selection work intentionally enlarged existing frontend files after the History branch had captured its repository lint baseline.

Fix: regenerate the carry-forward baseline on the merged History branch without adding new domain violations.

Verification: repolint is clean and git diff --check passes.
Problem: the latest main-v2 transcript-selection work intentionally enlarged existing frontend files after the Usage branch had captured its repository lint baseline.

Fix: regenerate the carry-forward baseline on the merged Usage branch without adding new domain violations.

Verification: repolint, projection/statistics tests, and usage race tests pass.
# Conflicts:
#	desktop/frontend/scripts/check-bundle-budget.mjs
Problem: the latest main-v2 transcript-selection work intentionally enlarged existing frontend files after the Task branch had captured its repository lint baseline.

Fix: regenerate the carry-forward baseline on the merged Task branch without adding new domain violations.

Verification: repolint, focused Desktop tests, frontend production build, and task/control package tests pass.
# Conflicts:
#	tools/repolint/baseline.json
# Conflicts:
#	tools/repolint/baseline.json
# Conflicts:
#	tools/repolint/baseline.json
Problem: after Session Catalog adopted projectiondb, the old filesystem probes and migration entrypoint remained unused, while CI also flagged two modern Go style issues.

Root cause: the common projection extraction preserved obsolete package-local helpers to minimize the first refactor diff.

Fix: remove the dead remote-filesystem and migration helpers, use integer range in the benchmark, and iterate managed plugin path segments with strings.SplitSeq.

Verification: sessioncatalog, pluginpkg, and projectiondb tests pass; golangci-lint reports zero issues.
Problem: the shutdown barrier used an intentionally empty mutex critical section, which staticcheck rejects.

Fix: guard a reconcileDone flag with the same mutex used for scheduling so Close prevents late WaitGroup additions without an empty critical section.

Verification: taskcatalog tests pass and golangci-lint reports zero issues.
@SivanCola
SivanCola requested a review from esengine as a code owner August 10, 2026 17:52
@github-actions github-actions Bot added v2 Go rewrite (1.x) — main-v2 branch, active development desktop Wails desktop app (desktop/**) tui Terminal UI / CLI (internal/cli, internal/control) agent Core agent loop (internal/agent, internal/control) config Configuration & setup (internal/config) labels Aug 10, 2026
Problem
PR esengine#8257 failed Windows reindex, empty-cache path safety, history flush
semantics, locale gzip budget, and PR policy guards (docs/cache impact,
repolint baseline).

Root cause
SQLite file URIs were not Windows-safe, so disk open failed and fell back to
memory during rebuild. Sibling catalogs joined CacheDir without empty checks.
History Flush did not drain work; tool_output was always FTS-indexed; zh-TW
locale budget was tight to the byte.

Fix
- Cross-platform diskFileDSN and RequireDisk rebuild opens.
- Empty CacheDir => memory for history/usage/task catalogs.
- History Flush drains dirty roots and queue; signature-skip periodic rescan;
  stop indexing tool_output by default.
- Ratchet zh-TW/zh locale gzip budgets and refresh repolint baseline.

Verification
- go test ./internal/projectiondb ./internal/sessioncatalog ./internal/historycatalog ./internal/usagecatalog ./internal/taskcatalog ./internal/cli
- go test -race ./internal/projectiondb ./internal/sessioncatalog ./internal/historycatalog
- go run ./tools/repolint
…n close

Problem
CI still failed on lint (intrange), desktop race ordering for new CLI sessions
(LastActivityAt=0), Windows TempDir cleanup locking usage-catalog SQLite, and
migration-marker timing flakes. The branch was also behind main-v2.

Root cause
SyncMetadata inserted topics with last_activity_at=0/turns_state=valid before
sessions were indexed; reconcile requests could be dropped when the channel was
full; process-local usage/history catalogs kept SQLite handles open after tests;
marker assertions used a single Stat.

Fix
- Merge latest main-v2.
- Floor session activity on file mtime; SyncMetadata inherits live aggregates.
- Retain overflow reconcile requests in a dirty map and drain them.
- Close shared history/usage/task catalogs on desktop shutdown and test cleanup.
- Wait for migration markers; fix for-range lint.

Verification
- go test ./internal/sessioncatalog ./internal/stats ./internal/history ./internal/cli
- go test -race ./internal/sessioncatalog
- cd desktop && go test -race -run 'TestProjectTreeMigratesNewCLISession|TestCoveredRecoveryCopy|TestTopicMigrationMarker'
- go run ./tools/repolint
Problem
Integration PR esengine#8257 left six functional/concurrency gaps: history tool
tool_output was never indexed, History UI hid body-only hits, open/current
filters ran after pagination, MCP install/update/remove/reconnect did not
bump the controller extension generation, task control required a ready
SQLite catalog, and usage Ready ignored same-size rewrites.

Root cause
Disposable catalogs filtered or published after partial candidate windows,
and control-plane paths depended on projection readiness or incomplete
generation barriers.

Fix
- Index tool_output while keeping default search kinds excluding it.
- Fill ListHistorySessions/SearchHistoryContent pages after runtime filters,
  and show body hits with an independent body cursor in the History UI.
- Bump extensionGeneration on Install/Update/Remove/Reconnect MCP paths.
- Resolve task action projects from the allowlisted roots/FileStore path.
- Compare mtime_ns in usage Ready (and record mtime on live receipts).

Verification
- go test ./internal/historycatalog/ ./internal/usagecatalog/ ./internal/sessioncatalog/ ./internal/history/
- go test -race ./internal/historycatalog/ ./internal/usagecatalog/
- go test -run 'TestTaskActionProjectResolvesAllowlistWithoutCatalog|TestExtensionGenerationBumpsOnMCPMutationSites|TestTaskMonitor|TestStopTask|TestCloneDetached|TestApplyRuntime|TestCloseRemoved' ./desktop
- npx tsx desktop/frontend/src/__tests__/history-catalog-body-hits.test.ts
Problem
esengine#8257 could leave the project sidebar permanently empty when
project-tree:changed-v2 advanced revision before the initial shell
snapshot, and large-session hydration still waited on controller ready
while failed history loads looked like successful empty transcripts.

Root cause
Shell snapshots reused the disposable catalog revision watermark, and
HistorySlice/listSessions treated read failures as empty success without
preserving prior content or offering retry.

Fix
- Accept projects.json shells when the tree is empty even if catalog
  revision is already ahead; re-fetch the shell on empty-tree v2 events.
- Prefer cold HistorySliceForTab before controller ready; surface slice
  Error instead of silent empty pages.
- Defer transcript reset until history succeeds; keep placeholders/items
  on hydrate_error; expose retrySessionHistory + UI banner.
- Stop listSessions from swallowing catalog failures as [].

Verification
- go test -run 'TestHistorySliceColdPathBeforeControllerReady|TestHistorySliceReportsErrorInsteadOfEmptySuccess|TestProjectTreeShellSurvivesCatalogRevisionRace|TestHistorySlice|TestProjectTree|TestTopicArchive|TestArchive' ./desktop
- npx tsx desktop/frontend/src/__tests__/project-tree-shell-race.test.ts
- npx tsx desktop/frontend/src/__tests__/history-load-failure-contract.test.ts
- npx tsx desktop/frontend/src/__tests__/history-catalog-body-hits.test.ts
Problem
Review of esengine#8257 found residual blockers: body-only history hid the search
input, MCP ClearAuth skipped generation bumps and stale builds could
re-register deleted shared-host servers, task control could adopt SQLite
roots, open-status page fill disagreed with the frontend, and repolint
failed on size/complexity/trailing blanks.

Root cause
UI gated search on metadata session count; generation barriers were
incomplete for Host-owned MCP state; catalog rows overwrote allowlisted
project roots; SearchHistoryContent grew past complexity budget.

Fix
- Always show history search in non-trash mode, including body-only hits.
- Bump extensionGeneration on ClearMCPServerAuthentication; purge unwanted
  shared-host servers when a stale build loses the generation race.
- Keep allowlisted task project roots; catalog may only supply labels.
- Align backend open filter with open-but-not-current UI semantics.
- Split history search/index helpers to satisfy repolint; fix docs EOF.

Verification
- go run ./tools/repolint
- go test ./internal/historycatalog/ ./internal/usagecatalog/
- go test -run 'TestHistorySliceCold|TestHistorySliceReports|TestTaskAction|TestExtension|TestProjectTreeShell' ./desktop
- npx tsx desktop/frontend/src/__tests__/history-catalog-body-hits.test.ts
- npx tsx desktop/frontend/src/__tests__/history-load-failure-contract.test.ts
- git diff --check
Problem
After Clear Context, immediately switching Plan/layout/token mode could
briefly re-show the destroyed transcript. ClearSession rotated the backend
session path but the frontend only wiped the view; meta, TranscriptStore
resident cache, and hydrate identity still pointed at the old session.

Root cause
ClearSessionForTab returned no replacement identity. clearSession did not
evict TranscriptStore or apply a new sessionPath/generation. Hydrate only
checked request sequence numbers, so a post-clear load started with stale
meta.sessionPath could still preferResident-serve session A.

Fix
- ClearSession/ClearSessionForTab return SessionClearResult with path,
  revision, digest, and a bumped tab SessionGeneration.
- clearSession applies that identity atomically, resets transcript state,
  and evicts the tab from TranscriptStore.
- loadSessionDataForTab rejects results when meta sessionPath or
  sessionGeneration no longer match the load's identity.
- On MCP extension generation race, resetSharedHostMCP drops all shared-host
  clients (including same-name still-enabled servers) before rebuild.
- gofmt app.go; keep repolint clean.

Verification
- gofmt -l . (clean)
- go run ./tools/repolint
- go test -run 'TestClearSessionForTabReturnsReplacementIdentity|TestResetSharedHostMCPClearsStillEnabledFailure|TestClearSession|TestTabScoped' ./desktop
- npx tsx desktop/frontend/src/__tests__/clear-session-identity-contract.test.ts
Problem
af6b1cb failed Desktop CI: App.tsx JSX was invalid (two roots without a
Fragment), unused purgeUnwantedSharedHostServers and a classic for-range
failed macOS lint, and generation-mismatch cleanup wiped the entire shared
MCP Host including servers owned by other tabs or a newer rebuild.

Root cause
Hydrate-error UI was sibling-adjacent to Transcript in a ternary branch.
Host rollback used a full reset instead of build-scoped cleanup.

Fix
- Wrap Transcript + hydrate error in a Fragment.
- Remove unused purge helper; use for-range in cold history test.
- Snapshot Host servers before boot.Build; on generation loss or build
  failure roll back only names introduced by that build.
- sameMeta compares sessionGeneration; hydrate uses shared
  hydrateIdentityCurrent fence.
- Add deferred-barrier clear→late-A race test and keep source contracts.

Verification
- gofmt -l desktop
- go run ./tools/repolint
- go test -run 'TestClearSessionForTab|TestRollbackSharedHost|TestClearSession|TestTabScoped' ./desktop
- npx tsx desktop/frontend/src/__tests__/clear-session-identity-race.test.ts
- npx tsx desktop/frontend/src/__tests__/clear-session-identity-contract.test.ts
Problem:
Desktop CI failed with TS2305 because hydrateErrorState imported a non-existent
Item type from ./types. Host generation-loss rollback still used name-diff
snapshots, so a deterministic interleaving where sibling/new-generation resources
appear after the snapshot incorrectly deleted those live clients.

Root cause:
1. The helper was typed against a concrete Item export that does not exist in
   types (and created a fragile circular import surface).
2. Name-set diff has no ownership: any server name created after the snapshot
   was attributed to the lost build, including post-snapshot sibling work.

Fix:
- Make applyHydrateErrorState / hydratePlaceholderItems generic over TItem so
  the helper needs no types import.
- Assign Host-local Client instance IDs, journal registrations during
  RunWithRegistrationJournal, and roll back only with RemoveIfInstance.
- Controller builds wrap boot.Build in the journal; supersede/generation-loss
  paths call RollbackRegistration on journaled refs only.
- Add deterministic instance-scoped regression tests (including post-journal
  sibling preservation and same-name newer-generation survival).
- Make Client.close nil-safe for transport-less test clients.

Verification:
- go test ./internal/plugin/ -run 'TestRegistrationJournal|TestRunWithRegistration' -count=1
- go test -race ./internal/plugin/ -run 'TestRegistrationJournal|TestRunWithRegistration' -count=1
- cd desktop && go test -c . && go test -run TestClearSessionForTab -count=1
- cd desktop/frontend && pnpm typecheck
@github-actions github-actions Bot added the mcp MCP servers / plugins (internal/plugin, codegraph) label Aug 11, 2026
…atures

Problem:
Three P1 merge blockers remained on the catalogs integration head:
1) Host registration journal used a Host-global regJournalActive flag, so
   sibling hot-adds during a stale build were attributed and rolled back.
2) RemoveSession stored tombstones only after mutex waits; a 150ms desktop
   context could expire mid-lock, leave SQL undeleted, and ListTopics still
   returned archived topics (race-mode restore test).
3) Topic migration markers used strict mtime ordering, which failed on
   Windows coarse timestamps for new CLI sessions after the marker.

Root cause:
1) Ownership was time-window based rather than token-based.
2) Query paths trusted SQLite without an immediate removal overlay.
3) Marker validity compared file mtimes instead of directory content.

Fix:
- Replace the global journal with per-build RegistrationScope tokens
  propagated via context through EnsureConnected and LazyToolset; abort
  rejects late registrations; sibling writes without the token are ignored.
- Do not hold a Host lock across boot.Build (scopes do not serialize builds).
- Record removedPaths before any mutex; filter ListTopics/ListSessions/
  GetSession through the tombstone; TryLock durable DELETE and retry async.
- Persist migration markers as name+size directory signatures; new sessions
  invalidate without mtime ordering.
- Extract registration scope, removal, migration marker, and legacy migration
  helpers to keep repolint budgets clean (baseline carry-forward only).

Verification:
- go test ./internal/plugin/ -run TestRegistration (-race)
- go test ./internal/sessioncatalog/ -run 'TestRemoveSession|TestListTopics' (-race)
- cd desktop && go test -run 'TestProjectTreeMigratesNewCLISessionAfterProjectDirMarker|TestRestoreGlobalTopicSessionReindexesProjectTree|TestClearSessionForTab'
- cd desktop && go test -race -count=3 -run TestRestoreGlobalTopicSessionReindexesProjectTree
- go run ./tools/repolint
Problem:
Stale controller builds could roll back shared MCP instances owned by a newer build. Session tombstones could underfill paginated results and their same-revision refresh event could be ignored. Legacy topic migration markers could also miss same-size or event-log changes.

Root cause:
Registration scopes tracked creation without transferable instance claims, catalog pagination applied tombstones after SQL limits, and migration completion used only transcript names and sizes while explicit reconciliation still trusted that marker.

Fix:
Track exact MCP instance claims through scope commit or abort, scan catalog keyset pages until visible limits are satisfied, publish overlay refreshes that accept equal revisions, fingerprint bounded session artifacts and authoritative event logs, and add a marker-bypassing explicit migration path.

Verification:
go test ./internal/plugin ./internal/sessioncatalog
go test -race ./internal/plugin ./internal/sessioncatalog
go test ./desktop focused migration, tombstone, restore, clear-session, and registration cases
pnpm typecheck
npx tsx src/__tests__/project-tree-shell-race.test.ts
Problem:
- The integration branch lagged main-v2 and conflicted in Desktop startup, boot wiring, and frontend guardrails.
- In-memory shared-cache projections could return SQLITE_LOCKED during concurrent reconcile/write operations.
- A timed-out session catalog close could leave its database open, while macOS updater tests shared raw descriptor ownership with production-style helpers.

Root cause:
- The branch and main evolved the same runtime owners independently.
- SQLite shared-cache table locks do not honor busy_timeout across pooled in-memory connections.
- Session catalog close completion and test file-descriptor ownership lacked explicit lifetime fences.

Fix:
- Resolve the merge semantically, preserving catalog task-store and registration-scope behavior alongside upstream config warnings and agent runtime updates.
- Use one database/sql connection for memory projections, make session catalog close eventually complete exactly once, and duplicate updater handoff descriptors for exclusive ownership.

Verification:
- go test and go test -race ./internal/projectiondb ./internal/sessioncatalog
- Desktop session catalog regression: 100 ordinary runs and 30 race runs
- macOS updater handoff/TempDir regression: 100 ordinary runs and 20 race runs
Problem:
- main-v2 advanced while esengine#8257 was under review, adding task spend guards across agent, boot, config, and control.
- The repolint baseline conflicted with the catalog integration, and the new real-Build budget test used a machine-speed-dependent round threshold.

Root cause:
- Both branches changed boot assembly and repository budget snapshots from the same earlier base.
- Counting rounds does not prove a wall-clock budget boundary under different runner loads.

Fix:
- Merge main-v2 semantically while preserving the catalog TaskStore and observed-session wiring.
- Recompute the exact combined repolint baseline without increasing the finding count.
- Assert the typed task_budget/time pause with a bounded context instead of a round-count heuristic.

Verification:
- go test ./internal/boot passed in three independent processes
- go test -race -count=1 ./internal/boot
- focused task-budget and history real-Build tests
- go run ./tools/repolint
Problem:
- main-v2 advanced while esengine#8257 was under final review, adding trusted completion-contract input handling.

Root cause:
- The catalog integration branch needed the latest protected runtime behavior before final validation and publication.

Fix:
- Merge the latest main-v2 commit without conflicts, preserving both the catalog integration and trusted completion receipt routing.

Verification:
- Full root, Desktop, race, frontend, cross-platform, and repository gates will run on this exact merge head before push.
Problem:
- The live-context controller test failed before initial context and balance data loaded.

Root cause:
- Its fixture mocked only the legacy HistoryPageForTab method while startup hydration now requires HistorySliceForTab.

Fix:
- Reuse the shared history-slice fixture for an empty startup transcript.

Verification:
- pnpm exec tsx src/__tests__/use-controller-live-context.test.tsx
- pnpm test:all
- pnpm build
- go run ./tools/repolint
@github-actions github-actions Bot added the updater Auto-update / installer / release packaging label Aug 11, 2026
Problem:
- A late asynchronous controller build could launch a shared-host MCP while an explicit reconnect was still replacing the same single-instance server.
- The loser failed with an address-in-use error, and an early generation check still allowed stale registries to publish after a later MCP mutation.

Root cause:
- Optimistic boot intentionally runs outside runtimeAdmissionMu, but shared-host extension startup had no dedicated mutation fence.
- extensionGeneration was checked before the final admission boundary instead of as part of publication.

Fix:
- Add a reader/writer extension gate so controller boots remain concurrent with each other while MCP mutations exclude shared-host startup.
- Bump the extension generation before releasing a mutation and revalidate it both after entering the boot gate and under publication admission.
- Extract boot failure handling from the oversized tab assembly path and add a deterministic publication-fence regression.

Verification:
- go test -count=50 -run 'TestAuthorizeAndConnectMCPServerBlocksLateControllerBuild|TestControllerPublicationRejectsMutationThatCompletesAfterBoot' .
- go test -race -count=10 -run 'TestAuthorizeAndConnectMCPServerBlocksLateControllerBuild|TestControllerPublicationRejectsMutationThatCompletesAfterBoot' .
- go test -count=1 ./...
- go test -race -count=1 ./...
- go vet ./...
- golangci-lint run ./...
- go run ./tools/repolint
- CGO_ENABLED=0 cross-builds for darwin, windows, and linux
Problem:
- main-v2 advanced with esengine#8270 and esengine#8340 while the catalog integration was under review.
- Both branches changed the controller frontend and startup bundle budgets.

Root cause:
- The inbox recovery localization and opt-in task-spend defaults overlapped the catalog hydration guards and measured frontend budget ratchets.

Fix:
- Preserve the history hydration identity/error fences alongside locale-aware inbox recovery.
- Keep the latest task-spend defaults and use measured startup bundle budgets without widening the repolint baseline.

Verification:
- env REASONIX_RELEASE_CACHE_GUARD=1 go test -count=1 ./...
- env REASONIX_RELEASE_CACHE_GUARD=1 go test -race -count=1 ./...
- cd desktop && go test -count=1 ./...
- cd desktop && go test -race -count=1 ./...
- cd desktop/frontend && pnpm test:all && pnpm build && pnpm test:motion
- go vet ./... and pinned golangci-lint for native and cross-platform build tags
- CGO_ENABLED=0 root/Desktop builds for darwin amd64/arm64, windows amd64/arm64, and linux amd64
- Wails binding generation, repolint, release workflow contracts, SDK ordinary/race tests, and go mod tidy -diff
Problem:
- main-v2 advanced again with esengine#8329 after the catalog integration merge had been fully tested.

Root cause:
- The context gauge and compaction trigger previously measured different provider-visible inputs, and the frontend counted completion tokens as prompt occupancy.

Fix:
- Integrate the latest prompt/tool-schema accounting and frontend prompt-occupancy semantics without conflicts.

Verification:
- go test -count=1 ./internal/agent ./internal/provider ./internal/tool
- go test -race -count=1 ./internal/agent ./internal/provider ./internal/tool
- pnpm exec tsx src/__tests__/use-controller-stream-progress.test.ts
- pnpm build
- go run ./tools/repolint
- gofmt -l internal desktop
- git diff --cached --check
Problem:
- main-v2 advanced with esengine#8333 while PR esengine#8257 was being brought to an approvable state.
- App.tsx still carried the catalog branch's reference to the retired in-app smoke component.

Root cause:
- Both branches touched frontend startup while the base moved the WebView2 release check out of production code and into an exact-binary Windows smoke script.

Fix:
- Remove the retired production smoke import and preserve the external Windows startup gate, Chromium approval-animation gate, and release workflow contracts.

Verification:
- cd desktop/frontend && pnpm test:motion
- cd desktop/frontend && pnpm test:motion-browser
- cd desktop/frontend && pnpm build
- cd desktop && go test -count=1 ./...
- cd desktop && go test -race -count=1 ./...
- bash scripts/release-workflows.test.sh
- actionlint on changed CI/release workflows
- go run ./tools/repolint
- gofmt -l desktop internal cmd tools
- git diff --cached --check
Problem:
Catalog reindex commands only reconciled the existing database, metadata-only history edits could miss reconciliation, and concurrent rebuilds could race during replacement.

Root cause:
History, usage, and task entry points had no validated replacement pipeline; the history root signature ignored sidecar metadata; and projection rebuilds had no cross-process lifecycle lock.

Fix:
Build each derived catalog in a validated sibling database before atomic replacement, fence process-global managers during rebuild, retain task notifications across the fence, include metadata in history signatures, and serialize rebuilds with an advisory lock.

Verification:
- go test -count=1 ./...
- cd desktop && go test -count=1 ./...
- go test -race for boot and all affected catalog packages
- cd desktop && go test -race -count=1 ./...
- go vet for root and desktop modules
- make lint plus CI build-tag lint matrix
- CGO_ENABLED=0 builds for darwin, linux, and windows targets
Problem:
Windows CI could not remove temporary REASONIX_HOME directories after boot tests completed because v1.sqlite remained open.

Root cause:
The indexed history catalog is process-global, while several Build and rebuild tests changed config homes without fencing that shared lifecycle. Unix unlink semantics masked the leaked handle.

Fix:
Add a deterministic boot-test catalog fence that closes inherited state before each affected build and closes the replacement before temporary-directory cleanup. Apply it consistently to model boot and session-temp rebuild coverage.

Verification:
- focused boot lifecycle tests, 20 repetitions
- focused race tests, 5 repetitions
- go test -count=1 ./...
- go test -race -count=1 ./internal/boot
- make lint
- go vet ./...
- Windows amd64 boot test binary cross-compile
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Core agent loop (internal/agent, internal/control) config Configuration & setup (internal/config) desktop Wails desktop app (desktop/**) mcp MCP servers / plugins (internal/plugin, codegraph) tui Terminal UI / CLI (internal/cli, internal/control) updater Auto-update / installer / release packaging v2 Go rewrite (1.x) — main-v2 branch, active development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant