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
2 changes: 2 additions & 0 deletions packages/loopover-miner/lib/migrate-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { initAttemptLog, resolveAttemptLogDbPath } from "./attempt-log.js";
import { openReplaySnapshotStore, resolveReplaySnapshotDbPath } from "./replay-snapshot.js";
import { openWorktreeAllocator, resolveWorktreeAllocatorDbPath } from "./worktree-allocator.js";
import { initContributionProfileCache, resolveContributionProfileCacheDbPath } from "./contribution-profile-cache.js";
import { initPolicyVerdictCacheStore, resolvePolicyVerdictCacheDbPath } from "./policy-verdict-cache.js";

const MIGRATE_USAGE = "Usage: loopover-miner migrate [--json]";

Expand All @@ -39,6 +40,7 @@ const STORES = [
{ name: "replay-snapshot", resolveDbPath: resolveReplaySnapshotDbPath, open: openReplaySnapshotStore },
{ name: "worktree-allocator", resolveDbPath: resolveWorktreeAllocatorDbPath, open: (dbPath) => openWorktreeAllocator({ dbPath }) },
{ name: "contribution-profile", resolveDbPath: resolveContributionProfileCacheDbPath, open: initContributionProfileCache },
{ name: "policy-verdict-cache", resolveDbPath: resolvePolicyVerdictCacheDbPath, open: initPolicyVerdictCacheStore },
];

/** Read a store file's stamped schema version without ever creating it -- matches checkStoreIntegrity's
Expand Down
2 changes: 2 additions & 0 deletions packages/loopover-miner/lib/policy-verdict-cache.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export type PolicyVerdictCacheStore = {
etag: string,
verdict: AiPolicyVerdict,
): PolicyVerdictCacheWrite;
/** Delete every cached verdict row for one repo scope (#6987); returns the number of rows removed. */
purgeByRepo(repoScope: string): number;
close(): void;
};

Expand Down
9 changes: 9 additions & 0 deletions packages/loopover-miner/lib/policy-verdict-cache.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { normalizeLocalStoreDbPath, openLocalStoreDb, resolveLocalStoreDbPath } from "./local-store.js";
import { applySchemaMigrations } from "./schema-version.js";
import { POLICY_VERDICT_CACHE_PURGE_SPEC, purgeStoreByRepo } from "./store-maintenance.js";

