Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,111 changes: 984 additions & 127 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"trust:verify": "node scripts/verify-trust-attestation.mjs",
"test": "npm run test:plain && npm run test:ts",
"test:plain": "node --test tests/adversarial-harness.test.mjs tests/trust.test.mjs tests/release-signing.test.mjs tests/cli-install.test.mjs tests/trust-attestation.test.mjs",
"test:ts": "node --experimental-strip-types --test tests/site-stats.test.mjs tests/prompt-injection-guard.test.mjs tests/audit-skills.test.mjs tests/runtime.test.mjs tests/integrations.test.mjs tests/growth-revenue-split.test.mjs tests/trust-badge.test.mjs tests/bounties.test.mjs tests/trust-scoring.test.mjs tests/adversarial-holdout.test.mjs tests/adversarial-judge.test.mjs"
"test:ts": "node --experimental-strip-types --test tests/site-stats.test.mjs tests/certify.test.mjs tests/prompt-injection-guard.test.mjs tests/audit-skills.test.mjs tests/runtime.test.mjs tests/integrations.test.mjs tests/growth-revenue-split.test.mjs tests/trust-badge.test.mjs tests/bounties.test.mjs tests/trust-scoring.test.mjs tests/adversarial-holdout.test.mjs tests/adversarial-judge.test.mjs"
},
"dependencies": {
"@ai-sdk/openai-compatible": "^2.0.47",
Expand Down
20 changes: 14 additions & 6 deletions src/components/site/home/Proof.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,20 @@ export function Proof() {
execution feeds the Trust Score. Skills that survive real workloads bubble up; ones that
drift get re-scored and patched automatically.
</p>
<Link
to="/evaluation"
className="inline-flex h-9 items-center rounded-md border border-border bg-surface-elevated px-4 text-sm font-medium text-foreground transition-colors hover:bg-accent"
>
See how skills are evaluated →
</Link>
<div className="flex flex-wrap items-center justify-center gap-2">
<Link
to="/evaluation"
className="inline-flex h-9 items-center rounded-md border border-border bg-surface-elevated px-4 text-sm font-medium text-foreground transition-colors hover:bg-accent"
>
See how skills are evaluated →
</Link>
<Link
to="/certify"
className="inline-flex h-9 items-center rounded-md border border-border bg-surface-elevated px-4 text-sm font-medium text-foreground transition-colors hover:bg-accent"
>
Certify your own skill →
</Link>
</div>
</div>
</div>
</section>
Expand Down
34 changes: 28 additions & 6 deletions src/lib/badges/trust-badge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,38 @@ export function renderTrustBadge(b: BadgeInput): string {
return svg(label, valueText, color);
}

export function renderAdversarialBadge(opts: { vertical?: string; pass_rate?: number | null }): string {
export function renderAdversarialBadge(opts: {
vertical?: string;
pass_rate?: number | null;
}): string {
const label = `adversarial${opts.vertical ? `:${opts.vertical}` : ""}`;
const has = typeof opts.pass_rate === "number" && Number.isFinite(opts.pass_rate);
const valueText = has ? `${(opts.pass_rate! * 100).toFixed(0)}%` : "—";
const color = has
? (opts.pass_rate! >= 0.9 ? COLOR_HEX.green
: opts.pass_rate! >= 0.75 ? COLOR_HEX.yellow
: opts.pass_rate! >= 0.5 ? COLOR_HEX.orange
: COLOR_HEX.red)
? opts.pass_rate! >= 0.9
? COLOR_HEX.green
: opts.pass_rate! >= 0.75
? COLOR_HEX.yellow
: opts.pass_rate! >= 0.5
? COLOR_HEX.orange
: COLOR_HEX.red
: COLOR_HEX.gray;
return svg(label, valueText, color);
}

// Badge for external certifications (/api/public/certify). The label is
// "skill audit" — NOT "trust score" — because a content audit alone doesn't
// carry the adversarial + real-world evidence the registry Trust Score does.
export function renderCertifiedBadge(opts: {
value?: string | null;
score0to100?: number | null;
}): string {
const has = typeof opts.score0to100 === "number" && Number.isFinite(opts.score0to100);
const valueText = has && opts.value ? opts.value : "—";
const color = has ? COLOR_HEX[badgeColor(opts.score0to100! / 100)] : COLOR_HEX.gray;
return svg("skill audit", valueText, color);
}

// Minimal shields.io-shaped badge — no external font needed.
function svg(label: string, value: string, color: string): string {
const labelW = textWidth(label) + 10;
Expand Down Expand Up @@ -70,5 +89,8 @@ function textWidth(s: string): number {
}

function esc(s: string): string {
return s.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c]!));
return s.replace(
/[&<>"]/g,
(c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]!,
);
}
108 changes: 108 additions & 0 deletions src/lib/certify/certify.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Server-side core of the public certification API.
// Runs the same proprietary review engine as the `review_skill` MCP tool,
// records the result keyed by content hash, and hands back a badge the
// author can embed anywhere — even for skills never hosted on the registry.

import { supabaseAdmin as _supabaseAdmin } from "@/integrations/supabase/client.server";
import { computeReview } from "@/lib/mcp/tools/skills";
import { inspectContent } from "@/lib/security/prompt-injection-guard";
import {
CERTIFY_ENGINE_VERSION,
type CertificationRecord,
type CertifyRequest,
sha256Hex,
} from "./core";

// external_certifications is newer than the generated Supabase types; the
// admin client is cast like the other API routes until types are regenerated.
const supabaseAdmin = _supabaseAdmin as any;

export interface CertifyResult {
record: CertificationRecord;
report: {
verdict_score: number;
structural_score: number;
content_quality_score: number;
top_actions: unknown[];
input_warning: unknown;
injection_findings: { severity: string; count: number };
};
}

export async function certifyContent(req: CertifyRequest): Promise<CertifyResult> {
// The guard never blocks a certification — a hostile file simply scores
// what it scores — but findings are persisted so the verify endpoint can
// surface them and the badge can't launder an injection attempt.
const guard = inspectContent(req.content);

const core = await computeReview({
name: req.name,
type: req.type,
content: req.content,
doc_class: "auto",
semantic_check: true,
});

const content_sha256 = await sha256Hex(req.content);
const verdict_score = Number(core.verdict_score ?? 0);
const overall_score = Math.round(verdict_score);
const grade = String(core.grade ?? "F — rewrite recommended");

const { data, error } = await supabaseAdmin
.from("external_certifications")
.insert({
name: req.name,
type: req.type,
content_sha256,
overall_score,
grade,
engine_version: CERTIFY_ENGINE_VERSION,
report: {
structural_score: core.structural_score,
content_quality_score: core.content_quality_score,
doc_class: core.doc_class,
language: core.language,
input_warning: core.input_warning ?? null,
guard: { severity: guard.severity, findings: guard.findings.length },
},
})
.select("id, created_at")
.single();
if (error || !data) {
throw new Error(`failed to persist certification: ${error?.message ?? "no row returned"}`);
}

return {
record: {
id: data.id,
name: req.name,
type: req.type,
content_sha256,
overall_score,
grade,
engine_version: CERTIFY_ENGINE_VERSION,
created_at: data.created_at,
},
report: {
verdict_score,
structural_score: Number(core.structural_score ?? 0),
content_quality_score: Number(core.content_quality_score ?? 0),
top_actions: (core.top_actions as unknown[]) ?? [],
input_warning: core.input_warning ?? null,
injection_findings: { severity: guard.severity, count: guard.findings.length },
},
};
}

export async function getCertification(
id: string,
): Promise<(CertificationRecord & { report: unknown }) | null> {
const { data } = await supabaseAdmin
.from("external_certifications")
.select(
"id, name, type, content_sha256, overall_score, grade, engine_version, report, created_at",
)
.eq("id", id)
.maybeSingle();
return data ?? null;
}
78 changes: 78 additions & 0 deletions src/lib/certify/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Pure helpers for the public certification API ("trust badge as a service").
// Kept free of Supabase/network imports so they can be unit-tested directly.

export const CERTIFY_ENGINE_VERSION = "1.0.0";

export const CERTIFIABLE_TYPES = ["skill", "playbook", "soul", "guardrail"] as const;
export type CertifiableType = (typeof CERTIFIABLE_TYPES)[number];

export const CONTENT_MIN_CHARS = 20;
export const CONTENT_MAX_CHARS = 120_000;

export interface CertificationRecord {
id: string;
name: string;
type: CertifiableType;
content_sha256: string;
overall_score: number; // 0-100, review engine verdict axis
grade: string; // "A — battle-ready" … "F — rewrite recommended"
engine_version: string;
created_at: string;
}

/** SHA-256 hex via Web Crypto — works on Cloudflare Workers and Node 18+. */
export async function sha256Hex(content: string): Promise<string> {
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(content));
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
}

export interface CertifyRequest {
name: string;
type: CertifiableType;
content: string;
}

/** Validate the POST body without zod so the contract is testable in isolation. */
export function parseCertifyRequest(
body: unknown,
): { ok: true; value: CertifyRequest } | { ok: false; error: string } {
if (typeof body !== "object" || body === null)
return { ok: false, error: "body must be a JSON object" };
const b = body as Record<string, unknown>;
const name = typeof b.name === "string" ? b.name.trim() : "";
if (!name || name.length > 200) return { ok: false, error: "name is required (1-200 chars)" };
const type = typeof b.type === "string" ? b.type : "skill";
if (!CERTIFIABLE_TYPES.includes(type as CertifiableType)) {
return { ok: false, error: `type must be one of: ${CERTIFIABLE_TYPES.join(", ")}` };
}
const content = typeof b.content === "string" ? b.content : "";
if (content.length < CONTENT_MIN_CHARS) {
return {
ok: false,
error: `content is required (>= ${CONTENT_MIN_CHARS} chars) — pass the raw file, not a summary`,
};
}
if (content.length > CONTENT_MAX_CHARS) {
return { ok: false, error: `content too large (max ${CONTENT_MAX_CHARS} chars)` };
}
return { ok: true, value: { name, type: type as CertifiableType, content } };
}

/** Short value shown on the badge, e.g. "B · 84/100". */
export function badgeValue(record: Pick<CertificationRecord, "overall_score" | "grade">): string {
const letter = record.grade.charAt(0);
return `${letter} · ${record.overall_score}/100`;
}

export function badgeUrl(origin: string, id: string): string {
return `${origin}/api/badges/certified/${id}.svg`;
}

export function verifyUrl(origin: string, id: string): string {
return `${origin}/api/public/certifications/${id}`;
}

/** Markdown snippet authors paste into their README. */
export function embedMarkdown(origin: string, id: string): string {
return `[![skill audit](${badgeUrl(origin, id)})](${verifyUrl(origin, id)})`;
}
6 changes: 4 additions & 2 deletions src/lib/mcp/tools/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1325,7 +1325,7 @@ export const getMethodologyTool = defineTool({
// review_skills_batch. Produces the full scored payload EXCEPT the per-call
// concerns (caller delta + feedback request + next_steps), which the tools add.
// ----------------------------------------------------------------------------
type ReviewArgs = {
export type ReviewArgs = {
name: string;
type: "skill" | "playbook" | "soul" | "guardrail";
content: string;
Expand All @@ -1346,7 +1346,9 @@ function reviewCacheKey(a: ReviewArgs): string {
return [a.type, a.doc_class, a.semantic_check ? "sem" : "nosem", a.language ?? "auto", fnv1aHex(a.content)].join("|");
}

async function computeReview(a: ReviewArgs): Promise<Record<string, unknown>> {
// Exported so the public certification API (/api/public/certify) runs the
// exact same engine as the review_skill MCP tool — one scorer, two doors.
export async function computeReview(a: ReviewArgs): Promise<Record<string, unknown>> {
const cacheKey = reviewCacheKey(a);
const hit = REVIEW_CACHE.get(cacheKey);
if (hit && Date.now() - hit.at < REVIEW_CACHE_TTL_MS) {
Expand Down
Loading
Loading