diff --git a/scripts/crabbox-staging-claims.mts b/scripts/crabbox-staging-claims.mts index 710fc31a65d6..77f67c582ff7 100644 --- a/scripts/crabbox-staging-claims.mts +++ b/scripts/crabbox-staging-claims.mts @@ -30,7 +30,9 @@ const inventorySchema = z.object({ .array( z.object({ leaseId: z.string().min(1).max(512), - repoRoot: pathSchema, + // Crabbox records provider resources not yet attached to a repository + // with an empty root, and never clears an attached root without reclaim. + repoRoot: z.union([z.literal(""), pathSchema]), }), ) .max(10_000), @@ -304,7 +306,7 @@ export async function verifyNoStagingClaims(params: { if (Date.now() >= deadline) { throw new ClaimInventoryHold("Native claim inventory exceeded its inspection budget."); } - if (within(sourceRoot, directoryLocation(claim.repoRoot).directory)) { + if (claim.repoRoot && within(sourceRoot, directoryLocation(claim.repoRoot).directory)) { matches.add(claim.leaseId); if (matches.size === 16) { break; diff --git a/test/scripts/crabbox-staging-claims.test.ts b/test/scripts/crabbox-staging-claims.test.ts index 79da2045cf5a..e88b2fd807f6 100644 --- a/test/scripts/crabbox-staging-claims.test.ts +++ b/test/scripts/crabbox-staging-claims.test.ts @@ -172,6 +172,27 @@ it("returns only bounded matching lease IDs for source descendants and canonical } }); +it("ignores native claims not attached to any repository", async () => { + const context = fixture(); + nativeResponse( + output([ + { leaseId: "cbx_unattached", repoRoot: "" }, + { leaseId: "cbx_neighbor", repoRoot: context.source + "-neighbor" }, + ]), + ); + expect(await verifyNoStagingClaims(context)).toEqual({ ok: true }); + nativeResponse( + output([ + { leaseId: "cbx_unattached", repoRoot: "" }, + { leaseId: "cbx_stage", repoRoot: context.source }, + ]), + ); + expect(await verifyNoStagingClaims(context)).toMatchObject({ + ok: false, + matchingLeaseIds: ["cbx_stage"], + }); +}); + it.each([ ["nonzero partial inventory", output(), 2], [