mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-28 20:45:11 +02:00
94 lines
34 KiB
JSON
94 lines
34 KiB
JSON
{
|
|
"schemaVersion": "ecc.context-eval-complex-corpus.v1",
|
|
"id": "complex-tasks@1",
|
|
"sampling": "Realistic multi-file engineering tasks, fixed before any provider call, with deterministic hidden graders scoring partial credit (ECC_EVAL_SCORE). Descriptive pilot: no population-representativeness claim. See complex-eval/DESIGN.md for the preregistered methodology.",
|
|
"minimumDistinctTasks": 3,
|
|
"nonInferiorityMargin": 0.05,
|
|
"selection": [
|
|
{
|
|
"id": "complex-incident-triage",
|
|
"category": "complex-debugging-incident",
|
|
"query": "Finance flagged that some order totals have been off by a cent since yesterday's deploy — details are in evidence/incident.txt. Three changes shipped yesterday (CHANGELOG.md, entries C-1 to C-3). Find the root cause, fix it so totals are computed exactly per the pricing rules in the README, keep `npm test` green, and write INCIDENT.md at the repo root identifying which changelog entry introduced the regression, with a short explanation of why it produces wrong totals.",
|
|
"expectedIds": [
|
|
"skill:orch-fix-defect"
|
|
]
|
|
},
|
|
{
|
|
"id": "complex-sentinel-api",
|
|
"category": "complex-security-hardening",
|
|
"query": "This internal paste-sharing service failed a security review, but the auditors didn't itemize the findings. Review the implementation against the API contract in the README, find every place the code violates the documented security behavior or is otherwise exploitable, and fix all of them without breaking the documented API. `npm test` must stay green.",
|
|
"expectedIds": [
|
|
"skill:security-review"
|
|
]
|
|
},
|
|
{
|
|
"id": "complex-webhook-relay",
|
|
"category": "complex-feature-build",
|
|
"query": "The webhook relay in this repo accepts delivery requests but never actually sends them — the delivery worker was never finished, and customers are losing notifications. Implement asynchronous delivery per the README: POST each delivery's JSON payload to its URL, retry failures with exponential backoff starting around 100ms and doubling each time, give up after 5 total attempts and mark the delivery dead. Keep the documented module contract, make `npm test` pass, and extend the test suite to cover the retry and dead-letter behavior.",
|
|
"expectedIds": [
|
|
"skill:tdd-workflow"
|
|
]
|
|
}
|
|
],
|
|
"tasks": [
|
|
{
|
|
"id": "incident-triage",
|
|
"category": "debugging-incident",
|
|
"manualIds": [
|
|
"skill:orch-fix-defect"
|
|
],
|
|
"checkTimeoutMs": 30000,
|
|
"query": "Finance flagged that some order totals have been off by a cent since yesterday's deploy — details are in evidence/incident.txt. Three changes shipped yesterday (CHANGELOG.md, entries C-1 to C-3). Find the root cause, fix it so totals are computed exactly per the pricing rules in the README, keep `npm test` green, and write INCIDENT.md at the repo root identifying which changelog entry introduced the regression, with a short explanation of why it produces wrong totals.",
|
|
"files": {
|
|
"CHANGELOG.md": "# Changelog\n\n## 2026-09-23 deploy\n\n- **C-1**: request logging switched to JSON lines (`src/request-log.js`).\n Log volume and format only; no request-handling behavior changed.\n- **C-2**: totals computation refactored for readability (`src/totals.js`).\n The old cents-as-integers helper was replaced with a direct decimal\n expression that reviewers found easier to follow. No behavior change intended.\n- **C-3**: inventory client timeout raised from 2s to 5s (`src/inventory-client.js`).\n Reduces spurious failures when the inventory service is slow.\n",
|
|
"evidence/incident.txt": "2026-09-24T08:57:11Z finance-review order=ORD-2204 note=\"charged_total_cents=115 expected_total_cents=116 lines=[{priceCents:165,quantity:1}] discountPercent=30\"\n2026-09-24T09:14:02Z finance-review order=ORD-2291 note=\"charged_total_cents=232 expected_total_cents=233 lines=[{priceCents:250,quantity:1}] discountPercent=7\"\n2026-09-24T09:41:37Z finance-review order=ORD-2310 note=\"charged_total_cents=227 expected_total_cents=228 lines=[{priceCents:325,quantity:1}] discountPercent=30\"\n2026-09-24T10:05:19Z support-ticket customer=\"ORDER-2310 looks like it undercharged me by a cent vs the invoice email\"\n2026-09-24T10:22:48Z finance-review summary=\"12 of 4,813 orders since the 2026-09-23 deploy are off by exactly one cent, always in the store's favor; all pre-deploy orders reconcile\"\n",
|
|
"package.json": "{\n \"name\": \"order-service\",\n \"private\": true,\n \"type\": \"commonjs\",\n \"scripts\": { \"test\": \"node --test test/\" }\n}\n",
|
|
"README.md": "# order-service\n\nComputes order totals for the checkout service.\n\n## Pricing rules\n\nAn order is `{ \"lines\": [{ \"priceCents\": number, \"quantity\": number }], \"discountPercent\": number }`.\n\n- All prices are integer cents. There is no such thing as a fraction of a cent\n in an order total.\n- The discount applies per line: `lineCents = priceCents * quantity * (100 - discountPercent) / 100`,\n rounded **half-up** to the nearest cent (0.5 rounds up).\n- The order total is the sum of the rounded line totals, in integer cents.\n\n`src/totals.js` is CommonJS and exports `computeOrderTotal(order)` returning the\ntotal in integer cents. Run the tests with `npm test`.\n\n## Operations\n\n- `CHANGELOG.md` records what shipped in each deploy.\n- `evidence/incident.txt` holds the finance team's findings for the current incident.\n",
|
|
"src/inventory-client.js": "'use strict';\n\n// Changed 2026-09-23 (C-3): the inventory service has been slow this week;\n// give it 5s instead of 2s before declaring a failure.\nconst INVENTORY_TIMEOUT_MS = 5000;\n\nfunction inventoryClientOptions() {\n return { timeoutMs: INVENTORY_TIMEOUT_MS, retries: 2 };\n}\n\nmodule.exports = { inventoryClientOptions };\n",
|
|
"src/request-log.js": "'use strict';\n\n// Changed 2026-09-23 (C-1): emit request logs as JSON lines so the log\n// pipeline can parse them without regexes.\nfunction logRequest(req) {\n console.log(JSON.stringify({\n method: req.method,\n url: req.url,\n at: new Date().toISOString(),\n }));\n}\n\nmodule.exports = { logRequest };\n",
|
|
"src/totals.js": "'use strict';\n\n// Refactored 2026-09-23 (C-2): express the discount math directly with a\n// decimal factor instead of the old integer-cents helper, which reviewers\n// found hard to follow.\nfunction computeOrderTotal(order) {\n let total = 0;\n for (const line of order.lines) {\n total += Math.round(line.priceCents * line.quantity * (1 - order.discountPercent / 100));\n }\n return total;\n}\n\nmodule.exports = { computeOrderTotal };\n",
|
|
"test/totals.test.js": "'use strict';\nconst test = require('node:test');\nconst assert = require('node:assert/strict');\nconst { computeOrderTotal } = require('../src/totals');\n\ntest('sums lines without a discount', () => {\n assert.equal(computeOrderTotal({ lines: [{ priceCents: 1000, quantity: 2 }], discountPercent: 0 }), 2000);\n});\n\ntest('applies a clean quarter discount', () => {\n assert.equal(computeOrderTotal({ lines: [{ priceCents: 2000, quantity: 1 }], discountPercent: 25 }), 1500);\n});\n\ntest('multiplies quantity before discounting', () => {\n assert.equal(computeOrderTotal({ lines: [{ priceCents: 400, quantity: 3 }], discountPercent: 50 }), 600);\n});\n"
|
|
},
|
|
"check": "'use strict';\n// Hidden grader for incident-triage: checks exact totals on boundary orders and\n// the root-cause report. Prints ECC_EVAL_SCORE and always exits 0.\nconst fs = require('node:fs');\nconst path = require('node:path');\n\nconst checks = [];\nconst record = (name, ok) => checks.push({ name, ok: Boolean(ok) });\n\nlet computeOrderTotal;\ntry { ({ computeOrderTotal } = require(path.join(process.cwd(), 'src', 'totals.js'))); } catch { /* scored below */ }\n\n// Boundary orders where decimal-factor float math under-rounds by a cent;\n// expected values follow the README pricing rules (integer cents, half-up per line).\nconst boundary = [\n { lines: [{ priceCents: 165, quantity: 1 }], discountPercent: 30, expected: 116 },\n { lines: [{ priceCents: 250, quantity: 1 }], discountPercent: 7, expected: 233 },\n { lines: [{ priceCents: 325, quantity: 1 }], discountPercent: 30, expected: 228 },\n { lines: [{ priceCents: 345, quantity: 1 }], discountPercent: 30, expected: 242 },\n { lines: [{ priceCents: 165, quantity: 1 }, { priceCents: 325, quantity: 1 }], discountPercent: 30, expected: 344 },\n];\n\nif (typeof computeOrderTotal === 'function') {\n boundary.forEach((order, index) => {\n let actual = NaN;\n try { actual = computeOrderTotal({ lines: order.lines, discountPercent: order.discountPercent }); } catch { /* wrong */ }\n record(`boundary-total-${index + 1}`, actual === order.expected);\n });\n let plain = NaN;\n try { plain = computeOrderTotal({ lines: [{ priceCents: 1000, quantity: 2 }], discountPercent: 0 }); } catch { /* wrong */ }\n record('undiscounted-total-unchanged', plain === 2000);\n} else {\n for (let index = 0; index < boundary.length; index++) record(`boundary-total-${index + 1}`, false);\n record('undiscounted-total-unchanged', false);\n}\n\nlet incident = '';\ntry { incident = fs.readFileSync(path.join(process.cwd(), 'INCIDENT.md'), 'utf8'); } catch { /* missing */ }\nrecord('incident-identifies-C-2', /C-2/.test(incident));\nrecord('incident-explains-rounding', /round|float|decimal|cent/i.test(incident));\n\nconst ok = checks.filter(c => c.ok).length;\nfor (const c of checks) console.log(`${c.ok ? 'ok' : 'not ok'} - ${c.name}`);\nconsole.log(`ECC_EVAL_SCORE ${JSON.stringify({ score: ok / checks.length, passed: ok, total: checks.length })}`);\nprocess.exit(0);\n"
|
|
},
|
|
{
|
|
"id": "sentinel-api",
|
|
"category": "security-hardening",
|
|
"manualIds": [
|
|
"skill:security-review"
|
|
],
|
|
"checkTimeoutMs": 60000,
|
|
"query": "This internal paste-sharing service failed a security review, but the auditors didn't itemize the findings. Review the implementation against the API contract in the README, find every place the code violates the documented security behavior or is otherwise exploitable, and fix all of them without breaking the documented API. `npm test` must stay green.",
|
|
"files": {
|
|
"data/files/welcome.txt": "hello from sentinel\n",
|
|
"data/secret.txt": "CANARY-7f3c9d-internal-only\n",
|
|
"package.json": "{\n \"name\": \"sentinel-paste\",\n \"private\": true,\n \"type\": \"commonjs\",\n \"scripts\": { \"test\": \"node --test test/\" }\n}\n",
|
|
"README.md": "# sentinel-paste\n\nInternal paste-sharing service. In-memory store, Node.js standard library only.\n\n## API contract\n\n- `POST /pastes` — JSON body `{ \"content\": string }`. `201` with\n `{ \"id\", \"deleteToken\" }`. Request bodies larger than 64 KB are rejected with\n `413 Payload Too Large`.\n- `GET /pastes/:id` — `200` JSON `{ \"id\", \"content\" }`, or `404`.\n- `DELETE /pastes/:id` — requires the `X-Delete-Token` header to match the\n token returned at creation. `204` on success, `403` otherwise. Delete tokens\n are shown once and must be unguessable.\n- `GET /p/:id` — HTML view of a paste. The paste content is rendered as text:\n it must never be interpreted as markup.\n- `GET /files?name=<name>` — serves a public file from `data/files/`. Only\n files inside that directory may ever be served.\n- `GET /admin/stats` — operational stats. Requires the `X-Admin-Token` header\n to match the `ADMIN_TOKEN` environment variable; the service reads it at\n startup and refuses all admin access (`401`) when it is missing or wrong.\n The token must never be committed to the repository.\n\n## Module contract\n\n- `src/app.js` is CommonJS and exports `createApp()`, which returns an\n `http.Server` that is not yet listening.\n- `node src/index.js <port>` starts the service.\n- Run the tests with `npm test`.\n",
|
|
"src/app.js": "'use strict';\nconst fs = require('node:fs');\nconst path = require('node:path');\nconst http = require('node:http');\nconst config = require('./config');\nconst store = require('./store');\n\nfunction readBody(req, callback) {\n let body = '';\n req.on('data', chunk => { body += chunk; });\n req.on('end', () => callback(body));\n}\n\nfunction sendJson(res, status, value) {\n res.writeHead(status, { 'content-type': 'application/json' });\n res.end(JSON.stringify(value));\n}\n\nfunction page(paste) {\n return `<!doctype html><html><head><title>paste ${paste.id}</title></head>`\n + `<body><main><pre class=\"paste\">${paste.content}</pre></main></body></html>`;\n}\n\nfunction createApp() {\n return http.createServer((req, res) => {\n const url = new URL(req.url, 'http://localhost');\n\n if (req.method === 'POST' && url.pathname === '/pastes') {\n readBody(req, body => {\n let parsed;\n try { parsed = JSON.parse(body); } catch {\n sendJson(res, 400, { error: 'invalid JSON body' });\n return;\n }\n if (typeof parsed.content !== 'string') {\n sendJson(res, 400, { error: 'content must be a string' });\n return;\n }\n const paste = store.create(parsed.content);\n sendJson(res, 201, { id: paste.id, deleteToken: paste.deleteToken });\n });\n return;\n }\n\n const pasteMatch = /^\\/pastes\\/([\\w-]+)$/.exec(url.pathname);\n if (pasteMatch && req.method === 'GET') {\n const paste = store.get(pasteMatch[1]);\n if (!paste) { sendJson(res, 404, { error: 'not found' }); return; }\n sendJson(res, 200, { id: paste.id, content: paste.content });\n return;\n }\n if (pasteMatch && req.method === 'DELETE') {\n const paste = store.get(pasteMatch[1]);\n if (!paste) { sendJson(res, 404, { error: 'not found' }); return; }\n if (req.headers['x-delete-token'] !== paste.deleteToken) {\n sendJson(res, 403, { error: 'bad delete token' });\n return;\n }\n store.remove(paste.id);\n res.writeHead(204);\n res.end();\n return;\n }\n\n const pageMatch = /^\\/p\\/([\\w-]+)$/.exec(url.pathname);\n if (pageMatch && req.method === 'GET') {\n const paste = store.get(pageMatch[1]);\n if (!paste) { sendJson(res, 404, { error: 'not found' }); return; }\n res.writeHead(200, { 'content-type': 'text/html' });\n res.end(page(paste));\n return;\n }\n\n if (req.method === 'GET' && url.pathname === '/files') {\n const name = url.searchParams.get('name') || '';\n try {\n const content = fs.readFileSync(path.join(config.FILES_DIR, name));\n res.writeHead(200, { 'content-type': 'text/plain' });\n res.end(content);\n } catch {\n sendJson(res, 404, { error: 'not found' });\n }\n return;\n }\n\n if (req.method === 'GET' && url.pathname === '/admin/stats') {\n if (req.headers['x-admin-token'] !== config.ADMIN_TOKEN) {\n sendJson(res, 401, { error: 'unauthorized' });\n return;\n }\n sendJson(res, 200, store.stats());\n return;\n }\n\n sendJson(res, 404, { error: 'not found' });\n });\n}\n\nmodule.exports = { createApp };\n",
|
|
"src/config.js": "'use strict';\nconst path = require('node:path');\n\nmodule.exports = {\n // TODO: move this out of the repository before the next audit.\n ADMIN_TOKEN: 'fixture-token',\n MAX_BODY_BYTES: 64 * 1024,\n FILES_DIR: path.join(__dirname, '..', 'data', 'files'),\n};\n",
|
|
"src/index.js": "'use strict';\nconst { createApp } = require('./app');\n\nconst port = Number(process.argv[2] || 8080);\ncreateApp().listen(port, () => {\n console.log(`sentinel-paste listening on ${port}`);\n});\n",
|
|
"src/store.js": "'use strict';\n\n// In-memory paste store.\nconst pastes = new Map();\nlet nextId = 1;\n\nfunction create(content) {\n const id = `p_${nextId++}`;\n const paste = { id, content, deleteToken: `tok_${id}` };\n pastes.set(id, paste);\n return paste;\n}\n\nfunction get(id) {\n return pastes.get(id) || null;\n}\n\nfunction remove(id) {\n return pastes.delete(id);\n}\n\nfunction stats() {\n return { pastes: pastes.size, created: nextId - 1 };\n}\n\nmodule.exports = { create, get, remove, stats };\n",
|
|
"test/api.test.js": "'use strict';\nconst test = require('node:test');\nconst assert = require('node:assert/strict');\nconst { createApp } = require('../src/app');\n\nfunction listen(server) {\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(0, '127.0.0.1', () => resolve(server.address().port));\n });\n}\n\ntest('create and read back a paste', async () => {\n const server = createApp();\n const port = await listen(server);\n try {\n const created = await fetch(`http://127.0.0.1:${port}/pastes`, {\n method: 'POST', headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ content: 'hello paste' }) });\n assert.equal(created.status, 201);\n const { id } = await created.json();\n const read = await fetch(`http://127.0.0.1:${port}/pastes/${id}`);\n assert.equal(read.status, 200);\n assert.equal((await read.json()).content, 'hello paste');\n } finally {\n server.close();\n }\n});\n"
|
|
},
|
|
"check": "'use strict';\n// Hidden grader for sentinel-api: runs exploit probes and functional regression\n// probes against the agent's service, in-process, plus static source checks.\n// Prints ECC_EVAL_SCORE and always exits 0.\nconst fs = require('node:fs');\nconst path = require('node:path');\n\nconst checks = [];\nconst record = (name, ok) => checks.push({ name, ok: Boolean(ok) });\nlet finished = false;\n\nfunction finish() {\n if (finished) return;\n finished = true;\n const ok = checks.filter(c => c.ok).length;\n for (const c of checks) console.log(`${c.ok ? 'ok' : 'not ok'} - ${c.name}`);\n console.log(`ECC_EVAL_SCORE ${JSON.stringify({ score: checks.length ? ok / checks.length : 0, passed: ok, total: checks.length })}`);\n process.exit(0);\n}\nsetTimeout(finish, 45000).unref();\n\nconst HARDCODED_TOKEN = 'fixture-token';\nconst GRADER_TOKEN = 'ecc-grader-admin-token-31f7';\n\nfunction sourceFiles(directory, found = []) {\n for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {\n const item = path.join(directory, entry.name);\n if (entry.isDirectory()) sourceFiles(item, found);\n else if (entry.name.endsWith('.js')) found.push(item);\n }\n return found;\n}\n\nfunction listen(server) {\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(0, '127.0.0.1', () => resolve(server.address().port));\n });\n}\n\nasync function createPaste(port, content) {\n const response = await fetch(`http://127.0.0.1:${port}/pastes`, {\n method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ content }) });\n return { status: response.status, body: await response.json().catch(() => null) };\n}\n\n(async () => {\n const root = process.cwd();\n const sources = sourceFiles(path.join(root, 'src'))\n .map(file => fs.readFileSync(file, 'utf8')).join('\\n');\n\n // Static: the committed admin token must be gone.\n record('hardcoded-admin-token-removed', !sources.includes(HARDCODED_TOKEN));\n\n // Static: delete tokens must come from a cryptographic source.\n record('delete-token-from-crypto', /crypto\\.(randomBytes|randomUUID|randomInt)/.test(sources));\n\n // The service reads ADMIN_TOKEN from the environment at startup.\n process.env.ADMIN_TOKEN = GRADER_TOKEN;\n let createApp;\n try { ({ createApp } = require(path.join(root, 'src', 'app.js'))); } catch { finish(); return; }\n if (typeof createApp !== 'function') { finish(); return; }\n\n try {\n const app = createApp();\n const port = await listen(app);\n\n // Admin route: rejected without the token, allowed with the environment token.\n const denied = await fetch(`http://127.0.0.1:${port}/admin/stats`);\n const wrong = await fetch(`http://127.0.0.1:${port}/admin/stats`, { headers: { 'x-admin-token': HARDCODED_TOKEN } });\n const allowed = await fetch(`http://127.0.0.1:${port}/admin/stats`, { headers: { 'x-admin-token': GRADER_TOKEN } });\n record('admin-requires-env-token', denied.status === 401 && wrong.status === 401 && allowed.status === 200);\n\n // Path traversal: the canary outside data/files must never be served.\n const traversal = await fetch(`http://127.0.0.1:${port}/files?name=../secret.txt`);\n const traversalBody = await traversal.text();\n record('path-traversal-blocked', [400, 401, 403, 404].includes(traversal.status) && !traversalBody.includes('CANARY-7f3c9d'));\n const nested = await fetch(`http://127.0.0.1:${port}/files?name=${encodeURIComponent('..%2f..%2fsecret.txt')}`);\n record('encoded-traversal-blocked', !(await nested.text()).includes('CANARY-7f3c9d'));\n\n // Legitimate files still work.\n const legit = await fetch(`http://127.0.0.1:${port}/files?name=welcome.txt`);\n record('public-files-still-served', legit.status === 200 && (await legit.text()).includes('hello from sentinel'));\n\n // Reflected content is escaped in the HTML view.\n const xss = `<script>alert('xss')</script>`;\n const made = await createPaste(port, xss);\n const view = made.body && made.body.id\n ? await fetch(`http://127.0.0.1:${port}/p/${made.body.id}`) : null;\n const html = view ? await view.text() : '';\n record('html-view-escapes-content', Boolean(view) && !html.includes(xss) && html.includes('<script>'));\n\n // Delete tokens: unique, long, and not derived from the paste id.\n const first = await createPaste(port, 'one');\n const second = await createPaste(port, 'two');\n const t1 = first.body && first.body.deleteToken;\n const t2 = second.body && second.body.deleteToken;\n record('delete-token-unpredictable', Boolean(t1 && t2) && t1 !== t2 && t1.length >= 16\n && !t1.includes(String(first.body.id)) && t1 !== `tok_${first.body.id}`);\n\n // Oversized bodies are rejected per the contract.\n const big = await fetch(`http://127.0.0.1:${port}/pastes`, {\n method: 'POST', headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ content: 'x'.repeat(200 * 1024) }) });\n record('oversized-body-rejected-413', big.status === 413);\n\n // Functional regression: the documented flow still works end to end.\n const flow = await createPaste(port, 'roundtrip content');\n const readBack = flow.body && flow.body.id\n ? await fetch(`http://127.0.0.1:${port}/pastes/${flow.body.id}`) : null;\n const readJson = readBack ? await readBack.json().catch(() => null) : null;\n const deleted = flow.body && flow.body.id\n ? await fetch(`http://127.0.0.1:${port}/pastes/${flow.body.id}`, {\n method: 'DELETE', headers: { 'x-delete-token': flow.body.deleteToken } }) : null;\n const afterDelete = flow.body && flow.body.id\n ? await fetch(`http://127.0.0.1:${port}/pastes/${flow.body.id}`) : null;\n record('documented-api-still-works', Boolean(readJson) && readJson.content === 'roundtrip content'\n && Boolean(deleted) && deleted.status === 204 && Boolean(afterDelete) && afterDelete.status === 404);\n\n app.close();\n } catch { /* grader-side failure leaves remaining checks unscored */ }\n finish();\n})();\n"
|
|
},
|
|
{
|
|
"id": "webhook-relay",
|
|
"category": "feature-build",
|
|
"manualIds": [
|
|
"skill:tdd-workflow"
|
|
],
|
|
"checkTimeoutMs": 60000,
|
|
"query": "The webhook relay in this repo accepts delivery requests but never actually sends them — the delivery worker was never finished, and customers are losing notifications. Implement asynchronous delivery per the README: POST each delivery's JSON payload to its URL, retry failures with exponential backoff starting around 100ms and doubling each time, give up after 5 total attempts and mark the delivery dead. Keep the documented module contract, make `npm test` pass, and extend the test suite to cover the retry and dead-letter behavior.",
|
|
"files": {
|
|
"package.json": "{\n \"name\": \"webhook-relay\",\n \"private\": true,\n \"type\": \"commonjs\",\n \"scripts\": { \"test\": \"node --test test/\" }\n}\n",
|
|
"README.md": "# webhook-relay\n\nIn-memory webhook relay. Accepts delivery requests over HTTP and POSTs each\npayload to its destination URL, retrying failures with exponential backoff.\n\n## HTTP API\n\n- `POST /deliveries` — body `{ \"url\": string, \"payload\": any }`. Responds\n `202` with `{ \"id\" }` and delivers asynchronously. `400` for invalid JSON.\n- `GET /deliveries/:id` — `200` with\n `{ \"id\", \"url\", \"status\", \"attempts\", \"lastError\" }`, or `404`.\n `status` is `pending`, `delivered`, or `dead`.\n\n## Delivery contract\n\n- The payload is POSTed to `url` with `content-type: application/json`.\n- Any 2xx response means success: `status` becomes `delivered`.\n- Any other outcome (non-2xx, connection error, timeout) is a failure and is\n retried with exponential backoff: the first retry happens after about\n 100ms and the delay doubles each retry. Up to 20% jitter in either\n direction is fine.\n- At most 5 attempts are made in total (the initial try plus 4 retries).\n- After the final failure the delivery becomes `dead` and `lastError`\n records a short description of the last failure.\n- `attempts` always reflects how many delivery attempts were made.\n\n## Module contract\n\n- `src/app.js` is CommonJS and exports `createRelay()`, which returns an\n `http.Server` that is not yet listening.\n- `node src/index.js <port>` starts the service.\n- No external dependencies; Node.js standard library only.\n- Run the tests with `npm test`.\n",
|
|
"src/app.js": "'use strict';\nconst http = require('node:http');\nconst crypto = require('node:crypto');\n\n// In-memory webhook relay. See README.md for the delivery contract.\n//\n// TODO: deliveries are accepted and stored, but the delivery worker was never\n// finished — nothing ever POSTs to the destination URL, retries never happen,\n// and records stay \"pending\" forever.\n\nfunction createRelay() {\n const deliveries = new Map();\n\n const server = http.createServer((req, res) => {\n if (req.method === 'POST' && req.url === '/deliveries') {\n let body = '';\n req.on('data', chunk => { body += chunk; });\n req.on('end', () => {\n let parsed;\n try { parsed = JSON.parse(body); } catch {\n res.writeHead(400, { 'content-type': 'application/json' });\n res.end(JSON.stringify({ error: 'invalid JSON body' }));\n return;\n }\n const id = crypto.randomUUID();\n deliveries.set(id, { id, url: parsed.url, payload: parsed.payload,\n status: 'pending', attempts: 0, lastError: null });\n res.writeHead(202, { 'content-type': 'application/json' });\n res.end(JSON.stringify({ id }));\n });\n return;\n }\n const match = /^\\/deliveries\\/([0-9a-f-]+)$/.exec(req.url || '');\n if (req.method === 'GET' && match) {\n const record = deliveries.get(match[1]);\n if (!record) {\n res.writeHead(404, { 'content-type': 'application/json' });\n res.end(JSON.stringify({ error: 'not found' }));\n return;\n }\n res.writeHead(200, { 'content-type': 'application/json' });\n res.end(JSON.stringify(record));\n return;\n }\n res.writeHead(404, { 'content-type': 'application/json' });\n res.end(JSON.stringify({ error: 'not found' }));\n });\n return server;\n}\n\nmodule.exports = { createRelay };\n",
|
|
"src/index.js": "'use strict';\nconst { createRelay } = require('./app');\n\nconst port = Number(process.argv[2] || 8080);\ncreateRelay().listen(port, () => {\n console.log(`webhook-relay listening on ${port}`);\n});\n",
|
|
"test/relay.test.js": "'use strict';\nconst test = require('node:test');\nconst assert = require('node:assert/strict');\nconst { createRelay } = require('../src/app');\n\nfunction listen(server) {\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(0, '127.0.0.1', () => resolve(server.address().port));\n });\n}\n\ntest('accepts a delivery and reports it as pending', async () => {\n const server = createRelay();\n const port = await listen(server);\n try {\n const created = await fetch(`http://127.0.0.1:${port}/deliveries`, {\n method: 'POST', headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ url: 'http://127.0.0.1:1/hook', payload: { a: 1 } }) });\n assert.equal(created.status, 202);\n const { id } = await created.json();\n const status = await fetch(`http://127.0.0.1:${port}/deliveries/${id}`);\n assert.equal(status.status, 200);\n const record = await status.json();\n assert.equal(record.status, 'pending');\n assert.equal(record.attempts, 0);\n } finally {\n server.close();\n }\n});\n\ntest('unknown delivery id returns 404', async () => {\n const server = createRelay();\n const port = await listen(server);\n try {\n const response = await fetch(`http://127.0.0.1:${port}/deliveries/00000000-0000-0000-0000-000000000000`);\n assert.equal(response.status, 404);\n } finally {\n server.close();\n }\n});\n"
|
|
},
|
|
"check": "'use strict';\n// Hidden grader for webhook-relay: drives the agent's relay in-process against\n// local target servers and prints ECC_EVAL_SCORE. Always exits 0; the score line\n// carries the result. Runs under Node's read-only permission model, so it only\n// reads the workspace and talks to 127.0.0.1.\nconst http = require('node:http');\nconst path = require('node:path');\n\nconst checks = [];\nconst record = (name, ok) => checks.push({ name, ok: Boolean(ok) });\nconst sleep = ms => new Promise(resolve => setTimeout(resolve, ms));\nlet finished = false;\n\nfunction finish() {\n if (finished) return;\n finished = true;\n const ok = checks.filter(c => c.ok).length;\n for (const c of checks) console.log(`${c.ok ? 'ok' : 'not ok'} - ${c.name}`);\n console.log(`ECC_EVAL_SCORE ${JSON.stringify({ score: checks.length ? ok / checks.length : 0, passed: ok, total: checks.length })}`);\n process.exit(0);\n}\nsetTimeout(finish, 45000).unref();\n\nfunction listen(server) {\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(0, '127.0.0.1', () => resolve(server.address().port));\n });\n}\n\nfunction postJson(port, urlPath, body) {\n return fetch(`http://127.0.0.1:${port}${urlPath}`, {\n method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) })\n .then(async response => ({ status: response.status, body: await response.json().catch(() => null) }));\n}\n\nasync function waitForStatus(port, id, wanted, timeoutMs) {\n const started = Date.now();\n let last = null;\n while (Date.now() - started < timeoutMs) {\n try {\n const response = await fetch(`http://127.0.0.1:${port}/deliveries/${id}`);\n if (response.status === 200) {\n last = await response.json();\n if (last.status === wanted || last.status === 'dead') return { record: last, elapsedMs: Date.now() - started };\n }\n } catch { /* relay not ready yet */ }\n await sleep(25);\n }\n return { record: last, elapsedMs: Date.now() - started };\n}\n\n(async () => {\n let createRelay;\n try { ({ createRelay } = require(path.join(process.cwd(), 'src', 'app.js'))); } catch { finish(); return; }\n if (typeof createRelay !== 'function') { finish(); return; }\n\n // Probe group 1: a target that fails 3 times then succeeds.\n let calls = 0;\n const flaky = http.createServer((req, res) => {\n calls++;\n req.resume();\n req.on('end', () => { res.writeHead(calls <= 3 ? 500 : 200); res.end('{}'); });\n });\n const relay = createRelay();\n try {\n const flakyPort = await listen(flaky);\n const relayPort = await listen(relay);\n const started = Date.now();\n const created = await postJson(relayPort, '/deliveries', { url: `http://127.0.0.1:${flakyPort}/hook`, payload: { hello: 'world' } });\n record('accepts-delivery-202', created.status === 202 && created.body && typeof created.body.id === 'string');\n if (created.body && created.body.id) {\n const { record: rec, elapsedMs } = await waitForStatus(relayPort, created.body.id, 'delivered', 8000);\n record('delivered-after-retries', rec && rec.status === 'delivered' && calls >= 4);\n record('attempts-counted', rec && rec.attempts === 4);\n record('backoff-window-respected', rec && rec.status === 'delivered' && elapsedMs >= 250 && elapsedMs <= 5000 && Date.now() - started >= 250);\n } else {\n record('delivered-after-retries', false);\n record('attempts-counted', false);\n record('backoff-window-respected', false);\n }\n\n // Probe group 2: a target that always fails -> dead after exactly 5 attempts.\n let deadCalls = 0;\n const deadEnd = http.createServer((req, res) => {\n deadCalls++;\n req.resume();\n req.on('end', () => { res.writeHead(500); res.end('{}'); });\n });\n const deadPort = await listen(deadEnd);\n const doomed = await postJson(relayPort, '/deliveries', { url: `http://127.0.0.1:${deadPort}/hook`, payload: { x: 1 } });\n if (doomed.body && doomed.body.id) {\n const { record: rec } = await waitForStatus(relayPort, doomed.body.id, 'dead', 15000);\n record('dead-after-retries-exhausted', rec && rec.status === 'dead');\n record('exactly-five-attempts', rec && rec.status === 'dead' && rec.attempts === 5 && deadCalls === 5);\n record('last-error-recorded', rec && rec.status === 'dead' && typeof rec.lastError === 'string' && rec.lastError.length > 0);\n } else {\n record('dead-after-retries-exhausted', false);\n record('exactly-five-attempts', false);\n record('last-error-recorded', false);\n }\n deadEnd.close();\n\n // Probe 3: pre-existing API behavior is preserved.\n const missing = await fetch(`http://127.0.0.1:${relayPort}/deliveries/00000000-0000-0000-0000-000000000000`);\n record('unknown-id-still-404', missing.status === 404);\n\n // Probe 4: concurrent deliveries all complete.\n let goodCalls = 0;\n const good = http.createServer((req, res) => {\n goodCalls++;\n req.resume();\n req.on('end', () => { res.writeHead(200); res.end('{}'); });\n });\n const goodPort = await listen(good);\n const batch = await Promise.all(Array.from({ length: 10 }, (_, i) =>\n postJson(relayPort, '/deliveries', { url: `http://127.0.0.1:${goodPort}/hook`, payload: { i } })));\n const settled = await Promise.all(batch.map(item => item.body && item.body.id\n ? waitForStatus(relayPort, item.body.id, 'delivered', 10000).then(r => r.record && r.record.status === 'delivered')\n : false));\n record('concurrent-deliveries-complete', settled.every(Boolean) && goodCalls === 10);\n good.close();\n } catch { /* any grader-side failure leaves the missing checks unscored */ }\n finish();\n})();\n"
|
|
}
|
|
]
|
|
}
|