@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.
package/scripts/query.js CHANGED
@@ -492,9 +492,8 @@ var require_ignore = __commonJS({
492
492
  });
493
493
 
494
494
  // ../../packages/persistence/src/database.ts
495
- import { randomUUID as randomUUID9 } from "crypto";
496
- import { existsSync, renameSync as renameSync2, rmSync as rmSync2 } from "fs";
497
- import { join, sep } from "path";
495
+ import { randomUUID as randomUUID10 } from "crypto";
496
+ import { join as join2, sep } from "path";
498
497
  import { DatabaseSync } from "node:sqlite";
499
498
 
500
499
  // ../../packages/schema/src/drizzle/sqlite-ddl.ts
@@ -574,6 +573,14 @@ var SQLITE_MIGRATIONS = [
574
573
  {
575
574
  tag: "0018_serious_tana_nile",
576
575
  sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
576
+ },
577
+ {
578
+ tag: "0019_audit_started_at_index",
579
+ sql: "CREATE INDEX `idx_audit_started_at` ON `audit_events` (`started_at`);"
580
+ },
581
+ {
582
+ tag: "0020_secret_vault_pagination_indexes",
583
+ 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');"
577
584
  }
578
585
  ];
579
586
 
@@ -15371,7 +15378,17 @@ var Finding = external_exports.object({
15371
15378
  }).meta({ id: "Finding" });
15372
15379
  var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
15373
15380
  var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
