@akasecurity/ai-tc-claude-code 0.9.4 → 0.9.5

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.
@@ -492,13 +492,12 @@ var require_ignore = __commonJS({
492
492
  });
493
493
 
494
494
  // ../../packages/plugin-sdk/src/config.ts
495
- import { existsSync as existsSync4 } from "fs";
496
- import { join as join7 } from "path";
495
+ import { existsSync as existsSync5 } from "fs";
496
+ import { join as join8 } from "path";
497
497
 
498
498
  // ../../packages/persistence/src/database.ts
499
- import { randomUUID as randomUUID9 } from "crypto";
500
- import { existsSync, renameSync as renameSync2, rmSync as rmSync2 } from "fs";
501
- import { join, sep } from "path";
499
+ import { randomUUID as randomUUID10 } from "crypto";
500
+ import { join as join2, sep } from "path";
502
501
  import { DatabaseSync } from "node:sqlite";
503
502
 
504
503
  // ../../packages/schema/src/drizzle/sqlite-ddl.ts
@@ -578,6 +577,14 @@ var SQLITE_MIGRATIONS = [
578
577
  {
579
578
  tag: "0018_serious_tana_nile",
580
579
  sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
580
+ },
581
+ {
582
+ tag: "0019_audit_started_at_index",
583
+ sql: "CREATE INDEX `idx_audit_started_at` ON `audit_events` (`started_at`);"
584
+ },
585
+ {
586
+ tag: "0020_secret_vault_pagination_indexes",
587
+ sql: "CREATE INDEX `idx_secret_vault_last_seen` ON `secret_vault` (`last_seen`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_reuse` ON `secret_vault` (`occurrence_count`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_at` ON `secret_vault_deref` (`at`,`id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_signal` ON `secret_vault_deref` (`at`,`id`) WHERE `reason` NOT IN ('display', 'view-render');"
581
588
  }
582
589
  ];
583
590
 
@@ -15315,7 +15322,17 @@ var Finding = external_exports.object({
15315
15322
  }).meta({ id: "Finding" });
15316
15323
  var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
15317
15324
  var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
