diff --git a/README.md b/README.md index c403366..d9b9127 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,80 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). +# Covenant -## Getting Started +**The governed connection layer.** One call that discovers, authorizes, executes, proves, and learns. -First, run the development server: +MCP asks what an agent *can* do. APIs *do* it. Neither makes the call itself accountable. Covenant fuses both into a single governed connection — and seals a cryptographic proof every time. -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev +> The connection is the asset. + +--- + +## What it is + +Covenant treats every agent→capability call as a **covenant**: a binding, verifiable agreement that runs through nine governed phases before any side effect happens, and leaves a tamper-evident proof behind. + +``` +agent (any model) ──▶ ◆ covenant · 9 governed phases ──▶ capability ──▶ ⛓ sealed proof → PGL ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +### The 9-phase pipeline -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +| # | Phase | What it decides | +|---|-------|-----------------| +| 1 | Identity & Security | Resolve agent, verify **Ed25519** signature, reject replays | +| 2 | Capability & Policy | Resolve capability, compose system/owner/runtime policies, compute effective permissions | +| 3 | Safety & Anomaly | Score the call against behavioral baselines; quarantine on high severity | +| 4 | Cost & Budget | Enforce per-agent budgets and overage policy | +| 5 | Approval | Hold for M-of-N human quorum when required | +| 6 | Execution | Invoke the capability, capture output | +| 7 | Evidence & Proof | Seal a **SHA-256 hash-chained** evidence record (who/what/when/why/how) | +| 8 | Audit & Compliance | Log, classify, set retention | +| 9 | Response | Update trust, return the verdict + proof | -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. +## This is real, not a mock -## Learn More +- **Real cryptography** — Ed25519 keypairs per agent, canonical request signing, signature verification, and a SHA-256 hash-chained evidence ledger (each record links to the previous; the chain is replayable). See `src/lib/covenant/crypto.ts`. +- **Real policy engine** — three-tier composition (system → owner → runtime), conflict detection, deterministic resolution (system-wins, then most-restrictive), and live effective-permission calculation. See `src/lib/covenant/governance.ts`. +- **Real safety** — statistical anomaly detection vs. behavioral baselines (request spikes via μ+3σ, new-capability access, off-hours, failure spikes), trust suppression, quarantine, and approval quorum. See `src/lib/covenant/safety.ts`. +- **Real intelligence** — cost attribution + budgeting and a fused risk score (trust + anomalies + denials + budget pressure → threat level). See `src/lib/covenant/intelligence.ts`. +- Fully typed TypeScript, **no `any`**. -To learn more about Next.js, take a look at the following resources: +## The interface + +The pipeline *is* the product. The app is an instrument panel, not a CRUD admin: + +- **Console** — build a call, sign it, and watch all nine phases decide it in real time; expand any phase to see its reasoning. Override toggles (tamper signature, bypass policy/safety/cost) let you see each gate's effect. +- **Registry** — live capability discovery: "what can this agent do, right now?" +- **Agents** — trust + fused risk per agent, with suspend control. +- **Ledger** — the hash-chained evidence trail, with backward chain replay from any record. +- **Governance** — policy composition with enable/disable toggles and a live effective-permissions probe. +- **Safety** — anomaly feed and the quarantine queue with M-of-N approve/deny. + +## Run it + +```bash +npm install +npm run dev # http://localhost:3000 +npm run build # production build +npm run lint # eslint +``` -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +The runtime seeds a realistic fleet (agents, capabilities, three-tier policies, cost models, baselines) and warms the ledger with signed traffic on first load, so every view is alive immediately. -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! +## API -## Deploy on Vercel +| Method | Route | Purpose | +|--------|-------|---------| +| `POST` | `/api/request` | Sign + run a call through the pipeline | +| `GET` | `/api/state` | Full runtime snapshot | +| `GET` | `/api/discover/{agentId}` | Capability discovery (effective permissions) | +| `GET` | `/api/compose?agent_id&capability_id` | Policy composition + effective permissions | +| `GET` | `/api/pgl/{hash}` | Retrieve an evidence record | +| `GET` | `/api/replay/{hash}` | Walk the hash chain backwards | +| `POST` | `/api/quarantine/{id}` | Approve / deny a quarantined request | +| `POST` | `/api/policy/{id}` | Enable / disable a policy | +| `POST` | `/api/agent/{id}` | Toggle agent suspension | +| `POST` | `/api/budget` | Set an agent's budget for a capability | -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +## Stack -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. +Next.js 14 (App Router) · TypeScript · Tailwind CSS · Framer Motion · Node `crypto`. Part of the **Veklom** ecosystem. diff --git a/package-lock.json b/package-lock.json index 655fa65..a5704fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,9 @@ "name": "covenant", "version": "0.1.0", "dependencies": { - "next": "14.2.5", + "framer-motion": "^11.11.17", + "lucide-react": "^0.454.0", + "next": "^14.2.35", "react": "^18", "react-dom": "^18" }, @@ -17,7 +19,7 @@ "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8", - "eslint-config-next": "14.2.5", + "eslint-config-next": "^14.2.35", "postcss": "^8", "tailwindcss": "^3.4.1", "typescript": "^5" @@ -277,15 +279,15 @@ } }, "node_modules/@next/env": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.5.tgz", - "integrity": "sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==", + "version": "14.2.35", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.35.tgz", + "integrity": "sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.5.tgz", - "integrity": "sha512-LY3btOpPh+OTIpviNojDpUdIbHW9j0JBYBjsIp8IxtDFfYFyORvw3yNq6N231FVqQA7n7lwaf7xHbVJlA1ED7g==", + "version": "14.2.35", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.35.tgz", + "integrity": "sha512-Jw9A3ICz2183qSsqwi7fgq4SBPiNfmOLmTPXKvlnzstUwyvBrtySiY+8RXJweNAs9KThb1+bYhZh9XWcNOr2zQ==", "dev": true, "license": "MIT", "dependencies": { @@ -293,9 +295,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz", - "integrity": "sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.33.tgz", + "integrity": "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==", "cpu": [ "arm64" ], @@ -309,9 +311,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz", - "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.33.tgz", + "integrity": "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==", "cpu": [ "x64" ], @@ -325,9 +327,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz", - "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.33.tgz", + "integrity": "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==", "cpu": [ "arm64" ], @@ -341,9 +343,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz", - "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.33.tgz", + "integrity": "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==", "cpu": [ "arm64" ], @@ -357,9 +359,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz", - "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.33.tgz", + "integrity": "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==", "cpu": [ "x64" ], @@ -373,9 +375,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz", - "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.33.tgz", + "integrity": "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==", "cpu": [ "x64" ], @@ -389,9 +391,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz", - "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.33.tgz", + "integrity": "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==", "cpu": [ "arm64" ], @@ -405,9 +407,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz", - "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.33.tgz", + "integrity": "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==", "cpu": [ "ia32" ], @@ -421,9 +423,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz", - "integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==", + "version": "14.2.33", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.33.tgz", + "integrity": "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==", "cpu": [ "x64" ], @@ -581,61 +583,160 @@ "@types/react": "^18.0.0" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.1.tgz", + "integrity": "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/type-utils": "8.61.1", + "@typescript-eslint/utils": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.61.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/@typescript-eslint/parser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", - "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.1.tgz", + "integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "7.2.0", - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/typescript-estree": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "debug": "^4.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.1.tgz", + "integrity": "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.61.1", + "@typescript-eslint/types": "^8.61.1", + "debug": "^4.4.3" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", - "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.1.tgz", + "integrity": "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0" + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz", + "integrity": "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.1.tgz", + "integrity": "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/utils": "8.61.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/@typescript-eslint/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", - "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.1.tgz", + "integrity": "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==", "dev": true, "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -643,78 +744,127 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", - "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.1.tgz", + "integrity": "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "@typescript-eslint/project-service": "8.61.1", + "@typescript-eslint/tsconfig-utils": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@typescript-eslint/utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.1.tgz", + "integrity": "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", - "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.1.tgz", + "integrity": "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.2.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "8.61.1", + "eslint-visitor-keys": "^5.0.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", @@ -1186,16 +1336,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/array.prototype.findlast": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", @@ -1776,19 +1916,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -2107,15 +2234,16 @@ } }, "node_modules/eslint-config-next": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.5.tgz", - "integrity": "sha512-zogs9zlOiZ7ka+wgUnmcM0KBEDjo4Jis7kxN1jvC0N4wynQ2MIx/KBkg4mVF63J5EK4W0QMCn7xO3vNisjaAoA==", + "version": "14.2.35", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.35.tgz", + "integrity": "sha512-BpLsv01UisH193WyT/1lpHqq5iJ/Orfz9h/NOOlAmTUq4GY349PextQ62K4XpnaM9supeiEn3TaOTeQO07gURg==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "14.2.5", + "@next/eslint-plugin-next": "14.2.35", "@rushstack/eslint-patch": "^1.3.3", - "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", + "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.28.1", @@ -2637,6 +2765,31 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/framer-motion": { + "version": "11.11.17", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.11.17.tgz", + "integrity": "sha512-O8QzvoKiuzI5HSAHbcYuL6xU+ZLXbrH7C8Akaato4JzQbX2ULNeniqC2Vo5eiCtFktX9XsJ+7nUhxcl2E2IjpA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2879,27 +3032,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -3788,6 +3920,15 @@ "dev": true, "license": "ISC" }, + "node_modules/lucide-react": { + "version": "0.454.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.454.0.tgz", + "integrity": "sha512-hw7zMDwykCLnEzgncEEjHeA6+45aeEzRYuKHuyRSOPkhko+J3ySGjGIzu+mmMfDFG1vazHepMaYFYHbTFAZAAQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -3916,13 +4057,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.5.tgz", - "integrity": "sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==", - "deprecated": "This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details.", + "version": "14.2.35", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.35.tgz", + "integrity": "sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig==", "license": "MIT", "dependencies": { - "@next/env": "14.2.5", + "@next/env": "14.2.35", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", @@ -3937,15 +4077,15 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.5", - "@next/swc-darwin-x64": "14.2.5", - "@next/swc-linux-arm64-gnu": "14.2.5", - "@next/swc-linux-arm64-musl": "14.2.5", - "@next/swc-linux-x64-gnu": "14.2.5", - "@next/swc-linux-x64-musl": "14.2.5", - "@next/swc-win32-arm64-msvc": "14.2.5", - "@next/swc-win32-ia32-msvc": "14.2.5", - "@next/swc-win32-x64-msvc": "14.2.5" + "@next/swc-darwin-arm64": "14.2.33", + "@next/swc-darwin-x64": "14.2.33", + "@next/swc-linux-arm64-gnu": "14.2.33", + "@next/swc-linux-arm64-musl": "14.2.33", + "@next/swc-linux-x64-gnu": "14.2.33", + "@next/swc-linux-x64-musl": "14.2.33", + "@next/swc-win32-arm64-msvc": "14.2.33", + "@next/swc-win32-ia32-msvc": "14.2.33", + "@next/swc-win32-x64-msvc": "14.2.33" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -4311,16 +4451,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5063,16 +5193,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5569,16 +5689,16 @@ } }, "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/ts-interface-checker": { diff --git a/package.json b/package.json index d2da1b9..ead2691 100644 --- a/package.json +++ b/package.json @@ -9,18 +9,20 @@ "lint": "next lint" }, "dependencies": { + "framer-motion": "^11.11.17", + "lucide-react": "^0.454.0", + "next": "^14.2.35", "react": "^18", - "react-dom": "^18", - "next": "14.2.5" + "react-dom": "^18" }, "devDependencies": { - "typescript": "^5", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "eslint": "^8", + "eslint-config-next": "^14.2.35", "postcss": "^8", "tailwindcss": "^3.4.1", - "eslint": "^8", - "eslint-config-next": "14.2.5" + "typescript": "^5" } } diff --git a/src/app/agents/page.tsx b/src/app/agents/page.tsx new file mode 100644 index 0000000..77b7f78 --- /dev/null +++ b/src/app/agents/page.tsx @@ -0,0 +1,121 @@ +"use client"; + +import { Power, ShieldCheck } from "lucide-react"; +import { useLive } from "@/components/useLive"; +import { Eyebrow, Panel, Meter } from "@/components/ui"; +import { cx, postJSON, refreshLive, THREAT_STYLE, SEVERITY_STYLE } from "@/components/util"; +import type { AgentView } from "@/lib/covenant/api"; + +export default function AgentsPage() { + const { data, refresh } = useLive(); + const agents = data?.agents ?? []; + + const toggleSuspend = async (id: string) => { + await postJSON(`/api/agent/${id}`, {}); + refresh(); + refreshLive(); + }; + + return ( +
+ Agents · trust & risk +

