Files
obelisk/app/package.json
T
tommy0103andClaude Opus 4.8 e3e61cc7ab fix: coordinate sqlite index writers
Implement the full ADR-0006 plan: three-layer separation of transaction
correctness, retry policy, and cross-process writer coordination.

Layer 1 — scripts/tx.ts (transaction correctness):
- runWriteTransaction executes work exactly once; no internal retry.
- BEGIN IMMEDIATE takes the write lock up front (avoids SQLITE_BUSY_SNAPSHOT).
- Guarded rollback: checks inTransaction() via adapter before attempting
  ROLLBACK; never masks the primary exception.
- WriteTxDiagnostics attached to errors: phase, code, label,
  rollbackSucceeded, rollbackError, transactionActive.
- Binding adapters (betterSqliteTransactionAdapter, nodeSqliteTransactionAdapter)
  mapping better-sqlite3's `.inTransaction` and node:sqlite's `.isTransaction`.
- configureConnection centralizes WAL + synchronous + busy_timeout.

Layer 2 — scripts/write-coordinator.ts (retry policy):
- runRetryableWriteTransaction: bounded retry with total time budget.
- Only retries when the transaction confirmed ended (transactionActive=false)
  and the error is SQLITE_BUSY during work/commit phase.
- BEGIN-phase BUSY = abort entire build (isBeginBusyFailure); the caller
  returns `{ deferred: true, reason: 'writer_busy' }` instead of waiting.
- hasUnusableTransaction detects a still-active transaction after failure;
  aborts the build immediately, never retries.

Layer 3 — scripts/writer-lease.ts (cross-process coordination):
- acquireWriterLease: dedicated writer.lock.sqlite with busy_timeout=0 +
  BEGIN IMMEDIATE. Non-blocking attempt; bounded wait with retryDelayMs.
- writerLockPathFor derives lock path from the target DB path.
- Lease held for the entire build; released on completion or failure.
- Lock DB uses DELETE journal (not WAL); crash/close auto-releases.
- All consumers obey: skill acquires at build start (returns deferred if
  unavailable); app daemon (via worker) acquires for its build cycle.

Build semantics changes:
- affectedSessionIds updated only after successful commit.
- BuildIndexResult gains skipped/skippedFiles for observability.
- Skill finalize failure now fails the build (was silently warned).
- Checkpoint changed to PASSIVE (TRUNCATE reserved for maintenance/exit).
- Skill buildIndex returns { deferred, reason } on lease contention;
  indexer-service reschedules the build (deferredRetryMs) without publishing
  a heartbeat (so the build-deferred state is visible to cross-process
  arbitration).
- Service publishes heartbeat immediately on start() for correct arbitration.

Tests:
- tests/write-transaction.test.mjs: single-shot execution, diagnostics
  propagation, auto-rolled-back transaction detected, rollback failure
  captured as metadata, BEGIN IMMEDIATE semantics.
- tests/writer-lease.test.mjs: acquire/release, contention returns null,
  bounded wait with release during budget.
- tests/app-writer-lease.test.mjs: better-sqlite3 adapter integration.
- tests/app-rollback-guard.test.mjs: rewritten — transient BUSY recovered
  by coordinator, persistent BUSY skips file, begin-busy aborts build,
  live-transaction aborts build, phantom affectedSessionIds prevented.
- tests/daemon-arbitration.test.mjs: skill defers to fresh app heartbeat,
  builds when heartbeat is stale.
- tests/app-indexer-service.test.mjs: new cases for deferred-retry
  scheduling and immediate heartbeat on start.
- app/tests/electron-concurrency.mjs + child: dual-child IPC structure for
  real better-sqlite3 contention (holder acquires lock → build child starts
  → delayed release → result collected; persistent contention bounded).

ADR-0006 updated to reflect the implemented design.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 00:42:43 +08:00

75 lines
1.9 KiB
JSON

{
"name": "obelisk",
"version": "0.1.0",
"description": "Memory management for Obelisk — let Claude Code search its own memory",
"main": "out/main/index.js",
"scripts": {
"start": "electron-vite preview",
"dev": "electron-vite dev",
"build": "electron-vite build && electron-builder",
"pack": "electron-vite build && electron-builder --dir",
"dist": "electron-vite build && electron-builder --mac --win --linux",
"dist:mac": "electron-vite build && electron-builder --mac",
"dist:win": "electron-vite build && electron-builder --win",
"dist:linux": "electron-vite build && electron-builder --linux",
"test:electron": "electron-vite build && electron --no-sandbox tests/electron-concurrency.mjs"
},
"build": {
"appId": "com.obelisk.app",
"productName": "Obelisk",
"artifactName": "${productName}-${version}-${os}-${arch}.${ext}",
"directories": {
"output": "release"
},
"mac": {
"icon": "build/icon.icns",
"target": [
"dmg",
"zip"
],
"category": "public.app-category.developer-tools"
},
"win": {
"target": [
"nsis",
"portable"
]
},
"linux": {
"icon": "build/icon.png",
"target": [
"AppImage",
"deb"
],
"category": "Development"
},
"files": [
"out/**"
],
"asarUnpack": [
"node_modules/better-sqlite3/**/*"
],
"extraResources": [
{
"from": "../scripts/schema.sql",
"to": "scripts/schema.sql"
}
]
},
"dependencies": {
"better-sqlite3": "^11.0.0",
"chokidar": "^4.0.3"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.13",
"@vitejs/plugin-vue": "^5.0.0",
"electron": "^33.0.0",
"electron-builder": "^25.0.0",
"electron-vite": "^5.0.0",
"vite": "^6.0.0",
"vue": "^3.4.0",
"vue-router": "^4.3.0"
},
"type": "module"
}