mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 00:47:57 +00:00
fix: verify bounded skills.sh Trending activation (#3366)
This commit is contained in:
@@ -6,7 +6,6 @@ import { internalAction, internalMutation, internalQuery } from "./_generated/se
|
||||
import {
|
||||
CANONICAL_TRENDING_FIRST_PAGE_SIZE,
|
||||
CANONICAL_TRENDING_LANE_LIMIT,
|
||||
CANONICAL_TRENDING_PUBLISHER_CAP,
|
||||
CANONICAL_TRENDING_RANKING_VERSION,
|
||||
CANONICAL_TRENDING_WINDOW_HOURS,
|
||||
blendCanonicalTrendingPools,
|
||||
@@ -17,7 +16,7 @@ import {
|
||||
decodeCanonicalTrendingCursor,
|
||||
encodeCanonicalTrendingCursor,
|
||||
isFreshExternalTrendingRun,
|
||||
retainTopCanonicalTrendingCandidates,
|
||||
retainCanonicalTrendingLaneCandidates,
|
||||
type CanonicalTrendingMaterializationCandidate,
|
||||
} from "./lib/canonicalTrending";
|
||||
import { forEachCanonicalTrendingSourcePage } from "./lib/canonicalTrendingPagination";
|
||||
@@ -123,11 +122,6 @@ const operationsValidator = v.object({
|
||||
functionCalls: v.number(),
|
||||
});
|
||||
|
||||
const LANE_DIVERSITY_RESERVE = {
|
||||
size: CANONICAL_TRENDING_FIRST_PAGE_SIZE,
|
||||
publisherCap: CANONICAL_TRENDING_PUBLISHER_CAP,
|
||||
};
|
||||
|
||||
export const getNativeSourceBatchInternal = internalQuery({
|
||||
args: { skillIds: v.array(v.id("skills")) },
|
||||
handler: async (ctx, args) => {
|
||||
@@ -856,17 +850,13 @@ export const materializeInternal = internalAction({
|
||||
}
|
||||
}
|
||||
// The fetched batch is capped at 100, so each lane stays within 100 rows of its limit.
|
||||
nativeCandidates = retainTopCanonicalTrendingCandidates(
|
||||
nativeCandidates = retainCanonicalTrendingLaneCandidates(
|
||||
nativeCandidates,
|
||||
"clawhub-trending",
|
||||
CANONICAL_TRENDING_LANE_LIMIT,
|
||||
LANE_DIVERSITY_RESERVE,
|
||||
);
|
||||
risingCandidates = retainTopCanonicalTrendingCandidates(
|
||||
risingCandidates = retainCanonicalTrendingLaneCandidates(
|
||||
risingCandidates,
|
||||
"clawhub-rising",
|
||||
CANONICAL_TRENDING_LANE_LIMIT,
|
||||
LANE_DIVERSITY_RESERVE,
|
||||
);
|
||||
for (const skillId of skillIds) usageBySkill.delete(String(skillId));
|
||||
};
|
||||
@@ -997,11 +987,9 @@ export const materializeInternal = internalAction({
|
||||
const candidate = buildExternalCanonicalTrendingCandidate(digest);
|
||||
if (candidate) externalCandidates.push(candidate);
|
||||
}
|
||||
externalCandidates = retainTopCanonicalTrendingCandidates(
|
||||
externalCandidates = retainCanonicalTrendingLaneCandidates(
|
||||
externalCandidates,
|
||||
"skills-sh-trending",
|
||||
CANONICAL_TRENDING_LANE_LIMIT,
|
||||
LANE_DIVERSITY_RESERVE,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
encodeCanonicalTrendingCursor,
|
||||
isFreshExternalTrendingRun,
|
||||
retainTopCanonicalTrendingCandidates,
|
||||
retainCanonicalTrendingLaneCandidates,
|
||||
sortCanonicalTrendingPools,
|
||||
type CanonicalTrendingCandidate,
|
||||
} from "./canonicalTrending";
|
||||
@@ -220,6 +221,21 @@ describe("canonical Trending ordering", () => {
|
||||
"gamma",
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps the shared diversity reserve in addition to the bounded lane leaders", () => {
|
||||
const retained = retainCanonicalTrendingLaneCandidates(
|
||||
Array.from({ length: 1_001 }, (_, index) =>
|
||||
candidate(`external-${index}`, "skills-sh-trending", {
|
||||
publisherKey: index === 1_000 ? "beta" : "alpha",
|
||||
upstreamRank: index + 1,
|
||||
}),
|
||||
),
|
||||
"skills-sh-trending",
|
||||
);
|
||||
|
||||
expect(retained).toHaveLength(1_001);
|
||||
expect(retained.at(-1)?.identity).toBe("external-1000");
|
||||
});
|
||||
});
|
||||
|
||||
describe("canonical Trending cursors", () => {
|
||||
|
||||
@@ -375,6 +375,16 @@ export function retainTopCanonicalTrendingCandidates<T extends CanonicalTrending
|
||||
);
|
||||
}
|
||||
|
||||
export function retainCanonicalTrendingLaneCandidates<T extends CanonicalTrendingCandidate>(
|
||||
candidates: readonly T[],
|
||||
lane: CanonicalTrendingLane,
|
||||
) {
|
||||
return retainTopCanonicalTrendingCandidates(candidates, lane, CANONICAL_TRENDING_LANE_LIMIT, {
|
||||
size: CANONICAL_TRENDING_FIRST_PAGE_SIZE,
|
||||
publisherCap: CANONICAL_TRENDING_PUBLISHER_CAP,
|
||||
});
|
||||
}
|
||||
|
||||
export function sortCanonicalTrendingPools<T extends CanonicalTrendingCandidate>(
|
||||
pools: CanonicalTrendingPools<T>,
|
||||
): CanonicalTrendingPools<T> {
|
||||
|
||||
@@ -534,6 +534,146 @@ describe("skills.sh mirror visibility operations", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("verifies the bounded Trending lane when eligible production rows exceed its limit", async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const now = Date.now();
|
||||
const rowCount = 1_001;
|
||||
const { leaderboardRunId, trendingRunId } = await t.run(async (ctx) => {
|
||||
const createdLeaderboardRunId = await ctx.db.insert(
|
||||
"skillsShMirrorRuns",
|
||||
mirrorRun({
|
||||
snapshotId: "skills-sh:leaderboard:bounded",
|
||||
sourceView: "leaderboard",
|
||||
sourceTotal: rowCount,
|
||||
counts: mirrorRunCounts({
|
||||
observed: rowCount,
|
||||
inserted: rowCount,
|
||||
detailsInserted: rowCount,
|
||||
}),
|
||||
startedAt: now - 3_000,
|
||||
completedAt: now - 2_000,
|
||||
updatedAt: now - 2_000,
|
||||
}),
|
||||
);
|
||||
const createdTrendingRunId = await ctx.db.insert(
|
||||
"skillsShMirrorRuns",
|
||||
mirrorRun({
|
||||
snapshotId: "skills-sh:trending:bounded",
|
||||
sourceView: "trending",
|
||||
sourceTotal: rowCount,
|
||||
counts: mirrorRunCounts({
|
||||
observed: rowCount,
|
||||
trendingJoined: rowCount,
|
||||
}),
|
||||
startedAt: now - 1_500,
|
||||
completedAt: now - 1_000,
|
||||
updatedAt: now - 1_000,
|
||||
}),
|
||||
);
|
||||
for (let index = 0; index < rowCount; index += 1) {
|
||||
const slug = `bounded-${index}`;
|
||||
const owner = `publisher-${index}`;
|
||||
await ctx.db.insert(
|
||||
"skillsShMirrorDigests",
|
||||
digest({
|
||||
externalId: `${owner}/skills/${slug}`,
|
||||
owner,
|
||||
slug,
|
||||
normalizedSlug: slug,
|
||||
normalizedSlugFirstToken: slug,
|
||||
displayName: `Bounded ${index}`,
|
||||
normalizedDisplayName: `bounded ${index}`,
|
||||
normalizedDisplayNameFirstToken: "bounded",
|
||||
searchText: `bounded ${index}`,
|
||||
sourceUrl: `https://skills.sh/${owner}/skills/${slug}`,
|
||||
canonicalRepoUrl: `https://github.com/${owner}/skills`,
|
||||
githubPath: `skills/${slug}`,
|
||||
lastObservedRunId: createdLeaderboardRunId,
|
||||
trendingObservedRunId: createdTrendingRunId,
|
||||
trendingRank: index + 1,
|
||||
trendingLifetimeInstalls: rowCount - index,
|
||||
trendingObservedAt: now - 1_000,
|
||||
}),
|
||||
);
|
||||
}
|
||||
await ctx.db.insert("skillsShMirrorControls", {
|
||||
key: "global",
|
||||
enabled: true,
|
||||
paused: false,
|
||||
maxRowsPerRun: 50_000,
|
||||
maxRowsPerBatch: 50,
|
||||
maxDetailBytes: 65_536,
|
||||
latestCompletedLeaderboardRunId: createdLeaderboardRunId,
|
||||
updatedBy: "codex-test",
|
||||
reason: "CLAW-603 bounded activation verification",
|
||||
updatedAt: now - 2_000,
|
||||
});
|
||||
return {
|
||||
leaderboardRunId: createdLeaderboardRunId,
|
||||
trendingRunId: createdTrendingRunId,
|
||||
};
|
||||
});
|
||||
|
||||
await t.mutation(internal.canonicalTrending.startSnapshotInternal, {
|
||||
snapshotId: "skills-native-bounded",
|
||||
generatedAt: now - 1_000,
|
||||
expiresAt: now + 24 * 60 * 60 * 1_000,
|
||||
windowStartDay: 40,
|
||||
windowEndDay: 40,
|
||||
windowStartHour: 960,
|
||||
windowEndHour: 983,
|
||||
});
|
||||
await t.mutation(internal.canonicalTrending.startNativePoolInternal, {
|
||||
poolId: "skills-native-bounded",
|
||||
generatedAt: now - 1_000,
|
||||
expiresAt: now + 24 * 60 * 60 * 1_000,
|
||||
windowStartHour: 960,
|
||||
windowEndHour: 983,
|
||||
sealedGeneration: 1,
|
||||
});
|
||||
await t.mutation(internal.canonicalTrending.finalizeNativePoolInternal, {
|
||||
poolId: "skills-native-bounded",
|
||||
completedAt: now - 500,
|
||||
sourceCounts: { clawhubTrending: 0, clawhubRising: 0 },
|
||||
operations: { documentsRead: 10, documentsWritten: 2, functionCalls: 3 },
|
||||
});
|
||||
await t.mutation(internal.canonicalTrending.finalizeSnapshotInternal, {
|
||||
snapshotId: "skills-native-bounded",
|
||||
completedAt: now - 500,
|
||||
totalItems: 0,
|
||||
sourceCounts: { clawhubTrending: 0, clawhubRising: 0, skillsShTrending: 0 },
|
||||
operations: { documentsRead: 10, documentsWritten: 2, functionCalls: 3 },
|
||||
nativePoolId: "skills-native-bounded",
|
||||
});
|
||||
|
||||
await expect(
|
||||
t.action(internal.skillsShMirrorVisibility.verifyAndActivateInternal, {
|
||||
actor: "codex-test",
|
||||
reason: "CLAW-603 bounded production-sized activation",
|
||||
confirm: "activate-skills-sh-public-test",
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
activated: true,
|
||||
leaderboard: { sourceTotal: rowCount, accepted: rowCount, rejected: 0 },
|
||||
trending: { sourceTotal: rowCount, joined: rowCount, missing: 0 },
|
||||
corpus: {
|
||||
activationRunAccepted: rowCount,
|
||||
activationRunTrendingEligible: rowCount,
|
||||
activationRunTrendingSelected: 1_000,
|
||||
},
|
||||
trendingSnapshot: {
|
||||
sourceCounts: { skillsShTrending: 1_000 },
|
||||
nativePool: { reused: true },
|
||||
},
|
||||
});
|
||||
const activatedRuns = await t.run(async (ctx) => ({
|
||||
leaderboard: await ctx.db.get(leaderboardRunId),
|
||||
trending: await ctx.db.get(trendingRunId),
|
||||
}));
|
||||
expect(activatedRuns.leaderboard?.activationSnapshotId).toEqual(expect.any(String));
|
||||
expect(activatedRuns.leaderboard?.activatedTrendingRunId).toBe(trendingRunId);
|
||||
});
|
||||
|
||||
it("keeps the public gate closed when activation has to build the native pool", async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const now = Date.now();
|
||||
|
||||
@@ -4,6 +4,12 @@ import { internal } from "./_generated/api";
|
||||
import type { Doc } from "./_generated/dataModel";
|
||||
import type { ActionCtx, MutationCtx } from "./_generated/server";
|
||||
import { internalAction, internalMutation, internalQuery } from "./functions";
|
||||
import {
|
||||
buildExternalCanonicalTrendingCandidate,
|
||||
type CanonicalTrendingCandidate,
|
||||
retainCanonicalTrendingLaneCandidates,
|
||||
} from "./lib/canonicalTrending";
|
||||
import { isPublicSkillsShMirrorDigest } from "./lib/skillsShMirrorPublic";
|
||||
import {
|
||||
assertSkillsShPublicVisibilityMutationAllowed,
|
||||
isSkillsShMirrorSourceEligible,
|
||||
@@ -838,7 +844,9 @@ export const getAuditPageInternal = internalQuery({
|
||||
tombstoned: 0,
|
||||
activationRunAccepted: 0,
|
||||
activationRunTrendingEligible: 0,
|
||||
activationRunTrendingSelected: 0,
|
||||
};
|
||||
const activationRunTrendingCandidates: CanonicalTrendingCandidate[] = [];
|
||||
for (const digest of page.page) {
|
||||
const sourceEligible = isSkillsShMirrorSourceEligible(digest);
|
||||
const publicationFlags = skillsShMirrorPublicationFlags(digest);
|
||||
@@ -870,15 +878,25 @@ export const getAuditPageInternal = internalQuery({
|
||||
if (
|
||||
args.trendingRunId !== undefined &&
|
||||
digest.trendingObservedRunId === args.trendingRunId &&
|
||||
publicationFlags.publicVisible &&
|
||||
publicationFlags.installable &&
|
||||
Number.isSafeInteger(digest.trendingRank) &&
|
||||
(digest.trendingRank ?? 0) >= 1
|
||||
isPublicSkillsShMirrorDigest(digest)
|
||||
) {
|
||||
counts.activationRunTrendingEligible += 1;
|
||||
const candidate = buildExternalCanonicalTrendingCandidate(digest);
|
||||
if (candidate) {
|
||||
counts.activationRunTrendingEligible += 1;
|
||||
activationRunTrendingCandidates.push({
|
||||
identity: candidate.identity,
|
||||
lane: candidate.lane,
|
||||
publisherKey: candidate.publisherKey,
|
||||
installs24h: candidate.installs24h,
|
||||
bookmarks24h: candidate.bookmarks24h,
|
||||
createdAt: candidate.createdAt,
|
||||
updatedAt: candidate.updatedAt,
|
||||
upstreamRank: candidate.upstreamRank,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return { ...page, page: counts };
|
||||
return { ...page, page: counts, activationRunTrendingCandidates };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -907,7 +925,9 @@ async function audit(
|
||||
tombstoned: 0,
|
||||
activationRunAccepted: 0,
|
||||
activationRunTrendingEligible: 0,
|
||||
activationRunTrendingSelected: 0,
|
||||
};
|
||||
let activationRunTrendingCandidates: CanonicalTrendingCandidate[] = [];
|
||||
while (true) {
|
||||
const result = (await ctx.runQuery(internal.skillsShMirrorVisibility.getAuditPageInternal, {
|
||||
paginationOpts: { cursor, numItems: MAX_BATCH_SIZE },
|
||||
@@ -916,10 +936,15 @@ async function audit(
|
||||
continueCursor: string;
|
||||
isDone: boolean;
|
||||
page: typeof counts;
|
||||
activationRunTrendingCandidates: CanonicalTrendingCandidate[];
|
||||
};
|
||||
for (const key of Object.keys(counts) as Array<keyof typeof counts>) {
|
||||
counts[key] += result.page[key];
|
||||
}
|
||||
activationRunTrendingCandidates = retainCanonicalTrendingLaneCandidates(
|
||||
[...activationRunTrendingCandidates, ...result.activationRunTrendingCandidates],
|
||||
"skills-sh-trending",
|
||||
);
|
||||
batches += 1;
|
||||
if (counts.total > MAX_ROWS) {
|
||||
throw new Error(`skills.sh visibility audit exceeded ${MAX_ROWS} rows`);
|
||||
@@ -927,6 +952,7 @@ async function audit(
|
||||
if (result.isDone) break;
|
||||
cursor = result.continueCursor;
|
||||
}
|
||||
counts.activationRunTrendingSelected = activationRunTrendingCandidates.length;
|
||||
return {
|
||||
ok: true as const,
|
||||
batches,
|
||||
@@ -1011,7 +1037,7 @@ export const verifyAndActivateInternal = internalAction({
|
||||
}
|
||||
if (
|
||||
trendingSnapshot.sourceCounts.skillsShTrending !==
|
||||
corpusAudit.counts.activationRunTrendingEligible
|
||||
corpusAudit.counts.activationRunTrendingSelected
|
||||
) {
|
||||
throw new Error("skills.sh Trending activation snapshot failed source verification");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user