diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29c04e4f..4963af0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -179,6 +179,8 @@ jobs: - name: publish-new-version specs: e2e/local-auth/publish-skill-lifecycle.pw.test.ts grep: skill publishers can create a skill + - name: old-cli-publish + specs: e2e/local-auth/old-cli-publish-compat.pw.test.ts steps: - uses: actions/checkout@v7 diff --git a/.github/workflows/prepublication-publish-checks.yml b/.github/workflows/prepublication-publish-checks.yml index ff05fae7..b8ac5c5d 100644 --- a/.github/workflows/prepublication-publish-checks.yml +++ b/.github/workflows/prepublication-publish-checks.yml @@ -49,46 +49,25 @@ jobs: PREPUBLICATION_CHECK_LIMIT: ${{ inputs['batch-limit'] || '2' }} PREPUBLICATION_CHECK_MAX_JOBS: ${{ inputs['max-jobs'] || '' }} PREPUBLICATION_CHECK_MAX_RUNTIME_MINUTES: ${{ inputs['max-runtime-minutes'] || '8' }} - CODEX_SECURITY_SCAN_TIMEOUT_MS: ${{ vars.CODEX_SECURITY_SCAN_TIMEOUT_MS || '240000' }} + PREPUBLICATION_CLAWSCAN_TIMEOUT_MS: ${{ vars.PREPUBLICATION_CLAWSCAN_TIMEOUT_MS || '240000' }} PREPUBLICATION_TRUFFLEHOG_IMAGE: ${{ vars.PREPUBLICATION_TRUFFLEHOG_IMAGE || 'ghcr.io/trufflesecurity/trufflehog:3.95.6@sha256:96f8429082cb2d4ae73b1096dcdb2f5aa139881d97042b0c5e5fa226a392e056' }} PREPUBLICATION_WORKER_ID: "github-actions:${{ github.run_id }}:${{ github.run_attempt }}:${{ matrix.shard }}" - SKILLSPECTOR_PROVIDER: openai steps: - uses: actions/checkout@v7 - uses: ./.github/actions/setup-bun - - uses: actions/setup-python@v6 - with: - python-version: "3.12" - - - name: Install Codex CLI + - name: Install ClawScan CLI run: | set -euo pipefail - if ! command -v codex >/dev/null 2>&1; then - npm install -g @openai/codex@0.142.3 - fi - codex --version - - - name: Install SkillSpector - run: | - set -euo pipefail - python -m venv "$RUNNER_TEMP/skillspector-venv" - source "$RUNNER_TEMP/skillspector-venv/bin/activate" - python -m pip install --upgrade pip - python -m pip install 'git+https://github.com/NVIDIA/skillspector.git@8f37cfa' - echo "$RUNNER_TEMP/skillspector-venv/bin" >> "$GITHUB_PATH" - skillspector --help >/dev/null - - - name: Authenticate Codex CLI - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - run: printf '%s' "$OPENAI_API_KEY" | codex login --with-api-key + npm install -g @openclaw/clawscan@0.1.2 + clawscan --version - name: Run pre-publication publish worker env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} SECURITY_SCAN_WORKER_TOKEN: ${{ secrets.SECURITY_SCAN_WORKER_TOKEN }} + VIRUSTOTAL_API_KEY: ${{ secrets.VT_API_KEY }} run: | bun run publish:prepublication-worker -- \ --batch-limit "$PREPUBLICATION_CHECK_LIMIT" \ diff --git a/.github/workflows/preview-proof.yml b/.github/workflows/preview-proof.yml new file mode 100644 index 00000000..62a64f7b --- /dev/null +++ b/.github/workflows/preview-proof.yml @@ -0,0 +1,148 @@ +name: Preview Proof + +on: + workflow_dispatch: + inputs: + preview_url: + description: "Vercel preview URL to verify" + required: true + type: string + checkout_ref: + description: "Optional git ref or SHA to check out; use this when dispatching from main for a PR preview" + required: false + type: string + expected_sha: + description: "Expected git SHA after checkout" + required: false + type: string + +permissions: + contents: read + +jobs: + dispatch-info: + name: preview-proof-dispatch-info + runs-on: ubuntu-latest + steps: + - name: Summarize dispatch + env: + CHECKOUT_REF: ${{ inputs.checkout_ref }} + EXPECTED_SHA: ${{ inputs.expected_sha }} + PREVIEW_URL: ${{ inputs.preview_url }} + run: | + { + echo "## Preview proof dispatch" + echo + echo "- Workflow ref: \`${GITHUB_REF}\`" + echo "- Workflow SHA: \`${GITHUB_SHA}\`" + echo "- Checkout ref: \`${CHECKOUT_REF:-$GITHUB_SHA}\`" + echo "- Expected SHA: \`${EXPECTED_SHA:-not supplied}\`" + echo "- Preview URL: ${PREVIEW_URL}" + } >> "$GITHUB_STEP_SUMMARY" + + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then + echo "::notice::Protected preview proof only runs from refs/heads/main. Re-run from main with checkout_ref set to the PR SHA." + fi + + preview-proof: + name: preview-proof + needs: dispatch-info + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: + name: Test + + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ inputs.checkout_ref || github.sha }} + + - name: Verify checked-out revision + env: + EXPECTED_SHA: ${{ inputs.expected_sha }} + run: | + set -euo pipefail + actual_sha="$(git rev-parse HEAD)" + echo "actual_sha=$actual_sha" >> "$GITHUB_ENV" + if [[ -n "$EXPECTED_SHA" && "$actual_sha" != "$EXPECTED_SHA" ]]; then + echo "::error::Expected $EXPECTED_SHA but checked out $actual_sha" + exit 1 + fi + + - uses: ./.github/actions/setup-bun + + - name: Verify protected preview public routes + env: + PREVIEW_URL: ${{ inputs.preview_url }} + VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }} + run: | + set -euo pipefail + bun <<'TS' + const previewUrl = process.env.PREVIEW_URL?.trim(); + const bypassSecret = process.env.VERCEL_AUTOMATION_BYPASS_SECRET?.trim(); + if (!previewUrl) throw new Error("PREVIEW_URL is required"); + if (!bypassSecret) throw new Error("VERCEL_AUTOMATION_BYPASS_SECRET is required"); + + const base = new URL(previewUrl); + const headers = { "x-vercel-protection-bypass": bypassSecret }; + + async function fetchText(path: string) { + const response = await fetch(new URL(path, base), { headers }); + if (!response.ok) { + throw new Error(`${path} returned ${response.status} ${response.statusText}`); + } + return await response.text(); + } + + async function fetchJson(path: string) { + const response = await fetch(new URL(path, base), { + headers: { ...headers, accept: "application/json" }, + }); + if (!response.ok) { + throw new Error(`${path} returned ${response.status} ${response.statusText}`); + } + return await response.json(); + } + + const home = await fetchText("/"); + if (!home.includes("ClawHub")) { + throw new Error("Preview home page did not render the ClawHub shell"); + } + + const detail = await fetchJson("/api/v1/skills/gifgrep"); + if (detail?.skill?.slug !== "gifgrep") { + throw new Error(`Expected gifgrep API detail, received ${JSON.stringify(detail)}`); + } + + const skillFile = await fetchText("/api/v1/skills/gifgrep/file?path=SKILL.md"); + if (!skillFile.trim()) { + throw new Error("Preview skill file route returned an empty SKILL.md"); + } + + console.log( + JSON.stringify( + { + previewUrl: base.toString(), + skillSlug: detail.skill.slug, + ownerHandle: detail.owner?.handle ?? null, + latestVersion: detail.latestVersion?.version ?? null, + skillFileBytes: skillFile.length, + }, + null, + 2, + ), + ); + TS + + - name: Write proof summary + env: + PREVIEW_URL: ${{ inputs.preview_url }} + run: | + { + echo "## Preview proof" + echo + echo "- Git SHA: \`${actual_sha}\`" + echo "- Preview URL: ${PREVIEW_URL}" + echo "- Verified routes: \`/\`, \`/api/v1/skills/gifgrep\`, \`/api/v1/skills/gifgrep/file?path=SKILL.md\`" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts index 4b1fc809..cba3b9b5 100644 --- a/convex/_generated/api.d.ts +++ b/convex/_generated/api.d.ts @@ -105,6 +105,7 @@ import type * as lib_reporting from "../lib/reporting.js"; import type * as lib_reservedHandles from "../lib/reservedHandles.js"; import type * as lib_reservedSlugs from "../lib/reservedSlugs.js"; import type * as lib_retentionPolicy from "../lib/retentionPolicy.js"; +import type * as lib_searchRanking from "../lib/searchRanking.js"; import type * as lib_searchText from "../lib/searchText.js"; import type * as lib_securityPrompt from "../lib/securityPrompt.js"; import type * as lib_securityScanPolicy from "../lib/securityScanPolicy.js"; @@ -125,6 +126,7 @@ import type * as lib_skillZip from "../lib/skillZip.js"; import type * as lib_skills_index from "../lib/skills/index.js"; import type * as lib_skills_slugResolution from "../lib/skills/slugResolution.js"; import type * as lib_staticPublishScan from "../lib/staticPublishScan.js"; +import type * as lib_testSeed from "../lib/testSeed.js"; import type * as lib_tokens from "../lib/tokens.js"; import type * as lib_userSearch from "../lib/userSearch.js"; import type * as lib_userSkillStats from "../lib/userSkillStats.js"; @@ -269,6 +271,7 @@ declare const fullApi: ApiFromModules<{ "lib/reservedHandles": typeof lib_reservedHandles; "lib/reservedSlugs": typeof lib_reservedSlugs; "lib/retentionPolicy": typeof lib_retentionPolicy; + "lib/searchRanking": typeof lib_searchRanking; "lib/searchText": typeof lib_searchText; "lib/securityPrompt": typeof lib_securityPrompt; "lib/securityScanPolicy": typeof lib_securityScanPolicy; @@ -289,6 +292,7 @@ declare const fullApi: ApiFromModules<{ "lib/skills/index": typeof lib_skills_index; "lib/skills/slugResolution": typeof lib_skills_slugResolution; "lib/staticPublishScan": typeof lib_staticPublishScan; + "lib/testSeed": typeof lib_testSeed; "lib/tokens": typeof lib_tokens; "lib/userSearch": typeof lib_userSearch; "lib/userSkillStats": typeof lib_userSkillStats; diff --git a/convex/devSeed.ts b/convex/devSeed.ts index a4da1544..6fdb16b3 100644 --- a/convex/devSeed.ts +++ b/convex/devSeed.ts @@ -5208,6 +5208,51 @@ export const getAccountRecreationState: ReturnType<typeof rawInternalMutation> = }, }); +export const getPrePublicationSkillAttemptState: ReturnType<typeof rawInternalMutation> = + rawInternalMutation({ + args: { + attemptId: v.id("publishAttempts"), + }, + handler: async (ctx, args) => { + const attempt = await ctx.db.get(args.attemptId); + if (!attempt || attempt.kind !== "skill") { + return { + ok: true as const, + attemptExists: Boolean(attempt), + skillExists: false, + versionExists: false, + }; + } + + const skill = attempt.skillId ? await ctx.db.get(attempt.skillId) : null; + const version = attempt.skillVersionId ? await ctx.db.get(attempt.skillVersionId) : null; + + return { + ok: true as const, + attemptExists: true, + attempt: { + attemptId: attempt._id, + status: attempt.status, + slug: attempt.slug, + version: attempt.version, + skillId: attempt.skillId ?? null, + skillVersionId: attempt.skillVersionId ?? null, + filesCount: attempt.files.length, + hasSkillInsertArgs: attempt.skillInsertArgs !== undefined, + hasFollowup: attempt.followup !== undefined, + trufflehogStatus: attempt.checks.trufflehog.status, + trufflehogRedactedFindingCount: attempt.checks.trufflehog.redactedFindings?.length ?? 0, + clawscanStatus: attempt.checks.clawscan.status, + blockedAt: attempt.blockedAt ?? null, + }, + skillExists: Boolean(skill), + skillLatestVersionId: skill?.latestVersionId ?? null, + versionExists: Boolean(version), + versionPublicationStatus: version?.publicationStatus ?? null, + }; + }, + }); + async function upsertRoleHelpFixtureUser(ctx: MutationCtx, user: RoleHelpFixtureUser) { const now = Date.now(); const existing = await ctx.db diff --git a/convex/lib/skillFileAccess.ts b/convex/lib/skillFileAccess.ts index 9bcfc2ba..bd7c15f6 100644 --- a/convex/lib/skillFileAccess.ts +++ b/convex/lib/skillFileAccess.ts @@ -14,6 +14,7 @@ export type SkillFileModerationInfo = { type SkillVersionSecuritySource = { _id: Id<"skillVersions"> | string; + publicationStatus?: "pending" | "published" | "blocked" | null; llmAnalysis?: { status?: string | null; verdict?: string | null; @@ -132,15 +133,35 @@ export function isSkillVersionForSkill( return version?.skillId === skillId; } +export function isPublishedSkillVersion( + version: + | { + publicationStatus?: string | null; + } + | null + | undefined, +) { + return Boolean( + version && + (version.publicationStatus === undefined || version.publicationStatus === "published"), + ); +} + export function isPublicSkillVersionAvailableForSkill( version: | { skillId?: Id<"skills"> | string | null; softDeletedAt?: number | null; + publicationStatus?: string | null; } | null | undefined, skillId: Id<"skills"> | string, ) { - return Boolean(version && !version.softDeletedAt && isSkillVersionForSkill(version, skillId)); + return Boolean( + version && + !version.softDeletedAt && + isPublishedSkillVersion(version) && + isSkillVersionForSkill(version, skillId), + ); } diff --git a/convex/lib/skillPublish.test.ts b/convex/lib/skillPublish.test.ts index efcf5ab8..012d6172 100644 --- a/convex/lib/skillPublish.test.ts +++ b/convex/lib/skillPublish.test.ts @@ -419,7 +419,7 @@ description: Research helper for literature reviews. ); }); - it("stages publish attempts without creating a public version inline", async () => { + it("staged publishes create a real pending version and return legacy-compatible ids", async () => { const storedFiles = new Map([ [ "_storage:skill", @@ -431,19 +431,27 @@ description: Security scanner smoke fixture. ], ]); const runMutation = vi.fn(async (_ref: unknown, args: Record<string, unknown>) => { - if ("skillInsertArgs" in args) { + if ("publicationStatus" in args) { + return { + skillId: "skills:demo", + versionId: "skillVersions:pending", + publicationStatus: "pending", + }; + } + if ("skillVersionId" in args) { return { attemptId: "publishAttempts:security-scanner-smoke", status: "pending_checks", }; } - throw new Error("publish should not create a public version before checks pass"); + throw new Error("unexpected staged publish mutation"); }); const scheduler = { runAfter: vi.fn() }; const ctx = { runQuery: vi .fn() .mockResolvedValueOnce(null) + .mockResolvedValueOnce(null) .mockResolvedValueOnce({ _id: "users:1", handle: "demo", createdAt: 1 }), runMutation, scheduler, @@ -481,24 +489,122 @@ description: Security scanner smoke fixture. ); expect(result).toEqual({ + skillId: "skills:demo", + versionId: "skillVersions:pending", status: "pending", - attemptId: "publishAttempts:security-scanner-smoke", slug: "security-scanner-smoke", version: "1.0.0", + publicationStatus: "pending", + attemptId: "publishAttempts:security-scanner-smoke", }); - expect(runMutation).toHaveBeenCalledTimes(1); + expect(runMutation).toHaveBeenCalledTimes(2); expect(runMutation).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ - skillInsertArgs: expect.objectContaining({ - slug: "security-scanner-smoke", - version: "1.0.0", - }), + slug: "security-scanner-smoke", + version: "1.0.0", + publicationStatus: "pending", + deferredAiEnrichment: expect.any(Object), + }), + ); + expect(runMutation).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + skillId: "skills:demo", + skillVersionId: "skillVersions:pending", + artifactFingerprint: expect.any(String), + }), + ); + expect(runMutation).not.toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + skillInsertArgs: expect.anything(), }), ); expect(scheduler.runAfter).not.toHaveBeenCalled(); }); + it("cleans up the pending version when staged publish attempt creation fails", async () => { + const storedFiles = new Map([ + [ + "_storage:skill", + `--- +description: Security scanner smoke fixture. +--- +# Security Scanner Smoke +`, + ], + ]); + const runMutation = vi.fn(async (_ref: unknown, args: Record<string, unknown>) => { + if ("publicationStatus" in args) { + return { + skillId: "skills:demo", + versionId: "skillVersions:pending", + publicationStatus: "pending", + createdNewParent: true, + }; + } + if ("skillVersionId" in args) { + throw new Error("attempt creation outage"); + } + if ("versionId" in args && "createdNewParent" in args) { + return { deleted: true, parentDeleted: true }; + } + throw new Error("unexpected staged publish mutation"); + }); + const ctx = { + runQuery: vi + .fn() + .mockResolvedValueOnce(null) + .mockResolvedValueOnce(null) + .mockResolvedValueOnce({ _id: "users:1", handle: "demo", createdAt: 1 }), + runMutation, + scheduler: { runAfter: vi.fn() }, + storage: { + get: vi.fn(async (storageId: string) => { + const content = storedFiles.get(storageId); + return content === undefined ? null : new Blob([content]); + }), + }, + }; + + await expect( + stageSkillPublishAttemptForUser( + ctx as never, + "users:1" as never, + { + slug: "security-scanner-smoke", + displayName: "Security Scanner Smoke", + version: "1.0.0", + changelog: "Initial release", + files: [ + { + path: "SKILL.md", + size: 90, + storageId: "_storage:skill" as never, + sha256: "a".repeat(64), + contentType: "text/markdown", + }, + ], + }, + { + bypassGitHubAccountAge: true, + bypassQualityGate: true, + skipWebhook: true, + }, + ), + ).rejects.toThrow("attempt creation outage"); + + expect(runMutation).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + skillId: "skills:demo", + versionId: "skillVersions:pending", + createdNewParent: true, + }), + ); + }); + it("rejects duplicate staged skill versions before creating a publish attempt", async () => { const runMutation = vi.fn(async () => { throw new Error("duplicate publish should not create an attempt"); @@ -554,21 +660,62 @@ description: Security scanner smoke fixture. expect(ctx.storage.get).not.toHaveBeenCalled(); }); - it("finalizes a clean staged publish through insertVersion and then enqueues scans", async () => { - const insertArgs = { - userId: "users:1", - slug: "security-scanner-smoke", - displayName: "Security Scanner Smoke", - version: "1.0.0", - embedding: [0, 1, 2], + it("rejects staged skill versions reserved by a retained publish attempt", async () => { + const runMutation = vi.fn(async () => { + throw new Error("duplicate publish should not create an attempt"); + }); + const ctx = { + runQuery: vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce({ + attemptId: "publishAttempts:secret-blocked", + status: "blocked", + }), + runMutation, + scheduler: { runAfter: vi.fn() }, + storage: { + get: vi.fn(), + }, }; + + await expect( + stageSkillPublishAttemptForUser( + ctx as never, + "users:1" as never, + { + slug: "security-scanner-smoke", + displayName: "Security Scanner Smoke", + version: "1.0.0", + changelog: "Duplicate release", + files: [ + { + path: "SKILL.md", + size: 90, + storageId: "_storage:skill" as never, + sha256: "a".repeat(64), + contentType: "text/markdown", + }, + ], + }, + { + bypassGitHubAccountAge: true, + bypassQualityGate: true, + skipWebhook: true, + }, + ), + ).rejects.toThrow("Version 1.0.0 already exists. Increment the version number and try again."); + + expect(runMutation).not.toHaveBeenCalled(); + expect(ctx.storage.get).not.toHaveBeenCalled(); + }); + + it("finalizes a clean staged publish by promoting the existing pending version", async () => { const runMutation = vi.fn(async (_ref: unknown, args: Record<string, unknown>) => { if ("claimId" in args && !("result" in args)) { return { status: "claimed", attemptId: "publishAttempts:security-scanner-smoke", createdAt: Date.parse("2026-07-07T15:00:00Z"), - skillInsertArgs: insertArgs, + skillId: "skills:demo", + versionId: "skillVersions:pending", followup: { skipWebhook: true, slug: "security-scanner-smoke", @@ -577,10 +724,10 @@ description: Security scanner smoke fixture. }, }; } - if ("version" in args && "embedding" in args) { + if ("versionId" in args && !("result" in args)) { return { skillId: "skills:demo", - versionId: "skillVersions:demo", + versionId: "skillVersions:pending", embeddingId: "skillEmbeddings:demo", }; } @@ -599,6 +746,29 @@ description: Security scanner smoke fixture. const scheduler = { runAfter: vi.fn() }; const ctx = { runMutation, + runQuery: vi.fn(async (_ref: unknown, args: Record<string, unknown>) => + "versionId" in args + ? { + userId: "users:1", + displayName: "Security Scanner Smoke", + version: "1.0.0", + changelog: "Initial release", + changelogSource: "user", + tags: ["latest"], + files: [], + parsed: { frontmatter: {}, license: "MIT-0" }, + staticScan: { + status: "clean", + reasonCodes: [], + findings: [], + summary: "No suspicious patterns detected.", + engineVersion: "test", + checkedAt: 1, + }, + embedding: [0, 1, 2], + } + : null, + ), scheduler, }; @@ -609,39 +779,46 @@ description: Security scanner smoke fixture. expect(result).toEqual({ skillId: "skills:demo", - versionId: "skillVersions:demo", + versionId: "skillVersions:pending", embeddingId: "skillEmbeddings:demo", }); - expect(runMutation).toHaveBeenCalledWith(expect.anything(), insertArgs); + expect(runMutation).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + versionId: "skillVersions:pending", + }), + ); + expect(runMutation).not.toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + slug: "security-scanner-smoke", + version: "1.0.0", + publicationStatus: undefined, + }), + ); expect(scheduler.runAfter).toHaveBeenCalledWith(0, expect.anything(), { - versionId: "skillVersions:demo", + versionId: "skillVersions:pending", }); expect(scheduler.runAfter).toHaveBeenCalledWith(0, expect.anything(), { - versionId: "skillVersions:demo", + versionId: "skillVersions:pending", source: "publish", }); expect(scheduler.runAfter).toHaveBeenCalledWith(15_000, expect.anything(), { - versionId: "skillVersions:demo", + versionId: "skillVersions:pending", source: "publish", preserveActiveJob: true, preserveExistingJob: true, }); }); - it("releases the staged publish finalization claim when insertion fails", async () => { - const insertArgs = { - userId: "users:1", - slug: "security-scanner-smoke", - displayName: "Security Scanner Smoke", - version: "1.0.0", - embedding: [0, 1, 2], - }; + it("releases the staged publish finalization claim when promotion fails", async () => { const runMutation = vi.fn(async (_ref: unknown, args: Record<string, unknown>) => { if ("claimId" in args && !("error" in args) && !("result" in args)) { return { status: "claimed", attemptId: "publishAttempts:security-scanner-smoke", - skillInsertArgs: insertArgs, + skillId: "skills:demo", + versionId: "skillVersions:pending", followup: { skipWebhook: true, slug: "security-scanner-smoke", @@ -650,8 +827,8 @@ description: Security scanner smoke fixture. }, }; } - if ("version" in args && "embedding" in args) { - throw new Error("transient insert failure"); + if ("versionId" in args) { + throw new Error("transient promotion failure"); } if ("error" in args) { return { @@ -663,19 +840,40 @@ description: Security scanner smoke fixture. }); const ctx = { runMutation, - runQuery: vi.fn(async () => null), + runQuery: vi.fn(async (_ref: unknown, args: Record<string, unknown>) => + "versionId" in args + ? { + userId: "users:1", + displayName: "Security Scanner Smoke", + version: "1.0.0", + changelog: "Initial release", + changelogSource: "user", + files: [], + parsed: { frontmatter: {}, license: "MIT-0" }, + staticScan: { + status: "clean", + reasonCodes: [], + findings: [], + summary: "No suspicious patterns detected.", + engineVersion: "test", + checkedAt: 1, + }, + embedding: [0, 1, 2], + } + : null, + ), scheduler: { runAfter: vi.fn() }, }; await expect( finalizeSkillPublishAttempt(ctx as never, "publishAttempts:security-scanner-smoke" as never), - ).rejects.toThrow("transient insert failure"); + ).rejects.toThrow("transient promotion failure"); expect(runMutation).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ attemptId: "publishAttempts:security-scanner-smoke", - error: "transient insert failure", + error: "transient promotion failure", }), ); expect(ctx.scheduler.runAfter).not.toHaveBeenCalled(); @@ -744,6 +942,60 @@ description: Security scanner smoke fixture. }); }); + it("finalizes legacy attempts that still store insert args instead of pending version ids", async () => { + const insertArgs = { + userId: "users:1", + slug: "security-scanner-smoke", + displayName: "Security Scanner Smoke", + version: "1.0.0", + embedding: [0, 1, 2], + }; + const publishResult = { + skillId: "skills:demo", + versionId: "skillVersions:demo", + embeddingId: "skillEmbeddings:demo", + }; + const runMutation = vi.fn(async (_ref: unknown, args: Record<string, unknown>) => { + if ("claimId" in args && !("result" in args)) { + return { + status: "claimed", + attemptId: "publishAttempts:legacy", + skillInsertArgs: insertArgs, + followup: { + skipWebhook: true, + slug: "security-scanner-smoke", + version: "1.0.0", + displayName: "Security Scanner Smoke", + }, + }; + } + if ("version" in args && "embedding" in args) return publishResult; + if ("result" in args) { + return { + attemptId: "publishAttempts:legacy", + status: "finalized", + result: args.result, + }; + } + throw new Error("unexpected mutation"); + }); + const scheduler = { runAfter: vi.fn() }; + const ctx = { + runMutation, + runQuery: vi.fn(), + scheduler, + }; + + await expect( + finalizeSkillPublishAttempt(ctx as never, "publishAttempts:legacy" as never), + ).resolves.toEqual(publishResult); + + expect(runMutation).toHaveBeenCalledWith(expect.anything(), insertArgs); + expect(scheduler.runAfter).toHaveBeenCalledWith(0, expect.anything(), { + versionId: "skillVersions:demo", + }); + }); + it("merges github source into metadata", () => { const merged = __test.mergeSourceIntoMetadata( { clawdis: { emoji: "x" } }, diff --git a/convex/lib/skillPublish.ts b/convex/lib/skillPublish.ts index a696eb59..50ca20d3 100644 --- a/convex/lib/skillPublish.ts +++ b/convex/lib/skillPublish.ts @@ -78,14 +78,13 @@ function normalizeStoredSkillCategoryOverride(categories: readonly string[] | un export type PublishResult = { skillId: Id<"skills">; versionId: Id<"skillVersions">; - embeddingId: Id<"skillEmbeddings">; -}; - -export type PendingPublishResult = { - status: "pending"; - attemptId: Id<"publishAttempts">; - slug: string; - version: string; + embeddingId?: Id<"skillEmbeddings">; + status?: "pending" | "published"; + slug?: string; + version?: string; + publicationStatus?: "pending" | "published"; + attemptId?: Id<"publishAttempts">; + createdNewParent?: boolean; }; type SkillPublishFollowup = { @@ -96,7 +95,7 @@ type SkillPublishFollowup = { displayName: string; }; -export type SkillPublishResult = PublishResult | PendingPublishResult; +export type SkillPublishResult = PublishResult; export type PublishVersionArgs = { slug: string; @@ -219,6 +218,23 @@ async function publishVersionForUserInternal( ); } } + if (options.stagePrePublicationChecks) { + const existingAttempt = (await ctx.runQuery( + internal.publishAttempts.findExistingPublishAttemptForArtifactInternal, + { + kind: "skill", + slug: normalizedSlug, + version, + userId, + ownerPublisherId: options.ownerPublisherId, + }, + )) as { attemptId: Id<"publishAttempts"> } | null; + if (existingAttempt) { + throw new ConvexError( + `Version ${version} already exists. Increment the version number and try again.`, + ); + } + } const isNewSkill = !existingSkill; // For new skills, enforce the full write-path rules (length, pattern, @@ -496,12 +512,23 @@ async function publishVersionForUserInternal( return publishResult; } - const staged = (await ctx.runMutation( - internal.publishAttempts.createSkillPublishAttemptInternal, - { + const pendingInsertArgs = { + ...skillInsertArgs, + publicationStatus: "pending" as const, + }; + const pendingResult = (await ctx.runMutation( + internal.skills.insertVersion, + pendingInsertArgs, + )) as PublishResult; + + const staged = (await ctx + .runMutation(internal.publishAttempts.createSkillPublishAttemptInternal, { userId, ownerPublisherId: options.ownerPublisherId, sourceOwnerPublisherId: options.sourceOwnerPublisherId, + skillId: pendingResult.skillId, + skillVersionId: pendingResult.versionId, + createdNewParent: pendingResult.createdNewParent, slug, displayName, version, @@ -517,13 +544,20 @@ async function publishVersionForUserInternal( ...file, path: file.path, })), - skillInsertArgs: stripUndefinedForStoredAttempt(skillInsertArgs), + scanContext: buildSkillPublishAttemptScanContext(skillInsertArgs), followup: { skipWebhook: followup.skipWebhook, ownerHandle, }, - }, - )) as { + }) + .catch(async (error) => { + await ctx.runMutation(internal.skills.discardPendingPublicationInternal, { + skillId: pendingResult.skillId, + versionId: pendingResult.versionId, + createdNewParent: pendingResult.createdNewParent, + }); + throw error; + })) as { attemptId: Id<"publishAttempts">; status: string; result?: PublishResult; @@ -533,7 +567,15 @@ async function publishVersionForUserInternal( return staged.result; } - return { status: "pending", attemptId: staged.attemptId, slug, version }; + return { + skillId: pendingResult.skillId, + versionId: pendingResult.versionId, + status: "pending", + slug, + version, + publicationStatus: "pending", + attemptId: staged.attemptId, + }; } export async function finalizeSkillPublishAttempt( @@ -549,7 +591,9 @@ export async function finalizeSkillPublishAttempt( status: "claimed"; attemptId: Id<"publishAttempts">; createdAt: number; - skillInsertArgs: unknown; + skillId?: Id<"skills">; + versionId?: Id<"skillVersions">; + skillInsertArgs?: unknown; followup: SkillPublishFollowup; } | { @@ -565,11 +609,28 @@ export async function finalizeSkillPublishAttempt( let publishResult: PublishResult; try { - const skillInsertArgs = await prepareSkillInsertArgsForFinalization(ctx, claim.skillInsertArgs); - publishResult = (await ctx.runMutation( - internal.skills.insertVersion, - skillInsertArgs as never, - )) as PublishResult; + if (claim.versionId) { + const rawPublishArgs = await ctx.runQuery( + internal.skills.getPendingVersionPublishArgsInternal, + { + versionId: claim.versionId, + }, + ); + const skillInsertArgs = await prepareSkillInsertArgsForFinalization(ctx, rawPublishArgs); + publishResult = (await ctx.runMutation(internal.skills.publishPendingVersionInternal, { + versionId: claim.versionId, + publishArgs: skillInsertArgs, + })) as PublishResult; + } else { + const skillInsertArgs = await prepareSkillInsertArgsForFinalization( + ctx, + claim.skillInsertArgs, + ); + publishResult = (await ctx.runMutation( + internal.skills.insertVersion, + skillInsertArgs as never, + )) as PublishResult; + } } catch (error) { const existingResult = (await ctx.runQuery( internal.publishAttempts.findSkillPublishAttemptPublicResultInternal, @@ -687,6 +748,27 @@ function stringField(record: Record<string, unknown>, field: string) { return typeof value === "string" ? value : ""; } +function buildSkillPublishAttemptScanContext(insertArgs: unknown) { + const record = + insertArgs && typeof insertArgs === "object" ? (insertArgs as Record<string, unknown>) : {}; + const parsed = + record.parsed && typeof record.parsed === "object" + ? (record.parsed as Record<string, unknown>) + : {}; + return stripUndefinedForStoredAttempt({ + version: { + staticScan: record.staticScan, + parsed: { + metadata: parsed.metadata, + clawdis: parsed.clawdis, + license: parsed.license, + }, + qualityAssessment: record.qualityAssessment, + sourceProvenance: record.sourceProvenance, + }, + }); +} + async function releaseSkillPublishAttemptFinalizationClaim( ctx: ActionCtx, attemptId: Id<"publishAttempts">, diff --git a/convex/packages.public.test.ts b/convex/packages.public.test.ts index d986b645..60a62d64 100644 --- a/convex/packages.public.test.ts +++ b/convex/packages.public.test.ts @@ -40,6 +40,8 @@ import { getVersionByName, getVersionSecurityByNameForViewerInternal, insertReleaseInternal, + publishPendingReleaseInternal, + finalizePackagePublishAttemptInternal, findPackagePublishResultInternal, listPackageModerationQueueInternal, listPluginExportPageInternal, @@ -219,6 +221,7 @@ const insertReleaseInternalHandler = ( displayName: string; family: "skill" | "code-plugin" | "bundle-plugin"; version: string; + publicationStatus?: "pending" | "published"; changelog: string; icon?: string; tags: string[]; @@ -273,6 +276,22 @@ const findPackagePublishResultInternalHandler = ( { ok: true; packageId: string; releaseId: string } | null > )._handler; +const publishPendingReleaseInternalHandler = ( + publishPendingReleaseInternal as unknown as WrappedHandler< + { + releaseId: string; + }, + unknown + > +)._handler; +const finalizePackagePublishAttemptInternalHandler = ( + finalizePackagePublishAttemptInternal as unknown as WrappedHandler< + { + attemptId: string; + }, + unknown + > +)._handler; const reservePackageNameInternalHandler = ( reservePackageNameInternal as unknown as WrappedHandler< { @@ -6024,6 +6043,43 @@ describe("packages public queries", () => { expect(result?.latestRelease).toBeNull(); }); + it("hides package shells that only have pending unpublished releases", async () => { + const { ctx } = makePackageCtx({ + pkg: makePackageDoc({ + latestReleaseId: undefined, + latestVersionSummary: undefined, + stats: { downloads: 0, installs: 0, stars: 0, versions: 0 }, + }), + latestRelease: null, + }); + + await expect( + getByNameHandler(ctx, { + name: "demo-plugin", + }), + ).resolves.toBeNull(); + }); + + it("keeps published non-latest packages visible without latest pointers", async () => { + const { ctx } = makePackageCtx({ + pkg: makePackageDoc({ + latestReleaseId: undefined, + latestVersionSummary: undefined, + stats: { downloads: 0, installs: 0, stars: 0, versions: 1 }, + }), + latestRelease: null, + }); + + await expect( + getByNameHandler(ctx, { + name: "demo-plugin", + }), + ).resolves.toMatchObject({ + package: { name: "demo-plugin", latestVersion: null }, + latestRelease: null, + }); + }); + it("hides soft-deleted releases from public version lists", async () => { const { ctx, releaseIndexNames } = makePackageCtx({ versionsPage: { @@ -6050,6 +6106,78 @@ describe("packages public queries", () => { expect(releaseIndexNames).toContain("by_package_active_created"); }); + it("fills public package version pages after skipping pending releases", async () => { + const releases = [ + makeReleaseDoc({ + _id: "packageReleases:pending", + version: "2.0.0", + publicationStatus: "pending", + }), + makeReleaseDoc({ + _id: "packageReleases:published", + version: "1.0.0", + }), + ]; + const paginate = vi.fn( + async ({ cursor, numItems }: { cursor: string | null; numItems: number }) => { + const start = cursor ? Number(cursor) : 0; + const page = releases.slice(start, start + numItems); + const next = start + page.length; + return { + page, + isDone: next >= releases.length, + continueCursor: next >= releases.length ? "" : String(next), + }; + }, + ); + const releaseIndexNames: string[] = []; + const ctx = { + db: { + get: vi.fn(async (id: string) => + typeof id === "string" && id.startsWith("users:") + ? { _id: id, handle: id.split(":").pop() ?? "user" } + : null, + ), + query: vi.fn((table: string) => { + if (table === "packages") { + return { + withIndex: vi.fn(() => ({ + unique: vi.fn().mockResolvedValue(makePackageDoc()), + })), + }; + } + if (table === "packageReleases") { + return { + withIndex: vi.fn((indexName: string) => { + releaseIndexNames.push(indexName); + return { + order: vi.fn(() => ({ + paginate, + })), + }; + }), + }; + } + throw new Error(`Unexpected table ${table}`); + }), + }, + }; + + const result = await listVersionsHandler(ctx as never, { + name: "demo-plugin", + paginationOpts: { cursor: null, numItems: 1 }, + }); + + expect(result).toMatchObject({ + page: [{ version: "1.0.0" }], + isDone: true, + continueCursor: "", + }); + expect(paginate).toHaveBeenNthCalledWith(1, { cursor: null, numItems: 1 }); + expect(paginate).toHaveBeenNthCalledWith(2, { cursor: "1", numItems: 1 }); + expect(releaseIndexNames).toEqual(["by_package_active_created", "by_package_active_created"]); + }); + it("soft-deletes packages and active releases for the owner", async () => { const { ctx, insert, patch } = makeSoftDeletePackageCtx({ releases: [ @@ -6942,6 +7070,172 @@ describe("packages public queries", () => { ); }); + it("creates pending package releases without updating public latest pointers", async () => { + const existingPackage = makePackageDoc({ + latestReleaseId: "packageReleases:old", + latestVersionSummary: { version: "1.0.0" }, + tags: { latest: "packageReleases:old" }, + stats: { downloads: 0, installs: 0, stars: 0, versions: 1 }, + }); + const priorRelease = makeReleaseDoc({ + _id: "packageReleases:old", + version: "1.0.0", + distTags: ["latest"], + }); + const ctx = makeInsertReleaseCtx(existingPackage, [priorRelease]); + + await expect( + insertReleaseInternalHandler(ctx, { + actorUserId: "users:owner", + ownerUserId: "users:owner", + name: "demo-plugin", + displayName: "Demo Plugin", + family: "code-plugin", + version: "2.0.0", + publicationStatus: "pending", + changelog: "next", + tags: ["latest"], + summary: "pending summary", + files: [], + integritySha256: "pending-sha", + }), + ).resolves.toMatchObject({ + ok: true, + packageId: "packages:demo", + releaseId: "packageReleases:new", + publicationStatus: "pending", + createdNewParent: false, + }); + + expect(ctx.insert).toHaveBeenCalledWith( + "packageReleases", + expect.objectContaining({ + publicationStatus: "pending", + pendingPublication: expect.objectContaining({ + tags: ["latest"], + displayName: "Demo Plugin", + }), + }), + ); + expect(ctx.patch).not.toHaveBeenCalledWith("packages:demo", expect.anything()); + expect(ctx.patch).not.toHaveBeenCalledWith("packageReleases:old", expect.anything()); + }); + + it("publishes a pending package release by promoting the existing row", async () => { + vi.spyOn(Date, "now").mockReturnValue(1_700_000_000_000); + const existingPackage = makePackageDoc({ + latestReleaseId: "packageReleases:old", + latestVersionSummary: { version: "1.0.0" }, + tags: { latest: "packageReleases:old" }, + stats: { downloads: 0, installs: 0, stars: 0, versions: 1 }, + }); + const priorRelease = makeReleaseDoc({ + _id: "packageReleases:old", + version: "1.0.0", + distTags: ["latest"], + publicationStatus: "published", + }); + const pendingRelease = makeReleaseDoc({ + _id: "packageReleases:pending", + version: "2.0.0", + publicationStatus: "pending", + pendingPublication: { + displayName: "Demo Plugin", + tags: ["latest"], + channel: "community", + isOfficial: false, + }, + distTags: ["latest"], + summary: "pending summary", + changelog: "next", + integritySha256: "pending-sha", + verification: { scanStatus: "pending" }, + llmAnalysis: { + status: "clean", + verdict: "clean", + checkedAt: 1_700_000_000_000, + }, + }); + const ctx = makeInsertReleaseCtx(existingPackage, [priorRelease, pendingRelease], { + "packageReleases:pending": pendingRelease, + "packages:demo": existingPackage, + }); + + await expect( + publishPendingReleaseInternalHandler(ctx, { + releaseId: "packageReleases:pending", + }), + ).resolves.toEqual({ + ok: true, + packageId: "packages:demo", + releaseId: "packageReleases:pending", + }); + + expect(ctx.patch).toHaveBeenCalledWith("packageReleases:pending", { + publicationStatus: "published", + pendingPublication: undefined, + verification: { scanStatus: "clean" }, + }); + expect(ctx.patch).toHaveBeenCalledWith("packageReleases:old", { distTags: [] }); + expect(ctx.patch).toHaveBeenCalledWith( + "packages:demo", + expect.objectContaining({ + latestReleaseId: "packageReleases:pending", + latestVersionSummary: expect.objectContaining({ version: "2.0.0" }), + tags: { latest: "packageReleases:pending" }, + stats: { downloads: 0, installs: 0, stars: 0, versions: 2 }, + scanStatus: "clean", + }), + ); + }); + + it("releases release-backed finalization claims when pending promotion fails", async () => { + const promotionError = new Error("promotion failed"); + const runMutation = vi.fn(async (_ref: unknown, args: unknown) => { + if (typeof args === "object" && args !== null && "attemptId" in args && !("error" in args)) { + return { + status: "claimed", + attemptId: "publishAttempts:demo", + packageId: "packages:demo", + releaseId: "packageReleases:pending", + packageFollowup: {}, + }; + } + if (typeof args === "object" && args !== null && "releaseId" in args) { + throw promotionError; + } + if (typeof args === "object" && args !== null && "error" in args) { + return { attemptId: "publishAttempts:demo", status: "ready_to_finalize" }; + } + throw new Error(`Unexpected mutation args ${JSON.stringify(args)}`); + }); + const ctx = { + runMutation, + runQuery: vi.fn(async () => { + throw new Error("legacy insert recovery should not run for release-backed attempts"); + }), + }; + + await expect( + finalizePackagePublishAttemptInternalHandler(ctx as never, { + attemptId: "publishAttempts:demo", + }), + ).rejects.toThrow("promotion failed"); + + expect(runMutation).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ releaseId: "packageReleases:pending" }), + ); + expect(runMutation).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + attemptId: "publishAttempts:demo", + error: "promotion failed", + }), + ); + expect(ctx.runQuery).not.toHaveBeenCalled(); + }); + it("recovers idempotent package publish results for the same owner", async () => { const release = makeReleaseDoc({ integritySha256: "abc123" }); const ctx = { @@ -6986,6 +7280,49 @@ describe("packages public queries", () => { }); }); + it("does not recover pending package publish results as public successes", async () => { + const release = makeReleaseDoc({ + integritySha256: "abc123", + publicationStatus: "pending", + }); + const ctx = { + db: { + query: vi.fn((table: string) => { + if (table === "packages") { + return { + withIndex: vi.fn(() => ({ + unique: vi.fn().mockResolvedValue( + makePackageDoc({ + ownerUserId: "users:owner", + ownerPublisherId: "publishers:owner", + }), + ), + })), + }; + } + if (table === "packageReleases") { + return { + withIndex: vi.fn(() => ({ + unique: vi.fn().mockResolvedValue(release), + })), + }; + } + throw new Error(`Unexpected table ${table}`); + }), + }, + }; + + await expect( + findPackagePublishResultInternalHandler(ctx as never, { + name: "demo-plugin", + version: "1.0.0", + integritySha256: "abc123", + ownerUserId: "users:owner", + ownerPublisherId: "publishers:owner", + }), + ).resolves.toBeNull(); + }); + it("does not recover idempotent package publish results for another owner", async () => { const ctx = { db: { @@ -8473,6 +8810,7 @@ describe("packages public queries", () => { .mockResolvedValueOnce(trustedPublisher) .mockResolvedValueOnce(makePackageDoc({ family: "bundle-plugin" })) .mockResolvedValueOnce(trustedPublisher) + .mockResolvedValueOnce(null) .mockResolvedValueOnce(null), runMutation, runAction: vi.fn(async () => makeCleanPackageInspectorResult()), @@ -8512,7 +8850,21 @@ describe("packages public queries", () => { if ( typeof args === "object" && args !== null && - "packageInsertArgs" in args && + "publicationStatus" in args && + (args as { publicationStatus?: string }).publicationStatus === "pending" + ) { + return { + ok: true, + packageId: "packages:demo", + releaseId: "packageReleases:pending", + publicationStatus: "pending", + createdNewParent: false, + }; + } + if ( + typeof args === "object" && + args !== null && + "packageReleaseId" in args && "packageFollowup" in args ) { return { @@ -8581,6 +8933,9 @@ describe("packages public queries", () => { ).resolves.toMatchObject({ ok: true, status: "pending", + packageId: "packages:demo", + releaseId: "packageReleases:pending", + publicationStatus: "pending", attemptId: "publishAttempts:demo", packageName: "demo-plugin", version: "1.0.0", @@ -8596,6 +8951,243 @@ describe("packages public queries", () => { expect(runMutation).toHaveBeenCalledWith(expect.anything(), { tokenId: "packagePublishTokens:1", }); + expect(runMutation).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + packageId: "packages:demo", + packageReleaseId: "packageReleases:pending", + scanContext: expect.any(Object), + }), + ); + }); + + it("cleans up orphan pending package releases instead of wedging future retries", async () => { + const previousFlag = process.env.CLAWHUB_STAGED_PREPUBLICATION_PUBLISHES; + process.env.CLAWHUB_STAGED_PREPUBLICATION_PUBLISHES = "1"; + const runMutation = vi.fn(async (_ref: unknown, args: unknown) => { + if ( + typeof args === "object" && + args !== null && + "publicationStatus" in args && + (args as { publicationStatus?: string }).publicationStatus === "pending" + ) { + throw new Error("orphan recovery should not insert a second pending release"); + } + if ( + typeof args === "object" && + args !== null && + "packageReleaseId" in args && + "packageFollowup" in args + ) { + throw new Error("orphan cleanup should reject before creating a publish attempt"); + } + if ( + typeof args === "object" && + args !== null && + "releaseId" in args && + "createdNewParent" in args + ) { + return { deleted: true, parentDeleted: true }; + } + return null; + }); + const trustedPublisher = { + _id: "packageTrustedPublishers:1", + packageId: "packages:demo", + provider: "github-actions", + repository: "openclaw/openclaw", + repositoryId: "1", + repositoryOwner: "openclaw", + repositoryOwnerId: "2", + workflowFilename: "plugin-clawhub-release.yml", + environment: "clawhub-release", + }; + const orphanParent = makePackageDoc({ + latestReleaseId: undefined, + latestVersionSummary: undefined, + stats: { downloads: 0, installs: 0, stars: 0, versions: 0 }, + }); + const orphanRelease = makeReleaseDoc({ + _id: "packageReleases:orphan", + packageId: "packages:demo", + integritySha256: "b8107c6a51a6a7554e20d2963348212b80bc816fb59492ee163792103a6b7df6", + publicationStatus: "pending", + }); + const ctx = { + runQuery: vi + .fn() + .mockResolvedValueOnce({ + _id: "packagePublishTokens:1", + packageId: "packages:demo", + provider: "github-actions", + repository: "openclaw/openclaw", + repositoryId: "1", + repositoryOwner: "openclaw", + repositoryOwnerId: "2", + workflowFilename: "plugin-clawhub-release.yml", + environment: "clawhub-release", + version: "1.0.0", + sha: "abc123", + ref: "refs/heads/main", + runId: "100", + runAttempt: "1", + expiresAt: Date.now() + 60_000, + }) + .mockResolvedValueOnce(trustedPublisher) + .mockResolvedValueOnce(orphanParent) + .mockResolvedValueOnce(trustedPublisher) + .mockResolvedValueOnce(orphanRelease) + .mockResolvedValueOnce(null), + runMutation, + runAction: vi.fn(async () => makeCleanPackageInspectorResult()), + scheduler: { + runAfter: vi.fn(), + }, + storage: makePackageManifestStorage(), + }; + + try { + await expect( + publishPackageForTrustedPublisherInternalHandler(ctx as never, { + publishTokenId: "packagePublishTokens:1", + payload: { + name: "demo-plugin", + family: "bundle-plugin", + version: "1.0.0", + changelog: "init", + bundle: { hostTargets: ["desktop"] }, + files: [packageManifestFile], + }, + }), + ).rejects.toThrow( + "Previous pending publish for 1.0.0 did not finish creating security checks. It was cleaned up; retry the publish.", + ); + } finally { + if (previousFlag === undefined) { + delete process.env.CLAWHUB_STAGED_PREPUBLICATION_PUBLISHES; + } else { + process.env.CLAWHUB_STAGED_PREPUBLICATION_PUBLISHES = previousFlag; + } + } + + expect(runMutation).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + packageId: "packages:demo", + releaseId: "packageReleases:orphan", + createdNewParent: true, + }), + ); + }); + + it("cleans up pending package releases when staged publish attempt creation fails", async () => { + const previousFlag = process.env.CLAWHUB_STAGED_PREPUBLICATION_PUBLISHES; + process.env.CLAWHUB_STAGED_PREPUBLICATION_PUBLISHES = "1"; + const runMutation = vi.fn(async (_ref: unknown, args: unknown) => { + if ( + typeof args === "object" && + args !== null && + "publicationStatus" in args && + (args as { publicationStatus?: string }).publicationStatus === "pending" + ) { + return { + ok: true, + packageId: "packages:demo", + releaseId: "packageReleases:pending", + publicationStatus: "pending", + createdNewParent: false, + }; + } + if (typeof args === "object" && args !== null && "packageReleaseId" in args) { + throw new Error("attempt creation outage"); + } + if ( + typeof args === "object" && + args !== null && + "releaseId" in args && + "createdNewParent" in args + ) { + return { deleted: true, parentDeleted: true }; + } + return null; + }); + const trustedPublisher = { + _id: "packageTrustedPublishers:1", + packageId: "packages:demo", + provider: "github-actions", + repository: "openclaw/openclaw", + repositoryId: "1", + repositoryOwner: "openclaw", + repositoryOwnerId: "2", + workflowFilename: "plugin-clawhub-release.yml", + environment: "clawhub-release", + }; + const ctx = { + runQuery: vi + .fn() + .mockResolvedValueOnce({ + _id: "packagePublishTokens:1", + packageId: "packages:demo", + provider: "github-actions", + repository: "openclaw/openclaw", + repositoryId: "1", + repositoryOwner: "openclaw", + repositoryOwnerId: "2", + workflowFilename: "plugin-clawhub-release.yml", + environment: "clawhub-release", + version: "1.0.0", + sha: "abc123", + ref: "refs/heads/main", + runId: "100", + runAttempt: "1", + expiresAt: Date.now() + 60_000, + }) + .mockResolvedValueOnce(trustedPublisher) + .mockResolvedValueOnce(makePackageDoc({ family: "bundle-plugin" })) + .mockResolvedValueOnce(trustedPublisher) + .mockResolvedValueOnce(null) + .mockResolvedValueOnce(null), + runMutation, + runAction: vi.fn(async () => makeCleanPackageInspectorResult()), + scheduler: { + runAfter: vi.fn(), + }, + storage: makePackageManifestStorage(), + }; + + try { + await expect( + publishPackageForTrustedPublisherInternalHandler(ctx as never, { + publishTokenId: "packagePublishTokens:1", + payload: { + name: "demo-plugin", + family: "bundle-plugin", + version: "1.0.0", + changelog: "init", + bundle: { hostTargets: ["desktop"] }, + files: [packageManifestFile], + }, + }), + ).rejects.toThrow("attempt creation outage"); + } finally { + if (previousFlag === undefined) { + delete process.env.CLAWHUB_STAGED_PREPUBLICATION_PUBLISHES; + } else { + process.env.CLAWHUB_STAGED_PREPUBLICATION_PUBLISHES = previousFlag; + } + } + + expect(runMutation).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + packageId: "packages:demo", + releaseId: "packageReleases:pending", + createdNewParent: false, + }), + ); + expect(runMutation).not.toHaveBeenCalledWith(expect.anything(), { + tokenId: "packagePublishTokens:1", + }); }); it("rejects duplicate staged package releases before creating a publish attempt", async () => { @@ -8678,6 +9270,86 @@ describe("packages public queries", () => { expect(runMutation).not.toHaveBeenCalled(); }); + it("rejects staged package releases reserved by a retained publish attempt", async () => { + const previousFlag = process.env.CLAWHUB_STAGED_PREPUBLICATION_PUBLISHES; + process.env.CLAWHUB_STAGED_PREPUBLICATION_PUBLISHES = "1"; + const runMutation = vi.fn(async () => { + throw new Error("duplicate publish should not create an attempt"); + }); + const trustedPublisher = { + _id: "packageTrustedPublishers:1", + packageId: "packages:demo", + provider: "github-actions", + repository: "openclaw/openclaw", + repositoryId: "1", + repositoryOwner: "openclaw", + repositoryOwnerId: "2", + workflowFilename: "plugin-clawhub-release.yml", + environment: "clawhub-release", + }; + const ctx = { + runQuery: vi + .fn() + .mockResolvedValueOnce({ + _id: "packagePublishTokens:1", + packageId: "packages:demo", + provider: "github-actions", + repository: "openclaw/openclaw", + repositoryId: "1", + repositoryOwner: "openclaw", + repositoryOwnerId: "2", + workflowFilename: "plugin-clawhub-release.yml", + environment: "clawhub-release", + version: "1.0.0", + sha: "abc123", + ref: "refs/heads/main", + runId: "100", + runAttempt: "1", + expiresAt: Date.now() + 60_000, + }) + .mockResolvedValueOnce(trustedPublisher) + .mockResolvedValueOnce(makePackageDoc({ family: "bundle-plugin" })) + .mockResolvedValueOnce(trustedPublisher) + .mockResolvedValueOnce(null) + .mockResolvedValueOnce({ + attemptId: "publishAttempts:secret-blocked", + status: "blocked", + }), + runMutation, + runAction: vi.fn(async () => makeCleanPackageInspectorResult()), + scheduler: { + runAfter: vi.fn(), + }, + storage: makePackageManifestStorage(), + }; + + try { + await expect( + publishPackageForTrustedPublisherInternalHandler(ctx as never, { + publishTokenId: "packagePublishTokens:1", + payload: { + name: "demo-plugin", + family: "bundle-plugin", + version: "1.0.0", + changelog: "duplicate", + bundle: { hostTargets: ["desktop"] }, + files: [packageManifestFile], + }, + }), + ).rejects.toThrow( + "Version 1.0.0 already exists. Increment the version number and try again.", + ); + } finally { + if (previousFlag === undefined) { + delete process.env.CLAWHUB_STAGED_PREPUBLICATION_PUBLISHES; + } else { + process.env.CLAWHUB_STAGED_PREPUBLICATION_PUBLISHES = previousFlag; + } + } + + expect(runMutation).not.toHaveBeenCalled(); + }); + it("accepts trusted publish tokens when no environment is pinned", async () => { const runMutation = vi.fn(async (_ref: unknown, args: unknown) => { if ( diff --git a/convex/packages.ts b/convex/packages.ts index f141d808..2ff99e42 100644 --- a/convex/packages.ts +++ b/convex/packages.ts @@ -497,6 +497,8 @@ const internalRefs = internal as unknown as { markPackageInspectorFindingsEmailedInternal: unknown; claimPackageInspectorScanBatchInternal: unknown; ingestPackageInspectorScanResultsInternal: unknown; + discardPendingPackagePublicationInternal: unknown; + publishPendingReleaseInternal: unknown; }; packageInspectorNode: { runPackageInspectorForPublishInternal: unknown; @@ -519,6 +521,7 @@ const internalRefs = internal as unknown as { }; publishAttempts: { createPackagePublishAttemptInternal: unknown; + findExistingPublishAttemptForArtifactInternal: unknown; claimPackagePublishAttemptForFinalizationInternal: unknown; releasePackagePublishAttemptFinalizationClaimInternal: unknown; recordPackagePublishAttemptFinalizedInternal: unknown; @@ -1121,6 +1124,22 @@ function resolvePublicPackageScanStatus( return pkg.scanStatus; } +function isPublishedPackageRelease( + release: Doc<"packageReleases"> | null | undefined, +): release is Doc<"packageReleases"> { + return Boolean( + release && + !release.softDeletedAt && + (release.publicationStatus === undefined || release.publicationStatus === "published"), + ); +} + +function hasNoPublishedPackageVersions( + pkg: Pick<Doc<"packages">, "latestReleaseId" | "latestVersionSummary" | "stats">, +) { + return !pkg.latestReleaseId && !pkg.latestVersionSummary && (pkg.stats?.versions ?? 0) <= 0; +} + function normalizePublicPackageSourcePath(sourcePath: unknown) { if (typeof sourcePath !== "string") return undefined; const trimmed = sourcePath.trim(); @@ -1151,10 +1170,18 @@ function toPublicPackage( latestRelease?: Doc<"packageReleases"> | null, ): PublicPackageDoc | null { if (!pkg || pkg.softDeletedAt) return null; + if (hasNoPublishedPackageVersions(pkg)) return null; + if ( + latestRelease !== undefined && + latestRelease && + (latestRelease.publicationStatus === "pending" || latestRelease.publicationStatus === "blocked") + ) { + return null; + } const latestVersion = latestRelease === undefined ? (pkg.latestVersionSummary?.version ?? null) - : latestRelease && !latestRelease.softDeletedAt + : isPublishedPackageRelease(latestRelease) ? latestRelease.version : null; const scanStatus = resolvePublicPackageScanStatus(pkg, latestRelease); @@ -1178,7 +1205,7 @@ function toPublicPackage( artifact: latestRelease === undefined ? pkg.latestVersionSummary?.artifact - : latestRelease && !latestRelease.softDeletedAt + : isPublishedPackageRelease(latestRelease) ? packageArtifactSummary(latestRelease) : undefined, scanStatus, @@ -1203,6 +1230,50 @@ function toPublicPackageRelease(release: Doc<"packageReleases">) { }; } +async function paginatePublishedPackageReleases( + ctx: QueryCtx, + packageId: Id<"packages">, + paginationOpts: { cursor: string | null; numItems: number }, +) { + const targetCount = Math.max(1, Math.min(paginationOpts.numItems, MAX_PUBLIC_LIST_PAGE_SIZE)); + const page: Doc<"packageReleases">[] = []; + let cursor = paginationOpts.cursor; + let isDone = false; + let continueCursor = ""; + let remainingScanBudget = Math.max( + targetCount, + Math.min( + MAX_PUBLIC_LIST_FILTER_SCAN_DOCUMENTS, + targetCount * MAX_PUBLIC_LIST_FILTER_SCAN_PAGES, + ), + ); + + for (let scanPages = 0; scanPages < MAX_PUBLIC_LIST_FILTER_SCAN_PAGES; scanPages += 1) { + if (page.length >= targetCount || isDone || remainingScanBudget <= 0) break; + const pageSize = Math.min(remainingScanBudget, targetCount - page.length); + const result = await ctx.db + .query("packageReleases") + .withIndex("by_package_active_created", (q) => + q.eq("packageId", packageId).eq("softDeletedAt", undefined), + ) + .order("desc") + .paginate({ cursor, numItems: pageSize }); + remainingScanBudget -= pageSize; + cursor = result.continueCursor; + continueCursor = result.continueCursor; + isDone = result.isDone; + for (const release of result.page) { + if (isPublishedPackageRelease(release)) { + page.push(release); + if (page.length >= targetCount) break; + } + } + if (result.page.length === 0) break; + } + + return { page, isDone, continueCursor: isDone ? "" : continueCursor }; +} + function packageArtifactSummary( release: Pick< Doc<"packageReleases">, @@ -2677,10 +2748,9 @@ export const getByName = query({ ); return { package: publicPackage, - latestRelease: - latestRelease && !latestRelease.softDeletedAt - ? toPublicPackageRelease(latestRelease) - : null, + latestRelease: isPublishedPackageRelease(latestRelease) + ? toPublicPackageRelease(latestRelease) + : null, owner, }; }, @@ -2908,10 +2978,9 @@ export const getByNameForStaff = query({ return { package: pkg, - latestRelease: - latestRelease && !latestRelease.softDeletedAt - ? toPublicPackageRelease(latestRelease) - : null, + latestRelease: isPublishedPackageRelease(latestRelease) + ? toPublicPackageRelease(latestRelease) + : null, owner, highlighted: highlighted ? { @@ -2942,10 +3011,9 @@ export const getByNameForViewerInternal = internalQuery({ ); return { package: publicPackage, - latestRelease: - latestRelease && !latestRelease.softDeletedAt - ? toPublicPackageRelease(latestRelease) - : null, + latestRelease: isPublishedPackageRelease(latestRelease) + ? toPublicPackageRelease(latestRelease) + : null, owner, }; }, @@ -2960,13 +3028,7 @@ export const listVersions = query({ const viewerUserId = await getOptionalViewerUserId(ctx); const pkg = await getReadablePackageByName(ctx, args.name, viewerUserId); if (!pkg) return { page: [], isDone: true, continueCursor: "" }; - const result = await ctx.db - .query("packageReleases") - .withIndex("by_package_active_created", (q) => - q.eq("packageId", pkg._id).eq("softDeletedAt", undefined), - ) - .order("desc") - .paginate(args.paginationOpts); + const result = await paginatePublishedPackageReleases(ctx, pkg._id, args.paginationOpts); return { ...result, page: result.page.map(toPublicPackageRelease), @@ -2983,13 +3045,7 @@ export const listVersionsForViewerInternal = internalQuery({ handler: async (ctx, args) => { const pkg = await getReadablePackageByName(ctx, args.name, args.viewerUserId); if (!pkg) return { page: [], isDone: true, continueCursor: "" }; - const result = await ctx.db - .query("packageReleases") - .withIndex("by_package_active_created", (q) => - q.eq("packageId", pkg._id).eq("softDeletedAt", undefined), - ) - .order("desc") - .paginate(args.paginationOpts); + const result = await paginatePublishedPackageReleases(ctx, pkg._id, args.paginationOpts); return { ...result, page: result.page.map(toPublicPackageRelease), @@ -3012,7 +3068,7 @@ export const getVersionByName = query({ q.eq("packageId", pkg._id).eq("version", args.version), ) .unique(); - if (!release || release.softDeletedAt) return null; + if (!isPublishedPackageRelease(release)) return null; const latestRelease = pkg.latestReleaseId === release._id ? release @@ -3043,7 +3099,7 @@ export const getVersionByNameForViewerInternal = internalQuery({ q.eq("packageId", pkg._id).eq("version", args.version), ) .unique(); - if (!release || release.softDeletedAt) return null; + if (!isPublishedPackageRelease(release)) return null; const latestRelease = pkg.latestReleaseId === release._id ? release @@ -3074,7 +3130,7 @@ export const getVersionSecurityByNameForViewerInternal = internalQuery({ q.eq("packageId", pkg._id).eq("version", args.version), ) .unique(); - if (!release || release.softDeletedAt) return null; + if (!isPublishedPackageRelease(release)) return null; const latestRelease = pkg.latestReleaseId === release._id ? release @@ -3194,32 +3250,31 @@ export const listAuditPage = query({ verificationTier: pkg.verification?.tier ?? null, }, owner, - latestRelease: - latestRelease && !latestRelease.softDeletedAt - ? { - version: latestRelease.version, - createdAt: latestRelease.createdAt, - vtAnalysis: latestRelease.vtAnalysis, - llmAnalysis: latestRelease.llmAnalysis, - staticScan: latestRelease.staticScan - ? { - status: latestRelease.staticScan.status, - reasonCodes: latestRelease.staticScan.reasonCodes, - findings: (latestRelease.staticScan.findings ?? []).map((finding) => ({ - code: finding.code, - severity: finding.severity, - file: finding.file, - line: finding.line, - message: finding.message, - evidence: "", - })), - summary: latestRelease.staticScan.summary, - engineVersion: latestRelease.staticScan.engineVersion, - checkedAt: latestRelease.staticScan.checkedAt, - } - : null, - } - : null, + latestRelease: isPublishedPackageRelease(latestRelease) + ? { + version: latestRelease.version, + createdAt: latestRelease.createdAt, + vtAnalysis: latestRelease.vtAnalysis, + llmAnalysis: latestRelease.llmAnalysis, + staticScan: latestRelease.staticScan + ? { + status: latestRelease.staticScan.status, + reasonCodes: latestRelease.staticScan.reasonCodes, + findings: (latestRelease.staticScan.findings ?? []).map((finding) => ({ + code: finding.code, + severity: finding.severity, + file: finding.file, + line: finding.line, + message: finding.message, + evidence: "", + })), + summary: latestRelease.staticScan.summary, + engineVersion: latestRelease.staticScan.engineVersion, + checkedAt: latestRelease.staticScan.checkedAt, + } + : null, + } + : null, }); } @@ -4353,7 +4408,7 @@ export const findPackagePublishResultInternal = internalQuery({ q.eq("packageId", pkg._id).eq("version", args.version), ) .unique(); - if (!release || release.softDeletedAt || release.integritySha256 !== args.integritySha256) { + if (!isPublishedPackageRelease(release) || release.integritySha256 !== args.integritySha256) { return null; } return { ok: true as const, packageId: pkg._id, releaseId: release._id }; @@ -7743,24 +7798,55 @@ async function publishPackageImpl( ) ?? []; if (options.stagePrePublicationChecks) { + let existingRelease: Doc<"packageReleases"> | null = null; if (existingPackage) { - const existingRelease = await runQueryRef<Doc<"packageReleases"> | null>( + existingRelease = await runQueryRef<Doc<"packageReleases"> | null>( ctx, internalRefs.packages.getReleaseByPackageAndVersionInternal, { packageId: existingPackage._id, version }, ); - const canReuseExistingRelease = - packageInsertArgs.allowExistingRelease && - existingRelease && - !existingRelease.softDeletedAt && - existingRelease.integritySha256 === integritySha256; - if (existingRelease && !canReuseExistingRelease) { + } + const existingAttempt = await runQueryRef<null | { attemptId: Id<"publishAttempts"> }>( + ctx, + internalRefs.publishAttempts.findExistingPublishAttemptForArtifactInternal, + { + kind: "package", + slug: name, + version, + }, + ); + if (existingAttempt) { + throw new ConvexError( + `Version ${version} already exists. Increment the version number and try again.`, + ); + } + if (existingPackage && existingRelease) { + if (!existingRelease.softDeletedAt && existingRelease.publicationStatus === "pending") { + await runMutationRef(ctx, internalRefs.packages.discardPendingPackagePublicationInternal, { + packageId: existingPackage._id, + releaseId: existingRelease._id, + createdNewParent: hasNoPublishedPackageVersions(existingPackage), + }); throw new ConvexError( - `Version ${version} already exists. Increment the version number and try again.`, + `Previous pending publish for ${version} did not finish creating security checks. It was cleaned up; retry the publish.`, ); } + throw new ConvexError( + `Version ${version} already exists. Increment the version number and try again.`, + ); } + const pendingResult = await runMutationRef<{ + ok: true; + packageId: Id<"packages">; + releaseId: Id<"packageReleases">; + publicationStatus?: "pending" | "published"; + createdNewParent?: boolean; + }>(ctx, internalRefs.packages.insertReleaseInternal, { + ...packageInsertArgs, + publicationStatus: "pending", + }); + const staged = await runMutationRef<{ attemptId: Id<"publishAttempts">; status: string; @@ -7769,6 +7855,9 @@ async function publishPackageImpl( userId: actorUserId, ownerUserId, ownerPublisherId, + packageId: pendingResult.packageId, + packageReleaseId: pendingResult.releaseId, + createdNewParent: pendingResult.createdNewParent, name, displayName, version, @@ -7782,7 +7871,8 @@ async function publishPackageImpl( }), artifactFingerprint: integritySha256, files, - packageInsertArgs: stripUndefinedForStoredAttempt(packageInsertArgs), + clawpackStorageId: packageInsertArgs.clawpackStorageId, + scanContext: buildPackagePublishAttemptScanContext(packageInsertArgs), packageFollowup: stripUndefinedForStoredAttempt({ ownerUserId, ownerPublisherId, @@ -7819,6 +7909,13 @@ async function publishPackageImpl( } : undefined, }), + }).catch(async (error) => { + await runMutationRef(ctx, internalRefs.packages.discardPendingPackagePublicationInternal, { + packageId: pendingResult.packageId, + releaseId: pendingResult.releaseId, + createdNewParent: pendingResult.createdNewParent, + }); + throw error; }); if (auth.kind === "github-actions") { await runMutationRef(ctx, internalRefs.packagePublishTokens.revokeInternal, { @@ -7833,6 +7930,9 @@ async function publishPackageImpl( return { ok: true as const, status: "pending" as const, + packageId: pendingResult.packageId, + releaseId: pendingResult.releaseId, + publicationStatus: "pending" as const, attemptId: staged.attemptId, packageName: name, version, @@ -7994,7 +8094,9 @@ export const finalizePackagePublishAttemptInternal = internalAction({ | { status: "claimed"; attemptId: Id<"publishAttempts">; - packageInsertArgs: unknown; + packageId?: Id<"packages">; + releaseId?: Id<"packageReleases">; + packageInsertArgs?: unknown; packageFollowup: unknown; } | { @@ -8011,12 +8113,21 @@ export const finalizePackagePublishAttemptInternal = internalAction({ let publishResult: { ok: true; packageId: Id<"packages">; releaseId: Id<"packageReleases"> }; try { - publishResult = await runMutationRef( - ctx, - internalRefs.packages.insertReleaseInternal, - claim.packageInsertArgs, - ); + publishResult = + claim.releaseId !== undefined + ? await runMutationRef(ctx, internalRefs.packages.publishPendingReleaseInternal, { + releaseId: claim.releaseId, + }) + : await runMutationRef( + ctx, + internalRefs.packages.insertReleaseInternal, + claim.packageInsertArgs, + ); } catch (error) { + if (claim.releaseId !== undefined) { + await releasePackagePublishAttemptFinalizationClaim(ctx, claim.attemptId, claimId, error); + throw error; + } const insertArgs = claim.packageInsertArgs as { name?: string; version?: string; @@ -8127,6 +8238,28 @@ function stripUndefinedForStoredAttempt(value: unknown): unknown { return result; } +function buildPackagePublishAttemptScanContext(insertArgs: Record<string, unknown>) { + const verification = + insertArgs.verification && + typeof insertArgs.verification === "object" && + !Array.isArray(insertArgs.verification) + ? (insertArgs.verification as Record<string, unknown>) + : {}; + return stripUndefinedForStoredAttempt({ + trustedOpenClawPlugin: verification.trustedOpenClawPlugin === true ? true : undefined, + release: { + staticScan: insertArgs.staticScan, + pluginManifestSummary: insertArgs.pluginManifestSummary, + verification: insertArgs.verification, + artifactKind: insertArgs.artifactKind, + npmIntegrity: insertArgs.npmIntegrity, + npmShasum: insertArgs.npmShasum, + npmTarballName: insertArgs.npmTarballName, + source: insertArgs.source, + }, + }); +} + function buildPackageFinalizationClaimId() { return typeof crypto.randomUUID === "function" ? crypto.randomUUID() @@ -9048,6 +9181,7 @@ async function listPackageInspectorScanBatch( const items: PackageInspectorScanBatchItem[] = []; for (const release of page.page) { if (items.length >= batchSize) break; + if (!isPublishedPackageRelease(release)) continue; const pkg = await ctx.db.get(release.packageId); if ( !pkg || @@ -9137,7 +9271,7 @@ export const getPackageInspectorArtifactInternal = internalQuery({ }, handler: async (ctx, args) => { const release = await ctx.db.get(args.releaseId); - if (!release || release.softDeletedAt) return null; + if (!isPublishedPackageRelease(release)) return null; const pkg = await ctx.db.get(release.packageId); if ( !pkg || @@ -9232,6 +9366,190 @@ async function sendResendEmail(args: { to: string; subject: string; text: string } } +function pendingPackagePublicationMetadata(release: Doc<"packageReleases">) { + return release.pendingPublication && + typeof release.pendingPublication === "object" && + !Array.isArray(release.pendingPublication) + ? (release.pendingPublication as Record<string, unknown>) + : {}; +} + +function stringPendingField(metadata: Record<string, unknown>, field: string, fallback?: string) { + const value = metadata[field]; + return typeof value === "string" ? value : fallback; +} + +function booleanPendingField(metadata: Record<string, unknown>, field: string, fallback: boolean) { + const value = metadata[field]; + return typeof value === "boolean" ? value : fallback; +} + +function stringArrayPendingField(metadata: Record<string, unknown>, field: string) { + const value = metadata[field]; + return Array.isArray(value) + ? value.filter((entry): entry is string => typeof entry === "string") + : undefined; +} + +export const discardPendingPackagePublicationInternal = internalMutation({ + args: { + packageId: v.id("packages"), + releaseId: v.id("packageReleases"), + createdNewParent: v.optional(v.boolean()), + }, + handler: async (ctx, args) => { + const release = await ctx.db.get(args.releaseId); + if ( + !release || + release.packageId !== args.packageId || + release.publicationStatus !== "pending" + ) { + return { deleted: false }; + } + + const storageIds = new Set<Id<"_storage">>(); + for (const file of release.files ?? []) { + if (typeof file.storageId === "string") { + storageIds.add(file.storageId as Id<"_storage">); + } + } + if (typeof release.clawpackStorageId === "string") { + storageIds.add(release.clawpackStorageId as Id<"_storage">); + } + + await ctx.db.delete(release._id); + await Promise.allSettled([...storageIds].map((storageId) => ctx.storage.delete(storageId))); + + let parentDeleted = false; + if (args.createdNewParent) { + const pkg = await ctx.db.get(args.packageId); + if (pkg && !pkg.latestReleaseId) { + const remainingReleases = await ctx.db + .query("packageReleases") + .withIndex("by_package", (q) => q.eq("packageId", args.packageId)) + .take(1); + if (remainingReleases.length === 0) { + await ctx.db.delete(args.packageId); + parentDeleted = true; + } + } + } + + return { deleted: true, parentDeleted }; + }, +}); + +export const publishPendingReleaseInternal = internalMutation({ + args: { + releaseId: v.id("packageReleases"), + }, + handler: async (ctx, args) => { + const release = await ctx.db.get(args.releaseId); + if (!release || release.softDeletedAt) { + throw new ConvexError("Pending package release not found"); + } + if (release.publicationStatus === undefined || release.publicationStatus === "published") { + return { + ok: true as const, + packageId: release.packageId, + releaseId: release._id, + }; + } + if (release.publicationStatus !== "pending") { + throw new ConvexError(`Package release is ${release.publicationStatus}, not pending.`); + } + + const pkg = await ctx.db.get(release.packageId); + if (!pkg || pkg.softDeletedAt || pkg.family === "skill") { + throw new ConvexError("Package not found"); + } + + const now = Date.now(); + const metadata = pendingPackagePublicationMetadata(release); + const effectiveTags = stringArrayPendingField(metadata, "tags") ?? release.distTags ?? []; + const shouldPromoteLatest = effectiveTags.includes("latest"); + const scanStatus = resolvePackageReleaseScanStatus(release); + const releaseVerification = release.verification + ? { ...release.verification, scanStatus } + : release.verification; + const publishedRelease = { + ...release, + publicationStatus: "published" as const, + pendingPublication: undefined, + verification: releaseVerification, + } as Doc<"packageReleases">; + + const priorReleases = await ctx.db + .query("packageReleases") + .withIndex("by_package", (q) => q.eq("packageId", pkg._id)) + .collect(); + + const nextTags = { ...pkg.tags }; + for (const tag of effectiveTags) nextTags[tag] = release._id; + for (const priorRelease of priorReleases) { + if (priorRelease._id === release._id || !isPublishedPackageRelease(priorRelease)) continue; + const nextDistTags = (priorRelease.distTags ?? []).filter( + (tag) => !effectiveTags.includes(tag), + ); + if (nextDistTags.length === (priorRelease.distTags ?? []).length) continue; + await ctx.db.patch(priorRelease._id, { distTags: nextDistTags }); + } + + await ctx.db.patch(release._id, { + publicationStatus: "published", + pendingPublication: undefined, + verification: releaseVerification, + }); + + await ctx.db.patch(pkg._id, { + displayName: stringPendingField(metadata, "displayName", pkg.displayName), + ownerUserId: pkg.ownerUserId, + ownerPublisherId: pkg.ownerPublisherId, + family: pkg.family, + summary: shouldPromoteLatest ? release.summary : pkg.summary, + icon: shouldPromoteLatest ? release.icon : pkg.icon, + categories: shouldPromoteLatest + ? stringArrayPendingField(metadata, "categories") + : pkg.categories, + topics: shouldPromoteLatest ? stringArrayPendingField(metadata, "topics") : pkg.topics, + ...(shouldPromoteLatest + ? { + inferredCategories: undefined, + inferredTopics: undefined, + inferredFromReleaseId: undefined, + inferredCategoryConfidence: undefined, + inferredTopicConfidence: undefined, + inferredClassifierVersion: undefined, + inferredTopicClassifierVersion: undefined, + inferredInputHash: undefined, + inferredTopicInputHash: undefined, + inferredAt: undefined, + } + : {}), + sourceRepo: stringPendingField(metadata, "sourceRepo", release.sourceRepo), + runtimeId: shouldPromoteLatest ? release.runtimeId : pkg.runtimeId, + channel: stringPendingField(metadata, "channel", pkg.channel) as Doc<"packages">["channel"], + isOfficial: booleanPendingField(metadata, "isOfficial", pkg.isOfficial), + latestReleaseId: shouldPromoteLatest ? release._id : pkg.latestReleaseId, + latestVersionSummary: shouldPromoteLatest + ? packageLatestSummaryFromRelease(publishedRelease) + : pkg.latestVersionSummary, + tags: nextTags, + compatibility: shouldPromoteLatest ? release.compatibility : pkg.compatibility, + verification: shouldPromoteLatest ? releaseVerification : pkg.verification, + scanStatus: shouldPromoteLatest ? scanStatus : pkg.scanStatus, + stats: { ...pkg.stats, versions: (pkg.stats?.versions ?? 0) + 1 }, + updatedAt: now, + }); + + return { + ok: true as const, + packageId: pkg._id, + releaseId: release._id, + }; + }, +}); + export const insertReleaseInternal = internalMutation({ args: { actorUserId: v.id("users"), @@ -9257,6 +9575,7 @@ export const insertReleaseInternal = internalMutation({ displayName: v.string(), family: v.union(v.literal("skill"), v.literal("code-plugin"), v.literal("bundle-plugin")), version: v.string(), + publicationStatus: v.optional(v.union(v.literal("pending"), v.literal("published"))), changelog: v.string(), icon: v.optional(v.string()), tags: v.array(v.string()), @@ -9302,6 +9621,8 @@ export const insertReleaseInternal = internalMutation({ }, handler: async (ctx, args) => { const now = Date.now(); + const publicationStatus = args.publicationStatus ?? "published"; + const pendingPublication = publicationStatus === "pending"; const prePublicationScanStatus = args.llmAnalysis ? normalizePackageScanStatus(args.llmAnalysis.verdict ?? args.llmAnalysis.status) : undefined; @@ -9420,6 +9741,7 @@ export const insertReleaseInternal = internalMutation({ } } + const createdNewParent = !existing; const pkgId = existing?._id ?? (await ctx.db.insert("packages", { @@ -9498,6 +9820,24 @@ export const insertReleaseInternal = internalMutation({ const releaseId = await ctx.db.insert("packageReleases", { packageId: pkgId, version: args.version, + publicationStatus, + pendingPublication: pendingPublication + ? { + displayName: args.displayName, + ownerUserId: args.ownerUserId, + ownerPublisherId: args.ownerPublisherId, + family: args.family, + summary: args.summary, + icon: args.icon, + categories: args.categories, + topics: args.topics, + sourceRepo: args.sourceRepo, + runtimeId: args.runtimeId, + channel: nextChannel, + isOfficial: nextIsOfficial, + tags: effectiveTags, + } + : undefined, changelog: args.changelog, summary: args.summary, icon: args.icon, @@ -9533,6 +9873,15 @@ export const insertReleaseInternal = internalMutation({ const pkg = existing ?? (await ctx.db.get(pkgId)); if (!pkg) throw new ConvexError("Package insert failed"); + if (pendingPublication) { + return { + ok: true as const, + packageId: pkgId, + releaseId, + publicationStatus, + createdNewParent, + }; + } const nextTags = { ...pkg.tags }; for (const tag of effectiveTags) nextTags[tag] = releaseId; diff --git a/convex/publishAttempts.test.ts b/convex/publishAttempts.test.ts index 8fdb59b9..3f977a17 100644 --- a/convex/publishAttempts.test.ts +++ b/convex/publishAttempts.test.ts @@ -5,6 +5,7 @@ import { claimPrePublicationChecks, claimReadyPublishAttemptFinalizationRetryInternal, completePendingPublishAttemptChecksInternal, + recordSkillPublishAttemptFinalizedInternal, releasePackagePublishAttemptFinalizationClaimInternal, releaseSkillPublishAttemptFinalizationClaimInternal, } from "./publishAttempts"; @@ -39,6 +40,11 @@ const releasePackageFinalizationHandler = ( _handler: (ctx: unknown, args: unknown) => Promise<unknown>; } )._handler; +const recordSkillFinalizedHandler = ( + recordSkillPublishAttemptFinalizedInternal as unknown as { + _handler: (ctx: unknown, args: unknown) => Promise<unknown>; + } +)._handler; describe("publishAttempts", () => { it("leases staged publish check claims long enough for scanner timeouts", async () => { @@ -612,6 +618,63 @@ describe("publishAttempts", () => { expect(transientCtx.db.patch.mock.calls[0]?.[1]).not.toHaveProperty("failedAt"); }); + it("clears private pending skill metadata when finalization is recorded", async () => { + const now = Date.now(); + const ctx = { + db: { + delete: vi.fn(), + get: vi.fn(async (id: string) => + id === "publishAttempts:demo" + ? { + _id: "publishAttempts:demo", + kind: "skill", + status: "finalizing", + skillVersionId: "skillVersions:pending", + followup: {}, + finalizationClaimId: "finalize:claim", + finalizationClaimExpiresAt: now + 60_000, + } + : null, + ), + insert: vi.fn(), + normalizeId: vi.fn(), + patch: vi.fn(), + query: vi.fn(), + replace: vi.fn(), + system: {}, + }, + }; + const result = { + skillId: "skills:demo", + versionId: "skillVersions:pending", + embeddingId: "skillEmbeddings:demo", + publicationStatus: "published", + }; + + await expect( + recordSkillFinalizedHandler(ctx, { + attemptId: "publishAttempts:demo", + claimId: "finalize:claim", + result, + }), + ).resolves.toEqual({ + attemptId: "publishAttempts:demo", + status: "finalized", + result, + }); + + expect(ctx.db.patch).toHaveBeenCalledWith( + "publishAttempts:demo", + expect.objectContaining({ + status: "finalized", + result, + }), + ); + expect(ctx.db.patch).toHaveBeenCalledWith("skillVersions:pending", { + pendingPublication: undefined, + }); + }); + it("stores suspicious analysis with the staged insert before finalization", async () => { const now = Date.now(); const llmAnalysis = { @@ -755,6 +818,9 @@ describe("publishAttempts", () => { kind: "skill", status: "pending_checks", userId: "users:publisher", + skillId: "skills:secret", + skillVersionId: "skillVersions:secret", + createdNewParent: true, slug: "secret-skill", version: "1.0.0", artifactFingerprint: "fingerprint", @@ -762,6 +828,10 @@ describe("publishAttempts", () => { checkClaimExpiresAt: Date.now() + 60_000, files: [{ storageId: "_storage:secret-skill" }], }) + .mockResolvedValueOnce({ + _id: "skills:secret", + latestVersionId: undefined, + }) .mockResolvedValueOnce({ _id: "users:publisher", handle: "publisher", @@ -771,7 +841,23 @@ describe("publishAttempts", () => { insert: vi.fn(), replace: vi.fn(), delete: vi.fn(), - query: vi.fn(), + query: vi.fn((table: string) => { + if (table === "skillVersionFingerprints") { + return { + withIndex: vi.fn(() => ({ + take: vi.fn(async () => [{ _id: "skillVersionFingerprints:secret" }]), + })), + }; + } + if (table === "skillVersions") { + return { + withIndex: vi.fn(() => ({ + take: vi.fn(async () => []), + })), + }; + } + throw new Error(`Unexpected table ${table}`); + }), normalizeId: vi.fn(), system: {}, }, @@ -802,6 +888,9 @@ describe("publishAttempts", () => { }); expect(ctx.storage.delete).toHaveBeenCalledWith("_storage:secret-skill"); + expect(ctx.db.delete).toHaveBeenCalledWith("skillVersionFingerprints:secret"); + expect(ctx.db.delete).toHaveBeenCalledWith("skillVersions:secret"); + expect(ctx.db.delete).toHaveBeenCalledWith("skills:secret"); expect(ctx.db.patch).toHaveBeenCalledWith( "publishAttempts:demo", expect.objectContaining({ @@ -823,6 +912,79 @@ describe("publishAttempts", () => { }); }); + it("keeps existing skill parents when TruffleHog blocks a pending new version", async () => { + const ctx = { + db: { + get: vi + .fn() + .mockResolvedValueOnce({ + _id: "publishAttempts:demo", + kind: "skill", + status: "pending_checks", + userId: "users:publisher", + skillId: "skills:existing", + skillVersionId: "skillVersions:pending", + createdNewParent: false, + slug: "existing-skill", + version: "2.0.0", + artifactFingerprint: "fingerprint", + checkClaimId: "checks:claim", + checkClaimExpiresAt: Date.now() + 60_000, + files: [{ storageId: "_storage:secret-skill" }], + }) + .mockResolvedValueOnce({ + _id: "users:publisher", + handle: "publisher", + email: "publisher@example.com", + }), + patch: vi.fn(), + insert: vi.fn(), + replace: vi.fn(), + delete: vi.fn(), + query: vi.fn((table: string) => { + if (table === "skillVersionFingerprints") { + return { + withIndex: vi.fn(() => ({ + take: vi.fn(async () => [{ _id: "skillVersionFingerprints:pending" }]), + })), + }; + } + throw new Error(`Unexpected table ${table}`); + }), + normalizeId: vi.fn(), + system: {}, + }, + scheduler: { + runAfter: vi.fn(), + }, + storage: { + delete: vi.fn(), + }, + }; + + await expect( + completePendingChecksHandler(ctx, { + attemptId: "publishAttempts:demo", + claimId: "checks:claim", + artifactFingerprint: "fingerprint", + trufflehog: { + status: "blocked", + summary: "redacted TruffleHog finding", + redactedFindings: ["redacted-secret"], + }, + clawscan: { status: "clean" }, + }), + ).resolves.toMatchObject({ + attemptId: "publishAttempts:demo", + kind: "skill", + status: "blocked", + }); + + expect(ctx.db.delete).toHaveBeenCalledWith("skillVersionFingerprints:pending"); + expect(ctx.db.delete).toHaveBeenCalledWith("skillVersions:pending"); + expect(ctx.db.delete).not.toHaveBeenCalledWith("skills:existing"); + }); + it("keeps TruffleHog-positive attempts pending when secret storage deletion fails", async () => { const ctx = { db: { diff --git a/convex/publishAttempts.ts b/convex/publishAttempts.ts index 0c7bf5fb..0d64c035 100644 --- a/convex/publishAttempts.ts +++ b/convex/publishAttempts.ts @@ -9,11 +9,25 @@ const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000; const CHECK_CLAIM_LEASE_MS = 30 * 60 * 1000; const CHECK_RETRY_BACKOFF_MS = 5 * 60 * 1000; const FINALIZATION_CLAIM_LEASE_MS = 10 * 60 * 1000; +const PUBLISH_ATTEMPT_STATUSES = [ + "pending_checks", + "ready_to_finalize", + "finalizing", + "finalized", + "blocked", + "failed", + "expired", +] as const; const publishResultValidator = v.object({ skillId: v.id("skills"), versionId: v.id("skillVersions"), - embeddingId: v.id("skillEmbeddings"), + embeddingId: v.optional(v.id("skillEmbeddings")), + status: v.optional(v.union(v.literal("pending"), v.literal("published"))), + slug: v.optional(v.string()), + version: v.optional(v.string()), + publicationStatus: v.optional(v.union(v.literal("pending"), v.literal("published"))), + attemptId: v.optional(v.id("publishAttempts")), }); const packagePublishResultValidator = v.object({ @@ -123,6 +137,9 @@ export const createSkillPublishAttemptInternal = internalMutation({ userId: v.id("users"), ownerPublisherId: v.optional(v.id("publishers")), sourceOwnerPublisherId: v.optional(v.id("publishers")), + skillId: v.id("skills"), + skillVersionId: v.id("skillVersions"), + createdNewParent: v.optional(v.boolean()), slug: v.string(), displayName: v.string(), version: v.string(), @@ -137,7 +154,7 @@ export const createSkillPublishAttemptInternal = internalMutation({ contentType: v.optional(v.string()), }), ), - skillInsertArgs: v.any(), + scanContext: v.optional(v.any()), followup: v.object({ skipWebhook: v.optional(v.boolean()), ownerHandle: v.optional(v.string()), @@ -160,6 +177,9 @@ export const createSkillPublishAttemptInternal = internalMutation({ userId: args.userId, ownerPublisherId: args.ownerPublisherId, sourceOwnerPublisherId: args.sourceOwnerPublisherId, + skillId: args.skillId, + skillVersionId: args.skillVersionId, + createdNewParent: args.createdNewParent, slug: args.slug, displayName: args.displayName, version: args.version, @@ -170,7 +190,7 @@ export const createSkillPublishAttemptInternal = internalMutation({ trufflehog: { status: "pending" }, clawscan: { status: "pending" }, }, - skillInsertArgs: args.skillInsertArgs, + scanContext: args.scanContext, followup: args.followup, createdAt: now, updatedAt: now, @@ -197,11 +217,56 @@ function isTerminalRetriableAttemptStatus(status: string) { return status === "blocked" || status === "failed" || status === "expired"; } +export const findExistingPublishAttemptForArtifactInternal = internalQuery({ + args: { + kind: v.union(v.literal("skill"), v.literal("package")), + slug: v.string(), + version: v.string(), + userId: v.optional(v.id("users")), + ownerPublisherId: v.optional(v.id("publishers")), + }, + handler: async (ctx, args) => { + for (const status of PUBLISH_ATTEMPT_STATUSES) { + const attempts = await ctx.db + .query("publishAttempts") + .withIndex("by_kind_status_slug_version_created", (q) => + q + .eq("kind", args.kind) + .eq("status", status) + .eq("slug", args.slug) + .eq("version", args.version), + ) + .order("desc") + .take(25); + const match = attempts.find((attempt) => { + if (args.kind === "package") return true; + if (args.ownerPublisherId !== undefined) { + return attempt.ownerPublisherId === args.ownerPublisherId; + } + return attempt.ownerPublisherId === undefined && attempt.userId === args.userId; + }); + if (match) { + return { + attemptId: match._id, + status: match.status, + kind: match.kind, + slug: match.slug, + version: match.version, + }; + } + } + return null; + }, +}); + export const createPackagePublishAttemptInternal = internalMutation({ args: { userId: v.id("users"), ownerUserId: v.id("users"), ownerPublisherId: v.optional(v.id("publishers")), + packageId: v.id("packages"), + packageReleaseId: v.id("packageReleases"), + createdNewParent: v.optional(v.boolean()), name: v.string(), displayName: v.string(), version: v.string(), @@ -216,7 +281,9 @@ export const createPackagePublishAttemptInternal = internalMutation({ contentType: v.optional(v.string()), }), ), - packageInsertArgs: v.any(), + clawpackStorageId: v.optional(v.id("_storage")), + scanContext: v.optional(v.any()), + packageInsertArgs: v.optional(v.any()), packageFollowup: v.any(), }, handler: async (ctx, args) => { @@ -236,6 +303,9 @@ export const createPackagePublishAttemptInternal = internalMutation({ userId: args.userId, ownerUserId: args.ownerUserId, ownerPublisherId: args.ownerPublisherId, + packageId: args.packageId, + packageReleaseId: args.packageReleaseId, + createdNewParent: args.createdNewParent, slug: args.name, displayName: args.displayName, version: args.version, @@ -246,6 +316,8 @@ export const createPackagePublishAttemptInternal = internalMutation({ trufflehog: { status: "pending" }, clawscan: { status: "pending" }, }, + clawpackStorageId: args.clawpackStorageId, + scanContext: args.scanContext, packageInsertArgs: args.packageInsertArgs, packageFollowup: args.packageFollowup, createdAt: now, @@ -259,9 +331,11 @@ export const createPackagePublishAttemptInternal = internalMutation({ function getSecretBlockedStorageIds(attempt: { files: Array<{ storageId: Id<"_storage"> }>; + clawpackStorageId?: Id<"_storage">; packageInsertArgs?: unknown; }) { const storageIds = new Set<Id<"_storage">>(attempt.files.map((file) => file.storageId)); + if (attempt.clawpackStorageId) storageIds.add(attempt.clawpackStorageId); const packageInsertArgs = attempt.packageInsertArgs; if (packageInsertArgs && typeof packageInsertArgs === "object") { const clawpackStorageId = (packageInsertArgs as { clawpackStorageId?: unknown }) @@ -273,7 +347,11 @@ function getSecretBlockedStorageIds(attempt: { return [...storageIds]; } -function buildSkillAttemptScanContext(attempt: { skillInsertArgs?: unknown }) { +function buildSkillAttemptScanContext(attempt: { + scanContext?: unknown; + skillInsertArgs?: unknown; +}) { + if (attempt.scanContext) return attempt.scanContext; const skillInsertArgs = asRecord(attempt.skillInsertArgs); const parsed = asRecord(skillInsertArgs.parsed); return withoutUndefined({ @@ -290,7 +368,11 @@ function buildSkillAttemptScanContext(attempt: { skillInsertArgs?: unknown }) { }); } -function buildPackageAttemptScanContext(attempt: { packageInsertArgs?: unknown }) { +function buildPackageAttemptScanContext(attempt: { + scanContext?: unknown; + packageInsertArgs?: unknown; +}) { + if (attempt.scanContext) return attempt.scanContext; const packageInsertArgs = asRecord(attempt.packageInsertArgs); const verification = asRecord(packageInsertArgs.verification); return withoutUndefined({ @@ -308,11 +390,70 @@ function buildPackageAttemptScanContext(attempt: { packageInsertArgs?: unknown } }); } -function publishAttemptClawpackStorageId(attempt: { packageInsertArgs?: unknown }) { +function publishAttemptClawpackStorageId(attempt: { + clawpackStorageId?: Id<"_storage">; + packageInsertArgs?: unknown; +}) { + if (attempt.clawpackStorageId) return attempt.clawpackStorageId; const clawpackStorageId = asRecord(attempt.packageInsertArgs).clawpackStorageId; return typeof clawpackStorageId === "string" ? (clawpackStorageId as Id<"_storage">) : undefined; } +async function deleteSecretBlockedPendingSkillArtifact( + ctx: MutationCtx, + attempt: { + skillId?: Id<"skills">; + skillVersionId?: Id<"skillVersions">; + createdNewParent?: boolean; + }, +) { + if (!attempt.skillVersionId) return; + + const fingerprints = await ctx.db + .query("skillVersionFingerprints") + .withIndex("by_version", (q) => q.eq("versionId", attempt.skillVersionId!)) + .take(100); + for (const fingerprint of fingerprints) { + await ctx.db.delete(fingerprint._id); + } + await ctx.db.delete(attempt.skillVersionId); + + if (!attempt.createdNewParent || !attempt.skillId) return; + const skill = await ctx.db.get(attempt.skillId); + if (!skill || skill.latestVersionId) return; + const remainingVersions = await ctx.db + .query("skillVersions") + .withIndex("by_skill", (q) => q.eq("skillId", attempt.skillId!)) + .take(1); + if (remainingVersions.length === 0) { + await ctx.db.delete(attempt.skillId); + } +} + +async function deleteSecretBlockedPendingPackageArtifact( + ctx: MutationCtx, + attempt: { + packageId?: Id<"packages">; + packageReleaseId?: Id<"packageReleases">; + createdNewParent?: boolean; + }, +) { + if (!attempt.packageReleaseId) return; + + await ctx.db.delete(attempt.packageReleaseId); + + if (!attempt.createdNewParent || !attempt.packageId) return; + const pkg = await ctx.db.get(attempt.packageId); + if (!pkg || pkg.latestReleaseId) return; + const remainingReleases = await ctx.db + .query("packageReleases") + .withIndex("by_package", (q) => q.eq("packageId", attempt.packageId!)) + .take(1); + if (remainingReleases.length === 0) { + await ctx.db.delete(attempt.packageId); + } +} + export const recordSkillPublishAttemptChecksPassedInternal = internalMutation({ args: { attemptId: v.id("publishAttempts"), @@ -398,6 +539,11 @@ export const completePendingPublishAttemptChecksInternal = internalMutation({ await Promise.all( getSecretBlockedStorageIds(attempt).map((storageId) => ctx.storage.delete(storageId)), ); + if (attempt.kind === "skill") { + await deleteSecretBlockedPendingSkillArtifact(ctx, attempt); + } else if (attempt.kind === "package") { + await deleteSecretBlockedPendingPackageArtifact(ctx, attempt); + } await ctx.db.patch(attempt._id, { status: "blocked", checks, @@ -418,6 +564,25 @@ export const completePendingPublishAttemptChecksInternal = internalMutation({ } if (args.clawscan.status === "blocked") { + if (attempt.kind === "skill" && attempt.skillVersionId) { + await ctx.db.patch(attempt.skillVersionId, { + publicationStatus: "blocked", + llmAnalysis: args.clawscanAnalysis, + publishAttemptId: attempt._id, + }); + } + if (attempt.kind === "package" && attempt.packageReleaseId) { + const release = await ctx.db.get(attempt.packageReleaseId); + const verification = release?.verification + ? { ...release.verification, scanStatus: "malicious" as const } + : release?.verification; + await ctx.db.patch(attempt.packageReleaseId, { + publicationStatus: "blocked", + verification, + llmAnalysis: args.clawscanAnalysis, + publishAttemptId: attempt._id, + }); + } await ctx.db.patch(attempt._id, { status: "blocked", checks, @@ -453,6 +618,19 @@ export const completePendingPublishAttemptChecksInternal = internalMutation({ return { attemptId: attempt._id, kind: attempt.kind, status: "pending_checks" as const }; } + if (attempt.kind === "skill" && attempt.skillVersionId && args.clawscanAnalysis) { + await ctx.db.patch(attempt.skillVersionId, { + llmAnalysis: args.clawscanAnalysis, + publishAttemptId: attempt._id, + }); + } + if (attempt.kind === "package" && attempt.packageReleaseId && args.clawscanAnalysis) { + await ctx.db.patch(attempt.packageReleaseId, { + llmAnalysis: args.clawscanAnalysis, + publishAttemptId: attempt._id, + }); + } + await ctx.db.patch(attempt._id, { status: "ready_to_finalize", checks, @@ -535,6 +713,10 @@ export const claimPendingPublishAttemptChecksInternal = internalMutation({ ownerUserId: attempt.ownerUserId, ownerPublisherId: attempt.ownerPublisherId, sourceOwnerPublisherId: attempt.sourceOwnerPublisherId, + skillId: attempt.skillId, + versionId: attempt.skillVersionId, + packageId: attempt.packageId, + releaseId: attempt.packageReleaseId, slug: attempt.slug, displayName: attempt.displayName, version: attempt.version, @@ -614,6 +796,10 @@ export const claimReadyPublishAttemptFinalizationRetryInternal = internalMutatio ownerUserId: attempt.ownerUserId, ownerPublisherId: attempt.ownerPublisherId, sourceOwnerPublisherId: attempt.sourceOwnerPublisherId, + skillId: attempt.skillId, + versionId: attempt.skillVersionId, + packageId: attempt.packageId, + releaseId: attempt.packageReleaseId, slug: attempt.slug, displayName: attempt.displayName, version: attempt.version, @@ -661,6 +847,8 @@ export const claimSkillPublishAttemptForFinalizationInternal = internalMutation( status: "claimed" as const, attemptId: attempt._id, createdAt: attempt.createdAt, + skillId: attempt.skillId, + versionId: attempt.skillVersionId, skillInsertArgs: attempt.skillInsertArgs, followup: buildSkillPublishFollowup(attempt), }; @@ -702,6 +890,8 @@ export const claimPackagePublishAttemptForFinalizationInternal = internalMutatio return { status: "claimed" as const, attemptId: attempt._id, + packageId: attempt.packageId, + releaseId: attempt.packageReleaseId, packageInsertArgs: attempt.packageInsertArgs, packageFollowup: attempt.packageFollowup, }; @@ -774,6 +964,11 @@ export const recordSkillPublishAttemptFinalizedInternal = internalMutation({ finalizedAt: now, updatedAt: now, }); + if (attempt.skillVersionId && attempt.skillVersionId === args.result.versionId) { + await ctx.db.patch(attempt.skillVersionId, { + pendingPublication: undefined, + }); + } return { attemptId: attempt._id, status: "finalized" as const, result: args.result }; }, @@ -867,6 +1062,7 @@ export const findSkillPublishAttemptPublicResultInternal = internalQuery({ skillId: skill._id, versionId: version._id, embeddingId: embedding._id, + publicationStatus: "published" as const, }; }, }); @@ -914,6 +1110,10 @@ export const claimPrePublicationChecks: ReturnType<typeof action> = action({ ownerUserId?: Id<"users">; ownerPublisherId?: Id<"publishers">; sourceOwnerPublisherId?: Id<"publishers">; + skillId?: Id<"skills">; + versionId?: Id<"skillVersions">; + packageId?: Id<"packages">; + releaseId?: Id<"packageReleases">; slug: string; displayName: string; version: string; @@ -1067,15 +1267,28 @@ async function requireSkillPublishAttempt( result?: { skillId: Id<"skills">; versionId: Id<"skillVersions">; - embeddingId: Id<"skillEmbeddings">; + embeddingId?: Id<"skillEmbeddings">; + status?: "pending" | "published"; + slug?: string; + version?: string; + publicationStatus?: "pending" | "published"; + attemptId?: Id<"publishAttempts">; }; + skillId?: Id<"skills">; + skillVersionId?: Id<"skillVersions">; }; - if (typed.kind !== "skill" || !typed.skillInsertArgs || !typed.followup) { + if ( + typed.kind !== "skill" || + !typed.followup || + (!typed.skillVersionId && !typed.skillInsertArgs) + ) { throw new ConvexError("Skill publish attempt not found."); } return typed as typeof typed & { kind: "skill"; - skillInsertArgs: unknown; + skillId?: Id<"skills">; + skillVersionId?: Id<"skillVersions">; + skillInsertArgs?: unknown; followup: { skipWebhook?: boolean; ownerHandle?: string }; }; } @@ -1101,6 +1314,8 @@ async function requirePackagePublishAttempt( | "expired"; packageInsertArgs?: unknown; packageFollowup?: unknown; + packageId?: Id<"packages">; + packageReleaseId?: Id<"packageReleases">; finalizationClaimId?: string; finalizationClaimExpiresAt?: number; result?: { @@ -1109,7 +1324,7 @@ async function requirePackagePublishAttempt( releaseId: Id<"packageReleases">; }; }; - if (typed.kind !== "package" || !typed.packageInsertArgs) { + if (typed.kind !== "package" || (!typed.packageReleaseId && !typed.packageInsertArgs)) { throw new ConvexError("Package publish attempt not found."); } return typed; diff --git a/convex/schema.ts b/convex/schema.ts index 82dfc56a..247daa01 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -990,6 +990,11 @@ const skillSlugAliases = defineTable({ const skillVersions = defineTable({ skillId: v.id("skills"), version: v.string(), + publicationStatus: v.optional( + v.union(v.literal("pending"), v.literal("published"), v.literal("blocked")), + ), + publishAttemptId: v.optional(v.id("publishAttempts")), + pendingPublication: v.optional(v.any()), fingerprint: v.optional(v.string()), sourceProvenance: v.optional( v.object({ @@ -1125,6 +1130,12 @@ const publishAttempts = defineTable({ ownerUserId: v.optional(v.id("users")), ownerPublisherId: v.optional(v.id("publishers")), sourceOwnerPublisherId: v.optional(v.id("publishers")), + skillId: v.optional(v.id("skills")), + skillVersionId: v.optional(v.id("skillVersions")), + packageId: v.optional(v.id("packages")), + packageReleaseId: v.optional(v.id("packageReleases")), + createdNewParent: v.optional(v.boolean()), + clawpackStorageId: v.optional(v.id("_storage")), slug: v.string(), displayName: v.string(), version: v.string(), @@ -1137,6 +1148,7 @@ const publishAttempts = defineTable({ }), skillInsertArgs: v.optional(v.any()), packageInsertArgs: v.optional(v.any()), + scanContext: v.optional(v.any()), followup: v.optional( v.object({ skipWebhook: v.optional(v.boolean()), @@ -1636,6 +1648,11 @@ const packages = defineTable({ const packageReleases = defineTable({ packageId: v.id("packages"), version: v.string(), + publicationStatus: v.optional( + v.union(v.literal("pending"), v.literal("published"), v.literal("blocked")), + ), + publishAttemptId: v.optional(v.id("publishAttempts")), + pendingPublication: v.optional(v.any()), changelog: v.string(), summary: v.optional(v.string()), icon: v.optional(v.string()), @@ -1655,6 +1672,7 @@ const packageReleases = defineTable({ extractedPackageJson: v.optional(v.any()), extractedPluginManifest: v.optional(v.any()), normalizedBundleManifest: v.optional(v.any()), + manifestSearchTerms: v.optional(v.array(v.string())), pluginManifestSummary: v.optional(pluginManifestSummaryValidator), compatibility: packageCompatibilityValidator, runtimeId: v.optional(v.string()), @@ -2044,6 +2062,7 @@ const packageSearchDigest = defineTable({ categories: v.optional(v.array(v.string())), topics: v.optional(v.array(v.string())), pluginCategoryTags: v.optional(v.array(v.string())), + manifestSearchTerms: v.optional(v.array(v.string())), verificationTier: v.optional(packageVerificationTierValidator), stats: v.optional(packageStatsValidator), recommendedScore: v.optional(v.number()), diff --git a/convex/skills.ts b/convex/skills.ts index ffa6182f..893665a6 100644 --- a/convex/skills.ts +++ b/convex/skills.ts @@ -7566,6 +7566,32 @@ function isStaleCursorError(error: unknown) { return patterns.some((p) => msg.includes(p)); } +async function paginatePublicSkillVersions( + ctx: QueryCtx, + skillId: Id<"skills">, + initialCursor: string | null, + limit: number, +) { + const scanLimit = Math.max( + limit, + Math.min(MAX_FILTERED_PUBLIC_LIST_SCAN_ROWS, limit * MAX_FILTERED_PUBLIC_LIST_SCAN_PAGES), + ); + const runPaginate = (pageCursor: string | null) => + ctx.db + .query("skillVersions") + .withIndex("by_skill_active_created", (q) => + q.eq("skillId", skillId).eq("softDeletedAt", undefined), + ) + .order("desc") + .paginate({ cursor: pageCursor, numItems: scanLimit }); + const page = await paginateWithStaleCursorRecovery(runPaginate, initialCursor); + const items = page.page + .filter((version) => isPublicSkillVersionAvailableForSkill(version, skillId)) + .slice(0, limit); + + return { items, nextCursor: page.isDone ? null : page.continueCursor }; +} + export const countPublicSkills = query({ args: {}, handler: async (ctx) => { @@ -7581,20 +7607,29 @@ export const listVersions = query({ const authUserId = await getAuthUserId(ctx); const actor = authUserId ? await ctx.db.get(authUserId) : null; const isStaff = actor?.role === "admin" || actor?.role === "moderator"; - const versions = isStaff - ? await ctx.db - .query("skillVersions") - .withIndex("by_skill", (q) => q.eq("skillId", args.skillId)) - .order("desc") - .take(limit) - : await ctx.db + if (isStaff) { + const versions = await ctx.db + .query("skillVersions") + .withIndex("by_skill", (q) => q.eq("skillId", args.skillId)) + .order("desc") + .take(limit); + return versions.map((version) => toPublicSkillVersion(version)!); + } + if (actor) { + const skill = await ctx.db.get(args.skillId); + if (skill && (await canManageSkillOwnerForActor(ctx, actor, skill))) { + const versions = await ctx.db .query("skillVersions") .withIndex("by_skill_active_created", (q) => q.eq("skillId", args.skillId).eq("softDeletedAt", undefined), ) .order("desc") .take(limit); - return versions.map((version) => toPublicSkillVersion(version)!); + return versions.map((version) => toPublicSkillVersion(version)!); + } + } + const publicVersions = await paginatePublicSkillVersions(ctx, args.skillId, null, limit); + return publicVersions.items.map((version) => toPublicSkillVersion(version)!); }, }); @@ -7606,20 +7641,11 @@ export const listVersionsPage = query({ }, handler: async (ctx, args) => { const limit = clampInt(args.limit ?? 20, 1, MAX_LIST_LIMIT); - const runPaginate = (cursor: string | null) => - ctx.db - .query("skillVersions") - .withIndex("by_skill_active_created", (q) => - q.eq("skillId", args.skillId).eq("softDeletedAt", undefined), - ) - .order("desc") - .paginate({ cursor, numItems: limit }); - const { page, isDone, continueCursor } = await paginateWithStaleCursorRecovery( - runPaginate, - args.cursor ?? null, - ); - const items = page.map((version) => toPublicSkillVersion(version)!); - return { items, nextCursor: isDone ? null : continueCursor }; + const page = await paginatePublicSkillVersions(ctx, args.skillId, args.cursor ?? null, limit); + return { + items: page.items.map((version) => toPublicSkillVersion(version)!), + nextCursor: page.nextCursor, + }; }, }); @@ -7627,7 +7653,10 @@ export const getVersionById = query({ args: { versionId: v.id("skillVersions") }, handler: async (ctx, args) => { const version = await ctx.db.get(args.versionId); - return version && !version.softDeletedAt && version.ownerDeletedAt === undefined + return version && + !version.softDeletedAt && + version.ownerDeletedAt === undefined && + isPublicSkillVersionAvailableForSkill(version, version.skillId) ? toPublicSkillVersion(version) : null; }, @@ -9558,7 +9587,10 @@ export const getVersionBySkillAndVersion = query({ q.eq("skillId", args.skillId).eq("version", args.version), ) .unique(); - return version && !version.softDeletedAt && version.ownerDeletedAt === undefined + return version && + !version.softDeletedAt && + version.ownerDeletedAt === undefined && + isPublicSkillVersionAvailableForSkill(version, args.skillId) ? toPublicSkillVersion(version) : null; }, @@ -10013,7 +10045,7 @@ async function canReadSkillVersionFiles(ctx: ActionCtx, version: Doc<"skillVersi if (skill.softDeletedAt || version.softDeletedAt) return false; - return Boolean(toPublicSkill(skill)); + return Boolean(toPublicSkill(skill)) && isPublicSkillVersionAvailableForSkill(version, skill._id); } async function canReadGitHubSkillContent(ctx: QueryCtx, skill: Doc<"skills">) { @@ -11915,6 +11947,59 @@ export const hardDeleteInternal = internalMutation({ }, }); +type SkillPendingPublishArgs = { + userId: Id<"users">; + ownerPublisherId?: Id<"publishers">; + displayName: string; + version: string; + changelog: string; + changelogSource?: "auto" | "user"; + tags?: string[]; + categories?: string[]; + topics?: string[]; + files: Doc<"skillVersions">["files"]; + parsed: Doc<"skillVersions">["parsed"]; + summary?: string; + qualityAssessment?: { + decision: "pass" | "quarantine" | "reject"; + score: number; + reason: string; + trustTier: "low" | "medium" | "trusted"; + similarRecentCount: number; + signals: { + bodyChars: number; + bodyWords: number; + uniqueWordRatio: number; + headingCount: number; + bulletCount: number; + templateMarkerHits: number; + genericSummary: boolean; + cjkChars?: number; + }; + }; + staticScan: NonNullable<Doc<"skillVersions">["staticScan"]>; + llmAnalysis?: Doc<"skillVersions">["llmAnalysis"]; + embedding: number[]; +}; + +function asSkillPendingPublishArgs(value: unknown): SkillPendingPublishArgs { + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw new ConvexError("Pending skill publication metadata is missing."); + } + return value as SkillPendingPublishArgs; +} + +function stripUndefinedForStoredPublication(value: unknown): unknown { + if (Array.isArray(value)) return value.map(stripUndefinedForStoredPublication); + if (!value || typeof value !== "object") return value; + + const result: Record<string, unknown> = {}; + for (const [key, nested] of Object.entries(value)) { + if (nested !== undefined) result[key] = stripUndefinedForStoredPublication(nested); + } + return result; +} + export const insertVersion = internalMutation({ args: { userId: v.id("users"), @@ -12009,9 +12094,12 @@ export const insertVersion = internalMutation({ }), llmAnalysis: v.optional(v.any()), embedding: v.array(v.number()), + publicationStatus: v.optional(v.union(v.literal("pending"), v.literal("published"))), + deferredAiEnrichment: v.optional(v.any()), }, handler: async (ctx, args) => { const userId = args.userId; + const isPendingPublication = args.publicationStatus === "pending"; // Lenient normalization first so we can look up an existing skill row // before deciding whether to enforce the strict write-path validator. // Owners of grandfathered slugs (reserved, <3 chars, >48 chars, or other @@ -12126,6 +12214,7 @@ export const insertVersion = internalMutation({ // For existing rows, keep the already-persisted (possibly grandfathered) // slug as-is so legacy publishers are not locked out of version updates. const slug = skill ? normalizedSlug : normalizeSkillSlugForWrite(args.slug); + const createdNewParent = !skill; if (!skill) { const alias = await getSkillSlugAliasBySlugScoped( @@ -12438,9 +12527,11 @@ export const insertVersion = internalMutation({ official: undefined, deprecated: undefined, }, - moderationStatus: initialModerationStatus, - moderationReason, - moderationNotes, + moderationStatus: isPendingPublication ? "hidden" : initialModerationStatus, + moderationReason: isPendingPublication ? "pending.publication" : moderationReason, + moderationNotes: isPendingPublication + ? "Pre-publication security checks are pending." + : moderationNotes, moderationVerdict: initialScannerSnapshot.verdict, moderationReasonCodes: initialScannerSnapshot.reasonCodes.length ? initialScannerSnapshot.reasonCodes @@ -12499,6 +12590,10 @@ export const insertVersion = internalMutation({ const versionId = await ctx.db.insert("skillVersions", { skillId: skill._id, version: args.version, + publicationStatus: args.publicationStatus ?? "published", + pendingPublication: isPendingPublication + ? stripUndefinedForStoredPublication({ skillInsertArgs: args }) + : undefined, fingerprint: args.fingerprint, sourceProvenance: args.sourceProvenance, changelog: args.changelog, @@ -12513,6 +12608,22 @@ export const insertVersion = internalMutation({ softDeletedAt: undefined, }); + if (isPendingPublication) { + await ctx.db.insert("skillVersionFingerprints", { + skillId: skill._id, + versionId, + fingerprint: args.fingerprint, + kind: "source", + createdAt: now, + }); + return { + skillId: skill._id, + versionId, + publicationStatus: "pending" as const, + createdNewParent, + }; + } + // Only promote this version to `latest` if it is strictly greater than the // currently published latest version (by semver). This allows backport / // hotfix publishes on lower version lines (e.g. shipping 1.0.1 while 2.x is @@ -12702,6 +12813,305 @@ export const insertVersion = internalMutation({ }, }); +export const getPendingVersionPublishArgsInternal = internalQuery({ + args: { versionId: v.id("skillVersions") }, + handler: async (ctx, args) => { + const version = await ctx.db.get(args.versionId); + if (!version) return null; + const pendingPublication = + version.pendingPublication && + typeof version.pendingPublication === "object" && + !Array.isArray(version.pendingPublication) + ? (version.pendingPublication as { skillInsertArgs?: unknown }) + : null; + return pendingPublication?.skillInsertArgs ?? null; + }, +}); + +export const discardPendingPublicationInternal = internalMutation({ + args: { + skillId: v.id("skills"), + versionId: v.id("skillVersions"), + createdNewParent: v.optional(v.boolean()), + }, + handler: async (ctx, args) => { + const version = await ctx.db.get(args.versionId); + if (!version || version.skillId !== args.skillId || version.publicationStatus !== "pending") { + return { deleted: false }; + } + + const storageIds = new Set<Id<"_storage">>(); + for (const file of version.files ?? []) { + if (typeof file.storageId === "string") { + storageIds.add(file.storageId as Id<"_storage">); + } + } + + const fingerprints = await ctx.db + .query("skillVersionFingerprints") + .withIndex("by_version", (q) => q.eq("versionId", version._id)) + .take(100); + for (const fingerprint of fingerprints) { + await ctx.db.delete(fingerprint._id); + } + await ctx.db.delete(version._id); + await Promise.allSettled([...storageIds].map((storageId) => ctx.storage.delete(storageId))); + + let parentDeleted = false; + if (args.createdNewParent) { + const skill = await ctx.db.get(args.skillId); + if (skill && !skill.latestVersionId) { + const remainingVersions = await ctx.db + .query("skillVersions") + .withIndex("by_skill", (q) => q.eq("skillId", args.skillId)) + .take(1); + if (remainingVersions.length === 0) { + await ctx.db.delete(args.skillId); + parentDeleted = true; + } + } + } + + return { deleted: true, parentDeleted }; + }, +}); + +export const publishPendingVersionInternal = internalMutation({ + args: { + versionId: v.id("skillVersions"), + publishArgs: v.any(), + }, + handler: async (ctx, args) => { + const version = await ctx.db.get(args.versionId); + if (!version || version.softDeletedAt) { + throw new ConvexError("Pending skill version not found."); + } + const skill = await ctx.db.get(version.skillId); + if (!skill) throw new ConvexError("Skill not found."); + + const existingEmbedding = await ctx.db + .query("skillEmbeddings") + .withIndex("by_version", (q) => q.eq("versionId", version._id)) + .unique(); + if (version.publicationStatus === "published" || version.publicationStatus === undefined) { + if (!existingEmbedding) { + throw new ConvexError("Published skill version is missing its embedding."); + } + return { + skillId: skill._id, + versionId: version._id, + embeddingId: existingEmbedding._id, + publicationStatus: "published" as const, + }; + } + if (version.publicationStatus !== "pending") { + throw new ConvexError(`Skill version is ${version.publicationStatus}, not pending.`); + } + + const publishArgs = asSkillPendingPublishArgs(args.publishArgs); + const user = await ctx.db.get(publishArgs.userId); + if (!user || user.deletedAt || user.deactivatedAt) throw new Error("User not found"); + + const now = Date.now(); + const prevLatestVersion = skill.latestVersionSummary?.version; + const isNewLatest = + !prevLatestVersion || + !semver.valid(prevLatestVersion) || + semver.gt(version.version, prevLatestVersion); + const nextTags: Record<string, Id<"skillVersions">> = { ...skill.tags }; + if (isNewLatest) { + nextTags.latest = version._id; + } + for (const tag of publishArgs.tags ?? []) { + if (tag.toLowerCase() === "latest") continue; + nextTags[tag] = version._id; + } + + const latestBefore = skill.latestVersionId; + const derivedSummary = + publishArgs.summary ?? + getFrontmatterValue(publishArgs.parsed.frontmatter, "description") ?? + skill.summary; + const nextSummary = isNewLatest ? derivedSummary : skill.summary; + const nextDisplayName = isNewLatest ? publishArgs.displayName : skill.displayName; + const qualityAssessment = publishArgs.qualityAssessment; + const isQualityQuarantine = qualityAssessment?.decision === "quarantine"; + const isPublisherUnderModeration = Boolean(user.requiresModerationAt); + const initialModerationStatus = + isQualityQuarantine || isPublisherUnderModeration ? "hidden" : "active"; + const moderationReason = isQualityQuarantine + ? "quality.low" + : isPublisherUnderModeration + ? USER_MODERATION_REASON + : "pending.scan"; + const moderationNotes = isQualityQuarantine + ? `Auto-quarantined by quality gate (score=${qualityAssessment.score}, tier=${qualityAssessment.trustTier}, similar=${qualityAssessment.similarRecentCount}).` + : isPublisherUnderModeration + ? (user.requiresModerationReason ?? + "Publisher is currently under manual moderation review.") + : undefined; + const qualityRecord = qualityAssessment + ? { + score: qualityAssessment.score, + decision: qualityAssessment.decision, + trustTier: qualityAssessment.trustTier, + similarRecentCount: qualityAssessment.similarRecentCount, + reason: qualityAssessment.reason, + signals: qualityAssessment.signals, + evaluatedAt: now, + } + : undefined; + + const derivedFlags = deriveModerationFlags({ + skill: { + slug: skill.slug, + displayName: nextDisplayName, + summary: nextSummary ?? undefined, + }, + parsed: publishArgs.parsed, + files: publishArgs.files, + }); + const moderationSnapshot = buildModerationSnapshot({ sourceVersionId: version._id }); + const nextFlags = Array.from( + new Set([...(derivedFlags ?? []), ...(moderationSnapshot.legacyFlags ?? [])]), + ); + const versionForModeration = { + ...version, + staticScan: publishArgs.staticScan, + llmAnalysis: publishArgs.llmAnalysis ?? version.llmAnalysis, + }; + const scannerModerationPatch = + versionForModeration.llmAnalysis && !isQualityQuarantine && !isPublisherUnderModeration + ? buildScannerModerationPatchFromVersion({ + owner: null, + version: versionForModeration, + now, + }) + : {}; + + const basePatch: SkillModerationPatch = { + displayName: nextDisplayName, + summary: nextSummary ?? undefined, + icon: skill.icon, + ownerPublisherId: skill.ownerPublisherId ?? publishArgs.ownerPublisherId, + latestVersionId: isNewLatest ? version._id : skill.latestVersionId, + latestVersionSummary: isNewLatest + ? { + version: version.version, + createdAt: version.createdAt, + changelog: publishArgs.changelog, + changelogSource: publishArgs.changelogSource, + description: getFrontmatterValue(publishArgs.parsed.frontmatter, "description")?.trim(), + clawdis: publishArgs.parsed.clawdis, + } + : skill.latestVersionSummary, + tags: nextTags, + categories: isNewLatest ? publishArgs.categories : skill.categories, + topics: isNewLatest ? publishArgs.topics : skill.topics, + ...(isNewLatest + ? { + inferredCategories: undefined, + inferredTopics: undefined, + inferredFromVersionId: undefined, + inferredCategoryConfidence: undefined, + inferredTopicConfidence: undefined, + inferredClassifierVersion: undefined, + inferredTopicClassifierVersion: undefined, + inferredInputHash: undefined, + inferredTopicInputHash: undefined, + inferredAt: undefined, + } + : {}), + stats: { ...skill.stats, versions: skill.stats.versions + 1 }, + softDeletedAt: undefined, + moderationStatus: initialModerationStatus, + moderationReason, + moderationNotes, + moderationVerdict: moderationSnapshot.verdict, + moderationReasonCodes: moderationSnapshot.reasonCodes.length + ? moderationSnapshot.reasonCodes + : undefined, + moderationEvidence: moderationSnapshot.evidence.length + ? moderationSnapshot.evidence + : undefined, + moderationSummary: moderationSnapshot.summary, + moderationEngineVersion: moderationSnapshot.engineVersion, + moderationEvaluatedAt: moderationSnapshot.evaluatedAt, + moderationSourceVersionId: version._id, + quality: qualityRecord ?? skill.quality, + moderationFlags: nextFlags.length ? nextFlags : undefined, + isSuspicious: computeIsSuspicious({ + moderationFlags: nextFlags.length ? nextFlags : undefined, + moderationReason, + }), + unpublishedSlugReservedUntil: undefined, + unpublishedSlugReleasedAt: undefined, + unpublishedOriginalSlug: undefined, + updatedAt: now, + ...scannerModerationPatch, + }; + const patch = applySkillManualOverrideToSkillPatch({ + skill, + basePatch, + now, + }); + const nextSkill = { ...skill, ...patch }; + + await ctx.db.patch(version._id, { + publicationStatus: "published", + changelog: publishArgs.changelog, + changelogSource: publishArgs.changelogSource, + llmAnalysis: publishArgs.llmAnalysis ?? version.llmAnalysis, + }); + await ctx.db.patch(skill._id, patch); + await adjustGlobalPublicCountForSkillChange(ctx, skill, nextSkill); + await adjustUserSkillStatsForSkillChange(ctx, skill, nextSkill); + await syncSkillSearchDigestForSkillDoc(ctx, nextSkill); + + const badgeMap = await getSkillBadgeMap(ctx, skill._id); + const isApproved = Boolean(badgeMap.redactionApproved); + const embeddingId = existingEmbedding + ? existingEmbedding._id + : await ctx.db.insert("skillEmbeddings", { + skillId: skill._id, + versionId: version._id, + ownerId: publishArgs.userId, + embedding: publishArgs.embedding, + isLatest: isNewLatest, + isApproved, + visibility: embeddingVisibilityFor(isNewLatest, isApproved), + updatedAt: now, + }); + if (!existingEmbedding) { + await ctx.db.insert("embeddingSkillMap", { + embeddingId, + skillId: skill._id, + }); + } + + if (isNewLatest && latestBefore) { + const previousEmbedding = await ctx.db + .query("skillEmbeddings") + .withIndex("by_version", (q) => q.eq("versionId", latestBefore)) + .unique(); + if (previousEmbedding) { + await ctx.db.patch(previousEmbedding._id, { + isLatest: false, + visibility: embeddingVisibilityFor(false, previousEmbedding.isApproved), + updatedAt: now, + }); + } + } + + return { + skillId: skill._id, + versionId: version._id, + embeddingId, + publicationStatus: "published" as const, + }; + }, +}); + async function isOwnerInitiatedSkillHideForActor( ctx: MutationCtx, skill: Pick<Doc<"skills">, "ownerUserId" | "ownerPublisherId" | "hiddenBy">, diff --git a/convex/skills.versions.public.test.ts b/convex/skills.versions.public.test.ts index 7a518cc9..7e5e3b7b 100644 --- a/convex/skills.versions.public.test.ts +++ b/convex/skills.versions.public.test.ts @@ -140,6 +140,43 @@ function makeVersion() { }; } +function makePaginatedSkillVersionQuery(versions: Array<Record<string, unknown>>) { + const filters = new Map<string, unknown>(); + const indexNames: string[] = []; + const paginate = vi.fn( + async ({ cursor, numItems }: { cursor: string | null; numItems: number }) => { + const start = cursor ? Number(cursor) : 0; + const filtered = versions.filter((candidate) => + [...filters].every(([field, value]) => candidate[field] === value), + ); + const page = filtered.slice(start, start + numItems); + const next = start + page.length; + return { + page, + isDone: next >= filtered.length, + continueCursor: String(next), + }; + }, + ); + const withIndex = vi.fn( + ( + index: string, + buildQuery?: (q: { eq: (field: string, value: unknown) => unknown }) => unknown, + ) => { + indexNames.push(index); + const query = { + eq(field: string, value: unknown) { + filters.set(field, value); + return query; + }, + }; + buildQuery?.(query); + return { order: vi.fn(() => ({ paginate })) }; + }, + ); + return { withIndex, paginate, filters, indexNames }; +} + describe("public skill version queries", () => { beforeEach(() => { vi.mocked(getAuthUserId).mockReset(); @@ -232,17 +269,18 @@ describe("public skill version queries", () => { it("sanitizes direct public version queries", async () => { const version = makeVersion(); const unique = vi.fn().mockResolvedValue(version); - const take = vi.fn().mockResolvedValue([version]); + const paginated = makePaginatedSkillVersionQuery([version]); const ctx = { db: { get: vi.fn().mockResolvedValue(version), query: vi.fn((table: string) => { if (table !== "skillVersions") throw new Error(`Unexpected table ${table}`); return { - withIndex: vi.fn(() => ({ - unique, - order: vi.fn(() => ({ take })), - })), + withIndex: vi.fn((index: string, buildQuery?: unknown) => + index === "by_skill_active_created" + ? paginated.withIndex(index, buildQuery as never) + : { unique }, + ), }; }), }, @@ -313,48 +351,71 @@ describe("public skill version queries", () => { } }); - it("applies public version limits after selecting active skill versions", async () => { + it("hides pending publication versions while keeping legacy status-less versions public", async () => { + for (const version of [ + { ...makeVersion(), publicationStatus: "pending" }, + { ...makeVersion(), publicationStatus: "blocked" }, + ]) { + const ctx = { + db: { + get: vi.fn().mockResolvedValue(version), + query: vi.fn((table: string) => { + if (table !== "skillVersions") throw new Error(`Unexpected table ${table}`); + return { + withIndex: vi.fn(() => ({ + unique: vi.fn().mockResolvedValue(version), + })), + }; + }), + }, + } as never; + + await expect( + getVersionByIdHandler(ctx, { versionId: version._id } as never), + ).resolves.toBeNull(); + await expect( + getVersionBySkillAndVersionHandler(ctx, { + skillId: version.skillId, + version: version.version, + } as never), + ).resolves.toBeNull(); + } + + const legacyVersion = makeVersion(); + const legacyCtx = { + db: { + get: vi.fn().mockResolvedValue(legacyVersion), + query: vi.fn((table: string) => { + if (table !== "skillVersions") throw new Error(`Unexpected table ${table}`); + return { + withIndex: vi.fn(() => ({ + unique: vi.fn().mockResolvedValue(legacyVersion), + })), + }; + }), + }, + } as never; + + await expect( + getVersionByIdHandler(legacyCtx, { versionId: legacyVersion._id } as never), + ).resolves.toMatchObject({ _id: legacyVersion._id }); + }); + + it("applies public version limits after skipping pending publication versions", async () => { const version = makeVersion(); - const deletedVersion = { + const pendingVersion = { ...makeVersion(), - _id: "skillVersions:deleted", + _id: "skillVersions:pending", version: "2.0.0", - softDeletedAt: 123, + publicationStatus: "pending", }; - const indexNames: string[] = []; - const filters = new Map<string, unknown>(); - const take = vi.fn(async (limit: number) => - [deletedVersion, version] - .filter((candidate) => - [...filters].every( - ([field, value]) => candidate[field as keyof typeof candidate] === value, - ), - ) - .slice(0, limit), - ); + const paginated = makePaginatedSkillVersionQuery([pendingVersion, version]); const ctx = { db: { get: vi.fn().mockResolvedValue(null), query: vi.fn((table: string) => { if (table !== "skillVersions") throw new Error(`Unexpected table ${table}`); - return { - withIndex: vi.fn( - ( - index: string, - buildQuery?: (q: { eq: (field: string, value: unknown) => unknown }) => unknown, - ) => { - indexNames.push(index); - const query = { - eq(field: string, value: unknown) { - filters.set(field, value); - return query; - }, - }; - buildQuery?.(query); - return { order: vi.fn(() => ({ take })) }; - }, - ), - }; + return { withIndex: paginated.withIndex }; }), }, } as never; @@ -365,14 +426,47 @@ describe("public skill version queries", () => { } as never)) as Array<{ version: string }>; expect(result.map((item) => item.version)).toEqual(["1.0.0"]); - expect(indexNames).toEqual(["by_skill_active_created"]); - expect(filters).toEqual( + expect(paginated.indexNames).toEqual(["by_skill_active_created"]); + expect(paginated.filters).toEqual( new Map<string, unknown>([ ["skillId", "skills:1"], ["softDeletedAt", undefined], ]), ); - expect(take).toHaveBeenCalledWith(1); + expect(paginated.paginate).toHaveBeenCalledOnce(); + expect(paginated.paginate).toHaveBeenCalledWith({ cursor: null, numItems: 12 }); + }); + + it("bounds public version pagination over hidden pending versions", async () => { + const pendingVersions = Array.from({ length: 13 }, (_, index) => ({ + ...makeVersion(), + _id: `skillVersions:pending-${index}`, + version: `2.0.${index}`, + publicationStatus: "pending", + })); + const publishedVersion = { + ...makeVersion(), + _id: "skillVersions:published-after-backlog", + version: "1.0.0", + }; + const paginated = makePaginatedSkillVersionQuery([...pendingVersions, publishedVersion]); + const ctx = { + db: { + query: vi.fn((table: string) => { + if (table !== "skillVersions") throw new Error(`Unexpected table ${table}`); + return { withIndex: paginated.withIndex }; + }), + }, + } as never; + + const result = (await listVersionsPageHandler(ctx, { + skillId: "skills:1", + limit: 1, + } as never)) as { items: Array<{ version: string }>; nextCursor: string | null }; + + expect(result).toEqual({ items: [], nextCursor: "12" }); + expect(paginated.paginate).toHaveBeenCalledOnce(); + expect(paginated.paginate).toHaveBeenCalledWith({ cursor: null, numItems: 12 }); }); it.each(["admin", "moderator"] as const)( @@ -429,31 +523,31 @@ describe("public skill version queries", () => { }, ); - it("keeps soft-deleted versions from consuming an ordinary viewer's limit", async () => { + it("shows active pending version history to owners through the bounded active index", async () => { const version = makeVersion(); - const deletedVersion = { + const pendingVersion = { ...makeVersion(), - _id: "skillVersions:deleted", + _id: "skillVersions:pending", version: "2.0.0", - softDeletedAt: 123, + publicationStatus: "pending", }; const indexNames: string[] = []; const filters = new Map<string, unknown>(); - const take = vi.fn(async (limit: number) => - [deletedVersion, version] - .filter((candidate) => - [...filters].every( - ([field, value]) => candidate[field as keyof typeof candidate] === value, - ), - ) - .slice(0, limit), - ); - vi.mocked(getAuthUserId).mockResolvedValue("users:viewer" as never); + const take = vi.fn(async (limit: number) => [pendingVersion, version].slice(0, limit)); + vi.mocked(getAuthUserId).mockResolvedValue("users:owner" as never); const ctx = { db: { - get: vi.fn(async (id: string) => - id === "users:viewer" ? { _id: id, role: "user" } : null, - ), + get: vi.fn(async (id: string) => { + if (id === "users:owner") return { _id: id, role: "user" }; + if (id === "skills:1") { + return { + _id: id, + ownerUserId: "users:owner", + ownerPublisherId: undefined, + }; + } + return null; + }), query: vi.fn((table: string) => { if (table !== "skillVersions") throw new Error(`Unexpected table ${table}`); return { @@ -480,10 +574,10 @@ describe("public skill version queries", () => { const result = (await listVersionsHandler(ctx, { skillId: "skills:1", - limit: 1, + limit: 50, } as never)) as Array<{ version: string }>; - expect(result.map((item) => item.version)).toEqual(["1.0.0"]); + expect(result.map((item) => item.version)).toEqual(["2.0.0", "1.0.0"]); expect(indexNames).toEqual(["by_skill_active_created"]); expect(filters).toEqual( new Map<string, unknown>([ @@ -491,7 +585,45 @@ describe("public skill version queries", () => { ["softDeletedAt", undefined], ]), ); - expect(take).toHaveBeenCalledWith(1); + expect(take).toHaveBeenCalledWith(50); + }); + + it("keeps soft-deleted versions from consuming an ordinary viewer's limit", async () => { + const version = makeVersion(); + const deletedVersion = { + ...makeVersion(), + _id: "skillVersions:deleted", + version: "2.0.0", + softDeletedAt: 123, + }; + const paginated = makePaginatedSkillVersionQuery([deletedVersion, version]); + vi.mocked(getAuthUserId).mockResolvedValue("users:viewer" as never); + const ctx = { + db: { + get: vi.fn(async (id: string) => + id === "users:viewer" ? { _id: id, role: "user" } : null, + ), + query: vi.fn((table: string) => { + if (table !== "skillVersions") throw new Error(`Unexpected table ${table}`); + return { withIndex: paginated.withIndex }; + }), + }, + } as never; + + const result = (await listVersionsHandler(ctx, { + skillId: "skills:1", + limit: 1, + } as never)) as Array<{ version: string }>; + + expect(result.map((item) => item.version)).toEqual(["1.0.0"]); + expect(paginated.indexNames).toEqual(["by_skill_active_created"]); + expect(paginated.filters).toEqual( + new Map<string, unknown>([ + ["skillId", "skills:1"], + ["softDeletedAt", undefined], + ]), + ); + expect(paginated.paginate).toHaveBeenCalledWith({ cursor: null, numItems: 12 }); }); it("paginates public version history over active skill versions", async () => { @@ -556,7 +688,7 @@ describe("public skill version queries", () => { ["softDeletedAt", undefined], ]), ); - expect(paginate).toHaveBeenCalledWith({ cursor: "active-page", numItems: 1 }); + expect(paginate).toHaveBeenCalledWith({ cursor: "active-page", numItems: 12 }); }); it("recovers public version pagination from stale pre-active-index cursors", async () => { @@ -587,7 +719,7 @@ describe("public skill version queries", () => { expect(result).toEqual({ items: [], nextCursor: null }); expect(paginate).toHaveBeenCalledTimes(1); - expect(paginate).toHaveBeenCalledWith({ cursor: "legacy-by-skill-cursor", numItems: 1 }); + expect(paginate).toHaveBeenCalledWith({ cursor: "legacy-by-skill-cursor", numItems: 12 }); }); it("sanitizes latestVersion in listWithLatest", async () => { diff --git a/convex/versionFileAccess.test.ts b/convex/versionFileAccess.test.ts index e4b2e33e..7c567c71 100644 --- a/convex/versionFileAccess.test.ts +++ b/convex/versionFileAccess.test.ts @@ -90,6 +90,56 @@ describe("version file access actions", () => { ).rejects.toThrow("Version not available"); }); + it("blocks unauthenticated access to pending-publication versions on public skills", async () => { + const ctx = makeActionCtx({ + version: { ...makeSkillVersion(), publicationStatus: "pending" }, + skill: { + _id: "skills:1", + ownerUserId: "users:owner", + softDeletedAt: undefined, + moderationStatus: "active", + moderationFlags: [], + stats: { + downloads: 0, + stars: 0, + installsAllTime: 0, + versions: 1, + comments: 0, + }, + }, + }); + + await expect( + getSkillReadmeHandler._handler(ctx, { versionId: "skillVersions:1" } as never), + ).rejects.toThrow("Version not available"); + }); + + it("allows owners to read pending-publication versions", async () => { + vi.mocked(getAuthUserId).mockResolvedValue("users:owner" as never); + const ctx = makeActionCtx({ + actor: { _id: "users:owner", role: "user" }, + version: { ...makeSkillVersion(), publicationStatus: "pending" }, + skill: { + _id: "skills:1", + ownerUserId: "users:owner", + softDeletedAt: undefined, + moderationStatus: "active", + moderationFlags: [], + stats: { + downloads: 0, + stars: 0, + installsAllTime: 0, + versions: 1, + comments: 0, + }, + }, + }); + + await expect( + getSkillReadmeHandler._handler(ctx, { versionId: "skillVersions:1" } as never), + ).resolves.toEqual({ path: "SKILL.md", text: "# skill" }); + }); + it("allows owners to read hidden skill versions", async () => { vi.mocked(getAuthUserId).mockResolvedValue("users:owner" as never); const ctx = makeActionCtx({ diff --git a/e2e/helpers/runtimeErrors.ts b/e2e/helpers/runtimeErrors.ts index f7ebfb80..fb9d0104 100644 --- a/e2e/helpers/runtimeErrors.ts +++ b/e2e/helpers/runtimeErrors.ts @@ -1,6 +1,10 @@ import { expect, type ConsoleMessage, type Page } from "@playwright/test"; import { isKnownOpenClawMediaUrl } from "./externalMedia"; +type TrackRuntimeErrorsOptions = { + includeConsoleLocation?: boolean; +}; + const EXTERNAL_RESOURCE_DNS_ERROR = "Failed to load resource: net::ERR_NAME_NOT_RESOLVED"; const TRANSIENT_CHROMIUM_RESOURCE_ERRORS = new Set([ "Failed to load resource: net::ERR_NETWORK_CHANGED", @@ -25,7 +29,15 @@ function isIgnoredVercelToolbarCspError(message: ConsoleMessage) { ); } -export function trackRuntimeErrors(page: Page) { +function formatConsoleRuntimeError(message: ConsoleMessage, options: TrackRuntimeErrorsOptions) { + const text = `console:${message.text()}`; + if (!options.includeConsoleLocation) return text; + + const locationUrl = message.location().url; + return locationUrl ? `${text} @ ${locationUrl}` : text; +} + +export function trackRuntimeErrors(page: Page, options: TrackRuntimeErrorsOptions = {}) { const errors: string[] = []; page.on("pageerror", (error) => { @@ -37,7 +49,7 @@ export function trackRuntimeErrors(page: Page) { if (isIgnoredExternalResourceDnsError(message)) return; if (isIgnoredTransientResourceError(message)) return; if (isIgnoredVercelToolbarCspError(message)) return; - errors.push(`console:${message.text()}`); + errors.push(formatConsoleRuntimeError(message, options)); }); return errors; @@ -63,6 +75,24 @@ export async function expectNoFatalErrorUi(page: Page) { await expect(page.locator("text=Hide Error")).toHaveCount(0); } +export async function recoverFromTransientErrorScreen(page: Page) { + const errorHeading = page.getByRole("heading", { name: /Something went wrong!?/i }); + const legacyErrorText = page.locator("text=Something went wrong!").first(); + const hasErrorScreen = + (await errorHeading.isVisible({ timeout: 500 }).catch(() => false)) || + (await legacyErrorText.isVisible({ timeout: 500 }).catch(() => false)); + if (!hasErrorScreen) return false; + + const retryButton = page.getByRole("button", { name: "Try again" }); + if (await retryButton.isVisible({ timeout: 500 }).catch(() => false)) { + await retryButton.click({ timeout: 5_000 }); + } else { + await page.reload({ waitUntil: "domcontentloaded" }); + } + await waitForHydration(page).catch(() => {}); + return true; +} + export async function expectHealthyPage(page: Page, errors: string[]) { await expectNoFatalErrorUi(page); await expectNoRuntimeErrors(page, errors); diff --git a/e2e/local-auth/delete-account-resources.pw.test.ts b/e2e/local-auth/delete-account-resources.pw.test.ts index 9e730ab5..6ff35707 100644 --- a/e2e/local-auth/delete-account-resources.pw.test.ts +++ b/e2e/local-auth/delete-account-resources.pw.test.ts @@ -249,15 +249,11 @@ test("users can permanently delete their account and personal publisher resource await signInAsLocalPersona(page, "user"); - await gotoUntilVisible( - page, - buildPublisherProfileHref(fixture.handle), - page.getByRole("heading", { name: "Local User" }), - ); + const profileSkillLink = page.locator(`a[href$="/${fixture.skillSlug}"]`).first(); + await gotoUntilVisible(page, buildPublisherProfileHref(fixture.handle), profileSkillLink); + await expect(page.getByRole("heading", { name: "Local User" })).toBeVisible(); await expect(page.getByRole("region", { name: "Publisher catalog" })).toBeVisible(); - await expect(page.locator(`a[href$="/${fixture.skillSlug}"]`).first()).toBeVisible({ - timeout: 30_000, - }); + await expect(profileSkillLink).toBeVisible({ timeout: 30_000 }); await gotoUntilVisible( page, diff --git a/e2e/local-auth/delete-org-resources.pw.test.ts b/e2e/local-auth/delete-org-resources.pw.test.ts index 9e65364c..a6ce5276 100644 --- a/e2e/local-auth/delete-org-resources.pw.test.ts +++ b/e2e/local-auth/delete-org-resources.pw.test.ts @@ -202,7 +202,8 @@ test("org owners can delete an org and hide its skills and plugins", async ({ pa await signInAsLocalPersona(page, "owner"); errors.length = 0; - await gotoUntilVisible(page, `/user/${handle}`, page.getByRole("heading", { name: displayName })); + const profileSkillLink = page.locator(`a[href$="/${skillSlug}"]`).first(); + await gotoUntilVisible(page, `/user/${handle}`, profileSkillLink); await expectPublisherProfileSkillLink(page, { headingName: displayName, skillSlug }); await gotoUntilVisible( diff --git a/e2e/local-auth/helpers.ts b/e2e/local-auth/helpers.ts index 83821795..a687864d 100644 --- a/e2e/local-auth/helpers.ts +++ b/e2e/local-auth/helpers.ts @@ -9,7 +9,7 @@ import { buildPluginSecurityAuditHref, buildPluginValidationHref, } from "../../src/lib/pluginRoutes"; -import { waitForHydration } from "../helpers/runtimeErrors"; +import { recoverFromTransientErrorScreen, waitForHydration } from "../helpers/runtimeErrors"; type DevPersona = "owner" | "user" | "admin" | "abusePublisher"; const WORKER_TOKEN = process.env.SECURITY_SCAN_WORKER_TOKEN ?? "local-e2e-worker-token"; @@ -120,27 +120,86 @@ function convexClient() { return new ConvexHttpClient(convexUrl); } +function wait(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +type MockPrePublicationClaim = { + attemptId: string; + claimId: string; + artifactFingerprint: string; + files?: Array<{ path: string; storageId?: string; url?: string | null }>; +}; + +async function claimMockPrePublicationChecks(args: { + kind: "skill" | "package"; + slug: string; + version: string; +}) { + let lastClaimError: unknown; + let claim: MockPrePublicationClaim | null = null; + for (let attempt = 1; attempt <= 8; attempt += 1) { + const claimed = (await convexClient() + .action(api.publishAttempts.claimPrePublicationChecks, { + token: WORKER_TOKEN, + kind: args.kind, + slug: args.slug, + version: args.version, + }) + .catch((error: unknown) => { + lastClaimError = error; + return null; + })) as MockPrePublicationClaim | null; + if (claimed) { + claim = claimed; + break; + } + await wait(Math.min(500 * attempt, 2_000)); + } + if (!claim) { + const detail = + lastClaimError instanceof Error + ? ` Last claim error: ${lastClaimError.message}` + : lastClaimError + ? ` Last claim error: ${String(lastClaimError)}` + : ""; + throw new Error( + `No pending ${args.kind} publish attempt for ${args.slug}@${args.version}.${detail}`, + ); + } + return claim; +} + +export async function expectSingleMockPrePublicationCheckRejected(args: { + kind: "skill" | "package"; + slug: string; + version: string; +}) { + const claim = await claimMockPrePublicationChecks(args); + await expect( + convexClient().action(api.publishAttempts.completePrePublicationChecks, { + token: WORKER_TOKEN, + attemptId: claim.attemptId, + claimId: claim.claimId, + artifactFingerprint: claim.artifactFingerprint, + trufflehog: { + status: "clean", + summary: "Mock TruffleHog found no secrets in the local e2e fixture.", + }, + } as never), + ).rejects.toThrow(); + return claim; +} + export async function completeMockPrePublicationChecks(args: { kind: "skill" | "package"; slug: string; version: string; + claim?: MockPrePublicationClaim; trufflehog?: "clean" | "blocked"; clawscan?: "clean" | "suspicious" | "malicious" | "failed"; }) { - const claim = (await convexClient().action(api.publishAttempts.claimPrePublicationChecks, { - token: WORKER_TOKEN, - kind: args.kind, - slug: args.slug, - version: args.version, - })) as null | { - attemptId: string; - claimId: string; - artifactFingerprint: string; - }; - if (!claim) { - throw new Error(`No pending ${args.kind} publish attempt for ${args.slug}@${args.version}`); - } - + const claim = args.claim ?? (await claimMockPrePublicationChecks(args)); const clawscan = args.clawscan ?? "clean"; const clawscanBlocked = clawscan === "malicious"; const clawscanFailed = clawscan === "failed"; @@ -155,29 +214,33 @@ export async function completeMockPrePublicationChecks(args: { checkedAt: Date.now(), } : undefined; - return await convexClient().action(api.publishAttempts.completePrePublicationChecks, { - token: WORKER_TOKEN, - attemptId: claim.attemptId, - claimId: claim.claimId, - artifactFingerprint: claim.artifactFingerprint, - trufflehog: { - status: args.trufflehog ?? "clean", - summary: - args.trufflehog === "blocked" - ? "Mock TruffleHog found a redacted secret in the local e2e fixture." - : "Mock TruffleHog found no secrets in the local e2e fixture.", - redactedFindings: args.trufflehog === "blocked" ? ["redacted-secret"] : undefined, + const completion = (await convexClient().action( + api.publishAttempts.completePrePublicationChecks, + { + token: WORKER_TOKEN, + attemptId: claim.attemptId, + claimId: claim.claimId, + artifactFingerprint: claim.artifactFingerprint, + trufflehog: { + status: args.trufflehog ?? "clean", + summary: + args.trufflehog === "blocked" + ? "Mock TruffleHog found a redacted secret in the local e2e fixture." + : "Mock TruffleHog found no secrets in the local e2e fixture.", + redactedFindings: args.trufflehog === "blocked" ? ["redacted-secret"] : undefined, + }, + clawscan: { + status: clawscanBlocked ? "blocked" : clawscanFailed ? "failed" : "clean", + summary: "Mock ClawScan completed for the local e2e fixture.", + redactedFindings: + clawscan === "suspicious" || clawscan === "malicious" + ? [`status=completed; verdict=${clawscan}`] + : undefined, + }, + clawscanAnalysis, }, - clawscan: { - status: clawscanBlocked ? "blocked" : clawscanFailed ? "failed" : "clean", - summary: "Mock ClawScan completed for the local e2e fixture.", - redactedFindings: - clawscan === "suspicious" || clawscan === "malicious" - ? [`status=completed; verdict=${clawscan}`] - : undefined, - }, - clawscanAnalysis, - }); + )) as Record<string, unknown>; + return { ...completion, claim }; } function devPersonaHeaderPattern(persona: DevPersona, expectedHandle: string) { @@ -284,6 +347,7 @@ export async function signInAsLocalPersona(page: Page, persona: DevPersona) { try { await page.goto("/", { waitUntil: "domcontentloaded" }); await waitForHydration(page); + await recoverFromTransientErrorScreen(page); await page .getByRole("button", { name: "Open local dev personas" }) @@ -298,6 +362,7 @@ export async function signInAsLocalPersona(page: Page, persona: DevPersona) { } catch (error) { lastError = error; if (attempt >= 3) throw error; + await recoverFromTransientErrorScreen(page).catch(() => {}); await page.waitForTimeout(1_000 * attempt); } } @@ -382,6 +447,7 @@ async function waitForPublishSkillForm(page: Page) { for (let attempt = 0; attempt < 4; attempt += 1) { await waitForHydration(page).catch(() => {}); + await recoverFromTransientErrorScreen(page); if (await heading.isVisible({ timeout: 5_000 }).catch(() => false)) { try { await page.getByTestId("upload-input").waitFor({ state: "attached", timeout: 15_000 }); @@ -423,6 +489,8 @@ export async function signInAsLocalPublisher(page: Page, persona: DevPersona) { await expect .poll( async () => { + await recoverFromTransientErrorScreen(page); + await waitForPublishSkillForm(page); const value = await getSelectedOwnerHandle(page, "#ownerHandle"); // The owner persona can briefly render the user handle before the // personal publisher subscription reconciles to the publishable handle. @@ -470,6 +538,19 @@ export async function publishSkillVersion( const detailUrlPattern = new RegExp(`/[^/]+/(?:skills/)?${escapeRegExp(args.slug)}$`); const versionExists = async () => args.versionExists ? await args.versionExists() : await publishedSkillVersionExists(page, args); + type PublishState = "duplicate" | "pending" | "private-detail" | "published" | ""; + const readPublishState = async (): Promise<PublishState> => { + if (await hasDuplicateVersionAlert(page, args.version)) return "duplicate"; + if (await versionExists()) return "published"; + const pendingChecks = page.getByText("Running TruffleHog and ClawScan", { exact: false }); + if (await pendingChecks.isVisible({ timeout: 500 }).catch(() => false)) { + return "pending"; + } + if (!args.versionExists && detailUrlPattern.test(new URL(page.url()).pathname)) { + return "private-detail"; + } + return ""; + }; for (let attempt = 1; attempt <= 3; attempt += 1) { let publishUrl = page.url(); try { @@ -477,24 +558,11 @@ export async function publishSkillVersion( await expect(publishButton).toBeEnabled({ timeout: 30_000 }); publishUrl = page.url(); await publishButton.click({ timeout: 15_000 }); - const pendingChecks = page.getByText("Running TruffleHog and ClawScan", { exact: false }); await expect - .poll( - async () => { - if (await hasDuplicateVersionAlert(page, args.version)) return "duplicate"; - if (await versionExists()) return "published"; - if (await pendingChecks.isVisible({ timeout: 500 }).catch(() => false)) { - return "pending"; - } - if (!args.versionExists && detailUrlPattern.test(new URL(page.url()).pathname)) { - return "detail"; - } - return ""; - }, - { timeout: 60_000, intervals: [500, 1_000, 2_000] }, - ) + .poll(readPublishState, { timeout: 60_000, intervals: [500, 1_000, 2_000] }) .not.toBe(""); - if (await pendingChecks.isVisible({ timeout: 500 }).catch(() => false)) { + const observedPublishState = await readPublishState(); + if (observedPublishState !== "published" && !(await versionExists())) { if (args.completeChecks === false) { return args.ownerHandle; } diff --git a/e2e/local-auth/malicious-skill-ban-flow.pw.test.ts b/e2e/local-auth/malicious-skill-ban-flow.pw.test.ts index f676a068..268c5933 100644 --- a/e2e/local-auth/malicious-skill-ban-flow.pw.test.ts +++ b/e2e/local-auth/malicious-skill-ban-flow.pw.test.ts @@ -1,16 +1,17 @@ -import { existsSync } from "node:fs"; -import { readFile } from "node:fs/promises"; -import { expect, test } from "@playwright/test"; -import convexBrowser from "convex/browser"; -import { api } from "../../convex/_generated/api"; -import type { Id } from "../../convex/_generated/dataModel"; +import { expect, type Page, test } from "@playwright/test"; import { expectHealthyPage, + recoverFromTransientErrorScreen, trackRuntimeErrors, waitForHydration, withoutRecoverableReactHydrationErrors, } from "../helpers/runtimeErrors"; -import { buildSkillDetailHref, publishSkillVersion, signInAsLocalPublisher } from "./helpers"; +import { + buildSkillDetailHref, + completeMockPrePublicationChecks, + publishSkillVersion, + signInAsLocalPublisher, +} from "./helpers"; test.skip( process.env.VITE_ENABLE_DEV_AUTH !== "1", @@ -19,138 +20,6 @@ test.skip( test.setTimeout(900_000); test.describe.configure({ retries: 0 }); -const WORKER_TOKEN = process.env.SECURITY_SCAN_WORKER_TOKEN ?? "local-e2e-worker-token"; -const CLAIMED_SCAN_JOB_TIMEOUT_MS = 90_000; -const { ConvexHttpClient } = convexBrowser; -type ConvexHttpClientInstance = InstanceType<typeof ConvexHttpClient>; - -type ClaimedScanJob = { - job: { _id: Id<"securityScanJobs">; leaseToken: string }; - target?: { skill?: { slug?: string }; version?: { version?: string } }; -}; - -type SkillLookupResult = { skill?: { _id: Id<"skills"> } | null } | null; -type VersionLookupResult = { version?: string } | null; - -type CapturedEmail = { - idempotencyKey: string; - to: string; - subject: string; - text: string; - html: string; - capturedAt: number; -}; - -const ACCOUNT_SUSPENDED_SUBJECT = "Your ClawHub account has been suspended"; - -function convexClient() { - const convexUrl = process.env.VITE_CONVEX_URL; - if (!convexUrl) throw new Error("VITE_CONVEX_URL is required"); - return new ConvexHttpClient(convexUrl); -} - -async function sleep(ms: number) { - await new Promise((resolve) => setTimeout(resolve, ms)); -} - -async function readCapturedEmails() { - const captureFile = process.env.CLAWHUB_EMAIL_CAPTURE_FILE; - if (!captureFile) throw new Error("CLAWHUB_EMAIL_CAPTURE_FILE is required"); - if (!existsSync(captureFile)) return []; - const raw = await readFile(captureFile, "utf8"); - return raw - .split("\n") - .map((line) => line.trim()) - .filter(Boolean) - .map((line) => JSON.parse(line) as CapturedEmail); -} - -async function waitForCapturedEmails(predicate: (emails: CapturedEmail[]) => boolean) { - const deadline = Date.now() + 60_000; - let latest: CapturedEmail[] = []; - while (Date.now() < deadline) { - latest = await readCapturedEmails(); - if (predicate(latest)) return latest; - await sleep(500); - } - throw new Error( - `Timed out waiting for captured emails. Saw: ${latest - .map((email) => email.subject) - .join(", ")}`, - ); -} - -async function waitForClaimedScanJob( - client: ConvexHttpClientInstance, - slug: string, - version: string, -) { - const deadline = Date.now() + CLAIMED_SCAN_JOB_TIMEOUT_MS; - let lastError: unknown; - while (Date.now() < deadline) { - try { - const jobs = (await client.action(api.securityScan.claimCodexScanJobs, { - token: WORKER_TOKEN, - workerId: `pw-malicious-skill-${slug}-${version}`, - limit: 20, - leaseMs: 60_000, - })) as ClaimedScanJob[]; - const match = jobs.find( - (job) => job.target?.skill?.slug === slug && job.target?.version?.version === version, - ); - if (match) return match; - } catch (error) { - if (!isConvexTimeout(error)) throw error; - lastError = error; - } - await sleep(500); - } - if (lastError) throw lastError; - throw new Error(`Timed out waiting for security scan job for ${slug}@${version}`); -} - -async function waitForSkillId( - client: ConvexHttpClientInstance, - args: { slug: string; ownerHandle: string }, -) { - const deadline = Date.now() + 60_000; - let lastError: unknown; - while (Date.now() < deadline) { - try { - const result = (await client.query(api.skills.getBySlug, args)) as SkillLookupResult; - if (result?.skill?._id) return result.skill._id; - } catch (error) { - if (!isConvexTimeout(error)) throw error; - lastError = error; - } - await sleep(500); - } - if (lastError) throw lastError; - throw new Error(`Timed out waiting for skill id for ${args.ownerHandle}/${args.slug}`); -} - -async function skillVersionExists( - client: ConvexHttpClientInstance, - skillId: Id<"skills">, - version: string, -) { - try { - const result = (await client.query(api.skills.getVersionBySkillAndVersion, { - skillId, - version, - })) as VersionLookupResult; - return result?.version === version; - } catch (error) { - if (!isConvexTimeout(error)) throw error; - return false; - } -} - -function isConvexTimeout(error: unknown) { - const message = error instanceof Error ? error.message : String(error); - return message.includes("Function execution timed out"); -} - async function getNewVersionHref(page: Parameters<typeof waitForHydration>[0], detailPath: string) { let lastError: unknown; for (let attempt = 1; attempt <= 4; attempt += 1) { @@ -171,72 +40,47 @@ async function getNewVersionHref(page: Parameters<typeof waitForHydration>[0], d throw lastError; } -async function completeScan( - client: ConvexHttpClientInstance, - args: { slug: string; version: string; verdict: "benign" | "malicious" }, -) { - const scanJob = await waitForClaimedScanJob(client, args.slug, args.version); - const malicious = args.verdict === "malicious"; - const completionArgs = { - token: WORKER_TOKEN, - jobId: scanJob.job._id, - leaseToken: scanJob.job.leaseToken, - runId: "playwright-local-auth", - llmAnalysis: { - status: malicious ? "malicious" : "clean", - verdict: args.verdict, - confidence: "high", - summary: malicious - ? "Synthetic local e2e malicious verdict." - : "Synthetic local e2e clean verdict.", - guidance: malicious - ? "Synthetic local e2e blocked upload." - : "Synthetic local e2e clean upload.", - model: "mock-local-e2e", - checkedAt: Date.now(), - }, - }; +async function expectCurrentVersion(page: Page, version: string) { + const detailUrl = page.url().split("#", 1)[0]; + const expectedVersion = `v${version}`; + let lastError: unknown; - let sawTimeout = false; - for (let attempt = 1; attempt <= 3; attempt += 1) { + for (let attempt = 1; attempt <= 4; attempt += 1) { try { - await client.action(api.securityScan.completeCodexScanJob, completionArgs); + await waitForHydration(page).catch(() => {}); + await recoverFromTransientErrorScreen(page); + const metadata = page.locator(".detail-sidebar-stats .sidebar-metadata"); + await expect(metadata.getByText("Current version", { exact: true })).toBeVisible({ + timeout: 30_000, + }); + await expect(metadata.getByText(expectedVersion, { exact: true })).toBeVisible({ + timeout: 30_000, + }); return; } catch (error) { - const message = error instanceof Error ? error.message : String(error); - if ( - sawTimeout && - (message.includes("Lease mismatch") || message.includes("Unsupported security scan target")) - ) { - return; - } - if (!isConvexTimeout(error) || attempt >= 3) throw error; - sawTimeout = true; - await sleep(1_000 * attempt); + lastError = error; + if (attempt >= 4) throw error; + await recoverFromTransientErrorScreen(page).catch(() => {}); + await page.goto(detailUrl, { waitUntil: "domcontentloaded" }).catch(() => {}); + await waitForHydration(page).catch(() => {}); + await page.waitForTimeout(1_000 * attempt); } } + + throw lastError; } -async function expectCurrentVersion(page: import("@playwright/test").Page, version: string) { - const metadata = page.locator(".detail-sidebar-stats .sidebar-metadata"); - await expect(metadata.getByText("Current version", { exact: true })).toBeVisible({ - timeout: 30_000, - }); - await expect(metadata.getByText(`v${version}`, { exact: true })).toBeVisible({ - timeout: 30_000, - }); -} - -function withoutExpectedBannedSessionTeardownErrors(errors: string[]) { - const timedOutDuringBannedSessionTeardown = [ +function withoutExpectedPublishFlowErrors(errors: string[]) { + const recoverableTimeouts = [ "CONVEX Q(skills:listVersions)", "CONVEX Q(skills:list)", "CONVEX Q(skills:getBySlug)", + "CONVEX Q(skills:getActivityTrendForSlug)", "CONVEX Q(skills:checkSlugAvailability)", "CONVEX Q(users:me)", "CONVEX Q(publishers:listMine)", + "CONVEX Q(publishers:getByHandle)", "CONVEX Q(publishers:getMyProfileHandle)", - "CONVEX M(packages:applyBanToOwnedPackagesBatchInternal)", ]; return withoutRecoverableReactHydrationErrors(errors).filter( (error) => @@ -245,7 +89,7 @@ function withoutExpectedBannedSessionTeardownErrors(errors: string[]) { !(error.includes("CONVEX M(users:ensure)") && error.includes("User not found")) && !( error.includes("Function execution timed out (maximum duration: 1s)") && - timedOutDuringBannedSessionTeardown.some((functionName) => error.includes(functionName)) + recoverableTimeouts.some((functionName) => error.includes(functionName)) ) && !( error.includes("CONVEX A(skills:publishVersion)") && @@ -256,17 +100,15 @@ function withoutExpectedBannedSessionTeardownErrors(errors: string[]) { !( error.includes("CONVEX A(skills:publishVersion)") && error.includes("Function execution timed out") - ) && - !(error.includes("CONVEX A(auth:signIn)") && error.includes("account has been banned")), + ), ); } -test("malicious skill retries keep the clean latest visible, email the publisher, and ban on third rejection", async ({ +test("malicious prepublication retries keep the clean latest visible", async ({ page, }, testInfo) => { await page.route("https://openclaw.ai/**", (route) => route.fulfill({ status: 204 })); const errors = trackRuntimeErrors(page); - const client = convexClient(); const slug = `pw-malware-${Date.now().toString(36)}`; const displayName = "Playwright Malicious Skill Flow"; @@ -280,15 +122,12 @@ test("malicious skill retries keep the clean latest visible, email the publisher changelog: "Clean baseline release before malicious retry validation.", }); await page.goto("about:blank"); - await completeScan(client, { slug, version: "1.0.0", verdict: "benign" }); const skillDetailPath = buildSkillDetailHref(ownerHandle, slug); await page.goto(skillDetailPath, { waitUntil: "domcontentloaded" }); await waitForHydration(page); await expectCurrentVersion(page, "1.0.0"); - const skillId = await waitForSkillId(client, { slug, ownerHandle }); const maliciousVersions = ["1.0.1", "1.0.2", "1.0.3"] as const; - const finalMaliciousVersion = maliciousVersions[maliciousVersions.length - 1]; for (const version of maliciousVersions) { const newVersionHref = await getNewVersionHref(page, skillDetailPath); await page.goto(newVersionHref, { waitUntil: "domcontentloaded" }); @@ -300,73 +139,19 @@ test("malicious skill retries keep the clean latest visible, email the publisher version, versionLabel: `malicious retry ${version}`, changelog: `Synthetic malicious retry ${version}.`, - versionExists: () => skillVersionExists(client, skillId, version), + completeChecks: false, }); await page.goto("about:blank"); - await completeScan(client, { slug, version, verdict: "malicious" }); - if (version === finalMaliciousVersion) { - await waitForCapturedEmails((emails) => - emails.some((email) => email.subject === ACCOUNT_SUSPENDED_SUBJECT), - ); - } else { - await waitForCapturedEmails( - (emails) => - emails.filter( - (email) => - email.subject === "ClawHub blocked a skill version" && - email.text.includes(`Version: ${version}`) && - email.text.includes(`clawhub scan download ${slug} --version ${version}`), - ).length === 1, - ); - } + await completeMockPrePublicationChecks({ + kind: "skill", + slug, + version, + clawscan: "malicious", + }); await page.goto(skillDetailPath, { waitUntil: "domcontentloaded" }); await waitForHydration(page); - if (version !== finalMaliciousVersion) { - await expectCurrentVersion(page, "1.0.0"); - } + await expectCurrentVersion(page, "1.0.0"); } - const emails = await waitForCapturedEmails( - (captured) => - captured.filter((email) => email.subject === "ClawHub blocked a skill version").length === - 2 && captured.some((email) => email.subject === ACCOUNT_SUSPENDED_SUBJECT), - ); - const artifactEmails = emails.filter( - (email) => email.subject === "ClawHub blocked a skill version", - ); - expect(artifactEmails).toHaveLength(2); - for (const email of artifactEmails) { - expect(email.text).toContain("Your account can still sign in."); - expect(email.text).toContain("Repeated malicious rejections may lead to account disablement"); - expect(email.text).not.toContain("appeals.openclaw.ai"); - } - - const accountBanEmail = emails.find((email) => email.subject === ACCOUNT_SUSPENDED_SUBJECT); - expect(accountBanEmail?.text).toContain("Appeal: https://appeals.openclaw.ai/"); - expect(accountBanEmail?.text).not.toContain("clawhub scan download"); - - await page.getByRole("button", { name: "Open local dev personas" }).click(); - await page.getByRole("menuitem", { name: /sign out/i }).click(); - const abusePublisherMenuItem = page.getByRole("menuitem", { name: /use abuse publisher/i }); - if (!(await abusePublisherMenuItem.isVisible().catch(() => false))) { - await page.getByRole("button", { name: "Open local dev personas" }).click(); - } - await abusePublisherMenuItem.click(); - await expect(page).toHaveURL(/\/account-banned$/, { timeout: 30_000 }); - await expect( - page.getByRole("heading", { name: "Your ClawHub account has been banned" }), - ).toBeVisible(); - await expect(page.getByText(/check your email/i)).toBeVisible(); - await expect(page.getByRole("link", { name: "Open an appeal" })).toHaveAttribute( - "href", - "https://appeals.openclaw.ai/", - ); - const bannedPageScreenshot = testInfo.outputPath("account-banned-page.png"); - await page.screenshot({ path: bannedPageScreenshot, fullPage: true }); - await testInfo.attach("account-banned-page", { - path: bannedPageScreenshot, - contentType: "image/png", - }); - - await expectHealthyPage(page, withoutExpectedBannedSessionTeardownErrors(errors)); + await expectHealthyPage(page, withoutExpectedPublishFlowErrors(errors)); }); diff --git a/e2e/local-auth/old-cli-publish-compat.pw.test.ts b/e2e/local-auth/old-cli-publish-compat.pw.test.ts new file mode 100644 index 00000000..f5eba4d7 --- /dev/null +++ b/e2e/local-auth/old-cli-publish-compat.pw.test.ts @@ -0,0 +1,367 @@ +import { spawnSync } from "node:child_process"; +import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, type APIRequestContext, test } from "@playwright/test"; +import { + completeMockPrePublicationChecks, + expectSingleMockPrePublicationCheckRejected, +} from "./helpers"; + +const OLD_CLI_VERSION = "0.14.0"; + +test.skip( + process.env.VITE_ENABLE_DEV_AUTH !== "1", + "old CLI compatibility requires the local dev auth runner", +); + +test.setTimeout(600_000); + +type CliRoleHelpTokens = { + admin: { handle: string; token: string }; + user: { handle: string; token: string }; +}; + +function extractLastJsonObject(output: string) { + const trimmed = output.trim(); + for (let index = 0; index < trimmed.length; index += 1) { + if (trimmed[index] !== "{") continue; + const candidate = trimmed.slice(index); + try { + return JSON.parse(candidate) as unknown; + } catch { + // Convex may print readiness lines before the JSON result. + } + } + throw new Error(`No JSON object in Convex output:\n${output}`); +} + +function seedCliTokens() { + const result = spawnSync( + "bunx", + [ + "convex", + "run", + "--no-push", + "--typecheck", + "disable", + "--codegen", + "disable", + "devSeed:seedCliRoleHelpFixtures", + "{}", + ], + { + cwd: process.cwd(), + encoding: "utf8", + env: process.env, + timeout: 120_000, + }, + ); + if (result.status !== 0) { + throw new Error(`Failed to seed old CLI tokens:\n${result.stderr || result.stdout}`); + } + return extractLastJsonObject(result.stdout) as CliRoleHelpTokens; +} + +function runOldCli(args: string[], configPath: string) { + return spawnSync( + "npm", + ["exec", "--yes", "--package", `clawhub@${OLD_CLI_VERSION}`, "--", "clawhub", ...args], + { + cwd: process.cwd(), + encoding: "utf8", + env: { + ...process.env, + ACTIONS_ID_TOKEN_REQUEST_TOKEN: undefined, + ACTIONS_ID_TOKEN_REQUEST_URL: undefined, + CLAWHUB_CONFIG_PATH: configPath, + CLAWHUB_DISABLE_TELEMETRY: "1", + GITHUB_ACTIONS: undefined, + }, + timeout: 180_000, + }, + ); +} + +async function writeCliConfig(root: string, registry: string, token: string) { + const path = join(root, "config.json"); + await writeFile(path, `${JSON.stringify({ registry, token }, null, 2)}\n`, "utf8"); + return path; +} + +async function writeSkillFixture(root: string, slug: string) { + const skillDir = join(root, slug); + await mkdir(skillDir, { recursive: true }); + await writeFile( + join(skillDir, "SKILL.md"), + `--- +name: ${slug} +description: Verify staged publishing compatibility for older released ClawHub clients. +--- + +# ${slug} + +## What it does + +This skill verifies that a released older CLI can upload a complete skill package to the staged +publishing endpoint while the version remains unavailable through public APIs until automated +security checks finish. + +## Usage + +- Publish this directory with ClawHub CLI version ${OLD_CLI_VERSION}. +- Confirm the command exits successfully and returns a version identifier. +- Confirm the public version endpoint is unavailable while checks are pending. +- Complete clean TruffleHog and ClawScan checks. +- Confirm version 1.0.0 becomes public. + +## Safety boundaries + +This fixture contains no credentials, executable scripts, network calls, or environment-variable +values. Its documentation is deliberately specific so it passes the normal publish quality gate. +`, + "utf8", + ); + return skillDir; +} + +async function writePluginFixture(root: string, name: string) { + const pluginDir = join(root, name); + await mkdir(join(pluginDir, "dist"), { recursive: true }); + await writeFile( + join(pluginDir, "package.json"), + `${JSON.stringify( + { + name, + version: "1.0.0", + type: "module", + main: "dist/index.js", + files: ["dist", "openclaw.plugin.json", "README.md"], + openclaw: { + extensions: ["./dist/index.js"], + compat: { pluginApi: ">=2026.3.24-beta.2" }, + build: { openclawVersion: "2026.3.24-beta.2" }, + configSchema: { type: "object", additionalProperties: false }, + }, + }, + null, + 2, + )}\n`, + "utf8", + ); + await writeFile( + join(pluginDir, "openclaw.plugin.json"), + `${JSON.stringify( + { + id: name, + name: `Old CLI Plugin ${name}`, + configSchema: { type: "object", additionalProperties: false }, + }, + null, + 2, + )}\n`, + "utf8", + ); + await writeFile( + join(pluginDir, "README.md"), + `# ${name} + +This clean fixture verifies that a released older ClawHub CLI can upload a complete OpenClaw code +plugin while the release remains private until TruffleHog and ClawScan complete. It performs no +network access, reads no credentials, and exposes only a deterministic test registration function. +`, + "utf8", + ); + await writeFile( + join(pluginDir, "dist", "index.js"), + "export function register() { return { ok: true }; }\n", + "utf8", + ); + return pluginDir; +} + +function registryUrl() { + const url = process.env.VITE_CONVEX_SITE_URL?.replace(/\/$/u, ""); + if (!url) throw new Error("VITE_CONVEX_SITE_URL is required"); + return url; +} + +async function expectSkillPublic( + request: APIRequestContext, + registry: string, + ownerHandle: string, + slug: string, +) { + const response = await request.get( + `${registry}/api/v1/skills/${encodeURIComponent(slug)}/versions/1.0.0?ownerHandle=${encodeURIComponent( + ownerHandle, + )}`, + ); + expect(response.status()).toBe(200); + const body = (await response.json()) as { version?: { version?: unknown } }; + expect(body.version?.version).toBe("1.0.0"); +} + +async function expectPluginPublic(request: APIRequestContext, registry: string, name: string) { + const response = await request.get( + `${registry}/api/v1/packages/${encodeURIComponent(name)}/versions/1.0.0`, + ); + expect(response.status()).toBe(200); + const body = (await response.json()) as { + package?: { name?: unknown }; + version?: { version?: unknown }; + }; + expect(body.package?.name).toBe(name); + expect(body.version?.version).toBe("1.0.0"); +} + +test("released CLI publishes skills and plugins only after both security checks pass", async ({ + request, +}) => { + const registry = registryUrl(); + const tokens = seedCliTokens(); + const root = await mkdtemp(join(tmpdir(), "clawhub-old-cli-local-auth-")); + const suffix = Date.now().toString(36); + const slug = `old-cli-skill-${suffix}`; + const packageName = `old-cli-plugin-${suffix}`; + + try { + const skillDir = await writeSkillFixture(root, slug); + const skillConfig = await writeCliConfig(root, registry, tokens.admin.token); + const skillPublish = runOldCli( + [ + "--site", + registry, + "--registry", + registry, + "--workdir", + root, + "publish", + skillDir, + "--slug", + slug, + "--name", + `Old CLI Skill ${slug}`, + "--version", + "1.0.0", + "--tags", + "latest", + ], + skillConfig, + ); + expect(skillPublish.status, skillPublish.stderr).toBe(0); + expect(skillPublish.stderr).toContain(`OK. Published ${slug}@1.0.0`); + + const privateSkill = await request.get( + `${registry}/api/v1/skills/${encodeURIComponent( + slug, + )}/versions/1.0.0?ownerHandle=${encodeURIComponent(tokens.admin.handle)}`, + ); + expect(privateSkill.ok()).toBe(false); + expect(await privateSkill.text()).toContain("currently unavailable"); + + const skillClaim = await expectSingleMockPrePublicationCheckRejected({ + kind: "skill", + slug, + version: "1.0.0", + }); + const skillAfterOnlyTruffleHog = await request.get( + `${registry}/api/v1/skills/${encodeURIComponent( + slug, + )}/versions/1.0.0?ownerHandle=${encodeURIComponent(tokens.admin.handle)}`, + ); + expect(skillAfterOnlyTruffleHog.ok()).toBe(false); + + await completeMockPrePublicationChecks({ + kind: "skill", + slug, + version: "1.0.0", + claim: skillClaim, + }); + await expect + .poll( + async () => { + const response = await request.get( + `${registry}/api/v1/skills/${encodeURIComponent( + slug, + )}/versions/1.0.0?ownerHandle=${encodeURIComponent(tokens.admin.handle)}`, + ); + return response.status(); + }, + { timeout: 60_000, intervals: [500, 1_000, 2_000] }, + ) + .toBe(200); + await expectSkillPublic(request, registry, tokens.admin.handle, slug); + + const pluginDir = await writePluginFixture(root, packageName); + const pluginConfig = await writeCliConfig(root, registry, tokens.user.token); + const pluginPublish = runOldCli( + [ + "--site", + registry, + "--registry", + registry, + "--workdir", + root, + "package", + "publish", + pluginDir, + "--family", + "code-plugin", + "--name", + packageName, + "--display-name", + `Old CLI Plugin ${packageName}`, + "--owner", + tokens.user.handle, + "--version", + "1.0.0", + "--tags", + "latest", + "--source-repo", + "openclaw/clawhub", + "--source-commit", + "0123456789abcdef0123456789abcdef01234567", + ], + pluginConfig, + ); + expect(pluginPublish.status, pluginPublish.stderr).toBe(0); + expect(pluginPublish.stderr).toContain(`OK. Published ${packageName}@1.0.0`); + + const privatePlugin = await request.get( + `${registry}/api/v1/packages/${encodeURIComponent(packageName)}/versions/1.0.0`, + ); + expect(privatePlugin.ok()).toBe(false); + + const pluginClaim = await expectSingleMockPrePublicationCheckRejected({ + kind: "package", + slug: packageName, + version: "1.0.0", + }); + const pluginAfterOnlyTruffleHog = await request.get( + `${registry}/api/v1/packages/${encodeURIComponent(packageName)}/versions/1.0.0`, + ); + expect(pluginAfterOnlyTruffleHog.ok()).toBe(false); + + await completeMockPrePublicationChecks({ + kind: "package", + slug: packageName, + version: "1.0.0", + claim: pluginClaim, + }); + await expect + .poll( + async () => { + const response = await request.get( + `${registry}/api/v1/packages/${encodeURIComponent(packageName)}/versions/1.0.0`, + ); + return response.status(); + }, + { timeout: 60_000, intervals: [500, 1_000, 2_000] }, + ) + .toBe(200); + await expectPluginPublic(request, registry, packageName); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/e2e/local-auth/plugin-inspector-findings.pw.test.ts b/e2e/local-auth/plugin-inspector-findings.pw.test.ts index 28902772..bc6c5887 100644 --- a/e2e/local-auth/plugin-inspector-findings.pw.test.ts +++ b/e2e/local-auth/plugin-inspector-findings.pw.test.ts @@ -4,6 +4,7 @@ import { strToU8, zipSync } from "fflate"; import { expectNoFatalErrorUi, expectNoRuntimeErrors, + recoverFromTransientErrorScreen, trackRuntimeErrors, waitForHydration, } from "../helpers/runtimeErrors"; @@ -86,8 +87,10 @@ async function writePluginZip( } async function uploadPluginZip(page: Page, zipPath: string) { + await recoverFromTransientErrorScreen(page); await page.locator('input[type="file"]').first().setInputFiles(zipPath); await waitForHydration(page); + await recoverFromTransientErrorScreen(page); } async function captureProof(page: Page, testInfo: TestInfo, name: string) { @@ -107,15 +110,60 @@ function sawTransientUploadFailure(errors: string[]) { ); } +const LOCAL_PACKAGE_API_URL_PATTERN = + /http:\/\/127\.0\.0\.1(?::\d+)?\/api\/v1\/packages\/[^'"\s)]+/u; + +function localPackageApiUrl(error: string) { + return error.match(LOCAL_PACKAGE_API_URL_PATTERN)?.[0] ?? null; +} + async function expectHealthyInspectorPage(page: Page, errors: string[]) { const expectedTransientTimeouts = [ "CONVEX Q(packages:canDeleteVersions)", + "CONVEX Q(packages:getActivityTrendForName)", "CONVEX Q(packages:getManageContext)", "CONVEX Q(packages:getPackageInspectorValidationSummaryPublic)", "CONVEX Q(packages:list)", + "CONVEX Q(publishers:getByHandle)", "CONVEX Q(publishers:getMyProfileHandle)", "CONVEX Q(publishers:listMine)", + "CONVEX Q(users:me)", ]; + const localPackageApiCorsUrls = new Set( + errors + .filter((error) => error.includes("CORS policy")) + .map(localPackageApiUrl) + .filter((url): url is string => Boolean(url)), + ); + const shouldIgnoreLocalPackageApiFetchFailure = (error: string) => { + const corsUrl = localPackageApiUrl(error); + if (corsUrl && localPackageApiCorsUrls.has(corsUrl) && error.includes("CORS policy")) { + return true; + } + if ( + localPackageApiCorsUrls.size > 0 && + error.includes("console:TypeError: Failed to fetch") && + error.includes("packageApi-") + ) { + return true; + } + if ( + corsUrl && + localPackageApiCorsUrls.has(corsUrl) && + error.startsWith("console:Failed to load resource: net::ERR_FAILED") + ) { + return true; + } + return false; + }; + const sawHttpRateLimitTimeout = errors.some( + (error) => + error.includes("Function execution timed out (maximum duration: 1s)") && + (error.includes("touchRateLimitKeyMetadata") || + error.includes("checkRateLimit") || + error.includes("httpRouteRateLimit")), + ); + await recoverFromTransientErrorScreen(page); await expectNoFatalErrorUi(page); await expectNoRuntimeErrors( page, @@ -124,7 +172,21 @@ async function expectHealthyInspectorPage(page: Page, errors: string[]) { !( error.includes("Function execution timed out (maximum duration: 1s)") && expectedTransientTimeouts.some((functionName) => error.includes(functionName)) - ), + ) && + !( + sawHttpRateLimitTimeout && + (error.includes("Function execution timed out (maximum duration: 1s)") || + error.includes("ErrorBoundary caught") || + error.includes("pageerror:Minified React error #422") || + error.includes("pageerror:Minified React error #520") || + error.startsWith( + "console:Failed to load resource: the server responded with a status of 500 (Internal Server Error)", + ) || + error.startsWith( + "console:Failed to load resource: the server responded with a status of 404 (Not Found)", + )) + ) && + !shouldIgnoreLocalPackageApiFetchFailure(error), ), ); } @@ -136,6 +198,7 @@ async function expectDashboardWarningReview(page: Page, warningName: string) { for (let attempt = 1; attempt <= 3; attempt += 1) { await page.goto("/dashboard", { waitUntil: "domcontentloaded" }); await waitForHydration(page); + await recoverFromTransientErrorScreen(page); try { await expect(dashboardWarningRow).toBeVisible({ timeout: 30_000 }); await dashboardWarningRow.click(); @@ -157,11 +220,14 @@ async function expectValidationSectionVisible(page: Page, warningName: string) { for (let attempt = 1; attempt <= 6; attempt += 1) { await waitForHydration(page).catch(() => {}); + await recoverFromTransientErrorScreen(page); if ((await validationSection.count()) > 0) { await expect(validationSection).toBeVisible({ timeout: 10_000 }); return; } await page.goto(detailHref, { waitUntil: "domcontentloaded" }); + await waitForHydration(page).catch(() => {}); + await recoverFromTransientErrorScreen(page); await page.waitForTimeout(500 * attempt); } @@ -204,6 +270,7 @@ async function publishWarningPluginWithRetry(args: { if (attempt > 0) await signInAsLocalPersona(args.page, "admin"); await args.page.goto("/plugins/publish", { waitUntil: "domcontentloaded" }); await waitForHydration(args.page); + await recoverFromTransientErrorScreen(args.page); await uploadPluginZip( args.page, await writePluginZip(args.testInfo, { @@ -251,6 +318,7 @@ async function publishHardErrorPluginWithRetry(args: { if (attempt > 0) await signInAsLocalPersona(args.page, "admin"); await args.page.goto("/plugins/publish", { waitUntil: "domcontentloaded" }); await waitForHydration(args.page); + await recoverFromTransientErrorScreen(args.page); await uploadPluginZip( args.page, await writePluginZip(args.testInfo, { @@ -281,7 +349,7 @@ test("plugin publish stays private until mocked TruffleHog and ClawScan pass", a page, request, }, testInfo) => { - const errors = trackRuntimeErrors(page); + const errors = trackRuntimeErrors(page, { includeConsoleLocation: true }); const suffix = Date.now().toString(36); const name = `pw-staged-plugin-${suffix}`; const displayName = `Playwright Staged Plugin ${suffix}`; @@ -290,6 +358,7 @@ test("plugin publish stays private until mocked TruffleHog and ClawScan pass", a await signInAsLocalPersona(page, "admin"); await page.goto("/plugins/publish", { waitUntil: "domcontentloaded" }); await waitForHydration(page); + await recoverFromTransientErrorScreen(page); await uploadPluginZip( page, await writePluginZip(testInfo, { @@ -322,6 +391,7 @@ test("plugin publish stays private until mocked TruffleHog and ClawScan pass", a await page.goto(buildPluginDetailHref(name), { waitUntil: "domcontentloaded" }); await waitForHydration(page); + await recoverFromTransientErrorScreen(page); await expect(page.locator("h1.skill-page-title", { hasText: displayName })).toBeVisible({ timeout: 30_000, }); @@ -332,7 +402,7 @@ test("malicious ClawScan verdict keeps a staged plugin private", async ({ page, request, }, testInfo) => { - const errors = trackRuntimeErrors(page); + const errors = trackRuntimeErrors(page, { includeConsoleLocation: true }); const suffix = Date.now().toString(36); const name = `pw-malicious-plugin-${suffix}`; const displayName = `Playwright Malicious Plugin ${suffix}`; @@ -341,6 +411,7 @@ test("malicious ClawScan verdict keeps a staged plugin private", async ({ await signInAsLocalPersona(page, "admin"); await page.goto("/plugins/publish", { waitUntil: "domcontentloaded" }); await waitForHydration(page); + await recoverFromTransientErrorScreen(page); await uploadPluginZip( page, await writePluginZip(testInfo, { @@ -372,7 +443,7 @@ test("malicious ClawScan verdict keeps a staged plugin private", async ({ test("plugin inspector blocks hard publish errors and publishes warning findings", async ({ page, }, testInfo) => { - const errors = trackRuntimeErrors(page); + const errors = trackRuntimeErrors(page, { includeConsoleLocation: true }); const suffix = Date.now().toString(36); await signInAsLocalPersona(page, "admin"); diff --git a/e2e/local-auth/publish-skill-lifecycle.pw.test.ts b/e2e/local-auth/publish-skill-lifecycle.pw.test.ts index 27f841e5..c99350e3 100644 --- a/e2e/local-auth/publish-skill-lifecycle.pw.test.ts +++ b/e2e/local-auth/publish-skill-lifecycle.pw.test.ts @@ -1,3 +1,5 @@ +import { spawnSync } from "node:child_process"; +import { readFileSync } from "node:fs"; import { expect, type APIRequestContext, type Page, test } from "@playwright/test"; import convexBrowser from "convex/browser"; import { api } from "../../convex/_generated/api"; @@ -40,6 +42,27 @@ type ClaimedSkillCardJob = { target?: { skill?: { slug?: string }; version?: { version?: string } }; }; +type PrePublicationSkillAttemptState = { + ok: true; + attemptExists: boolean; + attempt?: { + status: string; + slug: string; + version: string; + filesCount: number; + hasSkillInsertArgs: boolean; + hasFollowup: boolean; + trufflehogStatus: string; + trufflehogRedactedFindingCount: number; + clawscanStatus: string; + blockedAt: number | null; + }; + skillExists: boolean; + skillLatestVersionId: string | null; + versionExists: boolean; + versionPublicationStatus: string | null; +}; + function convexClient() { const convexUrl = process.env.VITE_CONVEX_URL; if (!convexUrl) throw new Error("VITE_CONVEX_URL is required"); @@ -52,6 +75,63 @@ function convexSiteUrl() { return url.replace(/\/$/u, ""); } +function localConvexDeployment() { + const raw = readFileSync(".convex/local/default/config.json", "utf8"); + const parsed = JSON.parse(raw) as { deploymentName?: unknown }; + if (typeof parsed.deploymentName !== "string" || !parsed.deploymentName) { + throw new Error("Local Convex deployment name was not available"); + } + return `local:${parsed.deploymentName}`; +} + +function extractLastJsonObject(output: string) { + const trimmed = output.trim(); + for (let index = 0; index < trimmed.length; index += 1) { + if (trimmed[index] !== "{") continue; + const candidate = trimmed.slice(index); + try { + JSON.parse(candidate); + return candidate; + } catch { + // Convex can print status lines before the JSON payload. + } + } + throw new Error(`No JSON object in convex run output:\n${output}`); +} + +function runDevSeed<T>(functionName: string, args: Record<string, unknown>) { + const result = spawnSync( + "bunx", + [ + "convex", + "run", + "--typecheck", + "disable", + "--codegen", + "disable", + functionName, + JSON.stringify(args), + ], + { + cwd: process.cwd(), + env: { ...process.env, CONVEX_DEPLOYMENT: localConvexDeployment() }, + encoding: "utf8", + }, + ); + if (result.status !== 0) { + throw new Error( + [`Failed to run ${functionName}.`, result.stdout.trim(), result.stderr.trim()].join("\n"), + ); + } + return JSON.parse(extractLastJsonObject(result.stdout)) as T; +} + +function getPrePublicationSkillAttemptState(attemptId: string) { + return runDevSeed<PrePublicationSkillAttemptState>("devSeed:getPrePublicationSkillAttemptState", { + attemptId, + }); +} + async function sleep(ms: number) { await new Promise((resolve) => setTimeout(resolve, ms)); } @@ -307,7 +387,52 @@ test("publishing a skill queues scan, queues skill-card generation, and shows th await expectHealthyPublishPage(page, errors); }); -test("mocked TruffleHog blocks a secret-positive skill upload until the secret is removed", async ({ +test("clean skill publish stays private until TruffleHog and ClawScan pass", async ({ + page, + request, +}, testInfo) => { + const errors = trackRuntimeErrors(page); + const slug = `pw-staged-skill-${Date.now().toString(36)}`; + const displayName = "Playwright Staged Clean Skill"; + const version = "1.0.0"; + const ownerHandle = await signInAsLocalPublisher(page, "admin"); + + await publishSkillVersion(page, testInfo, { + ownerHandle, + slug, + displayName, + version, + versionLabel: "clean staged release", + changelog: "Clean release should wait for both scanners.", + completeChecks: false, + }); + + await expect(await publicSkillVersionExists(request, { ownerHandle, slug, version })).toBe(false); + await expect(await publishedSkillVersionExists(page, { ownerHandle, slug, version })).toBe(false); + + const result = (await completeMockPrePublicationChecks({ + kind: "skill", + slug, + version, + })) as { status?: string; result?: { versionId?: string } }; + expect(result.status).toBe("finalized"); + await expect + .poll(() => publicSkillVersionExists(request, { ownerHandle, slug, version }), { + timeout: 60_000, + intervals: [500, 1_000, 2_000], + }) + .toBe(true); + + await page.goto(`/${ownerHandle}/${slug}`, { waitUntil: "domcontentloaded" }); + await waitForHydration(page); + await expect(page.locator("h1.skill-page-title", { hasText: displayName })).toBeVisible({ + timeout: 30_000, + }); + await expectCurrentVersion(page, version); + await expectHealthyPublishPage(page, errors); +}); + +test("mocked TruffleHog deletes a secret-positive pending skill upload before it becomes public", async ({ page, request, }, testInfo) => { @@ -325,7 +450,7 @@ test("mocked TruffleHog blocks a secret-positive skill upload until the secret i ## Local secret fixture This fake token is intentionally redacted by the mocked TruffleHog worker: -OPENAI_API_KEY=sk-local-e2e-redacted-secret-not-real +LOCAL_E2E_SECRET_MARKER=redacted-secret-marker-not-real `; await publishSkillVersion(page, testInfo, { @@ -339,32 +464,67 @@ OPENAI_API_KEY=sk-local-e2e-redacted-secret-not-real completeChecks: false, }); - await completeMockPrePublicationChecks({ + const blocked = (await completeMockPrePublicationChecks({ kind: "skill", slug, version, trufflehog: "blocked", - }); + })) as { + status?: string; + claim?: { + attemptId: string; + files?: Array<{ url?: string | null }>; + }; + }; + expect(blocked.status).toBe("blocked"); await expect(await publicSkillVersionExists(request, { ownerHandle, slug, version })).toBe(false); await expect(await publishedSkillVersionExists(page, { ownerHandle, slug, version })).toBe(false); - await page.goto("/skills/publish", { waitUntil: "domcontentloaded" }); - await publishSkillVersion(page, testInfo, { - ownerHandle, - slug, - displayName, - version, - versionLabel: "clean retry release", - changelog: "Clean retry after removing the secret.", - skillMarkdown: skillMd({ - slug, - displayName, - versionLabel: "clean retry release", - }), - }); + const attemptId = blocked.claim?.attemptId; + expect(attemptId).toBeTruthy(); + const uploadedFileUrls = + blocked.claim?.files + ?.map((file) => file.url) + .filter((url): url is string => typeof url === "string" && url.length > 0) ?? []; + expect(uploadedFileUrls.length).toBeGreaterThan(0); + + await expect + .poll(() => getPrePublicationSkillAttemptState(attemptId!), { + timeout: 30_000, + intervals: [500, 1_000, 2_000], + }) + .toEqual( + expect.objectContaining({ + attemptExists: true, + attempt: expect.objectContaining({ + status: "blocked", + slug, + version, + filesCount: 0, + hasSkillInsertArgs: false, + hasFollowup: false, + trufflehogStatus: "blocked", + trufflehogRedactedFindingCount: 1, + clawscanStatus: "clean", + }), + skillExists: false, + versionExists: false, + }), + ); + + for (const url of uploadedFileUrls) { + await expect + .poll( + async () => { + const response = await request.get(url, { timeout: 2_000 }).catch(() => null); + return response?.ok() ?? false; + }, + { timeout: 30_000, intervals: [500, 1_000, 2_000] }, + ) + .toBe(false); + } - await expectCurrentVersion(page, version); await expectHealthyPublishPage(page, errors); }); diff --git a/e2e/local-auth/version-delete.pw.test.ts b/e2e/local-auth/version-delete.pw.test.ts index a2c8544e..b4962ba8 100644 --- a/e2e/local-auth/version-delete.pw.test.ts +++ b/e2e/local-auth/version-delete.pw.test.ts @@ -1,12 +1,14 @@ import { spawnSync } from "node:child_process"; import { readFileSync } from "node:fs"; -import { expect, type Locator, test } from "@playwright/test"; +import { expect, test } from "@playwright/test"; import { buildSkillDetailHref } from "../../src/lib/ownerRoute"; import { buildPluginDetailHref } from "../../src/lib/pluginRoutes"; import { expectNoFatalErrorUi, + recoverFromTransientErrorScreen, trackRuntimeErrors, waitForHydration, + withoutRecoverableReactHydrationErrors, } from "../helpers/runtimeErrors"; import { signInAsLocalPersona } from "./helpers"; @@ -189,8 +191,10 @@ function isExpectedVersionDeletionRuntimeError(error: string) { return [ "[CONVEX Q(packages:canDeleteVersions)]", "[CONVEX Q(packages:getActivityTrendForName)]", + "[CONVEX Q(packages:getPackageInspectorValidationSummaryPublic)]", "[CONVEX Q(packages:getManageContext)]", "[CONVEX Q(packages:listPackageInspectorWarningsForManager)]", + "[CONVEX Q(publishers:getByHandle)]", "[CONVEX Q(publishers:getMyProfileHandle)]", "[CONVEX Q(publishers:listMine)]", "[CONVEX Q(skills:getActivityTrendForSlug)]", @@ -224,6 +228,11 @@ function versionToggle(page: Parameters<typeof expectNoFatalErrorUi>[0], version .filter({ hasText: new RegExp(`^v${version.replaceAll(".", "\\.")}`) }); } +async function ensureVersionsTab(page: Parameters<typeof expectNoFatalErrorUi>[0]) { + await recoverFromTransientErrorScreen(page); + await page.getByRole("tab", { name: "Versions" }).click({ timeout: 30_000 }); +} + async function openDeleteDialog(page: Parameters<typeof expectNoFatalErrorUi>[0]) { const deleteButton = page.getByRole("button", { name: `Delete version ${OLDER_VERSION}` }); let lastError: unknown; @@ -239,31 +248,85 @@ async function openDeleteDialog(page: Parameters<typeof expectNoFatalErrorUi>[0] await page.keyboard.press("Escape").catch(() => {}); await page.reload({ waitUntil: "domcontentloaded" }); await waitForHydration(page); - await page.getByRole("tab", { name: "Versions" }).click({ timeout: 30_000 }); + await ensureVersionsTab(page); await page.waitForTimeout(1_000 * attempt); } } throw lastError; } -async function confirmDeleteDialog(dialog: Locator) { - const deleteButton = dialog.getByRole("button", { name: "Delete version" }); - await expect(deleteButton).toBeVisible({ timeout: 30_000 }); - await expect(deleteButton).toBeEnabled({ timeout: 30_000 }); - await deleteButton.click({ timeout: 30_000 }); +async function confirmDeleteDialog(page: Parameters<typeof expectNoFatalErrorUi>[0]) { + let lastError: unknown; + for (let attempt = 1; attempt <= 3; attempt += 1) { + let deleteButton: ReturnType<typeof page.getByRole> | null = null; + try { + await recoverFromTransientErrorScreen(page); + const dialog = page.getByRole("dialog"); + deleteButton = dialog.getByRole("button", { name: "Delete version" }); + await expect(deleteButton).toBeVisible({ timeout: 30_000 }); + await expect(deleteButton).toBeEnabled({ timeout: 30_000 }); + } catch (error) { + lastError = error; + if (attempt >= 3) throw error; + await page.keyboard.press("Escape").catch(() => {}); + await page.reload({ waitUntil: "domcontentloaded" }); + await waitForHydration(page); + await ensureVersionsTab(page); + await openDeleteDialog(page); + await page.waitForTimeout(1_000 * attempt); + } + if (!deleteButton) continue; + try { + await deleteButton.click({ timeout: 30_000 }); + return; + } catch (error) { + await recoverFromTransientErrorScreen(page).catch(() => {}); + await ensureVersionsTab(page).catch(() => {}); + if ((await versionToggle(page, OLDER_VERSION).count()) === 0) return; + throw error; + } + } + throw lastError; } async function expectVersionsList(page: Parameters<typeof expectNoFatalErrorUi>[0]) { - await expect(versionToggle(page, OLDER_VERSION)).toBeVisible(); - await expect(versionToggle(page, LATEST_VERSION)).toBeVisible(); - await expect(page.getByRole("button", { name: `Delete version ${OLDER_VERSION}` })).toBeVisible(); - await expect(page.getByRole("button", { name: `Delete version ${LATEST_VERSION}` })).toHaveCount( - 0, - ); - await expect(page.getByRole("button", { name: /restore/i })).toHaveCount(0); + let lastError: unknown; + for (let attempt = 1; attempt <= 4; attempt += 1) { + try { + await waitForHydration(page).catch(() => {}); + await recoverFromTransientErrorScreen(page); + await ensureVersionsTab(page); + const versionsPanel = page.getByRole("tabpanel", { name: "Versions" }); + const retryButton = versionsPanel.getByRole("button", { name: "Try again" }); + if (await retryButton.isVisible({ timeout: 500 }).catch(() => false)) { + await retryButton.click({ timeout: 5_000 }); + await waitForHydration(page).catch(() => {}); + } + + await expect(versionToggle(page, OLDER_VERSION)).toBeVisible({ timeout: 30_000 }); + await expect(versionToggle(page, LATEST_VERSION)).toBeVisible({ timeout: 30_000 }); + await expect( + page.getByRole("button", { name: `Delete version ${OLDER_VERSION}` }), + ).toBeVisible({ timeout: 30_000 }); + await expect( + page.getByRole("button", { name: `Delete version ${LATEST_VERSION}` }), + ).toHaveCount(0); + await expect(page.getByRole("button", { name: /restore/i })).toHaveCount(0); + return; + } catch (error) { + lastError = error; + if (attempt >= 4) throw error; + await recoverFromTransientErrorScreen(page).catch(() => {}); + await page.reload({ waitUntil: "domcontentloaded" }).catch(() => {}); + await waitForHydration(page).catch(() => {}); + await page.waitForTimeout(1_000 * attempt); + } + } + throw lastError; } async function expectPublicVersionsList(page: Parameters<typeof expectNoFatalErrorUi>[0]) { + await ensureVersionsTab(page); await expect(versionToggle(page, OLDER_VERSION)).toHaveCount(0); await expect(versionToggle(page, LATEST_VERSION)).toBeVisible(); await expect(page.getByRole("button", { name: /delete version/i })).toHaveCount(0); @@ -348,8 +411,8 @@ test("owners can permanently delete individual non-latest skill and plugin versi await page.goto(skillDetailHref, { waitUntil: "domcontentloaded" }); await waitForHydration(page); + await recoverFromTransientErrorScreen(page); await expect(page.locator(".skill-page-title")).toHaveText(skillDisplayName, { timeout: 30_000 }); - await page.getByRole("tab", { name: "Versions" }).click(); await expectVersionsList(page); await page.screenshot({ path: testInfo.outputPath("skill-version-delete-before.png"), @@ -361,8 +424,9 @@ test("owners can permanently delete individual non-latest skill and plugin versi path: testInfo.outputPath("skill-version-delete-confirmation.png"), fullPage: true, }); - await confirmDeleteDialog(skillDialog); + await confirmDeleteDialog(page); await expect(skillDialog).toHaveCount(0); + await ensureVersionsTab(page); await expect(versionToggle(page, OLDER_VERSION)).toHaveCount(0); await expect(versionToggle(page, LATEST_VERSION)).toBeVisible(); await expect(page.getByRole("button", { name: /restore/i })).toHaveCount(0); @@ -375,10 +439,10 @@ test("owners can permanently delete individual non-latest skill and plugin versi waitUntil: "domcontentloaded", }); await waitForHydration(page); + await recoverFromTransientErrorScreen(page); await expect(page.locator(".skill-page-title")).toHaveText(packageDisplayName, { timeout: 30_000, }); - await page.getByRole("tab", { name: "Versions" }).click(); await expectVersionsList(page); await page.screenshot({ path: testInfo.outputPath("plugin-version-delete-before.png"), @@ -390,8 +454,9 @@ test("owners can permanently delete individual non-latest skill and plugin versi path: testInfo.outputPath("plugin-version-delete-confirmation.png"), fullPage: true, }); - await confirmDeleteDialog(packageDialog); + await confirmDeleteDialog(page); await expect(packageDialog).toHaveCount(0); + await ensureVersionsTab(page); await expect(versionToggle(page, OLDER_VERSION)).toHaveCount(0); await expect(versionToggle(page, LATEST_VERSION)).toBeVisible(); await expect(page.getByRole("button", { name: /restore/i })).toHaveCount(0); @@ -460,21 +525,23 @@ test("owners can permanently delete individual non-latest skill and plugin versi waitUntil: "domcontentloaded", }); await waitForHydration(publicPage); + await recoverFromTransientErrorScreen(publicPage); await expect(publicPage.locator(".skill-page-title")).toHaveText(skillDisplayName); - await publicPage.getByRole("tab", { name: "Versions" }).click(); await expectPublicVersionsList(publicPage); await publicPage.goto(pluginDetailHref, { waitUntil: "domcontentloaded", }); await waitForHydration(publicPage); + await recoverFromTransientErrorScreen(publicPage); await expect(publicPage.locator(".skill-page-title")).toHaveText(packageDisplayName); - await publicPage.getByRole("tab", { name: "Versions" }).click(); await expectPublicVersionsList(publicPage); await expectNoFatalErrorUi(publicPage); - expect(publicErrors.filter((error) => !isExpectedVersionDeletionRuntimeError(error))).toEqual( - [], - ); + expect( + withoutRecoverableReactHydrationErrors(publicErrors).filter( + (error) => !isExpectedVersionDeletionRuntimeError(error), + ), + ).toEqual([]); } finally { await publicContext.close(); } @@ -500,6 +567,11 @@ test("owners can permanently delete individual non-latest skill and plugin versi await page.reload({ waitUntil: "domcontentloaded" }); await waitForHydration(page); + await recoverFromTransientErrorScreen(page); await expectNoFatalErrorUi(page); - expect(errors.filter((error) => !isExpectedVersionDeletionRuntimeError(error))).toEqual([]); + expect( + withoutRecoverableReactHydrationErrors(errors).filter( + (error) => !isExpectedVersionDeletionRuntimeError(error), + ), + ).toEqual([]); }); diff --git a/packages/clawhub/src/cli/commands/packages.test.ts b/packages/clawhub/src/cli/commands/packages.test.ts index 37e7e112..2f3a9bb0 100644 --- a/packages/clawhub/src/cli/commands/packages.test.ts +++ b/packages/clawhub/src/cli/commands/packages.test.ts @@ -1283,6 +1283,104 @@ describe("package commands", () => { } }); + it("reports pending security checks for staged package publishes", async () => { + const workdir = await makeTmpWorkdir(); + try { + const folder = join(workdir, "pending-plugin"); + await mkdir(join(folder, "dist"), { recursive: true }); + await writeFile( + join(folder, "package.json"), + makeCodePluginPackageJson({ + name: "@scope/pending-plugin", + displayName: "Pending Plugin", + version: "1.0.0", + files: ["dist", "openclaw.plugin.json"], + }), + "utf8", + ); + await writeFile( + join(folder, "openclaw.plugin.json"), + JSON.stringify({ id: "pending.plugin" }), + "utf8", + ); + await writeFile(join(folder, "dist", "index.js"), "export const demo = true;\n", "utf8"); + + httpMocks.apiRequestForm.mockResolvedValueOnce({ + ok: true, + packageId: "pkg_1", + releaseId: "rel_1", + publicationStatus: "pending", + attemptId: "attempt_1", + }); + + await cmdPublishPackage(makeOpts(workdir), "pending-plugin", { + owner: "@openclaw", + sourceRepo: "openclaw/pending-plugin", + sourceCommit: "abc123", + }); + + expect(uiMocks.spinner.succeed).toHaveBeenCalledWith( + "OK. Uploaded @scope/pending-plugin@1.0.0; security checks are pending before it becomes public (rel_1)", + ); + expect(mockWrite).not.toHaveBeenCalled(); + } finally { + await rm(workdir, { recursive: true, force: true }); + } + }); + + it("includes pending package publish metadata in json output", async () => { + const workdir = await makeTmpWorkdir(); + try { + const folder = join(workdir, "json-pending-plugin"); + await mkdir(join(folder, "dist"), { recursive: true }); + await writeFile( + join(folder, "package.json"), + makeCodePluginPackageJson({ + name: "@scope/json-pending-plugin", + displayName: "JSON Pending Plugin", + version: "1.0.0", + files: ["dist", "openclaw.plugin.json"], + }), + "utf8", + ); + await writeFile( + join(folder, "openclaw.plugin.json"), + JSON.stringify({ id: "json.pending.plugin" }), + "utf8", + ); + await writeFile(join(folder, "dist", "index.js"), "export const demo = true;\n", "utf8"); + + httpMocks.apiRequestForm.mockResolvedValueOnce({ + ok: true, + packageId: "pkg_1", + releaseId: "rel_1", + publicationStatus: "pending", + attemptId: "attempt_1", + }); + + await cmdPublishPackage(makeOpts(workdir), "json-pending-plugin", { + owner: "@openclaw", + sourceRepo: "openclaw/json-pending-plugin", + sourceCommit: "abc123", + json: true, + }); + + expect(uiMocks.spinner.succeed).not.toHaveBeenCalled(); + expect(mockWrite).toHaveBeenCalledTimes(1); + const output = JSON.parse(String(mockWrite.mock.calls[0]?.[0] ?? "")); + expect(output).toEqual( + expect.objectContaining({ + name: "@scope/json-pending-plugin", + releaseId: "rel_1", + publicationStatus: "pending", + attemptId: "attempt_1", + }), + ); + } finally { + await rm(workdir, { recursive: true, force: true }); + } + }); + it("sends explicit empty catalog metadata to clear existing package values", async () => { const workdir = await makeTmpWorkdir(); try { diff --git a/packages/clawhub/src/cli/commands/packages.ts b/packages/clawhub/src/cli/commands/packages.ts index 0cccdd73..d2e8cd40 100644 --- a/packages/clawhub/src/cli/commands/packages.ts +++ b/packages/clawhub/src/cli/commands/packages.ts @@ -975,12 +975,15 @@ export async function cmdPublishPackage( ApiV1PackagePublishResponseSchema, ); + const isPendingPublication = result.publicationStatus === "pending"; if (options.json) { process.stdout.write( `${JSON.stringify( { ...plan.output, releaseId: result.releaseId, + publicationStatus: result.publicationStatus, + attemptId: result.attemptId, inspectorFindings: result.inspectorFindings, }, null, @@ -989,7 +992,9 @@ export async function cmdPublishPackage( ); } else { spinner?.succeed( - `OK. Published ${plan.payload.name}@${plan.payload.version} (${result.releaseId})`, + isPendingPublication + ? `OK. Uploaded ${plan.payload.name}@${plan.payload.version}; security checks are pending before it becomes public (${result.releaseId})` + : `OK. Published ${plan.payload.name}@${plan.payload.version} (${result.releaseId})`, ); printPackageInspectorFindings(result); } diff --git a/packages/clawhub/src/cli/commands/publish.test.ts b/packages/clawhub/src/cli/commands/publish.test.ts index 9e0b6058..75f5a2f6 100644 --- a/packages/clawhub/src/cli/commands/publish.test.ts +++ b/packages/clawhub/src/cli/commands/publish.test.ts @@ -132,6 +132,41 @@ describe("cmdPublish", () => { } }); + it("reports pending security checks for staged publish responses", async () => { + const workdir = await makeTmpWorkdir(); + try { + const folder = join(workdir, "pending-skill"); + await mkdir(folder, { recursive: true }); + await writeFile(join(folder, "SKILL.md"), "# Skill\n", "utf8"); + httpMocks.apiRequest.mockRejectedValueOnce( + new Error("Skill not found or unavailable to this account."), + ); + httpMocks.apiRequestForm.mockResolvedValueOnce({ + ok: true, + skillId: "skill_1", + versionId: "ver_pending", + publicationStatus: "pending", + attemptId: "attempt_1", + }); + + const result = await cmdPublish(makeOpts(workdir), "pending-skill", {}); + + expect(result).toMatchObject({ + status: "pending-publication", + slug: "pending-skill", + version: "1.0.0", + versionId: "ver_pending", + publicationStatus: "pending", + attemptId: "attempt_1", + }); + expect(uiMocks.spinner.succeed).toHaveBeenCalledWith( + "OK. Uploaded pending-skill@1.0.0; security checks are pending before it becomes public (ver_pending)", + ); + } finally { + await rm(workdir, { recursive: true, force: true }); + } + }); + it("defaults a changed skill to the next patch version", async () => { const workdir = await makeTmpWorkdir(); try { diff --git a/packages/clawhub/src/cli/commands/publish.ts b/packages/clawhub/src/cli/commands/publish.ts index e484c777..e9b6426e 100644 --- a/packages/clawhub/src/cli/commands/publish.ts +++ b/packages/clawhub/src/cli/commands/publish.ts @@ -18,7 +18,7 @@ import { normalizeGitHubRepo } from "./github.js"; type SkillPublishResult = { ok: true; - status: "unchanged" | "would-publish" | "published"; + status: "unchanged" | "would-publish" | "published" | "pending-publication"; slug: string; displayName: string; folder: string; @@ -27,6 +27,8 @@ type SkillPublishResult = { fileCount: number; fingerprint: string; versionId?: string; + publicationStatus?: "pending" | "published"; + attemptId?: string; }; export async function cmdPublish( @@ -198,8 +200,9 @@ export async function cmdPublish( ApiV1PublishResponseSchema, ); + const isPendingPublication = result.publicationStatus === "pending"; const publishResult = buildPublishResult({ - status: "published", + status: isPendingPublication ? "pending-publication" : "published", slug, displayName, folder, @@ -208,8 +211,14 @@ export async function cmdPublish( fileCount: filesOnDisk.length, fingerprint: hashed.fingerprint, versionId: result.versionId, + publicationStatus: result.publicationStatus, + attemptId: result.attemptId, }); - spinner?.succeed(`OK. Published ${slug}@${version} (${result.versionId})`); + spinner?.succeed( + isPendingPublication + ? `OK. Uploaded ${slug}@${version}; security checks are pending before it becomes public (${result.versionId})` + : `OK. Published ${slug}@${version} (${result.versionId})`, + ); writePublishJsonIfRequested(options.json, publishResult); return publishResult; } catch (error) { diff --git a/packages/clawhub/src/schema/packages.ts b/packages/clawhub/src/schema/packages.ts index d431aef1..0e6a662f 100644 --- a/packages/clawhub/src/schema/packages.ts +++ b/packages/clawhub/src/schema/packages.ts @@ -861,6 +861,8 @@ export const ApiV1PackagePublishResponseSchema = type({ ok: "true", packageId: "string", releaseId: "string", + publicationStatus: '"pending"|"published"?', + attemptId: "string?", inspectorFindings: type({ findingKind: '"warning"|"error"', code: "string", diff --git a/packages/clawhub/src/schema/schemas.test.ts b/packages/clawhub/src/schema/schemas.test.ts index f9db5432..131b36c2 100644 --- a/packages/clawhub/src/schema/schemas.test.ts +++ b/packages/clawhub/src/schema/schemas.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest"; import { parseArk } from "./ark"; +import { ApiV1PackagePublishResponseSchema } from "./packages"; import { ApiV1SearchResponseSchema, ApiV1SkillRescanResponseSchema, @@ -58,6 +59,24 @@ describe("packages/clawhub skill metadata schema", () => { expect(parsed.results[0]?.owner?.displayName).toBe("OpenClaw"); }); + it("parses pending package publish responses with legacy IDs", () => { + const parsed = parseArk( + ApiV1PackagePublishResponseSchema, + { + ok: true, + packageId: "packages:demo", + releaseId: "packageReleases:demo", + publicationStatus: "pending", + attemptId: "publishAttempts:demo", + }, + "Package publish response", + ); + + expect(parsed.releaseId).toBe("packageReleases:demo"); + expect(parsed.publicationStatus).toBe("pending"); + expect(parsed.attemptId).toBe("publishAttempts:demo"); + }); + it("parses flattened skill verification envelopes", () => { const parsed = parseArk( ApiV1SkillVerifyResponseSchema, diff --git a/packages/clawhub/src/schema/schemas.ts b/packages/clawhub/src/schema/schemas.ts index a4cbb012..99ecacbd 100644 --- a/packages/clawhub/src/schema/schemas.ts +++ b/packages/clawhub/src/schema/schemas.ts @@ -108,6 +108,11 @@ export const ApiCliPublishResponseSchema = type({ ok: "true", skillId: "string", versionId: "string", + status: '"pending"|"published"?', + slug: "string?", + version: "string?", + publicationStatus: '"pending"|"published"?', + attemptId: "string?", }); export const CliSkillDeleteRequestSchema = type({ @@ -885,6 +890,11 @@ export const ApiV1PublishResponseSchema = type({ ok: "true", skillId: "string", versionId: "string", + status: '"pending"|"published"?', + slug: "string?", + version: "string?", + publicationStatus: '"pending"|"published"?', + attemptId: "string?", }); export const ApiV1DeleteResponseSchema = type({ diff --git a/packages/schema/dist/catalogFeed.d.ts b/packages/schema/dist/catalogFeed.d.ts index 9c8e8d75..d270b615 100644 --- a/packages/schema/dist/catalogFeed.d.ts +++ b/packages/schema/dist/catalogFeed.d.ts @@ -1,7 +1,7 @@ import { type inferred } from "arktype"; -export declare const CatalogFeedStateSchema: import("arktype/internal/variants/string.ts").StringType<"available" | "recommended" | "disabled" | "blocked" | "deprecated", {}>; +export declare const CatalogFeedStateSchema: import("arktype/internal/variants/string.ts").StringType<"available" | "blocked" | "deprecated" | "disabled" | "recommended", {}>; export type CatalogFeedState = (typeof CatalogFeedStateSchema)[inferred]; -export declare const CatalogFeedPublisherTrustSchema: import("arktype/internal/variants/string.ts").StringType<"official" | "community", {}>; +export declare const CatalogFeedPublisherTrustSchema: import("arktype/internal/variants/string.ts").StringType<"community" | "official", {}>; export type CatalogFeedPublisherTrust = (typeof CatalogFeedPublisherTrustSchema)[inferred]; export declare const CatalogFeedGitHubSourceSchema: import("arktype/internal/variants/object.ts").ObjectType<{ repo: string; @@ -24,14 +24,13 @@ export declare const CatalogFeedInstallCandidateSchema: import("arktype/internal }, {}>; export type CatalogFeedInstallCandidate = (typeof CatalogFeedInstallCandidateSchema)[inferred]; export declare const CatalogFeedPluginEntrySchema: import("arktype/internal/variants/object.ts").ObjectType<{ - type: "plugin"; id: string; title: string; version: string; - state: "available" | "recommended" | "disabled" | "blocked" | "deprecated"; + state: "available" | "blocked" | "deprecated" | "disabled" | "recommended"; publisher: { id: string; - trust: "official" | "community"; + trust: "community" | "official"; }; install: { candidates: { @@ -47,17 +46,17 @@ export declare const CatalogFeedPluginEntrySchema: import("arktype/internal/vari } | undefined; }[]; }; + type: "plugin"; }, {}>; export type CatalogFeedPluginEntry = (typeof CatalogFeedPluginEntrySchema)[inferred]; export declare const CatalogFeedSkillEntrySchema: import("arktype/internal/variants/object.ts").ObjectType<{ - type: "skill"; id: string; title: string; version: string; - state: "available" | "recommended" | "disabled" | "blocked" | "deprecated"; + state: "available" | "blocked" | "deprecated" | "disabled" | "recommended"; publisher: { id: string; - trust: "official" | "community"; + trust: "community" | "official"; }; install: { candidates: { @@ -73,41 +72,41 @@ export declare const CatalogFeedSkillEntrySchema: import("arktype/internal/varia } | undefined; }[]; }; + type: "skill"; }, {}>; export type CatalogFeedSkillEntry = (typeof CatalogFeedSkillEntrySchema)[inferred]; export declare const CatalogFeedEntrySchema: import("arktype/internal/variants/object.ts").ObjectType<{ + id: string; + title: string; + version: string; + state: "available" | "blocked" | "deprecated" | "disabled" | "recommended"; + publisher: { + id: string; + trust: "community" | "official"; + }; + install: { + candidates: { + sourceRef: string; + package: string; + version: string; + integrity: string; + github?: { + repo: string; + path: string; + commit: string; + contentHash: string; + } | undefined; + }[]; + }; type: "plugin"; - id: string; - title: string; - version: string; - state: "available" | "recommended" | "disabled" | "blocked" | "deprecated"; - publisher: { - id: string; - trust: "official" | "community"; - }; - install: { - candidates: { - sourceRef: string; - package: string; - version: string; - integrity: string; - github?: { - repo: string; - path: string; - commit: string; - contentHash: string; - } | undefined; - }[]; - }; } | { - type: "skill"; id: string; title: string; version: string; - state: "available" | "recommended" | "disabled" | "blocked" | "deprecated"; + state: "available" | "blocked" | "deprecated" | "disabled" | "recommended"; publisher: { id: string; - trust: "official" | "community"; + trust: "community" | "official"; }; install: { candidates: { @@ -123,6 +122,7 @@ export declare const CatalogFeedEntrySchema: import("arktype/internal/variants/o } | undefined; }[]; }; + type: "skill"; }, {}>; export type CatalogFeedEntry = (typeof CatalogFeedEntrySchema)[inferred]; export declare const CatalogFeedSchema: import("arktype/internal/variants/object.ts").ObjectType<{ @@ -131,56 +131,56 @@ export declare const CatalogFeedSchema: import("arktype/internal/variants/object generatedAt: string; sequence: number; expiresAt: string; - entries: ({ - type: "plugin"; - id: string; - title: string; - version: string; - state: "available" | "recommended" | "disabled" | "blocked" | "deprecated"; - publisher: { - id: string; - trust: "official" | "community"; - }; - install: { - candidates: { - sourceRef: string; - package: string; - version: string; - integrity: string; - github?: { - repo: string; - path: string; - commit: string; - contentHash: string; - } | undefined; - }[]; - }; - } | { - type: "skill"; - id: string; - title: string; - version: string; - state: "available" | "recommended" | "disabled" | "blocked" | "deprecated"; - publisher: { - id: string; - trust: "official" | "community"; - }; - install: { - candidates: { - sourceRef: string; - package: string; - version: string; - integrity: string; - github?: { - repo: string; - path: string; - commit: string; - contentHash: string; - } | undefined; - }[]; - }; - })[]; description?: string | undefined; + entries: ({ + id: string; + title: string; + version: string; + state: "available" | "blocked" | "deprecated" | "disabled" | "recommended"; + publisher: { + id: string; + trust: "community" | "official"; + }; + install: { + candidates: { + sourceRef: string; + package: string; + version: string; + integrity: string; + github?: { + repo: string; + path: string; + commit: string; + contentHash: string; + } | undefined; + }[]; + }; + type: "plugin"; + } | { + id: string; + title: string; + version: string; + state: "available" | "blocked" | "deprecated" | "disabled" | "recommended"; + publisher: { + id: string; + trust: "community" | "official"; + }; + install: { + candidates: { + sourceRef: string; + package: string; + version: string; + integrity: string; + github?: { + repo: string; + path: string; + commit: string; + contentHash: string; + } | undefined; + }[]; + }; + type: "skill"; + })[]; }, {}>; export type CatalogFeed = (typeof CatalogFeedSchema)[inferred]; /** diff --git a/packages/schema/dist/catalogMetadata.d.ts b/packages/schema/dist/catalogMetadata.d.ts index 5f8aa6f8..ca5c77c4 100644 --- a/packages/schema/dist/catalogMetadata.d.ts +++ b/packages/schema/dist/catalogMetadata.d.ts @@ -151,8 +151,8 @@ export declare const SKILL_CATEGORY_DEFINITIONS: readonly [{ }]; export type PluginCategorySlug = (typeof PLUGIN_CATEGORY_DEFINITIONS)[number]["slug"]; export type SkillCategorySlug = (typeof SKILL_CATEGORY_DEFINITIONS)[number]["slug"]; -export declare const PLUGIN_CATEGORY_SLUGS: ("other" | "channels" | "models" | "memory" | "context" | "voice" | "media" | "web" | "tools" | "runtime" | "gateway" | "security")[]; -export declare const SKILL_CATEGORY_SLUGS: ("other" | "security" | "integrations" | "automation" | "research" | "development" | "productivity" | "communication" | "creative" | "knowledge" | "agents" | "operations" | "finance" | "lifestyle")[]; +export declare const PLUGIN_CATEGORY_SLUGS: ("channels" | "context" | "gateway" | "media" | "memory" | "models" | "other" | "runtime" | "security" | "tools" | "voice" | "web")[]; +export declare const SKILL_CATEGORY_SLUGS: ("agents" | "automation" | "communication" | "creative" | "development" | "finance" | "integrations" | "knowledge" | "lifestyle" | "operations" | "other" | "productivity" | "research" | "security")[]; export declare function isPluginCategorySlug(value: string | null | undefined): value is PluginCategorySlug; export declare function isSkillCategorySlug(value: string | null | undefined): value is SkillCategorySlug; export declare function normalizePluginCategories(values: readonly string[] | null | undefined): PluginCategorySlug[]; diff --git a/packages/schema/dist/packages.d.ts b/packages/schema/dist/packages.d.ts index 25399964..1f1a2753 100644 --- a/packages/schema/dist/packages.d.ts +++ b/packages/schema/dist/packages.d.ts @@ -8,11 +8,11 @@ export declare function getPackageScopeOwnerMismatch(name: string, ownerHandle: suggestedName: string; message: string; } | null; -export declare const PackageFamilySchema: import("arktype/internal/variants/string.ts").StringType<"skill" | "code-plugin" | "bundle-plugin", {}>; +export declare const PackageFamilySchema: import("arktype/internal/variants/string.ts").StringType<"bundle-plugin" | "code-plugin" | "skill", {}>; export type PackageFamily = (typeof PackageFamilySchema)[inferred]; -export declare const PackageChannelSchema: import("arktype/internal/variants/string.ts").StringType<"official" | "community" | "private", {}>; +export declare const PackageChannelSchema: import("arktype/internal/variants/string.ts").StringType<"community" | "official" | "private", {}>; export type PackageChannel = (typeof PackageChannelSchema)[inferred]; -export declare const PackageVerificationTierSchema: import("arktype/internal/variants/string.ts").StringType<"structural" | "source-linked" | "provenance-verified" | "rebuild-verified", {}>; +export declare const PackageVerificationTierSchema: import("arktype/internal/variants/string.ts").StringType<"provenance-verified" | "rebuild-verified" | "source-linked" | "structural", {}>; export type PackageVerificationTier = (typeof PackageVerificationTierSchema)[inferred]; export declare const PackageVerificationScopeSchema: import("arktype/internal/variants/string.ts").StringType<"artifact-only" | "dependency-graph-aware", {}>; export type PackageVerificationScope = (typeof PackageVerificationScopeSchema)[inferred]; @@ -25,23 +25,6 @@ export declare const PackageCompatibilitySchema: import("arktype/internal/varian export type PackageCompatibility = (typeof PackageCompatibilitySchema)[inferred]; export declare const PluginManifestSummarySchema: import("arktype/internal/variants/object.ts").ObjectType<{ schemaVersion: number; - configFields: { - name: string; - required: boolean; - sensitive: boolean; - description?: string | undefined; - }[]; - mcpServers: { - name: string; - }[]; - bundledSkills: { - name: string; - rootPath: string; - skillMdPath: string; - sha256: string; - size: number; - description?: string | undefined; - }[]; compatibility?: { pluginApiRange?: string | undefined; builtWithOpenClawVersion?: string | undefined; @@ -54,10 +37,27 @@ export declare const PluginManifestSummarySchema: import("arktype/internal/varia version?: string | undefined; family?: string | undefined; } | undefined; + configFields: { + name: string; + description?: string | undefined; + required: boolean; + sensitive: boolean; + }[]; + mcpServers: { + name: string; + }[]; + bundledSkills: { + name: string; + description?: string | undefined; + rootPath: string; + skillMdPath: string; + sha256: string; + size: number; + }[]; }, {}>; export type PluginManifestSummary = (typeof PluginManifestSummarySchema)[inferred]; export declare const PackageVerificationSummarySchema: import("arktype/internal/variants/object.ts").ObjectType<{ - tier: "structural" | "source-linked" | "provenance-verified" | "rebuild-verified"; + tier: "provenance-verified" | "rebuild-verified" | "source-linked" | "structural"; scope: "artifact-only" | "dependency-graph-aware"; summary?: string | undefined; sourceRepo?: string | undefined; @@ -66,7 +66,7 @@ export declare const PackageVerificationSummarySchema: import("arktype/internal/ sourcePath?: string | undefined; hasProvenance?: boolean | undefined; trustedOpenClawPlugin?: boolean | undefined; - scanStatus?: "clean" | "suspicious" | "malicious" | "pending" | "not-run" | undefined; + scanStatus?: "clean" | "malicious" | "not-run" | "pending" | "suspicious" | undefined; }, {}>; export type PackageVerificationSummary = (typeof PackageVerificationSummarySchema)[inferred]; export declare const PackageStatsSchema: import("arktype/internal/variants/object.ts").ObjectType<{ @@ -78,23 +78,23 @@ export declare const PackageStatsSchema: import("arktype/internal/variants/objec export type PackageStats = (typeof PackageStatsSchema)[inferred]; export declare const PackageArtifactKindSchema: import("arktype/internal/variants/string.ts").StringType<"legacy-zip" | "npm-pack", {}>; export type PackageArtifactKind = (typeof PackageArtifactKindSchema)[inferred]; -export declare const PackageReleaseModerationStateSchema: import("arktype/internal/variants/string.ts").StringType<"approved" | "revoked" | "quarantined", {}>; +export declare const PackageReleaseModerationStateSchema: import("arktype/internal/variants/string.ts").StringType<"approved" | "quarantined" | "revoked", {}>; export type PackageReleaseModerationState = (typeof PackageReleaseModerationStateSchema)[inferred]; -export declare const PackageReportStatusSchema: import("arktype/internal/variants/string.ts").StringType<"open" | "confirmed" | "dismissed", {}>; +export declare const PackageReportStatusSchema: import("arktype/internal/variants/string.ts").StringType<"confirmed" | "dismissed" | "open", {}>; export type PackageReportStatus = (typeof PackageReportStatusSchema)[inferred]; -export declare const PackageReportFinalActionSchema: import("arktype/internal/variants/string.ts").StringType<"revoke" | "none" | "quarantine", {}>; +export declare const PackageReportFinalActionSchema: import("arktype/internal/variants/string.ts").StringType<"none" | "quarantine" | "revoke", {}>; export type PackageReportFinalAction = (typeof PackageReportFinalActionSchema)[inferred]; -export declare const PackageReportListStatusSchema: import("arktype/internal/variants/string.ts").StringType<"open" | "confirmed" | "dismissed" | "all", {}>; +export declare const PackageReportListStatusSchema: import("arktype/internal/variants/string.ts").StringType<"all" | "confirmed" | "dismissed" | "open", {}>; export type PackageReportListStatus = (typeof PackageReportListStatusSchema)[inferred]; -export declare const PackageAppealStatusSchema: import("arktype/internal/variants/string.ts").StringType<"open" | "accepted" | "rejected", {}>; +export declare const PackageAppealStatusSchema: import("arktype/internal/variants/string.ts").StringType<"accepted" | "open" | "rejected", {}>; export type PackageAppealStatus = (typeof PackageAppealStatusSchema)[inferred]; -export declare const PackageAppealFinalActionSchema: import("arktype/internal/variants/string.ts").StringType<"none" | "approve", {}>; +export declare const PackageAppealFinalActionSchema: import("arktype/internal/variants/string.ts").StringType<"approve" | "none", {}>; export type PackageAppealFinalAction = (typeof PackageAppealFinalActionSchema)[inferred]; -export declare const PackageAppealListStatusSchema: import("arktype/internal/variants/string.ts").StringType<"open" | "all" | "accepted" | "rejected", {}>; +export declare const PackageAppealListStatusSchema: import("arktype/internal/variants/string.ts").StringType<"accepted" | "all" | "open" | "rejected", {}>; export type PackageAppealListStatus = (typeof PackageAppealListStatusSchema)[inferred]; -export declare const PackageOfficialMigrationPhaseSchema: import("arktype/internal/variants/string.ts").StringType<"blocked" | "published" | "planned" | "clawpack-ready" | "legacy-zip-only" | "metadata-ready" | "ready-for-openclaw", {}>; +export declare const PackageOfficialMigrationPhaseSchema: import("arktype/internal/variants/string.ts").StringType<"blocked" | "clawpack-ready" | "legacy-zip-only" | "metadata-ready" | "planned" | "published" | "ready-for-openclaw", {}>; export type PackageOfficialMigrationPhase = (typeof PackageOfficialMigrationPhaseSchema)[inferred]; -export declare const PackageOfficialMigrationListPhaseSchema: import("arktype/internal/variants/string.ts").StringType<"blocked" | "all" | "published" | "planned" | "clawpack-ready" | "legacy-zip-only" | "metadata-ready" | "ready-for-openclaw", {}>; +export declare const PackageOfficialMigrationListPhaseSchema: import("arktype/internal/variants/string.ts").StringType<"all" | "blocked" | "clawpack-ready" | "legacy-zip-only" | "metadata-ready" | "planned" | "published" | "ready-for-openclaw", {}>; export type PackageOfficialMigrationListPhase = (typeof PackageOfficialMigrationListPhaseSchema)[inferred]; export declare const PackageArtifactSummarySchema: import("arktype/internal/variants/object.ts").ObjectType<{ kind: "legacy-zip" | "npm-pack"; @@ -128,22 +128,22 @@ export declare const PackagePublishArtifactSchema: import("arktype/internal/vari export type PackagePublishArtifact = (typeof PackagePublishArtifactSchema)[inferred]; export declare const PackageVtAnalysisSchema: import("arktype/internal/variants/object.ts").ObjectType<{ status: string; - checkedAt: number; verdict?: string | undefined; analysis?: string | undefined; source?: string | undefined; + checkedAt: number; }, {}>; export type PackageVtAnalysis = (typeof PackageVtAnalysisSchema)[inferred]; export declare const PackageSkillSpectorIssueSchema: import("arktype/internal/variants/object.ts").ObjectType<{ issueId: string; - severity: string; - explanation: string; category?: string | undefined; pattern?: string | undefined; + severity: string; confidence?: number | undefined; file?: string | undefined; startLine?: number | undefined; endLine?: number | undefined; + explanation: string; remediation?: string | undefined; finding?: string | undefined; codeSnippet?: string | undefined; @@ -151,28 +151,28 @@ export declare const PackageSkillSpectorIssueSchema: import("arktype/internal/va export type PackageSkillSpectorIssue = (typeof PackageSkillSpectorIssueSchema)[inferred]; export declare const PackageSkillSpectorAnalysisSchema: import("arktype/internal/variants/object.ts").ObjectType<{ status: string; + score?: number | undefined; + severity?: string | undefined; + recommendation?: string | undefined; issueCount: number; issues: { issueId: string; - severity: string; - explanation: string; category?: string | undefined; pattern?: string | undefined; + severity: string; confidence?: number | undefined; file?: string | undefined; startLine?: number | undefined; endLine?: number | undefined; + explanation: string; remediation?: string | undefined; finding?: string | undefined; codeSnippet?: string | undefined; }[]; - checkedAt: number; - score?: number | undefined; - severity?: string | undefined; - recommendation?: string | undefined; scannerVersion?: string | undefined; summary?: string | undefined; error?: string | undefined; + checkedAt: number; }, {}>; export type PackageSkillSpectorAnalysis = (typeof PackageSkillSpectorAnalysisSchema)[inferred]; export declare const PackageLlmAnalysisDimensionSchema: import("arktype/internal/variants/object.ts").ObjectType<{ @@ -184,7 +184,6 @@ export declare const PackageLlmAnalysisDimensionSchema: import("arktype/internal export type PackageLlmAnalysisDimension = (typeof PackageLlmAnalysisDimensionSchema)[inferred]; export declare const PackageLlmAnalysisSchema: import("arktype/internal/variants/object.ts").ObjectType<{ status: string; - checkedAt: number; verdict?: string | undefined; confidence?: string | undefined; summary?: string | undefined; @@ -199,6 +198,7 @@ export declare const PackageLlmAnalysisSchema: import("arktype/internal/variants agenticRiskFindings?: unknown[] | undefined; riskSummary?: unknown; model?: string | undefined; + checkedAt: number; }, {}>; export type PackageLlmAnalysis = (typeof PackageLlmAnalysisSchema)[inferred]; export declare const PackageStaticFindingSchema: import("arktype/internal/variants/object.ts").ObjectType<{ @@ -260,13 +260,13 @@ export declare function isPackageMultipartUploadTooLarge(input: PackageMultipart export declare function getPackageMultipartSizeError(): string; export declare const PackagePublishMetadataSchema: import("arktype/internal/variants/object.ts").ObjectType<{ name: string; - family: "skill" | "code-plugin" | "bundle-plugin"; - version: string; - changelog: string; displayName?: string | undefined; ownerHandle?: string | undefined; + family: "bundle-plugin" | "code-plugin" | "skill"; + version: string; + changelog: string; manualOverrideReason?: string | undefined; - channel?: "official" | "community" | "private" | undefined; + channel?: "community" | "official" | "private" | undefined; tags?: string[] | undefined; categories?: string[] | undefined; topics?: string[] | undefined; @@ -287,33 +287,14 @@ export declare const PackagePublishMetadataSchema: import("arktype/internal/vari }, {}>; export type PackagePublishMetadata = (typeof PackagePublishMetadataSchema)[inferred]; export declare const ServerPackagePublishRequestSchema: import("arktype/internal/variants/object.ts").ObjectType<{ - files: { - path: string; - size: number; - storageId: string; - sha256: string; - contentType?: string | undefined; - }[]; name: string; - family: "skill" | "code-plugin" | "bundle-plugin"; - version: string; - changelog: string; - artifact?: { - kind: "npm-pack"; - storageId: string; - sha256: string; - size: number; - format: "tgz"; - npmIntegrity: string; - npmShasum: string; - npmTarballName: string; - npmUnpackedSize: number; - npmFileCount: number; - } | undefined; displayName?: string | undefined; ownerHandle?: string | undefined; + family: "bundle-plugin" | "code-plugin" | "skill"; + version: string; + changelog: string; manualOverrideReason?: string | undefined; - channel?: "official" | "community" | "private" | undefined; + channel?: "community" | "official" | "private" | undefined; tags?: string[] | undefined; categories?: string[] | undefined; topics?: string[] | undefined; @@ -331,24 +312,43 @@ export declare const ServerPackagePublishRequestSchema: import("arktype/internal format?: string | undefined; hostTargets?: string[] | undefined; } | undefined; + artifact?: { + kind: "npm-pack"; + storageId: string; + sha256: string; + size: number; + format: "tgz"; + npmIntegrity: string; + npmShasum: string; + npmTarballName: string; + npmUnpackedSize: number; + npmFileCount: number; + } | undefined; + files: { + path: string; + size: number; + storageId: string; + sha256: string; + contentType?: string | undefined; + }[]; }, {}>; export type ServerPackagePublishRequest = (typeof ServerPackagePublishRequestSchema)[inferred]; export declare const PackageListItemSchema: import("arktype/internal/variants/object.ts").ObjectType<{ name: string; displayName: string; - family: "skill" | "code-plugin" | "bundle-plugin"; - channel: "official" | "community" | "private"; - isOfficial: boolean; - createdAt: number; - updatedAt: number; + family: "bundle-plugin" | "code-plugin" | "skill"; runtimeId?: string | null | undefined; + channel: "community" | "official" | "private"; + isOfficial: boolean; summary?: string | null | undefined; icon?: string | null | undefined; ownerHandle?: string | null | undefined; + createdAt: number; + updatedAt: number; latestVersion?: string | null | undefined; categories?: string[] | undefined; topics?: string[] | undefined; - verificationTier?: "structural" | "source-linked" | "provenance-verified" | "rebuild-verified" | null | undefined; + verificationTier?: "provenance-verified" | "rebuild-verified" | "source-linked" | "structural" | null | undefined; stats?: { downloads: number; installs: number; @@ -361,19 +361,19 @@ export declare const ApiV1PackageListResponseSchema: import("arktype/internal/va items: { name: string; displayName: string; - family: "skill" | "code-plugin" | "bundle-plugin"; - channel: "official" | "community" | "private"; - isOfficial: boolean; - createdAt: number; - updatedAt: number; + family: "bundle-plugin" | "code-plugin" | "skill"; runtimeId?: string | null | undefined; + channel: "community" | "official" | "private"; + isOfficial: boolean; summary?: string | null | undefined; icon?: string | null | undefined; ownerHandle?: string | null | undefined; + createdAt: number; + updatedAt: number; latestVersion?: string | null | undefined; categories?: string[] | undefined; topics?: string[] | undefined; - verificationTier?: "structural" | "source-linked" | "provenance-verified" | "rebuild-verified" | null | undefined; + verificationTier?: "provenance-verified" | "rebuild-verified" | "source-linked" | "structural" | null | undefined; stats?: { downloads: number; installs: number; @@ -390,19 +390,19 @@ export declare const ApiV1PackageSearchResponseSchema: import("arktype/internal/ package: { name: string; displayName: string; - family: "skill" | "code-plugin" | "bundle-plugin"; - channel: "official" | "community" | "private"; - isOfficial: boolean; - createdAt: number; - updatedAt: number; + family: "bundle-plugin" | "code-plugin" | "skill"; runtimeId?: string | null | undefined; + channel: "community" | "official" | "private"; + isOfficial: boolean; summary?: string | null | undefined; icon?: string | null | undefined; ownerHandle?: string | null | undefined; + createdAt: number; + updatedAt: number; latestVersion?: string | null | undefined; categories?: string[] | undefined; topics?: string[] | undefined; - verificationTier?: "structural" | "source-linked" | "provenance-verified" | "rebuild-verified" | null | undefined; + verificationTier?: "provenance-verified" | "rebuild-verified" | "source-linked" | "structural" | null | undefined; stats?: { downloads: number; installs: number; @@ -417,19 +417,19 @@ export declare const ApiV1PackageResponseSchema: import("arktype/internal/varian package: { name: string; displayName: string; - family: "skill" | "code-plugin" | "bundle-plugin"; - channel: "official" | "community" | "private"; - isOfficial: boolean; - createdAt: number; - updatedAt: number; - tags: unknown; + family: "bundle-plugin" | "code-plugin" | "skill"; runtimeId?: string | null | undefined; + channel: "community" | "official" | "private"; + isOfficial: boolean; summary?: string | null | undefined; icon?: string | null | undefined; ownerHandle?: string | null | undefined; + createdAt: number; + updatedAt: number; latestVersion?: string | null | undefined; categories?: string[] | undefined; topics?: string[] | undefined; + tags: unknown; compatibility?: { pluginApiRange?: string | undefined; builtWithOpenClawVersion?: string | undefined; @@ -438,23 +438,6 @@ export declare const ApiV1PackageResponseSchema: import("arktype/internal/varian } | null | undefined; pluginManifestSummary?: { schemaVersion: number; - configFields: { - name: string; - required: boolean; - sensitive: boolean; - description?: string | undefined; - }[]; - mcpServers: { - name: string; - }[]; - bundledSkills: { - name: string; - rootPath: string; - skillMdPath: string; - sha256: string; - size: number; - description?: string | undefined; - }[]; compatibility?: { pluginApiRange?: string | undefined; builtWithOpenClawVersion?: string | undefined; @@ -467,9 +450,26 @@ export declare const ApiV1PackageResponseSchema: import("arktype/internal/varian version?: string | undefined; family?: string | undefined; } | undefined; + configFields: { + name: string; + description?: string | undefined; + required: boolean; + sensitive: boolean; + }[]; + mcpServers: { + name: string; + }[]; + bundledSkills: { + name: string; + description?: string | undefined; + rootPath: string; + skillMdPath: string; + sha256: string; + size: number; + }[]; } | null | undefined; verification?: { - tier: "structural" | "source-linked" | "provenance-verified" | "rebuild-verified"; + tier: "provenance-verified" | "rebuild-verified" | "source-linked" | "structural"; scope: "artifact-only" | "dependency-graph-aware"; summary?: string | undefined; sourceRepo?: string | undefined; @@ -478,7 +478,7 @@ export declare const ApiV1PackageResponseSchema: import("arktype/internal/varian sourcePath?: string | undefined; hasProvenance?: boolean | undefined; trustedOpenClawPlugin?: boolean | undefined; - scanStatus?: "clean" | "suspicious" | "malicious" | "pending" | "not-run" | undefined; + scanStatus?: "clean" | "malicious" | "not-run" | "pending" | "suspicious" | undefined; } | null | undefined; artifact?: { kind: "legacy-zip" | "npm-pack"; @@ -496,7 +496,7 @@ export declare const ApiV1PackageResponseSchema: import("arktype/internal/varian packageName?: string | undefined; version?: string | undefined; } | null | undefined; - scanStatus?: "clean" | "suspicious" | "malicious" | "pending" | "not-run" | undefined; + scanStatus?: "clean" | "malicious" | "not-run" | "pending" | "suspicious" | undefined; stats?: { downloads: number; installs: number; @@ -525,14 +525,14 @@ export declare const ApiV1PackageVersionResponseSchema: import("arktype/internal package: { name: string; displayName: string; - family: "skill" | "code-plugin" | "bundle-plugin"; + family: "bundle-plugin" | "code-plugin" | "skill"; } | null; version: { version: string; createdAt: number; changelog: string; - files: unknown; distTags?: string[] | undefined; + files: unknown; compatibility?: { pluginApiRange?: string | undefined; builtWithOpenClawVersion?: string | undefined; @@ -541,23 +541,6 @@ export declare const ApiV1PackageVersionResponseSchema: import("arktype/internal } | null | undefined; pluginManifestSummary?: { schemaVersion: number; - configFields: { - name: string; - required: boolean; - sensitive: boolean; - description?: string | undefined; - }[]; - mcpServers: { - name: string; - }[]; - bundledSkills: { - name: string; - rootPath: string; - skillMdPath: string; - sha256: string; - size: number; - description?: string | undefined; - }[]; compatibility?: { pluginApiRange?: string | undefined; builtWithOpenClawVersion?: string | undefined; @@ -570,9 +553,26 @@ export declare const ApiV1PackageVersionResponseSchema: import("arktype/internal version?: string | undefined; family?: string | undefined; } | undefined; + configFields: { + name: string; + description?: string | undefined; + required: boolean; + sensitive: boolean; + }[]; + mcpServers: { + name: string; + }[]; + bundledSkills: { + name: string; + description?: string | undefined; + rootPath: string; + skillMdPath: string; + sha256: string; + size: number; + }[]; } | null | undefined; verification?: { - tier: "structural" | "source-linked" | "provenance-verified" | "rebuild-verified"; + tier: "provenance-verified" | "rebuild-verified" | "source-linked" | "structural"; scope: "artifact-only" | "dependency-graph-aware"; summary?: string | undefined; sourceRepo?: string | undefined; @@ -581,7 +581,7 @@ export declare const ApiV1PackageVersionResponseSchema: import("arktype/internal sourcePath?: string | undefined; hasProvenance?: boolean | undefined; trustedOpenClawPlugin?: boolean | undefined; - scanStatus?: "clean" | "suspicious" | "malicious" | "pending" | "not-run" | undefined; + scanStatus?: "clean" | "malicious" | "not-run" | "pending" | "suspicious" | undefined; } | null | undefined; artifact?: { kind: "legacy-zip" | "npm-pack"; @@ -602,39 +602,38 @@ export declare const ApiV1PackageVersionResponseSchema: import("arktype/internal sha256hash?: string | null | undefined; vtAnalysis?: { status: string; - checkedAt: number; verdict?: string | undefined; analysis?: string | undefined; source?: string | undefined; + checkedAt: number; } | null | undefined; skillSpectorAnalysis?: { status: string; + score?: number | undefined; + severity?: string | undefined; + recommendation?: string | undefined; issueCount: number; issues: { issueId: string; - severity: string; - explanation: string; category?: string | undefined; pattern?: string | undefined; + severity: string; confidence?: number | undefined; file?: string | undefined; startLine?: number | undefined; endLine?: number | undefined; + explanation: string; remediation?: string | undefined; finding?: string | undefined; codeSnippet?: string | undefined; }[]; - checkedAt: number; - score?: number | undefined; - severity?: string | undefined; - recommendation?: string | undefined; scannerVersion?: string | undefined; summary?: string | undefined; error?: string | undefined; + checkedAt: number; } | null | undefined; llmAnalysis?: { status: string; - checkedAt: number; verdict?: string | undefined; confidence?: string | undefined; summary?: string | undefined; @@ -649,6 +648,7 @@ export declare const ApiV1PackageVersionResponseSchema: import("arktype/internal agenticRiskFindings?: unknown[] | undefined; riskSummary?: unknown; model?: string | undefined; + checkedAt: number; } | null | undefined; staticScan?: { status: string; @@ -672,12 +672,11 @@ export declare const ApiV1PackageArtifactResponseSchema: import("arktype/interna package: { name: string; displayName: string; - family: "skill" | "code-plugin" | "bundle-plugin"; + family: "bundle-plugin" | "code-plugin" | "skill"; }; version: string; artifact: { kind: "legacy-zip" | "npm-pack"; - downloadUrl: string; sha256?: string | undefined; size?: number | undefined; format?: string | undefined; @@ -686,6 +685,7 @@ export declare const ApiV1PackageArtifactResponseSchema: import("arktype/interna npmTarballName?: string | undefined; npmUnpackedSize?: number | undefined; npmFileCount?: number | undefined; + downloadUrl: string; tarballUrl?: string | undefined; legacyDownloadUrl?: string | undefined; source?: "clawhub" | undefined; @@ -700,30 +700,30 @@ export declare const ApiV1PackageSecurityResponseSchema: import("arktype/interna package: { name: string; displayName: string; - family: "skill" | "code-plugin" | "bundle-plugin"; + family: "bundle-plugin" | "code-plugin" | "skill"; }; release: { releaseId: string; version: string; - createdAt: number; artifactKind?: "legacy-zip" | "npm-pack" | null | undefined; artifactSha256?: string | undefined; npmIntegrity?: string | undefined; npmShasum?: string | undefined; npmTarballName?: string | undefined; + createdAt: number; }; trust: { - scanStatus: "clean" | "suspicious" | "malicious" | "pending" | "not-run"; + scanStatus: "clean" | "malicious" | "not-run" | "pending" | "suspicious"; + moderationState?: "approved" | "quarantined" | "revoked" | null | undefined; blockedFromDownload: boolean; reasons: string[]; pending: boolean; stale: boolean; - moderationState?: "approved" | "revoked" | "quarantined" | null | undefined; }; }, {}>; export type ApiV1PackageSecurityResponse = (typeof ApiV1PackageSecurityResponseSchema)[inferred]; export declare const PackageReleaseModerationRequestSchema: import("arktype/internal/variants/object.ts").ObjectType<{ - state: "approved" | "revoked" | "quarantined"; + state: "approved" | "quarantined" | "revoked"; reason: string; }, {}>; export type PackageReleaseModerationRequest = (typeof PackageReleaseModerationRequestSchema)[inferred]; @@ -742,9 +742,9 @@ export declare const ApiV1PackageReportResponseSchema: import("arktype/internal/ }, {}>; export type ApiV1PackageReportResponse = (typeof ApiV1PackageReportResponseSchema)[inferred]; export declare const PackageReportTriageRequestSchema: import("arktype/internal/variants/object.ts").ObjectType<{ - status: "open" | "confirmed" | "dismissed"; + status: "confirmed" | "dismissed" | "open"; note?: string | undefined; - finalAction?: "revoke" | "none" | "quarantine" | undefined; + finalAction?: "none" | "quarantine" | "revoke" | undefined; }, {}>; export type PackageReportTriageRequest = (typeof PackageReportTriageRequestSchema)[inferred]; export declare const PackageAppealRequestSchema: import("arktype/internal/variants/object.ts").ObjectType<{ @@ -759,13 +759,13 @@ export declare const ApiV1PackageAppealResponseSchema: import("arktype/internal/ appealId: string; packageId: string; releaseId: string; - status: "open" | "accepted" | "rejected"; + status: "accepted" | "open" | "rejected"; }, {}>; export type ApiV1PackageAppealResponse = (typeof ApiV1PackageAppealResponseSchema)[inferred]; export declare const PackageAppealResolveRequestSchema: import("arktype/internal/variants/object.ts").ObjectType<{ - status: "open" | "accepted" | "rejected"; + status: "accepted" | "open" | "rejected"; note?: string | undefined; - finalAction?: "none" | "approve" | undefined; + finalAction?: "approve" | "none" | undefined; }, {}>; export type PackageAppealResolveRequest = (typeof PackageAppealResolveRequestSchema)[inferred]; export declare const ApiV1PackageAppealListResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{ @@ -775,10 +775,10 @@ export declare const ApiV1PackageAppealListResponseSchema: import("arktype/inter releaseId: string; name: string; displayName: string; - family: "skill" | "code-plugin" | "bundle-plugin"; + family: "bundle-plugin" | "code-plugin" | "skill"; version: string; message: string; - status: "open" | "accepted" | "rejected"; + status: "accepted" | "open" | "rejected"; createdAt: number; submitter: { userId: string; @@ -788,7 +788,7 @@ export declare const ApiV1PackageAppealListResponseSchema: import("arktype/inter resolvedAt?: number | null | undefined; resolvedBy?: string | null | undefined; resolutionNote?: string | null | undefined; - actionTaken?: "none" | "approve" | null | undefined; + actionTaken?: "approve" | "none" | null | undefined; }[]; nextCursor: string | null; done: boolean; @@ -799,31 +799,31 @@ export declare const ApiV1PackageAppealResolveResponseSchema: import("arktype/in appealId: string; packageId: string; releaseId: string; - status: "open" | "accepted" | "rejected"; - actionTaken?: "none" | "approve" | undefined; + status: "accepted" | "open" | "rejected"; + actionTaken?: "approve" | "none" | undefined; }, {}>; export type ApiV1PackageAppealResolveResponse = (typeof ApiV1PackageAppealResolveResponseSchema)[inferred]; export declare const ApiV1PackageReportListResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{ items: { reportId: string; packageId: string; + releaseId?: string | null | undefined; name: string; displayName: string; - family: "skill" | "code-plugin" | "bundle-plugin"; - status: "open" | "confirmed" | "dismissed"; + family: "bundle-plugin" | "code-plugin" | "skill"; + version?: string | null | undefined; + reason?: string | null | undefined; + status: "confirmed" | "dismissed" | "open"; createdAt: number; reporter: { userId: string; handle?: string | null | undefined; displayName?: string | null | undefined; }; - releaseId?: string | null | undefined; - version?: string | null | undefined; - reason?: string | null | undefined; triagedAt?: number | null | undefined; triagedBy?: string | null | undefined; triageNote?: string | null | undefined; - actionTaken?: "revoke" | "none" | "quarantine" | null | undefined; + actionTaken?: "none" | "quarantine" | "revoke" | null | undefined; }[]; nextCursor: string | null; done: boolean; @@ -833,9 +833,9 @@ export declare const ApiV1PackageReportTriageResponseSchema: import("arktype/int ok: true; reportId: string; packageId: string; - status: "open" | "confirmed" | "dismissed"; + status: "confirmed" | "dismissed" | "open"; reportCount: number; - actionTaken?: "revoke" | "none" | "quarantine" | undefined; + actionTaken?: "none" | "quarantine" | "revoke" | undefined; }, {}>; export type ApiV1PackageReportTriageResponse = (typeof ApiV1PackageReportTriageResponseSchema)[inferred]; export declare const ApiV1PackageModerationStatusResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{ @@ -843,30 +843,30 @@ export declare const ApiV1PackageModerationStatusResponseSchema: import("arktype packageId: string; name: string; displayName: string; - family: "skill" | "code-plugin" | "bundle-plugin"; - channel: "official" | "community" | "private"; + family: "bundle-plugin" | "code-plugin" | "skill"; + channel: "community" | "official" | "private"; isOfficial: boolean; reportCount: number; lastReportedAt?: number | null | undefined; - scanStatus?: "clean" | "suspicious" | "malicious" | "pending" | "not-run" | undefined; + scanStatus?: "clean" | "malicious" | "not-run" | "pending" | "suspicious" | undefined; }; latestRelease: { releaseId: string; version: string; - scanStatus: "clean" | "suspicious" | "malicious" | "pending" | "not-run"; + artifactKind?: "legacy-zip" | "npm-pack" | null | undefined; + scanStatus: "clean" | "malicious" | "not-run" | "pending" | "suspicious"; + moderationState?: "approved" | "quarantined" | "revoked" | null | undefined; + moderationReason?: string | null | undefined; blockedFromDownload: boolean; reasons: string[]; createdAt: number; - artifactKind?: "legacy-zip" | "npm-pack" | null | undefined; - moderationState?: "approved" | "revoked" | "quarantined" | null | undefined; - moderationReason?: string | null | undefined; } | null; }, {}>; export type ApiV1PackageModerationStatusResponse = (typeof ApiV1PackageModerationStatusResponseSchema)[inferred]; export declare const PackageReadinessCheckSchema: import("arktype/internal/variants/object.ts").ObjectType<{ id: string; label: string; - status: "warn" | "fail" | "pass"; + status: "fail" | "pass" | "warn"; message: string; }, {}>; export type PackageReadinessCheck = (typeof PackageReadinessCheckSchema)[inferred]; @@ -874,7 +874,7 @@ export declare const ApiV1PackageReadinessResponseSchema: import("arktype/intern package: { name: string; displayName: string; - family: "skill" | "code-plugin" | "bundle-plugin"; + family: "bundle-plugin" | "code-plugin" | "skill"; isOfficial: boolean; latestVersion?: string | null | undefined; }; @@ -882,7 +882,7 @@ export declare const ApiV1PackageReadinessResponseSchema: import("arktype/intern checks: { id: string; label: string; - status: "warn" | "fail" | "pass"; + status: "fail" | "pass" | "warn"; message: string; }[]; blockers: string[]; @@ -898,31 +898,31 @@ export declare const ApiV1PackageTransferResponseSchema: import("arktype/interna packageId: string; name: string; ownerUserId: string; - channel: "official" | "community" | "private"; - isOfficial: boolean; ownerPublisherId?: string | undefined; + channel: "community" | "official" | "private"; + isOfficial: boolean; }, {}>; export type ApiV1PackageTransferResponse = (typeof ApiV1PackageTransferResponseSchema)[inferred]; export declare const PackageRepairNameRequestSchema: import("arktype/internal/variants/object.ts").ObjectType<{ nextName: string; - reason: string; retireTarget?: boolean | undefined; owner?: string | undefined; + reason: string; dryRun?: boolean | undefined; }, {}>; export type PackageRepairNameRequest = (typeof PackageRepairNameRequestSchema)[inferred]; export declare const PackageRepairNamePackageSchema: import("arktype/internal/variants/object.ts").ObjectType<{ packageId: string; name: string; - ownerUserId: string; - channel: "official" | "community" | "private"; runtimeId?: string | null | undefined; + ownerUserId: string; ownerPublisherId?: string | null | undefined; + channel: "community" | "official" | "private"; softDeletedAt?: number | null | undefined; }, {}>; export type PackageRepairNamePackage = (typeof PackageRepairNamePackageSchema)[inferred]; export declare const PackageRepairNameOperationSchema: import("arktype/internal/variants/object.ts").ObjectType<{ - action: "retire-target" | "rename-source" | "transfer-owner"; + action: "rename-source" | "retire-target" | "transfer-owner"; packageId?: string | undefined; from?: string | undefined; to?: string | undefined; @@ -935,29 +935,29 @@ export declare const ApiV1PackageRepairNameResponseSchema: import("arktype/inter source: { packageId: string; name: string; - ownerUserId: string; - channel: "official" | "community" | "private"; runtimeId?: string | null | undefined; + ownerUserId: string; ownerPublisherId?: string | null | undefined; + channel: "community" | "official" | "private"; softDeletedAt?: number | null | undefined; }; target: { packageId: string; name: string; - ownerUserId: string; - channel: "official" | "community" | "private"; runtimeId?: string | null | undefined; + ownerUserId: string; ownerPublisherId?: string | null | undefined; + channel: "community" | "official" | "private"; softDeletedAt?: number | null | undefined; } | null; + retiredName?: string | null | undefined; operations: { - action: "retire-target" | "rename-source" | "transfer-owner"; + action: "rename-source" | "retire-target" | "transfer-owner"; packageId?: string | undefined; from?: string | undefined; to?: string | undefined; owner?: string | undefined; }[]; - retiredName?: string | null | undefined; }, {}>; export type ApiV1PackageRepairNameResponse = (typeof ApiV1PackageRepairNameResponseSchema)[inferred]; export declare const PackageRepairRuntimeIdRequestSchema: import("arktype/internal/variants/object.ts").ObjectType<{ @@ -979,10 +979,10 @@ export declare const ApiV1PackageRepairRuntimeIdResponseSchema: import("arktype/ source: { packageId: string; name: string; - ownerUserId: string; - channel: "official" | "community" | "private"; runtimeId?: string | null | undefined; + ownerUserId: string; ownerPublisherId?: string | null | undefined; + channel: "community" | "official" | "private"; softDeletedAt?: number | null | undefined; }; operations: { @@ -1000,7 +1000,7 @@ export declare const PackageOfficialMigrationUpsertRequestSchema: import("arktyp sourceRepo?: string | undefined; sourcePath?: string | undefined; sourceCommit?: string | undefined; - phase?: "blocked" | "published" | "planned" | "clawpack-ready" | "legacy-zip-only" | "metadata-ready" | "ready-for-openclaw" | undefined; + phase?: "blocked" | "clawpack-ready" | "legacy-zip-only" | "metadata-ready" | "planned" | "published" | "ready-for-openclaw" | undefined; blockers?: string[] | undefined; hostTargetsComplete?: boolean | undefined; scanClean?: boolean | undefined; @@ -1013,20 +1013,20 @@ export declare const PackageOfficialMigrationItemSchema: import("arktype/interna migrationId: string; bundledPluginId: string; packageName: string; - phase: "blocked" | "published" | "planned" | "clawpack-ready" | "legacy-zip-only" | "metadata-ready" | "ready-for-openclaw"; - blockers: string[]; - hostTargetsComplete: boolean; - scanClean: boolean; - moderationApproved: boolean; - runtimeBundlesReady: boolean; - createdAt: number; - updatedAt: number; packageId?: string | null | undefined; owner?: string | null | undefined; sourceRepo?: string | null | undefined; sourcePath?: string | null | undefined; sourceCommit?: string | null | undefined; + phase: "blocked" | "clawpack-ready" | "legacy-zip-only" | "metadata-ready" | "planned" | "published" | "ready-for-openclaw"; + blockers: string[]; + hostTargetsComplete: boolean; + scanClean: boolean; + moderationApproved: boolean; + runtimeBundlesReady: boolean; notes?: string | null | undefined; + createdAt: number; + updatedAt: number; }, {}>; export type PackageOfficialMigrationItem = (typeof PackageOfficialMigrationItemSchema)[inferred]; export declare const ApiV1PackageOfficialMigrationListResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{ @@ -1034,20 +1034,20 @@ export declare const ApiV1PackageOfficialMigrationListResponseSchema: import("ar migrationId: string; bundledPluginId: string; packageName: string; - phase: "blocked" | "published" | "planned" | "clawpack-ready" | "legacy-zip-only" | "metadata-ready" | "ready-for-openclaw"; - blockers: string[]; - hostTargetsComplete: boolean; - scanClean: boolean; - moderationApproved: boolean; - runtimeBundlesReady: boolean; - createdAt: number; - updatedAt: number; packageId?: string | null | undefined; owner?: string | null | undefined; sourceRepo?: string | null | undefined; sourcePath?: string | null | undefined; sourceCommit?: string | null | undefined; + phase: "blocked" | "clawpack-ready" | "legacy-zip-only" | "metadata-ready" | "planned" | "published" | "ready-for-openclaw"; + blockers: string[]; + hostTargetsComplete: boolean; + scanClean: boolean; + moderationApproved: boolean; + runtimeBundlesReady: boolean; notes?: string | null | undefined; + createdAt: number; + updatedAt: number; }[]; nextCursor: string | null; done: boolean; @@ -1059,24 +1059,24 @@ export declare const ApiV1PackageOfficialMigrationResponseSchema: import("arktyp migrationId: string; bundledPluginId: string; packageName: string; - phase: "blocked" | "published" | "planned" | "clawpack-ready" | "legacy-zip-only" | "metadata-ready" | "ready-for-openclaw"; - blockers: string[]; - hostTargetsComplete: boolean; - scanClean: boolean; - moderationApproved: boolean; - runtimeBundlesReady: boolean; - createdAt: number; - updatedAt: number; packageId?: string | null | undefined; owner?: string | null | undefined; sourceRepo?: string | null | undefined; sourcePath?: string | null | undefined; sourceCommit?: string | null | undefined; + phase: "blocked" | "clawpack-ready" | "legacy-zip-only" | "metadata-ready" | "planned" | "published" | "ready-for-openclaw"; + blockers: string[]; + hostTargetsComplete: boolean; + scanClean: boolean; + moderationApproved: boolean; + runtimeBundlesReady: boolean; notes?: string | null | undefined; + createdAt: number; + updatedAt: number; }; }, {}>; export type ApiV1PackageOfficialMigrationResponse = (typeof ApiV1PackageOfficialMigrationResponseSchema)[inferred]; -export declare const PackageModerationQueueStatusSchema: import("arktype/internal/variants/string.ts").StringType<"open" | "blocked" | "all" | "manual", {}>; +export declare const PackageModerationQueueStatusSchema: import("arktype/internal/variants/string.ts").StringType<"all" | "blocked" | "manual" | "open", {}>; export type PackageModerationQueueStatus = (typeof PackageModerationQueueStatusSchema)[inferred]; export declare const ApiV1PackageModerationQueueResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{ items: { @@ -1084,20 +1084,20 @@ export declare const ApiV1PackageModerationQueueResponseSchema: import("arktype/ releaseId: string; name: string; displayName: string; - family: "skill" | "code-plugin" | "bundle-plugin"; - channel: "official" | "community" | "private"; + family: "bundle-plugin" | "code-plugin" | "skill"; + channel: "community" | "official" | "private"; isOfficial: boolean; version: string; createdAt: number; - scanStatus: "clean" | "suspicious" | "malicious" | "pending" | "not-run"; - reportCount: number; - reasons: string[]; artifactKind?: "legacy-zip" | "npm-pack" | null | undefined; - moderationState?: "approved" | "revoked" | "quarantined" | null | undefined; + scanStatus: "clean" | "malicious" | "not-run" | "pending" | "suspicious"; + moderationState?: "approved" | "quarantined" | "revoked" | null | undefined; moderationReason?: string | null | undefined; sourceRepo?: string | null | undefined; sourceCommit?: string | null | undefined; + reportCount: number; lastReportedAt?: number | null | undefined; + reasons: string[]; }[]; nextCursor: string | null; done: boolean; @@ -1107,7 +1107,7 @@ export declare const ApiV1PackageReleaseModerationResponseSchema: import("arktyp ok: true; packageId: string; releaseId: string; - state: "approved" | "revoked" | "quarantined"; + state: "approved" | "quarantined" | "revoked"; scanStatus: "clean" | "malicious"; }, {}>; export type ApiV1PackageReleaseModerationResponse = (typeof ApiV1PackageReleaseModerationResponseSchema)[inferred]; @@ -1115,13 +1115,15 @@ export declare const ApiV1PackagePublishResponseSchema: import("arktype/internal ok: true; packageId: string; releaseId: string; + publicationStatus?: "pending" | "published" | undefined; + attemptId?: string | undefined; inspectorFindings?: { findingKind: "error" | "warning"; code: string; - message: string; severity?: string | undefined; level?: string | undefined; issueClass?: string | undefined; + message: string; authorRemediation?: { summary: string; docsUrl?: string | undefined; diff --git a/packages/schema/dist/packages.js b/packages/schema/dist/packages.js index ea9fb3b5..ea6dc77e 100644 --- a/packages/schema/dist/packages.js +++ b/packages/schema/dist/packages.js @@ -705,6 +705,8 @@ export const ApiV1PackagePublishResponseSchema = type({ ok: "true", packageId: "string", releaseId: "string", + publicationStatus: '"pending"|"published"?', + attemptId: "string?", inspectorFindings: type({ findingKind: '"warning"|"error"', code: "string", diff --git a/packages/schema/dist/packages.js.map b/packages/schema/dist/packages.js.map index 35d03c08..994e7b26 100644 --- a/packages/schema/dist/packages.js.map +++ b/packages/schema/dist/packages.js.map @@ -1 +1 @@ -{"version":3,"file":"packages.js","sourceRoot":"","sources":["../src/packages.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"packages.js","sourceRoot":"","sources":["../src/packages.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/schema/dist/promotionsFeed.d.ts b/packages/schema/dist/promotionsFeed.d.ts index 212231c6..89f7e55a 100644 --- a/packages/schema/dist/promotionsFeed.d.ts +++ b/packages/schema/dist/promotionsFeed.d.ts @@ -10,17 +10,17 @@ export declare const PromotionsFeedEntrySchema: import("arktype/internal/variant slug: string; title: string; blurb: string; + sponsor?: string | undefined; startsAt: number; endsAt: number; + provider?: string | undefined; + authChoiceId?: string | undefined; + pluginNames?: string[] | undefined; models: { modelRef: string; alias?: string | undefined; suggestedDefault?: boolean | undefined; }[]; - sponsor?: string | undefined; - provider?: string | undefined; - authChoiceId?: string | undefined; - pluginNames?: string[] | undefined; signupUrl?: string | undefined; docsUrl?: string | undefined; launchPageUrl?: string | undefined; @@ -32,27 +32,27 @@ export declare const PromotionsFeedSchema: import("arktype/internal/variants/obj generatedAt: string; sequence: number; expiresAt: string; + description?: string | undefined; entries: { type: "promotion"; slug: string; title: string; blurb: string; + sponsor?: string | undefined; startsAt: number; endsAt: number; + provider?: string | undefined; + authChoiceId?: string | undefined; + pluginNames?: string[] | undefined; models: { modelRef: string; alias?: string | undefined; suggestedDefault?: boolean | undefined; }[]; - sponsor?: string | undefined; - provider?: string | undefined; - authChoiceId?: string | undefined; - pluginNames?: string[] | undefined; signupUrl?: string | undefined; docsUrl?: string | undefined; launchPageUrl?: string | undefined; }[]; - description?: string | undefined; }, {}>; export type PromotionsFeed = (typeof PromotionsFeedSchema)[inferred]; /** diff --git a/packages/schema/dist/schemas.d.ts b/packages/schema/dist/schemas.d.ts index 69008415..32041ad4 100644 --- a/packages/schema/dist/schemas.d.ts +++ b/packages/schema/dist/schemas.d.ts @@ -34,11 +34,11 @@ export declare const ApiCliWhoamiResponseSchema: import("arktype/internal/varian }, {}>; export declare const ApiSearchResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{ results: { - score: number; slug?: string | undefined; ownerHandle?: string | null | undefined; displayName?: string | undefined; version?: string | null | undefined; + score: number; }[]; }, {}>; export declare const ApiSkillMetaResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{ @@ -74,18 +74,11 @@ export declare const PublishSourceSchema: import("arktype/internal/variants/obje export declare const CliPublishRequestSchema: import("arktype/internal/variants/object.ts").ObjectType<{ slug: string; displayName: string; - version: string; - changelog: string; - files: { - path: string; - size: number; - storageId: string; - sha256: string; - contentType?: string | undefined; - }[]; ownerHandle?: string | undefined; sourceOwnerHandle?: string | undefined; migrateOwner?: boolean | undefined; + version: string; + changelog: string; acceptLicenseTerms?: boolean | undefined; tags?: string[] | undefined; categories?: string[] | undefined; @@ -104,12 +97,24 @@ export declare const CliPublishRequestSchema: import("arktype/internal/variants/ ownerHandle?: string | undefined; version?: string | undefined; } | undefined; + files: { + path: string; + size: number; + storageId: string; + sha256: string; + contentType?: string | undefined; + }[]; }, {}>; export type CliPublishRequest = (typeof CliPublishRequestSchema)[inferred]; export declare const ApiCliPublishResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{ ok: true; skillId: string; versionId: string; + status?: "pending" | "published" | undefined; + slug?: string | undefined; + version?: string | undefined; + publicationStatus?: "pending" | "published" | undefined; + attemptId?: string | undefined; }, {}>; export declare const CliSkillDeleteRequestSchema: import("arktype/internal/variants/object.ts").ObjectType<{ slug: string; @@ -150,7 +155,7 @@ export declare const ApiV1SkillInstallResolveResponseSchema: import("arktype/int } | { ok: false; slug: string; - reason: "archive_version_missing" | "github_source_missing" | "github_upstream_removed" | "github_upstream_missing" | "github_upstream_unknown" | "github_verification_pending" | "github_scan_failed"; + reason: "archive_version_missing" | "github_scan_failed" | "github_source_missing" | "github_upstream_missing" | "github_upstream_removed" | "github_upstream_unknown" | "github_verification_pending"; message: string; status: number; }, {}>; @@ -181,7 +186,7 @@ export declare const ApiV1WhoamiResponseSchema: import("arktype/internal/variant handle: string | null; displayName?: string | null | undefined; image?: string | null | undefined; - role?: "user" | "admin" | "moderator" | null | undefined; + role?: "admin" | "moderator" | "user" | null | undefined; }; }, {}>; export declare const ApiV1UserSearchResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{ @@ -190,7 +195,7 @@ export declare const ApiV1UserSearchResponseSchema: import("arktype/internal/var handle: string | null; displayName?: string | null | undefined; name?: string | null | undefined; - role?: "user" | "admin" | "moderator" | null | undefined; + role?: "admin" | "moderator" | "user" | null | undefined; }[]; total: number; }, {}>; @@ -243,12 +248,12 @@ export declare const ApiV1StaffEmailSendResponseSchema: import("arktype/internal export type ApiV1StaffEmailSendResponse = (typeof ApiV1StaffEmailSendResponseSchema)[inferred]; export declare const ApiV1SearchResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{ results: { - score: number; slug?: string | undefined; ownerHandle?: string | null | undefined; displayName?: string | undefined; summary?: string | null | undefined; version?: string | null | undefined; + score: number; downloads?: number | undefined; updatedAt?: number | undefined; owner?: { @@ -262,13 +267,13 @@ export declare const ApiV1SkillListResponseSchema: import("arktype/internal/vari items: { slug: string; displayName: string; + summary?: string | null | undefined; + description?: string | null | undefined; + topics?: string[] | undefined; tags: unknown; stats: unknown; createdAt: number; updatedAt: number; - summary?: string | null | undefined; - description?: string | null | undefined; - topics?: string[] | undefined; latestVersion?: { version: string; createdAt: number; @@ -290,13 +295,13 @@ export declare const ApiV1SkillResponseSchema: import("arktype/internal/variants skill: { slug: string; displayName: string; + summary?: string | null | undefined; + description?: string | null | undefined; + topics?: string[] | undefined; tags: unknown; stats: unknown; createdAt: number; updatedAt: number; - summary?: string | null | undefined; - description?: string | null | undefined; - topics?: string[] | undefined; } | null; latestVersion: { version: string; @@ -304,11 +309,6 @@ export declare const ApiV1SkillResponseSchema: import("arktype/internal/variants changelog: string; license?: "MIT-0" | null | undefined; } | null; - owner: { - handle: string | null; - displayName?: string | null | undefined; - image?: string | null | undefined; - } | null; metadata?: { setup: { key: string; @@ -317,10 +317,15 @@ export declare const ApiV1SkillResponseSchema: import("arktype/internal/variants os?: string[] | null | undefined; systems?: string[] | null | undefined; } | null | undefined; + owner: { + handle: string | null; + displayName?: string | null | undefined; + image?: string | null | undefined; + } | null; moderation?: { isSuspicious: boolean; isMalwareBlocked: boolean; - verdict?: "clean" | "suspicious" | "malicious" | undefined; + verdict?: "clean" | "malicious" | "suspicious" | undefined; reasonCodes?: string[] | undefined; updatedAt?: number | null | undefined; engineVersion?: string | null | undefined; @@ -331,20 +336,20 @@ export declare const ApiV1SkillModerationResponseSchema: import("arktype/interna moderation: { isSuspicious: boolean; isMalwareBlocked: boolean; - verdict: "clean" | "suspicious" | "malicious"; + verdict: "clean" | "malicious" | "suspicious"; reasonCodes: string[]; + updatedAt?: number | null | undefined; + engineVersion?: string | null | undefined; + summary?: string | null | undefined; + legacyReason?: string | null | undefined; evidence: { code: string; - severity: "info" | "warn" | "critical"; + severity: "critical" | "info" | "warn"; file: string; line: number; message: string; evidence: string; }[]; - updatedAt?: number | null | undefined; - engineVersion?: string | null | undefined; - summary?: string | null | undefined; - legacyReason?: string | null | undefined; } | null; }, {}>; export declare const SkillVersionRevokeRequestSchema: import("arktype/internal/variants/object.ts").ObjectType<{ @@ -364,21 +369,21 @@ export declare const ApiV1SkillVersionRevokeResponseSchema: import("arktype/inte skillHidden: boolean; }, {}>; export type ApiV1SkillVersionRevokeResponse = (typeof ApiV1SkillVersionRevokeResponseSchema)[inferred]; -export declare const SkillReportStatusSchema: import("arktype/internal/variants/string.ts").StringType<"open" | "confirmed" | "dismissed", {}>; +export declare const SkillReportStatusSchema: import("arktype/internal/variants/string.ts").StringType<"confirmed" | "dismissed" | "open", {}>; export type SkillReportStatus = (typeof SkillReportStatusSchema)[inferred]; -export declare const SkillReportFinalActionSchema: import("arktype/internal/variants/string.ts").StringType<"none" | "hide", {}>; +export declare const SkillReportFinalActionSchema: import("arktype/internal/variants/string.ts").StringType<"hide" | "none", {}>; export type SkillReportFinalAction = (typeof SkillReportFinalActionSchema)[inferred]; -export declare const SkillReportListStatusSchema: import("arktype/internal/variants/string.ts").StringType<"open" | "confirmed" | "dismissed" | "all", {}>; +export declare const SkillReportListStatusSchema: import("arktype/internal/variants/string.ts").StringType<"all" | "confirmed" | "dismissed" | "open", {}>; export type SkillReportListStatus = (typeof SkillReportListStatusSchema)[inferred]; -export declare const SkillAppealStatusSchema: import("arktype/internal/variants/string.ts").StringType<"open" | "accepted" | "rejected", {}>; +export declare const SkillAppealStatusSchema: import("arktype/internal/variants/string.ts").StringType<"accepted" | "open" | "rejected", {}>; export type SkillAppealStatus = (typeof SkillAppealStatusSchema)[inferred]; export declare const SkillAppealFinalActionSchema: import("arktype/internal/variants/string.ts").StringType<"none" | "restore", {}>; export type SkillAppealFinalAction = (typeof SkillAppealFinalActionSchema)[inferred]; -export declare const SkillAppealListStatusSchema: import("arktype/internal/variants/string.ts").StringType<"open" | "all" | "accepted" | "rejected", {}>; +export declare const SkillAppealListStatusSchema: import("arktype/internal/variants/string.ts").StringType<"accepted" | "all" | "open" | "rejected", {}>; export type SkillAppealListStatus = (typeof SkillAppealListStatusSchema)[inferred]; export declare const SkillAppealRequestSchema: import("arktype/internal/variants/object.ts").ObjectType<{ - message: string; version?: string | undefined; + message: string; }, {}>; export type SkillAppealRequest = (typeof SkillAppealRequestSchema)[inferred]; export declare const ApiV1SkillReportResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{ @@ -396,17 +401,17 @@ export declare const ApiV1SkillAppealResponseSchema: import("arktype/internal/va alreadyOpen: boolean; appealId: string; skillId: string; - status: "open" | "accepted" | "rejected"; + status: "accepted" | "open" | "rejected"; }, {}>; export type ApiV1SkillAppealResponse = (typeof ApiV1SkillAppealResponseSchema)[inferred]; export declare const SkillReportTriageRequestSchema: import("arktype/internal/variants/object.ts").ObjectType<{ - status: "open" | "confirmed" | "dismissed"; + status: "confirmed" | "dismissed" | "open"; note?: string | undefined; - finalAction?: "none" | "hide" | undefined; + finalAction?: "hide" | "none" | undefined; }, {}>; export type SkillReportTriageRequest = (typeof SkillReportTriageRequestSchema)[inferred]; export declare const SkillAppealResolveRequestSchema: import("arktype/internal/variants/object.ts").ObjectType<{ - status: "open" | "accepted" | "rejected"; + status: "accepted" | "open" | "rejected"; note?: string | undefined; finalAction?: "none" | "restore" | undefined; }, {}>; @@ -415,22 +420,22 @@ export declare const ApiV1SkillReportListResponseSchema: import("arktype/interna items: { reportId: string; skillId: string; + skillVersionId?: string | null | undefined; slug: string; displayName: string; - status: "open" | "confirmed" | "dismissed"; + version?: string | null | undefined; + reason?: string | null | undefined; + status: "confirmed" | "dismissed" | "open"; createdAt: number; reporter: { userId: string; handle?: string | null | undefined; displayName?: string | null | undefined; }; - skillVersionId?: string | null | undefined; - version?: string | null | undefined; - reason?: string | null | undefined; triagedAt?: number | null | undefined; triagedBy?: string | null | undefined; triageNote?: string | null | undefined; - actionTaken?: "none" | "hide" | null | undefined; + actionTaken?: "hide" | "none" | null | undefined; }[]; nextCursor: string | null; done: boolean; @@ -440,27 +445,27 @@ export declare const ApiV1SkillReportTriageResponseSchema: import("arktype/inter ok: true; reportId: string; skillId: string; - status: "open" | "confirmed" | "dismissed"; + status: "confirmed" | "dismissed" | "open"; reportCount: number; - actionTaken?: "none" | "hide" | undefined; + actionTaken?: "hide" | "none" | undefined; }, {}>; export type ApiV1SkillReportTriageResponse = (typeof ApiV1SkillReportTriageResponseSchema)[inferred]; export declare const ApiV1SkillAppealListResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{ items: { appealId: string; skillId: string; + skillVersionId?: string | null | undefined; slug: string; displayName: string; + version?: string | null | undefined; message: string; - status: "open" | "accepted" | "rejected"; + status: "accepted" | "open" | "rejected"; createdAt: number; submitter: { userId: string; handle?: string | null | undefined; displayName?: string | null | undefined; }; - skillVersionId?: string | null | undefined; - version?: string | null | undefined; resolvedAt?: number | null | undefined; resolvedBy?: string | null | undefined; resolutionNote?: string | null | undefined; @@ -474,7 +479,7 @@ export declare const ApiV1SkillAppealResolveResponseSchema: import("arktype/inte ok: true; appealId: string; skillId: string; - status: "open" | "accepted" | "rejected"; + status: "accepted" | "open" | "rejected"; actionTaken?: "none" | "restore" | undefined; }, {}>; export type ApiV1SkillAppealResolveResponse = (typeof ApiV1SkillAppealResolveResponseSchema)[inferred]; @@ -492,12 +497,12 @@ export declare const ApiV1SkillRescanResponseSchema: import("arktype/internal/va version: string; skillId: string; githubContentHash: string; + jobId?: string | undefined; scheduled: boolean; alreadyQueued: boolean; - jobId?: string | undefined; }, {}>; export type ApiV1SkillRescanResponse = (typeof ApiV1SkillRescanResponseSchema)[inferred]; -export declare const ApiV1SkillScanStatusSchema: import("arktype/internal/variants/string.ts").StringType<"queued" | "running" | "succeeded" | "failed", {}>; +export declare const ApiV1SkillScanStatusSchema: import("arktype/internal/variants/string.ts").StringType<"failed" | "queued" | "running" | "succeeded", {}>; export type ApiV1SkillScanStatus = (typeof ApiV1SkillScanStatusSchema)[inferred]; export declare const ApiV1SkillScanSourceSchema: import("arktype/internal/variants/object.ts").ObjectType<{ kind: "upload"; @@ -520,63 +525,63 @@ export declare const ApiV1SkillScanSubmitRequestSchema: import("arktype/internal export type ApiV1SkillScanSubmitRequest = (typeof ApiV1SkillScanSubmitRequestSchema)[inferred]; export declare const ApiV1SkillScanQueueSchema: import("arktype/internal/variants/object.ts").ObjectType<{ queuedAhead: number; + queuedAheadIsEstimate?: boolean | undefined; position: number | null; running: number; - note: string; - queuedAheadIsEstimate?: boolean | undefined; runningIsEstimate?: boolean | undefined; + note: string; }, {}>; export type ApiV1SkillScanQueue = (typeof ApiV1SkillScanQueueSchema)[inferred]; export declare const ApiV1SkillScanSubmitResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{ ok: true; scanId: string; - status: "queued" | "running" | "succeeded" | "failed"; - sourceKind: "upload" | "published"; - update: boolean; jobId?: string | undefined; + status: "failed" | "queued" | "running" | "succeeded"; + sourceKind: "published" | "upload"; + update: boolean; alreadyQueued?: boolean | undefined; queue?: { queuedAhead: number; + queuedAheadIsEstimate?: boolean | undefined; position: number | null; running: number; - note: string; - queuedAheadIsEstimate?: boolean | undefined; runningIsEstimate?: boolean | undefined; + note: string; } | undefined; }, {}>; export type ApiV1SkillScanSubmitResponse = (typeof ApiV1SkillScanSubmitResponseSchema)[inferred]; export declare const ApiV1SkillScanStatusResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{ ok: true; scanId: string; - status: "queued" | "running" | "succeeded" | "failed"; - sourceKind: "upload" | "published"; - update: boolean; - createdAt: number; - updatedAt: number; jobId?: string | undefined; + status: "failed" | "queued" | "running" | "succeeded"; + sourceKind: "published" | "upload"; + update: boolean; writtenBack?: boolean | undefined; artifact?: unknown; report?: unknown; queue?: { queuedAhead: number; + queuedAheadIsEstimate?: boolean | undefined; position: number | null; running: number; - note: string; - queuedAheadIsEstimate?: boolean | undefined; runningIsEstimate?: boolean | undefined; + note: string; } | undefined; lastError?: string | undefined; + createdAt: number; + updatedAt: number; completedAt?: number | undefined; }, {}>; export type ApiV1SkillScanStatusResponse = (typeof ApiV1SkillScanStatusResponseSchema)[inferred]; export declare const ApiV1SkillScanDownloadManifestSchema: import("arktype/internal/variants/object.ts").ObjectType<{ scanId: string; - sourceKind: "upload" | "published"; + sourceKind: "published" | "upload"; update: boolean; - status: "queued" | "running" | "succeeded" | "failed"; + status: "failed" | "queued" | "running" | "succeeded"; + artifact?: unknown; createdAt: number; updatedAt: number; - artifact?: unknown; completedAt?: number | undefined; writtenBack?: boolean | undefined; }, {}>; @@ -685,12 +690,12 @@ export declare const ApiV1SkillVersionListResponseSchema: import("arktype/intern version: string; createdAt: number; changelog: string; - changelogSource?: "user" | "auto" | null | undefined; + changelogSource?: "auto" | "user" | null | undefined; }[]; nextCursor: string | null; }, {}>; export declare const SecurityStatusSchema: import("arktype/internal/variants/object.ts").ObjectType<{ - status: "error" | "clean" | "suspicious" | "malicious" | "pending"; + status: "clean" | "error" | "malicious" | "pending" | "suspicious"; hasWarnings: boolean; checkedAt: number | null; model: string | null; @@ -700,11 +705,11 @@ export declare const ApiV1SkillVersionResponseSchema: import("arktype/internal/v version: string; createdAt: number; changelog: string; - changelogSource?: "user" | "auto" | null | undefined; + changelogSource?: "auto" | "user" | null | undefined; license?: "MIT-0" | null | undefined; files?: unknown; security?: { - status: "error" | "clean" | "suspicious" | "malicious" | "pending"; + status: "clean" | "error" | "malicious" | "pending" | "suspicious"; hasWarnings: boolean; checkedAt: number | null; model: string | null; @@ -735,7 +740,7 @@ export declare const ApiV1SkillVerifyResponseSchema: import("arktype/internal/va publisherDisplayName: string | null; publisherProfileUrl: string | null; version: string; - resolvedFrom: "version" | "tag" | "latest"; + resolvedFrom: "latest" | "tag" | "version"; tag: string | null; createdAt: number; card: unknown; @@ -748,6 +753,11 @@ export declare const ApiV1PublishResponseSchema: import("arktype/internal/varian ok: true; skillId: string; versionId: string; + status?: "pending" | "published" | undefined; + slug?: string | undefined; + version?: string | undefined; + publicationStatus?: "pending" | "published" | undefined; + attemptId?: string | undefined; }, {}>; export declare const ApiV1DeleteResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{ ok: true; @@ -784,8 +794,6 @@ export declare const ApiV1TransferListResponseSchema: import("arktype/internal/v slug: string; displayName: string; }; - requestedAt: number; - expiresAt: number; fromUser?: { _id: string; handle: string | null; @@ -797,11 +805,13 @@ export declare const ApiV1TransferListResponseSchema: import("arktype/internal/v displayName: string | null; } | undefined; message?: string | undefined; + requestedAt: number; + expiresAt: number; }[]; }, {}>; export declare const ApiV1SetRoleResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{ ok: true; - role: "user" | "admin" | "moderator"; + role: "admin" | "moderator" | "user"; }, {}>; export declare const ApiV1ReclassifyBanResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{ ok: true; @@ -823,8 +833,8 @@ export declare const ApiV1UnstarResponseSchema: import("arktype/internal/variant alreadyUnstarred: boolean; }, {}>; export declare const SkillInstallSpecSchema: import("arktype/internal/variants/object.ts").ObjectType<{ - kind: "brew" | "node" | "go" | "uv"; id?: string | undefined; + kind: "brew" | "go" | "node" | "uv"; label?: string | undefined; bins?: string[] | undefined; formula?: string | undefined; @@ -859,7 +869,7 @@ export declare const EnvVarDeclarationSchema: import("arktype/internal/variants/ export type EnvVarDeclaration = (typeof EnvVarDeclarationSchema)[inferred]; export declare const DependencyDeclarationSchema: import("arktype/internal/variants/object.ts").ObjectType<{ name: string; - type: "other" | "brew" | "go" | "pip" | "npm" | "cargo" | "apt"; + type: "apt" | "brew" | "cargo" | "go" | "npm" | "other" | "pip"; version?: string | undefined; url?: string | undefined; repository?: string | undefined; @@ -887,8 +897,8 @@ export declare const ClawdisSkillMetadataSchema: import("arktype/internal/varian config?: string[] | undefined; } | undefined; install?: { - kind: "brew" | "node" | "go" | "uv"; id?: string | undefined; + kind: "brew" | "go" | "node" | "uv"; label?: string | undefined; bins?: string[] | undefined; formula?: string | undefined; @@ -912,7 +922,7 @@ export declare const ClawdisSkillMetadataSchema: import("arktype/internal/varian }[] | undefined; dependencies?: { name: string; - type: "other" | "brew" | "go" | "pip" | "npm" | "cargo" | "apt"; + type: "apt" | "brew" | "cargo" | "go" | "npm" | "other" | "pip"; version?: string | undefined; url?: string | undefined; repository?: string | undefined; diff --git a/packages/schema/dist/schemas.js b/packages/schema/dist/schemas.js index df467b94..61bda37f 100644 --- a/packages/schema/dist/schemas.js +++ b/packages/schema/dist/schemas.js @@ -92,6 +92,11 @@ export const ApiCliPublishResponseSchema = type({ ok: "true", skillId: "string", versionId: "string", + status: '"pending"|"published"?', + slug: "string?", + version: "string?", + publicationStatus: '"pending"|"published"?', + attemptId: "string?", }); export const CliSkillDeleteRequestSchema = type({ slug: "string", @@ -665,6 +670,11 @@ export const ApiV1PublishResponseSchema = type({ ok: "true", skillId: "string", versionId: "string", + status: '"pending"|"published"?', + slug: "string?", + version: "string?", + publicationStatus: '"pending"|"published"?', + attemptId: "string?", }); export const ApiV1DeleteResponseSchema = type({ ok: "true", diff --git a/packages/schema/dist/schemas.js.map b/packages/schema/dist/schemas.js.map index d4b7c15f..f03d607d 100644 --- a/packages/schema/dist/schemas.js.map +++ b/packages/schema/dist/schemas.js.map @@ -1 +1 @@ -{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/schema/src/packages.ts b/packages/schema/src/packages.ts index 49167a95..37132d69 100644 --- a/packages/schema/src/packages.ts +++ b/packages/schema/src/packages.ts @@ -893,6 +893,8 @@ export const ApiV1PackagePublishResponseSchema = type({ ok: "true", packageId: "string", releaseId: "string", + publicationStatus: '"pending"|"published"?', + attemptId: "string?", inspectorFindings: type({ findingKind: '"warning"|"error"', code: "string", diff --git a/packages/schema/src/schemas.test.ts b/packages/schema/src/schemas.test.ts index 8190809b..8c66377f 100644 --- a/packages/schema/src/schemas.test.ts +++ b/packages/schema/src/schemas.test.ts @@ -3,7 +3,11 @@ import { describe, expect, it } from "vitest"; import { parseArk } from "./ark"; import { DocsLinks, openClawDocsUrl } from "./docsLinks"; -import { getPackageScopeOwnerMismatch, inferPackageNameScope } from "./packages"; +import { + ApiV1PackagePublishResponseSchema, + getPackageScopeOwnerMismatch, + inferPackageNameScope, +} from "./packages"; import { ApiSearchResponseSchema, ApiV1SkillInstallResolveResponseSchema, @@ -75,6 +79,24 @@ describe("clawhub-schema", () => { expect(payload.acceptLicenseTerms).toBe(true); }); + it("accepts pending package publish responses with legacy IDs", () => { + const response = parseArk( + ApiV1PackagePublishResponseSchema, + { + ok: true, + packageId: "packages:demo", + releaseId: "packageReleases:demo", + publicationStatus: "pending", + attemptId: "publishAttempts:demo", + }, + "Package publish response", + ); + + expect(response.releaseId).toBe("packageReleases:demo"); + expect(response.publicationStatus).toBe("pending"); + expect(response.attemptId).toBe("publishAttempts:demo"); + }); + it("accepts publish payload with github source", () => { const payload = parseArk( CliPublishRequestSchema, diff --git a/packages/schema/src/schemas.ts b/packages/schema/src/schemas.ts index e83a7eb8..27ed2538 100644 --- a/packages/schema/src/schemas.ts +++ b/packages/schema/src/schemas.ts @@ -109,6 +109,11 @@ export const ApiCliPublishResponseSchema = type({ ok: "true", skillId: "string", versionId: "string", + status: '"pending"|"published"?', + slug: "string?", + version: "string?", + publicationStatus: '"pending"|"published"?', + attemptId: "string?", }); export const CliSkillDeleteRequestSchema = type({ @@ -793,6 +798,11 @@ export const ApiV1PublishResponseSchema = type({ ok: "true", skillId: "string", versionId: "string", + status: '"pending"|"published"?', + slug: "string?", + version: "string?", + publicationStatus: '"pending"|"published"?', + attemptId: "string?", }); export const ApiV1DeleteResponseSchema = type({ diff --git a/scripts/security/prepublication-worker-workflow.test.ts b/scripts/security/prepublication-worker-workflow.test.ts index 1c53a18b..12f99930 100644 --- a/scripts/security/prepublication-worker-workflow.test.ts +++ b/scripts/security/prepublication-worker-workflow.test.ts @@ -66,9 +66,10 @@ describe("pre-publication publish worker workflow", () => { expect(job.strategy?.matrix?.shard).toEqual([0, 1]); expect(job.strategy?.["max-parallel"]).toBe(2); expect(job.env).toMatchObject({ - CODEX_SECURITY_SCAN_TIMEOUT_MS: "${{ vars.CODEX_SECURITY_SCAN_TIMEOUT_MS || '240000' }}", CONVEX_URL: "${{ vars.CONVEX_URL || vars.VITE_CONVEX_URL || 'https://wry-manatee-359.convex.cloud' }}", + PREPUBLICATION_CLAWSCAN_TIMEOUT_MS: + "${{ vars.PREPUBLICATION_CLAWSCAN_TIMEOUT_MS || '240000' }}", PREPUBLICATION_CHECK_LIMIT: "${{ inputs['batch-limit'] || '2' }}", PREPUBLICATION_TRUFFLEHOG_IMAGE: "${{ vars.PREPUBLICATION_TRUFFLEHOG_IMAGE || 'ghcr.io/trufflesecurity/trufflehog:3.95.6@sha256:96f8429082cb2d4ae73b1096dcdb2f5aa139881d97042b0c5e5fa226a392e056' }}", @@ -76,15 +77,21 @@ describe("pre-publication publish worker workflow", () => { expect(String(job.env?.PREPUBLICATION_TRUFFLEHOG_IMAGE)).toContain("@sha256:"); expect(job.env).not.toHaveProperty("OPENAI_API_KEY"); expect(job.env).not.toHaveProperty("SECURITY_SCAN_WORKER_TOKEN"); + expect(job.env).not.toHaveProperty("CODEX_SECURITY_SCAN_TIMEOUT_MS"); const runStep = steps.find((step) => step.name === "Run pre-publication publish worker"); expect(runStep?.run).toContain("bun run publish:prepublication-worker"); - expect(steps.find((step) => step.name === "Install ClawScan CLI")).toBeUndefined(); + expect(steps.find((step) => step.name === "Install ClawScan CLI")?.run).toContain( + "npm install -g @openclaw/clawscan@0.1.2", + ); + expect(steps.find((step) => step.name === "Install Codex CLI")).toBeUndefined(); + expect(steps.find((step) => step.name === "Authenticate Codex CLI")).toBeUndefined(); + expect(steps.find((step) => step.name === "Install SkillSpector")).toBeUndefined(); expect(JSON.stringify(job)).not.toContain("CODEX_SECURITY_SCAN_SHADOW_CLAWSCAN"); - expect(JSON.stringify(job)).not.toContain("clawscan --version"); expect(runStep?.env).toEqual({ OPENAI_API_KEY: "${{ secrets.OPENAI_API_KEY }}", SECURITY_SCAN_WORKER_TOKEN: "${{ secrets.SECURITY_SCAN_WORKER_TOKEN }}", + VIRUSTOTAL_API_KEY: "${{ secrets.VT_API_KEY }}", }); for (const step of steps) { @@ -93,7 +100,10 @@ describe("pre-publication publish worker workflow", () => { stepName === "Run pre-publication publish worker", ); expect(stepUsesSecret(step, "OPENAI_API_KEY"), stepName).toBe( - stepName === "Authenticate Codex CLI" || stepName === "Run pre-publication publish worker", + stepName === "Run pre-publication publish worker", + ); + expect(stepUsesSecret(step, "VT_API_KEY"), stepName).toBe( + stepName === "Run pre-publication publish worker", ); } }); diff --git a/scripts/security/run-prepublication-worker.test.ts b/scripts/security/run-prepublication-worker.test.ts index f22dbdc0..e0e2e2b4 100644 --- a/scripts/security/run-prepublication-worker.test.ts +++ b/scripts/security/run-prepublication-worker.test.ts @@ -1,5 +1,5 @@ /* @vitest-environment node */ -import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; @@ -7,9 +7,9 @@ import type { Id } from "../../convex/_generated/dataModel"; import { claimBatchDrainedQueue, claimPrePublicationBatch, - configurePrePublicationCodexHome, processPrePublicationBatch, processPrePublicationAttempt, + runNativeClawScan, resolveTruffleHogImage, runNativeTruffleHog, } from "./run-prepublication-worker"; @@ -56,18 +56,6 @@ describe("pre-publication worker", () => { expect(claimBatchDrainedQueue(0, 6, 6)).toBe(false); }); - it("does not clear the Codex home configured by GitHub Actions login", () => { - const env = { - CI: "true", - GITHUB_ACTIONS: "true", - GITHUB_REPOSITORY: "openclaw/clawhub", - GITHUB_RUN_ID: "123", - } as NodeJS.ProcessEnv; - - expect(configurePrePublicationCodexHome(env)).toBeUndefined(); - expect(env).not.toHaveProperty("CODEX_HOME"); - }); - it("requires the TruffleHog image to be pinned by digest", () => { expect(resolveTruffleHogImage()).toContain("@sha256:"); expect(() => resolveTruffleHogImage("ghcr.io/trufflesecurity/trufflehog:3.95.6")).toThrow( @@ -75,7 +63,7 @@ describe("pre-publication worker", () => { ); }); - it("completes clean staged publishes after TruffleHog and ClawHub review pass", async () => { + it("completes clean staged publishes after TruffleHog and ClawScan pass", async () => { const client = { action: vi.fn().mockResolvedValue({ status: "finalized" }), }; @@ -84,26 +72,30 @@ describe("pre-publication worker", () => { status: "clean", summary: "TruffleHog found no verified secrets.", }); - const runClawHubReview = vi.fn().mockResolvedValue({ - llmAnalysis: { + const runClawScan = vi.fn().mockResolvedValue({ + analysis: { checkedAt: 123, confidence: "high", status: "clean", - summary: "ClawHub security review passed.", + summary: "ClawScan passed.", verdict: "benign", }, + check: { + status: "clean", + summary: "ClawScan passed.", + }, }); await expect( processPrePublicationAttempt(client, "worker-token", attempt, { - runClawHubReview, + runClawScan, runTruffleHog, writeWorkspace: vi.fn().mockResolvedValue(undefined), }), ).resolves.toMatchObject({ completed: true }); expect(runTruffleHog).toHaveBeenCalledTimes(1); - expect(runClawHubReview).toHaveBeenCalledTimes(1); + expect(runClawScan).toHaveBeenCalledTimes(1); expect(client.action).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ @@ -125,14 +117,19 @@ describe("pre-publication worker", () => { await expect( processPrePublicationAttempt(client, "worker-token", attempt, { - runClawHubReview: vi.fn().mockResolvedValue({ - llmAnalysis: { + runClawScan: vi.fn().mockResolvedValue({ + analysis: { checkedAt: 123, confidence: "high", status: "suspicious", summary: "The artifact needs moderator review.", verdict: "suspicious", }, + check: { + status: "clean", + summary: "The artifact needs moderator review.", + redactedFindings: ["status=suspicious; verdict=suspicious"], + }, }), runTruffleHog: vi.fn().mockResolvedValue({ status: "clean", @@ -165,14 +162,19 @@ describe("pre-publication worker", () => { await expect( processPrePublicationAttempt(client, "worker-token", attempt, { - runClawHubReview: vi.fn().mockResolvedValue({ - llmAnalysis: { + runClawScan: vi.fn().mockResolvedValue({ + analysis: { checkedAt: 123, confidence: "high", status: "malicious", summary: "The artifact contains intentional credential exfiltration.", verdict: "malicious", }, + check: { + status: "blocked", + summary: "The artifact contains intentional credential exfiltration.", + redactedFindings: ["status=malicious; verdict=malicious"], + }, }), runTruffleHog: vi.fn().mockResolvedValue({ status: "clean", @@ -241,7 +243,7 @@ describe("pre-publication worker", () => { ); }); - it("blocks secret-positive attempts without running ClawHub review", async () => { + it("blocks secret-positive attempts without running ClawScan", async () => { const client = { action: vi.fn().mockResolvedValue({ status: "blocked" }), }; @@ -250,17 +252,17 @@ describe("pre-publication worker", () => { summary: "TruffleHog found verified secret material.", redactedFindings: ["GitHub token in filesystem"], }); - const runClawHubReview = vi.fn(); + const runClawScan = vi.fn(); await expect( processPrePublicationAttempt(client, "worker-token", attempt, { - runClawHubReview, + runClawScan, runTruffleHog, writeWorkspace: vi.fn().mockResolvedValue(undefined), }), ).resolves.toMatchObject({ completed: true }); - expect(runClawHubReview).not.toHaveBeenCalled(); + expect(runClawScan).not.toHaveBeenCalled(); expect(client.action).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ @@ -288,7 +290,7 @@ describe("pre-publication worker", () => { await expect( processPrePublicationAttempt(client, "worker-token", attempt, { - runClawHubReview: vi.fn(), + runClawScan: vi.fn(), runTruffleHog, writeWorkspace: vi.fn().mockResolvedValue(undefined), }), @@ -311,7 +313,7 @@ describe("pre-publication worker", () => { const client = { action: vi.fn().mockResolvedValue({ status: "finalized" }), }; - const runClawHubReview = vi.fn(); + const runClawScan = vi.fn(); const runTruffleHog = vi.fn(); const writeWorkspace = vi.fn(); @@ -321,7 +323,7 @@ describe("pre-publication worker", () => { "worker-token", { ...attempt, status: "ready_to_finalize", files: [] }, { - runClawHubReview, + runClawScan, runTruffleHog, writeWorkspace, }, @@ -330,7 +332,7 @@ describe("pre-publication worker", () => { expect(writeWorkspace).not.toHaveBeenCalled(); expect(runTruffleHog).not.toHaveBeenCalled(); - expect(runClawHubReview).not.toHaveBeenCalled(); + expect(runClawScan).not.toHaveBeenCalled(); expect(client.action).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ @@ -341,7 +343,7 @@ describe("pre-publication worker", () => { ); }); - it("passes package ClawPack and manifest context into the ClawHub review job", async () => { + it("passes package ClawPack and manifest context into the ClawScan job", async () => { const packageAttempt = { ...attempt, kind: "package" as const, @@ -367,14 +369,18 @@ describe("pre-publication worker", () => { await expect( processPrePublicationAttempt(client, "worker-token", packageAttempt, { - runClawHubReview: vi.fn().mockResolvedValue({ - llmAnalysis: { + runClawScan: vi.fn().mockResolvedValue({ + analysis: { checkedAt: 123, confidence: "high", status: "clean", - summary: "ClawHub security review passed.", + summary: "ClawScan passed.", verdict: "benign", }, + check: { + status: "clean", + summary: "ClawScan passed.", + }, }), runTruffleHog: vi.fn().mockResolvedValue({ status: "clean", @@ -415,7 +421,7 @@ describe("pre-publication worker", () => { files: [{ ...attempt.files[0], url: null }], }, { - runClawHubReview: vi.fn(), + runClawScan: vi.fn(), runTruffleHog: vi.fn(), writeWorkspace: vi.fn().mockResolvedValue(undefined), }, @@ -437,6 +443,78 @@ describe("pre-publication worker", () => { ); }); + it("runs native ClawScan as the required non-shadow security gate", async () => { + const workspace = await tempDir(); + await mkdir(join(workspace, "artifact"), { recursive: true }); + await writeFile(join(workspace, "artifact", "SKILL.md"), "# Demo\n"); + const fakeClawScan = join(workspace, "fake-clawscan"); + await writeFile( + fakeClawScan, + `#!/usr/bin/env bash +set -euo pipefail +printf '%s\n' "$@" > "${workspace}/clawscan-args.txt" +output="" +while [ "$#" -gt 0 ]; do + if [ "$1" = "--output" ]; then + output="$2" + break + fi + shift +done +cat > "$output" <<'JSON' +{"schemaVersion":"clawscan-run-v1","profile":"clawhub","scanners":{"clawscan-static":{"status":"completed"},"skillspector":{"status":"completed"}},"judge":{"status":"completed","result":{"verdict":"benign","confidence":"high","summary":"Native ClawScan passed."}}} +JSON +`, + ); + await chmod(fakeClawScan, 0o755); + const previousCommand = process.env.PREPUBLICATION_CLAWSCAN_COMMAND; + const previousSandbox = process.env.PREPUBLICATION_CLAWSCAN_SANDBOX; + process.env.PREPUBLICATION_CLAWSCAN_COMMAND = fakeClawScan; + delete process.env.PREPUBLICATION_CLAWSCAN_SANDBOX; + + try { + await expect( + runNativeClawScan( + { + job: { + _id: String(attempt.attemptId), + attempts: 1, + hasMaliciousSignal: false, + leaseToken: attempt.claimId, + source: "pre-publication", + targetKind: "skillVersion", + waitForVtUntil: 0, + }, + target: {}, + }, + workspace, + ), + ).resolves.toEqual( + expect.objectContaining({ + analysis: expect.objectContaining({ + status: "clean", + verdict: "benign", + }), + check: { + status: "clean", + summary: "Native ClawScan passed.", + }, + }), + ); + + const args = await readFile(join(workspace, "clawscan-args.txt"), "utf8"); + expect(args).toContain("./artifact"); + expect(args).toContain("--profile\nclawhub"); + expect(args).toContain("--output\n"); + expect(args).not.toContain("--sandbox"); + } finally { + if (previousCommand === undefined) delete process.env.PREPUBLICATION_CLAWSCAN_COMMAND; + else process.env.PREPUBLICATION_CLAWSCAN_COMMAND = previousCommand; + if (previousSandbox === undefined) delete process.env.PREPUBLICATION_CLAWSCAN_SANDBOX; + else process.env.PREPUBLICATION_CLAWSCAN_SANDBOX = previousSandbox; + } + }); + it("maps TruffleHog verified-secret exit code to a blocked result", async () => { const workspace = await tempDir(); await mkdir(join(workspace, "artifact"), { recursive: true }); diff --git a/scripts/security/run-prepublication-worker.ts b/scripts/security/run-prepublication-worker.ts index 3c1165c4..d05b5a74 100644 --- a/scripts/security/run-prepublication-worker.ts +++ b/scripts/security/run-prepublication-worker.ts @@ -1,13 +1,11 @@ import { spawn } from "node:child_process"; -import { mkdirSync } from "node:fs"; -import { mkdtemp, rm } from "node:fs/promises"; +import { mkdtemp, readFile, rm, stat } from "node:fs/promises"; import { tmpdir } from "node:os"; import { basename, join, resolve } from "node:path"; import { pathToFileURL } from "node:url"; import { ConvexHttpClient } from "convex/browser"; import { api } from "../../convex/_generated/api"; import type { Id } from "../../convex/_generated/dataModel"; -import { assertCodexWorkerExecutionAllowed, resolveCodexWorkerHome } from "../codex-worker-guard"; import { createWorkerLogger } from "../lib/workerLogger"; import { maskKnownWorkerSecrets, @@ -15,11 +13,7 @@ import { redactWorkerPublicText, } from "../lib/workerRedaction"; import { - runCodex, - runSkillSpector, - resolveSkillSpectorScanInputs, type ClaimedJob, - type SkillSpectorAnalysis, type StoredLlmAnalysis, writeArtifactWorkspace, } from "./run-codex-scan-worker"; @@ -69,11 +63,13 @@ type TruffleHogResult = WorkerCheckResult & { exitCode?: number | null; }; +type ClawScanResult = { + check: WorkerCheckResult; + analysis?: StoredLlmAnalysis; +}; + type ProcessAttemptDeps = { - runClawHubReview?: ( - job: ClaimedJob, - workspace: string, - ) => Promise<{ llmAnalysis: StoredLlmAnalysis; skillSpectorAnalysis?: SkillSpectorAnalysis }>; + runClawScan?: (job: ClaimedJob, workspace: string) => Promise<ClawScanResult>; runTruffleHog?: (workspace: string) => Promise<TruffleHogResult>; writeWorkspace?: (job: ClaimedJob, workspace: string) => Promise<void>; }; @@ -86,13 +82,8 @@ const DEFAULT_TRUFFLEHOG_IMAGE = const TRUFFLEHOG_SECRET_EXIT_CODE = 183; const MAX_TRUFFLEHOG_FINDINGS = 10; const MAX_PUBLIC_SUMMARY_CHARS = 600; -const LOCAL_CODEX_HOME = join(rootDir(), ".codex/runtime/codex-workers/prepublication"); const logger = createWorkerLogger({ name: "prepublication-worker" }); -function rootDir() { - return resolve(new URL("../..", import.meta.url).pathname); -} - function parseArgs() { const args = process.argv.slice(2); const get = (name: string) => { @@ -331,57 +322,178 @@ export async function runNativeTruffleHog(workspace: string): Promise<TruffleHog }; } -export async function runNormalClawHubReview(job: ClaimedJob, workspace: string) { - const skillSpectorAnalysis = await runSkillSpectorIfApplicable(job, workspace); - const llmAnalysis = await runCodex(job, workspace, skillSpectorAnalysis, () => {}); - return { llmAnalysis, skillSpectorAnalysis }; +function clawScanTimeoutMs() { + const parsed = Number(process.env.PREPUBLICATION_CLAWSCAN_TIMEOUT_MS); + return Number.isFinite(parsed) && parsed > 0 ? parsed : 240_000; } -async function runSkillSpectorIfApplicable(job: ClaimedJob, workspace: string) { - const inputs = await resolveSkillSpectorScanInputs(workspace, job); - if (inputs.length === 0) return undefined; - return await runSkillSpector(workspace, inputs, () => {}); +function clawScanCommand() { + return process.env.PREPUBLICATION_CLAWSCAN_COMMAND?.trim() || "clawscan"; } -function clawHubReviewCheckResult(llmAnalysis: StoredLlmAnalysis): WorkerCheckResult { - const status = (llmAnalysis.status || llmAnalysis.verdict || "").trim().toLowerCase(); - const verdict = (llmAnalysis.verdict || "").trim().toLowerCase(); +async function fileExists(path: string) { + return Boolean(await stat(path).catch(() => null)); +} + +async function resolveNativeClawScanTarget(workspace: string, job: ClaimedJob) { + if (job.job.targetKind === "packageRelease") { + const packageRoot = join(workspace, "artifact", "package"); + if (await fileExists(join(packageRoot, "package.json"))) return "./artifact/package"; + } + return "./artifact"; +} + +function asRecord(value: unknown): Record<string, unknown> | undefined { + if (!value || typeof value !== "object" || Array.isArray(value)) return undefined; + return value as Record<string, unknown>; +} + +function readString(record: Record<string, unknown> | undefined, names: string[]) { + if (!record) return undefined; + for (const name of names) { + const value = record[name]; + if (typeof value === "string" && value.trim()) return value.trim(); + } + return undefined; +} + +function verdictToStoredStatus(verdict: string | undefined): StoredLlmAnalysis["status"] { + const normalized = verdict?.trim().toLowerCase(); + if (normalized === "benign" || normalized === "clean") return "clean"; + if (normalized === "suspicious") return "suspicious"; + if (normalized === "malicious") return "malicious"; + return "pending"; +} + +function collectClawScanScannerFailures(scanners: Record<string, unknown> | undefined) { + if (!scanners) return []; + const failures: string[] = []; + for (const [scanner, value] of Object.entries(scanners)) { + const scannerRecord = asRecord(value); + const status = readString(scannerRecord, ["status"]) ?? "unknown"; + if (status !== "completed") failures.push(`${scanner}=${status}`); + } + return failures; +} + +function storedAnalysisFromClawScanArtifact(artifact: unknown): { + analysis?: StoredLlmAnalysis; + error?: string; +} { + const record = asRecord(artifact); + const judge = asRecord(record?.judge); + const result = asRecord(judge?.result); + const judgeStatus = readString(judge, ["status"]); + const scannerFailures = collectClawScanScannerFailures(asRecord(record?.scanners)); + if (scannerFailures.length > 0) { + return { error: `ClawScan scanner did not complete: ${scannerFailures.join(", ")}` }; + } + if (judgeStatus !== "completed") { + return { error: `ClawScan judge status was ${judgeStatus ?? "missing"}` }; + } + const verdict = readString(result, ["verdict", "status"]); + if (!verdict) return { error: "ClawScan judge did not return a verdict" }; + + const dimensions = Array.isArray(result?.dimensions) + ? (result.dimensions as StoredLlmAnalysis["dimensions"]) + : undefined; + const confidence = readString(result, ["confidence"]); + const findings = readString(result, ["findings"]); + const guidance = readString(result, ["guidance"]); + const model = readString(result, ["model"]); + const summary = readString(result, ["summary"]); + return { + analysis: { + checkedAt: Date.now(), + status: verdictToStoredStatus(verdict), + verdict, + ...(confidence ? { confidence } : {}), + ...(dimensions ? { dimensions } : {}), + ...(findings ? { findings } : {}), + ...(guidance ? { guidance } : {}), + ...(model ? { model } : {}), + ...(summary ? { summary } : {}), + }, + }; +} + +function clawScanCheckResult(analysis: StoredLlmAnalysis): WorkerCheckResult { + const status = (analysis.status || analysis.verdict || "").trim().toLowerCase(); + const verdict = (analysis.verdict || "").trim().toLowerCase(); const normalizedVerdict = verdict || status; const summary = publicText( - llmAnalysis.summary ?? - llmAnalysis.findings ?? - `ClawHub security review returned ${llmAnalysis.status}.`, + analysis.summary ?? analysis.findings ?? `ClawScan returned ${analysis.status}.`, ); if (status === "clean" || normalizedVerdict === "benign") { return { status: "clean", - summary: summary || "ClawHub security review passed.", + summary: summary || "ClawScan passed.", }; } if (normalizedVerdict === "suspicious") { return { status: "clean", - summary: summary || "ClawHub security review requires user attention.", - redactedFindings: [ - publicText(`status=${llmAnalysis.status}; verdict=${llmAnalysis.verdict}`), - ], + summary: summary || "ClawScan returned suspicious review findings.", + redactedFindings: [publicText(`status=${analysis.status}; verdict=${analysis.verdict}`)], }; } if (normalizedVerdict !== "malicious") { return { status: "failed", - summary: summary || "ClawHub security review did not return a final verdict.", - redactedFindings: [ - publicText(`status=${llmAnalysis.status}; verdict=${llmAnalysis.verdict}`), - ], + summary: summary || "ClawScan did not return a final verdict.", + redactedFindings: [publicText(`status=${analysis.status}; verdict=${analysis.verdict}`)], }; } return { status: "blocked", - summary: - summary || - `ClawHub security review blocked the staged publish with status ${llmAnalysis.status}.`, - redactedFindings: [publicText(`status=${llmAnalysis.status}; verdict=${llmAnalysis.verdict}`)], + summary: summary || `ClawScan blocked the staged publish with status ${analysis.status}.`, + redactedFindings: [publicText(`status=${analysis.status}; verdict=${analysis.verdict}`)], + }; +} + +export async function runNativeClawScan( + job: ClaimedJob, + workspace: string, +): Promise<ClawScanResult> { + const artifactPath = join(workspace, "clawscan-result.json"); + const target = await resolveNativeClawScanTarget(workspace, job); + const command = clawScanCommand(); + const args = [target, "--profile", "clawhub", "--output", artifactPath]; + const sandbox = process.env.PREPUBLICATION_CLAWSCAN_SANDBOX?.trim(); + if (sandbox) { + args.push("--sandbox", sandbox); + const sandboxImage = process.env.PREPUBLICATION_CLAWSCAN_SANDBOX_IMAGE?.trim(); + if (sandbox === "docker" && sandboxImage) args.push("--sandbox-image", sandboxImage); + } + + const output = await runCommand(command, args, { + cwd: workspace, + timeoutMs: clawScanTimeoutMs(), + }); + if (output.code !== 0) { + return { + check: { + status: "failed", + summary: publicText( + `ClawScan failed before returning a verdict: ${output.stderr || output.stdout}`, + ), + }, + }; + } + + const raw = await readFile(artifactPath, "utf8"); + const parsed = storedAnalysisFromClawScanArtifact(JSON.parse(raw) as unknown); + if (parsed.error || !parsed.analysis) { + return { + check: { + status: "failed", + summary: publicText(parsed.error ?? "ClawScan did not return a usable result."), + }, + }; + } + return { + analysis: parsed.analysis, + check: clawScanCheckResult(parsed.analysis), }; } @@ -430,7 +542,7 @@ export async function processPrePublicationAttempt( }, { status: "clean", - summary: "Pre-publication ClawHub security review already passed.", + summary: "Pre-publication ClawScan already passed.", }, ); logger.info( @@ -461,7 +573,7 @@ export async function processPrePublicationAttempt( const startedAt = Date.now(); const writeWorkspace = deps.writeWorkspace ?? writeArtifactWorkspace; const runTruffleHog = deps.runTruffleHog ?? runNativeTruffleHog; - const runClawHubReview = deps.runClawHubReview ?? runNormalClawHubReview; + const runClawScan = deps.runClawScan ?? runNativeClawScan; let truffleHogBlocked = false; let completionStarted = false; try { @@ -473,7 +585,7 @@ export async function processPrePublicationAttempt( completionStarted = true; const result = await completeAttempt(client, token, attempt, trufflehog, { status: "failed", - summary: "ClawHub security review skipped because TruffleHog blocked the artifact.", + summary: "ClawScan skipped because TruffleHog blocked the artifact.", }); logger.info( { @@ -491,7 +603,7 @@ export async function processPrePublicationAttempt( completionStarted = true; const result = await completeAttempt(client, token, attempt, trufflehog, { status: "failed", - summary: "ClawHub security review skipped because TruffleHog failed.", + summary: "ClawScan skipped because TruffleHog failed.", }); return { completed: false, result }; } @@ -499,9 +611,9 @@ export async function processPrePublicationAttempt( let clawscan: WorkerCheckResult; let clawscanAnalysis: StoredLlmAnalysis | undefined; try { - const review = await runClawHubReview(job, workspace); - clawscanAnalysis = review.llmAnalysis; - clawscan = clawHubReviewCheckResult(review.llmAnalysis); + const review = await runClawScan(job, workspace); + clawscanAnalysis = review.analysis; + clawscan = review.check; } catch (error) { clawscan = { status: "failed", @@ -659,7 +771,6 @@ export function claimBatchDrainedQueue( async function main() { const { batchLimit, maxJobs, maxRuntimeMs } = parseArgs(); - assertCodexWorkerExecutionAllowed(process.env); maskKnownWorkerSecrets(); const convexUrl = process.env.CONVEX_URL ?? process.env.VITE_CONVEX_URL; if (!convexUrl) throw new Error("CONVEX_URL or VITE_CONVEX_URL is required"); @@ -706,15 +817,6 @@ async function main() { ); } -export function configurePrePublicationCodexHome(env: NodeJS.ProcessEnv = process.env) { - const codexHome = resolveCodexWorkerHome(env, LOCAL_CODEX_HOME); - if (!codexHome) return undefined; - env.CODEX_HOME = codexHome; - mkdirSync(codexHome, { recursive: true }); - return codexHome; -} - if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) { - configurePrePublicationCodexHome(); await main(); } diff --git a/src/__tests__/plugins-publish-route.test.tsx b/src/__tests__/plugins-publish-route.test.tsx index 2399b942..fd8253a6 100644 --- a/src/__tests__/plugins-publish-route.test.tsx +++ b/src/__tests__/plugins-publish-route.test.tsx @@ -6,7 +6,8 @@ import { getFunctionName } from "convex/server"; import { createElement } from "react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -const { toastErrorMock } = vi.hoisted(() => ({ +const { navigateMock, toastErrorMock } = vi.hoisted(() => ({ + navigateMock: vi.fn(), toastErrorMock: vi.fn(), })); @@ -15,6 +16,7 @@ vi.mock("@tanstack/react-router", () => ({ __config: config, __path: path, }), + useNavigate: () => navigateMock, useSearch: () => useSearchMock(), })); @@ -103,6 +105,7 @@ describe("plugins publish route", () => { useAuthStatusMock.mockReset(); useQueryMock.mockReset(); useSearchMock.mockReset(); + navigateMock.mockReset(); toastErrorMock.mockReset(); useSearchMock.mockReturnValue({ @@ -834,7 +837,7 @@ describe("plugins publish route", () => { expect(publishRelease).not.toHaveBeenCalled(); }); - it("shows pending verification messaging after staged plugin publish", async () => { + it("redirects to the dashboard after a staged plugin publish is accepted", async () => { publishRelease.mockResolvedValueOnce({ ok: true, status: "pending", @@ -876,12 +879,11 @@ describe("plugins publish route", () => { fireEvent.click(screen.getByRole("button", { name: "Publish plugin" })); - expect( - await screen.findByText(/Running TruffleHog and ClawScan before public listing\./i), - ).toBeTruthy(); - expect( - screen.getByRole("button", { name: "Publish plugin" }).getAttribute("disabled"), - ).not.toBeNull(); + await waitFor(() => { + expect(navigateMock).toHaveBeenCalledWith({ to: "/dashboard" }); + }); + expect(screen.queryByText(/Running TruffleHog and ClawScan/i)).toBeNull(); + expect(screen.queryByText("Publishing release...")).toBeNull(); }); it("warns when README references relative image paths but no source repo/commit is set", async () => { diff --git a/src/__tests__/skills-publish-route.test.tsx b/src/__tests__/skills-publish-route.test.tsx index 37023134..182d74d2 100644 --- a/src/__tests__/skills-publish-route.test.tsx +++ b/src/__tests__/skills-publish-route.test.tsx @@ -6,10 +6,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { api } from "../../convex/_generated/api"; import { Upload } from "../routes/skills/publish"; +const navigateMock = vi.fn(); + vi.mock("@tanstack/react-router", () => ({ Link: ({ children, to }: { children: ReactNode; to: string }) => <a href={to}>{children}</a>, createFileRoute: () => (config: { component: unknown }) => config, - useNavigate: () => vi.fn(), + useNavigate: () => navigateMock, useSearch: () => useSearchMock(), })); @@ -61,6 +63,7 @@ describe("Upload route", () => { useQueryMock.mockReset(); useAuthStatusMock.mockReset(); useSearchMock.mockReset(); + navigateMock.mockReset(); useSearchMock.mockReturnValue({ updateSlug: undefined, ownerHandle: undefined }); useActionCallCount = 0; useAuthStatusMock.mockReturnValue({ @@ -967,7 +970,7 @@ describe("Upload route", () => { expect(Object.hasOwn(args!, "icon")).toBe(false); }); - it("keeps publish disabled after staged skill publish is accepted", async () => { + it("redirects to the dashboard after a staged skill publish is accepted", async () => { generateUploadUrl.mockResolvedValue("https://upload.local"); publishVersion.mockResolvedValueOnce({ status: "pending", @@ -1004,10 +1007,11 @@ describe("Upload route", () => { }); fireEvent.click(publishButton); - expect( - await screen.findByText(/Running TruffleHog and ClawScan before public listing\./i), - ).toBeTruthy(); - expect(publishButton.getAttribute("disabled")).not.toBeNull(); + await waitFor(() => { + expect(navigateMock).toHaveBeenCalledWith({ to: "/dashboard" }); + }); + expect(screen.queryByText(/Running TruffleHog and ClawScan/i)).toBeNull(); + expect(screen.queryByText("Publishing…")).toBeNull(); }); it("omits icon when republishing a skill that still has a stored legacy icon", async () => { diff --git a/src/routes/plugins/publish.tsx b/src/routes/plugins/publish.tsx index 34cc3378..969f96c5 100644 --- a/src/routes/plugins/publish.tsx +++ b/src/routes/plugins/publish.tsx @@ -1,4 +1,4 @@ -import { createFileRoute, useSearch } from "@tanstack/react-router"; +import { createFileRoute, useNavigate, useSearch } from "@tanstack/react-router"; import { DocsLinks, getPackageScopeOwnerMismatch, isPluginCategorySlug } from "clawhub-schema"; import { useAction, useMutation, useQuery } from "convex/react"; import { ExternalLink, Info, Lock } from "lucide-react"; @@ -189,6 +189,7 @@ function PluginPublishError({ message }: { message: string }) { export function PublishPluginRoute() { const search = useSearch({ from: "/plugins/publish" }); + const navigate = useNavigate(); const { isAuthenticated, isLoading: isAuthLoading, me } = useAuthStatus(); const publishers = useQuery(api.publishers.listMine, me ? {} : "skip") as | Array<PublisherOwnerMembership> @@ -822,14 +823,12 @@ export function PublishPluginRoute() { return; } setIsSubmitting(true); - setStatus("Uploading files..."); setError(null); const uploaded = await buildPackageUploadEntries(files, { generateUploadUrl, hashFile, uploadFile, }); - setStatus("Publishing release..."); const result = await publishRelease({ payload: { name: name.trim(), @@ -879,9 +878,8 @@ export function PublishPluginRoute() { "status" in result && result.status === "pending" ) { - setStatus( - "Publish received. Running TruffleHog and ClawScan before public listing.", - ); + setStatus(null); + void navigate({ to: "/dashboard" }); } else { setStatus( "Published. Pending security checks and verification before public listing.", diff --git a/src/routes/skills/publish.tsx b/src/routes/skills/publish.tsx index e675a659..1ef7fc80 100644 --- a/src/routes/skills/publish.tsx +++ b/src/routes/skills/publish.tsx @@ -721,7 +721,6 @@ export function Upload() { return; } setIsSubmitting(true); - setStatus("Uploading files…"); try { const uploaded = [] as Array<{ path: string; @@ -749,7 +748,6 @@ export function Upload() { }); } - setStatus("Publishing…"); const result = await publishVersion({ ownerHandle: ownerHandle || undefined, sourceOwnerHandle: @@ -779,8 +777,8 @@ export function Upload() { setChangelogSource("user"); if (result) { if (typeof result === "object" && "status" in result && result.status === "pending") { - setStatus("Publish received. Running TruffleHog and ClawScan before public listing."); toast.success("Publish received. Security checks are running."); + void navigate({ to: "/dashboard" }); return; } const ownerParam = ownerHandle || me?.handle || (me?._id ? String(me._id) : "unknown");