diff --git a/satgate-landing/app/components/HomeClient.tsx b/satgate-landing/app/components/HomeClient.tsx index 279a1d4e..5ada2843 100644 --- a/satgate-landing/app/components/HomeClient.tsx +++ b/satgate-landing/app/components/HomeClient.tsx @@ -186,17 +186,17 @@ const LandingPage = () => { Humans and platforms set policy. Agents consume approved primitives. Upstreams get receipt-backed proof.

- - Start Free - + + Run authority proof + Build with SatGate - - See Demo + + Run the 90-second SatGate proof - - Agent Control Plane + + See an Evidence Pack
diff --git a/satgate-landing/app/evidence-pack-demo/page.tsx b/satgate-landing/app/evidence-pack-demo/page.tsx index b78c8624..8804ac20 100644 --- a/satgate-landing/app/evidence-pack-demo/page.tsx +++ b/satgate-landing/app/evidence-pack-demo/page.tsx @@ -240,7 +240,10 @@ export default function EvidencePackDemoPage() {
{receipts.map((receipt) => (
-
#{receipt.seq} · {receipt.ts.slice(11, 19)}
+
+
#{receipt.seq} · {receipt.ts.slice(11, 19)}
+
{receipt.receipt_id}
+

{receipt.type}

{receipt.action}

diff --git a/satgate-landing/app/protect/page.tsx b/satgate-landing/app/protect/page.tsx index 2dbbc4fb..d039dc44 100644 --- a/satgate-landing/app/protect/page.tsx +++ b/satgate-landing/app/protect/page.tsx @@ -5,7 +5,7 @@ import { Key, Shield, Lock, Unlock, Play, ArrowLeft, Copy, Check, ChevronRight, AlertTriangle, CheckCircle, XCircle, User, Bot, GitBranch, Clock, RefreshCw, Eye, Trash2, Wifi, WifiOff, - Ban, ShieldOff, Zap, Activity + Ban, ShieldOff, Zap, Activity, ArrowRight } from 'lucide-react'; import Link from 'next/link'; @@ -124,7 +124,7 @@ const faqJsonLd = { name: 'Why use revocable capability tokens for agents?', acceptedAnswer: { '@type': 'Answer', - text: 'Revocable capability tokens give agents narrow, expiring authority that can be delegated safely and killed instantly without rotating global API keys or service-account credentials.', + text: 'Revocable capability tokens give agents narrow, expiring authority that can be delegated safely and denied on the next governed request without rotating global API keys or service-account credentials.', }, }, { @@ -855,6 +855,30 @@ export default function ProtectDemoPage() {
+
+
+
+

Deterministic public proof path

+
+
+

Need the buyer-safe allow/deny/revoke proof?

+

+ The one-click Control scenes are useful for protocol exploration. The deterministic buyer proof lives on the public demo path and shows 401 no authority, 200 allowed, 402/403 denial, revoke/replay denial, and Evidence Pack export without auth or hidden shortcuts. +

+
+
+ + Run the 90-second proof + + + View Evidence Pack + +
+
+
+
+
+
{/* Scene Progress */} @@ -1044,7 +1068,7 @@ export default function ProtectDemoPage() { CISO Role

- Issues root credentials. Retains authority. Can revoke instantly. + Issues root credentials. Retains authority. Can revoke before the next governed request.

@@ -1377,11 +1401,11 @@ export default function ProtectDemoPage() {

- Instant Propagation + Next-request Enforcement

Unlike traditional IAM where revocation can take minutes/hours to propagate, - SatGate's ban list is checked on every request. The token is dead now. + SatGate checks revocation on every governed request. The next replay is denied at policy check.

@@ -1425,8 +1449,8 @@ export default function ProtectDemoPage() {
-
Instant
-
Revocation Time
+
Next request
+
Denied after revoke
Global
@@ -1498,10 +1522,10 @@ export default function ProtectDemoPage() { Run Again - Try Charge Mode + View Evidence Pack
{!useSimulation && adminToken && ( @@ -1783,7 +1807,7 @@ export default function ProtectDemoPage() {
{[ ['What does SatGate Control protect?', 'SatGate Control protects agent API and MCP tool calls by enforcing scoped capability tokens, budgets, delegation limits, revocation, and audit policy before requests reach upstream services.'], - ['Why use revocable capability tokens for agents?', 'Revocable capability tokens give agents narrow, expiring authority that can be delegated safely and killed instantly without rotating global API keys or service-account credentials.'], + ['Why use revocable capability tokens for agents?', 'Revocable capability tokens give agents narrow, expiring authority that can be delegated safely and denied on the next governed request without rotating global API keys or service-account credentials.'], ['How does Control differ from Prove?', 'Control enforces access, budget, scope, and revocation policy for agent activity. Prove preserves Evidence Pack receipts for paid-rail context and other agent decisions before API access is unlocked.'], ].map(([question, answer]) => (
diff --git a/satgate-landing/app/sandbox/page.tsx b/satgate-landing/app/sandbox/page.tsx index 975a8df3..1992c033 100644 --- a/satgate-landing/app/sandbox/page.tsx +++ b/satgate-landing/app/sandbox/page.tsx @@ -12,6 +12,7 @@ import { type StepStatus = 'pending' | 'running' | 'success' | 'blocked' | 'error' | 'revoked'; type DemoMode = 'kill-switch' | 'budget' | 'both'; +type GoldenStepTone = 'blocked' | 'issued' | 'allowed' | 'denied' | 'revoked' | 'proof'; interface FeedEvent { id: string; @@ -76,6 +77,152 @@ const sandboxDemos = [ body: 'Put paid-rail context behind policy and receipts without making L402/x402 the product center.', }, ]; +const goldenPathSteps: Array<{ + label: string; + status: string; + decision: string; + reason: string; + route: string; + budget: string; + receipt: string; + tone: GoldenStepTone; +}> = [ + { + label: '1 · No authority', + status: 'HTTP 401', + decision: 'denied', + reason: 'DEFAULT_PROTECTION: no capability token presented', + route: '/v1/invoices/search', + budget: 'No budget consumed', + receipt: 'rcpt_demo_unauth_001', + tone: 'blocked', + }, + { + label: '2 · Scoped authority issued', + status: 'capability minted', + decision: 'issued', + reason: 'tenant=acme-finance; scope=/v1/invoices/*; budget<=3.00 USD', + route: 'agent:invoice-reconciler-worker', + budget: '3.00 USD delegated budget', + receipt: 'rcpt_demo_001 / rcpt_demo_002', + tone: 'issued', + }, + { + label: '3 · Allowed action', + status: 'HTTP 200', + decision: 'allowed', + reason: 'allowed_under_policy', + route: 'mcp:invoices.search', + budget: '0.18 USD spent; 2.82 USD remaining', + receipt: 'rcpt_demo_003', + tone: 'allowed', + }, + { + label: '4 · Out-of-scope denial', + status: 'HTTP 403', + decision: 'blocked', + reason: 'scope_violation:no_customer_data_export', + route: '/v1/invoices/export', + budget: 'No export spend allowed', + receipt: 'rcpt_demo_006', + tone: 'denied', + }, + { + label: '5 · Budget denial', + status: 'HTTP 402', + decision: 'blocked', + reason: 'budget_exhausted', + route: '/v1/invoices/reconcile', + budget: '0.78 / 3.00 USD spent; remaining shown as 0.00 after denial', + receipt: 'rcpt_demo_007', + tone: 'denied', + }, + { + label: '6 · Revoke / replay denial', + status: 'HTTP 401/403', + decision: 'blocked', + reason: 'capability_revoked', + route: 'mcp:invoices.search', + budget: 'No additional spend after revoke', + receipt: 'rcpt_demo_008 / rcpt_demo_009', + tone: 'revoked', + }, +]; + +const goldenToneClasses: Record = { + blocked: 'border-red-500/40 bg-red-950/25 text-red-100', + issued: 'border-cyan-500/40 bg-cyan-950/25 text-cyan-100', + allowed: 'border-emerald-500/40 bg-emerald-950/25 text-emerald-100', + denied: 'border-orange-500/40 bg-orange-950/25 text-orange-100', + revoked: 'border-purple-500/40 bg-purple-950/25 text-purple-100', + proof: 'border-white/20 bg-white/10 text-white', +}; + +function ProofCtas({ compact = false }: { compact?: boolean }) { + const base = compact ? 'px-3 py-2 text-xs' : 'px-5 py-3 text-sm'; + return ( +
+ + View Evidence Pack + + + Download JSON + + + Verify schema + +
+ ); +} + +function GoldenPathSection() { + return ( +
+
+

90-second SatGate proof

+
+
+

+ Run authority proof: allow, deny, revoke, then inspect the Evidence Pack. +

+

+ This public proof path is deterministic and uses the same sample Evidence Pack shown in the viewer. It is a buyer-comprehension demo, not a production-promotion claim. +

+
+ Caveat: budget-before-upstream here is a response-shape proof path. Do not describe it as keyed upstream counter/log-delta proof unless that instrumentation is added separately. +
+
+ +
+
+
+ {goldenPathSteps.map((step) => ( +
+
+

{step.label}

+ {step.status} +
+
+
decision
{step.decision}
+
reason
{step.reason}
+
route/tool
{step.route}
+
budget impact
{step.budget}
+
receipt
{step.receipt}
+
+
+ ))} +
+

7 · Proof artifact exported

+

Evidence Pack ep_demo_2026_05_10_001 links every receipt and explains the policy, subject, tenant, route/tool, budget state, receipt chain, hash, and demo signature caveat.

+
+
+
+
+
+
+ ); +} + function BudgetBar({ label, spent, limit, color }: { label: string; spent: number; limit: number; color: string }) { if (limit === 0) return null; @@ -142,7 +289,7 @@ export default function SandboxPage() { addEvent({ id: 'alpha-header', agent: 'alpha', type: 'summary', status: 'running', label: '🔴 Agent Alpha — Admin Kill Switch', - detail: 'Can an admin instantly cut off a rogue agent? Watch.' }); + detail: 'Can an admin revoke authority and deny the next governed request? Watch.' }); // Step 1: Auth const authId = 'alpha-auth'; @@ -191,7 +338,7 @@ export default function SandboxPage() { await sleep(jitter(300)); if (abortRef.current) return; updateEvent(revokeId, { status: 'revoked', latencyMs: jitter(12), - detail: '✓ Token revoked instantly — agent has no idea yet.' }); + detail: '✓ Token revoked — the next governed request should be denied.' }); await sleep(600); if (abortRef.current) return; @@ -203,10 +350,10 @@ export default function SandboxPage() { await sleep(jitter(400)); if (abortRef.current) return; updateEvent(verifyId, { status: 'blocked', latencyMs: jitter(8), - detail: 'HTTP 401 — Blocked. Agent is permanently locked out. Zero human latency.' }); + detail: 'HTTP 401 — Blocked on the next governed request after revocation.' }); addEvent({ id: 'alpha-done', agent: 'alpha', type: 'summary', status: 'success', - label: '✓ Kill switch works: Authenticate → Call API → Admin Revoke → Instant Block' }); + label: '✓ Kill switch works: Authenticate → Call API → Admin Revoke → Next request blocked' }); return true; }, []); @@ -302,6 +449,9 @@ export default function SandboxPage() { await runBravo(); } + addEvent({ id: 'proof-exported', agent: 'bravo', type: 'summary', status: 'success', + label: '✓ Evidence Pack ready: inspect receipt chain, JSON export, schema, hash, and signature caveat', + detail: 'Open /evidence-pack-demo or download /evidence-packs/sample-evidence-pack.v1.json to verify the proof artifact.' }); setPhase('done'); setRunning(false); }, [selectedMode, runAlpha, runBravo]); @@ -425,14 +575,16 @@ export default function SandboxPage() { - Try It Free + View Evidence Pack
+ + {/* Demo Hub */}
@@ -441,14 +593,14 @@ export default function SandboxPage() { Mint. Control. Constrain. Govern.

- Start with scoped authority, then move through capability control, spend control, and paid-rail context. Each demo is a separate proof path. + Start with the 90-second authority proof, then move through capability control, spend control, and paid-rail context. Every proof moment links to the Evidence Pack or receipt export.

- - Grade economic firewall readiness → + + Run the 90-second SatGate proof → - - Start from a spend policy → + + See an Evidence Pack →
@@ -477,8 +629,8 @@ export default function SandboxPage() {

- Two scenarios CFOs care about: a rogue agent gets cut off instantly, and an agent - hits its budget ceiling and stops — before the bill arrives. + Two scenarios CFOs care about: a revoked agent is denied on the next governed request, and an agent + hits its budget ceiling before more value moves.

@@ -583,6 +735,14 @@ export default function SandboxPage() { )}
+ {phase === 'done' && !running && ( +
+

Proof artifact ready

+

The simulation is the explainer; the Evidence Pack is the buyer-visible proof artifact.

+ +
+ )} + {/* Architecture Expandable */}
diff --git a/satgate-landing/scripts/check_demo_ia.py b/satgate-landing/scripts/check_demo_ia.py index ca5d9326..bec1e47c 100644 --- a/satgate-landing/scripts/check_demo_ia.py +++ b/satgate-landing/scripts/check_demo_ia.py @@ -21,8 +21,8 @@ errors.append("homepage top nav still contains Live Demo") if "Open Sandbox" in home or ">Sandbox<" in home: errors.append("homepage still exposes Sandbox as public label") -if 'href="/sandbox"' not in home or "See Demo" not in home or ">Demo" not in home: - errors.append("homepage does not route Demo nav/CTA to /sandbox") +if 'href="/sandbox#golden-path"' not in home or "Run the 90-second SatGate proof" not in home or ">Demo" not in home: + errors.append("homepage does not route Demo nav/CTA to the deterministic /sandbox#golden-path proof") for crowded in ["Capability Auth", "Control Plane", "Tools", "Integrations", "Blog", "GitHub"]: desktop_marker = f'>{crowded}' if crowded != "GitHub" else f'>{crowded}' if desktop_marker in home.split("{/* Mobile menu button */}", 1)[0]: