From ba536578c0781eaad08129f11cad1e47b41b6ac3 Mon Sep 17 00:00:00 2001 From: srivtx Date: Fri, 4 Sep 2026 01:29:31 +0530 Subject: [PATCH] =?UTF-8?q?feat(kit):=20the=20agent=20kit=20=E2=80=94=20th?= =?UTF-8?q?e=20counter=20opens=20to=20outside=20agents?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GET /api/agent/kit: the machine-readable contract, generated from the running constants (TOOL_SCHEMAS, TRUST_TIERS, rail, brain) — the kit cannot drift from the code because it IS the code, serialized - scripts/agent-kit-demo.ts + make kit: the reference client — a process with no in-repo state walks search → add → attest → checkout → approve over pure HTTP, asserts every verdict (gate ALLOW, captured, receipt, chain intact) and exits non-zero on deviation - AGENT_KIT.md: the human twin — endpoint, message protocol, tool schemas, curl golden path, honesty notes (protocol-shaped adapters, test rails, integer paise) - why page: work-order 02 clears into shipped; the work order is three now (persistent desk, second counter, refunds as spans) - registered everywhere the tree is told: verify.mjs REQUIRED, README filemap, llms.txt, JUDGE.md desk ledger --- AGENT_KIT.md | 121 +++++++++++++++++++++++++++++++ JUDGE.md | 5 ++ Makefile | 6 +- README.md | 6 +- llms.txt | 2 + scripts/agent-kit-demo.ts | 128 +++++++++++++++++++++++++++++++++ scripts/verify.mjs | 2 + src/app/api/agent/kit/route.ts | 124 ++++++++++++++++++++++++++++++++ src/components/customs/why.tsx | 6 +- 9 files changed, 394 insertions(+), 6 deletions(-) create mode 100644 AGENT_KIT.md create mode 100644 scripts/agent-kit-demo.ts create mode 100644 src/app/api/agent/kit/route.ts diff --git a/AGENT_KIT.md b/AGENT_KIT.md new file mode 100644 index 0000000..a8b3c55 --- /dev/null +++ b/AGENT_KIT.md @@ -0,0 +1,121 @@ +# AGENT_KIT.md — bring your own agent + +The counter is not reserved for our buyer. The same HTTP surface the +playground uses is the kit's contract: an outside agent — your harness, a +notebook, a curl loop — speaks plain JSON to one endpoint and clears the +counter like any customer. Every check it passes is visible, every span +lands in the hash-chained ledger, and the reference client in `make kit` +proves the whole walk with no in-repo state. + +**Test mode only.** Live keys are refused at construction; the rail runs +Razorpay test mode where keys are set and a loudly-labeled simulation +otherwise. Nothing ever pretends to move real money. + +--- + +## The one endpoint + +```http +POST /api/chat +Content-Type: application/json + +{ "message": "search earbuds" } +``` + +| Field | | | +|---|---|---| +| `sessionId` | optional | omit on the first call; the response hands one back. Cart, tier and mandate live server-side under it. | +| `message` | required | the agent's utterance — the protocol is below | +| `adapter` | optional | `naive` (default) · `mcp` · `acp` — the wire format of the turn | +| `tier` | optional | the tier a *new* session starts at: `UNVERIFIED` / `ATTESTED` / `MANDATED` | + +The response carries `sessionId`, `cart`, `awaitingMandateApproval`, the +full `events[]` trace of the turn (every tool call, the gate's 10-check +card, the payment, the receipt) and parser-exact `suggestions[]`. + +Machine-readable kit: **`GET /api/agent/kit`** — generated from the same +constants the product runs on (`TOOL_SCHEMAS`, `TRUST_TIERS`, rail, brain), +so it cannot drift from the code. This file is its human twin. + +## The protocol (messages) + +| message | effect | +|---|---| +| `search [under <₹n>]` | catalog search → a `products` event | +| `add [×n]` | adds to the cart | +| `cart` | echoes the cart with the running total | +| `attest` | raises the trust tier (OTP-bound in production; asserted here) | +| `checkout` | drafts **and signs** a mandate for the cart, then waits | +| `approve` | releases the mandate → gate decides → rail captures | +| `status` | passport: tier, caps, cart, active mandate | +| `attack: ` | red-team: replays an authored attack against the live gate | + +Escalation is the session's own act: `attest` walks UNVERIFIED → ATTESTED → +MANDATED (₹500 → ₹5,000 → ₹50,000 caps). Approval is the principal's: +`checkout` drafts an Ed25519-signed mandate over canonical JSON and holds; +`bind_and_pay` refuses without a signed mandate in bounds. **No approval, +no money** — the fuzz corpus pins that. + +## Tools (the schemas the buyer agent runs) + +The same five schemas serve all three transports (`naive` / `mcp` / `acp` +— protocol-shaped; see `src/lib/customs/adapters/index.ts` and the +ablation for the measured overheads): + +- `search_catalog` — `{ query }` +- `get_product` — `{ productId }` +- `add_to_cart` — `{ productId, quantity }` +- `request_mandate` — `{}` (drafts + signs, waits for the principal) +- `bind_and_pay` — `{}` (binds against the mandate and pays) + +## The golden path, in curl + +```bash +BASE=http://localhost:3000 # or the live deployment + +# 1 — open a session and search +curl -s $BASE/api/chat -H 'content-type: application/json' \ + -d '{"message":"search earbuds"}' + +# 2 — add the first match (copy its id from the products event) +curl -s $BASE/api/chat -H 'content-type: application/json' \ + -d '{"sessionId":"ses_…","message":"add bud-pro-earbuds"}' + +# 3 — raise the tier (₹500 walk-in cap will not cover ₹4,999) +curl -s $BASE/api/chat -H 'content-type: application/json' \ + -d '{"sessionId":"ses_…","message":"attest"}' + +# 4 — checkout: mandate comes back signed, pendingApproval: true +curl -s $BASE/api/chat -H 'content-type: application/json' \ + -d '{"sessionId":"ses_…","message":"checkout"}' + +# 5 — approve: gate checklist ticks, rail captures, receipt lands +curl -s $BASE/api/chat -H 'content-type: application/json' \ + -d '{"sessionId":"ses_…","message":"approve"}' + +# the ledger remembers — and still verifies +curl -s $BASE/api/health +``` + +## The proof + +```bash +make kit # bun scripts/agent-kit-demo.ts $BASE_URL +``` + +The reference client imports nothing from `src/` — it is exactly what an +outside agent is. It walks search → add → attest → checkout → approve, +asserts every verdict (gate ALLOW, payment captured, receipt issued, chain +still verifying) and exits non-zero on any deviation. Its output is the +shortest demonstration that the counter is not ours alone. + +## Honesty notes + +- The buyer agent is still in-house; this kit publishes its surface so + *yours* can drive it. Real MCP-stdio / ACP wire transports are an + interface change, pre-logged in `ARCHITECTURE.md` and `ENGINEERING_LOG.md`. +- The gate is deterministic code — your agent's words become tool calls; + bounds are re-verified server-side at bind time regardless of what the + agent believes it was promised. +- Integer paise end to end. Floats never touch money; the refusal itself + is a fuzz case. diff --git a/JUDGE.md b/JUDGE.md index 52ba1f5..8762719 100644 --- a/JUDGE.md +++ b/JUDGE.md @@ -108,6 +108,11 @@ regenerated, never promised. `ok:true` with `rail: razorpay-test, simulated:false` (real test-mode rails), chain verified, deterministic seed, ephemeral state honestly labeled. CI keeps the URL in this file on every push. +- The agent kit: the counter's HTTP surface is published for outside agents — + `AGENT_KIT.md` (the contract) · `GET /api/agent/kit` (machine twin, + generated from the running constants) · `make kit` (the proof: a reference + client with no in-repo state walks search → add → attest → checkout → + approve over pure HTTP and asserts every verdict). - D1-1 payment mechanism: **executed with test keys on 2026-09-01** (`results/d1_1_spike.json`) — Orders API verified live; server-side tokenization refused by test mode (path A impossible, receipt in the log); diff --git a/Makefile b/Makefile index 5154278..d07a369 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help verify triage demo meter ablation fuzz project audit spike-d1-1 test all install +.PHONY: help verify triage demo meter ablation fuzz project audit spike-d1-1 kit test all install BUN := $(shell command -v bun 2>/dev/null) RUNNER := $(if $(BUN),bun,npx tsx) @@ -16,6 +16,7 @@ help: @echo " all every harness, then verify" @echo " demo how to run the product locally" @echo " spike-d1-1 payment-mechanism spike (needs Razorpay test keys)" + @echo " kit an external agent walks the golden path over pure HTTP" verify: ## evidence checks — the same ones CI runs on every push node scripts/verify.mjs @@ -26,6 +27,9 @@ triage: ## 60-second self-guided judge tour (prints claims, runs checks, exits 0 spike-d1-1: ## payment-mechanism spike (needs RAZORPAY_KEY_ID + RAZORPAY_KEY_SECRET, test keys only) node scripts/spike-d1-1.mjs +kit: ## the agent kit walkthrough — a client with no in-repo state clears the counter + bun scripts/agent-kit-demo.ts $${BASE_URL:-http://localhost:3000} + fuzz: ## regenerate results/conformance_matrix.json $(RUNNER) scripts/fuzz.ts diff --git a/README.md b/README.md index c5dc05c..499701a 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,8 @@ the **Why it exists** view and `PAPER.md` §7. | `VIDEO_TRANSCRIPT.md` | the 5:00 pitch script (recorded at submission) | | `CLEANUP.md` | operator runbook: what to delete before pushing to GitHub | | `DEPLOY.md` | operator runbook: run locally, keep it alive, deploy free, operate | -| `Makefile` | verify / triage / fuzz / ablation / meter / project / audit / test | +| `AGENT_KIT.md` | bring your own agent — the counter's HTTP contract + the `make kit` proof | +| `Makefile` | verify / triage / fuzz / ablation / meter / project / audit / kit / test | | `scripts/verify.mjs` | repo-evidence checks (CI entry, zero deps) | | `scripts/triage.mjs` | 60-second self-guided judge tour | | `scripts/fuzz.ts` | attack corpus harness → `results/conformance_matrix.json` | @@ -151,6 +152,7 @@ the **Why it exists** view and `PAPER.md` §7. | `scripts/audit.ts` | hash-chain walk + tamper control → `results/audit_chain.json` | | `scripts/ledger-fork.ts` | D5-1 regression: concurrent writers must converge, never fork | | `scripts/spike-d1-1.mjs` | payment-mechanism spike (needs Razorpay test keys) | +| `scripts/agent-kit-demo.ts` | the agent kit reference client — an outside agent clears the counter over HTTP | | `results/` | all measured numbers — JSON only, regeneration-only | | `src/lib/customs/gate/types.ts` | mandate schema + trust-tier policy (the contract) | | `src/lib/customs/gate/canonical.ts` | canonical JSON + lenient chain stringify | @@ -183,7 +185,7 @@ the **Why it exists** view and `PAPER.md` §7. | `src/components/customs/chat-events.tsx` | the transcript — tool calls, gate checklist, receipts | | `src/components/customs/theme.tsx` | the desk lamp — footer dark/light toggle, persisted, no-flash | | `src/components/customs/footer.tsx` | the footer — mark, quiet link columns, the theme toggle (x.ai pattern) | -| `src/app/api/` | route handlers: chat, state, decision, fuzz, webhook, health | +| `src/app/api/` | route handlers: chat, state, decision, fuzz, webhook, health, agent/kit | | `public/logo.svg` | the gate diamond (badge tile) | | `public/wordmark-light.svg` | the wordmark, light surfaces (this README) | | `public/wordmark-dark.svg` | the wordmark, dark surfaces (this README) | diff --git a/llms.txt b/llms.txt index 899f2b5..d08ca4f 100644 --- a/llms.txt +++ b/llms.txt @@ -19,6 +19,7 @@ Test mode only; without Razorpay keys the rail is a labeled simulation. - `make verify` — the exact checks CI runs on every push (zero deps) - `make test` — fuzz + ablation + audit + ledger-fork (concurrent-writer convergence), exit codes propagate - `make demo` — run the product (no keys needed) +- `make kit` — an external agent walks the golden path over pure HTTP (the agent-kit proof) ## key files @@ -31,6 +32,7 @@ Test mode only; without Razorpay keys the rail is a labeled simulation. - ENGINEERING_LOG.md — dated incidents; every incident became a test - CLEANUP.md — what to delete before pushing to GitHub - DEPLOY.md — the operating runbook: run locally, keep alive, deploy free, operate +- AGENT_KIT.md — bring your own agent: the counter's HTTP contract; /api/agent/kit is its machine twin; `make kit` proves an outside client clears the counter - src/lib/customs/gate/ — mandate contract, canonical JSON, decision checklist - src/lib/customs/ledger/ — hash-chained JSONL audit trail (the database) - src/lib/customs/fuzz/corpus.ts — the 12 authored attacks, expected verdicts diff --git a/scripts/agent-kit-demo.ts b/scripts/agent-kit-demo.ts new file mode 100644 index 0000000..5101f26 --- /dev/null +++ b/scripts/agent-kit-demo.ts @@ -0,0 +1,128 @@ +/** + * agent-kit-demo.ts — the reference client: an agent kit walkthrough that + * is ALSO the interop proof. This script holds no in-repo state and imports + * nothing from src/ — it is exactly what an outside agent is: a process + * speaking plain JSON over HTTP. It walks the golden path (search → add → + * attest → checkout → approve → capture) against a running customs instance, + * asserting each verdict, and exits non-zero if the counter ever deviates. + * + * bun scripts/agent-kit-demo.ts [base-url] # default http://localhost:3000 + * make kit # same thing, makefile voice + * + * Test mode only — live keys are refused at construction, and this client + * refuses to talk to anything that doesn't identify itself honestly. + */ + +const BASE = (process.argv[2] ?? process.env.BASE_URL ?? "http://localhost:3000").replace(/\/$/, ""); + +interface ChatEvent { + id: string; + ts: number; + role?: string; + text?: string; + kind?: string; + tool?: string; + products?: { id: string; name: string; pricePaise: number }[]; + lines?: { name: string; quantity: number; unitPricePaise: number }[]; + totalPaise?: number; + mandate?: { id: string; amountCapPaise: number }; + pendingApproval?: boolean; + orderId?: string; + decision?: { kind: string; code: string | null; checks: { label: string; pass: boolean | null }[] }; + status?: string; + rail?: string; + simulated?: boolean; + manifestNo?: string; + tier?: string; +} + +interface ChatResponse { + ok: boolean; + sessionId: string; + tier: string; + awaitingMandateApproval: boolean; + events: ChatEvent[]; + error?: string; +} + +const ofKind = (events: ChatEvent[], kind: string) => events.filter((e) => e.kind === kind); + +let failures = 0; +function expect(label: string, cond: boolean, detail = "") { + const line = `${cond ? "PASS" : "FAIL"} ${label}${detail ? " — " + detail : ""}`; + console.log(line); + if (!cond) failures += 1; +} + +async function chat(sessionId: string | null, message: string): Promise { + const res = await fetch(`${BASE}/api/chat`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ ...(sessionId ? { sessionId } : {}), message }), + }); + if (!res.ok) throw new Error(`/api/chat → HTTP ${res.status}`); + return (await res.json()) as ChatResponse; +} + +async function main() { + console.log(`── customs · agent kit — an external agent clears the counter ──`); + console.log(`base url: ${BASE}\n`); + + // 0 — the desk must identify itself, and its chain must verify + const health = (await (await fetch(`${BASE}/api/health`)).json()) as { + ok: boolean; + chainOk: boolean; + rail: { id: string; simulated: boolean }; + brain: string; + }; + expect("health answers ok:true", health.ok === true); + expect("ledger chain verifies", health.chainOk === true, `rail=${health.rail?.id} brain=${health.brain}`); + + // 1 — search the catalog + let r = await chat(null, "search earbuds"); + const sid = r.sessionId; + const products = ofKind(r.events, "products")[0]?.products ?? []; + expect("search returns matches", products.length > 0, `${products.length} matches · session ${sid}`); + const pick = products.find((p) => p.id === "bud-pro-earbuds") ?? products[0]; + console.log(` picked: ${pick.id} — ₹${(pick.pricePaise / 100).toLocaleString("en-IN")}\n`); + + // 2 — build the cart + r = await chat(sid, `add ${pick.id}`); + const cart = ofKind(r.events, "cart")[0]; + expect("cart holds the line", (cart?.lines?.length ?? 0) === 1, `total ₹${((cart?.totalPaise ?? 0) / 100).toLocaleString("en-IN")}`); + + // 3 — raise the tier so the cap covers the cart + r = await chat(sid, "attest"); + const tiered = ofKind(r.events, "tier").length > 0 || r.tier === "ATTESTED"; + expect("tier raised to ATTESTED", tiered, r.tier); + + // 4 — checkout: the desk drafts and signs a mandate, then waits + r = await chat(sid, "checkout"); + const mandate = ofKind(r.events, "mandate")[0]; + expect("mandate drafted and pending approval", r.awaitingMandateApproval === true && !!mandate, mandate?.mandate?.id); + + // 5 — approve: the gate decides, the rail captures + r = await chat(sid, "approve"); + const gate = ofKind(r.events, "gate")[0]; + const payment = ofKind(r.events, "payment")[0]; + const receipt = ofKind(r.events, "receipt")[0]; + expect("gate decision is ALLOW", gate?.decision?.kind === "ALLOW", `${gate?.decision?.checks?.filter((c) => c.pass).length ?? 0}/10 checks pass`); + expect("payment captured", payment?.status === "captured", `rail=${payment?.rail}${payment?.simulated ? " (simulated)" : ""}`); + expect("receipt issued", !!receipt?.manifestNo, `${receipt?.manifestNo} · order ${receipt?.orderId}`); + + // 6 — the chain still verifies after our order landed in it + const after = (await (await fetch(`${BASE}/api/health`)).json()) as { chainOk: boolean; events: number }; + expect("ledger chain verifies after capture", after.chainOk === true, `${after.events} events on the chain`); + + console.log( + `\n── the outside agent paid, bounded and gated · manifest ${receipt?.manifestNo ?? "?"} · ` + + `${failures === 0 ? "all checks green" : failures + " CHECKS FAILED"} ──` + ); + if (failures) process.exit(1); +} + +main().catch((err) => { + console.error(`agent kit walkthrough failed: ${err instanceof Error ? err.message : err}`); + console.error(`is the product running at ${BASE}? (bun run dev)`); + process.exit(1); +}); diff --git a/scripts/verify.mjs b/scripts/verify.mjs index cd0cba0..80897a5 100644 --- a/scripts/verify.mjs +++ b/scripts/verify.mjs @@ -24,8 +24,10 @@ const REQUIRED = [ "scripts/verify.mjs", "scripts/triage.mjs", "scripts/spike-d1-1.mjs", "scripts/fuzz.ts", "scripts/ablation.ts", "scripts/meter.ts", "scripts/project.ts", "scripts/audit.ts", "scripts/ledger-fork.ts", + "scripts/agent-kit-demo.ts", "AGENT_KIT.md", "src/app/page.tsx", "src/app/layout.tsx", "src/app/icon.svg", "src/app/api/chat/route.ts", "src/app/api/state/route.ts", + "src/app/api/agent/kit/route.ts", "src/app/api/decision/route.ts", "src/app/api/fuzz/route.ts", "src/app/api/hook/webhook/route.ts", "src/app/api/health/route.ts", "src/lib/customs/gate/types.ts", "src/lib/customs/gate/canonical.ts", diff --git a/src/app/api/agent/kit/route.ts b/src/app/api/agent/kit/route.ts new file mode 100644 index 0000000..33cb9cd --- /dev/null +++ b/src/app/api/agent/kit/route.ts @@ -0,0 +1,124 @@ +import { NextResponse } from "next/server"; +import { TOOL_SCHEMAS } from "@/lib/customs/adapters"; +import { TRUST_TIERS, TrustTier } from "@/lib/customs/gate/types"; +import { brainMode } from "@/lib/customs/agent/llm"; +import { railInfo } from "@/lib/customs/payments"; +import { CATALOG } from "@/lib/customs/store/catalog"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; + +/** + * GET /api/agent/kit — the machine-readable agent kit. + * + * The counter's HTTP surface, published: what an outside agent may call, + * in what order, and what comes back. Everything here is generated from + * the same constants the product runs on (TOOL_SCHEMAS, TRUST_TIERS, + * railInfo, brainMode) — the kit cannot drift from the code, because it + * IS the code, serialized. The human twin of this document is + * AGENT_KIT.md; the working proof is `make kit` (a client with no + * in-repo state walks the golden path over pure HTTP). + */ +export async function GET() { + const rail = railInfo(); + + const tiers = (Object.keys(TRUST_TIERS) as TrustTier[]).map((id) => { + const t = TRUST_TIERS[id]; + return { + id, + label: t.label, + blurb: t.blurb, + maxAmountPaise: t.maxAmountPaise, + maxItems: t.maxItems, + mandateTtlMs: t.mandateTtlMs, + }; + }); + + return NextResponse.json({ + kit: "customs-agent-kit", + version: 1, + summary: + "Both sides of the agentic counter behind one HTTP surface. An external agent " + + "speaks plain JSON to POST /api/chat: search the catalog, build a cart, raise its " + + "trust tier, request a signed spending mandate, approve it, and pay — every check " + + "visible, every span hash-chained. Test mode only: live keys are refused at construction.", + service: { + rail: { id: rail.id, label: rail.label, simulated: rail.simulated }, + brain: brainMode(), + catalogSize: CATALOG.length, + ledger: "hash-chained JSONL — every verdict, every span, every refusal", + }, + endpoints: { + chat: { + method: "POST", + path: "/api/chat", + contentType: "application/json", + body: { + sessionId: "string · omit on the first call; the response hands one back", + message: "string · required — the agent's utterance (see protocol.messages)", + adapter: '"naive" | "mcp" | "acp" · optional, default "naive" — the wire format of the turn', + tier: '"UNVERIFIED" | "ATTESTED" | "MANDATED" · optional — the tier a NEW session starts at', + }, + response: { + ok: "boolean", + sessionId: "string — reuse this on every following call", + buyerId: "string", + tier: "TrustTier", + cart: "[[productId, quantity]]", + awaitingMandateApproval: "boolean — when true, send the approve message", + events: "ChatEvent[] — the full turn, step by step (see events)", + suggestions: "{label, value}[] — parser-exact next messages", + brain: '"rules" | "llm"', + rail: "rail info of the deployment", + }, + }, + health: { method: "GET", path: "/api/health", note: "ok:true only when the ledger chain verifies" }, + state: { method: "GET", path: "/api/state", note: "the live ledger lines the control room renders" }, + reset: { method: "POST", path: "/api/reset", note: "re-seed the deterministic demo history" }, + kit: { method: "GET", path: "/api/agent/kit", note: "this document" }, + }, + protocol: { + session: "one sessionId per buying session; cart, tier and mandate live server-side under it", + escalation: "a session raises its own tier by sending \"attest\" — UNVERIFIED → ATTESTED → MANDATED", + approval: + "checkout drafts an Ed25519-signed mandate and holds for the principal: send \"approve\" to " + + "release it. No approval, no money — bind_and_pay refuses without a signed mandate in bounds.", + messages: [ + { message: "search [under <₹n>]", effect: "catalog search; a products event returns matches" }, + { message: "add [×n]", effect: "adds to the session cart" }, + { message: "cart", effect: "echoes the cart with the running total" }, + { message: "attest", effect: "raises the trust tier (OTP-bound in production; asserted here)" }, + { message: "checkout", effect: "drafts + signs a mandate for the cart; waits for approval" }, + { message: "approve", effect: "releases the mandate; the gate decides; the rail captures" }, + { message: "status", effect: "passport: tier, caps, cart, active mandate" }, + { message: "attack: ", effect: "red-team: replays an authored attack against the live gate" }, + ], + }, + tools: TOOL_SCHEMAS, + trustTiers: tiers, + events: { + "role:user|agent": "chat text", + step: "one tool call with its wire log (adapter frames, byte counts)", + products: "catalog matches {id, name, pricePaise, stock}", + cart: "cart lines + totalPaise (integer paise — floats never touch money)", + mandate: "the signed mandate view + pendingApproval", + gate: "the 10-check decision card: verdicts are reason codes, not vibes", + payment: "{status: captured | held | refused, rail, simulated}", + receipt: "the manifest: orderId, manifestNo, lines, totalPaise", + attack: "an authored attack replayed live: verdict + per-check detail", + tier: "a tier change with the new bounds", + }, + walkthrough: [ + { step: 1, message: "search earbuds", expect: "a products event with matches" }, + { step: 2, message: "add ", expect: "a cart event with totalPaise" }, + { step: 3, message: "attest", expect: "a tier event — the cap now covers the cart" }, + { step: 4, message: "checkout", expect: "a mandate event, pendingApproval: true" }, + { step: 5, message: "approve", expect: "gate → payment captured → receipt with manifestNo" }, + ], + proof: { + referenceClient: "scripts/agent-kit-demo.ts — a client with no in-repo state", + run: "make kit (BASE_URL env or the default http://localhost:3000)", + humanTwin: "AGENT_KIT.md", + }, + }); +} diff --git a/src/components/customs/why.tsx b/src/components/customs/why.tsx index 5a4f4cc..a8594d2 100644 --- a/src/components/customs/why.tsx +++ b/src/components/customs/why.tsx @@ -168,6 +168,7 @@ export function WhyPage({ onEnter }: { onEnter: (v: View) => void }) { "Authored attack corpus: every attack refused with its expected code (make fuzz)", "Protocol ablation across naive / MCP-style / ACP-style transports, wire overhead measured", "Channel P&L meter (agent GMV − AI serving cost) with the at-1M projection, assumptions declared", + "The agent kit — the counter's HTTP surface published for outside agents (AGENT_KIT.md, /api/agent/kit); a reference client with no in-repo state walks the golden path over pure HTTP (make kit)", "A 60-second machine-legible judge tour (make triage) and zero-dep evidence checks in CI (make verify)", ]} dot="bg-cleared" @@ -194,9 +195,8 @@ export function WhyPage({ onEnter }: { onEnter: (v: View) => void }) {
{[ ["01", "the persistent desk", "Move the JSONL ledger to a volume-backed host so state survives restarts — DEPLOY.md is the runbook."], - ["02", "the agent kit", "Publish the gate's tool schemas so an outside agent can clear the same counter — the wire formats already exist (the ablation ran three)."], - ["03", "a second counter", "Merchant tenancy and desk auth beyond the one demo catalog (Fieldnote Supply)."], - ["04", "refunds as ledger spans", "A signed reverse capture — same chain, same replay, same receipt."], + ["02", "a second counter", "Merchant tenancy and desk auth beyond the one demo catalog (Fieldnote Supply)."], + ["03", "refunds as ledger spans", "A signed reverse capture — same chain, same replay, same receipt."], ].map(([n, t, d]) => (