+ The fleet, scored continuously +

+

+ Trust moves on every covenant. Risk fuses trust, anomaly history, denials, and budget + pressure into a live threat level — and tells you what to do about it. +

+ +
+ {agents.map((a) => ( + toggleSuspend(a.identity.agent_id)} /> + ))} +
+
+ ); +} + +function AgentCard({ a, onToggle }: { a: AgentView; onToggle: () => void }) { + const t = a.trust; + return ( + +
+
+
+ {a.identity.agent_name} + + {a.identity.metadata.tier} · {a.identity.metadata.inference_provider} + +
+
{a.identity.agent_id}
+
+ +
+ +
+
+
+ TRUST + {t?.score ?? "—"} +
+
+ = 50 ? "signal" : "rose"} /> +
+
+ req {t?.total_requests ?? 0} + esc {t?.escalation_events ?? 0} + success {Math.round((t?.success_rate ?? 0) * 100)}% + denials {Math.round((t?.denial_frequency ?? 0) * 100)}% +
+
+ +
+
+ RISK + + {a.risk.overall_risk_score} + +
+
+ +
+
+ threat · {a.risk.threat_level} +
+
spend {a.spend}
+
+
+ +
+
+ {a.risk.risk_factors + .filter((f) => f.contribution > 0) + .map((f) => ( + + {f.factor_name} +{f.contribution} + + ))} +
+
+ + {a.risk.recommended_actions[0]} +
+
+
+ ); +} diff --git a/src/app/api/agent/[id]/route.ts b/src/app/api/agent/[id]/route.ts new file mode 100644 index 0000000..e8e820d --- /dev/null +++ b/src/app/api/agent/[id]/route.ts @@ -0,0 +1,12 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getEngine } from "@/lib/covenant/engine"; + +export const dynamic = "force-dynamic"; + +export async function POST( + _req: NextRequest, + { params }: { params: { id: string } }, +) { + const suspended = getEngine().runtime.toggleAgentSuspension(params.id); + return NextResponse.json({ agent_id: params.id, suspended }); +} diff --git a/src/app/api/budget/route.ts b/src/app/api/budget/route.ts new file mode 100644 index 0000000..07d4c38 --- /dev/null +++ b/src/app/api/budget/route.ts @@ -0,0 +1,21 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getEngine } from "@/lib/covenant/engine"; + +export const dynamic = "force-dynamic"; + +export async function POST(req: NextRequest) { + const body = (await req.json()) as { + agent_id: string; + capability_id: string; + budget: number; + }; + if (!body.agent_id || !body.capability_id || typeof body.budget !== "number") { + return NextResponse.json({ error: "agent_id, capability_id, budget required" }, { status: 400 }); + } + getEngine().runtime.intelligence.allocateBudget( + body.agent_id, + body.capability_id, + body.budget, + ); + return NextResponse.json({ ok: true }); +} diff --git a/src/app/api/compose/route.ts b/src/app/api/compose/route.ts new file mode 100644 index 0000000..5b63782 --- /dev/null +++ b/src/app/api/compose/route.ts @@ -0,0 +1,13 @@ +import { NextRequest, NextResponse } from "next/server"; +import { composeFor } from "@/lib/covenant/api"; + +export const dynamic = "force-dynamic"; + +export function GET(req: NextRequest) { + const agent = req.nextUrl.searchParams.get("agent_id"); + const cap = req.nextUrl.searchParams.get("capability_id"); + if (!agent || !cap) { + return NextResponse.json({ error: "agent_id and capability_id required" }, { status: 400 }); + } + return NextResponse.json(composeFor(agent, cap)); +} diff --git a/src/app/api/discover/[agentId]/route.ts b/src/app/api/discover/[agentId]/route.ts new file mode 100644 index 0000000..173d90c --- /dev/null +++ b/src/app/api/discover/[agentId]/route.ts @@ -0,0 +1,11 @@ +import { NextRequest, NextResponse } from "next/server"; +import { discover } from "@/lib/covenant/api"; + +export const dynamic = "force-dynamic"; + +export function GET( + _req: NextRequest, + { params }: { params: { agentId: string } }, +) { + return NextResponse.json({ capabilities: discover(params.agentId) }); +} diff --git a/src/app/api/pgl/[hash]/route.ts b/src/app/api/pgl/[hash]/route.ts new file mode 100644 index 0000000..b58d47d --- /dev/null +++ b/src/app/api/pgl/[hash]/route.ts @@ -0,0 +1,15 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getEngine } from "@/lib/covenant/engine"; + +export const dynamic = "force-dynamic"; + +export function GET( + _req: NextRequest, + { params }: { params: { hash: string } }, +) { + const evidence = getEngine().runtime.getEvidence(params.hash); + if (!evidence) { + return NextResponse.json({ error: "Evidence not found" }, { status: 404 }); + } + return NextResponse.json(evidence); +} diff --git a/src/app/api/policy/[id]/route.ts b/src/app/api/policy/[id]/route.ts new file mode 100644 index 0000000..edf00a0 --- /dev/null +++ b/src/app/api/policy/[id]/route.ts @@ -0,0 +1,13 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getEngine } from "@/lib/covenant/engine"; + +export const dynamic = "force-dynamic"; + +export async function POST( + req: NextRequest, + { params }: { params: { id: string } }, +) { + const body = (await req.json()) as { enabled: boolean }; + getEngine().runtime.setPolicyEnabled(params.id, body.enabled); + return NextResponse.json({ policy_id: params.id, enabled: body.enabled }); +} diff --git a/src/app/api/quarantine/[id]/route.ts b/src/app/api/quarantine/[id]/route.ts new file mode 100644 index 0000000..7f60ca6 --- /dev/null +++ b/src/app/api/quarantine/[id]/route.ts @@ -0,0 +1,20 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getEngine } from "@/lib/covenant/engine"; + +export const dynamic = "force-dynamic"; + +export async function POST( + req: NextRequest, + { params }: { params: { id: string } }, +) { + const body = (await req.json()) as { action: "approve" | "deny"; approver?: string }; + const safety = getEngine().runtime.safety; + const record = + body.action === "approve" + ? safety.approve(params.id, body.approver ?? `approver-${Date.now()}`) + : safety.deny(params.id); + if (!record) { + return NextResponse.json({ error: "Quarantine record not found" }, { status: 404 }); + } + return NextResponse.json(record); +} diff --git a/src/app/api/replay/[hash]/route.ts b/src/app/api/replay/[hash]/route.ts new file mode 100644 index 0000000..151fc35 --- /dev/null +++ b/src/app/api/replay/[hash]/route.ts @@ -0,0 +1,15 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getEngine } from "@/lib/covenant/engine"; + +export const dynamic = "force-dynamic"; + +export function GET( + _req: NextRequest, + { params }: { params: { hash: string } }, +) { + const result = getEngine().runtime.replay(params.hash); + if (!result.evidence) { + return NextResponse.json({ error: "Evidence not found" }, { status: 404 }); + } + return NextResponse.json(result); +} diff --git a/src/app/api/request/route.ts b/src/app/api/request/route.ts new file mode 100644 index 0000000..0d55b7c --- /dev/null +++ b/src/app/api/request/route.ts @@ -0,0 +1,24 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getEngine, type SignedCallInput } from "@/lib/covenant/engine"; + +export const dynamic = "force-dynamic"; + +export async function POST(req: NextRequest) { + const body = (await req.json()) as Partial; + if (!body.agent_id || !body.capability_id || !body.action) { + return NextResponse.json( + { error: "agent_id, capability_id and action are required" }, + { status: 400 }, + ); + } + const response = getEngine().signAndProcess({ + agent_id: body.agent_id, + capability_id: body.capability_id, + action: body.action, + input: body.input ?? {}, + approvals: body.approvals, + bypass: body.bypass, + tamper: body.tamper, + }); + return NextResponse.json(response); +} diff --git a/src/app/api/state/route.ts b/src/app/api/state/route.ts new file mode 100644 index 0000000..91623f0 --- /dev/null +++ b/src/app/api/state/route.ts @@ -0,0 +1,8 @@ +import { NextResponse } from "next/server"; +import { buildSnapshot } from "@/lib/covenant/api"; + +export const dynamic = "force-dynamic"; + +export function GET() { + return NextResponse.json(buildSnapshot()); +} diff --git a/src/app/console/page.tsx b/src/app/console/page.tsx new file mode 100644 index 0000000..922b38c --- /dev/null +++ b/src/app/console/page.tsx @@ -0,0 +1,315 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import { Play, Zap, ShieldOff, Beaker } from "lucide-react"; +import { useLive } from "@/components/useLive"; +import { Pipeline } from "@/components/Pipeline"; +import { Eyebrow, LiveDot, Panel, Verdict, KeyVal } from "@/components/ui"; +import { cx, postJSON, refreshLive, short } from "@/components/util"; +import type { CovenantResponse } from "@/lib/covenant/types"; + +interface Preset { + label: string; + desc: string; + agent: string; + cap: string; + action: string; + input: string; + approvals?: string; + tamper?: boolean; +} + +const PRESETS: Preset[] = [ + { label: "Clean read", desc: "in-policy, in-budget", agent: "agent-atlas", cap: "cap-db-read", action: "select", input: '{ "table": "customers", "limit": 10 }' }, + { label: "Payment · needs quorum", desc: "approval gate fires", agent: "agent-ledger", cap: "cap-payment", action: "issue", input: '{ "amount": 240, "to": "vendor-9" }' }, + { label: "Payment · approved", desc: "CFO signs off", agent: "agent-ledger", cap: "cap-payment", action: "issue", input: '{ "amount": 240, "to": "vendor-9" }', approvals: "human:cfo" }, + { label: "Policy denial", desc: "no grant for this agent", agent: "agent-echo", cap: "cap-db-write", action: "update", input: '{ "id": 7 }' }, + { label: "System-denied purge", desc: "immutable guardrail", agent: "agent-atlas", cap: "cap-purge", action: "purge", input: "{}" }, + { label: "Tampered signature", desc: "Phase 1 rejects", agent: "agent-scout", cap: "cap-search", action: "query", input: '{ "q": "x" }', tamper: true }, +]; + +export default function ConsolePage() { + const { data } = useLive(); + const [agent, setAgent] = useState("agent-atlas"); + const [cap, setCap] = useState("cap-db-read"); + const [action, setAction] = useState("select"); + const [input, setInput] = useState('{ "table": "customers", "limit": 10 }'); + const [approvals, setApprovals] = useState(""); + const [tamper, setTamper] = useState(false); + const [bypass, setBypass] = useState({ policy: false, safety: false, cost: false }); + const [resp, setResp] = useState(null); + const [runId, setRunId] = useState(0); + const [busy, setBusy] = useState(false); + const [err, setErr] = useState(null); + + const agents = useMemo(() => data?.agents ?? [], [data]); + const caps = useMemo(() => data?.capabilities ?? [], [data]); + + useEffect(() => { + if (agents.length && !agents.find((a) => a.identity.agent_id === agent)) { + setAgent(agents[0].identity.agent_id); + } + }, [agents, agent]); + + const selectedCap = useMemo( + () => caps.find((c) => c.capability_id === cap), + [caps, cap], + ); + + const applyPreset = (p: Preset) => { + setAgent(p.agent); + setCap(p.cap); + setAction(p.action); + setInput(p.input); + setApprovals(p.approvals ?? ""); + setTamper(Boolean(p.tamper)); + setBypass({ policy: false, safety: false, cost: false }); + }; + + const fire = async () => { + setBusy(true); + setErr(null); + let parsed: Record = {}; + try { + parsed = input.trim() ? (JSON.parse(input) as Record) : {}; + } catch { + setErr("Input is not valid JSON"); + setBusy(false); + return; + } + try { + const r = await postJSON("/api/request", { + agent_id: agent, + capability_id: cap, + action, + input: parsed, + approvals: approvals + .split(",") + .map((s) => s.trim()) + .filter(Boolean), + tamper, + bypass, + }); + setResp(r); + setRunId((n) => n + 1); + refreshLive(); + } catch { + setErr("Request failed"); + } finally { + setBusy(false); + } + }; + + return ( +
+
+
+ Live console +

+ Open a covenant +

+

+ Build a call, sign it with the agent's Ed25519 key, and watch all nine governed + phases decide it — in real time, with the reasoning exposed. +

+
+ +
+ + {/* presets */} +
+ {PRESETS.map((p) => ( + + ))} +
+ +
+ {/* builder */} + + Request builder +
+ + + + + + {selectedCap && ( +
+ {selectedCap.endpoint} · {selectedCap.metadata.category} +
+ )} +
+ + setAction(e.target.value)} + className="w-full rounded-lg border hairline bg-ink-900/70 px-3 py-2 font-mono text-sm text-white outline-none focus:border-signal/50" + /> + + +