15374
- var FindingProvider = external_exports.enum(["claudecode", "claudedesktop", "cursor", "copilot", "chatgpt", "api"]).meta({ id: "FindingProvider" });
15381
+ var FindingProvider = external_exports.enum([
15382
+ "claudecode",
15383
+ "claudedesktop",
15384
+ "cursor",
15385
+ "copilot",
15386
+ "chatgpt",
15387
+ "claudeai",
15388
+ "codex",
15389
+ "antigravity",
15390
+ "api"
15391
+ ]).meta({ id: "FindingProvider" });
15375
15392
  var FindingCategory = external_exports.enum([
15376
15393
  "secret",
15377
15394
  "pii",
@@ -15425,7 +15442,16 @@ var FindingInstance = external_exports.object({
15425
15442
  confidence: external_exports.number().min(0).max(1),
15426
15443
  // Lifecycle status (see FindingStatus). Optional so legacy callers/rows
15427
15444
  // that predate the resolution feature stay valid.
15428
- status: FindingStatus.optional()
15445
+ status: FindingStatus.optional(),
15446
+ // The audit event this finding was captured from. Optional so callers that
15447
+ // do not project it stay valid. An at-rest finding is content-addressed by
15448
+ // finding_key and its row is upserted on re-detection, so this names the
15449
+ // MOST RECENT detection event, not the first.
15450
+ eventId: external_exports.string().optional(),
15451
+ // The session that event belongs to, when it has one — the seam a
15452
+ // per-instance "view session" link needs. Absent for events captured
15453
+ // outside a session.
15454
+ sessionId: external_exports.string().optional()
15429
15455
  }).meta({ id: "FindingInstance" });
15430
15456
  var FindingGroup = external_exports.object({
15431
15457
  id: external_exports.string(),
@@ -15469,7 +15495,11 @@ var FindingFacets = external_exports.object({
15469
15495
  // for every instance, so every group lands in a bucket; a status-less
15470
15496
  // group (possible only for callers whose rows carry no statuses) is
15471
15497
  // counted under no value.
15472
- status: external_exports.array(FindingFacetItem)
15498
+ status: external_exports.array(FindingFacetItem),
15499
+ // Host tool (attributes.tool_name). Present only on the instance-level
15500
+ // reads, which can filter by it; the grouped read omits the dimension
15501
+ // because a group spans tools.
15502
+ tool: external_exports.array(FindingFacetItem).optional()
15473
15503
  }).meta({ id: "FindingFacets" });
15474
15504
  var DEFAULT_GROUPED_FINDINGS_LIMIT = 50;
15475
15505
  var ListGroupedFindingsQuery = external_exports.object({
@@ -15487,6 +15517,16 @@ var ListGroupedFindingsQuery = external_exports.object({
15487
15517
  // Scope to findings whose event carries this session id (the Activity page's
15488
15518
  // session → findings drilldown). Findings without a session never match.
15489
15519
  sessionId: external_exports.string().optional(),
15520
+ // Inclusive lower bound on the parent event's timestamp, so a caller arriving
15521
+ // from a time-scoped page (Activity's range) can carry that scope. Absent
15522
+ // means all time — this list has no default window.
15523
+ from: external_exports.iso.datetime().optional(),
15524
+ // A group or instance id that must appear in the page even when the cursor
15525
+ // has already advanced past its sort position. This is what keeps the
15526
+ // Findings page's one-shot ?finding= deep link resolving once the list
15527
+ // paginates: the target group is appended out of sort order rather than
15528
+ // scanning forward for it. Never affects totals, facets or the cursor.
15529
+ includeId: external_exports.string().optional(),
15490
15530
  groupBy: external_exports.literal("type").optional(),
15491
15531
  limit: external_exports.coerce.number().int().min(1).max(100).optional(),
15492
15532
  cursor: external_exports.string().optional()
@@ -15531,15 +15571,110 @@ var FindingInstanceDetail = FindingInstance.extend({
15531
15571
  detection: FindingDetectionRef,
15532
15572
  policy: FindingPolicyRef
15533
15573
  }).meta({ id: "FindingInstanceDetail" });
15574
+ var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
15575
+ var ListFindingInstancesQuery = external_exports.object({
15576
+ severity: external_exports.array(Severity).optional(),
15577
+ // Rule ids, the same vocabulary the grouped list's `subtype` carries.
15578
+ subtype: external_exports.array(external_exports.string()).optional(),
15579
+ provider: external_exports.array(FindingProvider).optional(),
15580
+ action: external_exports.array(FindingAction).optional(),
15581
+ // Matches each instance's OWN derived status (deriveFindingStatus), unlike
15582
+ // the grouped query's group-level fold.
15583
+ status: external_exports.array(FindingStatus).optional(),
15584
+ // Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
15585
+ // where the free-text `q` can only match the rendered "via Bash" label.
15586
+ tool: external_exports.array(external_exports.string()).optional(),
15587
+ // Exact repository / file-path matches, for the drill-down out of the
15588
+ // locations view. A row whose event carries no repo/file matches neither.
15589
+ repo: external_exports.string().optional(),
15590
+ file: external_exports.string().optional(),
15591
+ q: external_exports.string().optional(),
15592
+ sessionId: external_exports.string().optional(),
15593
+ from: external_exports.iso.datetime().optional(),
15594
+ limit: external_exports.coerce.number().int().min(1).max(200).optional(),
15595
+ cursor: external_exports.string().optional()
15596
+ });
15597
+ var ListFindingInstancesResponse = external_exports.object({
15598
+ // Instances matching the filters across the whole scope, not just this
15599
+ // page — cursor-independent, like the grouped list's totals.
15600
+ totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
15601
+ // Counts in INSTANCES here, where the grouped response counts groups. Each
15602
+ // dimension still excludes its own filter.
15603
+ facets: FindingFacets,
15604
+ items: external_exports.array(FindingInstanceDetail),
15605
+ nextCursor: external_exports.string().nullable()
15606
+ }).meta({ id: "ListFindingInstancesResponse" });
15607
+ var FindingLocationFile = external_exports.object({
15608
+ // Empty when the instances carried no file path (a prompt or a tool call
15609
+ // with no file attribution).
15610
+ file: external_exports.string(),
15611
+ instanceCount: external_exports.number().int().nonnegative(),
15612
+ maxSeverity: Severity,
15613
+ latestDetectedAt: external_exports.iso.datetime(),
15614
+ // Folded from the instances' derived statuses with the same
15615
+ // open-dominates precedence a group uses.
15616
+ status: FindingStatus.optional(),
15617
+ // Distinct rules seen at this location, capped — the row shows them as
15618
+ // chips, and the count is what conveys scale.
15619
+ ruleIds: external_exports.array(external_exports.string())
15620
+ }).meta({ id: "FindingLocationFile" });
15621
+ var FindingLocationRepo = external_exports.object({
15622
+ /** Empty when the instances carried no repo attribute. */
15623
+ repo: external_exports.string(),
15624
+ instanceCount: external_exports.number().int().nonnegative(),
15625
+ maxSeverity: Severity,
15626
+ latestDetectedAt: external_exports.iso.datetime(),
15627
+ status: FindingStatus.optional(),
15628
+ files: external_exports.array(FindingLocationFile)
15629
+ }).meta({ id: "FindingLocationRepo" });
15630
+ var ListFindingLocationsQuery = external_exports.object({
15631
+ severity: external_exports.array(Severity).optional(),
15632
+ subtype: external_exports.array(external_exports.string()).optional(),
15633
+ provider: external_exports.array(FindingProvider).optional(),
15634
+ action: external_exports.array(FindingAction).optional(),
15635
+ // Per-instance, as in ListFindingInstancesQuery: a location keeps the
15636
+ // instances that match, and folds its status from those.
15637
+ status: external_exports.array(FindingStatus).optional(),
15638
+ tool: external_exports.array(external_exports.string()).optional(),
15639
+ q: external_exports.string().optional(),
15640
+ sessionId: external_exports.string().optional(),
15641
+ from: external_exports.iso.datetime().optional(),
15642
+ limit: external_exports.coerce.number().int().min(1).max(500).optional()
15643
+ });
15644
+ var ListFindingLocationsResponse = external_exports.object({
15645
+ totals: external_exports.object({
15646
+ findings: external_exports.number().int().nonnegative(),
15647
+ repos: external_exports.number().int().nonnegative(),
15648
+ files: external_exports.number().int().nonnegative()
15649
+ }),
15650
+ /** Sorted by max severity, then most recent. */
15651
+ items: external_exports.array(FindingLocationRepo),
15652
+ /** Whether `limit` truncated the repo list. */
15653
+ hasMore: external_exports.boolean()
15654
+ }).meta({ id: "ListFindingLocationsResponse" });
15534
15655
 
15535
15656
  // ../../packages/schema/src/zod/harness-map.ts
15536
- var Harness = external_exports.enum(["claudecode", "cursor", "copilot", "codex", "windsurf", "claudedesktop", "chatgpt", "api"]).meta({ id: "Harness" });
15657
+ var Harness = external_exports.enum([
15658
+ "claudecode",
15659
+ "cursor",
15660
+ "copilot",
15661
+ "codex",
15662
+ "antigravity",
15663
+ "windsurf",
15664
+ "claudedesktop",
15665
+ "chatgpt",
15666
+ "claudeai",
15667
+ "api"
15668
+ ]).meta({ id: "Harness" });
15537
15669
  var TOOL_TO_HARNESS = {
15538
15670
  "claude-code": "claudecode",
15539
15671
  "claude-desktop": "claudedesktop",
15540
15672
  "github-copilot": "copilot",
15541
15673
  cursor: "cursor",
15542
- chatgpt: "chatgpt"
15674
+ chatgpt: "chatgpt",
15675
+ codex: "codex",
15676
+ antigravity: "antigravity",
15677
+ "claude-ai": "claudeai"
15543
15678
  };
15544
15679
 
15545
15680
  // ../../packages/schema/src/zod/meta.ts
@@ -15997,7 +16132,18 @@ var ActivityOverviewResponse = external_exports.object({
15997
16132
  // ../../packages/schema/src/zod/event.ts
15998
16133
  var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
15999
16134
  var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
16000
- var SourceTool = external_exports.enum(["claude-code", "claude-desktop", "cursor", "chatgpt", "github-copilot", "cli", "unknown"]).meta({ id: "SourceTool" });
16135
+ var SourceTool = external_exports.enum([
16136
+ "claude-code",
16137
+ "claude-desktop",
16138
+ "cursor",
16139
+ "chatgpt",
16140
+ "claude-ai",
16141
+ "github-copilot",
16142
+ "codex",
16143
+ "antigravity",
16144
+ "cli",
16145
+ "unknown"
16146
+ ]).meta({ id: "SourceTool" });
16001
16147
  var EventMetadata = external_exports.object({
16002
16148
  sessionId: external_exports.string().optional(),
16003
16149
  repo: external_exports.string().optional(),
@@ -16068,7 +16214,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
16068
16214
  var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
16069
16215
  var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
16070
16216
  var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
16071
- var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
16217
+ var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
16072
16218
  var AccessCounts = external_exports.object({
16073
16219
  open: external_exports.number().int().nonnegative(),
16074
16220
  approved: external_exports.number().int().nonnegative(),
@@ -16337,6 +16483,7 @@ var ExceptionBundleEntry = DetectionException.pick({
16337
16483
  useCount: true,
16338
16484
  conditions: true
16339
16485
  });
16486
+ var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
16340
16487
 
16341
16488
  // ../../packages/schema/src/zod/rule.ts
16342
16489
  var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
@@ -17147,6 +17294,35 @@ var EgressWriteSummary = external_exports.object({
17147
17294
  droppedFiles: external_exports.array(external_exports.string()).default([])
17148
17295
  }).meta({ id: "EgressWriteSummary" });
17149
17296
 
17297
+ // ../../packages/schema/src/zod/exception-action.ts
17298
+ var confirmation = external_exports.string().optional();
17299
+ var ApproveBlockedInput = external_exports.object({
17300
+ reference: external_exports.string(),
17301
+ scope: external_exports.string(),
17302
+ reason: external_exports.string(),
17303
+ confirmation
17304
+ });
17305
+ var AddExceptionInput = external_exports.object({
17306
+ ruleId: external_exports.string(),
17307
+ value: external_exports.string(),
17308
+ scope: external_exports.string(),
17309
+ reason: external_exports.string(),
17310
+ confirmation
17311
+ });
17312
+ var GrantRevealInput = external_exports.object({
17313
+ pointer: external_exports.string(),
17314
+ scope: external_exports.string(),
17315
+ justification: external_exports.string(),
17316
+ confirmation
17317
+ });
17318
+ var RevokeExceptionInput = external_exports.object({
17319
+ id: external_exports.string(),
17320
+ reason: external_exports.string()
17321
+ });
17322
+ var RotateKeyInput = external_exports.object({
17323
+ confirmation: external_exports.string()
17324
+ });
17325
+
17150
17326
  // ../../packages/schema/src/zod/findings-group-build.ts
17151
17327
  function toApiAction(dbVal) {
17152
17328
  const map2 = {
@@ -17202,6 +17378,8 @@ function buildFindingGroups(rows, opts = {}) {
17202
17378
  repo: r.repo,
17203
17379
  file: r.file,
17204
17380
  ...r.toolName === void 0 ? {} : { toolName: r.toolName },
17381
+ ...r.eventId === void 0 ? {} : { eventId: r.eventId },
17382
+ ...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
17205
17383
  action: toApiAction(effectiveDbAction),
17206
17384
  detectedAt: r.occurredAt,
17207
17385
  confidence: r.confidence,
@@ -17333,14 +17511,17 @@ function applyFindingFilters(groups, opts) {
17333
17511
  }
17334
17512
  var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
17335
17513
  var SEVERITY_RANK = SEVERITY_ORDER;
17514
+ function compareFindingGroupOrder(a, b) {
17515
+ const rankA = SEVERITY_RANK[a.severity] ?? -1;
17516
+ const rankB = SEVERITY_RANK[b.severity] ?? -1;
17517
+ const severityDiff = rankA - rankB;
17518
+ if (severityDiff !== 0) return severityDiff;
17519
+ const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
17520
+ if (recencyDiff !== 0) return recencyDiff;
17521
+ return a.id.localeCompare(b.id);
17522
+ }
17336
17523
  function sortFindingGroups(groups) {
17337
- return [...groups].sort((a, b) => {
17338
- const rankA = SEVERITY_RANK[a.severity] ?? -1;
17339
- const rankB = SEVERITY_RANK[b.severity] ?? -1;
17340
- const severityDiff = rankA - rankB;
17341
- if (severityDiff !== 0) return severityDiff;
17342
- return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
17343
- });
17524
+ return [...groups].sort(compareFindingGroupOrder);
17344
17525
  }
17345
17526
  function computeFindingFacets(allGroups, opts) {
17346
17527
  const forSeverity = applyFindingFilters(allGroups, {
@@ -17396,15 +17577,158 @@ function computeFindingFacets(allGroups, opts) {
17396
17577
  for (const g of forStatus) {
17397
17578
  if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
17398
17579
  }
17399
- const toItems = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
17580
+ const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
17581
+ return {
17582
+ severity: toItems2(severityMap),
17583
+ provider: toItems2(providerMap),
17584
+ action: toItems2(actionMap),
17585
+ subtype: toItems2(subtypeMap),
17586
+ status: toItems2(statusMap)
17587
+ };
17588
+ }
17589
+
17590
+ // ../../packages/schema/src/zod/findings-flat-build.ts
17591
+ function rowHaystack(row) {
17592
+ return [
17593
+ row.ruleId,
17594
+ row.category,
17595
+ row.maskedMatch,
17596
+ row.repo,
17597
+ row.file,
17598
+ row.toolName ? `via ${row.toolName}` : "",
17599
+ row.id
17600
+ ].join(" ").toLowerCase();
17601
+ }
17602
+ function matchesDimension(row, opts, dimension) {
17603
+ switch (dimension) {
17604
+ case "severity":
17605
+ return !opts.severity?.length || opts.severity.includes(row.severity);
17606
+ case "subtype":
17607
+ return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
17608
+ case "providers":
17609
+ return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
17610
+ case "actions":
17611
+ return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
17612
+ case "statuses":
17613
+ return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
17614
+ case "tools":
17615
+ return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
17616
+ case "repo":
17617
+ return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
17618
+ case "file":
17619
+ return opts.file === void 0 || opts.file === "" || row.file === opts.file;
17620
+ case "q":
17621
+ return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
17622
+ }
17623
+ }
17624
+ var DIMENSIONS = [
17625
+ "severity",
17626
+ "subtype",
17627
+ "providers",
17628
+ "actions",
17629
+ "statuses",
17630
+ "tools",
17631
+ "repo",
17632
+ "file",
17633
+ "q"
17634
+ ];
17635
+ function matchesInstanceFilters(row, opts, except) {
17636
+ for (const dimension of DIMENSIONS) {
17637
+ if (dimension === except) continue;
17638
+ if (!matchesDimension(row, opts, dimension)) return false;
17639
+ }
17640
+ return true;
17641
+ }
17642
+ function toItems(counts) {
17643
+ return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
17644
+ }
17645
+ function bump(counts, value) {
17646
+ counts.set(value, (counts.get(value) ?? 0) + 1);
17647
+ }
17648
+ function createInstanceFacetAccumulator(opts) {
17649
+ const severity = /* @__PURE__ */ new Map();
17650
+ const subtype = /* @__PURE__ */ new Map();
17651
+ const provider = /* @__PURE__ */ new Map();
17652
+ const action = /* @__PURE__ */ new Map();
17653
+ const status = /* @__PURE__ */ new Map();
17654
+ const tool = /* @__PURE__ */ new Map();
17655
+ return {
17656
+ add(row) {
17657
+ if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
17658
+ if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
17659
+ if (matchesInstanceFilters(row, opts, "providers")) {
17660
+ bump(provider, toApiProvider(row.sourceTool));
17661
+ }
17662
+ if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
17663
+ if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
17664
+ bump(status, row.status);
17665
+ }
17666
+ if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
17667
+ bump(tool, row.toolName);
17668
+ }
17669
+ },
17670
+ facets: () => ({
17671
+ severity: toItems(severity),
17672
+ subtype: toItems(subtype),
17673
+ provider: toItems(provider),
17674
+ action: toItems(action),
17675
+ status: toItems(status),
17676
+ tool: toItems(tool)
17677
+ })
17678
+ };
17679
+ }
17680
+ function toInstanceDetail(row) {
17681
+ const category = toApiCategory(row.category);
17682
+ return {
17683
+ id: row.id,
17684
+ provider: toApiProvider(row.sourceTool),
17685
+ repo: row.repo,
17686
+ file: row.file,
17687
+ ...row.toolName === void 0 ? {} : { toolName: row.toolName },
17688
+ eventId: row.eventId,
17689
+ ...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
17690
+ action: toApiAction(row.actionTaken),
17691
+ detectedAt: row.occurredAt,
17692
+ confidence: row.confidence,
17693
+ ...row.status === void 0 ? {} : { status: row.status },
17694
+ groupId: row.ruleId,
17695
+ category,
17696
+ subtype: row.ruleId,
17697
+ severity: row.severity,
17698
+ match: { maskedValue: row.maskedMatch, contextPrefix: "" },
17699
+ detection: { id: row.ruleId, name: null },
17700
+ policy: { id: `category:${category}`, name: category }
17701
+ };
17702
+ }
17703
+ var SEVERITY_ORDER2 = {
17704
+ critical: 0,
17705
+ high: 1,
17706
+ medium: 2,
17707
+ low: 3
17708
+ };
17709
+ function newLocationAccumulator() {
17400
17710
  return {
17401
- severity: toItems(severityMap),
17402
- provider: toItems(providerMap),
17403
- action: toItems(actionMap),
17404
- subtype: toItems(subtypeMap),
17405
- status: toItems(statusMap)
17711
+ instanceCount: 0,
17712
+ // Sorts after every known severity, so the first row always wins the
17713
+ // comparison below rather than an unknown value pinning the location.
17714
+ maxSeverityRank: Number.MAX_SAFE_INTEGER,
17715
+ maxSeverity: "low",
17716
+ latestDetectedAt: "",
17717
+ statuses: [],
17718
+ ruleIds: /* @__PURE__ */ new Set()
17406
17719
  };
17407
17720
  }
17721
+ function addToLocation(acc, row) {
17722
+ acc.instanceCount += 1;
17723
+ const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
17724
+ if (rank < acc.maxSeverityRank) {
17725
+ acc.maxSeverityRank = rank;
17726
+ acc.maxSeverity = row.severity;
17727
+ }
17728
+ if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
17729
+ acc.statuses.push(row.status);
17730
+ acc.ruleIds.add(row.ruleId);
17731
+ }
17408
17732
 
17409
17733
  // ../../packages/schema/src/zod/installed-pack.ts
17410
17734
  var InstalledPack = external_exports.object({
@@ -17538,6 +17862,50 @@ var VaultInventoryEntry = external_exports.object({
17538
17862
  revealGrantId: external_exports.string().nullable(),
17539
17863
  sightings: external_exports.array(VaultSighting)
17540
17864
  });
17865
+ var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
17866
+ var DEFAULT_VAULT_DEREFS_LIMIT = 50;
17867
+ var MAX_VAULT_PAGE_LIMIT = 200;
17868
+ var ListVaultInventoryQuery = external_exports.object({
17869
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
17870
+ // Opaque; names the last row of the page just served.
17871
+ cursor: external_exports.string().optional()
17872
+ });
17873
+ var ListVaultInventoryResponse = external_exports.object({
17874
+ // Vaulted values across the whole store, not just this page — cursor-
17875
+ // independent, so paging never changes what the count claims.
17876
+ totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
17877
+ items: external_exports.array(VaultInventoryEntry),
17878
+ // `null` once the last page is reached.
17879
+ nextCursor: external_exports.string().nullable()
17880
+ });
17881
+ var ListVaultReuseQuery = external_exports.object({
17882
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
17883
+ cursor: external_exports.string().optional()
17884
+ });
17885
+ var ListVaultReuseResponse = external_exports.object({
17886
+ // Reused values across the whole store — the number the section's claim
17887
+ // ("values detected in more than one place") is about.
17888
+ totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
17889
+ items: external_exports.array(VaultInventoryEntry),
17890
+ nextCursor: external_exports.string().nullable()
17891
+ });
17892
+ var ListVaultDerefsQuery = external_exports.object({
17893
+ // Include the batched, high-volume reasons (display, view-render). Omitted
17894
+ // hides them and counts them into `hiddenBatched` instead, so the model
17895
+ // crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
17896
+ // over a Server Action, which preserves the type, never as a URL param.
17897
+ includeBatched: external_exports.boolean().optional(),
17898
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
17899
+ cursor: external_exports.string().optional()
17900
+ });
17901
+ var ListVaultDerefsResponse = external_exports.object({
17902
+ items: external_exports.array(VaultDeref),
17903
+ nextCursor: external_exports.string().nullable(),
17904
+ // Display/view-render rows the query hid, over the WHOLE trail rather than
17905
+ // this page — it is the count the "N hidden" line and its toggle speak for.
17906
+ // Always 0 when `includeBatched` was set, since nothing was hidden.
17907
+ hiddenBatched: external_exports.number().int().nonnegative()
17908
+ });
17541
17909
  var VaultKeyCustody = external_exports.string();
17542
17910
  var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
17543
17911
  var VaultConsent = external_exports.object({
@@ -17910,7 +18278,7 @@ var TopSourcesQuery = external_exports.object({
17910
18278
  // Omit for both kinds.
17911
18279
  kind: external_exports.enum(SOURCE_KINDS).optional()
17912
18280
  });
17913
- var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
18281
+ var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
17914
18282
  var ScanCoverageProvider = external_exports.object({
17915
18283
  provider: Provider,
17916
18284
  // Percent of that provider's traffic scanned in the window. 0 when unsupported.
@@ -18163,6 +18531,138 @@ function captureId(sessionId, contentHash, filePath = null) {
18163
18531
  );
18164
18532
  }
18165
18533
 
18534
+ // ../../packages/persistence/src/internal/snapshot.ts
18535
+ import { randomUUID } from "crypto";
18536
+ import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
18537
+ import { basename, dirname, join } from "path";
18538
+
18539
+ // ../../packages/persistence/src/paths.ts
18540
+ import {
18541
+ chmodSync,
18542
+ linkSync,
18543
+ lstatSync,
18544
+ mkdirSync,
18545
+ renameSync,
18546
+ rmSync,
18547
+ writeFileSync
18548
+ } from "fs";
18549
+ import { threadId } from "worker_threads";
18550
+ var DATA_DIR_MODE = 448;
18551
+ var DATA_FILE_MODE = 384;
18552
+ var DB_FILENAME = "aka.db";
18553
+ function isSymlink(path) {
18554
+ try {
18555
+ return lstatSync(path).isSymbolicLink();
18556
+ } catch {
18557
+ return false;
18558
+ }
18559
+ }
18560
+ function chmodBestEffort(path, mode) {
18561
+ if (isSymlink(path)) return;
18562
+ try {
18563
+ chmodSync(path, mode);
18564
+ } catch {
18565
+ }
18566
+ }
18567
+ function tightenDir(dir) {
18568
+ chmodBestEffort(dir, DATA_DIR_MODE);
18569
+ }
18570
+ function ensureDataDirSync(dir) {
18571
+ mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
18572
+ tightenDir(dir);
18573
+ }
18574
+ function dbSidecars(file2) {
18575
+ return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
18576
+ }
18577
+ function tightenFile(file2) {
18578
+ chmodBestEffort(file2, DATA_FILE_MODE);
18579
+ }
18580
+ function tightenPerms(file2) {
18581
+ for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
18582
+ }
18583
+
18584
+ // ../../packages/persistence/src/internal/snapshot.ts
18585
+ function backupPath(file2, tag) {
18586
+ return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
18587
+ }
18588
+ var STALE_PARTIAL_MS = 5 * 6e4;
18589
+ function reapStalePartials(file2) {
18590
+ const dir = dirname(file2);
18591
+ const prefix = `${basename(file2)}.`;
18592
+ let entries;
18593
+ try {
18594
+ entries = readdirSync(dir);
18595
+ } catch {
18596
+ return;
18597
+ }
18598
+ for (const name of entries) {
18599
+ if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
18600
+ const partial2 = join(dir, name);
18601
+ try {
18602
+ if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
18603
+ rmSync2(partial2, { force: true });
18604
+ }
18605
+ } catch {
18606
+ }
18607
+ }
18608
+ }
18609
+ function snapshotStore(db, backup) {
18610
+ const partial2 = `${backup}.partial`;
18611
+ try {
18612
+ rmSync2(partial2, { force: true });
18613
+ db.prepare("VACUUM INTO ?").run(partial2);
18614
+ tightenFile(partial2);
18615
+ renameSync2(partial2, backup);
18616
+ } catch (error51) {
18617
+ try {
18618
+ rmSync2(partial2, { force: true });
18619
+ } catch {
18620
+ }
18621
+ throw error51;
18622
+ }
18623
+ }
18624
+ function moveStoreAside(file2, backup) {
18625
+ const undo = [];
18626
+ renameSync2(file2, backup);
18627
+ undo.push([backup, file2]);
18628
+ try {
18629
+ for (const sidecar of dbSidecars(file2)) {
18630
+ const moved = `${backup}${sidecar.slice(file2.length)}`;
18631
+ try {
18632
+ renameSync2(sidecar, moved);
18633
+ undo.push([moved, sidecar]);
18634
+ } catch {
18635
+ rmSync2(sidecar, { force: true });
18636
+ }
18637
+ }
18638
+ } catch (error51) {
18639
+ for (const [from, to] of undo.reverse()) {
18640
+ try {
18641
+ renameSync2(from, to);
18642
+ } catch {
18643
+ }
18644
+ }
18645
+ throw error51;
18646
+ }
18647
+ tightenPerms(backup);
18648
+ }
18649
+ function discardStore(file2, backup) {
18650
+ try {
18651
+ rmSync2(file2, { force: true });
18652
+ for (const sidecar of dbSidecars(file2)) {
18653
+ rmSync2(sidecar, { force: true });
18654
+ }
18655
+ } catch (error51) {
18656
+ if (existsSync(file2)) {
18657
+ try {
18658
+ rmSync2(backup, { force: true });
18659
+ } catch {
18660
+ }
18661
+ }
18662
+ throw error51;
18663
+ }
18664
+ }
18665
+
18166
18666
  // ../../packages/persistence/src/internal/sql-text.ts
18167
18667
  function escapeLikePattern(s) {
18168
18668
  return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
@@ -18304,38 +18804,6 @@ function mapRowsTolerant(rows, map2) {
18304
18804
  return out;
18305
18805
  }
18306
18806
 
18307
- // ../../packages/persistence/src/paths.ts
18308
- import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
18309
- var DATA_DIR_MODE = 448;
18310
- var DATA_FILE_MODE = 384;
18311
- var DB_FILENAME = "aka.db";
18312
- function chmodBestEffort(path, mode) {
18313
- try {
18314
- chmodSync(path, mode);
18315
- } catch {
18316
- }
18317
- }
18318
- function tightenDir(dir) {
18319
- chmodBestEffort(dir, DATA_DIR_MODE);
18320
- }
18321
- function ensureDataDirSync(dir) {
18322
- mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
18323
- tightenDir(dir);
18324
- }
18325
- function dbSidecars(file2) {
18326
- return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
18327
- }
18328
- function tightenFile(file2) {
18329
- try {
18330
- if (lstatSync(file2).isSymbolicLink()) return;
18331
- } catch {
18332
- }
18333
- chmodBestEffort(file2, DATA_FILE_MODE);
18334
- }
18335
- function tightenPerms(file2) {
18336
- for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
18337
- }
18338
-
18339
18807
  // ../../packages/persistence/src/migrations.ts
18340
18808
  function describeObject(object2) {
18341
18809
  return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
@@ -18451,9 +18919,9 @@ function applyLegacyDropMigration(db, file2) {
18451
18919
  }
18452
18920
  }
18453
18921
  function backupBeforeLegacyDrop(db, file2) {
18454
- const backup = `${file2}.pre-drop.${String(Date.now())}.bak`;
18455
- db.prepare("VACUUM INTO ?").run(backup);
18456
- tightenFile(backup);
18922
+ reapStalePartials(file2);
18923
+ const backup = backupPath(file2, "pre-drop");
18924
+ snapshotStore(db, backup);
18457
18925
  return backup;
18458
18926
  }
18459
18927
  var TOKEN_USAGE_COLUMNS = [
@@ -18797,6 +19265,25 @@ function parseJsonObject(s) {
18797
19265
  return void 0;
18798
19266
  }
18799
19267
 
19268
+ // ../../packages/persistence/src/internal/keyset-cursor.ts
19269
+ function encodeKeysetCursor(payload) {
19270
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
19271
+ }
19272
+ function decodeKeysetCursor(cursor) {
19273
+ const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
19274
+ if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
19275
+ // resumes from is epoch millis, and a payload carrying ±Infinity or a
19276
+ // fraction binds cleanly rather than failing — returning an EMPTY page with
19277
+ // a null cursor, which a caller reads as "end of list". That is the one
19278
+ // outcome a cursor that does not decode must never produce, since the
19279
+ // documented behaviour above is to restart from the top. (`1e999` is valid
19280
+ // JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
19281
+ Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
19282
+ return parsed;
19283
+ }
19284
+ return null;
19285
+ }
19286
+
18800
19287
  // ../../packages/persistence/src/repositories/activity.ts
18801
19288
  var DAY_MS = 864e5;
18802
19289
  var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
@@ -18842,16 +19329,6 @@ function utcWindow(nowMs) {
18842
19329
  const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
18843
19330
  return { startMs, endMs: startMs + DAY_MS };
18844
19331
  }
18845
- function encodeCursor(payload) {
18846
- return Buffer.from(JSON.stringify(payload)).toString("base64url");
18847
- }
18848
- function decodeCursor(cursor) {
18849
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
18850
- if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
18851
- return parsed;
18852
- }
18853
- return null;
18854
- }
18855
19332
  var DB_EVENT_TYPE_TO_KIND = {
18856
19333
  session: "session",
18857
19334
  prompt: "prompt",
@@ -18996,7 +19473,7 @@ var SqliteActivityRepository = class {
18996
19473
  return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
18997
19474
  }
18998
19475
  listSessions(query) {
18999
- const cursor = query.cursor ? decodeCursor(query.cursor) : null;
19476
+ const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
19000
19477
  const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
19001
19478
  const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
19002
19479
  const conditions = [SESSION_ROOT];
@@ -19070,7 +19547,7 @@ var SqliteActivityRepository = class {
19070
19547
  )
19071
19548
  );
19072
19549
  const last = page[page.length - 1];
19073
- const nextCursor = hasMore && last ? encodeCursor({ startedAtMs: last.started_at, id: last.id }) : null;
19550
+ const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
19074
19551
  return Promise.resolve({ items, nextCursor, emptyCount });
19075
19552
  }
19076
19553
  getSession(sessionId) {
@@ -19943,7 +20420,7 @@ var SqliteEventsRepository = class {
19943
20420
  };
19944
20421
 
19945
20422
  // ../../packages/persistence/src/repositories/exceptions.ts
19946
- import { randomUUID } from "crypto";
20423
+ import { randomUUID as randomUUID2 } from "crypto";
19947
20424
 
19948
20425
  // ../../packages/persistence/src/internal/sqlite-errors.ts
19949
20426
  var SQLITE_CONSTRAINT_UNIQUE = 2067;
@@ -19979,8 +20456,9 @@ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
19979
20456
  AND conditions IS NULL
19980
20457
  AND ${ACTIVE_PREDICATE}`;
19981
20458
  var SqliteExceptionsRepository = class {
19982
- constructor(db) {
20459
+ constructor(db, now = () => Date.now()) {
19983
20460
  this.db = db;
20461
+ this.now = now;
19984
20462
  this.consumeStmt = db.prepare(
19985
20463
  `UPDATE exceptions
19986
20464
  SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
@@ -19998,6 +20476,7 @@ var SqliteExceptionsRepository = class {
19998
20476
  );
19999
20477
  }
20000
20478
  db;
20479
+ now;
20001
20480
  consumeStmt;
20002
20481
  insertBlockedStmt;
20003
20482
  sweepBlockedStmt;
@@ -20024,8 +20503,8 @@ var SqliteExceptionsRepository = class {
20024
20503
  "provider conditions are not supported yet \u2014 a grant with one would never apply"
20025
20504
  );
20026
20505
  }
20027
- const id = randomUUID();
20028
- const now = Date.now();
20506
+ const id = randomUUID2();
20507
+ const now = this.now();
20029
20508
  try {
20030
20509
  this.insertExceptionRow(id, input, now);
20031
20510
  } catch (err) {
@@ -20103,7 +20582,7 @@ var SqliteExceptionsRepository = class {
20103
20582
  const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
20104
20583
  const rows = allRows(
20105
20584
  this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
20106
- opts?.includeTerminal ? {} : { now: Date.now() }
20585
+ opts?.includeTerminal ? {} : { now: this.now() }
20107
20586
  );
20108
20587
  const exceptions = mapRowsTolerant(rows, parseExceptionRow);
20109
20588
  return Promise.resolve(exceptions);
@@ -20138,7 +20617,7 @@ var SqliteExceptionsRepository = class {
20138
20617
  * already revoked.
20139
20618
  */
20140
20619
  revoke(id, revokedBy, reason) {
20141
- const now = Date.now();
20620
+ const now = this.now();
20142
20621
  const result = this.db.prepare(
20143
20622
  `UPDATE exceptions
20144
20623
  SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
@@ -20152,7 +20631,7 @@ var SqliteExceptionsRepository = class {
20152
20631
  * callers must treat identically — means it does not and the detection is
20153
20632
  * enforced as usual. Deliberately NOT wrapped in try/catch.
20154
20633
  */
20155
- consume(id, now = Date.now()) {
20634
+ consume(id, now = this.now()) {
20156
20635
  const result = this.consumeStmt.run({ id, now });
20157
20636
  return Promise.resolve(Number(result.changes) === 1);
20158
20637
  }
@@ -20161,7 +20640,7 @@ var SqliteExceptionsRepository = class {
20161
20640
  * version — what rides the policy bundle to the hook. Grants written under
20162
20641
  * a different (rotated-away) key never match, so they are excluded at read.
20163
20642
  */
20164
- activeBundleEntries(keyVersion, now = Date.now()) {
20643
+ activeBundleEntries(keyVersion, now = this.now()) {
20165
20644
  const rows = allRows(
20166
20645
  this.db.prepare(
20167
20646
  `SELECT * FROM exceptions
@@ -20193,7 +20672,7 @@ var SqliteExceptionsRepository = class {
20193
20672
  * than the retention window on every write, so the ledger self-limits.
20194
20673
  */
20195
20674
  recordBlocked(entry) {
20196
- const now = Date.now();
20675
+ const now = this.now();
20197
20676
  this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
20198
20677
  this.insertBlockedStmt.run({
20199
20678
  reference: entry.reference,
@@ -20216,7 +20695,7 @@ var SqliteExceptionsRepository = class {
20216
20695
  WHERE blocked_at > :cutoff
20217
20696
  ORDER BY blocked_at DESC, rowid DESC`
20218
20697
  ),
20219
- { cutoff: Date.now() - windowMs }
20698
+ { cutoff: this.now() - windowMs }
20220
20699
  );
20221
20700
  return Promise.resolve(
20222
20701
  rows.map((row) => ({
@@ -20244,8 +20723,9 @@ var SqliteExceptionsRepository = class {
20244
20723
  * evaluate conditions, and a narrowing clause that is ignored would WIDEN the
20245
20724
  * grant instead. Fail closed until reveal-side condition evaluation exists.
20246
20725
  */
20247
- activeRevealGrant(ruleId, valueFingerprint, keyVersion, now = Date.now()) {
20726
+ activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
20248
20727
  try {
20728
+ const at = now ?? this.now();
20249
20729
  const row = getRow(
20250
20730
  this.db.prepare(
20251
20731
  `SELECT id FROM exceptions
@@ -20254,7 +20734,7 @@ var SqliteExceptionsRepository = class {
20254
20734
  AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
20255
20735
  LIMIT 1`
20256
20736
  ),
20257
- { ruleId, valueFingerprint, keyVersion, now }
20737
+ { ruleId, valueFingerprint, keyVersion, now: at }
20258
20738
  );
20259
20739
  return Promise.resolve(row ?? null);
20260
20740
  } catch (err) {
@@ -20268,7 +20748,7 @@ var SqliteExceptionsRepository = class {
20268
20748
  * predicate, so correctness never depends on this sweep; it only bounds how
20269
20749
  * long the audit evidence is kept locally. Returns the deleted count.
20270
20750
  */
20271
- sweepTerminal(retentionMs, now = Date.now()) {
20751
+ sweepTerminal(retentionMs, now = this.now()) {
20272
20752
  const result = this.db.prepare(
20273
20753
  `DELETE FROM exceptions
20274
20754
  WHERE updated_at < :cutoff
@@ -20331,6 +20811,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
20331
20811
 
20332
20812
  // ../../packages/persistence/src/repositories/findings.ts
20333
20813
  var PREVIEW_INSTANCES_PER_GROUP = 200;
20814
+ var SCAN_BATCH_ROWS = 1e3;
20815
+ var DEFAULT_LOCATIONS_LIMIT = 100;
20816
+ var LOCATION_RULE_IDS_CAP = 20;
20817
+ function compareLocationOrder(a, b) {
20818
+ return compareFindingGroupOrder(
20819
+ {
20820
+ severity: a.maxSeverity,
20821
+ latestDetectedAt: a.latestDetectedAt,
20822
+ id: ""
20823
+ },
20824
+ {
20825
+ severity: b.maxSeverity,
20826
+ latestDetectedAt: b.latestDetectedAt,
20827
+ id: ""
20828
+ }
20829
+ );
20830
+ }
20334
20831
  var CONCAT_SEP = ",";
20335
20832
  var TUPLE_SEP = "|";
20336
20833
  function splitConcat(value) {
@@ -20343,6 +20840,33 @@ function deriveInstanceStatus(row) {
20343
20840
  latestResolutionStatus: row.latest_status
20344
20841
  });
20345
20842
  }
20843
+ function encodeGroupCursor(group) {
20844
+ const payload = {
20845
+ sev: group.severity,
20846
+ t: group.latestDetectedAt,
20847
+ id: group.id
20848
+ };
20849
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
20850
+ }
20851
+ function decodeGroupCursor(cursor) {
20852
+ const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
20853
+ if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
20854
+ return {
20855
+ severity: parsed.sev,
20856
+ latestDetectedAt: parsed.t,
20857
+ id: parsed.id
20858
+ };
20859
+ }
20860
+ return null;
20861
+ }
20862
+ function firstAfter(sorted, cursor) {
20863
+ const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
20864
+ return index === -1 ? sorted.length : index;
20865
+ }
20866
+ function findDeepLinked(sorted, page, id) {
20867
+ if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
20868
+ return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
20869
+ }
20346
20870
  var DAY_MS3 = 864e5;
20347
20871
  var SqliteFindingsRepository = class {
20348
20872
  constructor(db) {
@@ -20452,8 +20976,13 @@ var SqliteFindingsRepository = class {
20452
20976
  */
20453
20977
  listGroupedFindings(query) {
20454
20978
  const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
20455
- const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}`;
20456
- const sessionParams = query.sessionId ? { sessionId: query.sessionId } : {};
20979
+ const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
20980
+ const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
20981
+ const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
20982
+ const sessionParams = {
20983
+ ...query.sessionId ? { sessionId: query.sessionId } : {},
20984
+ ...fromMs === void 0 ? {} : { fromMs }
20985
+ };
20457
20986
  const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
20458
20987
  predicate,
20459
20988
  params: sessionParams
@@ -20461,7 +20990,8 @@ var SqliteFindingsRepository = class {
20461
20990
  const rows = allRows(
20462
20991
  this.db.prepare(
20463
20992
  `SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
20464
- occurred_at, source_tool, repo, file, tool_name, kind, finding_key, latest_status
20993
+ occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
20994
+ kind, finding_key, latest_status
20465
20995
  FROM (
20466
20996
  SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
20467
20997
  d.severity AS severity, f.masked_match AS masked_match,
@@ -20471,6 +21001,7 @@ var SqliteFindingsRepository = class {
20471
21001
  json_extract(e.attributes, '$.repo') AS repo,
20472
21002
  json_extract(e.attributes, '$.file_path') AS file,
20473
21003
  json_extract(e.attributes, '$.tool_name') AS tool_name,
21004
+ f.audit_event_id AS event_id, e.root_session_id AS session_id,
20474
21005
  e.event_type AS kind, f.finding_key AS finding_key,
20475
21006
  latest.status AS latest_status,
20476
21007
  ROW_NUMBER() OVER (
@@ -20502,6 +21033,8 @@ var SqliteFindingsRepository = class {
20502
21033
  repo: r.repo ?? "",
20503
21034
  file: r.file ?? "",
20504
21035
  ...r.tool_name === null ? {} : { toolName: r.tool_name },
21036
+ eventId: r.event_id,
21037
+ ...r.session_id === null ? {} : { sessionId: r.session_id },
20505
21038
  status: deriveInstanceStatus(r)
20506
21039
  }));
20507
21040
  const allGroups = buildFindingGroups(groupable, { aggregates });
@@ -20525,18 +21058,23 @@ var SqliteFindingsRepository = class {
20525
21058
  groups: sorted.length
20526
21059
  };
20527
21060
  const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
21061
+ const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
21062
+ const start = cursor === null ? 0 : firstAfter(sorted, cursor);
21063
+ const page = sorted.slice(start, start + limit);
21064
+ const lastOnPage = page.at(-1);
21065
+ const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
21066
+ const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
20528
21067
  const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
20529
- const items = sorted.slice(0, limit).map(
20530
- (g) => statusSet ? {
20531
- ...g,
20532
- instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
20533
- } : g
20534
- );
21068
+ const narrow = (g) => statusSet ? {
21069
+ ...g,
21070
+ instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
21071
+ } : g;
21072
+ const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
20535
21073
  return Promise.resolve({
20536
21074
  totals,
20537
21075
  facets,
20538
21076
  items,
20539
- nextCursor: null,
21077
+ nextCursor,
20540
21078
  ...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
20541
21079
  });
20542
21080
  }
@@ -20568,6 +21106,266 @@ var SqliteFindingsRepository = class {
20568
21106
  * request actually carries a `q`. (Substring matching is unaffected by a
20569
21107
  * path repeating across tuples.)
20570
21108
  */
21109
+ /**
21110
+ * The instance-level (flat) findings list: one row per finding, newest first,
21111
+ * paged by keyset.
21112
+ *
21113
+ * SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
21114
+ * bound are SQL predicates: nothing counts them, so narrowing the scan by
21115
+ * them changes no reported number. Severity, subtype, provider, action,
21116
+ * status, tool, repo, file and `q` all stay in JS — each has a facet, and a
21117
+ * facet excludes its own filter, so a row the filter rejects still has to be
21118
+ * counted. Pushing any of them into SQL would silently empty its own facet.
21119
+ * Several could not be expressed there anyway: status comes from the one
21120
+ * shared classifier (deriveFindingStatus), and provider 'api' means "a tool
21121
+ * none of the mappers names", which no IN-list can say.
21122
+ *
21123
+ * The scan runs from the top of the scope on every request, not from the
21124
+ * cursor: `totals` and `facets` describe the whole filtered scope and must not
21125
+ * move as the caller pages. Rows are pulled in batches so memory stays flat
21126
+ * while the counting runs, and only the page itself is retained.
21127
+ */
21128
+ listFindingInstances(query) {
21129
+ const opts = {
21130
+ severity: query.severity,
21131
+ subtype: query.subtype,
21132
+ providers: query.provider,
21133
+ actions: query.action,
21134
+ statuses: query.status,
21135
+ tools: query.tool,
21136
+ repo: query.repo,
21137
+ file: query.file,
21138
+ q: query.q
21139
+ };
21140
+ const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
21141
+ const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
21142
+ const accumulator = createInstanceFacetAccumulator(opts);
21143
+ const items = [];
21144
+ let total = 0;
21145
+ let last;
21146
+ let hasMore = false;
21147
+ for (const row of this.scanFindingRows({
21148
+ sessionId: query.sessionId,
21149
+ from: query.from
21150
+ })) {
21151
+ accumulator.add(row);
21152
+ if (!matchesInstanceFilters(row, opts)) continue;
21153
+ total += 1;
21154
+ if (items.length < limit) {
21155
+ items.push(toInstanceDetail(row));
21156
+ last = row;
21157
+ } else {
21158
+ hasMore = true;
21159
+ }
21160
+ }
21161
+ const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
21162
+ if (cursor !== null) {
21163
+ const resumed = this.pageAfter(cursor, opts, limit, query);
21164
+ return Promise.resolve({
21165
+ totals: { findings: total },
21166
+ facets: accumulator.facets(),
21167
+ items: resumed.items,
21168
+ nextCursor: resumed.nextCursor
21169
+ });
21170
+ }
21171
+ return Promise.resolve({
21172
+ totals: { findings: total },
21173
+ facets: accumulator.facets(),
21174
+ items,
21175
+ nextCursor
21176
+ });
21177
+ }
21178
+ /**
21179
+ * The page of matching rows strictly after `cursor`. Separate from the
21180
+ * counting pass because that one starts at the top of the scope by design;
21181
+ * this one narrows the scan with the same keyset predicate the activity list
21182
+ * uses, so a later page costs less than the first rather than more.
21183
+ */
21184
+ pageAfter(cursor, opts, limit, query) {
21185
+ const items = [];
21186
+ let last;
21187
+ let hasMore = false;
21188
+ for (const row of this.scanFindingRows({
21189
+ sessionId: query.sessionId,
21190
+ from: query.from,
21191
+ after: cursor
21192
+ })) {
21193
+ if (!matchesInstanceFilters(row, opts)) continue;
21194
+ if (items.length < limit) {
21195
+ items.push(toInstanceDetail(row));
21196
+ last = row;
21197
+ } else {
21198
+ hasMore = true;
21199
+ break;
21200
+ }
21201
+ }
21202
+ return {
21203
+ items,
21204
+ nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
21205
+ };
21206
+ }
21207
+ /**
21208
+ * The same findings folded by location: repository, then file within it.
21209
+ *
21210
+ * The grouping keys come from the capturing event's attributes, which is what
21211
+ * the local store relates a finding to — there is no finding↔asset row to
21212
+ * group by instead. A repo or file the event did not record folds into the
21213
+ * empty-string bucket, which the view renders but does not link, since no
21214
+ * filter can name it.
21215
+ */
21216
+ listFindingLocations(query) {
21217
+ const opts = {
21218
+ severity: query.severity,
21219
+ subtype: query.subtype,
21220
+ providers: query.provider,
21221
+ actions: query.action,
21222
+ statuses: query.status,
21223
+ tools: query.tool,
21224
+ q: query.q
21225
+ };
21226
+ const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
21227
+ const byRepo = /* @__PURE__ */ new Map();
21228
+ let total = 0;
21229
+ for (const row of this.scanFindingRows({
21230
+ sessionId: query.sessionId,
21231
+ from: query.from
21232
+ })) {
21233
+ if (!matchesInstanceFilters(row, opts)) continue;
21234
+ total += 1;
21235
+ let files = byRepo.get(row.repo);
21236
+ if (files === void 0) {
21237
+ files = /* @__PURE__ */ new Map();
21238
+ byRepo.set(row.repo, files);
21239
+ }
21240
+ let acc = files.get(row.file);
21241
+ if (acc === void 0) {
21242
+ acc = newLocationAccumulator();
21243
+ files.set(row.file, acc);
21244
+ }
21245
+ addToLocation(acc, row);
21246
+ }
21247
+ let fileCount = 0;
21248
+ const repos = [...byRepo.entries()].map(([repo, files]) => {
21249
+ fileCount += files.size;
21250
+ const fileRows = [...files.entries()].map(([file2, acc]) => ({
21251
+ file: file2,
21252
+ instanceCount: acc.instanceCount,
21253
+ maxSeverity: acc.maxSeverity,
21254
+ latestDetectedAt: acc.latestDetectedAt,
21255
+ ...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
21256
+ ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
21257
+ })).sort(compareLocationOrder);
21258
+ const rollup = fileRows.reduce(
21259
+ (a, f) => ({
21260
+ instanceCount: a.instanceCount + f.instanceCount,
21261
+ maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
21262
+ latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
21263
+ }),
21264
+ {
21265
+ instanceCount: 0,
21266
+ maxSeverity: fileRows[0]?.maxSeverity ?? "low",
21267
+ latestDetectedAt: ""
21268
+ }
21269
+ );
21270
+ const statuses = fileRows.map((f) => f.status);
21271
+ const folded = foldGroupStatus(statuses);
21272
+ return {
21273
+ repo,
21274
+ instanceCount: rollup.instanceCount,
21275
+ maxSeverity: rollup.maxSeverity,
21276
+ latestDetectedAt: rollup.latestDetectedAt,
21277
+ ...folded === void 0 ? {} : { status: folded },
21278
+ files: fileRows
21279
+ };
21280
+ });
21281
+ repos.sort(compareLocationOrder);
21282
+ return Promise.resolve({
21283
+ totals: { findings: total, repos: repos.length, files: fileCount },
21284
+ items: repos.slice(0, limit),
21285
+ hasMore: repos.length > limit
21286
+ });
21287
+ }
21288
+ /**
21289
+ * Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
21290
+ *
21291
+ * A generator so a caller streams the scope without it ever being an array:
21292
+ * the flat list counts and facets the whole filtered scope, which on a large
21293
+ * store is far more rows than any page. Each batch advances the same keyset
21294
+ * predicate the page read uses, so the scan is a sequence of bounded reads
21295
+ * rather than one unbounded result set.
21296
+ *
21297
+ * The latest-resolution lookup is the CORRELATED form, not the derived table
21298
+ * the grouped path joins: only `status` is needed, idx_finding_resolution_key
21299
+ * makes it a point lookup per row, and the derived table would re-materialize
21300
+ * a window over the whole resolution table once per batch.
21301
+ *
21302
+ * `scope` carries ONLY what no facet counts. A filter dimension narrowed here
21303
+ * would be missing from its own facet, which is computed by excluding that
21304
+ * dimension — see listFindingInstances.
21305
+ */
21306
+ *scanFindingRows(scope) {
21307
+ const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
21308
+ const params = [];
21309
+ if (scope.sessionId !== void 0 && scope.sessionId !== "") {
21310
+ conditions.push("e.root_session_id = ?");
21311
+ params.push(scope.sessionId);
21312
+ }
21313
+ if (scope.from !== void 0) {
21314
+ conditions.push("e.started_at >= ?");
21315
+ params.push(isoToEpochMillis(scope.from));
21316
+ }
21317
+ const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
21318
+ d.severity AS severity, f.masked_match AS masked_match,
21319
+ f.action_taken AS action_taken, f.confidence AS confidence,
21320
+ e.started_at AS occurred_at,
21321
+ json_extract(e.attributes, '$.source_tool') AS source_tool,
21322
+ json_extract(e.attributes, '$.repo') AS repo,
21323
+ json_extract(e.attributes, '$.file_path') AS file,
21324
+ json_extract(e.attributes, '$.tool_name') AS tool_name,
21325
+ f.audit_event_id AS event_id, e.root_session_id AS session_id,
21326
+ e.event_type AS kind, f.finding_key AS finding_key,
21327
+ ${latestResolutionStatusSql("f")} AS latest_status
21328
+ FROM inspection_findings f
21329
+ JOIN audit_events e ON e.id = f.audit_event_id
21330
+ JOIN inspection_definitions d ON d.id = f.inspection_definition_id
21331
+ WHERE ${conditions.join(" AND ")}
21332
+ AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
21333
+ ORDER BY e.started_at DESC, f.id DESC
21334
+ LIMIT ?`;
21335
+ let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
21336
+ for (; ; ) {
21337
+ const rows = allRows(this.db.prepare(sql), [
21338
+ ...params,
21339
+ after.startedAtMs,
21340
+ after.startedAtMs,
21341
+ after.id,
21342
+ SCAN_BATCH_ROWS
21343
+ ]);
21344
+ for (const r of rows) {
21345
+ yield {
21346
+ id: r.id,
21347
+ ruleId: r.rule_id,
21348
+ category: r.category,
21349
+ severity: r.severity,
21350
+ maskedMatch: r.masked_match,
21351
+ actionTaken: r.action_taken,
21352
+ confidence: r.confidence,
21353
+ occurredAt: epochMillisToIso(r.occurred_at),
21354
+ sourceTool: r.source_tool,
21355
+ repo: r.repo ?? "",
21356
+ file: r.file ?? "",
21357
+ ...r.tool_name === null ? {} : { toolName: r.tool_name },
21358
+ eventId: r.event_id,
21359
+ ...r.session_id === null ? {} : { sessionId: r.session_id },
21360
+ status: deriveInstanceStatus(r)
21361
+ };
21362
+ }
21363
+ if (rows.length < SCAN_BATCH_ROWS) return;
21364
+ const lastRow = rows[rows.length - 1];
21365
+ if (lastRow === void 0) return;
21366
+ after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
21367
+ }
21368
+ }
20571
21369
  groupAggregates(withSearchText, scope) {
20572
21370
  const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
20573
21371
  group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
@@ -20828,7 +21626,7 @@ var SqliteInspectionFindingsRepository = class {
20828
21626
  };
20829
21627
 
20830
21628
  // ../../packages/persistence/src/repositories/installed-packs.ts
20831
- import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
21629
+ import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
20832
21630
 
20833
21631
  // ../../packages/persistence/src/semver.ts
20834
21632
  function parse3(version2) {
@@ -20979,7 +21777,7 @@ var SqliteInstalledPacksRepository = class {
20979
21777
  let behind = false;
20980
21778
  for (const row of rows) {
20981
21779
  const params = {
20982
- id: randomUUID2(),
21780
+ id: randomUUID3(),
20983
21781
  namespace: row.namespace,
20984
21782
  packId: row.packId,
20985
21783
  version: row.version,
@@ -20991,7 +21789,7 @@ var SqliteInstalledPacksRepository = class {
20991
21789
  if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
20992
21790
  this.upsertAvailableStmt.run({
20993
21791
  ...params,
20994
- id: randomUUID2(),
21792
+ id: randomUUID3(),
20995
21793
  recordedBy: meta3?.recordedBy ?? null
20996
21794
  });
20997
21795
  } else {
@@ -21314,14 +22112,15 @@ var SqliteInventoryRepository = class {
21314
22112
  };
21315
22113
 
21316
22114
  // ../../packages/persistence/src/repositories/inventory-assets.ts
21317
- import { randomUUID as randomUUID3 } from "crypto";
22115
+ import { randomUUID as randomUUID4 } from "crypto";
21318
22116
  var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
21319
22117
  var VALID_HARNESS_IDS = new Set(HarnessId.options);
21320
22118
  var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
21321
22119
  var HARNESS_LABELS = {
21322
22120
  claudecode: "Claude Code",
21323
22121
  cursor: "Cursor",
21324
- codex: "Codex"
22122
+ codex: "Codex",
22123
+ antigravity: "Antigravity"
21325
22124
  };
21326
22125
  var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
21327
22126
  var EMPTY_PROJECT_AGG = {
@@ -21336,6 +22135,7 @@ function resolveHarnessId(attrs, row) {
21336
22135
  if (t.includes("claudecode") || t === "claude") return "claudecode";
21337
22136
  if (t.includes("cursor")) return "cursor";
21338
22137
  if (t.includes("codex")) return "codex";
22138
+ if (t.includes("antigravity")) return "antigravity";
21339
22139
  return null;
21340
22140
  }
21341
22141
  function isLiveRealClaudeCode(rows) {
@@ -21794,7 +22594,7 @@ var SqliteInventoryAssetsRepository = class {
21794
22594
  `INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
21795
22595
  VALUES (:id, :projectId, :path, :access, :now, :now)
21796
22596
  ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
21797
- ).run({ id: randomUUID3(), projectId, path, access, now: Date.now() });
22597
+ ).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
21798
22598
  }
21799
22599
  return true;
21800
22600
  }
@@ -21815,7 +22615,7 @@ var SqliteInventoryAssetsRepository = class {
21815
22615
  `INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
21816
22616
  VALUES (:id, :assetId, :trust, :now, :now)
21817
22617
  ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
21818
- ).run({ id: randomUUID3(), assetId, trust, now: Date.now() });
22618
+ ).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
21819
22619
  }
21820
22620
  this.configRowsCache = void 0;
21821
22621
  return "ok";
@@ -22112,7 +22912,7 @@ var SqliteInventoryAssetsRepository = class {
22112
22912
  };
22113
22913
 
22114
22914
  // ../../packages/persistence/src/repositories/policies.ts
22115
- import { randomUUID as randomUUID4 } from "crypto";
22915
+ import { randomUUID as randomUUID5 } from "crypto";
22116
22916
  var SqlitePoliciesRepository = class {
22117
22917
  constructor(db) {
22118
22918
  this.db = db;
@@ -22147,7 +22947,7 @@ var SqlitePoliciesRepository = class {
22147
22947
  failOpenTransaction(this.db, () => {
22148
22948
  for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
22149
22949
  stmt.run({
22150
- id: randomUUID4(),
22950
+ id: randomUUID5(),
22151
22951
  target: JSON.stringify({ category }),
22152
22952
  action,
22153
22953
  now: Date.now()
@@ -22167,7 +22967,7 @@ var SqlitePoliciesRepository = class {
22167
22967
  `INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
22168
22968
  VALUES (:id, 'global', :target, :action, 1, :now, :now)
22169
22969
  ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
22170
- ).run({ id: randomUUID4(), target: JSON.stringify({ category }), action, now });
22970
+ ).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
22171
22971
  }
22172
22972
  // Caps every global per-category policy currently set to block/redact down
22173
22973
  // to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
@@ -22235,7 +23035,7 @@ var SqlitePolicyCatalogRepository = class {
22235
23035
  };
22236
23036
 
22237
23037
  // ../../packages/persistence/src/repositories/project-files.ts
22238
- import { randomUUID as randomUUID5 } from "crypto";
23038
+ import { randomUUID as randomUUID6 } from "crypto";
22239
23039
  var SqliteProjectFilesRepository = class {
22240
23040
  constructor(db) {
22241
23041
  this.db = db;
@@ -22267,7 +23067,7 @@ var SqliteProjectFilesRepository = class {
22267
23067
  const stamp = Math.max(now, maxStamp + 1);
22268
23068
  for (const file2 of scan2.files) {
22269
23069
  this.upsertStmt.run({
22270
- id: randomUUID5(),
23070
+ id: randomUUID6(),
22271
23071
  projectId,
22272
23072
  path: file2.path,
22273
23073
  name: file2.name,
@@ -22281,7 +23081,7 @@ var SqliteProjectFilesRepository = class {
22281
23081
  };
22282
23082
 
22283
23083
  // ../../packages/persistence/src/repositories/resolutions.ts
22284
- import { randomUUID as randomUUID6 } from "crypto";
23084
+ import { randomUUID as randomUUID7 } from "crypto";
22285
23085
  var SqliteResolutionsRepository = class {
22286
23086
  constructor(db, now = () => Date.now()) {
22287
23087
  this.db = db;
@@ -22335,7 +23135,7 @@ var SqliteResolutionsRepository = class {
22335
23135
  */
22336
23136
  insertResolution(r) {
22337
23137
  this.insertStmt.run({
22338
- id: randomUUID6(),
23138
+ id: randomUUID7(),
22339
23139
  findingKey: r.findingKey,
22340
23140
  status: FindingStatus.parse(r.status),
22341
23141
  method: ResolutionMethod.parse(r.method),
@@ -22394,13 +23194,51 @@ var SqliteRuleProbeCacheRepository = class {
22394
23194
  this.readStmt = db.prepare(
22395
23195
  `SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
22396
23196
  );
23197
+ this.countQuarantinedStmt = db.prepare(
23198
+ `SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
23199
+ );
23200
+ this.clearQuarantinedStmt = db.prepare(
23201
+ `DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
23202
+ );
22397
23203
  }
22398
23204
  db;
22399
23205
  upsertStmt;
22400
23206
  readStmt;
23207
+ countQuarantinedStmt;
23208
+ clearQuarantinedStmt;
22401
23209
  getVerdict(ruleKey) {
22402
23210
  return getRow(this.readStmt, { ruleKey });
22403
23211
  }
23212
+ /** How many rules are currently excluded by a cached quarantine verdict. */
23213
+ countQuarantined() {
23214
+ return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
23215
+ }
23216
+ /**
23217
+ * Forgets every quarantine verdict, so the rules behind them are measured
23218
+ * again on the next load. This is the undo for a verdict the machine reached
23219
+ * on its own: a rule terminated mid-scan is cached forever and dropped from
23220
+ * every later scan, and a timing verdict is a wall-clock judgement that a
23221
+ * loaded or slow machine can reach about a rule that is in fact fine.
23222
+ *
23223
+ * Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
23224
+ * keeping, and dropping it would make every rule pay the battery again.
23225
+ *
23226
+ * Reports `refused` from the write's own result rather than inferring it from
23227
+ * the row count. The two are NOT the same answer: `failOpenTransaction`
23228
+ * swallows a contended DELETE (another writer holding the lock past
23229
+ * `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
23230
+ * leaves the count unchanged, which is indistinguishable from "there was
23231
+ * nothing to clear". An undo that reports success while the quarantines are
23232
+ * still in place is worse than one that fails, because the rules it claimed
23233
+ * to restore are silently still disabled.
23234
+ */
23235
+ clearQuarantined() {
23236
+ const before = this.countQuarantined();
23237
+ const committed = failOpenTransaction(this.db, () => {
23238
+ this.clearQuarantinedStmt.run();
23239
+ });
23240
+ return { refused: !committed, cleared: before - this.countQuarantined() };
23241
+ }
22404
23242
  setVerdict(ruleKey, verdict, worstProbeMs) {
22405
23243
  failOpenTransaction(this.db, () => {
22406
23244
  this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
@@ -22455,7 +23293,39 @@ var SqliteScanLedgerRepository = class {
22455
23293
  };
22456
23294
 
22457
23295
  // ../../packages/persistence/src/repositories/secret-vault.ts
22458
- import { randomUUID as randomUUID7 } from "crypto";
23296
+ import { randomUUID as randomUUID8 } from "crypto";
23297
+ function pageLimit(requested, fallback) {
23298
+ if (requested === void 0) return fallback;
23299
+ return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
23300
+ }
23301
+ function encodeReuseCursor(payload) {
23302
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
23303
+ }
23304
+ function decodeReuseCursor(cursor) {
23305
+ const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
23306
+ if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
23307
+ // ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
23308
+ // null cursor, which the caller reads as "end of list" — the one outcome a
23309
+ // malformed cursor must never produce, since restarting from the top is the
23310
+ // documented behaviour and the only recoverable one. (`1e999` is valid JSON
23311
+ // and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
23312
+ Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
23313
+ return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
23314
+ }
23315
+ return null;
23316
+ }
23317
+ var REUSED_PREDICATE = `(v.occurrence_count > 1
23318
+ OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
23319
+ var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
23320
+ v.occurrence_count, v.first_seen, v.last_seen`;
23321
+ function toSighting(row) {
23322
+ return {
23323
+ location: row.location,
23324
+ kind: row.kind,
23325
+ firstSeen: new Date(row.first_seen).toISOString(),
23326
+ lastSeen: new Date(row.last_seen).toISOString()
23327
+ };
23328
+ }
22459
23329
  var SELECT_COLUMNS = `
22460
23330
  pointer_id AS pointerId,
22461
23331
  value_fingerprint AS valueFingerprint,
@@ -22639,39 +23509,67 @@ var SqliteSecretVaultRepository = class {
22639
23509
  VALUES (:id, :pointerId, :location, :kind, :now, :now)
22640
23510
  ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
22641
23511
  ).run({
22642
- id: randomUUID7(),
23512
+ id: randomUUID8(),
22643
23513
  pointerId: entry.pointerId,
22644
23514
  location: entry.location,
22645
23515
  kind: entry.kind,
22646
23516
  now
22647
23517
  });
22648
23518
  }
22649
- listSightings(pointerId) {
23519
+ /**
23520
+ * Sightings for a whole page of pointers, in ONE query grouped in JS rather
23521
+ * than one query per row. A pointer with no sightings still gets an entry, so
23522
+ * the caller never has to distinguish "none" from "missing".
23523
+ *
23524
+ * The `IN` list is sized to the page, so this statement cannot be cached on
23525
+ * the instance the way the fixed-shape ones in the constructor are.
23526
+ */
23527
+ sightingsFor(pointerIds) {
23528
+ const byPointer = new Map(pointerIds.map((id) => [id, []]));
23529
+ if (pointerIds.length === 0) return byPointer;
22650
23530
  const rows = allRows(
22651
23531
  this.db.prepare(
22652
- `SELECT location, kind, first_seen, last_seen FROM secret_vault_sighting
22653
- WHERE pointer_id = :pointerId ORDER BY last_seen DESC`
23532
+ `SELECT pointer_id, location, kind, first_seen, last_seen
23533
+ FROM secret_vault_sighting
23534
+ WHERE pointer_id IN (${placeholders(pointerIds.length)})
23535
+ ORDER BY last_seen DESC`
22654
23536
  ),
22655
- { pointerId }
23537
+ pointerIds
22656
23538
  );
23539
+ for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
23540
+ return byPointer;
23541
+ }
23542
+ /** Hydrate a page of raw inventory rows with their sightings, batched. */
23543
+ toInventoryEntries(rows) {
23544
+ const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
22657
23545
  return rows.map((r) => ({
22658
- location: r.location,
22659
- kind: r.kind,
23546
+ pointerId: r.pointer_id,
23547
+ category: r.category,
23548
+ ...r.provider === null ? {} : { provider: r.provider },
23549
+ maskedMatch: r.masked_match,
23550
+ occurrences: r.occurrence_count,
22660
23551
  firstSeen: new Date(r.first_seen).toISOString(),
22661
- lastSeen: new Date(r.last_seen).toISOString()
23552
+ lastSeen: new Date(r.last_seen).toISOString(),
23553
+ revealGrantId: r.grant_id,
23554
+ sightings: sightings.get(r.pointer_id) ?? []
22662
23555
  }));
22663
23556
  }
22664
23557
  /**
22665
- * The dashboard inventory: every vaulted value's descriptor data joined with
22666
- * its sightings and the active reveal-to-model grant when one exists.
22667
- * Raw-free by construction — neither the fingerprint nor the ciphertext
22668
- * columns are selected.
23558
+ * The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
23559
+ * value's descriptor data joined with its sightings and the active
23560
+ * reveal-to-model grant when one exists. Raw-free by construction — neither
23561
+ * the fingerprint nor the ciphertext columns are selected.
23562
+ *
23563
+ * `totals.values` counts the whole store, not the page, so the count a reader
23564
+ * sees never depends on how far they have paged.
22669
23565
  */
22670
- listInventory(now = Date.now()) {
23566
+ listInventory(query = {}, now = Date.now()) {
23567
+ const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
23568
+ const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
23569
+ const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
22671
23570
  const rows = allRows(
22672
23571
  this.db.prepare(
22673
- `SELECT v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
22674
- v.occurrence_count, v.first_seen, v.last_seen,
23572
+ `SELECT ${INVENTORY_COLUMNS},
22675
23573
  (SELECT e.id FROM exceptions e
22676
23574
  WHERE e.rule_id = v.rule_id
22677
23575
  AND e.value_fingerprint = v.value_fingerprint
@@ -22679,45 +23577,109 @@ var SqliteSecretVaultRepository = class {
22679
23577
  AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
22680
23578
  LIMIT 1) AS grant_id
22681
23579
  FROM secret_vault v
22682
- ORDER BY v.last_seen DESC`
23580
+ ${where}
23581
+ ORDER BY v.last_seen DESC, v.pointer_id DESC
23582
+ LIMIT :limit`
22683
23583
  ),
22684
- { now }
23584
+ bindParams({
23585
+ now,
23586
+ limit: limit + 1,
23587
+ ...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
23588
+ })
22685
23589
  );
22686
- return rows.map((r) => ({
22687
- pointerId: r.pointer_id,
22688
- category: r.category,
22689
- ...r.provider === null ? {} : { provider: r.provider },
22690
- maskedMatch: r.masked_match,
22691
- occurrences: r.occurrence_count,
22692
- firstSeen: new Date(r.first_seen).toISOString(),
22693
- lastSeen: new Date(r.last_seen).toISOString(),
22694
- revealGrantId: r.grant_id,
22695
- sightings: this.listSightings(r.pointer_id)
22696
- }));
23590
+ const hasMore = rows.length > limit;
23591
+ const page = hasMore ? rows.slice(0, limit) : rows;
23592
+ const last = page[page.length - 1];
23593
+ return {
23594
+ totals: { values: this.countEntries() },
23595
+ items: this.toInventoryEntries(page),
23596
+ // Minted from the last row of the PAGE, never the extra probe row.
23597
+ nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
23598
+ };
22697
23599
  }
22698
23600
  /**
22699
- * The de-reference trail, newest first. By default the batched, high-volume
22700
- * reasons (display, view-render) are hidden and counted instead the rows
22701
- * that matter as a signal are the model crossings, and burying them under
22702
- * render noise would defeat the audit's purpose.
23601
+ * Values reused on this machine detected more than once, or written to more
23602
+ * than one location most-reused first, one page at a time.
23603
+ *
23604
+ * Its own read rather than a filter over an inventory page: reuse is a
23605
+ * property of the whole store, and deriving it from 50 newest rows would
23606
+ * under-report exactly the values a reader most needs to see.
22703
23607
  */
22704
- listDerefs(opts) {
22705
- const limit = opts?.limit ?? 200;
22706
- const where = opts?.includeBatched === true ? "" : `WHERE reason NOT IN ('display', 'view-render')`;
23608
+ listReuse(query = {}, now = Date.now()) {
23609
+ const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
23610
+ const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
23611
+ const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
23612
+ OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
23613
+ const rows = allRows(
23614
+ this.db.prepare(
23615
+ `SELECT ${INVENTORY_COLUMNS},
23616
+ (SELECT e.id FROM exceptions e
23617
+ WHERE e.rule_id = v.rule_id
23618
+ AND e.value_fingerprint = v.value_fingerprint
23619
+ AND e.key_version = v.fingerprint_key_version
23620
+ AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
23621
+ LIMIT 1) AS grant_id
23622
+ FROM secret_vault v
23623
+ WHERE ${REUSED_PREDICATE} ${after}
23624
+ ORDER BY v.occurrence_count DESC, v.pointer_id DESC
23625
+ LIMIT :limit`
23626
+ ),
23627
+ bindParams({
23628
+ now,
23629
+ limit: limit + 1,
23630
+ ...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
23631
+ })
23632
+ );
23633
+ const hasMore = rows.length > limit;
23634
+ const page = hasMore ? rows.slice(0, limit) : rows;
23635
+ const last = page[page.length - 1];
23636
+ return {
23637
+ totals: { reused: this.countReused() },
23638
+ items: this.toInventoryEntries(page),
23639
+ nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
23640
+ };
23641
+ }
23642
+ /**
23643
+ * The de-reference trail, newest first, one page at a time. By default the
23644
+ * batched, high-volume reasons (display, view-render) are hidden and counted
23645
+ * instead — the rows that matter as a signal are the model crossings, and
23646
+ * burying them under render noise would defeat the audit's purpose.
23647
+ *
23648
+ * `hiddenBatched` counts the whole trail rather than the page: it is what the
23649
+ * view's "N hidden" line and its toggle speak for, so it must not shrink as
23650
+ * the reader pages.
23651
+ */
23652
+ listDerefs(query = {}) {
23653
+ const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
23654
+ const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
23655
+ const conditions = [];
23656
+ if (query.includeBatched !== true) {
23657
+ conditions.push(`reason NOT IN ('display', 'view-render')`);
23658
+ }
23659
+ if (cursor !== null) {
23660
+ conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
23661
+ }
23662
+ const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
22707
23663
  const rows = allRows(
22708
23664
  this.db.prepare(
22709
23665
  `SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
22710
23666
  FROM secret_vault_deref ${where}
22711
- ORDER BY at DESC, rowid DESC LIMIT :limit`
23667
+ ORDER BY at DESC, id DESC LIMIT :limit`
22712
23668
  ),
22713
- { limit }
23669
+ bindParams({
23670
+ limit: limit + 1,
23671
+ ...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
23672
+ })
22714
23673
  );
22715
- const hiddenBatched = opts?.includeBatched === true ? 0 : countScalar(
23674
+ const hasMore = rows.length > limit;
23675
+ const page = hasMore ? rows.slice(0, limit) : rows;
23676
+ const last = page[page.length - 1];
23677
+ const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
22716
23678
  this.db,
22717
23679
  `SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
22718
23680
  );
22719
23681
  return {
22720
- rows: rows.map((r) => ({
23682
+ items: page.map((r) => ({
22721
23683
  id: r.id,
22722
23684
  pointerId: r.pointer_id,
22723
23685
  at: new Date(r.at).toISOString(),
@@ -22727,12 +23689,20 @@ var SqliteSecretVaultRepository = class {
22727
23689
  ...r.grant_id === null ? {} : { grantId: r.grant_id },
22728
23690
  pointerCount: r.pointer_count
22729
23691
  })),
23692
+ nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
22730
23693
  hiddenBatched
22731
23694
  };
22732
23695
  }
22733
23696
  countEntries() {
22734
23697
  return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
22735
23698
  }
23699
+ /** Values reused on this machine — the reuse list's page-independent total. */
23700
+ countReused() {
23701
+ return countScalar(
23702
+ this.db,
23703
+ `SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
23704
+ );
23705
+ }
22736
23706
  };
22737
23707
 
22738
23708
  // ../../packages/persistence/src/repositories/security.ts
@@ -22747,7 +23717,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
22747
23717
  var SCAN_COVERAGE = [
22748
23718
  { provider: "claudecode", coverage: 100, supported: true },
22749
23719
  { provider: "cursor", coverage: 0, supported: false },
22750
- { provider: "codex", coverage: 0, supported: false },
23720
+ { provider: "codex", coverage: 80, supported: true },
23721
+ { provider: "antigravity", coverage: 60, supported: true },
23722
+ { provider: "claudeai", coverage: 0, supported: false },
22751
23723
  { provider: "chatgpt", coverage: 0, supported: false },
22752
23724
  { provider: "copilot", coverage: 0, supported: false },
22753
23725
  { provider: "api", coverage: 0, supported: false }
@@ -23080,7 +24052,7 @@ var SqliteSecurityRepository = class {
23080
24052
  };
23081
24053
 
23082
24054
  // ../../packages/persistence/src/repositories/shares.ts
23083
- import { randomUUID as randomUUID8 } from "crypto";
24055
+ import { randomUUID as randomUUID9 } from "crypto";
23084
24056
  var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
23085
24057
  var IN_CHUNK = 500;
23086
24058
  var KIND_ORDER = ["provider", "internal", "external", "ip"];
@@ -23336,7 +24308,7 @@ var SqliteSharesRepository = class {
23336
24308
  (id, destination_id, host, decision, created_at, updated_at)
23337
24309
  VALUES (:id, :destinationId, :host, :decision, :now, :now)`
23338
24310
  ).run({
23339
- id: randomUUID8(),
24311
+ id: randomUUID9(),
23340
24312
  destinationId,
23341
24313
  host: dest.host,
23342
24314
  decision,
@@ -23485,7 +24457,7 @@ var SqliteSharesRepository = class {
23485
24457
  let destinationId = destIds.get(hit.host);
23486
24458
  if (destinationId === void 0) {
23487
24459
  destStmt.run({
23488
- id: randomUUID8(),
24460
+ id: randomUUID9(),
23489
24461
  kind: hit.kind,
23490
24462
  name: hit.name,
23491
24463
  host: hit.host,
@@ -23501,7 +24473,7 @@ var SqliteSharesRepository = class {
23501
24473
  let endpointId = endpointIds.get(endpointKey);
23502
24474
  if (endpointId === void 0) {
23503
24475
  endpointStmt.run({
23504
- id: randomUUID8(),
24476
+ id: randomUUID9(),
23505
24477
  destinationId,
23506
24478
  method: hit.method,
23507
24479
  transport: hit.transport,
@@ -23514,7 +24486,7 @@ var SqliteSharesRepository = class {
23514
24486
  endpointIds.set(endpointKey, endpointId);
23515
24487
  }
23516
24488
  siteStmt.run({
23517
- id: randomUUID8(),
24489
+ id: randomUUID9(),
23518
24490
  endpointId,
23519
24491
  project: input.project,
23520
24492
  projectKey: input.projectKey,
@@ -23879,6 +24851,9 @@ function purgeSampleData(db) {
23879
24851
  }
23880
24852
 
23881
24853
  // ../../packages/persistence/src/database.ts
24854
+ var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
24855
+ "aka.persistence.unsafeTestOnlyRawHandle"
24856
+ );
23882
24857
  function linkHost(input, hostId) {
23883
24858
  return hostId ? { ...input, hostId } : input;
23884
24859
  }
@@ -23900,21 +24875,34 @@ function openWithPragmas(file2) {
23900
24875
  }
23901
24876
  return db;
23902
24877
  }
23903
- function backupLegacyStore(file2) {
23904
- const backup = `${file2}.legacy.${String(Date.now())}.bak`;
23905
- renameSync2(file2, backup);
23906
- tightenFile(backup);
23907
- for (const sidecar of dbSidecars(file2)) {
23908
- if (existsSync(sidecar)) rmSync2(sidecar);
24878
+ function backupLegacyStore(db, file2) {
24879
+ reapStalePartials(file2);
24880
+ const backup = backupPath(file2, "legacy");
24881
+ let snapshotted = false;
24882
+ let snapshotError;
24883
+ try {
24884
+ snapshotStore(db, backup);
24885
+ snapshotted = true;
24886
+ } catch (error51) {
24887
+ snapshotError = error51;
24888
+ } finally {
24889
+ db.close();
23909
24890
  }
24891
+ if (!snapshotted) {
24892
+ akaWarn(
24893
+ `Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
24894
+ );
24895
+ moveStoreAside(file2, backup);
24896
+ return backup;
24897
+ }
24898
+ discardStore(file2, backup);
23910
24899
  return backup;
23911
24900
  }
23912
24901
  function openAndInitialize(file2) {
23913
24902
  let db = openWithPragmas(file2);
23914
24903
  try {
23915
24904
  if (isForeignSqliteLineage(db)) {
23916
- db.close();
23917
- const backup = backupLegacyStore(file2);
24905
+ const backup = backupLegacyStore(db, file2);
23918
24906
  db = openWithPragmas(file2);
23919
24907
  akaWarn(
23920
24908
  `Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
@@ -23958,7 +24946,7 @@ function openAndInitialize(file2) {
23958
24946
  }
23959
24947
  function openLocalDatabase(dir) {
23960
24948
  ensureDataDirSync(dir);
23961
- const file2 = join(dir, DB_FILENAME);
24949
+ const file2 = join2(dir, DB_FILENAME);
23962
24950
  const {
23963
24951
  db,
23964
24952
  events,
@@ -24075,7 +25063,7 @@ function openLocalDatabase(dir) {
24075
25063
  const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
24076
25064
  if (!definitionId) continue;
24077
25065
  inspectionFindings.insertFinding({
24078
- id: randomUUID9(),
25066
+ id: randomUUID10(),
24079
25067
  auditEventId: record2.scanEvent.id,
24080
25068
  inspectionDefinitionId: definitionId,
24081
25069
  span: finding.span,
@@ -24181,22 +25169,38 @@ function openLocalDatabase(dir) {
24181
25169
  transaction,
24182
25170
  close: () => {
24183
25171
  db.close();
24184
- }
25172
+ },
25173
+ // Last, and a plain value rather than a getter, so `{ ...db }` carries it.
25174
+ [UNSAFE_TEST_ONLY_RAW_HANDLE]: db
24185
25175
  };
24186
25176
  }
24187
25177
 
25178
+ // ../../packages/persistence/src/file-lock.ts
25179
+ import { randomUUID as randomUUID11 } from "crypto";
25180
+ import {
25181
+ closeSync,
25182
+ existsSync as existsSync2,
25183
+ openSync,
25184
+ readFileSync,
25185
+ rmSync as rmSync3,
25186
+ statSync as statSync2,
25187
+ writeFileSync as writeFileSync2
25188
+ } from "fs";
25189
+ import { hostname as hostname3 } from "os";
25190
+ var PARK = new Int32Array(new SharedArrayBuffer(4));
25191
+
24188
25192
  // ../../packages/persistence/src/finding-key.ts
24189
25193
  import { createHash as createHash3 } from "crypto";
24190
25194
 
24191
25195
  // ../../packages/persistence/src/fingerprint.ts
24192
25196
  import { createHmac, randomBytes } from "crypto";
24193
- import { existsSync as existsSync2, readFileSync } from "fs";
24194
- import { join as join2 } from "path";
25197
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
25198
+ import { join as join3 } from "path";
24195
25199
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
24196
- var KEY_FILENAME = "exception.key";
25200
+ var EXCEPTION_KEY_FILENAME = "exception.key";
24197
25201
  var KEY_MATERIAL_BYTES = 32;
24198
25202
  function keyFilePath(dataDir2) {
24199
- return join2(dataDir2, KEY_FILENAME);
25203
+ return join3(dataDir2, EXCEPTION_KEY_FILENAME);
24200
25204
  }
24201
25205
  function parseKeyFile(raw) {
24202
25206
  const parsed = JSON.parse(raw);
@@ -24219,7 +25223,7 @@ function parseKeyFile(raw) {
24219
25223
  function readFingerprintKey(dataDir2) {
24220
25224
  let raw;
24221
25225
  try {
24222
- raw = readFileSync(keyFilePath(dataDir2), "utf8");
25226
+ raw = readFileSync2(keyFilePath(dataDir2), "utf8");
24223
25227
  } catch (err) {
24224
25228
  if (err.code === "ENOENT") return null;
24225
25229
  throw err instanceof Error ? err : new Error(String(err));
@@ -24231,18 +25235,18 @@ function readFingerprintKey(dataDir2) {
24231
25235
  import { renameSync as renameSync3 } from "fs";
24232
25236
  import { mkdir } from "fs/promises";
24233
25237
  import { homedir } from "os";
24234
- import { join as join3 } from "path";
25238
+ import { join as join4 } from "path";
24235
25239
  function defaultDataDir() {
24236
- return join3(homedir(), ".aka");
25240
+ return join4(homedir(), ".aka");
24237
25241
  }
24238
25242
  function settingsDir(base = defaultDataDir()) {
24239
- return join3(base, "settings");
25243
+ return join4(base, "settings");
24240
25244
  }
24241
25245
  function dataDir(base = defaultDataDir()) {
24242
- return join3(base, "data");
25246
+ return join4(base, "data");
24243
25247
  }
24244
25248
  function dbPath(base = defaultDataDir()) {
24245
- return join3(dataDir(base), "aka.db");
25249
+ return join4(dataDir(base), "aka.db");
24246
25250
  }
24247
25251
  function ensureLayoutDirSync(dir = defaultDataDir()) {
24248
25252
  ensureDataDirSync(dir);
@@ -24255,8 +25259,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
24255
25259
  for (const { name, dest } of moves) {
24256
25260
  try {
24257
25261
  ensureDataDirSync(dest);
24258
- const moved = join3(dest, name);
24259
- renameSync3(join3(base, name), moved);
25262
+ const moved = join4(dest, name);
25263
+ renameSync3(join4(base, name), moved);
24260
25264
  tightenFile(moved);
24261
25265
  } catch {
24262
25266
  }
@@ -24264,10 +25268,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
24264
25268
  }
24265
25269
 
24266
25270
  // ../../packages/persistence/src/settings.ts
24267
- import { readFileSync as readFileSync2 } from "fs";
24268
- import { join as join4 } from "path";
25271
+ import { readFileSync as readFileSync3 } from "fs";
25272
+ import { join as join5 } from "path";
25273
+ var SETTINGS_FILENAME = "settings.json";
24269
25274
  function readWorkspaceSettings(base = defaultDataDir()) {
24270
- const record2 = readJson(join4(settingsDir(base), "settings.json"));
25275
+ const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
24271
25276
  if (!record2) return defaultWorkspaceSettings();
24272
25277
  try {
24273
25278
  return WorkspaceSettings.parse(record2);
@@ -24278,7 +25283,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
24278
25283
  function readJson(file2) {
24279
25284
  let text;
24280
25285
  try {
24281
- text = readFileSync2(file2, "utf8");
25286
+ text = readFileSync3(file2, "utf8");
24282
25287
  } catch {
24283
25288
  return null;
24284
25289
  }
@@ -24300,34 +25305,34 @@ import { randomBytes as randomBytes2 } from "crypto";
24300
25305
  import {
24301
25306
  chmodSync as chmodSync2,
24302
25307
  mkdirSync as mkdirSync2,
24303
- readFileSync as readFileSync3,
25308
+ readFileSync as readFileSync4,
24304
25309
  renameSync as renameSync4,
24305
- rmSync as rmSync3,
24306
- statSync,
24307
- writeFileSync as writeFileSync2
25310
+ rmSync as rmSync4,
25311
+ statSync as statSync3,
25312
+ writeFileSync as writeFileSync3
24308
25313
  } from "fs";
24309
- import { join as join5 } from "path";
25314
+ import { join as join6 } from "path";
24310
25315
 
24311
25316
  // ../../packages/persistence/src/vault/vault.ts
24312
- import { randomBytes as randomBytes3, randomUUID as randomUUID10 } from "crypto";
25317
+ import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
24313
25318
 
24314
25319
  // ../../packages/persistence/src/warn-era-cap.ts
24315
- import { existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
24316
- import { join as join6 } from "path";
25320
+ import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
25321
+ import { join as join7 } from "path";
24317
25322
  var MARKER = "warn-era-capped";
24318
25323
  function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
24319
25324
  if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
24320
- const marker = join6(dataDir2, MARKER);
24321
- if (existsSync3(marker)) return { capped: 0, skipped: "already-run" };
25325
+ const marker = join7(dataDir2, MARKER);
25326
+ if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
24322
25327
  const capped = db.policies.capCategoryActions();
24323
- writeFileSync3(marker, `${new Date(Date.now()).toISOString()}
25328
+ writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
24324
25329
  `, { mode: DATA_FILE_MODE });
24325
25330
  return { capped };
24326
25331
  }
24327
25332
 
24328
25333
  // ../../packages/plugin-sdk/src/config.ts
24329
- import { existsSync as existsSync4 } from "fs";
24330
- import { join as join7 } from "path";
25334
+ import { existsSync as existsSync5 } from "fs";
25335
+ import { join as join8 } from "path";
24331
25336
 
24332
25337
  // ../../packages/plugin-sdk/src/provider-env.ts
24333
25338
  var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
@@ -24378,11 +25383,11 @@ function resolveProvider() {
24378
25383
  }
24379
25384
 
24380
25385
  // ../../packages/plugin-sdk/src/config.ts
24381
- function loadConfig(base = defaultDataDir()) {
25386
+ function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
24382
25387
  try {
24383
25388
  ensureLayoutDirSync(base);
24384
- const settingsFile = join7(settingsDir(base), "settings.json");
24385
- if (existsSync4(settingsFile)) tightenFile(settingsFile);
25389
+ const settingsFile = join8(settingsDir(base), "settings.json");
25390
+ if (existsSync5(settingsFile)) tightenFile(settingsFile);
24386
25391
  } catch {
24387
25392
  }
24388
25393
  migrateLegacyLayout(base);
@@ -24393,21 +25398,21 @@ function loadConfig(base = defaultDataDir()) {
24393
25398
  dbPath: dbPath(base),
24394
25399
  settingsDir: settingsDir(base),
24395
25400
  onboarded: settings.onboardedAt != null,
24396
- provider: resolveProviderSafe()
25401
+ provider: resolveProviderSafe(resolveProviderFn)
24397
25402
  };
24398
25403
  }
24399
- function resolveProviderSafe() {
25404
+ function resolveProviderSafe(resolveProviderFn) {
24400
25405
  try {
24401
- return resolveProvider();
25406
+ return resolveProviderFn();
24402
25407
  } catch {
24403
25408
  return { provider: "anthropic" };
24404
25409
  }
24405
25410
  }
24406
25411
 
24407
25412
  // ../../packages/plugin-sdk/src/config-inventory.ts
24408
- import { readdirSync, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
25413
+ import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
24409
25414
  import { homedir as homedir2 } from "os";
24410
- import { basename as basename2, join as join9 } from "path";
25415
+ import { basename as basename3, join as join10 } from "path";
24411
25416
 
24412
25417
  // ../../packages/detections/src/egress/registry.ts
24413
25418
  var EXTRACTOR_VERSION = "1";
@@ -26771,7 +27776,7 @@ var gcp_service_account_default = {
26771
27776
  severity: "critical",
26772
27777
  matcher: {
26773
27778
  type: "regex",
26774
- pattern: "\\b[a-z0-9][a-z0-9-]{2,60}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
27779
+ pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
26775
27780
  flags: "g"
26776
27781
  },
26777
27782
  examples: [
@@ -27143,40 +28148,71 @@ function bundledDetections() {
27143
28148
  }
27144
28149
 
27145
28150
  // ../../packages/plugin-sdk/src/repo.ts
27146
- import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
27147
- import { basename, dirname, isAbsolute, join as join8, sep as sep2 } from "path";
28151
+ import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
28152
+ import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
27148
28153
 
27149
28154
  // ../../packages/plugin-sdk/src/events.ts
27150
- import { createHash as createHash4, randomUUID as randomUUID11 } from "crypto";
28155
+ import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
28156
+
28157
+ // ../../packages/plugin-sdk/src/isolated-scan.ts
28158
+ import { existsSync as existsSync7 } from "fs";
28159
+ import { fileURLToPath } from "url";
28160
+ import { Worker } from "worker_threads";
27151
28161
 
27152
28162
  // ../../packages/plugin-sdk/src/inventory-resolver.ts
27153
- import { arch, hostname as hostname3, platform, release } from "os";
28163
+ import { arch, hostname as hostname4, platform, release } from "os";
27154
28164
 
27155
28165
  // ../../packages/plugin-sdk/src/nudge.ts
27156
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
27157
- import { join as join10 } from "path";
28166
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
28167
+ import { join as join11 } from "path";
27158
28168
 
27159
28169
  // ../../packages/plugin-sdk/src/paths.ts
27160
- import { readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
27161
- import { basename as basename3, dirname as dirname2, sep as sep3 } from "path";
28170
+ import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
28171
+ import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
27162
28172
 
27163
28173
  // ../../packages/plugin-sdk/src/project-files.ts
27164
28174
  var import_ignore = __toESM(require_ignore(), 1);
27165
- import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
27166
- import { basename as basename4, join as join11, relative, sep as sep4 } from "path";
28175
+ import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
28176
+ import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
28177
+
28178
+ // ../../packages/plugin-sdk/src/provider-env-antigravity.ts
28179
+ var optionalBaseUrl2 = external_exports.preprocess((v) => {
28180
+ if (typeof v === "string" && v.trim() === "") return void 0;
28181
+ return v;
28182
+ }, external_exports.string().optional()).catch(void 0);
28183
+ var optionalFlag = external_exports.preprocess((v) => {
28184
+ if (typeof v !== "string") return false;
28185
+ const normalized = v.trim().toLowerCase();
28186
+ return normalized !== "" && normalized !== "0" && normalized !== "false";
28187
+ }, external_exports.boolean()).catch(false);
28188
+ var antigravityProviderEnvShape = {
28189
+ GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
28190
+ GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
28191
+ };
28192
+ var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
28193
+
28194
+ // ../../packages/plugin-sdk/src/provider-env-codex.ts
28195
+ var optionalBaseUrl3 = external_exports.preprocess((v) => {
28196
+ if (typeof v === "string" && v.trim() === "") return void 0;
28197
+ return v;
28198
+ }, external_exports.string().optional()).catch(void 0);
28199
+ var codexProviderEnvShape = {
28200
+ OPENAI_BASE_URL: optionalBaseUrl3
28201
+ };
28202
+ var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
27167
28203
 
27168
28204
  // ../../packages/plugin-sdk/src/runtime.ts
27169
- import { randomUUID as randomUUID12 } from "crypto";
28205
+ import { randomUUID as randomUUID14 } from "crypto";
27170
28206
 
27171
28207
  // ../../packages/plugin-sdk/src/suppressions.ts
27172
28208
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
27173
28209
 
27174
28210
  // ../../packages/plugin-sdk/src/throttle.ts
27175
- import { mkdirSync as mkdirSync4, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
27176
- import { join as join12 } from "path";
28211
+ import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
28212
+ import { join as join13 } from "path";
27177
28213
 
27178
28214
  // ../../packages/plugin-runtime/src/standalone-gateway.ts
27179
- import { randomUUID as randomUUID13 } from "crypto";
28215
+ import { randomUUID as randomUUID15 } from "crypto";
27180
28216
 
27181
28217
  // ../../packages/plugin-runtime/src/recorder.ts
27182
28218
  var PLUGIN_RECORDER_BINARY = "plugin";
@@ -27338,7 +28374,7 @@ var StandaloneDataGateway = class {
27338
28374
  const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
27339
28375
  const installed = this.installedScanRules();
27340
28376
  const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
27341
- id: randomUUID13(),
28377
+ id: randomUUID15(),
27342
28378
  scope: "global",
27343
28379
  target: { ruleId },
27344
28380
  action,
@@ -27491,7 +28527,7 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
27491
28527
  }
27492
28528
 
27493
28529
  // ../../packages/plugin-runtime/src/handle-session-start.ts
27494
- import { randomUUID as randomUUID14 } from "crypto";
28530
+ import { randomUUID as randomUUID16 } from "crypto";
27495
28531
  var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
27496
28532
 
27497
28533
  // src/present.ts
@@ -27610,10 +28646,58 @@ function fenced(body) {
27610
28646
  return [fence, body, fence].join("\n");
27611
28647
  }
27612
28648
 
28649
+ // ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
28650
+ import { writeFileSync as writeFileSync7 } from "fs";
28651
+ import { join as join14 } from "path";
28652
+
28653
+ // ../../packages/setup-wizard/src/triage/plan-file.ts
28654
+ import { mkdtempSync, readFileSync as readFileSync9, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
28655
+ import { tmpdir } from "os";
28656
+ import { basename as basename6, dirname as dirname4, join as join15 } from "path";
28657
+ var SuppressionEntrySchema = external_exports.object({
28658
+ ruleId: external_exports.string(),
28659
+ category: DetectionCategory,
28660
+ valueFingerprint: external_exports.string(),
28661
+ keyVersion: external_exports.number(),
28662
+ maskedValue: external_exports.string(),
28663
+ justification: external_exports.string()
28664
+ });
28665
+ var ShowcaseCategorySchema = external_exports.object({
28666
+ category: DetectionCategory,
28667
+ action: BuiltinPolicyId,
28668
+ genuineCount: external_exports.number(),
28669
+ fpCount: external_exports.number(),
28670
+ reasoning: external_exports.string()
28671
+ });
28672
+ var JoinEntrySchema = external_exports.object({
28673
+ id: external_exports.string(),
28674
+ ruleId: external_exports.string(),
28675
+ category: DetectionCategory,
28676
+ valueFingerprint: external_exports.string().optional(),
28677
+ keyVersion: external_exports.number().optional(),
28678
+ maskedMatch: external_exports.string(),
28679
+ maskedContext: external_exports.string()
28680
+ });
28681
+ var PLAN_FILE_VERSION = 3;
28682
+ var PersistedPlanSchema = external_exports.object({
28683
+ version: external_exports.literal(PLAN_FILE_VERSION),
28684
+ // partialRecord (not record): a posture only covers the categories present in
28685
+ // the evidence, so an exhaustive-key record would reject every real plan.
28686
+ posture: external_exports.partialRecord(DetectionCategory, BuiltinPolicyId),
28687
+ entries: external_exports.array(SuppressionEntrySchema),
28688
+ showcase: external_exports.array(ShowcaseCategorySchema),
28689
+ join: external_exports.array(JoinEntrySchema),
28690
+ notes: external_exports.string(),
28691
+ // The store's per-category action at preview time. The downgrade view is
28692
+ // rendered from it at PREVIEW (renderPosturePlan); confirm reads it back ONLY to
28693
+ // compare against the live store and reject a stale plan (runConfirm's drift gate).
28694
+ current: external_exports.partialRecord(DetectionCategory, ActionTaken)
28695
+ });
28696
+
27613
28697
  // src/command-registry.ts
27614
- import { readdirSync as readdirSync4 } from "fs";
27615
- import { fileURLToPath } from "url";
27616
- var COMMANDS_DIR = fileURLToPath(new URL("../commands", import.meta.url));
28698
+ import { readdirSync as readdirSync5 } from "fs";
28699
+ import { fileURLToPath as fileURLToPath2 } from "url";
28700
+ var COMMANDS_DIR = fileURLToPath2(new URL("../commands", import.meta.url));
27617
28701
 
27618
28702
  // src/render.ts
27619
28703
  var SEVERITY_WEIGHT = { critical: 4, high: 3, medium: 2, low: 1 };