From 0c17ac960762b005fa7379914e253c4675b2ff45 Mon Sep 17 00:00:00 2001 From: bashybaranaba Date: Wed, 27 May 2026 23:43:49 +0300 Subject: [PATCH] feat: improve ui components to communicate licensing stuff --- examples/chat/.env.example | 6 +- examples/chat/app/(app)/chat/page.tsx | 2 +- examples/chat/app/(app)/provenance/page.tsx | 2 +- examples/chat/app/api/chat/route.ts | 133 +++++++--- examples/chat/components/chat/chat-input.tsx | 85 +++++- .../provenance/file-search-panel.tsx | 232 +++++++++++++++-- .../provenance/session-flow-diagram.tsx | 4 +- examples/chat/lib/db.ts | 2 +- examples/chat/lib/openai-client.ts | 2 +- examples/chat/lib/provenance.ts | 22 +- examples/chat/types/index.ts | 4 +- packages/provenancekit-ui/dist/index.cjs | 234 ++++++++++++++++- packages/provenancekit-ui/dist/index.d.cts | 37 ++- packages/provenancekit-ui/dist/index.d.ts | 37 ++- packages/provenancekit-ui/dist/index.js | 244 +++++++++++++++++- .../components/badge/provenance-popover.tsx | 40 +++ .../src/components/bundle/action-card.tsx | 50 +++- .../src/components/bundle/resource-card.tsx | 68 ++++- .../extensions/license-extension-view.tsx | 9 + .../components/primitives/license-chip.tsx | 7 +- .../provenance/file-provenance-tag.tsx | 9 +- .../tracker/tracker-action-item.tsx | 8 +- packages/provenancekit-ui/src/index.ts | 2 + .../provenancekit-ui/src/lib/extensions.ts | 24 ++ 24 files changed, 1153 insertions(+), 110 deletions(-) diff --git a/examples/chat/.env.example b/examples/chat/.env.example index 2ec0b4e..bea389b 100644 --- a/examples/chat/.env.example +++ b/examples/chat/.env.example @@ -10,6 +10,10 @@ MONGODB_URI=mongodb://localhost:27017/provenancekit-chat # ── AI Providers ────────────────────────────────────────────────────────────── # OpenAI (Required — primary provider, powers GPT-4o by default) OPENAI_API_KEY=sk-proj-... +# Optional image-generation model overrides. Defaults follow the current +# OpenAI Images API guide: GPT Image models return base64 PNG data by default. +# OPENAI_IMAGE_MODEL=gpt-image-2 +# OPENAI_IMAGE_FALLBACK_MODEL=gpt-image-1 # Anthropic (Optional — enables Claude models in the model selector) ANTHROPIC_API_KEY=sk-ant-... @@ -49,4 +53,4 @@ NEXT_PUBLIC_PK_DASHBOARD_URL=http://localhost:3000 # Base URL used when generating shareable provenance links (the /p/:shareId viewer) # In production this should point to your deployed provenancekit-app instance -NEXT_PUBLIC_SHARE_BASE_URL=http://localhost:3000 \ No newline at end of file +NEXT_PUBLIC_SHARE_BASE_URL=http://localhost:3000 diff --git a/examples/chat/app/(app)/chat/page.tsx b/examples/chat/app/(app)/chat/page.tsx index 728aca8..0464d2f 100644 --- a/examples/chat/app/(app)/chat/page.tsx +++ b/examples/chat/app/(app)/chat/page.tsx @@ -90,7 +90,7 @@ export default function ChatHomePage() {

{authenticated - ? "Every response is provenance-tracked. Ask me anything — or try DALL-E, TTS, or voice input." + ? "Every response is provenance-tracked. Ask me anything — or try image generation, TTS, or voice input." : "AI chat with built-in provenance tracking. Sign in to get started."}

