@datasynx/agentic-ai-cartography 2.8.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-bin.js +1 -1
- package/dist/{chunk-5D5ZZEZM.js → chunk-ASCA3UFM.js} +148 -2
- package/dist/chunk-ASCA3UFM.js.map +1 -0
- package/dist/{chunk-TBPGFEMQ.js → chunk-W4Q3TXHR.js} +162 -2
- package/dist/chunk-W4Q3TXHR.js.map +1 -0
- package/dist/cli.js +4 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +316 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +105 -1
- package/dist/index.d.ts +105 -1
- package/dist/index.js +312 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp-bin.js +1 -1
- package/llms-full.txt +1 -0
- package/package.json +1 -1
- package/server.json +2 -2
- package/dist/chunk-5D5ZZEZM.js.map +0 -1
- package/dist/chunk-TBPGFEMQ.js.map +0 -1
|
@@ -979,6 +979,156 @@ function handleGraphqlGet() {
|
|
|
979
979
|
return { status: 200, body: SDL };
|
|
980
980
|
}
|
|
981
981
|
|
|
982
|
+
// src/web/dashboard.ts
|
|
983
|
+
var STYLE = `
|
|
984
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
985
|
+
:root{--bg:#0f1419;--panel:#161b22;--line:#2d333b;--fg:#e6edf3;--dim:#8b949e;--accent:#3b82f6;--ok:#3fb950;--warn:#d29922;--crit:#f85149}
|
|
986
|
+
body{font:13px/1.5 ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,sans-serif;background:var(--bg);color:var(--fg);height:100vh;display:flex;flex-direction:column;overflow:hidden}
|
|
987
|
+
header{display:flex;align-items:center;gap:12px;padding:8px 14px;border-bottom:1px solid var(--line);background:var(--panel)}
|
|
988
|
+
header h1{font-size:15px;font-weight:600;letter-spacing:.3px}
|
|
989
|
+
header .ver{color:var(--dim);font-size:11px}
|
|
990
|
+
header .spacer{flex:1}
|
|
991
|
+
header input{background:var(--bg);border:1px solid var(--line);color:var(--fg);border-radius:6px;padding:5px 8px;font-size:12px;width:200px}
|
|
992
|
+
header input:focus{outline:none;border-color:var(--accent)}
|
|
993
|
+
header button{background:var(--accent);border:none;color:#fff;border-radius:6px;padding:6px 12px;font-size:12px;cursor:pointer}
|
|
994
|
+
main{flex:1;display:grid;grid-template-columns:300px 1fr 320px;overflow:hidden}
|
|
995
|
+
.col{overflow:auto;padding:12px;border-right:1px solid var(--line)}
|
|
996
|
+
.col:last-child{border-right:none;border-left:1px solid var(--line)}
|
|
997
|
+
.card{background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:10px 12px;margin-bottom:10px}
|
|
998
|
+
.card h2{font-size:11px;text-transform:uppercase;letter-spacing:.6px;color:var(--dim);margin-bottom:8px}
|
|
999
|
+
.stat{display:flex;justify-content:space-between;padding:2px 0}
|
|
1000
|
+
.stat b{font-variant-numeric:tabular-nums}
|
|
1001
|
+
.bar{height:4px;border-radius:2px;background:var(--accent);margin-top:2px}
|
|
1002
|
+
#search{width:100%;background:var(--bg);border:1px solid var(--line);color:var(--fg);border-radius:6px;padding:6px 8px;margin-bottom:8px}
|
|
1003
|
+
.node-item{padding:6px 8px;border-radius:6px;cursor:pointer;border:1px solid transparent}
|
|
1004
|
+
.node-item:hover{background:var(--panel)}
|
|
1005
|
+
.node-item.sel{background:var(--panel);border-color:var(--accent)}
|
|
1006
|
+
.node-item .t{color:var(--dim);font-size:11px}
|
|
1007
|
+
#center{position:relative;padding:0}
|
|
1008
|
+
#graph{display:block;width:100%;height:100%;background:radial-gradient(circle at 50% 40%,#11161d,#0d1117)}
|
|
1009
|
+
#empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--dim);text-align:center;padding:20px}
|
|
1010
|
+
.kv{display:flex;justify-content:space-between;gap:8px;padding:3px 0;border-bottom:1px solid var(--line)}
|
|
1011
|
+
.kv span:first-child{color:var(--dim)}
|
|
1012
|
+
.kv span:last-child{text-align:right;word-break:break-all}
|
|
1013
|
+
.chip{display:inline-block;background:var(--bg);border:1px solid var(--line);border-radius:10px;padding:1px 8px;font-size:11px;margin:2px 2px 0 0}
|
|
1014
|
+
.sev-high,.sev-critical{color:var(--crit)} .sev-medium,.sev-warning{color:var(--warn)} .sev-low,.sev-info{color:var(--dim)}
|
|
1015
|
+
#toast{position:fixed;bottom:14px;left:50%;transform:translateX(-50%);background:var(--crit);color:#fff;padding:8px 14px;border-radius:6px;font-size:12px;opacity:0;transition:opacity .2s;pointer-events:none}
|
|
1016
|
+
#toast.show{opacity:1}
|
|
1017
|
+
`;
|
|
1018
|
+
var SCRIPT = String.raw`
|
|
1019
|
+
const $=(s)=>document.querySelector(s), api=(p)=>{
|
|
1020
|
+
const h={accept:'application/json'};
|
|
1021
|
+
const t=sessionStorage.getItem('cartograph_token'); if(t) h.authorization='Bearer '+t;
|
|
1022
|
+
const tn=sessionStorage.getItem('cartograph_tenant'); if(tn) h['x-cartograph-tenant']=tn;
|
|
1023
|
+
return fetch(p,{headers:h}).then(async r=>{ if(!r.ok){ const e=new Error('http '+r.status); e.status=r.status; throw e; } return r.json(); });
|
|
1024
|
+
};
|
|
1025
|
+
function toast(m){ const t=$('#toast'); t.textContent=m; t.classList.add('show'); setTimeout(()=>t.classList.remove('show'),2600); }
|
|
1026
|
+
let NODES=[], SELECTED=null;
|
|
1027
|
+
|
|
1028
|
+
async function boot(){
|
|
1029
|
+
try{
|
|
1030
|
+
const s=await api('/v1/summary'); renderSummary(s);
|
|
1031
|
+
const n=await api('/v1/nodes?limit=1000'); NODES=n.nodes; renderList(NODES);
|
|
1032
|
+
}catch(e){
|
|
1033
|
+
if(e.status===401){ toast('Unauthorized — enter a bearer token and Reload.'); }
|
|
1034
|
+
else if(e.status===404){ const em=$('#empty'); em.textContent='No discovery session yet. Run a scan, then Reload.'; em.style.display='flex'; }
|
|
1035
|
+
else toast('Failed to load: '+e.message);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
function renderSummary(s){
|
|
1039
|
+
const max=Math.max(1,...Object.values(s.nodesByType));
|
|
1040
|
+
const types=Object.entries(s.nodesByType).sort((a,b)=>b[1]-a[1]).slice(0,12)
|
|
1041
|
+
.map(([k,v])=>'<div class="stat"><span>'+esc(k)+'</span><b>'+v+'</b></div><div class="bar" style="width:'+(v/max*100)+'%"></div>').join('');
|
|
1042
|
+
const anom=(s.anomalies||[]).slice(0,12).map(a=>'<div class="stat"><span class="sev-'+a.severity+'">'+esc(a.kind)+'</span><span class="t">'+esc(a.nodeId)+'</span></div>').join('') || '<div class="t">none</div>';
|
|
1043
|
+
$('#summary').innerHTML=
|
|
1044
|
+
'<div class="card"><h2>Totals</h2><div class="stat"><span>Nodes</span><b>'+s.totals.nodes+'</b></div><div class="stat"><span>Edges</span><b>'+s.totals.edges+'</b></div>'+(s.contributors!=null?'<div class="stat"><span>Contributors</span><b>'+s.contributors+'</b></div>':'')+'</div>'+
|
|
1045
|
+
'<div class="card"><h2>Nodes by type</h2>'+types+'</div>'+
|
|
1046
|
+
'<div class="card"><h2>Anomalies</h2>'+anom+'</div>';
|
|
1047
|
+
}
|
|
1048
|
+
function renderList(nodes){
|
|
1049
|
+
$('#list').innerHTML=nodes.map(n=>'<div class="node-item" data-id="'+esc(n.id)+'"><div>'+esc(n.name)+'</div><div class="t">'+esc(n.type)+'</div></div>').join('')||'<div class="t">no nodes</div>';
|
|
1050
|
+
$('#list').querySelectorAll('.node-item').forEach(el=>el.onclick=()=>select(el.dataset.id));
|
|
1051
|
+
}
|
|
1052
|
+
function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c])); }
|
|
1053
|
+
|
|
1054
|
+
async function select(id){
|
|
1055
|
+
SELECTED=id;
|
|
1056
|
+
$('#list').querySelectorAll('.node-item').forEach(el=>el.classList.toggle('sel',el.dataset.id===id));
|
|
1057
|
+
const node=NODES.find(n=>n.id===id);
|
|
1058
|
+
try{
|
|
1059
|
+
const dep=await api('/v1/nodes/'+encodeURIComponent(id)+'/dependencies?direction=both&maxDepth=2');
|
|
1060
|
+
renderDetail(node,dep); drawGraph(id,dep);
|
|
1061
|
+
}catch(e){ toast('drill-down failed: '+e.message); }
|
|
1062
|
+
}
|
|
1063
|
+
function renderDetail(node,dep){
|
|
1064
|
+
if(!node){ $('#detail').innerHTML='<div class="t">node not in current page</div>'; return; }
|
|
1065
|
+
const fields=[['id',node.id],['type',node.type],['name',node.name],['confidence',node.confidence],['domain',node.domain],['owner',node.owner]]
|
|
1066
|
+
.filter(([,v])=>v!=null).map(([k,v])=>'<div class="kv"><span>'+k+'</span><span>'+esc(v)+'</span></div>').join('');
|
|
1067
|
+
const tags=(node.tags||[]).map(t=>'<span class="chip">'+esc(t)+'</span>').join('');
|
|
1068
|
+
const edges=(dep.edges||[]).map(e=>'<div class="kv"><span>'+esc(e.relationship)+'</span><span>'+esc(e.sourceId===node.id?('→ '+e.targetId):('← '+e.sourceId))+'</span></div>').join('')||'<div class="t">no dependencies</div>';
|
|
1069
|
+
$('#detail').innerHTML='<div class="card"><h2>Node</h2>'+fields+(tags?'<div style="margin-top:6px">'+tags+'</div>':'')+'</div><div class="card"><h2>Dependencies ('+(dep.nodes?dep.nodes.length:0)+')</h2>'+edges+'</div>';
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
const cv=()=>$('#graph'), ctx=()=>cv().getContext('2d');
|
|
1073
|
+
function drawGraph(rootId,dep){
|
|
1074
|
+
$('#empty').style.display='none';
|
|
1075
|
+
const c=cv(); const dpr=window.devicePixelRatio||1; const w=c.clientWidth,h=c.clientHeight;
|
|
1076
|
+
c.width=w*dpr; c.height=h*dpr; const g=ctx(); g.setTransform(dpr,0,0,dpr,0,0); g.clearRect(0,0,w,h);
|
|
1077
|
+
const nodes=dep.nodes||[]; const cx=w/2,cy=h/2;
|
|
1078
|
+
// root at center; others on a circle, radius by depth.
|
|
1079
|
+
const pos={}; pos[rootId]={x:cx,y:cy};
|
|
1080
|
+
const others=nodes.filter(n=>n.id!==rootId);
|
|
1081
|
+
others.forEach((n,i)=>{ const a=(i/Math.max(1,others.length))*Math.PI*2; const r=90+(n.depth||1)*70; pos[n.id]={x:cx+Math.cos(a)*r,y:cy+Math.sin(a)*r}; });
|
|
1082
|
+
// edges
|
|
1083
|
+
g.strokeStyle='#30363d'; g.lineWidth=1.2;
|
|
1084
|
+
(dep.edges||[]).forEach(e=>{ const a=pos[e.sourceId],b=pos[e.targetId]; if(a&&b){ g.beginPath(); g.moveTo(a.x,a.y); g.lineTo(b.x,b.y); g.stroke(); } });
|
|
1085
|
+
// nodes
|
|
1086
|
+
const byId={}; nodes.forEach(n=>byId[n.id]=n);
|
|
1087
|
+
Object.entries(pos).forEach(([id,p])=>{ const n=byId[id]; const root=id===rootId;
|
|
1088
|
+
g.beginPath(); g.arc(p.x,p.y,root?13:8,0,Math.PI*2);
|
|
1089
|
+
g.fillStyle=root?'#3b82f6':'#21262d'; g.fill(); g.lineWidth=root?2:1; g.strokeStyle=root?'#60a5fa':'#484f58'; g.stroke();
|
|
1090
|
+
g.fillStyle='#c9d1d9'; g.font=(root?'600 12px':'11px')+' ui-sans-serif'; g.textAlign='center';
|
|
1091
|
+
g.fillText((n&&n.name?n.name:id).slice(0,22),p.x,p.y-(root?20:14));
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
document.addEventListener('DOMContentLoaded',()=>{
|
|
1096
|
+
const t=sessionStorage.getItem('cartograph_token'); if(t)$('#token').value=t;
|
|
1097
|
+
const tn=sessionStorage.getItem('cartograph_tenant'); if(tn)$('#tenant').value=tn;
|
|
1098
|
+
$('#reload').onclick=()=>{ sessionStorage.setItem('cartograph_token',$('#token').value.trim()); sessionStorage.setItem('cartograph_tenant',$('#tenant').value.trim()); boot(); };
|
|
1099
|
+
$('#search').oninput=(e)=>{ const q=e.target.value.toLowerCase(); renderList(NODES.filter(n=>n.name.toLowerCase().includes(q)||n.id.toLowerCase().includes(q)||n.type.toLowerCase().includes(q))); };
|
|
1100
|
+
boot();
|
|
1101
|
+
});
|
|
1102
|
+
`;
|
|
1103
|
+
function dashboardHtml(opts = {}) {
|
|
1104
|
+
const version = opts.version ?? "";
|
|
1105
|
+
return `<!DOCTYPE html>
|
|
1106
|
+
<html lang="en"><head>
|
|
1107
|
+
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
1108
|
+
<title>Cartograph dashboard</title>
|
|
1109
|
+
<style>${STYLE}</style>
|
|
1110
|
+
</head><body>
|
|
1111
|
+
<header>
|
|
1112
|
+
<h1>Cartograph</h1><span class="ver">${version ? `v${version}` : ""}</span>
|
|
1113
|
+
<span class="spacer"></span>
|
|
1114
|
+
<input id="tenant" placeholder="tenant (optional)" autocomplete="off">
|
|
1115
|
+
<input id="token" type="password" placeholder="bearer token" autocomplete="off">
|
|
1116
|
+
<button id="reload">Reload</button>
|
|
1117
|
+
</header>
|
|
1118
|
+
<main>
|
|
1119
|
+
<div class="col"><div id="summary"></div></div>
|
|
1120
|
+
<div class="col" id="center"><canvas id="graph"></canvas><div id="empty">Select a node to explore its dependencies.</div></div>
|
|
1121
|
+
<div class="col">
|
|
1122
|
+
<input id="search" placeholder="Search nodes\u2026" autocomplete="off">
|
|
1123
|
+
<div id="list"></div>
|
|
1124
|
+
<div id="detail"></div>
|
|
1125
|
+
</div>
|
|
1126
|
+
</main>
|
|
1127
|
+
<div id="toast"></div>
|
|
1128
|
+
<script>${SCRIPT}</script>
|
|
1129
|
+
</body></html>`;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
982
1132
|
// src/api/server.ts
|
|
983
1133
|
var DEPENDENCIES_RE = /^\/v1\/nodes\/(.+)\/dependencies$/;
|
|
984
1134
|
var MAX_GRAPHQL_BYTES = 1024 * 1024;
|
|
@@ -1020,6 +1170,8 @@ async function runApi(opts) {
|
|
|
1020
1170
|
});
|
|
1021
1171
|
const restDeps = { backend: opts.backend, version: opts.version };
|
|
1022
1172
|
const openApiDoc = buildOpenApiDocument({ version: opts.version });
|
|
1173
|
+
const dashboardEnabled = opts.dashboard !== false;
|
|
1174
|
+
const dashboardPage = dashboardEnabled ? dashboardHtml({ version: opts.version }) : "";
|
|
1023
1175
|
const allowedOrigins = opts.allowedOrigins ?? [];
|
|
1024
1176
|
assertSafeBind({ host, port: requestedPort, ...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {}, ...token ? { token } : {} });
|
|
1025
1177
|
let allowedHosts = opts.allowedHosts ?? [];
|
|
@@ -1062,6 +1214,11 @@ async function runApi(opts) {
|
|
|
1062
1214
|
finish(200);
|
|
1063
1215
|
return;
|
|
1064
1216
|
}
|
|
1217
|
+
if (dashboardEnabled && (path === "/" || path === "/app") && req.method === "GET") {
|
|
1218
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8", ...cors }).end(dashboardPage);
|
|
1219
|
+
finish(200);
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1065
1222
|
if (path === "/v1/health") {
|
|
1066
1223
|
if (req.method !== "GET") {
|
|
1067
1224
|
send(res, 405, { error: "method not allowed" }, { allow: "GET", ...cors });
|
|
@@ -1198,6 +1355,7 @@ function parseApiArgs(argv) {
|
|
|
1198
1355
|
const a = argv[i];
|
|
1199
1356
|
if (a === "--http") continue;
|
|
1200
1357
|
else if (a === "--no-graphql") opts.graphql = false;
|
|
1358
|
+
else if (a === "--no-dashboard") opts.dashboard = false;
|
|
1201
1359
|
else if (a === "--port") opts.port = Number(argv[++i]);
|
|
1202
1360
|
else if (a === "--host") opts.host = argv[++i];
|
|
1203
1361
|
else if (a === "--allowed-hosts") opts.allowedHosts = splitList(argv[++i]);
|
|
@@ -1233,12 +1391,14 @@ async function startApi(opts = {}) {
|
|
|
1233
1391
|
...opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {},
|
|
1234
1392
|
...token ? { token } : {},
|
|
1235
1393
|
...opts.graphql === false ? { graphql: false } : {},
|
|
1394
|
+
...opts.dashboard === false ? { dashboard: false } : {},
|
|
1236
1395
|
...opts.tenant ? { tenant: { defaultTenant: normalizeTenant(opts.tenant) } } : {},
|
|
1237
1396
|
log
|
|
1238
1397
|
});
|
|
1239
1398
|
const graphqlNote = opts.graphql === false ? " [REST only]" : " + /graphql";
|
|
1399
|
+
const dashNote = opts.dashboard === false ? "" : ` \xB7 dashboard http://${host}:${port}/`;
|
|
1240
1400
|
log(
|
|
1241
|
-
`Cartograph API (REST${graphqlNote}) on http://${host}:${port}/v1${token ? " (auth: bearer token required)" : ""} (tenant: ${normalizeTenant(opts.tenant)})`
|
|
1401
|
+
`Cartograph API (REST${graphqlNote}) on http://${host}:${port}/v1${token ? " (auth: bearer token required)" : ""} (tenant: ${normalizeTenant(opts.tenant)})${dashNote}`
|
|
1242
1402
|
);
|
|
1243
1403
|
return server;
|
|
1244
1404
|
}
|
|
@@ -1249,4 +1409,4 @@ export {
|
|
|
1249
1409
|
parseApiArgs,
|
|
1250
1410
|
startApi
|
|
1251
1411
|
};
|
|
1252
|
-
//# sourceMappingURL=chunk-
|
|
1412
|
+
//# sourceMappingURL=chunk-W4Q3TXHR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api/start.ts","../src/store/query.ts","../src/api/server.ts","../src/api/tenant.ts","../src/backstage.ts","../src/api/schemas.ts","../src/api/rest.ts","../src/api/openapi.ts","../src/api/graphql.ts","../src/web/dashboard.ts"],"sourcesContent":["/**\n * Shared entry logic for the read-only API server (4.2), used by both the dedicated\n * `cartography-api` binary and the `api` CLI sub-command. Mirrors `src/mcp/start.ts`:\n * opens the catalog, builds the SQLite query backend, resolves the bearer token from\n * `--token`/`CARTOGRAPHY_HTTP_TOKEN`, and starts `runApi`. All logging is to stderr;\n * the token value is never logged (only whether one is set).\n */\n\nimport type { Server } from 'node:http';\nimport { readFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { CartographyDB, normalizeTenant } from '../db.js';\nimport { defaultConfig } from '../types.js';\nimport { createSqliteQueryBackend } from '../store/query.js';\nimport { SqliteCredentialStore } from '../auth/identity.js';\nimport { runApi } from './server.js';\n\nexport interface StartApiOptions {\n dbPath?: string;\n session?: string | 'latest';\n port?: number;\n host?: string;\n allowedHosts?: string[];\n allowedOrigins?: string[];\n token?: string;\n /** Expose `/graphql` (default true). */\n graphql?: boolean;\n /** Serve the web dashboard at `/` and `/app` (default true; 4.1). */\n dashboard?: boolean;\n /** Default tenant served when a request names none. */\n tenant?: string;\n /** Reject unauthenticated requests even on loopback (RBAC `required` mode). */\n authRequired?: boolean;\n log?: (msg: string) => void;\n}\n\nexport interface ParsedApiArgs extends StartApiOptions {\n /** `--help`/`-h` was passed; the caller should print usage and exit 0. */\n help?: boolean;\n}\n\nfunction readVersion(): string {\n try {\n const dir = import.meta.dirname ?? dirname(fileURLToPath(import.meta.url));\n return (JSON.parse(readFileSync(resolve(dir, '..', 'package.json'), 'utf-8')).version as string) ?? '0.0.0';\n } catch {\n return '0.0.0';\n }\n}\n\n/** Parse `cartography-api` argv into StartApiOptions (unit-testable, no side effects). */\nexport function parseApiArgs(argv: string[]): ParsedApiArgs {\n const opts: ParsedApiArgs = {};\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n if (a === '--http') continue; // symmetry with `mcp`; HTTP is the only API transport\n else if (a === '--no-graphql') opts.graphql = false;\n else if (a === '--no-dashboard') opts.dashboard = false;\n else if (a === '--port') opts.port = Number(argv[++i]);\n else if (a === '--host') opts.host = argv[++i];\n else if (a === '--allowed-hosts') opts.allowedHosts = splitList(argv[++i]);\n else if (a === '--allowed-origins') opts.allowedOrigins = splitList(argv[++i]);\n else if (a === '--token') opts.token = argv[++i];\n else if (a === '--db') opts.dbPath = argv[++i];\n else if (a === '--session') opts.session = argv[++i];\n else if (a === '--tenant' || a === '--org') opts.tenant = argv[++i];\n else if (a === '--auth-required') opts.authRequired = true;\n else if (a === '--help' || a === '-h') opts.help = true;\n }\n return opts;\n}\n\nfunction splitList(raw: string | undefined): string[] {\n return (raw ?? '').split(',').map((s) => s.trim()).filter(Boolean);\n}\n\n/** Open the catalog, build the read backend, and start the API server. Returns the server. */\nexport async function startApi(opts: StartApiOptions = {}): Promise<Server> {\n const log = opts.log ?? ((m: string) => process.stderr.write(m + '\\n'));\n const db = new CartographyDB(opts.dbPath ?? defaultConfig().dbPath);\n const backend = createSqliteQueryBackend(db, opts.session ?? 'latest');\n const token = opts.token ?? process.env['CARTOGRAPHY_HTTP_TOKEN'];\n const host = opts.host ?? '127.0.0.1';\n const port = opts.port ?? 3737;\n const version = readVersion();\n\n // RBAC (4.5): the credential store is always wired; it only enforces once an admin\n // has added credentials (`cartography auth add`). Empty store → legacy shared/open behavior.\n const authStore = new SqliteCredentialStore(db);\n\n const server = await runApi({\n host,\n port,\n backend,\n version,\n auth: { store: authStore, ...(opts.authRequired ? { required: true } : {}) },\n ...(opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {}),\n ...(opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {}),\n ...(token ? { token } : {}),\n ...(opts.graphql === false ? { graphql: false } : {}),\n ...(opts.dashboard === false ? { dashboard: false } : {}),\n ...(opts.tenant ? { tenant: { defaultTenant: normalizeTenant(opts.tenant) } } : {}),\n log,\n });\n\n const graphqlNote = opts.graphql === false ? ' [REST only]' : ' + /graphql';\n const dashNote = opts.dashboard === false ? '' : ` · dashboard http://${host}:${port}/`;\n log(\n `Cartograph API (REST${graphqlNote}) on http://${host}:${port}/v1` +\n `${token ? ' (auth: bearer token required)' : ''} (tenant: ${normalizeTenant(opts.tenant)})${dashNote}`,\n );\n return server;\n}\n","/**\n * `QueryBackend` — the **read-only** query seam for the API server (4.2).\n *\n * This is deliberately distinct from {@link StoreBackend} (`src/store/backend.ts`),\n * which is the central-collector **write/ingest** seam. The two seams have opposite\n * shapes: ingest merges incoming deltas; this one answers topology questions. A\n * non-SQLite backend (4.3) implements both. Keeping them separate means the API\n * never gains a write path and the ingest core never gains a query path.\n *\n * Every method takes a {@link TenantContext}. Session resolution is tenant-scoped, so\n * a caller bound to tenant A can never read tenant B's topology — even by naming a\n * session id that belongs to B (it resolves to \"not found\", never B's data). This\n * mirrors the MCP server's `resolveSession` tenant guard exactly.\n */\n\nimport type { CartographyDB, GraphSummary, TraversalResult } from '../db.js';\nimport type { NodeRow, EdgeRow, SessionRow, TopologyDiff } from '../types.js';\n\n/** The tenant (org-scope) a request is bound to. `'local'` (DEFAULT_TENANT) until a real org is supplied. */\nexport interface TenantContext {\n tenant: string;\n}\n\nexport interface NodeQuery {\n search?: string;\n types?: readonly string[];\n limit?: number;\n offset?: number;\n}\n\nexport interface DependencyQuery {\n direction?: 'downstream' | 'upstream' | 'both';\n maxDepth?: number;\n}\n\nexport interface NodesResult {\n nodes: NodeRow[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport interface HealthResult {\n store: 'sqlite';\n sessions: number;\n}\n\n/** A requested resource (session / diff endpoint) does not exist for this tenant → REST 404. */\nexport class NotFoundError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'NotFoundError';\n }\n}\n\n/** Narrow, read-only view of the topology store. Tenant is required on every call. */\nexport interface QueryBackend {\n /** Aggregate, low-token index of the resolved session. Throws {@link NotFoundError} if no session resolves. */\n summary(ctx: TenantContext, sessionId?: string): GraphSummary;\n /** Page/search nodes of the resolved session. Throws {@link NotFoundError} if no session resolves. */\n nodes(ctx: TenantContext, q: NodeQuery, sessionId?: string): NodesResult;\n /** One node by id (or `undefined` if absent). Throws {@link NotFoundError} if no session resolves. */\n node(ctx: TenantContext, id: string, sessionId?: string): NodeRow | undefined;\n /** All edges of the resolved session (for full-topology consumers, e.g. the Backstage catalog). Throws {@link NotFoundError} if no session resolves. */\n edges(ctx: TenantContext, sessionId?: string): EdgeRow[];\n /** Dependency traversal from a node. Throws {@link NotFoundError} if no session resolves. */\n dependencies(ctx: TenantContext, id: string, q: DependencyQuery, sessionId?: string): TraversalResult;\n /** Compare two sessions (both must belong to the tenant). Throws {@link NotFoundError} on an unknown/foreign id. */\n diff(ctx: TenantContext, base: string, current: string): TopologyDiff;\n /** All sessions for this tenant, newest first. */\n sessions(ctx: TenantContext): SessionRow[];\n /** Liveness/coverage probe (never resolves a session). */\n health(ctx: TenantContext): HealthResult;\n}\n\n/** Hard cap on a single page of nodes; mirrors the API edge clamp. */\nconst MAX_NODE_LIMIT = 1000;\n/** Hard cap on traversal depth; mirrors `getDependencies` (src/db.ts) and the API edge clamp. */\nconst MAX_DEPTH = 64;\n\n/** Clamp to [min, max] and floor — a fractional limit/offset/depth would break the SQL/contract. */\nfunction clamp(value: number, min: number, max: number): number {\n return Math.floor(Math.max(min, Math.min(value, max)));\n}\n\n/**\n * `QueryBackend` over the local `CartographyDB`. A thin read adapter: the schema,\n * migrations, and SQL all live in `db.ts`; this only resolves the tenant-scoped\n * session and forwards. Constructing it adds no state and no schema.\n */\nexport class SqliteQueryBackend implements QueryBackend {\n constructor(\n private readonly db: CartographyDB,\n private readonly defaultSession: string | 'latest' = 'latest',\n ) {}\n\n /**\n * Resolve the session id for a request, scoped to `ctx.tenant`. An explicit id must\n * belong to the tenant or it resolves to undefined (cross-tenant isolation); else the\n * newest `discover` session for the tenant. Mirrors `resolveSession` in the MCP server.\n */\n private resolveSession(ctx: TenantContext, sessionId?: string): string {\n const requested = sessionId ?? (this.defaultSession === 'latest' ? undefined : this.defaultSession);\n if (requested) {\n const s = this.db.getSession(requested);\n if (s && s.tenant === ctx.tenant) return s.id;\n throw new NotFoundError(`session not found`);\n }\n const latest = this.db.getLatestSession('discover', ctx.tenant) ?? this.db.getLatestSession(undefined, ctx.tenant);\n if (!latest) throw new NotFoundError(`no session available`);\n return latest.id;\n }\n\n summary(ctx: TenantContext, sessionId?: string): GraphSummary {\n return this.db.getGraphSummary(this.resolveSession(ctx, sessionId));\n }\n\n nodes(ctx: TenantContext, q: NodeQuery, sessionId?: string): NodesResult {\n const sid = this.resolveSession(ctx, sessionId);\n const limit = clamp(q.limit ?? 100, 1, MAX_NODE_LIMIT);\n const offset = Math.floor(Math.max(0, q.offset ?? 0));\n const total = this.db.getNodeCount(sid);\n if (q.search) {\n const nodes = this.db.searchNodes(sid, q.search, { ...(q.types ? { types: q.types } : {}), limit });\n return { nodes, total: nodes.length, limit, offset: 0 };\n }\n const nodes = this.db.getNodes(sid, { limit, offset });\n return { nodes, total, limit, offset };\n }\n\n node(ctx: TenantContext, id: string, sessionId?: string): NodeRow | undefined {\n return this.db.getNode(this.resolveSession(ctx, sessionId), id);\n }\n\n edges(ctx: TenantContext, sessionId?: string): EdgeRow[] {\n return this.db.getEdges(this.resolveSession(ctx, sessionId));\n }\n\n dependencies(ctx: TenantContext, id: string, q: DependencyQuery, sessionId?: string): TraversalResult {\n const sid = this.resolveSession(ctx, sessionId);\n return this.db.getDependencies(sid, id, {\n direction: q.direction ?? 'downstream',\n maxDepth: clamp(q.maxDepth ?? 8, 1, MAX_DEPTH),\n });\n }\n\n diff(ctx: TenantContext, base: string, current: string): TopologyDiff {\n // Tenant-isolate both endpoints before touching the diff SQL: a foreign session id is 404, not data.\n for (const id of [base, current]) {\n const s = this.db.getSession(id);\n if (!s || s.tenant !== ctx.tenant) throw new NotFoundError(`session not found`);\n }\n try {\n return this.db.diffSessions(base, current);\n } catch (err) {\n throw new NotFoundError(err instanceof Error ? err.message : 'diff failed');\n }\n }\n\n sessions(ctx: TenantContext): SessionRow[] {\n return this.db.getSessions(ctx.tenant);\n }\n\n health(ctx: TenantContext): HealthResult {\n return { store: 'sqlite', sessions: this.db.getSessions(ctx.tenant).length };\n }\n}\n\n/** Construct the default SQLite-backed read query backend. */\nexport function createSqliteQueryBackend(db: CartographyDB, defaultSession: string | 'latest' = 'latest'): QueryBackend {\n return new SqliteQueryBackend(db, defaultSession);\n}\n","/**\n * The read-only API HTTP server (4.2), on Node's built-in `http` (zero new runtime dep).\n *\n * Request flow mirrors the MCP transport (`src/mcp/transports.ts`): the CVE-2025-66414\n * bind guards run at startup (shared `assertSafeBind`); per request the Host header is\n * checked against the allowlist (DNS-rebinding), then the bearer token is verified\n * **before any backend access**, then the tenant is resolved, then the route dispatches.\n * REST handlers are pure (`rest.ts`); GraphQL is wired when enabled (`graphql.ts`). One\n * structured stderr access line per request — never the token, never query values.\n */\n\nimport http, { type IncomingMessage, type ServerResponse } from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport { DEFAULT_TENANT } from '../db.js';\nimport { assertSafeBind, bearerToken, defaultAllowedHosts, type BindGuardOptions } from './auth.js';\nimport { resolveTenant, InvalidTenantError, type TenantOptions } from './tenant.js';\nimport { resolvePrincipal } from '../auth/identity.js';\nimport { authorize, AuthorizationError } from '../auth/rbac.js';\nimport type { CredentialStore } from '../auth/types.js';\nimport type { QueryBackend } from '../store/query.js';\nimport type { RestDeps } from './rest.js';\nimport { handleSummary, handleNodes, handleDependencies, handleDiff, handleSessions, handleHealth, handleBackstageCatalog } from './rest.js';\nimport { buildOpenApiDocument } from './openapi.js';\nimport { executeGraphql, handleGraphqlGet } from './graphql.js';\nimport { dashboardHtml } from '../web/dashboard.js';\n\nexport interface ApiServerOptions extends BindGuardOptions {\n backend: QueryBackend;\n version: string;\n /** CORS Origin allowlist. Default: none (same-origin only). */\n allowedOrigins?: string[];\n /** Tenant resolution options (header name / default tenant). */\n tenant?: TenantOptions;\n /** Expose `/graphql` (default true). */\n graphql?: boolean;\n /** Serve the web dashboard at `/` and `/app` (default true; 4.1). */\n dashboard?: boolean;\n /**\n * RBAC (4.5). When `store` holds credentials, the API runs in RBAC mode: a request's\n * bearer token must resolve to a {@link Principal} (else 401), the principal's role must\n * permit `read` (else 403), and reads are **pinned to the principal's tenant** (any\n * caller-supplied tenant header/param is ignored). Without a populated store the legacy\n * behavior is preserved: the configured shared `token` (or open loopback) is one implicit\n * admin that may still select a tenant via header/param.\n */\n auth?: { store?: CredentialStore; required?: boolean };\n /** Access logger (stderr). */\n log?: (msg: string) => void;\n}\n\nconst DEPENDENCIES_RE = /^\\/v1\\/nodes\\/(.+)\\/dependencies$/;\nconst MAX_GRAPHQL_BYTES = 1024 * 1024; // 1 MB query body cap\n\nfunction send(res: ServerResponse, status: number, body: unknown, headers: Record<string, string> = {}): void {\n res.writeHead(status, { 'content-type': 'application/json', ...headers }).end(JSON.stringify(body));\n}\n\nasync function readBody(req: IncomingMessage, cap: number): Promise<{ overflow: boolean; value: unknown }> {\n const chunks: Buffer[] = [];\n let total = 0;\n let overflow = false;\n for await (const chunk of req) {\n if (overflow) continue;\n const buf = chunk as Buffer;\n total += buf.length;\n if (total > cap) { overflow = true; chunks.length = 0; continue; }\n chunks.push(buf);\n }\n if (overflow) return { overflow: true, value: undefined };\n if (chunks.length === 0) return { overflow: false, value: undefined };\n try { return { overflow: false, value: JSON.parse(Buffer.concat(chunks).toString('utf8')) }; }\n catch { return { overflow: false, value: undefined }; }\n}\n\n/** Start the read-only API server. Resolves once it is listening. */\nexport async function runApi(opts: ApiServerOptions): Promise<http.Server> {\n const host = opts.host ?? '127.0.0.1';\n const requestedPort = opts.port ?? 3737;\n const token = opts.token;\n const graphqlEnabled = opts.graphql !== false;\n const defaultTenant = opts.tenant?.defaultTenant ?? DEFAULT_TENANT;\n const authStore = opts.auth?.store;\n // RBAC mode is active only when real credentials exist; otherwise the legacy\n // shared-token / open-dev behavior (and tenant-by-header) is preserved.\n const rbacMode = !!(authStore && authStore.count() > 0);\n const log = opts.log ?? (() => {});\n const restDeps: RestDeps = { backend: opts.backend, version: opts.version };\n const openApiDoc = buildOpenApiDocument({ version: opts.version });\n const dashboardEnabled = opts.dashboard !== false;\n const dashboardPage = dashboardEnabled ? dashboardHtml({ version: opts.version }) : '';\n const allowedOrigins = opts.allowedOrigins ?? [];\n\n // CVE-2025-66414 + mandatory-token guards (shared with the MCP transport).\n assertSafeBind({ host, port: requestedPort, ...(opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {}), ...(token ? { token } : {}) });\n\n // Filled in after listen() so port:0 (ephemeral) still produces a correct Host allowlist.\n let allowedHosts: string[] = opts.allowedHosts ?? [];\n\n const corsHeaders = (req: IncomingMessage): Record<string, string> => {\n const origin = req.headers['origin'];\n if (typeof origin === 'string' && allowedOrigins.includes(origin)) {\n return {\n 'access-control-allow-origin': origin,\n 'vary': 'Origin',\n 'access-control-allow-methods': 'GET, POST, OPTIONS',\n 'access-control-allow-headers': 'authorization, content-type, x-cartograph-tenant',\n };\n }\n return {};\n };\n\n const server = http.createServer((req, res) => {\n const started = Date.now();\n let tenantLabel = '-';\n const finish = (status: number): void => {\n log(`[cartography-api] ${req.method ?? '-'} ${req.url ?? '-'} ${status} ${Date.now() - started}ms tenant=${tenantLabel}`);\n };\n void (async () => {\n try {\n const url = new URL(req.url ?? '/', `http://${req.headers['host'] ?? host}`);\n const path = url.pathname;\n const cors = corsHeaders(req);\n\n // CORS preflight.\n if (req.method === 'OPTIONS') { res.writeHead(204, cors).end(); finish(204); return; }\n\n // DNS-rebinding protection: the Host header must be on the allowlist.\n const hostHeader = (req.headers['host'] ?? '').toLowerCase();\n if (!allowedHosts.some((h) => h.toLowerCase() === hostHeader)) { send(res, 403, { error: 'host not allowed' }, cors); finish(403); return; }\n\n // Public endpoints — no auth and, crucially, NO caller-supplied tenant: health uses the\n // server's default tenant so an unauthenticated probe can't enumerate other tenants' session\n // counts (`?tenant=acme` oracle). They are also handled before tenant resolution so a probe\n // with a malformed tenant header still succeeds.\n if (path === '/v1/openapi.json' && req.method === 'GET') { send(res, 200, openApiDoc, cors); finish(200); return; }\n // Web dashboard (4.1) — the page SHELL is public (its `/v1` data fetches are\n // gated by the bearer/RBAC like any other client). Served at `/` and `/app`.\n if (dashboardEnabled && (path === '/' || path === '/app') && req.method === 'GET') {\n res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', ...cors }).end(dashboardPage);\n finish(200);\n return;\n }\n if (path === '/v1/health') {\n if (req.method !== 'GET') { send(res, 405, { error: 'method not allowed' }, { allow: 'GET', ...cors }); finish(405); return; }\n tenantLabel = defaultTenant;\n const r = handleHealth({ tenant: defaultTenant }, restDeps);\n send(res, r.status, r.body, cors);\n finish(r.status);\n return;\n }\n\n // Authenticate (RBAC 4.5) before touching any backend state. Resolve the bearer\n // token to a principal; 401 if it doesn't resolve (RBAC unknown token / wrong\n // shared token / required-but-absent).\n const principal = resolvePrincipal(bearerToken(req.headers['authorization']), {\n ...(authStore ? { store: authStore } : {}),\n ...(token ? { sharedToken: token } : {}),\n defaultTenant,\n ...(opts.auth?.required ? { required: true } : {}),\n });\n if (!principal) {\n send(res, 401, { error: 'unauthorized' }, { 'www-authenticate': 'Bearer', ...cors });\n finish(401);\n return;\n }\n // Every API endpoint is a read; a principal below `viewer` cannot exist, so this\n // only ever fails if a future write endpoint demands a higher role.\n try {\n authorize(principal, 'read');\n } catch (err) {\n if (err instanceof AuthorizationError) { send(res, 403, { error: 'forbidden' }, cors); finish(403); return; }\n throw err;\n }\n\n // Tenant: in RBAC mode pin to the principal's tenant (ignore any caller-supplied\n // tenant — no cross-tenant read by header spoofing). Otherwise (shared-token / open\n // dev) preserve the legacy behavior: the implicit admin may select a tenant.\n let ctx;\n if (rbacMode) {\n ctx = { tenant: principal.tenant };\n tenantLabel = principal.tenant;\n } else {\n try {\n ctx = resolveTenant(req, url, opts.tenant ?? {});\n tenantLabel = ctx.tenant;\n } catch (err) {\n if (err instanceof InvalidTenantError) { send(res, 400, { error: 'invalid tenant' }, cors); finish(400); return; }\n throw err;\n }\n }\n\n // ── GraphQL ──\n if (graphqlEnabled && path === '/graphql') {\n if (req.method === 'GET') { const g = handleGraphqlGet(); res.writeHead(g.status, { 'content-type': 'text/plain; charset=utf-8', ...cors }).end(g.body); finish(g.status); return; }\n if (req.method === 'POST') {\n const { overflow, value } = await readBody(req, MAX_GRAPHQL_BYTES);\n if (overflow) { send(res, 413, { error: 'payload too large' }, cors); finish(413); return; }\n const result = await executeGraphql(ctx, value, { backend: opts.backend });\n send(res, 200, result, cors);\n finish(200);\n return;\n }\n send(res, 405, { error: 'method not allowed' }, { allow: 'GET, POST', ...cors });\n finish(405);\n return;\n }\n\n // ── REST (GET only; /v1/health + /v1/openapi.json already handled above) ──\n if (path.startsWith('/v1/')) {\n if (req.method !== 'GET') { send(res, 405, { error: 'method not allowed' }, { allow: 'GET', ...cors }); finish(405); return; }\n const result = dispatchRest(ctx, path, url, restDeps);\n if (result) { send(res, result.status, result.body, cors); finish(result.status); return; }\n }\n\n send(res, 404, { error: 'not found' }, cors);\n finish(404);\n } catch (err) {\n process.stderr.write(`[cartography-api] request failed: ${err instanceof Error ? err.message : String(err)}\\n`);\n if (!res.headersSent) send(res, 500, { error: 'internal error' });\n finish(500);\n }\n })();\n });\n\n await new Promise<void>((resolve) => server.listen(requestedPort, host, resolve));\n const actualPort = (server.address() as AddressInfo).port;\n if (allowedHosts.length === 0) allowedHosts = defaultAllowedHosts(host, actualPort);\n return server;\n}\n\n/** Route a `/v1/...` GET to its REST handler. Returns `undefined` for an unknown path (→ 404). */\nfunction dispatchRest(ctx: { tenant: string }, path: string, url: URL, deps: RestDeps): { status: number; body: unknown } | undefined {\n switch (path) {\n case '/v1/summary': return handleSummary(ctx, url, deps);\n case '/v1/nodes': return handleNodes(ctx, url, deps);\n case '/v1/diff': return handleDiff(ctx, url, deps);\n case '/v1/sessions': return handleSessions(ctx, deps);\n case '/v1/backstage/catalog': return handleBackstageCatalog(ctx, deps);\n default: {\n const m = DEPENDENCIES_RE.exec(path);\n if (m) return handleDependencies(ctx, decodeURIComponent(m[1]), url, deps);\n return undefined;\n }\n }\n}\n","/**\n * Per-request tenant resolution for the API server (4.2).\n *\n * The tenant (org-scope) is a first-class request property: it is resolved once,\n * up front, and threaded into every {@link QueryBackend} call so isolation is\n * structural, not bolted on. A request may name a tenant via the\n * `X-Cartograph-Tenant` header or a `?tenant=` query param; absent either, it\n * defaults to the server's configured default (normally `DEFAULT_TENANT='local'`).\n *\n * Validation reuses `normalizeTenant` (the single charset-allowlisted validator,\n * `^[\\w.@:+-]{1,128}$`) — but here we **reject** a malformed value with a typed\n * error (→ HTTP 400) rather than silently falling back, so a client never believes\n * it is scoped to one tenant while being served another. The raw input is never\n * reflected into a response.\n */\n\nimport type { IncomingMessage } from 'node:http';\nimport { normalizeTenant, DEFAULT_TENANT } from '../db.js';\nimport type { TenantContext } from '../store/query.js';\n\nexport const TENANT_HEADER = 'x-cartograph-tenant';\n\n/** The supplied tenant value did not pass the charset/length allowlist → HTTP 400. */\nexport class InvalidTenantError extends Error {\n constructor() {\n super('invalid tenant');\n this.name = 'InvalidTenantError';\n }\n}\n\nexport interface TenantOptions {\n /** Default tenant when the request names none. Defaults to `DEFAULT_TENANT` ('local'). */\n defaultTenant?: string;\n /** Header to read the tenant from. Defaults to `x-cartograph-tenant`. */\n header?: string;\n}\n\n/**\n * Resolve the tenant from the request header or `?tenant=` query param, else the\n * configured default. A supplied-but-malformed value throws {@link InvalidTenantError}\n * (the caller maps it to a 400) instead of silently defaulting.\n */\nexport function resolveTenant(req: IncomingMessage, url: URL, opts: TenantOptions = {}): TenantContext {\n const headerName = (opts.header ?? TENANT_HEADER).toLowerCase();\n const raw = headerValue(req, headerName) ?? url.searchParams.get('tenant') ?? undefined;\n\n if (raw === undefined || raw === '') {\n return { tenant: opts.defaultTenant ?? DEFAULT_TENANT };\n }\n\n // Reject over-length up front: normalizeTenant *truncates* to 128 chars before validating,\n // so without this two distinct long tenant names would collide on a shared 128-char prefix\n // and be silently served the same partition. The doc promise is \"never silently re-scoped\".\n if (raw.trim().length > 128) {\n throw new InvalidTenantError();\n }\n\n // normalizeTenant returns DEFAULT_TENANT for anything that fails the allowlist. We\n // must distinguish \"client legitimately asked for the default tenant\" from \"client\n // sent garbage\", so we re-validate: a non-default raw value that normalizes to the\n // default was malformed → 400.\n const normalized = normalizeTenant(raw);\n if (normalized === DEFAULT_TENANT && raw.trim() !== DEFAULT_TENANT) {\n throw new InvalidTenantError();\n }\n return { tenant: normalized };\n}\n\nfunction headerValue(req: IncomingMessage, name: string): string | undefined {\n const v = req.headers[name];\n if (Array.isArray(v)) return v[0];\n return v;\n}\n","/**\n * Backstage catalog entity mapping (4.6).\n *\n * A dependency-free, transport-agnostic mapper: `toBackstageEntities` turns the\n * discovered topology into plain typed Backstage entity objects, and `entitiesToYaml`\n * serializes them to the multi-doc `catalog-info.yaml` format. It NEVER imports\n * `@backstage/*` — Backstage stays an optional adapter, never a core dependency\n * (ROADMAP locked constraints). The legacy `exportBackstageYAML` is re-expressed over\n * this mapper and stays byte-identical (snapshot-guarded). The same typed entities are\n * served live over the API (`GET /v1/backstage/catalog`) so a Backstage instance can\n * consume the topology as a continuously-refreshed data source.\n */\n\nimport type { NodeRow, EdgeRow } from './types.js';\n\n/** Node types that map to a Backstage `Component` (the rest → `API` or `Resource`). */\nconst COMPONENT_TYPES = ['web_service', 'container', 'pod'];\n\n/** Sanitize an id into a Backstage-entity-name-safe token (shared with the Mermaid exporters). */\nfunction sanitize(id: string): string {\n return id.replace(/[^a-zA-Z0-9_]/g, '_');\n}\n\nexport interface BackstageEntity {\n apiVersion: 'backstage.io/v1alpha1';\n kind: 'Component' | 'API' | 'Resource';\n metadata: {\n name: string;\n annotations: Record<string, string>;\n };\n spec: {\n type: string;\n lifecycle: string;\n owner: string;\n dependsOn?: string[];\n };\n}\n\nexport interface BackstageMapOptions {\n /** Default owner when a node carries none (the org/tenant). */\n org?: string;\n}\n\n/** Map discovered nodes/edges to typed Backstage catalog entities. Pure, deterministic. */\nexport function toBackstageEntities(nodes: NodeRow[], edges: EdgeRow[], opts: BackstageMapOptions = {}): BackstageEntity[] {\n const owner = opts.org ?? 'unknown';\n return nodes.map((node) => {\n const kind: BackstageEntity['kind'] = COMPONENT_TYPES.includes(node.type)\n ? 'Component'\n : node.type === 'api_endpoint'\n ? 'API'\n : 'Resource';\n const dependsOn = edges.filter((e) => e.sourceId === node.id).map((e) => `resource:default/${sanitize(e.targetId)}`);\n return {\n apiVersion: 'backstage.io/v1alpha1',\n kind,\n metadata: {\n name: sanitize(node.id),\n annotations: {\n 'cartography/discovered-at': node.discoveredAt,\n 'cartography/confidence': String(node.confidence),\n },\n },\n spec: {\n type: node.type,\n lifecycle: 'production',\n owner: node.owner ?? owner,\n ...(dependsOn.length > 0 ? { dependsOn } : {}),\n },\n };\n });\n}\n\n/** Serialize entities to the multi-doc `catalog-info.yaml` string (byte-identical to the legacy exporter). */\nexport function entitiesToYaml(entities: BackstageEntity[]): string {\n return entities\n .map((e) => {\n const lines = [\n `apiVersion: ${e.apiVersion}`,\n `kind: ${e.kind}`,\n `metadata:`,\n ` name: ${e.metadata.name}`,\n ` annotations:`,\n ...Object.entries(e.metadata.annotations).map(([k, v]) => ` ${k}: \"${v}\"`),\n `spec:`,\n ` type: ${e.spec.type}`,\n ` lifecycle: ${e.spec.lifecycle}`,\n ` owner: ${e.spec.owner}`,\n ...(e.spec.dependsOn && e.spec.dependsOn.length > 0\n ? [' dependsOn:', ...e.spec.dependsOn.map((d) => ` - ${d}`)]\n : []),\n ];\n return lines.join('\\n');\n })\n .join('\\n---\\n');\n}\n","/**\n * Response contracts for the read-only API (4.2), as zod schemas.\n *\n * These are the **single source of truth**: the OpenAPI document and the GraphQL SDL\n * are generated from them (`openapi.ts`/`graphql.ts`), and the REST handlers validate\n * their outgoing bodies against them in non-production (a cheap correctness guard). The\n * projections are deliberately narrower than the internal row types — they exclude\n * session attribution (`hostname`/`user`/`machineId`/`organization`/`config`) and node\n * `metadata`/`globalId`/`contentHash`, so the API never leaks raw identifying data\n * (consent posture, SPEC §6). The node model itself is already host:port-level and\n * redacted at ingest.\n */\n\nimport { z } from 'zod';\nimport { COST_PERIODS, ANOMALY_KINDS, ANOMALY_SEVERITIES } from '../types.js';\n\nexport const DIRECTIONS = ['downstream', 'upstream', 'both'] as const;\n\nconst CostSchema = z.object({\n amount: z.number(),\n currency: z.string(),\n period: z.enum(COST_PERIODS),\n source: z.string().optional(),\n});\n\n/** Public node projection (no metadata/globalId/contentHash/session internals). */\nexport const NodeSchema = z.object({\n id: z.string(),\n type: z.string(),\n name: z.string(),\n confidence: z.number(),\n domain: z.string().optional(),\n subDomain: z.string().optional(),\n qualityScore: z.number().optional(),\n owner: z.string().optional(),\n cost: CostSchema.optional(),\n tags: z.array(z.string()),\n});\n\nexport const EdgeSchema = z.object({\n sourceId: z.string(),\n targetId: z.string(),\n relationship: z.string(),\n confidence: z.number(),\n evidence: z.string(),\n});\n\nexport const AnomalySchema = z.object({\n nodeId: z.string(),\n kind: z.enum(ANOMALY_KINDS),\n severity: z.enum(ANOMALY_SEVERITIES),\n reason: z.string(),\n});\n\nconst TopConnectedSchema = z.object({\n id: z.string(),\n name: z.string(),\n type: z.string(),\n degree: z.number().int(),\n});\n\nconst CostByDomainSchema = z.object({\n domain: z.string(),\n currency: z.string(),\n period: z.string(),\n total: z.number(),\n nodes: z.number().int(),\n});\n\nconst CostByOwnerSchema = z.object({\n owner: z.string(),\n currency: z.string(),\n period: z.string(),\n total: z.number(),\n nodes: z.number().int(),\n});\n\nexport const SummaryResponse = z.object({\n sessionId: z.string(),\n totals: z.object({ nodes: z.number().int(), edges: z.number().int() }),\n nodesByType: z.record(z.string(), z.number().int()),\n nodesByDomain: z.record(z.string(), z.number().int()),\n edgesByRelationship: z.record(z.string(), z.number().int()),\n topConnected: z.array(TopConnectedSchema),\n anomalies: z.array(AnomalySchema),\n contributors: z.number().int(),\n costByDomain: z.array(CostByDomainSchema),\n costByOwner: z.array(CostByOwnerSchema),\n costCoverage: z.object({ withCost: z.number().int(), total: z.number().int() }),\n});\n\nexport const NodesResponse = z.object({\n nodes: z.array(NodeSchema),\n total: z.number().int(),\n limit: z.number().int(),\n offset: z.number().int(),\n});\n\nconst DependencyNodeSchema = NodeSchema.extend({ depth: z.number().int() });\n\nexport const DependenciesResponse = z.object({\n root: NodeSchema.optional(),\n direction: z.enum(DIRECTIONS),\n maxDepth: z.number().int(),\n nodes: z.array(DependencyNodeSchema),\n edges: z.array(EdgeSchema),\n});\n\nconst SessionEndpointSchema = z.object({\n sessionId: z.string(),\n startedAt: z.string(),\n nodeCount: z.number().int(),\n edgeCount: z.number().int(),\n});\n\nconst NodeChangeSchema = z.object({\n id: z.string(),\n changedFields: z.array(z.string()),\n confidenceDelta: z.number(),\n});\n\nexport const DiffResponse = z.object({\n base: SessionEndpointSchema,\n current: SessionEndpointSchema,\n summary: z.object({\n nodesAdded: z.number().int(),\n nodesRemoved: z.number().int(),\n nodesChanged: z.number().int(),\n edgesAdded: z.number().int(),\n edgesRemoved: z.number().int(),\n }),\n nodes: z.object({\n added: z.array(NodeSchema),\n removed: z.array(NodeSchema),\n changed: z.array(NodeChangeSchema),\n unchanged: z.number().int(),\n }),\n edges: z.object({\n added: z.array(EdgeSchema),\n removed: z.array(EdgeSchema),\n unchanged: z.number().int(),\n }),\n anomalies: z.object({ added: z.array(AnomalySchema) }),\n});\n\nexport const SessionSchema = z.object({\n id: z.string(),\n mode: z.literal('discover'),\n startedAt: z.string(),\n completedAt: z.string().optional(),\n name: z.string().optional(),\n tenant: z.string(),\n lastScannedAt: z.string().optional(),\n});\n\nexport const SessionsResponse = z.object({ sessions: z.array(SessionSchema) });\n\nexport const HealthResponse = z.object({\n status: z.literal('ok'),\n version: z.string(),\n store: z.literal('sqlite'),\n sessions: z.number().int(),\n});\n\nexport const ErrorResponse = z.object({\n error: z.string(),\n code: z.string().optional(),\n});\n\n/** A Backstage catalog entity (4.6) — mirrors `BackstageEntity` in src/backstage.ts. */\nexport const BackstageEntitySchema = z.object({\n apiVersion: z.literal('backstage.io/v1alpha1'),\n kind: z.enum(['Component', 'API', 'Resource']),\n metadata: z.object({\n name: z.string(),\n annotations: z.record(z.string(), z.string()),\n }),\n spec: z.object({\n type: z.string(),\n lifecycle: z.string(),\n owner: z.string(),\n dependsOn: z.array(z.string()).optional(),\n }),\n});\n\nexport const BackstageCatalogResponse = z.object({ entities: z.array(BackstageEntitySchema) });\n\n/** Named registry of every response schema — drives the OpenAPI `components.schemas`. */\nexport const API_SCHEMAS = {\n Node: NodeSchema,\n Edge: EdgeSchema,\n Anomaly: AnomalySchema,\n Summary: SummaryResponse,\n Nodes: NodesResponse,\n Dependencies: DependenciesResponse,\n Diff: DiffResponse,\n Session: SessionSchema,\n Sessions: SessionsResponse,\n Health: HealthResponse,\n Error: ErrorResponse,\n BackstageEntity: BackstageEntitySchema,\n BackstageCatalog: BackstageCatalogResponse,\n} as const;\n","/**\n * REST handlers for the read-only API (4.2).\n *\n * Pure functions of `(ctx, url, deps)` → `{ status, body }` with **no `http` coupling**,\n * so they are unit-testable without a socket and reusable by the GraphQL resolvers. Each\n * projects the internal row/summary types down to the public `schemas.ts` shape (dropping\n * session attribution + node metadata — consent posture), clamps pagination/depth at the\n * edge, and validates its own outgoing body against the matching zod schema in\n * non-production (a cheap contract guard that surfaces drift loudly in tests).\n */\n\nimport { z } from 'zod';\nimport type { NodeRow, EdgeRow, SessionRow, TopologyDiff } from '../types.js';\nimport type { Anomaly } from '../types.js';\nimport type { QueryBackend, TenantContext } from '../store/query.js';\nimport { NotFoundError } from '../store/query.js';\nimport { toBackstageEntities } from '../backstage.js';\nimport {\n SummaryResponse, NodesResponse, DependenciesResponse, DiffResponse, SessionsResponse, HealthResponse,\n BackstageCatalogResponse, DIRECTIONS,\n} from './schemas.js';\n\n/** Max nodes included in the Backstage catalog (one page; matches the node pagination cap). */\nconst BACKSTAGE_NODE_CAP = 1000;\n\nexport interface RestDeps {\n backend: QueryBackend;\n version: string;\n}\n\nexport interface RestResult {\n status: number;\n body: unknown;\n}\n\n// ── projections (internal row → public API shape) ──────────────────────────────\n// Exported so the GraphQL resolvers (graphql.ts) reuse the exact same projection,\n// keeping REST and GraphQL byte-identical and the consent posture in one place.\n\nexport function toApiNode(n: NodeRow): Record<string, unknown> {\n const out: Record<string, unknown> = { id: n.id, type: n.type, name: n.name, confidence: n.confidence, tags: n.tags };\n if (n.domain !== undefined) out['domain'] = n.domain;\n if (n.subDomain !== undefined) out['subDomain'] = n.subDomain;\n if (n.qualityScore !== undefined) out['qualityScore'] = n.qualityScore;\n if (n.owner !== undefined) out['owner'] = n.owner;\n if (n.cost !== undefined) out['cost'] = n.cost;\n return out;\n}\n\nexport function toApiEdge(e: EdgeRow): Record<string, unknown> {\n return { sourceId: e.sourceId, targetId: e.targetId, relationship: e.relationship, confidence: e.confidence, evidence: e.evidence };\n}\n\nexport function toApiSession(s: SessionRow): Record<string, unknown> {\n const out: Record<string, unknown> = { id: s.id, mode: s.mode, startedAt: s.startedAt, tenant: s.tenant };\n if (s.completedAt !== undefined) out['completedAt'] = s.completedAt;\n if (s.name !== undefined) out['name'] = s.name;\n if (s.lastScannedAt !== undefined) out['lastScannedAt'] = s.lastScannedAt;\n return out;\n}\n\nexport function toApiAnomaly(a: Anomaly): Record<string, unknown> {\n return { nodeId: a.nodeId, kind: a.kind, severity: a.severity, reason: a.reason };\n}\n\n/** Project a `TraversalResult` to the public Dependencies shape (shared by REST + GraphQL). */\nexport function projectDependencies(r: { root?: NodeRow; direction: string; maxDepth: number; nodes: Array<NodeRow & { depth: number }>; edges: EdgeRow[] }): Record<string, unknown> {\n return {\n ...(r.root ? { root: toApiNode(r.root) } : {}),\n direction: r.direction,\n maxDepth: r.maxDepth,\n nodes: r.nodes.map((n) => ({ ...toApiNode(n), depth: n.depth })),\n edges: r.edges.map(toApiEdge),\n };\n}\n\n/** Project a `TopologyDiff` to the public Diff shape (shared by REST + GraphQL). */\nexport function projectDiff(diff: TopologyDiff): Record<string, unknown> {\n return {\n base: { sessionId: diff.base.sessionId, startedAt: diff.base.startedAt, nodeCount: diff.base.nodeCount, edgeCount: diff.base.edgeCount },\n current: { sessionId: diff.current.sessionId, startedAt: diff.current.startedAt, nodeCount: diff.current.nodeCount, edgeCount: diff.current.edgeCount },\n summary: diff.summary,\n nodes: {\n added: diff.nodes.added.map(toApiNode),\n removed: diff.nodes.removed.map(toApiNode),\n changed: diff.nodes.changed.map((c) => ({ id: c.id, changedFields: c.changedFields, confidenceDelta: c.confidenceDelta })),\n unchanged: diff.nodes.unchanged,\n },\n edges: {\n added: diff.edges.added.map(toApiEdge),\n removed: diff.edges.removed.map(toApiEdge),\n unchanged: diff.edges.unchanged,\n },\n anomalies: { added: diff.anomalies.added.map(toApiAnomaly) },\n };\n}\n\n// ── helpers ────────────────────────────────────────────────────────────────────\n\nfunction ok(body: unknown): RestResult {\n return { status: 200, body };\n}\nfunction badRequest(error: string): RestResult {\n return { status: 400, body: { error } };\n}\nfunction notFound(error = 'not found'): RestResult {\n return { status: 404, body: { error } };\n}\n\n/** Run a handler body, mapping a thrown {@link NotFoundError} to a 404. */\nfunction guard(fn: () => RestResult): RestResult {\n try {\n return fn();\n } catch (err) {\n if (err instanceof NotFoundError) return notFound(err.message);\n throw err;\n }\n}\n\n/** In non-production, assert the outgoing body matches its declared schema (contract guard). */\nfunction validateOut(schema: z.ZodType, body: unknown): unknown {\n if (process.env['NODE_ENV'] !== 'production') {\n const r = schema.safeParse(body);\n if (!r.success) throw new Error(`API response failed its own schema contract: ${r.error.message}`);\n }\n return body;\n}\n\n/** Parse an integer query param. A non-integer (float, NaN, junk) yields undefined → the\n * backend default applies, never a fractional value that would 500 in SQLite (`LIMIT 2.5`). */\nfunction intParam(url: URL, name: string): number | undefined {\n const raw = url.searchParams.get(name);\n if (raw === null || raw.trim() === '') return undefined;\n const n = Number(raw);\n return Number.isInteger(n) ? n : undefined;\n}\n\nfunction sessionParam(url: URL): string | undefined {\n return url.searchParams.get('session') ?? undefined;\n}\n\n// ── handlers ─────────────────────────────────────────────────────────────────\n\nexport function handleSummary(ctx: TenantContext, url: URL, d: RestDeps): RestResult {\n return guard(() => ok(validateOut(SummaryResponse, d.backend.summary(ctx, sessionParam(url)))));\n}\n\nexport function handleNodes(ctx: TenantContext, url: URL, d: RestDeps): RestResult {\n return guard(() => {\n const search = url.searchParams.get('search') ?? undefined;\n const typesRaw = url.searchParams.get('types');\n const types = typesRaw ? typesRaw.split(',').map((s) => s.trim()).filter(Boolean) : undefined;\n const limit = intParam(url, 'limit');\n const offset = intParam(url, 'offset');\n const r = d.backend.nodes(\n ctx,\n { ...(search ? { search } : {}), ...(types ? { types } : {}), ...(limit !== undefined ? { limit } : {}), ...(offset !== undefined ? { offset } : {}) },\n sessionParam(url),\n );\n return ok(validateOut(NodesResponse, { nodes: r.nodes.map(toApiNode), total: r.total, limit: r.limit, offset: r.offset }));\n });\n}\n\nexport function handleDependencies(ctx: TenantContext, id: string, url: URL, d: RestDeps): RestResult {\n const directionRaw = url.searchParams.get('direction');\n if (directionRaw !== null && !(DIRECTIONS as readonly string[]).includes(directionRaw)) {\n return badRequest(`direction must be one of ${DIRECTIONS.join(', ')}`);\n }\n return guard(() => {\n const direction = (directionRaw ?? undefined) as 'downstream' | 'upstream' | 'both' | undefined;\n const maxDepth = intParam(url, 'maxDepth');\n const r = d.backend.dependencies(\n ctx,\n id,\n { ...(direction ? { direction } : {}), ...(maxDepth !== undefined ? { maxDepth } : {}) },\n sessionParam(url),\n );\n return ok(validateOut(DependenciesResponse, projectDependencies(r)));\n });\n}\n\nexport function handleDiff(ctx: TenantContext, url: URL, d: RestDeps): RestResult {\n const base = url.searchParams.get('base');\n const current = url.searchParams.get('current');\n if (!base || !current) return badRequest('both `base` and `current` query params are required');\n return guard(() => {\n const diff: TopologyDiff = d.backend.diff(ctx, base, current);\n return ok(validateOut(DiffResponse, projectDiff(diff)));\n });\n}\n\nexport function handleSessions(ctx: TenantContext, d: RestDeps): RestResult {\n return guard(() => ok(validateOut(SessionsResponse, { sessions: d.backend.sessions(ctx).map(toApiSession) })));\n}\n\nexport function handleHealth(ctx: TenantContext, d: RestDeps): RestResult {\n const h = d.backend.health(ctx);\n return ok(validateOut(HealthResponse, { status: 'ok', version: d.version, store: h.store, sessions: h.sessions }));\n}\n\n/**\n * The live Backstage catalog (4.6): the tenant's topology mapped to Backstage entities.\n * A Backstage `catalog.locations` entry of type `url` (or a community provider) points\n * here for a continuously-refreshed data source. Inherits the API's auth + tenant pinning.\n */\nexport function handleBackstageCatalog(ctx: TenantContext, d: RestDeps): RestResult {\n return guard(() => {\n const page = d.backend.nodes(ctx, { limit: BACKSTAGE_NODE_CAP });\n const edges = d.backend.edges(ctx);\n const entities = toBackstageEntities(page.nodes, edges, { org: ctx.tenant });\n return ok(validateOut(BackstageCatalogResponse, { entities }));\n });\n}\n","/**\n * OpenAPI 3.1 document generation for the read-only API (4.2).\n *\n * The document is **generated from the zod response schemas** (`schemas.ts`), never\n * hand-maintained, so it cannot drift from what the server actually returns. A\n * committed copy lives at `docs/api/openapi.json`; a test asserts the built document\n * deep-equals it (drift guard) and validates under `ajv`.\n *\n * `zodToJsonSchema` is a small, fail-closed projection covering exactly the zod\n * constructs `schemas.ts` uses (object/array/string/number/integer/boolean/enum/\n * literal/record/optional). An unsupported construct throws, so a future schema\n * change can't be silently mis-projected. (The provider tool layer has its own flat\n * converter in `src/providers/zod-schema.ts`; this one is recursive and serves the\n * API's nested response shapes.)\n */\n\nimport { z } from 'zod';\nimport { API_SCHEMAS } from './schemas.js';\n\n/** zod-internal view (v4): every schema exposes its definition under `.def`. */\ninterface ZodDef {\n type?: string;\n shape?: Record<string, z.ZodTypeAny>;\n element?: z.ZodTypeAny;\n valueType?: z.ZodTypeAny;\n innerType?: z.ZodTypeAny;\n values?: unknown[];\n entries?: Record<string, string>;\n checks?: { _zod?: { def?: { check?: string } } }[];\n}\n\nfunction defOf(schema: z.ZodTypeAny): ZodDef {\n return (schema as unknown as { def?: ZodDef }).def ?? {};\n}\n\n/** Peel an optional/nullable wrapper, reporting whether the field is required. */\nfunction unwrapOptional(schema: z.ZodTypeAny): { inner: z.ZodTypeAny; optional: boolean } {\n const def = defOf(schema);\n if ((def.type === 'optional' || def.type === 'nullable') && def.innerType) {\n return { inner: def.innerType, optional: true };\n }\n return { inner: schema, optional: false };\n}\n\n/** Project a zod schema to a JSON-Schema (2020-12) fragment. Fail-closed on the unknown. */\nexport function zodToJsonSchema(schema: z.ZodTypeAny): Record<string, unknown> {\n const def = defOf(schema);\n switch (def.type) {\n case 'string':\n return { type: 'string' };\n case 'number': {\n const isInt = (def.checks ?? []).some((c) => c._zod?.def?.check === 'number_format');\n return { type: isInt ? 'integer' : 'number' };\n }\n case 'boolean':\n return { type: 'boolean' };\n case 'literal': {\n const values = def.values ?? [];\n return values.length === 1 ? { const: values[0] } : { enum: values };\n }\n case 'enum':\n return { type: 'string', enum: Object.values(def.entries ?? {}) };\n case 'array':\n return { type: 'array', items: def.element ? zodToJsonSchema(def.element) : {} };\n case 'record':\n return { type: 'object', additionalProperties: def.valueType ? zodToJsonSchema(def.valueType) : true };\n case 'optional':\n case 'nullable':\n return def.innerType ? zodToJsonSchema(def.innerType) : {};\n case 'object': {\n const shape = def.shape ?? {};\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n for (const key of Object.keys(shape)) {\n const { inner, optional } = unwrapOptional(shape[key]);\n properties[key] = zodToJsonSchema(inner);\n if (!optional) required.push(key);\n }\n return { type: 'object', properties, required, additionalProperties: false };\n }\n default:\n throw new Error(`zodToJsonSchema: unsupported zod construct \"${def.type ?? 'unknown'}\". Extend src/api/openapi.ts.`);\n }\n}\n\nexport interface OpenApiOptions {\n version: string;\n}\n\nconst TENANT_PARAM = {\n name: 'tenant',\n in: 'query',\n required: false,\n description: 'Tenant/org scope (also accepted via the X-Cartograph-Tenant header). Defaults to \"local\".',\n schema: { type: 'string' },\n};\nconst SESSION_PARAM = {\n name: 'session',\n in: 'query',\n required: false,\n description: 'Session id to query, or omit for the latest discovery session.',\n schema: { type: 'string' },\n};\n\nfunction errorResponses(): Record<string, unknown> {\n const err = { description: 'Error', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } };\n return { '400': { ...err, description: 'Bad request' }, '401': { ...err, description: 'Unauthorized' }, '404': { ...err, description: 'Not found' } };\n}\n\nfunction ok(ref: string, description: string): Record<string, unknown> {\n return { description, content: { 'application/json': { schema: { $ref: `#/components/schemas/${ref}` } } } };\n}\n\n/** Build the OpenAPI 3.1 document from the zod schemas + the static route table. Deterministic. */\nexport function buildOpenApiDocument(opts: OpenApiOptions): Record<string, unknown> {\n const schemas: Record<string, unknown> = {};\n for (const [name, schema] of Object.entries(API_SCHEMAS)) {\n schemas[name] = zodToJsonSchema(schema as z.ZodTypeAny);\n }\n\n return {\n openapi: '3.1.0',\n info: {\n title: 'Cartograph API',\n version: opts.version,\n description: 'Read-only REST API over the discovered infrastructure/agentic-AI topology. ' +\n 'Every endpoint is tenant-scoped and bearer-authenticated.',\n },\n servers: [{ url: '/' }],\n security: [{ bearerAuth: [] }],\n components: {\n securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer' } },\n schemas,\n },\n paths: {\n '/v1/health': {\n get: {\n summary: 'Liveness + store/coverage probe',\n security: [],\n responses: { '200': ok('Health', 'Service health') },\n },\n },\n '/v1/openapi.json': {\n get: {\n summary: 'This OpenAPI document',\n security: [],\n responses: { '200': { description: 'OpenAPI 3.1 document', content: { 'application/json': { schema: { type: 'object' } } } } },\n },\n },\n '/v1/summary': {\n get: {\n summary: 'Low-token topology aggregate for the resolved session',\n parameters: [SESSION_PARAM, TENANT_PARAM],\n responses: { '200': ok('Summary', 'Topology summary'), ...errorResponses() },\n },\n },\n '/v1/nodes': {\n get: {\n summary: 'List/search/paginate nodes',\n parameters: [\n { name: 'search', in: 'query', required: false, description: 'Lexical/semantic search anchor.', schema: { type: 'string' } },\n { name: 'types', in: 'query', required: false, description: 'Comma-separated node-type filter.', schema: { type: 'string' } },\n { name: 'limit', in: 'query', required: false, description: 'Page size (default 100, max 1000).', schema: { type: 'integer' } },\n { name: 'offset', in: 'query', required: false, description: 'Page offset (ignored for search).', schema: { type: 'integer' } },\n SESSION_PARAM, TENANT_PARAM,\n ],\n responses: { '200': ok('Nodes', 'A page of nodes'), ...errorResponses() },\n },\n },\n '/v1/nodes/{id}/dependencies': {\n get: {\n summary: 'Dependency traversal from a node',\n parameters: [\n { name: 'id', in: 'path', required: true, description: 'Node id (\"{type}:{id}\").', schema: { type: 'string' } },\n { name: 'direction', in: 'query', required: false, description: 'downstream | upstream | both (default downstream).', schema: { type: 'string', enum: ['downstream', 'upstream', 'both'] } },\n { name: 'maxDepth', in: 'query', required: false, description: 'Traversal depth (default 8, max 64).', schema: { type: 'integer' } },\n SESSION_PARAM, TENANT_PARAM,\n ],\n responses: { '200': ok('Dependencies', 'Traversal result'), ...errorResponses() },\n },\n },\n '/v1/diff': {\n get: {\n summary: 'Compare two sessions (drift)',\n parameters: [\n { name: 'base', in: 'query', required: true, description: 'Base session id.', schema: { type: 'string' } },\n { name: 'current', in: 'query', required: true, description: 'Current session id.', schema: { type: 'string' } },\n TENANT_PARAM,\n ],\n responses: { '200': ok('Diff', 'Topology delta'), ...errorResponses() },\n },\n },\n '/v1/sessions': {\n get: {\n summary: 'List discovery sessions for the tenant',\n parameters: [TENANT_PARAM],\n responses: { '200': ok('Sessions', 'Sessions'), ...errorResponses() },\n },\n },\n '/v1/backstage/catalog': {\n get: {\n summary: 'The tenant topology as Backstage catalog entities (live data source, 4.6)',\n parameters: [SESSION_PARAM, TENANT_PARAM],\n responses: { '200': ok('BackstageCatalog', 'Backstage catalog entities'), ...errorResponses() },\n },\n },\n },\n };\n}\n","/**\n * Hand-rolled, zero-dependency GraphQL layer for the read-only API (4.2).\n *\n * Mirrors REST over `POST /graphql` (and serves the SDL on `GET /graphql`) without\n * adding a `graphql`/`apollo` runtime dependency. Resolvers delegate to the same\n * {@link QueryBackend} and reuse the REST projections (`rest.ts`), so REST and GraphQL\n * return byte-identical shapes and the consent posture stays in one place. It is\n * strictly **read-only**: there is no `Mutation` type and a `mutation` document is\n * rejected. A small tokenizer/parser handles the query subset the schema needs\n * (fields, arguments, variables, nested selections) and a minimal `__schema`\n * introspection response keeps GraphiQL-style clients working.\n */\n\nimport type { QueryBackend, TenantContext } from '../store/query.js';\nimport { toApiNode, toApiSession, projectDependencies, projectDiff } from './rest.js';\n\nexport interface GraphqlDeps {\n backend: QueryBackend;\n}\n\nexport interface GraphqlResult {\n data?: unknown;\n errors?: Array<{ message: string }>;\n}\n\nexport const SDL = `# Cartograph read-only GraphQL API (4.2). Mirrors the REST surface.\nschema { query: Query }\n\ntype Query {\n summary(session: String): Summary\n nodes(search: String, types: [String!], limit: Int, offset: Int, session: String): NodeConnection\n node(id: String!, session: String): Node\n dependencies(id: String!, direction: Direction, maxDepth: Int, session: String): Dependencies\n diff(base: String!, current: String!): Diff\n sessions: [Session!]!\n}\n\nenum Direction { downstream upstream both }\n\ntype Totals { nodes: Int! edges: Int! }\ntype Count { key: String! value: Int! }\ntype TopConnected { id: String! name: String! type: String! degree: Int! }\ntype Anomaly { nodeId: String! kind: String! severity: String! reason: String! }\ntype Cost { amount: Float! currency: String! period: String! source: String }\ntype CostRollup { key: String! currency: String! period: String! total: Float! nodes: Int! }\ntype CostCoverage { withCost: Int! total: Int! }\n\ntype Node {\n id: String! type: String! name: String! confidence: Float!\n domain: String subDomain: String qualityScore: Float owner: String cost: Cost tags: [String!]!\n}\ntype DependencyNode {\n id: String! type: String! name: String! confidence: Float!\n domain: String subDomain: String qualityScore: Float owner: String cost: Cost tags: [String!]! depth: Int!\n}\ntype Edge { sourceId: String! targetId: String! relationship: String! confidence: Float! evidence: String! }\n\ntype Summary {\n sessionId: String!\n totals: Totals!\n topConnected: [TopConnected!]!\n anomalies: [Anomaly!]!\n contributors: Int!\n costByDomain: [CostRollup!]!\n costByOwner: [CostRollup!]!\n costCoverage: CostCoverage!\n}\n\ntype NodeConnection { nodes: [Node!]! total: Int! limit: Int! offset: Int! }\ntype Dependencies { root: Node direction: Direction! maxDepth: Int! nodes: [DependencyNode!]! edges: [Edge!]! }\n\ntype SessionEndpoint { sessionId: String! startedAt: String! nodeCount: Int! edgeCount: Int! }\ntype DiffSummary { nodesAdded: Int! nodesRemoved: Int! nodesChanged: Int! edgesAdded: Int! edgesRemoved: Int! }\ntype NodeChange { id: String! changedFields: [String!]! confidenceDelta: Float! }\ntype DiffNodes { added: [Node!]! removed: [Node!]! changed: [NodeChange!]! unchanged: Int! }\ntype DiffEdges { added: [Edge!]! removed: [Edge!]! unchanged: Int! }\ntype DiffAnomalies { added: [Anomaly!]! }\ntype Diff {\n base: SessionEndpoint! current: SessionEndpoint! summary: DiffSummary!\n nodes: DiffNodes! edges: DiffEdges! anomalies: DiffAnomalies!\n}\n\ntype Session { id: String! mode: String! startedAt: String! completedAt: String name: String tenant: String! lastScannedAt: String }\n`;\n\n// ── resolvers (delegate to the backend, reuse the REST projections) ──────────────\n\ntype Args = Record<string, unknown>;\n\nconst resolvers: Record<string, (ctx: TenantContext, args: Args, backend: QueryBackend) => unknown> = {\n summary: (ctx, args, backend) => backend.summary(ctx, str(args['session'])),\n nodes: (ctx, args, backend) => {\n const r = backend.nodes(\n ctx,\n {\n ...(str(args['search']) ? { search: str(args['search']) } : {}),\n ...(Array.isArray(args['types']) ? { types: (args['types'] as unknown[]).map(String) } : {}),\n ...(num(args['limit']) !== undefined ? { limit: num(args['limit']) } : {}),\n ...(num(args['offset']) !== undefined ? { offset: num(args['offset']) } : {}),\n },\n str(args['session']),\n );\n return { nodes: r.nodes.map(toApiNode), total: r.total, limit: r.limit, offset: r.offset };\n },\n node: (ctx, args, backend) => {\n const n = backend.node(ctx, String(args['id']), str(args['session']));\n return n ? toApiNode(n) : null;\n },\n dependencies: (ctx, args, backend) => {\n const r = backend.dependencies(\n ctx,\n String(args['id']),\n {\n ...(str(args['direction']) ? { direction: str(args['direction']) as 'downstream' | 'upstream' | 'both' } : {}),\n ...(num(args['maxDepth']) !== undefined ? { maxDepth: num(args['maxDepth']) } : {}),\n },\n str(args['session']),\n );\n return projectDependencies(r);\n },\n diff: (ctx, args, backend) => projectDiff(backend.diff(ctx, String(args['base']), String(args['current']))),\n sessions: (ctx, _args, backend) => backend.sessions(ctx).map(toApiSession),\n};\n\nfunction str(v: unknown): string | undefined {\n return typeof v === 'string' ? v : undefined;\n}\nfunction num(v: unknown): number | undefined {\n // Integer-only: a fractional limit/offset/maxDepth would 500 in SQLite or break the contract.\n return typeof v === 'number' && Number.isInteger(v) ? v : undefined;\n}\n\n// ── tiny GraphQL query parser (subset: fields, args, variables, nested selections) ──\n\ninterface Selection {\n name: string;\n alias: string;\n args: Args;\n selections: Selection[];\n}\n\nconst NAME_RE = /[_A-Za-z][_0-9A-Za-z]*/y;\n\nfunction tokenize(src: string): string[] {\n const tokens: string[] = [];\n let i = 0;\n while (i < src.length) {\n const c = src[i];\n if (/\\s|,/.test(c)) { i++; continue; }\n if (c === '#') { while (i < src.length && src[i] !== '\\n') i++; continue; }\n if ('{}()[]:!$'.includes(c)) { tokens.push(c); i++; continue; }\n if (c === '\"') {\n let j = i + 1;\n let s = '';\n while (j < src.length && src[j] !== '\"') { s += src[j]; j++; }\n tokens.push(JSON.stringify(s)); // keep quoted to mark as string literal\n i = j + 1;\n continue;\n }\n NAME_RE.lastIndex = i;\n const m = NAME_RE.exec(src);\n if (m && m.index === i) { tokens.push(m[0]); i = NAME_RE.lastIndex; continue; }\n // number (incl. negative / float)\n const numMatch = /-?\\d+(\\.\\d+)?/y;\n numMatch.lastIndex = i;\n const nm = numMatch.exec(src);\n if (nm && nm.index === i) { tokens.push(nm[0]); i = numMatch.lastIndex; continue; }\n throw new Error(`unexpected character '${c}'`);\n }\n return tokens;\n}\n\n/** Explicit selection-set nesting cap (defense-in-depth; our schema nests ≤4 deep). */\nconst MAX_SELECTION_DEPTH = 32;\n\nclass Parser {\n private pos = 0;\n private depth = 0;\n constructor(private readonly tokens: string[], private readonly variables: Args) {}\n\n private peek(): string | undefined { return this.tokens[this.pos]; }\n private next(): string { return this.tokens[this.pos++]; }\n private expect(tok: string): void {\n if (this.tokens[this.pos] !== tok) throw new Error(`expected '${tok}', got '${this.tokens[this.pos] ?? '<eof>'}'`);\n this.pos++;\n }\n\n parseDocument(): Selection[] {\n // Optional `query`/`mutation`/`subscription` Name (varDefs)\n if (this.peek() === 'mutation' || this.peek() === 'subscription') {\n throw new Error('only query operations are supported (read-only API)');\n }\n if (this.peek() === 'query') {\n this.next();\n if (this.peek() && this.peek() !== '{' && this.peek() !== '(') this.next(); // operation name\n if (this.peek() === '(') this.skipBalanced('(', ')'); // variable definitions\n }\n this.expect('{');\n const selections = this.parseSelectionSet();\n return selections;\n }\n\n private skipBalanced(open: string, close: string): void {\n this.expect(open);\n let depth = 1;\n while (depth > 0) {\n const t = this.next();\n if (t === undefined) throw new Error('unbalanced');\n if (t === open) depth++;\n else if (t === close) depth--;\n }\n }\n\n private parseSelectionSet(): Selection[] {\n if (++this.depth > MAX_SELECTION_DEPTH) throw new Error(`selection set nested deeper than ${MAX_SELECTION_DEPTH}`);\n const out: Selection[] = [];\n while (this.peek() !== '}') {\n if (this.peek() === undefined) throw new Error('unexpected end of selection set');\n out.push(this.parseSelection());\n }\n this.expect('}');\n this.depth--;\n return out;\n }\n\n private parseSelection(): Selection {\n let name = this.next();\n const alias = name;\n if (this.peek() === ':') { this.next(); name = this.next(); } // `alias: field` — keep alias, take field name\n const args: Args = {};\n if (this.peek() === '(') {\n this.next();\n while (this.peek() !== ')') {\n const argName = this.next();\n this.expect(':');\n args[argName] = this.parseValue();\n }\n this.expect(')');\n }\n let selections: Selection[] = [];\n if (this.peek() === '{') { this.next(); selections = this.parseSelectionSet(); }\n return { name, alias, args, selections };\n }\n\n private parseValue(): unknown {\n const t = this.next();\n if (t === '$') { const v = this.next(); return this.variables[v]; }\n if (t === '[') {\n const arr: unknown[] = [];\n while (this.peek() !== ']') arr.push(this.parseValue());\n this.expect(']');\n return arr;\n }\n if (t.startsWith('\"')) return JSON.parse(t); // string literal\n if (t === 'true') return true;\n if (t === 'false') return false;\n if (t === 'null') return null;\n if (/^-?\\d+(\\.\\d+)?$/.test(t)) return Number(t);\n return t; // enum / bare identifier\n }\n}\n\n// ── projection of resolved values against the requested selection set ────────────\n\nfunction project(value: unknown, selections: Selection[]): unknown {\n if (value === null || value === undefined) return null;\n if (selections.length === 0) return value;\n if (Array.isArray(value)) return value.map((v) => project(v, selections));\n if (typeof value !== 'object') return value;\n const obj = value as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const sel of selections) {\n if (sel.name === '__typename') { out[sel.alias] = undefined; continue; }\n out[sel.alias] = project(obj[sel.name], sel.selections);\n }\n return out;\n}\n\n// ── minimal introspection ────────────────────────────────────────────────────\n\nfunction introspectionSchema(): Record<string, unknown> {\n const names = [...SDL.matchAll(/^(?:type|enum)\\s+([_A-Za-z][_0-9A-Za-z]*)/gm)].map((m) => m[1]);\n const types = names.map((name) => ({ name, kind: /^[A-Z]/.test(name) ? 'OBJECT' : 'SCALAR' }));\n return {\n __schema: {\n queryType: { name: 'Query' },\n mutationType: null,\n subscriptionType: null,\n types,\n directives: [],\n },\n };\n}\n\n/** Execute a `{ query, variables, operationName }` request. Read-only; rejects mutations. */\nexport async function executeGraphql(ctx: TenantContext, body: unknown, deps: GraphqlDeps): Promise<GraphqlResult> {\n const req = (body ?? {}) as { query?: unknown; variables?: unknown };\n if (typeof req.query !== 'string' || req.query.trim() === '') {\n return { errors: [{ message: 'missing query' }] };\n }\n const variables = (typeof req.variables === 'object' && req.variables !== null ? req.variables : {}) as Args;\n\n let selections: Selection[];\n try {\n selections = new Parser(tokenize(req.query), variables).parseDocument();\n } catch (err) {\n return { errors: [{ message: `syntax error: ${err instanceof Error ? err.message : String(err)}` }] };\n }\n\n const data: Record<string, unknown> = {};\n const errors: Array<{ message: string }> = [];\n for (const sel of selections) {\n try {\n if (sel.name === '__schema') {\n data[sel.alias] = project(introspectionSchema()['__schema'], sel.selections);\n continue;\n }\n if (sel.name === '__typename') { data[sel.alias] = 'Query'; continue; }\n const resolver = resolvers[sel.name];\n if (!resolver) { errors.push({ message: `Cannot query field \"${sel.name}\" on type \"Query\"` }); continue; }\n const resolved = resolver(ctx, sel.args, deps.backend);\n data[sel.alias] = project(resolved, sel.selections);\n } catch (err) {\n errors.push({ message: err instanceof Error ? err.message : String(err) });\n }\n }\n\n return errors.length > 0 ? { data, errors } : { data };\n}\n\n/** `GET /graphql` → the SDL as text/plain. */\nexport function handleGraphqlGet(): { status: number; body: string } {\n return { status: 200, body: SDL };\n}\n","/**\n * The self-hostable web dashboard (4.1).\n *\n * `dashboardHtml()` returns a SINGLE self-contained HTML document — inlined CSS +\n * vanilla JS, **no CDN, no React/Vite/D3, zero new dependency** — that fetches the\n * live `/v1/*` API of the same server (WS 4.2) and renders a searchable node list, an\n * interactive Canvas topology with drill-down, and a detail panel. It is served by the\n * API server at `GET /` and `GET /app`; the page shell is public, but the DATA it\n * fetches is gated by the existing `/v1` bearer/RBAC (WS 4.2/4.5) — the page carries a\n * bearer token (entered once, kept in sessionStorage) and an optional tenant header.\n *\n * Because the dashboard renders from the API response shapes (the `schemas.ts`\n * projections), it inherits the consent-safe projection (no raw node metadata) for free.\n */\n\nexport interface DashboardOptions {\n /** Server version, shown in the header. */\n version?: string;\n}\n\nconst STYLE = `\n*{box-sizing:border-box;margin:0;padding:0}\n:root{--bg:#0f1419;--panel:#161b22;--line:#2d333b;--fg:#e6edf3;--dim:#8b949e;--accent:#3b82f6;--ok:#3fb950;--warn:#d29922;--crit:#f85149}\nbody{font:13px/1.5 ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,sans-serif;background:var(--bg);color:var(--fg);height:100vh;display:flex;flex-direction:column;overflow:hidden}\nheader{display:flex;align-items:center;gap:12px;padding:8px 14px;border-bottom:1px solid var(--line);background:var(--panel)}\nheader h1{font-size:15px;font-weight:600;letter-spacing:.3px}\nheader .ver{color:var(--dim);font-size:11px}\nheader .spacer{flex:1}\nheader input{background:var(--bg);border:1px solid var(--line);color:var(--fg);border-radius:6px;padding:5px 8px;font-size:12px;width:200px}\nheader input:focus{outline:none;border-color:var(--accent)}\nheader button{background:var(--accent);border:none;color:#fff;border-radius:6px;padding:6px 12px;font-size:12px;cursor:pointer}\nmain{flex:1;display:grid;grid-template-columns:300px 1fr 320px;overflow:hidden}\n.col{overflow:auto;padding:12px;border-right:1px solid var(--line)}\n.col:last-child{border-right:none;border-left:1px solid var(--line)}\n.card{background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:10px 12px;margin-bottom:10px}\n.card h2{font-size:11px;text-transform:uppercase;letter-spacing:.6px;color:var(--dim);margin-bottom:8px}\n.stat{display:flex;justify-content:space-between;padding:2px 0}\n.stat b{font-variant-numeric:tabular-nums}\n.bar{height:4px;border-radius:2px;background:var(--accent);margin-top:2px}\n#search{width:100%;background:var(--bg);border:1px solid var(--line);color:var(--fg);border-radius:6px;padding:6px 8px;margin-bottom:8px}\n.node-item{padding:6px 8px;border-radius:6px;cursor:pointer;border:1px solid transparent}\n.node-item:hover{background:var(--panel)}\n.node-item.sel{background:var(--panel);border-color:var(--accent)}\n.node-item .t{color:var(--dim);font-size:11px}\n#center{position:relative;padding:0}\n#graph{display:block;width:100%;height:100%;background:radial-gradient(circle at 50% 40%,#11161d,#0d1117)}\n#empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--dim);text-align:center;padding:20px}\n.kv{display:flex;justify-content:space-between;gap:8px;padding:3px 0;border-bottom:1px solid var(--line)}\n.kv span:first-child{color:var(--dim)}\n.kv span:last-child{text-align:right;word-break:break-all}\n.chip{display:inline-block;background:var(--bg);border:1px solid var(--line);border-radius:10px;padding:1px 8px;font-size:11px;margin:2px 2px 0 0}\n.sev-high,.sev-critical{color:var(--crit)} .sev-medium,.sev-warning{color:var(--warn)} .sev-low,.sev-info{color:var(--dim)}\n#toast{position:fixed;bottom:14px;left:50%;transform:translateX(-50%);background:var(--crit);color:#fff;padding:8px 14px;border-radius:6px;font-size:12px;opacity:0;transition:opacity .2s;pointer-events:none}\n#toast.show{opacity:1}\n`;\n\nconst SCRIPT = String.raw`\nconst $=(s)=>document.querySelector(s), api=(p)=>{\n const h={accept:'application/json'};\n const t=sessionStorage.getItem('cartograph_token'); if(t) h.authorization='Bearer '+t;\n const tn=sessionStorage.getItem('cartograph_tenant'); if(tn) h['x-cartograph-tenant']=tn;\n return fetch(p,{headers:h}).then(async r=>{ if(!r.ok){ const e=new Error('http '+r.status); e.status=r.status; throw e; } return r.json(); });\n};\nfunction toast(m){ const t=$('#toast'); t.textContent=m; t.classList.add('show'); setTimeout(()=>t.classList.remove('show'),2600); }\nlet NODES=[], SELECTED=null;\n\nasync function boot(){\n try{\n const s=await api('/v1/summary'); renderSummary(s);\n const n=await api('/v1/nodes?limit=1000'); NODES=n.nodes; renderList(NODES);\n }catch(e){\n if(e.status===401){ toast('Unauthorized — enter a bearer token and Reload.'); }\n else if(e.status===404){ const em=$('#empty'); em.textContent='No discovery session yet. Run a scan, then Reload.'; em.style.display='flex'; }\n else toast('Failed to load: '+e.message);\n }\n}\nfunction renderSummary(s){\n const max=Math.max(1,...Object.values(s.nodesByType));\n const types=Object.entries(s.nodesByType).sort((a,b)=>b[1]-a[1]).slice(0,12)\n .map(([k,v])=>'<div class=\"stat\"><span>'+esc(k)+'</span><b>'+v+'</b></div><div class=\"bar\" style=\"width:'+(v/max*100)+'%\"></div>').join('');\n const anom=(s.anomalies||[]).slice(0,12).map(a=>'<div class=\"stat\"><span class=\"sev-'+a.severity+'\">'+esc(a.kind)+'</span><span class=\"t\">'+esc(a.nodeId)+'</span></div>').join('') || '<div class=\"t\">none</div>';\n $('#summary').innerHTML=\n '<div class=\"card\"><h2>Totals</h2><div class=\"stat\"><span>Nodes</span><b>'+s.totals.nodes+'</b></div><div class=\"stat\"><span>Edges</span><b>'+s.totals.edges+'</b></div>'+(s.contributors!=null?'<div class=\"stat\"><span>Contributors</span><b>'+s.contributors+'</b></div>':'')+'</div>'+\n '<div class=\"card\"><h2>Nodes by type</h2>'+types+'</div>'+\n '<div class=\"card\"><h2>Anomalies</h2>'+anom+'</div>';\n}\nfunction renderList(nodes){\n $('#list').innerHTML=nodes.map(n=>'<div class=\"node-item\" data-id=\"'+esc(n.id)+'\"><div>'+esc(n.name)+'</div><div class=\"t\">'+esc(n.type)+'</div></div>').join('')||'<div class=\"t\">no nodes</div>';\n $('#list').querySelectorAll('.node-item').forEach(el=>el.onclick=()=>select(el.dataset.id));\n}\nfunction esc(s){ return String(s==null?'':s).replace(/[&<>\"]/g,c=>({'&':'&','<':'<','>':'>','\"':'"'}[c])); }\n\nasync function select(id){\n SELECTED=id;\n $('#list').querySelectorAll('.node-item').forEach(el=>el.classList.toggle('sel',el.dataset.id===id));\n const node=NODES.find(n=>n.id===id);\n try{\n const dep=await api('/v1/nodes/'+encodeURIComponent(id)+'/dependencies?direction=both&maxDepth=2');\n renderDetail(node,dep); drawGraph(id,dep);\n }catch(e){ toast('drill-down failed: '+e.message); }\n}\nfunction renderDetail(node,dep){\n if(!node){ $('#detail').innerHTML='<div class=\"t\">node not in current page</div>'; return; }\n const fields=[['id',node.id],['type',node.type],['name',node.name],['confidence',node.confidence],['domain',node.domain],['owner',node.owner]]\n .filter(([,v])=>v!=null).map(([k,v])=>'<div class=\"kv\"><span>'+k+'</span><span>'+esc(v)+'</span></div>').join('');\n const tags=(node.tags||[]).map(t=>'<span class=\"chip\">'+esc(t)+'</span>').join('');\n const edges=(dep.edges||[]).map(e=>'<div class=\"kv\"><span>'+esc(e.relationship)+'</span><span>'+esc(e.sourceId===node.id?('→ '+e.targetId):('← '+e.sourceId))+'</span></div>').join('')||'<div class=\"t\">no dependencies</div>';\n $('#detail').innerHTML='<div class=\"card\"><h2>Node</h2>'+fields+(tags?'<div style=\"margin-top:6px\">'+tags+'</div>':'')+'</div><div class=\"card\"><h2>Dependencies ('+(dep.nodes?dep.nodes.length:0)+')</h2>'+edges+'</div>';\n}\n\nconst cv=()=>$('#graph'), ctx=()=>cv().getContext('2d');\nfunction drawGraph(rootId,dep){\n $('#empty').style.display='none';\n const c=cv(); const dpr=window.devicePixelRatio||1; const w=c.clientWidth,h=c.clientHeight;\n c.width=w*dpr; c.height=h*dpr; const g=ctx(); g.setTransform(dpr,0,0,dpr,0,0); g.clearRect(0,0,w,h);\n const nodes=dep.nodes||[]; const cx=w/2,cy=h/2;\n // root at center; others on a circle, radius by depth.\n const pos={}; pos[rootId]={x:cx,y:cy};\n const others=nodes.filter(n=>n.id!==rootId);\n others.forEach((n,i)=>{ const a=(i/Math.max(1,others.length))*Math.PI*2; const r=90+(n.depth||1)*70; pos[n.id]={x:cx+Math.cos(a)*r,y:cy+Math.sin(a)*r}; });\n // edges\n g.strokeStyle='#30363d'; g.lineWidth=1.2;\n (dep.edges||[]).forEach(e=>{ const a=pos[e.sourceId],b=pos[e.targetId]; if(a&&b){ g.beginPath(); g.moveTo(a.x,a.y); g.lineTo(b.x,b.y); g.stroke(); } });\n // nodes\n const byId={}; nodes.forEach(n=>byId[n.id]=n);\n Object.entries(pos).forEach(([id,p])=>{ const n=byId[id]; const root=id===rootId;\n g.beginPath(); g.arc(p.x,p.y,root?13:8,0,Math.PI*2);\n g.fillStyle=root?'#3b82f6':'#21262d'; g.fill(); g.lineWidth=root?2:1; g.strokeStyle=root?'#60a5fa':'#484f58'; g.stroke();\n g.fillStyle='#c9d1d9'; g.font=(root?'600 12px':'11px')+' ui-sans-serif'; g.textAlign='center';\n g.fillText((n&&n.name?n.name:id).slice(0,22),p.x,p.y-(root?20:14));\n });\n}\n\ndocument.addEventListener('DOMContentLoaded',()=>{\n const t=sessionStorage.getItem('cartograph_token'); if(t)$('#token').value=t;\n const tn=sessionStorage.getItem('cartograph_tenant'); if(tn)$('#tenant').value=tn;\n $('#reload').onclick=()=>{ sessionStorage.setItem('cartograph_token',$('#token').value.trim()); sessionStorage.setItem('cartograph_tenant',$('#tenant').value.trim()); boot(); };\n $('#search').oninput=(e)=>{ const q=e.target.value.toLowerCase(); renderList(NODES.filter(n=>n.name.toLowerCase().includes(q)||n.id.toLowerCase().includes(q)||n.type.toLowerCase().includes(q))); };\n boot();\n});\n`;\n\n/** Build the complete dashboard HTML document. */\nexport function dashboardHtml(opts: DashboardOptions = {}): string {\n const version = opts.version ?? '';\n return `<!DOCTYPE html>\n<html lang=\"en\"><head>\n<meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>Cartograph dashboard</title>\n<style>${STYLE}</style>\n</head><body>\n<header>\n <h1>Cartograph</h1><span class=\"ver\">${version ? `v${version}` : ''}</span>\n <span class=\"spacer\"></span>\n <input id=\"tenant\" placeholder=\"tenant (optional)\" autocomplete=\"off\">\n <input id=\"token\" type=\"password\" placeholder=\"bearer token\" autocomplete=\"off\">\n <button id=\"reload\">Reload</button>\n</header>\n<main>\n <div class=\"col\"><div id=\"summary\"></div></div>\n <div class=\"col\" id=\"center\"><canvas id=\"graph\"></canvas><div id=\"empty\">Select a node to explore its dependencies.</div></div>\n <div class=\"col\">\n <input id=\"search\" placeholder=\"Search nodes…\" autocomplete=\"off\">\n <div id=\"list\"></div>\n <div id=\"detail\"></div>\n </div>\n</main>\n<div id=\"toast\"></div>\n<script>${SCRIPT}</script>\n</body></html>`;\n}\n"],"mappings":"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","names":["nodes","ok","resolve"]}
|
package/dist/cli.js
CHANGED
|
@@ -11,12 +11,12 @@ import {
|
|
|
11
11
|
runDrift,
|
|
12
12
|
runLocalDiscovery,
|
|
13
13
|
startMcp
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-ASCA3UFM.js";
|
|
15
15
|
import {
|
|
16
16
|
entitiesToYaml,
|
|
17
17
|
startApi,
|
|
18
18
|
toBackstageEntities
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-W4Q3TXHR.js";
|
|
20
20
|
import {
|
|
21
21
|
CartographyDB,
|
|
22
22
|
buildCartographyToolHandlers,
|
|
@@ -5030,10 +5030,11 @@ error: ${err instanceof Error ? err.message : String(err)}
|
|
|
5030
5030
|
process.exitCode = 1;
|
|
5031
5031
|
}
|
|
5032
5032
|
});
|
|
5033
|
-
program.command("api").description("Run the read-only REST/GraphQL API server over the topology store (4.2)").option("--http", "Use HTTP transport (default; kept for symmetry with mcp)", true).option("--port <n>", "HTTP port", "3737").option("--host <h>", "HTTP host", "127.0.0.1").option("--allowed-hosts <list>", "Comma-separated Host allowlist (required for non-loopback --host)").option("--allowed-origins <list>", "Comma-separated CORS Origin allowlist (default: same-origin only)").option("--token <secret>", "Bearer token required on requests (or CARTOGRAPHY_HTTP_TOKEN); mandatory for non-loopback --host").option("--db <path>", "DB path").option("--session <id>", 'Session to serve (id or "latest")', "latest").option("--tenant <id>", "Default tenant whose topology to serve (alias: --org; default: local)").option("--org <id>", "Alias for --tenant").option("--no-graphql", "Disable the /graphql endpoint (REST only)").option("--auth-required", "Reject unauthenticated requests even on loopback (RBAC required mode)", false).action(async (opts) => {
|
|
5033
|
+
program.command("api").description("Run the read-only REST/GraphQL API server over the topology store (4.2)").option("--http", "Use HTTP transport (default; kept for symmetry with mcp)", true).option("--port <n>", "HTTP port", "3737").option("--host <h>", "HTTP host", "127.0.0.1").option("--allowed-hosts <list>", "Comma-separated Host allowlist (required for non-loopback --host)").option("--allowed-origins <list>", "Comma-separated CORS Origin allowlist (default: same-origin only)").option("--token <secret>", "Bearer token required on requests (or CARTOGRAPHY_HTTP_TOKEN); mandatory for non-loopback --host").option("--db <path>", "DB path").option("--session <id>", 'Session to serve (id or "latest")', "latest").option("--tenant <id>", "Default tenant whose topology to serve (alias: --org; default: local)").option("--org <id>", "Alias for --tenant").option("--no-graphql", "Disable the /graphql endpoint (REST only)").option("--no-dashboard", "Disable the web dashboard at / and /app (4.1)").option("--auth-required", "Reject unauthenticated requests even on loopback (RBAC required mode)", false).action(async (opts) => {
|
|
5034
5034
|
try {
|
|
5035
5035
|
await startApi({
|
|
5036
5036
|
authRequired: opts.authRequired === true,
|
|
5037
|
+
dashboard: opts.dashboard,
|
|
5037
5038
|
port: parseInt(opts.port, 10),
|
|
5038
5039
|
host: opts.host,
|
|
5039
5040
|
allowedHosts: opts.allowedHosts ? String(opts.allowedHosts).split(",").map((h) => h.trim()).filter(Boolean) : void 0,
|