Four interactive notes on meeting the EU AI Act without breaking the GDPR — built for startups, SMEs, and engineering teams. Every figure runs the real logic.
Articles 11, 12 and 14 and Annex IV apply to high-risk AI systems.
5 min read
GDPR vs. the AI Act: Why Default LLM Tracing Can Keep More Than You Need
Teams preparing for Art. 12 logging for high-risk AI systems reuse their tracing stack. By default it keeps what data protection law wants kept least: the raw prompt.
Spurvik SDK, in processHMAC-SHA256 with your key; text dropped from memory spurvik 1.0.8 · MIT
digests
Your EU event store180,000signed events, 0 transcripts
Fig. 1.1Where personal data sits at rest after go-liveINS-01 · rev 2026.10
Day 180 is the six-month floor in Art. 19(1) and 26(6). At 1,000 conversations a day, a default setup then holds 180,000 plain-text transcripts: names, CPR numbers, diagnoses, whatever users typed.Fair note. LangSmith and Helicone offer masking, self-hosting and EU regions. Teams must configure them; the default logs raw text. Based on public documentation, October 2026.
Stated purposeKeep transcripts six months “to comply with Art. 12” for high-risk AI systems
Necessity, Art. 5(1)(c)Does Art. 12 need the text? For most high-risk systems, no.
Resultfails necessityArt. 26(6) points back to the GDPR.
Storing prompts is not unlawful in itself; with a lawful basis and short retention, many teams keep them for debugging. The conflict starts when the AI Act becomes the reason to keep them. And users volunteer Art. 9 health data (“I’m in hospital this week”), so free text cannot be bounded in advance. Only what you keep can.
An event log that supports your Art. 12 logging obligations for high-risk AI systems records what happened. It does not need to hold what was said.
Fig. 1.2One character changed, an unrelated digestINS-02 · rev 2026.10
Same text and key, same digest. So a disputed transcript can be checked by recomputing, without the log ever holding it. The key matters: a plain SHA-256 of a CPR number is enumerable in seconds; without your key, the HMAC is not.Key custody. Keep the key in a KMS apart from the logs, rotate it, and record key_version.
Before: typical tracing callfull text at rest
// Captured on every request, kept for the store's retention period
tracer.log({
user_id: session.userId,
prompt: messages.at(-1).content,
completion: result.text,
tools: result.toolCalls,
});
One precision a DPO will ask for: a keyed-HMAC digest is pseudonymised data under GDPR Art. 4(5), not anonymous, while the key exists. Treat the store as personal data. What shrinks is what you hold, digests instead of transcripts. That is why we state “no raw prompt text persisted” and “pseudonymised HMAC digests”: no raw personal data is written to the event log.Erasure. Destroying a key version makes its digests unusable for verification — they can no longer be checked against the original key. Align key lifetimes with retention.
Field names refer to the AI Act article a field is designed to help with. They do not mean the output meets that article.
Open-source MIT packages on npm (spurvik 1.0.8, spurvik-sdk 1.0.8, and spurvik-mcp-server 1.0.4; source at github.com/spurvik/spurvik) write these signed events today, into a store you choose in the EU, so your pipeline supports your Art. 12 logging obligations for high-risk AI systems. EU-hosted retention: planned
Deterministic Pre-Flight Routing with System-One (Jev): Stopping Agent Retry Loops and Enforcing Art. 14 Human-in-the-Loop for High-Risk AI Before Side Effects Fire
A retry is cheap the first time and expensive the fortieth. A payment sent before anyone looks is expensive the first time. System-One runs before either.
Articles 11, 12 and 14 and Annex IV apply to high-risk AI systems.
Fig. 2.1System-One, an in-browser port of the SDK routerINS-03 · rev 2026.10
Pick send_payment and flip shadow and active: the enforced action stays ask_human. Pick No signals: proceed wins with only about 55%, because four 0.2 logits still share the mass.Receipt key. The figure uses a public demo key. In production the key is yours and never leaves your infrastructure.
stop_retry fires once an error has repeated twice.
Premature side effect irreversible
No gate too latesend_payment→reviewer
System-One in timeask_human→reviewer→send_payment
An email already sent cannot be recalled.
For high-risk AI systems, Art. 14(4)(d) and (e) require that a person can override the output or halt the system. Both presuppose the decision point comes before the irreversible act.Scope. Art. 14 sets duties only for high-risk AI systems; for Annex III it applies from 2 December 2027. Many teams gate every side-effecting agent anyway.
Same state, same decision, reproducible months later.
Microseconds
Regular expressions and a five-number softmax. No network call.
Unpersuadable
A regex cannot be talked out of its answer.
The name borrows Kahneman’s System 1: the reflex that fires before the model deliberates. Internally, Jev.Status. Available in our open-source MIT SDK on npm (spurvik 1.0.8 · github.com/spurvik/spurvik).
Spurvik starts inside one company’s codebase. The Stage F cross-organisational agent mandate protocol (planned) starts when its agents transact with another company’s, and you may need to show who authorised what.
Author
EyeTea Solutions ApS (CVR 37093378)
Cites
AI Act Art. 50(1); for high-risk AI systems Art. 11 and Annex IV, 12, 14, 17, 19(1), 26(6); GDPR Art. 25, 30, 32
Source
Stage F cross-organisational agent mandate protocol (planned) headers and machine courtroom spurvik 1.0.8 · MIT
Fig. 3.1Six-stage governance & Stage F cross-organisational agent mandate protocol (planned), stages A to FINS-04 · rev 2026.10
A to C live inside one company: where does AI run, were people told, could a person stop it? D and E turn those answers into evidence that survives time. F carries it across a company boundary.
Fig. 3.2Delete one event: the table still verifies, the chain does notINS-05 · rev 2026.10
Each entry’s HMAC covers the previous entry’s HMAC, so removing or editing one breaks verification from that point on. Entries hold pseudonymised HMAC digests and structural fields only, with no raw prompt text persisted, kept at least six months (Art. 19(1), 26(6)).Where it runs. Open-source MIT SDK on npm (spurvik 1.0.8 · github.com/spurvik/spurvik): Keyed HMAC-SHA256 hash-chained receipts + Article 14 HITL gate. EU-hosted retention is planned.
machine-courtroom.tsTypeScript
export functionverifyArt12HmacChain(chain, hmacKey) {
let prev = "GENESIS";
for (const entry of chain) {
if (entry.previousHmac !== prev) return false;
const expected = createHmac("sha256", hmacKey)
.update(`${entry.eventId}|${entry.timestamp}|${entry.payloadHash}|${entry.previousHmac}`)
.digest("hex");
if (expected !== entry.entryHmac) return false;
prev = entry.entryHmac;
}
return true;
}
Illustrative. Planned feature; identifiers are made up.
Signed payload, depth 3
Fig. 3.3Delegation chain with authority decay and maxDepthINS-06 · rev 2026.10
A mandate states which tools an agent may call, what it may spend and how far it may delegate. It is canonicalised RFC 8785-style. Records are signed as HMAC-SHA-256 digests with a key you hold. Public-key signatures and did:key identities are planned. Each hop multiplies the caps by authorityDecayFactor; beyond maxDepth 3, delegation is rejected.Field names. The spurvik 1.0.8 SDK keeps the Usd suffix on cap fields; currency sets the unit.
Illustrative demo. Not output from the spurvik SDK.
Articles 11, 12 and 14 and Annex IV apply to high-risk AI systems.
Fig. 3.4Machine courtroom: integrity, bounds, oversight, in that orderINS-07 · rev 2026.10
Checks run in a fixed order and stop at the first failure. Integrity first: a verdict on a log with broken chained HMAC-SHA256 digests is rejected immediately. Bounds before oversight: a reviewer’s click cannot approve a counterparty the mandate never allowed. The liable entity is always the mandate issuer, and the verdict itself is signed.
Not a court. It reads a mandate both parties relied on; disputes still go where the contract sends them.
Not a conformity assessment. Nothing here certifies an AI system.
Current stage. A to C run in our open-source MIT SDK on npm (spurvik 1.0.8, source at github.com/spurvik/spurvik); hosted D retention is planned; E is a draft export; F is the Stage F cross-organisational agent mandate protocol (planned).
“Planetary” means scope: the jurisdiction field (EU-DK, US-DE, UK-LLOYDS, GLOBAL) lets agents under different regimes verify each other. We start with Denmark.
4. AI Builder Playbook: Adding Art. 50 Transparency, plus Art. 12 & 14 Controls for High-Risk AI in Lovable, v0, Replit & Cursor
You shipped an AI feature in a weekend using Lovable, v0, Replit, or Cursor. Now an EU customer asks how you handle Art. 50 transparency and, for high-risk AI systems, Art. 12 logging and Art. 14 human oversight. Here is the plain-language three-control blueprint.
Startups and SMEs building with AI coding tools rarely have a full-time Data Protection Officer. Instead of 200-page policy binders, you can wire three concrete runtime controls directly into your application:
1. Art. 50(1) Notice Badge
Show a clear badge at first contact naming the legal company your AI assistant acts for, and log art50_notice_shown: true.
2. Art. 12 Keyed-HMAC Chain
Supports your Art. 12 logging obligations (for high-risk AI systems) with pseudonymised HMAC digests and chained HMAC-SHA256 digests — with no raw prompt text persisted.
3. Art. 14 Pre-Flight Gate
Run System-One (Jev) before tool execution so side-effecting calls (send_payment, delete, email) pause for human approval. This supports your Art. 14 human-oversight obligations for high-risk AI systems.
Articles 11, 12 and 14 and Annex IV apply to high-risk AI systems.
Both adapters live in spurvik (1.0.7 on npm, MIT) and take the same SpurvikConfig object as withSpurvik. They record keyed HMAC-SHA-256 fingerprints without sending prompt or output text to the control plane, and check the halt state before model calls when halt enforcement is on.
spurvik/mastra · withSpurvikMastranpm 1.0.7
import { Agent } from "@mastra/core/agent";
import { provenanceFromMetadata, withSpurvikMastra } from "spurvik/mastra";
const spurvik = withSpurvikMastra(yourLanguageModel, {
apiKey: process.env.SPURVIK_API_KEY!,
signingSecret: process.env.SPURVIK_SIGNING_SECRET!,
contentHash: { mode: "keyed", secret: process.env.SPURVIK_CONTENT_HASH_SECRET! },
baseUrl: process.env.SPURVIK_BASE_URL!, // required in production
riskTier: "limited_art50", // Sample value; set your own classification
});
const agent = new Agent({
id: "support-agent",
name: "Support Agent",
instructions: "Reply briefly.",
model: spurvik.model,
});
const out = await agent.generate("Hello");
const receipt = provenanceFromMetadata(out.providerMetadata);
await spurvik.flush();
import { Agent, OpenAIProvider, Runner } from "@openai/agents";
import { provenanceFromAgentsResponse, withSpurvikModelProvider } from "spurvik/openai-agents";
const spurvik = withSpurvikModelProvider(
new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY, useResponses: true }),
{
apiKey: process.env.SPURVIK_API_KEY!,
signingSecret: process.env.SPURVIK_SIGNING_SECRET!,
contentHash: { mode: "keyed", secret: process.env.SPURVIK_CONTENT_HASH_SECRET! },
baseUrl: process.env.SPURVIK_BASE_URL!, // required in production
riskTier: "limited_art50", // Sample value; set your own classification
},
);
const runner = new Runner({ modelProvider: spurvik, tracingDisabled: true, traceIncludeSensitiveData: false });
const agent = new Agent({ name: "Support Agent", instructions: "Reply briefly.", model: "gpt-4o-mini" });
const result = await runner.run(agent, "Hello");
const receipt = provenanceFromAgentsResponse(result.rawResponses.at(-1));
await spurvik.flush();
Articles 12 and 14 apply to high-risk AI systems. Its provenance record supports, but does not replace, Article 50(2) machine-readable marking.
§How to Add EU AI Act Art. 50 Notices, Keyed HMAC Logs (Art. 12) & Human Gates (Art. 14) to Next.js, Vercel AI SDK & LangGraph
For TypeScript teams running Next.js App Router with the Vercel AI SDK (spurvik/ai) and LangGraph (spurvik/langgraph), our open-source MIT packages on npm — spurvik@1.0.8 (alias spurvik-sdk@1.0.8) and spurvik-mcp-server@1.0.4 (source at github.com/spurvik/spurvik) — wire three concrete runtime controls into your stack. Article 50 applies now. For generative systems already on the market before 2 August 2026, marking and detection are due by 2 December 2026 (Commission guidelines C(2026) 7702 final, para 153). Spurvik's provenance record supports, but does not replace, Article 50(2) machine-readable marking. The log is hash-chained, with HMAC-signed receipts (keyed with a secret you control). No prompt or answer text is stored in the default mode. For high-risk AI systems (such as Annex III systems ahead of 2 December 2027 under Art. 113(c)(i)), the keyed HMAC receipt chain and the hosted human-oversight queue (Pro and higher) are designed to support the Article 12 logging and Article 14 human oversight measures you put in place; riskTier and role classification remain yours (with Annex I product-safety high-risk AI systems applying from 2 August 2028, as amended by Regulation (EU) 2026/1744, in force since 27 July 2026).
Author: EyeTea Solutions ApS (CVR 37093378) · Trekantområdet, Denmark · Packages: spurvik@1.0.8 · spurvik-sdk@1.0.8 · spurvik-mcp-server@1.0.4 · MIT (github.com/spurvik/spurvik) · Scope: EU AI Act Art. 50(1), 50(2), 50(3); for high-risk AI systems, Art. 12 & Art. 14; GDPR Art. 5(1)(c), 25, 32.
// app/api/chat/route.ts — Next.js App Router + Vercel AI SDK (spurvik@1.0.8, MIT)
import { after } from "next/server";
import { generateText, wrapLanguageModel } from "ai";
import { openai } from "@ai-sdk/openai";
import {
spurvikMiddleware,
provenanceFromMetadata,
extractFinishReason,
isInterruptedFinishReason,
dedupeToolApprovalResponses,
} from "spurvik/ai";
// Create once at module scope; secrets stay on the server (never in NEXT_PUBLIC_).
const spurvik = spurvikMiddleware({
apiKey: process.env.SPURVIK_API_KEY!,
signingSecret: process.env.SPURVIK_SIGNING_SECRET!,
contentHash: { mode: "keyed", secret: process.env.SPURVIK_CONTENT_HASH_SECRET! },
baseUrl: process.env.SPURVIK_BASE_URL ?? "https://console.spurvik.com",
riskTier: "limited_art50", // your own classification ("minimal" | "limited_art50" | "high_annex3")
article50Disclosure: true, // set true only when your UI renders an Art. 50(1) notice
registerProcessHooks: false,
telemetry: { flushTimeoutMs: 3000 },
});
const model = wrapLanguageModel({ model: openai("gpt-4o-mini"), middleware: spurvik });
export async function POST(req: Request) {
const { prompt, approvals = [] } = (await req.json()) as { prompt: string; approvals?: unknown[] };
const cleanApprovals = dedupeToolApprovalResponses(approvals);
const result = await generateText({ model, prompt });
const receipt = provenanceFromMetadata(result.providerMetadata);
const finishReason = extractFinishReason(result.finishReason);
const interrupted = isInterruptedFinishReason(finishReason);
// Flush queued keyed-HMAC telemetry after the response completes.
after(() => spurvik.flush());
// Direct OpenAI/Anthropic/Gemini clients can also use withSpurvik(client, config).
// Its provenance record supports, but does not replace, Article 50(2) machine-readable marking.
return Response.json(
{ text: result.text, receiptId: receipt?.eventId ?? null, interrupted, approvalsCount: cleanApprovals.length },
{ headers: { "X-Spurvik-Receipt-Id": receipt?.eventId ?? "" } },
);
}
Step 2 · LangGraph (spurvik/langgraph) + Human GateArt. 14 Gate
// lib/support-graph.ts — LangGraph state graph + Article 14 human gate (spurvik/langgraph 1.0.7, MIT)
import { Command } from "@langchain/langgraph";
import {
withSpurvikLangGraph,
spurvikInterrupt,
resolveLangGraphInterrupts,
provenanceFromLangGraphState,
type SpurvikConfig,
} from "spurvik/langgraph";
const spurvikConfig: SpurvikConfig = {
apiKey: process.env.SPURVIK_API_KEY!,
signingSecret: process.env.SPURVIK_SIGNING_SECRET!,
contentHash: { mode: "keyed", secret: process.env.SPURVIK_CONTENT_HASH_SECRET! },
baseUrl: process.env.SPURVIK_BASE_URL ?? "https://console.spurvik.com",
riskTier: "high_annex3", // high-risk AI systems (Art. 12 logging & Art. 14 human oversight)
article50Disclosure: true,
};
// Inside a graph node before a consequential action (e.g., refund, wire, record deletion):
export async function issueRefundNode(state: { orderId: string; amountEur: number }) {
const decision = await spurvikInterrupt(
{
actionName: "issue_refund",
callId: "refund_" + state.orderId,
payload: { orderId: state.orderId, amountEur: state.amountEur },
},
spurvikConfig,
);
// Hosted queue (Pro and higher) records the decision, when, who, and the reason given (stored as entered, not checked).
// Direct SDK clients can also call requireHumanApproval and catch SpurvikHitlRejectedError / SpurvikHitlTimeoutError / SpurvikHaltedError / SpurvikApiError.
if (decision.status !== "approved") return { status: "paused_or_rejected", approvalStatus: decision.status };
return { status: "executed", approvalId: decision.resume.approvalId };
}
// Wrap your compiled StateGraph and resolve __interrupt__ items when resuming:
export async function runSupportGraph(compiledGraph: any, messages: unknown[], threadId: string) {
const graph = withSpurvikLangGraph(compiledGraph, spurvikConfig, { modelId: "support-langgraph" });
const state = await graph.invoke({ messages }, { configurable: { thread_id: threadId } });
const receipt = provenanceFromLangGraphState(state);
const interrupts = (state as { __interrupt__?: any[] }).__interrupt__ ?? [];
if (interrupts.length > 0) {
const resolved = await resolveLangGraphInterrupts(interrupts, spurvikConfig);
await graph.flush();
const paused = !resolved.decisions.every((d) => d.status === "approved");
return { paused, command: paused ? null : new Command(resolved.command), receiptId: receipt?.eventId };
}
await graph.flush();
return { paused: false, state, receiptId: receipt?.eventId };
}
Step 3 · Next.js UI Disclosure (<AIDisclosureLabel />, 24 EU languages)Art. 50(1) · 24 langs
// app/chat/page.tsx — Next.js UI disclosure (Art. 50(1) & 50(3), 24 EU languages incl. Polish locale="pl")
import type { Metadata } from "next";
import { AIDisclosureLabel } from "spurvik/react";
// Note: spurvik/react (included in spurvik@1.0.8 on npm),
// or install the self-contained component via: pnpm dlx shadcn@latest add https://spurvik.com/r/eu-ai-act.json
export const metadata: Metadata = {
other: {
"ai-disclosure": "ai-assisted", // machine-readable page <meta name="ai-disclosure" content="ai-assisted">
},
};
export default function SupportChatPage({ locale = "en" }: { locale?: "en" | "pl" | "da" | "de" | "fr" }) {
return (
<section aria-label="Customer support assistant">
{/* Always-visible Art. 50(1) interaction notice in 24 EU languages (e.g. English "en" or Polish "pl") */}
<AIDisclosureLabel
notice="interaction"
systemName="Support Assistant"
locale={locale}
presentation="inline"
/>
{/* Polish (locale="pl") example for Art. 50(1) / 50(3) disclosure: */}
<AIDisclosureLabel
notice="interaction"
systemName="Support Assistant"
locale="pl"
presentation="block"
/>
</section>
);
}
Scope & Art. 50(2) boundary: Spurvik does not mark content itself. Its provenance record supports, but does not replace, Article 50(2) machine-readable marking. In Step 2, the hosted human-oversight queue (Pro and higher) records the decision, when it was made, who made it, and the reason given; Spurvik stores that reason as entered and doesn’t check it. For high-risk AI systems, it is designed to support the Article 12 logging and Article 14 human oversight measures you put in place.
§Quickstart: wrap one OpenAI call and check a signed receipt
Install the SDK from npm: npm install spurvik (version 1.0.8; the same build is published as spurvik-sdk). The source is at github.com/spurvik/spurvik under the MIT licence. The SDK and MCP server are MIT and run in your own stack. Set SPURVIK_API_KEY, SPURVIK_SIGNING_SECRET, SPURVIK_CONTENT_HASH_SECRET, and SPURVIK_BASE_URL (https://console.spurvik.com, the hosted console) on your server. The SDK does not read these variables by itself; your code passes them in.
1. Wrap a call and save the record · app.jsnpm 1.0.7
import { writeFile } from "node:fs/promises";
import OpenAI from "openai";
import { provenanceOf, withSpurvik } from "spurvik";
const ai = withSpurvik(new OpenAI(), {
apiKey: process.env.SPURVIK_API_KEY,
signingSecret: process.env.SPURVIK_SIGNING_SECRET,
contentHash: { mode: "keyed", secret: process.env.SPURVIK_CONTENT_HASH_SECRET },
baseUrl: process.env.SPURVIK_BASE_URL,
riskTier: "limited_art50", // your own classification (as provider or deployer); Spurvik does not classify your use case
article50Disclosure: false, // set true only if your UI shows an AI interaction notice
onTelemetryError: (error) => console.error("Spurvik telemetry was not delivered:", error.message),
});
const completion = await ai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Hello" }],
});
console.log("Model said:", completion.choices[0].message.content);
const record = await provenanceOf(completion);
await writeFile("record.json", JSON.stringify(record, null, 2));
console.log("Saved provenance record", record.eventId, "to record.json");
await ai._spurvik.shutdown(); // send queued telemetry before the script ends
2. Check the record's signature · check-record.jsHMAC-SHA-256
import { readFile } from "node:fs/promises";
import { verifyProvenanceSignature } from "spurvik";
const record = JSON.parse(await readFile("record.json", "utf8"));
const secret = process.env.SPURVIK_SIGNING_SECRET;
// The record first, the signing secret second.
console.log("Signature matches:", await verifyProvenanceSignature(record, secret));
console.log("After editing modelId:", await verifyProvenanceSignature({ ...record, modelId: "some-other-model" }, secret));
// Signature matches: true
// After editing modelId: false
Spurvik does not mark content itself. Its provenance record supports, but does not replace, Article 50(2) machine-readable marking. A match shows the signed fields still match the HMAC-SHA-256 signature made with your signing secret. It does not show that a notice was shown, that output is marked, or that your system meets the Act. Anyone who holds the secret can make a matching signature, so limit who holds it.
Technical documentation (spurvik@1.0.8, MIT). The Spurvik plugin gives Claude Code three slash commands and one skill. The commands are instructions to Claude, not a scanner program. Claude reads your files and proposes edits; you review them. A finding is not a legal conclusion. The plugin is in the public spurvik/spurvik repository (MIT) under plugins/spurvik; add it in Claude Code with /plugin marketplace add spurvik/spurvik.
Slash commands & guardrails skillplugins/spurvik
# /spurvik:check
# Claude searches your workspace and reports model clients that are not
# wrapped, logs that print prompt text, AI chat UIs with no visible AI
# notice (Art. 50(1)), and side-effect tools with no human-oversight call
# (for high-risk AI systems). The table is a to-do list, not evidence of conformity.
# /spurvik:wrap [file]
# Claude adds withSpurvik or spurvikMiddleware, shows the diff,
# and runs your typecheck if you have one.
# /spurvik:engineering-summary
# Claude writes SPURVIK-ENGINEERING-SUMMARY.md for your DPO or counsel.
# Not evidence of conformity and not legal advice.
# Skill: eu-ai-act-guardrails
Bundled local stdio MCP · plugins/spurvik/.mcp.jsonstdio MCP · d18dfba
{
"mcpServers": {
"spurvik": {
"command": "node",
"args": [
"${CLAUDE_PLUGIN_ROOT}/bin/spurvik-mcp.mjs",
"--root",
"${CLAUDE_PROJECT_DIR}"
]
}
}
}
// Four read-only tools: spurvik_setup_snippet, spurvik_find_unwrapped_calls,
// spurvik_explain_receipt, and spurvik_marking_hooks.
// Local stdio; no network calls.
The MCP server is built from the same source as spurvik-mcp-server 1.0.4 on npm (MIT). Its tools are read-only and make no network calls. Spurvik does not mark content itself. Its provenance record supports, but does not replace, Article 50(2) machine-readable marking.
No. Spurvik records your AI calls with HMAC-SHA-256 receipts and offers hooks where you add your own AI notice and your own marking. Its provenance record supports, but does not replace, Article 50(2) machine-readable marking.
When does Article 50 apply?
Article 50 transparency obligations apply from 2 August 2026. For generative systems placed on the market before 2 August 2026, Article 50(2) applies from 2 December 2026 (Art. 111(4)); marking and detection are due by that date (Commission guidelines C(2026) 7702 final, para 153). Article 50 does not require logging. Please check these dates with your own counsel.
What does a matching signature tell me?
A match shows the signed fields still match the HMAC-SHA-256 signature made with your signing secret. It does not show that a notice was shown, that output is marked, or that your system meets the Act.
Who can halt an AI action or decide on a review?
Any organisation member can halt or decide in the console. Deciding through the API needs a reviewer token, which only an organisation owner can issue. You choose who in your organisation does human oversight (Art. 26(2), for high-risk AI systems).
Can I change my plan in the console?
No. The console does not change plans. Its settings page says: “This console does not change an organisation's plan.” Write to us to change plans.
Does using Spurvik make my system meet the EU AI Act?
No. Spurvik is software designed to support parts of your own work under the Act, such as AI notices and, for high-risk AI systems, record-keeping and human oversight. Whether and how the Act applies to you is a legal question for your own counsel. We cannot give legal advice.
§2 December 2026: Article 50(2) for earlier systems
Providers of AI systems generating synthetic text, image, audio or video that were placed on the EU market before 2 August 2026 must meet Article 50(2) by 2 December 2026 (Art. 111(4) AI Act, as amended by Reg. (EU) 2026/1744); marking and detection are due by that date (Commission guidelines C(2026) 7702 final, para 153). Systems placed on the market since 2 August 2026 have had to meet it from launch. The 2 February 2027 date is a voluntary Code of Practice milestone for Section 1 signatories only.
An API response field is not Article 50(2) marking: copying the text drops it. The Commission's Code of Practice on transparency of AI-generated content says relying on fingerprinting or logging alone “is not considered sufficient to meet the quality requirements specified in Article 50(2) AI Act and in Commitment 3” (Section 1, Sub-measure 1.1.3; checked 29 Oct 2026). Keep Spurvik’s records as evidence, and mark the content itself with your own marking method.
General information, not legal advice. Its provenance record supports, but does not replace, Article 50(2) machine-readable marking. Please check these dates with your own counsel.
Test your repo with the scanner (npm i -D spurvik-mcp-server@1.0.4, then ./node_modules/.bin/spurvik-mcp-server --root .): Paste your route handler or agent config into the scanner to inspect Art. 50 findings and, for high-risk AI systems, Art. 12 and 14 findings.
Replace raw console/DB prompt logs: Use keyed HMAC-SHA256 digests so your telemetry supports your Art. 12 logging obligations (for high-risk AI systems) with no raw prompt text persisted.
Gate high-impact tools in shadow or active mode: Wrap tool calls with System-One routing in our open-source MIT SDK from npm (spurvik 1.0.8 with withSpurvik + <AIDisclosureLabel />, source at github.com/spurvik/spurvik) or test the interactive 2×2 cockpit at console.spurvik.com/sandbox.