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>
75 lines
1.9 KiB
JSON
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"
|
|
}
|