Context
packages/loopover-miner/lib/local-store.ts's resolveLocalStoreDbPath(defaultDbFileName, explicitEnvVarName, env) is the shared path-resolution helper every local store's resolve*DbPath function is meant to call — precedence: an explicit per-store env var, then LOOPOVER_MINER_CONFIG_DIR, then XDG_CONFIG_HOME (falling back to ~/.config), joined with loopover-miner. Closed issue #7083 fixed this exact duplication for governor-ledger.js, prediction-ledger.js, and plan-store.js; every store this repo added afterward (e.g. prediction-ledger.ts's resolvePredictionLedgerDbPath) correctly delegates in one line: return resolveLocalStoreDbPath(defaultDbFileName, "LOOPOVER_MINER_PREDICTION_LEDGER_DB", env);.
Three stores still hand-duplicate the same ~10-line precedence chain by hand instead of delegating:
packages/loopover-miner/lib/orb-export.ts's resolveOrbExportDbPath — hand-rolls the identical explicit-env-var / LOOPOVER_MINER_CONFIG_DIR / XDG_CONFIG_HOME chain.
packages/loopover-miner/lib/deny-hook-synthesis.ts's resolveDenyHookSynthesisDbPath — the same duplication, checking LOOPOVER_MINER_DENY_HOOK_SYNTHESIS_DB.
packages/loopover-miner/lib/laptop-init.ts's resolveLaptopStateDbPath (via its own private resolveMinerStateDir) — the same LOOPOVER_MINER_CONFIG_DIR/XDG_CONFIG_HOME chain, but with no explicit per-store env var override at all, unlike every other store.
laptop-init.ts's resolveMinerStateDir doc comment says it "mirrors resolveMinerStateDir in status.js — kept local to avoid import cycles"; that stated reason is about avoiding an import of status.js (a real concern, since status.js likely imports many stores), not about local-store.js — local-store.ts itself imports only node:fs/node:os/node:path/node:sqlite, process-lifecycle.js, and store-db-adapter.js, none of which import laptop-init.ts, so importing resolveLocalStoreDbPath from local-store.js directly is not subject to the cycle the comment warns about.
This issue is scoped to path-RESOLUTION duplication only. It is deliberately separate from (and has no ordering dependency on) the sibling issue covering these same three files' DB-OPEN boilerplate bypassing local-store.js's crash-safe openLocalStoreDb — that is a different function, a different bug, and can be fixed independently in either order.
Requirements
⚠️ Required pattern. Delegate to resolveLocalStoreDbPath exactly the way prediction-ledger.ts's resolvePredictionLedgerDbPath already does — a one-line body, defaultDbFileName and an explicit per-store env-var name as the two literal arguments.
orb-export.ts's resolveOrbExportDbPath becomes return resolveLocalStoreDbPath(defaultDbFileName, "LOOPOVER_MINER_ORB_EXPORT_DB", env); (the env var name is unchanged — orb-export.ts already reads LOOPOVER_MINER_ORB_EXPORT_DB today, so this is a pure refactor with byte-identical resolved paths for every existing caller).
deny-hook-synthesis.ts's resolveDenyHookSynthesisDbPath becomes the equivalent one-line delegation using its existing LOOPOVER_MINER_DENY_HOOK_SYNTHESIS_DB env var name — also a pure refactor, byte-identical resolved paths.
laptop-init.ts's resolveLaptopStateDbPath becomes return resolveLocalStoreDbPath(defaultDbFileName, "LOOPOVER_MINER_LAPTOP_STATE_DB", env);, importing resolveLocalStoreDbPath from ./local-store.js. This is not a pure refactor: it adds a new explicit per-store env var override (LOOPOVER_MINER_LAPTOP_STATE_DB) that did not exist before, matching every sibling store's own convention. When that env var is unset (the common case today), the resolved path is unchanged. Do not remove or alter resolveMinerStateDir's private LOOPOVER_MINER_CONFIG_DIR/XDG_CONFIG_HOME duplication for OTHER callers in laptop-init.ts that are not path-resolution for the SQLite file (if any exist) — this issue is scoped to resolveLaptopStateDbPath only.
- Regenerate
packages/loopover-miner/docs/env-reference.md with npm run miner:env-reference (the miner package's own generator, packages/loopover-miner/scripts/generate-env-reference.mjs — distinct from the top-level npm run selfhost:env-reference, which covers src/selfhost/** only). The doc currently lists LOOPOVER_MINER_ORB_EXPORT_DB/LOOPOVER_MINER_DENY_HOOK_SYNTHESIS_DB with a default of "" (picked up from their current direct env.NAME access); already-delegating stores like LOOPOVER_MINER_PREDICTION_LEDGER_DB/LOOPOVER_MINER_PLAN_STORE_DB instead show (none), since the generator's default-value extraction only recognizes the direct env.NAME ?? "default"/env.NAME.trim() : "" shapes, not a value routed through resolveLocalStoreDbPath's parameter. Expect the regenerated doc's default column for both entries to change from "" to (none) to match every other delegating store, plus a new LOOPOVER_MINER_LAPTOP_STATE_DB row. Commit the regenerated file — npm run miner:env-reference:check (part of test:ci) fails on drift.
Deliverables
Test Coverage Requirements
packages/loopover-miner/** is not Codecov-gated, but npm run test:ci must stay green. Existing path-resolution tests in test/unit/miner-orb-export.test.ts, the deny-hook-synthesis test file, and test/unit/miner-laptop-init.test.ts must keep passing unmodified for the default (no env override) case; add a test asserting LOOPOVER_MINER_LAPTOP_STATE_DB now overrides resolveLaptopStateDbPath's resolved path (the new behavior), and assert orb-export/deny-hook-synthesis's resolved paths are byte-identical before and after the refactor for a representative set of env combinations (explicit var set, only LOOPOVER_MINER_CONFIG_DIR set, only XDG_CONFIG_HOME set, neither set).
Expected Outcome
All local stores in this package resolve their DB path through one shared, single-source-of-truth helper, and laptop-state.sqlite3 gains the same explicit-env-var override every sibling store already supports.
Links & Resources
Context
packages/loopover-miner/lib/local-store.ts'sresolveLocalStoreDbPath(defaultDbFileName, explicitEnvVarName, env)is the shared path-resolution helper every local store'sresolve*DbPathfunction is meant to call — precedence: an explicit per-store env var, thenLOOPOVER_MINER_CONFIG_DIR, thenXDG_CONFIG_HOME(falling back to~/.config), joined withloopover-miner. Closed issue #7083 fixed this exact duplication forgovernor-ledger.js,prediction-ledger.js, andplan-store.js; every store this repo added afterward (e.g.prediction-ledger.ts'sresolvePredictionLedgerDbPath) correctly delegates in one line:return resolveLocalStoreDbPath(defaultDbFileName, "LOOPOVER_MINER_PREDICTION_LEDGER_DB", env);.Three stores still hand-duplicate the same ~10-line precedence chain by hand instead of delegating:
packages/loopover-miner/lib/orb-export.ts'sresolveOrbExportDbPath— hand-rolls the identical explicit-env-var /LOOPOVER_MINER_CONFIG_DIR/XDG_CONFIG_HOMEchain.packages/loopover-miner/lib/deny-hook-synthesis.ts'sresolveDenyHookSynthesisDbPath— the same duplication, checkingLOOPOVER_MINER_DENY_HOOK_SYNTHESIS_DB.packages/loopover-miner/lib/laptop-init.ts'sresolveLaptopStateDbPath(via its own privateresolveMinerStateDir) — the sameLOOPOVER_MINER_CONFIG_DIR/XDG_CONFIG_HOMEchain, but with no explicit per-store env var override at all, unlike every other store.laptop-init.ts'sresolveMinerStateDirdoc comment says it "mirrorsresolveMinerStateDirin status.js — kept local to avoid import cycles"; that stated reason is about avoiding an import ofstatus.js(a real concern, sincestatus.jslikely imports many stores), not aboutlocal-store.js—local-store.tsitself imports onlynode:fs/node:os/node:path/node:sqlite,process-lifecycle.js, andstore-db-adapter.js, none of which importlaptop-init.ts, so importingresolveLocalStoreDbPathfromlocal-store.jsdirectly is not subject to the cycle the comment warns about.This issue is scoped to path-RESOLUTION duplication only. It is deliberately separate from (and has no ordering dependency on) the sibling issue covering these same three files' DB-OPEN boilerplate bypassing
local-store.js's crash-safeopenLocalStoreDb— that is a different function, a different bug, and can be fixed independently in either order.Requirements
orb-export.ts'sresolveOrbExportDbPathbecomesreturn resolveLocalStoreDbPath(defaultDbFileName, "LOOPOVER_MINER_ORB_EXPORT_DB", env);(the env var name is unchanged —orb-export.tsalready readsLOOPOVER_MINER_ORB_EXPORT_DBtoday, so this is a pure refactor with byte-identical resolved paths for every existing caller).deny-hook-synthesis.ts'sresolveDenyHookSynthesisDbPathbecomes the equivalent one-line delegation using its existingLOOPOVER_MINER_DENY_HOOK_SYNTHESIS_DBenv var name — also a pure refactor, byte-identical resolved paths.laptop-init.ts'sresolveLaptopStateDbPathbecomesreturn resolveLocalStoreDbPath(defaultDbFileName, "LOOPOVER_MINER_LAPTOP_STATE_DB", env);, importingresolveLocalStoreDbPathfrom./local-store.js. This is not a pure refactor: it adds a new explicit per-store env var override (LOOPOVER_MINER_LAPTOP_STATE_DB) that did not exist before, matching every sibling store's own convention. When that env var is unset (the common case today), the resolved path is unchanged. Do not remove or alterresolveMinerStateDir's privateLOOPOVER_MINER_CONFIG_DIR/XDG_CONFIG_HOMEduplication for OTHER callers inlaptop-init.tsthat are not path-resolution for the SQLite file (if any exist) — this issue is scoped toresolveLaptopStateDbPathonly.packages/loopover-miner/docs/env-reference.mdwithnpm run miner:env-reference(the miner package's own generator,packages/loopover-miner/scripts/generate-env-reference.mjs— distinct from the top-levelnpm run selfhost:env-reference, which coverssrc/selfhost/**only). The doc currently listsLOOPOVER_MINER_ORB_EXPORT_DB/LOOPOVER_MINER_DENY_HOOK_SYNTHESIS_DBwith a default of""(picked up from their current directenv.NAMEaccess); already-delegating stores likeLOOPOVER_MINER_PREDICTION_LEDGER_DB/LOOPOVER_MINER_PLAN_STORE_DBinstead show(none), since the generator's default-value extraction only recognizes the directenv.NAME ?? "default"/env.NAME.trim() : ""shapes, not a value routed throughresolveLocalStoreDbPath's parameter. Expect the regenerated doc's default column for both entries to change from""to(none)to match every other delegating store, plus a newLOOPOVER_MINER_LAPTOP_STATE_DBrow. Commit the regenerated file —npm run miner:env-reference:check(part oftest:ci) fails on drift.Deliverables
orb-export.ts,deny-hook-synthesis.ts,laptop-init.tsall delegate theirresolve*DbPathtoresolveLocalStoreDbPath.docs/env-reference.mdregenerated if applicable (see Requirements).Test Coverage Requirements
packages/loopover-miner/**is not Codecov-gated, butnpm run test:cimust stay green. Existing path-resolution tests intest/unit/miner-orb-export.test.ts, the deny-hook-synthesis test file, andtest/unit/miner-laptop-init.test.tsmust keep passing unmodified for the default (no env override) case; add a test assertingLOOPOVER_MINER_LAPTOP_STATE_DBnow overridesresolveLaptopStateDbPath's resolved path (the new behavior), and assertorb-export/deny-hook-synthesis's resolved paths are byte-identical before and after the refactor for a representative set of env combinations (explicit var set, onlyLOOPOVER_MINER_CONFIG_DIRset, onlyXDG_CONFIG_HOMEset, neither set).Expected Outcome
All local stores in this package resolve their DB path through one shared, single-source-of-truth helper, and
laptop-state.sqlite3gains the same explicit-env-var override every sibling store already supports.Links & Resources
packages/loopover-miner/lib/local-store.ts(resolveLocalStoreDbPath)packages/loopover-miner/lib/orb-export.ts,lib/deny-hook-synthesis.ts,lib/laptop-init.ts(the files to change)packages/loopover-miner/lib/prediction-ledger.ts(resolvePredictionLedgerDbPath— the pattern to mirror)