Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/gittensory-miner/lib/claim-ledger.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ export type ListClaimsFilter = {
export type ClaimLedger = {
dbPath: string;
recordClaim(claim: RecordClaimInput): ClaimEntry;
claimIssue(repoFullName: string, issueNumber: number, note?: string): ClaimEntry;
releaseClaim(repoFullName: string, issueNumber: number): ClaimEntry | null;
expireClaim(repoFullName: string, issueNumber: number): ClaimEntry | null;
listClaims(filter?: ListClaimsFilter): ClaimEntry[];
listActiveClaims(repoFullName?: string): ClaimEntry[];
close(): void;
};

Expand All @@ -43,4 +45,8 @@ export function expireClaim(repoFullName: string, issueNumber: number): ClaimEnt

export function listClaims(filter?: ListClaimsFilter): ClaimEntry[];

export function claimIssue(repoFullName: string, issueNumber: number, note?: string): ClaimEntry;

export function listActiveClaims(repoFullName?: string): ClaimEntry[];

export function closeDefaultClaimLedger(): void;
21 changes: 20 additions & 1 deletion packages/gittensory-miner/lib/claim-ledger.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export function openClaimLedger(dbPath = resolveClaimLedgerDbPath()) {
return status;
}

return {
const ledger = {
dbPath: resolvedPath,
recordClaim(claim) {
const repoFullName = normalizeRepoFullName(claim?.repoFullName);
Expand Down Expand Up @@ -178,10 +178,19 @@ export function openClaimLedger(dbPath = resolveClaimLedgerDbPath()) {
}
return rows.map(rowToClaim);
},
claimIssue(repoFullName, issueNumber, note) {
return ledger.recordClaim({ repoFullName, issueNumber, note });
},
listActiveClaims(repoFullName) {
const filter = { status: "active" };
if (repoFullName !== undefined) filter.repoFullName = repoFullName;
return ledger.listClaims(filter);
},
close() {
db.close();
},
};
return ledger;
}

function getDefaultClaimLedger() {
Expand All @@ -205,6 +214,16 @@ export function listClaims(filter) {
return getDefaultClaimLedger().listClaims(filter);
}

/** Foundation-phase alias for `recordClaim({ repoFullName, issueNumber, note })`. (#3351) */
export function claimIssue(repoFullName, issueNumber, note) {
return getDefaultClaimLedger().claimIssue(repoFullName, issueNumber, note);
}

/** List only `active` claims, optionally scoped to one repo. (#3351) */
export function listActiveClaims(repoFullName) {
return getDefaultClaimLedger().listActiveClaims(repoFullName);
}

export function closeDefaultClaimLedger() {
if (!defaultClaimLedger) return;
defaultClaimLedger.close();
Expand Down
45 changes: 45 additions & 0 deletions test/unit/miner-claim-ledger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { join } from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
CLAIM_STATUSES,
claimIssue,
closeDefaultClaimLedger,
listActiveClaims,
openClaimLedger,
resolveClaimLedgerDbPath,
} from "../../packages/gittensory-miner/lib/claim-ledger.js";
Expand All @@ -23,6 +25,8 @@ function tempLedger() {
afterEach(() => {
for (const ledger of ledgers.splice(0)) ledger.close();
closeDefaultClaimLedger();
vi.restoreAllMocks();
vi.unstubAllEnvs();
vi.useRealTimers();
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
});
Expand Down Expand Up @@ -136,4 +140,45 @@ describe("gittensory-miner claim ledger (#2314)", () => {
expect(source).toContain("does NOT adjudicate contested duplicates");
expect(source).toContain("isDuplicateClusterWinnerByClaim");
});

it("claimIssue and listActiveClaims expose the foundation-phase API surface (#3351)", () => {
const ledger = tempLedger();
const claim = ledger.claimIssue("o/a", 42, "via-alias");
expect(claim).toMatchObject({ repoFullName: "o/a", issueNumber: 42, status: "active", note: "via-alias" });
expect(ledger.listActiveClaims()).toEqual([claim]);
ledger.claimIssue("o/b", 1);
ledger.releaseClaim("o/a", 42);
expect(ledger.listActiveClaims("o/a")).toEqual([]);
expect(ledger.listActiveClaims("o/b").map((c) => c.issueNumber)).toEqual([1]);
expect(ledger.listActiveClaims().map((c) => c.repoFullName)).toEqual(["o/b"]);
expect(ledger.claimIssue("o/c", 3).note).toBeNull();
});

it("claimIssue on an already-active claim is idempotent (#3353)", () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-07-05T00:00:00Z"));
const ledger = tempLedger();
const first = ledger.claimIssue("o/a", 7, "first");
vi.setSystemTime(new Date("2026-07-05T01:00:00Z"));
const second = ledger.claimIssue("o/a", 7, "ignored");
expect(second).toEqual(first);
expect(ledger.listActiveClaims()).toHaveLength(1);
});

it("top-level claimIssue and listActiveClaims use the default ledger store", () => {
const root = tempRoot();
vi.stubEnv("GITTENSORY_MINER_CLAIM_LEDGER_DB", join(root, "claim-ledger.sqlite3"));
closeDefaultClaimLedger();
const claim = claimIssue("o/a", 99, "default-store");
expect(claim).toMatchObject({ issueNumber: 99, status: "active" });
expect(listActiveClaims()).toEqual([claim]);
expect(listActiveClaims("o/a")).toEqual([claim]);
expect(listActiveClaims("o/missing")).toEqual([]);
});
});

function tempRoot() {
const root = mkdtempSync(join(tmpdir(), "gittensory-miner-claim-default-"));
roots.push(root);
return root;
}
Loading