15318
- var FindingProvider = external_exports.enum(["claudecode", "claudedesktop", "cursor", "copilot", "chatgpt", "api"]).meta({ id: "FindingProvider" });
15325
+ var FindingProvider = external_exports.enum([
15326
+ "claudecode",
15327
+ "claudedesktop",
15328
+ "cursor",
15329
+ "copilot",
15330
+ "chatgpt",
15331
+ "claudeai",
15332
+ "codex",
15333
+ "antigravity",
15334
+ "api"
15335
+ ]).meta({ id: "FindingProvider" });
15319
15336
  var FindingCategory = external_exports.enum([
15320
15337
  "secret",
15321
15338
  "pii",
@@ -15369,7 +15386,16 @@ var FindingInstance = external_exports.object({
15369
15386
  confidence: external_exports.number().min(0).max(1),
15370
15387
  // Lifecycle status (see FindingStatus). Optional so legacy callers/rows
15371
15388
  // that predate the resolution feature stay valid.
15372
- status: FindingStatus.optional()
15389
+ status: FindingStatus.optional(),
15390
+ // The audit event this finding was captured from. Optional so callers that
15391
+ // do not project it stay valid. An at-rest finding is content-addressed by
15392
+ // finding_key and its row is upserted on re-detection, so this names the
15393
+ // MOST RECENT detection event, not the first.
15394
+ eventId: external_exports.string().optional(),
15395
+ // The session that event belongs to, when it has one — the seam a
15396
+ // per-instance "view session" link needs. Absent for events captured
15397
+ // outside a session.
15398
+ sessionId: external_exports.string().optional()
15373
15399
  }).meta({ id: "FindingInstance" });
15374
15400
  var FindingGroup = external_exports.object({
15375
15401
  id: external_exports.string(),
@@ -15413,7 +15439,11 @@ var FindingFacets = external_exports.object({
15413
15439
  // for every instance, so every group lands in a bucket; a status-less
15414
15440
  // group (possible only for callers whose rows carry no statuses) is
15415
15441
  // counted under no value.
15416
- status: external_exports.array(FindingFacetItem)
15442
+ status: external_exports.array(FindingFacetItem),
15443
+ // Host tool (attributes.tool_name). Present only on the instance-level
15444
+ // reads, which can filter by it; the grouped read omits the dimension
15445
+ // because a group spans tools.
15446
+ tool: external_exports.array(FindingFacetItem).optional()
15417
15447
  }).meta({ id: "FindingFacets" });
15418
15448
  var DEFAULT_GROUPED_FINDINGS_LIMIT = 50;
15419
15449
  var ListGroupedFindingsQuery = external_exports.object({
@@ -15431,6 +15461,16 @@ var ListGroupedFindingsQuery = external_exports.object({
15431
15461
  // Scope to findings whose event carries this session id (the Activity page's
15432
15462
  // session → findings drilldown). Findings without a session never match.
15433
15463
  sessionId: external_exports.string().optional(),
15464
+ // Inclusive lower bound on the parent event's timestamp, so a caller arriving
15465
+ // from a time-scoped page (Activity's range) can carry that scope. Absent
15466
+ // means all time — this list has no default window.
15467
+ from: external_exports.iso.datetime().optional(),
15468
+ // A group or instance id that must appear in the page even when the cursor
15469
+ // has already advanced past its sort position. This is what keeps the
15470
+ // Findings page's one-shot ?finding= deep link resolving once the list
15471
+ // paginates: the target group is appended out of sort order rather than
15472
+ // scanning forward for it. Never affects totals, facets or the cursor.
15473
+ includeId: external_exports.string().optional(),
15434
15474
  groupBy: external_exports.literal("type").optional(),
15435
15475
  limit: external_exports.coerce.number().int().min(1).max(100).optional(),
15436
15476
  cursor: external_exports.string().optional()
@@ -15475,15 +15515,110 @@ var FindingInstanceDetail = FindingInstance.extend({
15475
15515
  detection: FindingDetectionRef,
15476
15516
  policy: FindingPolicyRef
15477
15517
  }).meta({ id: "FindingInstanceDetail" });
15518
+ var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
15519
+ var ListFindingInstancesQuery = external_exports.object({
15520
+ severity: external_exports.array(Severity).optional(),
15521
+ // Rule ids, the same vocabulary the grouped list's `subtype` carries.
15522
+ subtype: external_exports.array(external_exports.string()).optional(),
15523
+ provider: external_exports.array(FindingProvider).optional(),
15524
+ action: external_exports.array(FindingAction).optional(),
15525
+ // Matches each instance's OWN derived status (deriveFindingStatus), unlike
15526
+ // the grouped query's group-level fold.
15527
+ status: external_exports.array(FindingStatus).optional(),
15528
+ // Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
15529
+ // where the free-text `q` can only match the rendered "via Bash" label.
15530
+ tool: external_exports.array(external_exports.string()).optional(),
15531
+ // Exact repository / file-path matches, for the drill-down out of the
15532
+ // locations view. A row whose event carries no repo/file matches neither.
15533
+ repo: external_exports.string().optional(),
15534
+ file: external_exports.string().optional(),
15535
+ q: external_exports.string().optional(),
15536
+ sessionId: external_exports.string().optional(),
15537
+ from: external_exports.iso.datetime().optional(),
15538
+ limit: external_exports.coerce.number().int().min(1).max(200).optional(),
15539
+ cursor: external_exports.string().optional()
15540
+ });
15541
+ var ListFindingInstancesResponse = external_exports.object({
15542
+ // Instances matching the filters across the whole scope, not just this
15543
+ // page — cursor-independent, like the grouped list's totals.
15544
+ totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
15545
+ // Counts in INSTANCES here, where the grouped response counts groups. Each
15546
+ // dimension still excludes its own filter.
15547
+ facets: FindingFacets,
15548
+ items: external_exports.array(FindingInstanceDetail),
15549
+ nextCursor: external_exports.string().nullable()
15550
+ }).meta({ id: "ListFindingInstancesResponse" });
15551
+ var FindingLocationFile = external_exports.object({
15552
+ // Empty when the instances carried no file path (a prompt or a tool call
15553
+ // with no file attribution).
15554
+ file: external_exports.string(),
15555
+ instanceCount: external_exports.number().int().nonnegative(),
15556
+ maxSeverity: Severity,
15557
+ latestDetectedAt: external_exports.iso.datetime(),
15558
+ // Folded from the instances' derived statuses with the same
15559
+ // open-dominates precedence a group uses.
15560
+ status: FindingStatus.optional(),
15561
+ // Distinct rules seen at this location, capped — the row shows them as
15562
+ // chips, and the count is what conveys scale.
15563
+ ruleIds: external_exports.array(external_exports.string())
15564
+ }).meta({ id: "FindingLocationFile" });
15565
+ var FindingLocationRepo = external_exports.object({
15566
+ /** Empty when the instances carried no repo attribute. */
15567
+ repo: external_exports.string(),
15568
+ instanceCount: external_exports.number().int().nonnegative(),
15569
+ maxSeverity: Severity,
15570
+ latestDetectedAt: external_exports.iso.datetime(),
15571
+ status: FindingStatus.optional(),
15572
+ files: external_exports.array(FindingLocationFile)
15573
+ }).meta({ id: "FindingLocationRepo" });
15574
+ var ListFindingLocationsQuery = external_exports.object({
15575
+ severity: external_exports.array(Severity).optional(),
15576
+ subtype: external_exports.array(external_exports.string()).optional(),
15577
+ provider: external_exports.array(FindingProvider).optional(),
15578
+ action: external_exports.array(FindingAction).optional(),
15579
+ // Per-instance, as in ListFindingInstancesQuery: a location keeps the
15580
+ // instances that match, and folds its status from those.
15581
+ status: external_exports.array(FindingStatus).optional(),
15582
+ tool: external_exports.array(external_exports.string()).optional(),
15583
+ q: external_exports.string().optional(),
15584
+ sessionId: external_exports.string().optional(),
15585
+ from: external_exports.iso.datetime().optional(),
15586
+ limit: external_exports.coerce.number().int().min(1).max(500).optional()
15587
+ });
15588
+ var ListFindingLocationsResponse = external_exports.object({
15589
+ totals: external_exports.object({
15590
+ findings: external_exports.number().int().nonnegative(),
15591
+ repos: external_exports.number().int().nonnegative(),
15592
+ files: external_exports.number().int().nonnegative()
15593
+ }),
15594
+ /** Sorted by max severity, then most recent. */
15595
+ items: external_exports.array(FindingLocationRepo),
15596
+ /** Whether `limit` truncated the repo list. */
15597
+ hasMore: external_exports.boolean()
15598
+ }).meta({ id: "ListFindingLocationsResponse" });
15478
15599
 
15479
15600
  // ../../packages/schema/src/zod/harness-map.ts
15480
- var Harness = external_exports.enum(["claudecode", "cursor", "copilot", "codex", "windsurf", "claudedesktop", "chatgpt", "api"]).meta({ id: "Harness" });
15601
+ var Harness = external_exports.enum([
15602
+ "claudecode",
15603
+ "cursor",
15604
+ "copilot",
15605
+ "codex",
15606
+ "antigravity",
15607
+ "windsurf",
15608
+ "claudedesktop",
15609
+ "chatgpt",
15610
+ "claudeai",
15611
+ "api"
15612
+ ]).meta({ id: "Harness" });
15481
15613
  var TOOL_TO_HARNESS = {
15482
15614
  "claude-code": "claudecode",
15483
15615
  "claude-desktop": "claudedesktop",
15484
15616
  "github-copilot": "copilot",
15485
15617
  cursor: "cursor",
15486
- chatgpt: "chatgpt"
15618
+ chatgpt: "chatgpt",
15619
+ codex: "codex",
15620
+ antigravity: "antigravity",
15621
+ "claude-ai": "claudeai"
15487
15622
  };
15488
15623
 
15489
15624
  // ../../packages/schema/src/zod/meta.ts
@@ -15941,7 +16076,18 @@ var ActivityOverviewResponse = external_exports.object({
15941
16076
  // ../../packages/schema/src/zod/event.ts
15942
16077
  var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
15943
16078
  var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
15944
- var SourceTool = external_exports.enum(["claude-code", "claude-desktop", "cursor", "chatgpt", "github-copilot", "cli", "unknown"]).meta({ id: "SourceTool" });
16079
+ var SourceTool = external_exports.enum([
16080
+ "claude-code",
16081
+ "claude-desktop",
16082
+ "cursor",
16083
+ "chatgpt",
16084
+ "claude-ai",
16085
+ "github-copilot",
16086
+ "codex",
16087
+ "antigravity",
16088
+ "cli",
16089
+ "unknown"
16090
+ ]).meta({ id: "SourceTool" });
15945
16091
  var EventMetadata = external_exports.object({
15946
16092
  sessionId: external_exports.string().optional(),
15947
16093
  repo: external_exports.string().optional(),
@@ -16012,7 +16158,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
16012
16158
  var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
16013
16159
  var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
16014
16160
  var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
16015
- var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
16161
+ var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
16016
16162
  var AccessCounts = external_exports.object({
16017
16163
  open: external_exports.number().int().nonnegative(),
16018
16164
  approved: external_exports.number().int().nonnegative(),
@@ -16281,6 +16427,7 @@ var ExceptionBundleEntry = DetectionException.pick({
16281
16427
  useCount: true,
16282
16428
  conditions: true
16283
16429
  });
16430
+ var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
16284
16431
 
16285
16432
  // ../../packages/schema/src/zod/rule.ts
16286
16433
  var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
@@ -17091,6 +17238,35 @@ var EgressWriteSummary = external_exports.object({
17091
17238
  droppedFiles: external_exports.array(external_exports.string()).default([])
17092
17239
  }).meta({ id: "EgressWriteSummary" });
17093
17240
 
17241
+ // ../../packages/schema/src/zod/exception-action.ts
17242
+ var confirmation = external_exports.string().optional();
17243
+ var ApproveBlockedInput = external_exports.object({
17244
+ reference: external_exports.string(),
17245
+ scope: external_exports.string(),
17246
+ reason: external_exports.string(),
17247
+ confirmation
17248
+ });
17249
+ var AddExceptionInput = external_exports.object({
17250
+ ruleId: external_exports.string(),
17251
+ value: external_exports.string(),
17252
+ scope: external_exports.string(),
17253
+ reason: external_exports.string(),
17254
+ confirmation
17255
+ });
17256
+ var GrantRevealInput = external_exports.object({
17257
+ pointer: external_exports.string(),
17258
+ scope: external_exports.string(),
17259
+ justification: external_exports.string(),
17260
+ confirmation
17261
+ });
17262
+ var RevokeExceptionInput = external_exports.object({
17263
+ id: external_exports.string(),
17264
+ reason: external_exports.string()
17265
+ });
17266
+ var RotateKeyInput = external_exports.object({
17267
+ confirmation: external_exports.string()
17268
+ });
17269
+
17094
17270
  // ../../packages/schema/src/zod/findings-group-build.ts
17095
17271
  function toApiAction(dbVal) {
17096
17272
  const map2 = {
@@ -17146,6 +17322,8 @@ function buildFindingGroups(rows, opts = {}) {
17146
17322
  repo: r.repo,
17147
17323
  file: r.file,
17148
17324
  ...r.toolName === void 0 ? {} : { toolName: r.toolName },
17325
+ ...r.eventId === void 0 ? {} : { eventId: r.eventId },
17326
+ ...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
17149
17327
  action: toApiAction(effectiveDbAction),
17150
17328
  detectedAt: r.occurredAt,
17151
17329
  confidence: r.confidence,
@@ -17277,14 +17455,17 @@ function applyFindingFilters(groups, opts) {
17277
17455
  }
17278
17456
  var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
17279
17457
  var SEVERITY_RANK = SEVERITY_ORDER;
17458
+ function compareFindingGroupOrder(a, b) {
17459
+ const rankA = SEVERITY_RANK[a.severity] ?? -1;
17460
+ const rankB = SEVERITY_RANK[b.severity] ?? -1;
17461
+ const severityDiff = rankA - rankB;
17462
+ if (severityDiff !== 0) return severityDiff;
17463
+ const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
17464
+ if (recencyDiff !== 0) return recencyDiff;
17465
+ return a.id.localeCompare(b.id);
17466
+ }
17280
17467
  function sortFindingGroups(groups) {
17281
- return [...groups].sort((a, b) => {
17282
- const rankA = SEVERITY_RANK[a.severity] ?? -1;
17283
- const rankB = SEVERITY_RANK[b.severity] ?? -1;
17284
- const severityDiff = rankA - rankB;
17285
- if (severityDiff !== 0) return severityDiff;
17286
- return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
17287
- });
17468
+ return [...groups].sort(compareFindingGroupOrder);
17288
17469
  }
17289
17470
  function computeFindingFacets(allGroups, opts) {
17290
17471
  const forSeverity = applyFindingFilters(allGroups, {
@@ -17340,15 +17521,158 @@ function computeFindingFacets(allGroups, opts) {
17340
17521
  for (const g of forStatus) {
17341
17522
  if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
17342
17523
  }
17343
- const toItems = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
17524
+ const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
17525
+ return {
17526
+ severity: toItems2(severityMap),
17527
+ provider: toItems2(providerMap),
17528
+ action: toItems2(actionMap),
17529
+ subtype: toItems2(subtypeMap),
17530
+ status: toItems2(statusMap)
17531
+ };
17532
+ }
17533
+
17534
+ // ../../packages/schema/src/zod/findings-flat-build.ts
17535
+ function rowHaystack(row) {
17536
+ return [
17537
+ row.ruleId,
17538
+ row.category,
17539
+ row.maskedMatch,
17540
+ row.repo,
17541
+ row.file,
17542
+ row.toolName ? `via ${row.toolName}` : "",
17543
+ row.id
17544
+ ].join(" ").toLowerCase();
17545
+ }
17546
+ function matchesDimension(row, opts, dimension) {
17547
+ switch (dimension) {
17548
+ case "severity":
17549
+ return !opts.severity?.length || opts.severity.includes(row.severity);
17550
+ case "subtype":
17551
+ return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
17552
+ case "providers":
17553
+ return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
17554
+ case "actions":
17555
+ return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
17556
+ case "statuses":
17557
+ return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
17558
+ case "tools":
17559
+ return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
17560
+ case "repo":
17561
+ return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
17562
+ case "file":
17563
+ return opts.file === void 0 || opts.file === "" || row.file === opts.file;
17564
+ case "q":
17565
+ return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
17566
+ }
17567
+ }
17568
+ var DIMENSIONS = [
17569
+ "severity",
17570
+ "subtype",
17571
+ "providers",
17572
+ "actions",
17573
+ "statuses",
17574
+ "tools",
17575
+ "repo",
17576
+ "file",
17577
+ "q"
17578
+ ];
17579
+ function matchesInstanceFilters(row, opts, except) {
17580
+ for (const dimension of DIMENSIONS) {
17581
+ if (dimension === except) continue;
17582
+ if (!matchesDimension(row, opts, dimension)) return false;
17583
+ }
17584
+ return true;
17585
+ }
17586
+ function toItems(counts) {
17587
+ return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
17588
+ }
17589
+ function bump(counts, value) {
17590
+ counts.set(value, (counts.get(value) ?? 0) + 1);
17591
+ }
17592
+ function createInstanceFacetAccumulator(opts) {
17593
+ const severity = /* @__PURE__ */ new Map();
17594
+ const subtype = /* @__PURE__ */ new Map();
17595
+ const provider = /* @__PURE__ */ new Map();
17596
+ const action = /* @__PURE__ */ new Map();
17597
+ const status = /* @__PURE__ */ new Map();
17598
+ const tool = /* @__PURE__ */ new Map();
17599
+ return {
17600
+ add(row) {
17601
+ if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
17602
+ if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
17603
+ if (matchesInstanceFilters(row, opts, "providers")) {
17604
+ bump(provider, toApiProvider(row.sourceTool));
17605
+ }
17606
+ if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
17607
+ if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
17608
+ bump(status, row.status);
17609
+ }
17610
+ if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
17611
+ bump(tool, row.toolName);
17612
+ }
17613
+ },
17614
+ facets: () => ({
17615
+ severity: toItems(severity),
17616
+ subtype: toItems(subtype),
17617
+ provider: toItems(provider),
17618
+ action: toItems(action),
17619
+ status: toItems(status),
17620
+ tool: toItems(tool)
17621
+ })
17622
+ };
17623
+ }
17624
+ function toInstanceDetail(row) {
17625
+ const category = toApiCategory(row.category);
17626
+ return {
17627
+ id: row.id,
17628
+ provider: toApiProvider(row.sourceTool),
17629
+ repo: row.repo,
17630
+ file: row.file,
17631
+ ...row.toolName === void 0 ? {} : { toolName: row.toolName },
17632
+ eventId: row.eventId,
17633
+ ...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
17634
+ action: toApiAction(row.actionTaken),
17635
+ detectedAt: row.occurredAt,
17636
+ confidence: row.confidence,
17637
+ ...row.status === void 0 ? {} : { status: row.status },
17638
+ groupId: row.ruleId,
17639
+ category,
17640
+ subtype: row.ruleId,
17641
+ severity: row.severity,
17642
+ match: { maskedValue: row.maskedMatch, contextPrefix: "" },
17643
+ detection: { id: row.ruleId, name: null },
17644
+ policy: { id: `category:${category}`, name: category }
17645
+ };
17646
+ }
17647
+ var SEVERITY_ORDER2 = {
17648
+ critical: 0,
17649
+ high: 1,
17650
+ medium: 2,
17651
+ low: 3
17652
+ };
17653
+ function newLocationAccumulator() {
17344
17654
  return {
17345
- severity: toItems(severityMap),
17346
- provider: toItems(providerMap),
17347
- action: toItems(actionMap),
17348
- subtype: toItems(subtypeMap),
17349
- status: toItems(statusMap)
17655
+ instanceCount: 0,
17656
+ // Sorts after every known severity, so the first row always wins the
17657
+ // comparison below rather than an unknown value pinning the location.
17658
+ maxSeverityRank: Number.MAX_SAFE_INTEGER,
17659
+ maxSeverity: "low",
17660
+ latestDetectedAt: "",
17661
+ statuses: [],
17662
+ ruleIds: /* @__PURE__ */ new Set()
17350
17663
  };
17351
17664
  }
17665
+ function addToLocation(acc, row) {
17666
+ acc.instanceCount += 1;
17667
+ const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
17668
+ if (rank < acc.maxSeverityRank) {
17669
+ acc.maxSeverityRank = rank;
17670
+ acc.maxSeverity = row.severity;
17671
+ }
17672
+ if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
17673
+ acc.statuses.push(row.status);
17674
+ acc.ruleIds.add(row.ruleId);
17675
+ }
17352
17676
 
17353
17677
  // ../../packages/schema/src/zod/installed-pack.ts
17354
17678
  var InstalledPack = external_exports.object({
@@ -17482,6 +17806,50 @@ var VaultInventoryEntry = external_exports.object({
17482
17806
  revealGrantId: external_exports.string().nullable(),
17483
17807
  sightings: external_exports.array(VaultSighting)
17484
17808
  });
17809
+ var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
17810
+ var DEFAULT_VAULT_DEREFS_LIMIT = 50;
17811
+ var MAX_VAULT_PAGE_LIMIT = 200;
17812
+ var ListVaultInventoryQuery = external_exports.object({
17813
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
17814
+ // Opaque; names the last row of the page just served.
17815
+ cursor: external_exports.string().optional()
17816
+ });
17817
+ var ListVaultInventoryResponse = external_exports.object({
17818
+ // Vaulted values across the whole store, not just this page — cursor-
17819
+ // independent, so paging never changes what the count claims.
17820
+ totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
17821
+ items: external_exports.array(VaultInventoryEntry),
17822
+ // `null` once the last page is reached.
17823
+ nextCursor: external_exports.string().nullable()
17824
+ });
17825
+ var ListVaultReuseQuery = external_exports.object({
17826
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
17827
+ cursor: external_exports.string().optional()
17828
+ });
17829
+ var ListVaultReuseResponse = external_exports.object({
17830
+ // Reused values across the whole store — the number the section's claim
17831
+ // ("values detected in more than one place") is about.
17832
+ totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
17833
+ items: external_exports.array(VaultInventoryEntry),
17834
+ nextCursor: external_exports.string().nullable()
17835
+ });
17836
+ var ListVaultDerefsQuery = external_exports.object({
17837
+ // Include the batched, high-volume reasons (display, view-render). Omitted
17838
+ // hides them and counts them into `hiddenBatched` instead, so the model
17839
+ // crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
17840
+ // over a Server Action, which preserves the type, never as a URL param.
17841
+ includeBatched: external_exports.boolean().optional(),
17842
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
17843
+ cursor: external_exports.string().optional()
17844
+ });
17845
+ var ListVaultDerefsResponse = external_exports.object({
17846
+ items: external_exports.array(VaultDeref),
17847
+ nextCursor: external_exports.string().nullable(),
17848
+ // Display/view-render rows the query hid, over the WHOLE trail rather than
17849
+ // this page — it is the count the "N hidden" line and its toggle speak for.
17850
+ // Always 0 when `includeBatched` was set, since nothing was hidden.
17851
+ hiddenBatched: external_exports.number().int().nonnegative()
17852
+ });
17485
17853
  var VaultKeyCustody = external_exports.string();
17486
17854
  var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
17487
17855
  var VaultConsent = external_exports.object({
@@ -17854,7 +18222,7 @@ var TopSourcesQuery = external_exports.object({
17854
18222
  // Omit for both kinds.
17855
18223
  kind: external_exports.enum(SOURCE_KINDS).optional()
17856
18224
  });
17857
- var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
18225
+ var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
17858
18226
  var ScanCoverageProvider = external_exports.object({
17859
18227
  provider: Provider,
17860
18228
  // Percent of that provider's traffic scanned in the window. 0 when unsupported.
@@ -18107,6 +18475,138 @@ function captureId(sessionId, contentHash, filePath = null) {
18107
18475
  );
18108
18476
  }
18109
18477
 
18478
+ // ../../packages/persistence/src/internal/snapshot.ts
18479
+ import { randomUUID } from "crypto";
18480
+ import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
18481
+ import { basename, dirname, join } from "path";
18482
+
18483
+ // ../../packages/persistence/src/paths.ts
18484
+ import {
18485
+ chmodSync,
18486
+ linkSync,
18487
+ lstatSync,
18488
+ mkdirSync,
18489
+ renameSync,
18490
+ rmSync,
18491
+ writeFileSync
18492
+ } from "fs";
18493
+ import { threadId } from "worker_threads";
18494
+ var DATA_DIR_MODE = 448;
18495
+ var DATA_FILE_MODE = 384;
18496
+ var DB_FILENAME = "aka.db";
18497
+ function isSymlink(path) {
18498
+ try {
18499
+ return lstatSync(path).isSymbolicLink();
18500
+ } catch {
18501
+ return false;
18502
+ }
18503
+ }
18504
+ function chmodBestEffort(path, mode) {
18505
+ if (isSymlink(path)) return;
18506
+ try {
18507
+ chmodSync(path, mode);
18508
+ } catch {
18509
+ }
18510
+ }
18511
+ function tightenDir(dir) {
18512
+ chmodBestEffort(dir, DATA_DIR_MODE);
18513
+ }
18514
+ function ensureDataDirSync(dir) {
18515
+ mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
18516
+ tightenDir(dir);
18517
+ }
18518
+ function dbSidecars(file2) {
18519
+ return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
18520
+ }
18521
+ function tightenFile(file2) {
18522
+ chmodBestEffort(file2, DATA_FILE_MODE);
18523
+ }
18524
+ function tightenPerms(file2) {
18525
+ for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
18526
+ }
18527
+
18528
+ // ../../packages/persistence/src/internal/snapshot.ts
18529
+ function backupPath(file2, tag) {
18530
+ return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
18531
+ }
18532
+ var STALE_PARTIAL_MS = 5 * 6e4;
18533
+ function reapStalePartials(file2) {
18534
+ const dir = dirname(file2);
18535
+ const prefix = `${basename(file2)}.`;
18536
+ let entries;
18537
+ try {
18538
+ entries = readdirSync(dir);
18539
+ } catch {
18540
+ return;
18541
+ }
18542
+ for (const name of entries) {
18543
+ if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
18544
+ const partial2 = join(dir, name);
18545
+ try {
18546
+ if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
18547
+ rmSync2(partial2, { force: true });
18548
+ }
18549
+ } catch {
18550
+ }
18551
+ }
18552
+ }
18553
+ function snapshotStore(db, backup) {
18554
+ const partial2 = `${backup}.partial`;
18555
+ try {
18556
+ rmSync2(partial2, { force: true });
18557
+ db.prepare("VACUUM INTO ?").run(partial2);
18558
+ tightenFile(partial2);
18559
+ renameSync2(partial2, backup);
18560
+ } catch (error51) {
18561
+ try {
18562
+ rmSync2(partial2, { force: true });
18563
+ } catch {
18564
+ }
18565
+ throw error51;
18566
+ }
18567
+ }
18568
+ function moveStoreAside(file2, backup) {
18569
+ const undo = [];
18570
+ renameSync2(file2, backup);
18571
+ undo.push([backup, file2]);
18572
+ try {
18573
+ for (const sidecar of dbSidecars(file2)) {
18574
+ const moved = `${backup}${sidecar.slice(file2.length)}`;
18575
+ try {
18576
+ renameSync2(sidecar, moved);
18577
+ undo.push([moved, sidecar]);
18578
+ } catch {
18579
+ rmSync2(sidecar, { force: true });
18580
+ }
18581
+ }
18582
+ } catch (error51) {
18583
+ for (const [from, to] of undo.reverse()) {
18584
+ try {
18585
+ renameSync2(from, to);
18586
+ } catch {
18587
+ }
18588
+ }
18589
+ throw error51;
18590
+ }
18591
+ tightenPerms(backup);
18592
+ }
18593
+ function discardStore(file2, backup) {
18594
+ try {
18595
+ rmSync2(file2, { force: true });
18596
+ for (const sidecar of dbSidecars(file2)) {
18597
+ rmSync2(sidecar, { force: true });
18598
+ }
18599
+ } catch (error51) {
18600
+ if (existsSync(file2)) {
18601
+ try {
18602
+ rmSync2(backup, { force: true });
18603
+ } catch {
18604
+ }
18605
+ }
18606
+ throw error51;
18607
+ }
18608
+ }
18609
+
18110
18610
  // ../../packages/persistence/src/internal/sql-text.ts
18111
18611
  function escapeLikePattern(s) {
18112
18612
  return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
@@ -18248,38 +18748,6 @@ function mapRowsTolerant(rows, map2) {
18248
18748
  return out;
18249
18749
  }
18250
18750
 
18251
- // ../../packages/persistence/src/paths.ts
18252
- import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
18253
- var DATA_DIR_MODE = 448;
18254
- var DATA_FILE_MODE = 384;
18255
- var DB_FILENAME = "aka.db";
18256
- function chmodBestEffort(path, mode) {
18257
- try {
18258
- chmodSync(path, mode);
18259
- } catch {
18260
- }
18261
- }
18262
- function tightenDir(dir) {
18263
- chmodBestEffort(dir, DATA_DIR_MODE);
18264
- }
18265
- function ensureDataDirSync(dir) {
18266
- mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
18267
- tightenDir(dir);
18268
- }
18269
- function dbSidecars(file2) {
18270
- return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
18271
- }
18272
- function tightenFile(file2) {
18273
- try {
18274
- if (lstatSync(file2).isSymbolicLink()) return;
18275
- } catch {
18276
- }
18277
- chmodBestEffort(file2, DATA_FILE_MODE);
18278
- }
18279
- function tightenPerms(file2) {
18280
- for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
18281
- }
18282
-
18283
18751
  // ../../packages/persistence/src/migrations.ts
18284
18752
  function describeObject(object2) {
18285
18753
  return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
@@ -18395,9 +18863,9 @@ function applyLegacyDropMigration(db, file2) {
18395
18863
  }
18396
18864
  }
18397
18865
  function backupBeforeLegacyDrop(db, file2) {
18398
- const backup = `${file2}.pre-drop.${String(Date.now())}.bak`;
18399
- db.prepare("VACUUM INTO ?").run(backup);
18400
- tightenFile(backup);
18866
+ reapStalePartials(file2);
18867
+ const backup = backupPath(file2, "pre-drop");
18868
+ snapshotStore(db, backup);
18401
18869
  return backup;
18402
18870
  }
18403
18871
  var TOKEN_USAGE_COLUMNS = [
@@ -18741,6 +19209,25 @@ function parseJsonObject(s) {
18741
19209
  return void 0;
18742
19210
  }
18743
19211
 
19212
+ // ../../packages/persistence/src/internal/keyset-cursor.ts
19213
+ function encodeKeysetCursor(payload) {
19214
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
19215
+ }
19216
+ function decodeKeysetCursor(cursor) {
19217
+ const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
19218
+ if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
19219
+ // resumes from is epoch millis, and a payload carrying ±Infinity or a
19220
+ // fraction binds cleanly rather than failing — returning an EMPTY page with
19221
+ // a null cursor, which a caller reads as "end of list". That is the one
19222
+ // outcome a cursor that does not decode must never produce, since the
19223
+ // documented behaviour above is to restart from the top. (`1e999` is valid
19224
+ // JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
19225
+ Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
19226
+ return parsed;
19227
+ }
19228
+ return null;
19229
+ }
19230
+
18744
19231
  // ../../packages/persistence/src/repositories/activity.ts
18745
19232
  var DAY_MS = 864e5;
18746
19233
  var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
@@ -18786,16 +19273,6 @@ function utcWindow(nowMs) {
18786
19273
  const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
18787
19274
  return { startMs, endMs: startMs + DAY_MS };
18788
19275
  }
18789
- function encodeCursor(payload) {
18790
- return Buffer.from(JSON.stringify(payload)).toString("base64url");
18791
- }
18792
- function decodeCursor(cursor) {
18793
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
18794
- if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
18795
- return parsed;
18796
- }
18797
- return null;
18798
- }
18799
19276
  var DB_EVENT_TYPE_TO_KIND = {
18800
19277
  session: "session",
18801
19278
  prompt: "prompt",
@@ -18940,7 +19417,7 @@ var SqliteActivityRepository = class {
18940
19417
  return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
18941
19418
  }
18942
19419
  listSessions(query) {
18943
- const cursor = query.cursor ? decodeCursor(query.cursor) : null;
19420
+ const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
18944
19421
  const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
18945
19422
  const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
18946
19423
  const conditions = [SESSION_ROOT];
@@ -19014,7 +19491,7 @@ var SqliteActivityRepository = class {
19014
19491
  )
19015
19492
  );
19016
19493
  const last = page[page.length - 1];
19017
- const nextCursor = hasMore && last ? encodeCursor({ startedAtMs: last.started_at, id: last.id }) : null;
19494
+ const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
19018
19495
  return Promise.resolve({ items, nextCursor, emptyCount });
19019
19496
  }
19020
19497
  getSession(sessionId) {
@@ -19887,7 +20364,7 @@ var SqliteEventsRepository = class {
19887
20364
  };
19888
20365
 
19889
20366
  // ../../packages/persistence/src/repositories/exceptions.ts
19890
- import { randomUUID } from "crypto";
20367
+ import { randomUUID as randomUUID2 } from "crypto";
19891
20368
 
19892
20369
  // ../../packages/persistence/src/internal/sqlite-errors.ts
19893
20370
  var SQLITE_CONSTRAINT_UNIQUE = 2067;
@@ -19923,8 +20400,9 @@ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
19923
20400
  AND conditions IS NULL
19924
20401
  AND ${ACTIVE_PREDICATE}`;
19925
20402
  var SqliteExceptionsRepository = class {
19926
- constructor(db) {
20403
+ constructor(db, now = () => Date.now()) {
19927
20404
  this.db = db;
20405
+ this.now = now;
19928
20406
  this.consumeStmt = db.prepare(
19929
20407
  `UPDATE exceptions
19930
20408
  SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
@@ -19942,6 +20420,7 @@ var SqliteExceptionsRepository = class {
19942
20420
  );
19943
20421
  }
19944
20422
  db;
20423
+ now;
19945
20424
  consumeStmt;
19946
20425
  insertBlockedStmt;
19947
20426
  sweepBlockedStmt;
@@ -19968,8 +20447,8 @@ var SqliteExceptionsRepository = class {
19968
20447
  "provider conditions are not supported yet \u2014 a grant with one would never apply"
19969
20448
  );
19970
20449
  }
19971
- const id = randomUUID();
19972
- const now = Date.now();
20450
+ const id = randomUUID2();
20451
+ const now = this.now();
19973
20452
  try {
19974
20453
  this.insertExceptionRow(id, input, now);
19975
20454
  } catch (err) {
@@ -20047,7 +20526,7 @@ var SqliteExceptionsRepository = class {
20047
20526
  const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
20048
20527
  const rows = allRows(
20049
20528
  this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
20050
- opts?.includeTerminal ? {} : { now: Date.now() }
20529
+ opts?.includeTerminal ? {} : { now: this.now() }
20051
20530
  );
20052
20531
  const exceptions = mapRowsTolerant(rows, parseExceptionRow);
20053
20532
  return Promise.resolve(exceptions);
@@ -20082,7 +20561,7 @@ var SqliteExceptionsRepository = class {
20082
20561
  * already revoked.
20083
20562
  */
20084
20563
  revoke(id, revokedBy, reason) {
20085
- const now = Date.now();
20564
+ const now = this.now();
20086
20565
  const result = this.db.prepare(
20087
20566
  `UPDATE exceptions
20088
20567
  SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
@@ -20096,7 +20575,7 @@ var SqliteExceptionsRepository = class {
20096
20575
  * callers must treat identically — means it does not and the detection is
20097
20576
  * enforced as usual. Deliberately NOT wrapped in try/catch.
20098
20577
  */
20099
- consume(id, now = Date.now()) {
20578
+ consume(id, now = this.now()) {
20100
20579
  const result = this.consumeStmt.run({ id, now });
20101
20580
  return Promise.resolve(Number(result.changes) === 1);
20102
20581
  }
@@ -20105,7 +20584,7 @@ var SqliteExceptionsRepository = class {
20105
20584
  * version — what rides the policy bundle to the hook. Grants written under
20106
20585
  * a different (rotated-away) key never match, so they are excluded at read.
20107
20586
  */
20108
- activeBundleEntries(keyVersion, now = Date.now()) {
20587
+ activeBundleEntries(keyVersion, now = this.now()) {
20109
20588
  const rows = allRows(
20110
20589
  this.db.prepare(
20111
20590
  `SELECT * FROM exceptions
@@ -20137,7 +20616,7 @@ var SqliteExceptionsRepository = class {
20137
20616
  * than the retention window on every write, so the ledger self-limits.
20138
20617
  */
20139
20618
  recordBlocked(entry) {
20140
- const now = Date.now();
20619
+ const now = this.now();
20141
20620
  this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
20142
20621
  this.insertBlockedStmt.run({
20143
20622
  reference: entry.reference,
@@ -20160,7 +20639,7 @@ var SqliteExceptionsRepository = class {
20160
20639
  WHERE blocked_at > :cutoff
20161
20640
  ORDER BY blocked_at DESC, rowid DESC`
20162
20641
  ),
20163
- { cutoff: Date.now() - windowMs }
20642
+ { cutoff: this.now() - windowMs }
20164
20643
  );
20165
20644
  return Promise.resolve(
20166
20645
  rows.map((row) => ({
@@ -20188,8 +20667,9 @@ var SqliteExceptionsRepository = class {
20188
20667
  * evaluate conditions, and a narrowing clause that is ignored would WIDEN the
20189
20668
  * grant instead. Fail closed until reveal-side condition evaluation exists.
20190
20669
  */
20191
- activeRevealGrant(ruleId, valueFingerprint, keyVersion, now = Date.now()) {
20670
+ activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
20192
20671
  try {
20672
+ const at = now ?? this.now();
20193
20673
  const row = getRow(
20194
20674
  this.db.prepare(
20195
20675
  `SELECT id FROM exceptions
@@ -20198,7 +20678,7 @@ var SqliteExceptionsRepository = class {
20198
20678
  AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
20199
20679
  LIMIT 1`
20200
20680
  ),
20201
- { ruleId, valueFingerprint, keyVersion, now }
20681
+ { ruleId, valueFingerprint, keyVersion, now: at }
20202
20682
  );
20203
20683
  return Promise.resolve(row ?? null);
20204
20684
  } catch (err) {
@@ -20212,7 +20692,7 @@ var SqliteExceptionsRepository = class {
20212
20692
  * predicate, so correctness never depends on this sweep; it only bounds how
20213
20693
  * long the audit evidence is kept locally. Returns the deleted count.
20214
20694
  */
20215
- sweepTerminal(retentionMs, now = Date.now()) {
20695
+ sweepTerminal(retentionMs, now = this.now()) {
20216
20696
  const result = this.db.prepare(
20217
20697
  `DELETE FROM exceptions
20218
20698
  WHERE updated_at < :cutoff
@@ -20275,6 +20755,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
20275
20755
 
20276
20756
  // ../../packages/persistence/src/repositories/findings.ts
20277
20757
  var PREVIEW_INSTANCES_PER_GROUP = 200;
20758
+ var SCAN_BATCH_ROWS = 1e3;
20759
+ var DEFAULT_LOCATIONS_LIMIT = 100;
20760
+ var LOCATION_RULE_IDS_CAP = 20;
20761
+ function compareLocationOrder(a, b) {
20762
+ return compareFindingGroupOrder(
20763
+ {
20764
+ severity: a.maxSeverity,
20765
+ latestDetectedAt: a.latestDetectedAt,
20766
+ id: ""
20767
+ },
20768
+ {
20769
+ severity: b.maxSeverity,
20770
+ latestDetectedAt: b.latestDetectedAt,
20771
+ id: ""
20772
+ }
20773
+ );
20774
+ }
20278
20775
  var CONCAT_SEP = ",";
20279
20776
  var TUPLE_SEP = "|";
20280
20777
  function splitConcat(value) {
@@ -20287,6 +20784,33 @@ function deriveInstanceStatus(row) {
20287
20784
  latestResolutionStatus: row.latest_status
20288
20785
  });
20289
20786
  }
20787
+ function encodeGroupCursor(group) {
20788
+ const payload = {
20789
+ sev: group.severity,
20790
+ t: group.latestDetectedAt,
20791
+ id: group.id
20792
+ };
20793
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
20794
+ }
20795
+ function decodeGroupCursor(cursor) {
20796
+ const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
20797
+ if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
20798
+ return {
20799
+ severity: parsed.sev,
20800
+ latestDetectedAt: parsed.t,
20801
+ id: parsed.id
20802
+ };
20803
+ }
20804
+ return null;
20805
+ }
20806
+ function firstAfter(sorted, cursor) {
20807
+ const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
20808
+ return index === -1 ? sorted.length : index;
20809
+ }
20810
+ function findDeepLinked(sorted, page, id) {
20811
+ if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
20812
+ return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
20813
+ }
20290
20814
  var DAY_MS3 = 864e5;
20291
20815
  var SqliteFindingsRepository = class {
20292
20816
  constructor(db) {
@@ -20396,8 +20920,13 @@ var SqliteFindingsRepository = class {
20396
20920
  */
20397
20921
  listGroupedFindings(query) {
20398
20922
  const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
20399
- const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}`;
20400
- const sessionParams = query.sessionId ? { sessionId: query.sessionId } : {};
20923
+ const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
20924
+ const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
20925
+ const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
20926
+ const sessionParams = {
20927
+ ...query.sessionId ? { sessionId: query.sessionId } : {},
20928
+ ...fromMs === void 0 ? {} : { fromMs }
20929
+ };
20401
20930
  const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
20402
20931
  predicate,
20403
20932
  params: sessionParams
@@ -20405,7 +20934,8 @@ var SqliteFindingsRepository = class {
20405
20934
  const rows = allRows(
20406
20935
  this.db.prepare(
20407
20936
  `SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
20408
- occurred_at, source_tool, repo, file, tool_name, kind, finding_key, latest_status
20937
+ occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
20938
+ kind, finding_key, latest_status
20409
20939
  FROM (
20410
20940
  SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
20411
20941
  d.severity AS severity, f.masked_match AS masked_match,
@@ -20415,6 +20945,7 @@ var SqliteFindingsRepository = class {
20415
20945
  json_extract(e.attributes, '$.repo') AS repo,
20416
20946
  json_extract(e.attributes, '$.file_path') AS file,
20417
20947
  json_extract(e.attributes, '$.tool_name') AS tool_name,
20948
+ f.audit_event_id AS event_id, e.root_session_id AS session_id,
20418
20949
  e.event_type AS kind, f.finding_key AS finding_key,
20419
20950
  latest.status AS latest_status,
20420
20951
  ROW_NUMBER() OVER (
@@ -20446,6 +20977,8 @@ var SqliteFindingsRepository = class {
20446
20977
  repo: r.repo ?? "",
20447
20978
  file: r.file ?? "",
20448
20979
  ...r.tool_name === null ? {} : { toolName: r.tool_name },
20980
+ eventId: r.event_id,
20981
+ ...r.session_id === null ? {} : { sessionId: r.session_id },
20449
20982
  status: deriveInstanceStatus(r)
20450
20983
  }));
20451
20984
  const allGroups = buildFindingGroups(groupable, { aggregates });
@@ -20469,18 +21002,23 @@ var SqliteFindingsRepository = class {
20469
21002
  groups: sorted.length
20470
21003
  };
20471
21004
  const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
21005
+ const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
21006
+ const start = cursor === null ? 0 : firstAfter(sorted, cursor);
21007
+ const page = sorted.slice(start, start + limit);
21008
+ const lastOnPage = page.at(-1);
21009
+ const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
21010
+ const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
20472
21011
  const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
20473
- const items = sorted.slice(0, limit).map(
20474
- (g) => statusSet ? {
20475
- ...g,
20476
- instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
20477
- } : g
20478
- );
21012
+ const narrow = (g) => statusSet ? {
21013
+ ...g,
21014
+ instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
21015
+ } : g;
21016
+ const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
20479
21017
  return Promise.resolve({
20480
21018
  totals,
20481
21019
  facets,
20482
21020
  items,
20483
- nextCursor: null,
21021
+ nextCursor,
20484
21022
  ...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
20485
21023
  });
20486
21024
  }
@@ -20512,6 +21050,266 @@ var SqliteFindingsRepository = class {
20512
21050
  * request actually carries a `q`. (Substring matching is unaffected by a
20513
21051
  * path repeating across tuples.)
20514
21052
  */
21053
+ /**
21054
+ * The instance-level (flat) findings list: one row per finding, newest first,
21055
+ * paged by keyset.
21056
+ *
21057
+ * SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
21058
+ * bound are SQL predicates: nothing counts them, so narrowing the scan by
21059
+ * them changes no reported number. Severity, subtype, provider, action,
21060
+ * status, tool, repo, file and `q` all stay in JS — each has a facet, and a
21061
+ * facet excludes its own filter, so a row the filter rejects still has to be
21062
+ * counted. Pushing any of them into SQL would silently empty its own facet.
21063
+ * Several could not be expressed there anyway: status comes from the one
21064
+ * shared classifier (deriveFindingStatus), and provider 'api' means "a tool
21065
+ * none of the mappers names", which no IN-list can say.
21066
+ *
21067
+ * The scan runs from the top of the scope on every request, not from the
21068
+ * cursor: `totals` and `facets` describe the whole filtered scope and must not
21069
+ * move as the caller pages. Rows are pulled in batches so memory stays flat
21070
+ * while the counting runs, and only the page itself is retained.
21071
+ */
21072
+ listFindingInstances(query) {
21073
+ const opts = {
21074
+ severity: query.severity,
21075
+ subtype: query.subtype,
21076
+ providers: query.provider,
21077
+ actions: query.action,
21078
+ statuses: query.status,
21079
+ tools: query.tool,
21080
+ repo: query.repo,
21081
+ file: query.file,
21082
+ q: query.q
21083
+ };
21084
+ const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
21085
+ const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
21086
+ const accumulator = createInstanceFacetAccumulator(opts);
21087
+ const items = [];
21088
+ let total = 0;
21089
+ let last;
21090
+ let hasMore = false;
21091
+ for (const row of this.scanFindingRows({
21092
+ sessionId: query.sessionId,
21093
+ from: query.from
21094
+ })) {
21095
+ accumulator.add(row);
21096
+ if (!matchesInstanceFilters(row, opts)) continue;
21097
+ total += 1;
21098
+ if (items.length < limit) {
21099
+ items.push(toInstanceDetail(row));
21100
+ last = row;
21101
+ } else {
21102
+ hasMore = true;
21103
+ }
21104
+ }
21105
+ const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
21106
+ if (cursor !== null) {
21107
+ const resumed = this.pageAfter(cursor, opts, limit, query);
21108
+ return Promise.resolve({
21109
+ totals: { findings: total },
21110
+ facets: accumulator.facets(),
21111
+ items: resumed.items,
21112
+ nextCursor: resumed.nextCursor
21113
+ });
21114
+ }
21115
+ return Promise.resolve({
21116
+ totals: { findings: total },
21117
+ facets: accumulator.facets(),
21118
+ items,
21119
+ nextCursor
21120
+ });
21121
+ }
21122
+ /**
21123
+ * The page of matching rows strictly after `cursor`. Separate from the
21124
+ * counting pass because that one starts at the top of the scope by design;
21125
+ * this one narrows the scan with the same keyset predicate the activity list
21126
+ * uses, so a later page costs less than the first rather than more.
21127
+ */
21128
+ pageAfter(cursor, opts, limit, query) {
21129
+ const items = [];
21130
+ let last;
21131
+ let hasMore = false;
21132
+ for (const row of this.scanFindingRows({
21133
+ sessionId: query.sessionId,
21134
+ from: query.from,
21135
+ after: cursor
21136
+ })) {
21137
+ if (!matchesInstanceFilters(row, opts)) continue;
21138
+ if (items.length < limit) {
21139
+ items.push(toInstanceDetail(row));
21140
+ last = row;
21141
+ } else {
21142
+ hasMore = true;
21143
+ break;
21144
+ }
21145
+ }
21146
+ return {
21147
+ items,
21148
+ nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
21149
+ };
21150
+ }
21151
+ /**
21152
+ * The same findings folded by location: repository, then file within it.
21153
+ *
21154
+ * The grouping keys come from the capturing event's attributes, which is what
21155
+ * the local store relates a finding to — there is no finding↔asset row to
21156
+ * group by instead. A repo or file the event did not record folds into the
21157
+ * empty-string bucket, which the view renders but does not link, since no
21158
+ * filter can name it.
21159
+ */
21160
+ listFindingLocations(query) {
21161
+ const opts = {
21162
+ severity: query.severity,
21163
+ subtype: query.subtype,
21164
+ providers: query.provider,
21165
+ actions: query.action,
21166
+ statuses: query.status,
21167
+ tools: query.tool,
21168
+ q: query.q
21169
+ };
21170
+ const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
21171
+ const byRepo = /* @__PURE__ */ new Map();
21172
+ let total = 0;
21173
+ for (const row of this.scanFindingRows({
21174
+ sessionId: query.sessionId,
21175
+ from: query.from
21176
+ })) {
21177
+ if (!matchesInstanceFilters(row, opts)) continue;
21178
+ total += 1;
21179
+ let files = byRepo.get(row.repo);
21180
+ if (files === void 0) {
21181
+ files = /* @__PURE__ */ new Map();
21182
+ byRepo.set(row.repo, files);
21183
+ }
21184
+ let acc = files.get(row.file);
21185
+ if (acc === void 0) {
21186
+ acc = newLocationAccumulator();
21187
+ files.set(row.file, acc);
21188
+ }
21189
+ addToLocation(acc, row);
21190
+ }
21191
+ let fileCount = 0;
21192
+ const repos = [...byRepo.entries()].map(([repo, files]) => {
21193
+ fileCount += files.size;
21194
+ const fileRows = [...files.entries()].map(([file2, acc]) => ({
21195
+ file: file2,
21196
+ instanceCount: acc.instanceCount,
21197
+ maxSeverity: acc.maxSeverity,
21198
+ latestDetectedAt: acc.latestDetectedAt,
21199
+ ...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
21200
+ ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
21201
+ })).sort(compareLocationOrder);
21202
+ const rollup = fileRows.reduce(
21203
+ (a, f) => ({
21204
+ instanceCount: a.instanceCount + f.instanceCount,
21205
+ maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
21206
+ latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
21207
+ }),
21208
+ {
21209
+ instanceCount: 0,
21210
+ maxSeverity: fileRows[0]?.maxSeverity ?? "low",
21211
+ latestDetectedAt: ""
21212
+ }
21213
+ );
21214
+ const statuses = fileRows.map((f) => f.status);
21215
+ const folded = foldGroupStatus(statuses);
21216
+ return {
21217
+ repo,
21218
+ instanceCount: rollup.instanceCount,
21219
+ maxSeverity: rollup.maxSeverity,
21220
+ latestDetectedAt: rollup.latestDetectedAt,
21221
+ ...folded === void 0 ? {} : { status: folded },
21222
+ files: fileRows
21223
+ };
21224
+ });
21225
+ repos.sort(compareLocationOrder);
21226
+ return Promise.resolve({
21227
+ totals: { findings: total, repos: repos.length, files: fileCount },
21228
+ items: repos.slice(0, limit),
21229
+ hasMore: repos.length > limit
21230
+ });
21231
+ }
21232
+ /**
21233
+ * Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
21234
+ *
21235
+ * A generator so a caller streams the scope without it ever being an array:
21236
+ * the flat list counts and facets the whole filtered scope, which on a large
21237
+ * store is far more rows than any page. Each batch advances the same keyset
21238
+ * predicate the page read uses, so the scan is a sequence of bounded reads
21239
+ * rather than one unbounded result set.
21240
+ *
21241
+ * The latest-resolution lookup is the CORRELATED form, not the derived table
21242
+ * the grouped path joins: only `status` is needed, idx_finding_resolution_key
21243
+ * makes it a point lookup per row, and the derived table would re-materialize
21244
+ * a window over the whole resolution table once per batch.
21245
+ *
21246
+ * `scope` carries ONLY what no facet counts. A filter dimension narrowed here
21247
+ * would be missing from its own facet, which is computed by excluding that
21248
+ * dimension — see listFindingInstances.
21249
+ */
21250
+ *scanFindingRows(scope) {
21251
+ const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
21252
+ const params = [];
21253
+ if (scope.sessionId !== void 0 && scope.sessionId !== "") {
21254
+ conditions.push("e.root_session_id = ?");
21255
+ params.push(scope.sessionId);
21256
+ }
21257
+ if (scope.from !== void 0) {
21258
+ conditions.push("e.started_at >= ?");
21259
+ params.push(isoToEpochMillis(scope.from));
21260
+ }
21261
+ const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
21262
+ d.severity AS severity, f.masked_match AS masked_match,
21263
+ f.action_taken AS action_taken, f.confidence AS confidence,
21264
+ e.started_at AS occurred_at,
21265
+ json_extract(e.attributes, '$.source_tool') AS source_tool,
21266
+ json_extract(e.attributes, '$.repo') AS repo,
21267
+ json_extract(e.attributes, '$.file_path') AS file,
21268
+ json_extract(e.attributes, '$.tool_name') AS tool_name,
21269
+ f.audit_event_id AS event_id, e.root_session_id AS session_id,
21270
+ e.event_type AS kind, f.finding_key AS finding_key,
21271
+ ${latestResolutionStatusSql("f")} AS latest_status
21272
+ FROM inspection_findings f
21273
+ JOIN audit_events e ON e.id = f.audit_event_id
21274
+ JOIN inspection_definitions d ON d.id = f.inspection_definition_id
21275
+ WHERE ${conditions.join(" AND ")}
21276
+ AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
21277
+ ORDER BY e.started_at DESC, f.id DESC
21278
+ LIMIT ?`;
21279
+ let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
21280
+ for (; ; ) {
21281
+ const rows = allRows(this.db.prepare(sql), [
21282
+ ...params,
21283
+ after.startedAtMs,
21284
+ after.startedAtMs,
21285
+ after.id,
21286
+ SCAN_BATCH_ROWS
21287
+ ]);
21288
+ for (const r of rows) {
21289
+ yield {
21290
+ id: r.id,
21291
+ ruleId: r.rule_id,
21292
+ category: r.category,
21293
+ severity: r.severity,
21294
+ maskedMatch: r.masked_match,
21295
+ actionTaken: r.action_taken,
21296
+ confidence: r.confidence,
21297
+ occurredAt: epochMillisToIso(r.occurred_at),
21298
+ sourceTool: r.source_tool,
21299
+ repo: r.repo ?? "",
21300
+ file: r.file ?? "",
21301
+ ...r.tool_name === null ? {} : { toolName: r.tool_name },
21302
+ eventId: r.event_id,
21303
+ ...r.session_id === null ? {} : { sessionId: r.session_id },
21304
+ status: deriveInstanceStatus(r)
21305
+ };
21306
+ }
21307
+ if (rows.length < SCAN_BATCH_ROWS) return;
21308
+ const lastRow = rows[rows.length - 1];
21309
+ if (lastRow === void 0) return;
21310
+ after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
21311
+ }
21312
+ }
20515
21313
  groupAggregates(withSearchText, scope) {
20516
21314
  const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
20517
21315
  group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
@@ -20772,7 +21570,7 @@ var SqliteInspectionFindingsRepository = class {
20772
21570
  };
20773
21571
 
20774
21572
  // ../../packages/persistence/src/repositories/installed-packs.ts
20775
- import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
21573
+ import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
20776
21574
 
20777
21575
  // ../../packages/persistence/src/semver.ts
20778
21576
  function parse3(version2) {
@@ -20923,7 +21721,7 @@ var SqliteInstalledPacksRepository = class {
20923
21721
  let behind = false;
20924
21722
  for (const row of rows) {
20925
21723
  const params = {
20926
- id: randomUUID2(),
21724
+ id: randomUUID3(),
20927
21725
  namespace: row.namespace,
20928
21726
  packId: row.packId,
20929
21727
  version: row.version,
@@ -20935,7 +21733,7 @@ var SqliteInstalledPacksRepository = class {
20935
21733
  if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
20936
21734
  this.upsertAvailableStmt.run({
20937
21735
  ...params,
20938
- id: randomUUID2(),
21736
+ id: randomUUID3(),
20939
21737
  recordedBy: meta3?.recordedBy ?? null
20940
21738
  });
20941
21739
  } else {
@@ -21258,14 +22056,15 @@ var SqliteInventoryRepository = class {
21258
22056
  };
21259
22057
 
21260
22058
  // ../../packages/persistence/src/repositories/inventory-assets.ts
21261
- import { randomUUID as randomUUID3 } from "crypto";
22059
+ import { randomUUID as randomUUID4 } from "crypto";
21262
22060
  var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
21263
22061
  var VALID_HARNESS_IDS = new Set(HarnessId.options);
21264
22062
  var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
21265
22063
  var HARNESS_LABELS = {
21266
22064
  claudecode: "Claude Code",
21267
22065
  cursor: "Cursor",
21268
- codex: "Codex"
22066
+ codex: "Codex",
22067
+ antigravity: "Antigravity"
21269
22068
  };
21270
22069
  var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
21271
22070
  var EMPTY_PROJECT_AGG = {
@@ -21280,6 +22079,7 @@ function resolveHarnessId(attrs, row) {
21280
22079
  if (t.includes("claudecode") || t === "claude") return "claudecode";
21281
22080
  if (t.includes("cursor")) return "cursor";
21282
22081
  if (t.includes("codex")) return "codex";
22082
+ if (t.includes("antigravity")) return "antigravity";
21283
22083
  return null;
21284
22084
  }
21285
22085
  function isLiveRealClaudeCode(rows) {
@@ -21738,7 +22538,7 @@ var SqliteInventoryAssetsRepository = class {
21738
22538
  `INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
21739
22539
  VALUES (:id, :projectId, :path, :access, :now, :now)
21740
22540
  ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
21741
- ).run({ id: randomUUID3(), projectId, path, access, now: Date.now() });
22541
+ ).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
21742
22542
  }
21743
22543
  return true;
21744
22544
  }
@@ -21759,7 +22559,7 @@ var SqliteInventoryAssetsRepository = class {
21759
22559
  `INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
21760
22560
  VALUES (:id, :assetId, :trust, :now, :now)
21761
22561
  ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
21762
- ).run({ id: randomUUID3(), assetId, trust, now: Date.now() });
22562
+ ).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
21763
22563
  }
21764
22564
  this.configRowsCache = void 0;
21765
22565
  return "ok";
@@ -22056,7 +22856,7 @@ var SqliteInventoryAssetsRepository = class {
22056
22856
  };
22057
22857
 
22058
22858
  // ../../packages/persistence/src/repositories/policies.ts
22059
- import { randomUUID as randomUUID4 } from "crypto";
22859
+ import { randomUUID as randomUUID5 } from "crypto";
22060
22860
  var SqlitePoliciesRepository = class {
22061
22861
  constructor(db) {
22062
22862
  this.db = db;
@@ -22091,7 +22891,7 @@ var SqlitePoliciesRepository = class {
22091
22891
  failOpenTransaction(this.db, () => {
22092
22892
  for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
22093
22893
  stmt.run({
22094
- id: randomUUID4(),
22894
+ id: randomUUID5(),
22095
22895
  target: JSON.stringify({ category }),
22096
22896
  action,
22097
22897
  now: Date.now()
@@ -22111,7 +22911,7 @@ var SqlitePoliciesRepository = class {
22111
22911
  `INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
22112
22912
  VALUES (:id, 'global', :target, :action, 1, :now, :now)
22113
22913
  ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
22114
- ).run({ id: randomUUID4(), target: JSON.stringify({ category }), action, now });
22914
+ ).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
22115
22915
  }
22116
22916
  // Caps every global per-category policy currently set to block/redact down
22117
22917
  // to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
@@ -22179,7 +22979,7 @@ var SqlitePolicyCatalogRepository = class {
22179
22979
  };
22180
22980
 
22181
22981
  // ../../packages/persistence/src/repositories/project-files.ts
22182
- import { randomUUID as randomUUID5 } from "crypto";
22982
+ import { randomUUID as randomUUID6 } from "crypto";
22183
22983
  var SqliteProjectFilesRepository = class {
22184
22984
  constructor(db) {
22185
22985
  this.db = db;
@@ -22211,7 +23011,7 @@ var SqliteProjectFilesRepository = class {
22211
23011
  const stamp = Math.max(now, maxStamp + 1);
22212
23012
  for (const file2 of scan2.files) {
22213
23013
  this.upsertStmt.run({
22214
- id: randomUUID5(),
23014
+ id: randomUUID6(),
22215
23015
  projectId,
22216
23016
  path: file2.path,
22217
23017
  name: file2.name,
@@ -22225,7 +23025,7 @@ var SqliteProjectFilesRepository = class {
22225
23025
  };
22226
23026
 
22227
23027
  // ../../packages/persistence/src/repositories/resolutions.ts
22228
- import { randomUUID as randomUUID6 } from "crypto";
23028
+ import { randomUUID as randomUUID7 } from "crypto";
22229
23029
  var SqliteResolutionsRepository = class {
22230
23030
  constructor(db, now = () => Date.now()) {
22231
23031
  this.db = db;
@@ -22279,7 +23079,7 @@ var SqliteResolutionsRepository = class {
22279
23079
  */
22280
23080
  insertResolution(r) {
22281
23081
  this.insertStmt.run({
22282
- id: randomUUID6(),
23082
+ id: randomUUID7(),
22283
23083
  findingKey: r.findingKey,
22284
23084
  status: FindingStatus.parse(r.status),
22285
23085
  method: ResolutionMethod.parse(r.method),
@@ -22338,13 +23138,51 @@ var SqliteRuleProbeCacheRepository = class {
22338
23138
  this.readStmt = db.prepare(
22339
23139
  `SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
22340
23140
  );
23141
+ this.countQuarantinedStmt = db.prepare(
23142
+ `SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
23143
+ );
23144
+ this.clearQuarantinedStmt = db.prepare(
23145
+ `DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
23146
+ );
22341
23147
  }
22342
23148
  db;
22343
23149
  upsertStmt;
22344
23150
  readStmt;
23151
+ countQuarantinedStmt;
23152
+ clearQuarantinedStmt;
22345
23153
  getVerdict(ruleKey) {
22346
23154
  return getRow(this.readStmt, { ruleKey });
22347
23155
  }
23156
+ /** How many rules are currently excluded by a cached quarantine verdict. */
23157
+ countQuarantined() {
23158
+ return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
23159
+ }
23160
+ /**
23161
+ * Forgets every quarantine verdict, so the rules behind them are measured
23162
+ * again on the next load. This is the undo for a verdict the machine reached
23163
+ * on its own: a rule terminated mid-scan is cached forever and dropped from
23164
+ * every later scan, and a timing verdict is a wall-clock judgement that a
23165
+ * loaded or slow machine can reach about a rule that is in fact fine.
23166
+ *
23167
+ * Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
23168
+ * keeping, and dropping it would make every rule pay the battery again.
23169
+ *
23170
+ * Reports `refused` from the write's own result rather than inferring it from
23171
+ * the row count. The two are NOT the same answer: `failOpenTransaction`
23172
+ * swallows a contended DELETE (another writer holding the lock past
23173
+ * `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
23174
+ * leaves the count unchanged, which is indistinguishable from "there was
23175
+ * nothing to clear". An undo that reports success while the quarantines are
23176
+ * still in place is worse than one that fails, because the rules it claimed
23177
+ * to restore are silently still disabled.
23178
+ */
23179
+ clearQuarantined() {
23180
+ const before = this.countQuarantined();
23181
+ const committed = failOpenTransaction(this.db, () => {
23182
+ this.clearQuarantinedStmt.run();
23183
+ });
23184
+ return { refused: !committed, cleared: before - this.countQuarantined() };
23185
+ }
22348
23186
  setVerdict(ruleKey, verdict, worstProbeMs) {
22349
23187
  failOpenTransaction(this.db, () => {
22350
23188
  this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
@@ -22399,7 +23237,39 @@ var SqliteScanLedgerRepository = class {
22399
23237
  };
22400
23238
 
22401
23239
  // ../../packages/persistence/src/repositories/secret-vault.ts
22402
- import { randomUUID as randomUUID7 } from "crypto";
23240
+ import { randomUUID as randomUUID8 } from "crypto";
23241
+ function pageLimit(requested, fallback) {
23242
+ if (requested === void 0) return fallback;
23243
+ return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
23244
+ }
23245
+ function encodeReuseCursor(payload) {
23246
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
23247
+ }
23248
+ function decodeReuseCursor(cursor) {
23249
+ const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
23250
+ if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
23251
+ // ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
23252
+ // null cursor, which the caller reads as "end of list" — the one outcome a
23253
+ // malformed cursor must never produce, since restarting from the top is the
23254
+ // documented behaviour and the only recoverable one. (`1e999` is valid JSON
23255
+ // and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
23256
+ Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
23257
+ return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
23258
+ }
23259
+ return null;
23260
+ }
23261
+ var REUSED_PREDICATE = `(v.occurrence_count > 1
23262
+ OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
23263
+ var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
23264
+ v.occurrence_count, v.first_seen, v.last_seen`;
23265
+ function toSighting(row) {
23266
+ return {
23267
+ location: row.location,
23268
+ kind: row.kind,
23269
+ firstSeen: new Date(row.first_seen).toISOString(),
23270
+ lastSeen: new Date(row.last_seen).toISOString()
23271
+ };
23272
+ }
22403
23273
  var SELECT_COLUMNS = `
22404
23274
  pointer_id AS pointerId,
22405
23275
  value_fingerprint AS valueFingerprint,
@@ -22583,39 +23453,67 @@ var SqliteSecretVaultRepository = class {
22583
23453
  VALUES (:id, :pointerId, :location, :kind, :now, :now)
22584
23454
  ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
22585
23455
  ).run({
22586
- id: randomUUID7(),
23456
+ id: randomUUID8(),
22587
23457
  pointerId: entry.pointerId,
22588
23458
  location: entry.location,
22589
23459
  kind: entry.kind,
22590
23460
  now
22591
23461
  });
22592
23462
  }
22593
- listSightings(pointerId) {
23463
+ /**
23464
+ * Sightings for a whole page of pointers, in ONE query grouped in JS rather
23465
+ * than one query per row. A pointer with no sightings still gets an entry, so
23466
+ * the caller never has to distinguish "none" from "missing".
23467
+ *
23468
+ * The `IN` list is sized to the page, so this statement cannot be cached on
23469
+ * the instance the way the fixed-shape ones in the constructor are.
23470
+ */
23471
+ sightingsFor(pointerIds) {
23472
+ const byPointer = new Map(pointerIds.map((id) => [id, []]));
23473
+ if (pointerIds.length === 0) return byPointer;
22594
23474
  const rows = allRows(
22595
23475
  this.db.prepare(
22596
- `SELECT location, kind, first_seen, last_seen FROM secret_vault_sighting
22597
- WHERE pointer_id = :pointerId ORDER BY last_seen DESC`
23476
+ `SELECT pointer_id, location, kind, first_seen, last_seen
23477
+ FROM secret_vault_sighting
23478
+ WHERE pointer_id IN (${placeholders(pointerIds.length)})
23479
+ ORDER BY last_seen DESC`
22598
23480
  ),
22599
- { pointerId }
23481
+ pointerIds
22600
23482
  );
23483
+ for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
23484
+ return byPointer;
23485
+ }
23486
+ /** Hydrate a page of raw inventory rows with their sightings, batched. */
23487
+ toInventoryEntries(rows) {
23488
+ const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
22601
23489
  return rows.map((r) => ({
22602
- location: r.location,
22603
- kind: r.kind,
23490
+ pointerId: r.pointer_id,
23491
+ category: r.category,
23492
+ ...r.provider === null ? {} : { provider: r.provider },
23493
+ maskedMatch: r.masked_match,
23494
+ occurrences: r.occurrence_count,
22604
23495
  firstSeen: new Date(r.first_seen).toISOString(),
22605
- lastSeen: new Date(r.last_seen).toISOString()
23496
+ lastSeen: new Date(r.last_seen).toISOString(),
23497
+ revealGrantId: r.grant_id,
23498
+ sightings: sightings.get(r.pointer_id) ?? []
22606
23499
  }));
22607
23500
  }
22608
23501
  /**
22609
- * The dashboard inventory: every vaulted value's descriptor data joined with
22610
- * its sightings and the active reveal-to-model grant when one exists.
22611
- * Raw-free by construction — neither the fingerprint nor the ciphertext
22612
- * columns are selected.
23502
+ * The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
23503
+ * value's descriptor data joined with its sightings and the active
23504
+ * reveal-to-model grant when one exists. Raw-free by construction — neither
23505
+ * the fingerprint nor the ciphertext columns are selected.
23506
+ *
23507
+ * `totals.values` counts the whole store, not the page, so the count a reader
23508
+ * sees never depends on how far they have paged.
22613
23509
  */
22614
- listInventory(now = Date.now()) {
23510
+ listInventory(query = {}, now = Date.now()) {
23511
+ const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
23512
+ const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
23513
+ const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
22615
23514
  const rows = allRows(
22616
23515
  this.db.prepare(
22617
- `SELECT v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
22618
- v.occurrence_count, v.first_seen, v.last_seen,
23516
+ `SELECT ${INVENTORY_COLUMNS},
22619
23517
  (SELECT e.id FROM exceptions e
22620
23518
  WHERE e.rule_id = v.rule_id
22621
23519
  AND e.value_fingerprint = v.value_fingerprint
@@ -22623,45 +23521,109 @@ var SqliteSecretVaultRepository = class {
22623
23521
  AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
22624
23522
  LIMIT 1) AS grant_id
22625
23523
  FROM secret_vault v
22626
- ORDER BY v.last_seen DESC`
23524
+ ${where}
23525
+ ORDER BY v.last_seen DESC, v.pointer_id DESC
23526
+ LIMIT :limit`
22627
23527
  ),
22628
- { now }
23528
+ bindParams({
23529
+ now,
23530
+ limit: limit + 1,
23531
+ ...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
23532
+ })
22629
23533
  );
22630
- return rows.map((r) => ({
22631
- pointerId: r.pointer_id,
22632
- category: r.category,
22633
- ...r.provider === null ? {} : { provider: r.provider },
22634
- maskedMatch: r.masked_match,
22635
- occurrences: r.occurrence_count,
22636
- firstSeen: new Date(r.first_seen).toISOString(),
22637
- lastSeen: new Date(r.last_seen).toISOString(),
22638
- revealGrantId: r.grant_id,
22639
- sightings: this.listSightings(r.pointer_id)
22640
- }));
23534
+ const hasMore = rows.length > limit;
23535
+ const page = hasMore ? rows.slice(0, limit) : rows;
23536
+ const last = page[page.length - 1];
23537
+ return {
23538
+ totals: { values: this.countEntries() },
23539
+ items: this.toInventoryEntries(page),
23540
+ // Minted from the last row of the PAGE, never the extra probe row.
23541
+ nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
23542
+ };
22641
23543
  }
22642
23544
  /**
22643
- * The de-reference trail, newest first. By default the batched, high-volume
22644
- * reasons (display, view-render) are hidden and counted instead the rows
22645
- * that matter as a signal are the model crossings, and burying them under
22646
- * render noise would defeat the audit's purpose.
23545
+ * Values reused on this machine detected more than once, or written to more
23546
+ * than one location most-reused first, one page at a time.
23547
+ *
23548
+ * Its own read rather than a filter over an inventory page: reuse is a
23549
+ * property of the whole store, and deriving it from 50 newest rows would
23550
+ * under-report exactly the values a reader most needs to see.
22647
23551
  */
22648
- listDerefs(opts) {
22649
- const limit = opts?.limit ?? 200;
22650
- const where = opts?.includeBatched === true ? "" : `WHERE reason NOT IN ('display', 'view-render')`;
23552
+ listReuse(query = {}, now = Date.now()) {
23553
+ const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
23554
+ const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
23555
+ const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
23556
+ OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
23557
+ const rows = allRows(
23558
+ this.db.prepare(
23559
+ `SELECT ${INVENTORY_COLUMNS},
23560
+ (SELECT e.id FROM exceptions e
23561
+ WHERE e.rule_id = v.rule_id
23562
+ AND e.value_fingerprint = v.value_fingerprint
23563
+ AND e.key_version = v.fingerprint_key_version
23564
+ AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
23565
+ LIMIT 1) AS grant_id
23566
+ FROM secret_vault v
23567
+ WHERE ${REUSED_PREDICATE} ${after}
23568
+ ORDER BY v.occurrence_count DESC, v.pointer_id DESC
23569
+ LIMIT :limit`
23570
+ ),
23571
+ bindParams({
23572
+ now,
23573
+ limit: limit + 1,
23574
+ ...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
23575
+ })
23576
+ );
23577
+ const hasMore = rows.length > limit;
23578
+ const page = hasMore ? rows.slice(0, limit) : rows;
23579
+ const last = page[page.length - 1];
23580
+ return {
23581
+ totals: { reused: this.countReused() },
23582
+ items: this.toInventoryEntries(page),
23583
+ nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
23584
+ };
23585
+ }
23586
+ /**
23587
+ * The de-reference trail, newest first, one page at a time. By default the
23588
+ * batched, high-volume reasons (display, view-render) are hidden and counted
23589
+ * instead — the rows that matter as a signal are the model crossings, and
23590
+ * burying them under render noise would defeat the audit's purpose.
23591
+ *
23592
+ * `hiddenBatched` counts the whole trail rather than the page: it is what the
23593
+ * view's "N hidden" line and its toggle speak for, so it must not shrink as
23594
+ * the reader pages.
23595
+ */
23596
+ listDerefs(query = {}) {
23597
+ const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
23598
+ const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
23599
+ const conditions = [];
23600
+ if (query.includeBatched !== true) {
23601
+ conditions.push(`reason NOT IN ('display', 'view-render')`);
23602
+ }
23603
+ if (cursor !== null) {
23604
+ conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
23605
+ }
23606
+ const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
22651
23607
  const rows = allRows(
22652
23608
  this.db.prepare(
22653
23609
  `SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
22654
23610
  FROM secret_vault_deref ${where}
22655
- ORDER BY at DESC, rowid DESC LIMIT :limit`
23611
+ ORDER BY at DESC, id DESC LIMIT :limit`
22656
23612
  ),
22657
- { limit }
23613
+ bindParams({
23614
+ limit: limit + 1,
23615
+ ...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
23616
+ })
22658
23617
  );
22659
- const hiddenBatched = opts?.includeBatched === true ? 0 : countScalar(
23618
+ const hasMore = rows.length > limit;
23619
+ const page = hasMore ? rows.slice(0, limit) : rows;
23620
+ const last = page[page.length - 1];
23621
+ const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
22660
23622
  this.db,
22661
23623
  `SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
22662
23624
  );
22663
23625
  return {
22664
- rows: rows.map((r) => ({
23626
+ items: page.map((r) => ({
22665
23627
  id: r.id,
22666
23628
  pointerId: r.pointer_id,
22667
23629
  at: new Date(r.at).toISOString(),
@@ -22671,12 +23633,20 @@ var SqliteSecretVaultRepository = class {
22671
23633
  ...r.grant_id === null ? {} : { grantId: r.grant_id },
22672
23634
  pointerCount: r.pointer_count
22673
23635
  })),
23636
+ nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
22674
23637
  hiddenBatched
22675
23638
  };
22676
23639
  }
22677
23640
  countEntries() {
22678
23641
  return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
22679
23642
  }
23643
+ /** Values reused on this machine — the reuse list's page-independent total. */
23644
+ countReused() {
23645
+ return countScalar(
23646
+ this.db,
23647
+ `SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
23648
+ );
23649
+ }
22680
23650
  };
22681
23651
 
22682
23652
  // ../../packages/persistence/src/repositories/security.ts
@@ -22691,7 +23661,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
22691
23661
  var SCAN_COVERAGE = [
22692
23662
  { provider: "claudecode", coverage: 100, supported: true },
22693
23663
  { provider: "cursor", coverage: 0, supported: false },
22694
- { provider: "codex", coverage: 0, supported: false },
23664
+ { provider: "codex", coverage: 80, supported: true },
23665
+ { provider: "antigravity", coverage: 60, supported: true },
23666
+ { provider: "claudeai", coverage: 0, supported: false },
22695
23667
  { provider: "chatgpt", coverage: 0, supported: false },
22696
23668
  { provider: "copilot", coverage: 0, supported: false },
22697
23669
  { provider: "api", coverage: 0, supported: false }
@@ -23024,7 +23996,7 @@ var SqliteSecurityRepository = class {
23024
23996
  };
23025
23997
 
23026
23998
  // ../../packages/persistence/src/repositories/shares.ts
23027
- import { randomUUID as randomUUID8 } from "crypto";
23999
+ import { randomUUID as randomUUID9 } from "crypto";
23028
24000
  var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
23029
24001
  var IN_CHUNK = 500;
23030
24002
  var KIND_ORDER = ["provider", "internal", "external", "ip"];
@@ -23280,7 +24252,7 @@ var SqliteSharesRepository = class {
23280
24252
  (id, destination_id, host, decision, created_at, updated_at)
23281
24253
  VALUES (:id, :destinationId, :host, :decision, :now, :now)`
23282
24254
  ).run({
23283
- id: randomUUID8(),
24255
+ id: randomUUID9(),
23284
24256
  destinationId,
23285
24257
  host: dest.host,
23286
24258
  decision,
@@ -23429,7 +24401,7 @@ var SqliteSharesRepository = class {
23429
24401
  let destinationId = destIds.get(hit.host);
23430
24402
  if (destinationId === void 0) {
23431
24403
  destStmt.run({
23432
- id: randomUUID8(),
24404
+ id: randomUUID9(),
23433
24405
  kind: hit.kind,
23434
24406
  name: hit.name,
23435
24407
  host: hit.host,
@@ -23445,7 +24417,7 @@ var SqliteSharesRepository = class {
23445
24417
  let endpointId = endpointIds.get(endpointKey);
23446
24418
  if (endpointId === void 0) {
23447
24419
  endpointStmt.run({
23448
- id: randomUUID8(),
24420
+ id: randomUUID9(),
23449
24421
  destinationId,
23450
24422
  method: hit.method,
23451
24423
  transport: hit.transport,
@@ -23458,7 +24430,7 @@ var SqliteSharesRepository = class {
23458
24430
  endpointIds.set(endpointKey, endpointId);
23459
24431
  }
23460
24432
  siteStmt.run({
23461
- id: randomUUID8(),
24433
+ id: randomUUID9(),
23462
24434
  endpointId,
23463
24435
  project: input.project,
23464
24436
  projectKey: input.projectKey,
@@ -23823,6 +24795,9 @@ function purgeSampleData(db) {
23823
24795
  }
23824
24796
 
23825
24797
  // ../../packages/persistence/src/database.ts
24798
+ var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
24799
+ "aka.persistence.unsafeTestOnlyRawHandle"
24800
+ );
23826
24801
  function linkHost(input, hostId) {
23827
24802
  return hostId ? { ...input, hostId } : input;
23828
24803
  }
@@ -23844,21 +24819,34 @@ function openWithPragmas(file2) {
23844
24819
  }
23845
24820
  return db;
23846
24821
  }
23847
- function backupLegacyStore(file2) {
23848
- const backup = `${file2}.legacy.${String(Date.now())}.bak`;
23849
- renameSync2(file2, backup);
23850
- tightenFile(backup);
23851
- for (const sidecar of dbSidecars(file2)) {
23852
- if (existsSync(sidecar)) rmSync2(sidecar);
24822
+ function backupLegacyStore(db, file2) {
24823
+ reapStalePartials(file2);
24824
+ const backup = backupPath(file2, "legacy");
24825
+ let snapshotted = false;
24826
+ let snapshotError;
24827
+ try {
24828
+ snapshotStore(db, backup);
24829
+ snapshotted = true;
24830
+ } catch (error51) {
24831
+ snapshotError = error51;
24832
+ } finally {
24833
+ db.close();
23853
24834
  }
24835
+ if (!snapshotted) {
24836
+ akaWarn(
24837
+ `Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
24838
+ );
24839
+ moveStoreAside(file2, backup);
24840
+ return backup;
24841
+ }
24842
+ discardStore(file2, backup);
23854
24843
  return backup;
23855
24844
  }
23856
24845
  function openAndInitialize(file2) {
23857
24846
  let db = openWithPragmas(file2);
23858
24847
  try {
23859
24848
  if (isForeignSqliteLineage(db)) {
23860
- db.close();
23861
- const backup = backupLegacyStore(file2);
24849
+ const backup = backupLegacyStore(db, file2);
23862
24850
  db = openWithPragmas(file2);
23863
24851
  akaWarn(
23864
24852
  `Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
@@ -23902,7 +24890,7 @@ function openAndInitialize(file2) {
23902
24890
  }
23903
24891
  function openLocalDatabase(dir) {
23904
24892
  ensureDataDirSync(dir);
23905
- const file2 = join(dir, DB_FILENAME);
24893
+ const file2 = join2(dir, DB_FILENAME);
23906
24894
  const {
23907
24895
  db,
23908
24896
  events,
@@ -24019,7 +25007,7 @@ function openLocalDatabase(dir) {
24019
25007
  const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
24020
25008
  if (!definitionId) continue;
24021
25009
  inspectionFindings.insertFinding({
24022
- id: randomUUID9(),
25010
+ id: randomUUID10(),
24023
25011
  auditEventId: record2.scanEvent.id,
24024
25012
  inspectionDefinitionId: definitionId,
24025
25013
  span: finding.span,
@@ -24125,22 +25113,38 @@ function openLocalDatabase(dir) {
24125
25113
  transaction,
24126
25114
  close: () => {
24127
25115
  db.close();
24128
- }
25116
+ },
25117
+ // Last, and a plain value rather than a getter, so `{ ...db }` carries it.
25118
+ [UNSAFE_TEST_ONLY_RAW_HANDLE]: db
24129
25119
  };
24130
25120
  }
24131
25121
 
25122
+ // ../../packages/persistence/src/file-lock.ts
25123
+ import { randomUUID as randomUUID11 } from "crypto";
25124
+ import {
25125
+ closeSync,
25126
+ existsSync as existsSync2,
25127
+ openSync,
25128
+ readFileSync,
25129
+ rmSync as rmSync3,
25130
+ statSync as statSync2,
25131
+ writeFileSync as writeFileSync2
25132
+ } from "fs";
25133
+ import { hostname as hostname3 } from "os";
25134
+ var PARK = new Int32Array(new SharedArrayBuffer(4));
25135
+
24132
25136
  // ../../packages/persistence/src/finding-key.ts
24133
25137
  import { createHash as createHash3 } from "crypto";
24134
25138
 
24135
25139
  // ../../packages/persistence/src/fingerprint.ts
24136
25140
  import { createHmac, randomBytes } from "crypto";
24137
- import { existsSync as existsSync2, readFileSync } from "fs";
24138
- import { join as join2 } from "path";
25141
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
25142
+ import { join as join3 } from "path";
24139
25143
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
24140
- var KEY_FILENAME = "exception.key";
25144
+ var EXCEPTION_KEY_FILENAME = "exception.key";
24141
25145
  var KEY_MATERIAL_BYTES = 32;
24142
25146
  function keyFilePath(dataDir2) {
24143
- return join2(dataDir2, KEY_FILENAME);
25147
+ return join3(dataDir2, EXCEPTION_KEY_FILENAME);
24144
25148
  }
24145
25149
  function parseKeyFile(raw) {
24146
25150
  const parsed = JSON.parse(raw);
@@ -24163,7 +25167,7 @@ function parseKeyFile(raw) {
24163
25167
  function readFingerprintKey(dataDir2) {
24164
25168
  let raw;
24165
25169
  try {
24166
- raw = readFileSync(keyFilePath(dataDir2), "utf8");
25170
+ raw = readFileSync2(keyFilePath(dataDir2), "utf8");
24167
25171
  } catch (err) {
24168
25172
  if (err.code === "ENOENT") return null;
24169
25173
  throw err instanceof Error ? err : new Error(String(err));
@@ -24175,18 +25179,18 @@ function readFingerprintKey(dataDir2) {
24175
25179
  import { renameSync as renameSync3 } from "fs";
24176
25180
  import { mkdir } from "fs/promises";
24177
25181
  import { homedir } from "os";
24178
- import { join as join3 } from "path";
25182
+ import { join as join4 } from "path";
24179
25183
  function defaultDataDir() {
24180
- return join3(homedir(), ".aka");
25184
+ return join4(homedir(), ".aka");
24181
25185
  }
24182
25186
  function settingsDir(base = defaultDataDir()) {
24183
- return join3(base, "settings");
25187
+ return join4(base, "settings");
24184
25188
  }
24185
25189
  function dataDir(base = defaultDataDir()) {
24186
- return join3(base, "data");
25190
+ return join4(base, "data");
24187
25191
  }
24188
25192
  function dbPath(base = defaultDataDir()) {
24189
- return join3(dataDir(base), "aka.db");
25193
+ return join4(dataDir(base), "aka.db");
24190
25194
  }
24191
25195
  function ensureLayoutDirSync(dir = defaultDataDir()) {
24192
25196
  ensureDataDirSync(dir);
@@ -24199,8 +25203,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
24199
25203
  for (const { name, dest } of moves) {
24200
25204
  try {
24201
25205
  ensureDataDirSync(dest);
24202
- const moved = join3(dest, name);
24203
- renameSync3(join3(base, name), moved);
25206
+ const moved = join4(dest, name);
25207
+ renameSync3(join4(base, name), moved);
24204
25208
  tightenFile(moved);
24205
25209
  } catch {
24206
25210
  }
@@ -24208,10 +25212,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
24208
25212
  }
24209
25213
 
24210
25214
  // ../../packages/persistence/src/settings.ts
24211
- import { readFileSync as readFileSync2 } from "fs";
24212
- import { join as join4 } from "path";
25215
+ import { readFileSync as readFileSync3 } from "fs";
25216
+ import { join as join5 } from "path";
25217
+ var SETTINGS_FILENAME = "settings.json";
24213
25218
  function readWorkspaceSettings(base = defaultDataDir()) {
24214
- const record2 = readJson(join4(settingsDir(base), "settings.json"));
25219
+ const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
24215
25220
  if (!record2) return defaultWorkspaceSettings();
24216
25221
  try {
24217
25222
  return WorkspaceSettings.parse(record2);
@@ -24222,7 +25227,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
24222
25227
  function readJson(file2) {
24223
25228
  let text;
24224
25229
  try {
24225
- text = readFileSync2(file2, "utf8");
25230
+ text = readFileSync3(file2, "utf8");
24226
25231
  } catch {
24227
25232
  return null;
24228
25233
  }
@@ -24244,27 +25249,27 @@ import { randomBytes as randomBytes2 } from "crypto";
24244
25249
  import {
24245
25250
  chmodSync as chmodSync2,
24246
25251
  mkdirSync as mkdirSync2,
24247
- readFileSync as readFileSync3,
25252
+ readFileSync as readFileSync4,
24248
25253
  renameSync as renameSync4,
24249
- rmSync as rmSync3,
24250
- statSync,
24251
- writeFileSync as writeFileSync2
25254
+ rmSync as rmSync4,
25255
+ statSync as statSync3,
25256
+ writeFileSync as writeFileSync3
24252
25257
  } from "fs";
24253
- import { join as join5 } from "path";
25258
+ import { join as join6 } from "path";
24254
25259
 
24255
25260
  // ../../packages/persistence/src/vault/vault.ts
24256
- import { randomBytes as randomBytes3, randomUUID as randomUUID10 } from "crypto";
25261
+ import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
24257
25262
 
24258
25263
  // ../../packages/persistence/src/warn-era-cap.ts
24259
- import { existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
24260
- import { join as join6 } from "path";
25264
+ import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
25265
+ import { join as join7 } from "path";
24261
25266
  var MARKER = "warn-era-capped";
24262
25267
  function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
24263
25268
  if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
24264
- const marker = join6(dataDir2, MARKER);
24265
- if (existsSync3(marker)) return { capped: 0, skipped: "already-run" };
25269
+ const marker = join7(dataDir2, MARKER);
25270
+ if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
24266
25271
  const capped = db.policies.capCategoryActions();
24267
- writeFileSync3(marker, `${new Date(Date.now()).toISOString()}
25272
+ writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
24268
25273
  `, { mode: DATA_FILE_MODE });
24269
25274
  return { capped };
24270
25275
  }
@@ -24318,11 +25323,11 @@ function resolveProvider() {
24318
25323
  }
24319
25324
 
24320
25325
  // ../../packages/plugin-sdk/src/config.ts
24321
- function loadConfig(base = defaultDataDir()) {
25326
+ function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
24322
25327
  try {
24323
25328
  ensureLayoutDirSync(base);
24324
- const settingsFile = join7(settingsDir(base), "settings.json");
24325
- if (existsSync4(settingsFile)) tightenFile(settingsFile);
25329
+ const settingsFile = join8(settingsDir(base), "settings.json");
25330
+ if (existsSync5(settingsFile)) tightenFile(settingsFile);
24326
25331
  } catch {
24327
25332
  }
24328
25333
  migrateLegacyLayout(base);
@@ -24333,21 +25338,21 @@ function loadConfig(base = defaultDataDir()) {
24333
25338
  dbPath: dbPath(base),
24334
25339
  settingsDir: settingsDir(base),
24335
25340
  onboarded: settings.onboardedAt != null,
24336
- provider: resolveProviderSafe()
25341
+ provider: resolveProviderSafe(resolveProviderFn)
24337
25342
  };
24338
25343
  }
24339
- function resolveProviderSafe() {
25344
+ function resolveProviderSafe(resolveProviderFn) {
24340
25345
  try {
24341
- return resolveProvider();
25346
+ return resolveProviderFn();
24342
25347
  } catch {
24343
25348
  return { provider: "anthropic" };
24344
25349
  }
24345
25350
  }
24346
25351
 
24347
25352
  // ../../packages/plugin-sdk/src/config-inventory.ts
24348
- import { readdirSync, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
25353
+ import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
24349
25354
  import { homedir as homedir2 } from "os";
24350
- import { basename as basename2, join as join9 } from "path";
25355
+ import { basename as basename3, join as join10 } from "path";
24351
25356
 
24352
25357
  // ../../packages/detections/src/egress/registry.ts
24353
25358
  var EXTRACTOR_VERSION = "1";
@@ -26711,7 +27716,7 @@ var gcp_service_account_default = {
26711
27716
  severity: "critical",
26712
27717
  matcher: {
26713
27718
  type: "regex",
26714
- pattern: "\\b[a-z0-9][a-z0-9-]{2,60}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
27719
+ pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
26715
27720
  flags: "g"
26716
27721
  },
26717
27722
  examples: [
@@ -27083,40 +28088,71 @@ function bundledDetections() {
27083
28088
  }
27084
28089
 
27085
28090
  // ../../packages/plugin-sdk/src/repo.ts
27086
- import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
27087
- import { basename, dirname, isAbsolute, join as join8, sep as sep2 } from "path";
28091
+ import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
28092
+ import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
27088
28093
 
27089
28094
  // ../../packages/plugin-sdk/src/events.ts
27090
- import { createHash as createHash4, randomUUID as randomUUID11 } from "crypto";
28095
+ import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
28096
+
28097
+ // ../../packages/plugin-sdk/src/isolated-scan.ts
28098
+ import { existsSync as existsSync7 } from "fs";
28099
+ import { fileURLToPath } from "url";
28100
+ import { Worker } from "worker_threads";
27091
28101
 
27092
28102
  // ../../packages/plugin-sdk/src/inventory-resolver.ts
27093
- import { arch, hostname as hostname3, platform, release } from "os";
28103
+ import { arch, hostname as hostname4, platform, release } from "os";
27094
28104
 
27095
28105
  // ../../packages/plugin-sdk/src/nudge.ts
27096
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
27097
- import { join as join10 } from "path";
28106
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
28107
+ import { join as join11 } from "path";
27098
28108
 
27099
28109
  // ../../packages/plugin-sdk/src/paths.ts
27100
- import { readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
27101
- import { basename as basename3, dirname as dirname2, sep as sep3 } from "path";
28110
+ import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
28111
+ import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
27102
28112
 
27103
28113
  // ../../packages/plugin-sdk/src/project-files.ts
27104
28114
  var import_ignore = __toESM(require_ignore(), 1);
27105
- import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
27106
- import { basename as basename4, join as join11, relative, sep as sep4 } from "path";
28115
+ import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
28116
+ import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
28117
+
28118
+ // ../../packages/plugin-sdk/src/provider-env-antigravity.ts
28119
+ var optionalBaseUrl2 = external_exports.preprocess((v) => {
28120
+ if (typeof v === "string" && v.trim() === "") return void 0;
28121
+ return v;
28122
+ }, external_exports.string().optional()).catch(void 0);
28123
+ var optionalFlag = external_exports.preprocess((v) => {
28124
+ if (typeof v !== "string") return false;
28125
+ const normalized = v.trim().toLowerCase();
28126
+ return normalized !== "" && normalized !== "0" && normalized !== "false";
28127
+ }, external_exports.boolean()).catch(false);
28128
+ var antigravityProviderEnvShape = {
28129
+ GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
28130
+ GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
28131
+ };
28132
+ var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
28133
+
28134
+ // ../../packages/plugin-sdk/src/provider-env-codex.ts
28135
+ var optionalBaseUrl3 = external_exports.preprocess((v) => {
28136
+ if (typeof v === "string" && v.trim() === "") return void 0;
28137
+ return v;
28138
+ }, external_exports.string().optional()).catch(void 0);
28139
+ var codexProviderEnvShape = {
28140
+ OPENAI_BASE_URL: optionalBaseUrl3
28141
+ };
28142
+ var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
27107
28143
 
27108
28144
  // ../../packages/plugin-sdk/src/runtime.ts
27109
- import { randomUUID as randomUUID12 } from "crypto";
28145
+ import { randomUUID as randomUUID14 } from "crypto";
27110
28146
 
27111
28147
  // ../../packages/plugin-sdk/src/suppressions.ts
27112
28148
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
27113
28149
 
27114
28150
  // ../../packages/plugin-sdk/src/throttle.ts
27115
- import { mkdirSync as mkdirSync4, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
27116
- import { join as join12 } from "path";
28151
+ import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
28152
+ import { join as join13 } from "path";
27117
28153
 
27118
28154
  // ../../packages/plugin-runtime/src/standalone-gateway.ts
27119
- import { randomUUID as randomUUID13 } from "crypto";
28155
+ import { randomUUID as randomUUID15 } from "crypto";
27120
28156
 
27121
28157
  // ../../packages/plugin-runtime/src/recorder.ts
27122
28158
  var PLUGIN_RECORDER_BINARY = "plugin";
@@ -27278,7 +28314,7 @@ var StandaloneDataGateway = class {
27278
28314
  const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
27279
28315
  const installed = this.installedScanRules();
27280
28316
  const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
27281
- id: randomUUID13(),
28317
+ id: randomUUID15(),
27282
28318
  scope: "global",
27283
28319
  target: { ruleId },
27284
28320
  action,
@@ -27431,7 +28467,7 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
27431
28467
  }
27432
28468
 
27433
28469
  // ../../packages/plugin-runtime/src/handle-session-start.ts
27434
- import { randomUUID as randomUUID14 } from "crypto";
28470
+ import { randomUUID as randomUUID16 } from "crypto";
27435
28471
  var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
27436
28472
 
27437
28473
  // src/hooks/shared.ts
@@ -27458,10 +28494,58 @@ async function readStdin() {
27458
28494
  });
27459
28495
  }
27460
28496
 
28497
+ // ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
28498
+ import { writeFileSync as writeFileSync7 } from "fs";
28499
+ import { join as join14 } from "path";
28500
+
28501
+ // ../../packages/setup-wizard/src/triage/plan-file.ts
28502
+ import { mkdtempSync, readFileSync as readFileSync9, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
28503
+ import { tmpdir } from "os";
28504
+ import { basename as basename6, dirname as dirname4, join as join15 } from "path";
28505
+ var SuppressionEntrySchema = external_exports.object({
28506
+ ruleId: external_exports.string(),
28507
+ category: DetectionCategory,
28508
+ valueFingerprint: external_exports.string(),
28509
+ keyVersion: external_exports.number(),
28510
+ maskedValue: external_exports.string(),
28511
+ justification: external_exports.string()
28512
+ });
28513
+ var ShowcaseCategorySchema = external_exports.object({
28514
+ category: DetectionCategory,
28515
+ action: BuiltinPolicyId,
28516
+ genuineCount: external_exports.number(),
28517
+ fpCount: external_exports.number(),
28518
+ reasoning: external_exports.string()
28519
+ });
28520
+ var JoinEntrySchema = external_exports.object({
28521
+ id: external_exports.string(),
28522
+ ruleId: external_exports.string(),
28523
+ category: DetectionCategory,
28524
+ valueFingerprint: external_exports.string().optional(),
28525
+ keyVersion: external_exports.number().optional(),
28526
+ maskedMatch: external_exports.string(),
28527
+ maskedContext: external_exports.string()
28528
+ });
28529
+ var PLAN_FILE_VERSION = 3;
28530
+ var PersistedPlanSchema = external_exports.object({
28531
+ version: external_exports.literal(PLAN_FILE_VERSION),
28532
+ // partialRecord (not record): a posture only covers the categories present in
28533
+ // the evidence, so an exhaustive-key record would reject every real plan.
28534
+ posture: external_exports.partialRecord(DetectionCategory, BuiltinPolicyId),
28535
+ entries: external_exports.array(SuppressionEntrySchema),
28536
+ showcase: external_exports.array(ShowcaseCategorySchema),
28537
+ join: external_exports.array(JoinEntrySchema),
28538
+ notes: external_exports.string(),
28539
+ // The store's per-category action at preview time. The downgrade view is
28540
+ // rendered from it at PREVIEW (renderPosturePlan); confirm reads it back ONLY to
28541
+ // compare against the live store and reject a stale plan (runConfirm's drift gate).
28542
+ current: external_exports.partialRecord(DetectionCategory, ActionTaken)
28543
+ });
28544
+
27461
28545
  // src/command-registry.ts
27462
- import { readdirSync as readdirSync4 } from "fs";
27463
- import { fileURLToPath } from "url";
27464
- var COMMANDS_DIR = fileURLToPath(new URL("../commands", import.meta.url));
28546
+ import { readdirSync as readdirSync5 } from "fs";
28547
+ import { fileURLToPath as fileURLToPath2 } from "url";
28548
+ var COMMANDS_DIR = fileURLToPath2(new URL("../commands", import.meta.url));
27465
28549
 
27466
28550
  // src/present.ts
27467
28551
  var SHADE = {