mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 00:47:57 +00:00
fix: reuse ready native trending preflight (#3350)
This commit is contained in:
@@ -552,6 +552,39 @@ describe("canonical Trending snapshot storage", () => {
|
||||
).toEqual({ status: "unavailable" });
|
||||
});
|
||||
|
||||
it("returns the current native-only snapshot for guarded preflight reuse", async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const now = Date.now();
|
||||
await t.mutation(internal.canonicalTrending.startSnapshotInternal, {
|
||||
snapshotId: "skills-native-preflight-ready",
|
||||
generatedAt: now - 1_000,
|
||||
expiresAt: now + 24 * 60 * 60 * 1_000,
|
||||
windowStartDay: 40,
|
||||
windowEndDay: 40,
|
||||
});
|
||||
await t.mutation(internal.canonicalTrending.finalizeSnapshotInternal, {
|
||||
snapshotId: "skills-native-preflight-ready",
|
||||
completedAt: now - 500,
|
||||
totalItems: 0,
|
||||
sourceCounts: { clawhubTrending: 3, clawhubRising: 2, skillsShTrending: 0 },
|
||||
operations: { documentsRead: 10, documentsWritten: 2, functionCalls: 3 },
|
||||
});
|
||||
|
||||
await expect(
|
||||
t.query(internal.canonicalTrending.getReadyNativeSnapshotInternal, { now }),
|
||||
).resolves.toEqual({
|
||||
status: "ready",
|
||||
snapshotId: "skills-native-preflight-ready",
|
||||
generatedAt: new Date(now - 1_000).toISOString(),
|
||||
windowHours: 24,
|
||||
rankingVersion: "skills-trending-v2",
|
||||
totalItems: 0,
|
||||
sourceCounts: { clawhubTrending: 3, clawhubRising: 2, skillsShTrending: 0 },
|
||||
operations: { documentsRead: 10, documentsWritten: 2, functionCalls: 3 },
|
||||
reused: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("never serves a snapshot produced by the legacy ranking algorithm", async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const now = Date.now();
|
||||
|
||||
@@ -705,6 +705,41 @@ export const materializeInternal = internalAction({
|
||||
},
|
||||
});
|
||||
|
||||
export const getReadyNativeSnapshotInternal = internalQuery({
|
||||
args: { now: v.number() },
|
||||
handler: async (ctx, args) => {
|
||||
const snapshot = await ctx.db
|
||||
.query("canonicalTrendingSnapshots")
|
||||
.withIndex("by_kind_and_status_and_expires_at", (q) =>
|
||||
q.eq("kind", "skills").eq("status", "ready").gt("expiresAt", args.now),
|
||||
)
|
||||
.order("desc")
|
||||
.first();
|
||||
if (
|
||||
!snapshot ||
|
||||
snapshot.generatedAt + SNAPSHOT_MAX_SERVING_AGE_MS <= args.now ||
|
||||
snapshot.rankingVersion !== CANONICAL_TRENDING_RANKING_VERSION ||
|
||||
snapshot.totalItems === undefined ||
|
||||
!snapshot.sourceCounts ||
|
||||
snapshot.sourceCounts.skillsShTrending !== 0 ||
|
||||
!snapshot.operations
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
status: "ready" as const,
|
||||
snapshotId: snapshot.snapshotId,
|
||||
generatedAt: new Date(snapshot.generatedAt).toISOString(),
|
||||
windowHours: snapshot.windowHours,
|
||||
rankingVersion: snapshot.rankingVersion,
|
||||
totalItems: snapshot.totalItems,
|
||||
sourceCounts: snapshot.sourceCounts,
|
||||
operations: snapshot.operations,
|
||||
reused: true as const,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const getPageInternal = internalQuery({
|
||||
args: {
|
||||
cursor: v.union(v.string(), v.null()),
|
||||
|
||||
@@ -616,6 +616,52 @@ describe("skills.sh mirror visibility operations", () => {
|
||||
).rejects.toThrow("skills.sh mirror control is not active");
|
||||
});
|
||||
|
||||
it("reuses a fresh native-only snapshot instead of rematerializing preflight", async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const now = Date.now();
|
||||
await t.mutation(internal.canonicalTrending.startSnapshotInternal, {
|
||||
snapshotId: "skills-native-preflight-existing",
|
||||
generatedAt: now - 1_000,
|
||||
expiresAt: now + 24 * 60 * 60 * 1_000,
|
||||
windowStartDay: 40,
|
||||
windowEndDay: 40,
|
||||
});
|
||||
await t.mutation(internal.canonicalTrending.finalizeSnapshotInternal, {
|
||||
snapshotId: "skills-native-preflight-existing",
|
||||
completedAt: now - 500,
|
||||
totalItems: 0,
|
||||
sourceCounts: { clawhubTrending: 3, clawhubRising: 2, skillsShTrending: 0 },
|
||||
operations: { documentsRead: 10, documentsWritten: 2, functionCalls: 3 },
|
||||
});
|
||||
|
||||
await expect(
|
||||
t.action(internal.skillsShMirrorVisibility.prepareNativeTrendingInternal, {
|
||||
actor: "codex-test",
|
||||
reason: "reuse current native-only Trending",
|
||||
confirm: "deactivate-skills-sh-public-test",
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
ok: true,
|
||||
nativeTrending: {
|
||||
status: "ready",
|
||||
snapshotId: "skills-native-preflight-existing",
|
||||
sourceCounts: { skillsShTrending: 0 },
|
||||
reused: true,
|
||||
},
|
||||
});
|
||||
const state = await t.run(async (ctx) => ({
|
||||
snapshots: await ctx.db.query("canonicalTrendingSnapshots").collect(),
|
||||
control: await ctx.db
|
||||
.query("skillsShMirrorControls")
|
||||
.withIndex("by_key", (q) => q.eq("key", "global"))
|
||||
.unique(),
|
||||
}));
|
||||
expect(state).toMatchObject({
|
||||
snapshots: [expect.objectContaining({ snapshotId: "skills-native-preflight-existing" })],
|
||||
});
|
||||
expect(state.control?.activationLockToken).toBeUndefined();
|
||||
});
|
||||
|
||||
it("rejects a completed Trending run older than the selected leaderboard import", async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
await t.run(async (ctx) => {
|
||||
|
||||
@@ -18,7 +18,7 @@ const MAX_ROWS = 50_000;
|
||||
const ACTIVATION_LOCK_LEASE_MS = 15 * 60_000;
|
||||
|
||||
const internalRefs = internal as unknown as {
|
||||
canonicalTrending: { materializeInternal: unknown };
|
||||
canonicalTrending: { getReadyNativeSnapshotInternal: unknown; materializeInternal: unknown };
|
||||
skillsShMirrorVisibility: {
|
||||
beginActivationInternal: unknown;
|
||||
beginDeactivationInternal: unknown;
|
||||
@@ -499,7 +499,22 @@ export const beginNativeTrendingInternal = internalMutation({
|
||||
},
|
||||
});
|
||||
|
||||
async function materializeNativeTrending(ctx: Pick<ActionCtx, "runAction">, lockToken: string) {
|
||||
async function materializeNativeTrending(
|
||||
ctx: Pick<ActionCtx, "runAction" | "runQuery">,
|
||||
lockToken: string,
|
||||
) {
|
||||
const reusable = (await ctx.runQuery(
|
||||
internalRefs.canonicalTrending.getReadyNativeSnapshotInternal as never,
|
||||
{ now: Date.now() } as never,
|
||||
)) as {
|
||||
status: "ready";
|
||||
snapshotId: string;
|
||||
sourceCounts: { clawhubTrending: number; clawhubRising: number; skillsShTrending: 0 };
|
||||
reused: true;
|
||||
} | null;
|
||||
// Native-only data is independent of skills.sh run chronology. The activation path
|
||||
// always materializes its mixed snapshot after verifying the exact imported runs.
|
||||
if (reusable) return reusable;
|
||||
const nativeTrending = (await ctx.runAction(
|
||||
internalRefs.canonicalTrending.materializeInternal as never,
|
||||
{ activationLockToken: lockToken, skillsShMode: "native-only" } as never,
|
||||
@@ -515,7 +530,7 @@ async function materializeNativeTrending(ctx: Pick<ActionCtx, "runAction">, lock
|
||||
}
|
||||
|
||||
async function materializeNativeTrendingWithLock(
|
||||
ctx: Pick<ActionCtx, "runAction" | "runMutation">,
|
||||
ctx: Pick<ActionCtx, "runAction" | "runMutation" | "runQuery">,
|
||||
args: { actor: string; reason: string; confirm: string },
|
||||
) {
|
||||
const lockToken = `skills-sh-native-trending:${crypto.randomUUID()}`;
|
||||
|
||||
Reference in New Issue
Block a user