Reformat src/*.mjs with Prettier; add format/format:check scripts

Every route handler was written as one dense semicolon-chained line
(server.mjs's main handler was over 2000 characters wide) — real
width, not a VSCode wrapping artifact. Ran Prettier to break statement
structure into properly indented multi-line code; left template
literal string contents untouched since Prettier doesn't reformat
inside those, so the large HTML-generating return strings are still
single long lines by nature, just no longer compounded by unformatted
surrounding JS. Verified: tests pass, node --check passes, and the
app renders identically before/after (screenshotted the diff).

No devDependency added — npx prettier@3.9.5 pinned in the new scripts
keeps the zero-runtime-dependency pledge intact; formatting is a dev-
time concern only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Fredrik Johansson
2026-07-21 05:17:30 +02:00
co-authored by Claude Sonnet 5
parent cdcc3c7937
commit 4b7b5a42d3
7 changed files with 782 additions and 76 deletions
+4
View File
@@ -0,0 +1,4 @@
{
"printWidth": 100,
"singleQuote": true
}
+1 -1
View File
@@ -1 +1 @@
{"name":"trace","version":"0.1.0","private":true,"type":"module","scripts":{"dev":"node --watch --env-file-if-exists=.env src/server.mjs","start":"node --env-file-if-exists=.env src/server.mjs","test":"node --test","build":"node --check src/server.mjs"},"engines":{"node":">=24"}} {"name":"trace","version":"0.1.0","private":true,"type":"module","scripts":{"dev":"node --watch --env-file-if-exists=.env src/server.mjs","start":"node --env-file-if-exists=.env src/server.mjs","test":"node --test","build":"node --check src/server.mjs","format":"npx --yes prettier@3.9.5 --write \"src/*.mjs\" \"test/*.mjs\"","format:check":"npx --yes prettier@3.9.5 --check \"src/*.mjs\" \"test/*.mjs\""},"engines":{"node":">=24"}}
+70 -8
View File
@@ -1,9 +1,71 @@
const FIX_WORDS=/\b(fix(?:e[ds])?|bug|broken|race|crash|regression|rollback|restore[ds]?|duplicate[ds]?|auth|fail(?:ed|ure|ing)?)\b/i; const FIX_WORDS =
const validRepo=value=>/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/i.test(value); /\b(fix(?:e[ds])?|bug|broken|race|crash|regression|rollback|restore[ds]?|duplicate[ds]?|auth|fail(?:ed|ure|ing)?)\b/i;
export async function scanGiteaCandidates({baseUrl,repositories,token='',limit=100,fetchImpl=fetch}){ const validRepo = (value) => /^[a-z0-9_.-]+\/[a-z0-9_.-]+$/i.test(value);
const candidates=[],errors=[],headers={accept:'application/json'};if(token)headers.authorization=`token ${token}`; export async function scanGiteaCandidates({
for(const repo of repositories){if(!validRepo(repo)){errors.push({repo,error:'invalid owner/repository identifier'});continue}const [owner,name]=repo.split('/');let seen=0; baseUrl,
try{for(let page=1;seen<limit;page++){const count=Math.min(50,limit-seen),url=`${baseUrl.replace(/\/$/,'')}/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/commits?limit=${count}&page=${page}`;const response=await fetchImpl(url,{headers,signal:AbortSignal.timeout(5000)});if(!response.ok)throw new Error(`Gitea returned ${response.status}`);const commits=await response.json();if(!Array.isArray(commits))throw new Error('unexpected Gitea response');for(const commit of commits){seen++;const subject=String(commit.commit?.message||'').split('\n')[0].trim(),match=subject.match(FIX_WORDS);if(!match)continue;let paths='';try{const detail=await fetchImpl(`${baseUrl.replace(/\/$/,'')}/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/git/commits/${encodeURIComponent(commit.sha)}`,{headers,signal:AbortSignal.timeout(5000)});if(detail.ok){const data=await detail.json();paths=(data.files||[]).map(file=>file.filename).filter(Boolean).join('\n')}}catch{}candidates.push({repo,sha:commit.sha,committedAt:commit.commit?.committer?.date||commit.commit?.author?.date||commit.created||new Date().toISOString(),subject,paths,reason:`Gitea subject keyword: ${match[0].toLowerCase()}`})}if(commits.length<count)break}} repositories,
catch(error){errors.push({repo,error:error.message})} token = '',
}return{candidates,errors}; limit = 100,
fetchImpl = fetch,
}) {
const candidates = [],
errors = [],
headers = { accept: 'application/json' };
if (token) headers.authorization = `token ${token}`;
for (const repo of repositories) {
if (!validRepo(repo)) {
errors.push({ repo, error: 'invalid owner/repository identifier' });
continue;
}
const [owner, name] = repo.split('/');
let seen = 0;
try {
for (let page = 1; seen < limit; page++) {
const count = Math.min(50, limit - seen),
url = `${baseUrl.replace(/\/$/, '')}/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/commits?limit=${count}&page=${page}`;
const response = await fetchImpl(url, { headers, signal: AbortSignal.timeout(5000) });
if (!response.ok) throw new Error(`Gitea returned ${response.status}`);
const commits = await response.json();
if (!Array.isArray(commits)) throw new Error('unexpected Gitea response');
for (const commit of commits) {
seen++;
const subject = String(commit.commit?.message || '')
.split('\n')[0]
.trim(),
match = subject.match(FIX_WORDS);
if (!match) continue;
let paths = '';
try {
const detail = await fetchImpl(
`${baseUrl.replace(/\/$/, '')}/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/git/commits/${encodeURIComponent(commit.sha)}`,
{ headers, signal: AbortSignal.timeout(5000) },
);
if (detail.ok) {
const data = await detail.json();
paths = (data.files || [])
.map((file) => file.filename)
.filter(Boolean)
.join('\n');
}
} catch {}
candidates.push({
repo,
sha: commit.sha,
committedAt:
commit.commit?.committer?.date ||
commit.commit?.author?.date ||
commit.created ||
new Date().toISOString(),
subject,
paths,
reason: `Gitea subject keyword: ${match[0].toLowerCase()}`,
});
}
if (commits.length < count) break;
}
} catch (error) {
errors.push({ repo, error: error.message });
}
}
return { candidates, errors };
} }
+156 -16
View File
@@ -1,19 +1,159 @@
import crypto from 'node:crypto'; import crypto from 'node:crypto';
const RESULTS=new Set(['success','failed','cancelled','unknown']), TYPES=new Set(['build','deployment','release','rollback']); const RESULTS = new Set(['success', 'failed', 'cancelled', 'unknown']),
const send=(res,status,value)=>res.writeHead(status,{'content-type':'application/json; charset=utf-8','cache-control':status===200?'public, max-age=30':'no-store'}).end(JSON.stringify(value)); TYPES = new Set(['build', 'deployment', 'release', 'rollback']);
export function validateReceipt(v){const e=[];if(!v||typeof v!=='object'||Array.isArray(v))return['body must be an object'];if(v.schemaVersion!==1)e.push('schemaVersion must be 1');if(!TYPES.has(v.eventType))e.push('unsupported eventType');for(const f of ['eventId','project','occurredAt'])if(typeof v[f]!=='string'||!v[f].trim())e.push(`${f} is required`);if(v.occurredAt&&Number.isNaN(Date.parse(v.occurredAt)))e.push('occurredAt must be ISO-8601');if(!RESULTS.has(v.result))e.push('unsupported result');if(!v.source||typeof v.source.commit!=='string'||!/^[a-f0-9]{7,64}$/i.test(v.source.commit))e.push('source.commit must be a commit SHA');if(v.artifacts&&!Array.isArray(v.artifacts))e.push('artifacts must be an array');return e;} const send = (res, status, value) =>
export function createReceiptStore(db,config=''){ res
db.exec(`CREATE TABLE IF NOT EXISTS deployment_events(id INTEGER PRIMARY KEY AUTOINCREMENT,event_id TEXT NOT NULL UNIQUE,schema_version INTEGER NOT NULL,event_type TEXT NOT NULL,project TEXT NOT NULL,environment TEXT NOT NULL DEFAULT '',source_commit TEXT NOT NULL,source_branch TEXT NOT NULL DEFAULT '',result TEXT NOT NULL,health_result TEXT NOT NULL DEFAULT '',occurred_at TEXT NOT NULL,received_at TEXT NOT NULL,previous_event_id TEXT NOT NULL DEFAULT '',payload TEXT NOT NULL);CREATE INDEX IF NOT EXISTS deployment_events_project_time ON deployment_events(project,occurred_at DESC);`); .writeHead(status, {
const tokens=new Map(config.split(',').filter(Boolean).map(p=>{const i=p.indexOf(':');return[p.slice(i+1),p.slice(0,i)]})); 'content-type': 'application/json; charset=utf-8',
const authenticated=(req,project)=>{const token=req.headers.authorization?.replace(/^Bearer\s+/i,'');if(!token)return false;for(const[candidate,allowed]of tokens)if(candidate.length===token.length&&crypto.timingSafeEqual(Buffer.from(candidate),Buffer.from(token))&&allowed===project)return true;return false}; 'cache-control': status === 200 ? 'public, max-age=30' : 'no-store',
const publicEvent=row=>{if(!row)return null;const p=JSON.parse(row.payload);return{schemaVersion:row.schema_version,eventId:row.event_id,eventType:row.event_type,project:row.project,environment:row.environment||undefined,commit:row.source_commit,branch:row.source_branch||undefined,result:row.result,health:row.health_result||undefined,occurredAt:row.occurred_at,artifacts:(p.artifacts||[]).map(x=>({component:x.component,digest:x.digest?.slice(0,19)}))}}; })
async function handle(req,res,path){ .end(JSON.stringify(value));
if(path==='/api/health'&&req.method==='GET')return send(res,200,{ok:true}); export function validateReceipt(v) {
if(path==='/api/v1/events'&&req.method==='POST'){let raw='';for await(const c of req){raw+=c;if(raw.length>131072)return send(res,413,{error:'receipt too large'})}let v;try{v=JSON.parse(raw)}catch{return send(res,400,{error:'invalid JSON'})}const errors=validateReceipt(v);if(errors.length)return send(res,422,{error:'invalid receipt',details:errors});if(!authenticated(req,v.project))return send(res,401,{error:'invalid receipt credential'});if(db.prepare('SELECT event_id FROM deployment_events WHERE event_id=?').get(v.eventId))return send(res,200,{ok:true,eventId:v.eventId,duplicate:true});db.prepare(`INSERT INTO deployment_events(event_id,schema_version,event_type,project,environment,source_commit,source_branch,result,health_result,occurred_at,received_at,previous_event_id,payload) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(v.eventId,1,v.eventType,v.project,v.environment||'',v.source.commit,v.source.branch||'',v.result,v.verification?.health||'',v.occurredAt,new Date().toISOString(),v.previousEventId||'',raw);return send(res,201,{ok:true,eventId:v.eventId});} const e = [];
const m=path.match(/^\/api\/v1\/public\/projects\/([a-z0-9-]+)\/latest$/);if(m&&req.method==='GET'){const row=db.prepare("SELECT * FROM deployment_events WHERE project=? AND event_type='deployment' ORDER BY occurred_at DESC LIMIT 1").get(m[1]);return send(res,200,{project:m[1],deployment:publicEvent(row)})} if (!v || typeof v !== 'object' || Array.isArray(v)) return ['body must be an object'];
if(path==='/api/v1/public/projects'&&req.method==='GET'){const rows=db.prepare(`SELECT e.* FROM deployment_events e JOIN (SELECT project,MAX(occurred_at) occurred_at FROM deployment_events WHERE event_type='deployment' GROUP BY project) l ON l.project=e.project AND l.occurred_at=e.occurred_at ORDER BY e.project`).all();return send(res,200,{projects:rows.map(publicEvent)})}return false; if (v.schemaVersion !== 1) e.push('schemaVersion must be 1');
}return{handle,publicEvent}; if (!TYPES.has(v.eventType)) e.push('unsupported eventType');
for (const f of ['eventId', 'project', 'occurredAt'])
if (typeof v[f] !== 'string' || !v[f].trim()) e.push(`${f} is required`);
if (v.occurredAt && Number.isNaN(Date.parse(v.occurredAt))) e.push('occurredAt must be ISO-8601');
if (!RESULTS.has(v.result)) e.push('unsupported result');
if (
!v.source ||
typeof v.source.commit !== 'string' ||
!/^[a-f0-9]{7,64}$/i.test(v.source.commit)
)
e.push('source.commit must be a commit SHA');
if (v.artifacts && !Array.isArray(v.artifacts)) e.push('artifacts must be an array');
return e;
}
export function createReceiptStore(db, config = '') {
db.exec(
`CREATE TABLE IF NOT EXISTS deployment_events(id INTEGER PRIMARY KEY AUTOINCREMENT,event_id TEXT NOT NULL UNIQUE,schema_version INTEGER NOT NULL,event_type TEXT NOT NULL,project TEXT NOT NULL,environment TEXT NOT NULL DEFAULT '',source_commit TEXT NOT NULL,source_branch TEXT NOT NULL DEFAULT '',result TEXT NOT NULL,health_result TEXT NOT NULL DEFAULT '',occurred_at TEXT NOT NULL,received_at TEXT NOT NULL,previous_event_id TEXT NOT NULL DEFAULT '',payload TEXT NOT NULL);CREATE INDEX IF NOT EXISTS deployment_events_project_time ON deployment_events(project,occurred_at DESC);`,
);
const tokens = new Map(
config
.split(',')
.filter(Boolean)
.map((p) => {
const i = p.indexOf(':');
return [p.slice(i + 1), p.slice(0, i)];
}),
);
const authenticated = (req, project) => {
const token = req.headers.authorization?.replace(/^Bearer\s+/i, '');
if (!token) return false;
for (const [candidate, allowed] of tokens)
if (
candidate.length === token.length &&
crypto.timingSafeEqual(Buffer.from(candidate), Buffer.from(token)) &&
allowed === project
)
return true;
return false;
};
const publicEvent = (row) => {
if (!row) return null;
const p = JSON.parse(row.payload);
return {
schemaVersion: row.schema_version,
eventId: row.event_id,
eventType: row.event_type,
project: row.project,
environment: row.environment || undefined,
commit: row.source_commit,
branch: row.source_branch || undefined,
result: row.result,
health: row.health_result || undefined,
occurredAt: row.occurred_at,
artifacts: (p.artifacts || []).map((x) => ({
component: x.component,
digest: x.digest?.slice(0, 19),
})),
};
};
async function handle(req, res, path) {
if (path === '/api/health' && req.method === 'GET') return send(res, 200, { ok: true });
if (path === '/api/v1/events' && req.method === 'POST') {
let raw = '';
for await (const c of req) {
raw += c;
if (raw.length > 131072) return send(res, 413, { error: 'receipt too large' });
}
let v;
try {
v = JSON.parse(raw);
} catch {
return send(res, 400, { error: 'invalid JSON' });
}
const errors = validateReceipt(v);
if (errors.length) return send(res, 422, { error: 'invalid receipt', details: errors });
if (!authenticated(req, v.project))
return send(res, 401, { error: 'invalid receipt credential' });
if (db.prepare('SELECT event_id FROM deployment_events WHERE event_id=?').get(v.eventId))
return send(res, 200, { ok: true, eventId: v.eventId, duplicate: true });
db.prepare(
`INSERT INTO deployment_events(event_id,schema_version,event_type,project,environment,source_commit,source_branch,result,health_result,occurred_at,received_at,previous_event_id,payload) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)`,
).run(
v.eventId,
1,
v.eventType,
v.project,
v.environment || '',
v.source.commit,
v.source.branch || '',
v.result,
v.verification?.health || '',
v.occurredAt,
new Date().toISOString(),
v.previousEventId || '',
raw,
);
return send(res, 201, { ok: true, eventId: v.eventId });
}
const m = path.match(/^\/api\/v1\/public\/projects\/([a-z0-9-]+)\/latest$/);
if (m && req.method === 'GET') {
const row = db
.prepare(
"SELECT * FROM deployment_events WHERE project=? AND event_type='deployment' ORDER BY occurred_at DESC LIMIT 1",
)
.get(m[1]);
return send(res, 200, { project: m[1], deployment: publicEvent(row) });
}
if (path === '/api/v1/public/projects' && req.method === 'GET') {
const rows = db
.prepare(
`SELECT e.* FROM deployment_events e JOIN (SELECT project,MAX(occurred_at) occurred_at FROM deployment_events WHERE event_type='deployment' GROUP BY project) l ON l.project=e.project AND l.occurred_at=e.occurred_at ORDER BY e.project`,
)
.all();
return send(res, 200, { projects: rows.map(publicEvent) });
}
return false;
}
return { handle, publicEvent };
} }
const escapeHtml=value=>String(value??'').replaceAll('&','&amp;').replaceAll('<','&lt;').replaceAll('>','&gt;').replaceAll('"','&quot;'); const escapeHtml = (value) =>
export function renderEventsPage(db){const rows=db.prepare('SELECT * FROM deployment_events ORDER BY occurred_at DESC LIMIT 100').all();const counts=db.prepare('SELECT event_type,result,count(*) count FROM deployment_events GROUP BY event_type,result ORDER BY event_type,result').all();return`<!doctype html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width"><title>operational events · trace</title><style>:root{--b:#0d1117;--p:#161b22;--l:#30363d;--t:#c9d1d9;--m:#8b949e;--a:#58a6ff;--ok:#3fb950;--bad:#f85149}*{box-sizing:border-box}body{margin:0;background:var(--b);color:var(--t);font:14px/1.55 ui-monospace,monospace}header,main{max-width:1050px;margin:auto;padding:20px}header{display:flex;justify-content:space-between;border-bottom:1px solid var(--l)}a{color:var(--a);text-decoration:none}nav{display:flex;gap:18px}.summary{display:flex;gap:8px;flex-wrap:wrap}.badge{border:1px solid var(--l);padding:3px 8px;border-radius:99px;color:var(--m)}.event{background:var(--p);border:1px solid var(--l);border-left:3px solid var(--a);padding:16px;border-radius:8px;margin:10px 0}.event.failed{border-left-color:var(--bad)}.event.success{border-left-color:var(--ok)}.top{display:flex;justify-content:space-between;gap:12px;align-items:start}.meta{color:var(--m);font-size:12px}.result.success{color:var(--ok)}.result.failed{color:var(--bad)}code,pre{background:var(--b);border:1px solid var(--l);border-radius:5px}code{padding:2px 5px}pre{padding:12px;overflow:auto;max-height:360px;white-space:pre-wrap}details{margin-top:10px}h1{margin-bottom:5px}@media(max-width:600px){.top{display:block}}</style></head><body><header><a href=/><b>trace</b></a><nav><a href=/incidents/new>new incident</a><a href=/candidates>git inbox</a><a href=/events>operational events</a></nav></header><main><h1>Operational events</h1><p class=meta>Builds say what CI produced. Deployments say what a host actually ran.</p><div class=summary>${counts.map(x=>`<span class=badge>${escapeHtml(x.event_type)} · ${escapeHtml(x.result)}: ${x.count}</span>`).join('')}</div>${rows.map(row=>{const payload=JSON.parse(row.payload),artifacts=payload.artifacts||[];return`<article class="event ${escapeHtml(row.result)}"><div class=top><div><b>${escapeHtml(row.project)} · ${escapeHtml(row.event_type)}</b><p class=meta><code>${escapeHtml(row.source_commit.slice(0,12))}</code> ${escapeHtml(row.source_branch)} ${row.environment?`· ${escapeHtml(row.environment)}`:''}</p></div><span class="result ${escapeHtml(row.result)}">${escapeHtml(row.result)}</span></div><p class=meta>${escapeHtml(row.occurred_at)} · received ${escapeHtml(row.received_at)}</p>${artifacts.map(x=>`<p><b>${escapeHtml(x.component)}</b> <code>${escapeHtml(x.digest||'digest unavailable')}</code></p>`).join('')}<details><summary>raw private receipt</summary><pre>${escapeHtml(JSON.stringify(payload,null,2))}</pre></details></article>`}).join('')||'<p>No operational events received yet.</p>'}</main></body></html>`} String(value ?? '')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;');
export function renderEventsPage(db) {
const rows = db
.prepare('SELECT * FROM deployment_events ORDER BY occurred_at DESC LIMIT 100')
.all();
const counts = db
.prepare(
'SELECT event_type,result,count(*) count FROM deployment_events GROUP BY event_type,result ORDER BY event_type,result',
)
.all();
return `<!doctype html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width"><title>operational events · trace</title><style>:root{--b:#0d1117;--p:#161b22;--l:#30363d;--t:#c9d1d9;--m:#8b949e;--a:#58a6ff;--ok:#3fb950;--bad:#f85149}*{box-sizing:border-box}body{margin:0;background:var(--b);color:var(--t);font:14px/1.55 ui-monospace,monospace}header,main{max-width:1050px;margin:auto;padding:20px}header{display:flex;justify-content:space-between;border-bottom:1px solid var(--l)}a{color:var(--a);text-decoration:none}nav{display:flex;gap:18px}.summary{display:flex;gap:8px;flex-wrap:wrap}.badge{border:1px solid var(--l);padding:3px 8px;border-radius:99px;color:var(--m)}.event{background:var(--p);border:1px solid var(--l);border-left:3px solid var(--a);padding:16px;border-radius:8px;margin:10px 0}.event.failed{border-left-color:var(--bad)}.event.success{border-left-color:var(--ok)}.top{display:flex;justify-content:space-between;gap:12px;align-items:start}.meta{color:var(--m);font-size:12px}.result.success{color:var(--ok)}.result.failed{color:var(--bad)}code,pre{background:var(--b);border:1px solid var(--l);border-radius:5px}code{padding:2px 5px}pre{padding:12px;overflow:auto;max-height:360px;white-space:pre-wrap}details{margin-top:10px}h1{margin-bottom:5px}@media(max-width:600px){.top{display:block}}</style></head><body><header><a href=/><b>trace</b></a><nav><a href=/incidents/new>new incident</a><a href=/candidates>git inbox</a><a href=/events>operational events</a></nav></header><main><h1>Operational events</h1><p class=meta>Builds say what CI produced. Deployments say what a host actually ran.</p><div class=summary>${counts.map((x) => `<span class=badge>${escapeHtml(x.event_type)} · ${escapeHtml(x.result)}: ${x.count}</span>`).join('')}</div>${
rows
.map((row) => {
const payload = JSON.parse(row.payload),
artifacts = payload.artifacts || [];
return `<article class="event ${escapeHtml(row.result)}"><div class=top><div><b>${escapeHtml(row.project)} · ${escapeHtml(row.event_type)}</b><p class=meta><code>${escapeHtml(row.source_commit.slice(0, 12))}</code> ${escapeHtml(row.source_branch)} ${row.environment ? `· ${escapeHtml(row.environment)}` : ''}</p></div><span class="result ${escapeHtml(row.result)}">${escapeHtml(row.result)}</span></div><p class=meta>${escapeHtml(row.occurred_at)} · received ${escapeHtml(row.received_at)}</p>${artifacts.map((x) => `<p><b>${escapeHtml(x.component)}</b> <code>${escapeHtml(x.digest || 'digest unavailable')}</code></p>`).join('')}<details><summary>raw private receipt</summary><pre>${escapeHtml(JSON.stringify(payload, null, 2))}</pre></details></article>`;
})
.join('') || '<p>No operational events received yet.</p>'
}</main></body></html>`;
}
+451 -44
View File
@@ -8,8 +8,11 @@ import crypto from 'node:crypto';
import { createReceiptStore, renderEventsPage } from './receipts.mjs'; import { createReceiptStore, renderEventsPage } from './receipts.mjs';
import { scanGiteaCandidates } from './gitea.mjs'; import { scanGiteaCandidates } from './gitea.mjs';
const exec = promisify(execFile), port = Number(process.env.PORT || 3082), password = process.env.TRACE_PASSWORD || ''; const exec = promisify(execFile),
mkdirSync('data', { recursive: true }); mkdirSync('data/exports', { recursive: true }); port = Number(process.env.PORT || 3082),
password = process.env.TRACE_PASSWORD || '';
mkdirSync('data', { recursive: true });
mkdirSync('data/exports', { recursive: true });
const db = new DatabaseSync('data/trace.db'); const db = new DatabaseSync('data/trace.db');
db.exec(`PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; db.exec(`PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;
CREATE TABLE IF NOT EXISTS incidents(id TEXT PRIMARY KEY,title TEXT NOT NULL,project TEXT,status TEXT NOT NULL DEFAULT 'investigating',severity TEXT NOT NULL DEFAULT 'annoyance',detected_at TEXT NOT NULL,symptom TEXT NOT NULL,impact TEXT DEFAULT '',root_cause TEXT DEFAULT '',confidence TEXT DEFAULT 'unknown',fix TEXT DEFAULT '',verification TEXT DEFAULT '',prevention TEXT DEFAULT '',remaining_risk TEXT DEFAULT '',lesson TEXT DEFAULT '',public_summary TEXT DEFAULT '',created_at TEXT NOT NULL,updated_at TEXT NOT NULL); CREATE TABLE IF NOT EXISTS incidents(id TEXT PRIMARY KEY,title TEXT NOT NULL,project TEXT,status TEXT NOT NULL DEFAULT 'investigating',severity TEXT NOT NULL DEFAULT 'annoyance',detected_at TEXT NOT NULL,symptom TEXT NOT NULL,impact TEXT DEFAULT '',root_cause TEXT DEFAULT '',confidence TEXT DEFAULT 'unknown',fix TEXT DEFAULT '',verification TEXT DEFAULT '',prevention TEXT DEFAULT '',remaining_risk TEXT DEFAULT '',lesson TEXT DEFAULT '',public_summary TEXT DEFAULT '',created_at TEXT NOT NULL,updated_at TEXT NOT NULL);
@@ -17,53 +20,457 @@ CREATE TABLE IF NOT EXISTS timeline(id INTEGER PRIMARY KEY AUTOINCREMENT,inciden
CREATE TABLE IF NOT EXISTS hypotheses(id INTEGER PRIMARY KEY AUTOINCREMENT,incident_id TEXT NOT NULL REFERENCES incidents(id) ON DELETE CASCADE,statement TEXT NOT NULL,status TEXT NOT NULL DEFAULT 'untested',evidence TEXT DEFAULT ''); CREATE TABLE IF NOT EXISTS hypotheses(id INTEGER PRIMARY KEY AUTOINCREMENT,incident_id TEXT NOT NULL REFERENCES incidents(id) ON DELETE CASCADE,statement TEXT NOT NULL,status TEXT NOT NULL DEFAULT 'untested',evidence TEXT DEFAULT '');
CREATE TABLE IF NOT EXISTS candidates(id INTEGER PRIMARY KEY AUTOINCREMENT,repo TEXT NOT NULL,sha TEXT NOT NULL,committed_at TEXT NOT NULL,subject TEXT NOT NULL,paths TEXT DEFAULT '',reason TEXT NOT NULL,state TEXT NOT NULL DEFAULT 'new',incident_id TEXT,UNIQUE(repo,sha)); CREATE TABLE IF NOT EXISTS candidates(id INTEGER PRIMARY KEY AUTOINCREMENT,repo TEXT NOT NULL,sha TEXT NOT NULL,committed_at TEXT NOT NULL,subject TEXT NOT NULL,paths TEXT DEFAULT '',reason TEXT NOT NULL,state TEXT NOT NULL DEFAULT 'new',incident_id TEXT,UNIQUE(repo,sha));
CREATE TABLE IF NOT EXISTS receipts(id INTEGER PRIMARY KEY AUTOINCREMENT,project TEXT NOT NULL,commit_sha TEXT NOT NULL,deployed_at TEXT NOT NULL,environment TEXT DEFAULT '',health TEXT DEFAULT '',payload TEXT NOT NULL,UNIQUE(project,commit_sha,deployed_at));`); CREATE TABLE IF NOT EXISTS receipts(id INTEGER PRIMARY KEY AUTOINCREMENT,project TEXT NOT NULL,commit_sha TEXT NOT NULL,deployed_at TEXT NOT NULL,environment TEXT DEFAULT '',health TEXT DEFAULT '',payload TEXT NOT NULL,UNIQUE(project,commit_sha,deployed_at));`);
for(const sql of ["ALTER TABLE incidents ADD COLUMN published_at TEXT DEFAULT ''","ALTER TABLE incidents ADD COLUMN public_slug TEXT DEFAULT ''"]){try{db.exec(sql)}catch(error){if(!String(error.message).includes('duplicate column'))throw error;}} for (const sql of [
"ALTER TABLE incidents ADD COLUMN published_at TEXT DEFAULT ''",
"ALTER TABLE incidents ADD COLUMN public_slug TEXT DEFAULT ''",
]) {
try {
db.exec(sql);
} catch (error) {
if (!String(error.message).includes('duplicate column')) throw error;
}
}
const receiptStore = createReceiptStore(db, process.env.TRACE_RECEIPT_TOKENS || ''); const receiptStore = createReceiptStore(db, process.env.TRACE_RECEIPT_TOKENS || '');
const repos = new Map((process.env.TRACE_REPOS || '').split(',').filter(Boolean).map(x => { const i=x.indexOf(':'); return [x.slice(0,i),x.slice(i+1)]; })); const repos = new Map(
const giteaRepos=(process.env.TRACE_GITEA_REPOS||'').split(',').map(x=>x.trim()).filter(Boolean),giteaUrl=process.env.TRACE_GITEA_URL||'https://repo.explewd.com',giteaToken=process.env.TRACE_GITEA_TOKEN||''; (process.env.TRACE_REPOS || '')
const esc=s=>String(s??'').replaceAll('&','&amp;').replaceAll('<','&lt;').replaceAll('>','&gt;').replaceAll('"','&quot;'); .split(',')
const form=async req=>{let b='';for await(const c of req)b+=c;return Object.fromEntries(new URLSearchParams(b));}; .filter(Boolean)
function auth(req,res){if(!password)return true;const expected='Basic '+Buffer.from('trace:'+password).toString('base64');if(req.headers.authorization===expected)return true;res.writeHead(401,{'WWW-Authenticate':'Basic realm="trace"'}).end('Authentication required');return false;} .map((x) => {
function layout(title,body){return `<!doctype html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width"><title>${esc(title)} · trace</title><link rel=stylesheet href=/style.css></head><body><header><a href=/><b>trace</b></a><nav><a href=/incidents/new>new incident</a><a href=/candidates>git inbox</a><a href=/events>operational events</a></nav></header><main>${body}</main><footer>Private by default. Git discovers; a human explains.</footer><script>const incident=location.pathname.match(/^\\/incidents\\/([^/]+)$/);if(incident){document.querySelector('form[action$="/export"]')?.remove();const controls=document.createElement('div');controls.className='actions publication-actions';controls.innerHTML='<form method="post" action="/incidents/'+incident[1]+'/publish"><button>publish / update reviewed article</button></form><form method="post" action="/incidents/'+incident[1]+'/unpublish"><button>unpublish</button></form>';document.querySelector('main h1')?.after(controls)}</script></body></html>`;} const i = x.indexOf(':');
const fields=['impact','root_cause','fix','verification','prevention','remaining_risk','lesson','public_summary']; return [x.slice(0, i), x.slice(i + 1)];
const slugify=s=>String(s||'failure').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'')||'failure'; }),
const publicSeverity={annoyance:'paper-cut',degraded:'minor',unavailable:'significant','data-risk':'significant','security-risk':'significant'}; );
const publicStatus={investigating:'open',mitigated:'worked-around',resolved:'resolved',abandoned:'abandoned'}; const giteaRepos = (process.env.TRACE_GITEA_REPOS || '')
function publicMarkdown(x){return `---\ntitle: ${JSON.stringify(x.title)}\nsummary: ${JSON.stringify(x.public_summary||x.symptom)}\ndate: ${x.detected_at.slice(0,10)}\nprojectSlug: ${JSON.stringify(x.project||'')}\nkind: bug\nseverity: ${publicSeverity[x.severity]||'minor'}\nstatus: ${publicStatus[x.status]||'open'}\ntags: []\n---\n\n## Symptom\n\n${x.symptom}\n\n## What was actually happening\n\n${x.root_cause}\n\n## How we proved it\n\n${x.verification}\n\n## The fix\n\n${x.fix}\n\n## What changed afterward\n\n${x.prevention}\n\n## Remaining risk\n\n${x.remaining_risk}\n\n## Lesson\n\n${x.lesson}\n`;} .split(',')
function publicIncident(x){return {id:x.id,slug:x.public_slug,title:x.title,summary:x.public_summary||x.symptom,date:x.detected_at.slice(0,10),projectSlug:x.project||'',kind:'bug',severity:publicSeverity[x.severity]||'minor',status:publicStatus[x.status]||'open',publishedAt:x.published_at,updatedAt:x.updated_at,markdownUrl:`/api/v1/public/failures/${encodeURIComponent(x.public_slug)}.md`};} .map((x) => x.trim())
function publicDetail(x){return {...publicIncident(x),sections:[['Symptom',x.symptom],['What was actually happening',x.root_cause],['How we proved it',x.verification],['The fix',x.fix],['What changed afterward',x.prevention],['Remaining risk',x.remaining_risk],['Lesson',x.lesson]].filter(([,body])=>body)};} .filter(Boolean),
function incidentForm(v={}){return `<form method=post class=stack><label>Title<input name=title required value="${esc(v.title)}"></label><div class=cols><label>Project<input name=project value="${esc(v.project)}"></label><label>Detected<input type=datetime-local name=detected_at required value="${esc((v.detected_at||new Date().toISOString()).slice(0,16))}"></label><label>Status<select name=status>${['investigating','mitigated','resolved','abandoned'].map(x=>`<option ${v.status===x?'selected':''}>${x}</option>`)}</select></label><label>Severity<select name=severity>${['annoyance','degraded','unavailable','data-risk','security-risk'].map(x=>`<option ${v.severity===x?'selected':''}>${x}</option>`)}</select></label></div><label>Symptom<textarea name=symptom required>${esc(v.symptom)}</textarea></label>${fields.map(x=>`<label>${x.replaceAll('_',' ')}<textarea name=${x}>${esc(v[x])}</textarea></label>`).join('')}<label>Confidence<select name=confidence>${['unknown','low','medium','high'].map(x=>`<option ${v.confidence===x?'selected':''}>${x}</option>`)}</select></label><button>save</button></form>`;} giteaUrl = process.env.TRACE_GITEA_URL || 'https://repo.explewd.com',
function redirect(res,to){res.writeHead(303,{Location:to}).end();} giteaToken = process.env.TRACE_GITEA_TOKEN || '';
async function scan(){for(const [id,path] of repos){let stdout;try{stdout=(await exec('git',['-C',path,'log','--all','--no-merges','-n','300','--pretty=format:%H%x1f%ad%x1f%s','--date=iso-strict'],{maxBuffer:2e6})).stdout;}catch(error){console.warn(`Skipping unavailable repository ${id} at ${path}: ${error.message}`);continue;}for(const line of stdout.split('\n')){const [sha,date,subject]=line.split('\x1f');if(!/fix|bug|broken|race|crash|regression|rollback|restore|duplicate|auth|fail/i.test(subject||''))continue;let paths='';try{paths=(await exec('git',['-C',path,'show','--name-only','--pretty=format:',sha],{maxBuffer:2e5})).stdout.trim();}catch{}db.prepare('INSERT OR IGNORE INTO candidates(repo,sha,committed_at,subject,paths,reason) VALUES(?,?,?,?,?,?)').run(id,sha,date,subject,paths,'local git keyword');}}const remote=await scanGiteaCandidates({baseUrl:giteaUrl,repositories:giteaRepos,token:giteaToken});for(const candidate of remote.candidates)db.prepare('INSERT OR IGNORE INTO candidates(repo,sha,committed_at,subject,paths,reason) VALUES(?,?,?,?,?,?)').run(candidate.repo,candidate.sha,candidate.committedAt,candidate.subject,candidate.paths,candidate.reason);for(const error of remote.errors)console.warn(`Skipping Gitea repository ${error.repo}: ${error.error}`)} const esc = (s) =>
async function handler(req,res){if(!auth(req,res))return;const u=new URL(req.url,'http://x');if(u.pathname==='/style.css'){res.writeHead(200,{'content-type':'text/css'}).end(`:root{--b:#0d1117;--p:#161b22;--l:#30363d;--t:#c9d1d9;--m:#8b949e;--a:#58a6ff}*{box-sizing:border-box}body{margin:0;background:var(--b);color:var(--t);font:14px/1.55 ui-monospace,monospace}header,footer,main{max-width:1050px;margin:auto;padding:20px}header{display:flex;justify-content:space-between;border-bottom:1px solid var(--l)}a{color:var(--a);text-decoration:none}nav{display:flex;gap:18px}.card,form{background:var(--p);border:1px solid var(--l);padding:18px;border-radius:8px;margin:12px 0}.stack,label{display:grid;gap:6px}.stack{gap:14px}.cols{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}input,textarea,select,button{background:var(--b);border:1px solid var(--l);color:var(--t);padding:10px;font:inherit}textarea{min-height:75px}button{cursor:pointer;color:var(--a)}.meta{color:var(--m);font-size:12px}.badge{border:1px solid var(--l);padding:2px 7px;border-radius:99px}.timeline{border-left:2px solid var(--l);padding-left:18px}.actions{display:flex;gap:8px}@media(max-width:650px){.cols{grid-template-columns:1fr}}`);return;} String(s ?? '')
if(req.method==='GET'&&u.pathname==='/'){const rows=db.prepare('SELECT * FROM incidents ORDER BY detected_at DESC').all();const counts=db.prepare("SELECT status,count(*) n FROM incidents GROUP BY status").all();res.end(layout('incidents',`<h1>Incident notebook</h1><p class=meta>${counts.map(x=>`${x.status}: ${x.n}`).join(' · ')||'No incidents yet.'}</p>${rows.map(x=>`<a class=card style=display:block href=/incidents/${x.id}><b>${esc(x.title)}</b><p>${esc(x.symptom)}</p><span class=badge>${x.status}</span> <span class=meta>${esc(x.project)} · ${esc(x.detected_at)}</span></a>`).join('')}`));return;} .replaceAll('&', '&amp;')
if(req.method==='GET'&&u.pathname==='/incidents/new'){res.end(layout('new incident','<h1>Record what happened</h1>'+incidentForm()));return;} .replaceAll('<', '&lt;')
if(req.method==='POST'&&u.pathname==='/incidents/new'){const f=await form(req),id=crypto.randomUUID(),now=new Date().toISOString();db.prepare(`INSERT INTO incidents(id,title,project,status,severity,detected_at,symptom,impact,root_cause,confidence,fix,verification,prevention,remaining_risk,lesson,public_summary,created_at,updated_at) VALUES(${Array(18).fill('?').join(',')})`).run(id,f.title,f.project||'',f.status,f.severity,f.detected_at,f.symptom,...fields.slice(0,2).map(x=>f[x]||''),f.confidence||'unknown',...fields.slice(2).map(x=>f[x]||''),now,now);redirect(res,'/incidents/'+id);return;} .replaceAll('>', '&gt;')
const match=u.pathname.match(/^\/incidents\/([^/]+)$/);if(match){const x=db.prepare('SELECT * FROM incidents WHERE id=?').get(match[1]);if(!x){res.writeHead(404).end();return;}if(req.method==='POST'){const f=await form(req);db.prepare(`UPDATE incidents SET title=?,project=?,status=?,severity=?,detected_at=?,symptom=?,${fields.map(x=>x+'=?').join(',')},confidence=?,updated_at=? WHERE id=?`).run(f.title,f.project||'',f.status,f.severity,f.detected_at,f.symptom,...fields.map(x=>f[x]||''),f.confidence||'unknown',new Date().toISOString(),x.id);redirect(res,u.pathname);return;}const timeline=db.prepare('SELECT * FROM timeline WHERE incident_id=? ORDER BY at').all(x.id), hyps=db.prepare('SELECT * FROM hypotheses WHERE incident_id=?').all(x.id), commits=db.prepare('SELECT * FROM candidates WHERE incident_id=?').all(x.id);res.end(layout(x.title,`<h1>${esc(x.title)}</h1><div class=actions><form method=post action=/incidents/${x.id}/export><button>export public draft</button></form></div>${incidentForm(x)}<section class=card><h2>Timeline</h2><div class=timeline>${timeline.map(t=>`<p><b>${esc(t.kind)}</b> ${esc(t.at)}<br>${esc(t.body)}</p>`).join('')}</div><form method=post action=/incidents/${x.id}/timeline><div class=cols><input type=datetime-local name=at required><select name=kind>${['observation','hypothesis','action','result','decision','deploy','note'].map(k=>`<option>${k}</option>`)}</select></div><textarea name=body required></textarea><button>append</button></form></section><section class=card><h2>Hypotheses</h2>${hyps.map(h=>`<p><b>${esc(h.statement)}</b> <span class=badge>${h.status}</span><br>${esc(h.evidence)}</p>`).join('')}<form method=post action=/incidents/${x.id}/hypotheses><input name=statement required placeholder="We think…"><select name=status>${['untested','supported','rejected','inconclusive'].map(k=>`<option>${k}</option>`)}</select><textarea name=evidence placeholder=evidence></textarea><button>add</button></form></section><section class=card><h2>Linked commits</h2>${commits.map(c=>`<p><code>${c.sha.slice(0,7)}</code> ${esc(c.subject)}</p>`).join('')||'<p class=meta>None.</p>'}</section>`));return;} .replaceAll('"', '&quot;');
const sub=u.pathname.match(/^\/incidents\/([^/]+)\/(timeline|hypotheses|export)$/);if(req.method==='POST'&&sub){const f=await form(req),id=sub[1];if(sub[2]==='timeline')db.prepare('INSERT INTO timeline(incident_id,at,kind,body) VALUES(?,?,?,?)').run(id,f.at,f.kind,f.body);if(sub[2]==='hypotheses')db.prepare('INSERT INTO hypotheses(incident_id,statement,status,evidence) VALUES(?,?,?,?)').run(id,f.statement,f.status,f.evidence);if(sub[2]==='export'){const x=db.prepare('SELECT * FROM incidents WHERE id=?').get(id);const slug=x.title.toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'');const md=`---\ntitle: ${JSON.stringify(x.title)}\nsummary: ${JSON.stringify(x.public_summary||x.symptom)}\ndate: ${x.detected_at.slice(0,10)}\nprojectSlug: ${JSON.stringify(x.project||'')}\nkind: bug\nseverity: ${x.severity}\nstatus: ${x.status}\ntags: []\n---\n\n## Symptom\n\n${x.symptom}\n\n## What we thought\n\n<!-- Add reviewed hypothesis -->\n\n## What was actually happening\n\n${x.root_cause}\n\n## How we proved it\n\n${x.verification}\n\n## The fix\n\n${x.fix}\n\n## What changed afterward\n\n${x.prevention}\n\n## Remaining risk\n\n${x.remaining_risk}\n\n## Lesson\n\n${x.lesson}\n`;writeFileSync(join('data/exports',slug+'.md'),md);res.end(layout('export',`<h1>Public draft exported</h1><p><code>data/exports/${esc(slug)}.md</code></p><pre>${esc(md)}</pre>`));return;}redirect(res,'/incidents/'+id);return;} const form = async (req) => {
if(u.pathname==='/candidates'&&req.method==='POST'){await scan();redirect(res,'/candidates');return;}if(u.pathname==='/candidates'){const rows=db.prepare("SELECT * FROM candidates WHERE state='new' ORDER BY committed_at DESC").all();res.end(layout('git inbox',`<h1>Git inbox</h1><form method=post><button>scan configured repositories</button></form>${rows.map(c=>`<div class=card><b>${esc(c.subject)}</b><p class=meta>${esc(c.repo)} · ${c.sha.slice(0,10)} · ${esc(c.committed_at)}</p><details><summary>changed paths</summary><pre>${esc(c.paths)}</pre></details><form method=post action=/candidates/${c.id}/link><input name=incident_id placeholder="incident UUID"><button>link</button></form></div>`).join('')}`));return;} let b = '';
const link=u.pathname.match(/^\/candidates\/(\d+)\/link$/);if(link&&req.method==='POST'){const f=await form(req);db.prepare("UPDATE candidates SET incident_id=?,state='linked' WHERE id=?").run(f.incident_id,link[1]);redirect(res,'/candidates');return;} for await (const c of req) b += c;
if(u.pathname==='/api/receipts'&&req.method==='POST'){let raw='';for await(const c of req)raw+=c;const x=JSON.parse(raw);db.prepare('INSERT OR IGNORE INTO receipts(project,commit_sha,deployed_at,environment,health,payload) VALUES(?,?,?,?,?,?)').run(x.project,x.commit,x.deployedAt,x.environment||'',x.health||'',raw);res.writeHead(201,{'content-type':'application/json'}).end('{"ok":true}');return;} return Object.fromEntries(new URLSearchParams(b));
res.writeHead(404).end(layout('not found','<h1>Not found</h1>'));} };
function auth(req, res) {
if (!password) return true;
const expected = 'Basic ' + Buffer.from('trace:' + password).toString('base64');
if (req.headers.authorization === expected) return true;
res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="trace"' }).end('Authentication required');
return false;
}
function layout(title, body) {
return `<!doctype html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width"><title>${esc(title)} · trace</title><link rel=stylesheet href=/style.css></head><body><header><a href=/><b>trace</b></a><nav><a href=/incidents/new>new incident</a><a href=/candidates>git inbox</a><a href=/events>operational events</a></nav></header><main>${body}</main><footer>Private by default. Git discovers; a human explains.</footer><script>const incident=location.pathname.match(/^\\/incidents\\/([^/]+)$/);if(incident){document.querySelector('form[action$="/export"]')?.remove();const controls=document.createElement('div');controls.className='actions publication-actions';controls.innerHTML='<form method="post" action="/incidents/'+incident[1]+'/publish"><button>publish / update reviewed article</button></form><form method="post" action="/incidents/'+incident[1]+'/unpublish"><button>unpublish</button></form>';document.querySelector('main h1')?.after(controls)}</script></body></html>`;
}
const fields = [
'impact',
'root_cause',
'fix',
'verification',
'prevention',
'remaining_risk',
'lesson',
'public_summary',
];
const slugify = (s) =>
String(s || 'failure')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '') || 'failure';
const publicSeverity = {
annoyance: 'paper-cut',
degraded: 'minor',
unavailable: 'significant',
'data-risk': 'significant',
'security-risk': 'significant',
};
const publicStatus = {
investigating: 'open',
mitigated: 'worked-around',
resolved: 'resolved',
abandoned: 'abandoned',
};
function publicMarkdown(x) {
return `---\ntitle: ${JSON.stringify(x.title)}\nsummary: ${JSON.stringify(x.public_summary || x.symptom)}\ndate: ${x.detected_at.slice(0, 10)}\nprojectSlug: ${JSON.stringify(x.project || '')}\nkind: bug\nseverity: ${publicSeverity[x.severity] || 'minor'}\nstatus: ${publicStatus[x.status] || 'open'}\ntags: []\n---\n\n## Symptom\n\n${x.symptom}\n\n## What was actually happening\n\n${x.root_cause}\n\n## How we proved it\n\n${x.verification}\n\n## The fix\n\n${x.fix}\n\n## What changed afterward\n\n${x.prevention}\n\n## Remaining risk\n\n${x.remaining_risk}\n\n## Lesson\n\n${x.lesson}\n`;
}
function publicIncident(x) {
return {
id: x.id,
slug: x.public_slug,
title: x.title,
summary: x.public_summary || x.symptom,
date: x.detected_at.slice(0, 10),
projectSlug: x.project || '',
kind: 'bug',
severity: publicSeverity[x.severity] || 'minor',
status: publicStatus[x.status] || 'open',
publishedAt: x.published_at,
updatedAt: x.updated_at,
markdownUrl: `/api/v1/public/failures/${encodeURIComponent(x.public_slug)}.md`,
};
}
function publicDetail(x) {
return {
...publicIncident(x),
sections: [
['Symptom', x.symptom],
['What was actually happening', x.root_cause],
['How we proved it', x.verification],
['The fix', x.fix],
['What changed afterward', x.prevention],
['Remaining risk', x.remaining_risk],
['Lesson', x.lesson],
].filter(([, body]) => body),
};
}
function incidentForm(v = {}) {
return `<form method=post class=stack><label>Title<input name=title required value="${esc(v.title)}"></label><div class=cols><label>Project<input name=project value="${esc(v.project)}"></label><label>Detected<input type=datetime-local name=detected_at required value="${esc((v.detected_at || new Date().toISOString()).slice(0, 16))}"></label><label>Status<select name=status>${['investigating', 'mitigated', 'resolved', 'abandoned'].map((x) => `<option ${v.status === x ? 'selected' : ''}>${x}</option>`)}</select></label><label>Severity<select name=severity>${['annoyance', 'degraded', 'unavailable', 'data-risk', 'security-risk'].map((x) => `<option ${v.severity === x ? 'selected' : ''}>${x}</option>`)}</select></label></div><label>Symptom<textarea name=symptom required>${esc(v.symptom)}</textarea></label>${fields.map((x) => `<label>${x.replaceAll('_', ' ')}<textarea name=${x}>${esc(v[x])}</textarea></label>`).join('')}<label>Confidence<select name=confidence>${['unknown', 'low', 'medium', 'high'].map((x) => `<option ${v.confidence === x ? 'selected' : ''}>${x}</option>`)}</select></label><button>save</button></form>`;
}
function redirect(res, to) {
res.writeHead(303, { Location: to }).end();
}
async function scan() {
for (const [id, path] of repos) {
let stdout;
try {
stdout = (
await exec(
'git',
[
'-C',
path,
'log',
'--all',
'--no-merges',
'-n',
'300',
'--pretty=format:%H%x1f%ad%x1f%s',
'--date=iso-strict',
],
{ maxBuffer: 2e6 },
)
).stdout;
} catch (error) {
console.warn(`Skipping unavailable repository ${id} at ${path}: ${error.message}`);
continue;
}
for (const line of stdout.split('\n')) {
const [sha, date, subject] = line.split('\x1f');
if (
!/fix|bug|broken|race|crash|regression|rollback|restore|duplicate|auth|fail/i.test(
subject || '',
)
)
continue;
let paths = '';
try {
paths = (
await exec('git', ['-C', path, 'show', '--name-only', '--pretty=format:', sha], {
maxBuffer: 2e5,
})
).stdout.trim();
} catch {}
db.prepare(
'INSERT OR IGNORE INTO candidates(repo,sha,committed_at,subject,paths,reason) VALUES(?,?,?,?,?,?)',
).run(id, sha, date, subject, paths, 'local git keyword');
}
}
const remote = await scanGiteaCandidates({
baseUrl: giteaUrl,
repositories: giteaRepos,
token: giteaToken,
});
for (const candidate of remote.candidates)
db.prepare(
'INSERT OR IGNORE INTO candidates(repo,sha,committed_at,subject,paths,reason) VALUES(?,?,?,?,?,?)',
).run(
candidate.repo,
candidate.sha,
candidate.committedAt,
candidate.subject,
candidate.paths,
candidate.reason,
);
for (const error of remote.errors)
console.warn(`Skipping Gitea repository ${error.repo}: ${error.error}`);
}
async function handler(req, res) {
if (!auth(req, res)) return;
const u = new URL(req.url, 'http://x');
if (u.pathname === '/style.css') {
res
.writeHead(200, { 'content-type': 'text/css' })
.end(
`:root{--b:#0d1117;--p:#161b22;--l:#30363d;--t:#c9d1d9;--m:#8b949e;--a:#58a6ff}*{box-sizing:border-box}body{margin:0;background:var(--b);color:var(--t);font:14px/1.55 ui-monospace,monospace}header,footer,main{max-width:1050px;margin:auto;padding:20px}header{display:flex;justify-content:space-between;border-bottom:1px solid var(--l)}a{color:var(--a);text-decoration:none}nav{display:flex;gap:18px}.card,form{background:var(--p);border:1px solid var(--l);padding:18px;border-radius:8px;margin:12px 0}.stack,label{display:grid;gap:6px}.stack{gap:14px}.cols{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}input,textarea,select,button{background:var(--b);border:1px solid var(--l);color:var(--t);padding:10px;font:inherit}textarea{min-height:75px}button{cursor:pointer;color:var(--a)}.meta{color:var(--m);font-size:12px}.badge{border:1px solid var(--l);padding:2px 7px;border-radius:99px}.timeline{border-left:2px solid var(--l);padding-left:18px}.actions{display:flex;gap:8px}@media(max-width:650px){.cols{grid-template-columns:1fr}}`,
);
return;
}
if (req.method === 'GET' && u.pathname === '/') {
const rows = db.prepare('SELECT * FROM incidents ORDER BY detected_at DESC').all();
const counts = db.prepare('SELECT status,count(*) n FROM incidents GROUP BY status').all();
res.end(
layout(
'incidents',
`<h1>Incident notebook</h1><p class=meta>${counts.map((x) => `${x.status}: ${x.n}`).join(' · ') || 'No incidents yet.'}</p>${rows.map((x) => `<a class=card style=display:block href=/incidents/${x.id}><b>${esc(x.title)}</b><p>${esc(x.symptom)}</p><span class=badge>${x.status}</span> <span class=meta>${esc(x.project)} · ${esc(x.detected_at)}</span></a>`).join('')}`,
),
);
return;
}
if (req.method === 'GET' && u.pathname === '/incidents/new') {
res.end(layout('new incident', '<h1>Record what happened</h1>' + incidentForm()));
return;
}
if (req.method === 'POST' && u.pathname === '/incidents/new') {
const f = await form(req),
id = crypto.randomUUID(),
now = new Date().toISOString();
db.prepare(
`INSERT INTO incidents(id,title,project,status,severity,detected_at,symptom,impact,root_cause,confidence,fix,verification,prevention,remaining_risk,lesson,public_summary,created_at,updated_at) VALUES(${Array(18).fill('?').join(',')})`,
).run(
id,
f.title,
f.project || '',
f.status,
f.severity,
f.detected_at,
f.symptom,
...fields.slice(0, 2).map((x) => f[x] || ''),
f.confidence || 'unknown',
...fields.slice(2).map((x) => f[x] || ''),
now,
now,
);
redirect(res, '/incidents/' + id);
return;
}
const match = u.pathname.match(/^\/incidents\/([^/]+)$/);
if (match) {
const x = db.prepare('SELECT * FROM incidents WHERE id=?').get(match[1]);
if (!x) {
res.writeHead(404).end();
return;
}
if (req.method === 'POST') {
const f = await form(req);
db.prepare(
`UPDATE incidents SET title=?,project=?,status=?,severity=?,detected_at=?,symptom=?,${fields.map((x) => x + '=?').join(',')},confidence=?,updated_at=? WHERE id=?`,
).run(
f.title,
f.project || '',
f.status,
f.severity,
f.detected_at,
f.symptom,
...fields.map((x) => f[x] || ''),
f.confidence || 'unknown',
new Date().toISOString(),
x.id,
);
redirect(res, u.pathname);
return;
}
const timeline = db.prepare('SELECT * FROM timeline WHERE incident_id=? ORDER BY at').all(x.id),
hyps = db.prepare('SELECT * FROM hypotheses WHERE incident_id=?').all(x.id),
commits = db.prepare('SELECT * FROM candidates WHERE incident_id=?').all(x.id);
res.end(
layout(
x.title,
`<h1>${esc(x.title)}</h1><div class=actions><form method=post action=/incidents/${x.id}/export><button>export public draft</button></form></div>${incidentForm(x)}<section class=card><h2>Timeline</h2><div class=timeline>${timeline.map((t) => `<p><b>${esc(t.kind)}</b> ${esc(t.at)}<br>${esc(t.body)}</p>`).join('')}</div><form method=post action=/incidents/${x.id}/timeline><div class=cols><input type=datetime-local name=at required><select name=kind>${['observation', 'hypothesis', 'action', 'result', 'decision', 'deploy', 'note'].map((k) => `<option>${k}</option>`)}</select></div><textarea name=body required></textarea><button>append</button></form></section><section class=card><h2>Hypotheses</h2>${hyps.map((h) => `<p><b>${esc(h.statement)}</b> <span class=badge>${h.status}</span><br>${esc(h.evidence)}</p>`).join('')}<form method=post action=/incidents/${x.id}/hypotheses><input name=statement required placeholder="We think…"><select name=status>${['untested', 'supported', 'rejected', 'inconclusive'].map((k) => `<option>${k}</option>`)}</select><textarea name=evidence placeholder=evidence></textarea><button>add</button></form></section><section class=card><h2>Linked commits</h2>${commits.map((c) => `<p><code>${c.sha.slice(0, 7)}</code> ${esc(c.subject)}</p>`).join('') || '<p class=meta>None.</p>'}</section>`,
),
);
return;
}
const sub = u.pathname.match(/^\/incidents\/([^/]+)\/(timeline|hypotheses|export)$/);
if (req.method === 'POST' && sub) {
const f = await form(req),
id = sub[1];
if (sub[2] === 'timeline')
db.prepare('INSERT INTO timeline(incident_id,at,kind,body) VALUES(?,?,?,?)').run(
id,
f.at,
f.kind,
f.body,
);
if (sub[2] === 'hypotheses')
db.prepare(
'INSERT INTO hypotheses(incident_id,statement,status,evidence) VALUES(?,?,?,?)',
).run(id, f.statement, f.status, f.evidence);
if (sub[2] === 'export') {
const x = db.prepare('SELECT * FROM incidents WHERE id=?').get(id);
const slug = x.title
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
const md = `---\ntitle: ${JSON.stringify(x.title)}\nsummary: ${JSON.stringify(x.public_summary || x.symptom)}\ndate: ${x.detected_at.slice(0, 10)}\nprojectSlug: ${JSON.stringify(x.project || '')}\nkind: bug\nseverity: ${x.severity}\nstatus: ${x.status}\ntags: []\n---\n\n## Symptom\n\n${x.symptom}\n\n## What we thought\n\n<!-- Add reviewed hypothesis -->\n\n## What was actually happening\n\n${x.root_cause}\n\n## How we proved it\n\n${x.verification}\n\n## The fix\n\n${x.fix}\n\n## What changed afterward\n\n${x.prevention}\n\n## Remaining risk\n\n${x.remaining_risk}\n\n## Lesson\n\n${x.lesson}\n`;
writeFileSync(join('data/exports', slug + '.md'), md);
res.end(
layout(
'export',
`<h1>Public draft exported</h1><p><code>data/exports/${esc(slug)}.md</code></p><pre>${esc(md)}</pre>`,
),
);
return;
}
redirect(res, '/incidents/' + id);
return;
}
if (u.pathname === '/candidates' && req.method === 'POST') {
await scan();
redirect(res, '/candidates');
return;
}
if (u.pathname === '/candidates') {
const rows = db
.prepare("SELECT * FROM candidates WHERE state='new' ORDER BY committed_at DESC")
.all();
res.end(
layout(
'git inbox',
`<h1>Git inbox</h1><form method=post><button>scan configured repositories</button></form>${rows.map((c) => `<div class=card><b>${esc(c.subject)}</b><p class=meta>${esc(c.repo)} · ${c.sha.slice(0, 10)} · ${esc(c.committed_at)}</p><details><summary>changed paths</summary><pre>${esc(c.paths)}</pre></details><form method=post action=/candidates/${c.id}/link><input name=incident_id placeholder="incident UUID"><button>link</button></form></div>`).join('')}`,
),
);
return;
}
const link = u.pathname.match(/^\/candidates\/(\d+)\/link$/);
if (link && req.method === 'POST') {
const f = await form(req);
db.prepare("UPDATE candidates SET incident_id=?,state='linked' WHERE id=?").run(
f.incident_id,
link[1],
);
redirect(res, '/candidates');
return;
}
if (u.pathname === '/api/receipts' && req.method === 'POST') {
let raw = '';
for await (const c of req) raw += c;
const x = JSON.parse(raw);
db.prepare(
'INSERT OR IGNORE INTO receipts(project,commit_sha,deployed_at,environment,health,payload) VALUES(?,?,?,?,?,?)',
).run(x.project, x.commit, x.deployedAt, x.environment || '', x.health || '', raw);
res.writeHead(201, { 'content-type': 'application/json' }).end('{"ok":true}');
return;
}
res.writeHead(404).end(layout('not found', '<h1>Not found</h1>'));
}
async function publicContent(req,res,path){ async function publicContent(req, res, path) {
if(req.method==='GET'&&path==='/api/v1/public/failures'){ if (req.method === 'GET' && path === '/api/v1/public/failures') {
const rows=db.prepare("SELECT * FROM incidents WHERE published_at<>'' ORDER BY detected_at DESC").all(); const rows = db
res.writeHead(200,{'content-type':'application/json; charset=utf-8','access-control-allow-origin':'*','cache-control':'public, max-age=60'}).end(JSON.stringify({schemaVersion:1,generatedAt:new Date().toISOString(),failures:rows.map(publicIncident)}));return true; .prepare("SELECT * FROM incidents WHERE published_at<>'' ORDER BY detected_at DESC")
.all();
res
.writeHead(200, {
'content-type': 'application/json; charset=utf-8',
'access-control-allow-origin': '*',
'cache-control': 'public, max-age=60',
})
.end(
JSON.stringify({
schemaVersion: 1,
generatedAt: new Date().toISOString(),
failures: rows.map(publicIncident),
}),
);
return true;
}
const md = path.match(/^\/api\/v1\/public\/failures\/([^/]+)\.md$/);
if (req.method === 'GET' && md) {
const x = db
.prepare("SELECT * FROM incidents WHERE public_slug=? AND published_at<>''")
.get(decodeURIComponent(md[1]));
if (!x) {
res.writeHead(404).end('Not found');
return true;
}
res
.writeHead(200, {
'content-type': 'text/markdown; charset=utf-8',
'access-control-allow-origin': '*',
'cache-control': 'public, max-age=60',
})
.end(publicMarkdown(x));
return true;
}
const detail = path.match(/^\/api\/v1\/public\/failures\/([^/]+)$/);
if (req.method === 'GET' && detail) {
const x = db
.prepare("SELECT * FROM incidents WHERE public_slug=? AND published_at<>''")
.get(decodeURIComponent(detail[1]));
if (!x) {
res
.writeHead(404, {
'content-type': 'application/json; charset=utf-8',
'access-control-allow-origin': '*',
})
.end('{"error":"not_found"}');
return true;
}
res
.writeHead(200, {
'content-type': 'application/json; charset=utf-8',
'access-control-allow-origin': '*',
'cache-control': 'public, max-age=60',
})
.end(JSON.stringify({ schemaVersion: 1, failure: publicDetail(x) }));
return true;
} }
const md=path.match(/^\/api\/v1\/public\/failures\/([^/]+)\.md$/);
if(req.method==='GET'&&md){const x=db.prepare("SELECT * FROM incidents WHERE public_slug=? AND published_at<>''").get(decodeURIComponent(md[1]));if(!x){res.writeHead(404).end('Not found');return true;}res.writeHead(200,{'content-type':'text/markdown; charset=utf-8','access-control-allow-origin':'*','cache-control':'public, max-age=60'}).end(publicMarkdown(x));return true;}
const detail=path.match(/^\/api\/v1\/public\/failures\/([^/]+)$/);
if(req.method==='GET'&&detail){const x=db.prepare("SELECT * FROM incidents WHERE public_slug=? AND published_at<>''").get(decodeURIComponent(detail[1]));if(!x){res.writeHead(404,{'content-type':'application/json; charset=utf-8','access-control-allow-origin':'*'}).end('{"error":"not_found"}');return true;}res.writeHead(200,{'content-type':'application/json; charset=utf-8','access-control-allow-origin':'*','cache-control':'public, max-age=60'}).end(JSON.stringify({schemaVersion:1,failure:publicDetail(x)}));return true;}
return false; return false;
} }
async function publicationAction(req,res,path){ async function publicationAction(req, res, path) {
const match=path.match(/^\/incidents\/([^/]+)\/(publish|unpublish)$/);if(!match||req.method!=='POST')return false;if(!auth(req,res))return true; const match = path.match(/^\/incidents\/([^/]+)\/(publish|unpublish)$/);
const x=db.prepare('SELECT * FROM incidents WHERE id=?').get(match[1]);if(!x){res.writeHead(404).end();return true;} if (!match || req.method !== 'POST') return false;
if(match[2]==='unpublish')db.prepare("UPDATE incidents SET published_at='',updated_at=? WHERE id=?").run(new Date().toISOString(),x.id); if (!auth(req, res)) return true;
else {let slug=x.public_slug||slugify(x.title);const collision=db.prepare('SELECT id FROM incidents WHERE public_slug=? AND id<>?').get(slug,x.id);if(collision)slug=`${slug}-${x.id.slice(0,8)}`;const now=new Date().toISOString();db.prepare('UPDATE incidents SET public_slug=?,published_at=?,updated_at=? WHERE id=?').run(slug,now,now,x.id);} const x = db.prepare('SELECT * FROM incidents WHERE id=?').get(match[1]);
redirect(res,`/incidents/${x.id}`);return true; if (!x) {
res.writeHead(404).end();
return true;
}
if (match[2] === 'unpublish')
db.prepare("UPDATE incidents SET published_at='',updated_at=? WHERE id=?").run(
new Date().toISOString(),
x.id,
);
else {
let slug = x.public_slug || slugify(x.title);
const collision = db
.prepare('SELECT id FROM incidents WHERE public_slug=? AND id<>?')
.get(slug, x.id);
if (collision) slug = `${slug}-${x.id.slice(0, 8)}`;
const now = new Date().toISOString();
db.prepare('UPDATE incidents SET public_slug=?,published_at=?,updated_at=? WHERE id=?').run(
slug,
now,
now,
x.id,
);
}
redirect(res, `/incidents/${x.id}`);
return true;
} }
http.createServer(async(req,res)=>{try{const path=new URL(req.url,'http://x').pathname;if(await publicContent(req,res,path))return;if(await publicationAction(req,res,path))return;if(path==='/events'){if(auth(req,res))res.end(renderEventsPage(db));return;}const handled=await receiptStore.handle(req,res,path);if(handled===false)await handler(req,res);}catch(e){console.error(e);if(!res.headersSent)res.writeHead(500);res.end('Internal error');}}).listen(port,()=>console.log(`trace listening on ${port}`)); http
.createServer(async (req, res) => {
try {
const path = new URL(req.url, 'http://x').pathname;
if (await publicContent(req, res, path)) return;
if (await publicationAction(req, res, path)) return;
if (path === '/events') {
if (auth(req, res)) res.end(renderEventsPage(db));
return;
}
const handled = await receiptStore.handle(req, res, path);
if (handled === false) await handler(req, res);
} catch (e) {
console.error(e);
if (!res.headersSent) res.writeHead(500);
res.end('Internal error');
}
})
.listen(port, () => console.log(`trace listening on ${port}`));
+48 -3
View File
@@ -1,3 +1,48 @@
import test from'node:test';import assert from'node:assert/strict';import{scanGiteaCandidates}from'../src/gitea.mjs'; import test from 'node:test';
test('finds subject fixes, paginates safely, and ignores body-only matches',async()=>{const calls=[],fetchImpl=async url=>{calls.push(url);if(url.includes('/git/commits/abc'))return new Response(JSON.stringify({files:[{filename:'web/verify.go'}]}));return new Response(JSON.stringify([{sha:'abc',commit:{message:'Fix DTLS verification\nDetailed body',committer:{date:'2026-07-20T00:00:00Z'}}},{sha:'def',commit:{message:'Add feature\nfix mentioned only in body',committer:{date:'2026-07-19T00:00:00Z'}}}]))};const out=await scanGiteaCandidates({baseUrl:'https://gitea.example',repositories:['explewd/flit'],fetchImpl});assert.equal(out.candidates.length,1);assert.equal(out.candidates[0].sha,'abc');assert.equal(out.candidates[0].paths,'web/verify.go');assert.equal(out.errors.length,0);assert.ok(calls[0].includes('limit=50&page=1'))}); import assert from 'node:assert/strict';
test('isolates invalid and failed repositories',async()=>{const out=await scanGiteaCandidates({baseUrl:'https://gitea.example',repositories:['bad','explewd/missing'],fetchImpl:async()=>new Response('',{status:404})});assert.equal(out.candidates.length,0);assert.equal(out.errors.length,2)}); import { scanGiteaCandidates } from '../src/gitea.mjs';
test('finds subject fixes, paginates safely, and ignores body-only matches', async () => {
const calls = [],
fetchImpl = async (url) => {
calls.push(url);
if (url.includes('/git/commits/abc'))
return new Response(JSON.stringify({ files: [{ filename: 'web/verify.go' }] }));
return new Response(
JSON.stringify([
{
sha: 'abc',
commit: {
message: 'Fix DTLS verification\nDetailed body',
committer: { date: '2026-07-20T00:00:00Z' },
},
},
{
sha: 'def',
commit: {
message: 'Add feature\nfix mentioned only in body',
committer: { date: '2026-07-19T00:00:00Z' },
},
},
]),
);
};
const out = await scanGiteaCandidates({
baseUrl: 'https://gitea.example',
repositories: ['explewd/flit'],
fetchImpl,
});
assert.equal(out.candidates.length, 1);
assert.equal(out.candidates[0].sha, 'abc');
assert.equal(out.candidates[0].paths, 'web/verify.go');
assert.equal(out.errors.length, 0);
assert.ok(calls[0].includes('limit=50&page=1'));
});
test('isolates invalid and failed repositories', async () => {
const out = await scanGiteaCandidates({
baseUrl: 'https://gitea.example',
repositories: ['bad', 'explewd/missing'],
fetchImpl: async () => new Response('', { status: 404 }),
});
assert.equal(out.candidates.length, 0);
assert.equal(out.errors.length, 2);
});
+52 -4
View File
@@ -1,4 +1,52 @@
import test from 'node:test';import assert from 'node:assert/strict';import {DatabaseSync}from'node:sqlite';import{createReceiptStore,validateReceipt}from'../src/receipts.mjs'; import test from 'node:test';
const valid={schemaVersion:1,eventType:'deployment',eventId:'goonk-prod-1',project:'goonk',occurredAt:'2026-07-20T20:00:00Z',result:'success',source:{commit:'577a62e'},verification:{health:'healthy'}}; import assert from 'node:assert/strict';
test('validates v1',()=>{assert.deepEqual(validateReceipt(valid),[]);assert.ok(validateReceipt({}).length)}); import { DatabaseSync } from 'node:sqlite';
test('is idempotent and public projection is allowlisted',async()=>{const db=new DatabaseSync(':memory:'),store=createReceiptStore(db,'goonk:secret');const req=()=>({method:'POST',headers:{authorization:'Bearer secret'},async *[Symbol.asyncIterator](){yield JSON.stringify(valid)}});const res=()=>{const x={status:0,body:''};x.writeHead=s=>{x.status=s;return x};x.end=b=>{x.body=b;return x};return x};let r=res();await store.handle(req(),r,'/api/v1/events');assert.equal(r.status,201);r=res();await store.handle(req(),r,'/api/v1/events');assert.equal(JSON.parse(r.body).duplicate,true);r=res();await store.handle({method:'GET',headers:{}},r,'/api/v1/public/projects/goonk/latest');const out=JSON.parse(r.body);assert.equal(out.deployment.commit,'577a62e');assert.equal(out.deployment.payload,undefined)}); import { createReceiptStore, validateReceipt } from '../src/receipts.mjs';
const valid = {
schemaVersion: 1,
eventType: 'deployment',
eventId: 'goonk-prod-1',
project: 'goonk',
occurredAt: '2026-07-20T20:00:00Z',
result: 'success',
source: { commit: '577a62e' },
verification: { health: 'healthy' },
};
test('validates v1', () => {
assert.deepEqual(validateReceipt(valid), []);
assert.ok(validateReceipt({}).length);
});
test('is idempotent and public projection is allowlisted', async () => {
const db = new DatabaseSync(':memory:'),
store = createReceiptStore(db, 'goonk:secret');
const req = () => ({
method: 'POST',
headers: { authorization: 'Bearer secret' },
async *[Symbol.asyncIterator]() {
yield JSON.stringify(valid);
},
});
const res = () => {
const x = { status: 0, body: '' };
x.writeHead = (s) => {
x.status = s;
return x;
};
x.end = (b) => {
x.body = b;
return x;
};
return x;
};
let r = res();
await store.handle(req(), r, '/api/v1/events');
assert.equal(r.status, 201);
r = res();
await store.handle(req(), r, '/api/v1/events');
assert.equal(JSON.parse(r.body).duplicate, true);
r = res();
await store.handle({ method: 'GET', headers: {} }, r, '/api/v1/public/projects/goonk/latest');
const out = JSON.parse(r.body);
assert.equal(out.deployment.commit, '577a62e');
assert.equal(out.deployment.payload, undefined);
});