@datasynx/agentic-ai-cartography 2.4.0 → 2.6.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 +2 -2
- package/dist/{chunk-X5JA2UDT.js → chunk-GA4427LB.js} +134 -14
- package/dist/chunk-GA4427LB.js.map +1 -0
- package/dist/{chunk-L4OSL7I6.js → chunk-PQ7Q6MI5.js} +130 -12
- package/dist/chunk-PQ7Q6MI5.js.map +1 -0
- package/dist/{chunk-B4QWX7CP.js → chunk-X3UWUX3G.js} +55 -11
- package/dist/chunk-X3UWUX3G.js.map +1 -0
- package/dist/cli.js +91 -33
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +363 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +262 -3
- package/dist/index.d.ts +262 -3
- package/dist/index.js +343 -58
- package/dist/index.js.map +1 -1
- package/dist/mcp-bin.js +2 -2
- package/package.json +1 -1
- package/server.json +2 -2
- package/dist/chunk-B4QWX7CP.js.map +0 -1
- package/dist/chunk-L4OSL7I6.js.map +0 -1
- package/dist/chunk-X5JA2UDT.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -2815,7 +2815,10 @@ CREATE TABLE IF NOT EXISTS activity_events (
|
|
|
2815
2815
|
duration_ms INTEGER,
|
|
2816
2816
|
command TEXT,
|
|
2817
2817
|
result_bytes INTEGER,
|
|
2818
|
-
tenant TEXT NOT NULL DEFAULT 'local'
|
|
2818
|
+
tenant TEXT NOT NULL DEFAULT 'local',
|
|
2819
|
+
actor_subject TEXT,
|
|
2820
|
+
actor_role TEXT,
|
|
2821
|
+
actor_tenant TEXT
|
|
2819
2822
|
);
|
|
2820
2823
|
|
|
2821
2824
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
@@ -2924,6 +2927,16 @@ CREATE INDEX IF NOT EXISTS idx_nodes_tenant_content ON nodes(tenant, content_has
|
|
|
2924
2927
|
CREATE INDEX IF NOT EXISTS idx_contrib_org ON node_contributors(organization, global_id);
|
|
2925
2928
|
CREATE INDEX IF NOT EXISTS idx_nodes_owner ON nodes(session_id, owner);
|
|
2926
2929
|
`;
|
|
2930
|
+
var AUTH_SCHEMA = `
|
|
2931
|
+
CREATE TABLE IF NOT EXISTS auth_credentials (
|
|
2932
|
+
token_hash TEXT PRIMARY KEY,
|
|
2933
|
+
subject TEXT NOT NULL,
|
|
2934
|
+
tenant TEXT NOT NULL DEFAULT 'local',
|
|
2935
|
+
role TEXT NOT NULL,
|
|
2936
|
+
created_at TEXT NOT NULL
|
|
2937
|
+
);
|
|
2938
|
+
CREATE INDEX IF NOT EXISTS idx_auth_subject ON auth_credentials(subject);
|
|
2939
|
+
`;
|
|
2927
2940
|
var CartographyDB = class {
|
|
2928
2941
|
db;
|
|
2929
2942
|
/** 3.6 anomaly settings; defaults apply when no `anomaly` config is supplied. */
|
|
@@ -2943,7 +2956,8 @@ var CartographyDB = class {
|
|
|
2943
2956
|
const version = this.db.pragma("user_version", { simple: true });
|
|
2944
2957
|
if (version === 0) {
|
|
2945
2958
|
this.db.exec(SCHEMA);
|
|
2946
|
-
this.db.
|
|
2959
|
+
this.db.exec(AUTH_SCHEMA);
|
|
2960
|
+
this.db.pragma("user_version = 15");
|
|
2947
2961
|
return;
|
|
2948
2962
|
} else if (version === 1) {
|
|
2949
2963
|
const cols = this.db.prepare("PRAGMA table_info(nodes)").all().map((c) => c.name);
|
|
@@ -3129,6 +3143,18 @@ var CartographyDB = class {
|
|
|
3129
3143
|
}
|
|
3130
3144
|
this.db.pragma("user_version = 14");
|
|
3131
3145
|
}
|
|
3146
|
+
const v14 = this.db.pragma("user_version", { simple: true });
|
|
3147
|
+
if (v14 < 15) {
|
|
3148
|
+
this.db.exec(AUTH_SCHEMA);
|
|
3149
|
+
const ev = this.db.prepare("PRAGMA table_info(activity_events)").all();
|
|
3150
|
+
if (ev.length > 0) {
|
|
3151
|
+
const cols = ev.map((c) => c.name);
|
|
3152
|
+
if (!cols.includes("actor_subject")) this.db.exec("ALTER TABLE activity_events ADD COLUMN actor_subject TEXT");
|
|
3153
|
+
if (!cols.includes("actor_role")) this.db.exec("ALTER TABLE activity_events ADD COLUMN actor_role TEXT");
|
|
3154
|
+
if (!cols.includes("actor_tenant")) this.db.exec("ALTER TABLE activity_events ADD COLUMN actor_tenant TEXT");
|
|
3155
|
+
}
|
|
3156
|
+
this.db.pragma("user_version = 15");
|
|
3157
|
+
}
|
|
3132
3158
|
}
|
|
3133
3159
|
close() {
|
|
3134
3160
|
this.db.pragma("optimize");
|
|
@@ -3559,13 +3585,13 @@ var CartographyDB = class {
|
|
|
3559
3585
|
});
|
|
3560
3586
|
}
|
|
3561
3587
|
// ── Events ──────────────────────────────
|
|
3562
|
-
insertEvent(sessionId, event, taskId) {
|
|
3588
|
+
insertEvent(sessionId, event, taskId, actor) {
|
|
3563
3589
|
const id = crypto.randomUUID();
|
|
3564
3590
|
const tenant = this.tenantOf(sessionId);
|
|
3565
3591
|
this.db.prepare(`
|
|
3566
3592
|
INSERT INTO activity_events
|
|
3567
|
-
(id, session_id, task_id, timestamp, event_type, process, pid, target, target_type, port, command, result_bytes, tenant)
|
|
3568
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3593
|
+
(id, session_id, task_id, timestamp, event_type, process, pid, target, target_type, port, command, result_bytes, tenant, actor_subject, actor_role, actor_tenant)
|
|
3594
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3569
3595
|
`).run(
|
|
3570
3596
|
id,
|
|
3571
3597
|
sessionId,
|
|
@@ -3579,9 +3605,52 @@ var CartographyDB = class {
|
|
|
3579
3605
|
event.port ?? null,
|
|
3580
3606
|
event.command ?? null,
|
|
3581
3607
|
event.resultBytes ?? null,
|
|
3582
|
-
tenant
|
|
3608
|
+
tenant,
|
|
3609
|
+
actor?.subject ?? null,
|
|
3610
|
+
actor?.role ?? null,
|
|
3611
|
+
actor?.tenant ?? null
|
|
3583
3612
|
);
|
|
3584
3613
|
}
|
|
3614
|
+
// ── RBAC credential store (4.5) ─────────────
|
|
3615
|
+
/** Number of stored credentials. `0` ⇒ no RBAC configured (fall back to shared/loopback). */
|
|
3616
|
+
countCredentials() {
|
|
3617
|
+
return this.db.prepare("SELECT COUNT(*) AS n FROM auth_credentials").get().n;
|
|
3618
|
+
}
|
|
3619
|
+
/** Look up a credential by its sha256 token hash. */
|
|
3620
|
+
findCredentialByHash(tokenHash) {
|
|
3621
|
+
const r = this.db.prepare("SELECT * FROM auth_credentials WHERE token_hash = ?").get(tokenHash);
|
|
3622
|
+
if (!r) return void 0;
|
|
3623
|
+
return {
|
|
3624
|
+
tokenHash: r["token_hash"],
|
|
3625
|
+
subject: r["subject"],
|
|
3626
|
+
tenant: r["tenant"],
|
|
3627
|
+
role: r["role"],
|
|
3628
|
+
createdAt: r["created_at"]
|
|
3629
|
+
};
|
|
3630
|
+
}
|
|
3631
|
+
/** Upsert a credential (idempotent on the token hash). Stores only the hash, never the raw token. */
|
|
3632
|
+
addCredential(rec) {
|
|
3633
|
+
this.db.prepare(`
|
|
3634
|
+
INSERT INTO auth_credentials (token_hash, subject, tenant, role, created_at)
|
|
3635
|
+
VALUES (?, ?, ?, ?, ?)
|
|
3636
|
+
ON CONFLICT(token_hash) DO UPDATE SET subject = excluded.subject, tenant = excluded.tenant, role = excluded.role
|
|
3637
|
+
`).run(rec.tokenHash, rec.subject, rec.tenant, rec.role, (/* @__PURE__ */ new Date()).toISOString());
|
|
3638
|
+
}
|
|
3639
|
+
/** List all credentials (token hashes only — the raw token is unrecoverable). */
|
|
3640
|
+
listCredentials() {
|
|
3641
|
+
const rows = this.db.prepare("SELECT * FROM auth_credentials ORDER BY created_at").all();
|
|
3642
|
+
return rows.map((r) => ({
|
|
3643
|
+
tokenHash: r["token_hash"],
|
|
3644
|
+
subject: r["subject"],
|
|
3645
|
+
tenant: r["tenant"],
|
|
3646
|
+
role: r["role"],
|
|
3647
|
+
createdAt: r["created_at"]
|
|
3648
|
+
}));
|
|
3649
|
+
}
|
|
3650
|
+
/** Revoke every credential for a subject. Returns the number removed. */
|
|
3651
|
+
revokeCredentialsBySubject(subject) {
|
|
3652
|
+
return this.db.prepare("DELETE FROM auth_credentials WHERE subject = ?").run(subject).changes;
|
|
3653
|
+
}
|
|
3585
3654
|
getEvents(sessionId, since) {
|
|
3586
3655
|
const rows = since ? this.db.prepare("SELECT * FROM activity_events WHERE session_id = ? AND timestamp > ? ORDER BY timestamp").all(sessionId, since) : this.db.prepare("SELECT * FROM activity_events WHERE session_id = ? ORDER BY timestamp").all(sessionId);
|
|
3587
3656
|
return rows.map((r) => {
|
|
@@ -4335,6 +4404,9 @@ var SqliteQueryBackend = class {
|
|
|
4335
4404
|
node(ctx, id, sessionId) {
|
|
4336
4405
|
return this.db.getNode(this.resolveSession(ctx, sessionId), id);
|
|
4337
4406
|
}
|
|
4407
|
+
edges(ctx, sessionId) {
|
|
4408
|
+
return this.db.getEdges(this.resolveSession(ctx, sessionId));
|
|
4409
|
+
}
|
|
4338
4410
|
dependencies(ctx, id, q, sessionId) {
|
|
4339
4411
|
const sid = this.resolveSession(ctx, sessionId);
|
|
4340
4412
|
return this.db.getDependencies(sid, id, {
|
|
@@ -5260,6 +5332,36 @@ async function runDrift(db, config, opts = {}) {
|
|
|
5260
5332
|
return alert;
|
|
5261
5333
|
}
|
|
5262
5334
|
|
|
5335
|
+
// src/auth/rbac.ts
|
|
5336
|
+
var ROLE_RANK = { viewer: 1, operator: 2, admin: 3 };
|
|
5337
|
+
var ACTION_MIN_ROLE = { read: "viewer", discovery: "operator", admin: "admin" };
|
|
5338
|
+
function can(role, action) {
|
|
5339
|
+
return ROLE_RANK[role] >= ROLE_RANK[ACTION_MIN_ROLE[action]];
|
|
5340
|
+
}
|
|
5341
|
+
var AuthorizationError = class extends Error {
|
|
5342
|
+
constructor(action, role) {
|
|
5343
|
+
super(`forbidden: role '${role}' may not perform '${action}'`);
|
|
5344
|
+
this.action = action;
|
|
5345
|
+
this.role = role;
|
|
5346
|
+
this.name = "AuthorizationError";
|
|
5347
|
+
}
|
|
5348
|
+
};
|
|
5349
|
+
function authorize(principal, action) {
|
|
5350
|
+
if (!can(principal.role, action)) throw new AuthorizationError(action, principal.role);
|
|
5351
|
+
}
|
|
5352
|
+
var TenantMismatchError = class extends Error {
|
|
5353
|
+
constructor() {
|
|
5354
|
+
super("forbidden: principal is not scoped to the requested tenant");
|
|
5355
|
+
this.name = "TenantMismatchError";
|
|
5356
|
+
}
|
|
5357
|
+
};
|
|
5358
|
+
function scopeReads(principal) {
|
|
5359
|
+
return principal.tenant;
|
|
5360
|
+
}
|
|
5361
|
+
function assertSameTenant(principal, requestedTenant) {
|
|
5362
|
+
if (requestedTenant !== principal.tenant) throw new TenantMismatchError();
|
|
5363
|
+
}
|
|
5364
|
+
|
|
5263
5365
|
// src/compliance/rulesets/baseline.ts
|
|
5264
5366
|
var baseline = RulesetSchema.parse({
|
|
5265
5367
|
name: "baseline",
|
|
@@ -5547,7 +5649,7 @@ async function resolveNlQuery(db, sessionId, search, raw, opts) {
|
|
|
5547
5649
|
|
|
5548
5650
|
// src/mcp/server.ts
|
|
5549
5651
|
var SERVER_NAME = "cartography";
|
|
5550
|
-
var SERVER_VERSION = "2.
|
|
5652
|
+
var SERVER_VERSION = "2.6.0";
|
|
5551
5653
|
var SERVICE_TYPES = NODE_TYPE_GROUPS.web;
|
|
5552
5654
|
var DATA_TYPES = NODE_TYPE_GROUPS.data;
|
|
5553
5655
|
var lexicalSearch = async (db, sessionId, query, opts) => db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));
|
|
@@ -5949,6 +6051,14 @@ function createMcpServer(opts = {}) {
|
|
|
5949
6051
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
5950
6052
|
},
|
|
5951
6053
|
async (args) => {
|
|
6054
|
+
if (opts.principal) {
|
|
6055
|
+
try {
|
|
6056
|
+
authorize(opts.principal, "discovery");
|
|
6057
|
+
} catch (err) {
|
|
6058
|
+
if (err instanceof AuthorizationError) return json({ error: `forbidden: role '${opts.principal.role}' may not run discovery (operator required)` });
|
|
6059
|
+
throw err;
|
|
6060
|
+
}
|
|
6061
|
+
}
|
|
5952
6062
|
let sid = resolveSession();
|
|
5953
6063
|
if (args.update) {
|
|
5954
6064
|
if (!sid) return json({ error: "No session to update; run discovery first." });
|
|
@@ -6091,7 +6201,41 @@ function defaultAllowedHosts(host2, port) {
|
|
|
6091
6201
|
return [`${host2}:${port}`, `localhost:${port}`, `127.0.0.1:${port}`];
|
|
6092
6202
|
}
|
|
6093
6203
|
|
|
6204
|
+
// src/auth/identity.ts
|
|
6205
|
+
import { createHash as createHash2 } from "crypto";
|
|
6206
|
+
function hashToken(token) {
|
|
6207
|
+
return createHash2("sha256").update(token, "utf8").digest("hex");
|
|
6208
|
+
}
|
|
6209
|
+
var SqliteCredentialStore = class {
|
|
6210
|
+
constructor(db) {
|
|
6211
|
+
this.db = db;
|
|
6212
|
+
}
|
|
6213
|
+
count() {
|
|
6214
|
+
return this.db.countCredentials();
|
|
6215
|
+
}
|
|
6216
|
+
findByHash(tokenHash) {
|
|
6217
|
+
return this.db.findCredentialByHash(tokenHash);
|
|
6218
|
+
}
|
|
6219
|
+
};
|
|
6220
|
+
function resolvePrincipal(presentedToken, opts) {
|
|
6221
|
+
const tenant = opts.defaultTenant ?? DEFAULT_TENANT;
|
|
6222
|
+
if (opts.store && opts.store.count() > 0) {
|
|
6223
|
+
if (!presentedToken) return void 0;
|
|
6224
|
+
const rec = opts.store.findByHash(hashToken(presentedToken));
|
|
6225
|
+
return rec ? { subject: rec.subject, tenant: rec.tenant, role: rec.role } : void 0;
|
|
6226
|
+
}
|
|
6227
|
+
if (opts.sharedToken) {
|
|
6228
|
+
if (!presentedToken || !timingSafeEqual(presentedToken, opts.sharedToken)) return void 0;
|
|
6229
|
+
return { subject: "shared-token", tenant, role: "admin" };
|
|
6230
|
+
}
|
|
6231
|
+
if (opts.required) return void 0;
|
|
6232
|
+
return { subject: "anonymous", tenant, role: "admin" };
|
|
6233
|
+
}
|
|
6234
|
+
|
|
6094
6235
|
// src/mcp/transports.ts
|
|
6236
|
+
function samePrincipal(a, b) {
|
|
6237
|
+
return a.subject === b.subject && a.tenant === b.tenant && a.role === b.role;
|
|
6238
|
+
}
|
|
6095
6239
|
async function runStdio(server) {
|
|
6096
6240
|
const transport = new StdioServerTransport();
|
|
6097
6241
|
await server.connect(transport);
|
|
@@ -6136,6 +6280,14 @@ async function runHttp(factory, opts = {}) {
|
|
|
6136
6280
|
assertSafeBind({ host: host2, port, ...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {}, ...opts.token ? { token: opts.token } : {} });
|
|
6137
6281
|
const allowedHosts = opts.allowedHosts ?? defaultAllowedHosts(host2, port);
|
|
6138
6282
|
const token = opts.token;
|
|
6283
|
+
const authStore = opts.auth?.store;
|
|
6284
|
+
const defaultTenant = opts.defaultTenant;
|
|
6285
|
+
const resolveAuth = (header) => resolvePrincipal(bearerToken(header), {
|
|
6286
|
+
...authStore ? { store: authStore } : {},
|
|
6287
|
+
...token ? { sharedToken: token } : {},
|
|
6288
|
+
...defaultTenant ? { defaultTenant } : {},
|
|
6289
|
+
...opts.auth?.required ? { required: true } : {}
|
|
6290
|
+
});
|
|
6139
6291
|
const transports = /* @__PURE__ */ new Map();
|
|
6140
6292
|
const httpServer = http.createServer(async (req, res) => {
|
|
6141
6293
|
try {
|
|
@@ -6145,7 +6297,8 @@ async function runHttp(factory, opts = {}) {
|
|
|
6145
6297
|
res.writeHead(404, { "content-type": "application/json" }).end('{"error":"not found"}');
|
|
6146
6298
|
return;
|
|
6147
6299
|
}
|
|
6148
|
-
|
|
6300
|
+
const principal = resolveAuth(req.headers["authorization"]);
|
|
6301
|
+
if (!principal) {
|
|
6149
6302
|
res.writeHead(401, { "content-type": "application/json", "www-authenticate": "Bearer" }).end('{"error":"unauthorized"}');
|
|
6150
6303
|
return;
|
|
6151
6304
|
}
|
|
@@ -6172,8 +6325,12 @@ async function runHttp(factory, opts = {}) {
|
|
|
6172
6325
|
const sessionId = req.headers["mcp-session-id"];
|
|
6173
6326
|
const existing = sessionId ? transports.get(sessionId) : void 0;
|
|
6174
6327
|
if (existing) {
|
|
6328
|
+
if (!samePrincipal(existing.principal, principal)) {
|
|
6329
|
+
res.writeHead(403, { "content-type": "application/json" }).end('{"error":"session belongs to a different principal"}');
|
|
6330
|
+
return;
|
|
6331
|
+
}
|
|
6175
6332
|
const body2 = req.method === "POST" ? await readJsonBody(req) : void 0;
|
|
6176
|
-
await existing.handleRequest(req, res, body2);
|
|
6333
|
+
await existing.transport.handleRequest(req, res, body2);
|
|
6177
6334
|
return;
|
|
6178
6335
|
}
|
|
6179
6336
|
if (req.method !== "POST") {
|
|
@@ -6187,13 +6344,13 @@ async function runHttp(factory, opts = {}) {
|
|
|
6187
6344
|
allowedHosts,
|
|
6188
6345
|
...opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {},
|
|
6189
6346
|
onsessioninitialized: (id) => {
|
|
6190
|
-
transports.set(id, transport);
|
|
6347
|
+
transports.set(id, { transport, principal });
|
|
6191
6348
|
}
|
|
6192
6349
|
});
|
|
6193
6350
|
transport.onclose = () => {
|
|
6194
6351
|
if (transport.sessionId) transports.delete(transport.sessionId);
|
|
6195
6352
|
};
|
|
6196
|
-
await factory().connect(transport);
|
|
6353
|
+
await factory(principal).connect(transport);
|
|
6197
6354
|
await transport.handleRequest(req, res, body);
|
|
6198
6355
|
} catch (err) {
|
|
6199
6356
|
process.stderr.write(`[cartography-mcp] HTTP request failed: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -7025,6 +7182,54 @@ function headerValue(req, name) {
|
|
|
7025
7182
|
return v;
|
|
7026
7183
|
}
|
|
7027
7184
|
|
|
7185
|
+
// src/backstage.ts
|
|
7186
|
+
var COMPONENT_TYPES = ["web_service", "container", "pod"];
|
|
7187
|
+
function sanitize(id) {
|
|
7188
|
+
return id.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
7189
|
+
}
|
|
7190
|
+
function toBackstageEntities(nodes, edges, opts = {}) {
|
|
7191
|
+
const owner = opts.org ?? "unknown";
|
|
7192
|
+
return nodes.map((node) => {
|
|
7193
|
+
const kind = COMPONENT_TYPES.includes(node.type) ? "Component" : node.type === "api_endpoint" ? "API" : "Resource";
|
|
7194
|
+
const dependsOn = edges.filter((e) => e.sourceId === node.id).map((e) => `resource:default/${sanitize(e.targetId)}`);
|
|
7195
|
+
return {
|
|
7196
|
+
apiVersion: "backstage.io/v1alpha1",
|
|
7197
|
+
kind,
|
|
7198
|
+
metadata: {
|
|
7199
|
+
name: sanitize(node.id),
|
|
7200
|
+
annotations: {
|
|
7201
|
+
"cartography/discovered-at": node.discoveredAt,
|
|
7202
|
+
"cartography/confidence": String(node.confidence)
|
|
7203
|
+
}
|
|
7204
|
+
},
|
|
7205
|
+
spec: {
|
|
7206
|
+
type: node.type,
|
|
7207
|
+
lifecycle: "production",
|
|
7208
|
+
owner: node.owner ?? owner,
|
|
7209
|
+
...dependsOn.length > 0 ? { dependsOn } : {}
|
|
7210
|
+
}
|
|
7211
|
+
};
|
|
7212
|
+
});
|
|
7213
|
+
}
|
|
7214
|
+
function entitiesToYaml(entities) {
|
|
7215
|
+
return entities.map((e) => {
|
|
7216
|
+
const lines = [
|
|
7217
|
+
`apiVersion: ${e.apiVersion}`,
|
|
7218
|
+
`kind: ${e.kind}`,
|
|
7219
|
+
`metadata:`,
|
|
7220
|
+
` name: ${e.metadata.name}`,
|
|
7221
|
+
` annotations:`,
|
|
7222
|
+
...Object.entries(e.metadata.annotations).map(([k, v]) => ` ${k}: "${v}"`),
|
|
7223
|
+
`spec:`,
|
|
7224
|
+
` type: ${e.spec.type}`,
|
|
7225
|
+
` lifecycle: ${e.spec.lifecycle}`,
|
|
7226
|
+
` owner: ${e.spec.owner}`,
|
|
7227
|
+
...e.spec.dependsOn && e.spec.dependsOn.length > 0 ? [" dependsOn:", ...e.spec.dependsOn.map((d) => ` - ${d}`)] : []
|
|
7228
|
+
];
|
|
7229
|
+
return lines.join("\n");
|
|
7230
|
+
}).join("\n---\n");
|
|
7231
|
+
}
|
|
7232
|
+
|
|
7028
7233
|
// src/api/schemas.ts
|
|
7029
7234
|
import { z as z8 } from "zod";
|
|
7030
7235
|
var DIRECTIONS = ["downstream", "upstream", "both"];
|
|
@@ -7160,6 +7365,21 @@ var ErrorResponse = z8.object({
|
|
|
7160
7365
|
error: z8.string(),
|
|
7161
7366
|
code: z8.string().optional()
|
|
7162
7367
|
});
|
|
7368
|
+
var BackstageEntitySchema = z8.object({
|
|
7369
|
+
apiVersion: z8.literal("backstage.io/v1alpha1"),
|
|
7370
|
+
kind: z8.enum(["Component", "API", "Resource"]),
|
|
7371
|
+
metadata: z8.object({
|
|
7372
|
+
name: z8.string(),
|
|
7373
|
+
annotations: z8.record(z8.string(), z8.string())
|
|
7374
|
+
}),
|
|
7375
|
+
spec: z8.object({
|
|
7376
|
+
type: z8.string(),
|
|
7377
|
+
lifecycle: z8.string(),
|
|
7378
|
+
owner: z8.string(),
|
|
7379
|
+
dependsOn: z8.array(z8.string()).optional()
|
|
7380
|
+
})
|
|
7381
|
+
});
|
|
7382
|
+
var BackstageCatalogResponse = z8.object({ entities: z8.array(BackstageEntitySchema) });
|
|
7163
7383
|
var API_SCHEMAS = {
|
|
7164
7384
|
Node: NodeSchema2,
|
|
7165
7385
|
Edge: EdgeSchema2,
|
|
@@ -7171,10 +7391,13 @@ var API_SCHEMAS = {
|
|
|
7171
7391
|
Session: SessionSchema,
|
|
7172
7392
|
Sessions: SessionsResponse,
|
|
7173
7393
|
Health: HealthResponse,
|
|
7174
|
-
Error: ErrorResponse
|
|
7394
|
+
Error: ErrorResponse,
|
|
7395
|
+
BackstageEntity: BackstageEntitySchema,
|
|
7396
|
+
BackstageCatalog: BackstageCatalogResponse
|
|
7175
7397
|
};
|
|
7176
7398
|
|
|
7177
7399
|
// src/api/rest.ts
|
|
7400
|
+
var BACKSTAGE_NODE_CAP = 1e3;
|
|
7178
7401
|
function toApiNode(n) {
|
|
7179
7402
|
const out = { id: n.id, type: n.type, name: n.name, confidence: n.confidence, tags: n.tags };
|
|
7180
7403
|
if (n.domain !== void 0) out["domain"] = n.domain;
|
|
@@ -7309,6 +7532,14 @@ function handleHealth(ctx, d) {
|
|
|
7309
7532
|
const h = d.backend.health(ctx);
|
|
7310
7533
|
return ok(validateOut(HealthResponse, { status: "ok", version: d.version, store: h.store, sessions: h.sessions }));
|
|
7311
7534
|
}
|
|
7535
|
+
function handleBackstageCatalog(ctx, d) {
|
|
7536
|
+
return guard(() => {
|
|
7537
|
+
const page = d.backend.nodes(ctx, { limit: BACKSTAGE_NODE_CAP });
|
|
7538
|
+
const edges = d.backend.edges(ctx);
|
|
7539
|
+
const entities = toBackstageEntities(page.nodes, edges, { org: ctx.tenant });
|
|
7540
|
+
return ok(validateOut(BackstageCatalogResponse, { entities }));
|
|
7541
|
+
});
|
|
7542
|
+
}
|
|
7312
7543
|
|
|
7313
7544
|
// src/api/openapi.ts
|
|
7314
7545
|
function defOf(schema) {
|
|
@@ -7465,6 +7696,13 @@ function buildOpenApiDocument(opts) {
|
|
|
7465
7696
|
parameters: [TENANT_PARAM],
|
|
7466
7697
|
responses: { "200": ok2("Sessions", "Sessions"), ...errorResponses() }
|
|
7467
7698
|
}
|
|
7699
|
+
},
|
|
7700
|
+
"/v1/backstage/catalog": {
|
|
7701
|
+
get: {
|
|
7702
|
+
summary: "The tenant topology as Backstage catalog entities (live data source, 4.6)",
|
|
7703
|
+
parameters: [SESSION_PARAM, TENANT_PARAM],
|
|
7704
|
+
responses: { "200": ok2("BackstageCatalog", "Backstage catalog entities"), ...errorResponses() }
|
|
7705
|
+
}
|
|
7468
7706
|
}
|
|
7469
7707
|
}
|
|
7470
7708
|
};
|
|
@@ -7820,6 +8058,8 @@ async function runApi(opts) {
|
|
|
7820
8058
|
const token = opts.token;
|
|
7821
8059
|
const graphqlEnabled = opts.graphql !== false;
|
|
7822
8060
|
const defaultTenant = opts.tenant?.defaultTenant ?? DEFAULT_TENANT;
|
|
8061
|
+
const authStore = opts.auth?.store;
|
|
8062
|
+
const rbacMode = !!(authStore && authStore.count() > 0);
|
|
7823
8063
|
const log2 = opts.log ?? (() => {
|
|
7824
8064
|
});
|
|
7825
8065
|
const restDeps = { backend: opts.backend, version: opts.version };
|
|
@@ -7878,23 +8118,44 @@ async function runApi(opts) {
|
|
|
7878
8118
|
finish(r.status);
|
|
7879
8119
|
return;
|
|
7880
8120
|
}
|
|
7881
|
-
|
|
8121
|
+
const principal = resolvePrincipal(bearerToken(req.headers["authorization"]), {
|
|
8122
|
+
...authStore ? { store: authStore } : {},
|
|
8123
|
+
...token ? { sharedToken: token } : {},
|
|
8124
|
+
defaultTenant,
|
|
8125
|
+
...opts.auth?.required ? { required: true } : {}
|
|
8126
|
+
});
|
|
8127
|
+
if (!principal) {
|
|
7882
8128
|
send(res, 401, { error: "unauthorized" }, { "www-authenticate": "Bearer", ...cors });
|
|
7883
8129
|
finish(401);
|
|
7884
8130
|
return;
|
|
7885
8131
|
}
|
|
7886
|
-
let ctx;
|
|
7887
8132
|
try {
|
|
7888
|
-
|
|
7889
|
-
tenantLabel = ctx.tenant;
|
|
8133
|
+
authorize(principal, "read");
|
|
7890
8134
|
} catch (err) {
|
|
7891
|
-
if (err instanceof
|
|
7892
|
-
send(res,
|
|
7893
|
-
finish(
|
|
8135
|
+
if (err instanceof AuthorizationError) {
|
|
8136
|
+
send(res, 403, { error: "forbidden" }, cors);
|
|
8137
|
+
finish(403);
|
|
7894
8138
|
return;
|
|
7895
8139
|
}
|
|
7896
8140
|
throw err;
|
|
7897
8141
|
}
|
|
8142
|
+
let ctx;
|
|
8143
|
+
if (rbacMode) {
|
|
8144
|
+
ctx = { tenant: principal.tenant };
|
|
8145
|
+
tenantLabel = principal.tenant;
|
|
8146
|
+
} else {
|
|
8147
|
+
try {
|
|
8148
|
+
ctx = resolveTenant(req, url, opts.tenant ?? {});
|
|
8149
|
+
tenantLabel = ctx.tenant;
|
|
8150
|
+
} catch (err) {
|
|
8151
|
+
if (err instanceof InvalidTenantError) {
|
|
8152
|
+
send(res, 400, { error: "invalid tenant" }, cors);
|
|
8153
|
+
finish(400);
|
|
8154
|
+
return;
|
|
8155
|
+
}
|
|
8156
|
+
throw err;
|
|
8157
|
+
}
|
|
8158
|
+
}
|
|
7898
8159
|
if (graphqlEnabled && path === "/graphql") {
|
|
7899
8160
|
if (req.method === "GET") {
|
|
7900
8161
|
const g = handleGraphqlGet();
|
|
@@ -7956,6 +8217,8 @@ function dispatchRest(ctx, path, url, deps) {
|
|
|
7956
8217
|
return handleDiff(ctx, url, deps);
|
|
7957
8218
|
case "/v1/sessions":
|
|
7958
8219
|
return handleSessions(ctx, deps);
|
|
8220
|
+
case "/v1/backstage/catalog":
|
|
8221
|
+
return handleBackstageCatalog(ctx, deps);
|
|
7959
8222
|
default: {
|
|
7960
8223
|
const m = DEPENDENCIES_RE.exec(path);
|
|
7961
8224
|
if (m) return handleDependencies(ctx, decodeURIComponent(m[1]), url, deps);
|
|
@@ -7964,6 +8227,30 @@ function dispatchRest(ctx, path, url, deps) {
|
|
|
7964
8227
|
}
|
|
7965
8228
|
}
|
|
7966
8229
|
|
|
8230
|
+
// src/auth/types.ts
|
|
8231
|
+
import { z as z9 } from "zod";
|
|
8232
|
+
var ROLES = ["viewer", "operator", "admin"];
|
|
8233
|
+
var RoleSchema = z9.enum(ROLES);
|
|
8234
|
+
var ACTIONS = ["read", "discovery", "admin"];
|
|
8235
|
+
var ActionSchema = z9.enum(ACTIONS);
|
|
8236
|
+
var PrincipalSchema = z9.object({
|
|
8237
|
+
subject: z9.string().min(1),
|
|
8238
|
+
tenant: z9.string().min(1),
|
|
8239
|
+
role: RoleSchema
|
|
8240
|
+
});
|
|
8241
|
+
var CredentialConfigSchema = z9.object({
|
|
8242
|
+
token: z9.string().min(1),
|
|
8243
|
+
subject: z9.string().min(1),
|
|
8244
|
+
tenant: z9.string().optional(),
|
|
8245
|
+
role: RoleSchema.default("viewer")
|
|
8246
|
+
});
|
|
8247
|
+
var AuthConfigSchema = z9.object({
|
|
8248
|
+
/** Seed credentials (merged into the SQLite store on startup). */
|
|
8249
|
+
credentials: z9.array(CredentialConfigSchema).optional(),
|
|
8250
|
+
/** Reject unauthenticated requests even on loopback (default: loopback dev stays open). */
|
|
8251
|
+
required: z9.boolean().optional()
|
|
8252
|
+
});
|
|
8253
|
+
|
|
7967
8254
|
// src/api/start.ts
|
|
7968
8255
|
import { readFileSync as readFileSync4 } from "fs";
|
|
7969
8256
|
import { dirname as dirname3, resolve } from "path";
|
|
@@ -7990,6 +8277,7 @@ function parseApiArgs(argv) {
|
|
|
7990
8277
|
else if (a === "--db") opts.dbPath = argv[++i];
|
|
7991
8278
|
else if (a === "--session") opts.session = argv[++i];
|
|
7992
8279
|
else if (a === "--tenant" || a === "--org") opts.tenant = argv[++i];
|
|
8280
|
+
else if (a === "--auth-required") opts.authRequired = true;
|
|
7993
8281
|
else if (a === "--help" || a === "-h") opts.help = true;
|
|
7994
8282
|
}
|
|
7995
8283
|
return opts;
|
|
@@ -8005,11 +8293,13 @@ async function startApi(opts = {}) {
|
|
|
8005
8293
|
const host2 = opts.host ?? "127.0.0.1";
|
|
8006
8294
|
const port = opts.port ?? 3737;
|
|
8007
8295
|
const version = readVersion();
|
|
8296
|
+
const authStore = new SqliteCredentialStore(db);
|
|
8008
8297
|
const server = await runApi({
|
|
8009
8298
|
host: host2,
|
|
8010
8299
|
port,
|
|
8011
8300
|
backend,
|
|
8012
8301
|
version,
|
|
8302
|
+
auth: { store: authStore, ...opts.authRequired ? { required: true } : {} },
|
|
8013
8303
|
...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {},
|
|
8014
8304
|
...opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {},
|
|
8015
8305
|
...token ? { token } : {},
|
|
@@ -8504,13 +8794,13 @@ function createClaudeProvider() {
|
|
|
8504
8794
|
}
|
|
8505
8795
|
|
|
8506
8796
|
// src/providers/shell.ts
|
|
8507
|
-
import { z as
|
|
8797
|
+
import { z as z10 } from "zod";
|
|
8508
8798
|
function createBashTool() {
|
|
8509
8799
|
const shell = IS_WIN ? "powershell" : "posix";
|
|
8510
8800
|
return {
|
|
8511
8801
|
name: "Bash",
|
|
8512
8802
|
description: "Run a read-only shell command (inspect ports, processes, config). Mutating or destructive commands are blocked by the read-only allowlist.",
|
|
8513
|
-
inputShape: { command:
|
|
8803
|
+
inputShape: { command: z10.string().describe("The read-only shell command to run") },
|
|
8514
8804
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
8515
8805
|
handler: async (args) => {
|
|
8516
8806
|
const command = String(args["command"] ?? "").trim();
|
|
@@ -9434,7 +9724,7 @@ var MERMAID_CLASSES = {
|
|
|
9434
9724
|
saas_tool: "fill:#2a1a2a,stroke:#9a3a9a,color:#daf",
|
|
9435
9725
|
unknown: "fill:#2a2a2a,stroke:#5a5a5a,color:#aaa"
|
|
9436
9726
|
};
|
|
9437
|
-
function
|
|
9727
|
+
function sanitize2(id) {
|
|
9438
9728
|
return id.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
9439
9729
|
}
|
|
9440
9730
|
function nodeLabel(node) {
|
|
@@ -9476,14 +9766,14 @@ function generateTopologyMermaid(nodes, edges) {
|
|
|
9476
9766
|
const label = LAYER_LABELS[layerKey] ?? layerKey;
|
|
9477
9767
|
lines.push(` subgraph ${layerKey}["${label}"]`);
|
|
9478
9768
|
for (const node of layerNodes) {
|
|
9479
|
-
lines.push(` ${
|
|
9769
|
+
lines.push(` ${sanitize2(node.id)}${nodeLabel(node)}:::${node.type.replace(/_/g, "")}`);
|
|
9480
9770
|
}
|
|
9481
9771
|
lines.push(" end");
|
|
9482
9772
|
lines.push("");
|
|
9483
9773
|
}
|
|
9484
9774
|
for (const edge of edges) {
|
|
9485
|
-
const src =
|
|
9486
|
-
const tgt =
|
|
9775
|
+
const src = sanitize2(edge.sourceId);
|
|
9776
|
+
const tgt = sanitize2(edge.targetId);
|
|
9487
9777
|
const label = EDGE_LABELS[edge.relationship] ?? edge.relationship;
|
|
9488
9778
|
const arrow = edge.confidence < 0.6 ? `-. "${label}" .->` : `-->|"${label}"|`;
|
|
9489
9779
|
lines.push(` ${src} ${arrow} ${tgt}`);
|
|
@@ -9509,12 +9799,12 @@ function generateDependencyMermaid(nodes, edges) {
|
|
|
9509
9799
|
}
|
|
9510
9800
|
lines.push("");
|
|
9511
9801
|
for (const node of usedNodes) {
|
|
9512
|
-
lines.push(` ${
|
|
9802
|
+
lines.push(` ${sanitize2(node.id)}${nodeLabel(node)}:::${node.type.replace(/_/g, "")}`);
|
|
9513
9803
|
}
|
|
9514
9804
|
lines.push("");
|
|
9515
9805
|
for (const edge of depEdges) {
|
|
9516
9806
|
const label = EDGE_LABELS[edge.relationship] ?? edge.relationship;
|
|
9517
|
-
lines.push(` ${
|
|
9807
|
+
lines.push(` ${sanitize2(edge.sourceId)} -->|"${label}"| ${sanitize2(edge.targetId)}`);
|
|
9518
9808
|
}
|
|
9519
9809
|
return lines.join("\n");
|
|
9520
9810
|
}
|
|
@@ -9565,44 +9855,21 @@ function generateDiffMermaid(diff) {
|
|
|
9565
9855
|
ensureEndpoint(e.targetId);
|
|
9566
9856
|
}
|
|
9567
9857
|
for (const { node, cls, suffix } of entries.values()) {
|
|
9568
|
-
lines.push(` ${
|
|
9858
|
+
lines.push(` ${sanitize2(node.id)}${diffNodeLabel(node, suffix)}:::${cls}`);
|
|
9569
9859
|
}
|
|
9570
9860
|
lines.push("");
|
|
9571
9861
|
for (const e of diff.edges.added) {
|
|
9572
9862
|
const label = EDGE_LABELS[e.relationship] ?? e.relationship;
|
|
9573
|
-
lines.push(` ${
|
|
9863
|
+
lines.push(` ${sanitize2(e.sourceId)} ==>|"+ ${label}"| ${sanitize2(e.targetId)}`);
|
|
9574
9864
|
}
|
|
9575
9865
|
for (const e of diff.edges.removed) {
|
|
9576
9866
|
const label = EDGE_LABELS[e.relationship] ?? e.relationship;
|
|
9577
|
-
lines.push(` ${
|
|
9867
|
+
lines.push(` ${sanitize2(e.sourceId)} -.->|"- ${label}"| ${sanitize2(e.targetId)}`);
|
|
9578
9868
|
}
|
|
9579
9869
|
return lines.join("\n");
|
|
9580
9870
|
}
|
|
9581
9871
|
function exportBackstageYAML(nodes, edges, org) {
|
|
9582
|
-
|
|
9583
|
-
const docs = [];
|
|
9584
|
-
for (const node of nodes) {
|
|
9585
|
-
const isComponent = ["web_service", "container", "pod"].includes(node.type);
|
|
9586
|
-
const isAPI = node.type === "api_endpoint";
|
|
9587
|
-
const kind = isComponent ? "Component" : isAPI ? "API" : "Resource";
|
|
9588
|
-
const deps = edges.filter((e) => e.sourceId === node.id).map((e) => ` - resource:default/${sanitize(e.targetId)}`);
|
|
9589
|
-
const doc = [
|
|
9590
|
-
`apiVersion: backstage.io/v1alpha1`,
|
|
9591
|
-
`kind: ${kind}`,
|
|
9592
|
-
`metadata:`,
|
|
9593
|
-
` name: ${sanitize(node.id)}`,
|
|
9594
|
-
` annotations:`,
|
|
9595
|
-
` cartography/discovered-at: "${node.discoveredAt}"`,
|
|
9596
|
-
` cartography/confidence: "${node.confidence}"`,
|
|
9597
|
-
`spec:`,
|
|
9598
|
-
` type: ${node.type}`,
|
|
9599
|
-
` lifecycle: production`,
|
|
9600
|
-
` owner: ${node.owner ?? owner}`,
|
|
9601
|
-
...deps.length > 0 ? [" dependsOn:", ...deps] : []
|
|
9602
|
-
].join("\n");
|
|
9603
|
-
docs.push(doc);
|
|
9604
|
-
}
|
|
9605
|
-
return docs.join("\n---\n");
|
|
9872
|
+
return entitiesToYaml(toBackstageEntities(nodes, edges, org !== void 0 ? { org } : {}));
|
|
9606
9873
|
}
|
|
9607
9874
|
function exportJSON(db, sessionId) {
|
|
9608
9875
|
const nodes = db.getNodes(sessionId);
|
|
@@ -11022,9 +11289,9 @@ async function runOnce(cfg, db) {
|
|
|
11022
11289
|
}
|
|
11023
11290
|
|
|
11024
11291
|
// src/sync/hash.ts
|
|
11025
|
-
import { createHash as
|
|
11292
|
+
import { createHash as createHash3 } from "crypto";
|
|
11026
11293
|
function shareHash(kind, payload) {
|
|
11027
|
-
return
|
|
11294
|
+
return createHash3("sha256").update(stableStringify({ kind, payload })).digest("hex");
|
|
11028
11295
|
}
|
|
11029
11296
|
|
|
11030
11297
|
// src/sync/classify.ts
|
|
@@ -11068,7 +11335,7 @@ function classify2(input) {
|
|
|
11068
11335
|
}
|
|
11069
11336
|
|
|
11070
11337
|
// src/sync/push.ts
|
|
11071
|
-
import { createHash as
|
|
11338
|
+
import { createHash as createHash4 } from "crypto";
|
|
11072
11339
|
var PUSH_SCHEMA_VERSION = 1;
|
|
11073
11340
|
var DEFAULT_BATCH = 100;
|
|
11074
11341
|
var DEFAULT_RETRIES = 4;
|
|
@@ -11082,7 +11349,7 @@ function defaultSleep(ms) {
|
|
|
11082
11349
|
}
|
|
11083
11350
|
function batchKey(items) {
|
|
11084
11351
|
const hashes = items.map((i) => i.contentHash).sort();
|
|
11085
|
-
return
|
|
11352
|
+
return createHash4("sha256").update(stableStringify(hashes)).digest("hex");
|
|
11086
11353
|
}
|
|
11087
11354
|
async function pushDeltas(config, items, opts = {}) {
|
|
11088
11355
|
const central = config.centralDb;
|
|
@@ -11287,6 +11554,10 @@ function checkClaudePrerequisites() {
|
|
|
11287
11554
|
}
|
|
11288
11555
|
}
|
|
11289
11556
|
export {
|
|
11557
|
+
ACTIONS,
|
|
11558
|
+
ActionSchema,
|
|
11559
|
+
AuthConfigSchema,
|
|
11560
|
+
AuthorizationError,
|
|
11290
11561
|
CLIENTS,
|
|
11291
11562
|
CONFIDENCE,
|
|
11292
11563
|
CartographyDB,
|
|
@@ -11295,6 +11566,7 @@ export {
|
|
|
11295
11566
|
ConditionSchema,
|
|
11296
11567
|
ConfigError,
|
|
11297
11568
|
ControlResultSchema,
|
|
11569
|
+
CredentialConfigSchema,
|
|
11298
11570
|
CsvCostSource,
|
|
11299
11571
|
DEFAULT_ANOMALY_THRESHOLDS,
|
|
11300
11572
|
DEFAULT_SERVER_NAME,
|
|
@@ -11314,8 +11586,11 @@ export {
|
|
|
11314
11586
|
PRIVATE_IP,
|
|
11315
11587
|
PUSH_SCHEMA_VERSION,
|
|
11316
11588
|
PagerDutySink,
|
|
11589
|
+
PrincipalSchema,
|
|
11317
11590
|
ProviderRegistry,
|
|
11318
11591
|
RELATION_TO_DIRECTION,
|
|
11592
|
+
ROLES,
|
|
11593
|
+
RoleSchema,
|
|
11319
11594
|
RuleCheckSchema,
|
|
11320
11595
|
RulesetSchema,
|
|
11321
11596
|
SCAN_ARG_PATTERNS,
|
|
@@ -11326,10 +11601,12 @@ export {
|
|
|
11326
11601
|
ScannerShape,
|
|
11327
11602
|
SharingLevelSchema,
|
|
11328
11603
|
SlackSink,
|
|
11604
|
+
SqliteCredentialStore,
|
|
11329
11605
|
SqliteQueryBackend,
|
|
11330
11606
|
SqliteStoreBackend,
|
|
11331
11607
|
StdoutSink,
|
|
11332
11608
|
TENANT_HEADER,
|
|
11609
|
+
TenantMismatchError,
|
|
11333
11610
|
VectorStore,
|
|
11334
11611
|
WebhookSink,
|
|
11335
11612
|
applyInstall,
|
|
@@ -11337,7 +11614,9 @@ export {
|
|
|
11337
11614
|
assertReadOnly,
|
|
11338
11615
|
assertSafeBind,
|
|
11339
11616
|
assertSafeScanArg,
|
|
11617
|
+
assertSameTenant,
|
|
11340
11618
|
assignColors,
|
|
11619
|
+
authorize,
|
|
11341
11620
|
bearerToken,
|
|
11342
11621
|
bookmarksScanner,
|
|
11343
11622
|
buildCartographyToolHandlers,
|
|
@@ -11345,6 +11624,7 @@ export {
|
|
|
11345
11624
|
buildOpenApiDocument,
|
|
11346
11625
|
buildReport,
|
|
11347
11626
|
buildSinks,
|
|
11627
|
+
can,
|
|
11348
11628
|
centralDbFromEnv,
|
|
11349
11629
|
checkBearer,
|
|
11350
11630
|
checkPrerequisites,
|
|
@@ -11393,6 +11673,7 @@ export {
|
|
|
11393
11673
|
diffTopology,
|
|
11394
11674
|
edgesToConnections,
|
|
11395
11675
|
enrichCosts,
|
|
11676
|
+
entitiesToYaml,
|
|
11396
11677
|
evaluateCheck,
|
|
11397
11678
|
evaluateRule,
|
|
11398
11679
|
evidenceLine,
|
|
@@ -11421,6 +11702,7 @@ export {
|
|
|
11421
11702
|
globalId,
|
|
11422
11703
|
groupByDomain,
|
|
11423
11704
|
handleGraphqlGet,
|
|
11705
|
+
hashToken,
|
|
11424
11706
|
hexCorners,
|
|
11425
11707
|
hexDistance,
|
|
11426
11708
|
hexNeighbors,
|
|
@@ -11487,6 +11769,7 @@ export {
|
|
|
11487
11769
|
renderDiff,
|
|
11488
11770
|
resolveEffectiveLevel,
|
|
11489
11771
|
resolveNlQuery,
|
|
11772
|
+
resolvePrincipal,
|
|
11490
11773
|
resolveSharingLevel,
|
|
11491
11774
|
resolveTenant,
|
|
11492
11775
|
revalidateAnonymized,
|
|
@@ -11506,6 +11789,7 @@ export {
|
|
|
11506
11789
|
safetyHook,
|
|
11507
11790
|
sanitizeUntrusted,
|
|
11508
11791
|
sanitizeValue,
|
|
11792
|
+
scopeReads,
|
|
11509
11793
|
scoreTopology,
|
|
11510
11794
|
securityRelevantChange,
|
|
11511
11795
|
serializeConfig,
|
|
@@ -11519,6 +11803,7 @@ export {
|
|
|
11519
11803
|
startApi,
|
|
11520
11804
|
stripSensitive,
|
|
11521
11805
|
timingSafeEqual,
|
|
11806
|
+
toBackstageEntities,
|
|
11522
11807
|
validateScanner,
|
|
11523
11808
|
vscodeDeeplink,
|
|
11524
11809
|
zodToJsonSchema
|