@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
  function harnessFromTool(tool) {
15489
15624
  return TOOL_TO_HARNESS[tool] ?? tool;
@@ -15944,7 +16079,18 @@ var ActivityOverviewResponse = external_exports.object({
15944
16079
  // ../../packages/schema/src/zod/event.ts
15945
16080
  var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
15946
16081
  var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
15947
- var SourceTool = external_exports.enum(["claude-code", "claude-desktop", "cursor", "chatgpt", "github-copilot", "cli", "unknown"]).meta({ id: "SourceTool" });
16082
+ var SourceTool = external_exports.enum([
16083
+ "claude-code",
16084
+ "claude-desktop",
16085
+ "cursor",
16086
+ "chatgpt",
16087
+ "claude-ai",
16088
+ "github-copilot",
16089
+ "codex",
16090
+ "antigravity",
16091
+ "cli",
16092
+ "unknown"
16093
+ ]).meta({ id: "SourceTool" });
15948
16094
  var EventMetadata = external_exports.object({
15949
16095
  sessionId: external_exports.string().optional(),
15950
16096
  repo: external_exports.string().optional(),
@@ -16015,7 +16161,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
16015
16161
  var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
16016
16162
  var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
16017
16163
  var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
16018
- var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
16164
+ var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
16019
16165
  var AccessCounts = external_exports.object({
16020
16166
  open: external_exports.number().int().nonnegative(),
16021
16167
  approved: external_exports.number().int().nonnegative(),
@@ -16284,6 +16430,7 @@ var ExceptionBundleEntry = DetectionException.pick({
16284
16430
  useCount: true,
16285
16431
  conditions: true
16286
16432
  });
16433
+ var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
16287
16434
 
16288
16435
  // ../../packages/schema/src/zod/rule.ts
16289
16436
  var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
@@ -17094,6 +17241,35 @@ var EgressWriteSummary = external_exports.object({
17094
17241
  droppedFiles: external_exports.array(external_exports.string()).default([])
17095
17242
  }).meta({ id: "EgressWriteSummary" });
17096
17243
 
17244
+ // ../../packages/schema/src/zod/exception-action.ts
17245
+ var confirmation = external_exports.string().optional();
17246
+ var ApproveBlockedInput = external_exports.object({
17247
+ reference: external_exports.string(),
17248
+ scope: external_exports.string(),
17249
+ reason: external_exports.string(),
17250
+ confirmation
17251
+ });
17252
+ var AddExceptionInput = external_exports.object({
17253
+ ruleId: external_exports.string(),
17254
+ value: external_exports.string(),
17255
+ scope: external_exports.string(),
17256
+ reason: external_exports.string(),
17257
+ confirmation
17258
+ });
17259
+ var GrantRevealInput = external_exports.object({
17260
+ pointer: external_exports.string(),
17261
+ scope: external_exports.string(),
17262
+ justification: external_exports.string(),
17263
+ confirmation
17264
+ });
17265
+ var RevokeExceptionInput = external_exports.object({
17266
+ id: external_exports.string(),
17267
+ reason: external_exports.string()
17268
+ });
17269
+ var RotateKeyInput = external_exports.object({
17270
+ confirmation: external_exports.string()
17271
+ });
17272
+
17097
17273
  // ../../packages/schema/src/zod/findings-group-build.ts
17098
17274
  function toApiAction(dbVal) {
17099
17275
  const map2 = {
@@ -17149,6 +17325,8 @@ function buildFindingGroups(rows, opts = {}) {
17149
17325
  repo: r.repo,
17150
17326
  file: r.file,
17151
17327
  ...r.toolName === void 0 ? {} : { toolName: r.toolName },
17328
+ ...r.eventId === void 0 ? {} : { eventId: r.eventId },
17329
+ ...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
17152
17330
  action: toApiAction(effectiveDbAction),
17153
17331
  detectedAt: r.occurredAt,
17154
17332
  confidence: r.confidence,
@@ -17280,14 +17458,17 @@ function applyFindingFilters(groups, opts) {
17280
17458
  }
17281
17459
  var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
17282
17460
  var SEVERITY_RANK = SEVERITY_ORDER;
17461
+ function compareFindingGroupOrder(a, b) {
17462
+ const rankA = SEVERITY_RANK[a.severity] ?? -1;
17463
+ const rankB = SEVERITY_RANK[b.severity] ?? -1;
17464
+ const severityDiff = rankA - rankB;
17465
+ if (severityDiff !== 0) return severityDiff;
17466
+ const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
17467
+ if (recencyDiff !== 0) return recencyDiff;
17468
+ return a.id.localeCompare(b.id);
17469
+ }
17283
17470
  function sortFindingGroups(groups) {
17284
- return [...groups].sort((a, b) => {
17285
- const rankA = SEVERITY_RANK[a.severity] ?? -1;
17286
- const rankB = SEVERITY_RANK[b.severity] ?? -1;
17287
- const severityDiff = rankA - rankB;
17288
- if (severityDiff !== 0) return severityDiff;
17289
- return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
17290
- });
17471
+ return [...groups].sort(compareFindingGroupOrder);
17291
17472
  }
17292
17473
  function computeFindingFacets(allGroups, opts) {
17293
17474
  const forSeverity = applyFindingFilters(allGroups, {
@@ -17343,15 +17524,158 @@ function computeFindingFacets(allGroups, opts) {
17343
17524
  for (const g of forStatus) {
17344
17525
  if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
17345
17526
  }
17346
- const toItems = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
17527
+ const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
17528
+ return {
17529
+ severity: toItems2(severityMap),
17530
+ provider: toItems2(providerMap),
17531
+ action: toItems2(actionMap),
17532
+ subtype: toItems2(subtypeMap),
17533
+ status: toItems2(statusMap)
17534
+ };
17535
+ }
17536
+
17537
+ // ../../packages/schema/src/zod/findings-flat-build.ts
17538
+ function rowHaystack(row) {
17539
+ return [
17540
+ row.ruleId,
17541
+ row.category,
17542
+ row.maskedMatch,
17543
+ row.repo,
17544
+ row.file,
17545
+ row.toolName ? `via ${row.toolName}` : "",
17546
+ row.id
17547
+ ].join(" ").toLowerCase();
17548
+ }
17549
+ function matchesDimension(row, opts, dimension) {
17550
+ switch (dimension) {
17551
+ case "severity":
17552
+ return !opts.severity?.length || opts.severity.includes(row.severity);
17553
+ case "subtype":
17554
+ return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
17555
+ case "providers":
17556
+ return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
17557
+ case "actions":
17558
+ return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
17559
+ case "statuses":
17560
+ return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
17561
+ case "tools":
17562
+ return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
17563
+ case "repo":
17564
+ return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
17565
+ case "file":
17566
+ return opts.file === void 0 || opts.file === "" || row.file === opts.file;
17567
+ case "q":
17568
+ return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
17569
+ }
17570
+ }
17571
+ var DIMENSIONS = [
17572
+ "severity",
17573
+ "subtype",
17574
+ "providers",
17575
+ "actions",
17576
+ "statuses",
17577
+ "tools",
17578
+ "repo",
17579
+ "file",
17580
+ "q"
17581
+ ];
17582
+ function matchesInstanceFilters(row, opts, except) {
17583
+ for (const dimension of DIMENSIONS) {
17584
+ if (dimension === except) continue;
17585
+ if (!matchesDimension(row, opts, dimension)) return false;
17586
+ }
17587
+ return true;
17588
+ }
17589
+ function toItems(counts) {
17590
+ return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
17591
+ }
17592
+ function bump(counts, value) {
17593
+ counts.set(value, (counts.get(value) ?? 0) + 1);
17594
+ }
17595
+ function createInstanceFacetAccumulator(opts) {
17596
+ const severity = /* @__PURE__ */ new Map();
17597
+ const subtype = /* @__PURE__ */ new Map();
17598
+ const provider = /* @__PURE__ */ new Map();
17599
+ const action = /* @__PURE__ */ new Map();
17600
+ const status = /* @__PURE__ */ new Map();
17601
+ const tool = /* @__PURE__ */ new Map();
17347
17602
  return {
17348
- severity: toItems(severityMap),
17349
- provider: toItems(providerMap),
17350
- action: toItems(actionMap),
17351
- subtype: toItems(subtypeMap),
17352
- status: toItems(statusMap)
17603
+ add(row) {
17604
+ if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
17605
+ if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
17606
+ if (matchesInstanceFilters(row, opts, "providers")) {
17607
+ bump(provider, toApiProvider(row.sourceTool));
17608
+ }
17609
+ if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
17610
+ if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
17611
+ bump(status, row.status);
17612
+ }
17613
+ if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
17614
+ bump(tool, row.toolName);
17615
+ }
17616
+ },
17617
+ facets: () => ({
17618
+ severity: toItems(severity),
17619
+ subtype: toItems(subtype),
17620
+ provider: toItems(provider),
17621
+ action: toItems(action),
17622
+ status: toItems(status),
17623
+ tool: toItems(tool)
17624
+ })
17625
+ };
17626
+ }
17627
+ function toInstanceDetail(row) {
17628
+ const category = toApiCategory(row.category);
17629
+ return {
17630
+ id: row.id,
17631
+ provider: toApiProvider(row.sourceTool),
17632
+ repo: row.repo,
17633
+ file: row.file,
17634
+ ...row.toolName === void 0 ? {} : { toolName: row.toolName },
17635
+ eventId: row.eventId,
17636
+ ...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
17637
+ action: toApiAction(row.actionTaken),
17638
+ detectedAt: row.occurredAt,
17639
+ confidence: row.confidence,
17640
+ ...row.status === void 0 ? {} : { status: row.status },
17641
+ groupId: row.ruleId,
17642
+ category,
17643
+ subtype: row.ruleId,
17644
+ severity: row.severity,
17645
+ match: { maskedValue: row.maskedMatch, contextPrefix: "" },
17646
+ detection: { id: row.ruleId, name: null },
17647
+ policy: { id: `category:${category}`, name: category }
17648
+ };
17649
+ }
17650
+ var SEVERITY_ORDER2 = {
17651
+ critical: 0,
17652
+ high: 1,
17653
+ medium: 2,
17654
+ low: 3
17655
+ };
17656
+ function newLocationAccumulator() {
17657
+ return {
17658
+ instanceCount: 0,
17659
+ // Sorts after every known severity, so the first row always wins the
17660
+ // comparison below rather than an unknown value pinning the location.
17661
+ maxSeverityRank: Number.MAX_SAFE_INTEGER,
17662
+ maxSeverity: "low",
17663
+ latestDetectedAt: "",
17664
+ statuses: [],
17665
+ ruleIds: /* @__PURE__ */ new Set()
17353
17666
  };
17354
17667
  }
17668
+ function addToLocation(acc, row) {
17669
+ acc.instanceCount += 1;
17670
+ const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
17671
+ if (rank < acc.maxSeverityRank) {
17672
+ acc.maxSeverityRank = rank;
17673
+ acc.maxSeverity = row.severity;
17674
+ }
17675
+ if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
17676
+ acc.statuses.push(row.status);
17677
+ acc.ruleIds.add(row.ruleId);
17678
+ }
17355
17679
 
17356
17680
  // ../../packages/schema/src/zod/installed-pack.ts
17357
17681
  var InstalledPack = external_exports.object({
@@ -17492,6 +17816,50 @@ var VaultInventoryEntry = external_exports.object({
17492
17816
  revealGrantId: external_exports.string().nullable(),
17493
17817
  sightings: external_exports.array(VaultSighting)
17494
17818
  });
17819
+ var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
17820
+ var DEFAULT_VAULT_DEREFS_LIMIT = 50;
17821
+ var MAX_VAULT_PAGE_LIMIT = 200;
17822
+ var ListVaultInventoryQuery = external_exports.object({
17823
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
17824
+ // Opaque; names the last row of the page just served.
17825
+ cursor: external_exports.string().optional()
17826
+ });
17827
+ var ListVaultInventoryResponse = external_exports.object({
17828
+ // Vaulted values across the whole store, not just this page — cursor-
17829
+ // independent, so paging never changes what the count claims.
17830
+ totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
17831
+ items: external_exports.array(VaultInventoryEntry),
17832
+ // `null` once the last page is reached.
17833
+ nextCursor: external_exports.string().nullable()
17834
+ });
17835
+ var ListVaultReuseQuery = external_exports.object({
17836
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
17837
+ cursor: external_exports.string().optional()
17838
+ });
17839
+ var ListVaultReuseResponse = external_exports.object({
17840
+ // Reused values across the whole store — the number the section's claim
17841
+ // ("values detected in more than one place") is about.
17842
+ totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
17843
+ items: external_exports.array(VaultInventoryEntry),
17844
+ nextCursor: external_exports.string().nullable()
17845
+ });
17846
+ var ListVaultDerefsQuery = external_exports.object({
17847
+ // Include the batched, high-volume reasons (display, view-render). Omitted
17848
+ // hides them and counts them into `hiddenBatched` instead, so the model
17849
+ // crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
17850
+ // over a Server Action, which preserves the type, never as a URL param.
17851
+ includeBatched: external_exports.boolean().optional(),
17852
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
17853
+ cursor: external_exports.string().optional()
17854
+ });
17855
+ var ListVaultDerefsResponse = external_exports.object({
17856
+ items: external_exports.array(VaultDeref),
17857
+ nextCursor: external_exports.string().nullable(),
17858
+ // Display/view-render rows the query hid, over the WHOLE trail rather than
17859
+ // this page — it is the count the "N hidden" line and its toggle speak for.
17860
+ // Always 0 when `includeBatched` was set, since nothing was hidden.
17861
+ hiddenBatched: external_exports.number().int().nonnegative()
17862
+ });
17495
17863
  var VaultKeyCustody = external_exports.string();
17496
17864
  var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
17497
17865
  var VAULT_CONSENT_VERSION = 1;
@@ -17868,7 +18236,7 @@ var TopSourcesQuery = external_exports.object({
17868
18236
  // Omit for both kinds.
17869
18237
  kind: external_exports.enum(SOURCE_KINDS).optional()
17870
18238
  });
17871
- var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
18239
+ var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
17872
18240
  var ScanCoverageProvider = external_exports.object({
17873
18241
  provider: Provider,
17874
18242
  // Percent of that provider's traffic scanned in the window. 0 when unsupported.
@@ -18121,6 +18489,195 @@ function captureId(sessionId, contentHash, filePath = null) {
18121
18489
  );
18122
18490
  }
18123
18491
 
18492
+ // ../../packages/persistence/src/internal/snapshot.ts
18493
+ import { randomUUID } from "crypto";
18494
+ import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
18495
+ import { basename, dirname, join } from "path";
18496
+
18497
+ // ../../packages/persistence/src/paths.ts
18498
+ import {
18499
+ chmodSync,
18500
+ linkSync,
18501
+ lstatSync,
18502
+ mkdirSync,
18503
+ renameSync,
18504
+ rmSync,
18505
+ writeFileSync
18506
+ } from "fs";
18507
+ import { threadId } from "worker_threads";
18508
+ var DATA_DIR_MODE = 448;
18509
+ var DATA_FILE_MODE = 384;
18510
+ var DB_FILENAME = "aka.db";
18511
+ function isSymlink(path) {
18512
+ try {
18513
+ return lstatSync(path).isSymbolicLink();
18514
+ } catch {
18515
+ return false;
18516
+ }
18517
+ }
18518
+ function chmodBestEffort(path, mode) {
18519
+ if (isSymlink(path)) return;
18520
+ try {
18521
+ chmodSync(path, mode);
18522
+ } catch {
18523
+ }
18524
+ }
18525
+ function tightenDir(dir) {
18526
+ chmodBestEffort(dir, DATA_DIR_MODE);
18527
+ }
18528
+ function ensureDataDirSync(dir) {
18529
+ mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
18530
+ tightenDir(dir);
18531
+ }
18532
+ function dbSidecars(file2) {
18533
+ return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
18534
+ }
18535
+ function tightenFile(file2) {
18536
+ chmodBestEffort(file2, DATA_FILE_MODE);
18537
+ }
18538
+ function tightenPerms(file2) {
18539
+ for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
18540
+ }
18541
+ function classifyOccupant(file2) {
18542
+ try {
18543
+ if (lstatSync(file2).isSymbolicLink()) return { kind: "symlink" };
18544
+ return { kind: "gone" };
18545
+ } catch (err) {
18546
+ if (err.code === "ENOENT") return { kind: "gone" };
18547
+ return { kind: "unknown", cause: err };
18548
+ }
18549
+ }
18550
+ var KeyUnclaimableError = class extends Error {
18551
+ code = "key-unclaimable";
18552
+ // `cause` is installed only when there IS one. Passing { cause: undefined }
18553
+ // defines the property anyway, so an error carrying nothing would still answer
18554
+ // `'cause' in err` — a present-but-empty field reads as a diagnosis that was
18555
+ // captured and then lost, which is worse than its plain absence.
18556
+ constructor(message, cause) {
18557
+ super(message, cause === void 0 ? void 0 : { cause });
18558
+ this.name = "KeyUnclaimableError";
18559
+ }
18560
+ };
18561
+ function createOwnerOnlyFileSync(file2, data) {
18562
+ const tmp = `${file2}.${String(process.pid)}.${String(threadId)}.new`;
18563
+ try {
18564
+ rmSync(tmp, { force: true });
18565
+ } catch {
18566
+ }
18567
+ let created;
18568
+ try {
18569
+ writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
18570
+ created = publishByLink(tmp, file2, data);
18571
+ } finally {
18572
+ try {
18573
+ rmSync(tmp, { force: true });
18574
+ } catch {
18575
+ }
18576
+ }
18577
+ if (created) tightenFile(file2);
18578
+ return created;
18579
+ }
18580
+ var LINK_UNSUPPORTED = /* @__PURE__ */ new Set(["EPERM", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EINVAL"]);
18581
+ function publishByLink(tmp, file2, data) {
18582
+ try {
18583
+ linkSync(tmp, file2);
18584
+ return true;
18585
+ } catch (err) {
18586
+ const code = err.code;
18587
+ if (code === "EEXIST") return false;
18588
+ if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
18589
+ }
18590
+ try {
18591
+ writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
18592
+ return true;
18593
+ } catch (err) {
18594
+ if (err.code === "EEXIST") return false;
18595
+ throw err;
18596
+ }
18597
+ }
18598
+
18599
+ // ../../packages/persistence/src/internal/snapshot.ts
18600
+ function backupPath(file2, tag) {
18601
+ return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
18602
+ }
18603
+ var STALE_PARTIAL_MS = 5 * 6e4;
18604
+ function reapStalePartials(file2) {
18605
+ const dir = dirname(file2);
18606
+ const prefix = `${basename(file2)}.`;
18607
+ let entries;
18608
+ try {
18609
+ entries = readdirSync(dir);
18610
+ } catch {
18611
+ return;
18612
+ }
18613
+ for (const name of entries) {
18614
+ if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
18615
+ const partial2 = join(dir, name);
18616
+ try {
18617
+ if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
18618
+ rmSync2(partial2, { force: true });
18619
+ }
18620
+ } catch {
18621
+ }
18622
+ }
18623
+ }
18624
+ function snapshotStore(db, backup) {
18625
+ const partial2 = `${backup}.partial`;
18626
+ try {
18627
+ rmSync2(partial2, { force: true });
18628
+ db.prepare("VACUUM INTO ?").run(partial2);
18629
+ tightenFile(partial2);
18630
+ renameSync2(partial2, backup);
18631
+ } catch (error51) {
18632
+ try {
18633
+ rmSync2(partial2, { force: true });
18634
+ } catch {
18635
+ }
18636
+ throw error51;
18637
+ }
18638
+ }
18639
+ function moveStoreAside(file2, backup) {
18640
+ const undo = [];
18641
+ renameSync2(file2, backup);
18642
+ undo.push([backup, file2]);
18643
+ try {
18644
+ for (const sidecar of dbSidecars(file2)) {
18645
+ const moved = `${backup}${sidecar.slice(file2.length)}`;
18646
+ try {
18647
+ renameSync2(sidecar, moved);
18648
+ undo.push([moved, sidecar]);
18649
+ } catch {
18650
+ rmSync2(sidecar, { force: true });
18651
+ }
18652
+ }
18653
+ } catch (error51) {
18654
+ for (const [from, to] of undo.reverse()) {
18655
+ try {
18656
+ renameSync2(from, to);
18657
+ } catch {
18658
+ }
18659
+ }
18660
+ throw error51;
18661
+ }
18662
+ tightenPerms(backup);
18663
+ }
18664
+ function discardStore(file2, backup) {
18665
+ try {
18666
+ rmSync2(file2, { force: true });
18667
+ for (const sidecar of dbSidecars(file2)) {
18668
+ rmSync2(sidecar, { force: true });
18669
+ }
18670
+ } catch (error51) {
18671
+ if (existsSync(file2)) {
18672
+ try {
18673
+ rmSync2(backup, { force: true });
18674
+ } catch {
18675
+ }
18676
+ }
18677
+ throw error51;
18678
+ }
18679
+ }
18680
+
18124
18681
  // ../../packages/persistence/src/internal/sql-text.ts
18125
18682
  function escapeLikePattern(s) {
18126
18683
  return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
@@ -18262,55 +18819,6 @@ function mapRowsTolerant(rows, map2) {
18262
18819
  return out;
18263
18820
  }
18264
18821
 
18265
- // ../../packages/persistence/src/paths.ts
18266
- import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
18267
- var DATA_DIR_MODE = 448;
18268
- var DATA_FILE_MODE = 384;
18269
- var DB_FILENAME = "aka.db";
18270
- function chmodBestEffort(path, mode) {
18271
- try {
18272
- chmodSync(path, mode);
18273
- } catch {
18274
- }
18275
- }
18276
- function tightenDir(dir) {
18277
- chmodBestEffort(dir, DATA_DIR_MODE);
18278
- }
18279
- function ensureDataDirSync(dir) {
18280
- mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
18281
- tightenDir(dir);
18282
- }
18283
- function dbSidecars(file2) {
18284
- return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
18285
- }
18286
- function tightenFile(file2) {
18287
- try {
18288
- if (lstatSync(file2).isSymbolicLink()) return;
18289
- } catch {
18290
- }
18291
- chmodBestEffort(file2, DATA_FILE_MODE);
18292
- }
18293
- function tightenPerms(file2) {
18294
- for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
18295
- }
18296
- function writeOwnerOnlyFileSync(file2, data) {
18297
- const tmp = `${file2}.${String(process.pid)}.tmp`;
18298
- try {
18299
- rmSync(tmp, { force: true });
18300
- } catch {
18301
- }
18302
- try {
18303
- writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
18304
- renameSync(tmp, file2);
18305
- } finally {
18306
- try {
18307
- rmSync(tmp, { force: true });
18308
- } catch {
18309
- }
18310
- }
18311
- tightenFile(file2);
18312
- }
18313
-
18314
18822
  // ../../packages/persistence/src/migrations.ts
18315
18823
  function describeObject(object2) {
18316
18824
  return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
@@ -18426,9 +18934,9 @@ function applyLegacyDropMigration(db, file2) {
18426
18934
  }
18427
18935
  }
18428
18936
  function backupBeforeLegacyDrop(db, file2) {
18429
- const backup = `${file2}.pre-drop.${String(Date.now())}.bak`;
18430
- db.prepare("VACUUM INTO ?").run(backup);
18431
- tightenFile(backup);
18937
+ reapStalePartials(file2);
18938
+ const backup = backupPath(file2, "pre-drop");
18939
+ snapshotStore(db, backup);
18432
18940
  return backup;
18433
18941
  }
18434
18942
  var TOKEN_USAGE_COLUMNS = [
@@ -18772,6 +19280,25 @@ function parseJsonObject(s) {
18772
19280
  return void 0;
18773
19281
  }
18774
19282
 
19283
+ // ../../packages/persistence/src/internal/keyset-cursor.ts
19284
+ function encodeKeysetCursor(payload) {
19285
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
19286
+ }
19287
+ function decodeKeysetCursor(cursor) {
19288
+ const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
19289
+ if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
19290
+ // resumes from is epoch millis, and a payload carrying ±Infinity or a
19291
+ // fraction binds cleanly rather than failing — returning an EMPTY page with
19292
+ // a null cursor, which a caller reads as "end of list". That is the one
19293
+ // outcome a cursor that does not decode must never produce, since the
19294
+ // documented behaviour above is to restart from the top. (`1e999` is valid
19295
+ // JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
19296
+ Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
19297
+ return parsed;
19298
+ }
19299
+ return null;
19300
+ }
19301
+
18775
19302
  // ../../packages/persistence/src/repositories/activity.ts
18776
19303
  var DAY_MS = 864e5;
18777
19304
  var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
@@ -18817,16 +19344,6 @@ function utcWindow(nowMs) {
18817
19344
  const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
18818
19345
  return { startMs, endMs: startMs + DAY_MS };
18819
19346
  }
18820
- function encodeCursor(payload) {
18821
- return Buffer.from(JSON.stringify(payload)).toString("base64url");
18822
- }
18823
- function decodeCursor(cursor) {
18824
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
18825
- if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
18826
- return parsed;
18827
- }
18828
- return null;
18829
- }
18830
19347
  var DB_EVENT_TYPE_TO_KIND = {
18831
19348
  session: "session",
18832
19349
  prompt: "prompt",
@@ -18971,7 +19488,7 @@ var SqliteActivityRepository = class {
18971
19488
  return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
18972
19489
  }
18973
19490
  listSessions(query) {
18974
- const cursor = query.cursor ? decodeCursor(query.cursor) : null;
19491
+ const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
18975
19492
  const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
18976
19493
  const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
18977
19494
  const conditions = [SESSION_ROOT];
@@ -19045,7 +19562,7 @@ var SqliteActivityRepository = class {
19045
19562
  )
19046
19563
  );
19047
19564
  const last = page[page.length - 1];
19048
- const nextCursor = hasMore && last ? encodeCursor({ startedAtMs: last.started_at, id: last.id }) : null;
19565
+ const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
19049
19566
  return Promise.resolve({ items, nextCursor, emptyCount });
19050
19567
  }
19051
19568
  getSession(sessionId) {
@@ -19918,7 +20435,7 @@ var SqliteEventsRepository = class {
19918
20435
  };
19919
20436
 
19920
20437
  // ../../packages/persistence/src/repositories/exceptions.ts
19921
- import { randomUUID } from "crypto";
20438
+ import { randomUUID as randomUUID2 } from "crypto";
19922
20439
 
19923
20440
  // ../../packages/persistence/src/internal/sqlite-errors.ts
19924
20441
  var SQLITE_CONSTRAINT_UNIQUE = 2067;
@@ -19954,8 +20471,9 @@ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
19954
20471
  AND conditions IS NULL
19955
20472
  AND ${ACTIVE_PREDICATE}`;
19956
20473
  var SqliteExceptionsRepository = class {
19957
- constructor(db) {
20474
+ constructor(db, now = () => Date.now()) {
19958
20475
  this.db = db;
20476
+ this.now = now;
19959
20477
  this.consumeStmt = db.prepare(
19960
20478
  `UPDATE exceptions
19961
20479
  SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
@@ -19973,6 +20491,7 @@ var SqliteExceptionsRepository = class {
19973
20491
  );
19974
20492
  }
19975
20493
  db;
20494
+ now;
19976
20495
  consumeStmt;
19977
20496
  insertBlockedStmt;
19978
20497
  sweepBlockedStmt;
@@ -19999,8 +20518,8 @@ var SqliteExceptionsRepository = class {
19999
20518
  "provider conditions are not supported yet \u2014 a grant with one would never apply"
20000
20519
  );
20001
20520
  }
20002
- const id = randomUUID();
20003
- const now = Date.now();
20521
+ const id = randomUUID2();
20522
+ const now = this.now();
20004
20523
  try {
20005
20524
  this.insertExceptionRow(id, input, now);
20006
20525
  } catch (err) {
@@ -20078,7 +20597,7 @@ var SqliteExceptionsRepository = class {
20078
20597
  const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
20079
20598
  const rows = allRows(
20080
20599
  this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
20081
- opts?.includeTerminal ? {} : { now: Date.now() }
20600
+ opts?.includeTerminal ? {} : { now: this.now() }
20082
20601
  );
20083
20602
  const exceptions = mapRowsTolerant(rows, parseExceptionRow);
20084
20603
  return Promise.resolve(exceptions);
@@ -20113,7 +20632,7 @@ var SqliteExceptionsRepository = class {
20113
20632
  * already revoked.
20114
20633
  */
20115
20634
  revoke(id, revokedBy, reason) {
20116
- const now = Date.now();
20635
+ const now = this.now();
20117
20636
  const result = this.db.prepare(
20118
20637
  `UPDATE exceptions
20119
20638
  SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
@@ -20127,7 +20646,7 @@ var SqliteExceptionsRepository = class {
20127
20646
  * callers must treat identically — means it does not and the detection is
20128
20647
  * enforced as usual. Deliberately NOT wrapped in try/catch.
20129
20648
  */
20130
- consume(id, now = Date.now()) {
20649
+ consume(id, now = this.now()) {
20131
20650
  const result = this.consumeStmt.run({ id, now });
20132
20651
  return Promise.resolve(Number(result.changes) === 1);
20133
20652
  }
@@ -20136,7 +20655,7 @@ var SqliteExceptionsRepository = class {
20136
20655
  * version — what rides the policy bundle to the hook. Grants written under
20137
20656
  * a different (rotated-away) key never match, so they are excluded at read.
20138
20657
  */
20139
- activeBundleEntries(keyVersion, now = Date.now()) {
20658
+ activeBundleEntries(keyVersion, now = this.now()) {
20140
20659
  const rows = allRows(
20141
20660
  this.db.prepare(
20142
20661
  `SELECT * FROM exceptions
@@ -20168,7 +20687,7 @@ var SqliteExceptionsRepository = class {
20168
20687
  * than the retention window on every write, so the ledger self-limits.
20169
20688
  */
20170
20689
  recordBlocked(entry) {
20171
- const now = Date.now();
20690
+ const now = this.now();
20172
20691
  this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
20173
20692
  this.insertBlockedStmt.run({
20174
20693
  reference: entry.reference,
@@ -20191,7 +20710,7 @@ var SqliteExceptionsRepository = class {
20191
20710
  WHERE blocked_at > :cutoff
20192
20711
  ORDER BY blocked_at DESC, rowid DESC`
20193
20712
  ),
20194
- { cutoff: Date.now() - windowMs }
20713
+ { cutoff: this.now() - windowMs }
20195
20714
  );
20196
20715
  return Promise.resolve(
20197
20716
  rows.map((row) => ({
@@ -20219,8 +20738,9 @@ var SqliteExceptionsRepository = class {
20219
20738
  * evaluate conditions, and a narrowing clause that is ignored would WIDEN the
20220
20739
  * grant instead. Fail closed until reveal-side condition evaluation exists.
20221
20740
  */
20222
- activeRevealGrant(ruleId, valueFingerprint, keyVersion, now = Date.now()) {
20741
+ activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
20223
20742
  try {
20743
+ const at = now ?? this.now();
20224
20744
  const row = getRow(
20225
20745
  this.db.prepare(
20226
20746
  `SELECT id FROM exceptions
@@ -20229,7 +20749,7 @@ var SqliteExceptionsRepository = class {
20229
20749
  AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
20230
20750
  LIMIT 1`
20231
20751
  ),
20232
- { ruleId, valueFingerprint, keyVersion, now }
20752
+ { ruleId, valueFingerprint, keyVersion, now: at }
20233
20753
  );
20234
20754
  return Promise.resolve(row ?? null);
20235
20755
  } catch (err) {
@@ -20243,7 +20763,7 @@ var SqliteExceptionsRepository = class {
20243
20763
  * predicate, so correctness never depends on this sweep; it only bounds how
20244
20764
  * long the audit evidence is kept locally. Returns the deleted count.
20245
20765
  */
20246
- sweepTerminal(retentionMs, now = Date.now()) {
20766
+ sweepTerminal(retentionMs, now = this.now()) {
20247
20767
  const result = this.db.prepare(
20248
20768
  `DELETE FROM exceptions
20249
20769
  WHERE updated_at < :cutoff
@@ -20306,6 +20826,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
20306
20826
 
20307
20827
  // ../../packages/persistence/src/repositories/findings.ts
20308
20828
  var PREVIEW_INSTANCES_PER_GROUP = 200;
20829
+ var SCAN_BATCH_ROWS = 1e3;
20830
+ var DEFAULT_LOCATIONS_LIMIT = 100;
20831
+ var LOCATION_RULE_IDS_CAP = 20;
20832
+ function compareLocationOrder(a, b) {
20833
+ return compareFindingGroupOrder(
20834
+ {
20835
+ severity: a.maxSeverity,
20836
+ latestDetectedAt: a.latestDetectedAt,
20837
+ id: ""
20838
+ },
20839
+ {
20840
+ severity: b.maxSeverity,
20841
+ latestDetectedAt: b.latestDetectedAt,
20842
+ id: ""
20843
+ }
20844
+ );
20845
+ }
20309
20846
  var CONCAT_SEP = ",";
20310
20847
  var TUPLE_SEP = "|";
20311
20848
  function splitConcat(value) {
@@ -20318,6 +20855,33 @@ function deriveInstanceStatus(row) {
20318
20855
  latestResolutionStatus: row.latest_status
20319
20856
  });
20320
20857
  }
20858
+ function encodeGroupCursor(group) {
20859
+ const payload = {
20860
+ sev: group.severity,
20861
+ t: group.latestDetectedAt,
20862
+ id: group.id
20863
+ };
20864
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
20865
+ }
20866
+ function decodeGroupCursor(cursor) {
20867
+ const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
20868
+ if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
20869
+ return {
20870
+ severity: parsed.sev,
20871
+ latestDetectedAt: parsed.t,
20872
+ id: parsed.id
20873
+ };
20874
+ }
20875
+ return null;
20876
+ }
20877
+ function firstAfter(sorted, cursor) {
20878
+ const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
20879
+ return index === -1 ? sorted.length : index;
20880
+ }
20881
+ function findDeepLinked(sorted, page, id) {
20882
+ if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
20883
+ return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
20884
+ }
20321
20885
  var DAY_MS3 = 864e5;
20322
20886
  var SqliteFindingsRepository = class {
20323
20887
  constructor(db) {
@@ -20427,8 +20991,13 @@ var SqliteFindingsRepository = class {
20427
20991
  */
20428
20992
  listGroupedFindings(query) {
20429
20993
  const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
20430
- const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}`;
20431
- const sessionParams = query.sessionId ? { sessionId: query.sessionId } : {};
20994
+ const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
20995
+ const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
20996
+ const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
20997
+ const sessionParams = {
20998
+ ...query.sessionId ? { sessionId: query.sessionId } : {},
20999
+ ...fromMs === void 0 ? {} : { fromMs }
21000
+ };
20432
21001
  const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
20433
21002
  predicate,
20434
21003
  params: sessionParams
@@ -20436,7 +21005,8 @@ var SqliteFindingsRepository = class {
20436
21005
  const rows = allRows(
20437
21006
  this.db.prepare(
20438
21007
  `SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
20439
- occurred_at, source_tool, repo, file, tool_name, kind, finding_key, latest_status
21008
+ occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
21009
+ kind, finding_key, latest_status
20440
21010
  FROM (
20441
21011
  SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
20442
21012
  d.severity AS severity, f.masked_match AS masked_match,
@@ -20446,6 +21016,7 @@ var SqliteFindingsRepository = class {
20446
21016
  json_extract(e.attributes, '$.repo') AS repo,
20447
21017
  json_extract(e.attributes, '$.file_path') AS file,
20448
21018
  json_extract(e.attributes, '$.tool_name') AS tool_name,
21019
+ f.audit_event_id AS event_id, e.root_session_id AS session_id,
20449
21020
  e.event_type AS kind, f.finding_key AS finding_key,
20450
21021
  latest.status AS latest_status,
20451
21022
  ROW_NUMBER() OVER (
@@ -20477,6 +21048,8 @@ var SqliteFindingsRepository = class {
20477
21048
  repo: r.repo ?? "",
20478
21049
  file: r.file ?? "",
20479
21050
  ...r.tool_name === null ? {} : { toolName: r.tool_name },
21051
+ eventId: r.event_id,
21052
+ ...r.session_id === null ? {} : { sessionId: r.session_id },
20480
21053
  status: deriveInstanceStatus(r)
20481
21054
  }));
20482
21055
  const allGroups = buildFindingGroups(groupable, { aggregates });
@@ -20500,18 +21073,23 @@ var SqliteFindingsRepository = class {
20500
21073
  groups: sorted.length
20501
21074
  };
20502
21075
  const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
21076
+ const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
21077
+ const start = cursor === null ? 0 : firstAfter(sorted, cursor);
21078
+ const page = sorted.slice(start, start + limit);
21079
+ const lastOnPage = page.at(-1);
21080
+ const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
21081
+ const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
20503
21082
  const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
20504
- const items = sorted.slice(0, limit).map(
20505
- (g) => statusSet ? {
20506
- ...g,
20507
- instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
20508
- } : g
20509
- );
21083
+ const narrow = (g) => statusSet ? {
21084
+ ...g,
21085
+ instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
21086
+ } : g;
21087
+ const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
20510
21088
  return Promise.resolve({
20511
21089
  totals,
20512
21090
  facets,
20513
21091
  items,
20514
- nextCursor: null,
21092
+ nextCursor,
20515
21093
  ...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
20516
21094
  });
20517
21095
  }
@@ -20543,6 +21121,266 @@ var SqliteFindingsRepository = class {
20543
21121
  * request actually carries a `q`. (Substring matching is unaffected by a
20544
21122
  * path repeating across tuples.)
20545
21123
  */
21124
+ /**
21125
+ * The instance-level (flat) findings list: one row per finding, newest first,
21126
+ * paged by keyset.
21127
+ *
21128
+ * SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
21129
+ * bound are SQL predicates: nothing counts them, so narrowing the scan by
21130
+ * them changes no reported number. Severity, subtype, provider, action,
21131
+ * status, tool, repo, file and `q` all stay in JS — each has a facet, and a
21132
+ * facet excludes its own filter, so a row the filter rejects still has to be
21133
+ * counted. Pushing any of them into SQL would silently empty its own facet.
21134
+ * Several could not be expressed there anyway: status comes from the one
21135
+ * shared classifier (deriveFindingStatus), and provider 'api' means "a tool
21136
+ * none of the mappers names", which no IN-list can say.
21137
+ *
21138
+ * The scan runs from the top of the scope on every request, not from the
21139
+ * cursor: `totals` and `facets` describe the whole filtered scope and must not
21140
+ * move as the caller pages. Rows are pulled in batches so memory stays flat
21141
+ * while the counting runs, and only the page itself is retained.
21142
+ */
21143
+ listFindingInstances(query) {
21144
+ const opts = {
21145
+ severity: query.severity,
21146
+ subtype: query.subtype,
21147
+ providers: query.provider,
21148
+ actions: query.action,
21149
+ statuses: query.status,
21150
+ tools: query.tool,
21151
+ repo: query.repo,
21152
+ file: query.file,
21153
+ q: query.q
21154
+ };
21155
+ const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
21156
+ const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
21157
+ const accumulator = createInstanceFacetAccumulator(opts);
21158
+ const items = [];
21159
+ let total = 0;
21160
+ let last;
21161
+ let hasMore = false;
21162
+ for (const row of this.scanFindingRows({
21163
+ sessionId: query.sessionId,
21164
+ from: query.from
21165
+ })) {
21166
+ accumulator.add(row);
21167
+ if (!matchesInstanceFilters(row, opts)) continue;
21168
+ total += 1;
21169
+ if (items.length < limit) {
21170
+ items.push(toInstanceDetail(row));
21171
+ last = row;
21172
+ } else {
21173
+ hasMore = true;
21174
+ }
21175
+ }
21176
+ const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
21177
+ if (cursor !== null) {
21178
+ const resumed = this.pageAfter(cursor, opts, limit, query);
21179
+ return Promise.resolve({
21180
+ totals: { findings: total },
21181
+ facets: accumulator.facets(),
21182
+ items: resumed.items,
21183
+ nextCursor: resumed.nextCursor
21184
+ });
21185
+ }
21186
+ return Promise.resolve({
21187
+ totals: { findings: total },
21188
+ facets: accumulator.facets(),
21189
+ items,
21190
+ nextCursor
21191
+ });
21192
+ }
21193
+ /**
21194
+ * The page of matching rows strictly after `cursor`. Separate from the
21195
+ * counting pass because that one starts at the top of the scope by design;
21196
+ * this one narrows the scan with the same keyset predicate the activity list
21197
+ * uses, so a later page costs less than the first rather than more.
21198
+ */
21199
+ pageAfter(cursor, opts, limit, query) {
21200
+ const items = [];
21201
+ let last;
21202
+ let hasMore = false;
21203
+ for (const row of this.scanFindingRows({
21204
+ sessionId: query.sessionId,
21205
+ from: query.from,
21206
+ after: cursor
21207
+ })) {
21208
+ if (!matchesInstanceFilters(row, opts)) continue;
21209
+ if (items.length < limit) {
21210
+ items.push(toInstanceDetail(row));
21211
+ last = row;
21212
+ } else {
21213
+ hasMore = true;
21214
+ break;
21215
+ }
21216
+ }
21217
+ return {
21218
+ items,
21219
+ nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
21220
+ };
21221
+ }
21222
+ /**
21223
+ * The same findings folded by location: repository, then file within it.
21224
+ *
21225
+ * The grouping keys come from the capturing event's attributes, which is what
21226
+ * the local store relates a finding to — there is no finding↔asset row to
21227
+ * group by instead. A repo or file the event did not record folds into the
21228
+ * empty-string bucket, which the view renders but does not link, since no
21229
+ * filter can name it.
21230
+ */
21231
+ listFindingLocations(query) {
21232
+ const opts = {
21233
+ severity: query.severity,
21234
+ subtype: query.subtype,
21235
+ providers: query.provider,
21236
+ actions: query.action,
21237
+ statuses: query.status,
21238
+ tools: query.tool,
21239
+ q: query.q
21240
+ };
21241
+ const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
21242
+ const byRepo = /* @__PURE__ */ new Map();
21243
+ let total = 0;
21244
+ for (const row of this.scanFindingRows({
21245
+ sessionId: query.sessionId,
21246
+ from: query.from
21247
+ })) {
21248
+ if (!matchesInstanceFilters(row, opts)) continue;
21249
+ total += 1;
21250
+ let files = byRepo.get(row.repo);
21251
+ if (files === void 0) {
21252
+ files = /* @__PURE__ */ new Map();
21253
+ byRepo.set(row.repo, files);
21254
+ }
21255
+ let acc = files.get(row.file);
21256
+ if (acc === void 0) {
21257
+ acc = newLocationAccumulator();
21258
+ files.set(row.file, acc);
21259
+ }
21260
+ addToLocation(acc, row);
21261
+ }
21262
+ let fileCount = 0;
21263
+ const repos = [...byRepo.entries()].map(([repo, files]) => {
21264
+ fileCount += files.size;
21265
+ const fileRows = [...files.entries()].map(([file2, acc]) => ({
21266
+ file: file2,
21267
+ instanceCount: acc.instanceCount,
21268
+ maxSeverity: acc.maxSeverity,
21269
+ latestDetectedAt: acc.latestDetectedAt,
21270
+ ...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
21271
+ ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
21272
+ })).sort(compareLocationOrder);
21273
+ const rollup = fileRows.reduce(
21274
+ (a, f) => ({
21275
+ instanceCount: a.instanceCount + f.instanceCount,
21276
+ maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
21277
+ latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
21278
+ }),
21279
+ {
21280
+ instanceCount: 0,
21281
+ maxSeverity: fileRows[0]?.maxSeverity ?? "low",
21282
+ latestDetectedAt: ""
21283
+ }
21284
+ );
21285
+ const statuses = fileRows.map((f) => f.status);
21286
+ const folded = foldGroupStatus(statuses);
21287
+ return {
21288
+ repo,
21289
+ instanceCount: rollup.instanceCount,
21290
+ maxSeverity: rollup.maxSeverity,
21291
+ latestDetectedAt: rollup.latestDetectedAt,
21292
+ ...folded === void 0 ? {} : { status: folded },
21293
+ files: fileRows
21294
+ };
21295
+ });
21296
+ repos.sort(compareLocationOrder);
21297
+ return Promise.resolve({
21298
+ totals: { findings: total, repos: repos.length, files: fileCount },
21299
+ items: repos.slice(0, limit),
21300
+ hasMore: repos.length > limit
21301
+ });
21302
+ }
21303
+ /**
21304
+ * Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
21305
+ *
21306
+ * A generator so a caller streams the scope without it ever being an array:
21307
+ * the flat list counts and facets the whole filtered scope, which on a large
21308
+ * store is far more rows than any page. Each batch advances the same keyset
21309
+ * predicate the page read uses, so the scan is a sequence of bounded reads
21310
+ * rather than one unbounded result set.
21311
+ *
21312
+ * The latest-resolution lookup is the CORRELATED form, not the derived table
21313
+ * the grouped path joins: only `status` is needed, idx_finding_resolution_key
21314
+ * makes it a point lookup per row, and the derived table would re-materialize
21315
+ * a window over the whole resolution table once per batch.
21316
+ *
21317
+ * `scope` carries ONLY what no facet counts. A filter dimension narrowed here
21318
+ * would be missing from its own facet, which is computed by excluding that
21319
+ * dimension — see listFindingInstances.
21320
+ */
21321
+ *scanFindingRows(scope) {
21322
+ const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
21323
+ const params = [];
21324
+ if (scope.sessionId !== void 0 && scope.sessionId !== "") {
21325
+ conditions.push("e.root_session_id = ?");
21326
+ params.push(scope.sessionId);
21327
+ }
21328
+ if (scope.from !== void 0) {
21329
+ conditions.push("e.started_at >= ?");
21330
+ params.push(isoToEpochMillis(scope.from));
21331
+ }
21332
+ const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
21333
+ d.severity AS severity, f.masked_match AS masked_match,
21334
+ f.action_taken AS action_taken, f.confidence AS confidence,
21335
+ e.started_at AS occurred_at,
21336
+ json_extract(e.attributes, '$.source_tool') AS source_tool,
21337
+ json_extract(e.attributes, '$.repo') AS repo,
21338
+ json_extract(e.attributes, '$.file_path') AS file,
21339
+ json_extract(e.attributes, '$.tool_name') AS tool_name,
21340
+ f.audit_event_id AS event_id, e.root_session_id AS session_id,
21341
+ e.event_type AS kind, f.finding_key AS finding_key,
21342
+ ${latestResolutionStatusSql("f")} AS latest_status
21343
+ FROM inspection_findings f
21344
+ JOIN audit_events e ON e.id = f.audit_event_id
21345
+ JOIN inspection_definitions d ON d.id = f.inspection_definition_id
21346
+ WHERE ${conditions.join(" AND ")}
21347
+ AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
21348
+ ORDER BY e.started_at DESC, f.id DESC
21349
+ LIMIT ?`;
21350
+ let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
21351
+ for (; ; ) {
21352
+ const rows = allRows(this.db.prepare(sql), [
21353
+ ...params,
21354
+ after.startedAtMs,
21355
+ after.startedAtMs,
21356
+ after.id,
21357
+ SCAN_BATCH_ROWS
21358
+ ]);
21359
+ for (const r of rows) {
21360
+ yield {
21361
+ id: r.id,
21362
+ ruleId: r.rule_id,
21363
+ category: r.category,
21364
+ severity: r.severity,
21365
+ maskedMatch: r.masked_match,
21366
+ actionTaken: r.action_taken,
21367
+ confidence: r.confidence,
21368
+ occurredAt: epochMillisToIso(r.occurred_at),
21369
+ sourceTool: r.source_tool,
21370
+ repo: r.repo ?? "",
21371
+ file: r.file ?? "",
21372
+ ...r.tool_name === null ? {} : { toolName: r.tool_name },
21373
+ eventId: r.event_id,
21374
+ ...r.session_id === null ? {} : { sessionId: r.session_id },
21375
+ status: deriveInstanceStatus(r)
21376
+ };
21377
+ }
21378
+ if (rows.length < SCAN_BATCH_ROWS) return;
21379
+ const lastRow = rows[rows.length - 1];
21380
+ if (lastRow === void 0) return;
21381
+ after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
21382
+ }
21383
+ }
20546
21384
  groupAggregates(withSearchText, scope) {
20547
21385
  const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
20548
21386
  group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
@@ -20803,7 +21641,7 @@ var SqliteInspectionFindingsRepository = class {
20803
21641
  };
20804
21642
 
20805
21643
  // ../../packages/persistence/src/repositories/installed-packs.ts
20806
- import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
21644
+ import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
20807
21645
 
20808
21646
  // ../../packages/persistence/src/semver.ts
20809
21647
  function parse3(version2) {
@@ -20954,7 +21792,7 @@ var SqliteInstalledPacksRepository = class {
20954
21792
  let behind = false;
20955
21793
  for (const row of rows) {
20956
21794
  const params = {
20957
- id: randomUUID2(),
21795
+ id: randomUUID3(),
20958
21796
  namespace: row.namespace,
20959
21797
  packId: row.packId,
20960
21798
  version: row.version,
@@ -20966,7 +21804,7 @@ var SqliteInstalledPacksRepository = class {
20966
21804
  if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
20967
21805
  this.upsertAvailableStmt.run({
20968
21806
  ...params,
20969
- id: randomUUID2(),
21807
+ id: randomUUID3(),
20970
21808
  recordedBy: meta3?.recordedBy ?? null
20971
21809
  });
20972
21810
  } else {
@@ -21289,14 +22127,15 @@ var SqliteInventoryRepository = class {
21289
22127
  };
21290
22128
 
21291
22129
  // ../../packages/persistence/src/repositories/inventory-assets.ts
21292
- import { randomUUID as randomUUID3 } from "crypto";
22130
+ import { randomUUID as randomUUID4 } from "crypto";
21293
22131
  var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
21294
22132
  var VALID_HARNESS_IDS = new Set(HarnessId.options);
21295
22133
  var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
21296
22134
  var HARNESS_LABELS = {
21297
22135
  claudecode: "Claude Code",
21298
22136
  cursor: "Cursor",
21299
- codex: "Codex"
22137
+ codex: "Codex",
22138
+ antigravity: "Antigravity"
21300
22139
  };
21301
22140
  var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
21302
22141
  var EMPTY_PROJECT_AGG = {
@@ -21311,6 +22150,7 @@ function resolveHarnessId(attrs, row) {
21311
22150
  if (t.includes("claudecode") || t === "claude") return "claudecode";
21312
22151
  if (t.includes("cursor")) return "cursor";
21313
22152
  if (t.includes("codex")) return "codex";
22153
+ if (t.includes("antigravity")) return "antigravity";
21314
22154
  return null;
21315
22155
  }
21316
22156
  function isLiveRealClaudeCode(rows) {
@@ -21769,7 +22609,7 @@ var SqliteInventoryAssetsRepository = class {
21769
22609
  `INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
21770
22610
  VALUES (:id, :projectId, :path, :access, :now, :now)
21771
22611
  ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
21772
- ).run({ id: randomUUID3(), projectId, path, access, now: Date.now() });
22612
+ ).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
21773
22613
  }
21774
22614
  return true;
21775
22615
  }
@@ -21790,7 +22630,7 @@ var SqliteInventoryAssetsRepository = class {
21790
22630
  `INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
21791
22631
  VALUES (:id, :assetId, :trust, :now, :now)
21792
22632
  ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
21793
- ).run({ id: randomUUID3(), assetId, trust, now: Date.now() });
22633
+ ).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
21794
22634
  }
21795
22635
  this.configRowsCache = void 0;
21796
22636
  return "ok";
@@ -22087,7 +22927,7 @@ var SqliteInventoryAssetsRepository = class {
22087
22927
  };
22088
22928
 
22089
22929
  // ../../packages/persistence/src/repositories/policies.ts
22090
- import { randomUUID as randomUUID4 } from "crypto";
22930
+ import { randomUUID as randomUUID5 } from "crypto";
22091
22931
  var SqlitePoliciesRepository = class {
22092
22932
  constructor(db) {
22093
22933
  this.db = db;
@@ -22122,7 +22962,7 @@ var SqlitePoliciesRepository = class {
22122
22962
  failOpenTransaction(this.db, () => {
22123
22963
  for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
22124
22964
  stmt.run({
22125
- id: randomUUID4(),
22965
+ id: randomUUID5(),
22126
22966
  target: JSON.stringify({ category }),
22127
22967
  action,
22128
22968
  now: Date.now()
@@ -22142,7 +22982,7 @@ var SqlitePoliciesRepository = class {
22142
22982
  `INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
22143
22983
  VALUES (:id, 'global', :target, :action, 1, :now, :now)
22144
22984
  ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
22145
- ).run({ id: randomUUID4(), target: JSON.stringify({ category }), action, now });
22985
+ ).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
22146
22986
  }
22147
22987
  // Caps every global per-category policy currently set to block/redact down
22148
22988
  // to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
@@ -22210,7 +23050,7 @@ var SqlitePolicyCatalogRepository = class {
22210
23050
  };
22211
23051
 
22212
23052
  // ../../packages/persistence/src/repositories/project-files.ts
22213
- import { randomUUID as randomUUID5 } from "crypto";
23053
+ import { randomUUID as randomUUID6 } from "crypto";
22214
23054
  var SqliteProjectFilesRepository = class {
22215
23055
  constructor(db) {
22216
23056
  this.db = db;
@@ -22242,7 +23082,7 @@ var SqliteProjectFilesRepository = class {
22242
23082
  const stamp = Math.max(now, maxStamp + 1);
22243
23083
  for (const file2 of scan2.files) {
22244
23084
  this.upsertStmt.run({
22245
- id: randomUUID5(),
23085
+ id: randomUUID6(),
22246
23086
  projectId,
22247
23087
  path: file2.path,
22248
23088
  name: file2.name,
@@ -22256,7 +23096,7 @@ var SqliteProjectFilesRepository = class {
22256
23096
  };
22257
23097
 
22258
23098
  // ../../packages/persistence/src/repositories/resolutions.ts
22259
- import { randomUUID as randomUUID6 } from "crypto";
23099
+ import { randomUUID as randomUUID7 } from "crypto";
22260
23100
  var SqliteResolutionsRepository = class {
22261
23101
  constructor(db, now = () => Date.now()) {
22262
23102
  this.db = db;
@@ -22310,7 +23150,7 @@ var SqliteResolutionsRepository = class {
22310
23150
  */
22311
23151
  insertResolution(r) {
22312
23152
  this.insertStmt.run({
22313
- id: randomUUID6(),
23153
+ id: randomUUID7(),
22314
23154
  findingKey: r.findingKey,
22315
23155
  status: FindingStatus.parse(r.status),
22316
23156
  method: ResolutionMethod.parse(r.method),
@@ -22369,13 +23209,51 @@ var SqliteRuleProbeCacheRepository = class {
22369
23209
  this.readStmt = db.prepare(
22370
23210
  `SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
22371
23211
  );
23212
+ this.countQuarantinedStmt = db.prepare(
23213
+ `SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
23214
+ );
23215
+ this.clearQuarantinedStmt = db.prepare(
23216
+ `DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
23217
+ );
22372
23218
  }
22373
23219
  db;
22374
23220
  upsertStmt;
22375
23221
  readStmt;
23222
+ countQuarantinedStmt;
23223
+ clearQuarantinedStmt;
22376
23224
  getVerdict(ruleKey) {
22377
23225
  return getRow(this.readStmt, { ruleKey });
22378
23226
  }
23227
+ /** How many rules are currently excluded by a cached quarantine verdict. */
23228
+ countQuarantined() {
23229
+ return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
23230
+ }
23231
+ /**
23232
+ * Forgets every quarantine verdict, so the rules behind them are measured
23233
+ * again on the next load. This is the undo for a verdict the machine reached
23234
+ * on its own: a rule terminated mid-scan is cached forever and dropped from
23235
+ * every later scan, and a timing verdict is a wall-clock judgement that a
23236
+ * loaded or slow machine can reach about a rule that is in fact fine.
23237
+ *
23238
+ * Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
23239
+ * keeping, and dropping it would make every rule pay the battery again.
23240
+ *
23241
+ * Reports `refused` from the write's own result rather than inferring it from
23242
+ * the row count. The two are NOT the same answer: `failOpenTransaction`
23243
+ * swallows a contended DELETE (another writer holding the lock past
23244
+ * `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
23245
+ * leaves the count unchanged, which is indistinguishable from "there was
23246
+ * nothing to clear". An undo that reports success while the quarantines are
23247
+ * still in place is worse than one that fails, because the rules it claimed
23248
+ * to restore are silently still disabled.
23249
+ */
23250
+ clearQuarantined() {
23251
+ const before = this.countQuarantined();
23252
+ const committed = failOpenTransaction(this.db, () => {
23253
+ this.clearQuarantinedStmt.run();
23254
+ });
23255
+ return { refused: !committed, cleared: before - this.countQuarantined() };
23256
+ }
22379
23257
  setVerdict(ruleKey, verdict, worstProbeMs) {
22380
23258
  failOpenTransaction(this.db, () => {
22381
23259
  this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
@@ -22430,7 +23308,39 @@ var SqliteScanLedgerRepository = class {
22430
23308
  };
22431
23309
 
22432
23310
  // ../../packages/persistence/src/repositories/secret-vault.ts
22433
- import { randomUUID as randomUUID7 } from "crypto";
23311
+ import { randomUUID as randomUUID8 } from "crypto";
23312
+ function pageLimit(requested, fallback) {
23313
+ if (requested === void 0) return fallback;
23314
+ return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
23315
+ }
23316
+ function encodeReuseCursor(payload) {
23317
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
23318
+ }
23319
+ function decodeReuseCursor(cursor) {
23320
+ const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
23321
+ if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
23322
+ // ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
23323
+ // null cursor, which the caller reads as "end of list" — the one outcome a
23324
+ // malformed cursor must never produce, since restarting from the top is the
23325
+ // documented behaviour and the only recoverable one. (`1e999` is valid JSON
23326
+ // and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
23327
+ Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
23328
+ return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
23329
+ }
23330
+ return null;
23331
+ }
23332
+ var REUSED_PREDICATE = `(v.occurrence_count > 1
23333
+ OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
23334
+ var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
23335
+ v.occurrence_count, v.first_seen, v.last_seen`;
23336
+ function toSighting(row) {
23337
+ return {
23338
+ location: row.location,
23339
+ kind: row.kind,
23340
+ firstSeen: new Date(row.first_seen).toISOString(),
23341
+ lastSeen: new Date(row.last_seen).toISOString()
23342
+ };
23343
+ }
22434
23344
  var SELECT_COLUMNS = `
22435
23345
  pointer_id AS pointerId,
22436
23346
  value_fingerprint AS valueFingerprint,
@@ -22614,39 +23524,67 @@ var SqliteSecretVaultRepository = class {
22614
23524
  VALUES (:id, :pointerId, :location, :kind, :now, :now)
22615
23525
  ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
22616
23526
  ).run({
22617
- id: randomUUID7(),
23527
+ id: randomUUID8(),
22618
23528
  pointerId: entry.pointerId,
22619
23529
  location: entry.location,
22620
23530
  kind: entry.kind,
22621
23531
  now
22622
23532
  });
22623
23533
  }
22624
- listSightings(pointerId) {
23534
+ /**
23535
+ * Sightings for a whole page of pointers, in ONE query grouped in JS rather
23536
+ * than one query per row. A pointer with no sightings still gets an entry, so
23537
+ * the caller never has to distinguish "none" from "missing".
23538
+ *
23539
+ * The `IN` list is sized to the page, so this statement cannot be cached on
23540
+ * the instance the way the fixed-shape ones in the constructor are.
23541
+ */
23542
+ sightingsFor(pointerIds) {
23543
+ const byPointer = new Map(pointerIds.map((id) => [id, []]));
23544
+ if (pointerIds.length === 0) return byPointer;
22625
23545
  const rows = allRows(
22626
23546
  this.db.prepare(
22627
- `SELECT location, kind, first_seen, last_seen FROM secret_vault_sighting
22628
- WHERE pointer_id = :pointerId ORDER BY last_seen DESC`
23547
+ `SELECT pointer_id, location, kind, first_seen, last_seen
23548
+ FROM secret_vault_sighting
23549
+ WHERE pointer_id IN (${placeholders(pointerIds.length)})
23550
+ ORDER BY last_seen DESC`
22629
23551
  ),
22630
- { pointerId }
23552
+ pointerIds
22631
23553
  );
23554
+ for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
23555
+ return byPointer;
23556
+ }
23557
+ /** Hydrate a page of raw inventory rows with their sightings, batched. */
23558
+ toInventoryEntries(rows) {
23559
+ const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
22632
23560
  return rows.map((r) => ({
22633
- location: r.location,
22634
- kind: r.kind,
23561
+ pointerId: r.pointer_id,
23562
+ category: r.category,
23563
+ ...r.provider === null ? {} : { provider: r.provider },
23564
+ maskedMatch: r.masked_match,
23565
+ occurrences: r.occurrence_count,
22635
23566
  firstSeen: new Date(r.first_seen).toISOString(),
22636
- lastSeen: new Date(r.last_seen).toISOString()
23567
+ lastSeen: new Date(r.last_seen).toISOString(),
23568
+ revealGrantId: r.grant_id,
23569
+ sightings: sightings.get(r.pointer_id) ?? []
22637
23570
  }));
22638
23571
  }
22639
23572
  /**
22640
- * The dashboard inventory: every vaulted value's descriptor data joined with
22641
- * its sightings and the active reveal-to-model grant when one exists.
22642
- * Raw-free by construction — neither the fingerprint nor the ciphertext
22643
- * columns are selected.
23573
+ * The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
23574
+ * value's descriptor data joined with its sightings and the active
23575
+ * reveal-to-model grant when one exists. Raw-free by construction — neither
23576
+ * the fingerprint nor the ciphertext columns are selected.
23577
+ *
23578
+ * `totals.values` counts the whole store, not the page, so the count a reader
23579
+ * sees never depends on how far they have paged.
22644
23580
  */
22645
- listInventory(now = Date.now()) {
23581
+ listInventory(query = {}, now = Date.now()) {
23582
+ const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
23583
+ const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
23584
+ const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
22646
23585
  const rows = allRows(
22647
23586
  this.db.prepare(
22648
- `SELECT v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
22649
- v.occurrence_count, v.first_seen, v.last_seen,
23587
+ `SELECT ${INVENTORY_COLUMNS},
22650
23588
  (SELECT e.id FROM exceptions e
22651
23589
  WHERE e.rule_id = v.rule_id
22652
23590
  AND e.value_fingerprint = v.value_fingerprint
@@ -22654,45 +23592,109 @@ var SqliteSecretVaultRepository = class {
22654
23592
  AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
22655
23593
  LIMIT 1) AS grant_id
22656
23594
  FROM secret_vault v
22657
- ORDER BY v.last_seen DESC`
23595
+ ${where}
23596
+ ORDER BY v.last_seen DESC, v.pointer_id DESC
23597
+ LIMIT :limit`
22658
23598
  ),
22659
- { now }
23599
+ bindParams({
23600
+ now,
23601
+ limit: limit + 1,
23602
+ ...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
23603
+ })
22660
23604
  );
22661
- return rows.map((r) => ({
22662
- pointerId: r.pointer_id,
22663
- category: r.category,
22664
- ...r.provider === null ? {} : { provider: r.provider },
22665
- maskedMatch: r.masked_match,
22666
- occurrences: r.occurrence_count,
22667
- firstSeen: new Date(r.first_seen).toISOString(),
22668
- lastSeen: new Date(r.last_seen).toISOString(),
22669
- revealGrantId: r.grant_id,
22670
- sightings: this.listSightings(r.pointer_id)
22671
- }));
23605
+ const hasMore = rows.length > limit;
23606
+ const page = hasMore ? rows.slice(0, limit) : rows;
23607
+ const last = page[page.length - 1];
23608
+ return {
23609
+ totals: { values: this.countEntries() },
23610
+ items: this.toInventoryEntries(page),
23611
+ // Minted from the last row of the PAGE, never the extra probe row.
23612
+ nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
23613
+ };
23614
+ }
23615
+ /**
23616
+ * Values reused on this machine — detected more than once, or written to more
23617
+ * than one location — most-reused first, one page at a time.
23618
+ *
23619
+ * Its own read rather than a filter over an inventory page: reuse is a
23620
+ * property of the whole store, and deriving it from 50 newest rows would
23621
+ * under-report exactly the values a reader most needs to see.
23622
+ */
23623
+ listReuse(query = {}, now = Date.now()) {
23624
+ const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
23625
+ const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
23626
+ const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
23627
+ OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
23628
+ const rows = allRows(
23629
+ this.db.prepare(
23630
+ `SELECT ${INVENTORY_COLUMNS},
23631
+ (SELECT e.id FROM exceptions e
23632
+ WHERE e.rule_id = v.rule_id
23633
+ AND e.value_fingerprint = v.value_fingerprint
23634
+ AND e.key_version = v.fingerprint_key_version
23635
+ AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
23636
+ LIMIT 1) AS grant_id
23637
+ FROM secret_vault v
23638
+ WHERE ${REUSED_PREDICATE} ${after}
23639
+ ORDER BY v.occurrence_count DESC, v.pointer_id DESC
23640
+ LIMIT :limit`
23641
+ ),
23642
+ bindParams({
23643
+ now,
23644
+ limit: limit + 1,
23645
+ ...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
23646
+ })
23647
+ );
23648
+ const hasMore = rows.length > limit;
23649
+ const page = hasMore ? rows.slice(0, limit) : rows;
23650
+ const last = page[page.length - 1];
23651
+ return {
23652
+ totals: { reused: this.countReused() },
23653
+ items: this.toInventoryEntries(page),
23654
+ nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
23655
+ };
22672
23656
  }
22673
23657
  /**
22674
- * The de-reference trail, newest first. By default the batched, high-volume
22675
- * reasons (display, view-render) are hidden and counted instead — the rows
22676
- * that matter as a signal are the model crossings, and burying them under
22677
- * render noise would defeat the audit's purpose.
23658
+ * The de-reference trail, newest first, one page at a time. By default the
23659
+ * batched, high-volume reasons (display, view-render) are hidden and counted
23660
+ * instead — the rows that matter as a signal are the model crossings, and
23661
+ * burying them under render noise would defeat the audit's purpose.
23662
+ *
23663
+ * `hiddenBatched` counts the whole trail rather than the page: it is what the
23664
+ * view's "N hidden" line and its toggle speak for, so it must not shrink as
23665
+ * the reader pages.
22678
23666
  */
22679
- listDerefs(opts) {
22680
- const limit = opts?.limit ?? 200;
22681
- const where = opts?.includeBatched === true ? "" : `WHERE reason NOT IN ('display', 'view-render')`;
23667
+ listDerefs(query = {}) {
23668
+ const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
23669
+ const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
23670
+ const conditions = [];
23671
+ if (query.includeBatched !== true) {
23672
+ conditions.push(`reason NOT IN ('display', 'view-render')`);
23673
+ }
23674
+ if (cursor !== null) {
23675
+ conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
23676
+ }
23677
+ const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
22682
23678
  const rows = allRows(
22683
23679
  this.db.prepare(
22684
23680
  `SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
22685
23681
  FROM secret_vault_deref ${where}
22686
- ORDER BY at DESC, rowid DESC LIMIT :limit`
23682
+ ORDER BY at DESC, id DESC LIMIT :limit`
22687
23683
  ),
22688
- { limit }
23684
+ bindParams({
23685
+ limit: limit + 1,
23686
+ ...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
23687
+ })
22689
23688
  );
22690
- const hiddenBatched = opts?.includeBatched === true ? 0 : countScalar(
23689
+ const hasMore = rows.length > limit;
23690
+ const page = hasMore ? rows.slice(0, limit) : rows;
23691
+ const last = page[page.length - 1];
23692
+ const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
22691
23693
  this.db,
22692
23694
  `SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
22693
23695
  );
22694
23696
  return {
22695
- rows: rows.map((r) => ({
23697
+ items: page.map((r) => ({
22696
23698
  id: r.id,
22697
23699
  pointerId: r.pointer_id,
22698
23700
  at: new Date(r.at).toISOString(),
@@ -22702,12 +23704,20 @@ var SqliteSecretVaultRepository = class {
22702
23704
  ...r.grant_id === null ? {} : { grantId: r.grant_id },
22703
23705
  pointerCount: r.pointer_count
22704
23706
  })),
23707
+ nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
22705
23708
  hiddenBatched
22706
23709
  };
22707
23710
  }
22708
23711
  countEntries() {
22709
23712
  return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
22710
23713
  }
23714
+ /** Values reused on this machine — the reuse list's page-independent total. */
23715
+ countReused() {
23716
+ return countScalar(
23717
+ this.db,
23718
+ `SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
23719
+ );
23720
+ }
22711
23721
  };
22712
23722
 
22713
23723
  // ../../packages/persistence/src/repositories/security.ts
@@ -22722,7 +23732,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
22722
23732
  var SCAN_COVERAGE = [
22723
23733
  { provider: "claudecode", coverage: 100, supported: true },
22724
23734
  { provider: "cursor", coverage: 0, supported: false },
22725
- { provider: "codex", coverage: 0, supported: false },
23735
+ { provider: "codex", coverage: 80, supported: true },
23736
+ { provider: "antigravity", coverage: 60, supported: true },
23737
+ { provider: "claudeai", coverage: 0, supported: false },
22726
23738
  { provider: "chatgpt", coverage: 0, supported: false },
22727
23739
  { provider: "copilot", coverage: 0, supported: false },
22728
23740
  { provider: "api", coverage: 0, supported: false }
@@ -23055,7 +24067,7 @@ var SqliteSecurityRepository = class {
23055
24067
  };
23056
24068
 
23057
24069
  // ../../packages/persistence/src/repositories/shares.ts
23058
- import { randomUUID as randomUUID8 } from "crypto";
24070
+ import { randomUUID as randomUUID9 } from "crypto";
23059
24071
  var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
23060
24072
  var IN_CHUNK = 500;
23061
24073
  var KIND_ORDER = ["provider", "internal", "external", "ip"];
@@ -23311,7 +24323,7 @@ var SqliteSharesRepository = class {
23311
24323
  (id, destination_id, host, decision, created_at, updated_at)
23312
24324
  VALUES (:id, :destinationId, :host, :decision, :now, :now)`
23313
24325
  ).run({
23314
- id: randomUUID8(),
24326
+ id: randomUUID9(),
23315
24327
  destinationId,
23316
24328
  host: dest.host,
23317
24329
  decision,
@@ -23460,7 +24472,7 @@ var SqliteSharesRepository = class {
23460
24472
  let destinationId = destIds.get(hit.host);
23461
24473
  if (destinationId === void 0) {
23462
24474
  destStmt.run({
23463
- id: randomUUID8(),
24475
+ id: randomUUID9(),
23464
24476
  kind: hit.kind,
23465
24477
  name: hit.name,
23466
24478
  host: hit.host,
@@ -23476,7 +24488,7 @@ var SqliteSharesRepository = class {
23476
24488
  let endpointId = endpointIds.get(endpointKey);
23477
24489
  if (endpointId === void 0) {
23478
24490
  endpointStmt.run({
23479
- id: randomUUID8(),
24491
+ id: randomUUID9(),
23480
24492
  destinationId,
23481
24493
  method: hit.method,
23482
24494
  transport: hit.transport,
@@ -23489,7 +24501,7 @@ var SqliteSharesRepository = class {
23489
24501
  endpointIds.set(endpointKey, endpointId);
23490
24502
  }
23491
24503
  siteStmt.run({
23492
- id: randomUUID8(),
24504
+ id: randomUUID9(),
23493
24505
  endpointId,
23494
24506
  project: input.project,
23495
24507
  projectKey: input.projectKey,
@@ -23854,6 +24866,9 @@ function purgeSampleData(db) {
23854
24866
  }
23855
24867
 
23856
24868
  // ../../packages/persistence/src/database.ts
24869
+ var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
24870
+ "aka.persistence.unsafeTestOnlyRawHandle"
24871
+ );
23857
24872
  function linkHost(input, hostId) {
23858
24873
  return hostId ? { ...input, hostId } : input;
23859
24874
  }
@@ -23875,21 +24890,34 @@ function openWithPragmas(file2) {
23875
24890
  }
23876
24891
  return db;
23877
24892
  }
23878
- function backupLegacyStore(file2) {
23879
- const backup = `${file2}.legacy.${String(Date.now())}.bak`;
23880
- renameSync2(file2, backup);
23881
- tightenFile(backup);
23882
- for (const sidecar of dbSidecars(file2)) {
23883
- if (existsSync(sidecar)) rmSync2(sidecar);
24893
+ function backupLegacyStore(db, file2) {
24894
+ reapStalePartials(file2);
24895
+ const backup = backupPath(file2, "legacy");
24896
+ let snapshotted = false;
24897
+ let snapshotError;
24898
+ try {
24899
+ snapshotStore(db, backup);
24900
+ snapshotted = true;
24901
+ } catch (error51) {
24902
+ snapshotError = error51;
24903
+ } finally {
24904
+ db.close();
24905
+ }
24906
+ if (!snapshotted) {
24907
+ akaWarn(
24908
+ `Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
24909
+ );
24910
+ moveStoreAside(file2, backup);
24911
+ return backup;
23884
24912
  }
24913
+ discardStore(file2, backup);
23885
24914
  return backup;
23886
24915
  }
23887
24916
  function openAndInitialize(file2) {
23888
24917
  let db = openWithPragmas(file2);
23889
24918
  try {
23890
24919
  if (isForeignSqliteLineage(db)) {
23891
- db.close();
23892
- const backup = backupLegacyStore(file2);
24920
+ const backup = backupLegacyStore(db, file2);
23893
24921
  db = openWithPragmas(file2);
23894
24922
  akaWarn(
23895
24923
  `Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
@@ -23933,7 +24961,7 @@ function openAndInitialize(file2) {
23933
24961
  }
23934
24962
  function openLocalDatabase(dir) {
23935
24963
  ensureDataDirSync(dir);
23936
- const file2 = join(dir, DB_FILENAME);
24964
+ const file2 = join2(dir, DB_FILENAME);
23937
24965
  const {
23938
24966
  db,
23939
24967
  events,
@@ -24050,7 +25078,7 @@ function openLocalDatabase(dir) {
24050
25078
  const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
24051
25079
  if (!definitionId) continue;
24052
25080
  inspectionFindings.insertFinding({
24053
- id: randomUUID9(),
25081
+ id: randomUUID10(),
24054
25082
  auditEventId: record2.scanEvent.id,
24055
25083
  inspectionDefinitionId: definitionId,
24056
25084
  span: finding.span,
@@ -24156,7 +25184,9 @@ function openLocalDatabase(dir) {
24156
25184
  transaction,
24157
25185
  close: () => {
24158
25186
  db.close();
24159
- }
25187
+ },
25188
+ // Last, and a plain value rather than a getter, so `{ ...db }` carries it.
25189
+ [UNSAFE_TEST_ONLY_RAW_HANDLE]: db
24160
25190
  };
24161
25191
  }
24162
25192
 
@@ -24180,18 +25210,32 @@ var UserGrantPolicyProvider = class {
24180
25210
  }
24181
25211
  };
24182
25212
 
25213
+ // ../../packages/persistence/src/file-lock.ts
25214
+ import { randomUUID as randomUUID11 } from "crypto";
25215
+ import {
25216
+ closeSync,
25217
+ existsSync as existsSync2,
25218
+ openSync,
25219
+ readFileSync,
25220
+ rmSync as rmSync3,
25221
+ statSync as statSync2,
25222
+ writeFileSync as writeFileSync2
25223
+ } from "fs";
25224
+ import { hostname as hostname3 } from "os";
25225
+ var PARK = new Int32Array(new SharedArrayBuffer(4));
25226
+
24183
25227
  // ../../packages/persistence/src/finding-key.ts
24184
25228
  import { createHash as createHash3 } from "crypto";
24185
25229
 
24186
25230
  // ../../packages/persistence/src/fingerprint.ts
24187
25231
  import { createHmac, randomBytes } from "crypto";
24188
- import { existsSync as existsSync2, readFileSync } from "fs";
24189
- import { join as join2 } from "path";
25232
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
25233
+ import { join as join3 } from "path";
24190
25234
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
24191
- var KEY_FILENAME = "exception.key";
25235
+ var EXCEPTION_KEY_FILENAME = "exception.key";
24192
25236
  var KEY_MATERIAL_BYTES = 32;
24193
25237
  function keyFilePath(dataDir2) {
24194
- return join2(dataDir2, KEY_FILENAME);
25238
+ return join3(dataDir2, EXCEPTION_KEY_FILENAME);
24195
25239
  }
24196
25240
  function parseKeyFile(raw) {
24197
25241
  const parsed = JSON.parse(raw);
@@ -24229,8 +25273,8 @@ var FloorUnreadableError = class extends Error {
24229
25273
  }
24230
25274
  };
24231
25275
  function storedKeyVersionFloor(dataDir2) {
24232
- const file2 = join2(dataDir2, DB_FILENAME);
24233
- if (!existsSync2(file2)) return 0;
25276
+ const file2 = join3(dataDir2, DB_FILENAME);
25277
+ if (!existsSync3(file2)) return 0;
24234
25278
  let db;
24235
25279
  try {
24236
25280
  db = new DatabaseSync2(file2, { readOnly: true });
@@ -24255,18 +25299,36 @@ function storedKeyVersionFloor(dataDir2) {
24255
25299
  db?.close();
24256
25300
  }
24257
25301
  }
24258
- function writeKeyFile(dataDir2, key) {
25302
+ function serializeKey(key) {
25303
+ return JSON.stringify({ version: key.version, material: key.material.toString("base64") });
25304
+ }
25305
+ function createKeyFile(dataDir2, key) {
24259
25306
  ensureDataDirSync(dataDir2);
24260
25307
  const file2 = keyFilePath(dataDir2);
24261
- const body = JSON.stringify({ version: key.version, material: key.material.toString("base64") });
24262
- writeOwnerOnlyFileSync(file2, `${body}
24263
- `);
24264
- return key;
25308
+ if (createOwnerOnlyFileSync(file2, `${serializeKey(key)}
25309
+ `)) return key;
25310
+ const winner = readFingerprintKey(dataDir2);
25311
+ if (winner) {
25312
+ tightenFile(file2);
25313
+ return winner;
25314
+ }
25315
+ const occupant = classifyOccupant(file2);
25316
+ throw new KeyUnclaimableError(occupantMessage(file2, occupant.kind), occupant.cause);
25317
+ }
25318
+ function occupantMessage(file2, kind) {
25319
+ switch (kind) {
25320
+ case "symlink":
25321
+ return `exception key file is a symlink (${file2}); remove it so a key can be created`;
25322
+ case "gone":
25323
+ return "exception key file was removed while it was being created";
25324
+ case "unknown":
25325
+ return `exception key file (${file2}) is occupied but cannot be inspected; check the permissions on its directory`;
25326
+ }
24265
25327
  }
24266
25328
  function readFingerprintKey(dataDir2) {
24267
25329
  let raw;
24268
25330
  try {
24269
- raw = readFileSync(keyFilePath(dataDir2), "utf8");
25331
+ raw = readFileSync2(keyFilePath(dataDir2), "utf8");
24270
25332
  } catch (err) {
24271
25333
  if (err.code === "ENOENT") return null;
24272
25334
  throw err instanceof Error ? err : new Error(String(err));
@@ -24279,7 +25341,7 @@ function loadOrCreateFingerprintKey(dataDir2) {
24279
25341
  tightenFile(keyFilePath(dataDir2));
24280
25342
  return existing;
24281
25343
  }
24282
- return writeKeyFile(dataDir2, {
25344
+ return createKeyFile(dataDir2, {
24283
25345
  version: storedKeyVersionFloor(dataDir2) + 1,
24284
25346
  material: randomBytes(KEY_MATERIAL_BYTES)
24285
25347
  });
@@ -24292,21 +25354,21 @@ function fingerprintValue(key, raw) {
24292
25354
  import { renameSync as renameSync3 } from "fs";
24293
25355
  import { mkdir } from "fs/promises";
24294
25356
  import { homedir } from "os";
24295
- import { join as join3 } from "path";
25357
+ import { join as join4 } from "path";
24296
25358
  function defaultDataDir() {
24297
- return join3(homedir(), ".aka");
25359
+ return join4(homedir(), ".aka");
24298
25360
  }
24299
25361
  function settingsDir(base = defaultDataDir()) {
24300
- return join3(base, "settings");
25362
+ return join4(base, "settings");
24301
25363
  }
24302
25364
  function dataDir(base = defaultDataDir()) {
24303
- return join3(base, "data");
25365
+ return join4(base, "data");
24304
25366
  }
24305
25367
  function dbPath(base = defaultDataDir()) {
24306
- return join3(dataDir(base), "aka.db");
25368
+ return join4(dataDir(base), "aka.db");
24307
25369
  }
24308
25370
  function keysDir(base = defaultDataDir()) {
24309
- return join3(base, "keys");
25371
+ return join4(base, "keys");
24310
25372
  }
24311
25373
  function ensureLayoutDirSync(dir = defaultDataDir()) {
24312
25374
  ensureDataDirSync(dir);
@@ -24319,8 +25381,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
24319
25381
  for (const { name, dest } of moves) {
24320
25382
  try {
24321
25383
  ensureDataDirSync(dest);
24322
- const moved = join3(dest, name);
24323
- renameSync3(join3(base, name), moved);
25384
+ const moved = join4(dest, name);
25385
+ renameSync3(join4(base, name), moved);
24324
25386
  tightenFile(moved);
24325
25387
  } catch {
24326
25388
  }
@@ -24328,10 +25390,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
24328
25390
  }
24329
25391
 
24330
25392
  // ../../packages/persistence/src/settings.ts
24331
- import { readFileSync as readFileSync2 } from "fs";
24332
- import { join as join4 } from "path";
25393
+ import { readFileSync as readFileSync3 } from "fs";
25394
+ import { join as join5 } from "path";
25395
+ var SETTINGS_FILENAME = "settings.json";
24333
25396
  function readWorkspaceSettings(base = defaultDataDir()) {
24334
- const record2 = readJson(join4(settingsDir(base), "settings.json"));
25397
+ const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
24335
25398
  if (!record2) return defaultWorkspaceSettings();
24336
25399
  try {
24337
25400
  return WorkspaceSettings.parse(record2);
@@ -24342,7 +25405,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
24342
25405
  function readJson(file2) {
24343
25406
  let text;
24344
25407
  try {
24345
- text = readFileSync2(file2, "utf8");
25408
+ text = readFileSync3(file2, "utf8");
24346
25409
  } catch {
24347
25410
  return null;
24348
25411
  }
@@ -24463,13 +25526,18 @@ import { randomBytes as randomBytes2 } from "crypto";
24463
25526
  import {
24464
25527
  chmodSync as chmodSync2,
24465
25528
  mkdirSync as mkdirSync2,
24466
- readFileSync as readFileSync3,
25529
+ readFileSync as readFileSync4,
24467
25530
  renameSync as renameSync4,
24468
- rmSync as rmSync3,
24469
- statSync,
24470
- writeFileSync as writeFileSync2
25531
+ rmSync as rmSync4,
25532
+ statSync as statSync3,
25533
+ writeFileSync as writeFileSync3
24471
25534
  } from "fs";
24472
- import { join as join5 } from "path";
25535
+ import { join as join6 } from "path";
25536
+ var VAULT_OCCUPANT_REASON = {
25537
+ symlink: "the path is a symlink; remove it so a keyring can be created",
25538
+ gone: "the path was occupied but holds no keyring (removed while it was being created)",
25539
+ unknown: "the path is occupied but cannot be inspected; check the permissions on its directory"
25540
+ };
24473
25541
  var VaultKeyEpochMissingError = class extends Error {
24474
25542
  version;
24475
25543
  constructor(version2) {
@@ -24562,28 +25630,28 @@ function claimRotationLock(lock, owner) {
24562
25630
  throw asError(err);
24563
25631
  }
24564
25632
  try {
24565
- writeFileSync2(join5(lock, LOCK_OWNER_FILE), `${owner}
25633
+ writeFileSync3(join6(lock, LOCK_OWNER_FILE), `${owner}
24566
25634
  `, { mode: DATA_FILE_MODE });
24567
25635
  return true;
24568
25636
  } catch (err) {
24569
- rmSync3(lock, { recursive: true, force: true });
25637
+ rmSync4(lock, { recursive: true, force: true });
24570
25638
  throw asError(err);
24571
25639
  }
24572
25640
  }
24573
25641
  function acquireRotationLock(keysDir2) {
24574
- const lock = join5(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
25642
+ const lock = join6(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
24575
25643
  const owner = randomBytes2(16).toString("hex");
24576
25644
  if (claimRotationLock(lock, owner)) return { lock, owner };
24577
25645
  let held;
24578
25646
  try {
24579
- held = statSync(lock);
25647
+ held = statSync3(lock);
24580
25648
  } catch {
24581
25649
  throw new Error(ROTATION_IN_PROGRESS);
24582
25650
  }
24583
25651
  if (Date.now() - held.mtimeMs < ROTATION_LOCK_STALE_MS) throw new Error(ROTATION_IN_PROGRESS);
24584
25652
  const aside = `${lock}.stale.${owner}`;
24585
25653
  try {
24586
- const now = statSync(lock);
25654
+ const now = statSync3(lock);
24587
25655
  if (now.ino !== held.ino || now.mtimeMs !== held.mtimeMs) {
24588
25656
  throw new Error(ROTATION_IN_PROGRESS);
24589
25657
  }
@@ -24592,17 +25660,17 @@ function acquireRotationLock(keysDir2) {
24592
25660
  if (err instanceof Error && err.message === ROTATION_IN_PROGRESS) throw err;
24593
25661
  throw new Error(ROTATION_IN_PROGRESS, { cause: err });
24594
25662
  }
24595
- rmSync3(aside, { recursive: true, force: true });
25663
+ rmSync4(aside, { recursive: true, force: true });
24596
25664
  if (!claimRotationLock(lock, owner)) throw new Error(ROTATION_IN_PROGRESS);
24597
25665
  return { lock, owner };
24598
25666
  }
24599
25667
  function releaseRotationLock(lease) {
24600
25668
  try {
24601
- if (readFileSync3(join5(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
25669
+ if (readFileSync4(join6(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
24602
25670
  } catch {
24603
25671
  return;
24604
25672
  }
24605
- rmSync3(lease.lock, { recursive: true, force: true });
25673
+ rmSync4(lease.lock, { recursive: true, force: true });
24606
25674
  }
24607
25675
  function withRotationLock(keysDir2, work) {
24608
25676
  ensureDataDirSync(keysDir2);
@@ -24619,7 +25687,7 @@ var FileKeyProvider = class {
24619
25687
  this.#keysDir = keysDir2;
24620
25688
  }
24621
25689
  get filePath() {
24622
- return join5(this.#keysDir, VAULT_KEY_FILENAME);
25690
+ return join6(this.#keysDir, VAULT_KEY_FILENAME);
24623
25691
  }
24624
25692
  loadOrCreate() {
24625
25693
  return asAsync(() => {
@@ -24649,7 +25717,7 @@ var FileKeyProvider = class {
24649
25717
  #read() {
24650
25718
  let raw;
24651
25719
  try {
24652
- raw = readFileSync3(this.filePath, "utf8");
25720
+ raw = readFileSync4(this.filePath, "utf8");
24653
25721
  } catch (err) {
24654
25722
  if (err.code === "ENOENT") return null;
24655
25723
  throw err instanceof Error ? err : new Error(String(err));
@@ -24657,34 +25725,32 @@ var FileKeyProvider = class {
24657
25725
  return parseKeyring(raw);
24658
25726
  }
24659
25727
  /**
24660
- * First mint: the keyring is created at its FINAL path with a
24661
- * creation-exclusive write, so two processes racing a fresh machine cannot
24662
- * each mint a different epoch 1 with tmp + rename the loser's replace
24663
- * would orphan everything the winner had already sealed. On EEXIST the
24664
- * loser re-reads and adopts the winner's keyring; it minted nothing.
24665
- * Atomic replace is unnecessary here: nothing can be mid-read of a file
24666
- * that did not exist, and a torn exclusive write parses as corrupt on the
24667
- * next read and fails secure rather than being re-minted over.
25728
+ * First mint: the keyring is CREATED, never replaced, so two processes racing
25729
+ * a fresh machine cannot each mint a different epoch 1 — with tmp + rename
25730
+ * the loser's replace would orphan everything the winner had already sealed.
25731
+ * The loser re-reads and adopts the winner's keyring; it minted nothing.
25732
+ *
25733
+ * `createOwnerOnlyFileSync` publishes by link rather than by an exclusive open
25734
+ * at the final path, so the keyring never exists at zero length: a reader —
25735
+ * including the loser, re-reading in order to adopt sees the file absent or
25736
+ * whole, and never mistakes a live keyring for a corrupt one. A corrupt file
25737
+ * still throws from the parse and is never re-minted over.
24668
25738
  */
24669
25739
  #createExclusive() {
24670
25740
  ensureDataDirSync(this.#keysDir);
24671
25741
  const keyring = mintKeyring();
24672
- try {
24673
- writeFileSync2(this.filePath, `${serializeKeyring(keyring)}
24674
- `, {
24675
- flag: "wx",
24676
- mode: DATA_FILE_MODE
24677
- });
24678
- } catch (err) {
24679
- if (err.code !== "EEXIST") throw asError(err);
24680
- const winner = this.#read();
24681
- if (!winner) {
24682
- throw new Error("vault: key file vanished during first mint", { cause: err });
24683
- }
24684
- return winner;
25742
+ if (createOwnerOnlyFileSync(this.filePath, `${serializeKeyring(keyring)}
25743
+ `)) return keyring;
25744
+ const winner = this.#read();
25745
+ if (!winner) {
25746
+ const occupant = classifyOccupant(this.filePath);
25747
+ throw new KeyUnclaimableError(
25748
+ `vault: cannot create a key file at ${this.filePath} \u2014 ${VAULT_OCCUPANT_REASON[occupant.kind]}`,
25749
+ occupant.cause
25750
+ );
24685
25751
  }
24686
25752
  tightenFileMode(this.filePath);
24687
- return keyring;
25753
+ return winner;
24688
25754
  }
24689
25755
  /**
24690
25756
  * Atomic tmp + rename so a crash mid-write cannot truncate the keyring.
@@ -24695,7 +25761,7 @@ var FileKeyProvider = class {
24695
25761
  ensureDataDirSync(this.#keysDir);
24696
25762
  const file2 = this.filePath;
24697
25763
  const tmp = `${file2}.tmp`;
24698
- writeFileSync2(tmp, `${serializeKeyring(keyring)}
25764
+ writeFileSync3(tmp, `${serializeKeyring(keyring)}
24699
25765
  `, { mode: DATA_FILE_MODE });
24700
25766
  renameSync4(tmp, file2);
24701
25767
  tightenFileMode(file2);
@@ -24821,7 +25887,7 @@ function createKeyProvider(custody, keysDir2) {
24821
25887
  }
24822
25888
 
24823
25889
  // ../../packages/persistence/src/vault/vault.ts
24824
- import { randomBytes as randomBytes3, randomUUID as randomUUID10 } from "crypto";
25890
+ import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
24825
25891
  var CONSENT_ABSENT = /* @__PURE__ */ Symbol("aka.vault.consentAbsent");
24826
25892
  var UNAVAILABLE = /* @__PURE__ */ Symbol("aka.vault.unavailable");
24827
25893
  var VAULT_PURGE_POINTER_ID = "*";
@@ -24848,14 +25914,12 @@ function parsePointer(token) {
24848
25914
  var SecretVault = class {
24849
25915
  #repo;
24850
25916
  #keys;
24851
- #fingerprintKey;
24852
25917
  #isConsented;
24853
25918
  #verifyGrant;
24854
25919
  #now;
24855
25920
  constructor(deps) {
24856
25921
  this.#repo = deps.repo;
24857
25922
  this.#keys = deps.keys;
24858
- this.#fingerprintKey = deps.fingerprintKey;
24859
25923
  this.#isConsented = deps.isConsented;
24860
25924
  this.#verifyGrant = deps.verifyGrant;
24861
25925
  this.#now = deps.now ?? (() => Date.now());
@@ -24864,10 +25928,23 @@ var SecretVault = class {
24864
25928
  * Store a value and return the pointer that stands for it. The same value
24865
25929
  * always yields the same pointer on this machine — one row, one pointer id,
24866
25930
  * one category — which is what makes dedup and reuse counting work.
25931
+ *
25932
+ * `fingerprintKey` is the exception-key epoch this value's fingerprint is
25933
+ * derived under — a different key from the vault's, with different rotation
25934
+ * semantics. It is a parameter of the WRITE rather than a constructor dep,
25935
+ * and a thunk rather than a value, so that the only way to reach a key is to
25936
+ * store something: a read-only caller never names it, and a caller whose
25937
+ * source mints on absence mints only once consent has actually opened the
25938
+ * write. `refreshFingerprints` takes its key the same way, for the same
25939
+ * reason.
25940
+ *
25941
+ * Resolved once per call, so the fingerprint and the version it is recorded
25942
+ * under can never come from two different epochs.
24867
25943
  */
24868
- async tokenize(raw, meta3) {
25944
+ async tokenize(raw, meta3, fingerprintKey) {
24869
25945
  if (!this.#isConsented()) return CONSENT_ABSENT;
24870
- const valueFingerprint = fingerprintValue(this.#fingerprintKey, raw);
25946
+ const fpKey = fingerprintKey();
25947
+ const valueFingerprint = fingerprintValue(fpKey, raw);
24871
25948
  const existing = this.#repo.byValueFingerprint(valueFingerprint);
24872
25949
  const now = this.#now();
24873
25950
  if (existing) {
@@ -24883,7 +25960,7 @@ var SecretVault = class {
24883
25960
  {
24884
25961
  pointerId: base32Encode(pointerId),
24885
25962
  valueFingerprint,
24886
- fingerprintKeyVersion: this.#fingerprintKey.version,
25963
+ fingerprintKeyVersion: fpKey.version,
24887
25964
  keyVersion: version2,
24888
25965
  // Recorded so the row stays OPENABLE if the wire-format constant ever
24889
25966
  // moves: it is part of this row's AEAD AAD. It is not a tag input —
@@ -25127,7 +26204,7 @@ var SecretVault = class {
25127
26204
  purgeVault() {
25128
26205
  const destroyed = this.#repo.purgeAll();
25129
26206
  this.#repo.recordDeref({
25130
- id: randomUUID10(),
26207
+ id: randomUUID12(),
25131
26208
  pointerId: VAULT_PURGE_POINTER_ID,
25132
26209
  at: this.#now(),
25133
26210
  target: "human",
@@ -25196,7 +26273,7 @@ var SecretVault = class {
25196
26273
  }
25197
26274
  #audit(pointerId, opts, outcome) {
25198
26275
  this.#repo.recordDeref({
25199
- id: randomUUID10(),
26276
+ id: randomUUID12(),
25200
26277
  pointerId,
25201
26278
  at: this.#now(),
25202
26279
  target: opts.target,
@@ -25211,15 +26288,15 @@ var SecretVault = class {
25211
26288
  };
25212
26289
 
25213
26290
  // ../../packages/persistence/src/warn-era-cap.ts
25214
- import { existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
25215
- import { join as join6 } from "path";
26291
+ import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
26292
+ import { join as join7 } from "path";
25216
26293
  var MARKER = "warn-era-capped";
25217
26294
  function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
25218
26295
  if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
25219
- const marker = join6(dataDir2, MARKER);
25220
- if (existsSync3(marker)) return { capped: 0, skipped: "already-run" };
26296
+ const marker = join7(dataDir2, MARKER);
26297
+ if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
25221
26298
  const capped = db.policies.capCategoryActions();
25222
- writeFileSync3(marker, `${new Date(Date.now()).toISOString()}
26299
+ writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
25223
26300
  `, { mode: DATA_FILE_MODE });
25224
26301
  return { capped };
25225
26302
  }
@@ -25283,11 +26360,11 @@ function providerFromModelId(modelId) {
25283
26360
  }
25284
26361
 
25285
26362
  // ../../packages/plugin-sdk/src/config.ts
25286
- function loadConfig(base = defaultDataDir()) {
26363
+ function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
25287
26364
  try {
25288
26365
  ensureLayoutDirSync(base);
25289
- const settingsFile = join7(settingsDir(base), "settings.json");
25290
- if (existsSync4(settingsFile)) tightenFile(settingsFile);
26366
+ const settingsFile = join8(settingsDir(base), "settings.json");
26367
+ if (existsSync5(settingsFile)) tightenFile(settingsFile);
25291
26368
  } catch {
25292
26369
  }
25293
26370
  migrateLegacyLayout(base);
@@ -25298,21 +26375,21 @@ function loadConfig(base = defaultDataDir()) {
25298
26375
  dbPath: dbPath(base),
25299
26376
  settingsDir: settingsDir(base),
25300
26377
  onboarded: settings.onboardedAt != null,
25301
- provider: resolveProviderSafe()
26378
+ provider: resolveProviderSafe(resolveProviderFn)
25302
26379
  };
25303
26380
  }
25304
- function resolveProviderSafe() {
26381
+ function resolveProviderSafe(resolveProviderFn) {
25305
26382
  try {
25306
- return resolveProvider();
26383
+ return resolveProviderFn();
25307
26384
  } catch {
25308
26385
  return { provider: "anthropic" };
25309
26386
  }
25310
26387
  }
25311
26388
 
25312
26389
  // ../../packages/plugin-sdk/src/config-inventory.ts
25313
- import { readdirSync, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
26390
+ import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
25314
26391
  import { homedir as homedir2 } from "os";
25315
- import { basename as basename2, join as join9 } from "path";
26392
+ import { basename as basename3, join as join10 } from "path";
25316
26393
 
25317
26394
  // ../../packages/detections/src/egress/registry.ts
25318
26395
  var EXTRACTOR_VERSION = "1";
@@ -27888,7 +28965,7 @@ var gcp_service_account_default = {
27888
28965
  severity: "critical",
27889
28966
  matcher: {
27890
28967
  type: "regex",
27891
- pattern: "\\b[a-z0-9][a-z0-9-]{2,60}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
28968
+ pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
27892
28969
  flags: "g"
27893
28970
  },
27894
28971
  examples: [
@@ -28309,8 +29386,8 @@ function scanText(text, ruleVersions) {
28309
29386
  }
28310
29387
 
28311
29388
  // ../../packages/plugin-sdk/src/repo.ts
28312
- import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
28313
- import { basename, dirname, isAbsolute, join as join8, sep as sep2 } from "path";
29389
+ import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
29390
+ import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
28314
29391
  function resolveRepoIdentity(cwd) {
28315
29392
  try {
28316
29393
  const root = findGitRoot(cwd);
@@ -28323,7 +29400,7 @@ function resolveRepoIdentity(cwd) {
28323
29400
  // win32) so the persistence layer's `/`-separated checkout-path patterns
28324
29401
  // (the ghost sweep + the read-side worktree filter) match it as written.
28325
29402
  url: url2 ?? headRoot.split(sep2).join("/"),
28326
- name: (url2 ? slugFromUrl(url2) : void 0) ?? basename(headRoot)
29403
+ name: (url2 ? slugFromUrl(url2) : void 0) ?? basename2(headRoot)
28327
29404
  };
28328
29405
  } catch {
28329
29406
  return void 0;
@@ -28343,36 +29420,36 @@ function resolveRepoNwo(cwd) {
28343
29420
  function findGitRoot(start) {
28344
29421
  let dir = start;
28345
29422
  for (; ; ) {
28346
- if (existsSync5(join8(dir, ".git"))) return dir;
28347
- const parent = dirname(dir);
29423
+ if (existsSync6(join9(dir, ".git"))) return dir;
29424
+ const parent = dirname2(dir);
28348
29425
  if (parent === dir) return void 0;
28349
29426
  dir = parent;
28350
29427
  }
28351
29428
  }
28352
29429
  function resolveGitContext(root) {
28353
- const dotGit = join8(root, ".git");
29430
+ const dotGit = join9(root, ".git");
28354
29431
  try {
28355
- if (statSync2(dotGit).isDirectory()) {
28356
- return { configPath: join8(dotGit, "config"), headRoot: root };
29432
+ if (statSync4(dotGit).isDirectory()) {
29433
+ return { configPath: join9(dotGit, "config"), headRoot: root };
28357
29434
  }
28358
29435
  } catch {
28359
29436
  return void 0;
28360
29437
  }
28361
29438
  const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
28362
29439
  if (!target) return void 0;
28363
- const gitdir = isAbsolute(target) ? target : join8(root, target);
28364
- if (existsSync5(join8(gitdir, "config"))) {
28365
- return { configPath: join8(gitdir, "config"), headRoot: root };
29440
+ const gitdir = isAbsolute(target) ? target : join9(root, target);
29441
+ if (existsSync6(join9(gitdir, "config"))) {
29442
+ return { configPath: join9(gitdir, "config"), headRoot: root };
28366
29443
  }
28367
- const commonRaw = safeRead(join8(gitdir, "commondir"))?.trim();
29444
+ const commonRaw = safeRead(join9(gitdir, "commondir"))?.trim();
28368
29445
  if (!commonRaw) return void 0;
28369
- const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join8(gitdir, commonRaw);
28370
- const headRoot = basename(commonGitDir) === ".git" ? dirname(commonGitDir) : root;
28371
- return { configPath: join8(commonGitDir, "config"), headRoot };
29446
+ const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join9(gitdir, commonRaw);
29447
+ const headRoot = basename2(commonGitDir) === ".git" ? dirname2(commonGitDir) : root;
29448
+ return { configPath: join9(commonGitDir, "config"), headRoot };
28372
29449
  }
28373
29450
  function safeRead(path) {
28374
29451
  try {
28375
- return readFileSync4(path, "utf8");
29452
+ return readFileSync5(path, "utf8");
28376
29453
  } catch {
28377
29454
  return void 0;
28378
29455
  }
@@ -28423,18 +29500,23 @@ function nwoFromUrl(url2) {
28423
29500
  }
28424
29501
 
28425
29502
  // ../../packages/plugin-sdk/src/events.ts
28426
- import { createHash as createHash4, randomUUID as randomUUID11 } from "crypto";
29503
+ import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
29504
+
29505
+ // ../../packages/plugin-sdk/src/isolated-scan.ts
29506
+ import { existsSync as existsSync7 } from "fs";
29507
+ import { fileURLToPath } from "url";
29508
+ import { Worker } from "worker_threads";
28427
29509
 
28428
29510
  // ../../packages/plugin-sdk/src/inventory-resolver.ts
28429
- import { arch, hostname as hostname3, platform, release } from "os";
29511
+ import { arch, hostname as hostname4, platform, release } from "os";
28430
29512
  function resolveInventoryContext(input) {
28431
29513
  const host = {
28432
29514
  objectType: "host",
28433
29515
  // Stable-ish machine id; os/arch live in the descriptive bag (a
28434
29516
  // harder machine id can replace this without a schema change).
28435
- identityKey: hostname3(),
28436
- title: hostname3(),
28437
- attributes: { host_name: hostname3(), os: platform(), os_version: release(), arch: arch() }
29517
+ identityKey: hostname4(),
29518
+ title: hostname4(),
29519
+ attributes: { host_name: hostname4(), os: platform(), os_version: release(), arch: arch() }
28438
29520
  };
28439
29521
  const harnessAttributes = {};
28440
29522
  if (input.harnessVersion != null) harnessAttributes.harness_version = input.harnessVersion;
@@ -28455,27 +29537,53 @@ function resolveInventoryContext(input) {
28455
29537
  }
28456
29538
 
28457
29539
  // ../../packages/plugin-sdk/src/nudge.ts
28458
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
28459
- import { join as join10 } from "path";
29540
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
29541
+ import { join as join11 } from "path";
28460
29542
 
28461
29543
  // ../../packages/plugin-sdk/src/paths.ts
28462
- import { readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
28463
- import { basename as basename3, dirname as dirname2, sep as sep3 } from "path";
29544
+ import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
29545
+ import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
28464
29546
 
28465
29547
  // ../../packages/plugin-sdk/src/project-files.ts
28466
29548
  var import_ignore = __toESM(require_ignore(), 1);
28467
- import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
28468
- import { basename as basename4, join as join11, relative, sep as sep4 } from "path";
29549
+ import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
29550
+ import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
29551
+
29552
+ // ../../packages/plugin-sdk/src/provider-env-antigravity.ts
29553
+ var optionalBaseUrl2 = external_exports.preprocess((v) => {
29554
+ if (typeof v === "string" && v.trim() === "") return void 0;
29555
+ return v;
29556
+ }, external_exports.string().optional()).catch(void 0);
29557
+ var optionalFlag = external_exports.preprocess((v) => {
29558
+ if (typeof v !== "string") return false;
29559
+ const normalized = v.trim().toLowerCase();
29560
+ return normalized !== "" && normalized !== "0" && normalized !== "false";
29561
+ }, external_exports.boolean()).catch(false);
29562
+ var antigravityProviderEnvShape = {
29563
+ GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
29564
+ GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
29565
+ };
29566
+ var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
29567
+
29568
+ // ../../packages/plugin-sdk/src/provider-env-codex.ts
29569
+ var optionalBaseUrl3 = external_exports.preprocess((v) => {
29570
+ if (typeof v === "string" && v.trim() === "") return void 0;
29571
+ return v;
29572
+ }, external_exports.string().optional()).catch(void 0);
29573
+ var codexProviderEnvShape = {
29574
+ OPENAI_BASE_URL: optionalBaseUrl3
29575
+ };
29576
+ var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
28469
29577
 
28470
29578
  // ../../packages/plugin-sdk/src/runtime.ts
28471
- import { randomUUID as randomUUID12 } from "crypto";
29579
+ import { randomUUID as randomUUID14 } from "crypto";
28472
29580
 
28473
29581
  // ../../packages/plugin-sdk/src/suppressions.ts
28474
29582
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
28475
29583
 
28476
29584
  // ../../packages/plugin-sdk/src/throttle.ts
28477
- import { mkdirSync as mkdirSync4, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
28478
- import { join as join12 } from "path";
29585
+ import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
29586
+ import { join as join13 } from "path";
28479
29587
 
28480
29588
  // ../../packages/plugin-sdk/src/tokenize.ts
28481
29589
  function redactedPlaceholder(category) {
@@ -28717,7 +29825,6 @@ function createVaultGlue(options) {
28717
29825
  const vault = new SecretVault({
28718
29826
  repo: db.secretVault,
28719
29827
  keys: createKeyProvider(settings.vaultKeyCustody, keysDir(base)),
28720
- fingerprintKey: loadOrCreateFingerprintKey(dir),
28721
29828
  // Read live so a revocation applies to the very next call, not the next
28722
29829
  // process.
28723
29830
  isConsented: () => isVaultConsentValid(readWorkspaceSettings(base).vaultConsent),
@@ -28738,8 +29845,10 @@ function createVaultGlue(options) {
28738
29845
  return decision.allow;
28739
29846
  }
28740
29847
  });
29848
+ let fingerprintKey;
29849
+ const fingerprintKeyForWrite = () => fingerprintKey ??= loadOrCreateFingerprintKey(dir);
28741
29850
  const vaultWithSightings = {
28742
- tokenize: (raw, meta3) => vault.tokenize(raw, meta3),
29851
+ tokenize: (raw, meta3) => vault.tokenize(raw, meta3, fingerprintKeyForWrite),
28743
29852
  detokenize: (token, opts) => vault.detokenize(token, opts),
28744
29853
  describePointer: (token) => vault.describePointer(token),
28745
29854
  resolvePointerIdentity: (token) => vault.resolvePointerIdentity(token),
@@ -28773,7 +29882,7 @@ var UNOPENABLE_VAULT = {
28773
29882
  };
28774
29883
 
28775
29884
  // ../../packages/plugin-runtime/src/standalone-gateway.ts
28776
- import { randomUUID as randomUUID13 } from "crypto";
29885
+ import { randomUUID as randomUUID15 } from "crypto";
28777
29886
 
28778
29887
  // ../../packages/plugin-runtime/src/recorder.ts
28779
29888
  var PLUGIN_RECORDER_BINARY = "plugin";
@@ -28935,7 +30044,7 @@ var StandaloneDataGateway = class {
28935
30044
  const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
28936
30045
  const installed = this.installedScanRules();
28937
30046
  const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
28938
- id: randomUUID13(),
30047
+ id: randomUUID15(),
28939
30048
  scope: "global",
28940
30049
  target: { ruleId },
28941
30050
  action,
@@ -29088,19 +30197,19 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
29088
30197
  }
29089
30198
 
29090
30199
  // ../../packages/plugin-runtime/src/handle-session-start.ts
29091
- import { randomUUID as randomUUID14 } from "crypto";
30200
+ import { randomUUID as randomUUID16 } from "crypto";
29092
30201
  var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
29093
30202
 
29094
30203
  // src/remediation/redact.ts
29095
- import { readFileSync as readFileSync9, realpathSync as realpathSync3, renameSync as renameSync5, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
30204
+ import { readFileSync as readFileSync10, realpathSync as realpathSync3, renameSync as renameSync5, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
29096
30205
  import { isAbsolute as isAbsolute2, relative as relative2, resolve } from "path";
29097
30206
 
29098
30207
  // src/history/transcripts.ts
29099
- import { readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
30208
+ import { readdirSync as readdirSync5, readFileSync as readFileSync9 } from "fs";
29100
30209
  import { homedir as homedir3 } from "os";
29101
- import { join as join13 } from "path";
30210
+ import { join as join14 } from "path";
29102
30211
  function transcriptsDir(home) {
29103
- return join13(home ?? homedir3(), ".claude", "projects");
30212
+ return join14(home ?? homedir3(), ".claude", "projects");
29104
30213
  }
29105
30214
  function isRecord(value) {
29106
30215
  return typeof value === "object" && value !== null;
@@ -29329,17 +30438,17 @@ function resolveRedactableArtifact(filePath, scope) {
29329
30438
  // src/history/tail.ts
29330
30439
  import { createHash as createHash5 } from "crypto";
29331
30440
  import {
29332
- closeSync,
30441
+ closeSync as closeSync2,
29333
30442
  fstatSync,
29334
30443
  mkdirSync as mkdirSync5,
29335
- openSync,
29336
- readFileSync as readFileSync10,
30444
+ openSync as openSync2,
30445
+ readFileSync as readFileSync11,
29337
30446
  readSync,
29338
- writeFileSync as writeFileSync7
30447
+ writeFileSync as writeFileSync8
29339
30448
  } from "fs";
29340
- import { join as join14 } from "path";
30449
+ import { join as join15 } from "path";
29341
30450
  function offsetsDir(dataDir2) {
29342
- return join14(dataDir2, "usage-offsets");
30451
+ return join15(dataDir2, "usage-offsets");
29343
30452
  }
29344
30453
  var SAFE_SESSION_ID = /^[A-Za-z0-9._-]+$/;
29345
30454
  function safeSessionId(sessionId) {
@@ -29349,11 +30458,11 @@ function safeSessionId(sessionId) {
29349
30458
  return createHash5("sha256").update(sessionId).digest("hex");
29350
30459
  }
29351
30460
  function offsetPath(dataDir2, sessionId) {
29352
- return join14(offsetsDir(dataDir2), safeSessionId(sessionId));
30461
+ return join15(offsetsDir(dataDir2), safeSessionId(sessionId));
29353
30462
  }
29354
30463
  function readOffset(dataDir2, sessionId) {
29355
30464
  try {
29356
- const raw = readFileSync10(offsetPath(dataDir2, sessionId), "utf8");
30465
+ const raw = readFileSync11(offsetPath(dataDir2, sessionId), "utf8");
29357
30466
  const parsed = JSON.parse(raw);
29358
30467
  if (typeof parsed === "object" && parsed !== null) {
29359
30468
  const rec = parsed;
@@ -29369,7 +30478,7 @@ function writeOffset(dataDir2, sessionId, value) {
29369
30478
  try {
29370
30479
  mkdirSync5(offsetsDir(dataDir2), { recursive: true, mode: DATA_DIR_MODE });
29371
30480
  const payload = value.lastPromptId !== void 0 ? { offset: value.offset, lastPromptId: value.lastPromptId } : { offset: value.offset };
29372
- writeFileSync7(offsetPath(dataDir2, sessionId), JSON.stringify(payload), {
30481
+ writeFileSync8(offsetPath(dataDir2, sessionId), JSON.stringify(payload), {
29373
30482
  mode: DATA_FILE_MODE
29374
30483
  });
29375
30484
  } catch {
@@ -29378,7 +30487,7 @@ function writeOffset(dataDir2, sessionId, value) {
29378
30487
  function readTail(transcriptPath, startOffset) {
29379
30488
  let fd;
29380
30489
  try {
29381
- fd = openSync(transcriptPath, "r");
30490
+ fd = openSync2(transcriptPath, "r");
29382
30491
  } catch {
29383
30492
  return { chunk: "", nextOffset: startOffset };
29384
30493
  }
@@ -29404,22 +30513,22 @@ function readTail(transcriptPath, startOffset) {
29404
30513
  return { chunk: "", nextOffset: startOffset };
29405
30514
  } finally {
29406
30515
  try {
29407
- closeSync(fd);
30516
+ closeSync2(fd);
29408
30517
  } catch {
29409
30518
  }
29410
30519
  }
29411
30520
  }
29412
30521
 
29413
30522
  // src/history/tail-scrub.ts
29414
- import { readFileSync as readFileSync11, renameSync as renameSync6, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync8 } from "fs";
30523
+ import { readFileSync as readFileSync12, renameSync as renameSync6, rmSync as rmSync6, statSync as statSync7, writeFileSync as writeFileSync9 } from "fs";
29415
30524
  var DEFAULT_MAX_SCRUB_BYTES = 32 * 1024 * 1024;
29416
30525
  async function scrubTranscriptTail(filePath, deps) {
29417
30526
  try {
29418
30527
  const realPath = resolveRedactableArtifact(filePath, deps.scope);
29419
30528
  if (realPath === null) return null;
29420
- const statBefore = statSync5(realPath);
30529
+ const statBefore = statSync7(realPath);
29421
30530
  if (statBefore.size > (deps.maxBytes ?? DEFAULT_MAX_SCRUB_BYTES)) return null;
29422
- const content = readFileSync11(realPath, "utf8");
30531
+ const content = readFileSync12(realPath, "utf8");
29423
30532
  const lines = content.split("\n");
29424
30533
  let rewritten = 0;
29425
30534
  for (const [i, line] of lines.entries()) {
@@ -29433,16 +30542,16 @@ async function scrubTranscriptTail(filePath, deps) {
29433
30542
  if (rewritten === 0) return { rewritten: 0 };
29434
30543
  const tmpPath = `${realPath}.aka-scrub.tmp`;
29435
30544
  try {
29436
- writeFileSync8(tmpPath, lines.join("\n"), { mode: statBefore.mode & 511 });
29437
- const statNow = statSync5(realPath);
30545
+ writeFileSync9(tmpPath, lines.join("\n"), { mode: statBefore.mode & 511 });
30546
+ const statNow = statSync7(realPath);
29438
30547
  if (statNow.size !== statBefore.size || statNow.mtimeMs !== statBefore.mtimeMs) {
29439
- rmSync5(tmpPath, { force: true, recursive: true });
30548
+ rmSync6(tmpPath, { force: true, recursive: true });
29440
30549
  return null;
29441
30550
  }
29442
30551
  renameSync6(tmpPath, realPath);
29443
30552
  } catch {
29444
30553
  try {
29445
- rmSync5(tmpPath, { force: true, recursive: true });
30554
+ rmSync6(tmpPath, { force: true, recursive: true });
29446
30555
  } catch {
29447
30556
  }
29448
30557
  return null;