feat(mcp): wire gittensory_feasibility_gate's claimStatus to the local claim ledger (#5157)#5389
Conversation
…l claim ledger (JSONbored#5157) gittensory_feasibility_gate took claimStatus as a purely caller-supplied string, never actually connected to real local claim state, even though packages/gittensory-miner/lib/claim-ledger.js already tracks it. Adds optional repoFullName/issueNumber inputs. When both are supplied and a local gittensory-miner install's claim ledger DB file exists, claimStatus is now read from that ledger (an active claim on the exact issue -> "claimed", otherwise "unclaimed") instead of trusting the caller-supplied value. The DB file's existence is checked BEFORE opening anything, so this advisory-only tool never creates the ledger as a side effect -- it stays strictly read-only, never calls recordClaim/releaseClaim/expireClaim, and never gains any ability to block, cancel, or override a claim or attempt; real claim-conflict authority remains entirely with the maintainer-only path. Falls back to today's caller-supplied-string behavior unchanged when repo/issue are omitted, the ledger file doesn't exist, or gittensory-miner isn't resolvable at all. The feasibility calculator's own decision logic is untouched -- this only changes where claimStatus is sourced from.
…ad of silently trusting the caller (JSONbored#5157) resolveLedgerClaimStatus's catch block previously swallowed every error from opening/querying the ledger, not just "package not found" -- so a real local install whose ledger DB file exists but is corrupt, locked, or unreadable would silently fall back to a caller-supplied claimStatus that might contradict the (unreadable) ground truth. Narrows the fallback: module-resolution failure or a missing DB file still return null (fall back to the caller-supplied value -- correct, since there's genuinely nothing local to check). A DB file that exists but fails to open/query now returns the existing "unknown" claimStatus value instead, which the calculator already treats as a neutral signal -- honest about the read failure rather than guessing.
…writable openClaimLedger (JSONbored#5157) resolveLedgerClaimStatus opened the claim ledger via openClaimLedger, which always runs CREATE TABLE IF NOT EXISTS plus a schema-version stamp on open -- a write, even against a file that merely exists but is empty/uninitialized. That contradicted this advisory-only tool's strictly-read-only guarantee: existsSync only confirms a file is there, not that its schema has already been created. Adds openClaimLedgerReadOnly to claim-ledger.js: opens the DB with node:sqlite's own `readOnly: true` mode (verified empirically that the lowercase `readonly` key is silently ignored and opens read-write anyway -- a real footgun, now called out in a comment) and touches the filesystem in no other way -- no mkdir/chmod, no CREATE TABLE, no migrations. This keeps the schema/query logic centralized in claim-ledger.js while getting a guarantee enforced by the SQLite driver itself, not just by which methods happen to get called. gittensory-mcp.js's resolveLedgerClaimStatus now uses this instead of openClaimLedger.
…t unit coverage for openClaimLedgerReadOnly (JSONbored#5157) openClaimLedgerReadOnly's db.prepare() can throw when the expected table is missing (a file exists but isn't a real claim ledger) -- before this fix, that left the already-opened DatabaseSync handle unclosed, leaking a file handle every time this path was hit. Also adds direct unit tests for openClaimLedgerReadOnly in the same test process (the prior tests only exercised it indirectly through a spawned MCP subprocess, which coverage instrumentation can't see) so Codecov's patch-coverage gate can actually measure this new function: listing active claims, scoping by repo, an empty-result case, malformed-input rejection, opening a nonexistent path, opening an existing-but-empty file (the resource-leak repro), and a regression pinning the exact readOnly-vs-readonly key gotcha the read-only guarantee depends on.
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5389 +/- ##
=======================================
Coverage 94.43% 94.44%
=======================================
Files 551 551
Lines 44223 44233 +10
Branches 14654 14654
=======================================
+ Hits 41764 41774 +10
Misses 1784 1784
Partials 675 675
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
Tip 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 ✅ Gittensory review result - approve/merge recommendedReview updated: 2026-07-12 16:17:51 UTC
✅ Suggested Action - Approve/Merge
Review summary Nits — 5 non-blocking
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
[BETA] Chat with GittensoryAsk Gittensory a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://gittensory.aethereal.dev/docs/gittensory-commands 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
Summary
gittensory_feasibility_gateinpackages/gittensory-mcp/bin/gittensory-mcp.jstookclaimStatusas a purely caller-supplied string -- the pure feasibility calculator was never actually connected to real local claim state, even thoughpackages/gittensory-miner/lib/claim-ledger.jsalready tracks it.repoFullName/issueNumberinputs tofeasibilityGateShape. When both are supplied AND a localgittensory-minerinstall's claim ledger DB file exists,claimStatusis now read from that ledger (an active claim on the exact issue ->"claimed", otherwise"unclaimed") instead of trusting the caller-supplied value.openClaimLedgerReadOnlytoclaim-ledger.js, which opens the DB withnode:sqlite's ownreadOnly: truemode and touches the filesystem in no other way (nomkdirSync/chmodSync, noCREATE TABLE IF NOT EXISTS, no schema migrations). Never callsrecordClaim/releaseClaim/expireClaim, and never gains any ability to block, cancel, or override a claim or attempt -- real claim-conflict authority stays entirely with the maintainer-only path (Wire claim-conflict resolution end-to-end #4848).repoFullName/issueNumberare omitted, the ledger DB file doesn't exist, or@jsonbored/gittensory-minerisn't resolvable at all.claimStatus: "unknown"value instead of silently trusting a caller-supplied string that might contradict ground truth we can't currently read.buildFeasibilityVerdict) is completely untouched.Resubmission history
Fourth submission for #5157, each round fixing a real, distinct finding from the review gate rather than resubmitting unchanged:
catchswallowed every ledger error, not just "package not found" -- fixed by returning"unknown"for an existing-but-unreadable ledger.resolveLedgerClaimStatusstill opened via the writableopenClaimLedger(schema-init DDL on every open -- a write, even against an empty file) -- fixed by adding a genuinely read-only opener. Also caught and fixed anode:sqlitefootgun in the process: the lowercasereadonlyoption key is silently ignored; only camelCasereadOnlyactually enforces it.codecov/patchfailed at 0% --packages/gittensory-miner/**turned out to now be inside Codecov'scoverage.includescope (a recent milestone change), and my newopenClaimLedgerReadOnlywas only exercised indirectly through a spawned MCP subprocess, which coverage instrumentation can't see. Fixed by adding direct unit tests for it in the same test process. Writing those tests surfaced one more real bug in my own code:openClaimLedgerReadOnly'sdb.prepare()throws when the expected table is missing, and the already-openedDatabaseSynchandle was never closed on that path -- a file-handle leak. Fixed with a try/catch that closes the connection before rethrowing, with a regression test (an existing-but-empty SQLite file) that reproduced it.Test plan
npx vitest run test/unit/mcp-feasibility-gate.test.ts-- 14/14 passing (E2E stdio-subprocess tests).npx vitest run test/unit/miner-claim-ledger.test.ts-- 33/35 passing; 2 pre-existing, unrelated Windows-only failures (apath.joinseparator mismatch and a POSIX-permission-bit check that doesn't apply on NTFS), confirmed viagit stashto exist identically without this PR. Newdescribe("openClaimLedgerReadOnly (#5157)", ...)block directly unit-tests: listing active claims scoped by repo, an empty-result case, malformed-input rejection, opening a nonexistent path, a regression pinning the exactreadOnlyvs.readonlykey gotcha (asserts a write against a{readOnly: true}connection throws), and a regression for the file-handle leak (an existing-but-empty SQLite file gains zero tables and the process doesn't error on cleanup).npm run typecheck-- clean.npm run build:mcp/npm run build:miner-- clean.npm run docs:drift-check-- clean.openClaimLedgerReadOnlyfunction is 100% within the full (minus the 2 known pre-existing failures)miner-claim-ledger.test.tsrun -- the only uncovered lines belong to pre-existing, untouched code (expireClaimand unused top-level ledger aliases).npm run test:coveragelocally (shared/resource-contended machine); relying on the targeted test runs above plus the isolated-coverage check.Fixes #5157.