// Local cache of resolved AI-usage-policy verdicts (#4843). Even with #4842's conditional-GET doc cache, the small
// but non-zero cost of resolving `resolveAiPolicyVerdict` from raw doc text was still paid on every discover run.
Expand Down Expand Up @@ -99,6 +100,14 @@ export function initPolicyVerdictCacheStore(dbPath = resolvePolicyVerdictCacheDb
putStatement.run(normalizedRepoScope, normalizedDecisiveDoc, normalizedEtag, serializedVerdict, updatedAt);
return { repoScope: normalizedRepoScope, decisiveDoc: normalizedDecisiveDoc, etag: normalizedEtag, verdict, updatedAt };
},
/**
* Delete every cached verdict row for one repo scope (#6987) -- the right-to-be-forgotten path
* `loopover-miner purge` invokes. Returns the number of rows removed. Reuses store-maintenance.js's
* identifier-guarded purgeStoreByRepo, exactly like the other repo-scoped stores.
*/
purgeByRepo(repoScope) {
return purgeStoreByRepo(db, POLICY_VERDICT_CACHE_PURGE_SPEC, normalizeRepoScope(repoScope));
},
close() {
db.close();
},
Expand Down
3 changes: 3 additions & 0 deletions packages/loopover-miner/lib/purge-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { initPortfolioQueueStore, resolvePortfolioQueueDbPath } from "./portfoli
import { initRunStateStore, resolveRunStateDbPath } from "./run-state.js";
import { initContributionProfileCache, resolveContributionProfileCacheDbPath } from "./contribution-profile-cache.js";
import { openGovernorState, resolveGovernorStateDbPath } from "./governor-state.js";
import { initPolicyVerdictCacheStore, resolvePolicyVerdictCacheDbPath } from "./policy-verdict-cache.js";
import { resolveAttemptLogDbPath } from "./attempt-log.js";
import {
CLAIM_LEDGER_PURGE_SPEC,
Expand All @@ -31,6 +32,7 @@ import {
CONTRIBUTION_PROFILE_CACHE_PURGE_SPEC,
GOVERNOR_REPUTATION_HISTORY_PURGE_SPEC,
GOVERNOR_OWN_SUBMISSIONS_PURGE_SPEC,
POLICY_VERDICT_CACHE_PURGE_SPEC,
countStoreByRepo,
describeError,
} from "./store-maintenance.js";
Expand All @@ -52,6 +54,7 @@ const REAL_PURGE_TARGETS = [
// governor-state holds TWO repo-scoped tables in one DB file; its store.purgeByRepo deletes both against a
// single handle (never reopening the file), and its dry-run count sums both via `specs` (#7091).
{ name: "governor-state", optionKey: "openGovernorState", opener: openGovernorState, resolveDbPath: resolveGovernorStateDbPath, specs: [GOVERNOR_REPUTATION_HISTORY_PURGE_SPEC, GOVERNOR_OWN_SUBMISSIONS_PURGE_SPEC] },
{ name: "policy-verdict-cache", optionKey: "initPolicyVerdictCacheStore", opener: initPolicyVerdictCacheStore, resolveDbPath: resolvePolicyVerdictCacheDbPath, spec: POLICY_VERDICT_CACHE_PURGE_SPEC },
];

function parseRepoArg(value, usage) {
Expand Down
2 changes: 2 additions & 0 deletions packages/loopover-miner/lib/status.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { resolveAttemptLogDbPath } from "./attempt-log.js";
import { resolveReplaySnapshotDbPath } from "./replay-snapshot.js";
import { resolveWorktreeAllocatorDbPath } from "./worktree-allocator.js";
import { resolveContributionProfileCacheDbPath } from "./contribution-profile-cache.js";
import { resolvePolicyVerdictCacheDbPath } from "./policy-verdict-cache.js";

// Slim laptop-mode CLI commands (#2288): `status` (what's installed + where local state lives) and `doctor` (is
// this laptop set up correctly). Both are read-only and 100% local — no repo-scanning, no coding-agent invocation,
Expand Down Expand Up @@ -318,6 +319,7 @@ function storeIntegrityChecks(env) {
["replay-snapshot", resolveReplaySnapshotDbPath(env)],
["worktree-allocator", resolveWorktreeAllocatorDbPath(env)],
["contribution-profile", resolveContributionProfileCacheDbPath(env)],
["policy-verdict-cache", resolvePolicyVerdictCacheDbPath(env)],
];
return stores.map(([name, dbPath]) => checkStoreIntegrity(`store-integrity:${name}`, dbPath));
}
Expand Down
1 change: 1 addition & 0 deletions packages/loopover-miner/lib/store-maintenance.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const RUN_STATE_PURGE_SPEC: LedgerPurgeSpec;
export const CONTRIBUTION_PROFILE_CACHE_PURGE_SPEC: LedgerPurgeSpec;
export const GOVERNOR_REPUTATION_HISTORY_PURGE_SPEC: LedgerPurgeSpec;
export const GOVERNOR_OWN_SUBMISSIONS_PURGE_SPEC: LedgerPurgeSpec;
export const POLICY_VERDICT_CACHE_PURGE_SPEC: LedgerPurgeSpec;

export type StoreIntegrityResult = { name: string; ok: boolean; detail: string };
export type LedgerRetentionPolicy = { maxAgeMs?: number; maxRows?: number };
Expand Down
6 changes: 6 additions & 0 deletions packages/loopover-miner/lib/store-maintenance.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ export const CONTRIBUTION_PROFILE_CACHE_PURGE_SPEC = { table: CONTRIBUTION_PROFI
export const GOVERNOR_REPUTATION_HISTORY_PURGE_SPEC = { table: "governor_reputation_history", repoColumn: "repo_full_name" };
export const GOVERNOR_OWN_SUBMISSIONS_PURGE_SPEC = { table: "governor_own_submissions", repoColumn: "repo_full_name" };

/** policy-verdict-cache (#6987), another repo-scoped store the earlier sweeps missed. Its `repo_scope TEXT
* PRIMARY KEY` is the per-repo column (a tenant forge host + `owner/repo`), the same `repoColumn` shape and
* internal-constant-only discipline as the specs above. `policy-doc-cache.js` stays out (keyed by URL, no repo
* column, exactly like `attempt-log.js`). */
export const POLICY_VERDICT_CACHE_PURGE_SPEC = { table: "policy_verdict_cache", repoColumn: "repo_scope" };

const SQL_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;

/** A readable message for a caught value, whether or not it is an Error. */
Expand Down
3 changes: 2 additions & 1 deletion test/unit/miner-migrate-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const STORE_NAMES = [
"replay-snapshot",
"worktree-allocator",
"contribution-profile",
"policy-verdict-cache",
];

afterEach(() => {
Expand All @@ -38,7 +39,7 @@ afterEach(() => {
});

describe("loopover-miner migrate (#4871)", () => {
it("covers the exact same twelve stores doctor's store-integrity sweep covers, in the same order, and skips every one when nothing has been created yet", () => {
it("covers the exact same thirteen stores doctor's store-integrity sweep covers, in the same order, and skips every one when nothing has been created yet", () => {
const env = tempEnv();
const results = runMigrateChecks(env);

Expand Down
13 changes: 13 additions & 0 deletions test/unit/miner-policy-verdict-cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,17 @@ describe("loopover-miner policy-verdict cache store (#4843)", () => {
it("throws on an empty explicit db path", () => {
expect(() => initPolicyVerdictCacheStore("")).toThrow("invalid_policy_verdict_cache_db_path");
});

it("purgeByRepo deletes only the given repo scope's row and returns the count (#6987)", () => {
const store = openStore();
store.put("acme/widgets", "AI-USAGE.md", '"v1"', VERDICT);
store.put("acme/other", "AI-USAGE.md", '"v2"', VERDICT);
expect(store.purgeByRepo("acme/widgets")).toBe(1);
expect(store.get("acme/widgets")).toBeNull();
expect(store.get("acme/other")).not.toBeNull();
});

it("purgeByRepo returns 0 when the repo scope has no cached verdict (#6987)", () => {
expect(openStore().purgeByRepo("acme/widgets")).toBe(0);
});
});
Loading