From d66f528c4f83d7029ab99c3e61ffe6290d81de4b Mon Sep 17 00:00:00 2001 From: Amar Saxena Date: Sat, 8 Aug 2026 16:32:52 +0530 Subject: [PATCH] feat: add webhook reliability architect kit --- .../.env.example | 4 + kits/webhook-reliability-architect/.gitignore | 6 + kits/webhook-reliability-architect/README.md | 119 ++ kits/webhook-reliability-architect/agent.md | 76 ++ .../apps/.env.example | 7 + .../apps/.gitignore | 6 + .../apps/AGENTS.md | 9 + .../apps/CLAUDE.md | 1 + .../apps/actions/orchestrate.ts | 101 ++ .../apps/app/globals.css | 195 ++++ .../apps/app/layout.tsx | 17 + .../apps/app/page.tsx | 437 +++++++ .../apps/lib/demo.ts | 231 ++++ .../apps/lib/lamatic-client.ts | 15 + .../apps/lib/types.ts | 91 ++ .../apps/next-env.d.ts | 7 + .../apps/next.config.mjs | 14 + .../apps/package-lock.json | 1033 +++++++++++++++++ .../apps/package.json | 24 + .../apps/tsconfig.json | 41 + .../constitutions/default.md | 33 + .../flows/webhook-reliability-architect.ts | 144 +++ .../lamatic.config.ts | 32 + ...uctor-llmnode-437_generative-model-name.ts | 15 + ...chitect_instructor-llmnode-437_system_0.md | 9 + ...architect_instructor-llmnode-437_user_1.md | 4 + 26 files changed, 2671 insertions(+) create mode 100644 kits/webhook-reliability-architect/.env.example create mode 100644 kits/webhook-reliability-architect/.gitignore create mode 100644 kits/webhook-reliability-architect/README.md create mode 100644 kits/webhook-reliability-architect/agent.md create mode 100644 kits/webhook-reliability-architect/apps/.env.example create mode 100644 kits/webhook-reliability-architect/apps/.gitignore create mode 100644 kits/webhook-reliability-architect/apps/AGENTS.md create mode 100644 kits/webhook-reliability-architect/apps/CLAUDE.md create mode 100644 kits/webhook-reliability-architect/apps/actions/orchestrate.ts create mode 100644 kits/webhook-reliability-architect/apps/app/globals.css create mode 100644 kits/webhook-reliability-architect/apps/app/layout.tsx create mode 100644 kits/webhook-reliability-architect/apps/app/page.tsx create mode 100644 kits/webhook-reliability-architect/apps/lib/demo.ts create mode 100644 kits/webhook-reliability-architect/apps/lib/lamatic-client.ts create mode 100644 kits/webhook-reliability-architect/apps/lib/types.ts create mode 100644 kits/webhook-reliability-architect/apps/next-env.d.ts create mode 100644 kits/webhook-reliability-architect/apps/next.config.mjs create mode 100644 kits/webhook-reliability-architect/apps/package-lock.json create mode 100644 kits/webhook-reliability-architect/apps/package.json create mode 100644 kits/webhook-reliability-architect/apps/tsconfig.json create mode 100644 kits/webhook-reliability-architect/constitutions/default.md create mode 100644 kits/webhook-reliability-architect/flows/webhook-reliability-architect.ts create mode 100644 kits/webhook-reliability-architect/lamatic.config.ts create mode 100644 kits/webhook-reliability-architect/model-configs/webhook-reliability-architect_instructor-llmnode-437_generative-model-name.ts create mode 100644 kits/webhook-reliability-architect/prompts/webhook-reliability-architect_instructor-llmnode-437_system_0.md create mode 100644 kits/webhook-reliability-architect/prompts/webhook-reliability-architect_instructor-llmnode-437_user_1.md diff --git a/kits/webhook-reliability-architect/.env.example b/kits/webhook-reliability-architect/.env.example new file mode 100644 index 000000000..25fde601f --- /dev/null +++ b/kits/webhook-reliability-architect/.env.example @@ -0,0 +1,4 @@ +WEBHOOK_RELIABILITY_ARCHITECT_FLOW_ID=your_deployed_flow_id +LAMATIC_API_URL=https://your-project-endpoint.example.com +LAMATIC_PROJECT_ID=your_project_id +LAMATIC_API_KEY=your_api_key diff --git a/kits/webhook-reliability-architect/.gitignore b/kits/webhook-reliability-architect/.gitignore new file mode 100644 index 000000000..c3f7dac52 --- /dev/null +++ b/kits/webhook-reliability-architect/.gitignore @@ -0,0 +1,6 @@ +.env +.env.local +node_modules/ +.next/ +dist/ +*.log diff --git a/kits/webhook-reliability-architect/README.md b/kits/webhook-reliability-architect/README.md new file mode 100644 index 000000000..a40dda777 --- /dev/null +++ b/kits/webhook-reliability-architect/README.md @@ -0,0 +1,119 @@ +# Webhook Reliability Architect + +Webhook Reliability Architect turns a webhook delivery contract into a concrete engineering brief: an idempotency key design, bounded retry schedule, dead-letter and replay procedure, observability SLO, failure-mode analysis, and failure-injection test matrix. + +It is designed for backend engineers reviewing webhooks that trigger real side effects such as payments, inventory updates, entitlements, or notifications. + +## Problem + +Webhook failures are deceptive. A receiver can commit the business mutation and still lose the acknowledgement. The sender then retries a request that appears failed even though the side effect already happened. Fixed-interval retries, unbounded queues, and informal replay procedures can multiply the damage. + +Teams need a repeatable way to answer: + +- What uniquely identifies one business event? +- How is the receipt stored atomically with the side effect? +- Which failures are retryable, and for how long? +- What moves to quarantine instead of retrying forever? +- How can an operator replay safely? +- Which metrics and failure tests prove the design? + +## Why this contribution is distinct + +The AgentKit registry already includes API review, incident analysis, generic debugging, and data-quality tools. This kit focuses specifically on **delivery semantics and side-effect safety**. It does not review source code or summarize an incident. It converts one webhook scenario into an operational contract that can be tested before production rollout. + +## What it produces + +- Risk score and concise executive summary +- Idempotency key, payload-hash conflict policy, storage model, and retention window +- Exponential backoff schedule with full jitter and delivery-age budget +- Retryable and non-retryable response classification +- Dead-letter record and operator replay checklist +- Delivery SLO, metrics, alert conditions, and structured log fields +- Failure-mode table and five failure-injection tests +- Staged rollout plan + +## Architecture + +```text +Next.js form + │ + ▼ +Server Action ── DEMO_MODE=true ──► deterministic local report + │ + └── live mode ──► Lamatic flow ──► structured reliability report +``` + +The Next.js application keeps Lamatic credentials on the server. In live mode it calls the deployed `webhook-reliability-architect` flow through the Lamatic SDK. Demo mode creates a deterministic report from the supplied scenario so reviewers can evaluate the interface without credentials. + +## Inputs + +The web interface collects the fields below and sends them to Lamatic as one JSON-encoded `scenario` string. Keeping a single flow input makes the contract easy to version while the application continues to validate every field before transmission. + +| Field | Purpose | +|---|---| +| System name | Human-readable workflow name | +| Event type | Stable event contract name, such as `payment.succeeded` | +| Business effect | Read-only, reversible write, notification, inventory, or financial | +| Delivery semantics | At-least-once, at-most-once, best effort, or unknown | +| Ordering required | Whether an older event may overwrite newer aggregate state | +| Max attempts / timeout / delivery age | Bounds for the proposed retry plan | +| Existing safeguards | Current signature, retry, deduplication, queue, and logging controls | +| Sample payload | Sanitized example used to identify event-key candidates | +| Failure context | Known incident or design concern | + +Do not submit credentials, secrets, personal data, or production payloads. + +## Run the app locally + +```bash +cd kits/webhook-reliability-architect/apps +npm install +cp .env.example .env.local +npm run dev +``` + +The example environment enables `DEMO_MODE=true`. Open `http://localhost:3000`, load the included payment-event scenario, and generate a report. + +## Connect a deployed Lamatic flow + +1. Import or recreate `flows/webhook-reliability-architect.ts` in Lamatic Studio. +2. Select a supported structured-output model and deploy the flow. +3. Set `DEMO_MODE=false` in `apps/.env.local`. +4. Add: + +```bash +WEBHOOK_RELIABILITY_ARCHITECT_FLOW_ID=your_deployed_flow_id +LAMATIC_API_URL=https://your-project-endpoint.example.com +LAMATIC_PROJECT_ID=your_project_id +LAMATIC_API_KEY=your_api_key +``` + +5. Restart the app and verify that the report header says **Live flow**. + +## Safety boundaries + +- This kit produces architecture guidance; it never changes queues, databases, retry settings, or production traffic. +- It does not guarantee exactly-once delivery. It recommends idempotent processing that contains duplicate delivery. +- A human engineer must validate retention, privacy, capacity, compliance, and transaction-boundary assumptions. +- Dead-letter replay is always operator-controlled and must reuse the original idempotency key. +- Raw secrets and personal data must not be included in prompts, payload examples, logs, or reports. + +## Validation + +From `apps/`: + +```bash +npm run typecheck +npm run build +``` + +Before contribution, also verify: + +- The Lamatic flow imports and runs in Studio. +- The live response matches the structured report contract. +- Demo and live modes render the same report shape. +- Only `kits/webhook-reliability-architect/` is changed in the PR. + +## Author + +Built by [Amar Kumar](https://github.com/amarkumar00) for the Lamatic AgentKit Challenge. diff --git a/kits/webhook-reliability-architect/agent.md b/kits/webhook-reliability-architect/agent.md new file mode 100644 index 000000000..b022cdfc6 --- /dev/null +++ b/kits/webhook-reliability-architect/agent.md @@ -0,0 +1,76 @@ +# Webhook Reliability Architect — Agent Guide + +## Overview + +Webhook Reliability Architect is a design-review agent for webhook systems that can trigger business side effects. It converts a sanitized delivery scenario into a structured reliability report covering idempotency, retries, dead-letter handling, observability, failure modes, failure injection, and staged rollout. + +## Purpose + +Distributed delivery is usually at least once: timeouts and lost acknowledgements make redelivery normal. The agent helps engineers replace informal retry logic with an explicit contract that prevents a duplicate request from becoming a duplicate business mutation. + +The goal is not to claim exactly-once transport. The goal is to make duplicate delivery safe, bound recovery work, and define evidence that the system behaves as designed. + +## Flow + +### `webhook-reliability-architect` + +- **Trigger:** Lamatic API Request node. +- **Inputs:** one JSON-encoded `scenario` string containing the system name, event type, business effect, delivery semantics, ordering requirement, retry bounds, current safeguards, sanitized payload, and failure context. +- **Processing:** A structured-output model evaluates duplicate-delivery risk and produces one report under a fixed schema. +- **Response:** Lamatic API Response node returns the report as `analysis`. +- **Use when:** Reviewing a new webhook, preparing a reliability hardening sprint, or converting an incident lesson into a testable design. +- **Do not use when:** The caller expects code deployment, queue mutation, production replay, security certification, or a guarantee of exactly-once delivery. + +## Required output + +The report must include: + +1. Executive summary, risk score, and risk level. +2. Explicit assumptions. +3. Idempotency key strategy and conflict behavior. +4. Bounded retry policy and attempt schedule. +5. Dead-letter record and safe replay checklist. +6. Delivery SLO, metrics, alerts, and log fields. +7. Failure modes with impact, signals, and mitigations. +8. Failure-injection tests with expected evidence. +9. Staged rollout steps. + +## Guardrails + +- Never ask for or expose API keys, signing secrets, customer data, payment credentials, or unredacted production payloads. +- Never recommend retrying invalid signatures, authorization failures, schema violations, or deterministic business-rule failures. +- Never claim exactly-once delivery. Prefer idempotent effects, atomic state transitions, or an inbox/outbox pattern. +- Never invent provider behavior. State assumptions when sender guarantees are missing. +- Never recommend blind bulk replay. Reuse original event identities and start with a canary. +- Separate transport acceptance from business completion where asynchronous processing is proposed. +- Treat financial, inventory, and entitlement mutations as high consequence. + +## Integration reference + +| Component | Purpose | Configuration | +|---|---|---| +| Lamatic API Request | Receives the scenario | Deployed flow endpoint | +| Structured-output model | Creates the report | Model selected in Lamatic Studio | +| Lamatic API Response | Returns `analysis` | Output mapping in the flow | +| Next.js app | Collects input and renders the report | Server-side Lamatic SDK | + +## Environment setup + +| Variable | Purpose | +|---|---| +| `WEBHOOK_RELIABILITY_ARCHITECT_FLOW_ID` | Deployed Lamatic flow ID | +| `LAMATIC_API_URL` | Lamatic project endpoint | +| `LAMATIC_PROJECT_ID` | Lamatic project identifier | +| `LAMATIC_API_KEY` | Server-side API key; never expose to the browser | +| `DEMO_MODE` | Uses the deterministic local report when `true` | + +## Common failure modes + +| Symptom | Likely cause | Fix | +|---|---|---| +| App says the flow ID is missing | Live mode is enabled without `WEBHOOK_RELIABILITY_ARCHITECT_FLOW_ID` | Add the deployed flow ID or enable demo mode | +| Lamatic returns an unexpected shape | Flow response mapping or structured schema drifted | Verify the response field is `analysis` and matches the documented object | +| Report proposes an unstable key | Payload has no provider event ID | Require a stable event ID or derive a signed canonical hash with an explicit namespace | +| Retry schedule exceeds business value | Delivery-age input is too large | Set a realistic age budget and move expired events to quarantine | +| Duplicate still repeats a side effect | Receipt and mutation are not atomic | Use the same transaction or a durable state machine before acknowledging | +| Replay creates new duplicates | Operator minted a new idempotency key | Replay using the original identity and inspect the existing receipt first | diff --git a/kits/webhook-reliability-architect/apps/.env.example b/kits/webhook-reliability-architect/apps/.env.example new file mode 100644 index 000000000..876210211 --- /dev/null +++ b/kits/webhook-reliability-architect/apps/.env.example @@ -0,0 +1,7 @@ +# Set to true to explore a deterministic sample report without Lamatic credentials. +DEMO_MODE=true + +WEBHOOK_RELIABILITY_ARCHITECT_FLOW_ID=your_deployed_flow_id +LAMATIC_API_URL=https://your-project-endpoint.example.com +LAMATIC_PROJECT_ID=your_project_id +LAMATIC_API_KEY=your_api_key diff --git a/kits/webhook-reliability-architect/apps/.gitignore b/kits/webhook-reliability-architect/apps/.gitignore new file mode 100644 index 000000000..88bf74a62 --- /dev/null +++ b/kits/webhook-reliability-architect/apps/.gitignore @@ -0,0 +1,6 @@ +.env +.env.local +.next/ +node_modules/ +*.log +*.tsbuildinfo diff --git a/kits/webhook-reliability-architect/apps/AGENTS.md b/kits/webhook-reliability-architect/apps/AGENTS.md new file mode 100644 index 000000000..643577dfa --- /dev/null +++ b/kits/webhook-reliability-architect/apps/AGENTS.md @@ -0,0 +1,9 @@ + + +# This is NOT the Next.js you know + +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices. + +This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean. + + diff --git a/kits/webhook-reliability-architect/apps/CLAUDE.md b/kits/webhook-reliability-architect/apps/CLAUDE.md new file mode 100644 index 000000000..43c994c2d --- /dev/null +++ b/kits/webhook-reliability-architect/apps/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/kits/webhook-reliability-architect/apps/actions/orchestrate.ts b/kits/webhook-reliability-architect/apps/actions/orchestrate.ts new file mode 100644 index 000000000..a2d3c1a27 --- /dev/null +++ b/kits/webhook-reliability-architect/apps/actions/orchestrate.ts @@ -0,0 +1,101 @@ +"use server"; + +import lamaticConfig from "../../lamatic.config"; +import { buildDemoReport } from "@/lib/demo"; +import { getLamaticClient } from "@/lib/lamatic-client"; +import type { AnalysisResult, ReliabilityReport, WebhookScenario } from "@/lib/types"; + +const FLOW_ENV_KEY = + lamaticConfig.steps[0]?.envKey ?? "WEBHOOK_RELIABILITY_ARCHITECT_FLOW_ID"; + +function validateScenario(scenario: WebhookScenario): string | null { + if (!scenario.systemName.trim()) return "System name is required."; + if (!scenario.eventType.trim()) return "Event type is required."; + if (scenario.maxAttempts < 1 || scenario.maxAttempts > 12) { + return "Max attempts must be between 1 and 12."; + } + if (scenario.timeoutSeconds < 1 || scenario.timeoutSeconds > 300) { + return "Timeout must be between 1 and 300 seconds."; + } + if (scenario.maxDeliveryAgeMinutes < 1 || scenario.maxDeliveryAgeMinutes > 10_080) { + return "Delivery age must be between 1 minute and 7 days."; + } + if (scenario.samplePayload.length > 20_000) return "Sample payload is too large."; + if (scenario.currentSafeguards.length > 8_000) return "Safeguard notes are too large."; + if (scenario.failureContext.length > 8_000) return "Failure context is too large."; + return null; +} + +function isReliabilityReport(value: unknown): value is ReliabilityReport { + if (!value || typeof value !== "object") return false; + const candidate = value as Partial; + return ( + typeof candidate.executiveSummary === "string" && + typeof candidate.riskScore === "number" && + typeof candidate.riskLevel === "string" && + Boolean(candidate.idempotencyPlan) && + Boolean(candidate.retryPlan) && + Array.isArray(candidate.failureModes) && + Array.isArray(candidate.testMatrix) + ); +} + +function parseReport(response: unknown): ReliabilityReport | null { + const envelope = response as { + result?: { analysis?: unknown; report?: unknown }; + analysis?: unknown; + report?: unknown; + }; + const raw = + envelope?.result?.analysis ?? + envelope?.result?.report ?? + envelope?.analysis ?? + envelope?.report; + + if (isReliabilityReport(raw)) return raw; + if (typeof raw !== "string") return null; + + try { + const parsed = JSON.parse(raw) as unknown; + return isReliabilityReport(parsed) ? parsed : null; + } catch { + return null; + } +} + +export async function analyzeWebhookScenario( + scenario: WebhookScenario, +): Promise { + const validationError = validateScenario(scenario); + if (validationError) return { success: false, error: validationError }; + + if (process.env.DEMO_MODE === "true") { + return { success: true, report: buildDemoReport(scenario), mode: "demo" }; + } + + const flowId = process.env[FLOW_ENV_KEY]; + if (!flowId) { + return { + success: false, + error: `${FLOW_ENV_KEY} is not configured. Set it in apps/.env.local or enable DEMO_MODE.`, + }; + } + + try { + const client = getLamaticClient(); + const response = await client.executeFlow(flowId, { + scenario: JSON.stringify(scenario), + }); + const report = parseReport(response); + if (!report) { + return { + success: false, + error: "Lamatic returned an unexpected response shape. Verify the deployed flow output schema.", + }; + } + return { success: true, report, mode: "live" }; + } catch (error) { + const message = error instanceof Error ? error.message : "Unknown Lamatic error."; + return { success: false, error: message }; + } +} diff --git a/kits/webhook-reliability-architect/apps/app/globals.css b/kits/webhook-reliability-architect/apps/app/globals.css new file mode 100644 index 000000000..e2968528e --- /dev/null +++ b/kits/webhook-reliability-architect/apps/app/globals.css @@ -0,0 +1,195 @@ +:root { + color-scheme: dark; + --bg: #07110f; + --panel: #0d1916; + --panel-strong: #11231e; + --line: #213b33; + --line-bright: #315b4e; + --text: #effcf6; + --muted: #9bb5ab; + --green: #7df0b7; + --green-strong: #24d889; + --amber: #ffc56e; + --red: #ff8d83; + --blue: #8fc9ff; + --shadow: 0 28px 80px rgba(0, 0, 0, 0.32); +} + +* { box-sizing: border-box; } +html { scroll-behavior: smooth; } +body { + margin: 0; + color: var(--text); + background: + radial-gradient(circle at 12% -10%, rgba(36, 216, 137, 0.14), transparent 30%), + radial-gradient(circle at 94% 14%, rgba(46, 122, 99, 0.14), transparent 24%), + var(--bg); + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + line-height: 1.55; +} + +button, input, textarea, select { font: inherit; } +button, select { cursor: pointer; } +a { color: inherit; } + +main { min-height: 100vh; } +.topbar { + align-items: center; + backdrop-filter: blur(18px); + background: rgba(7, 17, 15, 0.82); + border-bottom: 1px solid rgba(125, 240, 183, 0.13); + display: flex; + justify-content: space-between; + padding: 14px clamp(20px, 5vw, 72px); + position: sticky; + top: 0; + z-index: 10; +} +.brand { align-items: center; display: inline-flex; font-size: 14px; font-weight: 700; gap: 10px; text-decoration: none; } +.brand-mark { align-items: center; background: var(--green); border-radius: 10px; color: #06271b; display: inline-flex; height: 36px; justify-content: center; width: 36px; } +.studio-chip, .mode-chip { align-items: center; background: rgba(125, 240, 183, 0.08); border: 1px solid rgba(125, 240, 183, 0.2); border-radius: 999px; color: #c2ead8; display: inline-flex; font-size: 12px; font-weight: 650; gap: 8px; padding: 7px 11px; } +.pulse { background: var(--green-strong); border-radius: 50%; box-shadow: 0 0 0 5px rgba(36, 216, 137, 0.13); height: 7px; width: 7px; } + +.hero { align-items: center; display: grid; gap: clamp(32px, 6vw, 96px); grid-template-columns: minmax(0, 1.25fr) minmax(300px, .75fr); margin: 0 auto; max-width: 1440px; padding: clamp(72px, 9vw, 132px) clamp(20px, 5vw, 72px) 64px; } +.eyebrow, .step { color: var(--green); font-size: 11px; font-weight: 800; letter-spacing: .14em; margin: 0 0 10px; text-transform: uppercase; } +h1 { font-size: clamp(44px, 6.2vw, 88px); letter-spacing: -.055em; line-height: .98; margin: 0; max-width: 980px; } +.hero-lede { color: #b7cdc4; font-size: clamp(17px, 2vw, 21px); margin: 28px 0 0; max-width: 780px; } +.hero-signals { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 32px; } +.hero-signals span { align-items: center; background: rgba(255, 255, 255, .035); border: 1px solid var(--line); border-radius: 9px; color: #cee1d9; display: inline-flex; font-size: 12px; font-weight: 650; gap: 7px; padding: 9px 11px; } + +.signal-card { background: linear-gradient(145deg, rgba(17, 35, 30, .94), rgba(8, 20, 16, .98)); border: 1px solid var(--line-bright); border-radius: 22px; box-shadow: var(--shadow); padding: 28px; position: relative; } +.signal-card::before { background: var(--green); border-radius: 22px 22px 0 0; content: ""; height: 3px; left: 22px; position: absolute; right: 22px; top: -1px; } +.signal-label { color: var(--muted); font-size: 11px; font-weight: 800; letter-spacing: .13em; margin: 0; text-transform: uppercase; } +.signal-value { font-size: clamp(25px, 3vw, 38px); font-weight: 760; letter-spacing: -.035em; line-height: 1.08; margin: 14px 0 24px; } +.signal-flow { align-items: center; color: var(--green); display: grid; font-size: 10px; font-weight: 800; gap: 7px; grid-template-columns: repeat(7, auto); text-transform: uppercase; } +.signal-flow span { background: rgba(125, 240, 183, .06); border: 1px solid rgba(125, 240, 183, .17); border-radius: 7px; padding: 8px 6px; text-align: center; } +.signal-note { border-top: 1px solid var(--line); color: var(--muted); font-size: 13px; margin: 22px 0 0; padding-top: 18px; } + +.workspace { align-items: start; display: grid; gap: 22px; grid-template-columns: minmax(0, 1fr) 360px; margin: 0 auto; max-width: 1440px; padding: 28px clamp(20px, 5vw, 72px) 76px; } +.scenario-panel, .principles-panel, .report-card, .table-card, .rollout-card { background: rgba(13, 25, 22, .88); border: 1px solid var(--line); border-radius: 20px; } +.scenario-panel { box-shadow: var(--shadow); display: grid; gap: 22px; padding: clamp(22px, 4vw, 42px); } +.section-heading { align-items: start; display: flex; justify-content: space-between; } +h2 { font-size: clamp(28px, 3.4vw, 46px); letter-spacing: -.04em; line-height: 1.05; margin: 0; } +h3 { font-size: 21px; letter-spacing: -.025em; margin: 0; } +h4 { color: #d7ebe2; font-size: 12px; letter-spacing: .06em; margin: 22px 0 10px; text-transform: uppercase; } +.text-button { background: transparent; border: 0; color: var(--green); font-size: 13px; font-weight: 750; padding: 8px; } +.text-button:hover { color: white; } +.field-grid { display: grid; gap: 18px; } +.two-columns { grid-template-columns: repeat(2, minmax(0, 1fr)); } +.three-columns { grid-template-columns: repeat(3, minmax(0, 1fr)); } +label { color: #cbe0d7; display: grid; font-size: 12px; font-weight: 720; gap: 8px; letter-spacing: .015em; } +input, select, textarea { background: #091512; border: 1px solid #29473d; border-radius: 10px; color: var(--text); outline: none; padding: 12px 13px; transition: border-color .16s ease, box-shadow .16s ease; width: 100%; } +input:focus, select:focus, textarea:focus { border-color: var(--green); box-shadow: 0 0 0 3px rgba(125, 240, 183, .12); } +textarea { resize: vertical; } +.code-input, code { font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; } +.code-input { color: #c4f3dc; font-size: 12px; line-height: 1.65; } +.checkbox-row { align-items: center; background: rgba(125, 240, 183, .035); border: 1px solid var(--line); border-radius: 12px; display: flex; gap: 12px; padding: 13px; } +.checkbox-row input { accent-color: var(--green-strong); height: 18px; width: 18px; } +.checkbox-row span { display: grid; } +.checkbox-row small, .privacy-note { color: var(--muted); font-size: 11px; font-weight: 450; } +.primary-button, .secondary-button { align-items: center; border-radius: 10px; display: inline-flex; font-weight: 800; gap: 9px; justify-content: center; } +.primary-button { background: var(--green); border: 1px solid var(--green); color: #052117; min-height: 50px; padding: 13px 18px; } +.primary-button:hover { background: #9af8c9; transform: translateY(-1px); } +.primary-button:disabled { cursor: wait; opacity: .7; transform: none; } +.secondary-button { background: #10231d; border: 1px solid var(--line-bright); color: var(--text); min-height: 38px; padding: 9px 12px; } +.error-banner, .anti-pattern { align-items: flex-start; background: rgba(255, 141, 131, .07); border: 1px solid rgba(255, 141, 131, .28); border-radius: 12px; color: #ffd0cc; display: flex; font-size: 13px; gap: 10px; padding: 13px; } +.privacy-note { margin: -10px 0 0; text-align: center; } + +.principles-panel { padding: 28px; position: sticky; top: 84px; } +.principles-panel h2 { font-size: 28px; } +.principle-list { display: grid; gap: 0; list-style: none; margin: 28px 0; padding: 0; } +.principle-list li { border-top: 1px solid var(--line); display: grid; gap: 14px; grid-template-columns: 30px 1fr; padding: 17px 0; } +.principle-list li > span { align-items: center; border: 1px solid var(--line-bright); border-radius: 50%; color: var(--green); display: inline-flex; font-size: 11px; height: 27px; justify-content: center; width: 27px; } +.principle-list strong { font-size: 14px; } +.principle-list p, .anti-pattern p { color: var(--muted); font-size: 12px; margin: 3px 0 0; } + +.report { display: grid; gap: 22px; margin: 0 auto; max-width: 1440px; padding: 40px clamp(20px, 5vw, 72px) 100px; } +.report-header { align-items: end; border-bottom: 1px solid var(--line); display: flex; gap: 30px; justify-content: space-between; padding-bottom: 28px; } +.report-header > div:first-child { max-width: 920px; } +.report-header > div:first-child > p:last-child { color: var(--muted); font-size: 15px; margin: 14px 0 0; } +.report-actions { align-items: center; display: flex; flex-wrap: wrap; gap: 10px; } +.score-grid { display: grid; gap: 14px; grid-template-columns: 1.35fr repeat(3, 1fr); } +.risk-card, .metric-card { background: rgba(13, 25, 22, .9); border: 1px solid var(--line); border-radius: 16px; min-height: 132px; padding: 20px; } +.risk-card > div, .metric-card { align-items: center; display: flex; gap: 10px; } +.risk-card > div { color: var(--muted); font-size: 12px; } +.risk-card > strong { display: block; font-size: 48px; letter-spacing: -.05em; line-height: 1; margin-top: 12px; } +.risk-card small { color: var(--muted); font-size: 14px; letter-spacing: 0; } +.risk-card > p { font-size: 11px; font-weight: 900; letter-spacing: .12em; margin: 5px 0 0; text-transform: uppercase; } +.risk-low { border-color: rgba(125, 240, 183, .45); color: var(--green); } +.risk-moderate { border-color: rgba(255, 197, 110, .4); color: var(--amber); } +.risk-high, .risk-critical { border-color: rgba(255, 141, 131, .46); color: var(--red); } +.metric-card { color: var(--green); } +.metric-card div { display: grid; } +.metric-card span { color: var(--muted); font-size: 11px; font-weight: 700; } +.metric-card strong { color: var(--text); font-size: 22px; } + +.report-grid { display: grid; gap: 18px; grid-template-columns: repeat(2, minmax(0, 1fr)); } +.report-card, .table-card, .rollout-card { padding: 26px; } +.wide-card { grid-column: 1 / -1; } +.card-title { align-items: center; color: var(--green); display: flex; gap: 12px; margin-bottom: 17px; } +.card-title .step { margin: 0 0 2px; } +.card-title h3 { color: var(--text); } +.report-card > p { color: var(--muted); font-size: 13px; } +.contract-key { align-items: center; background: #07120f; border: 1px solid var(--line); border-radius: 11px; display: flex; gap: 14px; justify-content: space-between; margin: 18px 0; padding: 13px; } +.contract-key span { color: var(--muted); font-size: 11px; font-weight: 800; text-transform: uppercase; } +.contract-key code { color: var(--green); font-size: 12px; overflow-wrap: anywhere; } +.detail-list { display: grid; gap: 0; margin: 0; } +.detail-list div { border-top: 1px solid var(--line); display: grid; gap: 16px; grid-template-columns: 140px 1fr; padding: 13px 0; } +.detail-list dt { color: var(--muted); font-size: 11px; font-weight: 800; text-transform: uppercase; } +.detail-list dd { font-size: 13px; margin: 0; } +.retry-track { display: flex; gap: 8px; margin: 22px 0; overflow-x: auto; padding-bottom: 5px; } +.retry-step { background: #091512; border: 1px solid var(--line); border-radius: 10px; display: grid; flex: 1 0 130px; gap: 2px; padding: 12px; position: relative; } +.retry-step > span { align-items: center; background: var(--green); border-radius: 50%; color: #09241a; display: flex; font-size: 10px; font-weight: 900; height: 20px; justify-content: center; width: 20px; } +.retry-step strong { font-size: 17px; margin-top: 5px; } +.retry-step small { color: var(--muted); font-size: 10px; } +.condition-grid { display: grid; gap: 22px; grid-template-columns: repeat(2, minmax(0, 1fr)); } +ul, .check-list { color: #bed2ca; font-size: 12px; margin: 0; padding-left: 20px; } +li + li { margin-top: 8px; } +.slo { background: rgba(125, 240, 183, .05); border-left: 3px solid var(--green); margin: 16px 0; padding: 13px; } +.slo span { color: var(--green); font-size: 10px; font-weight: 900; letter-spacing: .12em; } +.slo p { font-size: 13px; margin: 3px 0 0; } + +.table-card { overflow: hidden; } +.table-wrap { overflow-x: auto; } +table { border-collapse: collapse; font-size: 12px; min-width: 820px; width: 100%; } +th { color: var(--green); font-size: 10px; letter-spacing: .1em; text-align: left; text-transform: uppercase; } +th, td { border-bottom: 1px solid var(--line); padding: 13px 12px; vertical-align: top; } +td { color: #bed2ca; } +td:first-child { color: var(--text); font-weight: 720; } + +.rollout-card { align-items: start; display: grid; gap: 40px; grid-template-columns: .8fr 1.2fr; } +.rollout-card ol { list-style: none; margin: 0; padding: 0; } +.rollout-card li { align-items: start; border-top: 1px solid var(--line); display: grid; gap: 14px; grid-template-columns: 34px 1fr; padding: 13px 0; } +.rollout-card li span { color: var(--green); font-size: 11px; font-weight: 900; } +.rollout-card li p { color: #c6d9d1; font-size: 13px; margin: 0; } + +footer { border-top: 1px solid var(--line); color: var(--muted); display: flex; font-size: 11px; gap: 20px; justify-content: space-between; margin: 0 clamp(20px, 5vw, 72px); padding: 24px 0 36px; } +footer span { color: var(--text); font-weight: 750; } +footer p { margin: 0; } +.spin { animation: spin .8s linear infinite; } +@keyframes spin { to { transform: rotate(360deg); } } + +@media (max-width: 1000px) { + .hero, .workspace { grid-template-columns: 1fr; } + .signal-card { max-width: 680px; } + .principles-panel { position: static; } + .score-grid { grid-template-columns: repeat(2, 1fr); } +} + +@media (max-width: 700px) { + .studio-chip { display: none; } + .hero { padding-top: 60px; } + .two-columns, .three-columns, .report-grid, .condition-grid, .rollout-card { grid-template-columns: 1fr; } + .wide-card { grid-column: auto; } + .score-grid { grid-template-columns: 1fr; } + .report-header { align-items: flex-start; flex-direction: column; } + .detail-list div { grid-template-columns: 1fr; gap: 4px; } + .signal-flow { grid-template-columns: repeat(4, auto); } + .signal-flow svg { display: none; } + footer { flex-direction: column; } +} + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { scroll-behavior: auto !important; transition: none !important; } + .spin { animation: none; } +} diff --git a/kits/webhook-reliability-architect/apps/app/layout.tsx b/kits/webhook-reliability-architect/apps/app/layout.tsx new file mode 100644 index 000000000..e8913020a --- /dev/null +++ b/kits/webhook-reliability-architect/apps/app/layout.tsx @@ -0,0 +1,17 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "Webhook Reliability Architect", + description: + "Turn a webhook delivery contract into an idempotency, retry, dead-letter, observability, and failure-testing blueprint.", +}; + +export default function RootLayout({ children }: Readonly<{ children: ReactNode }>) { + return ( + + {children} + + ); +} diff --git a/kits/webhook-reliability-architect/apps/app/page.tsx b/kits/webhook-reliability-architect/apps/app/page.tsx new file mode 100644 index 000000000..a5fb455ac --- /dev/null +++ b/kits/webhook-reliability-architect/apps/app/page.tsx @@ -0,0 +1,437 @@ +"use client"; + +import { + Activity, + AlertTriangle, + ArrowRight, + CheckCircle2, + Clock3, + Copy, + DatabaseZap, + FlaskConical, + Gauge, + Network, + RefreshCw, + Send, + ShieldCheck, +} from "lucide-react"; +import { FormEvent, useState, useTransition } from "react"; +import { analyzeWebhookScenario } from "@/actions/orchestrate"; +import type { + AnalysisResult, + BusinessEffect, + DeliverySemantics, + ReliabilityReport, + WebhookScenario, +} from "@/lib/types"; + +const SAMPLE_SCENARIO: WebhookScenario = { + systemName: "Checkout payment events", + eventType: "payment.succeeded", + businessEffect: "financial", + deliverySemantics: "at-least-once", + orderingRequired: true, + maxAttempts: 6, + timeoutSeconds: 10, + maxDeliveryAgeMinutes: 1_440, + currentSafeguards: + "HMAC signature verification and request logs. Retries currently use a fixed 10-second delay. There is no durable idempotency record or dead-letter queue.", + samplePayload: `{ + "event_id": "evt_01J8Z8K7G2", + "type": "payment.succeeded", + "occurred_at": "2026-08-08T08:30:00Z", + "data": { "order_id": "ord_4831", "amount": 4999, "currency": "INR" } +}`, + failureContext: + "The receiver occasionally times out after committing the order update, so the provider retries and duplicate confirmation emails are sent.", +}; + +const EFFECT_OPTIONS: Array<{ value: BusinessEffect; label: string }> = [ + { value: "read-only", label: "Read-only" }, + { value: "reversible-write", label: "Reversible write" }, + { value: "notification", label: "Notification" }, + { value: "inventory", label: "Inventory / entitlement" }, + { value: "financial", label: "Financial" }, +]; + +const DELIVERY_OPTIONS: Array<{ value: DeliverySemantics; label: string }> = [ + { value: "at-least-once", label: "At least once" }, + { value: "at-most-once", label: "At most once" }, + { value: "best-effort", label: "Best effort" }, + { value: "unknown", label: "Unknown" }, +]; + +function formatDelay(seconds: number): string { + if (seconds === 0) return "Immediate"; + if (seconds < 60) return `${seconds}s`; + if (seconds < 3_600) return `${Math.round(seconds / 60)}m`; + return `${Math.round(seconds / 3_600)}h`; +} + +function riskTone(level: ReliabilityReport["riskLevel"]): string { + return `risk-${level}`; +} + +export default function HomePage() { + const [scenario, setScenario] = useState(SAMPLE_SCENARIO); + const [result, setResult] = useState(null); + const [copied, setCopied] = useState(false); + const [isPending, startTransition] = useTransition(); + + function updateScenario( + key: Key, + value: WebhookScenario[Key], + ) { + setScenario((current) => ({ ...current, [key]: value })); + } + + function submitScenario(event: FormEvent) { + event.preventDefault(); + setCopied(false); + startTransition(async () => { + const nextResult = await analyzeWebhookScenario(scenario); + setResult(nextResult); + if (nextResult.success) { + window.requestAnimationFrame(() => { + document.getElementById("report")?.scrollIntoView({ behavior: "smooth" }); + }); + } + }); + } + + async function copyReport() { + if (!result?.report) return; + await navigator.clipboard.writeText(JSON.stringify(result.report, null, 2)); + setCopied(true); + window.setTimeout(() => setCopied(false), 2_000); + } + + const report = result?.report; + + return ( +
+
+ + + Webhook Reliability Architect + + + +
+ +
+
+

Failure-aware architecture, in one reviewable brief

+

Stop duplicate webhooks before they duplicate the business effect.

+

+ Describe a delivery contract. Get an idempotency design, bounded retry schedule, + dead-letter replay procedure, observability SLO, and failure-injection test matrix. +

+
+ Idempotency + Retries + Dead letters + SLOs +
+
+
+

Core invariant

+

One event → one business mutation

+ +

Redelivery returns the remembered result, not a second side effect.

+
+
+ +
+
+
+
+

01 · Delivery contract

+

Describe the webhook

+
+ +
+ +
+ + + + +
+ + + +
+ + + +
+ +