feat: add permanent skills.sh mirror storage (#3227)

* feat: add staged skills.sh mirror storage

* ci: allow guarded CLAW-563 Test deploy

* ci: expose guarded Test deploy diagnostics

* ci: defer branch guard to deploy step

* ci: deploy CLAW-563 PR head to Test

* ci: admit CLAW-563 PR Test job

* fix: make mirror source recovery durable

* ci: trigger labeled mirror load

* feat: activate mirror search queries

* fix: tighten mirror source typing

* fix: bypass protected Test mirror proof

* feat: attribute skill metrics by source

* feat: present stars as bookmarks

* style: format mirror proof changes

* fix: bypass protected mirror readback

* fix: resume mirror past missing scanner pages

* fix: fetch skills.sh mirror audits from api

* fix: validate structural skills.sh identities

* fix: resolve ambiguous skills.sh mirror identities

* feat: stabilize skills.sh mirror ingestion

* fix: account mirror identity conflicts in proof

* fix: quarantine invalid skills.sh detail ids

* fix: resume skills.sh mirror proof

* fix: preserve skills.sh mirror provenance

* fix: recover exact skills.sh mirror runs

* fix: recover stale skills.sh mirror runs

* fix: normalize skills.sh mirror topic facets

* feat: prove complete skills.sh leaderboard mirror

* fix: canonicalize skills.sh source page hashes

* test: enable skills.sh rollout in mirror tests

* ci: skip unrelated Test deploy pull requests

* fix: preserve Vercel preview marker in Test deploy

* fix: tighten Test deploy and metric reconciliation

* fix: bound mirror detail proof pages

* fix: delegate controlled mirror rate limits

* fix: preserve mirror reconciliation progress

* fix: release mirror retry responses

* fix: preserve stale mirror replay state

* fix: authenticate mirror source starts

* fix: delegate mirror identity rate limits

* ci: trigger mirror proof when labeled

* ci: couple mirror deploy and proof opt-in

* fix: admit permanent Vercel Test runtime

* fix: pass Test target to Vercel runtime

* test: align bookmark sync browser labels

* fix: preserve skills.sh source accounting

* fix: preflight active mirror runs

* fix: bind mirror snapshot accounting

* fix: reject truncated replay hashes

* fix: preserve live mirror overlay metadata
This commit is contained in:
Patrick Erichsen
2026-07-24 14:32:00 -05:00
committed by GitHub
parent 306035cad7
commit 0f84533e9c
63 changed files with 13855 additions and 138 deletions
+226 -5
View File
@@ -5,7 +5,18 @@ on:
workflows: ["CI"]
types: [completed]
branches: [main]
pull_request:
types: [synchronize, labeled]
workflow_dispatch:
inputs:
expected_sha:
description: Exact branch SHA permitted for a temporary protected Test deploy
required: false
type: string
branch_test_confirm:
description: Confirmation phrase for the CLAW-563 branch-only Test deploy
required: false
type: string
concurrency:
group: deploy-test
@@ -18,7 +29,16 @@ jobs:
deploy-test:
if: >-
(github.event_name == 'workflow_dispatch' &&
github.ref == 'refs/heads/main') ||
(github.ref == 'refs/heads/main' ||
(github.ref == 'refs/heads/pe/claw-563-skills-sh-mirror-10k' &&
github.actor == 'Patrick-Erichsen' &&
inputs.branch_test_confirm == 'deploy-claw-563-to-permanent-test' &&
inputs.expected_sha != ''))) ||
(github.event_name == 'pull_request' &&
github.event.pull_request.head.ref == 'pe/claw-563-skills-sh-mirror-10k' &&
github.event.pull_request.head.repo.full_name == github.repository &&
github.actor == 'Patrick-Erichsen' &&
contains(github.event.pull_request.labels.*.name, 'test-mirror-load')) ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push')
runs-on: ubuntu-latest
@@ -30,11 +50,17 @@ jobs:
deployment_url: ${{ steps.vercel.outputs.deployment_url }}
deploy_sha: ${{ steps.revision.outputs.deploy_sha }}
fixture_version: ${{ steps.revision.outputs.fixture_version }}
branch_test: ${{ steps.revision.outputs.branch_test }}
steps:
- uses: actions/checkout@v7.0.1
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.ref }}
ref: >-
${{
github.event_name == 'workflow_run' && github.event.workflow_run.head_sha ||
github.event_name == 'pull_request' && github.event.pull_request.head.sha ||
github.ref
}}
- name: Resolve deployment revision
id: revision
@@ -44,8 +70,30 @@ jobs:
git fetch --no-tags origin main
main_sha="$(git rev-parse origin/main)"
if [[ "$deploy_sha" != "$main_sha" ]]; then
echo "::error::Refusing stale Test deploy: $deploy_sha is not current main $main_sha"
exit 1
branch_test_allowed=false
if [[ "$GITHUB_EVENT_NAME" == workflow_dispatch ]] &&
[[ "$GITHUB_REF" == refs/heads/pe/claw-563-skills-sh-mirror-10k ]] &&
[[ "$GITHUB_ACTOR" == Patrick-Erichsen ]] &&
[[ "${{ inputs.branch_test_confirm }}" == deploy-claw-563-to-permanent-test ]] &&
[[ "${{ inputs.expected_sha }}" == "$deploy_sha" ]]
then
branch_test_allowed=true
fi
if [[ "$GITHUB_EVENT_NAME" == pull_request ]] &&
[[ "$GITHUB_HEAD_REF" == pe/claw-563-skills-sh-mirror-10k ]] &&
[[ "$GITHUB_ACTOR" == Patrick-Erichsen ]] &&
[[ "${{ github.event.pull_request.head.repo.full_name }}" == "$GITHUB_REPOSITORY" ]] &&
[[ "${{ github.event.pull_request.head.sha }}" == "$deploy_sha" ]]
then
branch_test_allowed=true
fi
if [[ "$branch_test_allowed" != true ]]; then
echo "::error::Refusing non-main Test deploy without the exact CLAW-563 branch guard"
exit 1
fi
echo "branch_test=true" >> "$GITHUB_OUTPUT"
else
echo "branch_test=false" >> "$GITHUB_OUTPUT"
fi
fixture_version="$(
git hash-object convex/devSeed.ts convex/lib/testSeed.ts |
@@ -138,6 +186,7 @@ jobs:
' <<< "$capabilities"
- name: Apply additive Test fixtures
if: steps.revision.outputs.branch_test != 'true'
env:
CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }}
CONVEX_DEPLOYMENT: ${{ vars.CONVEX_DEPLOYMENT }}
@@ -164,20 +213,22 @@ jobs:
--meta githubSha="${{ steps.revision.outputs.deploy_sha }}" \
--meta clawhubEnvironment=test \
--build-env CONVEX_DEPLOY_KEY= \
--build-env VERCEL_ENV=test \
--build-env VERCEL_TARGET_ENV=test \
--build-env CLAWHUB_ENV=test \
--build-env CLAWHUB_SKILLS_SH_ROLLOUT_MODE=test \
--build-env CLAWHUB_GITHUB_SKILL_SYNC_ROLLOUT_MODE=test \
--build-env CLAWHUB_SKILLS_SH_TEST_LIVE_FETCH_ENABLED=1 \
--build-env SITE_URL="$TEST_SITE_URL" \
--build-env VITE_CLAWHUB_DEPLOY_ENV=test \
--build-env VITE_CONVEX_URL="$VITE_CONVEX_URL" \
--build-env VITE_CONVEX_SITE_URL="$VITE_CONVEX_SITE_URL" \
--build-env VITE_SITE_URL="$TEST_SITE_URL" \
--env CONVEX_DEPLOY_KEY= \
--env VERCEL_TARGET_ENV=test \
--env CLAWHUB_ENV=test \
--env CLAWHUB_SKILLS_SH_ROLLOUT_MODE=test \
--env CLAWHUB_GITHUB_SKILL_SYNC_ROLLOUT_MODE=test \
--env CLAWHUB_SKILLS_SH_TEST_LIVE_FETCH_ENABLED=1 \
--env SITE_URL="$TEST_SITE_URL" \
--env VITE_CLAWHUB_DEPLOY_ENV=test \
--env VITE_CONVEX_URL="$VITE_CONVEX_URL" \
@@ -256,3 +307,173 @@ jobs:
echo "- Fixture version: \`${FIXTURE_VERSION:-unresolved}\`"
echo "- Result: \`${{ job.status }}\`"
} >> "$GITHUB_STEP_SUMMARY"
claw563-mirror-load:
needs: deploy-test
if: >-
github.event_name == 'pull_request' &&
github.event.pull_request.head.ref == 'pe/claw-563-skills-sh-mirror-10k' &&
contains(github.event.pull_request.labels.*.name, 'test-mirror-load')
runs-on: ubuntu-latest
timeout-minutes: 180
environment:
name: Test
steps:
- uses: actions/checkout@v7.0.1
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- uses: ./.github/actions/setup-bun
- name: Load and prove the authenticated leaderboard mirror foundation
env:
CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }}
DEPLOY_SHA: ${{ needs.deploy-test.outputs.deploy_sha }}
TEST_SITE_URL: ${{ vars.SITE_URL }}
VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}
run: |
set -euo pipefail
operator_token=""
cleanup() {
set +e
bunx convex run --no-push skillsShMirror:configureInternal \
'{
"actor":"CLAW-563 Test workflow",
"reason":"CLAW-563 proof cleanup",
"confirm":"enable-skills-sh-mirror-test",
"enabled":false,
"maxRowsPerRun":50000,
"maxRowsPerBatch":50,
"maxDetailBytes":65536
}' > claw563-control-disabled.json 2>&1
disable_exit=$?
bunx convex run --no-push devSeed:seedCliRoleHelpFixtures '{}' >/dev/null
token_rotation_exit=$?
if [[ -n "$operator_token" ]]; then
revoked_status="$(
curl \
--silent \
--output /dev/null \
--write-out '%{http_code}' \
--header "Authorization: Bearer $operator_token" \
https://academic-chihuahua-392.convex.site/api/v1/operator/skills-sh/catalog-test
)"
else
revoked_status="not-created"
fi
jq -n \
--argjson disableExit "$disable_exit" \
--argjson tokenRotationExit "$token_rotation_exit" \
--arg revokedStatus "$revoked_status" \
'{
disableExit:$disableExit,
tokenRotationExit:$tokenRotationExit,
revokedStatus:$revokedStatus
}' > claw563-cleanup.json
set -e
[[
"$disable_exit" -eq 0 &&
"$token_rotation_exit" -eq 0 &&
"$revoked_status" == "401"
]]
}
trap cleanup EXIT
deployment_json="$(bunx convex run --no-push appMeta:getDeploymentInfo)"
build_sha="$(jq -r '.appBuildSha // empty' <<<"$deployment_json")"
if [[ "$build_sha" != "$DEPLOY_SHA" ]]; then
echo "::error::Unexpected Convex Test build SHA: $build_sha"
exit 1
fi
token_seed="$(bunx convex run --no-push devSeed:seedCliRoleHelpFixtures '{}')"
operator_token="$(jq -r '.admin.token // empty' <<<"$token_seed")"
token_seed=""
if [[ -z "$operator_token" ]]; then
echo "::error::Test admin fixture token is missing"
exit 1
fi
echo "::add-mask::$operator_token"
export CLAWHUB_TEST_MIRROR_GATE_URL="$TEST_SITE_URL/ops/skills-sh/mirror-test"
export CLAWHUB_TEST_OPERATOR_TOKEN="$operator_token"
bun run skills-sh:prove-mirror > claw563-proof-output.json
curl \
--fail-with-body \
--silent \
--show-error \
--request POST \
--header "Authorization: Bearer $operator_token" \
--header "Content-Type: application/json" \
--header "x-vercel-protection-bypass: $VERCEL_AUTOMATION_BYPASS_SECRET" \
--data '{"operation":"read","externalId":"patrick-erichsen/skills/html"}' \
"$CLAWHUB_TEST_MIRROR_GATE_URL" > claw563-controlled-entry.json
jq -e '
.digest.externalId == "patrick-erichsen/skills/html" and
.digest.sourceType == "github" and
.digest.owner == "patrick-erichsen" and
.digest.repo == "skills" and
.digest.githubPath == "skills/html" and
.digest.githubCommit == "050daba89f6b6636470add5cb300aac46a412cf8" and
.digest.sourceContentHash == "42d2e89358ea927441dfede45c3b0cf89a21603bc7c32246f098d24a9cbea1ff" and
.digest.active == true and
.digest.publicVisible == false and
.digest.installable == false
' claw563-controlled-entry.json >/dev/null
curl \
--fail-with-body \
--silent \
--show-error \
--request POST \
--header "Authorization: Bearer $operator_token" \
--header "Content-Type: application/json" \
--header "x-vercel-protection-bypass: $VERCEL_AUTOMATION_BYPASS_SECRET" \
--data '{"operation":"read","externalId":"steipete/clawdis/discrawl"}' \
"$CLAWHUB_TEST_MIRROR_GATE_URL" > claw563-discrawl-entry.json
jq -e '
.digest.externalId == "steipete/clawdis/discrawl" and
.digest.sourceType == "github" and
.digest.owner == "steipete" and
.digest.repo == "clawdis" and
.digest.githubPath == ".agents/skills/discrawl" and
.digest.githubCommit == "690ed564419291ca6e832dc69b53061300075b62" and
.digest.sourceContentHash == "889dc43180b210dbca12f8291e007feb231250ecfdba90c4d3938a18125efb6d" and
.digest.active == true and
.digest.publicVisible == false and
.digest.installable == false
' claw563-discrawl-entry.json >/dev/null
jq -n \
--arg sourceSha "$DEPLOY_SHA" \
--arg deploymentUrl "$TEST_SITE_URL" \
'{
sourceSha:$sourceSha,
deploymentUrl:$deploymentUrl,
convexDeployment:"academic-chihuahua-392"
}' > claw563-deployment.json
cleanup
trap - EXIT
jq -e '
.disableExit == 0 and
.tokenRotationExit == 0 and
.revokedStatus == "401"
' claw563-cleanup.json >/dev/null
- name: Upload permanent Test mirror proof
if: always()
uses: actions/upload-artifact@v7
with:
name: claw563-mirror-proof
if-no-files-found: warn
path: |
proof/claw-563/skills-sh-mirror-test-proof.json
claw563-proof-output.json
claw563-controlled-entry.json
claw563-discrawl-entry.json
claw563-control-disabled.json
claw563-cleanup.json
claw563-deployment.json
+1
View File
@@ -46,6 +46,7 @@
"mermaid": "^11.16.0",
"mime": "4.1.0",
"monaco-editor": "0.56.0",
"parse5": "8.0.1",
"pino": "10.3.1",
"react": "19.2.7",
"react-dom": "19.2.7",
+2
View File
@@ -173,6 +173,7 @@ import type * as skillStatEvents from "../skillStatEvents.js";
import type * as skillTransfers from "../skillTransfers.js";
import type * as skills from "../skills.js";
import type * as skillsShCatalog from "../skillsShCatalog.js";
import type * as skillsShMirror from "../skillsShMirror.js";
import type * as stars from "../stars.js";
import type * as statsMaintenance from "../statsMaintenance.js";
import type * as telemetry from "../telemetry.js";
@@ -354,6 +355,7 @@ declare const fullApi: ApiFromModules<{
skillTransfers: typeof skillTransfers;
skills: typeof skills;
skillsShCatalog: typeof skillsShCatalog;
skillsShMirror: typeof skillsShMirror;
stars: typeof stars;
statsMaintenance: typeof statsMaintenance;
telemetry: typeof telemetry;
+2 -2
View File
@@ -11,7 +11,7 @@ import {
} from "./lib/downloadTrend";
import { normalizePackageName } from "./lib/packageRegistry";
import { canAccessPublisherOwnerScope } from "./lib/publishers";
import { readCanonicalStat } from "./lib/skillStats";
import { readPublicDownloads } from "./lib/skillStats";
const dashboardMetricSelectionValidator = v.union(
v.object({ kind: v.literal("skill"), slug: v.string() }),
@@ -46,7 +46,7 @@ async function aggregateSkillDownloads(ctx: QueryCtx, skills: Doc<"skills">[], e
);
for (const trend of trends) addPoints(points, trend);
return {
allTimeDownloads: skills.reduce((sum, skill) => sum + readCanonicalStat(skill, "downloads"), 0),
allTimeDownloads: skills.reduce((sum, skill) => sum + readPublicDownloads(skill), 0),
points,
};
}
+12
View File
@@ -2400,6 +2400,8 @@ description: Build HTML artifacts.
statsStars: 3,
statsInstallsCurrent: 2,
statsInstallsAllTime: 5,
statsSkillsShInstalls: 17,
statsGithubStars: 321,
stats: { downloads: 7, stars: 3, installsCurrent: 2, installsAllTime: 5, versions: 0 },
createdAt: 1,
updatedAt: 60,
@@ -2436,6 +2438,16 @@ description: Build HTML artifacts.
moderationReason: "pending.scan",
statsDownloads: 7,
statsStars: 3,
statsInstallsCurrent: 2,
statsInstallsAllTime: 5,
statsSkillsShInstalls: 17,
statsGithubStars: 321,
stats: {
downloads: 7,
stars: 3,
installsCurrent: 2,
installsAllTime: 5,
},
});
expect(tables.skills[0]).not.toHaveProperty("githubRemovedAt");
expect(tables.skills[0]).not.toHaveProperty("softDeletedAt");
+2 -3
View File
@@ -1922,8 +1922,7 @@ describe("httpApiV1 handlers", () => {
displayName: "Demo",
summary: "Summary",
updatedAt: 1,
statsDownloads: 42,
stats: { downloads: 1 },
stats: { downloads: 50 },
},
version: { version: "1.0.0" },
ownerHandle: "openclaw",
@@ -1951,7 +1950,7 @@ describe("httpApiV1 handlers", () => {
displayName: "Demo",
summary: "Summary",
version: "1.0.0",
downloads: 42,
downloads: 50,
updatedAt: 1,
ownerHandle: "openclaw",
owner: {
+399
View File
@@ -235,6 +235,405 @@ describe("skills.sh catalog Test HTTP API", () => {
expect(buildGitHubApiHeaders).not.toHaveBeenCalled();
});
it("routes mirror batches through the scanless mirror mutation", async () => {
const runMutation = vi.fn(async (_ref, args: Record<string, unknown>) => ({
status: "running",
page: args.page,
offset: 1,
}));
const ctx = {
runQuery: vi.fn(async () => ({
environment: "test",
deploymentName: "academic-chihuahua-392",
buildSha: "test-sha",
control: {},
})),
runMutation,
} as never;
const request = new Request("https://academic-chihuahua-392.convex.site/api/v1/ops", {
method: "POST",
body: JSON.stringify({
operation: "mirror-batch",
runId: "skillsShMirrorRuns:test",
leaseToken: "lease:test",
page: 0,
offset: 0,
pageLength: 500,
hasMore: true,
sourceTotal: 9_571,
sourceRequests: 2,
sourceBytes: 1_024,
rows: [{ externalId: "vercel-labs/skills/find-skills" }],
}),
});
const response = await skillsShCatalogTestV1Handler(ctx, request);
expect(response.status).toBe(200);
expect(runMutation).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
runId: "skillsShMirrorRuns:test",
leaseToken: "lease:test",
page: 0,
offset: 0,
sourceTotal: 9_571,
}),
);
});
it("stores and summarizes immutable mirror source pages", async () => {
const staging = {
environment: "test",
deploymentName: "academic-chihuahua-392",
buildSha: "test-sha",
control: {},
};
const rows = [
{
id: "vercel-labs/skills/find-skills",
installUrl: "https://github.com/vercel-labs/skills",
installs: 42,
name: "Find Skills",
slug: "find-skills",
source: "vercel-labs/skills",
sourceType: "github",
url: "https://skills.sh/vercel-labs/skills/find-skills",
},
];
const runMutation = vi.fn(async () => ({ stored: true, page: 0, rows: 1 }));
const storeCtx = { runQuery: vi.fn(async () => staging), runMutation } as never;
const storeResponse = await skillsShCatalogTestV1Handler(
storeCtx,
new Request("https://academic-chihuahua-392.convex.site/api/v1/ops", {
method: "POST",
body: JSON.stringify({
operation: "mirror-source-page-store",
snapshotHash: "a".repeat(64),
page: 0,
sourceTotal: 1,
pageLength: 1,
hasMore: false,
identityHash: "b".repeat(64),
contentHash: "c".repeat(64),
sourceBytes: 512,
serializedBytes: 768,
rows,
}),
}),
);
expect(storeResponse.status).toBe(200);
expect(runMutation).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
snapshotHash: "a".repeat(64),
page: 0,
rows,
}),
);
const summary = {
snapshotHash: "a".repeat(64),
pageDocuments: 1,
rows: 1,
sourceBytes: 512,
serializedBytes: 768,
};
const runQuery = vi.fn().mockResolvedValueOnce(staging).mockResolvedValueOnce(summary);
const summaryResponse = await skillsShCatalogTestV1Handler(
{ runQuery } as never,
new Request("https://academic-chihuahua-392.convex.site/api/v1/ops", {
method: "POST",
body: JSON.stringify({
operation: "mirror-source-summary",
snapshotHash: "a".repeat(64),
}),
}),
);
expect(summaryResponse.status).toBe(200);
expect(await summaryResponse.json()).toEqual(summary);
expect(runQuery).toHaveBeenCalledTimes(2);
});
it("routes guarded mirror batch lease claims and releases", async () => {
const runMutation = vi
.fn()
.mockResolvedValueOnce({
runId: "skillsShMirrorRuns:test",
page: 3,
offset: 50,
leaseToken: "lease:test",
leaseExpiresAt: Date.now() + 300_000,
})
.mockResolvedValueOnce({ released: true });
const ctx = {
runQuery: vi.fn(async () => ({
environment: "test",
deploymentName: "academic-chihuahua-392",
buildSha: "test-sha",
control: {},
})),
runMutation,
} as never;
for (const operation of ["mirror-batch-claim", "mirror-batch-release"] as const) {
const response = await skillsShCatalogTestV1Handler(
ctx,
new Request("https://academic-chihuahua-392.convex.site/api/v1/ops", {
method: "POST",
body: JSON.stringify({
operation,
runId: "skillsShMirrorRuns:test",
page: 3,
offset: 50,
leaseToken: "lease:test",
}),
}),
);
expect(response.status).toBe(200);
}
expect(runMutation).toHaveBeenNthCalledWith(
1,
expect.anything(),
expect.objectContaining({
runId: "skillsShMirrorRuns:test",
page: 3,
offset: 50,
leaseToken: "lease:test",
}),
);
expect(runMutation).toHaveBeenNthCalledWith(
2,
expect.anything(),
expect.objectContaining({
runId: "skillsShMirrorRuns:test",
page: 3,
offset: 50,
leaseToken: "lease:test",
}),
);
});
it("reads one mirror run for source-fetch preflight", async () => {
const runQuery = vi
.fn()
.mockResolvedValueOnce({
environment: "test",
deploymentName: "academic-chihuahua-392",
buildSha: "test-sha",
control: {},
})
.mockResolvedValueOnce({
runId: "skillsShMirrorRuns:test",
status: "paused",
page: 3,
offset: 50,
});
const ctx = { runQuery } as never;
const request = new Request("https://academic-chihuahua-392.convex.site/api/v1/ops", {
method: "POST",
body: JSON.stringify({
operation: "mirror-run",
runId: "skillsShMirrorRuns:test",
}),
});
const response = await skillsShCatalogTestV1Handler(ctx, request);
expect(response.status).toBe(200);
expect(await response.json()).toMatchObject({
status: "paused",
page: 3,
offset: 50,
});
expect(runQuery).toHaveBeenCalledTimes(2);
});
it("cancels one stale active mirror run with explicit confirmation", async () => {
const runQuery = vi.fn().mockResolvedValue({
environment: "test",
deploymentName: "academic-chihuahua-392",
buildSha: "test-sha",
control: {},
});
const runMutation = vi.fn().mockResolvedValue({
runId: "skillsShMirrorRuns:stale",
status: "canceled",
});
const ctx = { runQuery, runMutation } as never;
const request = new Request("https://academic-chihuahua-392.convex.site/api/v1/ops", {
method: "POST",
body: JSON.stringify({
operation: "mirror-cancel",
runId: "skillsShMirrorRuns:stale",
reason: "discard stale captured recovery",
confirm: "cancel-skills-sh-mirror-test-run",
}),
});
const response = await skillsShCatalogTestV1Handler(ctx, request);
expect(response.status).toBe(200);
expect(await response.json()).toEqual({
runId: "skillsShMirrorRuns:stale",
status: "canceled",
});
expect(runMutation).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
runId: "skillsShMirrorRuns:stale",
reason: "discard stale captured recovery",
confirm: "cancel-skills-sh-mirror-test-run",
}),
);
});
it("reads bounded mirror conflicts for an exact completed run", async () => {
const runQuery = vi
.fn()
.mockResolvedValueOnce({
environment: "test",
deploymentName: "academic-chihuahua-392",
buildSha: "test-sha",
control: {},
})
.mockResolvedValueOnce([
{
runId: "skillsShMirrorRuns:live",
externalId: "larksuite/cli/lark-doc",
kind: "source-quarantine",
},
]);
const ctx = { runQuery } as never;
const response = await skillsShCatalogTestV1Handler(
ctx,
new Request("https://academic-chihuahua-392.convex.site/api/v1/ops", {
method: "POST",
body: JSON.stringify({
operation: "mirror-conflicts",
runId: "skillsShMirrorRuns:live",
limit: 50,
}),
}),
);
expect(response.status).toBe(200);
expect(await response.json()).toEqual({
conflicts: [
{
runId: "skillsShMirrorRuns:live",
externalId: "larksuite/cli/lark-doc",
kind: "source-quarantine",
},
],
});
expect(runQuery).toHaveBeenCalledTimes(2);
});
it("reads bounded mirror classification reuse state", async () => {
const runQuery = vi
.fn()
.mockResolvedValueOnce({
environment: "test",
deploymentName: "academic-chihuahua-392",
buildSha: "test-sha",
control: {},
})
.mockResolvedValueOnce([
{
externalId: "patrick-erichsen/skills/html",
inferredClassifierVersion: "taxonomy-prototype-v9",
},
]);
const ctx = { runQuery } as never;
const response = await skillsShCatalogTestV1Handler(
ctx,
new Request("https://academic-chihuahua-392.convex.site/api/v1/ops", {
method: "POST",
body: JSON.stringify({
operation: "mirror-classification-states",
externalIds: ["patrick-erichsen/skills/html"],
}),
}),
);
expect(response.status).toBe(200);
expect(await response.json()).toMatchObject({
states: [{ externalId: "patrick-erichsen/skills/html" }],
});
expect(runQuery).toHaveBeenCalledTimes(2);
});
it("reads a bounded mirror facet proof page", async () => {
const runQuery = vi
.fn()
.mockResolvedValueOnce({
environment: "test",
deploymentName: "academic-chihuahua-392",
buildSha: "test-sha",
control: {},
})
.mockResolvedValueOnce({
page: [{ kind: "category", term: "development" }],
isDone: true,
continueCursor: "",
});
const ctx = { runQuery } as never;
const response = await skillsShCatalogTestV1Handler(
ctx,
new Request("https://academic-chihuahua-392.convex.site/api/v1/ops", {
method: "POST",
body: JSON.stringify({
operation: "mirror-facet-page",
cursor: null,
limit: 500,
}),
}),
);
expect(response.status).toBe(200);
expect(await response.json()).toMatchObject({
page: [{ kind: "category", term: "development" }],
isDone: true,
});
});
it("reads bounded captured mirror rows for replay", async () => {
const runQuery = vi
.fn()
.mockResolvedValueOnce({
environment: "test",
deploymentName: "academic-chihuahua-392",
buildSha: "test-sha",
control: {},
})
.mockResolvedValueOnce([
{
digest: { externalId: "patrick-erichsen/skills/html", active: true },
detail: null,
},
]);
const ctx = { runQuery } as never;
const response = await skillsShCatalogTestV1Handler(
ctx,
new Request("https://academic-chihuahua-392.convex.site/api/v1/ops", {
method: "POST",
body: JSON.stringify({
operation: "mirror-replay-rows",
externalIds: ["patrick-erichsen/skills/html"],
}),
}),
);
expect(response.status).toBe(200);
expect(await response.json()).toMatchObject({
rows: [{ digest: { externalId: "patrick-erichsen/skills/html" }, detail: null }],
});
});
it("fetches only owners missing from authenticated staging-live state", async () => {
const githubFetch = vi.fn(async (url: string, init?: RequestInit) => {
expect(init?.headers).toMatchObject({ Authorization: "Bearer placeholder" });
+280
View File
@@ -28,6 +28,29 @@ const internalRefs = internal as unknown as {
startFixtureRunInternal: unknown;
startStagingLiveRunInternal: unknown;
};
skillsShMirror: {
cancelRunInternal: unknown;
claimBatchLeaseInternal: unknown;
configureInternal: unknown;
getByExternalIdInternal: unknown;
getClassificationStatesInternal: unknown;
getDetailByExternalIdInternal: unknown;
getIsolationInternal: unknown;
getReplayRowsInternal: unknown;
getRunInternal: unknown;
getSourceCaptureSummaryInternal: unknown;
getStatusInternal: unknown;
listDetailsPageInternal: unknown;
listDigestsPageInternal: unknown;
listConflictsByRunInternal: unknown;
listFacetsPageInternal: unknown;
processBatchInternal: unknown;
reconcileBatchInternal: unknown;
releaseBatchLeaseInternal: unknown;
setPausedInternal: unknown;
startRunInternal: unknown;
storeSourcePageInternal: unknown;
};
};
const MAX_GITHUB_OWNER_RESOLUTIONS = 500;
const GITHUB_OWNER_RESOLUTION_CONCURRENCY = 8;
@@ -91,6 +114,19 @@ function requireBoolean(record: Record<string, unknown>, key: string) {
return value;
}
function requireStringArray(record: Record<string, unknown>, key: string, maxItems: number) {
const value = record[key];
if (
!Array.isArray(value) ||
value.length < 1 ||
value.length > maxItems ||
value.some((item) => typeof item !== "string" || !item.trim())
) {
throw new Error(`${key} must contain between 1 and ${maxItems} strings`);
}
return value as string[];
}
async function sha256Hex(bytes: Uint8Array) {
const digest = await crypto.subtle.digest("SHA-256", new Uint8Array(bytes).buffer);
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
@@ -426,6 +462,250 @@ export async function skillsShCatalogTestV1Handler(ctx: ActionCtx, request: Requ
const body = asRecord(await request.json());
if (!body) return text("Invalid JSON", 400, rate.headers);
const operation = requireString(body, "operation");
if (operation === "mirror-status") {
return json(
await runQueryRef(ctx, internalRefs.skillsShMirror.getStatusInternal, {}),
200,
rate.headers,
);
}
if (operation === "mirror-isolation") {
return json(
await runQueryRef(ctx, internalRefs.skillsShMirror.getIsolationInternal, {}),
200,
rate.headers,
);
}
if (operation === "mirror-run") {
return json(
await runQueryRef(ctx, internalRefs.skillsShMirror.getRunInternal, {
runId: requireString(body, "runId"),
}),
200,
rate.headers,
);
}
if (operation === "mirror-conflicts") {
const conflicts = await runQueryRef(
ctx,
internalRefs.skillsShMirror.listConflictsByRunInternal,
{
runId: requireString(body, "runId"),
limit: requireNumber(body, "limit"),
},
);
return json({ conflicts }, 200, rate.headers);
}
if (operation === "mirror-read") {
const externalId = requireString(body, "externalId");
const [digest, detail] = await Promise.all([
runQueryRef(ctx, internalRefs.skillsShMirror.getByExternalIdInternal, { externalId }),
runQueryRef(ctx, internalRefs.skillsShMirror.getDetailByExternalIdInternal, {
externalId,
}),
]);
return json({ digest, detail }, 200, rate.headers);
}
if (operation === "mirror-classification-states") {
const states = await runQueryRef(
ctx,
internalRefs.skillsShMirror.getClassificationStatesInternal,
{
externalIds: requireStringArray(body, "externalIds", 50),
},
);
return json({ states }, 200, rate.headers);
}
if (operation === "mirror-replay-rows") {
const rows = await runQueryRef(ctx, internalRefs.skillsShMirror.getReplayRowsInternal, {
externalIds: requireStringArray(body, "externalIds", 50),
});
return json({ rows }, 200, rate.headers);
}
if (operation === "mirror-source-summary") {
return json(
await runQueryRef(ctx, internalRefs.skillsShMirror.getSourceCaptureSummaryInternal, {
snapshotHash: requireString(body, "snapshotHash"),
}),
200,
rate.headers,
);
}
if (operation === "mirror-page") {
const cursor =
body.cursor === null || typeof body.cursor === "string"
? body.cursor
: (() => {
throw new Error("cursor is required");
})();
return json(
await runQueryRef(ctx, internalRefs.skillsShMirror.listDigestsPageInternal, {
cursor,
limit: requireNumber(body, "limit"),
}),
200,
rate.headers,
);
}
if (operation === "mirror-detail-page") {
const cursor =
body.cursor === null || typeof body.cursor === "string"
? body.cursor
: (() => {
throw new Error("cursor is required");
})();
return json(
await runQueryRef(ctx, internalRefs.skillsShMirror.listDetailsPageInternal, {
cursor,
limit: requireNumber(body, "limit"),
}),
200,
rate.headers,
);
}
if (operation === "mirror-facet-page") {
const cursor =
body.cursor === null || typeof body.cursor === "string"
? body.cursor
: (() => {
throw new Error("cursor is required");
})();
return json(
await runQueryRef(ctx, internalRefs.skillsShMirror.listFacetsPageInternal, {
cursor,
limit: requireNumber(body, "limit"),
}),
200,
rate.headers,
);
}
if (operation === "mirror-configure") {
return json(
await runMutationRef(ctx, internalRefs.skillsShMirror.configureInternal, {
actor: auth.user.handle,
reason: requireString(body, "reason"),
confirm: requireString(body, "confirm"),
enabled: requireBoolean(body, "enabled"),
maxRowsPerRun: requireNumber(body, "maxRowsPerRun"),
maxRowsPerBatch: requireNumber(body, "maxRowsPerBatch"),
maxDetailBytes: requireNumber(body, "maxDetailBytes"),
}),
200,
rate.headers,
);
}
if (operation === "mirror-start") {
return json(
await runMutationRef(ctx, internalRefs.skillsShMirror.startRunInternal, {
actor: auth.user.handle,
reason: requireString(body, "reason"),
snapshotId: requireString(body, "snapshotId"),
...(typeof body.sourceSnapshotHash === "string"
? { sourceSnapshotHash: requireString(body, "sourceSnapshotHash") }
: {}),
...(typeof body.sourceCaptureWrites === "number"
? { sourceCaptureWrites: requireNumber(body, "sourceCaptureWrites") }
: {}),
sourceTotal: requireNumber(body, "sourceTotal"),
sourcePageSize: requireNumber(body, "sourcePageSize"),
sourceMeasuredAt: requireString(body, "sourceMeasuredAt"),
}),
200,
rate.headers,
);
}
if (operation === "mirror-source-page-store") {
if (!Array.isArray(body.rows)) throw new Error("rows is required");
return json(
await runMutationRef(ctx, internalRefs.skillsShMirror.storeSourcePageInternal, {
snapshotHash: requireString(body, "snapshotHash"),
page: requireNumber(body, "page"),
sourceTotal: requireNumber(body, "sourceTotal"),
pageLength: requireNumber(body, "pageLength"),
hasMore: requireBoolean(body, "hasMore"),
identityHash: requireString(body, "identityHash"),
contentHash: requireString(body, "contentHash"),
sourceBytes: requireNumber(body, "sourceBytes"),
serializedBytes: requireNumber(body, "serializedBytes"),
rows: body.rows,
}),
200,
rate.headers,
);
}
if (operation === "mirror-batch") {
if (!Array.isArray(body.rows)) throw new Error("rows is required");
return json(
await runMutationRef(ctx, internalRefs.skillsShMirror.processBatchInternal, {
runId: requireString(body, "runId"),
leaseToken: requireString(body, "leaseToken"),
page: requireNumber(body, "page"),
offset: requireNumber(body, "offset"),
pageLength: requireNumber(body, "pageLength"),
hasMore: requireBoolean(body, "hasMore"),
sourceTotal: requireNumber(body, "sourceTotal"),
sourceRequests: requireNumber(body, "sourceRequests"),
sourceBytes: requireNumber(body, "sourceBytes"),
rows: body.rows,
}),
200,
rate.headers,
);
}
if (operation === "mirror-batch-claim" || operation === "mirror-batch-release") {
const args = {
runId: requireString(body, "runId"),
page: requireNumber(body, "page"),
offset: requireNumber(body, "offset"),
leaseToken: requireString(body, "leaseToken"),
};
return json(
await runMutationRef(
ctx,
operation === "mirror-batch-claim"
? internalRefs.skillsShMirror.claimBatchLeaseInternal
: internalRefs.skillsShMirror.releaseBatchLeaseInternal,
args,
),
200,
rate.headers,
);
}
if (operation === "mirror-pause") {
return json(
await runMutationRef(ctx, internalRefs.skillsShMirror.setPausedInternal, {
runId: requireString(body, "runId"),
paused: requireBoolean(body, "paused"),
actor: auth.user.handle,
reason: requireString(body, "reason"),
confirm: requireString(body, "confirm"),
}),
200,
rate.headers,
);
}
if (operation === "mirror-cancel") {
return json(
await runMutationRef(ctx, internalRefs.skillsShMirror.cancelRunInternal, {
runId: requireString(body, "runId"),
actor: auth.user.handle,
reason: requireString(body, "reason"),
confirm: requireString(body, "confirm"),
}),
200,
rate.headers,
);
}
if (operation === "mirror-reconcile") {
return json(
await runMutationRef(ctx, internalRefs.skillsShMirror.reconcileBatchInternal, {
runId: requireString(body, "runId"),
limit: requireNumber(body, "limit"),
}),
200,
rate.headers,
);
}
if (operation === "verify-canary") {
return json(await verifyControlledCanaryGitHubSource({}), 200, rate.headers);
}
+3 -3
View File
@@ -50,7 +50,6 @@ import {
getSkillFileModerationInfoFromSkill,
isSkillVersionForSkill,
} from "../lib/skillFileAccess";
import { readCanonicalStat } from "../lib/skillStats";
import {
buildDeterministicZip,
buildMergedExportZip,
@@ -101,7 +100,6 @@ type SearchSkillEntry = {
stars?: number;
installs?: number;
};
statsDownloads?: number;
} | null;
version: { version?: string; createdAt?: number } | null;
ownerHandle?: string | null;
@@ -1394,7 +1392,9 @@ export async function searchSkillsV1Handler(ctx: ActionCtx, request: Request) {
displayName: result.skill?.displayName,
summary: result.skill?.summary ?? null,
version: result.version?.version ?? null,
downloads: result.skill ? readCanonicalStat(result.skill, "downloads") : 0,
// searchSkills already returns the ordinary public skill shape, including
// the combined presentation value and no source-attribution fields.
downloads: result.skill?.stats.downloads ?? 0,
updatedAt: result.skill?.updatedAt,
ownerHandle: result.ownerHandle ?? owner?.handle ?? null,
owner,
+17 -1
View File
@@ -51,13 +51,15 @@ describe("public skill mapping", () => {
statsStars: 3,
statsInstallsCurrent: 5,
statsInstallsAllTime: 7,
statsSkillsShInstalls: 8,
statsGithubStars: 99,
});
const mapped = toPublicSkill(legacySkill);
expect(mapped).not.toBeNull();
expect(mapped?.stats).toEqual({
downloads: 12,
downloads: 20,
stars: 3,
installs: 7,
versions: 0,
@@ -65,6 +67,20 @@ describe("public skill mapping", () => {
});
});
it("does not expose source breakdowns on the ordinary public skill shape", () => {
const mapped = toPublicSkill(
makeSkill({
statsDownloads: 12,
statsSkillsShInstalls: 8,
statsGithubStars: 99,
}),
);
expect(mapped?.stats.downloads).toBe(20);
expect(mapped).not.toHaveProperty("statsSkillsShInstalls");
expect(mapped).not.toHaveProperty("statsGithubStars");
});
it("exposes GitHub-backed skill source fields", () => {
const mapped = toPublicSkill(
makeSkill({
+4 -2
View File
@@ -1,6 +1,6 @@
import type { Doc } from "../_generated/dataModel";
import { isPublicSkillDoc } from "./globalStats";
import { readCanonicalStat } from "./skillStats";
import { readCanonicalStat, readPublicDownloads } from "./skillStats";
export type PublicUser = Pick<
Doc<"users">,
@@ -101,6 +101,8 @@ export type HydratableSkill = Pick<
| "statsStars"
| "statsInstallsCurrent"
| "statsInstallsAllTime"
| "statsSkillsShInstalls"
| "statsGithubStars"
| "softDeletedAt"
| "moderationStatus"
| "moderationFlags"
@@ -149,7 +151,7 @@ export function toPublicSkill(skill: HydratableSkill | null | undefined): Public
if (!skill) return null;
if (!isPublicSkillDoc(skill)) return null;
const stats = {
downloads: readCanonicalStat(skill, "downloads"),
downloads: readPublicDownloads(skill),
stars: readCanonicalStat(skill, "stars"),
installs: readCanonicalStat(skill, "installsAllTime"),
versions: skill.stats?.versions ?? 0,
+9
View File
@@ -222,6 +222,15 @@ export const RETENTION_POLICIES = {
skillsShCatalogScanAttempts: permanent(
"Exact-hash skills.sh scan attempts are durable audit and deduplication history.",
),
skillsShMirrorControls: permanent("Durable skills.sh external mirror operator controls."),
skillsShMirrorRuns: permanent("Skills.sh external mirror cursor and reconciliation history."),
skillsShMirrorSourcePages: permanent(
"Immutable authenticated leaderboard source pages retained for mirror provenance.",
),
skillsShMirrorDigests: permanent("Normalized skills.sh external search digests."),
skillsShMirrorDetails: permanent("Bounded skills.sh external detail content."),
skillsShMirrorFacets: permanent("Indexed skills.sh external category and topic metadata."),
skillsShMirrorConflicts: permanent("Skills.sh external observation conflict audit history."),
publisherAbuseScoreRuns: permanent("Abuse scoring run history."),
publisherAbuseTemporalScanSamples: ephemeral(
"Exact temporal percentile samples are temporary scan working state.",
+4
View File
@@ -58,6 +58,8 @@ function makeSkillDoc(overrides: Record<string, unknown> = {}) {
statsStars: 5,
statsInstallsCurrent: 10,
statsInstallsAllTime: 100,
statsSkillsShInstalls: 8,
statsGithubStars: 250,
stats: {
downloads: 42,
installsCurrent: 10,
@@ -88,6 +90,8 @@ describe("extractDigestFields", () => {
expect(digest.statsStars).toBe(5);
expect(digest.statsInstallsCurrent).toBe(10);
expect(digest.statsInstallsAllTime).toBe(100);
expect(digest.statsSkillsShInstalls).toBe(8);
expect(digest.statsGithubStars).toBe(250);
expect(digest.recommendedScore).toBe(
computeRecommendationScore({ downloads: 42, installs: 100, stars: 5 }),
);
+2
View File
@@ -47,6 +47,8 @@ const SHARED_KEYS = [
"statsStars",
"statsInstallsCurrent",
"statsInstallsAllTime",
"statsSkillsShInstalls",
"statsGithubStars",
"softDeletedAt",
"moderationStatus",
"moderationFlags",
+49
View File
@@ -0,0 +1,49 @@
import { describe, expect, it } from "vitest";
import {
buildExternalSkillMetricPatch,
readPublicDownloads,
readSkillMetricSources,
} from "./skillStats";
const skill = {
statsDownloads: 40,
statsStars: 7,
statsInstallsCurrent: 3,
statsInstallsAllTime: 12,
statsSkillsShInstalls: 9,
statsGithubStars: 250,
stats: {
downloads: 40,
stars: 7,
installsCurrent: 3,
installsAllTime: 12,
},
};
describe("source-attributed skill metrics", () => {
it("keeps every source independently attributable", () => {
expect(readSkillMetricSources(skill)).toEqual({
clawHubDownloads: 40,
skillsShInstalls: 9,
openClawInstallsCurrent: 3,
openClawInstallsAllTime: 12,
githubStars: 250,
bookmarks: 7,
});
});
it("adds skills.sh installs to public downloads without double-counting OpenClaw installs", () => {
expect(readPublicDownloads(skill)).toBe(49);
});
it("builds a source-only refresh patch and preserves unknown GitHub popularity", () => {
expect(
buildExternalSkillMetricPatch({
skillsShInstalls: 11,
githubStars: undefined,
}),
).toEqual({
statsSkillsShInstalls: 11,
});
});
});
+38
View File
@@ -17,8 +17,20 @@ export type SkillStatReadable = {
statsStars?: number;
statsInstallsCurrent?: number;
statsInstallsAllTime?: number;
statsSkillsShInstalls?: number;
statsGithubStars?: number;
};
type ExternalSkillMetricSnapshot = {
skillsShInstalls?: number;
githubStars?: number;
};
function nonNegativeCount(value: number | undefined): number {
if (typeof value !== "number" || !Number.isFinite(value)) return 0;
return Math.max(0, Math.trunc(value));
}
/**
* Read the canonical value of a migrated stat field from a skill document.
*
@@ -42,6 +54,32 @@ export function readCanonicalStat(
return typeof skill[topLevelKey] === "number" ? skill[topLevelKey]! : (skill.stats[field] ?? 0);
}
export function readSkillMetricSources(skill: SkillStatReadable) {
return {
clawHubDownloads: readCanonicalStat(skill, "downloads"),
skillsShInstalls: nonNegativeCount(skill.statsSkillsShInstalls),
openClawInstallsCurrent: readCanonicalStat(skill, "installsCurrent"),
openClawInstallsAllTime: readCanonicalStat(skill, "installsAllTime"),
githubStars: nonNegativeCount(skill.statsGithubStars),
bookmarks: readCanonicalStat(skill, "stars"),
};
}
export function readPublicDownloads(skill: SkillStatReadable): number {
return readCanonicalStat(skill, "downloads") + nonNegativeCount(skill.statsSkillsShInstalls);
}
export function buildExternalSkillMetricPatch(snapshot: ExternalSkillMetricSnapshot) {
return {
...(snapshot.skillsShInstalls === undefined
? {}
: { statsSkillsShInstalls: nonNegativeCount(snapshot.skillsShInstalls) }),
...(snapshot.githubStars === undefined
? {}
: { statsGithubStars: nonNegativeCount(snapshot.githubStars) }),
};
}
export function applySkillStatDeltas(skill: Doc<"skills">, deltas: SkillStatDeltas) {
const currentDownloads = readCanonicalStat(skill, "downloads");
const currentStars = readCanonicalStat(skill, "stars");
+3 -1
View File
@@ -15,6 +15,7 @@ export type SkillsShCatalogFixtureRow = {
claimPublisherHandle?: string;
sourceContentHash: string;
installs: number;
githubStars?: number;
};
type SkillsShCatalogFixture = {
@@ -61,7 +62,8 @@ const PATRICK_HTML_CANARY: SkillsShCatalogFixtureRow = {
githubContentHash: "a47adb2c1ac33c088f664b5187971b63d2b958a7b9f01516d26005ca941a108f",
claimPublisherHandle: "patrick-erichsen",
sourceContentHash: "a47adb2c1ac33c088f664b5187971b63d2b958a7b9f01516d26005ca941a108f",
installs: 0,
installs: 17,
githubStars: 321,
};
const FROZEN_ROWS = frozenSnapshot.rows satisfies SkillsShCatalogFixtureRow[];
+12 -2
View File
@@ -283,7 +283,12 @@ const listPublishedPageHandler = (
paginationOpts: { cursor: string | null; numItems: number };
},
{
page: Array<{ displayName: string; href: string; kind: "skill" | "plugin" }>;
page: Array<{
displayName: string;
href: string;
kind: "skill" | "plugin";
downloads: number;
}>;
continueCursor: string;
isDone: boolean;
}
@@ -3139,6 +3144,7 @@ describe("publishers membership controls", () => {
slug: "demo",
displayName: "Demo Skill",
moderationStatus: "active",
statsSkillsShInstalls: 6,
stats: { downloads: 10, stars: 1, installsCurrent: 1, installsAllTime: 2 },
updatedAt: 5,
};
@@ -3188,6 +3194,7 @@ describe("publishers membership controls", () => {
});
expect(firstPage.page.map((item) => item.kind)).toEqual(["skill"]);
expect(firstPage.page[0]?.downloads).toBe(16);
expect(firstPage.continueCursor).toBe("1");
expect(firstPage.isDone).toBe(false);
expect(secondPage.page.map((item) => item.kind)).toEqual(["plugin"]);
@@ -3231,6 +3238,7 @@ describe("publishers membership controls", () => {
displayName: "Modern Low",
moderationStatus: "active",
statsDownloads: 7,
statsSkillsShInstalls: 100,
stats: { downloads: 7, stars: 1, installsCurrent: 1, installsAllTime: 2 },
updatedAt: 6,
};
@@ -3283,6 +3291,7 @@ describe("publishers membership controls", () => {
paginationOpts: { cursor: null, numItems: 1 },
});
legacyLookup.first.mockResolvedValue(null);
expect(firstPage.page[0]?.displayName).toBe("Legacy High");
const secondPage = await listPublishedPageHandler(ctx as never, {
handle: "legacy",
kind: "skill",
@@ -3463,6 +3472,7 @@ describe("publishers membership controls", () => {
ownerPublisherId: "publishers:openclaw",
softDeletedAt: undefined,
moderationStatus: "active",
statsSkillsShInstalls: 8,
stats: { downloads: 42, stars: 2, installsCurrent: 4, installsAllTime: 7 },
};
const pkg = {
@@ -3509,7 +3519,7 @@ describe("publishers membership controls", () => {
skills: 1,
packages: 1,
installs: 12,
downloads: 50,
downloads: 58,
stars: 3,
});
});
+5 -5
View File
@@ -44,7 +44,7 @@ import {
isHandleReservedForAnotherUser,
} from "./lib/reservedHandles";
import { syncSkillSearchDigestForSkill } from "./lib/skillSearchDigest";
import { readCanonicalStat } from "./lib/skillStats";
import { readCanonicalStat, readPublicDownloads } from "./lib/skillStats";
import { adjustUserSkillStatsForSkillChange } from "./lib/userSkillStats";
const MAX_PUBLIC_PUBLISHER_LIST_LIMIT = 500;
@@ -388,7 +388,7 @@ function getIndexedPublisherStatsFromRows(rows: PublisherPublishedRows): Publish
for (const skill of rows.skills) {
stats.skills += 1;
stats.installs += readCanonicalStat(skill, "installsAllTime");
stats.downloads += readCanonicalStat(skill, "downloads");
stats.downloads += readPublicDownloads(skill);
stats.stars += readCanonicalStat(skill, "stars");
}
@@ -416,7 +416,7 @@ function getPublisherPublishedItems(
inferredCategories: skill.inferredCategories,
latestVersionId: skill.latestVersionId,
inferredFromVersionId: skill.inferredFromVersionId,
downloads: readCanonicalStat(skill, "downloads"),
downloads: readPublicDownloads(skill),
installs: readCanonicalStat(skill, "installsAllTime"),
})),
...rows.packages.map((pkg) => ({
@@ -551,7 +551,7 @@ function toPublisherSkillCatalogItem(
icon: skill.icon ?? null,
href: `/${encodeURIComponent(publisher.handle)}/${encodeURIComponent(skill.slug)}`,
installs: readCanonicalStat(skill, "installsAllTime"),
downloads: readCanonicalStat(skill, "downloads"),
downloads: readPublicDownloads(skill),
stars: readCanonicalStat(skill, "stars"),
isOfficial: publisherOfficial || Boolean(skill.badges?.official),
updatedAt: skill.updatedAt,
@@ -2578,7 +2578,7 @@ export const listStarredPage = query({
icon: skill.icon ?? null,
href: `/${encodeURIComponent(ownerHandle)}/${encodeURIComponent(skill.slug)}`,
installs: readCanonicalStat(skill, "installsAllTime"),
downloads: readCanonicalStat(skill, "downloads"),
downloads: readPublicDownloads(skill),
stars: readCanonicalStat(skill, "stars"),
isOfficial: official || Boolean(skill.badges?.official),
updatedAt: skill.updatedAt,
+260
View File
@@ -921,6 +921,8 @@ const skills = defineTable({
statsStars: v.optional(v.number()),
statsInstallsCurrent: v.optional(v.number()),
statsInstallsAllTime: v.optional(v.number()),
statsSkillsShInstalls: v.optional(v.number()),
statsGithubStars: v.optional(v.number()),
installBackfill: v.optional(
v.object({
modelVersion: v.string(),
@@ -1347,6 +1349,8 @@ const skillSearchDigest = defineTable({
statsStars: v.optional(v.number()),
statsInstallsCurrent: v.optional(v.number()),
statsInstallsAllTime: v.optional(v.number()),
statsSkillsShInstalls: v.optional(v.number()),
statsGithubStars: v.optional(v.number()),
recommendedScore: v.optional(v.number()),
recommendedScoreVersion: v.optional(v.number()),
softDeletedAt: v.optional(v.number()),
@@ -2916,6 +2920,7 @@ const skillsShCatalogEntries = defineTable({
githubContentHash: v.optional(v.string()),
sourceContentHash: v.string(),
installs: v.number(),
githubStars: v.optional(v.number()),
sourceSnapshotId: v.string(),
reconciliation: v.optional(
v.object({
@@ -2996,6 +3001,254 @@ const skillsShCatalogScanAttempts = defineTable({
.index("by_status_and_created_at", ["status", "createdAt"])
.index("by_dispatch_kind_and_status_and_created_at", ["dispatchKind", "status", "createdAt"]);
const skillsShMirrorControls = defineTable({
key: v.literal("global"),
enabled: v.boolean(),
paused: v.boolean(),
maxRowsPerRun: v.number(),
maxRowsPerBatch: v.number(),
maxDetailBytes: v.number(),
updatedBy: v.string(),
reason: v.string(),
updatedAt: v.number(),
}).index("by_key", ["key"]);
const skillsShMirrorRunCountsValidator = v.object({
observed: v.number(),
inserted: v.number(),
updated: v.number(),
unchanged: v.number(),
rejected: v.number(),
quarantined: v.optional(v.number()),
quarantinedPreserved: v.optional(v.number()),
conflicts: v.number(),
detailsInserted: v.number(),
detailsUpdated: v.number(),
detailsUnchanged: v.number(),
detailsMissing: v.number(),
detailsTruncated: v.number(),
tombstoned: v.number(),
reactivated: v.number(),
scansPlanned: v.literal(0),
scansAdmitted: v.literal(0),
});
const skillsShMirrorRuns = defineTable({
snapshotId: v.string(),
sourceSnapshotHash: v.optional(v.string()),
sourceCaptureWrites: v.optional(v.number()),
status: v.union(
v.literal("running"),
v.literal("paused"),
v.literal("reconciling"),
v.literal("completed"),
v.literal("failed"),
v.literal("canceled"),
),
sourceTotal: v.number(),
sourcePageSize: v.number(),
sourceMeasuredAt: v.string(),
page: v.number(),
offset: v.number(),
batchLeaseToken: v.optional(v.string()),
batchLeaseExpiresAt: v.optional(v.number()),
reconcileCursor: v.optional(v.string()),
counts: skillsShMirrorRunCountsValidator,
operations: v.object({
functionCalls: v.number(),
dbReads: v.number(),
dbWrites: v.number(),
sourceRequests: v.number(),
sourceBytes: v.number(),
}),
actor: v.string(),
reason: v.string(),
startedAt: v.number(),
completedAt: v.optional(v.number()),
updatedAt: v.number(),
})
.index("by_started_at", ["startedAt"])
.index("by_status_and_updated_at", {
fields: ["status", "updatedAt"],
});
const skillsShMirrorSourcePages = defineTable({
snapshotHash: v.string(),
page: v.number(),
sourceTotal: v.number(),
pageLength: v.number(),
hasMore: v.boolean(),
identityHash: v.string(),
contentHash: v.string(),
sourceBytes: v.number(),
serializedBytes: v.number(),
rows: v.array(
v.object({
id: v.string(),
installUrl: v.union(v.string(), v.null()),
installs: v.number(),
name: v.string(),
slug: v.string(),
source: v.string(),
sourceType: v.string(),
url: v.string(),
}),
),
createdAt: v.number(),
}).index("by_snapshot_hash_and_page", ["snapshotHash", "page"]);
const skillsShMirrorUpstreamScannerValidator = v.object({
status: v.string(),
sourceCheckedAt: v.optional(v.string()),
sourceUrl: v.optional(v.string()),
});
const skillsShMirrorClassificationConfidenceValidator = v.union(
v.literal("high"),
v.literal("medium"),
v.literal("low"),
);
const skillsShMirrorDigests = defineTable({
externalId: v.string(),
sourceType: v.union(v.literal("github"), v.literal("well-known")),
upstreamSourceType: v.optional(v.string()),
owner: v.optional(v.string()),
repo: v.optional(v.string()),
sourceHost: v.optional(v.string()),
slug: v.string(),
normalizedSlug: v.string(),
normalizedSlugFirstToken: v.string(),
displayName: v.string(),
normalizedDisplayName: v.string(),
normalizedDisplayNameFirstToken: v.string(),
searchText: v.string(),
sourceUrl: v.string(),
canonicalRepoUrl: v.optional(v.string()),
githubPath: v.optional(v.string()),
githubCommit: v.optional(v.string()),
sourceContentHash: v.optional(v.string()),
upstreamInstalls: v.number(),
upstreamScanners: v.object({
genAgentTrustHub: skillsShMirrorUpstreamScannerValidator,
socket: skillsShMirrorUpstreamScannerValidator,
snyk: skillsShMirrorUpstreamScannerValidator,
}),
inferredCategories: v.optional(v.array(v.string())),
inferredTopics: v.optional(v.array(v.string())),
inferredCategoryConfidence: v.optional(skillsShMirrorClassificationConfidenceValidator),
inferredTopicConfidence: v.optional(skillsShMirrorClassificationConfidenceValidator),
inferredClassifierVersion: v.optional(v.string()),
inferredTopicClassifierVersion: v.optional(v.string()),
inferredInputHash: v.optional(v.string()),
inferredTopicInputHash: v.optional(v.string()),
inferredAt: v.optional(v.number()),
sourceFreshnessStatus: v.union(v.literal("observed-only"), v.literal("stale")),
staleQuarantineReason: v.optional(v.string()),
detailStatus: v.union(v.literal("available"), v.literal("missing")),
observationFingerprint: v.string(),
sourceSnapshotId: v.string(),
lastObservedRunId: v.id("skillsShMirrorRuns"),
active: v.boolean(),
publicVisible: v.literal(false),
installable: v.literal(false),
tombstonedAt: v.optional(v.number()),
firstObservedAt: v.number(),
lastObservedAt: v.number(),
createdAt: v.number(),
updatedAt: v.number(),
})
.index("by_external_id", ["externalId"])
.index("by_active_and_normalized_slug", {
fields: ["active", "normalizedSlug"],
})
.index("by_active_and_normalized_display_name", {
fields: ["active", "normalizedDisplayName"],
})
.index("by_active_and_normalized_slug_first_token", {
fields: ["active", "normalizedSlugFirstToken"],
})
.index("by_active_and_normalized_display_name_first_token", {
fields: ["active", "normalizedDisplayNameFirstToken"],
})
.index("by_active_and_source_type_and_owner_and_repo_and_external_id", {
fields: ["active", "sourceType", "owner", "repo", "externalId"],
})
.index("by_active_and_upstream_installs", {
fields: ["active", "upstreamInstalls"],
})
.index("by_source_type_and_external_id", {
fields: ["sourceType", "externalId"],
})
.index("by_last_observed_at", {
fields: ["lastObservedAt"],
})
.searchIndex("search_by_search_text", {
searchField: "searchText",
filterFields: ["active"],
});
const skillsShMirrorDetails = defineTable({
externalId: v.string(),
digestId: v.id("skillsShMirrorDigests"),
contentKind: v.union(v.literal("skill-md"), v.literal("readme")),
path: v.string(),
content: v.string(),
contentBytes: v.number(),
sourceBytes: v.number(),
sourceFileCount: v.number(),
truncated: v.boolean(),
sourceContentHash: v.optional(v.string()),
sourceSnapshotId: v.string(),
lastObservedRunId: v.id("skillsShMirrorRuns"),
createdAt: v.number(),
updatedAt: v.number(),
})
.index("by_external_id", ["externalId"])
.index("by_digest_id", {
fields: ["digestId"],
});
const skillsShMirrorFacets = defineTable({
digestId: v.id("skillsShMirrorDigests"),
externalId: v.string(),
kind: v.union(v.literal("category"), v.literal("topic")),
term: v.string(),
active: v.boolean(),
installs: v.number(),
createdAt: v.number(),
updatedAt: v.number(),
})
.index("by_digest_id_and_kind_and_term", ["digestId", "kind", "term"])
.index("by_active_and_kind_and_term_and_installs_and_external_id", [
"active",
"kind",
"term",
"installs",
"externalId",
]);
const skillsShMirrorConflicts = defineTable({
runId: v.id("skillsShMirrorRuns"),
externalId: v.string(),
kind: v.union(
v.literal("same-run-drift"),
v.literal("identity-mismatch"),
v.literal("source-quarantine"),
),
reason: v.optional(v.string()),
upstreamSourceType: v.optional(v.string()),
previousFingerprint: v.optional(v.string()),
observedFingerprint: v.string(),
page: v.number(),
offset: v.number(),
createdAt: v.number(),
})
.index("by_run_id", ["runId"])
.index("by_external_id_and_created_at", {
fields: ["externalId", "createdAt"],
});
const publisherAbuseScoreRuns = defineTable({
modelVersion: v.string(),
modelConfig: publisherAbuseModelConfigValidator,
@@ -3692,6 +3945,13 @@ export default defineSchema({
skillsShCatalogRuns,
skillsShCatalogEntries,
skillsShCatalogScanAttempts,
skillsShMirrorControls,
skillsShMirrorRuns,
skillsShMirrorSourcePages,
skillsShMirrorDigests,
skillsShMirrorDetails,
skillsShMirrorFacets,
skillsShMirrorConflicts,
publisherAbuseScoreRuns,
publisherAbuseTemporalScanSamples,
publisherAbuseTemporalScanCandidates,
+48
View File
@@ -2014,6 +2014,54 @@ describe("search helpers", () => {
expect(result.map((entry) => entry.skill.slug)).toEqual(["tool-installed", "tool-downloaded"]);
});
it("keeps skills.sh installs out of the native download ranking tie-breaker", async () => {
generateEmbeddingMock.mockResolvedValueOnce([0, 1, 2]);
const indexed = {
nativeDownloads: 10,
skill: makePublicSkill({
id: "skills:indexed",
slug: "tool-indexed",
displayName: "Tool",
downloads: 10_010,
installs: 0,
stars: 0,
}),
version: null,
ownerHandle: "owner",
owner: null,
};
const native = {
nativeDownloads: 20,
skill: makePublicSkill({
id: "skills:native",
slug: "tool-native",
displayName: "Tool",
downloads: 20,
installs: 0,
stars: 0,
}),
version: null,
ownerHandle: "owner",
owner: null,
};
const runQuery = vi
.fn()
.mockResolvedValueOnce(null) // getExactSkillSlugMatch
.mockResolvedValueOnce([]) // directPrefixSkillMatches
.mockResolvedValueOnce([indexed, native]); // lexicalFallbackSkills
const result = await searchSkillsHandler(
{
vectorSearch: vi.fn().mockResolvedValue([]),
runQuery,
},
{ query: "tool", limit: 2 },
);
expect(result.map((entry) => entry.skill.slug)).toEqual(["tool-native", "tool-indexed"]);
expect(result[0]).not.toHaveProperty("nativeDownloads");
});
it("uses digest doc instead of full skill doc in hydrateResults but revalidates the owner", async () => {
// Derive digest from makeSkillDoc so it stays in sync with schema changes.
const skillDoc = makeSkillDoc({
+11 -3
View File
@@ -35,6 +35,7 @@ import {
normalizeSkillSearchText,
} from "./lib/skillSearchDigest";
import { isSearchableSkillSlugShape, normalizeSkillSlug } from "./lib/skillSlugValidator";
import { readCanonicalStat } from "./lib/skillStats";
type OwnerInfo = { ownerHandle: string | null; owner: PublicPublisher | null };
@@ -75,6 +76,7 @@ async function withOfficialOwnerInfo(ctx: Pick<QueryCtx, "db">, ownerInfo: Owner
type SkillSearchEntry = {
embeddingId?: Id<"skillEmbeddings">;
nativeDownloads: number;
skill: NonNullable<ReturnType<typeof toPublicSkill>>;
version: Doc<"skillVersions"> | null;
ownerHandle: string | null;
@@ -89,7 +91,7 @@ type SearchResult = SkillSearchEntry &
SearchMatch & {
score: number;
};
type PublicSearchResult = SkillSearchEntry & {
type PublicSearchResult = Omit<SkillSearchEntry, "nativeDownloads"> & {
score: number;
};
@@ -239,7 +241,7 @@ function compareSkillTrustAndUsage(a: SkillSearchEntry, b: SkillSearchEntry) {
{ stars: a.skill.stats.stars, installsAllTime: a.skill.stats.installs },
{ stars: b.skill.stats.stars, installsAllTime: b.skill.stats.installs },
) ||
(b.skill.stats.downloads ?? 0) - (a.skill.stats.downloads ?? 0)
b.nativeDownloads - a.nativeDownloads
);
}
@@ -508,7 +510,9 @@ export const searchSkills: ReturnType<typeof action> = action({
b.skill.updatedAt - a.skill.updatedAt,
)
.slice(0, limit);
return rankedMatches.map(({ rankTier: _rankTier, ...entry }) => entry);
return rankedMatches.map(
({ nativeDownloads: _nativeDownloads, rankTier: _rankTier, ...entry }) => entry,
);
},
});
@@ -541,6 +545,7 @@ export const getExactSkillSlugMatch = internalQuery({
if (!publicSkill || !resolved.owner) return null;
const entry: SkillSearchEntry = {
nativeDownloads: readCanonicalStat(skill, "downloads"),
skill: publicSkill,
version: null as Doc<"skillVersions"> | null,
ownerHandle: resolved.ownerHandle,
@@ -851,6 +856,7 @@ export const directPrefixSkillMatches = internalQuery({
const publicSkill = toPublicSearchSkill(skill);
if (!publicSkill || !resolved.owner) return null;
return {
nativeDownloads: readCanonicalStat(skill, "downloads"),
skill: publicSkill,
version: null as Doc<"skillVersions"> | null,
ownerHandle: resolved.ownerHandle,
@@ -920,6 +926,7 @@ export const hydrateResults = internalQuery({
if (!publicSkill) return null;
return {
embeddingId,
nativeDownloads: readCanonicalStat(skill, "downloads"),
skill: publicSkill,
version: null as Doc<"skillVersions"> | null,
ownerHandle: resolved.ownerHandle,
@@ -1093,6 +1100,7 @@ export const lexicalFallbackSkills = internalQuery({
const publicSkill = toPublicSearchSkill(skill);
if (!publicSkill) return null;
return {
nativeDownloads: readCanonicalStat(skill, "downloads"),
skill: publicSkill,
version: null as Doc<"skillVersions"> | null,
ownerHandle: resolved.ownerHandle,
+32 -2
View File
@@ -208,7 +208,24 @@ describe("skills.listDashboardPaginated", () => {
it("paginates user dashboard skills through an active owner index", async () => {
vi.mocked(getAuthUserId).mockResolvedValue("users:owner" as never);
const { ctx, indexCalls } = makeCtx({
by_owner_active_updated: [makeSkill("slack")],
by_owner_active_updated: [
makeSkill("slack", {
statsDownloads: 12,
statsSkillsShInstalls: 8,
statsGithubStars: 99,
statsInstallsCurrent: 3,
statsInstallsAllTime: 7,
statsStars: 4,
stats: {
downloads: 12,
installsCurrent: 3,
installsAllTime: 7,
stars: 4,
versions: 1,
comments: 0,
},
}),
],
});
const result = await handler(
@@ -220,7 +237,20 @@ describe("skills.listDashboardPaginated", () => {
);
expect(indexCalls).toContain("by_owner_active_updated");
expect(result.page).toEqual([expect.objectContaining({ slug: "slack" })]);
expect(result.page).toEqual([
expect.objectContaining({
slug: "slack",
stats: expect.objectContaining({ downloads: 20 }),
metricSources: {
clawHubDownloads: 12,
skillsShInstalls: 8,
openClawInstallsCurrent: 3,
openClawInstallsAllTime: 7,
githubStars: 99,
bookmarks: 4,
},
}),
]);
});
it("includes linked-user legacy skills when paginating a personal publisher", async () => {
+29
View File
@@ -33,6 +33,7 @@ const listPackageCatalogPageHandler = (
family: "skill";
channel: "official" | "community";
isOfficial: boolean;
stats: { downloads: number };
}>;
isDone: boolean;
continueCursor: string;
@@ -344,6 +345,34 @@ describe("skills package catalog queries", () => {
]);
});
it("presents combined downloads without changing the native download index", async () => {
const indexNames: string[] = [];
const result = await listPackageCatalogPageHandler(
makeCtx(
[
{
page: [
makeDigest("indexed-skill", {
statsDownloads: 12,
statsSkillsShInstalls: 8,
}),
],
isDone: true,
continueCursor: "",
},
],
indexNames,
),
{
sort: "downloads",
paginationOpts: { cursor: null, numItems: 10 },
},
);
expect(indexNames).toContain("by_active_stats_downloads");
expect(result.page[0]?.stats.downloads).toBe(20);
});
it("normalizes and filters skill package catalog topics", async () => {
const indexNames: string[] = [];
const calendarSkill = makeDigest("calendar-skill", { topics: ["calendar"] });
+7 -3
View File
@@ -167,7 +167,7 @@ import {
upsertSkillSearchDigest,
} from "./lib/skillSearchDigest";
import { assertValidSkillSlug, normalizeSkillSlug } from "./lib/skillSlugValidator";
import { readCanonicalStat } from "./lib/skillStats";
import { readCanonicalStat, readPublicDownloads, readSkillMetricSources } from "./lib/skillStats";
import { normalizeSkillTags } from "./lib/skillTags";
import { runStaticPublishScan } from "./lib/staticPublishScan";
import { adjustUserSkillStatsForSkillChange } from "./lib/userSkillStats";
@@ -1984,6 +1984,7 @@ type DashboardSkillListItem = {
tags: Doc<"skills">["tags"];
badges: Doc<"skills">["badges"];
stats: Doc<"skills">["stats"];
metricSources: ReturnType<typeof readSkillMetricSources>;
moderationStatus?: Doc<"skills">["moderationStatus"];
moderationReason?: string;
moderationSummary?: string;
@@ -2377,7 +2378,7 @@ async function toDashboardSkillListItem(
const latestVersion = skill.latestVersionId ? await ctx.db.get(skill.latestVersionId) : null;
const stats = {
...skill.stats,
downloads: readCanonicalStat(skill, "downloads"),
downloads: readPublicDownloads(skill),
stars: readCanonicalStat(skill, "stars"),
installsCurrent: readCanonicalStat(skill, "installsCurrent"),
installsAllTime: readCanonicalStat(skill, "installsAllTime"),
@@ -2397,6 +2398,7 @@ async function toDashboardSkillListItem(
tags: skill.tags,
badges: skill.badges,
stats,
metricSources: readSkillMetricSources(skill),
moderationStatus: skill.moderationStatus,
moderationReason: skill.moderationReason,
moderationSummary: skill.moderationSummary,
@@ -6432,7 +6434,9 @@ async function toPublicSkillCatalogItem(
latestVersion: latestVersion?.version ?? null,
verificationTier: null,
stats: {
downloads: readDigestRankStat(digest, "downloads"),
// CLAW-561 changes presentation only. Download indexes and ranking remain
// native-only until a separately accepted indexed combined metric exists.
downloads: readPublicDownloads(digest),
installs: readDigestRankStat(digest, "installsAllTime"),
stars: readDigestRankStat(digest, "stars"),
versions: digest.stats.versions,
+132 -4
View File
@@ -19,6 +19,7 @@ import {
isExactSkillsShCatalogAttempt,
shouldPublishSkillsShCatalogEntry,
} from "./lib/skillsShCatalogPublication";
import { buildExternalSkillMetricPatch } from "./lib/skillStats";
import { validateFilePath } from "./lib/skillZip";
import { enqueueSkillsShCatalogScanRequest } from "./securityScan";
@@ -37,6 +38,10 @@ const MAX_WRITES_PER_BATCH = 100;
const MAX_SCAN_ADMISSIONS_PER_BATCH = 100;
const MAX_SCAN_ADMISSIONS_PER_RUN = 500;
const MAX_REAL_TEST_ADMISSIONS = 10;
const CLEARED_EXTERNAL_SKILL_METRICS = {
statsSkillsShInstalls: undefined,
statsGithubStars: undefined,
};
const MAX_DETERMINISTIC_COMPLETIONS_PER_BATCH = 50;
const fixtureIdValidator = v.union(
@@ -69,6 +74,7 @@ const stagingLiveRowValidator = v.object({
claimPublisherHandle: v.optional(v.string()),
sourceContentHash: v.string(),
installs: v.number(),
githubStars: v.optional(v.number()),
});
const sourceVerificationValidator = v.object({
githubOwnerId: v.number(),
@@ -168,8 +174,21 @@ async function reconcileNativeSkill(
const source = await ctx.db.get(skill.githubSourceId);
reads += 1;
if (source?.repo.trim().toLowerCase() !== `${row.owner}/${row.repo}`) continue;
const metricPatch = buildExternalSkillMetricPatch({
skillsShInstalls: row.installs,
githubStars: row.githubStars,
});
const metricPatchChanged = Object.entries(metricPatch).some(
([field, value]) => skill[field as keyof typeof skill] !== value,
);
return {
reads,
nativeMetricUpdate: metricPatchChanged
? {
skillId: skill._id,
patch: metricPatch,
}
: undefined,
reconciliation: {
kind: "exact-native" as const,
nativeSkillId: skill._id,
@@ -200,6 +219,54 @@ async function reconcileNativeSkill(
};
}
function nativeMetricSyncSkillIds(
existing: Doc<"skillsShCatalogEntries"> | null,
native: Awaited<ReturnType<typeof reconcileNativeSkill>>,
) {
const skillIds = new Set<Id<"skills">>();
if ("nativeMetricUpdate" in native && native.nativeMetricUpdate) {
skillIds.add(native.nativeMetricUpdate.skillId);
}
const previous = existing?.reconciliation;
if (
previous?.kind === "exact-native" &&
previous.nativeSkillId &&
(native.reconciliation.kind !== "exact-native" ||
native.reconciliation.nativeSkillId !== previous.nativeSkillId)
) {
skillIds.add(previous.nativeSkillId);
}
return [...skillIds];
}
export const syncNativeSkillMetricsFromCatalogEntryInternal = internalMutation({
args: {
entryId: v.id("skillsShCatalogEntries"),
skillId: v.id("skills"),
},
handler: async (ctx, args) => {
const [entry, skill] = await Promise.all([ctx.db.get(args.entryId), ctx.db.get(args.skillId)]);
if (!entry || !skill) return { applied: false };
const patch =
entry.reconciliation?.kind === "exact-native" &&
entry.reconciliation.nativeSkillId === args.skillId
? buildExternalSkillMetricPatch({
skillsShInstalls: entry.installs,
githubStars: entry.githubStars,
})
: CLEARED_EXTERNAL_SKILL_METRICS;
const changed = Object.entries(patch).some(
([field, value]) => skill[field as keyof typeof skill] !== value,
);
if (!changed) return { applied: false };
// The skills-table trigger in functions.ts refreshes the search digest from
// this patch, keeping the batch write budget independent of digest fan-out.
await ctx.db.patch(skill._id, patch);
return { applied: true };
},
});
function incrementReconciliationCounts(
counts: ReturnType<typeof normalizedCounts>,
reconciliation: Doc<"skillsShCatalogEntries">["reconciliation"],
@@ -950,6 +1017,7 @@ export const processStagingLiveBatchInternal = internalMutation({
}
const native = await reconcileNativeSkill(ctx, row, now);
readsUsed += native.reads;
const metricSkillIds = nativeMetricSyncSkillIds(existing, native);
const observationUnchanged = existing
? sameFixtureObservation(existing, row, native.reconciliation)
: false;
@@ -981,6 +1049,7 @@ export const processStagingLiveBatchInternal = internalMutation({
counts.observed += 1;
cursor += 1;
if (shouldPlanScan) counts.scansPlanned += 1;
let entryId = existing?._id;
if (existing) {
if (observationUnchanged) counts.unchanged += 1;
else {
@@ -1001,6 +1070,7 @@ export const processStagingLiveBatchInternal = internalMutation({
githubContentHash: row.githubContentHash,
sourceContentHash: row.sourceContentHash,
installs: row.installs,
githubStars: row.githubStars,
sourceSnapshotId: run.snapshotId,
reconciliation: native.reconciliation,
publicVisible: false,
@@ -1017,7 +1087,7 @@ export const processStagingLiveBatchInternal = internalMutation({
} else {
counts.wouldInsert += 1;
counts.inserted += 1;
await ctx.db.insert("skillsShCatalogEntries", {
entryId = await ctx.db.insert("skillsShCatalogEntries", {
externalId: row.externalId,
sourceKind: "staging-live",
githubOwnerId: row.githubOwnerId,
@@ -1032,6 +1102,7 @@ export const processStagingLiveBatchInternal = internalMutation({
githubContentHash: row.githubContentHash,
sourceContentHash: row.sourceContentHash,
installs: row.installs,
githubStars: row.githubStars,
sourceSnapshotId: run.snapshotId,
reconciliation: native.reconciliation,
publicVisible: false,
@@ -1043,6 +1114,18 @@ export const processStagingLiveBatchInternal = internalMutation({
});
}
writesUsed += 1;
if (entryId) {
for (const skillId of metricSkillIds) {
await ctx.scheduler.runAfter(
0,
internal.skillsShCatalog.syncNativeSkillMetricsFromCatalogEntryInternal,
{
entryId,
skillId,
},
);
}
}
}
if (cursor !== args.cursor + args.rows.length) {
@@ -1122,6 +1205,7 @@ export const processFixtureBatchInternal = internalMutation({
}
const native = await reconcileNativeSkill(ctx, row, now);
readsUsed += native.reads;
const metricSkillIds = nativeMetricSyncSkillIds(existing, native);
const observationUnchanged = existing
? sameFixtureObservation(existing, row, native.reconciliation)
@@ -1149,13 +1233,16 @@ export const processFixtureBatchInternal = internalMutation({
!existingAttempt &&
(!existing || contentChanged || existing.scanStatus !== "planned");
const entryWriteRequired = !run.dryRun;
if (entryWriteRequired && writesUsed + 2 > run.budgets.maxWritesPerBatch) break;
if (entryWriteRequired && writesUsed + 2 > run.budgets.maxWritesPerBatch) {
break;
}
incrementReconciliationCounts(counts, native.reconciliation);
counts.observed += 1;
cursor += 1;
entriesProcessed += 1;
if (shouldPlanScan) counts.scansPlanned += 1;
let entryId = existing?._id;
if (existing) {
if (observationUnchanged) counts.unchanged += 1;
else {
@@ -1177,6 +1264,7 @@ export const processFixtureBatchInternal = internalMutation({
githubContentHash: row.githubContentHash,
sourceContentHash: row.sourceContentHash,
installs: row.installs,
githubStars: row.githubStars,
sourceSnapshotId: fixture.snapshotId,
reconciliation: native.reconciliation,
// This gate has no publication seam; every catalog write reasserts dark visibility.
@@ -1193,12 +1281,24 @@ export const processFixtureBatchInternal = internalMutation({
});
writesUsed += 1;
}
if (entryId && entryWriteRequired) {
for (const skillId of metricSkillIds) {
await ctx.scheduler.runAfter(
0,
internal.skillsShCatalog.syncNativeSkillMetricsFromCatalogEntryInternal,
{
entryId,
skillId,
},
);
}
}
continue;
}
counts.wouldInsert += 1;
if (!run.dryRun) {
await ctx.db.insert("skillsShCatalogEntries", {
entryId = await ctx.db.insert("skillsShCatalogEntries", {
externalId: row.externalId,
sourceKind: fixture.sourceKind,
githubOwnerId: row.githubOwnerId,
@@ -1213,6 +1313,7 @@ export const processFixtureBatchInternal = internalMutation({
githubContentHash: row.githubContentHash,
sourceContentHash: row.sourceContentHash,
installs: row.installs,
githubStars: row.githubStars,
sourceSnapshotId: fixture.snapshotId,
reconciliation: native.reconciliation,
publicVisible: false,
@@ -1224,6 +1325,16 @@ export const processFixtureBatchInternal = internalMutation({
});
writesUsed += 1;
counts.inserted += 1;
for (const skillId of metricSkillIds) {
await ctx.scheduler.runAfter(
0,
internal.skillsShCatalog.syncNativeSkillMetricsFromCatalogEntryInternal,
{
entryId,
skillId,
},
);
}
}
}
@@ -2362,6 +2473,7 @@ export const rollbackFixtureRunInternal = internalMutation({
}
const fixture = getSkillsShCatalogFixture(run.fixtureId);
let deletedEntries = 0;
let nativeSkillsChanged = 0;
for (let index = 0; index < fixture.length; index += 1) {
const expected = normalizeIdentity(fixture.rowAt(index));
const entry = await ctx.db
@@ -2384,6 +2496,21 @@ export const rollbackFixtureRunInternal = internalMutation({
`Controlled canary has retained scan history: ${expected.externalId}`,
);
}
const nativeSkillId =
entry.reconciliation?.kind === "exact-native"
? entry.reconciliation.nativeSkillId
: undefined;
if (nativeSkillId) {
const nativeSkill = await ctx.db.get(nativeSkillId);
if (
nativeSkill &&
(nativeSkill.statsSkillsShInstalls !== undefined ||
nativeSkill.statsGithubStars !== undefined)
) {
await ctx.db.patch(nativeSkillId, CLEARED_EXTERNAL_SKILL_METRICS);
nativeSkillsChanged += 1;
}
}
await ctx.db.delete(entry._id);
deletedEntries += 1;
}
@@ -2392,7 +2519,7 @@ export const rollbackFixtureRunInternal = internalMutation({
actor: args.actor.trim(),
reason: args.reason.trim(),
deletedEntries,
nativeSkillsChanged: 0,
nativeSkillsChanged,
};
},
});
@@ -2760,6 +2887,7 @@ function sameFixtureObservation(
existing.githubContentHash === row.githubContentHash &&
existing.sourceContentHash === row.sourceContentHash &&
existing.installs === row.installs &&
existing.githubStars === row.githubStars &&
existingReconciliation?.kind === reconciliation.kind &&
existingReconciliation.nativeSkillId === reconciliation.nativeSkillId &&
existingReconciliation.nativeSlug === reconciliation.nativeSlug &&
+156 -9
View File
@@ -5,6 +5,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
import { api, internal } from "./_generated/api";
import type { Doc, Id } from "./_generated/dataModel";
import canarySkillMarkdown from "./fixtures/patrick-html-canary-SKILL.txt?raw";
import { extractDigestFields } from "./lib/skillSearchDigest";
import schema from "./schema";
const modules = import.meta.glob("./**/*.ts");
@@ -214,6 +215,11 @@ async function seedNativeSkill(
options: {
exactSource: boolean;
downloads: number;
bookmarks?: number;
openClawInstalls?: number;
skillsShInstalls?: number;
githubStars?: number;
seedDigest?: boolean;
},
) {
return await t.run(async (ctx) => {
@@ -250,26 +256,40 @@ async function seedNativeSkill(
tags: {},
moderationStatus: "active",
statsDownloads: options.downloads,
statsStars: 0,
statsInstallsCurrent: 0,
statsInstallsAllTime: 0,
statsStars: options.bookmarks ?? 0,
statsInstallsCurrent: options.openClawInstalls ?? 0,
statsInstallsAllTime: options.openClawInstalls ?? 0,
statsSkillsShInstalls: options.skillsShInstalls,
statsGithubStars: options.githubStars,
stats: {
downloads: options.downloads,
stars: 0,
installsCurrent: 0,
installsAllTime: 0,
stars: options.bookmarks ?? 0,
installsCurrent: options.openClawInstalls ?? 0,
installsAllTime: options.openClawInstalls ?? 0,
versions: 0,
comments: 0,
},
createdAt: 1,
updatedAt: 1,
});
if (options.seedDigest) {
const skill = await ctx.db.get(skillId);
if (!skill) throw new Error("seeded native skill missing");
await ctx.db.insert("skillSearchDigest", {
...extractDigestFields(skill),
ownerHandle: "native-owner",
ownerKind: "user",
ownerName: "Native Owner",
ownerDisplayName: "Native Owner",
});
}
return skillId;
});
}
describe("skills.sh controlled hidden metadata canary", () => {
afterEach(() => {
vi.useRealTimers();
vi.unstubAllEnvs();
});
@@ -324,17 +344,33 @@ describe("skills.sh controlled hidden metadata canary", () => {
).toHaveLength(0);
});
it("records an exact native match and preserves its downloads", async () => {
it("attaches upstream metrics to an exact native match without rewriting native counters", async () => {
vi.useFakeTimers();
useEnvironment(LOCAL_ENV);
const t = convexTest(schema, modules);
const nativeSkillId = await seedNativeSkill(t, { exactSource: true, downloads: 143 });
const nativeSkillId = await seedNativeSkill(t, {
exactSource: true,
downloads: 143,
bookmarks: 5,
openClawInstalls: 11,
skillsShInstalls: 2,
githubStars: 300,
seedDigest: true,
});
await configureCanary(t);
const { runId, run } = await runCanary(t);
await t.finishAllScheduledFunctions(vi.runAllTimers);
const readback = await t.query(internal.skillsShCatalog.getRunReconciliationInternal, {
runId,
});
const native = await t.run(async (ctx) => await ctx.db.get(nativeSkillId));
const digest = await t.run(async (ctx) =>
ctx.db
.query("skillSearchDigest")
.withIndex("by_skill", (q) => q.eq("skillId", nativeSkillId))
.unique(),
);
expect(run.counts).toMatchObject({
newExternal: 0,
@@ -352,10 +388,121 @@ describe("skills.sh controlled hidden metadata canary", () => {
expect(native).toMatchObject({
_id: nativeSkillId,
statsDownloads: 143,
stats: { downloads: 143 },
statsStars: 5,
statsInstallsCurrent: 11,
statsInstallsAllTime: 11,
statsSkillsShInstalls: 17,
statsGithubStars: 321,
stats: {
downloads: 143,
stars: 5,
installsCurrent: 11,
installsAllTime: 11,
},
githubCurrentCommit: CANARY_COMMIT,
githubCurrentContentHash: CANARY_CONTENT_HASH,
});
expect(digest).toMatchObject({
statsDownloads: 143,
statsSkillsShInstalls: 17,
statsGithubStars: 321,
});
});
it("clears exact-native upstream metrics when the controlled entry is removed", async () => {
vi.useFakeTimers();
useEnvironment(LOCAL_ENV);
const t = convexTest(schema, modules);
const nativeSkillId = await seedNativeSkill(t, {
exactSource: true,
downloads: 143,
bookmarks: 5,
openClawInstalls: 11,
seedDigest: true,
});
await configureCanary(t);
const { runId } = await runCanary(t);
await t.finishAllScheduledFunctions(vi.runAllTimers);
const rollback = await t.mutation(internal.skillsShCatalog.rollbackFixtureRunInternal, {
runId,
actor: "codex-test",
reason: "remove the controlled exact-native metadata",
confirm: "rollback-skills-sh-controlled-canary",
});
const native = await t.run(async (ctx) => await ctx.db.get(nativeSkillId));
const digest = await t.run(async (ctx) =>
ctx.db
.query("skillSearchDigest")
.withIndex("by_skill", (q) => q.eq("skillId", nativeSkillId))
.unique(),
);
expect(rollback).toMatchObject({
deletedEntries: 1,
nativeSkillsChanged: 1,
});
expect(native?.statsSkillsShInstalls).toBeUndefined();
expect(native?.statsGithubStars).toBeUndefined();
expect(digest?.statsSkillsShInstalls).toBeUndefined();
expect(digest?.statsGithubStars).toBeUndefined();
expect(native).toMatchObject({
statsDownloads: 143,
statsStars: 5,
statsInstallsCurrent: 11,
statsInstallsAllTime: 11,
stats: {
downloads: 143,
stars: 5,
installsCurrent: 11,
installsAllTime: 11,
},
});
});
it("clears upstream metrics when an exact native match becomes a route collision", async () => {
vi.useFakeTimers();
useEnvironment(LOCAL_ENV);
const t = convexTest(schema, modules);
const nativeSkillId = await seedNativeSkill(t, {
exactSource: true,
downloads: 143,
skillsShInstalls: 2,
githubStars: 300,
seedDigest: true,
});
await configureCanary(t);
await runCanary(t);
await t.finishAllScheduledFunctions(vi.runAllTimers);
await t.run(async (ctx) => {
await ctx.db.patch(nativeSkillId, {
githubCurrentCommit: "f".repeat(40),
});
});
const { run } = await runCanary(t);
await t.finishAllScheduledFunctions(vi.runAllTimers);
const native = await t.run(async (ctx) => await ctx.db.get(nativeSkillId));
const digest = await t.run(async (ctx) =>
ctx.db
.query("skillSearchDigest")
.withIndex("by_skill", (q) => q.eq("skillId", nativeSkillId))
.unique(),
);
expect(run.counts).toMatchObject({
exactNativeMatches: 0,
routeCollisions: 1,
});
expect(native?.statsSkillsShInstalls).toBeUndefined();
expect(native?.statsGithubStars).toBeUndefined();
expect(digest?.statsSkillsShInstalls).toBeUndefined();
expect(digest?.statsGithubStars).toBeUndefined();
expect(native).toMatchObject({
statsDownloads: 143,
stats: { downloads: 143 },
});
});
it("records a route collision without changing or attaching the native skill", async () => {
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -96,7 +96,8 @@ Stores your API token + cached registry URL.
### `star <skill>` / `unstar <skill>`
- Adds/removes a skill from your highlights.
- Adds/removes a skill from your Bookmarks. Command names remain `star` and
`unstar` for compatibility.
- Calls `POST /api/v1/stars/<slug>` and `DELETE /api/v1/stars/<slug>`.
- `--yes` skips confirmation.
+2 -1
View File
@@ -1590,7 +1590,8 @@ Response:
### `POST /api/v1/stars/{slug}` / `DELETE /api/v1/stars/{slug}`
Add/remove a star (highlights). Both endpoints are idempotent.
Add/remove a Bookmark. The legacy `stars` route and response field names remain
for compatibility. Both endpoints are idempotent.
Responses:
+3 -3
View File
@@ -22,7 +22,7 @@ test.skip(
test.setTimeout(180_000);
async function gotoUntilStarButtonReady(page: Page, detailPath: string): Promise<Locator> {
const starButton = page.getByRole("button", { name: "Star skill" });
const starButton = page.getByRole("button", { name: "Bookmark skill" });
let lastError: unknown;
for (let attempt = 1; attempt <= 4; attempt += 1) {
@@ -96,14 +96,14 @@ test("starring a skill survives refresh with the synchronized count", async ({
await starButton.click();
const unstarButton = page.getByRole("button", { name: "Unstar skill" });
const unstarButton = page.getByRole("button", { name: "Remove bookmark" });
await expect(unstarButton).toBeVisible({ timeout: 30_000 });
await expect(unstarButton).toContainText("1", { timeout: 30_000 });
await page.reload({ waitUntil: "domcontentloaded" });
await waitForHydration(page);
const refreshedUnstarButton = page.getByRole("button", { name: "Unstar skill" });
const refreshedUnstarButton = page.getByRole("button", { name: "Remove bookmark" });
await expect(refreshedUnstarButton).toBeVisible({ timeout: 30_000 });
await expect(refreshedUnstarButton).toContainText("1", { timeout: 30_000 });
+2
View File
@@ -73,6 +73,7 @@
"skills-sh:capture-500": "bun scripts/skills-sh-catalog/capture-frozen-snapshot.ts",
"skills-sh:prove-500": "bun scripts/skills-sh-catalog/prove-500.ts",
"skills-sh:prove-canary": "CLAWHUB_TEST_CATALOG_MODE=controlled-canary bun scripts/skills-sh-catalog/run-test-gate.ts",
"skills-sh:prove-mirror": "bun scripts/skills-sh-catalog/prove-mirror-test.ts",
"skills:install": "npx --yes skills@1.5.16 add openclaw/design-system --skill openclaw-design openclaw-brand openclaw-design-system openclaw-marketing-pages openclaw-design-audit --agent codex --copy --yes",
"test": "vitest run",
"test:e2e": "vitest run -c vitest.e2e.config.ts",
@@ -128,6 +129,7 @@
"mermaid": "^11.16.0",
"mime": "4.1.0",
"monaco-editor": "0.56.0",
"parse5": "8.0.1",
"pino": "10.3.1",
"react": "19.2.7",
"react-dom": "19.2.7",
@@ -0,0 +1,352 @@
import { describe, expect, it } from "vitest";
import {
buildMirrorStepRequest,
buildMirrorProofHeaders,
capturedMirrorSourceRunId,
findCompletedLiveMirrorRun,
findRecoverableMirrorRun,
mirrorRateLimitRetryDelayMs,
reconcileMirrorRunToCompletion,
resolveCompletedLiveMirrorRun,
mirrorRunFromPayload,
mirrorRunAccounting,
} from "./prove-mirror-request";
describe("skills.sh mirror proof request headers", () => {
it("carries the Test deployment protection bypass without changing operator auth", () => {
expect(buildMirrorProofHeaders("operator-token", " bypass-secret ")).toEqual({
Authorization: "Bearer operator-token",
"Content-Type": "application/json",
"x-vercel-protection-bypass": "bypass-secret",
});
});
it("omits the bypass header outside protected deployments", () => {
expect(buildMirrorProofHeaders("operator-token")).toEqual({
Authorization: "Bearer operator-token",
"Content-Type": "application/json",
});
});
it("selects the newest durable active run for interruption recovery", () => {
expect(
findRecoverableMirrorRun({
runs: [
{
runId: "completed-run",
status: "completed",
page: 20,
offset: 0,
sourceTotal: 9_571,
sourcePageSize: 500,
sourceMeasuredAt: "2026-07-22T20:14:00.000Z",
startedAt: 1,
},
{
runId: "active-run",
snapshotId: "skills-sh:2026-07-22T21:18:13.365Z:9571",
status: "running",
page: 0,
offset: 50,
sourceTotal: 9_571,
sourcePageSize: 500,
sourceMeasuredAt: "2026-07-22T21:18:13.365Z",
startedAt: 2,
},
],
}),
).toEqual({
runId: "active-run",
snapshotId: "skills-sh:2026-07-22T21:18:13.365Z:9571",
status: "running",
page: 0,
offset: 50,
sourceTotal: 9_571,
sourcePageSize: 500,
sourceMeasuredAt: "2026-07-22T21:18:13.365Z",
startedAt: 2,
});
});
it("normalizes direct and nested mirror run responses", () => {
expect(mirrorRunFromPayload({ runId: "run", status: "completed" }, "reconcile")).toEqual({
runId: "run",
status: "completed",
});
expect(
mirrorRunFromPayload(
{ run: { runId: "run", status: "reconciling" }, cursor: "next" },
"reconcile",
),
).toEqual({
runId: "run",
status: "reconciling",
});
expect(() => mirrorRunFromPayload({ runId: "run" }, "start-replay")).toThrow(
'start-replay mirror response lacks run status: {"runId":"run"}',
);
});
it("keeps a resumed captured run on the replay operation and exact cursor", () => {
expect(
buildMirrorStepRequest({
runId: "captured-run",
page: 1,
offset: 50,
capturedSource: {
externalIds: Array.from({ length: 175 }, (_, index) => `owner/repo/skill-${index}`),
sourcePageSize: 100,
},
}),
).toMatchObject({
operation: "step-replay",
runId: "captured-run",
page: 1,
offset: 50,
pageLength: 75,
hasMore: false,
sourceTotal: 175,
externalIds: Array.from({ length: 25 }, (_, index) => `owner/repo/skill-${index + 150}`),
});
});
it("keeps live source steps bound to the server-side run", () => {
expect(
buildMirrorStepRequest({
runId: "live-run",
page: 3,
offset: 50,
}),
).toEqual({
operation: "step",
runId: "live-run",
page: 3,
offset: 50,
});
});
it("normalizes reconciliation responses through completion", async () => {
const responses = [
{ run: { runId: "run", status: "reconciling", page: 20, offset: 0 } },
{ runId: "run", status: "completed", page: 20, offset: 0 },
];
const reconcile = vi.fn(async () => responses.shift()!);
await expect(
reconcileMirrorRunToCompletion(
{ runId: "run", status: "reconciling", page: 20, offset: 0 },
reconcile,
),
).resolves.toEqual({
run: { runId: "run", status: "completed", page: 20, offset: 0 },
reconciliationBatches: 2,
});
expect(reconcile).toHaveBeenCalledTimes(2);
});
it("ties a captured replay recovery to its completed authenticated source run", () => {
const liveRun = {
runId: "live-run",
snapshotId: "skills-sh:2026-07-22T21:18:13.365Z:9571",
status: "completed",
page: 20,
offset: 0,
sourceTotal: 9_571,
sourcePageSize: 500,
sourceMeasuredAt: "2026-07-22T21:18:13.365Z",
startedAt: 1,
completedAt: 2,
counts: { observed: 9_571 },
operations: { sourceRequests: 18_360 },
};
const payload = { runs: [{ ...liveRun, status: "running" }, liveRun] };
expect(capturedMirrorSourceRunId("skills-sh-captured:live-run")).toBe("live-run");
expect(capturedMirrorSourceRunId("skills-sh:live-run")).toBeNull();
expect(findCompletedLiveMirrorRun(payload, "live-run")).toEqual(liveRun);
expect(findCompletedLiveMirrorRun(liveRun, "live-run")).toEqual(liveRun);
expect(findCompletedLiveMirrorRun(payload, "missing")).toBeNull();
});
it("resolves a completed captured ancestor to its authenticated live source run", async () => {
const liveRun = {
runId: "live-run",
snapshotId: "skills-sh:2026-07-22T21:18:13.365Z:9571",
status: "completed" as const,
page: 20,
offset: 0,
sourceTotal: 9_571,
sourcePageSize: 500,
sourceMeasuredAt: "2026-07-22T21:18:13.365Z",
startedAt: 1,
completedAt: 2,
counts: { observed: 9_571 },
operations: { sourceRequests: 18_360 },
};
const capturedRun = {
...liveRun,
runId: "captured-run",
snapshotId: "skills-sh-captured:live-run",
startedAt: 3,
completedAt: 4,
};
const readRun = vi.fn(async (runId: string) => {
expect(runId).toBe("live-run");
return liveRun;
});
await expect(
resolveCompletedLiveMirrorRun({
payload: { runs: [capturedRun] },
runId: "captured-run",
readRun,
}),
).resolves.toEqual(liveRun);
expect(readRun).toHaveBeenCalledTimes(1);
});
it("bounds cyclic captured-run lineage", async () => {
const completedRun = {
status: "completed" as const,
page: 20,
offset: 0,
sourceTotal: 9_571,
sourcePageSize: 500,
sourceMeasuredAt: "2026-07-22T21:18:13.365Z",
startedAt: 1,
completedAt: 2,
counts: { observed: 9_571 },
operations: { sourceRequests: 18_360 },
};
const first = {
...completedRun,
runId: "first",
snapshotId: "skills-sh-captured:second",
};
const second = {
...completedRun,
runId: "second",
snapshotId: "skills-sh-captured:first",
};
const readRun = vi.fn(async (runId: string) => (runId === "first" ? first : second));
await expect(
resolveCompletedLiveMirrorRun({
payload: { runs: [first] },
runId: "first",
readRun,
}),
).resolves.toBeNull();
expect(readRun).toHaveBeenCalledTimes(1);
});
it("treats a missing captured ancestor as stale lineage", async () => {
const capturedRun = {
runId: "captured-run",
snapshotId: "skills-sh-captured:missing-live-run",
status: "completed" as const,
page: 20,
offset: 0,
sourceTotal: 9_571,
sourcePageSize: 500,
sourceMeasuredAt: "2026-07-22T21:18:13.365Z",
startedAt: 1,
completedAt: 2,
counts: { observed: 9_571 },
operations: { sourceRequests: 18_360 },
};
const readRun = vi.fn(async () => null);
await expect(
resolveCompletedLiveMirrorRun({
payload: { runs: [capturedRun] },
runId: "captured-run",
readRun,
}),
).resolves.toBeNull();
});
it("fails closed without canceling when captured lineage exceeds the read bound", async () => {
const completedRun = {
status: "completed" as const,
page: 20,
offset: 0,
sourceTotal: 9_571,
sourcePageSize: 500,
sourceMeasuredAt: "2026-07-22T21:18:13.365Z",
startedAt: 1,
completedAt: 2,
counts: { observed: 9_571 },
operations: { sourceRequests: 18_360 },
};
const runs = new Map(
Array.from({ length: 9 }, (_, index) => [
`captured-${index}`,
{
...completedRun,
runId: `captured-${index}`,
snapshotId: `skills-sh-captured:captured-${index + 1}`,
},
]),
);
await expect(
resolveCompletedLiveMirrorRun({
payload: { runs: [runs.get("captured-0")] },
runId: "captured-0",
readRun: async (runId) => runs.get(runId) ?? null,
}),
).rejects.toThrow("captured mirror lineage exceeded 8 runs");
});
it("bounds rate-limit recovery delays while preserving Retry-After", () => {
expect(mirrorRateLimitRetryDelayMs(429, "17", 0)).toBe(17_000);
expect(mirrorRateLimitRetryDelayMs(429, "120", 0)).toBe(120_000);
expect(mirrorRateLimitRetryDelayMs(429, null, 3)).toBe(8_000);
expect(mirrorRateLimitRetryDelayMs(502, "17", 0)).toBeNull();
});
it("accounts fail-closed identity conflicts separately from source quarantines", () => {
expect(
mirrorRunAccounting(9_571, {
conflicts: 205,
rejected: 205,
quarantined: 166,
}),
).toEqual({
accepted: 9_366,
rejected: 205,
quarantined: 166,
});
});
it("rejects unrecorded failures and impossible quarantine counts", () => {
expect(() =>
mirrorRunAccounting(100, {
rejected: 0,
quarantined: 0,
}),
).toThrow("mirror conflicts must be a nonnegative integer");
expect(() =>
mirrorRunAccounting(100, {
conflicts: 0,
rejected: Number.NaN,
quarantined: 0,
}),
).toThrow("mirror rejected must be a nonnegative integer");
expect(() =>
mirrorRunAccounting(100, {
conflicts: 4,
rejected: 5,
quarantined: 3,
}),
).toThrow("mirror conflict accounting");
expect(() =>
mirrorRunAccounting(100, {
conflicts: 5,
rejected: 5,
quarantined: 6,
}),
).toThrow("mirror quarantine accounting");
});
});
@@ -0,0 +1,268 @@
export function buildMirrorProofHeaders(
operatorAuthorization: string,
vercelAutomationBypassSecret?: string,
) {
return {
Authorization: `Bearer ${operatorAuthorization}`,
"Content-Type": "application/json",
...(vercelAutomationBypassSecret?.trim()
? { "x-vercel-protection-bypass": vercelAutomationBypassSecret.trim() }
: {}),
};
}
export function mirrorRateLimitRetryDelayMs(
status: number,
retryAfterHeader: string | null,
attempt: number,
) {
if (status !== 429) return null;
const retryAfterSeconds = retryAfterHeader === null ? Number.NaN : Number(retryAfterHeader);
const requestedMs =
Number.isFinite(retryAfterSeconds) && retryAfterSeconds >= 0
? retryAfterSeconds * 1_000
: 1_000 * 2 ** attempt;
return Math.max(1_000, requestedMs);
}
export function mirrorRunAccounting(total: number, counts: Record<string, number>) {
if (!Number.isSafeInteger(total) || total < 0) {
throw new Error("mirror source total must be a nonnegative integer");
}
const requiredCount = (name: "conflicts" | "rejected" | "quarantined") => {
const value = counts[name];
if (!Number.isSafeInteger(value) || value < 0) {
throw new Error(`mirror ${name} must be a nonnegative integer`);
}
return value;
};
const rejected = requiredCount("rejected");
const quarantined = requiredCount("quarantined");
const conflicts = requiredCount("conflicts");
if (conflicts !== rejected) {
throw new Error("mirror conflict accounting does not equal rejected rows");
}
if (quarantined > rejected) {
throw new Error("mirror quarantine accounting exceeds rejected rows");
}
const accepted = total - rejected;
if (accepted < 0) {
throw new Error("mirror rejected rows exceed the source total");
}
return { accepted, rejected, quarantined };
}
export function mirrorRunFromPayload(
payload: Record<string, unknown>,
operation: string,
): Record<string, unknown> {
const nested = payload.run;
const candidates = [
payload,
nested && typeof nested === "object" && !Array.isArray(nested)
? (nested as Record<string, unknown>)
: null,
];
for (const candidate of candidates) {
if (
candidate &&
["running", "paused", "reconciling", "completed", "failed"].includes(String(candidate.status))
) {
return candidate;
}
}
const diagnostic = JSON.stringify(payload).slice(0, 1_000);
throw new Error(`${operation} mirror response lacks run status: ${diagnostic}`);
}
type CapturedMirrorStepSource = {
externalIds: string[];
sourcePageSize: number;
};
export function buildMirrorStepRequest(args: {
runId: string;
page: number;
offset: number;
capturedSource?: CapturedMirrorStepSource | null;
}) {
if (!args.capturedSource) {
return {
operation: "step" as const,
runId: args.runId,
page: args.page,
offset: args.offset,
};
}
const { externalIds, sourcePageSize } = args.capturedSource;
const pageStart = args.page * sourcePageSize;
const pageLength = Math.min(sourcePageSize, externalIds.length - pageStart);
const rowStart = pageStart + args.offset;
const rows = externalIds.slice(rowStart, rowStart + 50);
if (pageLength < 1 || rows.length < 1) {
throw new Error(`captured mirror replay has no rows for cursor ${args.page}:${args.offset}`);
}
return {
operation: "step-replay" as const,
runId: args.runId,
page: args.page,
offset: args.offset,
pageLength,
hasMore: pageStart + sourcePageSize < externalIds.length,
sourceTotal: externalIds.length,
externalIds: rows,
};
}
export async function reconcileMirrorRunToCompletion(
initialRun: Record<string, unknown>,
reconcile: () => Promise<Record<string, unknown>>,
) {
let run = initialRun;
let reconciliationBatches = 0;
while (run.status === "reconciling") {
if (reconciliationBatches >= 1_000) {
throw new Error("mirror reconciliation exceeded 1000 bounded batches");
}
run = mirrorRunFromPayload(await reconcile(), "reconcile");
reconciliationBatches += 1;
}
return { run, reconciliationBatches };
}
export type RecoverableMirrorRun = {
runId: string;
snapshotId: string;
status: "running" | "paused" | "reconciling";
page: number;
offset: number;
sourceTotal: number;
sourcePageSize: number;
sourceMeasuredAt: string;
startedAt: number;
};
export function findRecoverableMirrorRun(
payload: Record<string, unknown>,
): RecoverableMirrorRun | null {
if (!Array.isArray(payload.runs)) return null;
for (const value of payload.runs) {
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
const run = value as Record<string, unknown>;
if (
!["running", "paused", "reconciling"].includes(String(run.status)) ||
typeof run.runId !== "string" ||
typeof run.snapshotId !== "string" ||
typeof run.page !== "number" ||
typeof run.offset !== "number" ||
typeof run.sourceTotal !== "number" ||
typeof run.sourcePageSize !== "number" ||
typeof run.sourceMeasuredAt !== "string" ||
typeof run.startedAt !== "number"
) {
continue;
}
return run as RecoverableMirrorRun;
}
return null;
}
export type CompletedLiveMirrorRun = {
runId: string;
snapshotId: string;
status: "completed";
page: number;
offset: number;
sourceTotal: number;
sourcePageSize: number;
sourceMeasuredAt: string;
startedAt: number;
completedAt: number;
counts: Record<string, number>;
operations: Record<string, number>;
};
type CompletedMirrorRun = CompletedLiveMirrorRun;
export function capturedMirrorSourceRunId(snapshotId: string) {
const prefix = "skills-sh-captured:";
return snapshotId.startsWith(prefix) ? snapshotId.slice(prefix.length) || null : null;
}
function findCompletedMirrorRun(
payload: unknown,
runId?: string | null,
): CompletedMirrorRun | null {
const root =
payload && typeof payload === "object" && !Array.isArray(payload)
? (payload as Record<string, unknown>)
: null;
const candidates = root ? [root, ...(Array.isArray(root.runs) ? root.runs : [])] : [];
for (const value of candidates) {
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
const run = value as Record<string, unknown>;
if (
run.status !== "completed" ||
typeof run.runId !== "string" ||
(runId && run.runId !== runId) ||
typeof run.snapshotId !== "string" ||
typeof run.page !== "number" ||
typeof run.offset !== "number" ||
typeof run.sourceTotal !== "number" ||
typeof run.sourcePageSize !== "number" ||
typeof run.sourceMeasuredAt !== "string" ||
typeof run.startedAt !== "number" ||
typeof run.completedAt !== "number" ||
!run.counts ||
typeof run.counts !== "object" ||
Array.isArray(run.counts) ||
!run.operations ||
typeof run.operations !== "object" ||
Array.isArray(run.operations)
) {
continue;
}
return run as CompletedMirrorRun;
}
return null;
}
export function findCompletedLiveMirrorRun(
payload: unknown,
runId?: string | null,
): CompletedLiveMirrorRun | null {
const run = findCompletedMirrorRun(payload, runId);
if (
!run ||
!run.snapshotId.startsWith("skills-sh:") ||
run.snapshotId.startsWith("skills-sh-captured:")
) {
return null;
}
return run;
}
export async function resolveCompletedLiveMirrorRun(args: {
payload: unknown;
runId: string;
readRun: (runId: string) => Promise<unknown>;
}) {
const seen = new Set<string>();
let runId = args.runId;
let payload = args.payload;
for (let depth = 0; depth < 8; depth += 1) {
if (seen.has(runId)) return null;
seen.add(runId);
let run = findCompletedMirrorRun(payload, runId);
if (!run) {
payload = await args.readRun(runId);
run = findCompletedMirrorRun(payload, runId);
}
if (!run) return null;
const capturedRunId = capturedMirrorSourceRunId(run.snapshotId);
if (!capturedRunId) return findCompletedLiveMirrorRun(run, runId);
runId = capturedRunId;
payload = {};
}
throw new Error("captured mirror lineage exceeded 8 runs");
}
@@ -0,0 +1,776 @@
#!/usr/bin/env bun
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import {
SKILLS_SH_MIRROR_CONTROLLED_EXTERNAL_IDS,
parseSkillsShMirrorProofSnapshotId,
} from "../../server/skillsShCatalogSource";
import {
buildMirrorStepRequest,
buildMirrorProofHeaders,
capturedMirrorSourceRunId,
findRecoverableMirrorRun,
mirrorRateLimitRetryDelayMs,
reconcileMirrorRunToCompletion,
resolveCompletedLiveMirrorRun,
mirrorRunFromPayload,
mirrorRunAccounting,
type CompletedLiveMirrorRun,
type RecoverableMirrorRun,
} from "./prove-mirror-request";
const OUTPUT_PATH = resolve("proof/claw-563/skills-sh-mirror-test-proof.json");
const PROJECTED_SCALE = 700_000;
const MAX_RATE_LIMIT_RETRIES_PER_RUN = 30;
const MAX_RATE_LIMIT_WAIT_MS_PER_RUN = 30 * 60 * 1_000;
const MAX_DETAIL_PAGE_ROWS = 50;
function requireEnv(name: string) {
const value = process.env[name]?.trim();
if (!value) throw new Error(`${name} is required`);
return value;
}
const targetUrl = requireEnv("CLAWHUB_TEST_MIRROR_GATE_URL");
const operatorAuthorization = requireEnv("CLAWHUB_TEST_OPERATOR_TOKEN");
const vercelAutomationBypassSecret = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
async function callRaw(body: Record<string, unknown>) {
const startedAt = performance.now();
const response = await fetch(targetUrl, {
method: "POST",
headers: buildMirrorProofHeaders(operatorAuthorization, vercelAutomationBypassSecret),
body: JSON.stringify(body),
});
const text = await response.text();
let payload: Record<string, unknown>;
try {
payload = JSON.parse(text) as Record<string, unknown>;
} catch {
payload = { text };
}
return {
ok: response.ok,
status: response.status,
elapsedMs: performance.now() - startedAt,
payload,
retryAfter: response.headers.get("retry-after"),
};
}
function callFailure(body: Record<string, unknown>, result: Awaited<ReturnType<typeof callRaw>>) {
return new Error(
`${String(body.operation)} returned HTTP ${result.status}: ${JSON.stringify(result.payload)}`,
);
}
async function call(body: Record<string, unknown>) {
const result = await callRaw(body);
if (!result.ok) throw callFailure(body, result);
return result;
}
function requireRunId(payload: Record<string, unknown>) {
if (typeof payload.runId !== "string") throw new Error("mirror start did not return runId");
return payload.runId;
}
async function runMirror(
reason: string,
provePauseResume: boolean,
recoverableRun: RecoverableMirrorRun | null = null,
capturedSource: CapturedMirrorSource | null = null,
) {
const attemptStartedAt = Date.now();
const start = recoverableRun
? { payload: recoverableRun }
: capturedSource
? await call({
operation: "start-replay",
reason,
capturedRunId: capturedSource.capturedRunId,
sourceTotal: capturedSource.externalIds.length,
sourcePageSize: capturedSource.sourcePageSize,
sourceMeasuredAt: capturedSource.sourceMeasuredAt,
})
: await call({ operation: "start", reason });
const startRun = mirrorRunFromPayload(
start.payload,
recoverableRun ? "recover" : capturedSource ? "start-replay" : "start",
);
const startPayload = start.payload as Record<string, unknown>;
const runId = requireRunId(startRun);
const proofSource =
capturedSource === null
? parseSkillsShMirrorProofSnapshotId(String(startRun.snapshotId))
: null;
let page = recoverableRun?.page ?? 0;
let offset = recoverableRun?.offset ?? 0;
let steps = 0;
let pauseProof: Record<string, unknown> | null = null;
let run = startRun;
let recovery: Record<string, unknown> | null = null;
let rateLimitRetries = 0;
let rateLimitWaitMs = 0;
if (recoverableRun) {
recovery = {
runId,
status: recoverableRun.status,
cursor: { page, offset },
interruptedAtLeastOnce: true,
};
if (recoverableRun.status === "paused") {
run = (
await call({
operation: "resume",
runId,
reason: "CLAW-563 interrupted run recovery",
})
).payload;
}
}
while (run.status === "running") {
const stepRequest = buildMirrorStepRequest({
runId,
page,
offset,
capturedSource,
});
const step = await callRaw(stepRequest);
if (!step.ok) {
const delayMs = mirrorRateLimitRetryDelayMs(step.status, step.retryAfter, rateLimitRetries);
if (
delayMs === null ||
rateLimitRetries >= MAX_RATE_LIMIT_RETRIES_PER_RUN ||
rateLimitWaitMs + delayMs > MAX_RATE_LIMIT_WAIT_MS_PER_RUN
) {
throw callFailure(stepRequest, step);
}
rateLimitRetries += 1;
rateLimitWaitMs += delayMs;
await new Promise((resolve) => setTimeout(resolve, delayMs));
continue;
}
run = mirrorRunFromPayload(step.payload, String(stepRequest.operation));
steps += 1;
if (typeof run.page !== "number" || typeof run.offset !== "number") {
throw new Error("mirror step did not return a durable cursor");
}
page = run.page;
offset = run.offset;
if (provePauseResume && steps === 1) {
const pause = await call({
operation: "pause",
runId,
reason: "CLAW-563 deliberate pause proof",
});
const blocked = await callRaw(
buildMirrorStepRequest({
runId,
page,
offset,
capturedSource,
}),
);
if (blocked.ok || !JSON.stringify(blocked.payload).includes("paused")) {
throw new Error("paused mirror step did not fail closed");
}
const resume = await call({
operation: "resume",
runId,
reason: "CLAW-563 exact cursor resume proof",
});
pauseProof = {
pause: pause.payload,
blockedStatus: blocked.status,
blockedPayload: blocked.payload,
resume: resume.payload,
resumeCursor: { page, offset },
};
}
}
const reconciliation = await reconcileMirrorRunToCompletion(run, async () => {
const result = await call({ operation: "reconcile", runId, limit: 250 });
return result.payload;
});
run = reconciliation.run;
const reconciliationBatches = reconciliation.reconciliationBatches;
if (run.status !== "completed") {
throw new Error(`mirror run ended in unexpected status ${String(run.status)}`);
}
return {
runId,
run,
source: {
total: startPayload.sourceTotal,
catalogTotal: proofSource?.catalogTotal ?? startPayload.sourceTotal,
controlledExternalIds: proofSource?.controlledExternalIds ?? [],
controlledOverlayExternalIds: proofSource?.controlledOverlayExternalIds ?? [],
controlledSupplementExternalIds: proofSource?.controlledSupplementExternalIds ?? [],
evidence: proofSource?.evidence ?? null,
capture:
startPayload.sourceCapture &&
typeof startPayload.sourceCapture === "object" &&
!Array.isArray(startPayload.sourceCapture)
? startPayload.sourceCapture
: null,
measuredAt: startPayload.sourceMeasuredAt,
pageSize: startPayload.sourcePageSize,
},
recovery,
steps,
reconciliationBatches,
pauseProof,
rateLimitRecovery: {
retries: rateLimitRetries,
waitedMs: rateLimitWaitMs,
retryBudget: MAX_RATE_LIMIT_RETRIES_PER_RUN,
waitBudgetMs: MAX_RATE_LIMIT_WAIT_MS_PER_RUN,
},
elapsedMs: Date.now() - (recoverableRun?.startedAt ?? attemptStartedAt),
attemptElapsedMs: Date.now() - attemptStartedAt,
};
}
async function collectPages(
operation: "page" | "detail-page" | "facet-page",
validate: (document: Record<string, unknown>) => void,
) {
const documents: Record<string, unknown>[] = [];
let cursor: string | null = null;
let calls = 0;
let count = 0;
let serializedBytes = 0;
do {
const result = await call({
operation,
cursor,
limit: operation === "detail-page" ? MAX_DETAIL_PAGE_ROWS : 500,
});
const page = result.payload.page;
if (!Array.isArray(page)) throw new Error(`${operation} did not return a page`);
for (const document of page as Record<string, unknown>[]) {
validate(document);
count += 1;
serializedBytes += Buffer.byteLength(JSON.stringify(document), "utf8");
if (operation === "page") documents.push(document);
}
calls += 1;
cursor =
result.payload.isDone === true
? null
: typeof result.payload.continueCursor === "string"
? result.payload.continueCursor
: null;
if (result.payload.isDone !== true && cursor === null) {
throw new Error(`${operation} did not return a continuation cursor`);
}
} while (cursor);
return {
count,
calls,
serializedBytes,
documents,
};
}
type MirrorRunProof = Awaited<ReturnType<typeof runMirror>>;
type CapturedMirrorSource = {
capturedRunId: string;
externalIds: string[];
sourceMeasuredAt: string;
sourcePageSize: number;
};
function completedLiveRunProof(run: CompletedLiveMirrorRun): MirrorRunProof {
const proofSource = parseSkillsShMirrorProofSnapshotId(run.snapshotId);
return {
runId: run.runId,
run,
source: {
total: run.sourceTotal,
catalogTotal: proofSource.catalogTotal,
controlledExternalIds: proofSource.controlledExternalIds,
controlledOverlayExternalIds: proofSource.controlledOverlayExternalIds,
controlledSupplementExternalIds: proofSource.controlledSupplementExternalIds,
evidence: proofSource.evidence ?? null,
capture: null,
measuredAt: run.sourceMeasuredAt,
pageSize: run.sourcePageSize,
},
recovery: {
runId: run.runId,
status: run.status,
cursor: { page: run.page, offset: run.offset },
completedBeforeProofContinuation: true,
},
steps: 0,
reconciliationBatches: 0,
pauseProof: null,
rateLimitRecovery: {
retries: 0,
waitedMs: 0,
retryBudget: MAX_RATE_LIMIT_RETRIES_PER_RUN,
waitBudgetMs: MAX_RATE_LIMIT_WAIT_MS_PER_RUN,
},
elapsedMs: run.completedAt - run.startedAt,
attemptElapsedMs: 0,
};
}
function runCounts(runResult: MirrorRunProof) {
const counts = runResult.run.counts;
if (!counts || typeof counts !== "object") throw new Error("mirror run lacks counts");
return counts as Record<string, number>;
}
function assertZeroCounts(counts: Record<string, number>, names: string[]) {
for (const name of names) {
if (counts[name] !== 0) throw new Error(`expected ${name}=0, received ${String(counts[name])}`);
}
}
function assertRunProof(runResult: MirrorRunProof, mode: "first" | "identical") {
const counts = runCounts(runResult);
const total = Number(runResult.source.total);
const { accepted } = mirrorRunAccounting(total, counts);
if (counts.observed !== total) {
throw new Error(`mirror observed ${String(counts.observed)} of ${total} source rows`);
}
assertZeroCounts(counts, ["scansPlanned", "scansAdmitted"]);
if ((counts.inserted ?? 0) + (counts.updated ?? 0) + (counts.unchanged ?? 0) !== accepted) {
throw new Error(`${mode} run digest accounting does not equal the source total`);
}
if (
(counts.detailsInserted ?? 0) +
(counts.detailsUpdated ?? 0) +
(counts.detailsUnchanged ?? 0) +
(counts.detailsMissing ?? 0) !==
accepted
) {
throw new Error(`${mode} run detail accounting does not equal the source total`);
}
if (mode === "identical") {
assertZeroCounts(counts, [
"inserted",
"updated",
"detailsInserted",
"detailsUpdated",
"tombstoned",
"reactivated",
]);
if (counts.unchanged !== accepted) throw new Error("identical rerun changed a digest");
}
}
function validateDigestIsolation(document: Record<string, unknown>) {
if (
document.active !== true ||
document.publicVisible !== false ||
document.installable !== false
) {
throw new Error(`mirror digest isolation failed: ${String(document.externalId)}`);
}
}
function validateDigest(document: Record<string, unknown>) {
validateDigestIsolation(document);
for (const field of [
"normalizedSlug",
"normalizedSlugFirstToken",
"normalizedDisplayName",
"normalizedDisplayNameFirstToken",
"searchText",
"upstreamSourceType",
"inferredClassifierVersion",
"inferredTopicClassifierVersion",
"inferredInputHash",
"inferredTopicInputHash",
]) {
if (typeof document[field] !== "string" || !document[field]) {
throw new Error(`mirror digest lacks ${field}: ${String(document.externalId)}`);
}
}
if (
!Array.isArray(document.inferredCategories) ||
document.inferredCategories.length < 1 ||
document.inferredCategories.length > 3 ||
!Array.isArray(document.inferredTopics) ||
document.inferredTopics.length > 5 ||
!["high", "medium", "low"].includes(String(document.inferredCategoryConfidence)) ||
!["high", "medium", "low"].includes(String(document.inferredTopicConfidence)) ||
typeof document.inferredAt !== "number"
) {
throw new Error(`mirror digest lacks bounded inference: ${String(document.externalId)}`);
}
const scanners = document.upstreamScanners;
if (!scanners || typeof scanners !== "object" || Array.isArray(scanners)) {
throw new Error(`mirror digest lacks upstream scanners: ${String(document.externalId)}`);
}
for (const provider of ["genAgentTrustHub", "socket", "snyk"]) {
const scanner = (scanners as Record<string, unknown>)[provider];
if (
!scanner ||
typeof scanner !== "object" ||
Array.isArray(scanner) ||
typeof (scanner as Record<string, unknown>).status !== "string"
) {
throw new Error(`mirror digest lacks ${provider} status: ${String(document.externalId)}`);
}
}
}
function validateFacet(document: Record<string, unknown>) {
if (
document.active !== true ||
!["category", "topic"].includes(String(document.kind)) ||
typeof document.term !== "string" ||
!document.term ||
typeof document.externalId !== "string" ||
typeof document.installs !== "number"
) {
throw new Error(`mirror facet is invalid: ${String(document.externalId)}`);
}
}
function validateDetail(document: Record<string, unknown>) {
const content = document.content;
const contentBytes = document.contentBytes;
if (
typeof content !== "string" ||
typeof contentBytes !== "number" ||
contentBytes > 64 * 1024 ||
Buffer.byteLength(content, "utf8") !== contentBytes
) {
throw new Error(`mirror detail exceeds its boundary: ${String(document.externalId)}`);
}
}
function percentile(values: number[], percentileValue: number) {
const sorted = [...values].sort((left, right) => left - right);
return sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * percentileValue))] ?? 0;
}
async function measureReads(externalIds: string[]) {
const results = [];
for (const externalId of externalIds) {
const samples = [];
for (let attempt = 0; attempt < 5; attempt += 1) {
const read = await call({ operation: "read", externalId });
if (!read.payload.digest) throw new Error(`indexed read missed ${externalId}`);
samples.push(read.elapsedMs);
}
results.push({
externalId,
samplesMs: samples,
medianMs: percentile(samples, 0.5),
p95Ms: percentile(samples, 0.95),
});
}
return results;
}
const cronSource = await readFile(resolve("convex/crons.ts"), "utf8");
if (cronSource.includes("skillsShMirror") || cronSource.includes("skills-sh/mirror")) {
throw new Error("skills.sh mirror scheduler reference exists");
}
const proofStartedAt = Date.now();
let proof: Record<string, unknown>;
await call({
operation: "configure",
enabled: true,
reason: "CLAW-563 authenticated leaderboard foundation proof",
});
try {
const isolationBefore = (await call({ operation: "isolation" })).payload;
const statusBefore = (await call({ operation: "status" })).payload;
let recoverableRun = findRecoverableMirrorRun(statusBefore);
const recoveredLiveRunId = recoverableRun
? capturedMirrorSourceRunId(recoverableRun.snapshotId)
: null;
let recoveredNormalizationRun = recoveredLiveRunId ? recoverableRun : null;
const completedLiveRun = recoveredLiveRunId
? await resolveCompletedLiveMirrorRun({
payload: statusBefore,
runId: recoveredLiveRunId,
readRun: async (runId) => (await call({ operation: "run", runId })).payload,
})
: null;
let discardedStaleRecovery: Record<string, unknown> | null = null;
if (recoveredNormalizationRun && !completedLiveRun) {
const discarded = (
await call({
operation: "discard",
runId: recoveredNormalizationRun.runId,
reason: `discard stale captured recovery without live source ${recoveredLiveRunId}`,
})
).payload;
if (discarded.status !== "canceled" || discarded.runId !== recoveredNormalizationRun.runId) {
throw new Error(
`discard stale captured recovery returned unexpected payload: ${JSON.stringify(discarded)}`,
);
}
discardedStaleRecovery = {
runId: recoveredNormalizationRun.runId,
missingLiveRunId: recoveredLiveRunId,
status: discarded.status,
};
recoverableRun = null;
recoveredNormalizationRun = null;
}
const firstRun = completedLiveRun
? completedLiveRunProof(completedLiveRun)
: await runMirror(
"CLAW-563 complete authenticated leaderboard enumeration",
true,
recoveredNormalizationRun ? null : recoverableRun,
);
const conflictsAfterFirstRun = (
await call({ operation: "conflicts", runId: firstRun.runId, limit: 50 })
).payload;
const liveRunConflicts = Array.isArray(conflictsAfterFirstRun.conflicts)
? (conflictsAfterFirstRun.conflicts as Record<string, unknown>[])
: [];
const capturedAfterLive = await collectPages("page", validateDigestIsolation);
const liveCapturedSource: CapturedMirrorSource = {
capturedRunId: firstRun.runId,
externalIds: capturedAfterLive.documents.map((document) => {
if (typeof document.externalId !== "string") {
throw new Error("captured mirror digest lacks externalId");
}
return document.externalId;
}),
sourceMeasuredAt: String(firstRun.source.measuredAt),
sourcePageSize: Number(firstRun.source.pageSize),
};
const normalizationRun = await runMirror(
"CLAW-563 authenticated captured snapshot normalization",
false,
recoveredNormalizationRun,
liveCapturedSource,
);
const capturedAfterNormalization = await collectPages("page", validateDigest);
const normalizedCapturedSource: CapturedMirrorSource = {
...liveCapturedSource,
capturedRunId: normalizationRun.runId,
externalIds: capturedAfterNormalization.documents.map((document) => {
if (typeof document.externalId !== "string") {
throw new Error("normalized mirror digest lacks externalId");
}
return document.externalId;
}),
};
const identicalRerun = await runMirror(
"CLAW-563 authenticated captured snapshot identical rerun",
false,
null,
normalizedCapturedSource,
);
assertRunProof(firstRun, "first");
assertRunProof(normalizationRun, "first");
assertRunProof(identicalRerun, "identical");
const [digests, details, facets] = await Promise.all([
collectPages("page", validateDigest),
collectPages("detail-page", validateDetail),
collectPages("facet-page", validateFacet),
]);
const firstCounts = runCounts(firstRun);
const sourceEvidence = firstRun.source.evidence;
if (!sourceEvidence) {
throw new Error("live mirror run lacks durable source pagination and field evidence");
}
if (sourceEvidence.fields.normalizedUpstreamTaxonomyFields.length > 0) {
throw new Error(
"skills.sh now exposes normalized taxonomy fields; mirror ingestion must prefer them",
);
}
const sourceSnapshotHash = parseSkillsShMirrorProofSnapshotId(
String(firstRun.run.snapshotId),
).sourceSnapshotHash;
if (!sourceSnapshotHash) {
throw new Error("live mirror run lacks a durable source snapshot hash");
}
const sourceCaptureSummary = (firstRun.source.capture ??
(await call({ operation: "source-summary", snapshotHash: sourceSnapshotHash }))
.payload) as Record<string, unknown>;
const sourceCapture = {
...sourceCaptureSummary,
actualDbWrites: Number(
sourceCaptureSummary.requestDbWrites ?? firstRun.run.sourceCaptureWrites ?? 0,
),
};
const firstAccounting = mirrorRunAccounting(Number(firstRun.source.total), firstCounts);
const quarantinedCount = firstAccounting.quarantined;
const quarantinedPreservedCount = firstCounts.quarantinedPreserved ?? 0;
const acceptedCount = firstAccounting.accepted;
const expectedDigestCount = acceptedCount + quarantinedPreservedCount;
if (digests.count !== expectedDigestCount) {
throw new Error(
`mirror digest count ${digests.count} does not match accepted or preserved total ${expectedDigestCount}`,
);
}
if (facets.count < digests.count) {
throw new Error("mirror facets do not cover every digest category fallback");
}
const digestExternalIds = new Set(digests.documents.map((document) => document.externalId));
for (const externalId of SKILLS_SH_MIRROR_CONTROLLED_EXTERNAL_IDS) {
if (!digestExternalIds.has(externalId)) {
throw new Error(`mirror proof lacks controlled supplement ${externalId}`);
}
}
const quarantineSamples = liveRunConflicts
.filter(
(conflict) => conflict.runId === firstRun.runId && conflict.kind === "source-quarantine",
)
.slice(0, 50);
if (quarantineSamples.length !== Math.min(quarantinedCount, 50)) {
throw new Error("mirror status did not expose the latest run quarantine samples");
}
const sampleIndexes = [0, Math.floor(digests.count / 2), digests.count - 1];
const sampleExternalIds = sampleIndexes.map((index) => {
const externalId = digests.documents[index]?.externalId;
if (typeof externalId !== "string") throw new Error("mirror digest lacks externalId");
return externalId;
});
const indexedReads = await measureReads(sampleExternalIds);
const isolationAfter = (await call({ operation: "isolation" })).payload;
if (JSON.stringify(isolationBefore) !== JSON.stringify(isolationAfter)) {
throw new Error("mirror proof changed scan isolation state");
}
const firstOperations = firstRun.run.operations as Record<string, number>;
const serializedStorageBytes =
digests.serializedBytes +
details.serializedBytes +
facets.serializedBytes +
Number(sourceCaptureSummary.serializedBytes ?? 0);
const rows = digests.count;
const sourceRows = Number(firstRun.source.total);
const sourceMeasurementRequests = sourceEvidence.pagination.requestedPages.length + 4;
const perRow = {
serializedStorageBytes: serializedStorageBytes / rows,
dbWrites: (firstOperations.dbWrites + sourceCapture.actualDbWrites) / sourceRows,
dbReads:
(firstOperations.dbReads + Number(sourceCaptureSummary.pageDocuments ?? 0)) / sourceRows,
sourceRequests: (firstOperations.sourceRequests + sourceMeasurementRequests) / sourceRows,
sourceBytes:
(firstOperations.sourceBytes + Number(sourceCaptureSummary.sourceBytes ?? 0)) / sourceRows,
};
proof = {
generatedAt: new Date().toISOString(),
target: {
environment: "permanent Test",
gateUrl: targetUrl,
productionWrites: 0,
schedules: 0,
publicVisibility: false,
installability: false,
publisherAttachment: false,
scanPlanning: false,
scanAdmission: false,
},
sourceContract: {
authenticated: "Vercel OIDC",
scope: "paginated skills.sh leaderboard",
databaseEnumeration: "out of v1 scope; exhaustive feed not required",
endpointExhausted: true,
databaseCoverageProven: false,
measuredLeaderboardTotal: firstRun.source.catalogTotal,
proofSourceTotal: firstRun.source.total,
measurementRequests: sourceMeasurementRequests,
endpointExhaustion: sourceEvidence.pagination,
rawFieldSets: sourceEvidence.fields,
taxonomyEnrichment: {
upstreamNormalizedFields: sourceEvidence.fields.normalizedUpstreamTaxonomyFields,
fallbackApplied: true,
fallbackGenerator: "ClawHub local deterministic catalog classifier",
llmGenerated: false,
},
controlledSupplement: {
authority: "immutable GitHub content",
environment: "permanent Test only",
productionFullSyncStrategy: false,
requiredCount: firstRun.source.controlledExternalIds.length,
observedRequiredExternalIds: firstRun.source.controlledExternalIds,
overlayExternalIds: firstRun.source.controlledOverlayExternalIds,
supplementExternalIds: firstRun.source.controlledSupplementExternalIds,
contractRequiredExternalIds: SKILLS_SH_MIRROR_CONTROLLED_EXTERNAL_IDS,
},
durableSourceCapture: sourceCapture,
pageSize: firstRun.source.pageSize,
measuredAt: firstRun.source.measuredAt,
documentedRateLimitRequestsPerMinute: 600,
fetchPolicy: {
mirrorBatchRows: 50,
sourcePageRows: 500,
detailAndPageConcurrency: 8,
minimumApiRequestIntervalMs: 125,
identityPage:
"512 KiB structural HTML fallback only for exact no-install well-known owner/repo rows",
recovery: "durable cursor retry with bounded Retry-After recovery",
},
},
discardedStaleRecovery,
firstRun,
normalizationRun,
identicalRerun,
quarantine: {
rejected: firstCounts.rejected,
quarantined: quarantinedCount,
preserved: quarantinedPreservedCount,
samples: quarantineSamples,
},
storage: {
digests: {
count: digests.count,
serializedBytes: digests.serializedBytes,
pageReads: digests.calls,
},
details: {
count: details.count,
serializedBytes: details.serializedBytes,
pageReads: details.calls,
},
facets: {
count: facets.count,
serializedBytes: facets.serializedBytes,
pageReads: facets.calls,
},
totalSerializedBytes: serializedStorageBytes,
bytesPerSourceRow: perRow.serializedStorageBytes,
},
isolation: {
before: isolationBefore,
after: isolationAfter,
unchanged: true,
},
indexedReads,
projectedHypotheticalDatabaseScale: {
assumedRows: PROJECTED_SCALE,
enumerationAvailable: false,
proofStatus: "cost projection only, not a completed database sync",
serializedStorageBytes: Math.ceil(perRow.serializedStorageBytes * PROJECTED_SCALE),
dbWrites: Math.ceil(perRow.dbWrites * PROJECTED_SCALE),
dbReads: Math.ceil(perRow.dbReads * PROJECTED_SCALE),
sourceRequests: Math.ceil(perRow.sourceRequests * PROJECTED_SCALE),
sourceBytes: Math.ceil(perRow.sourceBytes * PROJECTED_SCALE),
},
runtime: {
elapsedMs: Date.now() - proofStartedAt,
},
};
} finally {
const disabled = await callRaw({
operation: "configure",
enabled: false,
reason: "CLAW-563 proof cleanup: retain mirror hidden and paused",
});
if (!disabled.ok) {
throw new Error(`mirror cleanup failed: ${JSON.stringify(disabled.payload)}`);
}
}
await mkdir(dirname(OUTPUT_PATH), { recursive: true });
await writeFile(OUTPUT_PATH, `${JSON.stringify(proof, null, 2)}\n`);
console.log(JSON.stringify({ outputPath: OUTPUT_PATH, ...proof }));
@@ -0,0 +1,638 @@
import { getVercelOidcToken } from "@vercel/oidc";
import { defineEventHandler, getHeader, readBody } from "h3";
import {
buildSkillsShMirrorProofSnapshotId,
fetchSkillsShMirrorBatch,
fetchSkillsShMirrorControlledBatch,
getSkillsShCatalogTestSourcePolicy,
measureSkillsShMirrorProofSource,
parseSkillsShMirrorProofSnapshotId,
skillsShSourceRetryAfterSeconds,
} from "../../../skillsShCatalogSource";
import {
buildSkillsShMirrorReplayRows,
enrichSkillsShMirrorClassifications,
type SkillsShMirrorClassificationState,
} from "../../../skillsShMirrorClassification";
const TEST_CONVEX_SITE_URL = "https://academic-chihuahua-392.convex.site";
const OPERATOR_PATH = "/api/v1/operator/skills-sh/catalog-test";
const SOURCE_PAGE_SIZE = 500;
const MIRROR_BATCH_SIZE = 50;
const MAX_TEST_SOURCE_ROWS = 50_000;
const MAX_DETAIL_BYTES = 64 * 1024;
const MAX_DETAIL_PAGE_ROWS = 50;
const BATCH_LEASE_HEARTBEAT_INTERVAL_MS = 60_000;
type MirrorRequest = {
operation?:
| "configure"
| "start"
| "start-replay"
| "run"
| "step"
| "step-replay"
| "pause"
| "resume"
| "discard"
| "reconcile"
| "conflicts"
| "status"
| "isolation"
| "read"
| "source-summary"
| "page"
| "detail-page"
| "facet-page";
enabled?: boolean;
externalId?: string;
externalIds?: string[];
cursor?: string | null;
capturedRunId?: string;
hasMore?: boolean;
limit?: number;
offset?: number;
page?: number;
pageLength?: number;
reason?: string;
runId?: string;
snapshotHash?: string;
sourceMeasuredAt?: string;
sourcePageSize?: number;
sourceTotal?: number;
};
function jsonResponse(payload: unknown, status = 200, extraHeaders: Record<string, string> = {}) {
return new Response(JSON.stringify(payload), {
status,
headers: {
"Cache-Control": "no-store",
"Content-Type": "application/json; charset=utf-8",
...extraHeaders,
},
});
}
function requireString(value: string | undefined, name: string) {
const normalized = value?.trim();
if (!normalized) throw new Error(`${name} is required`);
return normalized;
}
function requireInteger(value: number | undefined, name: string, min: number, max: number) {
if (!Number.isInteger(value) || value === undefined || value < min || value > max) {
throw new Error(`${name} must be an integer between ${min} and ${max}`);
}
return value;
}
function assertNoActiveMirrorRun(status: Record<string, unknown>) {
if (!Array.isArray(status.runs)) {
throw new Error("skills.sh mirror status response lacks runs");
}
const activeRun = status.runs.find((value) => {
if (value === null || typeof value !== "object") return false;
const runStatus = (value as Record<string, unknown>).status;
return runStatus === "running" || runStatus === "paused" || runStatus === "reconciling";
});
if (activeRun === undefined) return;
const runId =
activeRun !== null &&
typeof activeRun === "object" &&
typeof (activeRun as Record<string, unknown>).runId === "string"
? `: ${(activeRun as Record<string, unknown>).runId}`
: "";
throw new Error(`skills.sh mirror already has an active run${runId}`);
}
async function callConvexOperator(authorization: string, body: Record<string, unknown>) {
const response = await fetch(`${TEST_CONVEX_SITE_URL}${OPERATOR_PATH}`, {
method: "POST",
headers: {
Accept: "application/json",
Authorization: authorization,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
const text = await response.text();
if (!response.ok) {
throw new Error(`Convex Test mirror operator returned HTTP ${response.status}: ${text}`);
}
return JSON.parse(text) as Record<string, unknown>;
}
function createBatchLeaseHeartbeat(args: {
authorization: string;
runId: string;
page: number;
offset: number;
leaseToken: string;
}) {
let nextHeartbeatAt = Date.now() + BATCH_LEASE_HEARTBEAT_INTERVAL_MS;
let pending: Promise<void> | null = null;
return async () => {
if (Date.now() < nextHeartbeatAt) return;
if (pending) return await pending;
pending = callConvexOperator(args.authorization, {
operation: "mirror-batch-claim",
runId: args.runId,
page: args.page,
offset: args.offset,
leaseToken: args.leaseToken,
})
.then(() => undefined)
.finally(() => {
nextHeartbeatAt = Date.now() + BATCH_LEASE_HEARTBEAT_INTERVAL_MS;
pending = null;
});
await pending;
};
}
export default defineEventHandler(async (event) => {
const policy = getSkillsShCatalogTestSourcePolicy(process.env);
if (!policy.allowed) return jsonResponse({ error: "not_found" }, 404);
const authorization = getHeader(event, "authorization")?.trim() ?? "";
if (!authorization.toLowerCase().startsWith("bearer ")) {
return jsonResponse({ error: "unauthorized" }, 401);
}
try {
const body = (await readBody(event)) as MirrorRequest;
const operation = body.operation;
if (operation === "status") {
return jsonResponse(await callConvexOperator(authorization, { operation: "mirror-status" }));
}
if (operation === "run") {
return jsonResponse(
await callConvexOperator(authorization, {
operation: "mirror-run",
runId: requireString(body.runId, "runId"),
}),
);
}
if (operation === "isolation") {
return jsonResponse(
await callConvexOperator(authorization, { operation: "mirror-isolation" }),
);
}
if (operation === "read") {
return jsonResponse(
await callConvexOperator(authorization, {
operation: "mirror-read",
externalId: requireString(body.externalId, "externalId"),
}),
);
}
if (operation === "source-summary") {
return jsonResponse(
await callConvexOperator(authorization, {
operation: "mirror-source-summary",
snapshotHash: requireString(body.snapshotHash, "snapshotHash"),
}),
);
}
if (operation === "conflicts") {
return jsonResponse(
await callConvexOperator(authorization, {
operation: "mirror-conflicts",
runId: requireString(body.runId, "runId"),
limit: requireInteger(body.limit, "limit", 1, 50),
}),
);
}
if (operation === "page") {
return jsonResponse(
await callConvexOperator(authorization, {
operation: "mirror-page",
cursor: body.cursor ?? null,
limit: requireInteger(body.limit, "limit", 1, 500),
}),
);
}
if (operation === "detail-page") {
return jsonResponse(
await callConvexOperator(authorization, {
operation: "mirror-detail-page",
cursor: body.cursor ?? null,
limit: requireInteger(body.limit, "limit", 1, MAX_DETAIL_PAGE_ROWS),
}),
);
}
if (operation === "facet-page") {
return jsonResponse(
await callConvexOperator(authorization, {
operation: "mirror-facet-page",
cursor: body.cursor ?? null,
limit: requireInteger(body.limit, "limit", 1, 500),
}),
);
}
if (operation === "configure") {
return jsonResponse(
await callConvexOperator(authorization, {
operation: "mirror-configure",
enabled: body.enabled === true,
reason: requireString(body.reason, "reason"),
confirm: "enable-skills-sh-mirror-test",
maxRowsPerRun: MAX_TEST_SOURCE_ROWS,
maxRowsPerBatch: MIRROR_BATCH_SIZE,
maxDetailBytes: MAX_DETAIL_BYTES,
}),
);
}
if (operation === "start") {
assertNoActiveMirrorRun(
await callConvexOperator(authorization, { operation: "mirror-status" }),
);
const oidcToken = await getVercelOidcToken();
const sourceMeasuredAt = new Date().toISOString();
const source = await measureSkillsShMirrorProofSource({ oidcToken });
if (source.catalogTotal < 1 || source.catalogTotal > MAX_TEST_SOURCE_ROWS) {
throw new Error(
`skills.sh source total ${source.catalogTotal} exceeds the Test mirror capacity`,
);
}
const sourceTotal = source.catalogTotal + source.controlledSupplementExternalIds.length;
if (sourceTotal > MAX_TEST_SOURCE_ROWS) {
throw new Error(`skills.sh proof source total ${sourceTotal} exceeds the Test capacity`);
}
const snapshotId = buildSkillsShMirrorProofSnapshotId(source);
const snapshot = parseSkillsShMirrorProofSnapshotId(snapshotId);
const sourceSnapshotHash = requireString(snapshot.sourceSnapshotHash, "sourceSnapshotHash");
let sourceCaptureWrites = 0;
for (const page of source.sourcePages) {
const stored = await callConvexOperator(authorization, {
operation: "mirror-source-page-store",
snapshotHash: sourceSnapshotHash,
...page,
});
if (stored.stored === true) sourceCaptureWrites += 1;
}
const sourceCapture = await callConvexOperator(authorization, {
operation: "mirror-source-summary",
snapshotHash: sourceSnapshotHash,
});
const result = await callConvexOperator(authorization, {
operation: "mirror-start",
reason: requireString(body.reason, "reason"),
snapshotId,
sourceSnapshotHash,
sourceCaptureWrites,
sourceTotal,
sourcePageSize: SOURCE_PAGE_SIZE,
sourceMeasuredAt,
});
return jsonResponse({
...result,
sourceTotal,
sourceCatalogTotal: source.catalogTotal,
controlledOverlayTotal: source.controlledOverlayExternalIds.length,
controlledSupplementTotal: source.controlledSupplementExternalIds.length,
sourceMeasurementRequests: source.sourceRequests,
sourceCapture: {
...sourceCapture,
requestDbWrites: sourceCaptureWrites,
},
sourceMeasuredAt,
sourcePageSize: SOURCE_PAGE_SIZE,
});
}
if (operation === "start-replay") {
const sourceMeasuredAt = requireString(body.sourceMeasuredAt, "sourceMeasuredAt");
if (Number.isNaN(Date.parse(sourceMeasuredAt))) {
throw new Error("sourceMeasuredAt must be an ISO timestamp");
}
const sourceTotal = requireInteger(body.sourceTotal, "sourceTotal", 1, MAX_TEST_SOURCE_ROWS);
const sourcePageSize = requireInteger(
body.sourcePageSize,
"sourcePageSize",
1,
SOURCE_PAGE_SIZE,
);
const result = await callConvexOperator(authorization, {
operation: "mirror-start",
reason: requireString(body.reason, "reason"),
snapshotId: `skills-sh-captured:${requireString(body.capturedRunId, "capturedRunId")}`,
sourceTotal,
sourcePageSize,
sourceMeasuredAt,
});
return jsonResponse({
...result,
sourceTotal,
sourceMeasuredAt,
sourcePageSize,
captured: true,
});
}
if (operation === "step") {
const runId = requireString(body.runId, "runId");
const page = requireInteger(body.page, "page", 0, 100_000);
const offset = requireInteger(body.offset, "offset", 0, SOURCE_PAGE_SIZE - 1);
const leaseToken = crypto.randomUUID();
const lease = await callConvexOperator(authorization, {
operation: "mirror-batch-claim",
runId,
page,
offset,
leaseToken,
});
try {
const sourceTotal = requireInteger(
typeof lease.sourceTotal === "number" ? lease.sourceTotal : undefined,
"lease.sourceTotal",
1,
MAX_TEST_SOURCE_ROWS,
);
const snapshot = parseSkillsShMirrorProofSnapshotId(
requireString(
typeof lease.snapshotId === "string" ? lease.snapshotId : undefined,
"lease.snapshotId",
),
);
if (
sourceTotal !==
snapshot.catalogTotal + snapshot.controlledSupplementExternalIds.length
) {
throw new Error("skills.sh mirror run proof source metadata is inconsistent");
}
const controlledPage = Math.ceil(snapshot.catalogTotal / SOURCE_PAGE_SIZE);
const beforeRequest = createBatchLeaseHeartbeat({
authorization,
runId,
page,
offset,
leaseToken,
});
const batch =
page === controlledPage && snapshot.controlledSupplementExternalIds.length > 0
? await fetchSkillsShMirrorControlledBatch(
{
page,
offset,
limit: MIRROR_BATCH_SIZE,
maxDetailBytes: MAX_DETAIL_BYTES,
sourceTotal,
externalIds: snapshot.controlledSupplementExternalIds,
},
{ beforeRequest },
)
: page < controlledPage
? await (async () => {
const capturedPage =
lease.sourcePage &&
typeof lease.sourcePage === "object" &&
!Array.isArray(lease.sourcePage)
? (lease.sourcePage as Record<string, unknown>)
: null;
const expectedPage = snapshot.evidence?.pagination.requestedPages.find(
(entry) => entry.page === page,
);
if (
!capturedPage ||
!expectedPage ||
capturedPage.page !== page ||
capturedPage.sourceTotal !== snapshot.catalogTotal ||
capturedPage.pageLength !== expectedPage.count ||
capturedPage.hasMore !== expectedPage.hasMore ||
capturedPage.identityHash !== expectedPage.identityHash ||
capturedPage.contentHash !== expectedPage.contentHash ||
!Array.isArray(capturedPage.rows)
) {
throw new Error(
`captured skills.sh leaderboard page does not match the proof: ${page}`,
);
}
const oidcToken = await getVercelOidcToken();
const catalogBatch = await fetchSkillsShMirrorBatch(
{ page, offset, limit: MIRROR_BATCH_SIZE, maxDetailBytes: MAX_DETAIL_BYTES },
{
oidcToken,
beforeRequest,
sourcePage: {
data: capturedPage.rows as never,
pagination: {
page,
perPage: SOURCE_PAGE_SIZE,
total: snapshot.catalogTotal,
hasMore: expectedPage.hasMore,
},
},
},
);
if (catalogBatch.sourceTotal !== snapshot.catalogTotal) {
throw new Error("skills.sh catalog source total changed during the run");
}
if (
expectedPage.count !== catalogBatch.pageLength ||
expectedPage.hasMore !== catalogBatch.hasMore ||
expectedPage.identityHash !== catalogBatch.sourcePageIdentityHash
) {
throw new Error(
`skills.sh ordered leaderboard page changed during the run: ${page}`,
);
}
const controlledOverlayExternalIds = new Set<string>(
snapshot.controlledOverlayExternalIds,
);
const overlayExternalIds = catalogBatch.rows.flatMap((row) =>
controlledOverlayExternalIds.has(row.externalId) ? [row.externalId] : [],
);
const overlay =
overlayExternalIds.length > 0
? await fetchSkillsShMirrorControlledBatch(
{
page,
offset: 0,
limit: overlayExternalIds.length,
maxDetailBytes: MAX_DETAIL_BYTES,
sourceTotal,
externalIds: overlayExternalIds,
},
{ beforeRequest },
)
: null;
const overlayByExternalId = new Map(
overlay?.rows.map((row) => [row.externalId, row]),
);
return {
...catalogBatch,
sourceTotal,
hasMore:
catalogBatch.hasMore || snapshot.controlledSupplementExternalIds.length > 0,
sourceRequests: catalogBatch.sourceRequests + (overlay?.sourceRequests ?? 0),
sourceBytes: catalogBatch.sourceBytes + (overlay?.sourceBytes ?? 0),
rows: catalogBatch.rows.map((row) => {
const controlled = overlayByExternalId.get(row.externalId);
if (!controlled) return row;
if ("quarantined" in row) return controlled;
return {
...controlled,
upstreamSourceType: row.upstreamSourceType,
upstreamInstalls: row.upstreamInstalls,
upstreamScanners: row.upstreamScanners,
};
}),
};
})()
: (() => {
throw new Error("skills.sh mirror cursor is beyond the proof source");
})();
const externalIds = batch.rows.flatMap((row) =>
"quarantined" in row ? [] : [row.externalId],
);
const classificationState =
externalIds.length === 0
? { states: [] }
: await callConvexOperator(authorization, {
operation: "mirror-classification-states",
externalIds,
});
if (!Array.isArray(classificationState.states)) {
throw new Error("Convex Test mirror classification state is invalid");
}
const rows = enrichSkillsShMirrorClassifications(
batch.rows as Parameters<typeof enrichSkillsShMirrorClassifications>[0],
classificationState.states as SkillsShMirrorClassificationState[],
);
return jsonResponse(
await callConvexOperator(authorization, {
operation: "mirror-batch",
runId,
leaseToken,
...batch,
rows,
}),
);
} catch (error) {
try {
await callConvexOperator(authorization, {
operation: "mirror-batch-release",
runId,
page,
offset,
leaseToken,
});
} catch {
// The five-minute durable lease remains the crash/outage recovery path.
}
throw error;
}
}
if (operation === "step-replay") {
const runId = requireString(body.runId, "runId");
const page = requireInteger(body.page, "page", 0, 100_000);
const offset = requireInteger(body.offset, "offset", 0, SOURCE_PAGE_SIZE - 1);
const pageLength = requireInteger(body.pageLength, "pageLength", 1, SOURCE_PAGE_SIZE);
const sourceTotal = requireInteger(body.sourceTotal, "sourceTotal", 1, MAX_TEST_SOURCE_ROWS);
if (typeof body.hasMore !== "boolean") throw new Error("hasMore is required");
if (
!Array.isArray(body.externalIds) ||
body.externalIds.length < 1 ||
body.externalIds.length > MIRROR_BATCH_SIZE ||
body.externalIds.some((externalId) => typeof externalId !== "string" || !externalId.trim())
) {
throw new Error(`externalIds must contain between 1 and ${MIRROR_BATCH_SIZE} strings`);
}
const leaseToken = crypto.randomUUID();
await callConvexOperator(authorization, {
operation: "mirror-batch-claim",
runId,
page,
offset,
leaseToken,
});
try {
const captured = await callConvexOperator(authorization, {
operation: "mirror-replay-rows",
externalIds: body.externalIds,
});
if (!Array.isArray(captured.rows)) {
throw new Error("Convex Test mirror replay rows are invalid");
}
const rows = buildSkillsShMirrorReplayRows(captured.rows as never);
return jsonResponse(
await callConvexOperator(authorization, {
operation: "mirror-batch",
runId,
page,
offset,
leaseToken,
pageLength,
hasMore: body.hasMore,
sourceTotal,
sourceRequests: 0,
sourceBytes: 0,
rows,
}),
);
} catch (error) {
try {
await callConvexOperator(authorization, {
operation: "mirror-batch-release",
runId,
page,
offset,
leaseToken,
});
} catch {
// The five-minute durable lease remains the crash/outage recovery path.
}
throw error;
}
}
if (operation === "pause" || operation === "resume") {
return jsonResponse(
await callConvexOperator(authorization, {
operation: "mirror-pause",
runId: requireString(body.runId, "runId"),
paused: operation === "pause",
reason: requireString(body.reason, "reason"),
confirm: "set-skills-sh-mirror-pause",
}),
);
}
if (operation === "discard") {
return jsonResponse(
await callConvexOperator(authorization, {
operation: "mirror-cancel",
runId: requireString(body.runId, "runId"),
reason: requireString(body.reason, "reason"),
confirm: "cancel-skills-sh-mirror-test-run",
}),
);
}
if (operation === "reconcile") {
return jsonResponse(
await callConvexOperator(authorization, {
operation: "mirror-reconcile",
runId: requireString(body.runId, "runId"),
limit: requireInteger(body.limit ?? 250, "limit", 1, 250),
}),
);
}
return jsonResponse({ error: "unknown_operation" }, 400);
} catch (error) {
const retryAfterSeconds = skillsShSourceRetryAfterSeconds(error);
if (retryAfterSeconds !== null) {
return jsonResponse(
{
error: "skills_sh_source_rate_limited",
message: error instanceof Error ? error.message : "skills.sh source rate limited",
retryAfterSeconds,
},
429,
{ "Retry-After": String(retryAfterSeconds) },
);
}
return jsonResponse(
{
error: "skills_sh_mirror_test_failed",
message: error instanceof Error ? error.message : "Unknown Test mirror failure",
},
502,
);
}
});
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+353
View File
@@ -0,0 +1,353 @@
/* @vitest-environment node */
import { describe, expect, it, vi } from "vitest";
import { CLASSIFIER_VERSION, TOPIC_CLASSIFIER_VERSION } from "../convex/lib/catalogClassifier.mjs";
import {
buildSkillsShMirrorReplayRows,
enrichSkillsShMirrorClassifications,
} from "./skillsShMirrorClassification";
const row = {
externalId: "patrick-erichsen/skills/html",
slug: "html",
displayName: "HTML",
sourceContentHash: "a".repeat(64),
detail: {
content: "# HTML\n\nBuild interactive HTML artifacts and frontend prototypes.",
},
};
describe("skills.sh mirror classification enrichment", () => {
it("classifies bounded mirror detail content with the native inference contract", () => {
const [classified] = enrichSkillsShMirrorClassifications([row], [], 123);
expect(classified).toMatchObject({
externalId: row.externalId,
inferredCategories: expect.any(Array),
inferredTopics: expect.any(Array),
inferredCategoryConfidence: expect.stringMatching(/^(high|medium|low)$/),
inferredTopicConfidence: expect.stringMatching(/^(high|medium|low)$/),
inferredClassifierVersion: CLASSIFIER_VERSION,
inferredTopicClassifierVersion: TOPIC_CLASSIFIER_VERSION,
inferredInputHash: expect.any(String),
inferredTopicInputHash: expect.any(String),
inferredAt: 123,
});
expect(classified.inferredCategories.length).toBeGreaterThan(0);
});
it("reuses inference when source identity, content hash, and classifier versions match", () => {
const classify = vi.fn();
const inference = {
inferredCategories: ["development"],
inferredTopics: ["html"],
inferredCategoryConfidence: "high" as const,
inferredTopicConfidence: "medium" as const,
inferredClassifierVersion: CLASSIFIER_VERSION,
inferredTopicClassifierVersion: TOPIC_CLASSIFIER_VERSION,
inferredInputHash: "input-hash",
inferredTopicInputHash: "topic-input-hash",
inferredAt: 100,
};
const [classified] = enrichSkillsShMirrorClassifications(
[row],
[
{
externalId: row.externalId,
slug: row.slug,
displayName: row.displayName,
sourceContentHash: row.sourceContentHash,
...inference,
},
],
200,
classify,
);
expect(classified).toMatchObject(inference);
expect(classify).not.toHaveBeenCalled();
});
it("reclassifies when the source content hash or classifier version changes", () => {
const classify = vi.fn(() => ({
categories: [],
topics: [],
confidence: "low" as const,
topicConfidence: "low" as const,
classifierVersion: CLASSIFIER_VERSION,
topicClassifierVersion: TOPIC_CLASSIFIER_VERSION,
inputHash: "new-input",
topicInputHash: "new-topic-input",
}));
const staleState = {
externalId: row.externalId,
slug: row.slug,
displayName: row.displayName,
sourceContentHash: "b".repeat(64),
inferredCategories: ["development"],
inferredTopics: ["html"],
inferredCategoryConfidence: "high" as const,
inferredTopicConfidence: "high" as const,
inferredClassifierVersion: "taxonomy-old",
inferredTopicClassifierVersion: TOPIC_CLASSIFIER_VERSION,
inferredInputHash: "old-input",
inferredTopicInputHash: "old-topic-input",
inferredAt: 100,
};
const [classified] = enrichSkillsShMirrorClassifications([row], [staleState], 200, classify);
expect(classify).toHaveBeenCalledOnce();
expect(classified).toMatchObject({
inferredCategories: ["other"],
inferredTopics: [],
inferredClassifierVersion: CLASSIFIER_VERSION,
inferredTopicClassifierVersion: TOPIC_CLASSIFIER_VERSION,
inferredAt: 200,
});
});
it("reclassifies changed detail when a legacy state has no content hash", () => {
const classify = vi.fn(() => ({
categories: ["development"],
topics: ["html"],
confidence: "high" as const,
topicConfidence: "high" as const,
classifierVersion: CLASSIFIER_VERSION,
topicClassifierVersion: TOPIC_CLASSIFIER_VERSION,
inputHash: "new-input",
topicInputHash: "new-topic-input",
}));
const unhashedRow = {
...row,
sourceContentHash: undefined,
detail: { content: "# HTML\n\nChanged content." },
};
const state = {
externalId: row.externalId,
slug: row.slug,
displayName: row.displayName,
inferredCategories: ["other"],
inferredTopics: [],
inferredCategoryConfidence: "low" as const,
inferredTopicConfidence: "low" as const,
inferredClassifierVersion: CLASSIFIER_VERSION,
inferredTopicClassifierVersion: TOPIC_CLASSIFIER_VERSION,
inferredInputHash: "old-input",
inferredTopicInputHash: "old-topic-input",
inferredAt: 100,
};
const [classified] = enrichSkillsShMirrorClassifications([unhashedRow], [state], 200, classify);
expect(classify).toHaveBeenCalledOnce();
expect(classified).toMatchObject({
inferredCategories: ["development"],
inferredAt: 200,
});
});
it("reclassifies from the display-name stub when retained detail disappears", () => {
const [previous] = enrichSkillsShMirrorClassifications([row], [], 100);
const classify = vi.fn(() => ({
categories: [],
topics: [],
confidence: "low" as const,
topicConfidence: "low" as const,
classifierVersion: CLASSIFIER_VERSION,
topicClassifierVersion: TOPIC_CLASSIFIER_VERSION,
inputHash: "stub-input",
topicInputHash: "stub-topic-input",
}));
const withoutDetail = {
externalId: row.externalId,
slug: row.slug,
displayName: row.displayName,
};
const [classified] = enrichSkillsShMirrorClassifications(
[withoutDetail],
[previous],
200,
classify,
);
expect(classify).toHaveBeenCalledWith({
slug: "html",
text: "---\nname: HTML\n---\n# HTML",
});
expect(classified).toMatchObject({
inferredCategories: ["other"],
inferredInputHash: "stub-input",
inferredAt: 200,
});
});
it("reuses classification when the same no-detail stub is observed again", () => {
const withoutDetail = {
externalId: row.externalId,
slug: row.slug,
displayName: row.displayName,
};
const [previous] = enrichSkillsShMirrorClassifications([withoutDetail], [], 100);
const classify = vi.fn();
const [classified] = enrichSkillsShMirrorClassifications(
[withoutDetail],
[previous],
200,
classify,
);
expect(classify).not.toHaveBeenCalled();
expect(classified.inferredAt).toBe(100);
});
it("rebuilds bounded rows from the captured digest and detail snapshot", () => {
const [replayed] = buildSkillsShMirrorReplayRows(
[
{
digest: {
...row,
sourceType: "github",
upstreamSourceType: "github",
owner: "patrick-erichsen",
repo: "skills",
sourceUrl: "https://skills.sh/patrick-erichsen/skills/html",
canonicalRepoUrl: "https://github.com/patrick-erichsen/skills",
upstreamInstalls: 42,
upstreamScanners: {
genAgentTrustHub: { status: "unavailable" },
socket: { status: "unavailable" },
snyk: { status: "unavailable" },
},
},
detail: {
contentKind: "skill-md",
path: "skills/html/SKILL.md",
content: row.detail.content,
contentBytes: Buffer.byteLength(row.detail.content),
sourceBytes: Buffer.byteLength(row.detail.content),
sourceFileCount: 1,
truncated: false,
},
},
],
456,
);
expect(replayed).toMatchObject({
externalId: row.externalId,
owner: "patrick-erichsen",
repo: "skills",
detail: {
path: "skills/html/SKILL.md",
content: row.detail.content,
},
inferredCategories: expect.any(Array),
inferredAt: 456,
});
});
it("preserves stale replay rows as quarantine observations", () => {
expect(
buildSkillsShMirrorReplayRows([
{
quarantined: true,
externalId: "larksuite/cli/lark-doc",
upstreamSourceType: "well-known",
reason: "identity-page-fetch-failed",
},
] as never),
).toEqual([
{
quarantined: true,
externalId: "larksuite/cli/lark-doc",
upstreamSourceType: "well-known",
reason: "identity-page-fetch-failed",
},
]);
});
it("synthesizes the same bounded content hash while replaying legacy detail", () => {
const [replayed] = buildSkillsShMirrorReplayRows(
[
{
digest: {
externalId: row.externalId,
sourceType: "github",
upstreamSourceType: "github",
owner: "patrick-erichsen",
repo: "skills",
slug: row.slug,
displayName: row.displayName,
sourceUrl: "https://skills.sh/patrick-erichsen/skills/html",
canonicalRepoUrl: "https://github.com/patrick-erichsen/skills",
upstreamInstalls: 42,
upstreamScanners: {
genAgentTrustHub: { status: "unavailable" },
socket: { status: "unavailable" },
snyk: { status: "unavailable" },
},
},
detail: {
contentKind: "skill-md",
path: "SKILL.md",
content: "abc",
contentBytes: 3,
sourceBytes: 3,
sourceFileCount: 1,
truncated: false,
},
},
],
456,
);
if ("quarantined" in replayed) {
throw new Error("legacy detail replay was quarantined");
}
expect(replayed.sourceContentHash).toBe(
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
);
});
it("does not synthesize a full-content hash from truncated legacy detail", () => {
const [replayed] = buildSkillsShMirrorReplayRows([
{
digest: {
externalId: row.externalId,
sourceType: "github",
upstreamSourceType: "github",
owner: "patrick-erichsen",
repo: "skills",
slug: row.slug,
displayName: row.displayName,
sourceUrl: "https://skills.sh/patrick-erichsen/skills/html",
canonicalRepoUrl: "https://github.com/patrick-erichsen/skills",
upstreamInstalls: 42,
upstreamScanners: {
genAgentTrustHub: { status: "unavailable" },
socket: { status: "unavailable" },
snyk: { status: "unavailable" },
},
},
detail: {
contentKind: "skill-md",
path: "SKILL.md",
content: "bounded prefix",
contentBytes: 14,
sourceBytes: 128_000,
sourceFileCount: 1,
truncated: true,
},
},
]);
if ("quarantined" in replayed) {
throw new Error("legacy detail replay was quarantined");
}
expect(replayed.sourceContentHash).toBeUndefined();
});
});
+264
View File
@@ -0,0 +1,264 @@
import { createHash } from "node:crypto";
import {
CLASSIFIER_VERSION,
TOPIC_CLASSIFIER_VERSION,
classifySkill,
} from "../convex/lib/catalogClassifier.mjs";
const MAX_CLASSIFICATION_TEXT_LENGTH = 40_000;
type ClassificationConfidence = "high" | "medium" | "low";
type MirrorClassification = {
inferredCategories: string[];
inferredTopics: string[];
inferredCategoryConfidence: ClassificationConfidence;
inferredTopicConfidence: ClassificationConfidence;
inferredClassifierVersion: string;
inferredTopicClassifierVersion: string;
inferredInputHash: string;
inferredTopicInputHash: string;
inferredAt: number;
};
export type SkillsShMirrorClassificationState = MirrorClassification & {
externalId: string;
slug: string;
displayName: string;
sourceContentHash?: string;
};
type ClassifiableMirrorRow = {
quarantined?: never;
externalId: string;
slug: string;
displayName: string;
sourceContentHash?: string;
detail?: { content: string };
};
type QuarantinedMirrorRow = {
quarantined: true;
externalId: string;
};
type MirrorReplayPair = {
digest: {
externalId: string;
sourceType: "github" | "well-known";
upstreamSourceType?: string;
owner?: string;
repo?: string;
sourceHost?: string;
slug: string;
displayName: string;
sourceUrl: string;
canonicalRepoUrl?: string;
githubPath?: string;
githubCommit?: string;
sourceContentHash?: string;
upstreamInstalls: number;
upstreamScanners: {
genAgentTrustHub: { status: string; sourceCheckedAt?: string; sourceUrl?: string };
socket: { status: string; sourceCheckedAt?: string; sourceUrl?: string };
snyk: { status: string; sourceCheckedAt?: string; sourceUrl?: string };
};
inferredCategories?: string[];
inferredTopics?: string[];
inferredCategoryConfidence?: ClassificationConfidence;
inferredTopicConfidence?: ClassificationConfidence;
inferredClassifierVersion?: string;
inferredTopicClassifierVersion?: string;
inferredInputHash?: string;
inferredTopicInputHash?: string;
inferredAt?: number;
};
detail: {
contentKind: "skill-md" | "readme";
path: string;
content: string;
contentBytes: number;
sourceBytes: number;
sourceFileCount: number;
truncated: boolean;
} | null;
};
type MirrorReplayQuarantine = {
quarantined: true;
externalId: string;
upstreamSourceType: string;
reason: string;
};
type ClassifierOutput = {
categories: string[];
topics: string[];
confidence: ClassificationConfidence;
topicConfidence: ClassificationConfidence;
classifierVersion: string;
topicClassifierVersion: string;
inputHash: string;
topicInputHash: string;
};
type EnrichedMirrorRow<T> = T extends QuarantinedMirrorRow ? T : T & MirrorClassification;
function hasReusableClassification(
row: ClassifiableMirrorRow,
state: SkillsShMirrorClassificationState | undefined,
): state is SkillsShMirrorClassificationState {
return (
state !== undefined &&
state.slug === row.slug &&
state.displayName === row.displayName &&
(row.detail === undefined
? state.inferredInputHash === classificationInputHash(row)
: row.sourceContentHash !== undefined && state.sourceContentHash === row.sourceContentHash) &&
state.inferredClassifierVersion === CLASSIFIER_VERSION &&
state.inferredTopicClassifierVersion === TOPIC_CLASSIFIER_VERSION
);
}
function boundedContentHash(content: string) {
return createHash("sha256").update(content).digest("hex");
}
function classificationText(row: ClassifiableMirrorRow) {
const displayName = row.displayName.replace(/[\r\n]+/g, " ").trim() || row.slug;
const content = row.detail?.content ?? `# ${displayName}`;
return `---\nname: ${displayName}\n---\n${content}`.slice(0, MAX_CLASSIFICATION_TEXT_LENGTH);
}
function classificationInputHash(row: ClassifiableMirrorRow) {
return createHash("sha256")
.update(`${row.slug}\0${classificationText(row)}\0${JSON.stringify([])}`)
.digest("hex");
}
export function enrichSkillsShMirrorClassifications<
T extends ClassifiableMirrorRow | QuarantinedMirrorRow,
>(
rows: T[],
states: SkillsShMirrorClassificationState[],
inferredAt = Date.now(),
classify: (input: { slug?: string; text?: string }) => ClassifierOutput = classifySkill,
): Array<EnrichedMirrorRow<T>> {
const statesByExternalId = new Map(states.map((state) => [state.externalId, state]));
return rows.map((row) => {
if ("quarantined" in row) return row;
const state = statesByExternalId.get(row.externalId);
if (hasReusableClassification(row, state)) {
return {
...row,
inferredCategories: state.inferredCategories,
inferredTopics: state.inferredTopics,
inferredCategoryConfidence: state.inferredCategoryConfidence,
inferredTopicConfidence: state.inferredTopicConfidence,
inferredClassifierVersion: state.inferredClassifierVersion,
inferredTopicClassifierVersion: state.inferredTopicClassifierVersion,
inferredInputHash: state.inferredInputHash,
inferredTopicInputHash: state.inferredTopicInputHash,
inferredAt: state.inferredAt,
};
}
const result = classify({
slug: row.slug,
text: classificationText(row),
});
return {
...row,
inferredCategories: result.categories.length > 0 ? result.categories : ["other"],
inferredTopics: result.topics,
inferredCategoryConfidence: result.confidence,
inferredTopicConfidence: result.topicConfidence,
inferredClassifierVersion: result.classifierVersion,
inferredTopicClassifierVersion: result.topicClassifierVersion,
inferredInputHash: result.inputHash,
inferredTopicInputHash: result.topicInputHash,
inferredAt,
};
}) as Array<EnrichedMirrorRow<T>>;
}
function replayClassificationState(
digest: MirrorReplayPair["digest"],
): SkillsShMirrorClassificationState | null {
if (
!digest.inferredCategories ||
!digest.inferredTopics ||
!digest.inferredCategoryConfidence ||
!digest.inferredTopicConfidence ||
!digest.inferredClassifierVersion ||
!digest.inferredTopicClassifierVersion ||
!digest.inferredInputHash ||
!digest.inferredTopicInputHash ||
digest.inferredAt === undefined
) {
return null;
}
return {
externalId: digest.externalId,
slug: digest.slug,
displayName: digest.displayName,
...(digest.sourceContentHash ? { sourceContentHash: digest.sourceContentHash } : {}),
inferredCategories: digest.inferredCategories,
inferredTopics: digest.inferredTopics,
inferredCategoryConfidence: digest.inferredCategoryConfidence,
inferredTopicConfidence: digest.inferredTopicConfidence,
inferredClassifierVersion: digest.inferredClassifierVersion,
inferredTopicClassifierVersion: digest.inferredTopicClassifierVersion,
inferredInputHash: digest.inferredInputHash,
inferredTopicInputHash: digest.inferredTopicInputHash,
inferredAt: digest.inferredAt,
};
}
export function buildSkillsShMirrorReplayRows(
inputs: Array<MirrorReplayPair | MirrorReplayQuarantine>,
inferredAt = Date.now(),
) {
const pairs = inputs.filter((input): input is MirrorReplayPair => !("quarantined" in input));
const states = pairs.flatMap((pair) => {
const state = replayClassificationState(pair.digest);
return state ? [state] : [];
});
const rows = inputs.map((input) => {
if ("quarantined" in input) return input;
const { digest, detail } = input;
const sourceContentHash =
digest.sourceContentHash ??
(detail && !detail.truncated ? boundedContentHash(detail.content) : undefined);
return {
externalId: digest.externalId,
sourceType: digest.sourceType,
upstreamSourceType: digest.upstreamSourceType ?? digest.sourceType,
...(digest.owner ? { owner: digest.owner } : {}),
...(digest.repo ? { repo: digest.repo } : {}),
...(digest.sourceHost ? { sourceHost: digest.sourceHost } : {}),
slug: digest.slug,
displayName: digest.displayName,
sourceUrl: digest.sourceUrl,
...(digest.canonicalRepoUrl ? { canonicalRepoUrl: digest.canonicalRepoUrl } : {}),
...(digest.githubPath ? { githubPath: digest.githubPath } : {}),
...(digest.githubCommit ? { githubCommit: digest.githubCommit } : {}),
...(sourceContentHash ? { sourceContentHash } : {}),
upstreamInstalls: digest.upstreamInstalls,
upstreamScanners: digest.upstreamScanners,
...(detail
? {
detail: {
contentKind: detail.contentKind,
path: detail.path,
content: detail.content,
contentBytes: detail.contentBytes,
sourceBytes: detail.sourceBytes,
sourceFileCount: detail.sourceFileCount,
truncated: detail.truncated,
},
}
: {}),
};
});
return enrichSkillsShMirrorClassifications(rows, states, inferredAt);
}
File diff suppressed because it is too large Load Diff
+24 -4
View File
@@ -20,17 +20,37 @@ ip:<client ip>
This keeps a user id and IP with the same visible string in separate hash
domains for dedupe and local diagnostics.
## Counters
## Source-Attributed Counters
The dedupe table does not store user-vs-IP counters. It only gates whether a
download should emit the existing skill or package stat event. Public counters
still store one total:
for native ClawHub downloads still use the canonical skill download field:
```text
downloads
statsDownloads
```
Existing historical counts are not estimated or rewritten in this phase.
Skills mirrored from skills.sh store the upstream install count separately in
`statsSkillsShInstalls`. Public skill Downloads are computed at serialization:
```text
native skill: statsDownloads
skills.sh indexed: statsDownloads + statsSkillsShInstalls
```
The combined value is never written back into `statsDownloads`. Existing
historical ClawHub downloads remain intact, and search ranking continues to use
the native field.
OpenClaw install telemetry remains in `statsInstallsCurrent` and
`statsInstallsAllTime`; it is not added to public Downloads. GitHub popularity
is stored in `statsGithubStars`. Existing `stars` rows and `statsStars` count
ClawHub Bookmarks and retain those storage/API names for compatibility.
Source refresh, adoption, content replacement, and GitHub synchronization may
update source metadata or upstream counters, but must not reset or rewrite any
other metric source. Publisher dashboards receive the source breakdown while
ordinary public skill shapes expose only the combined Downloads value.
## Daily Package Graphs
+29
View File
@@ -98,6 +98,35 @@ not create or mutate `skills` rows during its planning gates.
- `githubScanStatus`: `pending`, `clean`, `suspicious`, `malicious`, or `failed`
- `githubRemovedAt`
## Permanent external skills.sh mirror
The full authenticated skills.sh mirror is separate from both native `skills`
and the controlled scan-admission catalog. It stores source observations in
`skillsShMirrorDigests` and bounded detail content in `skillsShMirrorDetails`;
controls, durable run cursors, and conflicts remain in their own mirror tables.
- GitHub identities are exact `owner/repo/slug` values. Well-known identities
are exact `sourceHost/slug` values and must not invent a repository owner.
- Every digest is permanently `publicVisible: false` and `installable: false`.
Mirror ingestion never creates native skills, publisher attachment, claims,
scan plans, or scan jobs.
- The digest stores normalized slug/display-name fields and a lean
`searchText`. Exact, prefix, first-token, popularity, freshness, and
full-text indexes are staged before activation on the permanent Test corpus.
- Gen Agent Trust Hub, Socket, and Snyk observations are stored independently
with a bounded status plus optional source timestamp and source link. These
are upstream claims only and must never be serialized as a ClawHub verdict.
- Detail storage retains at most one preferred `SKILL.md` or `README.md`,
capped at 64 KiB. The complete upstream file tree is never persisted for an
unclaimed mirror row.
- Snapshot ingestion uses bounded page/offset cursors. Pause is checked before
another source batch is fetched; resume continues at the exact stored cursor.
Reconciliation tombstones disappeared rows and reactivates later
observations without deleting or changing native skills.
- The mirror has no scheduler in this stage. Production activation, public
search/detail/install behavior, claims, and publisher attachment require
separately accepted work.
## Publisher Gate
The legacy `NVIDIA/skills` source keeps its existing production behavior.
+15 -2
View File
@@ -52,7 +52,18 @@ read_when:
- `moderationFlags`: `string[]` (automatic detection)
- `moderationNotes`, `moderationReason`
- `hiddenAt`, `hiddenBy`, `lastReviewedAt`, `reportCount`
- `stats`: `{ downloads, stars, versions, comments }` (`comments` is retained as a historical stat field; skill comments are retired)
- `stats`: legacy nested compatibility counters; canonical migrated counters live
in top-level fields.
- `statsDownloads`: native ClawHub downloads.
- `statsSkillsShInstalls`: upstream skills.sh installs, stored separately.
- `statsInstallsCurrent`, `statsInstallsAllTime`: OpenClaw install telemetry.
- `statsGithubStars`: upstream GitHub popularity.
- `statsStars`: ClawHub Bookmarks; existing `stars` rows and API names remain for
compatibility.
- Public Downloads are `statsDownloads` for native-only skills and
`statsDownloads + statsSkillsShInstalls` for skills.sh-indexed skills.
- Search ranking continues to use native counters rather than the combined
presentation value.
- `createdAt`, `updatedAt`
### SkillVersion
@@ -95,9 +106,11 @@ From SKILL.md frontmatter + AgentSkills + Clawdis extensions:
detail page and as hover text; 70 is a presentation rule, not a storage,
publish, API, or sync constraint.
### Star
### Bookmark
- `skillId`, `userId`, `createdAt`
- Stored in the legacy `stars` table and exposed through compatibility API
routes named `stars`; user-facing product language is Bookmark.
### AuditLog
+48 -5
View File
@@ -24,6 +24,9 @@ async function readWorkflow() {
};
jobs?: Record<string, WorkflowJob>;
on?: {
pull_request?: {
types?: string[];
};
workflow_dispatch?: unknown;
workflow_run?: {
branches?: string[];
@@ -36,27 +39,49 @@ async function readWorkflow() {
}
describe("Test deploy workflow", () => {
it("runs only after successful main CI or a manual dispatch", async () => {
it("admits main CI and exact guarded CLAW-563 branch deploys", async () => {
const workflow = await readWorkflow();
const job = workflow.jobs?.["deploy-test"];
const steps = job?.steps ?? [];
const revision = steps.find((step) => step.name === "Resolve deployment revision")?.run ?? "";
expect(workflow.on?.workflow_run).toEqual({
workflows: ["CI"],
types: ["completed"],
branches: ["main"],
});
expect(workflow.on?.pull_request).toEqual({
types: ["synchronize", "labeled"],
});
expect(workflow.on?.workflow_dispatch).toBeDefined();
expect(workflow.concurrency).toEqual({
group: "deploy-test",
"cancel-in-progress": false,
});
expect(job?.if).toContain("github.event_name == 'workflow_dispatch'");
expect(job?.if).toContain("github.ref == 'refs/heads/main'");
expect(job?.if).toContain("github.ref == 'refs/heads/pe/claw-563-skills-sh-mirror-10k'");
expect(job?.if).toContain("inputs.branch_test_confirm == 'deploy-claw-563-to-permanent-test'");
expect(job?.if).toContain("inputs.expected_sha != ''");
expect(job?.if).toContain("github.event_name == 'pull_request'");
expect(job?.if).toContain(
"github.event.pull_request.head.ref == 'pe/claw-563-skills-sh-mirror-10k'",
);
expect(job?.if).toContain("github.event.pull_request.head.repo.full_name == github.repository");
expect(job?.if).toContain("github.actor == 'Patrick-Erichsen'");
expect(job?.if).toContain(
"contains(github.event.pull_request.labels.*.name, 'test-mirror-load')",
);
expect(job?.if).toContain("github.event.workflow_run.conclusion == 'success'");
expect(job?.if).toContain("github.event.workflow_run.event == 'push'");
expect(job?.if).toContain("github.ref == 'refs/heads/main'");
expect(steps.find((step) => step.name === "Resolve deployment revision")?.run).toContain(
'deploy_sha" != "$main_sha',
);
expect(revision).toContain('deploy_sha" != "$main_sha');
expect(revision).toContain("refs/heads/pe/claw-563-skills-sh-mirror-10k");
expect(revision).toContain("Patrick-Erichsen");
expect(revision).toContain("deploy-claw-563-to-permanent-test");
expect(revision).toContain("${{ inputs.expected_sha }}");
expect(revision).toContain("${{ github.event.pull_request.head.sha }}");
expect(revision).toContain("${{ github.event.pull_request.head.repo.full_name }}");
expect(revision).toContain("$GITHUB_REPOSITORY");
});
it("uses only the Test environment and narrowly scoped secrets", async () => {
@@ -114,6 +139,9 @@ describe("Test deploy workflow", () => {
expect(deployStep?.run).toContain("--target=preview");
expect(deployStep?.run).toContain('--scope "$VERCEL_SCOPE"');
expect(deployStep?.run).toContain("--build-env CONVEX_DEPLOY_KEY=");
expect(deployStep?.run).not.toContain("--build-env VERCEL_ENV=");
expect(deployStep?.run).toContain("--build-env VERCEL_TARGET_ENV=test");
expect(deployStep?.run).toContain("--env VERCEL_TARGET_ENV=test");
expect(deployStep?.run).toContain("--build-env CLAWHUB_SKILLS_SH_ROLLOUT_MODE=test");
expect(deployStep?.run).toContain("--build-env CLAWHUB_GITHUB_SKILL_SYNC_ROLLOUT_MODE=test");
expect(deployStep?.run).toContain("--env CLAWHUB_SKILLS_SH_ROLLOUT_MODE=test");
@@ -137,4 +165,19 @@ describe("Test deploy workflow", () => {
expect(verify?.run).toContain(".skillsSh.runtimeEnabled == true");
expect(verify?.run).toContain(".githubSkillSync.selfServiceEnabled == true");
});
it("proves both immutable controlled mirror entries before cleanup", async () => {
const workflow = await readWorkflow();
const step = workflow.jobs?.["claw563-mirror-load"]?.steps?.find(
(candidate) =>
candidate.name === "Load and prove the authenticated leaderboard mirror foundation",
);
const run = step?.run ?? "";
expect(run).toContain('"externalId":"patrick-erichsen/skills/html"');
expect(run).toContain('"externalId":"steipete/clawdis/discrawl"');
expect(run).toContain("050daba89f6b6636470add5cb300aac46a412cf8");
expect(run).toContain("690ed564419291ca6e832dc69b53061300075b62");
expect(run).toContain("claw563-discrawl-entry.json");
});
});
+2 -2
View File
@@ -687,7 +687,7 @@ describe("Header", () => {
expect(labels).toEqual(["Home", "Skills", "Plugins", "Official", "Docs"]);
});
it("links profile and starred skills from the signed-in avatar menu", () => {
it("links profile and bookmarks from the signed-in avatar menu", () => {
profileHandleMock.mockReturnValue("patrick-profile");
authStatusMock.mockReturnValue({
isAuthenticated: true,
@@ -710,7 +710,7 @@ describe("Header", () => {
expect(profile.compareDocumentPosition(dashboard) & Node.DOCUMENT_POSITION_FOLLOWING).toBe(
Node.DOCUMENT_POSITION_FOLLOWING,
);
expect(screen.getByText("Stars").closest("a")?.getAttribute("href")).toBe("/stars");
expect(screen.getByText("Bookmarks").closest("a")?.getAttribute("href")).toBe("/stars");
expect(screen.getAllByText("Dashboard").length).toBeGreaterThan(0);
expect(screen.getByText("Settings")).toBeTruthy();
});
+5 -5
View File
@@ -1789,20 +1789,20 @@ describe("SkillDetailPage", () => {
render(<SkillDetailPage slug="weather" />);
const starButton = await screen.findByRole("button", { name: "Star skill" });
const starButton = await screen.findByRole("button", { name: "Bookmark skill" });
expect(starButton.textContent).toContain("8");
fireEvent.click(starButton);
await waitFor(() => {
expect(screen.getByRole("button", { name: "Unstar skill" }).textContent).toContain("9");
expect(screen.getByRole("button", { name: "Remove bookmark" }).textContent).toContain("9");
});
expect(toggleStarMock).toHaveBeenCalledWith({ skillId });
fireEvent.click(screen.getByRole("button", { name: "Unstar skill" }));
fireEvent.click(screen.getByRole("button", { name: "Remove bookmark" }));
await waitFor(() => {
expect(screen.getByRole("button", { name: "Star skill" }).textContent).toContain("8");
expect(screen.getByRole("button", { name: "Bookmark skill" }).textContent).toContain("8");
});
expect(toggleStarMock).toHaveBeenCalledTimes(2);
expect(routerInvalidateMock).toHaveBeenCalledTimes(2);
@@ -1869,7 +1869,7 @@ describe("SkillDetailPage", () => {
render(<SkillDetailPage slug="weather" />);
expect((await screen.findByRole("button", { name: "Unstar skill" })).textContent).toContain(
expect((await screen.findByRole("button", { name: "Remove bookmark" })).textContent).toContain(
"1",
);
});
+2 -2
View File
@@ -107,7 +107,7 @@ describe("SkillsIndex", () => {
const sortOptions = Array.from(
screen.getByRole("radiogroup", { name: "Skill view" }).querySelectorAll('[role="radio"]'),
).map((option) => option.textContent);
expect(sortOptions).toEqual(["All", "Trending", "Top", "Most starred", "Featured"]);
expect(sortOptions).toEqual(["All", "Trending", "Top", "Most bookmarked", "Featured"]);
});
it("offers Top without exposing downloads as a browse view", async () => {
@@ -144,7 +144,7 @@ describe("SkillsIndex", () => {
fireEvent.click(screen.getByRole("combobox", { name: "Sort" }));
const sortOptions = screen.getAllByRole("option").map((option) => option.textContent);
expect(views).toEqual(["All", "Trending", "Top", "Most starred", "Featured"]);
expect(views).toEqual(["All", "Trending", "Top", "Most bookmarked", "Featured"]);
expect(sortOptions).toEqual(["Recently updated", "Newest", "Name"]);
});
+1 -1
View File
@@ -649,7 +649,7 @@ export default function Header() {
<DropdownMenuItem asChild>
<Link to="/stars" className="flex items-center gap-2">
<Star size={14} aria-hidden="true" />
Stars
Bookmarks
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
+2 -2
View File
@@ -763,8 +763,8 @@ export function SkillDetailPage({
});
void router.invalidate();
} catch (error) {
console.error("Failed to toggle star", error);
toast.error(getUserFacingConvexError(error, "Unable to update star. Please try again."));
console.error("Failed to toggle bookmark", error);
toast.error(getUserFacingConvexError(error, "Unable to update bookmark. Please try again."));
}
};
+6 -6
View File
@@ -162,14 +162,14 @@ describe("SkillHeader", () => {
);
}
it("keeps signed-out star and report actions visible and routes clicks to sign-in", () => {
it("keeps signed-out bookmark and report actions visible and routes clicks to sign-in", () => {
const onToggleStar = vi.fn();
const onOpenReport = vi.fn();
const onRequireSignIn = vi.fn();
const { container } = renderHeader({ onToggleStar, onOpenReport, onRequireSignIn });
fireEvent.click(screen.getByRole("button", { name: "Star skill" }));
fireEvent.click(screen.getByRole("button", { name: "Bookmark skill" }));
fireEvent.click(screen.getByRole("button", { name: "Report" }));
expect(onRequireSignIn).toHaveBeenCalledTimes(2);
@@ -488,19 +488,19 @@ describe("SkillHeader", () => {
).toBeTruthy();
});
it("places Star in the sidebar without an outline button", () => {
it("places Bookmark in the sidebar without an outline button", () => {
const { container } = renderHeader();
const starBand = container.querySelector(".skill-sidebar-star-band");
expect(starBand).toBeTruthy();
const starButton = within(starBand as HTMLElement).getByRole("button", {
name: "Star skill",
name: "Bookmark skill",
});
expect(starButton.className).toContain("skill-sidebar-star-action");
expect(container.querySelector(".skill-hero-title-row .skill-title-actions")).toBeNull();
});
it("places Star on the creator row on mobile detail layout", () => {
it("places Bookmark on the creator row on mobile detail layout", () => {
setViewportWidth(390);
const { container } = renderHeader();
@@ -508,7 +508,7 @@ describe("SkillHeader", () => {
const creator = container.querySelector(".skill-hero-creator");
expect(creator).toBeTruthy();
const starButton = within(creator as HTMLElement).getByRole("button", {
name: "Star skill",
name: "Bookmark skill",
});
expect(starButton.className).toContain("skill-sidebar-star-action");
expect(starButton.closest(".skill-hero-creator-star")).toBeTruthy();
+3 -3
View File
@@ -201,21 +201,21 @@ export function SkillHeader({
const renderStarAction = () => (
<SignedInActionTooltip
isAuthenticated={isAuthenticated}
message="You must be signed in to star a skill"
message="You must be signed in to bookmark a skill"
>
<button
type="button"
className="skill-sidebar-action-link skill-sidebar-star-action"
onClick={isAuthenticated ? onToggleStar : onRequireSignIn}
aria-pressed={Boolean(isAuthenticated && isStarred)}
aria-label={isStarred ? "Unstar skill" : "Star skill"}
aria-label={isStarred ? "Remove bookmark" : "Bookmark skill"}
>
<Star
size={14}
aria-hidden="true"
fill={isAuthenticated && isStarred ? "currentColor" : "none"}
/>
{isAuthenticated && isStarred ? "Unstar" : "Star"}
{isAuthenticated && isStarred ? "Bookmarked" : "Bookmark"}
<span className="skill-action-count">{formattedStats.stars}</span>
</button>
</SignedInActionTooltip>
+1 -1
View File
@@ -212,7 +212,7 @@ function UserStatsTooltipContent({
</span>
<span
className="flex items-center gap-1 text-fs-xs text-ink-soft"
title="Stars received"
title="Bookmarks received"
>
<Star size={12} />
{formatCompactStat(stats.totalStars)}
@@ -0,0 +1,60 @@
/* @vitest-environment jsdom */
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { DashboardCatalogView } from "./DashboardCatalogView";
import type { DashboardCatalogItem, DashboardSkill } from "./types";
const skill = {
_id: "skills:demo",
_creationTime: 1,
slug: "demo",
displayName: "Demo",
ownerUserId: "users:owner",
ownerPath: "owner",
tags: {},
badges: {},
stats: {
downloads: 20,
installsCurrent: 3,
installsAllTime: 7,
stars: 4,
versions: 1,
},
metricSources: {
clawHubDownloads: 12,
skillsShInstalls: 8,
openClawInstallsCurrent: 3,
openClawInstallsAllTime: 7,
githubStars: 99,
bookmarks: 4,
},
latestVersion: null,
createdAt: 1,
updatedAt: 1,
} as DashboardSkill;
const item: DashboardCatalogItem = {
kind: "skill",
id: skill._id,
name: skill.displayName,
searchText: "demo",
data: skill,
updatedAt: skill.updatedAt,
installs: 7,
downloads: 20,
};
describe("DashboardCatalogView", () => {
it("lets publishers inspect the source breakdown behind combined downloads", () => {
render(
<DashboardCatalogView items={[item]} view="list" ownerHandle="owner" canManage={true} />,
);
expect(
screen.getByTitle(
"20 downloads: 12 ClawHub downloads + 8 skills.sh installs. 7 OpenClaw installs; 99 GitHub stars; 4 bookmarks.",
),
).toBeTruthy();
});
});
@@ -110,6 +110,7 @@ function SkillListRow({
secondary={packageRowSecondary(skill.updatedAt)}
status={skillArtifactStatus(skill)}
downloads={skill.stats?.downloads ?? 0}
downloadTitle={skillMetricSourceLabel(skill)}
menu={<CatalogRowMenu item={item} ownerHandle={ownerHandle} canManage={canManage} />}
/>
);
@@ -149,6 +150,7 @@ function CatalogRow({
secondary,
status,
downloads,
downloadTitle,
menu,
}: {
href: string;
@@ -159,6 +161,7 @@ function CatalogRow({
secondary: string;
status: ArtifactDisplayStatus;
downloads: number;
downloadTitle?: string;
menu: ReactNode;
}) {
return (
@@ -180,7 +183,10 @@ function CatalogRow({
<SecurityAuditMiniStatus status={status} />
</div>
<div className="skill-list-item-meta">
<span className="dashboard-catalog-downloads" title={metricLabel(downloads, "download")}>
<span
className="dashboard-catalog-downloads"
title={downloadTitle ?? metricLabel(downloads, "download")}
>
<Download size={14} aria-hidden="true" />
<span aria-hidden="true">{formatCompactStat(downloads)}</span>
<span className="sr-only">{metricLabel(downloads, "download")}</span>
@@ -216,6 +222,13 @@ function metricLabel(value: number, noun: string) {
return `${value} ${noun}${value === 1 ? "" : "s"}`;
}
function skillMetricSourceLabel(skill: DashboardSkill) {
const sources = skill.metricSources;
const downloads = skill.stats?.downloads ?? 0;
if (!sources) return metricLabel(downloads, "download");
return `${metricLabel(downloads, "download")}: ${sources.clawHubDownloads} ClawHub downloads + ${sources.skillsShInstalls} skills.sh installs. ${sources.openClawInstallsAllTime} OpenClaw installs; ${sources.githubStars} GitHub stars; ${sources.bookmarks} bookmarks.`;
}
function visibilityIcon(label: string) {
if (label !== "Hidden" && label !== "Removed") return undefined;
return (
@@ -238,6 +251,7 @@ function SkillGridCard({ skill, ownerHandle }: { skill: DashboardSkill; ownerHan
kindLabel="Skill"
status={skillArtifactStatus(skill)}
downloads={skill.stats?.downloads ?? 0}
downloadTitle={skillMetricSourceLabel(skill)}
updatedAt={skill.updatedAt}
/>
);
@@ -268,6 +282,7 @@ function DashboardCatalogGridCard({
kindLabel,
status,
downloads,
downloadTitle,
updatedAt,
}: {
href: string;
@@ -278,6 +293,7 @@ function DashboardCatalogGridCard({
kindLabel: "Skill" | "Plugin";
status: ArtifactDisplayStatus;
downloads: number;
downloadTitle?: string;
updatedAt: number;
}) {
return (
@@ -304,7 +320,7 @@ function DashboardCatalogGridCard({
</span>
<span
className="dashboard-catalog-grid-card-downloads"
title={metricLabel(downloads, "download")}
title={downloadTitle ?? metricLabel(downloads, "download")}
>
<Download size={13} aria-hidden="true" />
<span aria-hidden="true">{formatCompactStat(downloads)}</span>
+8
View File
@@ -27,6 +27,14 @@ export type DashboardSkill = Pick<
ownerPath: string;
detailHref?: string;
settingsHref?: string;
metricSources?: {
clawHubDownloads: number;
skillsShInstalls: number;
openClawInstallsCurrent: number;
openClawInstallsAllTime: number;
githubStars: number;
bookmarks: number;
};
pendingReview?: boolean;
qualityDecision?: "pass" | "quarantine" | "reject";
latestVersion: {
@@ -4,7 +4,7 @@ import { Skeleton } from "../ui/skeleton";
export function StarsSkeleton() {
return (
<main className="browse-page" aria-busy="true" aria-label="Loading starred skills">
<main className="browse-page" aria-busy="true" aria-label="Loading bookmarked skills">
<header className="stars-header">
<Skeleton className="h-9 w-48" />
</header>
+7 -7
View File
@@ -87,7 +87,7 @@ describe("Stars", () => {
render(<Stars />);
expect(screen.getByText("Sign in to see your highlights")).toBeTruthy();
expect(screen.getByText("Sign in to see your bookmarks")).toBeTruthy();
expect(screen.getByRole("button", { name: /sign in/i })).toBeTruthy();
});
@@ -101,7 +101,7 @@ describe("Stars", () => {
render(<Stars />);
expect(document.querySelector(".skeleton-list")).toBeTruthy();
expect(screen.queryByText("No stars yet")).toBeNull();
expect(screen.queryByText("No bookmarks yet")).toBeNull();
});
it("shows empty state when user has no stars", () => {
@@ -112,9 +112,9 @@ describe("Stars", () => {
render(<Stars />);
expect(screen.getByText("No stars yet")).toBeTruthy();
expect(screen.getByText("No bookmarks yet")).toBeTruthy();
expect(screen.getByRole("link", { name: "Browse skills" })).toBeTruthy();
expect(screen.queryByRole("combobox", { name: "Sort starred skills" })).toBeNull();
expect(screen.queryByRole("combobox", { name: "Sort bookmarked skills" })).toBeNull();
expect(screen.queryByRole("link", { name: "Grid view" })).toBeNull();
expect(screen.queryByRole("link", { name: "List view" })).toBeNull();
});
@@ -128,8 +128,8 @@ describe("Stars", () => {
render(<Stars />);
expect(screen.getByRole("heading", { name: "Test Skill" })).toBeTruthy();
expect(screen.getByLabelText("Unstar Test Skill")).toBeTruthy();
expect(screen.getByText("Your highlights")).toBeTruthy();
expect(screen.getByLabelText("Remove bookmark for Test Skill")).toBeTruthy();
expect(screen.getByText("Your bookmarks")).toBeTruthy();
});
it("calls toggleStar when unstar button is clicked", () => {
@@ -140,7 +140,7 @@ describe("Stars", () => {
});
render(<Stars />);
const unstarBtn = screen.getByLabelText("Unstar Test Skill");
const unstarBtn = screen.getByLabelText("Remove bookmark for Test Skill");
fireEvent.click(unstarBtn);
expect(toggleStarMock).toHaveBeenCalledWith({ skillId: "skill_1" });
+1 -1
View File
@@ -41,7 +41,7 @@ const SKILLS_VIEW_OPTIONS = [
{ value: "all", label: "All" },
{ value: "trending", label: "Trending" },
{ value: "top", label: "Top" },
{ value: "stars", label: "Most starred" },
{ value: "stars", label: "Most bookmarked" },
{ value: "featured", label: "Featured" },
];
+2 -2
View File
@@ -1167,8 +1167,8 @@ export function Upload() {
<span>
Move ownership of <strong>{trimmedSlug || "this skill"}</strong> from{" "}
<strong>@{existingOwnerHandle}</strong> to <strong>@{ownerHandle}</strong>.
Versions, tags, stats and stars are preserved; the old URL redirects to the
new one.
Versions, tags, stats and bookmarks are preserved; the old URL redirects to
the new one.
</span>
</label>
) : null}
+13 -13
View File
@@ -82,8 +82,8 @@ export function Stars() {
startTransition(() => {
updateOptimisticSkills({ type: "restore", skill });
});
console.error("Failed to unstar skill:", err);
toast.error("Unable to unstar this skill. Please try again.");
console.error("Failed to remove skill bookmark:", err);
toast.error("Unable to remove this bookmark. Please try again.");
});
};
@@ -95,8 +95,8 @@ export function Stars() {
return (
<SignInPrompt
icon={Star}
title="Sign in to see your highlights"
description="Star skills for quick access later."
title="Sign in to see your bookmarks"
description="Bookmark skills for quick access later."
/>
);
}
@@ -109,7 +109,7 @@ export function Stars() {
<main className="browse-page">
<header className="stars-header">
<h1 className="stars-header-title font-display text-3xl font-black leading-none text-[color:var(--ink)]">
Your highlights
Your bookmarks
</h1>
{hasStars ? (
<div className="stars-header-controls">
@@ -126,24 +126,24 @@ export function Stars() {
>
<SelectTrigger
className="stars-sort-trigger h-8 min-w-[140px] text-xs font-semibold"
aria-label="Sort starred skills"
aria-label="Sort bookmarked skills"
>
<ArrowDownUp className="mr-1.5 h-3.5 w-3.5" />
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="starred">Recently starred</SelectItem>
<SelectItem value="starred">Recently bookmarked</SelectItem>
<SelectItem value="updated" disabled={!canSortCompleteSet}>
Recently updated
</SelectItem>
<SelectItem value="stars" disabled={!canSortCompleteSet}>
Most stars
Most bookmarked
</SelectItem>
</SelectContent>
</Select>
<nav
className="publisher-filter-tabs publisher-view-tabs stars-view-tabs"
aria-label="Starred skills view"
aria-label="Bookmarked skills view"
>
<Link
to="/stars"
@@ -172,8 +172,8 @@ export function Stars() {
{skills.length === 0 ? (
<EmptyState
icon={Star}
title="No stars yet"
description="Browse skills and star your favorites."
title="No bookmarks yet"
description="Browse skills and bookmark your favorites."
action={{ label: "Browse skills", href: "/skills" }}
/>
) : activeView === "grid" ? (
@@ -198,7 +198,7 @@ export function Stars() {
e.stopPropagation();
handleUnstar(skill);
}}
aria-label={`Unstar ${skill.displayName}`}
aria-label={`Remove bookmark for ${skill.displayName}`}
className="stars-card-unstar text-[color:var(--gold)] hover:text-status-error-fg"
>
<Star className="h-4 w-4 fill-current" />
@@ -220,7 +220,7 @@ export function Stars() {
e.stopPropagation();
handleUnstar(skill);
}}
aria-label={`Unstar ${skill.displayName}`}
aria-label={`Remove bookmark for ${skill.displayName}`}
className="absolute right-3 top-1/2 -translate-y-1/2 text-[color:var(--gold)] hover:text-status-error-fg"
>
<Star className="h-4 w-4 fill-current" />
+4 -4
View File
@@ -156,7 +156,7 @@ const DEFAULT_PUBLISHER_BIO = "Publisher on Clawhub.";
const PROFILE_CATALOG_SORT_OPTIONS = [
{ value: "downloads", label: "Most downloaded" },
{ value: "recent", label: "Recent" },
{ value: "stars", label: "Stars" },
{ value: "stars", label: "Bookmarks" },
] as const;
const DEFAULT_PROFILE_CATALOG_SORT: ProfileCatalogSort = "downloads";
@@ -193,7 +193,7 @@ function buildCatalogTabOptions(publisher: PublicPublisherProfileItem) {
if (publisher.kind === "user") {
options.push({
value: "stars",
label: "Starred",
label: "Bookmarks",
count:
publisher.starredCount === undefined
? undefined
@@ -285,7 +285,7 @@ export function buildPublisherStatCards(
{
key: "stars",
value: formatCompactStat(publisher.stats.stars),
label: "stars",
label: "bookmarks",
icon: Star,
},
];
@@ -954,7 +954,7 @@ export function PublisherProfilePage({
: catalogSearch.trim().length > 0
? "No matching items"
: catalogTab === "stars"
? "No starred items yet"
? "No bookmarks yet"
: catalogTab === "plugins"
? "No published plugins yet"
: "No published skills yet"