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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -492,13 +492,12 @@ var require_ignore = __commonJS({
492
492
  });
493
493
 
494
494
  // ../../packages/plugin-sdk/src/config.ts
495
- import { existsSync as existsSync4 } from "fs";
496
- import { join as join7 } from "path";
495
+ import { existsSync as existsSync5 } from "fs";
496
+ import { join as join8 } from "path";
497
497
 
498
498
  // ../../packages/persistence/src/database.ts
499
- import { randomUUID as randomUUID9 } from "crypto";
500
- import { existsSync, renameSync as renameSync2, rmSync as rmSync2 } from "fs";
501
- import { join, sep } from "path";
499
+ import { randomUUID as randomUUID10 } from "crypto";
500
+ import { join as join2, sep } from "path";
502
501
  import { DatabaseSync } from "node:sqlite";
503
502
 
504
503
  // ../../packages/schema/src/drizzle/sqlite-ddl.ts
@@ -578,6 +577,14 @@ var SQLITE_MIGRATIONS = [
578
577
  {
579
578
  tag: "0018_serious_tana_nile",
580
579
  sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
580
+ },
581
+ {
582
+ tag: "0019_audit_started_at_index",
583
+ sql: "CREATE INDEX `idx_audit_started_at` ON `audit_events` (`started_at`);"
584
+ },
585
+ {
586
+ tag: "0020_secret_vault_pagination_indexes",
587
+ sql: "CREATE INDEX `idx_secret_vault_last_seen` ON `secret_vault` (`last_seen`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_reuse` ON `secret_vault` (`occurrence_count`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_at` ON `secret_vault_deref` (`at`,`id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_signal` ON `secret_vault_deref` (`at`,`id`) WHERE `reason` NOT IN ('display', 'view-render');"
581
588
  }
582
589
  ];
583
590
 
@@ -15315,7 +15322,17 @@ var Finding = external_exports.object({
15315
15322
  }).meta({ id: "Finding" });
15316
15323
  var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
15317
15324
  var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