diff --git a/examples/chat/app/(app)/provenance/page.tsx b/examples/chat/app/(app)/provenance/page.tsx index 6d2faca..dfbccf4 100644 --- a/examples/chat/app/(app)/provenance/page.tsx +++ b/examples/chat/app/(app)/provenance/page.tsx @@ -151,7 +151,7 @@ export default function ProvenanceExplorerPage() {

Search by file

- + {/* Stats row */} diff --git a/examples/chat/app/api/chat/route.ts b/examples/chat/app/api/chat/route.ts index ccdb10b..9473d16 100644 --- a/examples/chat/app/api/chat/route.ts +++ b/examples/chat/app/api/chat/route.ts @@ -2,7 +2,7 @@ * Chat API route — streaming AI chat with OpenAI tool calling. * * Tools available to the AI: - * • generate_image — DALL-E 3 image generation + * • generate_image — GPT Image generation * • text_to_speech — OpenAI TTS (tts-1) * • web_search — simulated (returns instruction for AI to answer from knowledge) * @@ -48,18 +48,69 @@ function getAIProvider(provider: SupportedProvider, model: string) { } } -const IMAGE_MODEL = process.env.OPENAI_IMAGE_MODEL ?? "gpt-image-1"; +const IMAGE_MODEL = process.env.OPENAI_IMAGE_MODEL ?? "gpt-image-2"; +const FALLBACK_IMAGE_MODEL = process.env.OPENAI_IMAGE_FALLBACK_MODEL ?? "gpt-image-1"; + +function isGptImageModel(model: string) { + return model.startsWith("gpt-image"); +} function normalizeImageSize(model: string, size: string) { - if (!model.startsWith("gpt-image")) return size; - if (size === "1792x1024") return "1536x1024"; - if (size === "1024x1792") return "1024x1536"; + if (!isGptImageModel(model)) return size; return size; } function normalizeImageQuality(model: string, quality: string) { - if (!model.startsWith("gpt-image")) return quality; - return quality === "hd" ? "high" : "medium"; + if (!isGptImageModel(model)) return quality; + return quality; +} + +function normalizeLegacyImageSize(size: string) { + if (size === "1536x1024") return "1792x1024"; + if (size === "1024x1536") return "1024x1792"; + if (size === "auto") return "1024x1024"; + return size; +} + +function normalizeLegacyImageQuality(quality: string) { + if (quality === "high") return "hd"; + return "standard"; +} + +function buildImageRequest({ + model, + prompt, + size, + quality, +}: { + model: string; + prompt: string; + size: string; + quality: string; +}) { + if (!isGptImageModel(model)) { + return { + model, + prompt, + size: normalizeLegacyImageSize(size), + quality: normalizeLegacyImageQuality(quality), + style: "vivid", + response_format: "url", + n: 1, + }; + } + + const imageRequest: Record = { + model, + prompt, + size: normalizeImageSize(model, size), + quality: normalizeImageQuality(model, quality), + output_format: "png", + moderation: "auto", + n: 1, + }; + + return imageRequest; } async function blobFromImageResult(imageUrl: string, b64Json?: string): Promise { @@ -313,7 +364,7 @@ export async function POST(req: Request) { // Track tool results during streaming for provenance recording. // `blob` holds the pre-downloaded image binary so we can upload it to IPFS // for real vector embeddings. Downloaded immediately inside the tool execute - // while the DALL-E URL is guaranteed fresh. + // while any generated-image URL or base64 payload is fresh. const toolResults: Array<{ name: string; result: unknown; blob?: Blob }> = []; // Capture user message text @@ -336,48 +387,58 @@ export async function POST(req: Request) { tools: { generate_image: tool({ description: - "Generate a high-quality image using DALL-E 3. Use this when the user asks to create, draw, generate, or visualize an image.", + "Generate an image using OpenAI GPT Image. Use this when the user asks to create, draw, generate, or visualize an image.", parameters: z.object({ prompt: z.string().describe("Detailed description of the image to generate"), size: z - .enum(["1024x1024", "1792x1024", "1024x1792"]) + .enum(["auto", "1024x1024", "1536x1024", "1024x1536"]) .optional() - .default("1024x1024") - .describe("Image dimensions. Wide/tall sizes are mapped to the active image model's closest supported size."), + .default("auto") + .describe("Image dimensions. Use auto unless the user asks for square, landscape, or portrait."), quality: z - .enum(["standard", "hd"]) - .optional() - .default("standard") - .describe("Image quality"), - style: z - .enum(["vivid", "natural"]) + .enum(["auto", "low", "medium", "high"]) .optional() - .default("vivid") - .describe("vivid=dramatic/hyper-real, natural=more subdued"), + .default("auto") + .describe("Rendering quality. Use low for drafts, medium/high for final assets, or auto by default."), }), - execute: async ({ prompt, size, quality, style }) => { + execute: async ({ prompt, size, quality }) => { try { if (!process.env.OPENAI_API_KEY) { throw new Error("OPENAI_API_KEY is not configured."); } const openai = getOpenAIClient(); - const imageModel = IMAGE_MODEL; - const imageSize = normalizeImageSize(imageModel, size ?? "1024x1024"); - const imageQuality = normalizeImageQuality(imageModel, quality ?? "standard"); - const imageRequest: Record = { - model: imageModel, - prompt, - size: imageSize, - quality: imageQuality, - n: 1, - }; - if (!imageModel.startsWith("gpt-image")) { - imageRequest.style = style ?? "vivid"; - imageRequest.response_format = "url"; + const requestedSize = size ?? "auto"; + const requestedQuality = quality ?? "auto"; + let imageModel = IMAGE_MODEL; + let response; + + try { + response = await openai.images.generate( + buildImageRequest({ + model: imageModel, + prompt, + size: requestedSize, + quality: requestedQuality, + }) as never + ); + } catch (primaryErr) { + if (imageModel === FALLBACK_IMAGE_MODEL) throw primaryErr; + console.warn( + `[chat] image generation failed with ${imageModel}; retrying with ${FALLBACK_IMAGE_MODEL}:`, + primaryErr instanceof Error ? primaryErr.message : primaryErr + ); + imageModel = FALLBACK_IMAGE_MODEL; + response = await openai.images.generate( + buildImageRequest({ + model: imageModel, + prompt, + size: requestedSize, + quality: requestedQuality, + }) as never + ); } - const response = await openai.images.generate(imageRequest as never); const firstImage = response.data?.[0] as { url?: string; b64_json?: string; revised_prompt?: string } | undefined; const b64Json = firstImage?.b64_json; const imageUrl = firstImage?.url ?? (b64Json ? `data:image/png;base64,${b64Json}` : ""); @@ -387,7 +448,7 @@ export async function POST(req: Request) { throw new Error("Image API returned no image data."); } - // Download the image binary while the URL is fresh (just returned by DALL-E). + // Download the image binary while the generated payload is fresh. // Storing it here avoids re-fetching later when the URL may have expired, // and lets the provenance recorder upload the real image to IPFS for embeddings. let imageBlob: Blob | undefined; diff --git a/examples/chat/components/chat/chat-input.tsx b/examples/chat/components/chat/chat-input.tsx index eb774a2..1dc2171 100644 --- a/examples/chat/components/chat/chat-input.tsx +++ b/examples/chat/components/chat/chat-input.tsx @@ -380,7 +380,7 @@ function AttachmentChip({ const governance = attachment.governance ?? defaultAttachmentGovernance("own-original"); return ( -
+
{isImage ? @@ -413,6 +413,89 @@ function AttachmentChip({ +
+ + + + +
+ onGovernanceChange?.({ ...governance, scope: e.target.value })} + placeholder="Authorized scope" + className="mt-1 h-6 rounded border border-border bg-background px-1 text-[10px] text-muted-foreground outline-none" + /> +
+ {governance.authorizationStatus === "authorized" ? ( + + ) : ( + + )} + + {governance.licenseType} · AI training {governance.aiTraining} + +
); } diff --git a/examples/chat/components/provenance/file-search-panel.tsx b/examples/chat/components/provenance/file-search-panel.tsx index 0a55f44..b942025 100644 --- a/examples/chat/components/provenance/file-search-panel.tsx +++ b/examples/chat/components/provenance/file-search-panel.tsx @@ -29,7 +29,9 @@ import { import { useRouter } from "next/navigation"; import { ProvenanceBundleView, ProvenanceGraph, useProvenanceKit } from "@/components/provenance/pk-ui"; import { cn } from "@/lib/utils"; +import { defaultAttachmentGovernance } from "@/lib/governance"; import type { Match } from "@provenancekit/sdk"; +import type { AttachmentGovernance, AttachmentGovernanceStatus } from "@/types"; // ── Thresholds ───────────────────────────────────────────────────────────────── /** ≥95% → show full provenance dialog. <95% → show claim/explore prompt. */ @@ -175,6 +177,106 @@ function MatchResultCard({ match, onClick }: { match: Match; onClick: () => void ); } +function GovernanceClaimControls({ + governance, + onChange, +}: { + governance: AttachmentGovernance; + onChange: (governance: AttachmentGovernance) => void; +}) { + return ( +
+
+ + + +
+
+ + +
+
+ ); +} + +async function claimFile(opts: { + file: File; + owned: boolean; + governance: AttachmentGovernance; + userId?: string; +}) { + const form = new FormData(); + form.append("file", opts.file, opts.file.name); + form.append("owned", String(opts.owned)); + form.append("userId", opts.userId ?? "anonymous"); + form.append("mimeType", opts.file.type); + form.append("governance", JSON.stringify(opts.governance)); + const res = await fetch("/api/pk-proxy/claim", { method: "POST", body: form }); + if (!res.ok) { + const data = await res.json().catch(() => ({})); + throw new Error(data.error ?? "Failed to record provenance claim"); + } + return res.json() as Promise<{ cid: string; status: "claimed" | "referenced" }>; +} + // ── Provenance File Dialog ───────────────────────────────────────────────────── type DialogTab = "preview" | "graph" | "overview"; @@ -185,21 +287,29 @@ function ProvenanceFileDialog({ match, file, previewUrl, + userId, }: { open: boolean; onClose: () => void; match: Match | null; file: File | null; previewUrl: string | null; + userId?: string; }) { const router = useRouter(); const [tab, setTab] = useState("overview"); const [claimExpanded, setClaimExpanded] = useState(false); + const [governance, setGovernance] = useState(defaultAttachmentGovernance("own-original")); + const [claiming, setClaiming] = useState(false); + const [claimedCid, setClaimedCid] = useState(null); + const [claimError, setClaimError] = useState(null); // Reset state when dialog closes or match changes useEffect(() => { if (!open) { setClaimExpanded(false); + setClaimedCid(null); + setClaimError(null); } }, [open]); @@ -216,6 +326,22 @@ function ProvenanceFileDialog({ const isHigh = pct >= HIGH_CONFIDENCE * 100; const isImage = file.type.startsWith("image/"); + async function handleClaim(owned: boolean) { + if (!file) return; + const claimTarget = file; + setClaiming(true); + setClaimError(null); + try { + const selectedGovernance = owned ? { ...governance, status: "own-original" as const } : governance; + const result = await claimFile({ file: claimTarget, owned, governance: selectedGovernance, userId }); + setClaimedCid(result.cid); + } catch (err) { + setClaimError(err instanceof Error ? err.message : "Failed to record claim"); + } finally { + setClaiming(false); + } + } + const tabs: { key: DialogTab; label: string }[] = [ ...(isImage ? [{ key: "preview" as const, label: "Preview" }] : []), { key: "overview" as const, label: "Overview" }, @@ -313,20 +439,38 @@ function ProvenanceFileDialog({

Record attribution with ProvenanceKit

-

- To formally claim this content as your work, use the ProvenanceKit SDK to - record a provenance bundle for your original file. This links your identity - as creator on-chain. +

+ Record this uploaded file now with license, authorization, AI-training, and review metadata.

- - View recording guide - - + +
+ + + {claimedCid && ( + + )} +
+ {claimError &&

{claimError}

}
+ + {claimedCid && ( + + )} +
+ {claimError &&

{claimError}

} )} @@ -499,6 +698,7 @@ export function FileSearchPanel() { match={dialogMatch} file={activeFile} previewUrl={previewUrl} + userId={userId} /> ); diff --git a/examples/chat/components/provenance/session-flow-diagram.tsx b/examples/chat/components/provenance/session-flow-diagram.tsx index a0a06c9..985f162 100644 --- a/examples/chat/components/provenance/session-flow-diagram.tsx +++ b/examples/chat/components/provenance/session-flow-diagram.tsx @@ -8,7 +8,7 @@ * │ * gpt-4o ──generate──> [Text response] * │ - * dall-e (tool) ──generate──> [Image] + * GPT Image (tool) ──generate──> [Image] * * Uses the /api/pk-proxy/session/:id/provenance endpoint. */ @@ -107,7 +107,7 @@ function shortCid(cid: string): string { /** * Returns the aiTool only when it's a *different* model from the entity performing the action. - * e.g. gpt-4o calling dall-e-3 → returns {provider:"openai",model:"dall-e-3"} + * e.g. gpt-4o calling gpt-image-2 -> returns {provider:"openai",model:"gpt-image-2"} * e.g. gpt-4o responding with aiTool=gpt-4o (self-metadata) → returns null (not a tool call) */ function getAITool(action: ActionData, entity?: EntityData): { provider: string; model: string } | null { diff --git a/examples/chat/lib/db.ts b/examples/chat/lib/db.ts index 15c6b9f..0f51c10 100644 --- a/examples/chat/lib/db.ts +++ b/examples/chat/lib/db.ts @@ -131,7 +131,7 @@ export interface IMessage { /** "recording" while async provenance write is in-flight; "recorded" on success; "failed" on error */ provenanceStatus?: "recording" | "recorded" | "failed"; provenance?: { cid: string; actionId?: string; promptCid?: string; sessionId?: string; bundle?: Record }; - /** Separate provenance record for a DALL-E generated image in this message */ + /** Separate provenance record for a GPT Image generated image in this message */ imageProvenance?: { cid: string; actionId?: string; status: "recording" | "recorded" | "failed"; bundle?: Record }; createdAt?: Date; updatedAt?: Date; diff --git a/examples/chat/lib/openai-client.ts b/examples/chat/lib/openai-client.ts index 6bbccb9..cd6cc94 100644 --- a/examples/chat/lib/openai-client.ts +++ b/examples/chat/lib/openai-client.ts @@ -1,6 +1,6 @@ /** * OpenAI SDK singleton. - * Used for DALL-E image generation, TTS, and STT. + * Used for GPT Image generation, TTS, and STT. * The streaming chat itself uses @ai-sdk/openai via Vercel AI SDK. */ diff --git a/examples/chat/lib/provenance.ts b/examples/chat/lib/provenance.ts index 18c9cec..d2c3f76 100644 --- a/examples/chat/lib/provenance.ts +++ b/examples/chat/lib/provenance.ts @@ -5,7 +5,7 @@ * * Tracks: * - recordChatProvenance: text prompt/response pairs with ext:ai@1.0.0 - * - recordImageProvenance: DALL-E generated images with ext:ai@1.0.0 + * - recordImageProvenance: GPT Image generated images with ext:ai@1.0.0 * * On-chain provenance: if CHAIN_PRIVATE_KEY + BASE_SEPOLIA_RPC_URL are set, * every pk.file() call also records the action on the Base Sepolia @@ -101,7 +101,7 @@ export interface ProvenanceResult { promptCid?: string; /** * Entity ID of the AI agent that performed this action. - * Pass this to recordImageProvenance so DALL-E image actions are attributed + * Pass this to recordImageProvenance so image actions are attributed * to the same conversation model rather than creating a duplicate entity. */ agentEntityId?: string; @@ -337,23 +337,23 @@ export async function recordChatProvenance(opts: { } /** - * Record provenance for a DALL-E generated image. + * Record provenance for a GPT Image generated image. * * Two-path strategy: * 1. If `imageBlob` is provided (pre-downloaded in the tool execute callback while - * the DALL-E URL was fresh), upload the real image binary to IPFS. This enables + * the generated image payload was fresh), upload the real image binary to IPFS. This enables * vector embeddings for content-based similarity search. * 2. If `imageBlob` is absent (download failed), fall back to a small JSON metadata * blob. The provenance record is still permanent and auditable — only embeddings * are missing. * * The caller (generate_image tool execute) is responsible for downloading the image - * immediately after DALL-E returns it, before the ephemeral URL can expire (~60 min). + * immediately after OpenAI returns it, before any ephemeral URL can expire. */ export async function recordImageProvenance(opts: { userPrivyDid: string; provider: SupportedProvider; - model: string; // "dall-e-3" + model: string; // e.g. "gpt-image-2" prompt: string; imageUrl: string; /** Pre-downloaded image binary — enables real IPFS storage and vector embeddings */ @@ -361,9 +361,9 @@ export async function recordImageProvenance(opts: { inputCids: string[]; // prompt CID(s) from the parent chat exchange sessionId: string | null; /** - * Entity ID of the conversation AI model (e.g. gpt-4o) that called DALL-E as a tool. - * When provided, the image action is attributed to this entity with dall-e-3 recorded - * only as aiTool metadata — no separate DALL-E entity is created. + * Entity ID of the conversation AI model (e.g. gpt-4o) that called image generation as a tool. + * When provided, the image action is attributed to this entity with the image model recorded + * only as aiTool metadata — no separate image-model entity is created. * This keeps the entity count at 2 (human + AI model) regardless of image tool usage. */ agentEntityId?: string; @@ -396,7 +396,7 @@ export async function recordImageProvenance(opts: { try { return await withRetry(async () => { - // DALL-E is a tool called by the conversation AI model, not a separate entity. + // Image generation is a tool called by the conversation AI model, not a separate entity. // Reuse the conversation model's entity ID if provided; fall back to deriving it // deterministically — no API call needed. const performerEntityId = @@ -432,7 +432,7 @@ export async function recordImageProvenance(opts: { reviewStatus: "reviewed", }, }, - // dall-e-3 is recorded as the tool used, not as the performer entity + // The image model is recorded as the tool used, not as the performer entity. aiTool: { provider: opts.provider, model: opts.model, diff --git a/examples/chat/types/index.ts b/examples/chat/types/index.ts index 660570c..556c0e8 100644 --- a/examples/chat/types/index.ts +++ b/examples/chat/types/index.ts @@ -110,7 +110,7 @@ export interface ChatMessage { content: string; /** Structured content parts (for multi-modal user messages) */ contentParts?: MessagePart[]; - /** Generated image URL from DALL-E tool */ + /** Generated image URL from the image-generation tool */ imageUrl?: string; imageRevisedPrompt?: string; /** Generated audio data URI from TTS tool */ @@ -136,7 +136,7 @@ export interface ChatMessage { /** Pre-fetched bundle data — when present, badge renders instantly with no extra fetch */ bundle?: Record; }; - /** Separate provenance record for a DALL-E generated image in this message */ + /** Separate provenance record for a GPT Image generated image in this message */ imageProvenance?: { cid: string; actionId?: string; diff --git a/packages/provenancekit-ui/dist/index.cjs b/packages/provenancekit-ui/dist/index.cjs index 5363329..e106eb9 100644 --- a/packages/provenancekit-ui/dist/index.cjs +++ b/packages/provenancekit-ui/dist/index.cjs @@ -72,7 +72,9 @@ __export(src_exports, { formatTxHash: () => formatTxHash, getAIAgentSafe: () => getAIAgentSafe, getAIToolSafe: () => getAIToolSafe, + getAuthorizationSafe: () => getAuthorizationSafe, getContribSafe: () => getContribSafe, + getGovernanceSafe: () => getGovernanceSafe, getLicenseSafe: () => getLicenseSafe, getOnchainSafe: () => getOnchainSafe, getPrimaryCreator: () => getPrimaryCreator, @@ -9198,6 +9200,7 @@ function getVerification(obj) { return VerificationExtension.parse(data); } var MAX_SAFE_WEIGHT = Number.MAX_SAFE_INTEGER; +var AUTHORIZATION_NAMESPACE = "ext:authorization@1.0.0"; var AuthorizationStatus = external_exports.enum([ "authorized", "unauthorized", @@ -9248,6 +9251,11 @@ var AuthorizationExtension = external_exports.object({ */ proof: external_exports.string().optional() }); +function getAuthorization(obj) { + const data = obj.extensions?.[AUTHORIZATION_NAMESPACE]; + if (!data) return void 0; + return AuthorizationExtension.parse(data); +} var OwnershipEvidenceType = external_exports.enum([ "self-declaration", // No external proof — just a formal assertion @@ -9413,6 +9421,29 @@ var X402Extension = external_exports.object({ (data) => data.requirements !== void 0 || data.proof !== void 0 || data.split !== void 0, { message: "At least one of requirements, proof, or split must be provided" } ); +var GOVERNANCE_NAMESPACE = "ext:governance@1.0.0"; +var GovernanceRequirement = external_exports.enum(["R1", "R2", "R3", "R4", "R5", "R6"]); +var GovernanceExtension = external_exports.object({ + workflow: external_exports.string().optional(), + purpose: external_exports.string().optional(), + roleInWorkflow: external_exports.enum(["source", "prompt", "generated-output", "derived-output", "reference", "final-output"]).optional(), + disclosure: external_exports.object({ + aiInvolved: external_exports.boolean().optional(), + humanContribution: external_exports.string().optional(), + aiContribution: external_exports.string().optional(), + publicSummary: external_exports.string().optional() + }).optional(), + requirements: external_exports.array(GovernanceRequirement).default([]), + policyBasis: external_exports.array(external_exports.string()).optional(), + riskFlags: external_exports.array(external_exports.string()).optional(), + reviewStatus: external_exports.enum(["not-reviewed", "review-required", "reviewed", "approved"]).optional(), + note: external_exports.string().optional() +}); +function getGovernance(obj) { + const data = obj.extensions?.[GOVERNANCE_NAMESPACE]; + if (!data) return void 0; + return GovernanceExtension.parse(data); +} // ../provenancekit-sdk/dist/index.mjs var ProvenanceKitError = class _ProvenanceKitError extends Error { @@ -10261,6 +10292,22 @@ function getWitnessSafe(action) { return null; } } +function getAuthorizationSafe(target) { + if (!target) return null; + try { + return getAuthorization(target) ?? null; + } catch { + return null; + } +} +function getGovernanceSafe(target) { + if (!target) return null; + try { + return getGovernance(target) ?? null; + } catch { + return null; + } +} function bundleHasAI(actions) { return actions.some((a) => getAIToolSafe(a) !== null); } @@ -10468,7 +10515,8 @@ function LicenseChip({ showIcons && !isPublicDomain && license && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [ license.commercial === false && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { title: "Non-commercial", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.DollarSign, { size: 10, strokeWidth: 2, className: "shrink-0 text-amber-500", "aria-label": "Non-commercial" }) }), license.derivatives === false && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { title: "No derivatives", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.GitBranch, { size: 10, strokeWidth: 2, className: "shrink-0 text-amber-500", "aria-label": "No derivatives" }) }), - license.shareAlike && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { title: "Share alike required", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.Share2, { size: 10, strokeWidth: 2, className: "shrink-0 text-blue-500", "aria-label": "Share alike required" }) }) + license.shareAlike && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { title: "Share alike required", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.Share2, { size: 10, strokeWidth: 2, className: "shrink-0 text-blue-500", "aria-label": "Share alike required" }) }), + license.aiTraining === "reserved" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { title: "AI training rights reserved", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.Ban, { size: 10, strokeWidth: 2, className: "shrink-0 text-red-500", "aria-label": "AI training rights reserved" }) }) ] }) ] } @@ -10643,6 +10691,28 @@ function findVerification(bundle) { } return null; } +function findAuthorization(bundle) { + for (let i = bundle.resources.length - 1; i >= 0; i--) { + const auth = getAuthorizationSafe(bundle.resources[i]); + if (auth) return auth; + } + for (let i = bundle.actions.length - 1; i >= 0; i--) { + const auth = getAuthorizationSafe(bundle.actions[i]); + if (auth) return auth; + } + return null; +} +function findGovernance(bundle) { + for (let i = bundle.resources.length - 1; i >= 0; i--) { + const governance = getGovernanceSafe(bundle.resources[i]); + if (governance) return governance; + } + for (let i = bundle.actions.length - 1; i >= 0; i--) { + const governance = getGovernanceSafe(bundle.actions[i]); + if (governance) return governance; + } + return null; +} function CredRow({ label, value }) { return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { padding: "9px 0" }, children: [ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: { margin: "0 0 2px", fontSize: 10, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.06em", color: "#9ca3af" }, children: label }), @@ -10661,6 +10731,8 @@ function ProvenancePopover({ const aiTools = getUniqueAITools(bundle); const license = findLicense(bundle); const verification = findVerification(bundle); + const authorization = findAuthorization(bundle); + const governance = findGovernance(bundle); const lastAction = bundle.actions[bundle.actions.length - 1]; const otherContributors = bundle.entities.filter((e) => e.id !== creator?.id); const verifiedLabel = verification?.status === "verified" ? verification.policyUsed ?? "Verified" : verification?.status === "partial" ? "Partially verified" : null; @@ -10700,6 +10772,15 @@ function ProvenancePopover({ if (license?.type) { rows.push({ label: "License", value: license.type }); } + if (license?.aiTraining) { + rows.push({ label: "AI training rights", value: license.aiTraining }); + } + if (authorization?.status) { + rows.push({ label: "Authorization", value: authorization.status }); + } + if (governance?.reviewStatus) { + rows.push({ label: "Governance review", value: governance.reviewStatus.replace(/-/g, " ") }); + } if (verifiedLabel) { rows.push({ label: "Signed with", value: verifiedLabel }); } @@ -11534,7 +11615,10 @@ function formatActionType3(t) { function ActionCard({ action }) { const aiTool = getAIToolSafe(action); const verification = getVerificationSafe(action); + const authorization = getAuthorizationSafe(action); + const governance = getGovernanceSafe(action); const isVerified = verification?.status === "verified"; + const needsReview = governance?.reviewStatus === "review-required" || authorization?.status === "pending" || authorization?.status === "unauthorized"; return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( "div", { @@ -11632,7 +11716,51 @@ function ActionCard({ action }) { verification?.policyUsed ?? "Verified" ] } - ) + ), + (authorization || governance) && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, marginTop: 6 }, children: [ + authorization?.status && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "span", + { + style: { + display: "inline-flex", + alignItems: "center", + gap: 5, + fontSize: 11, + color: authorization.status === "authorized" ? "#047857" : "#b45309", + background: authorization.status === "authorized" ? "rgba(16,185,129,0.08)" : "rgba(245,158,11,0.12)", + border: `1px solid ${authorization.status === "authorized" ? "rgba(16,185,129,0.22)" : "rgba(245,158,11,0.28)"}`, + borderRadius: 6, + padding: "2px 8px" + }, + title: authorization.scope, + children: [ + authorization.status === "authorized" ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react8.Shield, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react8.AlertTriangle, { size: 10 }), + authorization.status + ] + } + ), + governance?.reviewStatus && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)( + "span", + { + style: { + display: "inline-flex", + alignItems: "center", + gap: 5, + fontSize: 11, + color: needsReview ? "#b45309" : "#047857", + background: needsReview ? "rgba(245,158,11,0.12)" : "rgba(16,185,129,0.08)", + border: `1px solid ${needsReview ? "rgba(245,158,11,0.28)" : "rgba(16,185,129,0.22)"}`, + borderRadius: 6, + padding: "2px 8px" + }, + title: governance.note, + children: [ + needsReview ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react8.AlertTriangle, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react8.Shield, { size: 10 }), + governance.reviewStatus.replace(/-/g, " ") + ] + } + ) + ] }) ] }) ] }) }) ] @@ -11646,7 +11774,10 @@ var import_jsx_runtime16 = require("react/jsx-runtime"); function ResourceCard({ resource }) { const cid = resource.address?.ref; const license = getLicenseSafe(resource); + const authorization = getAuthorizationSafe(resource); + const governance = getGovernanceSafe(resource); const location = resource.locations?.[0]; + const needsReview = governance?.reviewStatus === "review-required" || authorization?.status === "pending" || authorization?.status === "unauthorized"; return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)( "div", { @@ -11700,6 +11831,71 @@ function ResourceCard({ resource }) { resource.size && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { fontSize: 11, color: "#94a3b8" }, children: resource.size < 1024 * 1024 ? `${(resource.size / 1024).toFixed(1)} KB` : `${(resource.size / 1024 / 1024).toFixed(1)} MB` }), license && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(LicenseChip, { license }) ] }), + (authorization || governance || license?.aiTraining) && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 8 }, children: [ + authorization?.status && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)( + "span", + { + style: { + display: "inline-flex", + alignItems: "center", + gap: 4, + fontSize: 11, + color: authorization.status === "authorized" ? "#047857" : "#b45309", + background: authorization.status === "authorized" ? "rgba(16,185,129,0.08)" : "rgba(245,158,11,0.12)", + border: `1px solid ${authorization.status === "authorized" ? "rgba(16,185,129,0.22)" : "rgba(245,158,11,0.28)"}`, + borderRadius: 6, + padding: "2px 7px" + }, + title: authorization.scope, + children: [ + authorization.status === "authorized" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react9.ShieldCheck, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react9.AlertTriangle, { size: 10 }), + authorization.status + ] + } + ), + license?.aiTraining && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)( + "span", + { + style: { + display: "inline-flex", + alignItems: "center", + gap: 4, + fontSize: 11, + color: license.aiTraining === "reserved" ? "#b91c1c" : "#475569", + background: license.aiTraining === "reserved" ? "rgba(239,68,68,0.08)" : "#f8fafc", + border: `1px solid ${license.aiTraining === "reserved" ? "rgba(239,68,68,0.22)" : "#e2e8f0"}`, + borderRadius: 6, + padding: "2px 7px" + }, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react9.Brain, { size: 10 }), + "AI training ", + license.aiTraining + ] + } + ), + governance?.reviewStatus && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)( + "span", + { + style: { + display: "inline-flex", + alignItems: "center", + gap: 4, + fontSize: 11, + color: needsReview ? "#b45309" : "#047857", + background: needsReview ? "rgba(245,158,11,0.12)" : "rgba(16,185,129,0.08)", + border: `1px solid ${needsReview ? "rgba(245,158,11,0.28)" : "rgba(16,185,129,0.22)"}`, + borderRadius: 6, + padding: "2px 7px" + }, + title: governance.note, + children: [ + needsReview ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react9.AlertTriangle, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react9.ShieldCheck, { size: 10 }), + governance.reviewStatus.replace(/-/g, " ") + ] + } + ) + ] }), cid && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)( "div", { @@ -12085,6 +12281,8 @@ function TrackerActionItem({ action, isLatest, isLast, className }) { const aiTool = getAIToolSafe(action); const verification = getVerificationSafe(action); const isVerified = verification?.status === "verified"; + const actionType = action.type ?? "action"; + const outputCount = action.outputs?.length ?? 0; const dotColor = isLatest ? "#22c55e" : "var(--pk-surface-border, #e2e8f0)"; const dotBorder = isLatest ? "#22c55e" : "var(--pk-surface-border, #e2e8f0)"; return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)( @@ -12143,7 +12341,7 @@ function TrackerActionItem({ action, isLatest, isLast, className }) { color: "var(--pk-foreground, #0f172a)", textTransform: "capitalize" }, - children: formatActionType4(action.type) + children: formatActionType4(actionType) } ), isLatest && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)( @@ -12205,11 +12403,11 @@ function TrackerActionItem({ action, isLatest, isLast, className }) { ] }), /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { fontSize: 11, color: "var(--pk-muted-foreground, #64748b)" }, children: [ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Timestamp, { iso: action.timestamp }), - action.outputs.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { style: { marginLeft: 8 }, children: [ + outputCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { style: { marginLeft: 8 }, children: [ "\u2192 ", - action.outputs.length, + outputCount, " output", - action.outputs.length !== 1 ? "s" : "" + outputCount !== 1 ? "s" : "" ] }) ] }) ] }) @@ -12724,6 +12922,21 @@ function LicenseExtensionView({ extension, className }) { extension.expires && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { children: [ "Expires: ", /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "font-medium text-[var(--pk-foreground)]", children: new Date(extension.expires).toLocaleDateString() }) + ] }), + extension.aiTraining && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { children: [ + "AI training: ", + /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "font-medium text-[var(--pk-foreground)] capitalize", children: extension.aiTraining }) + ] }), + extension.grantType && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { children: [ + "Grant type: ", + /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "font-medium text-[var(--pk-foreground)] capitalize", children: extension.grantType }) + ] }), + extension.grantedBy && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { children: [ + "Granted by: ", + /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("span", { className: "font-mono text-[var(--pk-foreground)]", children: [ + extension.grantedBy.slice(0, 16), + "\u2026" + ] }) ] }) ] }), extension.termsUrl && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)( @@ -14160,9 +14373,12 @@ function BundleSummary({ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react23.Tag, { size: 10, className: "shrink-0" }), /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "capitalize", children: topAction.type }) ] }), - licenseExt && (licenseExt.spdxId || licenseExt.name) && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center gap-1.5 text-[var(--pk-muted-foreground,#64748b)]", children: [ + licenseExt && (licenseExt.type || licenseExt.spdxId || licenseExt.name) && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center gap-1.5 text-[var(--pk-muted-foreground,#64748b)]", children: [ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react23.ShieldCheck, { size: 10, className: "shrink-0" }), - /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "truncate", children: licenseExt.spdxId ?? licenseExt.name }) + /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "truncate", children: [ + licenseExt.type ?? licenseExt.spdxId ?? licenseExt.name, + licenseExt.aiTraining ? ` \xB7 AI training ${licenseExt.aiTraining}` : "" + ] }) ] }), topAction?.timestamp && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center gap-1.5 text-[var(--pk-muted-foreground,#64748b)]", children: [ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react23.Calendar, { size: 10, className: "shrink-0" }), @@ -14330,7 +14546,9 @@ function FileProvenanceTag({ formatTxHash, getAIAgentSafe, getAIToolSafe, + getAuthorizationSafe, getContribSafe, + getGovernanceSafe, getLicenseSafe, getOnchainSafe, getPrimaryCreator, diff --git a/packages/provenancekit-ui/dist/index.d.cts b/packages/provenancekit-ui/dist/index.d.cts index b3cb9fd..758a35a 100644 --- a/packages/provenancekit-ui/dist/index.d.cts +++ b/packages/provenancekit-ui/dist/index.d.cts @@ -1,7 +1,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime'; import React from 'react'; import { z } from 'zod'; -import { AIAgentExtension, AIToolExtension, ContribExtension, LicenseExtension, OnchainExtension, VerificationExtension, WitnessExtension } from '@provenancekit/extensions'; +import { AIAgentExtension, AIToolExtension, AuthorizationExtension, ContribExtension, GovernanceExtension, LicenseExtension, OnchainExtension, VerificationExtension, WitnessExtension } from '@provenancekit/extensions'; import { ClassValue } from 'clsx'; /** @@ -844,6 +844,37 @@ interface FileOpts { aiTool?: AIToolOpts; }; resourceType?: string; + resourceExtensions?: Record; + attribution?: { + contrib?: { + weight: number; + basis?: "points" | "percentage" | "absolute"; + source?: "self-declared" | "agreed" | "calculated" | "verified" | "default"; + category?: string; + note?: string; + }; + license?: string | { + type: string; + commercial?: boolean; + derivatives?: boolean; + shareAlike?: boolean; + attribution?: "required" | "requested" | "none"; + attributionText?: string; + termsUrl?: string; + jurisdiction?: string; + expires?: string; + grantedBy?: string; + grantType?: "license" | "purchase" | "transfer" | "open" | "agreement"; + transactionRef?: string; + aiTraining?: "permitted" | "reserved" | "unspecified"; + }; + payment?: { + address: string; + chainId?: number; + method?: string; + currency?: string; + }; + }; sessionId?: string; } interface UploadOptions { @@ -1201,6 +1232,8 @@ declare function getContribSafe(attribution: Attribution | null | undefined): Co declare function getOnchainSafe(target: AnyEaaType | null | undefined): OnchainExtension | null; declare function getVerificationSafe(action: Action | null | undefined): VerificationExtension | null; declare function getWitnessSafe(action: Action | null | undefined): WitnessExtension | null; +declare function getAuthorizationSafe(target: AnyEaaType | null | undefined): AuthorizationExtension | null; +declare function getGovernanceSafe(target: AnyEaaType | null | undefined): GovernanceExtension | null; /** Check if any action in a bundle used an AI tool */ declare function bundleHasAI(actions: Action[]): boolean; /** Get the primary creator attribution (role === "creator", or first) */ @@ -1588,4 +1621,4 @@ interface FileProvenanceTagProps { } declare function FileProvenanceTag({ file, onViewDetail, onClaim, onMatchFound, topK, className, }: FileProvenanceTagProps): react_jsx_runtime.JSX.Element | null; -export { AIExtensionView, ActionCard, AttributionList, CidDisplay, ContribExtensionView, ContributionBar, EntityAvatar, EntityCard, FileOwnershipClaim, type FileOwnershipClaimProps, type FileOwnershipClaimResult, FileProvenanceTag, type FileProvenanceTagProps, FileUploadZone, LicenseChip, LicenseExtensionView, type MaybeRedactedAction, type MaybeRedactedEntity, type MaybeRedactedResource, OnchainExtensionView, ProvenanceBadge, type ProvenanceBadgeProps, ProvenanceBundleView, ProvenanceDocument, type ProvenanceDocumentProps, ProvenanceGraph, type ProvenanceGraphProps, ProvenanceKitProvider, type ProvenanceKitProviderProps, type ProvenanceKitTheme, ProvenancePopover, ProvenanceSearch, type ProvenanceSearchProps, ProvenanceTracker, type ProvenanceTrackerProps, RedactedItem, type RedactedItemDescriptor, type RedactedItemProps, type RedactedMarker, type RedactionConfig, ResourceCard, RoleBadge, type ShareConfig, type ShareData, ShareModal, type ShareModalProps, Timestamp, type UseDistributionResult, type UseProvenanceBundleResult, type UseProvenanceGraphResult, type UseSessionProvenanceResult, VerificationIndicator, VerificationView, bundleHasAI, cn, formatActionType, formatBps, formatBytes, formatChainName, formatCid, formatDate, formatDateAbsolute, formatRole, formatTxHash, getAIAgentSafe, getAIToolSafe, getContribSafe, getLicenseSafe, getOnchainSafe, getPrimaryCreator, getVerificationSafe, getWitnessSafe, useDistribution, useProvenanceBundle, useProvenanceGraph, useProvenanceKit, useSessionProvenance }; +export { AIExtensionView, ActionCard, AttributionList, CidDisplay, ContribExtensionView, ContributionBar, EntityAvatar, EntityCard, FileOwnershipClaim, type FileOwnershipClaimProps, type FileOwnershipClaimResult, FileProvenanceTag, type FileProvenanceTagProps, FileUploadZone, LicenseChip, LicenseExtensionView, type MaybeRedactedAction, type MaybeRedactedEntity, type MaybeRedactedResource, OnchainExtensionView, ProvenanceBadge, type ProvenanceBadgeProps, ProvenanceBundleView, ProvenanceDocument, type ProvenanceDocumentProps, ProvenanceGraph, type ProvenanceGraphProps, ProvenanceKitProvider, type ProvenanceKitProviderProps, type ProvenanceKitTheme, ProvenancePopover, ProvenanceSearch, type ProvenanceSearchProps, ProvenanceTracker, type ProvenanceTrackerProps, RedactedItem, type RedactedItemDescriptor, type RedactedItemProps, type RedactedMarker, type RedactionConfig, ResourceCard, RoleBadge, type ShareConfig, type ShareData, ShareModal, type ShareModalProps, Timestamp, type UseDistributionResult, type UseProvenanceBundleResult, type UseProvenanceGraphResult, type UseSessionProvenanceResult, VerificationIndicator, VerificationView, bundleHasAI, cn, formatActionType, formatBps, formatBytes, formatChainName, formatCid, formatDate, formatDateAbsolute, formatRole, formatTxHash, getAIAgentSafe, getAIToolSafe, getAuthorizationSafe, getContribSafe, getGovernanceSafe, getLicenseSafe, getOnchainSafe, getPrimaryCreator, getVerificationSafe, getWitnessSafe, useDistribution, useProvenanceBundle, useProvenanceGraph, useProvenanceKit, useSessionProvenance }; diff --git a/packages/provenancekit-ui/dist/index.d.ts b/packages/provenancekit-ui/dist/index.d.ts index b3cb9fd..758a35a 100644 --- a/packages/provenancekit-ui/dist/index.d.ts +++ b/packages/provenancekit-ui/dist/index.d.ts @@ -1,7 +1,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime'; import React from 'react'; import { z } from 'zod'; -import { AIAgentExtension, AIToolExtension, ContribExtension, LicenseExtension, OnchainExtension, VerificationExtension, WitnessExtension } from '@provenancekit/extensions'; +import { AIAgentExtension, AIToolExtension, AuthorizationExtension, ContribExtension, GovernanceExtension, LicenseExtension, OnchainExtension, VerificationExtension, WitnessExtension } from '@provenancekit/extensions'; import { ClassValue } from 'clsx'; /** @@ -844,6 +844,37 @@ interface FileOpts { aiTool?: AIToolOpts; }; resourceType?: string; + resourceExtensions?: Record; + attribution?: { + contrib?: { + weight: number; + basis?: "points" | "percentage" | "absolute"; + source?: "self-declared" | "agreed" | "calculated" | "verified" | "default"; + category?: string; + note?: string; + }; + license?: string | { + type: string; + commercial?: boolean; + derivatives?: boolean; + shareAlike?: boolean; + attribution?: "required" | "requested" | "none"; + attributionText?: string; + termsUrl?: string; + jurisdiction?: string; + expires?: string; + grantedBy?: string; + grantType?: "license" | "purchase" | "transfer" | "open" | "agreement"; + transactionRef?: string; + aiTraining?: "permitted" | "reserved" | "unspecified"; + }; + payment?: { + address: string; + chainId?: number; + method?: string; + currency?: string; + }; + }; sessionId?: string; } interface UploadOptions { @@ -1201,6 +1232,8 @@ declare function getContribSafe(attribution: Attribution | null | undefined): Co declare function getOnchainSafe(target: AnyEaaType | null | undefined): OnchainExtension | null; declare function getVerificationSafe(action: Action | null | undefined): VerificationExtension | null; declare function getWitnessSafe(action: Action | null | undefined): WitnessExtension | null; +declare function getAuthorizationSafe(target: AnyEaaType | null | undefined): AuthorizationExtension | null; +declare function getGovernanceSafe(target: AnyEaaType | null | undefined): GovernanceExtension | null; /** Check if any action in a bundle used an AI tool */ declare function bundleHasAI(actions: Action[]): boolean; /** Get the primary creator attribution (role === "creator", or first) */ @@ -1588,4 +1621,4 @@ interface FileProvenanceTagProps { } declare function FileProvenanceTag({ file, onViewDetail, onClaim, onMatchFound, topK, className, }: FileProvenanceTagProps): react_jsx_runtime.JSX.Element | null; -export { AIExtensionView, ActionCard, AttributionList, CidDisplay, ContribExtensionView, ContributionBar, EntityAvatar, EntityCard, FileOwnershipClaim, type FileOwnershipClaimProps, type FileOwnershipClaimResult, FileProvenanceTag, type FileProvenanceTagProps, FileUploadZone, LicenseChip, LicenseExtensionView, type MaybeRedactedAction, type MaybeRedactedEntity, type MaybeRedactedResource, OnchainExtensionView, ProvenanceBadge, type ProvenanceBadgeProps, ProvenanceBundleView, ProvenanceDocument, type ProvenanceDocumentProps, ProvenanceGraph, type ProvenanceGraphProps, ProvenanceKitProvider, type ProvenanceKitProviderProps, type ProvenanceKitTheme, ProvenancePopover, ProvenanceSearch, type ProvenanceSearchProps, ProvenanceTracker, type ProvenanceTrackerProps, RedactedItem, type RedactedItemDescriptor, type RedactedItemProps, type RedactedMarker, type RedactionConfig, ResourceCard, RoleBadge, type ShareConfig, type ShareData, ShareModal, type ShareModalProps, Timestamp, type UseDistributionResult, type UseProvenanceBundleResult, type UseProvenanceGraphResult, type UseSessionProvenanceResult, VerificationIndicator, VerificationView, bundleHasAI, cn, formatActionType, formatBps, formatBytes, formatChainName, formatCid, formatDate, formatDateAbsolute, formatRole, formatTxHash, getAIAgentSafe, getAIToolSafe, getContribSafe, getLicenseSafe, getOnchainSafe, getPrimaryCreator, getVerificationSafe, getWitnessSafe, useDistribution, useProvenanceBundle, useProvenanceGraph, useProvenanceKit, useSessionProvenance }; +export { AIExtensionView, ActionCard, AttributionList, CidDisplay, ContribExtensionView, ContributionBar, EntityAvatar, EntityCard, FileOwnershipClaim, type FileOwnershipClaimProps, type FileOwnershipClaimResult, FileProvenanceTag, type FileProvenanceTagProps, FileUploadZone, LicenseChip, LicenseExtensionView, type MaybeRedactedAction, type MaybeRedactedEntity, type MaybeRedactedResource, OnchainExtensionView, ProvenanceBadge, type ProvenanceBadgeProps, ProvenanceBundleView, ProvenanceDocument, type ProvenanceDocumentProps, ProvenanceGraph, type ProvenanceGraphProps, ProvenanceKitProvider, type ProvenanceKitProviderProps, type ProvenanceKitTheme, ProvenancePopover, ProvenanceSearch, type ProvenanceSearchProps, ProvenanceTracker, type ProvenanceTrackerProps, RedactedItem, type RedactedItemDescriptor, type RedactedItemProps, type RedactedMarker, type RedactionConfig, ResourceCard, RoleBadge, type ShareConfig, type ShareData, ShareModal, type ShareModalProps, Timestamp, type UseDistributionResult, type UseProvenanceBundleResult, type UseProvenanceGraphResult, type UseSessionProvenanceResult, VerificationIndicator, VerificationView, bundleHasAI, cn, formatActionType, formatBps, formatBytes, formatChainName, formatCid, formatDate, formatDateAbsolute, formatRole, formatTxHash, getAIAgentSafe, getAIToolSafe, getAuthorizationSafe, getContribSafe, getGovernanceSafe, getLicenseSafe, getOnchainSafe, getPrimaryCreator, getVerificationSafe, getWitnessSafe, useDistribution, useProvenanceBundle, useProvenanceGraph, useProvenanceKit, useSessionProvenance }; diff --git a/packages/provenancekit-ui/dist/index.js b/packages/provenancekit-ui/dist/index.js index 0745c02..960e86f 100644 --- a/packages/provenancekit-ui/dist/index.js +++ b/packages/provenancekit-ui/dist/index.js @@ -9116,6 +9116,7 @@ function getVerification(obj) { return VerificationExtension.parse(data); } var MAX_SAFE_WEIGHT = Number.MAX_SAFE_INTEGER; +var AUTHORIZATION_NAMESPACE = "ext:authorization@1.0.0"; var AuthorizationStatus = external_exports.enum([ "authorized", "unauthorized", @@ -9166,6 +9167,11 @@ var AuthorizationExtension = external_exports.object({ */ proof: external_exports.string().optional() }); +function getAuthorization(obj) { + const data = obj.extensions?.[AUTHORIZATION_NAMESPACE]; + if (!data) return void 0; + return AuthorizationExtension.parse(data); +} var OwnershipEvidenceType = external_exports.enum([ "self-declaration", // No external proof — just a formal assertion @@ -9331,6 +9337,29 @@ var X402Extension = external_exports.object({ (data) => data.requirements !== void 0 || data.proof !== void 0 || data.split !== void 0, { message: "At least one of requirements, proof, or split must be provided" } ); +var GOVERNANCE_NAMESPACE = "ext:governance@1.0.0"; +var GovernanceRequirement = external_exports.enum(["R1", "R2", "R3", "R4", "R5", "R6"]); +var GovernanceExtension = external_exports.object({ + workflow: external_exports.string().optional(), + purpose: external_exports.string().optional(), + roleInWorkflow: external_exports.enum(["source", "prompt", "generated-output", "derived-output", "reference", "final-output"]).optional(), + disclosure: external_exports.object({ + aiInvolved: external_exports.boolean().optional(), + humanContribution: external_exports.string().optional(), + aiContribution: external_exports.string().optional(), + publicSummary: external_exports.string().optional() + }).optional(), + requirements: external_exports.array(GovernanceRequirement).default([]), + policyBasis: external_exports.array(external_exports.string()).optional(), + riskFlags: external_exports.array(external_exports.string()).optional(), + reviewStatus: external_exports.enum(["not-reviewed", "review-required", "reviewed", "approved"]).optional(), + note: external_exports.string().optional() +}); +function getGovernance(obj) { + const data = obj.extensions?.[GOVERNANCE_NAMESPACE]; + if (!data) return void 0; + return GovernanceExtension.parse(data); +} // ../provenancekit-sdk/dist/index.mjs var ProvenanceKitError = class _ProvenanceKitError extends Error { @@ -10179,6 +10208,22 @@ function getWitnessSafe(action) { return null; } } +function getAuthorizationSafe(target) { + if (!target) return null; + try { + return getAuthorization(target) ?? null; + } catch { + return null; + } +} +function getGovernanceSafe(target) { + if (!target) return null; + try { + return getGovernance(target) ?? null; + } catch { + return null; + } +} function bundleHasAI(actions) { return actions.some((a) => getAIToolSafe(a) !== null); } @@ -10345,7 +10390,7 @@ function VerificationIndicator({ } // src/components/primitives/license-chip.tsx -import { Scale, DollarSign, GitBranch, Share2 } from "lucide-react"; +import { Scale, DollarSign, GitBranch, Share2, Ban } from "lucide-react"; import { Fragment, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime"; function formatLicenseLabel(type) { const shorts = { @@ -10386,7 +10431,8 @@ function LicenseChip({ showIcons && !isPublicDomain && license && /* @__PURE__ */ jsxs3(Fragment, { children: [ license.commercial === false && /* @__PURE__ */ jsx5("span", { title: "Non-commercial", children: /* @__PURE__ */ jsx5(DollarSign, { size: 10, strokeWidth: 2, className: "shrink-0 text-amber-500", "aria-label": "Non-commercial" }) }), license.derivatives === false && /* @__PURE__ */ jsx5("span", { title: "No derivatives", children: /* @__PURE__ */ jsx5(GitBranch, { size: 10, strokeWidth: 2, className: "shrink-0 text-amber-500", "aria-label": "No derivatives" }) }), - license.shareAlike && /* @__PURE__ */ jsx5("span", { title: "Share alike required", children: /* @__PURE__ */ jsx5(Share2, { size: 10, strokeWidth: 2, className: "shrink-0 text-blue-500", "aria-label": "Share alike required" }) }) + license.shareAlike && /* @__PURE__ */ jsx5("span", { title: "Share alike required", children: /* @__PURE__ */ jsx5(Share2, { size: 10, strokeWidth: 2, className: "shrink-0 text-blue-500", "aria-label": "Share alike required" }) }), + license.aiTraining === "reserved" && /* @__PURE__ */ jsx5("span", { title: "AI training rights reserved", children: /* @__PURE__ */ jsx5(Ban, { size: 10, strokeWidth: 2, className: "shrink-0 text-red-500", "aria-label": "AI training rights reserved" }) }) ] }) ] } @@ -10561,6 +10607,28 @@ function findVerification(bundle) { } return null; } +function findAuthorization(bundle) { + for (let i = bundle.resources.length - 1; i >= 0; i--) { + const auth = getAuthorizationSafe(bundle.resources[i]); + if (auth) return auth; + } + for (let i = bundle.actions.length - 1; i >= 0; i--) { + const auth = getAuthorizationSafe(bundle.actions[i]); + if (auth) return auth; + } + return null; +} +function findGovernance(bundle) { + for (let i = bundle.resources.length - 1; i >= 0; i--) { + const governance = getGovernanceSafe(bundle.resources[i]); + if (governance) return governance; + } + for (let i = bundle.actions.length - 1; i >= 0; i--) { + const governance = getGovernanceSafe(bundle.actions[i]); + if (governance) return governance; + } + return null; +} function CredRow({ label, value }) { return /* @__PURE__ */ jsxs6("div", { style: { padding: "9px 0" }, children: [ /* @__PURE__ */ jsx9("p", { style: { margin: "0 0 2px", fontSize: 10, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.06em", color: "#9ca3af" }, children: label }), @@ -10579,6 +10647,8 @@ function ProvenancePopover({ const aiTools = getUniqueAITools(bundle); const license = findLicense(bundle); const verification = findVerification(bundle); + const authorization = findAuthorization(bundle); + const governance = findGovernance(bundle); const lastAction = bundle.actions[bundle.actions.length - 1]; const otherContributors = bundle.entities.filter((e) => e.id !== creator?.id); const verifiedLabel = verification?.status === "verified" ? verification.policyUsed ?? "Verified" : verification?.status === "partial" ? "Partially verified" : null; @@ -10618,6 +10688,15 @@ function ProvenancePopover({ if (license?.type) { rows.push({ label: "License", value: license.type }); } + if (license?.aiTraining) { + rows.push({ label: "AI training rights", value: license.aiTraining }); + } + if (authorization?.status) { + rows.push({ label: "Authorization", value: authorization.status }); + } + if (governance?.reviewStatus) { + rows.push({ label: "Governance review", value: governance.reviewStatus.replace(/-/g, " ") }); + } if (verifiedLabel) { rows.push({ label: "Signed with", value: verifiedLabel }); } @@ -11451,7 +11530,7 @@ function EntityCard({ entity }) { } // src/components/bundle/action-card.tsx -import { Zap as Zap2, Bot as Bot4, Clock as Clock2, Shield as Shield2 } from "lucide-react"; +import { Zap as Zap2, Bot as Bot4, Clock as Clock2, Shield as Shield2, AlertTriangle } from "lucide-react"; import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime"; function formatActionType3(t) { return t.replace(/^ext:/, "").replace(/@[\d.]+$/, "").replace(/-/g, " ").replace(/\//g, " \xB7 "); @@ -11459,7 +11538,10 @@ function formatActionType3(t) { function ActionCard({ action }) { const aiTool = getAIToolSafe(action); const verification = getVerificationSafe(action); + const authorization = getAuthorizationSafe(action); + const governance = getGovernanceSafe(action); const isVerified = verification?.status === "verified"; + const needsReview = governance?.reviewStatus === "review-required" || authorization?.status === "pending" || authorization?.status === "unauthorized"; return /* @__PURE__ */ jsxs11( "div", { @@ -11557,7 +11639,51 @@ function ActionCard({ action }) { verification?.policyUsed ?? "Verified" ] } - ) + ), + (authorization || governance) && /* @__PURE__ */ jsxs11("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, marginTop: 6 }, children: [ + authorization?.status && /* @__PURE__ */ jsxs11( + "span", + { + style: { + display: "inline-flex", + alignItems: "center", + gap: 5, + fontSize: 11, + color: authorization.status === "authorized" ? "#047857" : "#b45309", + background: authorization.status === "authorized" ? "rgba(16,185,129,0.08)" : "rgba(245,158,11,0.12)", + border: `1px solid ${authorization.status === "authorized" ? "rgba(16,185,129,0.22)" : "rgba(245,158,11,0.28)"}`, + borderRadius: 6, + padding: "2px 8px" + }, + title: authorization.scope, + children: [ + authorization.status === "authorized" ? /* @__PURE__ */ jsx15(Shield2, { size: 10 }) : /* @__PURE__ */ jsx15(AlertTriangle, { size: 10 }), + authorization.status + ] + } + ), + governance?.reviewStatus && /* @__PURE__ */ jsxs11( + "span", + { + style: { + display: "inline-flex", + alignItems: "center", + gap: 5, + fontSize: 11, + color: needsReview ? "#b45309" : "#047857", + background: needsReview ? "rgba(245,158,11,0.12)" : "rgba(16,185,129,0.08)", + border: `1px solid ${needsReview ? "rgba(245,158,11,0.28)" : "rgba(16,185,129,0.22)"}`, + borderRadius: 6, + padding: "2px 8px" + }, + title: governance.note, + children: [ + needsReview ? /* @__PURE__ */ jsx15(AlertTriangle, { size: 10 }) : /* @__PURE__ */ jsx15(Shield2, { size: 10 }), + governance.reviewStatus.replace(/-/g, " ") + ] + } + ) + ] }) ] }) ] }) }) ] @@ -11566,12 +11692,15 @@ function ActionCard({ action }) { } // src/components/bundle/resource-card.tsx -import { Database as Database2, MapPin as MapPin2, Hash as Hash3, ExternalLink as ExternalLink2 } from "lucide-react"; +import { Database as Database2, MapPin as MapPin2, Hash as Hash3, ExternalLink as ExternalLink2, ShieldCheck as ShieldCheck2, AlertTriangle as AlertTriangle2, Brain } from "lucide-react"; import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime"; function ResourceCard({ resource }) { const cid = resource.address?.ref; const license = getLicenseSafe(resource); + const authorization = getAuthorizationSafe(resource); + const governance = getGovernanceSafe(resource); const location = resource.locations?.[0]; + const needsReview = governance?.reviewStatus === "review-required" || authorization?.status === "pending" || authorization?.status === "unauthorized"; return /* @__PURE__ */ jsxs12( "div", { @@ -11625,6 +11754,71 @@ function ResourceCard({ resource }) { resource.size && /* @__PURE__ */ jsx16("span", { style: { fontSize: 11, color: "#94a3b8" }, children: resource.size < 1024 * 1024 ? `${(resource.size / 1024).toFixed(1)} KB` : `${(resource.size / 1024 / 1024).toFixed(1)} MB` }), license && /* @__PURE__ */ jsx16(LicenseChip, { license }) ] }), + (authorization || governance || license?.aiTraining) && /* @__PURE__ */ jsxs12("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 8 }, children: [ + authorization?.status && /* @__PURE__ */ jsxs12( + "span", + { + style: { + display: "inline-flex", + alignItems: "center", + gap: 4, + fontSize: 11, + color: authorization.status === "authorized" ? "#047857" : "#b45309", + background: authorization.status === "authorized" ? "rgba(16,185,129,0.08)" : "rgba(245,158,11,0.12)", + border: `1px solid ${authorization.status === "authorized" ? "rgba(16,185,129,0.22)" : "rgba(245,158,11,0.28)"}`, + borderRadius: 6, + padding: "2px 7px" + }, + title: authorization.scope, + children: [ + authorization.status === "authorized" ? /* @__PURE__ */ jsx16(ShieldCheck2, { size: 10 }) : /* @__PURE__ */ jsx16(AlertTriangle2, { size: 10 }), + authorization.status + ] + } + ), + license?.aiTraining && /* @__PURE__ */ jsxs12( + "span", + { + style: { + display: "inline-flex", + alignItems: "center", + gap: 4, + fontSize: 11, + color: license.aiTraining === "reserved" ? "#b91c1c" : "#475569", + background: license.aiTraining === "reserved" ? "rgba(239,68,68,0.08)" : "#f8fafc", + border: `1px solid ${license.aiTraining === "reserved" ? "rgba(239,68,68,0.22)" : "#e2e8f0"}`, + borderRadius: 6, + padding: "2px 7px" + }, + children: [ + /* @__PURE__ */ jsx16(Brain, { size: 10 }), + "AI training ", + license.aiTraining + ] + } + ), + governance?.reviewStatus && /* @__PURE__ */ jsxs12( + "span", + { + style: { + display: "inline-flex", + alignItems: "center", + gap: 4, + fontSize: 11, + color: needsReview ? "#b45309" : "#047857", + background: needsReview ? "rgba(245,158,11,0.12)" : "rgba(16,185,129,0.08)", + border: `1px solid ${needsReview ? "rgba(245,158,11,0.28)" : "rgba(16,185,129,0.22)"}`, + borderRadius: 6, + padding: "2px 7px" + }, + title: governance.note, + children: [ + needsReview ? /* @__PURE__ */ jsx16(AlertTriangle2, { size: 10 }) : /* @__PURE__ */ jsx16(ShieldCheck2, { size: 10 }), + governance.reviewStatus.replace(/-/g, " ") + ] + } + ) + ] }), cid && /* @__PURE__ */ jsxs12( "div", { @@ -12010,6 +12204,8 @@ function TrackerActionItem({ action, isLatest, isLast, className }) { const aiTool = getAIToolSafe(action); const verification = getVerificationSafe(action); const isVerified = verification?.status === "verified"; + const actionType = action.type ?? "action"; + const outputCount = action.outputs?.length ?? 0; const dotColor = isLatest ? "#22c55e" : "var(--pk-surface-border, #e2e8f0)"; const dotBorder = isLatest ? "#22c55e" : "var(--pk-surface-border, #e2e8f0)"; return /* @__PURE__ */ jsxs16( @@ -12068,7 +12264,7 @@ function TrackerActionItem({ action, isLatest, isLast, className }) { color: "var(--pk-foreground, #0f172a)", textTransform: "capitalize" }, - children: formatActionType4(action.type) + children: formatActionType4(actionType) } ), isLatest && /* @__PURE__ */ jsx20( @@ -12130,11 +12326,11 @@ function TrackerActionItem({ action, isLatest, isLast, className }) { ] }), /* @__PURE__ */ jsxs16("div", { style: { fontSize: 11, color: "var(--pk-muted-foreground, #64748b)" }, children: [ /* @__PURE__ */ jsx20(Timestamp, { iso: action.timestamp }), - action.outputs.length > 0 && /* @__PURE__ */ jsxs16("span", { style: { marginLeft: 8 }, children: [ + outputCount > 0 && /* @__PURE__ */ jsxs16("span", { style: { marginLeft: 8 }, children: [ "\u2192 ", - action.outputs.length, + outputCount, " output", - action.outputs.length !== 1 ? "s" : "" + outputCount !== 1 ? "s" : "" ] }) ] }) ] }) @@ -12649,6 +12845,21 @@ function LicenseExtensionView({ extension, className }) { extension.expires && /* @__PURE__ */ jsxs22("div", { children: [ "Expires: ", /* @__PURE__ */ jsx26("span", { className: "font-medium text-[var(--pk-foreground)]", children: new Date(extension.expires).toLocaleDateString() }) + ] }), + extension.aiTraining && /* @__PURE__ */ jsxs22("div", { children: [ + "AI training: ", + /* @__PURE__ */ jsx26("span", { className: "font-medium text-[var(--pk-foreground)] capitalize", children: extension.aiTraining }) + ] }), + extension.grantType && /* @__PURE__ */ jsxs22("div", { children: [ + "Grant type: ", + /* @__PURE__ */ jsx26("span", { className: "font-medium text-[var(--pk-foreground)] capitalize", children: extension.grantType }) + ] }), + extension.grantedBy && /* @__PURE__ */ jsxs22("div", { children: [ + "Granted by: ", + /* @__PURE__ */ jsxs22("span", { className: "font-mono text-[var(--pk-foreground)]", children: [ + extension.grantedBy.slice(0, 16), + "\u2026" + ] }) ] }) ] }), extension.termsUrl && /* @__PURE__ */ jsxs22( @@ -13953,7 +14164,7 @@ function ShareModal({ // src/components/provenance/file-provenance-tag.tsx import { useEffect as useEffect7, useState as useState15, useCallback as useCallback8 } from "react"; import { - ShieldCheck as ShieldCheck2, + ShieldCheck as ShieldCheck3, ShieldOff as ShieldOff2, ChevronDown as ChevronDown4, ChevronUp as ChevronUp4, @@ -14119,9 +14330,12 @@ function BundleSummary({ /* @__PURE__ */ jsx34(Tag, { size: 10, className: "shrink-0" }), /* @__PURE__ */ jsx34("span", { className: "capitalize", children: topAction.type }) ] }), - licenseExt && (licenseExt.spdxId || licenseExt.name) && /* @__PURE__ */ jsxs30("div", { className: "flex items-center gap-1.5 text-[var(--pk-muted-foreground,#64748b)]", children: [ - /* @__PURE__ */ jsx34(ShieldCheck2, { size: 10, className: "shrink-0" }), - /* @__PURE__ */ jsx34("span", { className: "truncate", children: licenseExt.spdxId ?? licenseExt.name }) + licenseExt && (licenseExt.type || licenseExt.spdxId || licenseExt.name) && /* @__PURE__ */ jsxs30("div", { className: "flex items-center gap-1.5 text-[var(--pk-muted-foreground,#64748b)]", children: [ + /* @__PURE__ */ jsx34(ShieldCheck3, { size: 10, className: "shrink-0" }), + /* @__PURE__ */ jsxs30("span", { className: "truncate", children: [ + licenseExt.type ?? licenseExt.spdxId ?? licenseExt.name, + licenseExt.aiTraining ? ` \xB7 AI training ${licenseExt.aiTraining}` : "" + ] }) ] }), topAction?.timestamp && /* @__PURE__ */ jsxs30("div", { className: "flex items-center gap-1.5 text-[var(--pk-muted-foreground,#64748b)]", children: [ /* @__PURE__ */ jsx34(Calendar2, { size: 10, className: "shrink-0" }), @@ -14213,7 +14427,7 @@ function FileProvenanceTag({ onClick: () => setExpanded((v) => !v), className: "w-full flex items-center gap-1.5 px-2 pt-1.5 pb-1 hover:bg-[var(--pk-surface-muted,#f8fafc)] transition-colors", children: [ - /* @__PURE__ */ jsx34(ShieldCheck2, { size: 10, className: "shrink-0 text-[var(--pk-verified,#22c55e)]" }), + /* @__PURE__ */ jsx34(ShieldCheck3, { size: 10, className: "shrink-0 text-[var(--pk-verified,#22c55e)]" }), /* @__PURE__ */ jsx34("span", { className: "text-[10px] font-medium truncate flex-1 text-left text-[var(--pk-foreground,#0f172a)]", children: headerLabel }), topMatch.type && /* @__PURE__ */ jsx34("span", { className: "text-[10px] capitalize text-[var(--pk-muted-foreground,#64748b)] shrink-0 mr-1", children: topMatch.type }), expanded ? /* @__PURE__ */ jsx34(ChevronUp4, { size: 10, className: "shrink-0 text-[var(--pk-muted-foreground,#64748b)]" }) : /* @__PURE__ */ jsx34(ChevronDown4, { size: 10, className: "shrink-0 text-[var(--pk-muted-foreground,#64748b)]" }) @@ -14288,7 +14502,9 @@ export { formatTxHash, getAIAgentSafe, getAIToolSafe, + getAuthorizationSafe, getContribSafe, + getGovernanceSafe, getLicenseSafe, getOnchainSafe, getPrimaryCreator, diff --git a/packages/provenancekit-ui/src/components/badge/provenance-popover.tsx b/packages/provenancekit-ui/src/components/badge/provenance-popover.tsx index 917ba5f..8c26faf 100644 --- a/packages/provenancekit-ui/src/components/badge/provenance-popover.tsx +++ b/packages/provenancekit-ui/src/components/badge/provenance-popover.tsx @@ -9,6 +9,8 @@ import { getAIToolSafe, getVerificationSafe, getPrimaryCreator, + getAuthorizationSafe, + getGovernanceSafe, } from "../../lib/extensions"; import type { ProvenanceBundle } from "@provenancekit/sdk"; import type { AIToolExtension } from "../../lib/extensions"; @@ -67,6 +69,30 @@ function findVerification(bundle: ProvenanceBundle) { return null; } +function findAuthorization(bundle: ProvenanceBundle) { + for (let i = bundle.resources.length - 1; i >= 0; i--) { + const auth = getAuthorizationSafe(bundle.resources[i]!); + if (auth) return auth; + } + for (let i = bundle.actions.length - 1; i >= 0; i--) { + const auth = getAuthorizationSafe(bundle.actions[i]!); + if (auth) return auth; + } + return null; +} + +function findGovernance(bundle: ProvenanceBundle) { + for (let i = bundle.resources.length - 1; i >= 0; i--) { + const governance = getGovernanceSafe(bundle.resources[i]!); + if (governance) return governance; + } + for (let i = bundle.actions.length - 1; i >= 0; i--) { + const governance = getGovernanceSafe(bundle.actions[i]!); + if (governance) return governance; + } + return null; +} + // Credential row: small uppercase label above, value below function CredRow({ label, value }: { label: string; value: React.ReactNode }) { return ( @@ -96,6 +122,8 @@ export function ProvenancePopover({ const aiTools = getUniqueAITools(bundle); const license = findLicense(bundle); const verification = findVerification(bundle); + const authorization = findAuthorization(bundle); + const governance = findGovernance(bundle); const lastAction = bundle.actions[bundle.actions.length - 1]; const otherContributors = bundle.entities.filter((e) => e.id !== creator?.id); @@ -151,6 +179,18 @@ export function ProvenancePopover({ rows.push({ label: "License", value: license.type }); } + if (license?.aiTraining) { + rows.push({ label: "AI training rights", value: license.aiTraining }); + } + + if (authorization?.status) { + rows.push({ label: "Authorization", value: authorization.status }); + } + + if (governance?.reviewStatus) { + rows.push({ label: "Governance review", value: governance.reviewStatus.replace(/-/g, " ") }); + } + if (verifiedLabel) { rows.push({ label: "Signed with", value: verifiedLabel }); } diff --git a/packages/provenancekit-ui/src/components/bundle/action-card.tsx b/packages/provenancekit-ui/src/components/bundle/action-card.tsx index 528ef5c..442779e 100644 --- a/packages/provenancekit-ui/src/components/bundle/action-card.tsx +++ b/packages/provenancekit-ui/src/components/bundle/action-card.tsx @@ -1,9 +1,9 @@ "use client"; import React from "react"; -import { Zap, Bot, Clock, Shield } from "lucide-react"; +import { Zap, Bot, Clock, Shield, AlertTriangle } from "lucide-react"; import { Timestamp } from "../primitives/timestamp"; -import { getAIToolSafe, getVerificationSafe } from "../../lib/extensions"; +import { getAIToolSafe, getAuthorizationSafe, getGovernanceSafe, getVerificationSafe } from "../../lib/extensions"; import type { Action } from "@provenancekit/eaa-types"; interface ActionCardProps { @@ -17,7 +17,10 @@ function formatActionType(t: string): string { export function ActionCard({ action }: ActionCardProps) { const aiTool = getAIToolSafe(action); const verification = getVerificationSafe(action); + const authorization = getAuthorizationSafe(action); + const governance = getGovernanceSafe(action); const isVerified = verification?.status === "verified"; + const needsReview = governance?.reviewStatus === "review-required" || authorization?.status === "pending" || authorization?.status === "unauthorized"; return (
)} + + {(authorization || governance) && ( +
+ {authorization?.status && ( + + {authorization.status === "authorized" ? : } + {authorization.status} + + )} + {governance?.reviewStatus && ( + + {needsReview ? : } + {governance.reviewStatus.replace(/-/g, " ")} + + )} +
+ )}
diff --git a/packages/provenancekit-ui/src/components/bundle/resource-card.tsx b/packages/provenancekit-ui/src/components/bundle/resource-card.tsx index 5a6a9b7..8715650 100644 --- a/packages/provenancekit-ui/src/components/bundle/resource-card.tsx +++ b/packages/provenancekit-ui/src/components/bundle/resource-card.tsx @@ -1,9 +1,9 @@ "use client"; import React from "react"; -import { Database, MapPin, Hash, ExternalLink } from "lucide-react"; +import { Database, MapPin, Hash, ExternalLink, ShieldCheck, AlertTriangle, Brain } from "lucide-react"; import { LicenseChip } from "../primitives/license-chip"; -import { getLicenseSafe } from "../../lib/extensions"; +import { getAuthorizationSafe, getGovernanceSafe, getLicenseSafe } from "../../lib/extensions"; import type { Resource } from "@provenancekit/eaa-types"; interface ResourceCardProps { @@ -13,7 +13,10 @@ interface ResourceCardProps { export function ResourceCard({ resource }: ResourceCardProps) { const cid = resource.address?.ref; const license = getLicenseSafe(resource); + const authorization = getAuthorizationSafe(resource); + const governance = getGovernanceSafe(resource); const location = resource.locations?.[0]; + const needsReview = governance?.reviewStatus === "review-required" || authorization?.status === "pending" || authorization?.status === "unauthorized"; return (
}
+ {(authorization || governance || license?.aiTraining) && ( +
+ {authorization?.status && ( + + {authorization.status === "authorized" ? : } + {authorization.status} + + )} + {license?.aiTraining && ( + + + AI training {license.aiTraining} + + )} + {governance?.reviewStatus && ( + + {needsReview ? : } + {governance.reviewStatus.replace(/-/g, " ")} + + )} +
+ )} + {/* CID */} {cid && (
Expires: {new Date(extension.expires).toLocaleDateString()}
)} + {extension.aiTraining && ( +
AI training: {extension.aiTraining}
+ )} + {extension.grantType && ( +
Grant type: {extension.grantType}
+ )} + {extension.grantedBy && ( +
Granted by: {extension.grantedBy.slice(0, 16)}…
+ )} {extension.termsUrl && ( diff --git a/packages/provenancekit-ui/src/components/primitives/license-chip.tsx b/packages/provenancekit-ui/src/components/primitives/license-chip.tsx index 6b3d828..b99bb05 100644 --- a/packages/provenancekit-ui/src/components/primitives/license-chip.tsx +++ b/packages/provenancekit-ui/src/components/primitives/license-chip.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Scale, DollarSign, GitBranch, Share2 } from "lucide-react"; +import { Scale, DollarSign, GitBranch, Share2, Ban } from "lucide-react"; import { cn } from "../../lib/utils"; import type { LicenseExtension } from "../../lib/extensions"; @@ -67,6 +67,11 @@ export function LicenseChip({ )} + {license.aiTraining === "reserved" && ( + + + + )} )} diff --git a/packages/provenancekit-ui/src/components/provenance/file-provenance-tag.tsx b/packages/provenancekit-ui/src/components/provenance/file-provenance-tag.tsx index e67eb35..0d2de12 100644 --- a/packages/provenancekit-ui/src/components/provenance/file-provenance-tag.tsx +++ b/packages/provenancekit-ui/src/components/provenance/file-provenance-tag.tsx @@ -94,7 +94,7 @@ function BundleSummary({ const topResource = bundle.resources?.[0]; const licenseExt = topResource?.extensions?.["ext:license@1.0.0"] as - | { spdxId?: string; name?: string } + | { type?: string; spdxId?: string; name?: string; aiTraining?: string } | undefined; const aiExt = topAction?.extensions?.["ext:ai@1.0.0"] as | { provider?: string; model?: string } @@ -152,10 +152,13 @@ function BundleSummary({ )} - {licenseExt && (licenseExt.spdxId || licenseExt.name) && ( + {licenseExt && (licenseExt.type || licenseExt.spdxId || licenseExt.name) && (
- {licenseExt.spdxId ?? licenseExt.name} + + {licenseExt.type ?? licenseExt.spdxId ?? licenseExt.name} + {licenseExt.aiTraining ? ` · AI training ${licenseExt.aiTraining}` : ""} +
)} diff --git a/packages/provenancekit-ui/src/components/tracker/tracker-action-item.tsx b/packages/provenancekit-ui/src/components/tracker/tracker-action-item.tsx index 92cab0a..84bee80 100644 --- a/packages/provenancekit-ui/src/components/tracker/tracker-action-item.tsx +++ b/packages/provenancekit-ui/src/components/tracker/tracker-action-item.tsx @@ -19,6 +19,8 @@ export function TrackerActionItem({ action, isLatest, isLast, className }: Track const aiTool = getAIToolSafe(action); const verification = getVerificationSafe(action); const isVerified = verification?.status === "verified"; + const actionType = action.type ?? "action"; + const outputCount = action.outputs?.length ?? 0; const dotColor = isLatest ? "#22c55e" : "var(--pk-surface-border, #e2e8f0)"; const dotBorder = isLatest ? "#22c55e" : "var(--pk-surface-border, #e2e8f0)"; @@ -77,7 +79,7 @@ export function TrackerActionItem({ action, isLatest, isLast, className }: Track textTransform: "capitalize", }} > - {formatActionType(action.type)} + {formatActionType(actionType)} {isLatest && ( @@ -138,9 +140,9 @@ export function TrackerActionItem({ action, isLatest, isLast, className }: Track {/* Timestamp */}
- {action.outputs.length > 0 && ( + {outputCount > 0 && ( - → {action.outputs.length} output{action.outputs.length !== 1 ? "s" : ""} + → {outputCount} output{outputCount !== 1 ? "s" : ""} )}
diff --git a/packages/provenancekit-ui/src/index.ts b/packages/provenancekit-ui/src/index.ts index b8d1528..7aa9d7a 100644 --- a/packages/provenancekit-ui/src/index.ts +++ b/packages/provenancekit-ui/src/index.ts @@ -39,6 +39,8 @@ export { getOnchainSafe, getVerificationSafe, getWitnessSafe, + getAuthorizationSafe, + getGovernanceSafe, bundleHasAI, getPrimaryCreator, } from "./lib/extensions"; diff --git a/packages/provenancekit-ui/src/lib/extensions.ts b/packages/provenancekit-ui/src/lib/extensions.ts index 0df67d3..e102e06 100644 --- a/packages/provenancekit-ui/src/lib/extensions.ts +++ b/packages/provenancekit-ui/src/lib/extensions.ts @@ -11,6 +11,8 @@ import { getOnchain, getVerification, getWitness, + getAuthorization, + getGovernance, type AIToolExtension, type AIAgentExtension, type LicenseExtension, @@ -18,6 +20,8 @@ import { type OnchainExtension, type VerificationExtension, type WitnessExtension, + type AuthorizationExtension, + type GovernanceExtension, } from "@provenancekit/extensions"; import type { Action, Entity, Resource, Attribution } from "@provenancekit/eaa-types"; @@ -93,6 +97,24 @@ export function getWitnessSafe(action: Action | null | undefined): WitnessExtens } } +export function getAuthorizationSafe(target: AnyEaaType | null | undefined): AuthorizationExtension | null { + if (!target) return null; + try { + return getAuthorization(target as any) ?? null; + } catch { + return null; + } +} + +export function getGovernanceSafe(target: AnyEaaType | null | undefined): GovernanceExtension | null { + if (!target) return null; + try { + return getGovernance(target as any) ?? null; + } catch { + return null; + } +} + /** Check if any action in a bundle used an AI tool */ export function bundleHasAI(actions: Action[]): boolean { return actions.some((a) => getAIToolSafe(a) !== null); @@ -116,4 +138,6 @@ export type { OnchainExtension, VerificationExtension, WitnessExtension, + AuthorizationExtension, + GovernanceExtension, };