diff --git a/contrib/routes/issue-100-session-budget-suite/README.md b/contrib/routes/issue-100-session-budget-suite/README.md new file mode 100644 index 0000000..be6bed2 --- /dev/null +++ b/contrib/routes/issue-100-session-budget-suite/README.md @@ -0,0 +1,25 @@ +# Issue #100 - Session Budget Suite + +This suite simulates session key budget enforcement for an agent. + +## Endpoints + +### `GET /remaining-budget` +Returns the current remaining budget. + +### `POST /spend` +Attempts to apply a spend against the remaining budget. + +**Body:** +```json +{ + "amount": 100 +} +``` + +**Responses:** +- **200 OK**: If the spend is within the remaining budget. The budget is decremented. +- **403 Forbidden**: If the spend exceeds the remaining budget. The budget is left unchanged. + +## Running the Test +Execute `node test.js` to run a simulation of successful spends and a rejected over-budget spend. diff --git a/contrib/routes/issue-100-session-budget-suite/server.js b/contrib/routes/issue-100-session-budget-suite/server.js new file mode 100644 index 0000000..895d2fa --- /dev/null +++ b/contrib/routes/issue-100-session-budget-suite/server.js @@ -0,0 +1,49 @@ +const http = require('http'); +const url = require('url'); + +const PORT = 3100; + +// Mock budget state +let budget = 1000; + +const server = http.createServer((req, res) => { + const parsedUrl = url.parse(req.url, true); + + if (parsedUrl.pathname === '/remaining-budget' && req.method === 'GET') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ remaining_budget: budget })); + } else if (parsedUrl.pathname === '/spend' && req.method === 'POST') { + let body = ''; + req.on('data', chunk => { + body += chunk.toString(); + }); + req.on('end', () => { + try { + const { amount } = JSON.parse(body); + if (typeof amount !== 'number' || amount <= 0) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + return res.end(JSON.stringify({ error: "Invalid amount" })); + } + + if (amount > budget) { + res.writeHead(403, { 'Content-Type': 'application/json' }); + return res.end(JSON.stringify({ error: "Budget exhausted. Spend rejected.", remaining_budget: budget })); + } + + budget -= amount; + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: true, remaining_budget: budget })); + } catch (err) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: "Invalid JSON" })); + } + }); + } else { + res.writeHead(404); + res.end(); + } +}); + +server.listen(PORT, () => { + console.log(`Session budget server running on port ${PORT}`); +}); diff --git a/contrib/routes/issue-100-session-budget-suite/test.js b/contrib/routes/issue-100-session-budget-suite/test.js new file mode 100644 index 0000000..cda16fb --- /dev/null +++ b/contrib/routes/issue-100-session-budget-suite/test.js @@ -0,0 +1,47 @@ +const { spawn } = require('child_process'); + +const server = spawn('node', ['server.js'], { cwd: __dirname }); + +setTimeout(async () => { + try { + console.log('--- Initial Budget ---'); + let res = await fetch('http://localhost:3100/remaining-budget'); + console.log(`Body:`, await res.json()); + + console.log('\n--- Spend 400 (Within Budget) ---'); + res = await fetch('http://localhost:3100/spend', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ amount: 400 }) + }); + console.log(`Status: ${res.status}`); + console.log(`Body:`, await res.json()); + + console.log('\n--- Spend 500 (Within Budget) ---'); + res = await fetch('http://localhost:3100/spend', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ amount: 500 }) + }); + console.log(`Status: ${res.status}`); + console.log(`Body:`, await res.json()); + + console.log('\n--- Spend 200 (Over Budget) ---'); + res = await fetch('http://localhost:3100/spend', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ amount: 200 }) + }); + console.log(`Status: ${res.status}`); + console.log(`Body:`, await res.json()); + + console.log('\n--- Final Budget ---'); + res = await fetch('http://localhost:3100/remaining-budget'); + console.log(`Body:`, await res.json()); + + } catch (err) { + console.error(err); + } finally { + server.kill(); + } +}, 1000); diff --git a/contrib/routes/issue-101-policy-catalog-suite/README.md b/contrib/routes/issue-101-policy-catalog-suite/README.md new file mode 100644 index 0000000..8f9ffd1 --- /dev/null +++ b/contrib/routes/issue-101-policy-catalog-suite/README.md @@ -0,0 +1,30 @@ +# Issue #101 - Policy Catalog Suite + +This suite simulates a policy catalog with validation rules. + +## Endpoints + +### `GET /list-types` +Returns a list of available policy types. + +### `GET /get-rules?type={type}` +Returns the validation rules for a specific policy type. + +### `POST /validate` +Validates a candidate configuration against a specific policy type. + +**Body:** +```json +{ + "type": "spending_limit", + "config": { + "amount": 100 + } +} +``` + +**Responses:** +- **200 OK**: Returns a JSON object with a `valid` boolean and a `results` array detailing which rules passed or failed. + +## Running the Test +Execute `node test.js` to see passing and failing validations. diff --git a/contrib/routes/issue-101-policy-catalog-suite/server.js b/contrib/routes/issue-101-policy-catalog-suite/server.js new file mode 100644 index 0000000..8d61672 --- /dev/null +++ b/contrib/routes/issue-101-policy-catalog-suite/server.js @@ -0,0 +1,68 @@ +const http = require('http'); +const url = require('url'); + +const PORT = 3101; + +const policies = { + "spending_limit": { + rules: [ + { id: "min_amount", description: "Amount must be at least 10", validate: (config) => config.amount >= 10 }, + { id: "max_amount", description: "Amount must be at most 5000", validate: (config) => config.amount <= 5000 } + ] + }, + "time_window": { + rules: [ + { id: "start_hour", description: "Must start after 08:00", validate: (config) => config.start >= 8 }, + { id: "end_hour", description: "Must end before 18:00", validate: (config) => config.end <= 18 } + ] + } +}; + +const server = http.createServer((req, res) => { + const parsedUrl = url.parse(req.url, true); + + if (parsedUrl.pathname === '/list-types' && req.method === 'GET') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ types: Object.keys(policies) })); + } else if (parsedUrl.pathname === '/get-rules' && req.method === 'GET') { + const type = parsedUrl.query.type; + if (!type || !policies[type]) { + res.writeHead(404, { 'Content-Type': 'application/json' }); + return res.end(JSON.stringify({ error: "Policy type not found" })); + } + const rules = policies[type].rules.map(r => ({ id: r.id, description: r.description })); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ type, rules })); + } else if (parsedUrl.pathname === '/validate' && req.method === 'POST') { + let body = ''; + req.on('data', chunk => { body += chunk.toString(); }); + req.on('end', () => { + try { + const { type, config } = JSON.parse(body); + if (!type || !policies[type] || !config) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + return res.end(JSON.stringify({ error: "Invalid type or config" })); + } + + const results = policies[type].rules.map(rule => ({ + id: rule.id, + passed: rule.validate(config) + })); + + const allPassed = results.every(r => r.passed); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ type, valid: allPassed, results })); + } catch (err) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: "Invalid JSON" })); + } + }); + } else { + res.writeHead(404); + res.end(); + } +}); + +server.listen(PORT, () => { + console.log(`Policy catalog server running on port ${PORT}`); +}); diff --git a/contrib/routes/issue-101-policy-catalog-suite/test.js b/contrib/routes/issue-101-policy-catalog-suite/test.js new file mode 100644 index 0000000..5ee2d8a --- /dev/null +++ b/contrib/routes/issue-101-policy-catalog-suite/test.js @@ -0,0 +1,36 @@ +const { spawn } = require('child_process'); + +const server = spawn('node', ['server.js'], { cwd: __dirname }); + +setTimeout(async () => { + try { + console.log('--- List Types ---'); + let res = await fetch('http://localhost:3101/list-types'); + console.log(`Body:`, await res.json()); + + console.log('\n--- Get Rules (spending_limit) ---'); + res = await fetch('http://localhost:3101/get-rules?type=spending_limit'); + console.log(`Body:`, await res.json()); + + console.log('\n--- Validate: Passing Config ---'); + res = await fetch('http://localhost:3101/validate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ type: 'spending_limit', config: { amount: 100 } }) + }); + console.log(`Body:`, await res.json()); + + console.log('\n--- Validate: Failing Config ---'); + res = await fetch('http://localhost:3101/validate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ type: 'spending_limit', config: { amount: 5 } }) + }); + console.log(`Body:`, await res.json()); + + } catch (err) { + console.error(err); + } finally { + server.kill(); + } +}, 1000); diff --git a/contrib/routes/issue-109-audit-aggregation-suite/README.md b/contrib/routes/issue-109-audit-aggregation-suite/README.md new file mode 100644 index 0000000..1649722 --- /dev/null +++ b/contrib/routes/issue-109-audit-aggregation-suite/README.md @@ -0,0 +1,20 @@ +# Issue #109 - Audit Aggregation Suite + +This suite simulates an audit log API with filtering, pagination, and aggregation. + +## Endpoints + +### `GET /entries` +Returns a paginated list of audit logs. + +**Query Parameters:** +- `actor` (optional): Filter by actor string. +- `action` (optional): Filter by action string. +- `page` (optional): Page number (default: 1). +- `limit` (optional): Items per page (default: 2). + +### `GET /summary` +Returns an aggregated summary of logs grouped by action across the entire dataset. + +## Running the Test +Execute `node test.js` to fetch the summary and perform filtered, paginated queries on the entries. diff --git a/contrib/routes/issue-109-audit-aggregation-suite/server.js b/contrib/routes/issue-109-audit-aggregation-suite/server.js new file mode 100644 index 0000000..cfe6349 --- /dev/null +++ b/contrib/routes/issue-109-audit-aggregation-suite/server.js @@ -0,0 +1,53 @@ +const http = require('http'); +const url = require('url'); + +const PORT = 3109; + +// Mock audit log dataset +const logs = [ + { id: 1, actor: "alice", action: "login", timestamp: "2026-08-31T10:00:00Z" }, + { id: 2, actor: "bob", action: "transfer", timestamp: "2026-08-31T10:05:00Z" }, + { id: 3, actor: "alice", action: "transfer", timestamp: "2026-08-31T10:10:00Z" }, + { id: 4, actor: "charlie", action: "logout", timestamp: "2026-08-31T10:15:00Z" }, + { id: 5, actor: "alice", action: "login", timestamp: "2026-08-31T10:20:00Z" }, + { id: 6, actor: "bob", action: "login", timestamp: "2026-08-31T10:25:00Z" }, + { id: 7, actor: "alice", action: "transfer", timestamp: "2026-08-31T10:30:00Z" } +]; + +const server = http.createServer((req, res) => { + const parsedUrl = url.parse(req.url, true); + + if (parsedUrl.pathname === '/entries' && req.method === 'GET') { + const { actor, action, page = 1, limit = 2 } = parsedUrl.query; + + let filtered = logs; + if (actor) filtered = filtered.filter(l => l.actor === actor); + if (action) filtered = filtered.filter(l => l.action === action); + + const startIndex = (parseInt(page) - 1) * parseInt(limit); + const paginated = filtered.slice(startIndex, startIndex + parseInt(limit)); + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + total: filtered.length, + page: parseInt(page), + limit: parseInt(limit), + entries: paginated + })); + } else if (parsedUrl.pathname === '/summary' && req.method === 'GET') { + const summary = logs.reduce((acc, log) => { + acc[log.action] = (acc[log.action] || 0) + 1; + return acc; + }, {}); + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ summary })); + } else { + res.writeHead(404); + res.end(); + } +}); + +server.listen(PORT, () => { + console.log(`Audit aggregation server running on port ${PORT}`); +}); diff --git a/contrib/routes/issue-109-audit-aggregation-suite/test.js b/contrib/routes/issue-109-audit-aggregation-suite/test.js new file mode 100644 index 0000000..9e3b632 --- /dev/null +++ b/contrib/routes/issue-109-audit-aggregation-suite/test.js @@ -0,0 +1,24 @@ +const { spawn } = require('child_process'); + +const server = spawn('node', ['server.js'], { cwd: __dirname }); + +setTimeout(async () => { + try { + console.log('--- Summary Endpoint ---'); + let res = await fetch('http://localhost:3109/summary'); + console.log(`Body:`, await res.json()); + + console.log('\n--- Entries: Combined Filter (actor=alice, action=transfer, page=1, limit=2) ---'); + res = await fetch('http://localhost:3109/entries?actor=alice&action=transfer&page=1&limit=2'); + console.log(`Body:`, await res.json()); + + console.log('\n--- Entries: Pagination (page=2, limit=2) ---'); + res = await fetch('http://localhost:3109/entries?actor=alice&action=transfer&page=2&limit=2'); + console.log(`Body:`, await res.json()); + + } catch (err) { + console.error(err); + } finally { + server.kill(); + } +}, 1000); diff --git a/contrib/routes/issue-99-x402-challenge-suite/README.md b/contrib/routes/issue-99-x402-challenge-suite/README.md new file mode 100644 index 0000000..e9f40e3 --- /dev/null +++ b/contrib/routes/issue-99-x402-challenge-suite/README.md @@ -0,0 +1,18 @@ +# Issue #99 - x402 Challenge Suite + +This suite simulates an HTTP 402 Payment Required challenge workflow. + +## Endpoints + +### `GET /protected` +Returns protected content if a valid payment proof is provided in the headers. + +**Headers:** +- `x-payment-proof`: The payment proof string. + +**Responses:** +- **200 OK**: If `x-payment-proof` is exactly `valid_mock_proof_123`. +- **402 Payment Required**: If the proof is missing or invalid. Includes a JSON body with the payment challenge details. + +## Running the Test +Execute `node test.js` to see the simulated flow of an initial unauthenticated request, an invalid retry, and a successful retry. diff --git a/contrib/routes/issue-99-x402-challenge-suite/server.js b/contrib/routes/issue-99-x402-challenge-suite/server.js new file mode 100644 index 0000000..1594b95 --- /dev/null +++ b/contrib/routes/issue-99-x402-challenge-suite/server.js @@ -0,0 +1,30 @@ +const http = require('http'); + +const PORT = 3099; + +const server = http.createServer((req, res) => { + if (req.url === '/protected' && req.method === 'GET') { + const proof = req.headers['x-payment-proof']; + if (proof === 'valid_mock_proof_123') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: true, data: "Protected content accessed!" })); + } else { + res.writeHead(402, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + error: "Payment Required", + challenge: { + amount: 10, + currency: "CREDITS", + destination: "mock_wallet_address" + } + })); + } + } else { + res.writeHead(404); + res.end(); + } +}); + +server.listen(PORT, () => { + console.log(`x402 server running on port ${PORT}`); +}); diff --git a/contrib/routes/issue-99-x402-challenge-suite/test.js b/contrib/routes/issue-99-x402-challenge-suite/test.js new file mode 100644 index 0000000..e1c453e --- /dev/null +++ b/contrib/routes/issue-99-x402-challenge-suite/test.js @@ -0,0 +1,30 @@ +const { spawn } = require('child_process'); + +const server = spawn('node', ['server.js'], { cwd: __dirname }); + +setTimeout(async () => { + try { + console.log('--- Initial Request (No Proof) ---'); + const res1 = await fetch('http://localhost:3099/protected'); + console.log(`Status: ${res1.status}`); + console.log(`Body:`, await res1.json()); + + console.log('\n--- Retry Request (Invalid Proof) ---'); + const res2 = await fetch('http://localhost:3099/protected', { + headers: { 'x-payment-proof': 'invalid_proof' } + }); + console.log(`Status: ${res2.status}`); + console.log(`Body:`, await res2.json()); + + console.log('\n--- Retry Request (Valid Proof) ---'); + const res3 = await fetch('http://localhost:3099/protected', { + headers: { 'x-payment-proof': 'valid_mock_proof_123' } + }); + console.log(`Status: ${res3.status}`); + console.log(`Body:`, await res3.json()); + } catch (err) { + console.error(err); + } finally { + server.kill(); + } +}, 1000);