15318
- var FindingProvider = external_exports.enum(["claudecode", "claudedesktop", "cursor", "copilot", "chatgpt", "api"]).meta({ id: "FindingProvider" });
15325
+ var FindingProvider = external_exports.enum([
15326
+ "claudecode",
15327
+ "claudedesktop",
15328
+ "cursor",
15329
+ "copilot",
15330
+ "chatgpt",
15331
+ "claudeai",
15332
+ "codex",
15333
+ "antigravity",
15334
+ "api"
15335
+ ]).meta({ id: "FindingProvider" });
15319
15336
  var FindingCategory = external_exports.enum([
15320
15337
  "secret",
15321
15338
  "pii",
@@ -15369,7 +15386,16 @@ var FindingInstance = external_exports.object({
15369
15386
  confidence: external_exports.number().min(0).max(1),
15370
15387
  // Lifecycle status (see FindingStatus). Optional so legacy callers/rows
15371
15388
  // that predate the resolution feature stay valid.
15372
- status: FindingStatus.optional()
15389
+ status: FindingStatus.optional(),
15390
+ // The audit event this finding was captured from. Optional so callers that
15391
+ // do not project it stay valid. An at-rest finding is content-addressed by
15392
+ // finding_key and its row is upserted on re-detection, so this names the
15393
+ // MOST RECENT detection event, not the first.
15394
+ eventId: external_exports.string().optional(),
15395
+ // The session that event belongs to, when it has one — the seam a
15396
+ // per-instance "view session" link needs. Absent for events captured
15397
+ // outside a session.
15398
+ sessionId: external_exports.string().optional()
15373
15399
  }).meta({ id: "FindingInstance" });
15374
15400
  var FindingGroup = external_exports.object({
15375
15401
  id: external_exports.string(),
@@ -15413,7 +15439,11 @@ var FindingFacets = external_exports.object({
15413
15439
  // for every instance, so every group lands in a bucket; a status-less
15414
15440
  // group (possible only for callers whose rows carry no statuses) is
15415
15441
  // counted under no value.
15416
- status: external_exports.array(FindingFacetItem)
15442
+ status: external_exports.array(FindingFacetItem),
15443
+ // Host tool (attributes.tool_name). Present only on the instance-level
15444
+ // reads, which can filter by it; the grouped read omits the dimension
15445
+ // because a group spans tools.
15446
+ tool: external_exports.array(FindingFacetItem).optional()
15417
15447
  }).meta({ id: "FindingFacets" });
15418
15448
  var DEFAULT_GROUPED_FINDINGS_LIMIT = 50;
15419
15449
  var ListGroupedFindingsQuery = external_exports.object({
@@ -15431,6 +15461,16 @@ var ListGroupedFindingsQuery = external_exports.object({
15431
15461
  // Scope to findings whose event carries this session id (the Activity page's
15432
15462
  // session → findings drilldown). Findings without a session never match.
15433
15463
  sessionId: external_exports.string().optional(),
15464
+ // Inclusive lower bound on the parent event's timestamp, so a caller arriving
15465
+ // from a time-scoped page (Activity's range) can carry that scope. Absent
15466
+ // means all time — this list has no default window.
15467
+ from: external_exports.iso.datetime().optional(),
15468
+ // A group or instance id that must appear in the page even when the cursor
15469
+ // has already advanced past its sort position. This is what keeps the
15470
+ // Findings page's one-shot ?finding= deep link resolving once the list
15471
+ // paginates: the target group is appended out of sort order rather than
15472
+ // scanning forward for it. Never affects totals, facets or the cursor.
15473
+ includeId: external_exports.string().optional(),
15434
15474
  groupBy: external_exports.literal("type").optional(),
15435
15475
  limit: external_exports.coerce.number().int().min(1).max(100).optional(),
15436
15476
  cursor: external_exports.string().optional()
@@ -15475,15 +15515,110 @@ var FindingInstanceDetail = FindingInstance.extend({
15475
15515
  detection: FindingDetectionRef,
15476
15516
  policy: FindingPolicyRef
15477
15517
  }).meta({ id: "FindingInstanceDetail" });
15518
+ var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
15519
+ var ListFindingInstancesQuery = external_exports.object({
15520
+ severity: external_exports.array(Severity).optional(),
15521
+ // Rule ids, the same vocabulary the grouped list's `subtype` carries.
15522
+ subtype: external_exports.array(external_exports.string()).optional(),
15523
+ provider: external_exports.array(FindingProvider).optional(),
15524
+ action: external_exports.array(FindingAction).optional(),
15525
+ // Matches each instance's OWN derived status (deriveFindingStatus), unlike
15526
+ // the grouped query's group-level fold.
15527
+ status: external_exports.array(FindingStatus).optional(),
15528
+ // Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
15529
+ // where the free-text `q` can only match the rendered "via Bash" label.
15530
+ tool: external_exports.array(external_exports.string()).optional(),
15531
+ // Exact repository / file-path matches, for the drill-down out of the
15532
+ // locations view. A row whose event carries no repo/file matches neither.
15533
+ repo: external_exports.string().optional(),
15534
+ file: external_exports.string().optional(),
15535
+ q: external_exports.string().optional(),
15536
+ sessionId: external_exports.string().optional(),
15537
+ from: external_exports.iso.datetime().optional(),
15538
+ limit: external_exports.coerce.number().int().min(1).max(200).optional(),
15539
+ cursor: external_exports.string().optional()
15540
+ });
15541
+ var ListFindingInstancesResponse = external_exports.object({
15542
+ // Instances matching the filters across the whole scope, not just this
15543
+ // page — cursor-independent, like the grouped list's totals.
15544
+ totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
15545
+ // Counts in INSTANCES here, where the grouped response counts groups. Each
15546
+ // dimension still excludes its own filter.
15547
+ facets: FindingFacets,
15548
+ items: external_exports.array(FindingInstanceDetail),
15549
+ nextCursor: external_exports.string().nullable()
15550
+ }).meta({ id: "ListFindingInstancesResponse" });
15551
+ var FindingLocationFile = external_exports.object({
15552
+ // Empty when the instances carried no file path (a prompt or a tool call
15553
+ // with no file attribution).
15554
+ file: external_exports.string(),
15555
+ instanceCount: external_exports.number().int().nonnegative(),
15556
+ maxSeverity: Severity,
15557
+ latestDetectedAt: external_exports.iso.datetime(),
15558
+ // Folded from the instances' derived statuses with the same
15559
+ // open-dominates precedence a group uses.
15560
+ status: FindingStatus.optional(),
15561
+ // Distinct rules seen at this location, capped — the row shows them as
15562
+ // chips, and the count is what conveys scale.
15563
+ ruleIds: external_exports.array(external_exports.string())
15564
+ }).meta({ id: "FindingLocationFile" });
15565
+ var FindingLocationRepo = external_exports.object({
15566
+ /** Empty when the instances carried no repo attribute. */
15567
+ repo: external_exports.string(),
15568
+ instanceCount: external_exports.number().int().nonnegative(),
15569
+ maxSeverity: Severity,
15570
+ latestDetectedAt: external_exports.iso.datetime(),
15571
+ status: FindingStatus.optional(),
15572
+ files: external_exports.array(FindingLocationFile)
15573
+ }).meta({ id: "FindingLocationRepo" });
15574
+ var ListFindingLocationsQuery = external_exports.object({
15575
+ severity: external_exports.array(Severity).optional(),
15576
+ subtype: external_exports.array(external_exports.string()).optional(),
15577
+ provider: external_exports.array(FindingProvider).optional(),
15578
+ action: external_exports.array(FindingAction).optional(),
15579
+ // Per-instance, as in ListFindingInstancesQuery: a location keeps the
15580
+ // instances that match, and folds its status from those.
15581
+ status: external_exports.array(FindingStatus).optional(),
15582
+ tool: external_exports.array(external_exports.string()).optional(),
15583
+ q: external_exports.string().optional(),
15584
+ sessionId: external_exports.string().optional(),
15585
+ from: external_exports.iso.datetime().optional(),
15586
+ limit: external_exports.coerce.number().int().min(1).max(500).optional()
15587
+ });
15588
+ var ListFindingLocationsResponse = external_exports.object({
15589
+ totals: external_exports.object({
15590
+ findings: external_exports.number().int().nonnegative(),
15591
+ repos: external_exports.number().int().nonnegative(),
15592
+ files: external_exports.number().int().nonnegative()
15593
+ }),
15594
+ /** Sorted by max severity, then most recent. */
15595
+ items: external_exports.array(FindingLocationRepo),
15596
+ /** Whether `limit` truncated the repo list. */
15597
+ hasMore: external_exports.boolean()
15598
+ }).meta({ id: "ListFindingLocationsResponse" });
15478
15599
 
15479
15600
  // ../../packages/schema/src/zod/harness-map.ts
15480
- var Harness = external_exports.enum(["claudecode", "cursor", "copilot", "codex", "windsurf", "claudedesktop", "chatgpt", "api"]).meta({ id: "Harness" });
15601
+ var Harness = external_exports.enum([
15602
+ "claudecode",
15603
+ "cursor",
15604
+ "copilot",
15605
+ "codex",
15606
+ "antigravity",
15607
+ "windsurf",
15608
+ "claudedesktop",
15609
+ "chatgpt",
15610
+ "claudeai",
15611
+ "api"
15612
+ ]).meta({ id: "Harness" });
15481
15613
  var TOOL_TO_HARNESS = {
15482
15614
  "claude-code": "claudecode",
15483
15615
  "claude-desktop": "claudedesktop",
15484
15616
  "github-copilot": "copilot",
15485
15617
  cursor: "cursor",
15486
- chatgpt: "chatgpt"
15618
+ chatgpt: "chatgpt",
15619
+ codex: "codex",
15620
+ antigravity: "antigravity",
15621
+ "claude-ai": "claudeai"
15487
15622
  };
15488
15623
 
15489
15624
  // ../../packages/schema/src/zod/meta.ts
@@ -15941,7 +16076,18 @@ var ActivityOverviewResponse = external_exports.object({
15941
16076
  // ../../packages/schema/src/zod/event.ts
15942
16077
  var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
15943
16078
  var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
15944
- var SourceTool = external_exports.enum(["claude-code", "claude-desktop", "cursor", "chatgpt", "github-copilot", "cli", "unknown"]).meta({ id: "SourceTool" });
16079
+ var SourceTool = external_exports.enum([
16080
+ "claude-code",
16081
+ "claude-desktop",
16082
+ "cursor",
16083
+ "chatgpt",
16084
+ "claude-ai",
16085
+ "github-copilot",
16086
+ "codex",
16087
+ "antigravity",
16088
+ "cli",
16089
+ "unknown"
16090
+ ]).meta({ id: "SourceTool" });
15945
16091
  var EventMetadata = external_exports.object({
15946
16092
  sessionId: external_exports.string().optional(),
15947
16093
  repo: external_exports.string().optional(),
@@ -16012,7 +16158,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
16012
16158
  var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
16013
16159
  var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
16014
16160
  var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
16015
- var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
16161
+ var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
16016
16162
  var AccessCounts = external_exports.object({
16017
16163
  open: external_exports.number().int().nonnegative(),
16018
16164
  approved: external_exports.number().int().nonnegative(),
@@ -16281,6 +16427,7 @@ var ExceptionBundleEntry = DetectionException.pick({
16281
16427
  useCount: true,
16282
16428
  conditions: true
16283
16429
  });
16430
+ var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
16284
16431
 
16285
16432
  // ../../packages/schema/src/zod/rule.ts
16286
16433
  var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
@@ -17091,6 +17238,35 @@ var EgressWriteSummary = external_exports.object({
17091
17238
  droppedFiles: external_exports.array(external_exports.string()).default([])
17092
17239
  }).meta({ id: "EgressWriteSummary" });
17093
17240
 
17241
+ // ../../packages/schema/src/zod/exception-action.ts
17242
+ var confirmation = external_exports.string().optional();
17243
+ var ApproveBlockedInput = external_exports.object({
17244
+ reference: external_exports.string(),
17245
+ scope: external_exports.string(),
17246
+ reason: external_exports.string(),
17247
+ confirmation
17248
+ });
17249
+ var AddExceptionInput = external_exports.object({
17250
+ ruleId: external_exports.string(),
17251
+ value: external_exports.string(),
17252
+ scope: external_exports.string(),
17253
+ reason: external_exports.string(),
17254
+ confirmation
17255
+ });
17256
+ var GrantRevealInput = external_exports.object({
17257
+ pointer: external_exports.string(),
17258
+ scope: external_exports.string(),
17259
+ justification: external_exports.string(),
17260
+ confirmation
17261
+ });
17262
+ var RevokeExceptionInput = external_exports.object({
17263
+ id: external_exports.string(),
17264
+ reason: external_exports.string()
17265
+ });
17266
+ var RotateKeyInput = external_exports.object({
17267
+ confirmation: external_exports.string()
17268
+ });
17269
+
17094
17270
  // ../../packages/schema/src/zod/findings-group-build.ts
17095
17271
  function toApiAction(dbVal) {
17096
17272
  const map2 = {
@@ -17146,6 +17322,8 @@ function buildFindingGroups(rows, opts = {}) {
17146
17322
  repo: r.repo,
17147
17323
  file: r.file,
17148
17324
  ...r.toolName === void 0 ? {} : { toolName: r.toolName },
17325
+ ...r.eventId === void 0 ? {} : { eventId: r.eventId },
17326
+ ...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
17149
17327
  action: toApiAction(effectiveDbAction),
17150
17328
  detectedAt: r.occurredAt,
17151
17329
  confidence: r.confidence,
@@ -17277,14 +17455,17 @@ function applyFindingFilters(groups, opts) {
17277
17455
  }
17278
17456
  var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
17279
17457
  var SEVERITY_RANK = SEVERITY_ORDER;
17458
+ function compareFindingGroupOrder(a, b) {
17459
+ const rankA = SEVERITY_RANK[a.severity] ?? -1;
17460
+ const rankB = SEVERITY_RANK[b.severity] ?? -1;
17461
+ const severityDiff = rankA - rankB;
17462
+ if (severityDiff !== 0) return severityDiff;
17463
+ const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
17464
+ if (recencyDiff !== 0) return recencyDiff;
17465
+ return a.id.localeCompare(b.id);
17466
+ }
17280
17467
  function sortFindingGroups(groups) {
17281
- return [...groups].sort((a, b) => {
17282
- const rankA = SEVERITY_RANK[a.severity] ?? -1;
17283
- const rankB = SEVERITY_RANK[b.severity] ?? -1;
17284
- const severityDiff = rankA - rankB;
17285
- if (severityDiff !== 0) return severityDiff;
17286
- return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
17287
- });
17468
+ return [...groups].sort(compareFindingGroupOrder);
17288
17469
  }
17289
17470
  function computeFindingFacets(allGroups, opts) {
17290
17471
  const forSeverity = applyFindingFilters(allGroups, {
@@ -17340,15 +17521,158 @@ function computeFindingFacets(allGroups, opts) {
17340
17521
  for (const g of forStatus) {
17341
17522
  if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
17342
17523
  }
17343
- const toItems = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
17524
+ const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
17525
+ return {
17526
+ severity: toItems2(severityMap),
17527
+ provider: toItems2(providerMap),
17528
+ action: toItems2(actionMap),
17529
+ subtype: toItems2(subtypeMap),
17530
+ status: toItems2(statusMap)
17531
+ };
17532
+ }
17533
+
17534
+ // ../../packages/schema/src/zod/findings-flat-build.ts
17535
+ function rowHaystack(row) {
17536
+ return [
17537
+ row.ruleId,
17538
+ row.category,
17539
+ row.maskedMatch,
17540
+ row.repo,
17541
+ row.file,
17542
+ row.toolName ? `via ${row.toolName}` : "",
17543
+ row.id
17544
+ ].join(" ").toLowerCase();
17545
+ }
17546
+ function matchesDimension(row, opts, dimension) {
17547
+ switch (dimension) {
17548
+ case "severity":
17549
+ return !opts.severity?.length || opts.severity.includes(row.severity);
17550
+ case "subtype":
17551
+ return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
17552
+ case "providers":
17553
+ return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
17554
+ case "actions":
17555
+ return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
17556
+ case "statuses":
17557
+ return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
17558
+ case "tools":
17559
+ return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
17560
+ case "repo":
17561
+ return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
17562
+ case "file":
17563
+ return opts.file === void 0 || opts.file === "" || row.file === opts.file;
17564
+ case "q":
17565
+ return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
17566
+ }
17567
+ }
17568
+ var DIMENSIONS = [
17569
+ "severity",
17570
+ "subtype",
17571
+ "providers",
17572
+ "actions",
17573
+ "statuses",
17574
+ "tools",
17575
+ "repo",
17576
+ "file",
17577
+ "q"
17578
+ ];
17579
+ function matchesInstanceFilters(row, opts, except) {
17580
+ for (const dimension of DIMENSIONS) {
17581
+ if (dimension === except) continue;
17582
+ if (!matchesDimension(row, opts, dimension)) return false;
17583
+ }
17584
+ return true;
17585
+ }
17586
+ function toItems(counts) {
17587
+ return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
17588
+ }
17589
+ function bump(counts, value) {
17590
+ counts.set(value, (counts.get(value) ?? 0) + 1);
17591
+ }
17592
+ function createInstanceFacetAccumulator(opts) {
17593
+ const severity = /* @__PURE__ */ new Map();
17594
+ const subtype = /* @__PURE__ */ new Map();
17595
+ const provider = /* @__PURE__ */ new Map();
17596
+ const action = /* @__PURE__ */ new Map();
17597
+ const status = /* @__PURE__ */ new Map();
17598
+ const tool = /* @__PURE__ */ new Map();
17344
17599
  return {
17345
- severity: toItems(severityMap),
17346
- provider: toItems(providerMap),
17347
- action: toItems(actionMap),
17348
- subtype: toItems(subtypeMap),
17349
- status: toItems(statusMap)
17600
+ add(row) {
17601
+ if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
17602
+ if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
17603
+ if (matchesInstanceFilters(row, opts, "providers")) {
17604
+ bump(provider, toApiProvider(row.sourceTool));
17605
+ }
17606
+ if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
17607
+ if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
17608
+ bump(status, row.status);
17609
+ }
17610
+ if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
17611
+ bump(tool, row.toolName);
17612
+ }
17613
+ },
17614
+ facets: () => ({
17615
+ severity: toItems(severity),
17616
+ subtype: toItems(subtype),
17617
+ provider: toItems(provider),
17618
+ action: toItems(action),
17619
+ status: toItems(status),
17620
+ tool: toItems(tool)
17621
+ })
17622
+ };
17623
+ }
17624
+ function toInstanceDetail(row) {
17625
+ const category = toApiCategory(row.category);
17626
+ return {
17627
+ id: row.id,
17628
+ provider: toApiProvider(row.sourceTool),
17629
+ repo: row.repo,
17630
+ file: row.file,
17631
+ ...row.toolName === void 0 ? {} : { toolName: row.toolName },
17632
+ eventId: row.eventId,
17633
+ ...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
17634
+ action: toApiAction(row.actionTaken),
17635
+ detectedAt: row.occurredAt,
17636
+ confidence: row.confidence,
17637
+ ...row.status === void 0 ? {} : { status: row.status },
17638
+ groupId: row.ruleId,
17639
+ category,
17640
+ subtype: row.ruleId,
17641
+ severity: row.severity,
17642
+ match: { maskedValue: row.maskedMatch, contextPrefix: "" },
17643
+ detection: { id: row.ruleId, name: null },
17644
+ policy: { id: `category:${category}`, name: category }
17645
+ };
17646
+ }
17647
+ var SEVERITY_ORDER2 = {
17648
+ critical: 0,
17649
+ high: 1,
17650
+ medium: 2,
17651
+ low: 3
17652
+ };
17653
+ function newLocationAccumulator() {
17654
+ return {
17655
+ instanceCount: 0,
17656
+ // Sorts after every known severity, so the first row always wins the
17657
+ // comparison below rather than an unknown value pinning the location.
17658
+ maxSeverityRank: Number.MAX_SAFE_INTEGER,
17659
+ maxSeverity: "low",
17660
+ latestDetectedAt: "",
17661
+ statuses: [],
17662
+ ruleIds: /* @__PURE__ */ new Set()
17350
17663
  };
17351
17664
  }
17665
+ function addToLocation(acc, row) {
17666
+ acc.instanceCount += 1;
17667
+ const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
17668
+ if (rank < acc.maxSeverityRank) {
17669
+ acc.maxSeverityRank = rank;
17670
+ acc.maxSeverity = row.severity;
17671
+ }
17672
+ if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
17673
+ acc.statuses.push(row.status);
17674
+ acc.ruleIds.add(row.ruleId);
17675
+ }
17352
17676
 
17353
17677
  // ../../packages/schema/src/zod/installed-pack.ts
17354
17678
  var InstalledPack = external_exports.object({
@@ -17489,6 +17813,50 @@ var VaultInventoryEntry = external_exports.object({
17489
17813
  revealGrantId: external_exports.string().nullable(),
17490
17814
  sightings: external_exports.array(VaultSighting)
17491
17815
  });
17816
+ var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
17817
+ var DEFAULT_VAULT_DEREFS_LIMIT = 50;
17818
+ var MAX_VAULT_PAGE_LIMIT = 200;
17819
+ var ListVaultInventoryQuery = external_exports.object({
17820
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
17821
+ // Opaque; names the last row of the page just served.
17822
+ cursor: external_exports.string().optional()
17823
+ });
17824
+ var ListVaultInventoryResponse = external_exports.object({
17825
+ // Vaulted values across the whole store, not just this page — cursor-
17826
+ // independent, so paging never changes what the count claims.
17827
+ totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
17828
+ items: external_exports.array(VaultInventoryEntry),
17829
+ // `null` once the last page is reached.
17830
+ nextCursor: external_exports.string().nullable()
17831
+ });
17832
+ var ListVaultReuseQuery = external_exports.object({
17833
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
17834
+ cursor: external_exports.string().optional()
17835
+ });
17836
+ var ListVaultReuseResponse = external_exports.object({
17837
+ // Reused values across the whole store — the number the section's claim
17838
+ // ("values detected in more than one place") is about.
17839
+ totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
17840
+ items: external_exports.array(VaultInventoryEntry),
17841
+ nextCursor: external_exports.string().nullable()
17842
+ });
17843
+ var ListVaultDerefsQuery = external_exports.object({
17844
+ // Include the batched, high-volume reasons (display, view-render). Omitted
17845
+ // hides them and counts them into `hiddenBatched` instead, so the model
17846
+ // crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
17847
+ // over a Server Action, which preserves the type, never as a URL param.
17848
+ includeBatched: external_exports.boolean().optional(),
17849
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
17850
+ cursor: external_exports.string().optional()
17851
+ });
17852
+ var ListVaultDerefsResponse = external_exports.object({
17853
+ items: external_exports.array(VaultDeref),
17854
+ nextCursor: external_exports.string().nullable(),
17855
+ // Display/view-render rows the query hid, over the WHOLE trail rather than
17856
+ // this page — it is the count the "N hidden" line and its toggle speak for.
17857
+ // Always 0 when `includeBatched` was set, since nothing was hidden.
17858
+ hiddenBatched: external_exports.number().int().nonnegative()
17859
+ });
17492
17860
  var VaultKeyCustody = external_exports.string();
17493
17861
  var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
17494
17862
  var VAULT_EVENT_NOTE_MAX_POINTERS = 8;
@@ -17866,7 +18234,7 @@ var TopSourcesQuery = external_exports.object({
17866
18234
  // Omit for both kinds.
17867
18235
  kind: external_exports.enum(SOURCE_KINDS).optional()
17868
18236
  });
17869
- var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
18237
+ var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
17870
18238
  var ScanCoverageProvider = external_exports.object({
17871
18239
  provider: Provider,
17872
18240
  // Percent of that provider's traffic scanned in the window. 0 when unsupported.
@@ -18119,6 +18487,195 @@ function captureId(sessionId, contentHash, filePath = null) {
18119
18487
  );
18120
18488
  }
18121
18489
 
18490
+ // ../../packages/persistence/src/internal/snapshot.ts
18491
+ import { randomUUID } from "crypto";
18492
+ import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
18493
+ import { basename, dirname, join } from "path";
18494
+
18495
+ // ../../packages/persistence/src/paths.ts
18496
+ import {
18497
+ chmodSync,
18498
+ linkSync,
18499
+ lstatSync,
18500
+ mkdirSync,
18501
+ renameSync,
18502
+ rmSync,
18503
+ writeFileSync
18504
+ } from "fs";
18505
+ import { threadId } from "worker_threads";
18506
+ var DATA_DIR_MODE = 448;
18507
+ var DATA_FILE_MODE = 384;
18508
+ var DB_FILENAME = "aka.db";
18509
+ function isSymlink(path) {
18510
+ try {
18511
+ return lstatSync(path).isSymbolicLink();
18512
+ } catch {
18513
+ return false;
18514
+ }
18515
+ }
18516
+ function chmodBestEffort(path, mode) {
18517
+ if (isSymlink(path)) return;
18518
+ try {
18519
+ chmodSync(path, mode);
18520
+ } catch {
18521
+ }
18522
+ }
18523
+ function tightenDir(dir) {
18524
+ chmodBestEffort(dir, DATA_DIR_MODE);
18525
+ }
18526
+ function ensureDataDirSync(dir) {
18527
+ mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
18528
+ tightenDir(dir);
18529
+ }
18530
+ function dbSidecars(file2) {
18531
+ return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
18532
+ }
18533
+ function tightenFile(file2) {
18534
+ chmodBestEffort(file2, DATA_FILE_MODE);
18535
+ }
18536
+ function tightenPerms(file2) {
18537
+ for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
18538
+ }
18539
+ function classifyOccupant(file2) {
18540
+ try {
18541
+ if (lstatSync(file2).isSymbolicLink()) return { kind: "symlink" };
18542
+ return { kind: "gone" };
18543
+ } catch (err) {
18544
+ if (err.code === "ENOENT") return { kind: "gone" };
18545
+ return { kind: "unknown", cause: err };
18546
+ }
18547
+ }
18548
+ var KeyUnclaimableError = class extends Error {
18549
+ code = "key-unclaimable";
18550
+ // `cause` is installed only when there IS one. Passing { cause: undefined }
18551
+ // defines the property anyway, so an error carrying nothing would still answer
18552
+ // `'cause' in err` — a present-but-empty field reads as a diagnosis that was
18553
+ // captured and then lost, which is worse than its plain absence.
18554
+ constructor(message, cause) {
18555
+ super(message, cause === void 0 ? void 0 : { cause });
18556
+ this.name = "KeyUnclaimableError";
18557
+ }
18558
+ };
18559
+ function createOwnerOnlyFileSync(file2, data) {
18560
+ const tmp = `${file2}.${String(process.pid)}.${String(threadId)}.new`;
18561
+ try {
18562
+ rmSync(tmp, { force: true });
18563
+ } catch {
18564
+ }
18565
+ let created;
18566
+ try {
18567
+ writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
18568
+ created = publishByLink(tmp, file2, data);
18569
+ } finally {
18570
+ try {
18571
+ rmSync(tmp, { force: true });
18572
+ } catch {
18573
+ }
18574
+ }
18575
+ if (created) tightenFile(file2);
18576
+ return created;
18577
+ }
18578
+ var LINK_UNSUPPORTED = /* @__PURE__ */ new Set(["EPERM", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EINVAL"]);
18579
+ function publishByLink(tmp, file2, data) {
18580
+ try {
18581
+ linkSync(tmp, file2);
18582
+ return true;
18583
+ } catch (err) {
18584
+ const code = err.code;
18585
+ if (code === "EEXIST") return false;
18586
+ if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
18587
+ }
18588
+ try {
18589
+ writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
18590
+ return true;
18591
+ } catch (err) {
18592
+ if (err.code === "EEXIST") return false;
18593
+ throw err;
18594
+ }
18595
+ }
18596
+
18597
+ // ../../packages/persistence/src/internal/snapshot.ts
18598
+ function backupPath(file2, tag) {
18599
+ return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
18600
+ }
18601
+ var STALE_PARTIAL_MS = 5 * 6e4;
18602
+ function reapStalePartials(file2) {
18603
+ const dir = dirname(file2);
18604
+ const prefix = `${basename(file2)}.`;
18605
+ let entries;
18606
+ try {
18607
+ entries = readdirSync(dir);
18608
+ } catch {
18609
+ return;
18610
+ }
18611
+ for (const name of entries) {
18612
+ if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
18613
+ const partial2 = join(dir, name);
18614
+ try {
18615
+ if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
18616
+ rmSync2(partial2, { force: true });
18617
+ }
18618
+ } catch {
18619
+ }
18620
+ }
18621
+ }
18622
+ function snapshotStore(db, backup) {
18623
+ const partial2 = `${backup}.partial`;
18624
+ try {
18625
+ rmSync2(partial2, { force: true });
18626
+ db.prepare("VACUUM INTO ?").run(partial2);
18627
+ tightenFile(partial2);
18628
+ renameSync2(partial2, backup);
18629
+ } catch (error51) {
18630
+ try {
18631
+ rmSync2(partial2, { force: true });
18632
+ } catch {
18633
+ }
18634
+ throw error51;
18635
+ }
18636
+ }
18637
+ function moveStoreAside(file2, backup) {
18638
+ const undo = [];
18639
+ renameSync2(file2, backup);
18640
+ undo.push([backup, file2]);
18641
+ try {
18642
+ for (const sidecar of dbSidecars(file2)) {
18643
+ const moved = `${backup}${sidecar.slice(file2.length)}`;
18644
+ try {
18645
+ renameSync2(sidecar, moved);
18646
+ undo.push([moved, sidecar]);
18647
+ } catch {
18648
+ rmSync2(sidecar, { force: true });
18649
+ }
18650
+ }
18651
+ } catch (error51) {
18652
+ for (const [from, to] of undo.reverse()) {
18653
+ try {
18654
+ renameSync2(from, to);
18655
+ } catch {
18656
+ }
18657
+ }
18658
+ throw error51;
18659
+ }
18660
+ tightenPerms(backup);
18661
+ }
18662
+ function discardStore(file2, backup) {
18663
+ try {
18664
+ rmSync2(file2, { force: true });
18665
+ for (const sidecar of dbSidecars(file2)) {
18666
+ rmSync2(sidecar, { force: true });
18667
+ }
18668
+ } catch (error51) {
18669
+ if (existsSync(file2)) {
18670
+ try {
18671
+ rmSync2(backup, { force: true });
18672
+ } catch {
18673
+ }
18674
+ }
18675
+ throw error51;
18676
+ }
18677
+ }
18678
+
18122
18679
  // ../../packages/persistence/src/internal/sql-text.ts
18123
18680
  function escapeLikePattern(s) {
18124
18681
  return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
@@ -18260,55 +18817,6 @@ function mapRowsTolerant(rows, map2) {
18260
18817
  return out;
18261
18818
  }
18262
18819
 
18263
- // ../../packages/persistence/src/paths.ts
18264
- import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
18265
- var DATA_DIR_MODE = 448;
18266
- var DATA_FILE_MODE = 384;
18267
- var DB_FILENAME = "aka.db";
18268
- function chmodBestEffort(path, mode) {
18269
- try {
18270
- chmodSync(path, mode);
18271
- } catch {
18272
- }
18273
- }
18274
- function tightenDir(dir) {
18275
- chmodBestEffort(dir, DATA_DIR_MODE);
18276
- }
18277
- function ensureDataDirSync(dir) {
18278
- mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
18279
- tightenDir(dir);
18280
- }
18281
- function dbSidecars(file2) {
18282
- return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
18283
- }
18284
- function tightenFile(file2) {
18285
- try {
18286
- if (lstatSync(file2).isSymbolicLink()) return;
18287
- } catch {
18288
- }
18289
- chmodBestEffort(file2, DATA_FILE_MODE);
18290
- }
18291
- function tightenPerms(file2) {
18292
- for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
18293
- }
18294
- function writeOwnerOnlyFileSync(file2, data) {
18295
- const tmp = `${file2}.${String(process.pid)}.tmp`;
18296
- try {
18297
- rmSync(tmp, { force: true });
18298
- } catch {
18299
- }
18300
- try {
18301
- writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
18302
- renameSync(tmp, file2);
18303
- } finally {
18304
- try {
18305
- rmSync(tmp, { force: true });
18306
- } catch {
18307
- }
18308
- }
18309
- tightenFile(file2);
18310
- }
18311
-
18312
18820
  // ../../packages/persistence/src/migrations.ts
18313
18821
  function describeObject(object2) {
18314
18822
  return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
@@ -18424,9 +18932,9 @@ function applyLegacyDropMigration(db, file2) {
18424
18932
  }
18425
18933
  }
18426
18934
  function backupBeforeLegacyDrop(db, file2) {
18427
- const backup = `${file2}.pre-drop.${String(Date.now())}.bak`;
18428
- db.prepare("VACUUM INTO ?").run(backup);
18429
- tightenFile(backup);
18935
+ reapStalePartials(file2);
18936
+ const backup = backupPath(file2, "pre-drop");
18937
+ snapshotStore(db, backup);
18430
18938
  return backup;
18431
18939
  }
18432
18940
  var TOKEN_USAGE_COLUMNS = [
@@ -18770,6 +19278,25 @@ function parseJsonObject(s) {
18770
19278
  return void 0;
18771
19279
  }
18772
19280
 
19281
+ // ../../packages/persistence/src/internal/keyset-cursor.ts
19282
+ function encodeKeysetCursor(payload) {
19283
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
19284
+ }
19285
+ function decodeKeysetCursor(cursor) {
19286
+ const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
19287
+ if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
19288
+ // resumes from is epoch millis, and a payload carrying ±Infinity or a
19289
+ // fraction binds cleanly rather than failing — returning an EMPTY page with
19290
+ // a null cursor, which a caller reads as "end of list". That is the one
19291
+ // outcome a cursor that does not decode must never produce, since the
19292
+ // documented behaviour above is to restart from the top. (`1e999` is valid
19293
+ // JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
19294
+ Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
19295
+ return parsed;
19296
+ }
19297
+ return null;
19298
+ }
19299
+
18773
19300
  // ../../packages/persistence/src/repositories/activity.ts
18774
19301
  var DAY_MS = 864e5;
18775
19302
  var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
@@ -18815,16 +19342,6 @@ function utcWindow(nowMs) {
18815
19342
  const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
18816
19343
  return { startMs, endMs: startMs + DAY_MS };
18817
19344
  }
18818
- function encodeCursor(payload) {
18819
- return Buffer.from(JSON.stringify(payload)).toString("base64url");
18820
- }
18821
- function decodeCursor(cursor) {
18822
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
18823
- if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
18824
- return parsed;
18825
- }
18826
- return null;
18827
- }
18828
19345
  var DB_EVENT_TYPE_TO_KIND = {
18829
19346
  session: "session",
18830
19347
  prompt: "prompt",
@@ -18969,7 +19486,7 @@ var SqliteActivityRepository = class {
18969
19486
  return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
18970
19487
  }
18971
19488
  listSessions(query) {
18972
- const cursor = query.cursor ? decodeCursor(query.cursor) : null;
19489
+ const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
18973
19490
  const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
18974
19491
  const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
18975
19492
  const conditions = [SESSION_ROOT];
@@ -19043,7 +19560,7 @@ var SqliteActivityRepository = class {
19043
19560
  )
19044
19561
  );
19045
19562
  const last = page[page.length - 1];
19046
- const nextCursor = hasMore && last ? encodeCursor({ startedAtMs: last.started_at, id: last.id }) : null;
19563
+ const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
19047
19564
  return Promise.resolve({ items, nextCursor, emptyCount });
19048
19565
  }
19049
19566
  getSession(sessionId) {
@@ -19916,7 +20433,7 @@ var SqliteEventsRepository = class {
19916
20433
  };
19917
20434
 
19918
20435
  // ../../packages/persistence/src/repositories/exceptions.ts
19919
- import { randomUUID } from "crypto";
20436
+ import { randomUUID as randomUUID2 } from "crypto";
19920
20437
 
19921
20438
  // ../../packages/persistence/src/internal/sqlite-errors.ts
19922
20439
  var SQLITE_CONSTRAINT_UNIQUE = 2067;
@@ -19952,8 +20469,9 @@ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
19952
20469
  AND conditions IS NULL
19953
20470
  AND ${ACTIVE_PREDICATE}`;
19954
20471
  var SqliteExceptionsRepository = class {
19955
- constructor(db) {
20472
+ constructor(db, now = () => Date.now()) {
19956
20473
  this.db = db;
20474
+ this.now = now;
19957
20475
  this.consumeStmt = db.prepare(
19958
20476
  `UPDATE exceptions
19959
20477
  SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
@@ -19971,6 +20489,7 @@ var SqliteExceptionsRepository = class {
19971
20489
  );
19972
20490
  }
19973
20491
  db;
20492
+ now;
19974
20493
  consumeStmt;
19975
20494
  insertBlockedStmt;
19976
20495
  sweepBlockedStmt;
@@ -19997,8 +20516,8 @@ var SqliteExceptionsRepository = class {
19997
20516
  "provider conditions are not supported yet \u2014 a grant with one would never apply"
19998
20517
  );
19999
20518
  }
20000
- const id = randomUUID();
20001
- const now = Date.now();
20519
+ const id = randomUUID2();
20520
+ const now = this.now();
20002
20521
  try {
20003
20522
  this.insertExceptionRow(id, input, now);
20004
20523
  } catch (err) {
@@ -20076,7 +20595,7 @@ var SqliteExceptionsRepository = class {
20076
20595
  const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
20077
20596
  const rows = allRows(
20078
20597
  this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
20079
- opts?.includeTerminal ? {} : { now: Date.now() }
20598
+ opts?.includeTerminal ? {} : { now: this.now() }
20080
20599
  );
20081
20600
  const exceptions = mapRowsTolerant(rows, parseExceptionRow);
20082
20601
  return Promise.resolve(exceptions);
@@ -20111,7 +20630,7 @@ var SqliteExceptionsRepository = class {
20111
20630
  * already revoked.
20112
20631
  */
20113
20632
  revoke(id, revokedBy, reason) {
20114
- const now = Date.now();
20633
+ const now = this.now();
20115
20634
  const result = this.db.prepare(
20116
20635
  `UPDATE exceptions
20117
20636
  SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
@@ -20125,7 +20644,7 @@ var SqliteExceptionsRepository = class {
20125
20644
  * callers must treat identically — means it does not and the detection is
20126
20645
  * enforced as usual. Deliberately NOT wrapped in try/catch.
20127
20646
  */
20128
- consume(id, now = Date.now()) {
20647
+ consume(id, now = this.now()) {
20129
20648
  const result = this.consumeStmt.run({ id, now });
20130
20649
  return Promise.resolve(Number(result.changes) === 1);
20131
20650
  }
@@ -20134,7 +20653,7 @@ var SqliteExceptionsRepository = class {
20134
20653
  * version — what rides the policy bundle to the hook. Grants written under
20135
20654
  * a different (rotated-away) key never match, so they are excluded at read.
20136
20655
  */
20137
- activeBundleEntries(keyVersion, now = Date.now()) {
20656
+ activeBundleEntries(keyVersion, now = this.now()) {
20138
20657
  const rows = allRows(
20139
20658
  this.db.prepare(
20140
20659
  `SELECT * FROM exceptions
@@ -20166,7 +20685,7 @@ var SqliteExceptionsRepository = class {
20166
20685
  * than the retention window on every write, so the ledger self-limits.
20167
20686
  */
20168
20687
  recordBlocked(entry) {
20169
- const now = Date.now();
20688
+ const now = this.now();
20170
20689
  this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
20171
20690
  this.insertBlockedStmt.run({
20172
20691
  reference: entry.reference,
@@ -20189,7 +20708,7 @@ var SqliteExceptionsRepository = class {
20189
20708
  WHERE blocked_at > :cutoff
20190
20709
  ORDER BY blocked_at DESC, rowid DESC`
20191
20710
  ),
20192
- { cutoff: Date.now() - windowMs }
20711
+ { cutoff: this.now() - windowMs }
20193
20712
  );
20194
20713
  return Promise.resolve(
20195
20714
  rows.map((row) => ({
@@ -20217,8 +20736,9 @@ var SqliteExceptionsRepository = class {
20217
20736
  * evaluate conditions, and a narrowing clause that is ignored would WIDEN the
20218
20737
  * grant instead. Fail closed until reveal-side condition evaluation exists.
20219
20738
  */
20220
- activeRevealGrant(ruleId, valueFingerprint, keyVersion, now = Date.now()) {
20739
+ activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
20221
20740
  try {
20741
+ const at = now ?? this.now();
20222
20742
  const row = getRow(
20223
20743
  this.db.prepare(
20224
20744
  `SELECT id FROM exceptions
@@ -20227,7 +20747,7 @@ var SqliteExceptionsRepository = class {
20227
20747
  AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
20228
20748
  LIMIT 1`
20229
20749
  ),
20230
- { ruleId, valueFingerprint, keyVersion, now }
20750
+ { ruleId, valueFingerprint, keyVersion, now: at }
20231
20751
  );
20232
20752
  return Promise.resolve(row ?? null);
20233
20753
  } catch (err) {
@@ -20241,7 +20761,7 @@ var SqliteExceptionsRepository = class {
20241
20761
  * predicate, so correctness never depends on this sweep; it only bounds how
20242
20762
  * long the audit evidence is kept locally. Returns the deleted count.
20243
20763
  */
20244
- sweepTerminal(retentionMs, now = Date.now()) {
20764
+ sweepTerminal(retentionMs, now = this.now()) {
20245
20765
  const result = this.db.prepare(
20246
20766
  `DELETE FROM exceptions
20247
20767
  WHERE updated_at < :cutoff
@@ -20304,6 +20824,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
20304
20824
 
20305
20825
  // ../../packages/persistence/src/repositories/findings.ts
20306
20826
  var PREVIEW_INSTANCES_PER_GROUP = 200;
20827
+ var SCAN_BATCH_ROWS = 1e3;
20828
+ var DEFAULT_LOCATIONS_LIMIT = 100;
20829
+ var LOCATION_RULE_IDS_CAP = 20;
20830
+ function compareLocationOrder(a, b) {
20831
+ return compareFindingGroupOrder(
20832
+ {
20833
+ severity: a.maxSeverity,
20834
+ latestDetectedAt: a.latestDetectedAt,
20835
+ id: ""
20836
+ },
20837
+ {
20838
+ severity: b.maxSeverity,
20839
+ latestDetectedAt: b.latestDetectedAt,
20840
+ id: ""
20841
+ }
20842
+ );
20843
+ }
20307
20844
  var CONCAT_SEP = ",";
20308
20845
  var TUPLE_SEP = "|";
20309
20846
  function splitConcat(value) {
@@ -20316,6 +20853,33 @@ function deriveInstanceStatus(row) {
20316
20853
  latestResolutionStatus: row.latest_status
20317
20854
  });
20318
20855
  }
20856
+ function encodeGroupCursor(group) {
20857
+ const payload = {
20858
+ sev: group.severity,
20859
+ t: group.latestDetectedAt,
20860
+ id: group.id
20861
+ };
20862
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
20863
+ }
20864
+ function decodeGroupCursor(cursor) {
20865
+ const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
20866
+ if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
20867
+ return {
20868
+ severity: parsed.sev,
20869
+ latestDetectedAt: parsed.t,
20870
+ id: parsed.id
20871
+ };
20872
+ }
20873
+ return null;
20874
+ }
20875
+ function firstAfter(sorted, cursor) {
20876
+ const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
20877
+ return index === -1 ? sorted.length : index;
20878
+ }
20879
+ function findDeepLinked(sorted, page, id) {
20880
+ if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
20881
+ return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
20882
+ }
20319
20883
  var DAY_MS3 = 864e5;
20320
20884
  var SqliteFindingsRepository = class {
20321
20885
  constructor(db) {
@@ -20425,8 +20989,13 @@ var SqliteFindingsRepository = class {
20425
20989
  */
20426
20990
  listGroupedFindings(query) {
20427
20991
  const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
20428
- const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}`;
20429
- const sessionParams = query.sessionId ? { sessionId: query.sessionId } : {};
20992
+ const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
20993
+ const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
20994
+ const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
20995
+ const sessionParams = {
20996
+ ...query.sessionId ? { sessionId: query.sessionId } : {},
20997
+ ...fromMs === void 0 ? {} : { fromMs }
20998
+ };
20430
20999
  const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
20431
21000
  predicate,
20432
21001
  params: sessionParams
@@ -20434,7 +21003,8 @@ var SqliteFindingsRepository = class {
20434
21003
  const rows = allRows(
20435
21004
  this.db.prepare(
20436
21005
  `SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
20437
- occurred_at, source_tool, repo, file, tool_name, kind, finding_key, latest_status
21006
+ occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
21007
+ kind, finding_key, latest_status
20438
21008
  FROM (
20439
21009
  SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
20440
21010
  d.severity AS severity, f.masked_match AS masked_match,
@@ -20444,6 +21014,7 @@ var SqliteFindingsRepository = class {
20444
21014
  json_extract(e.attributes, '$.repo') AS repo,
20445
21015
  json_extract(e.attributes, '$.file_path') AS file,
20446
21016
  json_extract(e.attributes, '$.tool_name') AS tool_name,
21017
+ f.audit_event_id AS event_id, e.root_session_id AS session_id,
20447
21018
  e.event_type AS kind, f.finding_key AS finding_key,
20448
21019
  latest.status AS latest_status,
20449
21020
  ROW_NUMBER() OVER (
@@ -20475,6 +21046,8 @@ var SqliteFindingsRepository = class {
20475
21046
  repo: r.repo ?? "",
20476
21047
  file: r.file ?? "",
20477
21048
  ...r.tool_name === null ? {} : { toolName: r.tool_name },
21049
+ eventId: r.event_id,
21050
+ ...r.session_id === null ? {} : { sessionId: r.session_id },
20478
21051
  status: deriveInstanceStatus(r)
20479
21052
  }));
20480
21053
  const allGroups = buildFindingGroups(groupable, { aggregates });
@@ -20498,18 +21071,23 @@ var SqliteFindingsRepository = class {
20498
21071
  groups: sorted.length
20499
21072
  };
20500
21073
  const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
21074
+ const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
21075
+ const start = cursor === null ? 0 : firstAfter(sorted, cursor);
21076
+ const page = sorted.slice(start, start + limit);
21077
+ const lastOnPage = page.at(-1);
21078
+ const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
21079
+ const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
20501
21080
  const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
20502
- const items = sorted.slice(0, limit).map(
20503
- (g) => statusSet ? {
20504
- ...g,
20505
- instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
20506
- } : g
20507
- );
21081
+ const narrow = (g) => statusSet ? {
21082
+ ...g,
21083
+ instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
21084
+ } : g;
21085
+ const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
20508
21086
  return Promise.resolve({
20509
21087
  totals,
20510
21088
  facets,
20511
21089
  items,
20512
- nextCursor: null,
21090
+ nextCursor,
20513
21091
  ...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
20514
21092
  });
20515
21093
  }
@@ -20541,6 +21119,266 @@ var SqliteFindingsRepository = class {
20541
21119
  * request actually carries a `q`. (Substring matching is unaffected by a
20542
21120
  * path repeating across tuples.)
20543
21121
  */
21122
+ /**
21123
+ * The instance-level (flat) findings list: one row per finding, newest first,
21124
+ * paged by keyset.
21125
+ *
21126
+ * SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
21127
+ * bound are SQL predicates: nothing counts them, so narrowing the scan by
21128
+ * them changes no reported number. Severity, subtype, provider, action,
21129
+ * status, tool, repo, file and `q` all stay in JS — each has a facet, and a
21130
+ * facet excludes its own filter, so a row the filter rejects still has to be
21131
+ * counted. Pushing any of them into SQL would silently empty its own facet.
21132
+ * Several could not be expressed there anyway: status comes from the one
21133
+ * shared classifier (deriveFindingStatus), and provider 'api' means "a tool
21134
+ * none of the mappers names", which no IN-list can say.
21135
+ *
21136
+ * The scan runs from the top of the scope on every request, not from the
21137
+ * cursor: `totals` and `facets` describe the whole filtered scope and must not
21138
+ * move as the caller pages. Rows are pulled in batches so memory stays flat
21139
+ * while the counting runs, and only the page itself is retained.
21140
+ */
21141
+ listFindingInstances(query) {
21142
+ const opts = {
21143
+ severity: query.severity,
21144
+ subtype: query.subtype,
21145
+ providers: query.provider,
21146
+ actions: query.action,
21147
+ statuses: query.status,
21148
+ tools: query.tool,
21149
+ repo: query.repo,
21150
+ file: query.file,
21151
+ q: query.q
21152
+ };
21153
+ const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
21154
+ const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
21155
+ const accumulator = createInstanceFacetAccumulator(opts);
21156
+ const items = [];
21157
+ let total = 0;
21158
+ let last;
21159
+ let hasMore = false;
21160
+ for (const row of this.scanFindingRows({
21161
+ sessionId: query.sessionId,
21162
+ from: query.from
21163
+ })) {
21164
+ accumulator.add(row);
21165
+ if (!matchesInstanceFilters(row, opts)) continue;
21166
+ total += 1;
21167
+ if (items.length < limit) {
21168
+ items.push(toInstanceDetail(row));
21169
+ last = row;
21170
+ } else {
21171
+ hasMore = true;
21172
+ }
21173
+ }
21174
+ const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
21175
+ if (cursor !== null) {
21176
+ const resumed = this.pageAfter(cursor, opts, limit, query);
21177
+ return Promise.resolve({
21178
+ totals: { findings: total },
21179
+ facets: accumulator.facets(),
21180
+ items: resumed.items,
21181
+ nextCursor: resumed.nextCursor
21182
+ });
21183
+ }
21184
+ return Promise.resolve({
21185
+ totals: { findings: total },
21186
+ facets: accumulator.facets(),
21187
+ items,
21188
+ nextCursor
21189
+ });
21190
+ }
21191
+ /**
21192
+ * The page of matching rows strictly after `cursor`. Separate from the
21193
+ * counting pass because that one starts at the top of the scope by design;
21194
+ * this one narrows the scan with the same keyset predicate the activity list
21195
+ * uses, so a later page costs less than the first rather than more.
21196
+ */
21197
+ pageAfter(cursor, opts, limit, query) {
21198
+ const items = [];
21199
+ let last;
21200
+ let hasMore = false;
21201
+ for (const row of this.scanFindingRows({
21202
+ sessionId: query.sessionId,
21203
+ from: query.from,
21204
+ after: cursor
21205
+ })) {
21206
+ if (!matchesInstanceFilters(row, opts)) continue;
21207
+ if (items.length < limit) {
21208
+ items.push(toInstanceDetail(row));
21209
+ last = row;
21210
+ } else {
21211
+ hasMore = true;
21212
+ break;
21213
+ }
21214
+ }
21215
+ return {
21216
+ items,
21217
+ nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
21218
+ };
21219
+ }
21220
+ /**
21221
+ * The same findings folded by location: repository, then file within it.
21222
+ *
21223
+ * The grouping keys come from the capturing event's attributes, which is what
21224
+ * the local store relates a finding to — there is no finding↔asset row to
21225
+ * group by instead. A repo or file the event did not record folds into the
21226
+ * empty-string bucket, which the view renders but does not link, since no
21227
+ * filter can name it.
21228
+ */
21229
+ listFindingLocations(query) {
21230
+ const opts = {
21231
+ severity: query.severity,
21232
+ subtype: query.subtype,
21233
+ providers: query.provider,
21234
+ actions: query.action,
21235
+ statuses: query.status,
21236
+ tools: query.tool,
21237
+ q: query.q
21238
+ };
21239
+ const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
21240
+ const byRepo = /* @__PURE__ */ new Map();
21241
+ let total = 0;
21242
+ for (const row of this.scanFindingRows({
21243
+ sessionId: query.sessionId,
21244
+ from: query.from
21245
+ })) {
21246
+ if (!matchesInstanceFilters(row, opts)) continue;
21247
+ total += 1;
21248
+ let files = byRepo.get(row.repo);
21249
+ if (files === void 0) {
21250
+ files = /* @__PURE__ */ new Map();
21251
+ byRepo.set(row.repo, files);
21252
+ }
21253
+ let acc = files.get(row.file);
21254
+ if (acc === void 0) {
21255
+ acc = newLocationAccumulator();
21256
+ files.set(row.file, acc);
21257
+ }
21258
+ addToLocation(acc, row);
21259
+ }
21260
+ let fileCount = 0;
21261
+ const repos = [...byRepo.entries()].map(([repo, files]) => {
21262
+ fileCount += files.size;
21263
+ const fileRows = [...files.entries()].map(([file2, acc]) => ({
21264
+ file: file2,
21265
+ instanceCount: acc.instanceCount,
21266
+ maxSeverity: acc.maxSeverity,
21267
+ latestDetectedAt: acc.latestDetectedAt,
21268
+ ...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
21269
+ ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
21270
+ })).sort(compareLocationOrder);
21271
+ const rollup = fileRows.reduce(
21272
+ (a, f) => ({
21273
+ instanceCount: a.instanceCount + f.instanceCount,
21274
+ maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
21275
+ latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
21276
+ }),
21277
+ {
21278
+ instanceCount: 0,
21279
+ maxSeverity: fileRows[0]?.maxSeverity ?? "low",
21280
+ latestDetectedAt: ""
21281
+ }
21282
+ );
21283
+ const statuses = fileRows.map((f) => f.status);
21284
+ const folded = foldGroupStatus(statuses);
21285
+ return {
21286
+ repo,
21287
+ instanceCount: rollup.instanceCount,
21288
+ maxSeverity: rollup.maxSeverity,
21289
+ latestDetectedAt: rollup.latestDetectedAt,
21290
+ ...folded === void 0 ? {} : { status: folded },
21291
+ files: fileRows
21292
+ };
21293
+ });
21294
+ repos.sort(compareLocationOrder);
21295
+ return Promise.resolve({
21296
+ totals: { findings: total, repos: repos.length, files: fileCount },
21297
+ items: repos.slice(0, limit),
21298
+ hasMore: repos.length > limit
21299
+ });
21300
+ }
21301
+ /**
21302
+ * Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
21303
+ *
21304
+ * A generator so a caller streams the scope without it ever being an array:
21305
+ * the flat list counts and facets the whole filtered scope, which on a large
21306
+ * store is far more rows than any page. Each batch advances the same keyset
21307
+ * predicate the page read uses, so the scan is a sequence of bounded reads
21308
+ * rather than one unbounded result set.
21309
+ *
21310
+ * The latest-resolution lookup is the CORRELATED form, not the derived table
21311
+ * the grouped path joins: only `status` is needed, idx_finding_resolution_key
21312
+ * makes it a point lookup per row, and the derived table would re-materialize
21313
+ * a window over the whole resolution table once per batch.
21314
+ *
21315
+ * `scope` carries ONLY what no facet counts. A filter dimension narrowed here
21316
+ * would be missing from its own facet, which is computed by excluding that
21317
+ * dimension — see listFindingInstances.
21318
+ */
21319
+ *scanFindingRows(scope) {
21320
+ const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
21321
+ const params = [];
21322
+ if (scope.sessionId !== void 0 && scope.sessionId !== "") {
21323
+ conditions.push("e.root_session_id = ?");
21324
+ params.push(scope.sessionId);
21325
+ }
21326
+ if (scope.from !== void 0) {
21327
+ conditions.push("e.started_at >= ?");
21328
+ params.push(isoToEpochMillis(scope.from));
21329
+ }
21330
+ const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
21331
+ d.severity AS severity, f.masked_match AS masked_match,
21332
+ f.action_taken AS action_taken, f.confidence AS confidence,
21333
+ e.started_at AS occurred_at,
21334
+ json_extract(e.attributes, '$.source_tool') AS source_tool,
21335
+ json_extract(e.attributes, '$.repo') AS repo,
21336
+ json_extract(e.attributes, '$.file_path') AS file,
21337
+ json_extract(e.attributes, '$.tool_name') AS tool_name,
21338
+ f.audit_event_id AS event_id, e.root_session_id AS session_id,
21339
+ e.event_type AS kind, f.finding_key AS finding_key,
21340
+ ${latestResolutionStatusSql("f")} AS latest_status
21341
+ FROM inspection_findings f
21342
+ JOIN audit_events e ON e.id = f.audit_event_id
21343
+ JOIN inspection_definitions d ON d.id = f.inspection_definition_id
21344
+ WHERE ${conditions.join(" AND ")}
21345
+ AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
21346
+ ORDER BY e.started_at DESC, f.id DESC
21347
+ LIMIT ?`;
21348
+ let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
21349
+ for (; ; ) {
21350
+ const rows = allRows(this.db.prepare(sql), [
21351
+ ...params,
21352
+ after.startedAtMs,
21353
+ after.startedAtMs,
21354
+ after.id,
21355
+ SCAN_BATCH_ROWS
21356
+ ]);
21357
+ for (const r of rows) {
21358
+ yield {
21359
+ id: r.id,
21360
+ ruleId: r.rule_id,
21361
+ category: r.category,
21362
+ severity: r.severity,
21363
+ maskedMatch: r.masked_match,
21364
+ actionTaken: r.action_taken,
21365
+ confidence: r.confidence,
21366
+ occurredAt: epochMillisToIso(r.occurred_at),
21367
+ sourceTool: r.source_tool,
21368
+ repo: r.repo ?? "",
21369
+ file: r.file ?? "",
21370
+ ...r.tool_name === null ? {} : { toolName: r.tool_name },
21371
+ eventId: r.event_id,
21372
+ ...r.session_id === null ? {} : { sessionId: r.session_id },
21373
+ status: deriveInstanceStatus(r)
21374
+ };
21375
+ }
21376
+ if (rows.length < SCAN_BATCH_ROWS) return;
21377
+ const lastRow = rows[rows.length - 1];
21378
+ if (lastRow === void 0) return;
21379
+ after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
21380
+ }
21381
+ }
20544
21382
  groupAggregates(withSearchText, scope) {
20545
21383
  const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
20546
21384
  group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
@@ -20801,7 +21639,7 @@ var SqliteInspectionFindingsRepository = class {
20801
21639
  };
20802
21640
 
20803
21641
  // ../../packages/persistence/src/repositories/installed-packs.ts
20804
- import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
21642
+ import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
20805
21643
 
20806
21644
  // ../../packages/persistence/src/semver.ts
20807
21645
  function parse3(version2) {
@@ -20952,7 +21790,7 @@ var SqliteInstalledPacksRepository = class {
20952
21790
  let behind = false;
20953
21791
  for (const row of rows) {
20954
21792
  const params = {
20955
- id: randomUUID2(),
21793
+ id: randomUUID3(),
20956
21794
  namespace: row.namespace,
20957
21795
  packId: row.packId,
20958
21796
  version: row.version,
@@ -20964,7 +21802,7 @@ var SqliteInstalledPacksRepository = class {
20964
21802
  if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
20965
21803
  this.upsertAvailableStmt.run({
20966
21804
  ...params,
20967
- id: randomUUID2(),
21805
+ id: randomUUID3(),
20968
21806
  recordedBy: meta3?.recordedBy ?? null
20969
21807
  });
20970
21808
  } else {
@@ -21287,14 +22125,15 @@ var SqliteInventoryRepository = class {
21287
22125
  };
21288
22126
 
21289
22127
  // ../../packages/persistence/src/repositories/inventory-assets.ts
21290
- import { randomUUID as randomUUID3 } from "crypto";
22128
+ import { randomUUID as randomUUID4 } from "crypto";
21291
22129
  var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
21292
22130
  var VALID_HARNESS_IDS = new Set(HarnessId.options);
21293
22131
  var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
21294
22132
  var HARNESS_LABELS = {
21295
22133
  claudecode: "Claude Code",
21296
22134
  cursor: "Cursor",
21297
- codex: "Codex"
22135
+ codex: "Codex",
22136
+ antigravity: "Antigravity"
21298
22137
  };
21299
22138
  var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
21300
22139
  var EMPTY_PROJECT_AGG = {
@@ -21309,6 +22148,7 @@ function resolveHarnessId(attrs, row) {
21309
22148
  if (t.includes("claudecode") || t === "claude") return "claudecode";
21310
22149
  if (t.includes("cursor")) return "cursor";
21311
22150
  if (t.includes("codex")) return "codex";
22151
+ if (t.includes("antigravity")) return "antigravity";
21312
22152
  return null;
21313
22153
  }
21314
22154
  function isLiveRealClaudeCode(rows) {
@@ -21767,7 +22607,7 @@ var SqliteInventoryAssetsRepository = class {
21767
22607
  `INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
21768
22608
  VALUES (:id, :projectId, :path, :access, :now, :now)
21769
22609
  ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
21770
- ).run({ id: randomUUID3(), projectId, path, access, now: Date.now() });
22610
+ ).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
21771
22611
  }
21772
22612
  return true;
21773
22613
  }
@@ -21788,7 +22628,7 @@ var SqliteInventoryAssetsRepository = class {
21788
22628
  `INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
21789
22629
  VALUES (:id, :assetId, :trust, :now, :now)
21790
22630
  ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
21791
- ).run({ id: randomUUID3(), assetId, trust, now: Date.now() });
22631
+ ).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
21792
22632
  }
21793
22633
  this.configRowsCache = void 0;
21794
22634
  return "ok";
@@ -22085,7 +22925,7 @@ var SqliteInventoryAssetsRepository = class {
22085
22925
  };
22086
22926
 
22087
22927
  // ../../packages/persistence/src/repositories/policies.ts
22088
- import { randomUUID as randomUUID4 } from "crypto";
22928
+ import { randomUUID as randomUUID5 } from "crypto";
22089
22929
  var SqlitePoliciesRepository = class {
22090
22930
  constructor(db) {
22091
22931
  this.db = db;
@@ -22120,7 +22960,7 @@ var SqlitePoliciesRepository = class {
22120
22960
  failOpenTransaction(this.db, () => {
22121
22961
  for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
22122
22962
  stmt.run({
22123
- id: randomUUID4(),
22963
+ id: randomUUID5(),
22124
22964
  target: JSON.stringify({ category }),
22125
22965
  action,
22126
22966
  now: Date.now()
@@ -22140,7 +22980,7 @@ var SqlitePoliciesRepository = class {
22140
22980
  `INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
22141
22981
  VALUES (:id, 'global', :target, :action, 1, :now, :now)
22142
22982
  ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
22143
- ).run({ id: randomUUID4(), target: JSON.stringify({ category }), action, now });
22983
+ ).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
22144
22984
  }
22145
22985
  // Caps every global per-category policy currently set to block/redact down
22146
22986
  // to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
@@ -22208,7 +23048,7 @@ var SqlitePolicyCatalogRepository = class {
22208
23048
  };
22209
23049
 
22210
23050
  // ../../packages/persistence/src/repositories/project-files.ts
22211
- import { randomUUID as randomUUID5 } from "crypto";
23051
+ import { randomUUID as randomUUID6 } from "crypto";
22212
23052
  var SqliteProjectFilesRepository = class {
22213
23053
  constructor(db) {
22214
23054
  this.db = db;
@@ -22240,7 +23080,7 @@ var SqliteProjectFilesRepository = class {
22240
23080
  const stamp = Math.max(now, maxStamp + 1);
22241
23081
  for (const file2 of scan2.files) {
22242
23082
  this.upsertStmt.run({
22243
- id: randomUUID5(),
23083
+ id: randomUUID6(),
22244
23084
  projectId,
22245
23085
  path: file2.path,
22246
23086
  name: file2.name,
@@ -22254,7 +23094,7 @@ var SqliteProjectFilesRepository = class {
22254
23094
  };
22255
23095
 
22256
23096
  // ../../packages/persistence/src/repositories/resolutions.ts
22257
- import { randomUUID as randomUUID6 } from "crypto";
23097
+ import { randomUUID as randomUUID7 } from "crypto";
22258
23098
  var SqliteResolutionsRepository = class {
22259
23099
  constructor(db, now = () => Date.now()) {
22260
23100
  this.db = db;
@@ -22308,7 +23148,7 @@ var SqliteResolutionsRepository = class {
22308
23148
  */
22309
23149
  insertResolution(r) {
22310
23150
  this.insertStmt.run({
22311
- id: randomUUID6(),
23151
+ id: randomUUID7(),
22312
23152
  findingKey: r.findingKey,
22313
23153
  status: FindingStatus.parse(r.status),
22314
23154
  method: ResolutionMethod.parse(r.method),
@@ -22367,13 +23207,51 @@ var SqliteRuleProbeCacheRepository = class {
22367
23207
  this.readStmt = db.prepare(
22368
23208
  `SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
22369
23209
  );
23210
+ this.countQuarantinedStmt = db.prepare(
23211
+ `SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
23212
+ );
23213
+ this.clearQuarantinedStmt = db.prepare(
23214
+ `DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
23215
+ );
22370
23216
  }
22371
23217
  db;
22372
23218
  upsertStmt;
22373
23219
  readStmt;
23220
+ countQuarantinedStmt;
23221
+ clearQuarantinedStmt;
22374
23222
  getVerdict(ruleKey) {
22375
23223
  return getRow(this.readStmt, { ruleKey });
22376
23224
  }
23225
+ /** How many rules are currently excluded by a cached quarantine verdict. */
23226
+ countQuarantined() {
23227
+ return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
23228
+ }
23229
+ /**
23230
+ * Forgets every quarantine verdict, so the rules behind them are measured
23231
+ * again on the next load. This is the undo for a verdict the machine reached
23232
+ * on its own: a rule terminated mid-scan is cached forever and dropped from
23233
+ * every later scan, and a timing verdict is a wall-clock judgement that a
23234
+ * loaded or slow machine can reach about a rule that is in fact fine.
23235
+ *
23236
+ * Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
23237
+ * keeping, and dropping it would make every rule pay the battery again.
23238
+ *
23239
+ * Reports `refused` from the write's own result rather than inferring it from
23240
+ * the row count. The two are NOT the same answer: `failOpenTransaction`
23241
+ * swallows a contended DELETE (another writer holding the lock past
23242
+ * `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
23243
+ * leaves the count unchanged, which is indistinguishable from "there was
23244
+ * nothing to clear". An undo that reports success while the quarantines are
23245
+ * still in place is worse than one that fails, because the rules it claimed
23246
+ * to restore are silently still disabled.
23247
+ */
23248
+ clearQuarantined() {
23249
+ const before = this.countQuarantined();
23250
+ const committed = failOpenTransaction(this.db, () => {
23251
+ this.clearQuarantinedStmt.run();
23252
+ });
23253
+ return { refused: !committed, cleared: before - this.countQuarantined() };
23254
+ }
22377
23255
  setVerdict(ruleKey, verdict, worstProbeMs2) {
22378
23256
  failOpenTransaction(this.db, () => {
22379
23257
  this.upsertStmt.run({ ruleKey, verdict, worstProbeMs: worstProbeMs2, checkedAt: Date.now() });
@@ -22428,7 +23306,39 @@ var SqliteScanLedgerRepository = class {
22428
23306
  };
22429
23307
 
22430
23308
  // ../../packages/persistence/src/repositories/secret-vault.ts
22431
- import { randomUUID as randomUUID7 } from "crypto";
23309
+ import { randomUUID as randomUUID8 } from "crypto";
23310
+ function pageLimit(requested, fallback) {
23311
+ if (requested === void 0) return fallback;
23312
+ return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
23313
+ }
23314
+ function encodeReuseCursor(payload) {
23315
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
23316
+ }
23317
+ function decodeReuseCursor(cursor) {
23318
+ const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
23319
+ if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
23320
+ // ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
23321
+ // null cursor, which the caller reads as "end of list" — the one outcome a
23322
+ // malformed cursor must never produce, since restarting from the top is the
23323
+ // documented behaviour and the only recoverable one. (`1e999` is valid JSON
23324
+ // and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
23325
+ Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
23326
+ return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
23327
+ }
23328
+ return null;
23329
+ }
23330
+ var REUSED_PREDICATE = `(v.occurrence_count > 1
23331
+ OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
23332
+ var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
23333
+ v.occurrence_count, v.first_seen, v.last_seen`;
23334
+ function toSighting(row) {
23335
+ return {
23336
+ location: row.location,
23337
+ kind: row.kind,
23338
+ firstSeen: new Date(row.first_seen).toISOString(),
23339
+ lastSeen: new Date(row.last_seen).toISOString()
23340
+ };
23341
+ }
22432
23342
  var SELECT_COLUMNS = `
22433
23343
  pointer_id AS pointerId,
22434
23344
  value_fingerprint AS valueFingerprint,
@@ -22612,39 +23522,67 @@ var SqliteSecretVaultRepository = class {
22612
23522
  VALUES (:id, :pointerId, :location, :kind, :now, :now)
22613
23523
  ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
22614
23524
  ).run({
22615
- id: randomUUID7(),
23525
+ id: randomUUID8(),
22616
23526
  pointerId: entry.pointerId,
22617
23527
  location: entry.location,
22618
23528
  kind: entry.kind,
22619
23529
  now
22620
23530
  });
22621
23531
  }
22622
- listSightings(pointerId) {
23532
+ /**
23533
+ * Sightings for a whole page of pointers, in ONE query grouped in JS rather
23534
+ * than one query per row. A pointer with no sightings still gets an entry, so
23535
+ * the caller never has to distinguish "none" from "missing".
23536
+ *
23537
+ * The `IN` list is sized to the page, so this statement cannot be cached on
23538
+ * the instance the way the fixed-shape ones in the constructor are.
23539
+ */
23540
+ sightingsFor(pointerIds) {
23541
+ const byPointer = new Map(pointerIds.map((id) => [id, []]));
23542
+ if (pointerIds.length === 0) return byPointer;
22623
23543
  const rows = allRows(
22624
23544
  this.db.prepare(
22625
- `SELECT location, kind, first_seen, last_seen FROM secret_vault_sighting
22626
- WHERE pointer_id = :pointerId ORDER BY last_seen DESC`
23545
+ `SELECT pointer_id, location, kind, first_seen, last_seen
23546
+ FROM secret_vault_sighting
23547
+ WHERE pointer_id IN (${placeholders(pointerIds.length)})
23548
+ ORDER BY last_seen DESC`
22627
23549
  ),
22628
- { pointerId }
23550
+ pointerIds
22629
23551
  );
23552
+ for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
23553
+ return byPointer;
23554
+ }
23555
+ /** Hydrate a page of raw inventory rows with their sightings, batched. */
23556
+ toInventoryEntries(rows) {
23557
+ const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
22630
23558
  return rows.map((r) => ({
22631
- location: r.location,
22632
- kind: r.kind,
23559
+ pointerId: r.pointer_id,
23560
+ category: r.category,
23561
+ ...r.provider === null ? {} : { provider: r.provider },
23562
+ maskedMatch: r.masked_match,
23563
+ occurrences: r.occurrence_count,
22633
23564
  firstSeen: new Date(r.first_seen).toISOString(),
22634
- lastSeen: new Date(r.last_seen).toISOString()
23565
+ lastSeen: new Date(r.last_seen).toISOString(),
23566
+ revealGrantId: r.grant_id,
23567
+ sightings: sightings.get(r.pointer_id) ?? []
22635
23568
  }));
22636
23569
  }
22637
23570
  /**
22638
- * The dashboard inventory: every vaulted value's descriptor data joined with
22639
- * its sightings and the active reveal-to-model grant when one exists.
22640
- * Raw-free by construction — neither the fingerprint nor the ciphertext
22641
- * columns are selected.
23571
+ * The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
23572
+ * value's descriptor data joined with its sightings and the active
23573
+ * reveal-to-model grant when one exists. Raw-free by construction — neither
23574
+ * the fingerprint nor the ciphertext columns are selected.
23575
+ *
23576
+ * `totals.values` counts the whole store, not the page, so the count a reader
23577
+ * sees never depends on how far they have paged.
22642
23578
  */
22643
- listInventory(now = Date.now()) {
23579
+ listInventory(query = {}, now = Date.now()) {
23580
+ const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
23581
+ const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
23582
+ const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
22644
23583
  const rows = allRows(
22645
23584
  this.db.prepare(
22646
- `SELECT v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
22647
- v.occurrence_count, v.first_seen, v.last_seen,
23585
+ `SELECT ${INVENTORY_COLUMNS},
22648
23586
  (SELECT e.id FROM exceptions e
22649
23587
  WHERE e.rule_id = v.rule_id
22650
23588
  AND e.value_fingerprint = v.value_fingerprint
@@ -22652,45 +23590,109 @@ var SqliteSecretVaultRepository = class {
22652
23590
  AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
22653
23591
  LIMIT 1) AS grant_id
22654
23592
  FROM secret_vault v
22655
- ORDER BY v.last_seen DESC`
23593
+ ${where}
23594
+ ORDER BY v.last_seen DESC, v.pointer_id DESC
23595
+ LIMIT :limit`
22656
23596
  ),
22657
- { now }
23597
+ bindParams({
23598
+ now,
23599
+ limit: limit + 1,
23600
+ ...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
23601
+ })
22658
23602
  );
22659
- return rows.map((r) => ({
22660
- pointerId: r.pointer_id,
22661
- category: r.category,
22662
- ...r.provider === null ? {} : { provider: r.provider },
22663
- maskedMatch: r.masked_match,
22664
- occurrences: r.occurrence_count,
22665
- firstSeen: new Date(r.first_seen).toISOString(),
22666
- lastSeen: new Date(r.last_seen).toISOString(),
22667
- revealGrantId: r.grant_id,
22668
- sightings: this.listSightings(r.pointer_id)
22669
- }));
23603
+ const hasMore = rows.length > limit;
23604
+ const page = hasMore ? rows.slice(0, limit) : rows;
23605
+ const last = page[page.length - 1];
23606
+ return {
23607
+ totals: { values: this.countEntries() },
23608
+ items: this.toInventoryEntries(page),
23609
+ // Minted from the last row of the PAGE, never the extra probe row.
23610
+ nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
23611
+ };
23612
+ }
23613
+ /**
23614
+ * Values reused on this machine — detected more than once, or written to more
23615
+ * than one location — most-reused first, one page at a time.
23616
+ *
23617
+ * Its own read rather than a filter over an inventory page: reuse is a
23618
+ * property of the whole store, and deriving it from 50 newest rows would
23619
+ * under-report exactly the values a reader most needs to see.
23620
+ */
23621
+ listReuse(query = {}, now = Date.now()) {
23622
+ const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
23623
+ const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
23624
+ const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
23625
+ OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
23626
+ const rows = allRows(
23627
+ this.db.prepare(
23628
+ `SELECT ${INVENTORY_COLUMNS},
23629
+ (SELECT e.id FROM exceptions e
23630
+ WHERE e.rule_id = v.rule_id
23631
+ AND e.value_fingerprint = v.value_fingerprint
23632
+ AND e.key_version = v.fingerprint_key_version
23633
+ AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
23634
+ LIMIT 1) AS grant_id
23635
+ FROM secret_vault v
23636
+ WHERE ${REUSED_PREDICATE} ${after}
23637
+ ORDER BY v.occurrence_count DESC, v.pointer_id DESC
23638
+ LIMIT :limit`
23639
+ ),
23640
+ bindParams({
23641
+ now,
23642
+ limit: limit + 1,
23643
+ ...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
23644
+ })
23645
+ );
23646
+ const hasMore = rows.length > limit;
23647
+ const page = hasMore ? rows.slice(0, limit) : rows;
23648
+ const last = page[page.length - 1];
23649
+ return {
23650
+ totals: { reused: this.countReused() },
23651
+ items: this.toInventoryEntries(page),
23652
+ nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
23653
+ };
22670
23654
  }
22671
23655
  /**
22672
- * The de-reference trail, newest first. By default the batched, high-volume
22673
- * reasons (display, view-render) are hidden and counted instead — the rows
22674
- * that matter as a signal are the model crossings, and burying them under
22675
- * render noise would defeat the audit's purpose.
23656
+ * The de-reference trail, newest first, one page at a time. By default the
23657
+ * batched, high-volume reasons (display, view-render) are hidden and counted
23658
+ * instead — the rows that matter as a signal are the model crossings, and
23659
+ * burying them under render noise would defeat the audit's purpose.
23660
+ *
23661
+ * `hiddenBatched` counts the whole trail rather than the page: it is what the
23662
+ * view's "N hidden" line and its toggle speak for, so it must not shrink as
23663
+ * the reader pages.
22676
23664
  */
22677
- listDerefs(opts) {
22678
- const limit = opts?.limit ?? 200;
22679
- const where = opts?.includeBatched === true ? "" : `WHERE reason NOT IN ('display', 'view-render')`;
23665
+ listDerefs(query = {}) {
23666
+ const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
23667
+ const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
23668
+ const conditions = [];
23669
+ if (query.includeBatched !== true) {
23670
+ conditions.push(`reason NOT IN ('display', 'view-render')`);
23671
+ }
23672
+ if (cursor !== null) {
23673
+ conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
23674
+ }
23675
+ const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
22680
23676
  const rows = allRows(
22681
23677
  this.db.prepare(
22682
23678
  `SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
22683
23679
  FROM secret_vault_deref ${where}
22684
- ORDER BY at DESC, rowid DESC LIMIT :limit`
23680
+ ORDER BY at DESC, id DESC LIMIT :limit`
22685
23681
  ),
22686
- { limit }
23682
+ bindParams({
23683
+ limit: limit + 1,
23684
+ ...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
23685
+ })
22687
23686
  );
22688
- const hiddenBatched = opts?.includeBatched === true ? 0 : countScalar(
23687
+ const hasMore = rows.length > limit;
23688
+ const page = hasMore ? rows.slice(0, limit) : rows;
23689
+ const last = page[page.length - 1];
23690
+ const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
22689
23691
  this.db,
22690
23692
  `SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
22691
23693
  );
22692
23694
  return {
22693
- rows: rows.map((r) => ({
23695
+ items: page.map((r) => ({
22694
23696
  id: r.id,
22695
23697
  pointerId: r.pointer_id,
22696
23698
  at: new Date(r.at).toISOString(),
@@ -22700,12 +23702,20 @@ var SqliteSecretVaultRepository = class {
22700
23702
  ...r.grant_id === null ? {} : { grantId: r.grant_id },
22701
23703
  pointerCount: r.pointer_count
22702
23704
  })),
23705
+ nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
22703
23706
  hiddenBatched
22704
23707
  };
22705
23708
  }
22706
23709
  countEntries() {
22707
23710
  return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
22708
23711
  }
23712
+ /** Values reused on this machine — the reuse list's page-independent total. */
23713
+ countReused() {
23714
+ return countScalar(
23715
+ this.db,
23716
+ `SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
23717
+ );
23718
+ }
22709
23719
  };
22710
23720
 
22711
23721
  // ../../packages/persistence/src/repositories/security.ts
@@ -22720,7 +23730,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
22720
23730
  var SCAN_COVERAGE = [
22721
23731
  { provider: "claudecode", coverage: 100, supported: true },
22722
23732
  { provider: "cursor", coverage: 0, supported: false },
22723
- { provider: "codex", coverage: 0, supported: false },
23733
+ { provider: "codex", coverage: 80, supported: true },
23734
+ { provider: "antigravity", coverage: 60, supported: true },
23735
+ { provider: "claudeai", coverage: 0, supported: false },
22724
23736
  { provider: "chatgpt", coverage: 0, supported: false },
22725
23737
  { provider: "copilot", coverage: 0, supported: false },
22726
23738
  { provider: "api", coverage: 0, supported: false }
@@ -23053,7 +24065,7 @@ var SqliteSecurityRepository = class {
23053
24065
  };
23054
24066
 
23055
24067
  // ../../packages/persistence/src/repositories/shares.ts
23056
- import { randomUUID as randomUUID8 } from "crypto";
24068
+ import { randomUUID as randomUUID9 } from "crypto";
23057
24069
  var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
23058
24070
  var IN_CHUNK = 500;
23059
24071
  var KIND_ORDER = ["provider", "internal", "external", "ip"];
@@ -23309,7 +24321,7 @@ var SqliteSharesRepository = class {
23309
24321
  (id, destination_id, host, decision, created_at, updated_at)
23310
24322
  VALUES (:id, :destinationId, :host, :decision, :now, :now)`
23311
24323
  ).run({
23312
- id: randomUUID8(),
24324
+ id: randomUUID9(),
23313
24325
  destinationId,
23314
24326
  host: dest.host,
23315
24327
  decision,
@@ -23458,7 +24470,7 @@ var SqliteSharesRepository = class {
23458
24470
  let destinationId = destIds.get(hit.host);
23459
24471
  if (destinationId === void 0) {
23460
24472
  destStmt.run({
23461
- id: randomUUID8(),
24473
+ id: randomUUID9(),
23462
24474
  kind: hit.kind,
23463
24475
  name: hit.name,
23464
24476
  host: hit.host,
@@ -23474,7 +24486,7 @@ var SqliteSharesRepository = class {
23474
24486
  let endpointId = endpointIds.get(endpointKey);
23475
24487
  if (endpointId === void 0) {
23476
24488
  endpointStmt.run({
23477
- id: randomUUID8(),
24489
+ id: randomUUID9(),
23478
24490
  destinationId,
23479
24491
  method: hit.method,
23480
24492
  transport: hit.transport,
@@ -23487,7 +24499,7 @@ var SqliteSharesRepository = class {
23487
24499
  endpointIds.set(endpointKey, endpointId);
23488
24500
  }
23489
24501
  siteStmt.run({
23490
- id: randomUUID8(),
24502
+ id: randomUUID9(),
23491
24503
  endpointId,
23492
24504
  project: input.project,
23493
24505
  projectKey: input.projectKey,
@@ -23852,6 +24864,9 @@ function purgeSampleData(db) {
23852
24864
  }
23853
24865
 
23854
24866
  // ../../packages/persistence/src/database.ts
24867
+ var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
24868
+ "aka.persistence.unsafeTestOnlyRawHandle"
24869
+ );
23855
24870
  function linkHost(input, hostId) {
23856
24871
  return hostId ? { ...input, hostId } : input;
23857
24872
  }
@@ -23873,21 +24888,34 @@ function openWithPragmas(file2) {
23873
24888
  }
23874
24889
  return db;
23875
24890
  }
23876
- function backupLegacyStore(file2) {
23877
- const backup = `${file2}.legacy.${String(Date.now())}.bak`;
23878
- renameSync2(file2, backup);
23879
- tightenFile(backup);
23880
- for (const sidecar of dbSidecars(file2)) {
23881
- if (existsSync(sidecar)) rmSync2(sidecar);
24891
+ function backupLegacyStore(db, file2) {
24892
+ reapStalePartials(file2);
24893
+ const backup = backupPath(file2, "legacy");
24894
+ let snapshotted = false;
24895
+ let snapshotError;
24896
+ try {
24897
+ snapshotStore(db, backup);
24898
+ snapshotted = true;
24899
+ } catch (error51) {
24900
+ snapshotError = error51;
24901
+ } finally {
24902
+ db.close();
23882
24903
  }
24904
+ if (!snapshotted) {
24905
+ akaWarn(
24906
+ `Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
24907
+ );
24908
+ moveStoreAside(file2, backup);
24909
+ return backup;
24910
+ }
24911
+ discardStore(file2, backup);
23883
24912
  return backup;
23884
24913
  }
23885
24914
  function openAndInitialize(file2) {
23886
24915
  let db = openWithPragmas(file2);
23887
24916
  try {
23888
24917
  if (isForeignSqliteLineage(db)) {
23889
- db.close();
23890
- const backup = backupLegacyStore(file2);
24918
+ const backup = backupLegacyStore(db, file2);
23891
24919
  db = openWithPragmas(file2);
23892
24920
  akaWarn(
23893
24921
  `Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
@@ -23931,7 +24959,7 @@ function openAndInitialize(file2) {
23931
24959
  }
23932
24960
  function openLocalDatabase(dir) {
23933
24961
  ensureDataDirSync(dir);
23934
- const file2 = join(dir, DB_FILENAME);
24962
+ const file2 = join2(dir, DB_FILENAME);
23935
24963
  const {
23936
24964
  db,
23937
24965
  events,
@@ -24048,7 +25076,7 @@ function openLocalDatabase(dir) {
24048
25076
  const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
24049
25077
  if (!definitionId) continue;
24050
25078
  inspectionFindings.insertFinding({
24051
- id: randomUUID9(),
25079
+ id: randomUUID10(),
24052
25080
  auditEventId: record2.scanEvent.id,
24053
25081
  inspectionDefinitionId: definitionId,
24054
25082
  span: finding.span,
@@ -24154,7 +25182,9 @@ function openLocalDatabase(dir) {
24154
25182
  transaction,
24155
25183
  close: () => {
24156
25184
  db.close();
24157
- }
25185
+ },
25186
+ // Last, and a plain value rather than a getter, so `{ ...db }` carries it.
25187
+ [UNSAFE_TEST_ONLY_RAW_HANDLE]: db
24158
25188
  };
24159
25189
  }
24160
25190
 
@@ -24178,6 +25208,20 @@ var UserGrantPolicyProvider = class {
24178
25208
  }
24179
25209
  };
24180
25210
 
25211
+ // ../../packages/persistence/src/file-lock.ts
25212
+ import { randomUUID as randomUUID11 } from "crypto";
25213
+ import {
25214
+ closeSync,
25215
+ existsSync as existsSync2,
25216
+ openSync,
25217
+ readFileSync,
25218
+ rmSync as rmSync3,
25219
+ statSync as statSync2,
25220
+ writeFileSync as writeFileSync2
25221
+ } from "fs";
25222
+ import { hostname as hostname3 } from "os";
25223
+ var PARK = new Int32Array(new SharedArrayBuffer(4));
25224
+
24181
25225
  // ../../packages/persistence/src/finding-key.ts
24182
25226
  import { createHash as createHash3 } from "crypto";
24183
25227
  function normalizeFilePath(filePath) {
@@ -24190,13 +25234,13 @@ function computeFindingKey(input) {
24190
25234
 
24191
25235
  // ../../packages/persistence/src/fingerprint.ts
24192
25236
  import { createHmac, randomBytes } from "crypto";
24193
- import { existsSync as existsSync2, readFileSync } from "fs";
24194
- import { join as join2 } from "path";
25237
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
25238
+ import { join as join3 } from "path";
24195
25239
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
24196
- var KEY_FILENAME = "exception.key";
25240
+ var EXCEPTION_KEY_FILENAME = "exception.key";
24197
25241
  var KEY_MATERIAL_BYTES = 32;
24198
25242
  function keyFilePath(dataDir2) {
24199
- return join2(dataDir2, KEY_FILENAME);
25243
+ return join3(dataDir2, EXCEPTION_KEY_FILENAME);
24200
25244
  }
24201
25245
  function parseKeyFile(raw) {
24202
25246
  const parsed = JSON.parse(raw);
@@ -24234,8 +25278,8 @@ var FloorUnreadableError = class extends Error {
24234
25278
  }
24235
25279
  };
24236
25280
  function storedKeyVersionFloor(dataDir2) {
24237
- const file2 = join2(dataDir2, DB_FILENAME);
24238
- if (!existsSync2(file2)) return 0;
25281
+ const file2 = join3(dataDir2, DB_FILENAME);
25282
+ if (!existsSync3(file2)) return 0;
24239
25283
  let db;
24240
25284
  try {
24241
25285
  db = new DatabaseSync2(file2, { readOnly: true });
@@ -24260,18 +25304,36 @@ function storedKeyVersionFloor(dataDir2) {
24260
25304
  db?.close();
24261
25305
  }
24262
25306
  }
24263
- function writeKeyFile(dataDir2, key) {
25307
+ function serializeKey(key) {
25308
+ return JSON.stringify({ version: key.version, material: key.material.toString("base64") });
25309
+ }
25310
+ function createKeyFile(dataDir2, key) {
24264
25311
  ensureDataDirSync(dataDir2);
24265
25312
  const file2 = keyFilePath(dataDir2);
24266
- const body = JSON.stringify({ version: key.version, material: key.material.toString("base64") });
24267
- writeOwnerOnlyFileSync(file2, `${body}
24268
- `);
24269
- return key;
25313
+ if (createOwnerOnlyFileSync(file2, `${serializeKey(key)}
25314
+ `)) return key;
25315
+ const winner = readFingerprintKey(dataDir2);
25316
+ if (winner) {
25317
+ tightenFile(file2);
25318
+ return winner;
25319
+ }
25320
+ const occupant = classifyOccupant(file2);
25321
+ throw new KeyUnclaimableError(occupantMessage(file2, occupant.kind), occupant.cause);
25322
+ }
25323
+ function occupantMessage(file2, kind) {
25324
+ switch (kind) {
25325
+ case "symlink":
25326
+ return `exception key file is a symlink (${file2}); remove it so a key can be created`;
25327
+ case "gone":
25328
+ return "exception key file was removed while it was being created";
25329
+ case "unknown":
25330
+ return `exception key file (${file2}) is occupied but cannot be inspected; check the permissions on its directory`;
25331
+ }
24270
25332
  }
24271
25333
  function readFingerprintKey(dataDir2) {
24272
25334
  let raw;
24273
25335
  try {
24274
- raw = readFileSync(keyFilePath(dataDir2), "utf8");
25336
+ raw = readFileSync2(keyFilePath(dataDir2), "utf8");
24275
25337
  } catch (err) {
24276
25338
  if (err.code === "ENOENT") return null;
24277
25339
  throw err instanceof Error ? err : new Error(String(err));
@@ -24284,7 +25346,7 @@ function loadOrCreateFingerprintKey(dataDir2) {
24284
25346
  tightenFile(keyFilePath(dataDir2));
24285
25347
  return existing;
24286
25348
  }
24287
- return writeKeyFile(dataDir2, {
25349
+ return createKeyFile(dataDir2, {
24288
25350
  version: storedKeyVersionFloor(dataDir2) + 1,
24289
25351
  material: randomBytes(KEY_MATERIAL_BYTES)
24290
25352
  });
@@ -24297,21 +25359,21 @@ function fingerprintValue(key, raw) {
24297
25359
  import { renameSync as renameSync3 } from "fs";
24298
25360
  import { mkdir } from "fs/promises";
24299
25361
  import { homedir } from "os";
24300
- import { join as join3 } from "path";
25362
+ import { join as join4 } from "path";
24301
25363
  function defaultDataDir() {
24302
- return join3(homedir(), ".aka");
25364
+ return join4(homedir(), ".aka");
24303
25365
  }
24304
25366
  function settingsDir(base = defaultDataDir()) {
24305
- return join3(base, "settings");
25367
+ return join4(base, "settings");
24306
25368
  }
24307
25369
  function dataDir(base = defaultDataDir()) {
24308
- return join3(base, "data");
25370
+ return join4(base, "data");
24309
25371
  }
24310
25372
  function dbPath(base = defaultDataDir()) {
24311
- return join3(dataDir(base), "aka.db");
25373
+ return join4(dataDir(base), "aka.db");
24312
25374
  }
24313
25375
  function keysDir(base = defaultDataDir()) {
24314
- return join3(base, "keys");
25376
+ return join4(base, "keys");
24315
25377
  }
24316
25378
  function ensureLayoutDirSync(dir = defaultDataDir()) {
24317
25379
  ensureDataDirSync(dir);
@@ -24324,8 +25386,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
24324
25386
  for (const { name, dest } of moves) {
24325
25387
  try {
24326
25388
  ensureDataDirSync(dest);
24327
- const moved = join3(dest, name);
24328
- renameSync3(join3(base, name), moved);
25389
+ const moved = join4(dest, name);
25390
+ renameSync3(join4(base, name), moved);
24329
25391
  tightenFile(moved);
24330
25392
  } catch {
24331
25393
  }
@@ -24333,10 +25395,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
24333
25395
  }
24334
25396
 
24335
25397
  // ../../packages/persistence/src/settings.ts
24336
- import { readFileSync as readFileSync2 } from "fs";
24337
- import { join as join4 } from "path";
25398
+ import { readFileSync as readFileSync3 } from "fs";
25399
+ import { join as join5 } from "path";
25400
+ var SETTINGS_FILENAME = "settings.json";
24338
25401
  function readWorkspaceSettings(base = defaultDataDir()) {
24339
- const record2 = readJson(join4(settingsDir(base), "settings.json"));
25402
+ const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
24340
25403
  if (!record2) return defaultWorkspaceSettings();
24341
25404
  try {
24342
25405
  return WorkspaceSettings.parse(record2);
@@ -24347,7 +25410,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
24347
25410
  function readJson(file2) {
24348
25411
  let text;
24349
25412
  try {
24350
- text = readFileSync2(file2, "utf8");
25413
+ text = readFileSync3(file2, "utf8");
24351
25414
  } catch {
24352
25415
  return null;
24353
25416
  }
@@ -24468,13 +25531,18 @@ import { randomBytes as randomBytes2 } from "crypto";
24468
25531
  import {
24469
25532
  chmodSync as chmodSync2,
24470
25533
  mkdirSync as mkdirSync2,
24471
- readFileSync as readFileSync3,
25534
+ readFileSync as readFileSync4,
24472
25535
  renameSync as renameSync4,
24473
- rmSync as rmSync3,
24474
- statSync,
24475
- writeFileSync as writeFileSync2
25536
+ rmSync as rmSync4,
25537
+ statSync as statSync3,
25538
+ writeFileSync as writeFileSync3
24476
25539
  } from "fs";
24477
- import { join as join5 } from "path";
25540
+ import { join as join6 } from "path";
25541
+ var VAULT_OCCUPANT_REASON = {
25542
+ symlink: "the path is a symlink; remove it so a keyring can be created",
25543
+ gone: "the path was occupied but holds no keyring (removed while it was being created)",
25544
+ unknown: "the path is occupied but cannot be inspected; check the permissions on its directory"
25545
+ };
24478
25546
  var VaultKeyEpochMissingError = class extends Error {
24479
25547
  version;
24480
25548
  constructor(version2) {
@@ -24567,28 +25635,28 @@ function claimRotationLock(lock, owner) {
24567
25635
  throw asError(err);
24568
25636
  }
24569
25637
  try {
24570
- writeFileSync2(join5(lock, LOCK_OWNER_FILE), `${owner}
25638
+ writeFileSync3(join6(lock, LOCK_OWNER_FILE), `${owner}
24571
25639
  `, { mode: DATA_FILE_MODE });
24572
25640
  return true;
24573
25641
  } catch (err) {
24574
- rmSync3(lock, { recursive: true, force: true });
25642
+ rmSync4(lock, { recursive: true, force: true });
24575
25643
  throw asError(err);
24576
25644
  }
24577
25645
  }
24578
25646
  function acquireRotationLock(keysDir2) {
24579
- const lock = join5(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
25647
+ const lock = join6(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
24580
25648
  const owner = randomBytes2(16).toString("hex");
24581
25649
  if (claimRotationLock(lock, owner)) return { lock, owner };
24582
25650
  let held;
24583
25651
  try {
24584
- held = statSync(lock);
25652
+ held = statSync3(lock);
24585
25653
  } catch {
24586
25654
  throw new Error(ROTATION_IN_PROGRESS);
24587
25655
  }
24588
25656
  if (Date.now() - held.mtimeMs < ROTATION_LOCK_STALE_MS) throw new Error(ROTATION_IN_PROGRESS);
24589
25657
  const aside = `${lock}.stale.${owner}`;
24590
25658
  try {
24591
- const now = statSync(lock);
25659
+ const now = statSync3(lock);
24592
25660
  if (now.ino !== held.ino || now.mtimeMs !== held.mtimeMs) {
24593
25661
  throw new Error(ROTATION_IN_PROGRESS);
24594
25662
  }
@@ -24597,17 +25665,17 @@ function acquireRotationLock(keysDir2) {
24597
25665
  if (err instanceof Error && err.message === ROTATION_IN_PROGRESS) throw err;
24598
25666
  throw new Error(ROTATION_IN_PROGRESS, { cause: err });
24599
25667
  }
24600
- rmSync3(aside, { recursive: true, force: true });
25668
+ rmSync4(aside, { recursive: true, force: true });
24601
25669
  if (!claimRotationLock(lock, owner)) throw new Error(ROTATION_IN_PROGRESS);
24602
25670
  return { lock, owner };
24603
25671
  }
24604
25672
  function releaseRotationLock(lease) {
24605
25673
  try {
24606
- if (readFileSync3(join5(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
25674
+ if (readFileSync4(join6(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
24607
25675
  } catch {
24608
25676
  return;
24609
25677
  }
24610
- rmSync3(lease.lock, { recursive: true, force: true });
25678
+ rmSync4(lease.lock, { recursive: true, force: true });
24611
25679
  }
24612
25680
  function withRotationLock(keysDir2, work) {
24613
25681
  ensureDataDirSync(keysDir2);
@@ -24624,7 +25692,7 @@ var FileKeyProvider = class {
24624
25692
  this.#keysDir = keysDir2;
24625
25693
  }
24626
25694
  get filePath() {
24627
- return join5(this.#keysDir, VAULT_KEY_FILENAME);
25695
+ return join6(this.#keysDir, VAULT_KEY_FILENAME);
24628
25696
  }
24629
25697
  loadOrCreate() {
24630
25698
  return asAsync(() => {
@@ -24654,7 +25722,7 @@ var FileKeyProvider = class {
24654
25722
  #read() {
24655
25723
  let raw;
24656
25724
  try {
24657
- raw = readFileSync3(this.filePath, "utf8");
25725
+ raw = readFileSync4(this.filePath, "utf8");
24658
25726
  } catch (err) {
24659
25727
  if (err.code === "ENOENT") return null;
24660
25728
  throw err instanceof Error ? err : new Error(String(err));
@@ -24662,34 +25730,32 @@ var FileKeyProvider = class {
24662
25730
  return parseKeyring(raw);
24663
25731
  }
24664
25732
  /**
24665
- * First mint: the keyring is created at its FINAL path with a
24666
- * creation-exclusive write, so two processes racing a fresh machine cannot
24667
- * each mint a different epoch 1 with tmp + rename the loser's replace
24668
- * would orphan everything the winner had already sealed. On EEXIST the
24669
- * loser re-reads and adopts the winner's keyring; it minted nothing.
24670
- * Atomic replace is unnecessary here: nothing can be mid-read of a file
24671
- * that did not exist, and a torn exclusive write parses as corrupt on the
24672
- * next read and fails secure rather than being re-minted over.
25733
+ * First mint: the keyring is CREATED, never replaced, so two processes racing
25734
+ * a fresh machine cannot each mint a different epoch 1 — with tmp + rename
25735
+ * the loser's replace would orphan everything the winner had already sealed.
25736
+ * The loser re-reads and adopts the winner's keyring; it minted nothing.
25737
+ *
25738
+ * `createOwnerOnlyFileSync` publishes by link rather than by an exclusive open
25739
+ * at the final path, so the keyring never exists at zero length: a reader —
25740
+ * including the loser, re-reading in order to adopt sees the file absent or
25741
+ * whole, and never mistakes a live keyring for a corrupt one. A corrupt file
25742
+ * still throws from the parse and is never re-minted over.
24673
25743
  */
24674
25744
  #createExclusive() {
24675
25745
  ensureDataDirSync(this.#keysDir);
24676
25746
  const keyring = mintKeyring();
24677
- try {
24678
- writeFileSync2(this.filePath, `${serializeKeyring(keyring)}
24679
- `, {
24680
- flag: "wx",
24681
- mode: DATA_FILE_MODE
24682
- });
24683
- } catch (err) {
24684
- if (err.code !== "EEXIST") throw asError(err);
24685
- const winner = this.#read();
24686
- if (!winner) {
24687
- throw new Error("vault: key file vanished during first mint", { cause: err });
24688
- }
24689
- return winner;
25747
+ if (createOwnerOnlyFileSync(this.filePath, `${serializeKeyring(keyring)}
25748
+ `)) return keyring;
25749
+ const winner = this.#read();
25750
+ if (!winner) {
25751
+ const occupant = classifyOccupant(this.filePath);
25752
+ throw new KeyUnclaimableError(
25753
+ `vault: cannot create a key file at ${this.filePath} \u2014 ${VAULT_OCCUPANT_REASON[occupant.kind]}`,
25754
+ occupant.cause
25755
+ );
24690
25756
  }
24691
25757
  tightenFileMode(this.filePath);
24692
- return keyring;
25758
+ return winner;
24693
25759
  }
24694
25760
  /**
24695
25761
  * Atomic tmp + rename so a crash mid-write cannot truncate the keyring.
@@ -24700,7 +25766,7 @@ var FileKeyProvider = class {
24700
25766
  ensureDataDirSync(this.#keysDir);
24701
25767
  const file2 = this.filePath;
24702
25768
  const tmp = `${file2}.tmp`;
24703
- writeFileSync2(tmp, `${serializeKeyring(keyring)}
25769
+ writeFileSync3(tmp, `${serializeKeyring(keyring)}
24704
25770
  `, { mode: DATA_FILE_MODE });
24705
25771
  renameSync4(tmp, file2);
24706
25772
  tightenFileMode(file2);
@@ -24826,7 +25892,7 @@ function createKeyProvider(custody, keysDir2) {
24826
25892
  }
24827
25893
 
24828
25894
  // ../../packages/persistence/src/vault/vault.ts
24829
- import { randomBytes as randomBytes3, randomUUID as randomUUID10 } from "crypto";
25895
+ import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
24830
25896
  var CONSENT_ABSENT = /* @__PURE__ */ Symbol("aka.vault.consentAbsent");
24831
25897
  var UNAVAILABLE = /* @__PURE__ */ Symbol("aka.vault.unavailable");
24832
25898
  var VAULT_PURGE_POINTER_ID = "*";
@@ -24853,14 +25919,12 @@ function parsePointer(token) {
24853
25919
  var SecretVault = class {
24854
25920
  #repo;
24855
25921
  #keys;
24856
- #fingerprintKey;
24857
25922
  #isConsented;
24858
25923
  #verifyGrant;
24859
25924
  #now;
24860
25925
  constructor(deps) {
24861
25926
  this.#repo = deps.repo;
24862
25927
  this.#keys = deps.keys;
24863
- this.#fingerprintKey = deps.fingerprintKey;
24864
25928
  this.#isConsented = deps.isConsented;
24865
25929
  this.#verifyGrant = deps.verifyGrant;
24866
25930
  this.#now = deps.now ?? (() => Date.now());
@@ -24869,10 +25933,23 @@ var SecretVault = class {
24869
25933
  * Store a value and return the pointer that stands for it. The same value
24870
25934
  * always yields the same pointer on this machine — one row, one pointer id,
24871
25935
  * one category — which is what makes dedup and reuse counting work.
25936
+ *
25937
+ * `fingerprintKey` is the exception-key epoch this value's fingerprint is
25938
+ * derived under — a different key from the vault's, with different rotation
25939
+ * semantics. It is a parameter of the WRITE rather than a constructor dep,
25940
+ * and a thunk rather than a value, so that the only way to reach a key is to
25941
+ * store something: a read-only caller never names it, and a caller whose
25942
+ * source mints on absence mints only once consent has actually opened the
25943
+ * write. `refreshFingerprints` takes its key the same way, for the same
25944
+ * reason.
25945
+ *
25946
+ * Resolved once per call, so the fingerprint and the version it is recorded
25947
+ * under can never come from two different epochs.
24872
25948
  */
24873
- async tokenize(raw, meta3) {
25949
+ async tokenize(raw, meta3, fingerprintKey) {
24874
25950
  if (!this.#isConsented()) return CONSENT_ABSENT;
24875
- const valueFingerprint = fingerprintValue(this.#fingerprintKey, raw);
25951
+ const fpKey = fingerprintKey();
25952
+ const valueFingerprint = fingerprintValue(fpKey, raw);
24876
25953
  const existing = this.#repo.byValueFingerprint(valueFingerprint);
24877
25954
  const now = this.#now();
24878
25955
  if (existing) {
@@ -24888,7 +25965,7 @@ var SecretVault = class {
24888
25965
  {
24889
25966
  pointerId: base32Encode(pointerId),
24890
25967
  valueFingerprint,
24891
- fingerprintKeyVersion: this.#fingerprintKey.version,
25968
+ fingerprintKeyVersion: fpKey.version,
24892
25969
  keyVersion: version2,
24893
25970
  // Recorded so the row stays OPENABLE if the wire-format constant ever
24894
25971
  // moves: it is part of this row's AEAD AAD. It is not a tag input —
@@ -25132,7 +26209,7 @@ var SecretVault = class {
25132
26209
  purgeVault() {
25133
26210
  const destroyed = this.#repo.purgeAll();
25134
26211
  this.#repo.recordDeref({
25135
- id: randomUUID10(),
26212
+ id: randomUUID12(),
25136
26213
  pointerId: VAULT_PURGE_POINTER_ID,
25137
26214
  at: this.#now(),
25138
26215
  target: "human",
@@ -25201,7 +26278,7 @@ var SecretVault = class {
25201
26278
  }
25202
26279
  #audit(pointerId, opts, outcome) {
25203
26280
  this.#repo.recordDeref({
25204
- id: randomUUID10(),
26281
+ id: randomUUID12(),
25205
26282
  pointerId,
25206
26283
  at: this.#now(),
25207
26284
  target: opts.target,
@@ -25216,15 +26293,15 @@ var SecretVault = class {
25216
26293
  };
25217
26294
 
25218
26295
  // ../../packages/persistence/src/warn-era-cap.ts
25219
- import { existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
25220
- import { join as join6 } from "path";
26296
+ import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
26297
+ import { join as join7 } from "path";
25221
26298
  var MARKER = "warn-era-capped";
25222
26299
  function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
25223
26300
  if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
25224
- const marker = join6(dataDir2, MARKER);
25225
- if (existsSync3(marker)) return { capped: 0, skipped: "already-run" };
26301
+ const marker = join7(dataDir2, MARKER);
26302
+ if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
25226
26303
  const capped = db.policies.capCategoryActions();
25227
- writeFileSync3(marker, `${new Date(Date.now()).toISOString()}
26304
+ writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
25228
26305
  `, { mode: DATA_FILE_MODE });
25229
26306
  return { capped };
25230
26307
  }
@@ -25278,11 +26355,11 @@ function resolveProvider() {
25278
26355
  }
25279
26356
 
25280
26357
  // ../../packages/plugin-sdk/src/config.ts
25281
- function loadConfig(base = defaultDataDir()) {
26358
+ function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
25282
26359
  try {
25283
26360
  ensureLayoutDirSync(base);
25284
- const settingsFile = join7(settingsDir(base), "settings.json");
25285
- if (existsSync4(settingsFile)) tightenFile(settingsFile);
26361
+ const settingsFile = join8(settingsDir(base), "settings.json");
26362
+ if (existsSync5(settingsFile)) tightenFile(settingsFile);
25286
26363
  } catch {
25287
26364
  }
25288
26365
  migrateLegacyLayout(base);
@@ -25293,21 +26370,21 @@ function loadConfig(base = defaultDataDir()) {
25293
26370
  dbPath: dbPath(base),
25294
26371
  settingsDir: settingsDir(base),
25295
26372
  onboarded: settings.onboardedAt != null,
25296
- provider: resolveProviderSafe()
26373
+ provider: resolveProviderSafe(resolveProviderFn)
25297
26374
  };
25298
26375
  }
25299
- function resolveProviderSafe() {
26376
+ function resolveProviderSafe(resolveProviderFn) {
25300
26377
  try {
25301
- return resolveProvider();
26378
+ return resolveProviderFn();
25302
26379
  } catch {
25303
26380
  return { provider: "anthropic" };
25304
26381
  }
25305
26382
  }
25306
26383
 
25307
26384
  // ../../packages/plugin-sdk/src/config-inventory.ts
25308
- import { readdirSync, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
26385
+ import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
25309
26386
  import { homedir as homedir2 } from "os";
25310
- import { basename as basename2, join as join9 } from "path";
26387
+ import { basename as basename3, join as join10 } from "path";
25311
26388
 
25312
26389
  // ../../packages/detections/src/egress/registry.ts
25313
26390
  var EXTRACTOR_VERSION = "1";
@@ -27964,7 +29041,7 @@ var gcp_service_account_default = {
27964
29041
  severity: "critical",
27965
29042
  matcher: {
27966
29043
  type: "regex",
27967
- pattern: "\\b[a-z0-9][a-z0-9-]{2,60}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
29044
+ pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
27968
29045
  flags: "g"
27969
29046
  },
27970
29047
  examples: [
@@ -28343,15 +29420,15 @@ function bundledDetections() {
28343
29420
  }
28344
29421
 
28345
29422
  // ../../packages/plugin-sdk/src/repo.ts
28346
- import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
28347
- import { basename, dirname, isAbsolute, join as join8, sep as sep2 } from "path";
29423
+ import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
29424
+ import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
28348
29425
  function resolveRepo(cwd) {
28349
29426
  try {
28350
29427
  const root = findGitRoot(cwd);
28351
29428
  if (!root) return void 0;
28352
29429
  const ctx = resolveGitContext(root);
28353
29430
  const url2 = ctx ? remoteUrl(ctx) : void 0;
28354
- return (url2 ? slugFromUrl(url2) : void 0) ?? basename(ctx?.headRoot ?? root);
29431
+ return (url2 ? slugFromUrl(url2) : void 0) ?? basename2(ctx?.headRoot ?? root);
28355
29432
  } catch {
28356
29433
  return void 0;
28357
29434
  }
@@ -28359,36 +29436,36 @@ function resolveRepo(cwd) {
28359
29436
  function findGitRoot(start) {
28360
29437
  let dir = start;
28361
29438
  for (; ; ) {
28362
- if (existsSync5(join8(dir, ".git"))) return dir;
28363
- const parent = dirname(dir);
29439
+ if (existsSync6(join9(dir, ".git"))) return dir;
29440
+ const parent = dirname2(dir);
28364
29441
  if (parent === dir) return void 0;
28365
29442
  dir = parent;
28366
29443
  }
28367
29444
  }
28368
29445
  function resolveGitContext(root) {
28369
- const dotGit = join8(root, ".git");
29446
+ const dotGit = join9(root, ".git");
28370
29447
  try {
28371
- if (statSync2(dotGit).isDirectory()) {
28372
- return { configPath: join8(dotGit, "config"), headRoot: root };
29448
+ if (statSync4(dotGit).isDirectory()) {
29449
+ return { configPath: join9(dotGit, "config"), headRoot: root };
28373
29450
  }
28374
29451
  } catch {
28375
29452
  return void 0;
28376
29453
  }
28377
29454
  const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
28378
29455
  if (!target) return void 0;
28379
- const gitdir = isAbsolute(target) ? target : join8(root, target);
28380
- if (existsSync5(join8(gitdir, "config"))) {
28381
- return { configPath: join8(gitdir, "config"), headRoot: root };
29456
+ const gitdir = isAbsolute(target) ? target : join9(root, target);
29457
+ if (existsSync6(join9(gitdir, "config"))) {
29458
+ return { configPath: join9(gitdir, "config"), headRoot: root };
28382
29459
  }
28383
- const commonRaw = safeRead(join8(gitdir, "commondir"))?.trim();
29460
+ const commonRaw = safeRead(join9(gitdir, "commondir"))?.trim();
28384
29461
  if (!commonRaw) return void 0;
28385
- const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join8(gitdir, commonRaw);
28386
- const headRoot = basename(commonGitDir) === ".git" ? dirname(commonGitDir) : root;
28387
- return { configPath: join8(commonGitDir, "config"), headRoot };
29462
+ const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join9(gitdir, commonRaw);
29463
+ const headRoot = basename2(commonGitDir) === ".git" ? dirname2(commonGitDir) : root;
29464
+ return { configPath: join9(commonGitDir, "config"), headRoot };
28388
29465
  }
28389
29466
  function safeRead(path) {
28390
29467
  try {
28391
- return readFileSync4(path, "utf8");
29468
+ return readFileSync5(path, "utf8");
28392
29469
  } catch {
28393
29470
  return void 0;
28394
29471
  }
@@ -28426,13 +29503,13 @@ function slugFromUrl(url2) {
28426
29503
  }
28427
29504
 
28428
29505
  // ../../packages/plugin-sdk/src/events.ts
28429
- import { createHash as createHash4, randomUUID as randomUUID11 } from "crypto";
29506
+ import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
28430
29507
  function contentHashOf(text) {
28431
29508
  return createHash4("sha256").update(text).digest("hex");
28432
29509
  }
28433
29510
  function buildIngestEvent(input) {
28434
29511
  return {
28435
- id: randomUUID11(),
29512
+ id: randomUUID13(),
28436
29513
  sourceTool: input.sourceTool,
28437
29514
  kind: input.kind,
28438
29515
  occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
@@ -28443,82 +29520,507 @@ function buildIngestEvent(input) {
28443
29520
  // SDK boot in the fail-open hook path). Preserve any id the caller already set.
28444
29521
  metadata: {
28445
29522
  ...input.metadata,
28446
- correlationId: input.metadata?.correlationId ?? randomUUID11()
29523
+ correlationId: input.metadata?.correlationId ?? randomUUID13()
28447
29524
  }
28448
29525
  };
28449
29526
  }
28450
29527
 
28451
- // ../../packages/plugin-sdk/src/inventory-resolver.ts
28452
- import { arch, hostname as hostname3, platform, release } from "os";
28453
-
28454
- // ../../packages/plugin-sdk/src/nudge.ts
28455
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
28456
- import { join as join10 } from "path";
28457
-
28458
- // ../../packages/plugin-sdk/src/paths.ts
28459
- import { readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
28460
- import { basename as basename3, dirname as dirname2, sep as sep3 } from "path";
28461
-
28462
- // ../../packages/plugin-sdk/src/project-files.ts
28463
- var import_ignore = __toESM(require_ignore(), 1);
28464
- import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
28465
- import { basename as basename4, join as join11, relative, sep as sep4 } from "path";
29528
+ // ../../packages/plugin-sdk/src/isolated-scan.ts
29529
+ import { existsSync as existsSync7 } from "fs";
29530
+ import { fileURLToPath } from "url";
29531
+ import { Worker } from "worker_threads";
29532
+ var ISOLATED_SCAN_BUDGET_MS = 2e3;
29533
+ var ISOLATED_PROBE_BUDGET_MS = 1e3;
29534
+ var ISOLATED_START_BUDGET_MS = 5e3;
29535
+ var ATTRIBUTION_MIN_RULE_MS = 500;
29536
+ var ATTRIBUTION_MIN_SHARE = 0.5;
29537
+ var resolvedWorkerUrl;
29538
+ function resolveWorkerUrl() {
29539
+ if (resolvedWorkerUrl !== void 0) return resolvedWorkerUrl ?? void 0;
29540
+ for (const name of ["scan-worker.js", "scan-worker.ts"]) {
29541
+ const candidate = new URL(name, import.meta.url);
29542
+ try {
29543
+ if (existsSync7(fileURLToPath(candidate))) {
29544
+ resolvedWorkerUrl = candidate;
29545
+ return candidate;
29546
+ }
29547
+ } catch {
29548
+ }
29549
+ }
29550
+ resolvedWorkerUrl = null;
29551
+ return void 0;
29552
+ }
29553
+ function messageOf(error51) {
29554
+ return error51 instanceof Error ? error51.message : String(error51);
29555
+ }
29556
+ function createIsolatedScanner(data, opts = {}) {
29557
+ const budgetMs = opts.budgetMs ?? ISOLATED_SCAN_BUDGET_MS;
29558
+ const probeBudgetMs = opts.probeBudgetMs ?? ISOLATED_PROBE_BUDGET_MS;
29559
+ const startBudgetMs = opts.startBudgetMs ?? ISOLATED_START_BUDGET_MS;
29560
+ const minAttributionMs = opts.minAttributionMs ?? ATTRIBUTION_MIN_RULE_MS;
29561
+ let worker;
29562
+ let readyWorker;
29563
+ let broken;
29564
+ let closed = false;
29565
+ let nextJobId = 1;
29566
+ let pending;
29567
+ const terminating = /* @__PURE__ */ new Set();
29568
+ let chain = Promise.resolve();
29569
+ function clearTimers(job) {
29570
+ if (job.startupTimer !== void 0) clearTimeout(job.startupTimer);
29571
+ if (job.timer !== void 0) clearTimeout(job.timer);
29572
+ }
29573
+ function take() {
29574
+ const job = pending;
29575
+ if (!job) return void 0;
29576
+ pending = void 0;
29577
+ clearTimers(job);
29578
+ worker?.unref();
29579
+ return job;
29580
+ }
29581
+ function failPending(outcome) {
29582
+ take()?.fail(outcome);
29583
+ }
29584
+ function kill(dead) {
29585
+ if (worker === dead) worker = void 0;
29586
+ if (readyWorker === dead) readyWorker = void 0;
29587
+ const done = dead.terminate().catch(() => void 0);
29588
+ terminating.add(done);
29589
+ void done.finally(() => terminating.delete(done));
29590
+ }
29591
+ function onDeadline(job) {
29592
+ if (pending !== job) return;
29593
+ const now = performance.now();
29594
+ const runningMs = now - job.progressAt;
29595
+ const elapsedMs = now - job.startedAt;
29596
+ const blamed = job.progressIndex >= 0 && runningMs >= minAttributionMs && runningMs >= elapsedMs * ATTRIBUTION_MIN_SHARE;
29597
+ const culpritIndex = blamed ? job.progressIndex : void 0;
29598
+ kill(job.worker);
29599
+ failPending({ status: "timeout", culpritIndex, elapsedMs });
29600
+ }
29601
+ function ensureWorker() {
29602
+ if (worker) return worker;
29603
+ const url2 = opts.workerUrl ?? resolveWorkerUrl();
29604
+ if (!url2) {
29605
+ return {
29606
+ error: "the scan worker script was not found next to this bundle"
29607
+ };
29608
+ }
29609
+ let started;
29610
+ try {
29611
+ started = new Worker(url2, { workerData: data });
29612
+ } catch (error51) {
29613
+ return { error: `could not start the scan worker: ${messageOf(error51)}` };
29614
+ }
29615
+ opts.onWorkerStart?.(started.threadId);
29616
+ started.on("message", (message) => {
29617
+ if (worker !== started) return;
29618
+ if (message.kind === "ready") {
29619
+ readyWorker = started;
29620
+ if (pending?.worker === started) beginDeadline(pending);
29621
+ return;
29622
+ }
29623
+ if (message.kind === "progress") {
29624
+ if (pending?.worker === started) {
29625
+ pending.progressIndex = message.index;
29626
+ pending.progressAt = performance.now();
29627
+ }
29628
+ return;
29629
+ }
29630
+ if (pending?.id !== message.id) return;
29631
+ if (message.kind === "failed") {
29632
+ failPending({
29633
+ status: "unavailable",
29634
+ reason: `the scan worker failed: ${message.message}`
29635
+ });
29636
+ return;
29637
+ }
29638
+ const job = take();
29639
+ if (job && !job.reply(message)) {
29640
+ job.fail({ status: "unavailable", reason: "the scan worker answered the wrong job" });
29641
+ }
29642
+ });
29643
+ started.on("error", (error51) => {
29644
+ if (worker !== started) return;
29645
+ broken = messageOf(error51);
29646
+ worker = void 0;
29647
+ if (readyWorker === started) readyWorker = void 0;
29648
+ failPending({ status: "unavailable", reason: `the scan worker crashed: ${broken}` });
29649
+ });
29650
+ started.on("exit", () => {
29651
+ if (worker !== started) return;
29652
+ broken ??= "the scan worker exited before answering";
29653
+ worker = void 0;
29654
+ if (readyWorker === started) readyWorker = void 0;
29655
+ failPending({ status: "unavailable", reason: "the scan worker exited before answering" });
29656
+ });
29657
+ started.unref();
29658
+ worker = started;
29659
+ return started;
29660
+ }
29661
+ function beginDeadline(job) {
29662
+ if (job.startupTimer !== void 0) {
29663
+ clearTimeout(job.startupTimer);
29664
+ job.startupTimer = void 0;
29665
+ }
29666
+ if (job.timer !== void 0) return;
29667
+ job.startedAt = performance.now();
29668
+ job.progressAt = job.startedAt;
29669
+ job.timer = setTimeout(() => {
29670
+ onDeadline(job);
29671
+ }, job.budgetMs);
29672
+ }
29673
+ function runOne(spec, fail) {
29674
+ if (closed) {
29675
+ fail({ status: "unavailable", reason: "the scan worker is closed" });
29676
+ return;
29677
+ }
29678
+ if (broken !== void 0) {
29679
+ fail({ status: "unavailable", reason: `the scan worker crashed: ${broken}` });
29680
+ return;
29681
+ }
29682
+ const started = ensureWorker();
29683
+ if (!(started instanceof Worker)) {
29684
+ broken = started.error;
29685
+ fail({ status: "unavailable", reason: started.error });
29686
+ return;
29687
+ }
29688
+ const id = nextJobId++;
29689
+ const now = performance.now();
29690
+ const job = {
29691
+ id,
29692
+ worker: started,
29693
+ budgetMs: spec.budgetMs,
29694
+ startedAt: now,
29695
+ progressIndex: -1,
29696
+ progressAt: now,
29697
+ startupTimer: void 0,
29698
+ timer: void 0,
29699
+ reply: spec.reply,
29700
+ fail
29701
+ };
29702
+ pending = job;
29703
+ started.ref();
29704
+ if (readyWorker === started) {
29705
+ beginDeadline(job);
29706
+ } else {
29707
+ job.startupTimer = setTimeout(() => {
29708
+ if (pending !== job) return;
29709
+ kill(job.worker);
29710
+ failPending({
29711
+ status: "unavailable",
29712
+ reason: `the scan worker did not start within ${String(startBudgetMs)}ms`
29713
+ });
29714
+ }, startBudgetMs);
29715
+ }
29716
+ try {
29717
+ started.postMessage(spec.build(id));
29718
+ } catch (error51) {
29719
+ failPending({
29720
+ // The thread went away between the ref and the post.
29721
+ status: "unavailable",
29722
+ reason: `could not reach the scan worker: ${messageOf(error51)}`
29723
+ });
29724
+ }
29725
+ }
29726
+ function enqueue(spec) {
29727
+ const next = chain.then(
29728
+ () => new Promise((resolve) => {
29729
+ spec(resolve);
29730
+ })
29731
+ );
29732
+ chain = next.then(
29733
+ () => void 0,
29734
+ () => void 0
29735
+ );
29736
+ return next;
29737
+ }
29738
+ return {
29739
+ scan(text, context, scanOpts) {
29740
+ return enqueue((resolve) => {
29741
+ runOne(
29742
+ {
29743
+ budgetMs,
29744
+ build: (id) => ({
29745
+ kind: "scan",
29746
+ id,
29747
+ text,
29748
+ filePath: context?.filePath,
29749
+ attribute: scanOpts?.attribute === true
29750
+ }),
29751
+ reply: (message) => {
29752
+ if (message.kind !== "result") return false;
29753
+ resolve({ status: "ok", findings: message.findings });
29754
+ return true;
29755
+ }
29756
+ },
29757
+ resolve
29758
+ );
29759
+ });
29760
+ },
29761
+ probe(rule) {
29762
+ return enqueue((resolve) => {
29763
+ runOne(
29764
+ {
29765
+ budgetMs: probeBudgetMs,
29766
+ build: (id) => ({ kind: "probe", id, rule }),
29767
+ reply: (message) => {
29768
+ if (message.kind !== "probed") return false;
29769
+ resolve({ status: "ok", safe: message.safe, worstMs: message.worstMs });
29770
+ return true;
29771
+ }
29772
+ },
29773
+ resolve
29774
+ );
29775
+ });
29776
+ },
29777
+ async close() {
29778
+ closed = true;
29779
+ const live = worker;
29780
+ worker = void 0;
29781
+ readyWorker = void 0;
29782
+ failPending({ status: "unavailable", reason: "the scan worker is closed" });
29783
+ if (live) kill(live);
29784
+ await Promise.all([...terminating]);
29785
+ }
29786
+ };
29787
+ }
28466
29788
 
28467
29789
  // ../../packages/plugin-sdk/src/rule-quarantine.ts
28468
29790
  var PASS_BUDGET_MS = 2e3;
29791
+ var UNQUARANTINE_HINT = "clear it with `aka detections unquarantine`";
28469
29792
  function ruleProbeKey(rule) {
28470
29793
  if (rule.matcher.type !== "regex") return void 0;
28471
29794
  return contentHashOf(`${rule.matcher.pattern} ${rule.matcher.flags}`);
28472
29795
  }
28473
- function warnQuarantined(rule, worstMs) {
28474
- const timing = worstMs === void 0 ? "not verified in time" : `${worstMs.toFixed(1)}ms`;
29796
+ function warn(rule, verb, detail, recoverable) {
29797
+ const hint = recoverable ? ` (${UNQUARANTINE_HINT})` : "";
29798
+ process.stderr.write(`[aka] ${verb} rule "${rule.id}": ${detail}${hint}
29799
+ `);
29800
+ }
29801
+ function warnQuarantined(rule, worstMs, cached2) {
29802
+ warn(
29803
+ rule,
29804
+ "quarantined",
29805
+ Number.isFinite(worstMs) ? `regex matcher exceeded the ReDoS timing budget (${worstMs.toFixed(1)}ms); excluded from this scan.` : "the timing battery failed while measuring its regex matcher; excluded from this scan.",
29806
+ cached2
29807
+ );
29808
+ }
29809
+ function warnUnmeasured(rule) {
29810
+ warn(
29811
+ rule,
29812
+ "skipped",
29813
+ "the timing pre-flight ran out of time before this rule could be measured; excluded for the rest of this run, and measured again next time.",
29814
+ false
29815
+ );
29816
+ }
29817
+ function warnUnmeasurable(reason, count) {
28475
29818
  process.stderr.write(
28476
- `[aka] quarantined rule "${rule.id}": regex matcher exceeded the ReDoS timing budget (${timing}); excluded from this scan.
29819
+ `[aka] ${String(count)} pulled/custom-pack rule(s) could not be time-checked: ${reason}. That is a problem with this install, not with the rules \u2014 until it is fixed they are excluded from every scan on this machine. Nothing was quarantined, so reinstalling AKA brings them straight back.
28477
29820
  `
28478
29821
  );
28479
29822
  }
29823
+ async function quarantineRule(gateway, rule, worstMs, detail) {
29824
+ const key = ruleProbeKey(rule);
29825
+ let cached2 = false;
29826
+ if (key !== void 0) {
29827
+ try {
29828
+ await gateway.setRuleProbeVerdict(key, "quarantined", worstMs);
29829
+ cached2 = true;
29830
+ } catch {
29831
+ }
29832
+ }
29833
+ warn(rule, "quarantined", detail, cached2);
29834
+ }
28480
29835
  async function filterUnsafeRules(rules, gateway, opts) {
28481
29836
  const passBudgetMs = opts?.passBudgetMs ?? PASS_BUDGET_MS;
29837
+ const prober = opts?.prober;
28482
29838
  const passStart = performance.now();
28483
29839
  const safe = [];
28484
- for (const rule of rules) {
28485
- const key = ruleProbeKey(rule);
28486
- if (key === void 0) {
28487
- safe.push(rule);
28488
- continue;
29840
+ const unmeasurable = /* @__PURE__ */ new Map();
29841
+ try {
29842
+ for (const rule of rules) {
29843
+ const key = ruleProbeKey(rule);
29844
+ if (key === void 0) {
29845
+ safe.push(rule);
29846
+ continue;
29847
+ }
29848
+ let cached2;
29849
+ try {
29850
+ cached2 = await gateway.getRuleProbeVerdict(key);
29851
+ } catch {
29852
+ cached2 = void 0;
29853
+ }
29854
+ if (cached2) {
29855
+ if (cached2.verdict === "safe") safe.push(rule);
29856
+ else warnQuarantined(rule, cached2.worstProbeMs, true);
29857
+ continue;
29858
+ }
29859
+ if (performance.now() - passStart >= passBudgetMs) {
29860
+ warnUnmeasured(rule);
29861
+ continue;
29862
+ }
29863
+ let isSafe;
29864
+ let worstMs;
29865
+ if (prober) {
29866
+ const outcome = await prober.probe(rule);
29867
+ if (outcome.status === "unavailable") {
29868
+ unmeasurable.set(outcome.reason, (unmeasurable.get(outcome.reason) ?? 0) + 1);
29869
+ continue;
29870
+ }
29871
+ isSafe = outcome.status === "ok" ? outcome.safe : false;
29872
+ worstMs = outcome.status === "ok" ? outcome.worstMs : outcome.elapsedMs;
29873
+ } else {
29874
+ try {
29875
+ ({ safe: isSafe, worstMs } = checkRuleTiming(rule));
29876
+ } catch {
29877
+ isSafe = false;
29878
+ worstMs = Number.POSITIVE_INFINITY;
29879
+ }
29880
+ }
29881
+ let persisted = false;
29882
+ try {
29883
+ await gateway.setRuleProbeVerdict(key, isSafe ? "safe" : "quarantined", worstMs);
29884
+ persisted = true;
29885
+ } catch {
29886
+ }
29887
+ if (isSafe) safe.push(rule);
29888
+ else warnQuarantined(rule, worstMs, persisted);
28489
29889
  }
28490
- let cached2;
29890
+ } finally {
29891
+ for (const [reason, count] of unmeasurable) warnUnmeasurable(reason, count);
29892
+ }
29893
+ return safe;
29894
+ }
29895
+
29896
+ // ../../packages/plugin-sdk/src/guarded-scan.ts
29897
+ var DEFAULT_DEGRADE_SCOPE = "the rest of this process";
29898
+ function warnDegraded(scope, dropped, detail) {
29899
+ process.stderr.write(
29900
+ `[aka] isolated scanning is off for ${scope}: ${detail}. ${String(dropped)} pulled/custom-pack rule(s) are excluded; the built-in packs still run.
29901
+ `
29902
+ );
29903
+ }
29904
+ function createGuardedScanner(partition, gateway, opts) {
29905
+ const degradeScope = opts?.degradeScope ?? DEFAULT_DEGRADE_SCOPE;
29906
+ const verified = partition.verified;
29907
+ let unverified = partition.unverified;
29908
+ let isolated = unverified.length > 0 ? createIsolatedScanner({ verified, unverified }, opts) : void 0;
29909
+ let retired = false;
29910
+ function inProcess(text, context) {
29911
+ return scan(text, verified, context);
29912
+ }
29913
+ async function retire() {
29914
+ const live = isolated;
29915
+ isolated = void 0;
29916
+ unverified = [];
29917
+ if (live) await live.close();
29918
+ }
29919
+ async function degrade() {
29920
+ retired = true;
29921
+ await retire();
29922
+ }
29923
+ async function attempt(active, text, context, attribute) {
28491
29924
  try {
28492
- cached2 = await gateway.getRuleProbeVerdict(key);
28493
- } catch {
28494
- cached2 = void 0;
28495
- }
28496
- if (cached2) {
28497
- if (cached2.verdict === "safe") safe.push(rule);
28498
- else warnQuarantined(rule, cached2.worstProbeMs);
28499
- continue;
28500
- }
28501
- if (performance.now() - passStart >= passBudgetMs) {
28502
- warnQuarantined(rule, void 0);
28503
- continue;
29925
+ return await active.scan(text, context, { attribute });
29926
+ } catch (error51) {
29927
+ return {
29928
+ status: "unavailable",
29929
+ reason: error51 instanceof Error ? error51.message : "the scan worker failed unexpectedly"
29930
+ };
28504
29931
  }
28505
- let isSafe;
28506
- let worstMs;
28507
- try {
28508
- ({ safe: isSafe, worstMs } = checkRuleTiming(rule));
28509
- } catch {
28510
- isSafe = false;
28511
- worstMs = Number.POSITIVE_INFINITY;
29932
+ }
29933
+ async function guardedScan(text, context) {
29934
+ const active = isolated;
29935
+ if (!active) return inProcess(text, context);
29936
+ let outcome = await attempt(active, text, context, false);
29937
+ if (outcome.status === "ok") return outcome.findings;
29938
+ if (outcome.status === "timeout") outcome = await attempt(active, text, context, true);
29939
+ const dropped = unverified.length;
29940
+ if (outcome.status === "ok") {
29941
+ warnDegraded(
29942
+ degradeScope,
29943
+ dropped,
29944
+ "a scan overran its bound once and no rule could be held responsible"
29945
+ );
29946
+ const findings = outcome.findings;
29947
+ await degrade();
29948
+ return findings;
29949
+ }
29950
+ if (outcome.status === "timeout") {
29951
+ const culprit = outcome.culpritIndex === void 0 ? void 0 : unverified[outcome.culpritIndex];
29952
+ if (culprit) {
29953
+ await quarantineRule(
29954
+ gateway,
29955
+ culprit,
29956
+ outcome.elapsedMs,
29957
+ `it did not finish within the ${outcome.elapsedMs.toFixed(0)}ms isolated-scan bound and was terminated; excluded from every later scan.`
29958
+ );
29959
+ }
29960
+ warnDegraded(
29961
+ degradeScope,
29962
+ dropped,
29963
+ culprit ? `rule "${culprit.id}" had to be terminated mid-scan` : `a scan was terminated at the ${outcome.elapsedMs.toFixed(0)}ms bound and no single rule could be held responsible, so nothing was quarantined and the next process will try these rules again`
29964
+ );
29965
+ } else {
29966
+ warnDegraded(degradeScope, dropped, outcome.reason);
28512
29967
  }
28513
- await gateway.setRuleProbeVerdict(key, isSafe ? "safe" : "quarantined", worstMs);
28514
- if (isSafe) safe.push(rule);
28515
- else warnQuarantined(rule, worstMs);
29968
+ await degrade();
29969
+ return inProcess(text, context);
28516
29970
  }
28517
- return safe;
29971
+ return {
29972
+ scan: guardedScan,
29973
+ degraded: () => retired,
29974
+ async close() {
29975
+ await retire();
29976
+ }
29977
+ };
28518
29978
  }
28519
29979
 
29980
+ // ../../packages/plugin-sdk/src/inventory-resolver.ts
29981
+ import { arch, hostname as hostname4, platform, release } from "os";
29982
+
29983
+ // ../../packages/plugin-sdk/src/nudge.ts
29984
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
29985
+ import { join as join11 } from "path";
29986
+
29987
+ // ../../packages/plugin-sdk/src/paths.ts
29988
+ import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
29989
+ import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
29990
+
29991
+ // ../../packages/plugin-sdk/src/project-files.ts
29992
+ var import_ignore = __toESM(require_ignore(), 1);
29993
+ import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
29994
+ import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
29995
+
29996
+ // ../../packages/plugin-sdk/src/provider-env-antigravity.ts
29997
+ var optionalBaseUrl2 = external_exports.preprocess((v) => {
29998
+ if (typeof v === "string" && v.trim() === "") return void 0;
29999
+ return v;
30000
+ }, external_exports.string().optional()).catch(void 0);
30001
+ var optionalFlag = external_exports.preprocess((v) => {
30002
+ if (typeof v !== "string") return false;
30003
+ const normalized = v.trim().toLowerCase();
30004
+ return normalized !== "" && normalized !== "0" && normalized !== "false";
30005
+ }, external_exports.boolean()).catch(false);
30006
+ var antigravityProviderEnvShape = {
30007
+ GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
30008
+ GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
30009
+ };
30010
+ var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
30011
+
30012
+ // ../../packages/plugin-sdk/src/provider-env-codex.ts
30013
+ var optionalBaseUrl3 = external_exports.preprocess((v) => {
30014
+ if (typeof v === "string" && v.trim() === "") return void 0;
30015
+ return v;
30016
+ }, external_exports.string().optional()).catch(void 0);
30017
+ var codexProviderEnvShape = {
30018
+ OPENAI_BASE_URL: optionalBaseUrl3
30019
+ };
30020
+ var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
30021
+
28520
30022
  // ../../packages/plugin-sdk/src/runtime.ts
28521
- import { randomUUID as randomUUID12 } from "crypto";
30023
+ import { randomUUID as randomUUID14 } from "crypto";
28522
30024
  var ENFORCEMENT_CEILING_ENABLED = false;
28523
30025
  var ACTION_PRIORITY = ["block", "redact", "warn", "log", "allow"];
28524
30026
  function entryIsActive(entry, now) {
@@ -28543,6 +30045,7 @@ function createPluginRuntime(gateway, settings, opts) {
28543
30045
  const dataDir2 = opts?.dataDir;
28544
30046
  let policies = [];
28545
30047
  let rules = [];
30048
+ let scanner;
28546
30049
  let bundleExceptions = [];
28547
30050
  let initialized = false;
28548
30051
  const ruleActionIndex = /* @__PURE__ */ new Map();
@@ -28568,8 +30071,24 @@ function createPluginRuntime(gateway, settings, opts) {
28568
30071
  return key !== void 0 && bundledProbeKeys.has(key);
28569
30072
  });
28570
30073
  const needsGate = incoming.filter((rule) => !ciVerified.includes(rule));
28571
- const safeBundleRules = [...ciVerified, ...await filterUnsafeRules(needsGate, gateway)];
28572
- rules = bundle.rulesComplete ? safeBundleRules : [...getLoadedRules(), ...safeBundleRules];
30074
+ let prober;
30075
+ const gated = await filterUnsafeRules(needsGate, gateway, {
30076
+ prober: {
30077
+ probe: (rule) => {
30078
+ prober ??= createIsolatedScanner({ verified: [], unverified: [] }, opts?.scanIsolation);
30079
+ return prober.probe(rule);
30080
+ }
30081
+ }
30082
+ });
30083
+ await prober?.close();
30084
+ const verified = bundle.rulesComplete ? [...ciVerified] : [...getLoadedRules(), ...ciVerified];
30085
+ const unverified = [];
30086
+ for (const rule of gated) {
30087
+ if (rule.matcher.type === "regex") unverified.push(rule);
30088
+ else verified.push(rule);
30089
+ }
30090
+ rules = [...verified, ...unverified];
30091
+ scanner = createGuardedScanner({ verified, unverified }, gateway, opts?.scanIsolation);
28573
30092
  bundleExceptions = bundle.exceptions ?? [];
28574
30093
  initialized = true;
28575
30094
  }
@@ -28709,7 +30228,7 @@ function createPluginRuntime(gateway, settings, opts) {
28709
30228
  const pair = `${finding.ruleId}:${fp}`;
28710
30229
  if (seen.has(pair)) continue;
28711
30230
  seen.add(pair);
28712
- const reference = randomUUID12().replaceAll("-", "").slice(0, 6);
30231
+ const reference = randomUUID14().replaceAll("-", "").slice(0, 6);
28713
30232
  const maskedValue = maskMatch(finding.rawMatch);
28714
30233
  try {
28715
30234
  await gateway.recordBlockedDetection({
@@ -28733,8 +30252,10 @@ function createPluginRuntime(gateway, settings, opts) {
28733
30252
  async function evaluate(text, context, ctx) {
28734
30253
  try {
28735
30254
  await ensureInitialized();
30255
+ if (!scanner) throw new Error("the runtime initialized without a scanner");
28736
30256
  const shielded = shieldPointers(text);
28737
- const findings = dropShieldedFindings(scan(shielded.text, rules, context), shielded.spans);
30257
+ const matched = await scanner.scan(shielded.text, context);
30258
+ const findings = dropShieldedFindings(matched, shielded.spans);
28738
30259
  const fpCache = /* @__PURE__ */ new Map();
28739
30260
  const { excepted, exceptionIds } = await applyExceptions(findings, ctx, fpCache);
28740
30261
  const decision = decide(findings, text, excepted);
@@ -28791,7 +30312,7 @@ function createPluginRuntime(gateway, settings, opts) {
28791
30312
  valueFingerprint: findingKeyFingerprintKey ? fingerprintOf(findingKeyFingerprintKey, match, findingKeyFpCache) : maskedMatch
28792
30313
  }) : void 0;
28793
30314
  return {
28794
- id: randomUUID12(),
30315
+ id: randomUUID14(),
28795
30316
  eventId: event.id,
28796
30317
  ruleId: match.ruleId,
28797
30318
  category: match.category,
@@ -28817,21 +30338,28 @@ function createPluginRuntime(gateway, settings, opts) {
28817
30338
  const sorted = [...rules].sort((a, b) => a.id.localeCompare(b.id));
28818
30339
  return contentHashOf(JSON.stringify(sorted));
28819
30340
  } catch {
28820
- return `unresolved-${randomUUID12()}`;
30341
+ return `unresolved-${randomUUID14()}`;
28821
30342
  }
28822
30343
  }
30344
+ function scanIsolationDegraded() {
30345
+ return scanner?.degraded() ?? false;
30346
+ }
28823
30347
  async function close() {
30348
+ try {
30349
+ await scanner?.close();
30350
+ } catch {
30351
+ }
28824
30352
  await gateway.close();
28825
30353
  }
28826
- return { processText, capture, rulesetFingerprint, close };
30354
+ return { processText, capture, rulesetFingerprint, scanIsolationDegraded, close };
28827
30355
  }
28828
30356
 
28829
30357
  // ../../packages/plugin-sdk/src/suppressions.ts
28830
30358
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
28831
30359
 
28832
30360
  // ../../packages/plugin-sdk/src/throttle.ts
28833
- import { mkdirSync as mkdirSync4, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
28834
- import { join as join12 } from "path";
30361
+ import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
30362
+ import { join as join13 } from "path";
28835
30363
 
28836
30364
  // ../../packages/plugin-sdk/src/tokenize.ts
28837
30365
  function redactedPlaceholder(category) {
@@ -29076,7 +30604,6 @@ function createVaultGlue(options) {
29076
30604
  const vault = new SecretVault({
29077
30605
  repo: db.secretVault,
29078
30606
  keys: createKeyProvider(settings.vaultKeyCustody, keysDir(base)),
29079
- fingerprintKey: loadOrCreateFingerprintKey(dir),
29080
30607
  // Read live so a revocation applies to the very next call, not the next
29081
30608
  // process.
29082
30609
  isConsented: () => isVaultConsentValid(readWorkspaceSettings(base).vaultConsent),
@@ -29097,8 +30624,10 @@ function createVaultGlue(options) {
29097
30624
  return decision.allow;
29098
30625
  }
29099
30626
  });
30627
+ let fingerprintKey;
30628
+ const fingerprintKeyForWrite = () => fingerprintKey ??= loadOrCreateFingerprintKey(dir);
29100
30629
  const vaultWithSightings = {
29101
- tokenize: (raw, meta3) => vault.tokenize(raw, meta3),
30630
+ tokenize: (raw, meta3) => vault.tokenize(raw, meta3, fingerprintKeyForWrite),
29102
30631
  detokenize: (token, opts) => vault.detokenize(token, opts),
29103
30632
  describePointer: (token) => vault.describePointer(token),
29104
30633
  resolvePointerIdentity: (token) => vault.resolvePointerIdentity(token),
@@ -29133,17 +30662,17 @@ var UNOPENABLE_VAULT = {
29133
30662
 
29134
30663
  // src/protocol/marker.ts
29135
30664
  import { randomBytes as randomBytes4 } from "crypto";
29136
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, renameSync as renameSync5, writeFileSync as writeFileSync6 } from "fs";
29137
- import { join as join13 } from "path";
30665
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync9, renameSync as renameSync5, writeFileSync as writeFileSync7 } from "fs";
30666
+ import { join as join14 } from "path";
29138
30667
  var MARKER_FILE = "protocol-marker";
29139
30668
  function mintMarker() {
29140
30669
  return randomBytes4(8).toString("hex");
29141
30670
  }
29142
30671
  function sessionProtocolMarker(dataDir2, sessionId) {
29143
30672
  if (!sessionId) return mintMarker();
29144
- const path = join13(dataDir2, MARKER_FILE);
30673
+ const path = join14(dataDir2, MARKER_FILE);
29145
30674
  try {
29146
- const stored = JSON.parse(readFileSync8(path, "utf8"));
30675
+ const stored = JSON.parse(readFileSync9(path, "utf8"));
29147
30676
  if (stored.sessionId === sessionId && typeof stored.marker === "string" && /^[0-9a-f]{16}$/.test(stored.marker)) {
29148
30677
  return stored.marker;
29149
30678
  }
@@ -29152,8 +30681,8 @@ function sessionProtocolMarker(dataDir2, sessionId) {
29152
30681
  const marker = mintMarker();
29153
30682
  try {
29154
30683
  mkdirSync5(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
29155
- const tmp = join13(dataDir2, `${MARKER_FILE}.tmp`);
29156
- writeFileSync6(tmp, JSON.stringify({ sessionId, marker }), { mode: DATA_FILE_MODE });
30684
+ const tmp = join14(dataDir2, `${MARKER_FILE}.tmp`);
30685
+ writeFileSync7(tmp, JSON.stringify({ sessionId, marker }), { mode: DATA_FILE_MODE });
29157
30686
  renameSync5(tmp, path);
29158
30687
  } catch {
29159
30688
  }
@@ -29322,6 +30851,7 @@ function exceptionPointer(references) {
29322
30851
 
29323
30852
  // src/hooks/pre-tool-use-decision.ts
29324
30853
  var EXECUTABLE_REDACT_NOTE = "Masking inside an executable command would silently change what runs, so a redact policy blocks it instead.";
30854
+ var UNREDACTABLE_NOTE = "The redacted form of this input was unavailable, so the call is blocked rather than sent unmasked.";
29325
30855
  function pointerCategory(token) {
29326
30856
  const match = /^\[\[aka:([a-z_]+):/.exec(token);
29327
30857
  return match?.[1] ?? "secret";
@@ -29333,6 +30863,7 @@ async function decidePreToolUse(toolName, toolInput, scanned, tokenizeField) {
29333
30863
  const blockedReferences = [];
29334
30864
  const redactedReferences = [];
29335
30865
  let escalated = false;
30866
+ let escalatedUnredactable = false;
29336
30867
  let updatedInput = null;
29337
30868
  const realized = { pointers: [], degraded: [] };
29338
30869
  for (const { spec, text, result } of scanned) {
@@ -29343,8 +30874,6 @@ async function decidePreToolUse(toolName, toolInput, scanned, tokenizeField) {
29343
30874
  for (const finding of result.findings) blockedRules.add(finding.ruleId);
29344
30875
  if (result.blockedReferences) blockedReferences.push(...result.blockedReferences);
29345
30876
  } else if (action === "redact") {
29346
- for (const finding of result.findings) redactedRules.add(finding.ruleId);
29347
- if (result.blockedReferences) redactedReferences.push(...result.blockedReferences);
29348
30877
  let rewritten = result.text;
29349
30878
  const enforced = result.enforcedFindings ?? [];
29350
30879
  if (tokenizeField && enforced.length > 0) {
@@ -29358,7 +30887,13 @@ async function decidePreToolUse(toolName, toolInput, scanned, tokenizeField) {
29358
30887
  } catch {
29359
30888
  }
29360
30889
  }
29361
- if (rewritten !== null) {
30890
+ if (rewritten === null) {
30891
+ escalatedUnredactable = true;
30892
+ for (const finding of result.findings) blockedRules.add(finding.ruleId);
30893
+ if (result.blockedReferences) blockedReferences.push(...result.blockedReferences);
30894
+ } else {
30895
+ for (const finding of result.findings) redactedRules.add(finding.ruleId);
30896
+ if (result.blockedReferences) redactedReferences.push(...result.blockedReferences);
29362
30897
  updatedInput = replaceAtPath(updatedInput ?? toolInput, spec.path, rewritten);
29363
30898
  }
29364
30899
  } else if (action === "warn") {
@@ -29375,7 +30910,7 @@ async function decidePreToolUse(toolName, toolInput, scanned, tokenizeField) {
29375
30910
  subject: `${toolName} call`,
29376
30911
  ruleIds: [...blockedRules].join(", "),
29377
30912
  blockedRef: blockedReferences[0],
29378
- note: escalated ? EXECUTABLE_REDACT_NOTE : void 0
30913
+ note: escalated ? EXECUTABLE_REDACT_NOTE : escalatedUnredactable ? UNREDACTABLE_NOTE : void 0
29379
30914
  })
29380
30915
  }
29381
30916
  },
@@ -29541,11 +31076,11 @@ function baseMetadata(input) {
29541
31076
  }
29542
31077
 
29543
31078
  // src/hooks/store-health.ts
29544
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
29545
- import { join as join14 } from "path";
31079
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
31080
+ import { join as join15 } from "path";
29546
31081
 
29547
31082
  // ../../packages/plugin-runtime/src/standalone-gateway.ts
29548
- import { randomUUID as randomUUID13 } from "crypto";
31083
+ import { randomUUID as randomUUID15 } from "crypto";
29549
31084
 
29550
31085
  // ../../packages/plugin-runtime/src/recorder.ts
29551
31086
  var PLUGIN_RECORDER_BINARY = "plugin";
@@ -29707,7 +31242,7 @@ var StandaloneDataGateway = class {
29707
31242
  const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
29708
31243
  const installed = this.installedScanRules();
29709
31244
  const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
29710
- id: randomUUID13(),
31245
+ id: randomUUID15(),
29711
31246
  scope: "global",
29712
31247
  target: { ruleId },
29713
31248
  action,
@@ -29860,7 +31395,7 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
29860
31395
  }
29861
31396
 
29862
31397
  // ../../packages/plugin-runtime/src/handle-session-start.ts
29863
- import { randomUUID as randomUUID14 } from "crypto";
31398
+ import { randomUUID as randomUUID16 } from "crypto";
29864
31399
  var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
29865
31400
 
29866
31401
  // src/hooks/store-health.ts
@@ -29877,14 +31412,14 @@ function storeUnavailableMessage(dbPath2) {
29877
31412
  }
29878
31413
  function claimStoreUnavailableWarning(dataDir2, sessionId) {
29879
31414
  if (!sessionId) return true;
29880
- const path = join14(dataDir2, STORE_WARNING_MARKER);
31415
+ const path = join15(dataDir2, STORE_WARNING_MARKER);
29881
31416
  try {
29882
- if (readFileSync9(path, "utf8") === sessionId) return false;
31417
+ if (readFileSync10(path, "utf8") === sessionId) return false;
29883
31418
  } catch {
29884
31419
  }
29885
31420
  try {
29886
31421
  mkdirSync6(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
29887
- writeFileSync7(path, sessionId, { mode: DATA_FILE_MODE });
31422
+ writeFileSync8(path, sessionId, { mode: DATA_FILE_MODE });
29888
31423
  } catch {
29889
31424
  }
29890
31425
  return true;