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 =
},
});
+export const getPrePublicationSkillAttemptState: ReturnType =
+ 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) => {
- 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) => {
+ 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) => {
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) =>
+ "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) => {
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) =>
+ "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) => {
+ 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, field: string) {
return typeof value === "string" ? value : "";
}
+function buildSkillPublishAttemptScanContext(insertArgs: unknown) {
+ const record =
+ insertArgs && typeof insertArgs === "object" ? (insertArgs as Record) : {};
+ const parsed =
+ record.parsed && typeof record.parsed === "object"
+ ? (record.parsed as Record)
+ : {};
+ 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, "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 | null>(
+ existingRelease = await runQueryRef | 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 }>(
+ 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) {
+ const verification =
+ insertArgs.verification &&
+ typeof insertArgs.verification === "object" &&
+ !Array.isArray(insertArgs.verification)
+ ? (insertArgs.verification as Record)
+ : {};
+ 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)
+ : {};
+}
+
+function stringPendingField(metadata: Record, field: string, fallback?: string) {
+ const value = metadata[field];
+ return typeof value === "string" ? value : fallback;
+}
+
+function booleanPendingField(metadata: Record, field: string, fallback: boolean) {
+ const value = metadata[field];
+ return typeof value === "boolean" ? value : fallback;
+}
+
+function stringArrayPendingField(metadata: Record, 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>();
+ 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;
}
)._handler;
+const recordSkillFinalizedHandler = (
+ recordSkillPublishAttemptFinalizedInternal as unknown as {
+ _handler: (ctx: unknown, args: unknown) => Promise;
+ }
+)._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>(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 = 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["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 = {};
+ 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>();
+ 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> = { ...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, "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>) {
+ const filters = new Map();
+ 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();
- 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([
["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();
- 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([
@@ -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([
+ ["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;
+ 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 => {
+ 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;
-
-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[0], detailPath: string) {
let lastError: unknown;
for (let attempt = 1; attempt <= 4; attempt += 1) {
@@ -171,72 +40,47 @@ async function getNewVersionHref(page: Parameters[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(functionName: string, args: Record) {
+ 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("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[0], version
.filter({ hasText: new RegExp(`^v${version.replaceAll(".", "\\.")}`) });
}
+async function ensureVersionsTab(page: Parameters[0]) {
+ await recoverFromTransientErrorScreen(page);
+ await page.getByRole("tab", { name: "Versions" }).click({ timeout: 30_000 });
+}
+
async function openDeleteDialog(page: Parameters[0]) {
const deleteButton = page.getByRole("button", { name: `Delete version ${OLDER_VERSION}` });
let lastError: unknown;
@@ -239,31 +248,85 @@ async function openDeleteDialog(page: Parameters[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[0]) {
+ let lastError: unknown;
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
+ let deleteButton: ReturnType | 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[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[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":"AAAA,OAAO,EAAiB,IAAI,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAE1D,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;IACrC,QAAQ,EAAE,QAAQ;IAClB,KAAK,EAAE,SAAS;CACjB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;IACxC,OAAO,EAAE,QAAQ;IACjB,QAAQ,EAAE,SAAS;IACnB,aAAa,EAAE,SAAS;CACzB,CAAC,CAAC,EAAE,CAAC;IACJ,QAAQ,EAAE,QAAQ;IAClB,QAAQ,EAAE,SAAS;IACnB,aAAa,EAAE,SAAS;CACzB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC;IACjC,OAAO,EAAE,GAAG;IACZ,MAAM,EAAE;QACN,UAAU,EAAE;YACV,OAAO,EAAE,aAAa;YACtB,WAAW,EAAE,QAAQ;YACrB,WAAW,EAAE,SAAS;YACtB,MAAM,EAAE,UAAU;YAClB,SAAS,EAAE,SAAS;SACrB;KACF;CACF,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,CAAC;IAC7C,IAAI,EAAE;QACJ,MAAM,EAAE,aAAa;KACtB;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;IAC1C,OAAO,EAAE,IAAI,CAAC;QACZ,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,cAAc;QAC3B,WAAW,EAAE,SAAS;QACtB,OAAO,EAAE,cAAc;QACvB,KAAK,EAAE,QAAQ;KAChB,CAAC,CAAC,KAAK,EAAE;CACX,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,CAAC;IAC7C,aAAa,EAAE,IAAI,CAAC;QAClB,OAAO,EAAE,QAAQ;KAClB,CAAC,CAAC,QAAQ,EAAE;IACb,KAAK,EAAE,eAAe;CACvB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,6BAA6B,GAAG,IAAI,CAAC;IAChD,SAAS,EAAE,QAAQ;IACnB,YAAY,EAAE,QAAQ;CACvB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,CAAC;IAC9C,SAAS,EAAE,QAAQ;CACpB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;IACvC,IAAI,EAAE,QAAQ;IACd,IAAI,EAAE,QAAQ;IACd,SAAS,EAAE,QAAQ;IACnB,MAAM,EAAE,QAAQ;IAChB,WAAW,EAAE,SAAS;CACvB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,CAAC;IACtC,IAAI,EAAE,UAAU;IAChB,GAAG,EAAE,QAAQ;IACb,IAAI,EAAE,QAAQ;IACd,GAAG,EAAE,QAAQ;IACb,MAAM,EAAE,QAAQ;IAChB,IAAI,EAAE,QAAQ;IACd,UAAU,EAAE,QAAQ;CACrB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;IAC1C,IAAI,EAAE,QAAQ;IACd,WAAW,EAAE,QAAQ;IACrB,WAAW,EAAE,SAAS;IACtB,iBAAiB,EAAE,SAAS;IAC5B,YAAY,EAAE,UAAU;IACxB,OAAO,EAAE,QAAQ;IACjB,SAAS,EAAE,QAAQ;IACnB,kBAAkB,EAAE,UAAU;IAC9B,IAAI,EAAE,WAAW;IACjB,UAAU,EAAE,WAAW;IACvB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,mBAAmB,CAAC,QAAQ,EAAE;IACtC,MAAM,EAAE,IAAI,CAAC;QACX,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,SAAS;QACtB,OAAO,EAAE,SAAS;KACnB,CAAC,CAAC,QAAQ,EAAE;IACb,KAAK,EAAE,oBAAoB,CAAC,KAAK,EAAE;CACpC,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,CAAC;IAC9C,EAAE,EAAE,MAAM;IACV,OAAO,EAAE,QAAQ;IACjB,SAAS,EAAE,QAAQ;CACpB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,CAAC;IAC9C,IAAI,EAAE,QAAQ;IACd,MAAM,EAAE,SAAS;CAClB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,+BAA+B,GAAG,IAAI,CAAC;IAClD,EAAE,EAAE,MAAM;IACV,iBAAiB,EAAE,SAAS;CAC7B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,6BAA6B,GAAG,IAAI,CAAC;IAChD,KAAK,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC;IAC7C,aAAa,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC;CACtD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,sCAAsC,GAAG,IAAI,CAAC;IACzD,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,QAAQ;IACd,WAAW,EAAE,WAAW;IACxB,OAAO,EAAE;QACP,OAAO,EAAE,QAAQ;QACjB,WAAW,EAAE,QAAQ;KACtB;CACF,CAAC;KACC,EAAE,CAAC;IACF,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,QAAQ;IACd,WAAW,EAAE,UAAU;IACvB,MAAM,EAAE;QACN,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,QAAQ;QACd,MAAM,EAAE,QAAQ;QAChB,WAAW,EAAE,QAAQ;QACrB,SAAS,EAAE,QAAQ;KACpB;CACF,CAAC;KACD,EAAE,CAAC;IACF,EAAE,EAAE,OAAO;IACX,IAAI,EAAE,QAAQ;IACd,MAAM,EACJ,oLAAoL;IACtL,OAAO,EAAE,QAAQ;IACjB,MAAM,EAAE,QAAQ;CACjB,CAAC,CAAC;AAIL,MAAM,CAAC,MAAM,gCAAgC,GAAG,IAAI,CAAC;IACnD,KAAK,EAAE,WAAW;IAClB,IAAI,EAAE,QAAQ;IACd,WAAW,EAAE,SAAS;IACtB,OAAO,EAAE,SAAS;IAClB,uEAAuE;IACvE,MAAM,EAAE,SAAS;IACjB,SAAS,EAAE,SAAS;CACrB,CAAC,CAAC,EAAE,CAAC;IACJ,6EAA6E;IAC7E,KAAK,EAAE,IAAI,CAAC;QACV,MAAM,EAAE,QAAQ;QAChB,KAAK,EAAE,QAAQ;QACf,MAAM,EAAE,IAAI,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,cAAc;SACxB,CAAC,CAAC,KAAK,EAAE;KACX,CAAC,CAAC,KAAK,EAAE;CACX,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,oCAAoC,GAAG,IAAI,CAAC;IACvD,EAAE,EAAE,MAAM;CACX,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,CAAC;IAC5C,IAAI,EAAE;QACJ,MAAM,EAAE,aAAa;QACrB,WAAW,EAAE,cAAc;QAC3B,KAAK,EAAE,cAAc;QACrB,IAAI,EAAE,kCAAkC;KACzC;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,6BAA6B,GAAG,IAAI,CAAC;IAChD,KAAK,EAAE,IAAI,CAAC;QACV,MAAM,EAAE,QAAQ;QAChB,MAAM,EAAE,aAAa;QACrB,WAAW,EAAE,cAAc;QAC3B,IAAI,EAAE,cAAc;QACpB,IAAI,EAAE,kCAAkC;KACzC,CAAC,CAAC,KAAK,EAAE;IACV,KAAK,EAAE,QAAQ;CAChB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,kCAAkC,GAAG,IAAI,CAAC;IACrD,EAAE,EAAE,MAAM;IACV,WAAW,EAAE,QAAQ;IACrB,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,MAAM;IACf,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,kCAAkC,GAAG,IAAI,CAAC;IACrD,EAAE,EAAE,MAAM;IACV,WAAW,EAAE,QAAQ;IACrB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,SAAS;IACjB,OAAO,EAAE,SAAS;IAClB,YAAY,EAAE,QAAQ;IACtB,cAAc,EAAE,QAAQ;IACxB,WAAW,EAAE,QAAQ;CACtB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,mCAAmC,GAAG,IAAI,CAAC;IACtD,EAAE,EAAE,MAAM;IACV,WAAW,EAAE,QAAQ;IACrB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,SAAS;IACjB,WAAW,EAAE,SAAS;IACtB,YAAY,EAAE,QAAQ;IACtB,cAAc,EAAE,QAAQ;IACxB,WAAW,EAAE,QAAQ;IACrB,aAAa,EAAE,QAAQ;IACvB,oBAAoB,EAAE,QAAQ;IAC9B,iBAAiB,EAAE,SAAS;IAC5B,iBAAiB,EAAE,QAAQ;CAC5B,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,iCAAiC,GAAG,IAAI,CAAC;IACpD,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,MAAM;IACZ,SAAS,EAAE,IAAI,CAAC;QACd,KAAK,EAAE,QAAQ;QACf,SAAS,EAAE,QAAQ;QACnB,SAAS,EAAE,aAAa;KACzB,CAAC;IACF,OAAO,EAAE,QAAQ;IACjB,QAAQ,EAAE,QAAQ;IAClB,UAAU,EAAE,aAAa;CAC1B,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,CAAC;IAC5C,OAAO,EAAE,IAAI,CAAC;QACZ,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,cAAc;QAC3B,WAAW,EAAE,SAAS;QACtB,OAAO,EAAE,cAAc;QACvB,OAAO,EAAE,cAAc;QACvB,KAAK,EAAE,QAAQ;QACf,SAAS,EAAE,SAAS;QACpB,SAAS,EAAE,SAAS;QACpB,KAAK,EAAE,IAAI,CAAC;YACV,MAAM,EAAE,cAAc;YACtB,WAAW,EAAE,cAAc;YAC3B,KAAK,EAAE,cAAc;SACtB,CAAC;aACC,EAAE,CAAC,MAAM,CAAC;aACV,QAAQ,EAAE;KACd,CAAC,CAAC,KAAK,EAAE;CACX,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,4BAA4B,GAAG,IAAI,CAAC;IAC/C,KAAK,EAAE,IAAI,CAAC;QACV,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,QAAQ;QACrB,OAAO,EAAE,cAAc;QACvB,WAAW,EAAE,cAAc;QAC3B,MAAM,EAAE,WAAW;QACnB,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,SAAS;QAChB,SAAS,EAAE,QAAQ;QACnB,SAAS,EAAE,QAAQ;QACnB,aAAa,EAAE,IAAI,CAAC;YAClB,OAAO,EAAE,QAAQ;YACjB,SAAS,EAAE,QAAQ;YACnB,SAAS,EAAE,QAAQ;YACnB,OAAO,EAAE,0BAA0B,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE;SAC1D,CAAC,CAAC,QAAQ,EAAE;QACb,QAAQ,EAAE,IAAI,CAAC;YACb,KAAK,EAAE,IAAI,CAAC;gBACV,GAAG,EAAE,QAAQ;gBACb,QAAQ,EAAE,SAAS;aACpB,CAAC,CAAC,KAAK,EAAE;YACV,EAAE,EAAE,gBAAgB;YACpB,OAAO,EAAE,gBAAgB;SAC1B,CAAC;aACC,EAAE,CAAC,MAAM,CAAC;aACV,QAAQ,EAAE;KACd,CAAC,CAAC,KAAK,EAAE;IACV,UAAU,EAAE,aAAa;CAC1B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC;IAC3C,KAAK,EAAE,IAAI,CAAC;QACV,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,QAAQ;QACrB,OAAO,EAAE,cAAc;QACvB,WAAW,EAAE,cAAc;QAC3B,MAAM,EAAE,WAAW;QACnB,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,SAAS;QAChB,SAAS,EAAE,QAAQ;QACnB,SAAS,EAAE,QAAQ;KACpB,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC;IACb,aAAa,EAAE,IAAI,CAAC;QAClB,OAAO,EAAE,QAAQ;QACjB,SAAS,EAAE,QAAQ;QACnB,SAAS,EAAE,QAAQ;QACnB,OAAO,EAAE,0BAA0B,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE;KAC1D,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC;IACb,QAAQ,EAAE,IAAI,CAAC;QACb,KAAK,EAAE,IAAI,CAAC;YACV,GAAG,EAAE,QAAQ;YACb,QAAQ,EAAE,SAAS;SACpB,CAAC,CAAC,KAAK,EAAE;QACV,EAAE,EAAE,gBAAgB;QACpB,OAAO,EAAE,gBAAgB;KAC1B,CAAC;SACC,EAAE,CAAC,MAAM,CAAC;SACV,QAAQ,EAAE;IACb,KAAK,EAAE,IAAI,CAAC;QACV,MAAM,EAAE,aAAa;QACrB,WAAW,EAAE,cAAc;QAC3B,KAAK,EAAE,cAAc;KACtB,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC;IACb,UAAU,EAAE,IAAI,CAAC;QACf,YAAY,EAAE,SAAS;QACvB,gBAAgB,EAAE,SAAS;QAC3B,OAAO,EAAE,mCAAmC;QAC5C,WAAW,EAAE,WAAW;QACxB,SAAS,EAAE,cAAc;QACzB,aAAa,EAAE,cAAc;QAC7B,OAAO,EAAE,cAAc;KACxB,CAAC;SACC,EAAE,CAAC,MAAM,CAAC;SACV,QAAQ,EAAE;CACd,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,kCAAkC,GAAG,IAAI,CAAC;IACrD,UAAU,EAAE,IAAI,CAAC;QACf,YAAY,EAAE,SAAS;QACvB,gBAAgB,EAAE,SAAS;QAC3B,OAAO,EAAE,kCAAkC;QAC3C,WAAW,EAAE,UAAU;QACvB,SAAS,EAAE,cAAc;QACzB,aAAa,EAAE,cAAc;QAC7B,OAAO,EAAE,cAAc;QACvB,YAAY,EAAE,cAAc;QAC5B,QAAQ,EAAE,IAAI,CAAC;YACb,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE,0BAA0B;YACpC,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,QAAQ;YACjB,QAAQ,EAAE,QAAQ;SACnB,CAAC,CAAC,KAAK,EAAE;KACX,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC;CACd,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,+BAA+B,GAAG,IAAI,CAAC;IAClD,KAAK,EAAE,WAAW;IAClB,MAAM,EAAE,QAAQ;IAChB,WAAW,EAAE,SAAS;CACvB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,qCAAqC,GAAG,IAAI,CAAC;IACxD,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,QAAQ;IACjB,OAAO,EAAE,QAAQ;IACjB,SAAS,EAAE,QAAQ;IACnB,cAAc,EAAE,SAAS;IACzB,kBAAkB,EAAE,aAAa;IACjC,WAAW,EAAE,SAAS;CACvB,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC,gCAAgC,CAAC,CAAC;AAE9E,MAAM,CAAC,MAAM,4BAA4B,GAAG,IAAI,CAAC,eAAe,CAAC,CAAC;AAGlE,MAAM,CAAC,MAAM,2BAA2B,GAAG,uBAAuB,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;AAG/E,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC,8BAA8B,CAAC,CAAC;AAE5E,MAAM,CAAC,MAAM,4BAA4B,GAAG,IAAI,CAAC,kBAAkB,CAAC,CAAC;AAGrE,MAAM,CAAC,MAAM,2BAA2B,GAAG,uBAAuB,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;AAG/E,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC;IAC3C,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,QAAQ;CAClB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,8BAA8B,GAAG,IAAI,CAAC;IACjD,EAAE,EAAE,MAAM;IACV,QAAQ,EAAE,SAAS;IACnB,eAAe,EAAE,SAAS;IAC1B,QAAQ,EAAE,QAAQ;IAClB,OAAO,EAAE,QAAQ;IACjB,WAAW,EAAE,QAAQ;CACtB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,8BAA8B,GAAG,IAAI,CAAC;IACjD,EAAE,EAAE,MAAM;IACV,SAAS,EAAE,SAAS;IACpB,WAAW,EAAE,SAAS;IACtB,QAAQ,EAAE,QAAQ;IAClB,OAAO,EAAE,QAAQ;IACjB,MAAM,EAAE,uBAAuB;CAChC,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,8BAA8B,GAAG,IAAI,CAAC;IACjD,MAAM,EAAE,uBAAuB;IAC/B,IAAI,EAAE,SAAS;IACf,WAAW,EAAE,4BAA4B,CAAC,QAAQ,EAAE;CACrD,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,+BAA+B,GAAG,IAAI,CAAC;IAClD,MAAM,EAAE,uBAAuB;IAC/B,IAAI,EAAE,SAAS;IACf,WAAW,EAAE,4BAA4B,CAAC,QAAQ,EAAE;CACrD,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,kCAAkC,GAAG,IAAI,CAAC;IACrD,KAAK,EAAE,IAAI,CAAC;QACV,QAAQ,EAAE,QAAQ;QAClB,OAAO,EAAE,QAAQ;QACjB,cAAc,EAAE,cAAc;QAC9B,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,QAAQ;QACrB,OAAO,EAAE,cAAc;QACvB,MAAM,EAAE,cAAc;QACtB,MAAM,EAAE,uBAAuB;QAC/B,SAAS,EAAE,QAAQ;QACnB,QAAQ,EAAE,IAAI,CAAC;YACb,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,cAAc;YACtB,WAAW,EAAE,cAAc;SAC5B,CAAC;QACF,SAAS,EAAE,cAAc;QACzB,SAAS,EAAE,cAAc;QACzB,UAAU,EAAE,cAAc;QAC1B,WAAW,EAAE,4BAA4B,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE;KAChE,CAAC,CAAC,KAAK,EAAE;IACV,UAAU,EAAE,aAAa;IACzB,IAAI,EAAE,SAAS;CAChB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,oCAAoC,GAAG,IAAI,CAAC;IACvD,EAAE,EAAE,MAAM;IACV,QAAQ,EAAE,QAAQ;IAClB,OAAO,EAAE,QAAQ;IACjB,MAAM,EAAE,uBAAuB;IAC/B,WAAW,EAAE,QAAQ;IACrB,WAAW,EAAE,4BAA4B,CAAC,QAAQ,EAAE;CACrD,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,kCAAkC,GAAG,IAAI,CAAC;IACrD,KAAK,EAAE,IAAI,CAAC;QACV,QAAQ,EAAE,QAAQ;QAClB,OAAO,EAAE,QAAQ;QACjB,cAAc,EAAE,cAAc;QAC9B,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,QAAQ;QACrB,OAAO,EAAE,cAAc;QACvB,OAAO,EAAE,QAAQ;QACjB,MAAM,EAAE,uBAAuB;QAC/B,SAAS,EAAE,QAAQ;QACnB,SAAS,EAAE,IAAI,CAAC;YACd,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,cAAc;YACtB,WAAW,EAAE,cAAc;SAC5B,CAAC;QACF,UAAU,EAAE,cAAc;QAC1B,UAAU,EAAE,cAAc;QAC1B,cAAc,EAAE,cAAc;QAC9B,WAAW,EAAE,4BAA4B,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE;KAChE,CAAC,CAAC,KAAK,EAAE;IACV,UAAU,EAAE,aAAa;IACzB,IAAI,EAAE,SAAS;CAChB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,qCAAqC,GAAG,IAAI,CAAC;IACxD,EAAE,EAAE,MAAM;IACV,QAAQ,EAAE,QAAQ;IAClB,OAAO,EAAE,QAAQ;IACjB,MAAM,EAAE,uBAAuB;IAC/B,WAAW,EAAE,4BAA4B,CAAC,QAAQ,EAAE;CACrD,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,8BAA8B,GAAG,IAAI,CAAC;IACjD,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,QAAQ;IACjB,OAAO,EAAE,QAAQ;IACjB,cAAc,EAAE,QAAQ;IACxB,KAAK,EAAE,QAAQ;IACf,aAAa,EAAE,SAAS;CACzB,CAAC,CAAC,EAAE,CAAC;IACJ,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,QAAQ;IACjB,OAAO,EAAE,QAAQ;IACjB,iBAAiB,EAAE,QAAQ;IAC3B,KAAK,EAAE,SAAS;IAChB,SAAS,EAAE,SAAS;IACpB,aAAa,EAAE,SAAS;CACzB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,CAAC,yCAAyC,CAAC,CAAC;AAG1F,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,CAAC;IAC7C,IAAI,EAAE,UAAU;CACjB,CAAC,CAAC,EAAE,CAAC;IACJ,IAAI,EAAE,aAAa;IACnB,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,SAAS;CACnB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,iCAAiC,GAAG,IAAI,CAAC;IACpD,MAAM,EAAE,0BAA0B;IAClC,MAAM,EAAE,UAAU;CACnB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,CAAC;IAC5C,WAAW,EAAE,QAAQ;IACrB,qBAAqB,EAAE,UAAU;IACjC,QAAQ,EAAE,aAAa;IACvB,OAAO,EAAE,QAAQ;IACjB,iBAAiB,EAAE,UAAU;IAC7B,IAAI,EAAE,QAAQ;CACf,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,kCAAkC,GAAG,IAAI,CAAC;IACrD,EAAE,EAAE,MAAM;IACV,MAAM,EAAE,QAAQ;IAChB,KAAK,EAAE,SAAS;IAChB,MAAM,EAAE,0BAA0B;IAClC,UAAU,EAAE,sBAAsB;IAClC,MAAM,EAAE,SAAS;IACjB,aAAa,EAAE,UAAU;IACzB,KAAK,EAAE,yBAAyB,CAAC,QAAQ,EAAE;CAC5C,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,kCAAkC,GAAG,IAAI,CAAC;IACrD,EAAE,EAAE,MAAM;IACV,MAAM,EAAE,QAAQ;IAChB,KAAK,EAAE,SAAS;IAChB,MAAM,EAAE,0BAA0B;IAClC,UAAU,EAAE,sBAAsB;IAClC,MAAM,EAAE,SAAS;IACjB,WAAW,EAAE,UAAU;IACvB,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,UAAU;IAClB,KAAK,EAAE,yBAAyB,CAAC,QAAQ,EAAE;IAC3C,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,QAAQ;IACnB,SAAS,EAAE,QAAQ;IACnB,WAAW,EAAE,SAAS;CACvB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,oCAAoC,GAAG,IAAI,CAAC;IACvD,MAAM,EAAE,QAAQ;IAChB,UAAU,EAAE,sBAAsB;IAClC,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,0BAA0B;IAClC,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,QAAQ;IACnB,SAAS,EAAE,QAAQ;IACnB,WAAW,EAAE,SAAS;IACtB,WAAW,EAAE,UAAU;CACxB,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,sCAAsC,GAAG,IAAI,CAAC;IACzD,IAAI,EAAE,sBAAsB;IAC5B,MAAM,EAAE,cAAc;IACtB,SAAS,EAAE,SAAS;IACpB,MAAM,EAAE,UAAU;CACnB,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,uCAAuC,GAAG,IAAI,CAAC;IAC1D,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,qBAAqB;IAC3B,MAAM,EAAE,QAAQ;IAChB,aAAa,EAAE,QAAQ;IACvB,OAAO,EAAE,QAAQ;IACjB,MAAM,EAAE,UAAU;IAClB,UAAU,EAAE,aAAa;IACzB,IAAI,EAAE,SAAS;IACf,WAAW,EAAE,UAAU;CACxB,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,uCAAuC,GAAG,IAAI,CAAC;IAC1D,MAAM,EAAE,UAAU;CACnB,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,wCAAwC,GAAG,IAAI,CAAC;IAC3D,EAAE,EAAE,MAAM;IACV,KAAK,EAAE,QAAQ;IACf,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,QAAQ;IACjB,SAAS,EAAE,QAAQ;IACnB,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,QAAQ;IACjB,QAAQ,EAAE,QAAQ;IAClB,IAAI,EAAE,SAAS;IACf,YAAY,EAAE,UAAU;CACzB,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,gCAAgC,GAAG,IAAI,CAAC;IACnD,IAAI,EAAE,sBAAsB;IAC5B,MAAM,EAAE,cAAc;IACtB,SAAS,EAAE,SAAS;IACpB,MAAM,EAAE,UAAU;CACnB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,iCAAiC,GAAG,IAAI,CAAC;IACpD,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,qBAAqB;IAC3B,MAAM,EAAE,QAAQ;IAChB,aAAa,EAAE,QAAQ;IACvB,OAAO,EAAE,QAAQ;IACjB,MAAM,EAAE,UAAU;IAClB,UAAU,EAAE,aAAa;IACzB,IAAI,EAAE,SAAS;IACf,WAAW,EAAE,UAAU;CACxB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,sCAAsC,GAAG,IAAI,CAAC;IACzD,MAAM,EAAE,UAAU;CACnB,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,uCAAuC,GAAG,IAAI,CAAC;IAC1D,EAAE,EAAE,MAAM;IACV,KAAK,EAAE,QAAQ;IACf,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,QAAQ;IACjB,SAAS,EAAE,QAAQ;IACnB,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,QAAQ;IACjB,QAAQ,EAAE,QAAQ;IAClB,IAAI,EAAE,SAAS;IACf,YAAY,EAAE,UAAU;CACzB,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,sCAAsC,GAAG,IAAI,CAAC;IACzD,MAAM,EAAE,cAAc;IACtB,SAAS,EAAE,SAAS;IACpB,WAAW,EAAE,SAAS;IACtB,MAAM,EAAE,UAAU;CACnB,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,uCAAuC,GAAG,IAAI,CAAC;IAC1D,EAAE,EAAE,MAAM;IACV,MAAM,EAAE,SAAS;IACjB,KAAK,EAAE,QAAQ;IACf,WAAW,EAAE,QAAQ;IACrB,OAAO,EAAE,QAAQ;IACjB,SAAS,EAAE,QAAQ;IACnB,eAAe,EAAE,QAAQ;IACzB,MAAM,EAAE,QAAQ;IAChB,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,aAAa;IACrB,YAAY,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE;IACtC,aAAa,EAAE,IAAI,CAAC;QAClB,IAAI,EAAE,QAAQ;QACd,MAAM,EAAE,QAAQ;KACjB,CAAC,CAAC,KAAK,EAAE;CACX,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,mCAAmC,GAAG,IAAI,CAAC;IACtD,KAAK,EAAE,IAAI,CAAC;QACV,OAAO,EAAE,QAAQ;QACjB,SAAS,EAAE,QAAQ;QACnB,SAAS,EAAE,QAAQ;QACnB,eAAe,EAAE,qBAAqB;KACvC,CAAC,CAAC,KAAK,EAAE;IACV,UAAU,EAAE,aAAa;CAC1B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;IACvC,MAAM,EAAE,4DAA4D;IACpE,WAAW,EAAE,SAAS;IACtB,SAAS,EAAE,aAAa;IACxB,KAAK,EAAE,aAAa;CACrB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,+BAA+B,GAAG,IAAI,CAAC;IAClD,OAAO,EAAE,IAAI,CAAC;QACZ,OAAO,EAAE,QAAQ;QACjB,SAAS,EAAE,QAAQ;QACnB,SAAS,EAAE,QAAQ;QACnB,eAAe,EAAE,qBAAqB;QACtC,OAAO,EAAE,0BAA0B,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE;QACzD,KAAK,EAAE,UAAU;QACjB,QAAQ,EAAE,oBAAoB,CAAC,QAAQ,EAAE;KAC1C,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC;IACb,KAAK,EAAE,IAAI,CAAC;QACV,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,QAAQ;KACtB,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC;CACd,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,+BAA+B,GAAG,IAAI,CAAC;IAClD,KAAK,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC;IAC7C,aAAa,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC;CACtD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,8BAA8B,GAAG,IAAI,CAAC;IACjD,MAAM,EAAE,2BAA2B;IACnC,EAAE,EAAE,SAAS;IACb,QAAQ,EAAE,eAAe;IACzB,OAAO,EAAE,UAAU;IACnB,IAAI,EAAE,QAAQ;IACd,WAAW,EAAE,QAAQ;IACrB,OAAO,EAAE,QAAQ;IACjB,eAAe,EAAE,aAAa;IAC9B,oBAAoB,EAAE,aAAa;IACnC,mBAAmB,EAAE,aAAa;IAClC,OAAO,EAAE,QAAQ;IACjB,YAAY,EAAE,0BAA0B;IACxC,GAAG,EAAE,aAAa;IAClB,SAAS,EAAE,QAAQ;IACnB,IAAI,EAAE,SAAS;IACf,QAAQ,EAAE,SAAS;IACnB,UAAU,EAAE,SAAS;IACrB,QAAQ,EAAE,SAAS;IACnB,SAAS,EAAE,SAAS;CACrB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,CAAC;IAC7C,EAAE,EAAE,MAAM;IACV,OAAO,EAAE,QAAQ;IACjB,SAAS,EAAE,QAAQ;CACpB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,CAAC;IAC5C,EAAE,EAAE,MAAM;IACV,iBAAiB,EAAE,SAAS;CAC7B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,8BAA8B,GAAG,IAAI,CAAC;IACjD,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,QAAQ;IACd,YAAY,EAAE,QAAQ;CACvB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,6BAA6B,GAAG,IAAI,CAAC;IAChD,EAAE,EAAE,MAAM;IACV,UAAU,EAAE,QAAQ;IACpB,UAAU,EAAE,QAAQ;CACrB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,kCAAkC,GAAG,IAAI,CAAC;IACrD,EAAE,EAAE,MAAM;IACV,UAAU,EAAE,SAAS;IACrB,YAAY,EAAE,SAAS;IACvB,iBAAiB,EAAE,SAAS;IAC5B,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,SAAS;IACpB,WAAW,EAAE,UAAU;CACxB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,mCAAmC,GAAG,IAAI,CAAC;IACtD,EAAE,EAAE,MAAM;IACV,SAAS,EAAE,SAAS;CACrB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,+BAA+B,GAAG,IAAI,CAAC;IAClD,SAAS,EAAE,IAAI,CAAC;QACd,GAAG,EAAE,QAAQ;QACb,KAAK,EAAE,IAAI,CAAC;YACV,GAAG,EAAE,QAAQ;YACb,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,QAAQ;SACtB,CAAC;QACF,QAAQ,EAAE,IAAI,CAAC;YACb,GAAG,EAAE,QAAQ;YACb,MAAM,EAAE,aAAa;YACrB,WAAW,EAAE,aAAa;SAC3B,CAAC,CAAC,QAAQ,EAAE;QACb,MAAM,EAAE,IAAI,CAAC;YACX,GAAG,EAAE,QAAQ;YACb,MAAM,EAAE,aAAa;YACrB,WAAW,EAAE,aAAa;SAC3B,CAAC,CAAC,QAAQ,EAAE;QACb,OAAO,EAAE,SAAS;QAClB,WAAW,EAAE,QAAQ;QACrB,SAAS,EAAE,QAAQ;KACpB,CAAC,CAAC,KAAK,EAAE;CACX,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,CAAC;IAC7C,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,4BAA4B;CACnC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,gCAAgC,GAAG,IAAI,CAAC;IACnD,EAAE,EAAE,MAAM;IACV,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,aAAa;IACrB,cAAc,EAAE,aAAa;IAC7B,UAAU,EAAE,QAAQ;IACpB,OAAO,EAAE,SAAS;CACnB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;IAC1C,EAAE,EAAE,MAAM;IACV,OAAO,EAAE,SAAS;IAClB,cAAc,EAAE,SAAS;CAC1B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,CAAC;IAC5C,EAAE,EAAE,MAAM;IACV,SAAS,EAAE,SAAS;IACpB,gBAAgB,EAAE,SAAS;CAC5B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAC;IACzC,EAAE,EAAE,SAAS;IACb,IAAI,EAAE,yBAAyB;IAC/B,KAAK,EAAE,SAAS;IAChB,IAAI,EAAE,WAAW;IACjB,OAAO,EAAE,SAAS;IAClB,GAAG,EAAE,SAAS;IACd,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,SAAS;CAClB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,CAAC;IACtC,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,WAAW;CACrB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC;IAC3C,WAAW,EAAE,WAAW;IACxB,SAAS,EAAE,WAAW;IACtB,OAAO,EAAE,SAAS;CACnB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;IACxC,IAAI,EAAE,WAAW;IACjB,OAAO,EAAE,WAAW;IACpB,GAAG,EAAE,WAAW;IAChB,MAAM,EAAE,WAAW;CACpB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;IAC1C,IAAI,EAAE,QAAQ;IACd,QAAQ,EAAE,UAAU;IACpB,WAAW,EAAE,SAAS;CACvB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,CAAC;IAC9C,IAAI,EAAE,QAAQ;IACd,IAAI,EAAE,+CAA+C;IACrD,OAAO,EAAE,SAAS;IAClB,GAAG,EAAE,SAAS;IACd,UAAU,EAAE,SAAS;CACtB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;IACnC,QAAQ,EAAE,SAAS;IACnB,UAAU,EAAE,SAAS;IACrB,aAAa,EAAE,SAAS;IACxB,SAAS,EAAE,SAAS;CACrB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,CAAC;IAC7C,MAAM,EAAE,UAAU;IAClB,QAAQ,EAAE,SAAS;IACnB,UAAU,EAAE,SAAS;IACrB,KAAK,EAAE,SAAS;IAChB,QAAQ,EAAE,SAAS;IACnB,EAAE,EAAE,WAAW;IACf,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,qBAAqB,CAAC,QAAQ,EAAE;IAC1C,OAAO,EAAE,sBAAsB,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE;IAClD,GAAG,EAAE,mBAAmB,CAAC,QAAQ,EAAE;IACnC,MAAM,EAAE,wBAAwB,CAAC,QAAQ,EAAE;IAC3C,OAAO,EAAE,uBAAuB,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE;IACnD,YAAY,EAAE,2BAA2B,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE;IAC5D,MAAM,EAAE,SAAS;IACjB,KAAK,EAAE,gBAAgB,CAAC,QAAQ,EAAE;CACnC,CAAC,CAAC;AA2BH,2FAA2F;AAC3F,MAAM,iBAAiB,GAAsB,IAAI,CAAC;AAClD,KAAK,iBAAiB,CAAC"}
\ 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;
runTruffleHog?: (workspace: string) => Promise;
writeWorkspace?: (job: ClaimedJob, workspace: string) => Promise;
};
@@ -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 {});
- 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 | undefined {
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
+ return value as Record;
+}
+
+function readString(record: Record | 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 | 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 {
+ 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 }) => {children},
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
@@ -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");