haansi 0.1.47 → 0.1.48
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/haansi.js +2533 -645
- package/package.json +1 -1
package/dist/haansi.js
CHANGED
|
@@ -95,7 +95,7 @@ var require_package = __commonJS({
|
|
|
95
95
|
"package.json"(exports2, module2) {
|
|
96
96
|
module2.exports = {
|
|
97
97
|
name: "haansi",
|
|
98
|
-
version: "0.1.
|
|
98
|
+
version: "0.1.48",
|
|
99
99
|
description: "Haansi CLI - Session collector and MCP server for Claude Code & OpenAI Codex",
|
|
100
100
|
bin: {
|
|
101
101
|
haansi: "./dist/haansi.js"
|
|
@@ -122,13 +122,693 @@ var require_package = __commonJS({
|
|
|
122
122
|
}
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
+
// ../service-capture/common/agents/types.ts
|
|
126
|
+
var AGENT_IDS;
|
|
127
|
+
var init_types = __esm({
|
|
128
|
+
"../service-capture/common/agents/types.ts"() {
|
|
129
|
+
"use strict";
|
|
130
|
+
AGENT_IDS = ["claude-code", "codex"];
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ../service-capture/common/agents/claude-code.ts
|
|
135
|
+
function projectsDir() {
|
|
136
|
+
return (0, import_path.join)((0, import_os.homedir)(), ".claude", "projects");
|
|
137
|
+
}
|
|
138
|
+
function extractMeta(filePath) {
|
|
139
|
+
const result = {
|
|
140
|
+
cwd: null,
|
|
141
|
+
timestamp: null,
|
|
142
|
+
gitBranch: null
|
|
143
|
+
};
|
|
144
|
+
try {
|
|
145
|
+
const lines = (0, import_fs.readFileSync)(filePath, "utf-8").split("\n").filter((l) => l.trim()).slice(0, 50);
|
|
146
|
+
for (const line of lines) {
|
|
147
|
+
try {
|
|
148
|
+
const entry2 = JSON.parse(line);
|
|
149
|
+
if (!result.cwd && typeof entry2.cwd === "string")
|
|
150
|
+
result.cwd = entry2.cwd;
|
|
151
|
+
if (!result.timestamp && typeof entry2.timestamp === "string")
|
|
152
|
+
result.timestamp = entry2.timestamp;
|
|
153
|
+
if (!result.gitBranch && typeof entry2.gitBranch === "string")
|
|
154
|
+
result.gitBranch = entry2.gitBranch;
|
|
155
|
+
if (result.cwd && result.timestamp && result.gitBranch) break;
|
|
156
|
+
} catch {
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
var import_fs, import_path, import_os, claudeCodeAgent;
|
|
164
|
+
var init_claude_code = __esm({
|
|
165
|
+
"../service-capture/common/agents/claude-code.ts"() {
|
|
166
|
+
"use strict";
|
|
167
|
+
import_fs = require("fs");
|
|
168
|
+
import_path = require("path");
|
|
169
|
+
import_os = require("os");
|
|
170
|
+
claudeCodeAgent = {
|
|
171
|
+
id: "claude-code",
|
|
172
|
+
displayName: "Claude Code",
|
|
173
|
+
sessionsRoot() {
|
|
174
|
+
return projectsDir();
|
|
175
|
+
},
|
|
176
|
+
isInstalled() {
|
|
177
|
+
return (0, import_fs.existsSync)(projectsDir());
|
|
178
|
+
},
|
|
179
|
+
discoverSessions() {
|
|
180
|
+
const root = projectsDir();
|
|
181
|
+
if (!(0, import_fs.existsSync)(root)) return [];
|
|
182
|
+
const sessions = [];
|
|
183
|
+
const projectDirs = (0, import_fs.readdirSync)(root).map((name) => (0, import_path.join)(root, name)).filter((p) => {
|
|
184
|
+
try {
|
|
185
|
+
return (0, import_fs.statSync)(p).isDirectory();
|
|
186
|
+
} catch {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
for (const dir of projectDirs) {
|
|
191
|
+
let files;
|
|
192
|
+
try {
|
|
193
|
+
files = (0, import_fs.readdirSync)(dir).filter((f) => f.endsWith(".jsonl"));
|
|
194
|
+
} catch {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
for (const file2 of files) {
|
|
198
|
+
const filePath = (0, import_path.join)(dir, file2);
|
|
199
|
+
const sessionId = (0, import_path.basename)(file2, ".jsonl");
|
|
200
|
+
try {
|
|
201
|
+
const stat = (0, import_fs.statSync)(filePath);
|
|
202
|
+
const content = (0, import_fs.readFileSync)(filePath, "utf-8");
|
|
203
|
+
const lineCount = content.split("\n").filter((l) => l.trim()).length;
|
|
204
|
+
const meta3 = extractMeta(filePath);
|
|
205
|
+
sessions.push({
|
|
206
|
+
sessionId,
|
|
207
|
+
filePath,
|
|
208
|
+
lineCount,
|
|
209
|
+
sizeBytes: stat.size,
|
|
210
|
+
...meta3
|
|
211
|
+
});
|
|
212
|
+
} catch {
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return sessions;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// ../service-capture/common/agents/codex.ts
|
|
223
|
+
function codexHome() {
|
|
224
|
+
return process.env.CODEX_HOME || (0, import_path2.join)((0, import_os2.homedir)(), ".codex");
|
|
225
|
+
}
|
|
226
|
+
function sessionsDir() {
|
|
227
|
+
return (0, import_path2.join)(codexHome(), "sessions");
|
|
228
|
+
}
|
|
229
|
+
function walkRollouts(dir, out) {
|
|
230
|
+
let entries;
|
|
231
|
+
try {
|
|
232
|
+
entries = (0, import_fs2.readdirSync)(dir);
|
|
233
|
+
} catch {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
for (const name of entries) {
|
|
237
|
+
const full = (0, import_path2.join)(dir, name);
|
|
238
|
+
let isDir = false;
|
|
239
|
+
try {
|
|
240
|
+
isDir = (0, import_fs2.statSync)(full).isDirectory();
|
|
241
|
+
} catch {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (isDir) {
|
|
245
|
+
walkRollouts(full, out);
|
|
246
|
+
} else if (name.startsWith("rollout-") && name.endsWith(".jsonl")) {
|
|
247
|
+
out.push(full);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function readRolloutMeta(filePath, content) {
|
|
252
|
+
const fileName = (0, import_path2.basename)(filePath, ".jsonl");
|
|
253
|
+
const fromName = UUID_RE.exec(fileName)?.[1];
|
|
254
|
+
let cwd = null;
|
|
255
|
+
let timestamp = null;
|
|
256
|
+
let gitBranch = null;
|
|
257
|
+
let gitRemoteUrl = null;
|
|
258
|
+
let gitCommit = null;
|
|
259
|
+
let metaId = null;
|
|
260
|
+
const lines = content.split("\n").filter((l) => l.trim()).slice(0, 50);
|
|
261
|
+
for (const line of lines) {
|
|
262
|
+
try {
|
|
263
|
+
const entry2 = JSON.parse(line);
|
|
264
|
+
if (!timestamp && typeof entry2.timestamp === "string") {
|
|
265
|
+
timestamp = entry2.timestamp;
|
|
266
|
+
}
|
|
267
|
+
const p = entry2.payload ?? {};
|
|
268
|
+
if (entry2.type === "session_meta") {
|
|
269
|
+
if (typeof p.cwd === "string") cwd = p.cwd;
|
|
270
|
+
if (typeof p.id === "string") metaId = p.id;
|
|
271
|
+
const g = p.git ?? {};
|
|
272
|
+
const branch = g.branch ?? p.git_branch;
|
|
273
|
+
if (typeof branch === "string") gitBranch = branch;
|
|
274
|
+
const remote = g.repository_url ?? g.remote_url;
|
|
275
|
+
if (typeof remote === "string") gitRemoteUrl = remote;
|
|
276
|
+
const commit = g.commit_hash ?? g.commit;
|
|
277
|
+
if (typeof commit === "string") gitCommit = commit;
|
|
278
|
+
}
|
|
279
|
+
if (cwd && gitBranch && gitCommit && timestamp && metaId) break;
|
|
280
|
+
} catch {
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
sessionId: fromName ?? metaId ?? fileName,
|
|
285
|
+
cwd,
|
|
286
|
+
timestamp,
|
|
287
|
+
gitBranch,
|
|
288
|
+
gitRemoteUrl,
|
|
289
|
+
gitCommit
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
var import_fs2, import_path2, import_os2, UUID_RE, codexAgent;
|
|
293
|
+
var init_codex = __esm({
|
|
294
|
+
"../service-capture/common/agents/codex.ts"() {
|
|
295
|
+
"use strict";
|
|
296
|
+
import_fs2 = require("fs");
|
|
297
|
+
import_path2 = require("path");
|
|
298
|
+
import_os2 = require("os");
|
|
299
|
+
UUID_RE = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i;
|
|
300
|
+
codexAgent = {
|
|
301
|
+
id: "codex",
|
|
302
|
+
displayName: "OpenAI Codex",
|
|
303
|
+
sessionsRoot() {
|
|
304
|
+
return sessionsDir();
|
|
305
|
+
},
|
|
306
|
+
isInstalled() {
|
|
307
|
+
return (0, import_fs2.existsSync)(codexHome());
|
|
308
|
+
},
|
|
309
|
+
discoverSessions() {
|
|
310
|
+
const root = sessionsDir();
|
|
311
|
+
if (!(0, import_fs2.existsSync)(root)) return [];
|
|
312
|
+
const files = [];
|
|
313
|
+
walkRollouts(root, files);
|
|
314
|
+
const sessions = [];
|
|
315
|
+
for (const filePath of files) {
|
|
316
|
+
try {
|
|
317
|
+
const stat = (0, import_fs2.statSync)(filePath);
|
|
318
|
+
const content = (0, import_fs2.readFileSync)(filePath, "utf-8");
|
|
319
|
+
const lineCount = content.split("\n").filter((l) => l.trim()).length;
|
|
320
|
+
const meta3 = readRolloutMeta(filePath, content);
|
|
321
|
+
sessions.push({
|
|
322
|
+
sessionId: meta3.sessionId,
|
|
323
|
+
filePath,
|
|
324
|
+
lineCount,
|
|
325
|
+
sizeBytes: stat.size,
|
|
326
|
+
cwd: meta3.cwd,
|
|
327
|
+
timestamp: meta3.timestamp,
|
|
328
|
+
gitBranch: meta3.gitBranch,
|
|
329
|
+
gitRemoteUrl: meta3.gitRemoteUrl,
|
|
330
|
+
gitCommit: meta3.gitCommit
|
|
331
|
+
});
|
|
332
|
+
} catch {
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return sessions;
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// ../service-capture/common/agents/registry.ts
|
|
342
|
+
function getAgents() {
|
|
343
|
+
return AGENTS;
|
|
344
|
+
}
|
|
345
|
+
function getAgent(id) {
|
|
346
|
+
const agent = AGENTS.find((a) => a.id === id);
|
|
347
|
+
if (!agent) throw new Error(`Unknown agent: ${id}`);
|
|
348
|
+
return agent;
|
|
349
|
+
}
|
|
350
|
+
function detectInstalled() {
|
|
351
|
+
return AGENTS.filter((a) => a.isInstalled());
|
|
352
|
+
}
|
|
353
|
+
var AGENTS;
|
|
354
|
+
var init_registry = __esm({
|
|
355
|
+
"../service-capture/common/agents/registry.ts"() {
|
|
356
|
+
"use strict";
|
|
357
|
+
init_claude_code();
|
|
358
|
+
init_codex();
|
|
359
|
+
AGENTS = [claudeCodeAgent, codexAgent];
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// ../service-capture/common/agents/index.ts
|
|
364
|
+
var agents_exports = {};
|
|
365
|
+
__export(agents_exports, {
|
|
366
|
+
AGENT_IDS: () => AGENT_IDS,
|
|
367
|
+
claudeCodeAgent: () => claudeCodeAgent,
|
|
368
|
+
codexAgent: () => codexAgent,
|
|
369
|
+
detectInstalled: () => detectInstalled,
|
|
370
|
+
getAgent: () => getAgent,
|
|
371
|
+
getAgents: () => getAgents
|
|
372
|
+
});
|
|
373
|
+
var init_agents = __esm({
|
|
374
|
+
"../service-capture/common/agents/index.ts"() {
|
|
375
|
+
"use strict";
|
|
376
|
+
init_types();
|
|
377
|
+
init_claude_code();
|
|
378
|
+
init_codex();
|
|
379
|
+
init_registry();
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// src/lib/agent-selection.ts
|
|
384
|
+
var agent_selection_exports = {};
|
|
385
|
+
__export(agent_selection_exports, {
|
|
386
|
+
parseAgentFlag: () => parseAgentFlag,
|
|
387
|
+
promptForAgents: () => promptForAgents,
|
|
388
|
+
readSavedAgents: () => readSavedAgents,
|
|
389
|
+
resolveAgentSelection: () => resolveAgentSelection,
|
|
390
|
+
saveAgents: () => saveAgents
|
|
391
|
+
});
|
|
392
|
+
function parseAgentFlag(value) {
|
|
393
|
+
if (!value) return null;
|
|
394
|
+
const normalized = value.trim().toLowerCase();
|
|
395
|
+
if (normalized === "both" || normalized === "all") return [...VALID];
|
|
396
|
+
const out = [];
|
|
397
|
+
for (const part of normalized.split(",").map((s) => s.trim())) {
|
|
398
|
+
if (part === "claude" || part === "claude-code") {
|
|
399
|
+
if (!out.includes("claude-code")) out.push("claude-code");
|
|
400
|
+
} else if (part === "codex") {
|
|
401
|
+
if (!out.includes("codex")) out.push("codex");
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return out.length > 0 ? out : null;
|
|
405
|
+
}
|
|
406
|
+
function readSavedAgents() {
|
|
407
|
+
if (!(0, import_node_fs2.existsSync)(CONFIG_FILE)) return null;
|
|
408
|
+
try {
|
|
409
|
+
const cfg = JSON.parse((0, import_node_fs2.readFileSync)(CONFIG_FILE, "utf-8"));
|
|
410
|
+
if (Array.isArray(cfg.agents)) {
|
|
411
|
+
const agents = cfg.agents.filter(
|
|
412
|
+
(a) => VALID.includes(a)
|
|
413
|
+
);
|
|
414
|
+
return agents.length > 0 ? agents : null;
|
|
415
|
+
}
|
|
416
|
+
} catch {
|
|
417
|
+
}
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
function saveAgents(agents) {
|
|
421
|
+
let cfg = {};
|
|
422
|
+
if ((0, import_node_fs2.existsSync)(CONFIG_FILE)) {
|
|
423
|
+
try {
|
|
424
|
+
cfg = JSON.parse((0, import_node_fs2.readFileSync)(CONFIG_FILE, "utf-8"));
|
|
425
|
+
} catch {
|
|
426
|
+
cfg = {};
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
cfg.agents = agents;
|
|
430
|
+
(0, import_node_fs2.mkdirSync)(HAANSI_DIR, { recursive: true });
|
|
431
|
+
(0, import_node_fs2.writeFileSync)(CONFIG_FILE, JSON.stringify(cfg, null, 2) + "\n", {
|
|
432
|
+
encoding: "utf-8",
|
|
433
|
+
mode: 384
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
function resolveAgentSelection(flagValue) {
|
|
437
|
+
const fromFlag = parseAgentFlag(flagValue);
|
|
438
|
+
if (fromFlag) return { agents: fromFlag, source: "flag" };
|
|
439
|
+
const saved = readSavedAgents();
|
|
440
|
+
if (saved) return { agents: saved, source: "config" };
|
|
441
|
+
const detected = detectInstalled().map((a) => a.id);
|
|
442
|
+
if (detected.length > 0) return { agents: detected, source: "detected" };
|
|
443
|
+
return { agents: ["claude-code"], source: "default" };
|
|
444
|
+
}
|
|
445
|
+
async function promptForAgents() {
|
|
446
|
+
const installed = new Set(detectInstalled().map((a) => a.id));
|
|
447
|
+
const agents = getAgents();
|
|
448
|
+
console.log("\nWhich coding agents should Haansi connect?\n");
|
|
449
|
+
agents.forEach((a, i) => {
|
|
450
|
+
const mark = installed.has(a.id) ? "installed" : "not detected";
|
|
451
|
+
console.log(` ${i + 1}) ${a.displayName} (${mark})`);
|
|
452
|
+
});
|
|
453
|
+
const rl = readline.createInterface({
|
|
454
|
+
input: process.stdin,
|
|
455
|
+
output: process.stdout
|
|
456
|
+
});
|
|
457
|
+
const answer = await new Promise((resolve5) => {
|
|
458
|
+
rl.question(
|
|
459
|
+
"\nEnter numbers (comma-separated), or press Enter for all installed: ",
|
|
460
|
+
(a) => {
|
|
461
|
+
rl.close();
|
|
462
|
+
resolve5(a.trim());
|
|
463
|
+
}
|
|
464
|
+
);
|
|
465
|
+
});
|
|
466
|
+
if (!answer) {
|
|
467
|
+
const detected = agents.filter((a) => installed.has(a.id)).map((a) => a.id);
|
|
468
|
+
return detected.length > 0 ? detected : [...VALID];
|
|
469
|
+
}
|
|
470
|
+
const picked = [];
|
|
471
|
+
for (const token of answer.split(",").map((s) => s.trim())) {
|
|
472
|
+
const idx = parseInt(token, 10) - 1;
|
|
473
|
+
const agent = agents[idx];
|
|
474
|
+
if (agent && !picked.includes(agent.id)) picked.push(agent.id);
|
|
475
|
+
}
|
|
476
|
+
return picked.length > 0 ? picked : [...VALID];
|
|
477
|
+
}
|
|
478
|
+
var import_node_fs2, readline, VALID;
|
|
479
|
+
var init_agent_selection = __esm({
|
|
480
|
+
"src/lib/agent-selection.ts"() {
|
|
481
|
+
"use strict";
|
|
482
|
+
import_node_fs2 = require("node:fs");
|
|
483
|
+
readline = __toESM(require("node:readline"));
|
|
484
|
+
init_agents();
|
|
485
|
+
init_paths();
|
|
486
|
+
VALID = ["claude-code", "codex"];
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
// src/lib/codex-config.ts
|
|
491
|
+
function tomlString(value) {
|
|
492
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
493
|
+
return `"${escaped}"`;
|
|
494
|
+
}
|
|
495
|
+
function tomlStringArray(values) {
|
|
496
|
+
return `[${values.map(tomlString).join(", ")}]`;
|
|
497
|
+
}
|
|
498
|
+
function tomlInlineTable(entries) {
|
|
499
|
+
const parts = Object.entries(entries).map(
|
|
500
|
+
([k, v]) => `${k} = ${tomlString(v)}`
|
|
501
|
+
);
|
|
502
|
+
return `{ ${parts.join(", ")} }`;
|
|
503
|
+
}
|
|
504
|
+
function upsertTomlTable(content, tablePath, entries) {
|
|
505
|
+
const bodyLines = Object.entries(entries).map(([k, v]) => `${k} = ${v}`);
|
|
506
|
+
const block = [`[${tablePath}]`, ...bodyLines].join("\n");
|
|
507
|
+
const lines = content.split("\n");
|
|
508
|
+
const headerRe = new RegExp(
|
|
509
|
+
`^\\s*\\[\\s*${tablePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\]\\s*$`
|
|
510
|
+
);
|
|
511
|
+
const startIdx = lines.findIndex((l) => headerRe.test(l));
|
|
512
|
+
if (startIdx === -1) {
|
|
513
|
+
const trimmed = content.replace(/\s*$/, "");
|
|
514
|
+
return trimmed.length > 0 ? `${trimmed}
|
|
515
|
+
|
|
516
|
+
${block}
|
|
517
|
+
` : `${block}
|
|
518
|
+
`;
|
|
519
|
+
}
|
|
520
|
+
let endIdx = lines.length;
|
|
521
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
522
|
+
if (/^\s*\[/.test(lines[i])) {
|
|
523
|
+
endIdx = i;
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
const before = lines.slice(0, startIdx);
|
|
528
|
+
const after = lines.slice(endIdx);
|
|
529
|
+
const rebuilt = [...before, ...block.split("\n"), ...after].join("\n");
|
|
530
|
+
return rebuilt.replace(/\n{3,}/g, "\n\n");
|
|
531
|
+
}
|
|
532
|
+
var init_codex_config = __esm({
|
|
533
|
+
"src/lib/codex-config.ts"() {
|
|
534
|
+
"use strict";
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// src/commands/setup-mcp.ts
|
|
539
|
+
var setup_mcp_exports = {};
|
|
540
|
+
__export(setup_mcp_exports, {
|
|
541
|
+
HTTP_SERVERS: () => HTTP_SERVERS,
|
|
542
|
+
parseTarget: () => parseTarget,
|
|
543
|
+
refreshClaudeConfigTokens: () => refreshClaudeConfigTokens,
|
|
544
|
+
refreshCodexConfigTokens: () => refreshCodexConfigTokens,
|
|
545
|
+
refreshMcpTokens: () => refreshMcpTokens,
|
|
546
|
+
setupMcp: () => setupMcp
|
|
547
|
+
});
|
|
548
|
+
function parseTarget(arg) {
|
|
549
|
+
if (!arg || arg === "all") return "all";
|
|
550
|
+
if (arg === "solutions" || arg === "exchange" || arg === "digests" || arg === "onboarding" || arg === "skills")
|
|
551
|
+
return arg;
|
|
552
|
+
throw new Error(
|
|
553
|
+
`Unknown target: ${arg}. Use one of: solutions, exchange, digests, onboarding, skills, all (default).`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
function httpServerUrl(path2) {
|
|
557
|
+
return `${apiUrl().replace(/\/+$/, "")}${path2}`;
|
|
558
|
+
}
|
|
559
|
+
function setupClaudeMcp(target) {
|
|
560
|
+
let config6 = {};
|
|
561
|
+
if ((0, import_node_fs3.existsSync)(CLAUDE_JSON)) {
|
|
562
|
+
try {
|
|
563
|
+
config6 = JSON.parse((0, import_node_fs3.readFileSync)(CLAUDE_JSON, "utf-8"));
|
|
564
|
+
} catch {
|
|
565
|
+
console.error(
|
|
566
|
+
`Warning: Could not parse ${CLAUDE_JSON} \u2014 will overwrite with new config.`
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (!config6.mcpServers) config6.mcpServers = {};
|
|
571
|
+
const installed = [];
|
|
572
|
+
const warnings = [];
|
|
573
|
+
if (target === "all" || target === "solutions") {
|
|
574
|
+
const haansiBin = resolveHaansiBin();
|
|
575
|
+
config6.mcpServers["haansi-solutions"] = {
|
|
576
|
+
command: haansiBin,
|
|
577
|
+
args: ["mcp-server"],
|
|
578
|
+
env: { HAANSI_AGENT_SOURCE: "claude-code" }
|
|
579
|
+
};
|
|
580
|
+
installed.push(`haansi-solutions (stdio \u2192 ${haansiBin} mcp-server)`);
|
|
581
|
+
}
|
|
582
|
+
for (const http of HTTP_SERVERS) {
|
|
583
|
+
if (target !== "all" && target !== http.target) continue;
|
|
584
|
+
const token = resolveToken();
|
|
585
|
+
if (!token) {
|
|
586
|
+
warnings.push(
|
|
587
|
+
`${http.name} not registered: no token found. Run \`haansi auth login\` first, or set HAANSI_TOKEN.`
|
|
588
|
+
);
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
const url2 = httpServerUrl(http.path);
|
|
592
|
+
config6.mcpServers[http.name] = {
|
|
593
|
+
type: "http",
|
|
594
|
+
url: url2,
|
|
595
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
596
|
+
};
|
|
597
|
+
installed.push(`${http.name} (http \u2192 ${url2})`);
|
|
598
|
+
}
|
|
599
|
+
(0, import_node_fs3.writeFileSync)(CLAUDE_JSON, JSON.stringify(config6, null, 2) + "\n", "utf-8");
|
|
600
|
+
console.log(`
|
|
601
|
+
Claude Code \u2014 configured in ${CLAUDE_JSON}`);
|
|
602
|
+
for (const entry2 of installed) console.log(` \u2022 ${entry2}`);
|
|
603
|
+
for (const w of warnings) console.log(` ! ${w}`);
|
|
604
|
+
return { installed, warnings };
|
|
605
|
+
}
|
|
606
|
+
function setupCodexMcp(target) {
|
|
607
|
+
let content = "";
|
|
608
|
+
if ((0, import_node_fs3.existsSync)(CODEX_CONFIG)) {
|
|
609
|
+
try {
|
|
610
|
+
content = (0, import_node_fs3.readFileSync)(CODEX_CONFIG, "utf-8");
|
|
611
|
+
} catch {
|
|
612
|
+
console.error(
|
|
613
|
+
`Warning: Could not read ${CODEX_CONFIG} \u2014 will create a new file.`
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
const installed = [];
|
|
618
|
+
const warnings = [];
|
|
619
|
+
if (target === "all" || target === "solutions") {
|
|
620
|
+
const haansiBin = resolveHaansiBin();
|
|
621
|
+
content = upsertTomlTable(content, "mcp_servers.haansi-solutions", {
|
|
622
|
+
command: tomlString(haansiBin),
|
|
623
|
+
args: tomlStringArray(["mcp-server"]),
|
|
624
|
+
env: tomlInlineTable({ HAANSI_AGENT_SOURCE: "codex" })
|
|
625
|
+
});
|
|
626
|
+
installed.push(`haansi-solutions (stdio \u2192 ${haansiBin} mcp-server)`);
|
|
627
|
+
}
|
|
628
|
+
for (const http of HTTP_SERVERS) {
|
|
629
|
+
if (target !== "all" && target !== http.target) continue;
|
|
630
|
+
const token = resolveToken();
|
|
631
|
+
if (!token) {
|
|
632
|
+
warnings.push(
|
|
633
|
+
`${http.name} not registered: no token found. Run \`haansi auth login\` first, or set HAANSI_TOKEN.`
|
|
634
|
+
);
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
const url2 = httpServerUrl(http.path);
|
|
638
|
+
content = upsertTomlTable(content, `mcp_servers.${http.name}`, {
|
|
639
|
+
url: tomlString(url2),
|
|
640
|
+
http_headers: tomlInlineTable({ Authorization: `Bearer ${token}` })
|
|
641
|
+
});
|
|
642
|
+
installed.push(`${http.name} (http \u2192 ${url2})`);
|
|
643
|
+
}
|
|
644
|
+
(0, import_node_fs3.mkdirSync)(CODEX_HOME, { recursive: true });
|
|
645
|
+
(0, import_node_fs3.writeFileSync)(CODEX_CONFIG, content, "utf-8");
|
|
646
|
+
console.log(`
|
|
647
|
+
OpenAI Codex \u2014 configured in ${CODEX_CONFIG}`);
|
|
648
|
+
for (const entry2 of installed) console.log(` \u2022 ${entry2}`);
|
|
649
|
+
for (const w of warnings) console.log(` ! ${w}`);
|
|
650
|
+
return { installed, warnings };
|
|
651
|
+
}
|
|
652
|
+
function refreshClaudeConfigTokens(config6, token) {
|
|
653
|
+
const changed = [];
|
|
654
|
+
const scopes = [
|
|
655
|
+
config6.mcpServers,
|
|
656
|
+
...Object.values(config6.projects ?? {}).map(
|
|
657
|
+
(p) => p?.mcpServers
|
|
658
|
+
)
|
|
659
|
+
];
|
|
660
|
+
for (const servers of scopes) {
|
|
661
|
+
if (!servers || typeof servers !== "object") continue;
|
|
662
|
+
for (const [name, entry2] of Object.entries(
|
|
663
|
+
servers
|
|
664
|
+
)) {
|
|
665
|
+
if (!HTTP_SERVER_NAMES.has(name)) continue;
|
|
666
|
+
const auth = entry2?.headers?.Authorization;
|
|
667
|
+
if (typeof auth !== "string" || !HSK_BEARER.test(auth)) continue;
|
|
668
|
+
const next = `Bearer ${token}`;
|
|
669
|
+
if (auth === next) continue;
|
|
670
|
+
entry2.headers.Authorization = next;
|
|
671
|
+
changed.push(name);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return changed;
|
|
675
|
+
}
|
|
676
|
+
function refreshCodexConfigTokens(content, token) {
|
|
677
|
+
const changed = [];
|
|
678
|
+
const lines = content.split("\n");
|
|
679
|
+
let current = null;
|
|
680
|
+
for (let i = 0; i < lines.length; i++) {
|
|
681
|
+
const header = /^\s*\[\s*mcp_servers\.([^\]\s]+)\s*\]\s*$/.exec(lines[i]);
|
|
682
|
+
if (header) {
|
|
683
|
+
current = HTTP_SERVER_NAMES.has(header[1]) ? header[1] : null;
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
if (/^\s*\[/.test(lines[i])) {
|
|
687
|
+
current = null;
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
if (!current || !HSK_BEARER.test(lines[i])) continue;
|
|
691
|
+
const next = lines[i].replace(HSK_BEARER, `Bearer ${token}`);
|
|
692
|
+
if (next !== lines[i]) {
|
|
693
|
+
lines[i] = next;
|
|
694
|
+
if (!changed.includes(current)) changed.push(current);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return { content: lines.join("\n"), changed };
|
|
698
|
+
}
|
|
699
|
+
function refreshMcpTokens(token, files = {}) {
|
|
700
|
+
const claudeJson = files.claudeJson ?? CLAUDE_JSON;
|
|
701
|
+
const codexConfig = files.codexConfig ?? CODEX_CONFIG;
|
|
702
|
+
const updated = [];
|
|
703
|
+
try {
|
|
704
|
+
if ((0, import_node_fs3.existsSync)(claudeJson)) {
|
|
705
|
+
const config6 = JSON.parse((0, import_node_fs3.readFileSync)(claudeJson, "utf-8"));
|
|
706
|
+
const changed = refreshClaudeConfigTokens(config6, token);
|
|
707
|
+
if (changed.length > 0) {
|
|
708
|
+
(0, import_node_fs3.writeFileSync)(
|
|
709
|
+
claudeJson,
|
|
710
|
+
JSON.stringify(config6, null, 2) + "\n",
|
|
711
|
+
"utf-8"
|
|
712
|
+
);
|
|
713
|
+
updated.push(...changed.map((n) => `Claude Code: ${n}`));
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
} catch {
|
|
717
|
+
console.error(
|
|
718
|
+
`Warning: could not update the MCP token in ${claudeJson}. Run \`haansi setup-mcp\`.`
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
try {
|
|
722
|
+
if ((0, import_node_fs3.existsSync)(codexConfig)) {
|
|
723
|
+
const result = refreshCodexConfigTokens(
|
|
724
|
+
(0, import_node_fs3.readFileSync)(codexConfig, "utf-8"),
|
|
725
|
+
token
|
|
726
|
+
);
|
|
727
|
+
if (result.changed.length > 0) {
|
|
728
|
+
(0, import_node_fs3.writeFileSync)(codexConfig, result.content, "utf-8");
|
|
729
|
+
updated.push(...result.changed.map((n) => `Codex: ${n}`));
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
} catch {
|
|
733
|
+
console.error(
|
|
734
|
+
`Warning: could not update the MCP token in ${codexConfig}. Run \`haansi setup-mcp\`.`
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
if (updated.length > 0) {
|
|
738
|
+
console.log("Updated the token for your Haansi MCP servers:");
|
|
739
|
+
for (const u of updated) console.log(` \u2022 ${u}`);
|
|
740
|
+
console.log(
|
|
741
|
+
"Restart your coding agent (or reconnect with /mcp) to use it."
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
return updated;
|
|
745
|
+
}
|
|
746
|
+
async function setupMcp(targetArg, agentFlag) {
|
|
747
|
+
const target = parseTarget(targetArg);
|
|
748
|
+
const selection = resolveAgentSelection(agentFlag);
|
|
749
|
+
for (const agentId of selection.agents) {
|
|
750
|
+
WRITERS[agentId](target);
|
|
751
|
+
}
|
|
752
|
+
saveAgents(selection.agents);
|
|
753
|
+
const restart = selection.agents.map((a) => a === "codex" ? "Codex" : "Claude Code").join(" / ");
|
|
754
|
+
console.log(`
|
|
755
|
+
Restart ${restart} to activate the changes.`);
|
|
756
|
+
}
|
|
757
|
+
var import_node_fs3, import_node_path2, import_node_os2, CLAUDE_JSON, CODEX_HOME, CODEX_CONFIG, HTTP_SERVERS, HSK_BEARER, HTTP_SERVER_NAMES, WRITERS;
|
|
758
|
+
var init_setup_mcp = __esm({
|
|
759
|
+
"src/commands/setup-mcp.ts"() {
|
|
760
|
+
"use strict";
|
|
761
|
+
import_node_fs3 = require("node:fs");
|
|
762
|
+
import_node_path2 = require("node:path");
|
|
763
|
+
import_node_os2 = require("node:os");
|
|
764
|
+
init_paths();
|
|
765
|
+
init_agent_selection();
|
|
766
|
+
init_codex_config();
|
|
767
|
+
CLAUDE_JSON = (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".claude.json");
|
|
768
|
+
CODEX_HOME = process.env.CODEX_HOME || (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".codex");
|
|
769
|
+
CODEX_CONFIG = (0, import_node_path2.join)(CODEX_HOME, "config.toml");
|
|
770
|
+
HTTP_SERVERS = [
|
|
771
|
+
{
|
|
772
|
+
target: "exchange",
|
|
773
|
+
name: "haansi-exchange",
|
|
774
|
+
path: "/api/v1/memory/exchange/mcp"
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
target: "digests",
|
|
778
|
+
name: "haansi-digests",
|
|
779
|
+
path: "/api/v1/memory/digests/mcp"
|
|
780
|
+
},
|
|
781
|
+
// Published onboarding guides, so an agent can pick up a workstream from
|
|
782
|
+
// its handoff doc (docs/product/plans/2026-09-24-onboarding-guides.md §8.3).
|
|
783
|
+
{
|
|
784
|
+
target: "onboarding",
|
|
785
|
+
name: "haansi-onboarding",
|
|
786
|
+
path: "/api/v1/memory/onboarding/mcp"
|
|
787
|
+
},
|
|
788
|
+
// Published team skills, loaded progressively by coding agents
|
|
789
|
+
// (docs/product/plans/2026-10-10-memory-skills.md §7).
|
|
790
|
+
{
|
|
791
|
+
target: "skills",
|
|
792
|
+
name: "haansi-skills",
|
|
793
|
+
path: "/api/v1/memory/skills/mcp"
|
|
794
|
+
}
|
|
795
|
+
];
|
|
796
|
+
HSK_BEARER = /Bearer hsk_[A-Za-z0-9_-]+/;
|
|
797
|
+
HTTP_SERVER_NAMES = new Set(HTTP_SERVERS.map((s) => s.name));
|
|
798
|
+
WRITERS = {
|
|
799
|
+
"claude-code": setupClaudeMcp,
|
|
800
|
+
codex: setupCodexMcp
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
|
|
125
805
|
// src/commands/auth-login.ts
|
|
126
806
|
var auth_login_exports = {};
|
|
127
807
|
__export(auth_login_exports, {
|
|
128
808
|
authLogin: () => authLogin
|
|
129
809
|
});
|
|
130
810
|
function openBrowser(url2) {
|
|
131
|
-
const cmd = (0,
|
|
811
|
+
const cmd = (0, import_node_os3.platform)() === "darwin" ? "open" : (0, import_node_os3.platform)() === "win32" ? "start" : "xdg-open";
|
|
132
812
|
try {
|
|
133
813
|
(0, import_node_child_process2.exec)(`${cmd} ${JSON.stringify(url2)}`);
|
|
134
814
|
return true;
|
|
@@ -218,8 +898,8 @@ If the browser didn't open, visit this URL:
|
|
|
218
898
|
);
|
|
219
899
|
}
|
|
220
900
|
const { token: hskToken } = await tokenRes.json();
|
|
221
|
-
(0,
|
|
222
|
-
(0,
|
|
901
|
+
(0, import_node_fs4.mkdirSync)(HAANSI_DIR2, { recursive: true });
|
|
902
|
+
(0, import_node_fs4.writeFileSync)(
|
|
223
903
|
CREDENTIALS_FILE2,
|
|
224
904
|
JSON.stringify({ token: hskToken }, null, 2),
|
|
225
905
|
{
|
|
@@ -230,21 +910,23 @@ If the browser didn't open, visit this URL:
|
|
|
230
910
|
console.log(`
|
|
231
911
|
Authenticated successfully${orgName ? ` (${orgName})` : ""}!`);
|
|
232
912
|
console.log(`Token saved to ${CREDENTIALS_FILE2}`);
|
|
913
|
+
refreshMcpTokens(hskToken);
|
|
233
914
|
console.log(
|
|
234
915
|
"Run `haansi collect` to upload sessions or `haansi setup-mcp` to configure Claude Code."
|
|
235
916
|
);
|
|
236
917
|
}
|
|
237
|
-
var import_node_child_process2,
|
|
918
|
+
var import_node_child_process2, import_node_fs4, import_node_path3, import_node_os3, HAANSI_DIR2, CREDENTIALS_FILE2, POLL_INTERVAL_MS, TIMEOUT_MS;
|
|
238
919
|
var init_auth_login = __esm({
|
|
239
920
|
"src/commands/auth-login.ts"() {
|
|
240
921
|
"use strict";
|
|
241
922
|
import_node_child_process2 = require("node:child_process");
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
923
|
+
import_node_fs4 = require("node:fs");
|
|
924
|
+
import_node_path3 = require("node:path");
|
|
925
|
+
import_node_os3 = require("node:os");
|
|
245
926
|
init_paths();
|
|
246
|
-
|
|
247
|
-
|
|
927
|
+
init_setup_mcp();
|
|
928
|
+
HAANSI_DIR2 = (0, import_node_path3.join)((0, import_node_os3.homedir)(), ".haansi");
|
|
929
|
+
CREDENTIALS_FILE2 = (0, import_node_path3.join)(HAANSI_DIR2, "credentials.json");
|
|
248
930
|
POLL_INTERVAL_MS = 2e3;
|
|
249
931
|
TIMEOUT_MS = 5 * 60 * 1e3;
|
|
250
932
|
}
|
|
@@ -256,7 +938,7 @@ __export(init_exports, {
|
|
|
256
938
|
init: () => init
|
|
257
939
|
});
|
|
258
940
|
function prompt(question) {
|
|
259
|
-
const rl =
|
|
941
|
+
const rl = readline2.createInterface({
|
|
260
942
|
input: process.stdin,
|
|
261
943
|
output: process.stdout
|
|
262
944
|
});
|
|
@@ -304,28 +986,30 @@ async function init() {
|
|
|
304
986
|
}
|
|
305
987
|
console.log("\nValidating token...");
|
|
306
988
|
await validateToken(token, apiUrl2);
|
|
307
|
-
(0,
|
|
308
|
-
(0,
|
|
989
|
+
(0, import_node_fs5.mkdirSync)(HAANSI_DIR3, { recursive: true });
|
|
990
|
+
(0, import_node_fs5.writeFileSync)(CREDENTIALS_FILE3, JSON.stringify({ token }, null, 2), {
|
|
309
991
|
encoding: "utf-8",
|
|
310
992
|
mode: 384
|
|
311
993
|
});
|
|
312
994
|
console.log(`
|
|
313
995
|
Token saved to ${CREDENTIALS_FILE3}`);
|
|
996
|
+
refreshMcpTokens(token);
|
|
314
997
|
console.log(
|
|
315
998
|
"Run `haansi daemon` to start collecting or `haansi setup-mcp` to configure your coding agent(s) (Claude Code / Codex)."
|
|
316
999
|
);
|
|
317
1000
|
}
|
|
318
|
-
var
|
|
1001
|
+
var readline2, import_node_fs5, import_node_path4, import_node_os4, HAANSI_DIR3, CREDENTIALS_FILE3;
|
|
319
1002
|
var init_init = __esm({
|
|
320
1003
|
"src/commands/init.ts"() {
|
|
321
1004
|
"use strict";
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
1005
|
+
readline2 = __toESM(require("node:readline"));
|
|
1006
|
+
import_node_fs5 = require("node:fs");
|
|
1007
|
+
import_node_path4 = require("node:path");
|
|
1008
|
+
import_node_os4 = require("node:os");
|
|
326
1009
|
init_paths();
|
|
327
|
-
|
|
328
|
-
|
|
1010
|
+
init_setup_mcp();
|
|
1011
|
+
HAANSI_DIR3 = (0, import_node_path4.join)((0, import_node_os4.homedir)(), ".haansi");
|
|
1012
|
+
CREDENTIALS_FILE3 = (0, import_node_path4.join)(HAANSI_DIR3, "credentials.json");
|
|
329
1013
|
}
|
|
330
1014
|
});
|
|
331
1015
|
|
|
@@ -765,7 +1449,7 @@ var init_esm_node = __esm({
|
|
|
765
1449
|
});
|
|
766
1450
|
|
|
767
1451
|
// ../../packages/@company/observability/src/types.ts
|
|
768
|
-
var
|
|
1452
|
+
var init_types2 = __esm({
|
|
769
1453
|
"../../packages/@company/observability/src/types.ts"() {
|
|
770
1454
|
"use strict";
|
|
771
1455
|
}
|
|
@@ -1028,7 +1712,7 @@ var init_logger = __esm({
|
|
|
1028
1712
|
"use strict";
|
|
1029
1713
|
import_async_hooks = require("async_hooks");
|
|
1030
1714
|
init_esm_node();
|
|
1031
|
-
|
|
1715
|
+
init_types2();
|
|
1032
1716
|
SENSITIVE_KEY_RE = /^(password|passwd|secret|token|apikey|authorization|auth|authtoken|authkey|credentials?|connectionstring|privatekey)$/;
|
|
1033
1717
|
USER_CONTENT_KEY_RE = /^(question|questiontext|query|querytext|q|searchquery|searchtext|searchterm|searchterms|userquery|prompt|userprompt|usermessage|messagetext|messagecontent)$/;
|
|
1034
1718
|
FIRST_LINE_CAUSE_CHARS = 300;
|
|
@@ -1232,7 +1916,7 @@ var _cloudBackend2, MetricsService;
|
|
|
1232
1916
|
var init_metrics = __esm({
|
|
1233
1917
|
"../../packages/@company/observability/src/metrics.ts"() {
|
|
1234
1918
|
"use strict";
|
|
1235
|
-
|
|
1919
|
+
init_types2();
|
|
1236
1920
|
MetricsService = class {
|
|
1237
1921
|
projectId;
|
|
1238
1922
|
serviceName;
|
|
@@ -1526,7 +2210,7 @@ var init_observability_service = __esm({
|
|
|
1526
2210
|
"use strict";
|
|
1527
2211
|
init_logger();
|
|
1528
2212
|
init_metrics();
|
|
1529
|
-
|
|
2213
|
+
init_types2();
|
|
1530
2214
|
ObservabilityService = class {
|
|
1531
2215
|
logger;
|
|
1532
2216
|
metrics;
|
|
@@ -1713,7 +2397,7 @@ var SCALING_METRICS_SERVICE, SCALING_METRICS_NAMESPACE, SCALING_METRICS;
|
|
|
1713
2397
|
var init_scaling_metrics = __esm({
|
|
1714
2398
|
"../../packages/@company/observability/src/scaling-metrics.ts"() {
|
|
1715
2399
|
"use strict";
|
|
1716
|
-
|
|
2400
|
+
init_types2();
|
|
1717
2401
|
SCALING_METRICS_SERVICE = "backlog-reconciler";
|
|
1718
2402
|
SCALING_METRICS_NAMESPACE = `Haansi/${SCALING_METRICS_SERVICE}`;
|
|
1719
2403
|
SCALING_METRICS = {
|
|
@@ -1736,7 +2420,7 @@ var init_scaling_metrics = __esm({
|
|
|
1736
2420
|
REPLAY_PENDING_EVENTS: spec({
|
|
1737
2421
|
name: "haansi/replay/pending_events",
|
|
1738
2422
|
unit: "Count",
|
|
1739
|
-
description: "source_events a RUNNING
|
|
2423
|
+
description: "source_events a RUNNING replay (live re-run lease, or a checkpoint saved within the heartbeat) has yet to replay, past its watermark, all orgs.",
|
|
1740
2424
|
dimensions: ["environment"],
|
|
1741
2425
|
treatMissingData: "breaching",
|
|
1742
2426
|
missingDataRationale: "A re-run embeds every event inline through the batch embedders, but those rows are already normalized and never become claims, so neither backlog_rows nor claims_pending nor canonical_backlog_events can see the demand: one TEI task served a whole re-run at ~1 s/text (dev 2026-10-06). This is the only publisher of that demand; silence means it is unobserved, not finished. Breaching keeps the embedders up."
|
|
@@ -2038,7 +2722,7 @@ var init_health = __esm({
|
|
|
2038
2722
|
var init_degradation = __esm({
|
|
2039
2723
|
"../../packages/@company/observability/src/degradation.ts"() {
|
|
2040
2724
|
"use strict";
|
|
2041
|
-
|
|
2725
|
+
init_types2();
|
|
2042
2726
|
init_observability_service();
|
|
2043
2727
|
}
|
|
2044
2728
|
});
|
|
@@ -2056,7 +2740,7 @@ var init_src2 = __esm({
|
|
|
2056
2740
|
init_public_error();
|
|
2057
2741
|
init_health();
|
|
2058
2742
|
init_degradation();
|
|
2059
|
-
|
|
2743
|
+
init_types2();
|
|
2060
2744
|
defaultObservability = null;
|
|
2061
2745
|
getDefaultObservability = () => {
|
|
2062
2746
|
if (!defaultObservability) {
|
|
@@ -16580,9 +17264,1623 @@ var init_read_gate = __esm({
|
|
|
16580
17264
|
});
|
|
16581
17265
|
|
|
16582
17266
|
// ../../packages/@company/encryption/src/exclusions.ts
|
|
17267
|
+
function exchangeExclusions(database, table, columns) {
|
|
17268
|
+
return Object.entries(columns).map(([column, [category, reason]]) => ({
|
|
17269
|
+
database,
|
|
17270
|
+
table,
|
|
17271
|
+
column,
|
|
17272
|
+
category,
|
|
17273
|
+
reason
|
|
17274
|
+
}));
|
|
17275
|
+
}
|
|
17276
|
+
var EXCHANGE_CONTENT_REASON, EXCHANGE_VOCAB_REASON, EXCHANGE_LABEL_REASON, EXCHANGE_AUDIT_REASON, INTEL, EXCHANGE_QA_EXCLUSIONS, EXPLICIT_EXCLUSIONS;
|
|
16583
17277
|
var init_exclusions = __esm({
|
|
16584
17278
|
"../../packages/@company/encryption/src/exclusions.ts"() {
|
|
16585
17279
|
"use strict";
|
|
17280
|
+
EXCHANGE_CONTENT_REASON = "Exchange content published across orgs after redaction (exchange-redesign \xA75). Every org reads it, so per-org encryption cannot apply; the redaction pipeline is the control.";
|
|
17281
|
+
EXCHANGE_VOCAB_REASON = "A fixed vocabulary checked by a constraint; queries route and count on it.";
|
|
17282
|
+
EXCHANGE_LABEL_REASON = "A descriptive label for an agent client (e.g. an MCP client name) or a fixed taxonomy facet, shown next to a contribution; not personal content.";
|
|
17283
|
+
EXCHANGE_AUDIT_REASON = "An audit field describing a platform moderation or worker action on Exchange content, written by our doorways; audit queries filter on it.";
|
|
17284
|
+
INTEL = "haansi_intelligence_prod";
|
|
17285
|
+
EXCHANGE_QA_EXCLUSIONS = [
|
|
17286
|
+
...exchangeExclusions(INTEL, "exchange_posts", {
|
|
17287
|
+
post_type: ["operational", EXCHANGE_VOCAB_REASON],
|
|
17288
|
+
author_kind: ["operational", EXCHANGE_VOCAB_REASON],
|
|
17289
|
+
agent_label: ["not-personal", EXCHANGE_LABEL_REASON],
|
|
17290
|
+
disciplines: ["operational", EXCHANGE_LABEL_REASON],
|
|
17291
|
+
work_functions: ["operational", EXCHANGE_LABEL_REASON],
|
|
17292
|
+
desired_outcome: ["not-personal", EXCHANGE_CONTENT_REASON],
|
|
17293
|
+
constraints_text: ["not-personal", EXCHANGE_CONTENT_REASON],
|
|
17294
|
+
tried: ["not-personal", EXCHANGE_CONTENT_REASON]
|
|
17295
|
+
}),
|
|
17296
|
+
...exchangeExclusions(INTEL, "exchange_answers", {
|
|
17297
|
+
author_kind: ["operational", EXCHANGE_VOCAB_REASON],
|
|
17298
|
+
moderation_status: ["operational", EXCHANGE_VOCAB_REASON],
|
|
17299
|
+
agent_label: ["not-personal", EXCHANGE_LABEL_REASON],
|
|
17300
|
+
approach: ["not-personal", EXCHANGE_CONTENT_REASON],
|
|
17301
|
+
steps: ["not-personal", EXCHANGE_CONTENT_REASON],
|
|
17302
|
+
limitations: ["not-personal", EXCHANGE_CONTENT_REASON]
|
|
17303
|
+
}),
|
|
17304
|
+
...exchangeExclusions(INTEL, "exchange_revisions", {
|
|
17305
|
+
editor_kind: ["operational", EXCHANGE_VOCAB_REASON],
|
|
17306
|
+
redaction_version: ["operational", EXCHANGE_VOCAB_REASON],
|
|
17307
|
+
content: ["not-personal", EXCHANGE_CONTENT_REASON]
|
|
17308
|
+
}),
|
|
17309
|
+
...exchangeExclusions(INTEL, "exchange_comments", {
|
|
17310
|
+
author_kind: ["operational", EXCHANGE_VOCAB_REASON],
|
|
17311
|
+
moderation_status: ["operational", EXCHANGE_VOCAB_REASON],
|
|
17312
|
+
body: ["not-personal", EXCHANGE_CONTENT_REASON]
|
|
17313
|
+
}),
|
|
17314
|
+
...exchangeExclusions(INTEL, "exchange_reuse_events", {
|
|
17315
|
+
actor_kind: ["operational", EXCHANGE_VOCAB_REASON],
|
|
17316
|
+
outcome: ["operational", EXCHANGE_VOCAB_REASON],
|
|
17317
|
+
evidence_kind: ["operational", EXCHANGE_VOCAB_REASON],
|
|
17318
|
+
agent_label: ["not-personal", EXCHANGE_LABEL_REASON],
|
|
17319
|
+
context: ["not-personal", EXCHANGE_CONTENT_REASON],
|
|
17320
|
+
note: ["not-personal", EXCHANGE_CONTENT_REASON],
|
|
17321
|
+
idempotency_key: [
|
|
17322
|
+
"operational",
|
|
17323
|
+
"A client-chosen retry key, compared by equality to dedupe a write; carries no content."
|
|
17324
|
+
]
|
|
17325
|
+
}),
|
|
17326
|
+
...exchangeExclusions(INTEL, "exchange_reports", {
|
|
17327
|
+
target_kind: ["operational", EXCHANGE_VOCAB_REASON],
|
|
17328
|
+
reason: ["operational", EXCHANGE_VOCAB_REASON],
|
|
17329
|
+
detail: [
|
|
17330
|
+
"not-personal",
|
|
17331
|
+
"A reporter's note about Exchange content, read only by the reporter and platform moderators."
|
|
17332
|
+
]
|
|
17333
|
+
}),
|
|
17334
|
+
...exchangeExclusions(INTEL, "exchange_audit_outbox", {
|
|
17335
|
+
actor_kind: ["audit-actor", EXCHANGE_AUDIT_REASON],
|
|
17336
|
+
worker_name: ["audit-actor", EXCHANGE_AUDIT_REASON],
|
|
17337
|
+
action: ["operational", EXCHANGE_AUDIT_REASON],
|
|
17338
|
+
target_kind: ["operational", EXCHANGE_AUDIT_REASON],
|
|
17339
|
+
reason: ["operational", EXCHANGE_AUDIT_REASON],
|
|
17340
|
+
before_state: ["operational", EXCHANGE_AUDIT_REASON],
|
|
17341
|
+
after_state: ["operational", EXCHANGE_AUDIT_REASON]
|
|
17342
|
+
}),
|
|
17343
|
+
...exchangeExclusions("haansi_prod", "exchange_moderation_log", {
|
|
17344
|
+
actor_kind: ["audit-actor", EXCHANGE_AUDIT_REASON],
|
|
17345
|
+
worker_name: ["audit-actor", EXCHANGE_AUDIT_REASON],
|
|
17346
|
+
target_kind: ["operational", EXCHANGE_AUDIT_REASON],
|
|
17347
|
+
reason: ["operational", EXCHANGE_AUDIT_REASON],
|
|
17348
|
+
before_state: ["operational", EXCHANGE_AUDIT_REASON],
|
|
17349
|
+
after_state: ["operational", EXCHANGE_AUDIT_REASON]
|
|
17350
|
+
})
|
|
17351
|
+
];
|
|
17352
|
+
EXPLICIT_EXCLUSIONS = [
|
|
17353
|
+
...EXCHANGE_QA_EXCLUSIONS,
|
|
17354
|
+
// ── cli_tokens (cli-token-scopes/001): delegated scopes ──
|
|
17355
|
+
{
|
|
17356
|
+
database: "haansi_prod",
|
|
17357
|
+
table: "cli_tokens",
|
|
17358
|
+
column: "scopes",
|
|
17359
|
+
category: "operational",
|
|
17360
|
+
reason: "A fixed vocabulary of delegated permissions ('exchange:write'), checked by a constraint and read on every request to authorize writes."
|
|
17361
|
+
},
|
|
17362
|
+
// ── qna_messages (qna-charts/003): answer dependency manifest ──
|
|
17363
|
+
{
|
|
17364
|
+
database: "haansi_intelligence_prod",
|
|
17365
|
+
table: "qna_messages",
|
|
17366
|
+
column: "dependency_manifest",
|
|
17367
|
+
category: "operational",
|
|
17368
|
+
reason: "Tool names and artifact ids an answer was built from (no content); read on every follow-up to re-check access, server-side only."
|
|
17369
|
+
},
|
|
17370
|
+
// ── organization_mcp_servers (custom-mcp-servers/004): Q&A tool allowlist ──
|
|
17371
|
+
{
|
|
17372
|
+
database: "haansi_prod",
|
|
17373
|
+
table: "organization_mcp_servers",
|
|
17374
|
+
column: "tool_policy",
|
|
17375
|
+
category: "operational",
|
|
17376
|
+
reason: "An admin's map of a server's tool names to 'read' | 'write' (shape-checked, no free text); read on every ask to decide which tools Q&A may offer."
|
|
17377
|
+
},
|
|
17378
|
+
// ── qna_write_operations (qna-charts/002): retry-safe Q&A writes ──
|
|
17379
|
+
{
|
|
17380
|
+
database: "haansi_intelligence_prod",
|
|
17381
|
+
table: "qna_write_operations",
|
|
17382
|
+
column: "operation_id",
|
|
17383
|
+
category: "join-key",
|
|
17384
|
+
reason: "Server-assigned key of one Q&A tool action: '<question id>:<tool name>'. Matched by equality on every retry."
|
|
17385
|
+
},
|
|
17386
|
+
{
|
|
17387
|
+
database: "haansi_intelligence_prod",
|
|
17388
|
+
table: "qna_write_operations",
|
|
17389
|
+
column: "tool",
|
|
17390
|
+
category: "operational",
|
|
17391
|
+
reason: "Name of the built-in tool that wrote (e.g. create_artifact); a fixed vocabulary."
|
|
17392
|
+
},
|
|
17393
|
+
{
|
|
17394
|
+
database: "haansi_intelligence_prod",
|
|
17395
|
+
table: "qna_write_operations",
|
|
17396
|
+
column: "payload_fingerprint",
|
|
17397
|
+
category: "derived",
|
|
17398
|
+
reason: "sha256 of the tool payload, compared by equality to tell a retry from a changed request. Encrypting it would break the comparison; the payload itself is not stored."
|
|
17399
|
+
},
|
|
17400
|
+
{
|
|
17401
|
+
database: "haansi_intelligence_prod",
|
|
17402
|
+
table: "qna_write_operations",
|
|
17403
|
+
column: "state",
|
|
17404
|
+
category: "operational",
|
|
17405
|
+
reason: "'claimed' | 'done' (CHECK-constrained)."
|
|
17406
|
+
},
|
|
17407
|
+
{
|
|
17408
|
+
database: "haansi_intelligence_prod",
|
|
17409
|
+
table: "qna_write_operations",
|
|
17410
|
+
column: "surface",
|
|
17411
|
+
category: "operational",
|
|
17412
|
+
reason: "Product surface the write came from (web, slack, vscode, cli)."
|
|
17413
|
+
},
|
|
17414
|
+
{
|
|
17415
|
+
database: "haansi_intelligence_prod",
|
|
17416
|
+
table: "qna_write_operations",
|
|
17417
|
+
column: "notify_state",
|
|
17418
|
+
category: "operational",
|
|
17419
|
+
reason: "Outbox delivery state (CHECK-constrained vocabulary)."
|
|
17420
|
+
},
|
|
17421
|
+
{
|
|
17422
|
+
database: "haansi_intelligence_prod",
|
|
17423
|
+
table: "qna_write_operations",
|
|
17424
|
+
column: "notify_error",
|
|
17425
|
+
category: "operational",
|
|
17426
|
+
reason: "Error class name or a fixed sweep message; never message text or a Slack response body."
|
|
17427
|
+
},
|
|
17428
|
+
// ── slack_user_links (slack-installations/005): verified Slack ↔ member links ──
|
|
17429
|
+
{
|
|
17430
|
+
database: "haansi_prod",
|
|
17431
|
+
table: "slack_user_links",
|
|
17432
|
+
column: "team_id",
|
|
17433
|
+
category: "join-key",
|
|
17434
|
+
reason: "The Slack workspace id, the same key as organization_integrations.source_account_id; matched by equality on every bot event."
|
|
17435
|
+
},
|
|
17436
|
+
{
|
|
17437
|
+
database: "haansi_prod",
|
|
17438
|
+
table: "slack_user_links",
|
|
17439
|
+
column: "slack_user_id",
|
|
17440
|
+
category: "join-key",
|
|
17441
|
+
reason: "Slack user id (U\u2026), a pseudonymous platform identifier looked up by equality on every bot event, the same shape as source_entity_id."
|
|
17442
|
+
},
|
|
17443
|
+
{
|
|
17444
|
+
database: "haansi_prod",
|
|
17445
|
+
table: "slack_user_links",
|
|
17446
|
+
column: "link_source",
|
|
17447
|
+
category: "operational",
|
|
17448
|
+
reason: "A fixed enumeration (CHECK) of how the link was made; routes nothing personal."
|
|
17449
|
+
},
|
|
17450
|
+
{
|
|
17451
|
+
database: "haansi_prod",
|
|
17452
|
+
table: "slack_user_links",
|
|
17453
|
+
column: "revoked_reason",
|
|
17454
|
+
category: "operational",
|
|
17455
|
+
reason: "A short code written by our own code (e.g. 'relinked'); never user text."
|
|
17456
|
+
},
|
|
17457
|
+
// ── slack_installations (slack-installations/001): the workspace's own
|
|
17458
|
+
// installation record. Platform-level, no org; holds Slack-issued ids and
|
|
17459
|
+
// the credential ciphertext. ──
|
|
17460
|
+
{
|
|
17461
|
+
database: "haansi_prod",
|
|
17462
|
+
table: "slack_installations",
|
|
17463
|
+
column: "credentials_encrypted",
|
|
17464
|
+
category: "derived",
|
|
17465
|
+
reason: "Already encrypted by the application before it is stored, like organization_integrations.credentials_encrypted; encrypting the ciphertext again adds nothing."
|
|
17466
|
+
},
|
|
17467
|
+
{
|
|
17468
|
+
database: "haansi_prod",
|
|
17469
|
+
table: "slack_installations",
|
|
17470
|
+
column: "team_id",
|
|
17471
|
+
category: "join-key",
|
|
17472
|
+
reason: "The Slack workspace id, the SAME key as organization_integrations.source_account_id for slack rows and workspace_organization_mapping.workspace_id; looked up by equality on every bot event."
|
|
17473
|
+
},
|
|
17474
|
+
{
|
|
17475
|
+
database: "haansi_prod",
|
|
17476
|
+
table: "slack_installations",
|
|
17477
|
+
column: "app_id",
|
|
17478
|
+
category: "operational",
|
|
17479
|
+
reason: "Slack's id for OUR app (A\u2026), the same value in every row."
|
|
17480
|
+
},
|
|
17481
|
+
{
|
|
17482
|
+
database: "haansi_prod",
|
|
17483
|
+
table: "slack_installations",
|
|
17484
|
+
column: "enterprise_id",
|
|
17485
|
+
category: "join-key",
|
|
17486
|
+
reason: "Slack Enterprise Grid org id (E\u2026), a pseudonymous workspace-level identifier matched by equality; NULL for ordinary workspaces."
|
|
17487
|
+
},
|
|
17488
|
+
{
|
|
17489
|
+
database: "haansi_prod",
|
|
17490
|
+
table: "slack_installations",
|
|
17491
|
+
column: "bot_user_id",
|
|
17492
|
+
category: "operational",
|
|
17493
|
+
reason: "The bot's own Slack user id (U\u2026), needed in plaintext for Bolt to ignore its own messages. Identifies our bot, not a person."
|
|
17494
|
+
},
|
|
17495
|
+
{
|
|
17496
|
+
database: "haansi_prod",
|
|
17497
|
+
table: "slack_installations",
|
|
17498
|
+
column: "bot_id",
|
|
17499
|
+
category: "operational",
|
|
17500
|
+
reason: "The bot's own Slack bot id (B\u2026); identifies our bot, not a person."
|
|
17501
|
+
},
|
|
17502
|
+
{
|
|
17503
|
+
database: "haansi_prod",
|
|
17504
|
+
table: "slack_installations",
|
|
17505
|
+
column: "scopes",
|
|
17506
|
+
category: "operational",
|
|
17507
|
+
reason: "Granted OAuth scopes, a fixed list from the manifest; read to decide whether a reinstall is needed."
|
|
17508
|
+
},
|
|
17509
|
+
{
|
|
17510
|
+
database: "haansi_prod",
|
|
17511
|
+
table: "slack_installations",
|
|
17512
|
+
column: "installed_by_user_id",
|
|
17513
|
+
category: "join-key",
|
|
17514
|
+
reason: "Slack user id (U\u2026) of the admin who installed the app \u2014 a pseudonymous platform identifier, the same shape as source_entity_id elsewhere; used to attribute the install, never displayed."
|
|
17515
|
+
},
|
|
17516
|
+
// ── TI-58 runtime credential (WS9): already ciphertext ──
|
|
17517
|
+
{
|
|
17518
|
+
database: "haansi_prod",
|
|
17519
|
+
table: "organization_database_configs",
|
|
17520
|
+
column: "runtime_user_encrypted",
|
|
17521
|
+
category: "derived",
|
|
17522
|
+
reason: "Already encrypted by the application before it is stored, like the existing BYOD credential columns; encrypting the ciphertext again adds nothing."
|
|
17523
|
+
},
|
|
17524
|
+
{
|
|
17525
|
+
database: "haansi_prod",
|
|
17526
|
+
table: "organization_database_configs",
|
|
17527
|
+
column: "runtime_password_encrypted",
|
|
17528
|
+
category: "derived",
|
|
17529
|
+
reason: "Already encrypted by the application before it is stored, like the existing BYOD credential columns; encrypting the ciphertext again adds nothing."
|
|
17530
|
+
},
|
|
17531
|
+
// ── exchange_moderation_log (TI-18): an audit trail of OUR actions ──
|
|
17532
|
+
{
|
|
17533
|
+
database: "haansi_prod",
|
|
17534
|
+
table: "exchange_moderation_log",
|
|
17535
|
+
column: "action",
|
|
17536
|
+
category: "operational",
|
|
17537
|
+
reason: "A fixed vocabulary of moderation actions ('delete_post'), checked by a constraint. Audit queries filter on it."
|
|
17538
|
+
},
|
|
17539
|
+
{
|
|
17540
|
+
database: "haansi_prod",
|
|
17541
|
+
table: "exchange_moderation_log",
|
|
17542
|
+
column: "target_id",
|
|
17543
|
+
category: "structural",
|
|
17544
|
+
reason: "The id of the moderated Exchange post. An identifier the audit is looked up by, not content."
|
|
17545
|
+
},
|
|
17546
|
+
// ── session_object_pointers (WS6 phase 1): object locations, not content ──
|
|
17547
|
+
{
|
|
17548
|
+
database: "haansi_prod",
|
|
17549
|
+
table: "session_object_pointers",
|
|
17550
|
+
column: "user_id",
|
|
17551
|
+
category: "structural",
|
|
17552
|
+
reason: "The owning user's id, kept as text because it is part of the object key. An identifier the readers and the purge filter on, not personal content."
|
|
17553
|
+
},
|
|
17554
|
+
{
|
|
17555
|
+
database: "haansi_prod",
|
|
17556
|
+
table: "session_object_pointers",
|
|
17557
|
+
column: "session_id",
|
|
17558
|
+
category: "structural",
|
|
17559
|
+
reason: "The session id that names the object. Readers and the purge filter on it; it carries no content."
|
|
17560
|
+
},
|
|
17561
|
+
{
|
|
17562
|
+
database: "haansi_prod",
|
|
17563
|
+
table: "session_object_pointers",
|
|
17564
|
+
column: "bucket_kind",
|
|
17565
|
+
category: "operational",
|
|
17566
|
+
reason: "Which bucket the pointer names ('staging' or 'clean'). Routing state."
|
|
17567
|
+
},
|
|
17568
|
+
{
|
|
17569
|
+
database: "haansi_prod",
|
|
17570
|
+
table: "session_object_pointers",
|
|
17571
|
+
column: "object_key",
|
|
17572
|
+
category: "structural",
|
|
17573
|
+
reason: "The object's storage key, `<org>/<user>/<session>`-shaped and constrained to the org's prefix. Reads and versioned deletes resolve it by equality."
|
|
17574
|
+
},
|
|
17575
|
+
{
|
|
17576
|
+
database: "haansi_prod",
|
|
17577
|
+
table: "session_object_pointers",
|
|
17578
|
+
column: "version_id",
|
|
17579
|
+
category: "structural",
|
|
17580
|
+
reason: "The S3 version id a versioned delete targets. An opaque storage identifier."
|
|
17581
|
+
},
|
|
17582
|
+
// ── incident_signals agent columns: provenance and pointers, not content ──
|
|
17583
|
+
//
|
|
17584
|
+
// proposed_fields is NOT here — it can carry working_theory / root_cause
|
|
17585
|
+
// prose and is registered as an encrypted column. These four cannot.
|
|
17586
|
+
{
|
|
17587
|
+
database: "haansi_intelligence_prod",
|
|
17588
|
+
table: "incident_signals",
|
|
17589
|
+
column: "origin",
|
|
17590
|
+
category: "structural",
|
|
17591
|
+
reason: "A two-value vocabulary term ('system' | 'agent') that the server sets from the credential and every read filters on. Encrypting it would make the agent-proposal index unusable and hide provenance from the people it exists to inform."
|
|
17592
|
+
},
|
|
17593
|
+
{
|
|
17594
|
+
database: "haansi_intelligence_prod",
|
|
17595
|
+
table: "incident_signals",
|
|
17596
|
+
column: "agent_id",
|
|
17597
|
+
category: "operational",
|
|
17598
|
+
reason: "Which workload made a claim, derived by us from the credential \u2014 'credential:cli', 'user:<uuid>'. It names a caller of ours, not anything a customer wrote, and it is what an operator groups by when a particular agent starts proposing badly."
|
|
17599
|
+
},
|
|
17600
|
+
{
|
|
17601
|
+
database: "haansi_intelligence_prod",
|
|
17602
|
+
table: "incident_signals",
|
|
17603
|
+
column: "citations",
|
|
17604
|
+
category: "join-key",
|
|
17605
|
+
reason: "Typed references \u2014 {ref_type, ref_id} \u2014 to process_activity_facts rows in the same incident, validated server-side before the row is written. Pointers to content, never content; the rows they point at carry their own encryption."
|
|
17606
|
+
},
|
|
17607
|
+
{
|
|
17608
|
+
database: "haansi_intelligence_prod",
|
|
17609
|
+
table: "incident_signals",
|
|
17610
|
+
column: "producer",
|
|
17611
|
+
category: "not-personal",
|
|
17612
|
+
reason: "What produced a claim: model name, prompt version. Ours, not the customer's, and kept readable so a proposal that ages badly can be traced to the model that made it."
|
|
17613
|
+
},
|
|
17614
|
+
// ── incident_idempotency: request bookkeeping, never content ──
|
|
17615
|
+
//
|
|
17616
|
+
// The table records THAT a mutation already ran, keyed by
|
|
17617
|
+
// (organization_id, endpoint, idempotency_key). It deliberately stores no
|
|
17618
|
+
// response body — only the revision the mutation produced — because the
|
|
17619
|
+
// response would have been an incident overlay carrying root_cause and
|
|
17620
|
+
// resolution_summary, both registered encrypted columns, and a copy here
|
|
17621
|
+
// would be their plaintext sitting beside their own ciphertext. That design
|
|
17622
|
+
// choice is what makes these two columns safe to leave clear.
|
|
17623
|
+
{
|
|
17624
|
+
database: "haansi_intelligence_prod",
|
|
17625
|
+
table: "incident_idempotency",
|
|
17626
|
+
column: "endpoint",
|
|
17627
|
+
category: "operational",
|
|
17628
|
+
reason: "Our own route label \u2014 'declare', 'dismiss', 'patch'. Names a thing we wrote, not anything a customer did, and it is half of the UNIQUE constraint that makes a replay detectable: encrypting it destroys the conflict target and the same key would be accepted twice."
|
|
17629
|
+
},
|
|
17630
|
+
{
|
|
17631
|
+
database: "haansi_intelligence_prod",
|
|
17632
|
+
table: "incident_idempotency",
|
|
17633
|
+
column: "process_id",
|
|
17634
|
+
category: "join-key",
|
|
17635
|
+
reason: "The mined workstream's process_id \u2014 the same internal join key the incident overlay, signal and audit tables carry in the clear, kept here so a replay can read the overlay back without a second lookup. An identifier we generate, not anything a customer wrote."
|
|
17636
|
+
},
|
|
17637
|
+
{
|
|
17638
|
+
database: "haansi_intelligence_prod",
|
|
17639
|
+
table: "incident_idempotency",
|
|
17640
|
+
column: "idempotency_key",
|
|
17641
|
+
category: "join-key",
|
|
17642
|
+
reason: "An opaque client-generated token, matched by equality on every write as the other half of the UNIQUE constraint. Randomized encryption makes two copies of one key unequal, which turns every retry into a second apply \u2014 the exact failure the table exists to prevent."
|
|
17643
|
+
},
|
|
17644
|
+
{
|
|
17645
|
+
database: "haansi_intelligence_prod",
|
|
17646
|
+
table: "qna_messages",
|
|
17647
|
+
column: "rating_reasons",
|
|
17648
|
+
category: "operational",
|
|
17649
|
+
reason: "A server-validated five-code enum used by ARRAY DISTINCT/ORDER BY and platform-mining GROUP BY counts. Randomized encryption makes equal codes unequal and silently corrupts both aggregates; free-text feedback belongs in the separately encrypted rating_comment column."
|
|
17650
|
+
},
|
|
17651
|
+
// ── redaction_claims: a work queue that holds references, never content ──
|
|
17652
|
+
//
|
|
17653
|
+
// Same shape as embedding_claims below. The queue records WHICH staging
|
|
17654
|
+
// object needs redacting and WHO holds the lease; the session text itself is
|
|
17655
|
+
// fetched from object storage at claim time, redacted, and written to the
|
|
17656
|
+
// clean bucket. No session content is ever stored in a row here — that is
|
|
17657
|
+
// the property the whole design depends on, and it is why these four are
|
|
17658
|
+
// exclusions rather than a tier.
|
|
17659
|
+
{
|
|
17660
|
+
database: "haansi_prod",
|
|
17661
|
+
table: "redaction_claims",
|
|
17662
|
+
column: "staging_path",
|
|
17663
|
+
category: "join-key",
|
|
17664
|
+
reason: "The object key \u2014 org/user/session.jsonl. Part of the UNIQUE constraint that makes enqueue idempotent, so it is matched by equality on every insert; encrypting it destroys the conflict target and the queue would accept duplicate claims for one object. The identifiers it embeds are already columns on this table."
|
|
17665
|
+
},
|
|
17666
|
+
{
|
|
17667
|
+
database: "haansi_prod",
|
|
17668
|
+
table: "redaction_claims",
|
|
17669
|
+
column: "staging_token",
|
|
17670
|
+
category: "join-key",
|
|
17671
|
+
reason: "Identifies WHICH VERSION of the object this claim is for \u2014 the object's x-haansi-uploaded-at. The other half of the same UNIQUE constraint, matched by equality for the same reason. A timestamp, not personal data."
|
|
17672
|
+
},
|
|
17673
|
+
{
|
|
17674
|
+
database: "haansi_prod",
|
|
17675
|
+
table: "redaction_claims",
|
|
17676
|
+
column: "owner",
|
|
17677
|
+
category: "operational",
|
|
17678
|
+
reason: "Which WORKER holds the lease \u2014 hostname:pid. Names a process, not a person, and is compared by equality in the guarded write-back so a worker cannot complete another's claim. Encrypting it breaks that guard."
|
|
17679
|
+
},
|
|
17680
|
+
{
|
|
17681
|
+
database: "haansi_prod",
|
|
17682
|
+
table: "redaction_claims",
|
|
17683
|
+
column: "last_error",
|
|
17684
|
+
category: "operational",
|
|
17685
|
+
reason: "Operator diagnostics: why a claim was requeued or parked \u2014 a Presidio timeout, an object-store refusal. Truncated to 2000 characters at the write. It must never carry session TEXT, and does not: the worker records the exception message, never the content it was redacting. Encrypting it would make it unreadable in exactly the situation it exists for, which is someone asking why redaction stopped."
|
|
17686
|
+
},
|
|
17687
|
+
// ── embedding_claims: a work queue that holds references, never content ──
|
|
17688
|
+
//
|
|
17689
|
+
// The three text columns below exist because the leased embedding worker
|
|
17690
|
+
// needs to know WHICH row to embed and WHO holds the lease. The text to
|
|
17691
|
+
// embed is fetched from the target row at claim time and the vector written
|
|
17692
|
+
// straight back, so no customer content ever rests in this table.
|
|
17693
|
+
{
|
|
17694
|
+
database: "haansi_intelligence_prod",
|
|
17695
|
+
table: "embedding_claims",
|
|
17696
|
+
column: "source_table",
|
|
17697
|
+
category: "operational",
|
|
17698
|
+
reason: "A table NAME from the checked-in EMBEDDING_TABLES registry \u2014 one of six fixed values, never user input. Encrypting it would break the worker's own routing, since it dispatches on this column."
|
|
17699
|
+
},
|
|
17700
|
+
{
|
|
17701
|
+
database: "haansi_intelligence_prod",
|
|
17702
|
+
table: "embedding_claims",
|
|
17703
|
+
column: "owner",
|
|
17704
|
+
category: "operational",
|
|
17705
|
+
reason: "Identifies the WORKER holding the lease (hostname:pid), not a person. It is compared for equality on every write-back \u2014 the guard that stops a worker whose lease expired mid-inference overwriting the worker that took over \u2014 so it must remain matchable."
|
|
17706
|
+
},
|
|
17707
|
+
{
|
|
17708
|
+
database: "haansi_intelligence_prod",
|
|
17709
|
+
table: "embedding_claims",
|
|
17710
|
+
column: "last_error",
|
|
17711
|
+
category: "operational",
|
|
17712
|
+
reason: "A database or embedder error message, truncated to 2000 chars. It records why a claim failed (connection refused, embedder timeout), not the content being embedded \u2014 the worker fails BEFORE it holds any customer text, and the text is never part of the message. Kept readable because its only purpose is an operator diagnosing a stuck queue."
|
|
17713
|
+
},
|
|
17714
|
+
// ── source_events / sync_state / schema_migrations, classified 2026-09-13 ──
|
|
17715
|
+
// These arrived with the unified-integrations work and reached main
|
|
17716
|
+
// unclassified, which is what the ratchet caught.
|
|
17717
|
+
{
|
|
17718
|
+
database: "haansi_integrations_prod",
|
|
17719
|
+
table: "source_events",
|
|
17720
|
+
column: "normalize_error",
|
|
17721
|
+
category: "blocked",
|
|
17722
|
+
reason: "Should be encrypted, and is not. Unlike embedding_claims.last_error \u2014 which records a failure that happens BEFORE the worker holds any customer text \u2014 this is written from inside the normalizer while it is parsing raw_payload (normalization.ts markSourceEventNormalizeError, called with error.message from an arbitrary throw). A parse or validation error can quote the fragment it choked on, and that fragment is customer content. The predicates that read it are NULL checks, which survive encryption, so registering it is feasible \u2014 it needs the encryption job to sweep the existing plaintext rows, which is a backfill rather than a classification."
|
|
17723
|
+
},
|
|
17724
|
+
{
|
|
17725
|
+
database: "haansi_integrations_prod",
|
|
17726
|
+
table: "source_events",
|
|
17727
|
+
column: "object_type",
|
|
17728
|
+
category: "structural",
|
|
17729
|
+
reason: "Names the shape of the captured object (message, issue, pull_request). The normalizer dispatches on it and it is part of the uniqueness key, so it must stay readable; it is a vocabulary term, not content."
|
|
17730
|
+
},
|
|
17731
|
+
{
|
|
17732
|
+
database: "haansi_integrations_prod",
|
|
17733
|
+
table: "sync_state",
|
|
17734
|
+
column: "object_type",
|
|
17735
|
+
category: "structural",
|
|
17736
|
+
reason: "The same vocabulary term as source_events.object_type, keying the per-(platform, object_type) watermark. Encrypting it would make the cursor unreadable to the reader that must find it by that key."
|
|
17737
|
+
},
|
|
17738
|
+
{
|
|
17739
|
+
database: "haansi_integrations_prod",
|
|
17740
|
+
table: "source_events",
|
|
17741
|
+
column: "source_object_id",
|
|
17742
|
+
category: "join-key",
|
|
17743
|
+
reason: "The provider's own identifier for the object (a Slack ts, a GitHub node id). It is how a captured row is matched back to its source and de-duplicated across re-captures; encrypting it would break that equality. Pseudonymous by construction \u2014 it identifies an object, not a person."
|
|
17744
|
+
},
|
|
17745
|
+
{
|
|
17746
|
+
database: "haansi_integrations_prod",
|
|
17747
|
+
table: "source_events",
|
|
17748
|
+
column: "source_event_key",
|
|
17749
|
+
category: "join-key",
|
|
17750
|
+
reason: "The idempotency key a capture writes under, and the column the uniqueness constraint is built on. Equality here is what stops a replayed tap run from duplicating the corpus; an encrypted value with a random IV compares unequal to itself."
|
|
17751
|
+
},
|
|
17752
|
+
{
|
|
17753
|
+
database: "haansi_integrations_prod",
|
|
17754
|
+
table: "source_events",
|
|
17755
|
+
column: "ingest_mode",
|
|
17756
|
+
category: "operational",
|
|
17757
|
+
reason: "How the row arrived \u2014 tap, webhook, backfill. Read by the replay and catch-up paths to decide what may be re-processed. A closed vocabulary with no customer content."
|
|
17758
|
+
},
|
|
17759
|
+
{
|
|
17760
|
+
database: "haansi_integrations_prod",
|
|
17761
|
+
table: "source_events",
|
|
17762
|
+
column: "cursor_token",
|
|
17763
|
+
category: "operational",
|
|
17764
|
+
reason: "The provider watermark the row was captured at, in the same class as sync_state.cursor_value: opaque control-plane state that the resume path must read to avoid restarting a sync. Not source content."
|
|
17765
|
+
},
|
|
17766
|
+
{
|
|
17767
|
+
database: "haansi_integrations_prod",
|
|
17768
|
+
table: "source_events",
|
|
17769
|
+
column: "simulation_run_id",
|
|
17770
|
+
category: "not-personal",
|
|
17771
|
+
reason: "Tags a row as belonging to a simulation run so production reads can exclude it. Generated by us, never derived from the source."
|
|
17772
|
+
},
|
|
17773
|
+
{
|
|
17774
|
+
database: "haansi_integrations_prod",
|
|
17775
|
+
table: "schema_migrations",
|
|
17776
|
+
column: "git_sha",
|
|
17777
|
+
category: "operational",
|
|
17778
|
+
reason: "The commit a migration was applied from \u2014 SOC 2 CC8.1 audit-trail evidence about our own deploys. Carries no tenant data, and must stay readable to be evidence at all."
|
|
17779
|
+
},
|
|
17780
|
+
{
|
|
17781
|
+
database: "haansi_integrations_prod",
|
|
17782
|
+
table: "schema_migrations",
|
|
17783
|
+
column: "notes",
|
|
17784
|
+
category: "operational",
|
|
17785
|
+
reason: "Operator annotation on a ledger row (why a CONCURRENTLY migration was applied outside a transaction, that a row was backfill-stamped). Written by us about our own schema changes, not by or about a tenant."
|
|
17786
|
+
},
|
|
17787
|
+
{
|
|
17788
|
+
database: "haansi_integrations_prod",
|
|
17789
|
+
table: "source_accounts",
|
|
17790
|
+
column: "display_name",
|
|
17791
|
+
category: "blocked",
|
|
17792
|
+
reason: 'Personal data that should be encrypted, and is not. source_accounts has a COMPOSITE primary key (organization_id, source_platform, source_account_id) and no `id` column, while the encryption job addresses rows as `WHERE "<primaryKey>" = $1` \u2014 a single column. It was previously registered with primaryKey "id", which does not exist, so the job aborted when it reached this table. Pointing it at source_account_id instead would be worse than the crash: that column is not unique on its own, so one organization\'s UPDATE would rewrite another\'s row. Needs composite-key support in the job.'
|
|
17793
|
+
},
|
|
17794
|
+
{
|
|
17795
|
+
database: "haansi_intelligence_prod",
|
|
17796
|
+
table: "qna_charts",
|
|
17797
|
+
column: "engine",
|
|
17798
|
+
category: "structural",
|
|
17799
|
+
reason: "Which renderer draws the saved chart ('vega-lite'), CHECK-constrained. The reader dispatches on it; a code vocabulary term, not content."
|
|
17800
|
+
},
|
|
17801
|
+
{
|
|
17802
|
+
database: "haansi_intelligence_prod",
|
|
17803
|
+
table: "qna_charts",
|
|
17804
|
+
column: "tool_auth_scope",
|
|
17805
|
+
category: "operational",
|
|
17806
|
+
reason: "The access rule the chart's data was produced under ('org', 'permission:experts:read'), re-checked on every read. A code-authored policy name the read path must evaluate; no customer content."
|
|
17807
|
+
},
|
|
17808
|
+
{
|
|
17809
|
+
database: "haansi_integrations_prod",
|
|
17810
|
+
table: "capture_scan_ledger",
|
|
17811
|
+
column: "stream",
|
|
17812
|
+
category: "structural",
|
|
17813
|
+
reason: "The tap/poller stream name a scan covered (messages, issues). A fixed vocabulary the coverage reader filters on; no customer content."
|
|
17814
|
+
},
|
|
17815
|
+
{
|
|
17816
|
+
database: "haansi_integrations_prod",
|
|
17817
|
+
table: "capture_scan_ledger",
|
|
17818
|
+
column: "scope",
|
|
17819
|
+
category: "operational",
|
|
17820
|
+
reason: 'A fixed, code-authored description of what a scan covered ("every channel the app has joined"), taken from SCAN_PROOFS. Never customer content.'
|
|
17821
|
+
},
|
|
17822
|
+
{
|
|
17823
|
+
database: "haansi_integrations_prod",
|
|
17824
|
+
table: "capture_scan_ledger",
|
|
17825
|
+
column: "failure_reason",
|
|
17826
|
+
category: "operational",
|
|
17827
|
+
reason: "Why a capture scan stopped (a provider error class or our own message). Control-plane diagnostics for our runs, read by operators; it carries no captured content."
|
|
17828
|
+
},
|
|
17829
|
+
{
|
|
17830
|
+
database: "haansi_integrations_prod",
|
|
17831
|
+
table: "sync_state",
|
|
17832
|
+
column: "cursor_type",
|
|
17833
|
+
category: "operational",
|
|
17834
|
+
reason: "Names the cursor protocol, such as timestamp or page_token. The sync reader dispatches on it and it contains no customer content; encrypting it would prevent the ingestion worker from interpreting cursor_value."
|
|
17835
|
+
},
|
|
17836
|
+
{
|
|
17837
|
+
database: "haansi_integrations_prod",
|
|
17838
|
+
table: "sync_state",
|
|
17839
|
+
column: "cursor_value",
|
|
17840
|
+
category: "operational",
|
|
17841
|
+
reason: "An opaque provider watermark used to resume incremental ingestion. It is control-plane state rather than source content and must remain readable by the raw cursor upsert/read path to avoid restarting syncs."
|
|
17842
|
+
},
|
|
17843
|
+
{
|
|
17844
|
+
database: "haansi_integrations_prod",
|
|
17845
|
+
table: "source_accounts",
|
|
17846
|
+
column: "account_type",
|
|
17847
|
+
category: "operational",
|
|
17848
|
+
reason: "A fixed routing enum (user_oauth, org_oauth, or service_account), not a person attribute. Ingestion uses it to choose the account flow, so encrypting it would break control-plane dispatch predicates."
|
|
17849
|
+
},
|
|
17850
|
+
{
|
|
17851
|
+
database: "haansi_integrations_prod",
|
|
17852
|
+
table: "source_accounts",
|
|
17853
|
+
column: "metadata",
|
|
17854
|
+
category: "blocked",
|
|
17855
|
+
reason: "Provider metadata may contain tenant data and should be encrypted, but source_accounts has a composite primary key while the conversion job supports only one primary-key column. It shares display_name's blocker."
|
|
17856
|
+
},
|
|
17857
|
+
{
|
|
17858
|
+
database: "haansi_integrations_prod",
|
|
17859
|
+
table: "google_workspace_service_accounts",
|
|
17860
|
+
column: "key_json_encrypted",
|
|
17861
|
+
category: "structural",
|
|
17862
|
+
reason: "The service-account private key is already encrypted by the owning Google Workspace credential helper. Registering it for transparent encryption would double-wrap it and break the explicit decrypt reader."
|
|
17863
|
+
},
|
|
17864
|
+
{
|
|
17865
|
+
database: "haansi_integrations_prod",
|
|
17866
|
+
table: "google_workspace_service_accounts",
|
|
17867
|
+
column: "domain",
|
|
17868
|
+
category: "not-personal",
|
|
17869
|
+
reason: "The customer's Workspace domain identifies an organization rather than an individual and is required as routing configuration for delegated Google API calls. Individual administrator email is encrypted separately."
|
|
17870
|
+
},
|
|
17871
|
+
{
|
|
17872
|
+
database: "haansi_integrations_prod",
|
|
17873
|
+
table: "google_workspace_service_accounts",
|
|
17874
|
+
column: "delegated_scopes",
|
|
17875
|
+
category: "operational",
|
|
17876
|
+
reason: "A list of fixed Google OAuth permission names selected for delegation. It contains configuration rather than subject data and must remain readable when constructing the delegated credential."
|
|
17877
|
+
},
|
|
17878
|
+
{
|
|
17879
|
+
database: "haansi_integrations_prod",
|
|
17880
|
+
table: "google_workspace_service_accounts",
|
|
17881
|
+
column: "discovery_status",
|
|
17882
|
+
category: "operational",
|
|
17883
|
+
reason: "A bounded workflow-state value such as pending or completed. Operators and discovery code filter on it; it carries no customer content and encrypting it would break those status predicates."
|
|
17884
|
+
},
|
|
17885
|
+
{
|
|
17886
|
+
database: "haansi_integrations_prod",
|
|
17887
|
+
table: "google_workspace_service_accounts",
|
|
17888
|
+
column: "discovery_error",
|
|
17889
|
+
category: "blocked",
|
|
17890
|
+
reason: "A Google API error can repeat a tenant email or domain and should be encrypted, but updateDiscoveryStatus writes through a raw pool without organization context. Move that writer through the doorway before tiering it."
|
|
17891
|
+
},
|
|
17892
|
+
{
|
|
17893
|
+
database: "haansi_integrations_prod",
|
|
17894
|
+
table: "github_path_token_stats",
|
|
17895
|
+
column: "common_tokens",
|
|
17896
|
+
category: "derived",
|
|
17897
|
+
reason: "A derived set of high-frequency repository path tokens used for exact membership filtering during graph edge building. Encrypting the derived lookup set would make its token comparisons unusable."
|
|
17898
|
+
},
|
|
17899
|
+
{
|
|
17900
|
+
database: "haansi_prod",
|
|
17901
|
+
table: "platform_admin_access_log",
|
|
17902
|
+
column: "actor_email",
|
|
17903
|
+
category: "audit-actor",
|
|
17904
|
+
reason: "HAANSI staff, not a data subject. This log exists to prove WHO reached a customer's data; encrypting the actor defeats the control and the SOC 2 evidence it produces. The same address in organization_users IS encrypted \u2014 the distinction is the role the row plays, which no name-based rule can see."
|
|
17905
|
+
},
|
|
17906
|
+
{
|
|
17907
|
+
database: "haansi_prod",
|
|
17908
|
+
table: "machine_credential_audit_log",
|
|
17909
|
+
column: "actor_email",
|
|
17910
|
+
category: "audit-actor",
|
|
17911
|
+
reason: "Same as platform_admin_access_log.actor_email \u2014 audit trail actor."
|
|
17912
|
+
},
|
|
17913
|
+
{
|
|
17914
|
+
database: "haansi_prod",
|
|
17915
|
+
table: "waitlist_activity_log",
|
|
17916
|
+
column: "actor_email",
|
|
17917
|
+
category: "audit-actor",
|
|
17918
|
+
reason: "Same as platform_admin_access_log.actor_email \u2014 audit trail actor."
|
|
17919
|
+
},
|
|
17920
|
+
{
|
|
17921
|
+
database: "haansi_prod",
|
|
17922
|
+
table: "organizations",
|
|
17923
|
+
column: "name",
|
|
17924
|
+
category: "not-personal",
|
|
17925
|
+
reason: "The organisation's own name, which the product renders on every page and every admin surface. A company name is not personal data; for a one-person org it can be identifying, which is accepted here because encrypting it would blind the platform to its own tenants."
|
|
17926
|
+
},
|
|
17927
|
+
// Residency control plane (migration byod-residency/001). Recorded here the
|
|
17928
|
+
// moment the columns were added, which is the same gate doing the same job as
|
|
17929
|
+
// it did for the per-org key columns below.
|
|
17930
|
+
//
|
|
17931
|
+
// These are ROUTING STATE, not tenant content: closed unions enforced by CHECK
|
|
17932
|
+
// constraints, read on the resolution path of every tenant request to decide
|
|
17933
|
+
// which database answers it. Encrypting them would be self-defeating in the
|
|
17934
|
+
// literal sense — the resolver would have to decrypt a value in order to learn
|
|
17935
|
+
// where the decryption key's data lives. Same reasoning that kept
|
|
17936
|
+
// qna_messages.rating_reasons out: a value the database itself must compare
|
|
17937
|
+
// cannot be randomised ciphertext.
|
|
17938
|
+
{
|
|
17939
|
+
database: "haansi_prod",
|
|
17940
|
+
table: "organizations",
|
|
17941
|
+
column: "authoritative_residency",
|
|
17942
|
+
category: "not-personal",
|
|
17943
|
+
reason: "Which database is authoritative for this org's tenant content: 'platform' or 'byod', enforced by a CHECK constraint. Operational routing state, read before every tenant query, and not derived from customer data."
|
|
17944
|
+
},
|
|
17945
|
+
{
|
|
17946
|
+
database: "haansi_prod",
|
|
17947
|
+
table: "organizations",
|
|
17948
|
+
column: "migration_phase",
|
|
17949
|
+
category: "not-personal",
|
|
17950
|
+
reason: "Which residency migration step is in progress, from a closed set enforced by a CHECK constraint. Says nothing about the org beyond the fact that an operator started a migration, and the write fence compares it on the admission path."
|
|
17951
|
+
},
|
|
17952
|
+
// The per-org key columns themselves (migration encryption/005). Recorded
|
|
17953
|
+
// the moment they were added, because the Phase 0 gate demanded it — which
|
|
17954
|
+
// is the gate doing its job on the very next schema change.
|
|
17955
|
+
{
|
|
17956
|
+
database: "haansi_prod",
|
|
17957
|
+
table: "organizations",
|
|
17958
|
+
column: "encryption_key_id",
|
|
17959
|
+
category: "structural",
|
|
17960
|
+
reason: "Names which key encrypted this org's data (e.g. org:<uuid>:v1). It is the LABEL on the key, not personal data, and the resolver reads it to decide which key to unwrap \u2014 encrypting it would make the ciphertext undecryptable by the very lookup that needs it."
|
|
17961
|
+
},
|
|
17962
|
+
{
|
|
17963
|
+
database: "haansi_prod",
|
|
17964
|
+
table: "organizations",
|
|
17965
|
+
column: "encryption_key_wrapped",
|
|
17966
|
+
category: "structural",
|
|
17967
|
+
reason: "The org's DEK, already encrypted \u2014 by KMS, under the CMK. Encrypting it again with a key derived FROM it is circular. At rest it is an opaque blob useless without kms:Decrypt, which is the whole reason it can live in Postgres rather than as per-org Secrets Manager sprawl."
|
|
17968
|
+
},
|
|
17969
|
+
{
|
|
17970
|
+
database: "haansi_prod",
|
|
17971
|
+
table: "organizations",
|
|
17972
|
+
column: "encryption_previous_keys",
|
|
17973
|
+
category: "structural",
|
|
17974
|
+
reason: "Retired DEKs as {keyId: KMS-wrapped DEK}. Same argument as encryption_key_wrapped, one generation back: already encrypted under the CMK, and encrypting it with a key derived from itself is circular. Kept so ciphertext written before a rotation stays readable \u2014 and kept on this row so the deletion tick's crypto-shred destroys every generation in one statement."
|
|
17975
|
+
},
|
|
17976
|
+
{
|
|
17977
|
+
database: "haansi_prod",
|
|
17978
|
+
table: "organizations",
|
|
17979
|
+
column: "encryption_cmk_id",
|
|
17980
|
+
category: "structural",
|
|
17981
|
+
reason: "A KMS key ARN \u2014 infrastructure identity, not personal data. Needed in plaintext to know which CMK can unwrap this org's DEK, which matters the moment a BYOK org brings its own."
|
|
17982
|
+
},
|
|
17983
|
+
{
|
|
17984
|
+
database: "haansi_intelligence_prod",
|
|
17985
|
+
table: "person_source_identities",
|
|
17986
|
+
column: "native_user_id",
|
|
17987
|
+
category: "join-key",
|
|
17988
|
+
reason: "The platform-assigned user id (GitHub numeric id, Slack U\u2026, Atlassian accountId) that makes a native identity unique and lets a reintegration find the same row again. Pseudonymous, never an email: email-only identities leave it NULL and use the encrypted email + email_blind."
|
|
17989
|
+
},
|
|
17990
|
+
{
|
|
17991
|
+
database: "haansi_intelligence_prod",
|
|
17992
|
+
table: "activity_participants",
|
|
17993
|
+
column: "role",
|
|
17994
|
+
category: "operational",
|
|
17995
|
+
reason: "A seven-value CHECK-constrained enum (author, reviewer, \u2026) that the contribution model weights and filters on. No personal data."
|
|
17996
|
+
},
|
|
17997
|
+
{
|
|
17998
|
+
database: "haansi_intelligence_prod",
|
|
17999
|
+
table: "process_activity_facts",
|
|
18000
|
+
column: "event_verb",
|
|
18001
|
+
category: "operational",
|
|
18002
|
+
reason: "A nine-value CHECK-constrained enum (created, commented, reviewed, \u2026) copied from the canonical activity. No personal data."
|
|
18003
|
+
},
|
|
18004
|
+
{
|
|
18005
|
+
database: "haansi_intelligence_prod",
|
|
18006
|
+
table: "organization_mining_config",
|
|
18007
|
+
column: "heuristic_calibration",
|
|
18008
|
+
category: "derived",
|
|
18009
|
+
reason: "Calibrated heuristic thresholds (object-graph/122): three score cuts, the percentiles and sample size they came from, and a timestamp. Numbers computed from score distributions; no content or personal data."
|
|
18010
|
+
},
|
|
18011
|
+
{
|
|
18012
|
+
database: "haansi_intelligence_prod",
|
|
18013
|
+
table: "organization_mining_config",
|
|
18014
|
+
column: "rerun_hold_reason",
|
|
18015
|
+
category: "operational",
|
|
18016
|
+
reason: "Why a hard-reset re-run stopped midway (object-graph/123): a service error message such as 'Replay cancelled (service shutting down)', shown to operators. No content or personal data."
|
|
18017
|
+
},
|
|
18018
|
+
{
|
|
18019
|
+
database: "haansi_intelligence_prod",
|
|
18020
|
+
table: "process_activity_facts",
|
|
18021
|
+
column: "evidence_fingerprint",
|
|
18022
|
+
category: "derived",
|
|
18023
|
+
reason: "A sha256 hex digest of the event's sorted (role, object id) pairs, stamped by the extractor and compared by equality to tell whether an event's inputs changed. One-way over opaque ids; no content or personal data."
|
|
18024
|
+
},
|
|
18025
|
+
{
|
|
18026
|
+
database: "haansi_intelligence_prod",
|
|
18027
|
+
table: "expert_dirty_queue",
|
|
18028
|
+
column: "scope",
|
|
18029
|
+
category: "operational",
|
|
18030
|
+
reason: "A five-value CHECK-constrained enum naming what kind of thing is dirty. Part of the coalescing primary key; no personal data."
|
|
18031
|
+
},
|
|
18032
|
+
{
|
|
18033
|
+
database: "haansi_intelligence_prod",
|
|
18034
|
+
table: "expert_dirty_queue",
|
|
18035
|
+
column: "scope_id",
|
|
18036
|
+
category: "join-key",
|
|
18037
|
+
reason: "The UUID (as text) of the dirty person object, identity group, source identity or member, or '' for a whole org. Part of the coalescing primary key, matched by equality on every enqueue."
|
|
18038
|
+
},
|
|
18039
|
+
{
|
|
18040
|
+
database: "haansi_intelligence_prod",
|
|
18041
|
+
table: "expert_dirty_queue",
|
|
18042
|
+
column: "reasons",
|
|
18043
|
+
category: "operational",
|
|
18044
|
+
reason: "Fixed trigger reason codes (participants_insert, activity_update, \u2026) unioned with DISTINCT on coalesce. No personal data."
|
|
18045
|
+
},
|
|
18046
|
+
{
|
|
18047
|
+
database: "haansi_intelligence_prod",
|
|
18048
|
+
table: "expert_dirty_queue",
|
|
18049
|
+
column: "lease_owner",
|
|
18050
|
+
category: "operational",
|
|
18051
|
+
reason: "The worker id holding the processing lease. Infrastructure identity, compared by equality on acknowledgement."
|
|
18052
|
+
},
|
|
18053
|
+
{
|
|
18054
|
+
database: "haansi_prod",
|
|
18055
|
+
table: "expert_member_outbox",
|
|
18056
|
+
column: "change",
|
|
18057
|
+
category: "operational",
|
|
18058
|
+
reason: "A three-value CHECK-constrained enum (insert, update, delete). No personal data."
|
|
18059
|
+
},
|
|
18060
|
+
// ── mining_pending_decisions (object-graph/113): ids, codes and error strings ──
|
|
18061
|
+
{
|
|
18062
|
+
database: "haansi_intelligence_prod",
|
|
18063
|
+
table: "mining_pending_decisions",
|
|
18064
|
+
column: "reason",
|
|
18065
|
+
category: "operational",
|
|
18066
|
+
reason: "A six-value CHECK-constrained code for why the activity is pending (awaiting_llm, llm_empty, \u2026). No personal data."
|
|
18067
|
+
},
|
|
18068
|
+
{
|
|
18069
|
+
database: "haansi_intelligence_prod",
|
|
18070
|
+
table: "mining_pending_decisions",
|
|
18071
|
+
column: "last_error",
|
|
18072
|
+
category: "operational",
|
|
18073
|
+
reason: "The error class and message of the failed LLM attempt, truncated. Writers pass error text, never model output or event content."
|
|
18074
|
+
},
|
|
18075
|
+
{
|
|
18076
|
+
database: "haansi_intelligence_prod",
|
|
18077
|
+
table: "mining_pending_decisions",
|
|
18078
|
+
column: "rejected_reference",
|
|
18079
|
+
category: "operational",
|
|
18080
|
+
reason: "The process id or activity id the rejected decision named, echoed into the retry prompt. An identifier, not content."
|
|
18081
|
+
},
|
|
18082
|
+
{
|
|
18083
|
+
database: "haansi_intelligence_prod",
|
|
18084
|
+
table: "mining_pending_decisions",
|
|
18085
|
+
column: "candidate_process_ids",
|
|
18086
|
+
category: "join-key",
|
|
18087
|
+
reason: "Process ids the scorer offered for this activity; the decision is validated against this set by equality."
|
|
18088
|
+
},
|
|
18089
|
+
{
|
|
18090
|
+
database: "haansi_intelligence_prod",
|
|
18091
|
+
table: "mining_pending_decisions",
|
|
18092
|
+
column: "heuristic",
|
|
18093
|
+
category: "derived",
|
|
18094
|
+
reason: "Scorer output for the activity: numeric scores keyed by candidate process id. Derived from the evidence graph, carries no content."
|
|
18095
|
+
},
|
|
18096
|
+
// ── process_state_history (object-graph/114): ids, codes, the category ──
|
|
18097
|
+
{
|
|
18098
|
+
database: "haansi_intelligence_prod",
|
|
18099
|
+
table: "process_state_history",
|
|
18100
|
+
column: "process_id",
|
|
18101
|
+
category: "join-key",
|
|
18102
|
+
reason: "The mined process's process_id \u2014 the internal join key to process_registry (composite FK). An identifier we generate."
|
|
18103
|
+
},
|
|
18104
|
+
{
|
|
18105
|
+
database: "haansi_intelligence_prod",
|
|
18106
|
+
table: "process_state_history",
|
|
18107
|
+
column: "curation_state",
|
|
18108
|
+
category: "operational",
|
|
18109
|
+
reason: "A five-value CHECK-constrained curation code copied from process_registry.curation_state. No personal data."
|
|
18110
|
+
},
|
|
18111
|
+
{
|
|
18112
|
+
database: "haansi_intelligence_prod",
|
|
18113
|
+
table: "process_state_history",
|
|
18114
|
+
column: "category",
|
|
18115
|
+
category: "derived",
|
|
18116
|
+
reason: "The process's taxonomy category at that point, copied from process_registry.category, which is stored in the clear and read by equality. A label the classifier or a curator picked, not content."
|
|
18117
|
+
},
|
|
18118
|
+
{
|
|
18119
|
+
database: "haansi_intelligence_prod",
|
|
18120
|
+
table: "process_state_history",
|
|
18121
|
+
column: "merged_into",
|
|
18122
|
+
category: "join-key",
|
|
18123
|
+
reason: "The process_id this process was merged into \u2014 a process_registry join key the as-of read follows by equality."
|
|
18124
|
+
},
|
|
18125
|
+
{
|
|
18126
|
+
database: "haansi_intelligence_prod",
|
|
18127
|
+
table: "process_state_history",
|
|
18128
|
+
column: "actor_kind",
|
|
18129
|
+
category: "operational",
|
|
18130
|
+
reason: "A three-value CHECK-constrained code (user, system, model). The user, when there is one, is actor_user_id (a uuid)."
|
|
18131
|
+
},
|
|
18132
|
+
{
|
|
18133
|
+
database: "haansi_intelligence_prod",
|
|
18134
|
+
table: "process_state_history",
|
|
18135
|
+
column: "source_command",
|
|
18136
|
+
category: "operational",
|
|
18137
|
+
reason: "The typed command that wrote the row (merge, curation, \u2026), a CHECK-constrained lowercase identifier. No personal data."
|
|
18138
|
+
},
|
|
18139
|
+
{
|
|
18140
|
+
database: "haansi_intelligence_prod",
|
|
18141
|
+
table: "identity_disputes",
|
|
18142
|
+
column: "link_source",
|
|
18143
|
+
category: "operational",
|
|
18144
|
+
reason: "A two-value CHECK-constrained enum (manual, email_blind). No personal data."
|
|
18145
|
+
},
|
|
18146
|
+
{
|
|
18147
|
+
database: "haansi_intelligence_prod",
|
|
18148
|
+
table: "identity_disputes",
|
|
18149
|
+
column: "status",
|
|
18150
|
+
category: "operational",
|
|
18151
|
+
reason: "A four-value CHECK-constrained dispute state. No personal data."
|
|
18152
|
+
},
|
|
18153
|
+
{
|
|
18154
|
+
database: "haansi_intelligence_prod",
|
|
18155
|
+
table: "identity_disputes",
|
|
18156
|
+
column: "resolution",
|
|
18157
|
+
category: "operational",
|
|
18158
|
+
reason: "A CHECK-constrained resolution code. No personal data."
|
|
18159
|
+
},
|
|
18160
|
+
{
|
|
18161
|
+
database: "haansi_intelligence_prod",
|
|
18162
|
+
table: "member_identity_links",
|
|
18163
|
+
column: "link_source",
|
|
18164
|
+
category: "operational",
|
|
18165
|
+
reason: "A two-value CHECK-constrained enum (manual, email_blind). No personal data."
|
|
18166
|
+
},
|
|
18167
|
+
{
|
|
18168
|
+
database: "haansi_intelligence_prod",
|
|
18169
|
+
table: "member_identity_links",
|
|
18170
|
+
column: "state",
|
|
18171
|
+
category: "operational",
|
|
18172
|
+
reason: "A four-value CHECK-constrained link state. No personal data."
|
|
18173
|
+
},
|
|
18174
|
+
{
|
|
18175
|
+
database: "haansi_intelligence_prod",
|
|
18176
|
+
table: "member_identity_links",
|
|
18177
|
+
column: "conflict_reason",
|
|
18178
|
+
category: "operational",
|
|
18179
|
+
reason: "A CHECK-constrained reason code. No personal data."
|
|
18180
|
+
},
|
|
18181
|
+
{
|
|
18182
|
+
database: "haansi_intelligence_prod",
|
|
18183
|
+
table: "expert_profile_facts",
|
|
18184
|
+
column: "platform_breakdown",
|
|
18185
|
+
category: "derived",
|
|
18186
|
+
reason: "Counts and scores per platform and entity type. Numbers and enum labels only \u2014 no names or content."
|
|
18187
|
+
},
|
|
18188
|
+
{
|
|
18189
|
+
database: "haansi_intelligence_prod",
|
|
18190
|
+
table: "expert_profile_facts",
|
|
18191
|
+
column: "role_breakdown",
|
|
18192
|
+
category: "derived",
|
|
18193
|
+
reason: "Counts per participant role enum. No personal data."
|
|
18194
|
+
},
|
|
18195
|
+
{
|
|
18196
|
+
database: "haansi_intelligence_prod",
|
|
18197
|
+
table: "expert_profile_facts",
|
|
18198
|
+
column: "top_workstreams",
|
|
18199
|
+
category: "derived",
|
|
18200
|
+
reason: "Process ids with counts, scores and timestamps. Names are joined at read time from process_registry, so no plaintext name is copied here."
|
|
18201
|
+
},
|
|
18202
|
+
{
|
|
18203
|
+
database: "haansi_intelligence_prod",
|
|
18204
|
+
table: "expert_profile_facts",
|
|
18205
|
+
column: "collaborators",
|
|
18206
|
+
category: "derived",
|
|
18207
|
+
reason: "Organization-user ids with shared-activity counts. Names are joined at read time from organization_users."
|
|
18208
|
+
},
|
|
18209
|
+
{
|
|
18210
|
+
database: "haansi_intelligence_prod",
|
|
18211
|
+
table: "expert_profile_facts",
|
|
18212
|
+
column: "recent_activities",
|
|
18213
|
+
category: "derived",
|
|
18214
|
+
reason: "Activity ids, target object ids, role and verb enums, and timestamps for the profile feed. Titles are read from objects at request time, so no content or name is copied here."
|
|
18215
|
+
},
|
|
18216
|
+
// Digest delivery (migrations/digests-platform/001). Schedules, delivery
|
|
18217
|
+
// state and inbox references only; digest text stays in the encrypted
|
|
18218
|
+
// entry and is never copied here.
|
|
18219
|
+
{
|
|
18220
|
+
database: "haansi_prod",
|
|
18221
|
+
table: "digest_subscriptions",
|
|
18222
|
+
column: "scope",
|
|
18223
|
+
category: "operational",
|
|
18224
|
+
reason: "A two-value CHECK-constrained enum: member or org."
|
|
18225
|
+
},
|
|
18226
|
+
{
|
|
18227
|
+
database: "haansi_prod",
|
|
18228
|
+
table: "digest_subscriptions",
|
|
18229
|
+
column: "recipe",
|
|
18230
|
+
category: "operational",
|
|
18231
|
+
reason: "The prompt recipe key, a fixed code-defined string."
|
|
18232
|
+
},
|
|
18233
|
+
{
|
|
18234
|
+
database: "haansi_prod",
|
|
18235
|
+
table: "digest_subscriptions",
|
|
18236
|
+
column: "cadence",
|
|
18237
|
+
category: "operational",
|
|
18238
|
+
reason: "A CHECK-constrained enum naming the schedule length."
|
|
18239
|
+
},
|
|
18240
|
+
{
|
|
18241
|
+
database: "haansi_prod",
|
|
18242
|
+
table: "digest_subscriptions",
|
|
18243
|
+
column: "timezone",
|
|
18244
|
+
category: "operational",
|
|
18245
|
+
reason: "An IANA timezone name the schedule runs in."
|
|
18246
|
+
},
|
|
18247
|
+
{
|
|
18248
|
+
database: "haansi_prod",
|
|
18249
|
+
table: "digest_subscriptions",
|
|
18250
|
+
column: "channels",
|
|
18251
|
+
category: "operational",
|
|
18252
|
+
reason: "CHECK-constrained channel names (email, slack_dm)."
|
|
18253
|
+
},
|
|
18254
|
+
// Teams (migrations/digests-platform/004): admin-entered labels for how
|
|
18255
|
+
// the organization groups its people, shown to its own members. Not
|
|
18256
|
+
// customer content drawn from integrations.
|
|
18257
|
+
// Work logs (migrations/digests/004): `text` is encrypted (column
|
|
18258
|
+
// registry); these two are machine values.
|
|
18259
|
+
{
|
|
18260
|
+
database: "haansi_intelligence_prod",
|
|
18261
|
+
table: "digest_work_logs",
|
|
18262
|
+
column: "workstream_id",
|
|
18263
|
+
category: "operational",
|
|
18264
|
+
reason: "A process_registry id the author tagged the entry with."
|
|
18265
|
+
},
|
|
18266
|
+
{
|
|
18267
|
+
database: "haansi_intelligence_prod",
|
|
18268
|
+
table: "digest_work_logs",
|
|
18269
|
+
column: "source",
|
|
18270
|
+
category: "operational",
|
|
18271
|
+
reason: "A CHECK-constrained enum: web, cli, slack or mcp."
|
|
18272
|
+
},
|
|
18273
|
+
{
|
|
18274
|
+
database: "haansi_prod",
|
|
18275
|
+
table: "teams",
|
|
18276
|
+
column: "name",
|
|
18277
|
+
category: "operational",
|
|
18278
|
+
reason: "An admin-entered team label (e.g. Payments), shown to the org's own members."
|
|
18279
|
+
},
|
|
18280
|
+
{
|
|
18281
|
+
database: "haansi_prod",
|
|
18282
|
+
table: "teams",
|
|
18283
|
+
column: "description",
|
|
18284
|
+
category: "operational",
|
|
18285
|
+
reason: "An admin-entered one-line description of a team, shown to the org's own members."
|
|
18286
|
+
},
|
|
18287
|
+
{
|
|
18288
|
+
database: "haansi_prod",
|
|
18289
|
+
table: "digest_org_policies",
|
|
18290
|
+
column: "org_timezone",
|
|
18291
|
+
category: "operational",
|
|
18292
|
+
reason: "An IANA timezone name the org digest's weeks are in."
|
|
18293
|
+
},
|
|
18294
|
+
{
|
|
18295
|
+
database: "haansi_prod",
|
|
18296
|
+
table: "digest_org_policies",
|
|
18297
|
+
column: "allowed_channels",
|
|
18298
|
+
category: "operational",
|
|
18299
|
+
reason: "CHECK-constrained channel names (email, slack_dm)."
|
|
18300
|
+
},
|
|
18301
|
+
{
|
|
18302
|
+
database: "haansi_prod",
|
|
18303
|
+
table: "digest_deliveries",
|
|
18304
|
+
column: "timezone",
|
|
18305
|
+
category: "operational",
|
|
18306
|
+
reason: "An IANA timezone name the week was computed in."
|
|
18307
|
+
},
|
|
18308
|
+
{
|
|
18309
|
+
database: "haansi_prod",
|
|
18310
|
+
table: "digest_deliveries",
|
|
18311
|
+
column: "channel",
|
|
18312
|
+
category: "operational",
|
|
18313
|
+
reason: "A CHECK-constrained enum naming the delivery channel."
|
|
18314
|
+
},
|
|
18315
|
+
{
|
|
18316
|
+
database: "haansi_prod",
|
|
18317
|
+
table: "digest_deliveries",
|
|
18318
|
+
column: "status",
|
|
18319
|
+
category: "operational",
|
|
18320
|
+
reason: "A CHECK-constrained enum of delivery state."
|
|
18321
|
+
},
|
|
18322
|
+
{
|
|
18323
|
+
database: "haansi_prod",
|
|
18324
|
+
table: "digest_deliveries",
|
|
18325
|
+
column: "reason",
|
|
18326
|
+
category: "operational",
|
|
18327
|
+
reason: "A short code such as quiet or no_slack_identity, never an error message."
|
|
18328
|
+
},
|
|
18329
|
+
{
|
|
18330
|
+
database: "haansi_prod",
|
|
18331
|
+
table: "user_notifications",
|
|
18332
|
+
column: "kind",
|
|
18333
|
+
category: "operational",
|
|
18334
|
+
reason: "A CHECK-constrained enum naming the notification type."
|
|
18335
|
+
},
|
|
18336
|
+
{
|
|
18337
|
+
database: "haansi_prod",
|
|
18338
|
+
table: "user_notifications",
|
|
18339
|
+
column: "ref_id",
|
|
18340
|
+
category: "join-key",
|
|
18341
|
+
reason: "The id of the thing the notification points at, e.g. a digest entry UUID."
|
|
18342
|
+
},
|
|
18343
|
+
{
|
|
18344
|
+
database: "haansi_prod",
|
|
18345
|
+
table: "user_notifications",
|
|
18346
|
+
column: "data",
|
|
18347
|
+
category: "structural",
|
|
18348
|
+
reason: "Non-sensitive display fields only (period dates, status); content stays in the referenced record."
|
|
18349
|
+
},
|
|
18350
|
+
// Digests (migrations/digests/002). Queue state, citation ids and the
|
|
18351
|
+
// access log carry no content: the entry's text lives in knowledge_artifacts,
|
|
18352
|
+
// whose columns are already encrypted.
|
|
18353
|
+
{
|
|
18354
|
+
database: "haansi_intelligence_prod",
|
|
18355
|
+
table: "digest_generation_queue",
|
|
18356
|
+
column: "digest_scope",
|
|
18357
|
+
category: "operational",
|
|
18358
|
+
reason: "A two-value CHECK-constrained enum: member or org."
|
|
18359
|
+
},
|
|
18360
|
+
{
|
|
18361
|
+
database: "haansi_intelligence_prod",
|
|
18362
|
+
table: "digest_generation_queue",
|
|
18363
|
+
column: "period_kind",
|
|
18364
|
+
category: "operational",
|
|
18365
|
+
reason: "A CHECK-constrained enum naming the period length."
|
|
18366
|
+
},
|
|
18367
|
+
{
|
|
18368
|
+
database: "haansi_intelligence_prod",
|
|
18369
|
+
table: "digest_generation_queue",
|
|
18370
|
+
column: "timezone",
|
|
18371
|
+
category: "operational",
|
|
18372
|
+
reason: "An IANA timezone name the week is computed in."
|
|
18373
|
+
},
|
|
18374
|
+
{
|
|
18375
|
+
database: "haansi_intelligence_prod",
|
|
18376
|
+
table: "digest_generation_queue",
|
|
18377
|
+
column: "recipe",
|
|
18378
|
+
category: "operational",
|
|
18379
|
+
reason: "The prompt recipe key, a fixed code-defined string."
|
|
18380
|
+
},
|
|
18381
|
+
{
|
|
18382
|
+
database: "haansi_intelligence_prod",
|
|
18383
|
+
table: "digest_generation_queue",
|
|
18384
|
+
column: "reason",
|
|
18385
|
+
category: "operational",
|
|
18386
|
+
reason: "A three-value CHECK-constrained enum: scheduled, on_demand or regenerate."
|
|
18387
|
+
},
|
|
18388
|
+
{
|
|
18389
|
+
database: "haansi_intelligence_prod",
|
|
18390
|
+
table: "digest_generation_queue",
|
|
18391
|
+
column: "status",
|
|
18392
|
+
category: "operational",
|
|
18393
|
+
reason: "A four-value CHECK-constrained enum of queue state."
|
|
18394
|
+
},
|
|
18395
|
+
{
|
|
18396
|
+
database: "haansi_intelligence_prod",
|
|
18397
|
+
table: "digest_generation_queue",
|
|
18398
|
+
column: "lease_owner",
|
|
18399
|
+
category: "operational",
|
|
18400
|
+
reason: "The worker's host and process id holding the lease."
|
|
18401
|
+
},
|
|
18402
|
+
{
|
|
18403
|
+
database: "haansi_intelligence_prod",
|
|
18404
|
+
table: "digest_generation_queue",
|
|
18405
|
+
column: "error_code",
|
|
18406
|
+
category: "operational",
|
|
18407
|
+
reason: "A short code such as generation_error, never an error message."
|
|
18408
|
+
},
|
|
18409
|
+
{
|
|
18410
|
+
database: "haansi_intelligence_prod",
|
|
18411
|
+
table: "onboarding_guides",
|
|
18412
|
+
column: "status",
|
|
18413
|
+
category: "operational",
|
|
18414
|
+
reason: "A CHECK-constrained lifecycle enum (draft, published, unpublished, deleted) that decides who may see the guide; read by SQL predicates."
|
|
18415
|
+
},
|
|
18416
|
+
{
|
|
18417
|
+
database: "haansi_intelligence_prod",
|
|
18418
|
+
table: "onboarding_guides",
|
|
18419
|
+
column: "template_id",
|
|
18420
|
+
category: "operational",
|
|
18421
|
+
reason: "The id of a builtin report template (e.g. workstream-handoff), never user text."
|
|
18422
|
+
},
|
|
18423
|
+
{
|
|
18424
|
+
database: "haansi_intelligence_prod",
|
|
18425
|
+
table: "onboarding_guides",
|
|
18426
|
+
column: "audience",
|
|
18427
|
+
category: "operational",
|
|
18428
|
+
reason: "A short audience code (engineer, business, agent) chosen from a fixed list."
|
|
18429
|
+
},
|
|
18430
|
+
{
|
|
18431
|
+
database: "haansi_intelligence_prod",
|
|
18432
|
+
table: "onboarding_guide_source_refs",
|
|
18433
|
+
column: "ref_type",
|
|
18434
|
+
category: "operational",
|
|
18435
|
+
reason: "A CHECK-constrained enum naming the kind of cited source."
|
|
18436
|
+
},
|
|
18437
|
+
{
|
|
18438
|
+
database: "haansi_intelligence_prod",
|
|
18439
|
+
table: "onboarding_guide_source_refs",
|
|
18440
|
+
column: "ref_id",
|
|
18441
|
+
category: "join-key",
|
|
18442
|
+
reason: "The id of the cited workstream, incident, solution, artifact, object or guide \u2014 a UUID or a process id, matched by equality."
|
|
18443
|
+
},
|
|
18444
|
+
{
|
|
18445
|
+
database: "haansi_intelligence_prod",
|
|
18446
|
+
table: "onboarding_guide_source_refs",
|
|
18447
|
+
column: "state",
|
|
18448
|
+
category: "operational",
|
|
18449
|
+
reason: "A CHECK-constrained enum (live, revoked)."
|
|
18450
|
+
},
|
|
18451
|
+
{
|
|
18452
|
+
database: "haansi_intelligence_prod",
|
|
18453
|
+
table: "digest_evidence_refs",
|
|
18454
|
+
column: "evidence_key",
|
|
18455
|
+
category: "structural",
|
|
18456
|
+
reason: "The E-key (E1, E2, \u2026) the entry cites."
|
|
18457
|
+
},
|
|
18458
|
+
{
|
|
18459
|
+
database: "haansi_intelligence_prod",
|
|
18460
|
+
table: "digest_evidence_refs",
|
|
18461
|
+
column: "ref_type",
|
|
18462
|
+
category: "operational",
|
|
18463
|
+
reason: "A CHECK-constrained enum naming the kind of cited source."
|
|
18464
|
+
},
|
|
18465
|
+
{
|
|
18466
|
+
database: "haansi_intelligence_prod",
|
|
18467
|
+
table: "digest_evidence_refs",
|
|
18468
|
+
column: "ref_id",
|
|
18469
|
+
category: "join-key",
|
|
18470
|
+
reason: "The id of the cited activity, workstream, artifact or incident \u2014 a UUID or a process id, matched by equality."
|
|
18471
|
+
},
|
|
18472
|
+
{
|
|
18473
|
+
database: "haansi_intelligence_prod",
|
|
18474
|
+
table: "digest_access_log",
|
|
18475
|
+
column: "action",
|
|
18476
|
+
category: "operational",
|
|
18477
|
+
reason: "A CHECK-constrained enum naming what the actor did."
|
|
18478
|
+
},
|
|
18479
|
+
{
|
|
18480
|
+
database: "haansi_intelligence_prod",
|
|
18481
|
+
table: "digest_access_log",
|
|
18482
|
+
column: "surface",
|
|
18483
|
+
category: "operational",
|
|
18484
|
+
reason: "The product surface of the request (web, cli, mcp), taken from the credential."
|
|
18485
|
+
},
|
|
18486
|
+
{
|
|
18487
|
+
database: "haansi_intelligence_prod",
|
|
18488
|
+
table: "expert_evidence_refs",
|
|
18489
|
+
column: "source_type",
|
|
18490
|
+
category: "operational",
|
|
18491
|
+
reason: "A four-value CHECK-constrained enum naming the kind of cited source."
|
|
18492
|
+
},
|
|
18493
|
+
{
|
|
18494
|
+
database: "haansi_intelligence_prod",
|
|
18495
|
+
table: "expert_evidence_refs",
|
|
18496
|
+
column: "source_id",
|
|
18497
|
+
category: "join-key",
|
|
18498
|
+
reason: "The id of the cited activity, object, workstream or artifact \u2014 a UUID or a process id, matched by equality when a source is deleted."
|
|
18499
|
+
},
|
|
18500
|
+
{
|
|
18501
|
+
database: "haansi_intelligence_prod",
|
|
18502
|
+
table: "expert_profiles",
|
|
18503
|
+
column: "model",
|
|
18504
|
+
category: "operational",
|
|
18505
|
+
reason: "The model id that generated the version. Configuration, not content."
|
|
18506
|
+
},
|
|
18507
|
+
{
|
|
18508
|
+
database: "haansi_intelligence_prod",
|
|
18509
|
+
table: "expert_profiles",
|
|
18510
|
+
column: "prompt_version",
|
|
18511
|
+
category: "operational",
|
|
18512
|
+
reason: "A prompt revision label. Configuration, not content."
|
|
18513
|
+
},
|
|
18514
|
+
{
|
|
18515
|
+
database: "haansi_intelligence_prod",
|
|
18516
|
+
table: "expert_profiles",
|
|
18517
|
+
column: "input_hash",
|
|
18518
|
+
category: "derived",
|
|
18519
|
+
reason: "SHA-256 of the evidence pack, compared by equality to skip an unchanged regeneration. A digest, not content."
|
|
18520
|
+
},
|
|
18521
|
+
{
|
|
18522
|
+
database: "haansi_intelligence_prod",
|
|
18523
|
+
table: "expert_areas",
|
|
18524
|
+
column: "horizon",
|
|
18525
|
+
category: "operational",
|
|
18526
|
+
reason: "A two-value CHECK-constrained enum (established, current)."
|
|
18527
|
+
},
|
|
18528
|
+
{
|
|
18529
|
+
database: "haansi_intelligence_prod",
|
|
18530
|
+
table: "expert_areas",
|
|
18531
|
+
column: "depth",
|
|
18532
|
+
category: "operational",
|
|
18533
|
+
reason: "A three-value CHECK-constrained enum (owner, contributor, familiar)."
|
|
18534
|
+
},
|
|
18535
|
+
{
|
|
18536
|
+
database: "haansi_intelligence_prod",
|
|
18537
|
+
table: "expert_areas",
|
|
18538
|
+
column: "confidence",
|
|
18539
|
+
category: "operational",
|
|
18540
|
+
reason: "A three-value CHECK-constrained enum (high, medium, low)."
|
|
18541
|
+
},
|
|
18542
|
+
{
|
|
18543
|
+
database: "haansi_intelligence_prod",
|
|
18544
|
+
table: "expert_profiles",
|
|
18545
|
+
column: "validation_drops",
|
|
18546
|
+
category: "derived",
|
|
18547
|
+
reason: "object-graph/102. Integer counts of what the profile validator rejected, keyed by reason (area_work_shaped, uncited_claim, \u2026). Numbers only \u2014 none of the rejected text is kept, which is the whole point: the counts exist so a thin profile can be explained without storing what the model wrote about a person."
|
|
18548
|
+
},
|
|
18549
|
+
// Org-level expertise grouping (object-graph/100). The pass's lease and its
|
|
18550
|
+
// audit log; the only content-bearing columns on either table are the area
|
|
18551
|
+
// names, which are classified in column-registry.ts.
|
|
18552
|
+
{
|
|
18553
|
+
database: "haansi_intelligence_prod",
|
|
18554
|
+
table: "expert_grouping_state",
|
|
18555
|
+
column: "claimed_by",
|
|
18556
|
+
category: "operational",
|
|
18557
|
+
reason: "The pipeline worker id holding the org's grouping lease. Infrastructure identity, never content."
|
|
18558
|
+
},
|
|
18559
|
+
{
|
|
18560
|
+
database: "haansi_intelligence_prod",
|
|
18561
|
+
table: "expert_grouping_state",
|
|
18562
|
+
column: "last_input_hash",
|
|
18563
|
+
category: "derived",
|
|
18564
|
+
reason: "A SHA-256 digest of the vocabulary the last run saw, used to skip an unchanged org. One-way and carries no names."
|
|
18565
|
+
},
|
|
18566
|
+
{
|
|
18567
|
+
database: "haansi_intelligence_prod",
|
|
18568
|
+
table: "expert_area_merge_log",
|
|
18569
|
+
column: "action",
|
|
18570
|
+
category: "operational",
|
|
18571
|
+
reason: "A three-value CHECK-constrained enum (merge, rename, describe)."
|
|
18572
|
+
},
|
|
18573
|
+
{
|
|
18574
|
+
database: "haansi_intelligence_prod",
|
|
18575
|
+
table: "expert_area_merge_log",
|
|
18576
|
+
column: "model",
|
|
18577
|
+
category: "operational",
|
|
18578
|
+
reason: "The provider model id that produced the grouping, for attributing a bad run. Vendor identifier, never content."
|
|
18579
|
+
},
|
|
18580
|
+
{
|
|
18581
|
+
database: "haansi_intelligence_prod",
|
|
18582
|
+
table: "expert_profile_citations",
|
|
18583
|
+
column: "contribution",
|
|
18584
|
+
category: "derived",
|
|
18585
|
+
reason: "What the member contributed to one cited source: two counts and two dates (object-graph/128). No titles, names or text."
|
|
18586
|
+
},
|
|
18587
|
+
// Trending topics (trending-clustering/001, 002). A topic is stored as ids:
|
|
18588
|
+
// which item is its centre and which items belong to it. Titles are read
|
|
18589
|
+
// from the items themselves (knowledge_artifacts.title,
|
|
18590
|
+
// process_registry.name — both encrypted), never copied here.
|
|
18591
|
+
{
|
|
18592
|
+
database: "haansi_intelligence_prod",
|
|
18593
|
+
table: "artifact_clusters",
|
|
18594
|
+
column: "theme",
|
|
18595
|
+
category: "derived",
|
|
18596
|
+
reason: "Always the empty string. It once held a copy of the centre's title; the trending tick now writes it empty and the API reads the title from the item. Kept only because the column is NOT NULL."
|
|
18597
|
+
},
|
|
18598
|
+
{
|
|
18599
|
+
database: "haansi_intelligence_prod",
|
|
18600
|
+
table: "artifact_clusters",
|
|
18601
|
+
column: "theme_kind",
|
|
18602
|
+
category: "operational",
|
|
18603
|
+
reason: "Enum: 'artifact' or 'workstream' \u2014 which kind of item is the topic's centre. Never content."
|
|
18604
|
+
},
|
|
18605
|
+
{
|
|
18606
|
+
database: "haansi_intelligence_prod",
|
|
18607
|
+
table: "artifact_clusters",
|
|
18608
|
+
column: "theme_process_id",
|
|
18609
|
+
category: "derived",
|
|
18610
|
+
reason: "The process id of the workstream at a topic's centre. An opaque identifier into process_registry; its name stays there, encrypted."
|
|
18611
|
+
},
|
|
18612
|
+
{
|
|
18613
|
+
database: "haansi_intelligence_prod",
|
|
18614
|
+
table: "artifact_cluster_workstreams",
|
|
18615
|
+
column: "process_id",
|
|
18616
|
+
category: "derived",
|
|
18617
|
+
reason: "The process id of a workstream that belongs to a topic. An opaque identifier into process_registry; no name or text."
|
|
18618
|
+
},
|
|
18619
|
+
{
|
|
18620
|
+
database: "haansi_intelligence_prod",
|
|
18621
|
+
table: "org_expertise_areas",
|
|
18622
|
+
column: "curated_at",
|
|
18623
|
+
category: "operational",
|
|
18624
|
+
reason: "Manual edit marker used by grouping and description writers."
|
|
18625
|
+
},
|
|
18626
|
+
// Cited expertise-area descriptions (object-graph/129). The description
|
|
18627
|
+
// text itself is org_expertise_areas.description, which is encrypted.
|
|
18628
|
+
{
|
|
18629
|
+
database: "haansi_intelligence_prod",
|
|
18630
|
+
table: "org_expertise_areas",
|
|
18631
|
+
column: "description_citations",
|
|
18632
|
+
category: "derived",
|
|
18633
|
+
reason: "Per sentence of the description, the sources it cites, as '<source_type>:<source_id>' keys. Ids only \u2014 no titles, names or text."
|
|
18634
|
+
},
|
|
18635
|
+
{
|
|
18636
|
+
database: "haansi_intelligence_prod",
|
|
18637
|
+
table: "org_expertise_areas",
|
|
18638
|
+
column: "description_input_hash",
|
|
18639
|
+
category: "derived",
|
|
18640
|
+
reason: "SHA-256 of the evidence the description was written from, compared by equality to skip an unchanged area. One-way; carries no content."
|
|
18641
|
+
},
|
|
18642
|
+
{
|
|
18643
|
+
database: "haansi_intelligence_prod",
|
|
18644
|
+
table: "org_expertise_areas",
|
|
18645
|
+
column: "description_model",
|
|
18646
|
+
category: "operational",
|
|
18647
|
+
reason: "The provider model id that wrote the description. Vendor identifier, never content."
|
|
18648
|
+
},
|
|
18649
|
+
// Expert profile scheduling (object-graph/127).
|
|
18650
|
+
{
|
|
18651
|
+
database: "haansi_intelligence_prod",
|
|
18652
|
+
table: "expert_load_cycle_state",
|
|
18653
|
+
column: "hold_reason",
|
|
18654
|
+
category: "operational",
|
|
18655
|
+
reason: "A four-value CHECK-constrained enum naming why profile generation is held (initial_sync_pending, normalization_lag, mining_lag, settling)."
|
|
18656
|
+
},
|
|
18657
|
+
{
|
|
18658
|
+
database: "haansi_intelligence_prod",
|
|
18659
|
+
table: "expert_profile_facts",
|
|
18660
|
+
column: "synthesis_check_outcome",
|
|
18661
|
+
category: "operational",
|
|
18662
|
+
reason: "The synthesis pass's outcome code for the member's last evidence check (written, unchanged, no_evidence, rejected_*, failed). A fixed set of pipeline codes, never content."
|
|
18663
|
+
},
|
|
18664
|
+
{
|
|
18665
|
+
database: "haansi_intelligence_prod",
|
|
18666
|
+
table: "expert_profile_facts",
|
|
18667
|
+
column: "synthesis_check_hash",
|
|
18668
|
+
category: "derived",
|
|
18669
|
+
reason: "SHA-256 of the evidence pack seen at the last check, the same digest as expert_profiles.input_hash. One-way; carries no content."
|
|
18670
|
+
},
|
|
18671
|
+
{
|
|
18672
|
+
database: "haansi_intelligence_prod",
|
|
18673
|
+
table: "expert_synthesis_claims",
|
|
18674
|
+
column: "claimed_by",
|
|
18675
|
+
category: "operational",
|
|
18676
|
+
reason: "The pipeline worker id holding the synthesis lease. Infrastructure identity, never content."
|
|
18677
|
+
},
|
|
18678
|
+
{
|
|
18679
|
+
database: "haansi_intelligence_prod",
|
|
18680
|
+
table: "expert_synthesis_claims",
|
|
18681
|
+
column: "pending_input",
|
|
18682
|
+
category: "derived",
|
|
18683
|
+
reason: "A parked batch member's citation map: evidence keys (E1\u2026) mapped to source type, source id, integration platform and account key, plus an activity count, an input digest and a prompt version. No titles, names or summaries \u2014 the prompt text is never persisted (citationsOf strips it). Deleted when the attempt ends."
|
|
18684
|
+
},
|
|
18685
|
+
{
|
|
18686
|
+
database: "haansi_intelligence_prod",
|
|
18687
|
+
table: "expert_profile_curation",
|
|
18688
|
+
column: "hidden_platforms",
|
|
18689
|
+
category: "operational",
|
|
18690
|
+
reason: "Integration type names (github, slack, \u2026) whose evidence the member hid. A closed vocabulary, not content."
|
|
18691
|
+
},
|
|
18692
|
+
{
|
|
18693
|
+
database: "haansi_intelligence_prod",
|
|
18694
|
+
table: "expert_profile_curation_log",
|
|
18695
|
+
column: "changed_fields",
|
|
18696
|
+
category: "operational",
|
|
18697
|
+
reason: "Names of the curation fields an edit changed (headline, pinned areas, \u2026). Field names, never their values."
|
|
18698
|
+
},
|
|
18699
|
+
{
|
|
18700
|
+
database: "haansi_intelligence_prod",
|
|
18701
|
+
table: "expert_profile_curation_log",
|
|
18702
|
+
column: "before_hash",
|
|
18703
|
+
category: "derived",
|
|
18704
|
+
reason: "SHA-256 of the overlay before the edit, for audit comparison. A digest, not content."
|
|
18705
|
+
},
|
|
18706
|
+
{
|
|
18707
|
+
database: "haansi_intelligence_prod",
|
|
18708
|
+
table: "expert_profile_curation_log",
|
|
18709
|
+
column: "after_hash",
|
|
18710
|
+
category: "derived",
|
|
18711
|
+
reason: "SHA-256 of the overlay after the edit, for audit comparison. A digest, not content."
|
|
18712
|
+
},
|
|
18713
|
+
{
|
|
18714
|
+
database: "haansi_intelligence_prod",
|
|
18715
|
+
table: "objects",
|
|
18716
|
+
column: "canonical_id",
|
|
18717
|
+
category: "structural",
|
|
18718
|
+
reason: "The object graph's identity key \u2014 identity resolution and the merge path match on it, and object_relationships/event_object_links join through the row it identifies. Often an email address, so this is a KNOWN accepted disclosure, recorded rather than hidden: encrypting it non-deterministically breaks entity resolution, and deterministically leaks by frequency while breaking nothing an attacker relies on."
|
|
18719
|
+
},
|
|
18720
|
+
{
|
|
18721
|
+
database: "haansi_prod",
|
|
18722
|
+
table: "byod_migration_jobs",
|
|
18723
|
+
column: "operation",
|
|
18724
|
+
category: "operational",
|
|
18725
|
+
reason: "A closed enum of migration verbs, pinned by byod_migration_jobs_operation_check ('activate', 'disable', 'remove', 'enable_object_graph', 'disable_object_graph', 'retry'). It carries no tenant content, and the durable worker reads it to decide crash-safe finalization \u2014 encrypting a discriminator the recovery path compares against would make a half-finished migration unresumable."
|
|
18726
|
+
},
|
|
18727
|
+
// ── WS6 phase 2: session object attempts, revocations, erasure milestones ──
|
|
18728
|
+
{
|
|
18729
|
+
database: "haansi_prod",
|
|
18730
|
+
table: "session_object_attempts",
|
|
18731
|
+
column: "user_id",
|
|
18732
|
+
category: "structural",
|
|
18733
|
+
reason: "Identifier in the storage key layout, used to address and erase objects; the object bytes are encrypted under the per-attempt key."
|
|
18734
|
+
},
|
|
18735
|
+
{
|
|
18736
|
+
database: "haansi_prod",
|
|
18737
|
+
table: "session_object_attempts",
|
|
18738
|
+
column: "session_id",
|
|
18739
|
+
category: "structural",
|
|
18740
|
+
reason: "Identifier in the storage key layout, used to address and erase objects; the object bytes are encrypted under the per-attempt key."
|
|
18741
|
+
},
|
|
18742
|
+
{
|
|
18743
|
+
database: "haansi_prod",
|
|
18744
|
+
table: "session_object_attempts",
|
|
18745
|
+
column: "object_key",
|
|
18746
|
+
category: "structural",
|
|
18747
|
+
reason: "Identifier in the storage key layout, used to address and erase objects; the object bytes are encrypted under the per-attempt key."
|
|
18748
|
+
},
|
|
18749
|
+
{
|
|
18750
|
+
database: "haansi_prod",
|
|
18751
|
+
table: "session_object_attempts",
|
|
18752
|
+
column: "version_id",
|
|
18753
|
+
category: "structural",
|
|
18754
|
+
reason: "S3 version id returned by the store; addresses an object version for erasure."
|
|
18755
|
+
},
|
|
18756
|
+
{
|
|
18757
|
+
database: "haansi_prod",
|
|
18758
|
+
table: "session_object_attempts",
|
|
18759
|
+
column: "bucket_kind",
|
|
18760
|
+
category: "operational",
|
|
18761
|
+
reason: "Closed CHECK vocabulary the erasure and publication paths filter on."
|
|
18762
|
+
},
|
|
18763
|
+
{
|
|
18764
|
+
database: "haansi_prod",
|
|
18765
|
+
table: "session_object_attempts",
|
|
18766
|
+
column: "source_kind",
|
|
18767
|
+
category: "operational",
|
|
18768
|
+
reason: "Names the source whose generation the attempt was minted under; a vocabulary term, not content."
|
|
18769
|
+
},
|
|
18770
|
+
{
|
|
18771
|
+
database: "haansi_prod",
|
|
18772
|
+
table: "session_object_attempts",
|
|
18773
|
+
column: "status",
|
|
18774
|
+
category: "operational",
|
|
18775
|
+
reason: "Closed CHECK vocabulary the erasure and publication paths filter on."
|
|
18776
|
+
},
|
|
18777
|
+
{
|
|
18778
|
+
database: "haansi_prod",
|
|
18779
|
+
table: "session_object_attempts",
|
|
18780
|
+
column: "wrapped_key",
|
|
18781
|
+
category: "derived",
|
|
18782
|
+
reason: "Already ciphertext: the per-attempt data key wrapped by the org key."
|
|
18783
|
+
},
|
|
18784
|
+
{
|
|
18785
|
+
database: "haansi_prod",
|
|
18786
|
+
table: "session_object_attempts",
|
|
18787
|
+
column: "content_token",
|
|
18788
|
+
category: "derived",
|
|
18789
|
+
reason: "Staging token and redaction-manifest hash for idempotent re-publication; hashes and tokens, not content."
|
|
18790
|
+
},
|
|
18791
|
+
{
|
|
18792
|
+
database: "haansi_prod",
|
|
18793
|
+
table: "session_object_revocations",
|
|
18794
|
+
column: "scope_kind",
|
|
18795
|
+
category: "operational",
|
|
18796
|
+
reason: "Closed CHECK vocabulary the erasure and publication paths filter on."
|
|
18797
|
+
},
|
|
18798
|
+
{
|
|
18799
|
+
database: "haansi_prod",
|
|
18800
|
+
table: "session_object_revocations",
|
|
18801
|
+
column: "scope_id",
|
|
18802
|
+
category: "structural",
|
|
18803
|
+
reason: "User or session-source identifier a revocation applies to; erasure filters on it."
|
|
18804
|
+
},
|
|
18805
|
+
{
|
|
18806
|
+
database: "haansi_prod",
|
|
18807
|
+
table: "session_erasure_records",
|
|
18808
|
+
column: "user_id",
|
|
18809
|
+
category: "structural",
|
|
18810
|
+
reason: "Identifier of the erased user's scope; the milestone record must outlive the user's data to prove erasure."
|
|
18811
|
+
},
|
|
18812
|
+
{
|
|
18813
|
+
database: "haansi_prod",
|
|
18814
|
+
table: "session_erasure_records",
|
|
18815
|
+
column: "reason",
|
|
18816
|
+
category: "operational",
|
|
18817
|
+
reason: "Closed CHECK vocabulary the erasure and publication paths filter on."
|
|
18818
|
+
},
|
|
18819
|
+
// Memory → Skills (skills/001).
|
|
18820
|
+
{
|
|
18821
|
+
database: "haansi_intelligence_prod",
|
|
18822
|
+
table: "skills",
|
|
18823
|
+
column: "approved_audience",
|
|
18824
|
+
category: "operational",
|
|
18825
|
+
reason: "Closed CHECK vocabulary (org, owner) the skill access check filters on."
|
|
18826
|
+
},
|
|
18827
|
+
{
|
|
18828
|
+
database: "haansi_intelligence_prod",
|
|
18829
|
+
table: "skill_publications",
|
|
18830
|
+
column: "approved_audience",
|
|
18831
|
+
category: "operational",
|
|
18832
|
+
reason: "Closed CHECK vocabulary (org, owner) recorded per publish; read by the access check."
|
|
18833
|
+
},
|
|
18834
|
+
{
|
|
18835
|
+
database: "haansi_intelligence_prod",
|
|
18836
|
+
table: "skill_sources",
|
|
18837
|
+
column: "source_kind",
|
|
18838
|
+
category: "operational",
|
|
18839
|
+
reason: "Closed CHECK vocabulary naming which table a dependency resolves against."
|
|
18840
|
+
},
|
|
18841
|
+
{
|
|
18842
|
+
database: "haansi_intelligence_prod",
|
|
18843
|
+
table: "skill_sources",
|
|
18844
|
+
column: "content_fingerprint",
|
|
18845
|
+
category: "derived",
|
|
18846
|
+
reason: "sha256 of the captured source text, compared by equality to detect a changed source. Encrypting it would break the comparison; the text itself is not stored here."
|
|
18847
|
+
},
|
|
18848
|
+
{
|
|
18849
|
+
database: "haansi_intelligence_prod",
|
|
18850
|
+
table: "skill_candidates",
|
|
18851
|
+
column: "lane",
|
|
18852
|
+
category: "operational",
|
|
18853
|
+
reason: "Closed CHECK vocabulary (recurring, single) the Suggested tab filters on."
|
|
18854
|
+
},
|
|
18855
|
+
{
|
|
18856
|
+
database: "haansi_intelligence_prod",
|
|
18857
|
+
table: "skill_candidates",
|
|
18858
|
+
column: "audience_key",
|
|
18859
|
+
category: "structural",
|
|
18860
|
+
reason: "'shared' or a member's user id: which audience a group was formed within. An identifier, never text."
|
|
18861
|
+
},
|
|
18862
|
+
{
|
|
18863
|
+
database: "haansi_intelligence_prod",
|
|
18864
|
+
table: "skill_candidates",
|
|
18865
|
+
column: "member_fingerprint",
|
|
18866
|
+
category: "derived",
|
|
18867
|
+
reason: "sha256 of the sorted member ids, compared by equality to detect a changed group."
|
|
18868
|
+
},
|
|
18869
|
+
{
|
|
18870
|
+
database: "haansi_intelligence_prod",
|
|
18871
|
+
table: "skill_events",
|
|
18872
|
+
column: "channel",
|
|
18873
|
+
category: "operational",
|
|
18874
|
+
reason: "Closed CHECK vocabulary (mcp, qna, cli, web): where a skill was loaded from."
|
|
18875
|
+
},
|
|
18876
|
+
{
|
|
18877
|
+
database: "haansi_intelligence_prod",
|
|
18878
|
+
table: "skill_events",
|
|
18879
|
+
column: "event",
|
|
18880
|
+
category: "operational",
|
|
18881
|
+
reason: "Closed CHECK vocabulary (loaded, resource_loaded, synced, applied, helped, not_helped)."
|
|
18882
|
+
}
|
|
18883
|
+
];
|
|
16586
18884
|
}
|
|
16587
18885
|
});
|
|
16588
18886
|
|
|
@@ -22877,6 +25175,38 @@ var init_mining_config_fields = __esm({
|
|
|
22877
25175
|
}
|
|
22878
25176
|
});
|
|
22879
25177
|
|
|
25178
|
+
// ../../packages/@company/types/src/exchange.ts
|
|
25179
|
+
var EXCHANGE_DISCIPLINES, EXCHANGE_WORK_FUNCTIONS, EXCHANGE_DISCIPLINE_IDS, EXCHANGE_WORK_FUNCTION_IDS;
|
|
25180
|
+
var init_exchange = __esm({
|
|
25181
|
+
"../../packages/@company/types/src/exchange.ts"() {
|
|
25182
|
+
"use strict";
|
|
25183
|
+
EXCHANGE_DISCIPLINES = [
|
|
25184
|
+
{ id: "engineering", label: "Engineering" },
|
|
25185
|
+
{ id: "data", label: "Data" },
|
|
25186
|
+
{ id: "design", label: "Design" },
|
|
25187
|
+
{ id: "product", label: "Product" },
|
|
25188
|
+
{ id: "security", label: "Security" },
|
|
25189
|
+
{ id: "operations", label: "Operations" },
|
|
25190
|
+
{ id: "go_to_market", label: "Go-to-market" },
|
|
25191
|
+
{ id: "people", label: "People" }
|
|
25192
|
+
];
|
|
25193
|
+
EXCHANGE_WORK_FUNCTIONS = [
|
|
25194
|
+
{ id: "software_development", label: "Software development" },
|
|
25195
|
+
{ id: "infrastructure", label: "Infrastructure" },
|
|
25196
|
+
{ id: "incident_response", label: "Incident response" },
|
|
25197
|
+
{ id: "analytics", label: "Analytics" },
|
|
25198
|
+
{ id: "research", label: "Research" },
|
|
25199
|
+
{ id: "customer_success", label: "Customer success" },
|
|
25200
|
+
{ id: "sales", label: "Sales" },
|
|
25201
|
+
{ id: "marketing", label: "Marketing" },
|
|
25202
|
+
{ id: "finance", label: "Finance" },
|
|
25203
|
+
{ id: "legal", label: "Legal" }
|
|
25204
|
+
];
|
|
25205
|
+
EXCHANGE_DISCIPLINE_IDS = EXCHANGE_DISCIPLINES.map((d) => d.id);
|
|
25206
|
+
EXCHANGE_WORK_FUNCTION_IDS = EXCHANGE_WORK_FUNCTIONS.map((w) => w.id);
|
|
25207
|
+
}
|
|
25208
|
+
});
|
|
25209
|
+
|
|
22880
25210
|
// ../../packages/@company/types/src/index.ts
|
|
22881
25211
|
var init_src5 = __esm({
|
|
22882
25212
|
"../../packages/@company/types/src/index.ts"() {
|
|
@@ -22889,6 +25219,7 @@ var init_src5 = __esm({
|
|
|
22889
25219
|
init_query_dialect();
|
|
22890
25220
|
init_report_template();
|
|
22891
25221
|
init_mining_config_fields();
|
|
25222
|
+
init_exchange();
|
|
22892
25223
|
}
|
|
22893
25224
|
});
|
|
22894
25225
|
|
|
@@ -24760,11 +27091,149 @@ var init_path_policy = __esm({
|
|
|
24760
27091
|
});
|
|
24761
27092
|
|
|
24762
27093
|
// ../../packages/@company/pii/src/deterministic.ts
|
|
27094
|
+
var RULES, DETERMINISTIC_ENTITY_TYPES;
|
|
24763
27095
|
var init_deterministic = __esm({
|
|
24764
27096
|
"../../packages/@company/pii/src/deterministic.ts"() {
|
|
24765
27097
|
"use strict";
|
|
24766
27098
|
init_spans();
|
|
24767
27099
|
init_path_policy();
|
|
27100
|
+
RULES = [
|
|
27101
|
+
{
|
|
27102
|
+
id: "PRIVATE_KEY",
|
|
27103
|
+
entityType: "PRIVATE_KEY_BLOCK",
|
|
27104
|
+
pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g,
|
|
27105
|
+
replacement: "[REDACTED:PRIVATE_KEY]"
|
|
27106
|
+
},
|
|
27107
|
+
{
|
|
27108
|
+
id: "JWT",
|
|
27109
|
+
entityType: "JWT",
|
|
27110
|
+
pattern: /eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
|
|
27111
|
+
replacement: "[REDACTED:JWT]"
|
|
27112
|
+
},
|
|
27113
|
+
{
|
|
27114
|
+
id: "AWS_ACCESS_KEY",
|
|
27115
|
+
entityType: "AWS_ACCESS_KEY",
|
|
27116
|
+
pattern: /(?:^|[^A-Z0-9])(AKIA[0-9A-Z]{16})(?:[^A-Z0-9]|$)/gm,
|
|
27117
|
+
capture: 1,
|
|
27118
|
+
replacement: "[REDACTED:AWS_ACCESS_KEY]"
|
|
27119
|
+
},
|
|
27120
|
+
{
|
|
27121
|
+
id: "AWS_SECRET_KEY",
|
|
27122
|
+
entityType: "AWS_SECRET_KEY",
|
|
27123
|
+
pattern: /(?:aws[_-]?secret[_-]?access[_-]?key|AWS_SECRET_ACCESS_KEY)\s*[=:]\s*["']?([A-Za-z0-9/+]{40})["']?/gi,
|
|
27124
|
+
capture: 1,
|
|
27125
|
+
replacement: "[REDACTED:AWS_SECRET_KEY]"
|
|
27126
|
+
},
|
|
27127
|
+
{
|
|
27128
|
+
id: "API_KEY_ASSIGNMENT",
|
|
27129
|
+
entityType: "API_KEY",
|
|
27130
|
+
pattern: /(?:api[_-]?key|apikey|api[_-]?token|access[_-]?token|auth[_-]?token|bearer[_-]?token)\s*[=:]\s*["']?([A-Za-z0-9\-_.]{20,}?)["']?(?:\s|,|;|\n|$)/gi,
|
|
27131
|
+
capture: 1,
|
|
27132
|
+
replacement: "[REDACTED:API_KEY]"
|
|
27133
|
+
},
|
|
27134
|
+
{
|
|
27135
|
+
id: "ANTHROPIC_KEY",
|
|
27136
|
+
entityType: "ANTHROPIC_API_KEY",
|
|
27137
|
+
pattern: /sk-ant-[A-Za-z0-9\-_]{32,}/g,
|
|
27138
|
+
replacement: "[REDACTED:ANTHROPIC_KEY]"
|
|
27139
|
+
},
|
|
27140
|
+
{
|
|
27141
|
+
id: "OPENAI_KEY",
|
|
27142
|
+
entityType: "OPENAI_API_KEY",
|
|
27143
|
+
pattern: /sk-(?:proj-)?[A-Za-z0-9]{20,}/g,
|
|
27144
|
+
replacement: "[REDACTED:OPENAI_KEY]"
|
|
27145
|
+
},
|
|
27146
|
+
{
|
|
27147
|
+
id: "GITHUB_TOKEN",
|
|
27148
|
+
entityType: "GITHUB_TOKEN",
|
|
27149
|
+
pattern: /(?:ghp_|ghs_|github_pat_)[A-Za-z0-9_]{36,}/g,
|
|
27150
|
+
replacement: "[REDACTED:GITHUB_TOKEN]"
|
|
27151
|
+
},
|
|
27152
|
+
{
|
|
27153
|
+
id: "SLACK_TOKEN",
|
|
27154
|
+
entityType: "SLACK_TOKEN",
|
|
27155
|
+
pattern: /xox[bpars]-[A-Za-z0-9\-]{16,}/g,
|
|
27156
|
+
replacement: "[REDACTED:SLACK_TOKEN]"
|
|
27157
|
+
},
|
|
27158
|
+
{
|
|
27159
|
+
id: "GCP_PRIVATE_KEY",
|
|
27160
|
+
entityType: "GCP_PRIVATE_KEY",
|
|
27161
|
+
pattern: /"private_key"\s*:\s*"(-----BEGIN[^"]+)"/g,
|
|
27162
|
+
capture: 1,
|
|
27163
|
+
replacement: "[REDACTED:GCP_PRIVATE_KEY]"
|
|
27164
|
+
},
|
|
27165
|
+
{
|
|
27166
|
+
id: "DB_CONNECTION_STRING",
|
|
27167
|
+
entityType: "DB_CONNECTION_STRING",
|
|
27168
|
+
// The username may be EMPTY — `redis://:password@host` is the documented
|
|
27169
|
+
// Redis form and leaked past `[^@\s]+:` because that requires one. amqp,
|
|
27170
|
+
// amqps and rediss are the same shape and were simply missing from the
|
|
27171
|
+
// scheme list.
|
|
27172
|
+
pattern: /(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|rediss?|amqps?|clickhouse|mssql|mariadb):\/\/[^@\s]*:[^@\s]+@[^\s"']+/gi,
|
|
27173
|
+
replacement: "[REDACTED:DB_CONNECTION_STRING]"
|
|
27174
|
+
},
|
|
27175
|
+
{
|
|
27176
|
+
id: "SECRET_ASSIGNMENT",
|
|
27177
|
+
entityType: "SECRET",
|
|
27178
|
+
// QUOTED form. An unquoted assignment is a separate rule below: folding
|
|
27179
|
+
// them into one alternation makes the quote handling ambiguous and risks
|
|
27180
|
+
// the closing quote landing inside the captured value.
|
|
27181
|
+
pattern: /["']?(?:password|passwd|secret|client[_-]?secret|private[_-]?key|encryption[_-]?key|api[_-]?secret|auth[_-]?token)["']?\s*[=:]\s*["']([^"'\s]{6,})["']/gi,
|
|
27182
|
+
capture: 1,
|
|
27183
|
+
replacement: "[REDACTED:SECRET]"
|
|
27184
|
+
},
|
|
27185
|
+
{
|
|
27186
|
+
// UNQUOTED assignment: `DATABASE_PASSWORD=sup3rs3cret`, `PGPASSWORD=x`,
|
|
27187
|
+
// `--password xyz`. This is how a credential appears in a shell command or
|
|
27188
|
+
// an env file, which is most of how one appears in a coding transcript.
|
|
27189
|
+
//
|
|
27190
|
+
// The key must END in a secret-ish word so `PASSWORD_RESET_URL=/x` and
|
|
27191
|
+
// `SECRET_NAME=haansi/dev/FOO` do not match: those name a secret rather
|
|
27192
|
+
// than being one, and redacting them destroys useful context. A minimum
|
|
27193
|
+
// length of 6 keeps `password=` and `secret=true` out.
|
|
27194
|
+
id: "SECRET_ASSIGNMENT_UNQUOTED",
|
|
27195
|
+
entityType: "SECRET",
|
|
27196
|
+
pattern: /(?:^|[\s;&|"'`([{,])(?:[A-Za-z0-9_.-]*(?:password|passwd|secret|token|apikey|api_key))\s*[=:]\s*([^\s"'`;&|)\]}]{6,})/gim,
|
|
27197
|
+
capture: 1,
|
|
27198
|
+
replacement: "[REDACTED:SECRET]"
|
|
27199
|
+
},
|
|
27200
|
+
{
|
|
27201
|
+
id: "BASIC_AUTH_URL",
|
|
27202
|
+
entityType: "BASIC_AUTH",
|
|
27203
|
+
pattern: /https?:\/\/([^:@\s/]+:[^@\s/]+)@/g,
|
|
27204
|
+
capture: 1,
|
|
27205
|
+
replacement: "[REDACTED:BASIC_AUTH]"
|
|
27206
|
+
},
|
|
27207
|
+
{
|
|
27208
|
+
id: "CREDIT_CARD",
|
|
27209
|
+
entityType: "CREDIT_CARD",
|
|
27210
|
+
pattern: /\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12}|(?:2131|1800|35\d{3})\d{11})\b/g,
|
|
27211
|
+
replacement: "[REDACTED:CREDIT_CARD]"
|
|
27212
|
+
},
|
|
27213
|
+
{
|
|
27214
|
+
id: "HAANSI_TOKEN",
|
|
27215
|
+
entityType: "HAANSI_CLI_TOKEN",
|
|
27216
|
+
pattern: /hsk_[a-f0-9]{32,}/g,
|
|
27217
|
+
replacement: "[REDACTED:HAANSI_TOKEN]"
|
|
27218
|
+
},
|
|
27219
|
+
{
|
|
27220
|
+
id: "BEARER_TOKEN",
|
|
27221
|
+
entityType: "BEARER_TOKEN",
|
|
27222
|
+
pattern: /Authorization:\s*Bearer\s+([A-Za-z0-9\-_.+/=]{20,})/gi,
|
|
27223
|
+
capture: 1,
|
|
27224
|
+
replacement: "[REDACTED:BEARER_TOKEN]"
|
|
27225
|
+
},
|
|
27226
|
+
{
|
|
27227
|
+
id: "HEX_SECRET",
|
|
27228
|
+
entityType: "HEX_SECRET",
|
|
27229
|
+
pattern: /(?:token|secret|key|hash|salt)\s*[=:]\s*["']?([a-f0-9]{32,})["']?/gi,
|
|
27230
|
+
capture: 1,
|
|
27231
|
+
replacement: "[REDACTED:HEX_SECRET]"
|
|
27232
|
+
}
|
|
27233
|
+
];
|
|
27234
|
+
DETERMINISTIC_ENTITY_TYPES = new Set(
|
|
27235
|
+
RULES.map((rule) => rule.entityType)
|
|
27236
|
+
);
|
|
24768
27237
|
}
|
|
24769
27238
|
});
|
|
24770
27239
|
|
|
@@ -24812,264 +27281,6 @@ var init_src7 = __esm({
|
|
|
24812
27281
|
}
|
|
24813
27282
|
});
|
|
24814
27283
|
|
|
24815
|
-
// ../service-capture/common/agents/types.ts
|
|
24816
|
-
var AGENT_IDS;
|
|
24817
|
-
var init_types2 = __esm({
|
|
24818
|
-
"../service-capture/common/agents/types.ts"() {
|
|
24819
|
-
"use strict";
|
|
24820
|
-
AGENT_IDS = ["claude-code", "codex"];
|
|
24821
|
-
}
|
|
24822
|
-
});
|
|
24823
|
-
|
|
24824
|
-
// ../service-capture/common/agents/claude-code.ts
|
|
24825
|
-
function projectsDir() {
|
|
24826
|
-
return (0, import_path.join)((0, import_os.homedir)(), ".claude", "projects");
|
|
24827
|
-
}
|
|
24828
|
-
function extractMeta(filePath) {
|
|
24829
|
-
const result = {
|
|
24830
|
-
cwd: null,
|
|
24831
|
-
timestamp: null,
|
|
24832
|
-
gitBranch: null
|
|
24833
|
-
};
|
|
24834
|
-
try {
|
|
24835
|
-
const lines = (0, import_fs.readFileSync)(filePath, "utf-8").split("\n").filter((l) => l.trim()).slice(0, 50);
|
|
24836
|
-
for (const line of lines) {
|
|
24837
|
-
try {
|
|
24838
|
-
const entry2 = JSON.parse(line);
|
|
24839
|
-
if (!result.cwd && typeof entry2.cwd === "string")
|
|
24840
|
-
result.cwd = entry2.cwd;
|
|
24841
|
-
if (!result.timestamp && typeof entry2.timestamp === "string")
|
|
24842
|
-
result.timestamp = entry2.timestamp;
|
|
24843
|
-
if (!result.gitBranch && typeof entry2.gitBranch === "string")
|
|
24844
|
-
result.gitBranch = entry2.gitBranch;
|
|
24845
|
-
if (result.cwd && result.timestamp && result.gitBranch) break;
|
|
24846
|
-
} catch {
|
|
24847
|
-
}
|
|
24848
|
-
}
|
|
24849
|
-
} catch {
|
|
24850
|
-
}
|
|
24851
|
-
return result;
|
|
24852
|
-
}
|
|
24853
|
-
var import_fs, import_path, import_os, claudeCodeAgent;
|
|
24854
|
-
var init_claude_code = __esm({
|
|
24855
|
-
"../service-capture/common/agents/claude-code.ts"() {
|
|
24856
|
-
"use strict";
|
|
24857
|
-
import_fs = require("fs");
|
|
24858
|
-
import_path = require("path");
|
|
24859
|
-
import_os = require("os");
|
|
24860
|
-
claudeCodeAgent = {
|
|
24861
|
-
id: "claude-code",
|
|
24862
|
-
displayName: "Claude Code",
|
|
24863
|
-
sessionsRoot() {
|
|
24864
|
-
return projectsDir();
|
|
24865
|
-
},
|
|
24866
|
-
isInstalled() {
|
|
24867
|
-
return (0, import_fs.existsSync)(projectsDir());
|
|
24868
|
-
},
|
|
24869
|
-
discoverSessions() {
|
|
24870
|
-
const root = projectsDir();
|
|
24871
|
-
if (!(0, import_fs.existsSync)(root)) return [];
|
|
24872
|
-
const sessions = [];
|
|
24873
|
-
const projectDirs = (0, import_fs.readdirSync)(root).map((name) => (0, import_path.join)(root, name)).filter((p) => {
|
|
24874
|
-
try {
|
|
24875
|
-
return (0, import_fs.statSync)(p).isDirectory();
|
|
24876
|
-
} catch {
|
|
24877
|
-
return false;
|
|
24878
|
-
}
|
|
24879
|
-
});
|
|
24880
|
-
for (const dir of projectDirs) {
|
|
24881
|
-
let files;
|
|
24882
|
-
try {
|
|
24883
|
-
files = (0, import_fs.readdirSync)(dir).filter((f) => f.endsWith(".jsonl"));
|
|
24884
|
-
} catch {
|
|
24885
|
-
continue;
|
|
24886
|
-
}
|
|
24887
|
-
for (const file2 of files) {
|
|
24888
|
-
const filePath = (0, import_path.join)(dir, file2);
|
|
24889
|
-
const sessionId = (0, import_path.basename)(file2, ".jsonl");
|
|
24890
|
-
try {
|
|
24891
|
-
const stat = (0, import_fs.statSync)(filePath);
|
|
24892
|
-
const content = (0, import_fs.readFileSync)(filePath, "utf-8");
|
|
24893
|
-
const lineCount = content.split("\n").filter((l) => l.trim()).length;
|
|
24894
|
-
const meta3 = extractMeta(filePath);
|
|
24895
|
-
sessions.push({
|
|
24896
|
-
sessionId,
|
|
24897
|
-
filePath,
|
|
24898
|
-
lineCount,
|
|
24899
|
-
sizeBytes: stat.size,
|
|
24900
|
-
...meta3
|
|
24901
|
-
});
|
|
24902
|
-
} catch {
|
|
24903
|
-
}
|
|
24904
|
-
}
|
|
24905
|
-
}
|
|
24906
|
-
return sessions;
|
|
24907
|
-
}
|
|
24908
|
-
};
|
|
24909
|
-
}
|
|
24910
|
-
});
|
|
24911
|
-
|
|
24912
|
-
// ../service-capture/common/agents/codex.ts
|
|
24913
|
-
function codexHome() {
|
|
24914
|
-
return process.env.CODEX_HOME || (0, import_path2.join)((0, import_os2.homedir)(), ".codex");
|
|
24915
|
-
}
|
|
24916
|
-
function sessionsDir() {
|
|
24917
|
-
return (0, import_path2.join)(codexHome(), "sessions");
|
|
24918
|
-
}
|
|
24919
|
-
function walkRollouts(dir, out) {
|
|
24920
|
-
let entries;
|
|
24921
|
-
try {
|
|
24922
|
-
entries = (0, import_fs2.readdirSync)(dir);
|
|
24923
|
-
} catch {
|
|
24924
|
-
return;
|
|
24925
|
-
}
|
|
24926
|
-
for (const name of entries) {
|
|
24927
|
-
const full = (0, import_path2.join)(dir, name);
|
|
24928
|
-
let isDir = false;
|
|
24929
|
-
try {
|
|
24930
|
-
isDir = (0, import_fs2.statSync)(full).isDirectory();
|
|
24931
|
-
} catch {
|
|
24932
|
-
continue;
|
|
24933
|
-
}
|
|
24934
|
-
if (isDir) {
|
|
24935
|
-
walkRollouts(full, out);
|
|
24936
|
-
} else if (name.startsWith("rollout-") && name.endsWith(".jsonl")) {
|
|
24937
|
-
out.push(full);
|
|
24938
|
-
}
|
|
24939
|
-
}
|
|
24940
|
-
}
|
|
24941
|
-
function readRolloutMeta(filePath, content) {
|
|
24942
|
-
const fileName = (0, import_path2.basename)(filePath, ".jsonl");
|
|
24943
|
-
const fromName = UUID_RE.exec(fileName)?.[1];
|
|
24944
|
-
let cwd = null;
|
|
24945
|
-
let timestamp = null;
|
|
24946
|
-
let gitBranch = null;
|
|
24947
|
-
let gitRemoteUrl = null;
|
|
24948
|
-
let gitCommit = null;
|
|
24949
|
-
let metaId = null;
|
|
24950
|
-
const lines = content.split("\n").filter((l) => l.trim()).slice(0, 50);
|
|
24951
|
-
for (const line of lines) {
|
|
24952
|
-
try {
|
|
24953
|
-
const entry2 = JSON.parse(line);
|
|
24954
|
-
if (!timestamp && typeof entry2.timestamp === "string") {
|
|
24955
|
-
timestamp = entry2.timestamp;
|
|
24956
|
-
}
|
|
24957
|
-
const p = entry2.payload ?? {};
|
|
24958
|
-
if (entry2.type === "session_meta") {
|
|
24959
|
-
if (typeof p.cwd === "string") cwd = p.cwd;
|
|
24960
|
-
if (typeof p.id === "string") metaId = p.id;
|
|
24961
|
-
const g = p.git ?? {};
|
|
24962
|
-
const branch = g.branch ?? p.git_branch;
|
|
24963
|
-
if (typeof branch === "string") gitBranch = branch;
|
|
24964
|
-
const remote = g.repository_url ?? g.remote_url;
|
|
24965
|
-
if (typeof remote === "string") gitRemoteUrl = remote;
|
|
24966
|
-
const commit = g.commit_hash ?? g.commit;
|
|
24967
|
-
if (typeof commit === "string") gitCommit = commit;
|
|
24968
|
-
}
|
|
24969
|
-
if (cwd && gitBranch && gitCommit && timestamp && metaId) break;
|
|
24970
|
-
} catch {
|
|
24971
|
-
}
|
|
24972
|
-
}
|
|
24973
|
-
return {
|
|
24974
|
-
sessionId: fromName ?? metaId ?? fileName,
|
|
24975
|
-
cwd,
|
|
24976
|
-
timestamp,
|
|
24977
|
-
gitBranch,
|
|
24978
|
-
gitRemoteUrl,
|
|
24979
|
-
gitCommit
|
|
24980
|
-
};
|
|
24981
|
-
}
|
|
24982
|
-
var import_fs2, import_path2, import_os2, UUID_RE, codexAgent;
|
|
24983
|
-
var init_codex = __esm({
|
|
24984
|
-
"../service-capture/common/agents/codex.ts"() {
|
|
24985
|
-
"use strict";
|
|
24986
|
-
import_fs2 = require("fs");
|
|
24987
|
-
import_path2 = require("path");
|
|
24988
|
-
import_os2 = require("os");
|
|
24989
|
-
UUID_RE = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i;
|
|
24990
|
-
codexAgent = {
|
|
24991
|
-
id: "codex",
|
|
24992
|
-
displayName: "OpenAI Codex",
|
|
24993
|
-
sessionsRoot() {
|
|
24994
|
-
return sessionsDir();
|
|
24995
|
-
},
|
|
24996
|
-
isInstalled() {
|
|
24997
|
-
return (0, import_fs2.existsSync)(codexHome());
|
|
24998
|
-
},
|
|
24999
|
-
discoverSessions() {
|
|
25000
|
-
const root = sessionsDir();
|
|
25001
|
-
if (!(0, import_fs2.existsSync)(root)) return [];
|
|
25002
|
-
const files = [];
|
|
25003
|
-
walkRollouts(root, files);
|
|
25004
|
-
const sessions = [];
|
|
25005
|
-
for (const filePath of files) {
|
|
25006
|
-
try {
|
|
25007
|
-
const stat = (0, import_fs2.statSync)(filePath);
|
|
25008
|
-
const content = (0, import_fs2.readFileSync)(filePath, "utf-8");
|
|
25009
|
-
const lineCount = content.split("\n").filter((l) => l.trim()).length;
|
|
25010
|
-
const meta3 = readRolloutMeta(filePath, content);
|
|
25011
|
-
sessions.push({
|
|
25012
|
-
sessionId: meta3.sessionId,
|
|
25013
|
-
filePath,
|
|
25014
|
-
lineCount,
|
|
25015
|
-
sizeBytes: stat.size,
|
|
25016
|
-
cwd: meta3.cwd,
|
|
25017
|
-
timestamp: meta3.timestamp,
|
|
25018
|
-
gitBranch: meta3.gitBranch,
|
|
25019
|
-
gitRemoteUrl: meta3.gitRemoteUrl,
|
|
25020
|
-
gitCommit: meta3.gitCommit
|
|
25021
|
-
});
|
|
25022
|
-
} catch {
|
|
25023
|
-
}
|
|
25024
|
-
}
|
|
25025
|
-
return sessions;
|
|
25026
|
-
}
|
|
25027
|
-
};
|
|
25028
|
-
}
|
|
25029
|
-
});
|
|
25030
|
-
|
|
25031
|
-
// ../service-capture/common/agents/registry.ts
|
|
25032
|
-
function getAgents() {
|
|
25033
|
-
return AGENTS;
|
|
25034
|
-
}
|
|
25035
|
-
function getAgent(id) {
|
|
25036
|
-
const agent = AGENTS.find((a) => a.id === id);
|
|
25037
|
-
if (!agent) throw new Error(`Unknown agent: ${id}`);
|
|
25038
|
-
return agent;
|
|
25039
|
-
}
|
|
25040
|
-
function detectInstalled() {
|
|
25041
|
-
return AGENTS.filter((a) => a.isInstalled());
|
|
25042
|
-
}
|
|
25043
|
-
var AGENTS;
|
|
25044
|
-
var init_registry = __esm({
|
|
25045
|
-
"../service-capture/common/agents/registry.ts"() {
|
|
25046
|
-
"use strict";
|
|
25047
|
-
init_claude_code();
|
|
25048
|
-
init_codex();
|
|
25049
|
-
AGENTS = [claudeCodeAgent, codexAgent];
|
|
25050
|
-
}
|
|
25051
|
-
});
|
|
25052
|
-
|
|
25053
|
-
// ../service-capture/common/agents/index.ts
|
|
25054
|
-
var agents_exports = {};
|
|
25055
|
-
__export(agents_exports, {
|
|
25056
|
-
AGENT_IDS: () => AGENT_IDS,
|
|
25057
|
-
claudeCodeAgent: () => claudeCodeAgent,
|
|
25058
|
-
codexAgent: () => codexAgent,
|
|
25059
|
-
detectInstalled: () => detectInstalled,
|
|
25060
|
-
getAgent: () => getAgent,
|
|
25061
|
-
getAgents: () => getAgents
|
|
25062
|
-
});
|
|
25063
|
-
var init_agents = __esm({
|
|
25064
|
-
"../service-capture/common/agents/index.ts"() {
|
|
25065
|
-
"use strict";
|
|
25066
|
-
init_types2();
|
|
25067
|
-
init_claude_code();
|
|
25068
|
-
init_codex();
|
|
25069
|
-
init_registry();
|
|
25070
|
-
}
|
|
25071
|
-
});
|
|
25072
|
-
|
|
25073
27284
|
// ../service-capture/agent-sessions/collector.ts
|
|
25074
27285
|
var collector_exports = {};
|
|
25075
27286
|
__export(collector_exports, {
|
|
@@ -25596,113 +27807,6 @@ var init_collector = __esm({
|
|
|
25596
27807
|
}
|
|
25597
27808
|
});
|
|
25598
27809
|
|
|
25599
|
-
// src/lib/agent-selection.ts
|
|
25600
|
-
var agent_selection_exports = {};
|
|
25601
|
-
__export(agent_selection_exports, {
|
|
25602
|
-
parseAgentFlag: () => parseAgentFlag,
|
|
25603
|
-
promptForAgents: () => promptForAgents,
|
|
25604
|
-
readSavedAgents: () => readSavedAgents,
|
|
25605
|
-
resolveAgentSelection: () => resolveAgentSelection,
|
|
25606
|
-
saveAgents: () => saveAgents
|
|
25607
|
-
});
|
|
25608
|
-
function parseAgentFlag(value) {
|
|
25609
|
-
if (!value) return null;
|
|
25610
|
-
const normalized = value.trim().toLowerCase();
|
|
25611
|
-
if (normalized === "both" || normalized === "all") return [...VALID];
|
|
25612
|
-
const out = [];
|
|
25613
|
-
for (const part of normalized.split(",").map((s) => s.trim())) {
|
|
25614
|
-
if (part === "claude" || part === "claude-code") {
|
|
25615
|
-
if (!out.includes("claude-code")) out.push("claude-code");
|
|
25616
|
-
} else if (part === "codex") {
|
|
25617
|
-
if (!out.includes("codex")) out.push("codex");
|
|
25618
|
-
}
|
|
25619
|
-
}
|
|
25620
|
-
return out.length > 0 ? out : null;
|
|
25621
|
-
}
|
|
25622
|
-
function readSavedAgents() {
|
|
25623
|
-
if (!(0, import_node_fs4.existsSync)(CONFIG_FILE)) return null;
|
|
25624
|
-
try {
|
|
25625
|
-
const cfg = JSON.parse((0, import_node_fs4.readFileSync)(CONFIG_FILE, "utf-8"));
|
|
25626
|
-
if (Array.isArray(cfg.agents)) {
|
|
25627
|
-
const agents = cfg.agents.filter(
|
|
25628
|
-
(a) => VALID.includes(a)
|
|
25629
|
-
);
|
|
25630
|
-
return agents.length > 0 ? agents : null;
|
|
25631
|
-
}
|
|
25632
|
-
} catch {
|
|
25633
|
-
}
|
|
25634
|
-
return null;
|
|
25635
|
-
}
|
|
25636
|
-
function saveAgents(agents) {
|
|
25637
|
-
let cfg = {};
|
|
25638
|
-
if ((0, import_node_fs4.existsSync)(CONFIG_FILE)) {
|
|
25639
|
-
try {
|
|
25640
|
-
cfg = JSON.parse((0, import_node_fs4.readFileSync)(CONFIG_FILE, "utf-8"));
|
|
25641
|
-
} catch {
|
|
25642
|
-
cfg = {};
|
|
25643
|
-
}
|
|
25644
|
-
}
|
|
25645
|
-
cfg.agents = agents;
|
|
25646
|
-
(0, import_node_fs4.mkdirSync)(HAANSI_DIR, { recursive: true });
|
|
25647
|
-
(0, import_node_fs4.writeFileSync)(CONFIG_FILE, JSON.stringify(cfg, null, 2) + "\n", {
|
|
25648
|
-
encoding: "utf-8",
|
|
25649
|
-
mode: 384
|
|
25650
|
-
});
|
|
25651
|
-
}
|
|
25652
|
-
function resolveAgentSelection(flagValue) {
|
|
25653
|
-
const fromFlag = parseAgentFlag(flagValue);
|
|
25654
|
-
if (fromFlag) return { agents: fromFlag, source: "flag" };
|
|
25655
|
-
const saved = readSavedAgents();
|
|
25656
|
-
if (saved) return { agents: saved, source: "config" };
|
|
25657
|
-
const detected = detectInstalled().map((a) => a.id);
|
|
25658
|
-
if (detected.length > 0) return { agents: detected, source: "detected" };
|
|
25659
|
-
return { agents: ["claude-code"], source: "default" };
|
|
25660
|
-
}
|
|
25661
|
-
async function promptForAgents() {
|
|
25662
|
-
const installed = new Set(detectInstalled().map((a) => a.id));
|
|
25663
|
-
const agents = getAgents();
|
|
25664
|
-
console.log("\nWhich coding agents should Haansi connect?\n");
|
|
25665
|
-
agents.forEach((a, i) => {
|
|
25666
|
-
const mark = installed.has(a.id) ? "installed" : "not detected";
|
|
25667
|
-
console.log(` ${i + 1}) ${a.displayName} (${mark})`);
|
|
25668
|
-
});
|
|
25669
|
-
const rl = readline2.createInterface({
|
|
25670
|
-
input: process.stdin,
|
|
25671
|
-
output: process.stdout
|
|
25672
|
-
});
|
|
25673
|
-
const answer = await new Promise((resolve5) => {
|
|
25674
|
-
rl.question(
|
|
25675
|
-
"\nEnter numbers (comma-separated), or press Enter for all installed: ",
|
|
25676
|
-
(a) => {
|
|
25677
|
-
rl.close();
|
|
25678
|
-
resolve5(a.trim());
|
|
25679
|
-
}
|
|
25680
|
-
);
|
|
25681
|
-
});
|
|
25682
|
-
if (!answer) {
|
|
25683
|
-
const detected = agents.filter((a) => installed.has(a.id)).map((a) => a.id);
|
|
25684
|
-
return detected.length > 0 ? detected : [...VALID];
|
|
25685
|
-
}
|
|
25686
|
-
const picked = [];
|
|
25687
|
-
for (const token of answer.split(",").map((s) => s.trim())) {
|
|
25688
|
-
const idx = parseInt(token, 10) - 1;
|
|
25689
|
-
const agent = agents[idx];
|
|
25690
|
-
if (agent && !picked.includes(agent.id)) picked.push(agent.id);
|
|
25691
|
-
}
|
|
25692
|
-
return picked.length > 0 ? picked : [...VALID];
|
|
25693
|
-
}
|
|
25694
|
-
var import_node_fs4, readline2, VALID;
|
|
25695
|
-
var init_agent_selection = __esm({
|
|
25696
|
-
"src/lib/agent-selection.ts"() {
|
|
25697
|
-
"use strict";
|
|
25698
|
-
import_node_fs4 = require("node:fs");
|
|
25699
|
-
readline2 = __toESM(require("node:readline"));
|
|
25700
|
-
init_agents();
|
|
25701
|
-
init_paths();
|
|
25702
|
-
VALID = ["claude-code", "codex"];
|
|
25703
|
-
}
|
|
25704
|
-
});
|
|
25705
|
-
|
|
25706
27810
|
// ../../node_modules/node-cron/src/task.js
|
|
25707
27811
|
var require_task = __commonJS({
|
|
25708
27812
|
"../../node_modules/node-cron/src/task.js"(exports2, module2) {
|
|
@@ -43651,11 +45755,11 @@ var require_util2 = __commonJS({
|
|
|
43651
45755
|
return false;
|
|
43652
45756
|
}
|
|
43653
45757
|
exports2.schemaHasRules = schemaHasRules;
|
|
43654
|
-
function schemaHasRulesButRef(schema,
|
|
45758
|
+
function schemaHasRulesButRef(schema, RULES2) {
|
|
43655
45759
|
if (typeof schema == "boolean")
|
|
43656
45760
|
return !schema;
|
|
43657
45761
|
for (const key in schema)
|
|
43658
|
-
if (key !== "$ref" &&
|
|
45762
|
+
if (key !== "$ref" && RULES2.all[key])
|
|
43659
45763
|
return true;
|
|
43660
45764
|
return false;
|
|
43661
45765
|
}
|
|
@@ -45049,17 +47153,17 @@ var require_validate = __commonJS({
|
|
|
45049
47153
|
}
|
|
45050
47154
|
function schemaKeywords(it, types2, typeErrors, errsCount) {
|
|
45051
47155
|
const { gen, schema, data, allErrors, opts, self } = it;
|
|
45052
|
-
const { RULES } = self;
|
|
45053
|
-
if (schema.$ref && (opts.ignoreKeywordsWithRef || !(0, util_1.schemaHasRulesButRef)(schema,
|
|
45054
|
-
gen.block(() => keywordCode(it, "$ref",
|
|
47156
|
+
const { RULES: RULES2 } = self;
|
|
47157
|
+
if (schema.$ref && (opts.ignoreKeywordsWithRef || !(0, util_1.schemaHasRulesButRef)(schema, RULES2))) {
|
|
47158
|
+
gen.block(() => keywordCode(it, "$ref", RULES2.all.$ref.definition));
|
|
45055
47159
|
return;
|
|
45056
47160
|
}
|
|
45057
47161
|
if (!opts.jtd)
|
|
45058
47162
|
checkStrictTypes(it, types2);
|
|
45059
47163
|
gen.block(() => {
|
|
45060
|
-
for (const group of
|
|
47164
|
+
for (const group of RULES2.rules)
|
|
45061
47165
|
groupKeywords(group);
|
|
45062
|
-
groupKeywords(
|
|
47166
|
+
groupKeywords(RULES2.post);
|
|
45063
47167
|
});
|
|
45064
47168
|
function groupKeywords(group) {
|
|
45065
47169
|
if (!(0, applicability_1.shouldUseGroup)(schema, group))
|
|
@@ -46761,10 +48865,10 @@ var require_core = __commonJS({
|
|
|
46761
48865
|
}
|
|
46762
48866
|
// Remove keyword
|
|
46763
48867
|
removeKeyword(keyword) {
|
|
46764
|
-
const { RULES } = this;
|
|
46765
|
-
delete
|
|
46766
|
-
delete
|
|
46767
|
-
for (const group of
|
|
48868
|
+
const { RULES: RULES2 } = this;
|
|
48869
|
+
delete RULES2.keywords[keyword];
|
|
48870
|
+
delete RULES2.all[keyword];
|
|
48871
|
+
for (const group of RULES2.rules) {
|
|
46768
48872
|
const i = group.rules.findIndex((rule) => rule.keyword === keyword);
|
|
46769
48873
|
if (i >= 0)
|
|
46770
48874
|
group.rules.splice(i, 1);
|
|
@@ -46932,9 +49036,9 @@ var require_core = __commonJS({
|
|
|
46932
49036
|
}
|
|
46933
49037
|
var KEYWORD_NAME = /^[a-z_$][a-z0-9_$:-]*$/i;
|
|
46934
49038
|
function checkKeyword(keyword, def) {
|
|
46935
|
-
const { RULES } = this;
|
|
49039
|
+
const { RULES: RULES2 } = this;
|
|
46936
49040
|
(0, util_1.eachItem)(keyword, (kwd) => {
|
|
46937
|
-
if (
|
|
49041
|
+
if (RULES2.keywords[kwd])
|
|
46938
49042
|
throw new Error(`Keyword ${kwd} is already defined`);
|
|
46939
49043
|
if (!KEYWORD_NAME.test(kwd))
|
|
46940
49044
|
throw new Error(`Keyword ${kwd} has invalid name`);
|
|
@@ -46950,13 +49054,13 @@ var require_core = __commonJS({
|
|
|
46950
49054
|
const post = definition === null || definition === void 0 ? void 0 : definition.post;
|
|
46951
49055
|
if (dataType && post)
|
|
46952
49056
|
throw new Error('keyword with "post" flag cannot have "type"');
|
|
46953
|
-
const { RULES } = this;
|
|
46954
|
-
let ruleGroup = post ?
|
|
49057
|
+
const { RULES: RULES2 } = this;
|
|
49058
|
+
let ruleGroup = post ? RULES2.post : RULES2.rules.find(({ type: t }) => t === dataType);
|
|
46955
49059
|
if (!ruleGroup) {
|
|
46956
49060
|
ruleGroup = { type: dataType, rules: [] };
|
|
46957
|
-
|
|
49061
|
+
RULES2.rules.push(ruleGroup);
|
|
46958
49062
|
}
|
|
46959
|
-
|
|
49063
|
+
RULES2.keywords[keyword] = true;
|
|
46960
49064
|
if (!definition)
|
|
46961
49065
|
return;
|
|
46962
49066
|
const rule = {
|
|
@@ -46971,7 +49075,7 @@ var require_core = __commonJS({
|
|
|
46971
49075
|
addBeforeRule.call(this, ruleGroup, rule, definition.before);
|
|
46972
49076
|
else
|
|
46973
49077
|
ruleGroup.rules.push(rule);
|
|
46974
|
-
|
|
49078
|
+
RULES2.all[keyword] = rule;
|
|
46975
49079
|
(_a3 = definition.implements) === null || _a3 === void 0 ? void 0 : _a3.forEach((kwd) => this.addKeyword(kwd));
|
|
46976
49080
|
}
|
|
46977
49081
|
function addBeforeRule(ruleGroup, rule, before) {
|
|
@@ -50306,11 +52410,11 @@ var require_util3 = __commonJS({
|
|
|
50306
52410
|
return false;
|
|
50307
52411
|
}
|
|
50308
52412
|
exports2.schemaHasRules = schemaHasRules;
|
|
50309
|
-
function schemaHasRulesButRef(schema,
|
|
52413
|
+
function schemaHasRulesButRef(schema, RULES2) {
|
|
50310
52414
|
if (typeof schema == "boolean")
|
|
50311
52415
|
return !schema;
|
|
50312
52416
|
for (const key in schema)
|
|
50313
|
-
if (key !== "$ref" &&
|
|
52417
|
+
if (key !== "$ref" && RULES2.all[key])
|
|
50314
52418
|
return true;
|
|
50315
52419
|
return false;
|
|
50316
52420
|
}
|
|
@@ -51669,17 +53773,17 @@ var require_validate2 = __commonJS({
|
|
|
51669
53773
|
}
|
|
51670
53774
|
function schemaKeywords(it, types2, typeErrors, errsCount) {
|
|
51671
53775
|
const { gen, schema, data, allErrors, opts, self } = it;
|
|
51672
|
-
const { RULES } = self;
|
|
51673
|
-
if (schema.$ref && (opts.ignoreKeywordsWithRef || !(0, util_1.schemaHasRulesButRef)(schema,
|
|
51674
|
-
gen.block(() => keywordCode(it, "$ref",
|
|
53776
|
+
const { RULES: RULES2 } = self;
|
|
53777
|
+
if (schema.$ref && (opts.ignoreKeywordsWithRef || !(0, util_1.schemaHasRulesButRef)(schema, RULES2))) {
|
|
53778
|
+
gen.block(() => keywordCode(it, "$ref", RULES2.all.$ref.definition));
|
|
51675
53779
|
return;
|
|
51676
53780
|
}
|
|
51677
53781
|
if (!opts.jtd)
|
|
51678
53782
|
checkStrictTypes(it, types2);
|
|
51679
53783
|
gen.block(() => {
|
|
51680
|
-
for (const group of
|
|
53784
|
+
for (const group of RULES2.rules)
|
|
51681
53785
|
groupKeywords(group);
|
|
51682
|
-
groupKeywords(
|
|
53786
|
+
groupKeywords(RULES2.post);
|
|
51683
53787
|
});
|
|
51684
53788
|
function groupKeywords(group) {
|
|
51685
53789
|
if (!(0, applicability_1.shouldUseGroup)(schema, group))
|
|
@@ -52659,10 +54763,10 @@ var require_core3 = __commonJS({
|
|
|
52659
54763
|
}
|
|
52660
54764
|
// Remove keyword
|
|
52661
54765
|
removeKeyword(keyword) {
|
|
52662
|
-
const { RULES } = this;
|
|
52663
|
-
delete
|
|
52664
|
-
delete
|
|
52665
|
-
for (const group of
|
|
54766
|
+
const { RULES: RULES2 } = this;
|
|
54767
|
+
delete RULES2.keywords[keyword];
|
|
54768
|
+
delete RULES2.all[keyword];
|
|
54769
|
+
for (const group of RULES2.rules) {
|
|
52666
54770
|
const i = group.rules.findIndex((rule) => rule.keyword === keyword);
|
|
52667
54771
|
if (i >= 0)
|
|
52668
54772
|
group.rules.splice(i, 1);
|
|
@@ -52830,9 +54934,9 @@ var require_core3 = __commonJS({
|
|
|
52830
54934
|
}
|
|
52831
54935
|
var KEYWORD_NAME = /^[a-z_$][a-z0-9_$:-]*$/i;
|
|
52832
54936
|
function checkKeyword(keyword, def) {
|
|
52833
|
-
const { RULES } = this;
|
|
54937
|
+
const { RULES: RULES2 } = this;
|
|
52834
54938
|
(0, util_1.eachItem)(keyword, (kwd) => {
|
|
52835
|
-
if (
|
|
54939
|
+
if (RULES2.keywords[kwd])
|
|
52836
54940
|
throw new Error(`Keyword ${kwd} is already defined`);
|
|
52837
54941
|
if (!KEYWORD_NAME.test(kwd))
|
|
52838
54942
|
throw new Error(`Keyword ${kwd} has invalid name`);
|
|
@@ -52848,13 +54952,13 @@ var require_core3 = __commonJS({
|
|
|
52848
54952
|
const post = definition === null || definition === void 0 ? void 0 : definition.post;
|
|
52849
54953
|
if (dataType && post)
|
|
52850
54954
|
throw new Error('keyword with "post" flag cannot have "type"');
|
|
52851
|
-
const { RULES } = this;
|
|
52852
|
-
let ruleGroup = post ?
|
|
54955
|
+
const { RULES: RULES2 } = this;
|
|
54956
|
+
let ruleGroup = post ? RULES2.post : RULES2.rules.find(({ type: t }) => t === dataType);
|
|
52853
54957
|
if (!ruleGroup) {
|
|
52854
54958
|
ruleGroup = { type: dataType, rules: [] };
|
|
52855
|
-
|
|
54959
|
+
RULES2.rules.push(ruleGroup);
|
|
52856
54960
|
}
|
|
52857
|
-
|
|
54961
|
+
RULES2.keywords[keyword] = true;
|
|
52858
54962
|
if (!definition)
|
|
52859
54963
|
return;
|
|
52860
54964
|
const rule = {
|
|
@@ -52869,7 +54973,7 @@ var require_core3 = __commonJS({
|
|
|
52869
54973
|
addBeforeRule.call(this, ruleGroup, rule, definition.before);
|
|
52870
54974
|
else
|
|
52871
54975
|
ruleGroup.rules.push(rule);
|
|
52872
|
-
|
|
54976
|
+
RULES2.all[keyword] = rule;
|
|
52873
54977
|
(_a3 = definition.implements) === null || _a3 === void 0 ? void 0 : _a3.forEach((kwd) => this.addKeyword(kwd));
|
|
52874
54978
|
}
|
|
52875
54979
|
function addBeforeRule(ruleGroup, rule, before) {
|
|
@@ -81109,222 +83213,6 @@ var init_mcp_server2 = __esm({
|
|
|
81109
83213
|
}
|
|
81110
83214
|
});
|
|
81111
83215
|
|
|
81112
|
-
// src/lib/codex-config.ts
|
|
81113
|
-
function tomlString(value) {
|
|
81114
|
-
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
81115
|
-
return `"${escaped}"`;
|
|
81116
|
-
}
|
|
81117
|
-
function tomlStringArray(values) {
|
|
81118
|
-
return `[${values.map(tomlString).join(", ")}]`;
|
|
81119
|
-
}
|
|
81120
|
-
function tomlInlineTable(entries) {
|
|
81121
|
-
const parts = Object.entries(entries).map(
|
|
81122
|
-
([k, v]) => `${k} = ${tomlString(v)}`
|
|
81123
|
-
);
|
|
81124
|
-
return `{ ${parts.join(", ")} }`;
|
|
81125
|
-
}
|
|
81126
|
-
function upsertTomlTable(content, tablePath, entries) {
|
|
81127
|
-
const bodyLines = Object.entries(entries).map(([k, v]) => `${k} = ${v}`);
|
|
81128
|
-
const block = [`[${tablePath}]`, ...bodyLines].join("\n");
|
|
81129
|
-
const lines = content.split("\n");
|
|
81130
|
-
const headerRe = new RegExp(
|
|
81131
|
-
`^\\s*\\[\\s*${tablePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\]\\s*$`
|
|
81132
|
-
);
|
|
81133
|
-
const startIdx = lines.findIndex((l) => headerRe.test(l));
|
|
81134
|
-
if (startIdx === -1) {
|
|
81135
|
-
const trimmed = content.replace(/\s*$/, "");
|
|
81136
|
-
return trimmed.length > 0 ? `${trimmed}
|
|
81137
|
-
|
|
81138
|
-
${block}
|
|
81139
|
-
` : `${block}
|
|
81140
|
-
`;
|
|
81141
|
-
}
|
|
81142
|
-
let endIdx = lines.length;
|
|
81143
|
-
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
81144
|
-
if (/^\s*\[/.test(lines[i])) {
|
|
81145
|
-
endIdx = i;
|
|
81146
|
-
break;
|
|
81147
|
-
}
|
|
81148
|
-
}
|
|
81149
|
-
const before = lines.slice(0, startIdx);
|
|
81150
|
-
const after = lines.slice(endIdx);
|
|
81151
|
-
const rebuilt = [...before, ...block.split("\n"), ...after].join("\n");
|
|
81152
|
-
return rebuilt.replace(/\n{3,}/g, "\n\n");
|
|
81153
|
-
}
|
|
81154
|
-
var init_codex_config = __esm({
|
|
81155
|
-
"src/lib/codex-config.ts"() {
|
|
81156
|
-
"use strict";
|
|
81157
|
-
}
|
|
81158
|
-
});
|
|
81159
|
-
|
|
81160
|
-
// src/commands/setup-mcp.ts
|
|
81161
|
-
var setup_mcp_exports = {};
|
|
81162
|
-
__export(setup_mcp_exports, {
|
|
81163
|
-
HTTP_SERVERS: () => HTTP_SERVERS,
|
|
81164
|
-
parseTarget: () => parseTarget,
|
|
81165
|
-
setupMcp: () => setupMcp
|
|
81166
|
-
});
|
|
81167
|
-
function parseTarget(arg) {
|
|
81168
|
-
if (!arg || arg === "all") return "all";
|
|
81169
|
-
if (arg === "solutions" || arg === "exchange" || arg === "digests" || arg === "onboarding" || arg === "skills")
|
|
81170
|
-
return arg;
|
|
81171
|
-
throw new Error(
|
|
81172
|
-
`Unknown target: ${arg}. Use one of: solutions, exchange, digests, onboarding, skills, all (default).`
|
|
81173
|
-
);
|
|
81174
|
-
}
|
|
81175
|
-
function httpServerUrl(path2) {
|
|
81176
|
-
return `${apiUrl().replace(/\/+$/, "")}${path2}`;
|
|
81177
|
-
}
|
|
81178
|
-
function setupClaudeMcp(target) {
|
|
81179
|
-
let config6 = {};
|
|
81180
|
-
if ((0, import_node_fs5.existsSync)(CLAUDE_JSON)) {
|
|
81181
|
-
try {
|
|
81182
|
-
config6 = JSON.parse((0, import_node_fs5.readFileSync)(CLAUDE_JSON, "utf-8"));
|
|
81183
|
-
} catch {
|
|
81184
|
-
console.error(
|
|
81185
|
-
`Warning: Could not parse ${CLAUDE_JSON} \u2014 will overwrite with new config.`
|
|
81186
|
-
);
|
|
81187
|
-
}
|
|
81188
|
-
}
|
|
81189
|
-
if (!config6.mcpServers) config6.mcpServers = {};
|
|
81190
|
-
const installed = [];
|
|
81191
|
-
const warnings = [];
|
|
81192
|
-
if (target === "all" || target === "solutions") {
|
|
81193
|
-
const haansiBin = resolveHaansiBin();
|
|
81194
|
-
config6.mcpServers["haansi-solutions"] = {
|
|
81195
|
-
command: haansiBin,
|
|
81196
|
-
args: ["mcp-server"],
|
|
81197
|
-
env: { HAANSI_AGENT_SOURCE: "claude-code" }
|
|
81198
|
-
};
|
|
81199
|
-
installed.push(`haansi-solutions (stdio \u2192 ${haansiBin} mcp-server)`);
|
|
81200
|
-
}
|
|
81201
|
-
for (const http of HTTP_SERVERS) {
|
|
81202
|
-
if (target !== "all" && target !== http.target) continue;
|
|
81203
|
-
const token = resolveToken();
|
|
81204
|
-
if (!token) {
|
|
81205
|
-
warnings.push(
|
|
81206
|
-
`${http.name} not registered: no token found. Run \`haansi auth login\` first, or set HAANSI_TOKEN.`
|
|
81207
|
-
);
|
|
81208
|
-
continue;
|
|
81209
|
-
}
|
|
81210
|
-
const url2 = httpServerUrl(http.path);
|
|
81211
|
-
config6.mcpServers[http.name] = {
|
|
81212
|
-
type: "http",
|
|
81213
|
-
url: url2,
|
|
81214
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
81215
|
-
};
|
|
81216
|
-
installed.push(`${http.name} (http \u2192 ${url2})`);
|
|
81217
|
-
}
|
|
81218
|
-
(0, import_node_fs5.writeFileSync)(CLAUDE_JSON, JSON.stringify(config6, null, 2) + "\n", "utf-8");
|
|
81219
|
-
console.log(`
|
|
81220
|
-
Claude Code \u2014 configured in ${CLAUDE_JSON}`);
|
|
81221
|
-
for (const entry2 of installed) console.log(` \u2022 ${entry2}`);
|
|
81222
|
-
for (const w of warnings) console.log(` ! ${w}`);
|
|
81223
|
-
return { installed, warnings };
|
|
81224
|
-
}
|
|
81225
|
-
function setupCodexMcp(target) {
|
|
81226
|
-
let content = "";
|
|
81227
|
-
if ((0, import_node_fs5.existsSync)(CODEX_CONFIG)) {
|
|
81228
|
-
try {
|
|
81229
|
-
content = (0, import_node_fs5.readFileSync)(CODEX_CONFIG, "utf-8");
|
|
81230
|
-
} catch {
|
|
81231
|
-
console.error(
|
|
81232
|
-
`Warning: Could not read ${CODEX_CONFIG} \u2014 will create a new file.`
|
|
81233
|
-
);
|
|
81234
|
-
}
|
|
81235
|
-
}
|
|
81236
|
-
const installed = [];
|
|
81237
|
-
const warnings = [];
|
|
81238
|
-
if (target === "all" || target === "solutions") {
|
|
81239
|
-
const haansiBin = resolveHaansiBin();
|
|
81240
|
-
content = upsertTomlTable(content, "mcp_servers.haansi-solutions", {
|
|
81241
|
-
command: tomlString(haansiBin),
|
|
81242
|
-
args: tomlStringArray(["mcp-server"]),
|
|
81243
|
-
env: tomlInlineTable({ HAANSI_AGENT_SOURCE: "codex" })
|
|
81244
|
-
});
|
|
81245
|
-
installed.push(`haansi-solutions (stdio \u2192 ${haansiBin} mcp-server)`);
|
|
81246
|
-
}
|
|
81247
|
-
for (const http of HTTP_SERVERS) {
|
|
81248
|
-
if (target !== "all" && target !== http.target) continue;
|
|
81249
|
-
const token = resolveToken();
|
|
81250
|
-
if (!token) {
|
|
81251
|
-
warnings.push(
|
|
81252
|
-
`${http.name} not registered: no token found. Run \`haansi auth login\` first, or set HAANSI_TOKEN.`
|
|
81253
|
-
);
|
|
81254
|
-
continue;
|
|
81255
|
-
}
|
|
81256
|
-
const url2 = httpServerUrl(http.path);
|
|
81257
|
-
content = upsertTomlTable(content, `mcp_servers.${http.name}`, {
|
|
81258
|
-
url: tomlString(url2),
|
|
81259
|
-
http_headers: tomlInlineTable({ Authorization: `Bearer ${token}` })
|
|
81260
|
-
});
|
|
81261
|
-
installed.push(`${http.name} (http \u2192 ${url2})`);
|
|
81262
|
-
}
|
|
81263
|
-
(0, import_node_fs5.mkdirSync)(CODEX_HOME, { recursive: true });
|
|
81264
|
-
(0, import_node_fs5.writeFileSync)(CODEX_CONFIG, content, "utf-8");
|
|
81265
|
-
console.log(`
|
|
81266
|
-
OpenAI Codex \u2014 configured in ${CODEX_CONFIG}`);
|
|
81267
|
-
for (const entry2 of installed) console.log(` \u2022 ${entry2}`);
|
|
81268
|
-
for (const w of warnings) console.log(` ! ${w}`);
|
|
81269
|
-
return { installed, warnings };
|
|
81270
|
-
}
|
|
81271
|
-
async function setupMcp(targetArg, agentFlag) {
|
|
81272
|
-
const target = parseTarget(targetArg);
|
|
81273
|
-
const selection = resolveAgentSelection(agentFlag);
|
|
81274
|
-
for (const agentId of selection.agents) {
|
|
81275
|
-
WRITERS[agentId](target);
|
|
81276
|
-
}
|
|
81277
|
-
saveAgents(selection.agents);
|
|
81278
|
-
const restart = selection.agents.map((a) => a === "codex" ? "Codex" : "Claude Code").join(" / ");
|
|
81279
|
-
console.log(`
|
|
81280
|
-
Restart ${restart} to activate the changes.`);
|
|
81281
|
-
}
|
|
81282
|
-
var import_node_fs5, import_node_path4, import_node_os4, CLAUDE_JSON, CODEX_HOME, CODEX_CONFIG, HTTP_SERVERS, WRITERS;
|
|
81283
|
-
var init_setup_mcp = __esm({
|
|
81284
|
-
"src/commands/setup-mcp.ts"() {
|
|
81285
|
-
"use strict";
|
|
81286
|
-
import_node_fs5 = require("node:fs");
|
|
81287
|
-
import_node_path4 = require("node:path");
|
|
81288
|
-
import_node_os4 = require("node:os");
|
|
81289
|
-
init_paths();
|
|
81290
|
-
init_agent_selection();
|
|
81291
|
-
init_codex_config();
|
|
81292
|
-
CLAUDE_JSON = (0, import_node_path4.join)((0, import_node_os4.homedir)(), ".claude.json");
|
|
81293
|
-
CODEX_HOME = process.env.CODEX_HOME || (0, import_node_path4.join)((0, import_node_os4.homedir)(), ".codex");
|
|
81294
|
-
CODEX_CONFIG = (0, import_node_path4.join)(CODEX_HOME, "config.toml");
|
|
81295
|
-
HTTP_SERVERS = [
|
|
81296
|
-
{
|
|
81297
|
-
target: "exchange",
|
|
81298
|
-
name: "haansi-exchange",
|
|
81299
|
-
path: "/api/v1/memory/exchange/mcp"
|
|
81300
|
-
},
|
|
81301
|
-
{
|
|
81302
|
-
target: "digests",
|
|
81303
|
-
name: "haansi-digests",
|
|
81304
|
-
path: "/api/v1/memory/digests/mcp"
|
|
81305
|
-
},
|
|
81306
|
-
// Published onboarding guides, so an agent can pick up a workstream from
|
|
81307
|
-
// its handoff doc (docs/product/plans/2026-09-24-onboarding-guides.md §8.3).
|
|
81308
|
-
{
|
|
81309
|
-
target: "onboarding",
|
|
81310
|
-
name: "haansi-onboarding",
|
|
81311
|
-
path: "/api/v1/memory/onboarding/mcp"
|
|
81312
|
-
},
|
|
81313
|
-
// Published team skills, loaded progressively by coding agents
|
|
81314
|
-
// (docs/product/plans/2026-10-10-memory-skills.md §7).
|
|
81315
|
-
{
|
|
81316
|
-
target: "skills",
|
|
81317
|
-
name: "haansi-skills",
|
|
81318
|
-
path: "/api/v1/memory/skills/mcp"
|
|
81319
|
-
}
|
|
81320
|
-
];
|
|
81321
|
-
WRITERS = {
|
|
81322
|
-
"claude-code": setupClaudeMcp,
|
|
81323
|
-
codex: setupCodexMcp
|
|
81324
|
-
};
|
|
81325
|
-
}
|
|
81326
|
-
});
|
|
81327
|
-
|
|
81328
83216
|
// src/commands/setup-daemon.ts
|
|
81329
83217
|
var setup_daemon_exports = {};
|
|
81330
83218
|
__export(setup_daemon_exports, {
|