Skip to content
Draft
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
32 changes: 32 additions & 0 deletions convex/securityScan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4223,6 +4223,33 @@ describe("securityScan", () => {
expect(claimed.map((job) => job._id)).toEqual(["securityScanJobs:publish"]);
});

it.each([1, 4, 16])(
"reads only the needed native queue rows for a %i-job claim with GitHub rollout off",
async (limit) => {
vi.stubEnv("CLAWHUB_GITHUB_SKILL_SYNC_ROLLOUT_MODE", "off");
const jobs = Array.from({ length: 512 }, (_, index) =>
makeScanJob({
_id: `securityScanJobs:bulk-${index}`,
source: "bulk-rescan",
createdAt: index + 1,
nextRunAt: index + 1,
}),
);
const { ctx } = makeClaimCtx(jobs);

const claimed = await claimQueuedJobsInternalHandler(ctx, {
workerId: "shared-worker",
lane: "shared",
limit,
leaseMs: 60_000,
});

expect(claimed.map((job) => job._id)).toEqual(jobs.slice(0, limit).map((job) => job._id));
const pages = await Promise.all(ctx.runQuery.mock.results.map((result) => result.value));
expect(pages.reduce((total, page) => total + page.page.length, 0)).toBe(limit);
},
);

it("skips queued generic GitHub scans while still claiming NVIDIA scans when rollout is off", async () => {
vi.stubEnv("CLAWHUB_GITHUB_SKILL_SYNC_ROLLOUT_MODE", "off");
const genericJobs = Array.from({ length: 513 }, (_, index) =>
Expand Down Expand Up @@ -4290,6 +4317,11 @@ describe("securityScan", () => {
"securityScanJobs:nvidia",
"skillScanRequests:nvidia",
]);
// Sparse legacy jobs must not turn a one-job claim into hundreds of queries.
const publishPages = ctx.runQuery.mock.calls.filter(
([, args]) => (args as { source: string }).source === "publish",
);
expect(publishPages.length).toBeLessThanOrEqual(3);
});

it("lets the catalog lane claim only the lowest-priority catalog source", async () => {
Expand Down
13 changes: 9 additions & 4 deletions convex/securityScan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3105,6 +3105,7 @@ export const claimQueuedJobsInternal = internalMutation({
}
const eligible: Doc<"securityScanJobs">[] = [];
let cursor: string | null = null;
let pageSize = Math.min(takeLimit, MAX_CODEX_SCAN_CLAIM_LIMIT);
do {
const page: ReadySourceJobsForClaimPage = await runQueryRef<ReadySourceJobsForClaimPage>(
ctx,
Expand All @@ -3113,14 +3114,18 @@ export const claimQueuedJobsInternal = internalMutation({
source,
now,
cursor,
numItems: githubSkillSyncEnabled
? Math.min(takeLimit, MAX_CODEX_SCAN_CLAIM_LIMIT)
: MAX_CODEX_SCAN_CLAIM_LIMIT,
numItems: pageSize,
excludeGitHubSkillSync: !githubSkillSyncEnabled,
},
);
for (const job of page.page) {
if (await isJobRolloutClaimable(job)) eligible.push(job);
if (await isJobRolloutClaimable(job)) {
eligible.push(job);
} else {
// Native claims should not read the whole queue. Expand only after
// blocked legacy GitHub jobs require scanning past the first page.
pageSize = MAX_CODEX_SCAN_CLAIM_LIMIT;
}
if (eligible.length >= takeLimit) return eligible;
}
cursor = page.isDone ? null : page.continueCursor;
Expand Down
4 changes: 4 additions & 0 deletions specs/security-moderation.md
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,10 @@ See also: [acceptable-usage.md](./acceptable-usage.md) for the marketplace polic
backend claim path must cap only a single worker claim size and must not impose
a global active-scan ceiling; horizontal capacity is controlled by worker
dispatch count, worker batch limit, provider quotas, and cost monitoring.
- Normal scan claims read only enough ready queue rows to fill the worker's
remaining capacity. Broader pagination is reserved for skipping blocked legacy
GitHub jobs or the catalog lane's bounded admission window; disabling a rollout
must not make every native one-job claim read hundreds of unrelated jobs.
- The Skill Card verification envelope exposes ClawScan as the top-level
`security` verdict for install automation, with deterministic and third-party
scanner evidence grouped under `security.signals`. Clients should key install
Expand Down
Loading