Skip to content

fix(miner): scope claim-ledger by forge host, not bare repoFullName - #5576

Merged
JSONbored merged 1 commit into
mainfrom
forge-scope-ledger-keys
Jul 13, 2026
Merged

fix(miner): scope claim-ledger by forge host, not bare repoFullName#5576
JSONbored merged 1 commit into
mainfrom
forge-scope-ledger-keys

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

  • claim-ledger.js's UNIQUE(repo_full_name, issue_number) let two forge hosts (github.com vs. a GitHub Enterprise host, De-hardcode discovery from gittensory's own conventions #4784) serving a same-named owner/repo collide in the claim ledger.
  • Rebuilds the constraint to UNIQUE(api_base_url, repo_full_name, issue_number) via a real SQLite table-rebuild migration (schema-version.js's applySchemaMigrations), backfilling existing rows with the pre-De-hardcode discovery from gittensory's own conventions #4784 implicit default https://api.github.com.
  • Threads an optional apiBaseUrl through the store's API (recordClaim/releaseClaim/expireClaim/claimIssue), the admin CLI (--api-base-url on claim/release), and the expiry sweep (echoes each claim's own host back to expireClaim instead of defaulting — otherwise expiry could act on the wrong host's row).
  • releaseClaim/expireClaim now use UPDATE ... RETURNING * (matching portfolio-queue.js's existing pattern) instead of a separate post-UPDATE SELECT, removing a structurally-unreachable defensive branch.
  • Every existing caller is unaffected: apiBaseUrl defaults to https://api.github.com when omitted.

First of 5 stores from #5563 (portfolio-queue.js, governor-state.js's reputation table, run-state.js, deny-hook-synthesis.js follow in separate PRs, given file-count size). Full end-to-end forge-threading through the attempt/loop pipeline (attempt-cli.js, loop-cli.js) remains a distinct, pre-existing gap — none of that pipeline resolves apiBaseUrl for these particular stores today, so it is out of this issue's boundary.

Advances #5563

Test plan

  • npx vitest run test/unit/miner-claim-ledger*.test.ts — 49/49 passing, including a migration test (pre-existing on-disk file upgrades and preserves rows) and cross-host collision regression tests
  • npx tsc --noEmit clean
  • npm run build:miner clean
  • npm run test:coverage (full, unsharded) — 15803/15815 passing (12 pre-existing skips), 100% patch coverage on touched lines/branches
  • npm audit --audit-level=moderate — 0 vulnerabilities

UNIQUE(repo_full_name, issue_number) let two different forge hosts (github.com
vs. a GitHub Enterprise host, #4784) serving a same-named owner/repo collide
in the claim ledger. Rebuild the constraint to UNIQUE(api_base_url,
repo_full_name, issue_number), migrate existing rows via a real table
rebuild backfilling the pre-#4784 implicit default, and thread an optional
apiBaseUrl through the store's API, the CLI (--api-base-url), and the expiry
sweep (echoes each claim's own host instead of defaulting).

Advances #5563 (claim-ledger.js of 5 affected stores).
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
loopover-ui 9a974ae Commit Preview URL

Branch Preview URL
Jul 13 2026, 06:46 AM

@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.99%. Comparing base (738c312) to head (9a974ae).
⚠️ Report is 5 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5576      +/-   ##
==========================================
+ Coverage   94.98%   94.99%   +0.01%     
==========================================
  Files         573      573              
  Lines       45469    45490      +21     
  Branches    14680    14680              
==========================================
+ Hits        43187    43212      +25     
+ Misses       1531     1527       -4     
  Partials      751      751              
Flag Coverage Δ
shard-1 44.02% <39.02%> (-0.02%) ⬇️
shard-2 35.67% <29.26%> (+<0.01%) ⬆️
shard-3 31.94% <97.56%> (-0.17%) ⬇️
shard-4 31.79% <0.00%> (-1.10%) ⬇️
shard-5 32.76% <0.00%> (+0.88%) ⬆️
shard-6 44.56% <43.90%> (+0.38%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
packages/gittensory-miner/lib/claim-ledger-cli.js 99.41% <100.00%> (+0.05%) ⬆️
...ckages/gittensory-miner/lib/claim-ledger-expiry.js 100.00% <100.00%> (ø)
packages/gittensory-miner/lib/claim-ledger.js 100.00% <100.00%> (+4.49%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 13, 2026
@loopover-orb

loopover-orb Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Warning

🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨

⏸️ Gittensory review result - manual review recommended

Review updated: 2026-07-13 06:50:44 UTC

8 files · 2 AI reviewers · 2 blockers · readiness 93/100 · CI green · clean

⏸️ Suggested Action - Manual Review

Review summary
This PR fixes a real cross-host collision bug (#4784) by rebuilding the claim-ledger's UNIQUE constraint to include api_base_url, via a proper SQLite table-rebuild migration with backfill, and threads apiBaseUrl through recordClaim/releaseClaim/expireClaim/claimIssue, the CLI, and the expiry sweep (correctly echoing each claim's own host instead of defaulting). The releaseClaim/expireClaim switch to UPDATE...RETURNING * correctly eliminates a previously unreachable branch, and the migration/backfill/collision scenarios are covered by real regression tests (including an on-disk pre-migration file upgrade test). This is a narrow, well-targeted fix tied to a named issue, not scope creep.

Nits — 5 non-blocking
  • claim-ledger.js:39-42 normalizeApiBaseUrl trims whitespace but doesn't normalize a trailing slash (e.g. 'https:​//ghe.example.com/' vs 'https:​//ghe.example.com'), so two logically-identical hosts could still collide-avoid or fail to collide depending on caller formatting — worth a one-line doc note on the expected canonical form if not normalizing.
  • claim-ledger-cli.js dry-run JSON output for `claim` doesn't echo `apiBaseUrl` in the dry-run result object even though it's now a real input to the eventual write — minor asymmetry versus the real (non-dry-run) path.
  • Consider documenting in normalizeApiBaseUrl's comment that hosts are compared as opaque trimmed strings (no scheme/slash normalization), so future callers don't assume equivalence-checking beyond exact match.
  • In the dry-run branch of runClaimClaim/runClaimRelease (claim-ledger-cli.js), include `apiBaseUrl` in the reported dry-run object for parity with the real write path.
  • nit: `packages/gittensory-miner/lib/claim-ledger-cli.js:151` leaves the human `claim list` table without `apiBaseUrl`, so two same repo/issue rows from different hosts are visually indistinguishable unless the operator uses `--json`.

Concerns raised — review before merging

  • No linked issue detected — If this PR is intended to solve an issue, link it explicitly in the PR body.
  • Maintainer requires a linked issue — Link the relevant issue (for example Closes #123) before opening the PR.
📋 Copy for AI agents — paste into your coding agent
Fix the following blocker(s) from this PR review:

1. No linked issue detected — If this PR is intended to solve an issue, link it explicitly in the PR body.

2. Maintainer requires a linked issue — Link the relevant issue (for example `Closes #123`) before opening the PR.
Signal Result Evidence
Code review ❌ 2 blockers 2 reviewers, synthesized
Linked issue ⚠️ Missing No linked issue or no-issue rationale found.
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (no linked issue context).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 44 registered-repo PR(s), 36 merged, 379 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 44 PR(s), 379 issue(s).
Gate result ❌ Blocking Repo-configured hard blocker found.
Improvement ✅ Minor risk: clean · value: minor — Code changes are accompanied by test evidence. LLM value judgment: moderate — This fixes a real cross-host ledger collision in a narrow store boundary with a migration and regression coverage, while preserving default single-host callers.
Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: not available
  • Official Gittensor activity: 44 PR(s), 379 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Explain no-issue PR.
  • Link the issue being solved, or explicitly explain why this is a no-issue PR.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
[BETA] Chat with Gittensory

Ask 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.

  • @gittensory ask &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @gittensory chat &lt;question&gt; answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @gittensory mention with a real question is routed to the closest matching read-only command automatically -- no exact syntax required.

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.

  • Re-run Gittensory review

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 13, 2026
@JSONbored
JSONbored merged commit f3cbcc0 into main Jul 13, 2026
19 checks passed
@JSONbored
JSONbored deleted the forge-scope-ledger-keys branch July 13, 2026 06:50
JSONbored added a commit that referenced this pull request Jul 13, 2026
PRIMARY KEY (repo_full_name, identifier) let two different forge hosts
(github.com vs. a GitHub Enterprise host, #4784) serving a same-named
owner/repo collide in the portfolio queue. Rebuild the constraint to
PRIMARY KEY (api_base_url, repo_full_name, identifier) as the store's next
schema migration (v2 -> v3, after the existing leased_at migration),
backfilling existing rows with the pre-#4784 implicit default.

Thread an optional apiBaseUrl through the store's API (enqueue/markDone/
markFailed/reclaimStuckItem/requeueItem/batchClaim), the admin CLI
(--api-base-url on done/release/requeue), and the two places that already
had a resolved apiBaseUrl in scope but weren't passing it through:
enqueueRankedDiscovery (discover-cli.js's real discovery pipeline) and the
stuck-lease/malformed-identifier sweeps in portfolio-queue-expiry.js and
loop-cli.js, which now echo each item's own apiBaseUrl back instead of
defaulting -- a defaulted echo would touch the wrong host's row whenever
two hosts share an owner/repo+identifier.

markDone now uses UPDATE ... RETURNING (matching this store's own existing
statements), removing a structurally-unreachable defensive branch the old
separate-SELECT pattern left behind.

Every existing caller is unaffected: apiBaseUrl defaults to
https://api.github.com when omitted. claimNextBatch's engine-driven
selection (portfolio-queue-manager.js) has no forge dimension in
@jsonbored/gittensory-engine's PortfolioQueueItem shape -- documented as a
known, safe (no-collision) limitation rather than silently patched over.

Advances #5563 (portfolio-queue.js of 5 affected stores; claim-ledger.js
landed in #5576).
JSONbored added a commit that referenced this pull request Jul 13, 2026
PRIMARY KEY (repo_full_name, identifier) let two different forge hosts
(github.com vs. a GitHub Enterprise host, #4784) serving a same-named
owner/repo collide in the portfolio queue. Rebuild the constraint to
PRIMARY KEY (api_base_url, repo_full_name, identifier) as the store's next
schema migration (v2 -> v3, after the existing leased_at migration),
backfilling existing rows with the pre-#4784 implicit default.

Thread an optional apiBaseUrl through the store's API (enqueue/markDone/
markFailed/reclaimStuckItem/requeueItem/batchClaim), the admin CLI
(--api-base-url on done/release/requeue), and the two places that already
had a resolved apiBaseUrl in scope but weren't passing it through:
enqueueRankedDiscovery (discover-cli.js's real discovery pipeline) and the
stuck-lease/malformed-identifier sweeps in portfolio-queue-expiry.js and
loop-cli.js, which now echo each item's own apiBaseUrl back instead of
defaulting -- a defaulted echo would touch the wrong host's row whenever
two hosts share an owner/repo+identifier.

markDone now uses UPDATE ... RETURNING (matching this store's own existing
statements), removing a structurally-unreachable defensive branch the old
separate-SELECT pattern left behind.

Every existing caller is unaffected: apiBaseUrl defaults to
https://api.github.com when omitted. claimNextBatch's engine-driven
selection (portfolio-queue-manager.js) has no forge dimension in
@jsonbored/gittensory-engine's PortfolioQueueItem shape -- documented as a
known, safe (no-collision) limitation rather than silently patched over.

Advances #5563 (portfolio-queue.js of 5 affected stores; claim-ledger.js
landed in #5576).
JSONbored added a commit that referenced this pull request Jul 13, 2026
repo_full_name TEXT PRIMARY KEY let two forge hosts (github.com vs. a
GitHub Enterprise host, #4784) serving a same-named owner/repo share one
"current discover/plan/prepare state" row. Rebuild the constraint to
PRIMARY KEY (api_base_url, repo_full_name), backfilling existing rows with
the pre-#4784 implicit default. The migration uses INSERT OR IGNORE so a
pre-existing row with an already-invalid state (this store's read path
already fails closed on those) can't abort the whole rebuild.

Thread an optional apiBaseUrl through the store's API (getRunState/
setRunState) and the admin CLI (--api-base-url on state get/set). Every
existing caller is unaffected: apiBaseUrl defaults to
https://api.github.com when omitted.

manage-status.js's collectRunPortfolio folds run state into one row per
repo NAME (not per host) for its dashboard view -- documented in-code as a
known, safe (no data loss, read-only) display limitation for the narrow
case of the same repo name existing on two hosts, rather than silently
patched over; broadening that fold to be host-aware is a separate,
larger dashboard-shape change.

Advances #5563 (run-state.js of 5 affected stores; claim-ledger.js landed
in #5576, portfolio-queue.js in #5583).
JSONbored added a commit that referenced this pull request Jul 13, 2026
#5583)

PRIMARY KEY (repo_full_name, identifier) let two different forge hosts
(github.com vs. a GitHub Enterprise host, #4784) serving a same-named
owner/repo collide in the portfolio queue. Rebuild the constraint to
PRIMARY KEY (api_base_url, repo_full_name, identifier) as the store's next
schema migration (v2 -> v3, after the existing leased_at migration),
backfilling existing rows with the pre-#4784 implicit default.

Thread an optional apiBaseUrl through the store's API (enqueue/markDone/
markFailed/reclaimStuckItem/requeueItem/batchClaim), the admin CLI
(--api-base-url on done/release/requeue), and the two places that already
had a resolved apiBaseUrl in scope but weren't passing it through:
enqueueRankedDiscovery (discover-cli.js's real discovery pipeline) and the
stuck-lease/malformed-identifier sweeps in portfolio-queue-expiry.js and
loop-cli.js, which now echo each item's own apiBaseUrl back instead of
defaulting -- a defaulted echo would touch the wrong host's row whenever
two hosts share an owner/repo+identifier.

markDone now uses UPDATE ... RETURNING (matching this store's own existing
statements), removing a structurally-unreachable defensive branch the old
separate-SELECT pattern left behind.

Every existing caller is unaffected: apiBaseUrl defaults to
https://api.github.com when omitted. claimNextBatch's engine-driven
selection (portfolio-queue-manager.js) has no forge dimension in
@jsonbored/gittensory-engine's PortfolioQueueItem shape -- documented as a
known, safe (no-collision) limitation rather than silently patched over.

Advances #5563 (portfolio-queue.js of 5 affected stores; claim-ledger.js
landed in #5576).
JSONbored added a commit that referenced this pull request Jul 13, 2026
repo_full_name TEXT PRIMARY KEY let two forge hosts (github.com vs. a
GitHub Enterprise host, #4784) serving a same-named owner/repo share one
"current discover/plan/prepare state" row. Rebuild the constraint to
PRIMARY KEY (api_base_url, repo_full_name), backfilling existing rows with
the pre-#4784 implicit default. The migration uses INSERT OR IGNORE so a
pre-existing row with an already-invalid state (this store's read path
already fails closed on those) can't abort the whole rebuild.

Thread an optional apiBaseUrl through the store's API (getRunState/
setRunState) and the admin CLI (--api-base-url on state get/set). Every
existing caller is unaffected: apiBaseUrl defaults to
https://api.github.com when omitted.

manage-status.js's collectRunPortfolio folds run state into one row per
repo NAME (not per host) for its dashboard view -- documented in-code as a
known, safe (no data loss, read-only) display limitation for the narrow
case of the same repo name existing on two hosts, rather than silently
patched over; broadening that fold to be host-aware is a separate,
larger dashboard-shape change.

Advances #5563 (run-state.js of 5 affected stores; claim-ledger.js landed
in #5576, portfolio-queue.js in #5583).
JSONbored added a commit that referenced this pull request Jul 13, 2026
… repoFullName (#5591)

repo_full_name TEXT PRIMARY KEY let two forge hosts (github.com vs. a
GitHub Enterprise host, #4784) serving a same-named owner/repo share one
reputation-history row. Rebuild the constraint to
PRIMARY KEY (api_base_url, repo_full_name), following governor-state.js's
existing idempotent-ALTER convention (ensurePauseColumns) rather than
introducing the applySchemaMigrations framework into this file for the
first time -- a full PRIMARY KEY rebuild still needs the create-new/copy/
drop/rename dance, just gated by a column-presence check instead of a
version stamp.

Uses INSERT OR IGNORE for the copy step (a corrupted legacy row violating
the rebuilt table's NOT NULL columns is dropped, not a migration-aborting
crash), regression-tested the same way as run-state.js's #5563 fix.

Threads an optional apiBaseUrl through loadReputationHistory/
saveReputationHistory. loadReputationHistory/saveReputationHistory have no
real callers yet (attempt-runner.js's own comments note this governor
input isn't wired into the attempt pipeline yet), so there is no CLI
surface or call site to thread through -- this is purely the storage-layer
fix, mirroring openClaimLedgerReadOnly's scaffold-only precedent.

Advances #5563 (governor_reputation_history of 5 affected stores;
claim-ledger.js landed in #5576, portfolio-queue.js in #5583, run-state.js
in #5585; deny-hook-synthesis.js remains).
JSONbored added a commit that referenced this pull request Jul 13, 2026
…re repoFullName (#5595)

deny_rule_proposals' PRIMARY KEY (repo_full_name, id) let two forge hosts
(github.com vs. a GitHub Enterprise host, #4784) serving a same-named
owner/repo share one proposal row (and its maintainer approval decision).
Rebuild the constraint to PRIMARY KEY (api_base_url, repo_full_name, id).

This file has no schema-version framework of its own (unlike the
package's other local stores) -- it uses a raw DatabaseSync connection, no
applySchemaMigrations. Follows governor-state.js's idempotent
column-presence-gated rebuild convention instead of introducing a new
framework dependency here for the first time. Uses INSERT OR IGNORE for
the copy step, matching the fix already applied to every other #5563
migration in this epic: a legacy row with an already-invalid status value
(this store's own CHECK-constrained schema already rejects those) is
dropped, not a migration-aborting crash.

Threads an optional apiBaseUrl through refreshProposals/listProposals/
setProposalStatus/resolveEffectiveRules. initDenyHookSynthesisStore has no
real callers yet (feeds the consumption surface #2343 will eventually wire
into evaluateDenyHooks; this store owns derivation + audit, not live hook
interception), so there is no CLI surface or call site to thread through --
purely the storage-layer fix, mirroring governor_reputation_history's
scaffold-only precedent.

Closes #5563 (5th and final store: claim-ledger.js in #5576,
portfolio-queue.js in #5583, run-state.js in #5585, governor-state.js in
#5591, plus the claimNextBatch/migration hardening follow-up in #5594).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. manual-review Gittensor contributor context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant