Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions contrib/routes/issue-100-session-budget-suite/README.md
Original file line number Diff line number Diff line change
@@ -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.
49 changes: 49 additions & 0 deletions contrib/routes/issue-100-session-budget-suite/server.js
Original file line number Diff line number Diff line change
@@ -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}`);
});
47 changes: 47 additions & 0 deletions contrib/routes/issue-100-session-budget-suite/test.js
Original file line number Diff line number Diff line change
@@ -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);
30 changes: 30 additions & 0 deletions contrib/routes/issue-101-policy-catalog-suite/README.md
Original file line number Diff line number Diff line change
@@ -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.
68 changes: 68 additions & 0 deletions contrib/routes/issue-101-policy-catalog-suite/server.js
Original file line number Diff line number Diff line change
@@ -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}`);
});
36 changes: 36 additions & 0 deletions contrib/routes/issue-101-policy-catalog-suite/test.js
Original file line number Diff line number Diff line change
@@ -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);
20 changes: 20 additions & 0 deletions contrib/routes/issue-109-audit-aggregation-suite/README.md
Original file line number Diff line number Diff line change
@@ -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.
53 changes: 53 additions & 0 deletions contrib/routes/issue-109-audit-aggregation-suite/server.js
Original file line number Diff line number Diff line change
@@ -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}`);
});
24 changes: 24 additions & 0 deletions contrib/routes/issue-109-audit-aggregation-suite/test.js
Original file line number Diff line number Diff line change
@@ -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);
18 changes: 18 additions & 0 deletions contrib/routes/issue-99-x402-challenge-suite/README.md
Original file line number Diff line number Diff line change
@@ -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.
Loading
Loading