@akasecurity/cli 0.9.9 → 0.9.10

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.
Files changed (110) hide show
  1. package/dist/cli.js +2923 -1482
  2. package/extension/manifest.json +1 -1
  3. package/native-host/host.js +2092 -1286
  4. package/native-host/scan-worker.js +278 -176
  5. package/native-host/sync.js +1182 -918
  6. package/package.json +4 -4
  7. package/web-ui/package.json +1 -1
  8. package/web-ui/packages/dashboard-ui/package.json +1 -0
  9. package/web-ui/web-ui/.next/BUILD_ID +1 -1
  10. package/web-ui/web-ui/.next/app-build-manifest.json +56 -56
  11. package/web-ui/web-ui/.next/app-path-routes-manifest.json +6 -6
  12. package/web-ui/web-ui/.next/build-manifest.json +2 -2
  13. package/web-ui/web-ui/.next/prerender-manifest.json +10 -10
  14. package/web-ui/web-ui/.next/server/app/(app)/activity/page.js +2 -2
  15. package/web-ui/web-ui/.next/server/app/(app)/activity/page.js.nft.json +1 -1
  16. package/web-ui/web-ui/.next/server/app/(app)/activity/page_client-reference-manifest.js +1 -1
  17. package/web-ui/web-ui/.next/server/app/(app)/data-shares/page.js +2 -2
  18. package/web-ui/web-ui/.next/server/app/(app)/data-shares/page.js.nft.json +1 -1
  19. package/web-ui/web-ui/.next/server/app/(app)/data-shares/page_client-reference-manifest.js +1 -1
  20. package/web-ui/web-ui/.next/server/app/(app)/detections/page.js +2 -2
  21. package/web-ui/web-ui/.next/server/app/(app)/detections/page.js.nft.json +1 -1
  22. package/web-ui/web-ui/.next/server/app/(app)/detections/page_client-reference-manifest.js +1 -1
  23. package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page.js +2 -2
  24. package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page.js.nft.json +1 -1
  25. package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page_client-reference-manifest.js +1 -1
  26. package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page.js +1 -1
  27. package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page.js.nft.json +1 -1
  28. package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page_client-reference-manifest.js +1 -1
  29. package/web-ui/web-ui/.next/server/app/(app)/exceptions/page.js +1 -1
  30. package/web-ui/web-ui/.next/server/app/(app)/exceptions/page.js.nft.json +1 -1
  31. package/web-ui/web-ui/.next/server/app/(app)/exceptions/page_client-reference-manifest.js +1 -1
  32. package/web-ui/web-ui/.next/server/app/(app)/findings/page.js +2 -2
  33. package/web-ui/web-ui/.next/server/app/(app)/findings/page.js.nft.json +1 -1
  34. package/web-ui/web-ui/.next/server/app/(app)/findings/page_client-reference-manifest.js +1 -1
  35. package/web-ui/web-ui/.next/server/app/(app)/inventory/page.js +2 -2
  36. package/web-ui/web-ui/.next/server/app/(app)/inventory/page.js.nft.json +1 -1
  37. package/web-ui/web-ui/.next/server/app/(app)/inventory/page_client-reference-manifest.js +1 -1
  38. package/web-ui/web-ui/.next/server/app/(app)/policies/page.js +2 -2
  39. package/web-ui/web-ui/.next/server/app/(app)/policies/page.js.nft.json +1 -1
  40. package/web-ui/web-ui/.next/server/app/(app)/policies/page_client-reference-manifest.js +1 -1
  41. package/web-ui/web-ui/.next/server/app/(app)/scan/page.js +3 -3
  42. package/web-ui/web-ui/.next/server/app/(app)/scan/page.js.nft.json +1 -1
  43. package/web-ui/web-ui/.next/server/app/(app)/scan/page_client-reference-manifest.js +1 -1
  44. package/web-ui/web-ui/.next/server/app/(app)/security/page.js +2 -2
  45. package/web-ui/web-ui/.next/server/app/(app)/security/page.js.nft.json +1 -1
  46. package/web-ui/web-ui/.next/server/app/(app)/security/page_client-reference-manifest.js +1 -1
  47. package/web-ui/web-ui/.next/server/app/(app)/settings/page.js +1 -1
  48. package/web-ui/web-ui/.next/server/app/(app)/settings/page.js.nft.json +1 -1
  49. package/web-ui/web-ui/.next/server/app/(app)/settings/page_client-reference-manifest.js +1 -1
  50. package/web-ui/web-ui/.next/server/app/(app)/updates/page.js +2 -2
  51. package/web-ui/web-ui/.next/server/app/(app)/updates/page.js.nft.json +1 -1
  52. package/web-ui/web-ui/.next/server/app/(app)/updates/page_client-reference-manifest.js +1 -1
  53. package/web-ui/web-ui/.next/server/app/(app)/vault/page.js +2 -2
  54. package/web-ui/web-ui/.next/server/app/(app)/vault/page.js.nft.json +1 -1
  55. package/web-ui/web-ui/.next/server/app/(app)/vault/page_client-reference-manifest.js +1 -1
  56. package/web-ui/web-ui/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  57. package/web-ui/web-ui/.next/server/app/_not-found.html +1 -1
  58. package/web-ui/web-ui/.next/server/app/_not-found.rsc +1 -1
  59. package/web-ui/web-ui/.next/server/app/index.html +1 -1
  60. package/web-ui/web-ui/.next/server/app/index.rsc +1 -1
  61. package/web-ui/web-ui/.next/server/app/page_client-reference-manifest.js +1 -1
  62. package/web-ui/web-ui/.next/server/app-paths-manifest.json +6 -6
  63. package/web-ui/web-ui/.next/server/chunks/389.js +903 -0
  64. package/web-ui/web-ui/.next/server/chunks/475.js +1 -1
  65. package/web-ui/web-ui/.next/server/chunks/667.js +894 -0
  66. package/web-ui/web-ui/.next/server/chunks/851.js +1 -0
  67. package/web-ui/web-ui/.next/server/chunks/{318.js → 950.js} +3 -3
  68. package/web-ui/web-ui/.next/server/functions-config-manifest.json +2 -2
  69. package/web-ui/web-ui/.next/server/middleware-manifest.json +5 -5
  70. package/web-ui/web-ui/.next/server/pages/404.html +1 -1
  71. package/web-ui/web-ui/.next/server/pages/500.html +1 -1
  72. package/web-ui/web-ui/.next/server/server-reference-manifest.js +1 -1
  73. package/web-ui/web-ui/.next/server/server-reference-manifest.json +1 -1
  74. package/web-ui/web-ui/.next/static/chunks/907-207847c57011ba44.js +1 -0
  75. package/web-ui/web-ui/.next/static/chunks/app/(app)/activity/page-e4f94a75c631ce86.js +1 -0
  76. package/web-ui/web-ui/.next/static/chunks/app/(app)/data-shares/page-d70fcc5612f380b7.js +1 -0
  77. package/web-ui/web-ui/.next/static/chunks/app/(app)/detections/page-e1823ce15bb6277d.js +1 -0
  78. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/[id]/page-0fcffe4b9dff201f.js +1 -0
  79. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/new/page-c95079a2acd284fd.js +1 -0
  80. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/page-4b9f034bd078f5d3.js +1 -0
  81. package/web-ui/web-ui/.next/static/chunks/app/(app)/findings/page-b97fc083884ef341.js +1 -0
  82. package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/page-e96e7e947ed5f279.js +1 -0
  83. package/web-ui/web-ui/.next/static/chunks/app/(app)/policies/page-b90748d139a546c9.js +1 -0
  84. package/web-ui/web-ui/.next/static/chunks/app/(app)/scan/page-7a46abe149271870.js +1 -0
  85. package/web-ui/web-ui/.next/static/chunks/app/(app)/security/page-f4a1ee202d6e60a4.js +1 -0
  86. package/web-ui/web-ui/.next/static/chunks/app/(app)/settings/page-2ec6d863f1a31792.js +1 -0
  87. package/web-ui/web-ui/.next/static/chunks/app/(app)/updates/page-b388003fb0d3efd1.js +1 -0
  88. package/web-ui/web-ui/.next/static/chunks/app/(app)/vault/page-6bda74d234a1376a.js +1 -0
  89. package/web-ui/web-ui/dist/scan-worker.js +278 -176
  90. package/web-ui/web-ui/package.json +1 -0
  91. package/web-ui/web-ui/.next/server/chunks/237.js +0 -884
  92. package/web-ui/web-ui/.next/server/chunks/793.js +0 -893
  93. package/web-ui/web-ui/.next/server/chunks/83.js +0 -1
  94. package/web-ui/web-ui/.next/static/chunks/907-b6ee7720ac5bf516.js +0 -1
  95. package/web-ui/web-ui/.next/static/chunks/app/(app)/activity/page-0f1263f7b62606c9.js +0 -1
  96. package/web-ui/web-ui/.next/static/chunks/app/(app)/data-shares/page-39fd8cad4b6b709f.js +0 -1
  97. package/web-ui/web-ui/.next/static/chunks/app/(app)/detections/page-b8386e543b783552.js +0 -1
  98. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/[id]/page-62c9e95207311f3d.js +0 -1
  99. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/new/page-9792760d5acc2de1.js +0 -1
  100. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/page-807c39bb900256bd.js +0 -1
  101. package/web-ui/web-ui/.next/static/chunks/app/(app)/findings/page-e3a531d9590a623b.js +0 -1
  102. package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/page-11d47cdf3619126f.js +0 -1
  103. package/web-ui/web-ui/.next/static/chunks/app/(app)/policies/page-15e6d747c4e794c7.js +0 -1
  104. package/web-ui/web-ui/.next/static/chunks/app/(app)/scan/page-b390a81c67dd6d32.js +0 -1
  105. package/web-ui/web-ui/.next/static/chunks/app/(app)/security/page-07913f82a7e29f67.js +0 -1
  106. package/web-ui/web-ui/.next/static/chunks/app/(app)/settings/page-0898d0d22c14bb45.js +0 -1
  107. package/web-ui/web-ui/.next/static/chunks/app/(app)/updates/page-ad9483e25680d8b4.js +0 -1
  108. package/web-ui/web-ui/.next/static/chunks/app/(app)/vault/page-04450aa587f0d5ef.js +0 -1
  109. /package/web-ui/web-ui/.next/static/{MbmJUh_qusg2UF-ZJLjuR → k7tk0MLkPITbi_x6JL01X}/_buildManifest.js +0 -0
  110. /package/web-ui/web-ui/.next/static/{MbmJUh_qusg2UF-ZJLjuR → k7tk0MLkPITbi_x6JL01X}/_ssgManifest.js +0 -0
@@ -500,6 +500,7 @@ import { createHash as createHash4 } from "crypto";
500
500
  // ../../packages/persistence/src/attached-derived.ts
501
501
  import { rmSync } from "fs";
502
502
  import { join } from "path";
503
+ var POLICY_CACHE_FILENAME = "policy-cache.json";
503
504
  var ATTACHED_FORWARD_STATE_FILENAME = "attached-state.json";
504
505
  var ATTACHED_FORWARD_DROPS_FILENAME = "attached-forward-drops.json";
505
506
 
@@ -600,6 +601,30 @@ var SQLITE_MIGRATIONS = [
600
601
  {
601
602
  tag: "0022_audit_inspection_ms",
602
603
  sql: "ALTER TABLE `audit_events` ADD `inspection_ms` integer GENERATED ALWAYS AS (json_extract(attributes, '$.inspection_ms')) VIRTUAL;"
604
+ },
605
+ {
606
+ tag: "0023_secret_vault_user_authorized",
607
+ sql: "ALTER TABLE `secret_vault` ADD `user_authorized` integer DEFAULT 0 NOT NULL;"
608
+ },
609
+ {
610
+ tag: "0024_finding_resolution_key_created_index",
611
+ sql: "DROP INDEX IF EXISTS `idx_finding_resolution_key`;--> statement-breakpoint\nCREATE INDEX `idx_finding_resolution_key_created` ON `finding_resolution` (`finding_key`,`created_at`);"
612
+ },
613
+ {
614
+ tag: "0025_audit_capture_attribute_columns",
615
+ sql: "ALTER TABLE `audit_events` ADD `source_tool` text GENERATED ALWAYS AS (json_extract(attributes, '$.source_tool')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `repo` text GENERATED ALWAYS AS (json_extract(attributes, '$.repo')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `file_path` text GENERATED ALWAYS AS (json_extract(attributes, '$.file_path')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `tool_name` text GENERATED ALWAYS AS (json_extract(attributes, '$.tool_name')) VIRTUAL;"
616
+ },
617
+ {
618
+ tag: "0026_audit_llm_call_usage_columns",
619
+ sql: "ALTER TABLE `audit_events` ADD `service_tier` text GENERATED ALWAYS AS (json_extract(attributes, '$.service_tier')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `ephemeral_1h_input_tokens` integer GENERATED ALWAYS AS (json_extract(attributes, '$.ephemeral_1h_input_tokens')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `ephemeral_5m_input_tokens` integer GENERATED ALWAYS AS (json_extract(attributes, '$.ephemeral_5m_input_tokens')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `web_search_requests` integer GENERATED ALWAYS AS (json_extract(attributes, '$.web_search_requests')) VIRTUAL;"
620
+ },
621
+ {
622
+ tag: "0027_audit_llm_usage_index",
623
+ sql: "CREATE INDEX `idx_audit_llm_usage` ON `audit_events` (`started_at`,`root_session_id`,`provider`,`model`,`service_tier`,`input_tokens`,`output_tokens`,`cache_creation_input_tokens`,`cache_read_input_tokens`,`ephemeral_1h_input_tokens`,`ephemeral_5m_input_tokens`,`web_search_requests`) WHERE event_type = 'llm_call' AND attributes IS NOT NULL;"
624
+ },
625
+ {
626
+ tag: "0028_activity_session_probe_indexes",
627
+ sql: "CREATE INDEX `idx_audit_session_prompt` ON `audit_events` (`root_session_id`) WHERE event_type = 'prompt';--> statement-breakpoint\nCREATE INDEX `idx_audit_session_share` ON `audit_events` (`root_session_id`) WHERE event_type = 'share';--> statement-breakpoint\nCREATE INDEX `idx_audit_ended_at` ON `audit_events` (`ended_at`,`root_session_id`) WHERE ended_at IS NOT NULL;--> statement-breakpoint\nCREATE INDEX `idx_audit_session_ended` ON `audit_events` (`root_session_id`,`ended_at`) WHERE ended_at IS NOT NULL;--> statement-breakpoint\n-- Expression index for the activity list's turns rollup: a live-captured\n-- session's turns are the DISTINCT `run_key` across its `llm_call` leaves, and\n-- carrying the extracted key in the index answers that count from the index\n-- alone instead of parsing every leaf's attribute bag. Written by hand, as\n-- 0013's `idx_audit_code_change_path` was: drizzle-kit cannot emit an\n-- expression containing a comma, so this index is not declared in sqlite.ts.\nCREATE INDEX `idx_audit_session_run_key` ON `audit_events` (`root_session_id`, json_extract(`attributes`, '$.run_key')) WHERE `event_type` = 'llm_call';\n"
603
628
  }
604
629
  ];
605
630
 
@@ -22256,6 +22281,26 @@ var AttachTokenResponse = external_exports.union([
22256
22281
  AttachTokenExpired,
22257
22282
  external_exports.object({ status: printable(64) })
22258
22283
  ]);
22284
+ var DeviceCommandKind = external_exports.enum(["shares_rescan"]);
22285
+ var DeviceCommand = external_exports.object({
22286
+ id: printable(128).min(1),
22287
+ kind: DeviceCommandKind,
22288
+ issuedAt: printable(64).min(1),
22289
+ expiresAt: printable(64).min(1)
22290
+ }).strict();
22291
+ var DeviceCommandPollResponse = external_exports.object({ command: DeviceCommand.nullable() });
22292
+ var DeviceCommandFailureReason = external_exports.enum(["scan_failed", "no_projects", "expired"]);
22293
+ var DeviceCommandAckBody = external_exports.discriminatedUnion("outcome", [
22294
+ external_exports.object({
22295
+ outcome: external_exports.literal("reported"),
22296
+ projectsScanned: external_exports.number().int().nonnegative()
22297
+ }).strict(),
22298
+ external_exports.object({
22299
+ outcome: external_exports.literal("failed"),
22300
+ reason: DeviceCommandFailureReason,
22301
+ projectsScanned: external_exports.number().int().nonnegative()
22302
+ }).strict()
22303
+ ]);
22259
22304
 
22260
22305
  // ../../packages/schema/src/zod/registry.ts
22261
22306
  var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
@@ -22422,7 +22467,7 @@ var PackManifest = external_exports.object({
22422
22467
  }).meta({ id: "PackManifest" });
22423
22468
 
22424
22469
  // ../../packages/schema/src/zod/detection.ts
22425
- var OriginEnum = external_exports.enum(["library"]).meta({ id: "OriginEnum" });
22470
+ var OriginEnum = external_exports.enum(["library", "custom"]).meta({ id: "OriginEnum" });
22426
22471
  var DetectionFilterEnum = external_exports.enum(["all", "library", "custom", "customized", "updates"]);
22427
22472
  var LibraryStateEnum = external_exports.enum(["new", "imported", "update"]).meta({ id: "LibraryStateEnum" });
22428
22473
  var DetectionCounts = external_exports.object({
@@ -22559,14 +22604,17 @@ function optional2(key, parsed2, raw) {
22559
22604
  function isStringArray(value) {
22560
22605
  return Array.isArray(value) && value.every((entry) => typeof entry === "string");
22561
22606
  }
22607
+ var ORIGIN_VALUES = { library: true, custom: true };
22608
+ function resolveOrigin(origin) {
22609
+ return origin != null && Object.hasOwn(ORIGIN_VALUES, origin) ? origin : "library";
22610
+ }
22562
22611
  function summaryToDetectionListItem(s) {
22563
22612
  return {
22564
22613
  id: `${s.namespace}/${s.packId}`,
22565
22614
  name: s.name,
22566
22615
  version: s.version,
22567
22616
  enabled: s.enabled,
22568
- origin: "library",
22569
- // v1: every installed pack is library origin
22617
+ origin: resolveOrigin(s.origin),
22570
22618
  namespace: s.namespace,
22571
22619
  packId: s.packId,
22572
22620
  ruleCount: s.ruleCount,
@@ -22618,7 +22666,7 @@ function rowToDetectionDetail(row, findingsLast30d, update) {
22618
22666
  name: row.name,
22619
22667
  version: row.version,
22620
22668
  enabled: row.enabled,
22621
- origin: "library",
22669
+ origin: resolveOrigin(row.origin),
22622
22670
  namespace: row.namespace,
22623
22671
  packId: row.packId,
22624
22672
  ruleCount: row.rules.length,
@@ -22638,16 +22686,20 @@ function splitDetectionId(id) {
22638
22686
  }
22639
22687
  function buildDetectionsList(summaries, query) {
22640
22688
  const withUpdate = summaries.filter((s) => s.latestVersion != null);
22689
+ const originOf = (s) => resolveOrigin(s.origin);
22641
22690
  const counts = {
22642
22691
  all: summaries.length,
22643
- library: summaries.length,
22644
- // all origin=library in v1
22645
- custom: 0,
22692
+ library: summaries.filter((s) => originOf(s) === "library").length,
22693
+ custom: summaries.filter((s) => originOf(s) === "custom").length,
22694
+ // No origin member produces this, so it is 0 BY CONSTRUCTION rather than by
22695
+ // omission: `customized` would mean a LIBRARY pack whose rules were edited in
22696
+ // place, and that state does not exist — editing a library pack forks it. See
22697
+ // OriginEnum.
22646
22698
  customized: 0,
22647
22699
  updates: withUpdate.length
22648
22700
  };
22649
22701
  const filter = query.filter;
22650
- let filtered = filter === "custom" || filter === "customized" ? [] : filter === "updates" ? [...withUpdate] : [...summaries];
22702
+ let filtered = filter === "customized" ? [] : filter === "custom" ? summaries.filter((s) => originOf(s) === "custom") : filter === "library" ? summaries.filter((s) => originOf(s) === "library") : filter === "updates" ? [...withUpdate] : [...summaries];
22651
22703
  if (query.q) {
22652
22704
  const q = query.q.toLowerCase();
22653
22705
  filtered = filtered.filter(
@@ -22727,8 +22779,9 @@ var Event = external_exports.object({
22727
22779
  metadata: EventMetadata.optional()
22728
22780
  }).meta({ id: "Event" });
22729
22781
  var IngestEvent = Event.meta({ id: "IngestEvent" });
22782
+ var INGEST_BATCH_MAX = 100;
22730
22783
  var IngestBatch = external_exports.object({
22731
- events: external_exports.array(IngestEvent).min(1).max(100),
22784
+ events: external_exports.array(IngestEvent).min(1).max(INGEST_BATCH_MAX),
22732
22785
  // Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
22733
22786
  // additionally rejects any event whose contentHash the store has already
22734
22787
  // recorded — for re-runnable bulk ingest (worktree scan, transcript
@@ -23284,380 +23337,11 @@ var PatchInstalledPackRequest = external_exports.object({
23284
23337
  message: "At least one field must be provided"
23285
23338
  }).meta({ id: "PatchInstalledPackRequest" });
23286
23339
 
23287
- // ../../packages/schema/src/zod/vault.ts
23288
- var POINTER_FORMAT_VERSION = 2;
23289
- var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
23290
- var POINTER_TOKEN_PATTERN = new RegExp(
23291
- `\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
23292
- );
23293
- var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
23294
- function pointerTokenScanner() {
23295
- return new RegExp(POINTER_TOKEN_PATTERN.source, "g");
23296
- }
23297
- var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
23298
- var ParsedPointer = external_exports.object({
23299
- category: DetectionCategory,
23300
- keyVersion: external_exports.number().int().positive(),
23301
- pointerId: external_exports.string(),
23302
- tag: external_exports.string()
23303
- });
23304
- var VaultEntry = external_exports.object({
23305
- pointerId: external_exports.string(),
23306
- // The keyed HMAC of the raw value under `exception.key`, and the epoch it was
23307
- // derived under. This is what a reveal-to-model grant matches on, and it rotates
23308
- // independently of the vault encryption key below.
23309
- valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
23310
- fingerprintKeyVersion: external_exports.number().int().positive(),
23311
- // The vault-key epoch this row's ciphertext was sealed under.
23312
- keyVersion: external_exports.number().int().positive(),
23313
- // Fixed at first mint and never updated: the same value detected later under a
23314
- // different rule's category keeps the category it was minted with, so one
23315
- // value always produces exactly one wire token.
23316
- category: DetectionCategory,
23317
- ruleId: external_exports.string(),
23318
- // Partial-reveal preview for badges and listings. Never the raw value.
23319
- maskedMatch: external_exports.string(),
23320
- provider: external_exports.string().optional(),
23321
- ciphertext: external_exports.string(),
23322
- nonce: external_exports.string(),
23323
- authTag: external_exports.string(),
23324
- // How many times this value has been detected on this machine — the reuse
23325
- // signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
23326
- occurrenceCount: external_exports.number().int().nonnegative(),
23327
- firstSeen: external_exports.string(),
23328
- lastSeen: external_exports.string()
23329
- });
23330
- var PointerDescriptor = external_exports.object({
23331
- category: DetectionCategory,
23332
- provider: external_exports.string().optional(),
23333
- maskedMatch: external_exports.string(),
23334
- occurrences: external_exports.number().int().nonnegative(),
23335
- firstSeen: external_exports.string(),
23336
- lastSeen: external_exports.string()
23337
- });
23338
- var PointerIdentity = external_exports.object({
23339
- ruleId: external_exports.string(),
23340
- valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
23341
- fingerprintKeyVersion: external_exports.number().int().positive()
23342
- });
23343
- var DetokenizeTarget = external_exports.enum(["human", "model"]);
23344
- var VaultDerefReason = external_exports.enum([
23345
- "display",
23346
- "explicit-reveal",
23347
- "view-render",
23348
- "model-input",
23349
- "remediation",
23350
- "purge"
23351
- ]);
23352
- var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
23353
- var VaultDeref = external_exports.object({
23354
- id: external_exports.guid(),
23355
- pointerId: external_exports.string(),
23356
- at: external_exports.string(),
23357
- target: DetokenizeTarget,
23358
- reason: VaultDerefReason,
23359
- outcome: VaultDerefOutcome,
23360
- // Present only on a model-target crossing that a reveal grant authorized.
23361
- grantId: external_exports.string().optional(),
23362
- // How many pointers ONE batched render resolved. 1 for unbatched rows. Named
23363
- // apart from VaultEntry.occurrenceCount, which counts detections of a value.
23364
- pointerCount: external_exports.number().int().positive().default(1)
23365
- });
23366
- var VaultSightingKind = external_exports.enum([
23367
- "prompt",
23368
- "tool-input",
23369
- "tool-output",
23370
- "file",
23371
- "transcript"
23372
- ]);
23373
- var VaultSighting = external_exports.object({
23374
- location: external_exports.string(),
23375
- kind: VaultSightingKind,
23376
- firstSeen: external_exports.string(),
23377
- lastSeen: external_exports.string()
23378
- });
23379
- var VaultInventoryEntry = external_exports.object({
23380
- pointerId: external_exports.string(),
23381
- category: DetectionCategory,
23382
- provider: external_exports.string().optional(),
23383
- maskedMatch: external_exports.string(),
23384
- occurrences: external_exports.number().int().nonnegative(),
23385
- firstSeen: external_exports.string(),
23386
- lastSeen: external_exports.string(),
23387
- // The active reveal-to-model grant covering this value, when one exists —
23388
- // the inventory badges it, the row links to revocation.
23389
- revealGrantId: external_exports.string().nullable(),
23390
- sightings: external_exports.array(VaultSighting)
23391
- });
23392
- var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
23393
- var DEFAULT_VAULT_DEREFS_LIMIT = 50;
23394
- var MAX_VAULT_PAGE_LIMIT = 200;
23395
- var ListVaultInventoryQuery = external_exports.object({
23396
- limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
23397
- // Opaque; names the last row of the page just served.
23398
- cursor: external_exports.string().optional()
23399
- });
23400
- var ListVaultInventoryResponse = external_exports.object({
23401
- // Vaulted values across the whole store, not just this page — cursor-
23402
- // independent, so paging never changes what the count claims.
23403
- totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
23404
- items: external_exports.array(VaultInventoryEntry),
23405
- // `null` once the last page is reached.
23406
- nextCursor: external_exports.string().nullable()
23407
- });
23408
- var ListVaultReuseQuery = external_exports.object({
23409
- limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
23410
- cursor: external_exports.string().optional()
23411
- });
23412
- var ListVaultReuseResponse = external_exports.object({
23413
- // Reused values across the whole store — the number the section's claim
23414
- // ("values detected in more than one place") is about.
23415
- totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
23416
- items: external_exports.array(VaultInventoryEntry),
23417
- nextCursor: external_exports.string().nullable()
23418
- });
23419
- var ListVaultDerefsQuery = external_exports.object({
23420
- // Include the batched, high-volume reasons (display, view-render). Omitted
23421
- // hides them and counts them into `hiddenBatched` instead, so the model
23422
- // crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
23423
- // over a Server Action, which preserves the type, never as a URL param.
23424
- includeBatched: external_exports.boolean().optional(),
23425
- limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
23426
- cursor: external_exports.string().optional()
23427
- });
23428
- var ListVaultDerefsResponse = external_exports.object({
23429
- items: external_exports.array(VaultDeref),
23430
- nextCursor: external_exports.string().nullable(),
23431
- // Display/view-render rows the query hid, over the WHOLE trail rather than
23432
- // this page — it is the count the "N hidden" line and its toggle speak for.
23433
- // Always 0 when `includeBatched` was set, since nothing was hidden.
23434
- hiddenBatched: external_exports.number().int().nonnegative()
23435
- });
23436
- var VaultKeyCustody = external_exports.string();
23437
- var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
23438
- var VAULT_CONSENT_VERSION = 1;
23439
- var VaultConsent = external_exports.object({
23440
- acknowledgedAt: external_exports.iso.datetime(),
23441
- version: external_exports.number().int().positive()
23442
- });
23443
-
23444
- // ../../packages/schema/src/zod/local.ts
23445
- var WORKSPACE_SETTINGS_SPEC_VERSION = 6;
23446
- var MODEL_JUDGE_PAYLOAD_VERSION = 1;
23447
- var HISTORY_SYNC_PAYLOAD_VERSION = 1;
23448
- var RunMode = external_exports.enum(["standalone", "attached"]);
23449
- var ControlPlaneConnection = external_exports.object({
23450
- endpoint: external_exports.string().min(1),
23451
- // Display name for the deployment, shown instead of the raw endpoint.
23452
- label: external_exports.string().min(1).optional(),
23453
- attachedAt: external_exports.iso.datetime()
23454
- }).meta({ id: "ControlPlaneConnection" });
23455
- var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
23456
- var HistoricalAccess = external_exports.enum(["full", "session-only"]);
23457
- var ModelJudgeConsent = external_exports.object({
23458
- acknowledgedAt: external_exports.iso.datetime(),
23459
- payloadVersion: external_exports.number().int().positive()
23460
- });
23461
- var HistorySyncConsent = external_exports.object({
23462
- acknowledgedAt: external_exports.iso.datetime(),
23463
- payloadVersion: external_exports.number().int().positive(),
23464
- endpoint: external_exports.string()
23465
- });
23466
- function isHistorySyncConsentValid(consent, endpoint) {
23467
- if (consent === void 0 || endpoint === void 0) return false;
23468
- return consent.payloadVersion === HISTORY_SYNC_PAYLOAD_VERSION && consent.endpoint === endpoint;
23469
- }
23470
- var WorkspaceSettings = external_exports.object({
23471
- specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
23472
- runMode: RunMode.default("standalone"),
23473
- // Present only while attached; a detach clears it. Its presence is what makes
23474
- // `runMode: 'attached'` mean anything — see isAttached.
23475
- controlPlane: ControlPlaneConnection.optional(),
23476
- policy: SimpleDetectionPolicy.default("redact"),
23477
- // Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
23478
- historicalAccess: HistoricalAccess.default("session-only"),
23479
- // In-place egress extraction on the scan paths; disable to stop all Data
23480
- // Shares writes.
23481
- dataSharesInPlace: external_exports.boolean().default(true),
23482
- // Consent to keep a RECOVERABLE encrypted copy of detected values in the local
23483
- // vault, instead of destroying them. Absent by default: this is a custody
23484
- // change from one-way redaction, so it is never an assumed grant on upgrade.
23485
- // Revoking stops future vaulting; it does not erase what is already stored —
23486
- // purging the vault is the eraser.
23487
- vaultConsent: VaultConsent.optional(),
23488
- // Where the vault master key lives.
23489
- vaultKeyCustody: VaultKeyCustody.default("file"),
23490
- // How a pointer renders in assistant prose on screen (see VaultInlineReveal).
23491
- vaultInlineReveal: VaultInlineReveal.default("masked"),
23492
- // Absent until /aka:setup completes; its presence is what "onboarded" means.
23493
- onboardedAt: external_exports.iso.datetime().optional(),
23494
- // Records that the user consented to sending findings to the model API for
23495
- // the /aka:setup judge, along with the payload-shape version they agreed to.
23496
- // Absent until granted; a stale payloadVersion means the consent no longer
23497
- // covers the current payload and must be re-granted.
23498
- modelJudgeConsent: ModelJudgeConsent.optional(),
23499
- // Records that the user consented to sending the activity already recorded on
23500
- // this machine to the deployment it is attached to, along with the payload
23501
- // shape and the endpoint they agreed to. Absent until granted, and a grant for
23502
- // a different endpoint or an older payload no longer counts.
23503
- historySyncConsent: HistorySyncConsent.optional()
23504
- });
23505
- function defaultWorkspaceSettings() {
23506
- return WorkspaceSettings.parse({});
23507
- }
23508
- function isAttached(settings) {
23509
- return settings.runMode === "attached" && settings.controlPlane !== void 0;
23510
- }
23511
- function toInventoryRow(input2, id, now) {
23512
- return {
23513
- id,
23514
- objectType: input2.objectType,
23515
- location: input2.location ?? null,
23516
- title: input2.title ?? null,
23517
- hostId: input2.hostId ?? null,
23518
- attributes: JSON.stringify(input2.attributes),
23519
- firstSeen: now,
23520
- lastSeen: now
23521
- };
23522
- }
23523
- function toSourceProjectRow(input2, id, now) {
23524
- return {
23525
- id,
23526
- url: input2.url,
23527
- name: input2.name ?? null,
23528
- attributes: JSON.stringify(input2.attributes),
23529
- firstSeen: now,
23530
- lastSeen: now
23531
- };
23532
- }
23533
- function toAuditEventRow(input2) {
23534
- return {
23535
- id: input2.id,
23536
- parentId: input2.parentId ?? null,
23537
- rootSessionId: input2.rootSessionId ?? null,
23538
- eventType: input2.eventType,
23539
- hostId: input2.hostId ?? null,
23540
- harnessId: input2.harnessId ?? null,
23541
- sourceProjectId: input2.sourceProjectId ?? null,
23542
- startedAt: isoToEpochMillis(input2.startedAt),
23543
- endedAt: input2.endedAt ? isoToEpochMillis(input2.endedAt) : null,
23544
- severity: input2.severity ?? null,
23545
- priority: input2.priority ?? null,
23546
- content: input2.content ?? null,
23547
- contentHash: input2.contentHash ?? null,
23548
- attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
23549
- };
23550
- }
23551
- function toClassifiedDataRow(input2, id) {
23552
- return {
23553
- id,
23554
- class: input2.class,
23555
- label: input2.label ?? null,
23556
- attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
23557
- };
23558
- }
23559
- function toInspectionDefinitionRow(input2, id) {
23560
- return {
23561
- id,
23562
- ruleId: input2.ruleId,
23563
- name: input2.name,
23564
- category: input2.category,
23565
- severity: input2.severity,
23566
- definition: input2.definition,
23567
- version: input2.version
23568
- };
23569
- }
23570
- function toInspectionFindingRow(input2) {
23571
- return {
23572
- id: input2.id,
23573
- auditEventId: input2.auditEventId,
23574
- inspectionDefinitionId: input2.inspectionDefinitionId,
23575
- classifiedDataId: input2.classifiedDataId ?? null,
23576
- spanStart: input2.span.start,
23577
- spanEnd: input2.span.end,
23578
- maskedMatch: input2.maskedMatch,
23579
- actionTaken: input2.actionTaken,
23580
- confidence: input2.confidence,
23581
- findingKey: input2.findingKey ?? null,
23582
- firstDetectedAt: input2.firstDetectedAt ? isoToEpochMillis(input2.firstDetectedAt) : null
23583
- };
23584
- }
23585
- function toCaptureAttributes(event) {
23586
- const metadata = event.metadata;
23587
- return {
23588
- source_tool: event.sourceTool,
23589
- ...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
23590
- ...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
23591
- ...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
23592
- ...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
23593
- ...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
23594
- ...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
23595
- ...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
23596
- ...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
23597
- ...metadata?.inspectionMs !== void 0 ? { inspection_ms: metadata.inspectionMs } : {},
23598
- // `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
23599
- // has ever populated either), but every legacy metadata key still rides
23600
- // the bag rather than being silently dropped — CaptureAttributes'
23601
- // `.catchall(z.unknown())` carries the long tail.
23602
- ...metadata?.model !== void 0 ? { model: metadata.model } : {},
23603
- ...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
23604
- };
23605
- }
23606
- function captureDefinitionVersion(finding2) {
23607
- return `capture/${finding2.category}/${finding2.severity}`;
23608
- }
23609
- function toCaptureDefinitionInput(finding2) {
23610
- return {
23611
- ruleId: finding2.ruleId,
23612
- version: captureDefinitionVersion(finding2),
23613
- name: finding2.ruleId,
23614
- category: finding2.category,
23615
- severity: finding2.severity,
23616
- definition: JSON.stringify({ ruleId: finding2.ruleId })
23617
- };
23618
- }
23619
-
23620
- // ../../packages/schema/src/zod/managed.ts
23621
- var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
23622
- var MANAGED_SETTINGS_SPEC_VERSION = 1;
23623
- var ManagedSettingKey = external_exports.enum([
23624
- "runMode",
23625
- "historicalAccess",
23626
- "vaultConsent",
23627
- "vaultKeyCustody",
23628
- "vaultInlineReveal",
23629
- "modelJudgeConsent",
23630
- "dataSharesInPlace"
23631
- ]).meta({ id: "ManagedSettingKey" });
23632
- var ManagedSettingsValues = external_exports.object({
23633
- runMode: external_exports.enum(["standalone", "attached"]).optional(),
23634
- controlPlane: external_exports.object({
23635
- endpoint: external_exports.string().min(1),
23636
- label: external_exports.string().min(1).optional()
23637
- }).optional(),
23638
- historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
23639
- vaultConsent: external_exports.boolean().optional(),
23640
- vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
23641
- vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
23642
- modelJudgeConsent: external_exports.boolean().optional(),
23643
- dataSharesInPlace: external_exports.boolean().optional()
23644
- }).meta({ id: "ManagedSettingsValues" });
23645
- var ManagedSettings = external_exports.object({
23646
- specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
23647
- // Shown on every locked control, so the user can tell an administrative
23648
- // decision from a bug. Absent renders as a generic "your organization".
23649
- organization: external_exports.string().min(1).optional(),
23650
- // What the administrator pinned.
23651
- values: ManagedSettingsValues.default({}),
23652
- // Which of those the user may not change. A key here with no matching value
23653
- // freezes whatever the user last chose; a value with no lock is a DEFAULT
23654
- // the user may still override. The two are separable on purpose.
23655
- lockedFields: external_exports.array(ManagedSettingKey).default([])
23656
- }).meta({ id: "ManagedSettings" });
23657
-
23658
23340
  // ../../packages/schema/src/zod/policy.ts
23659
23341
  var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
23660
23342
  var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
23343
+ var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
23344
+ var PolicyProvenance = external_exports.enum(["builtin", "authored"]).meta({ id: "PolicyProvenance" });
23661
23345
  var Policy = external_exports.object({
23662
23346
  id: external_exports.guid(),
23663
23347
  scope: PolicyScope,
@@ -23667,7 +23351,27 @@ var Policy = external_exports.object({
23667
23351
  customKeywords: external_exports.array(external_exports.string()).optional(),
23668
23352
  // Display name — optional so older policy rows without name still parse.
23669
23353
  // Added for the findings API (policy.name column migration).
23670
- name: external_exports.string().optional()
23354
+ name: external_exports.string().optional(),
23355
+ // Whether an AUTHORED policy governs this row's target — not a claim about
23356
+ // which row this is. A producer that collapses several rows onto one target
23357
+ // must carry the marker onto whichever row survives, or the collapse decides
23358
+ // the answer; a survivor may therefore be a built-in expansion still marked
23359
+ // 'authored' because an authored sibling targeted the same thing.
23360
+ // Optional so an older producer — and an older on-disk cache — still parses;
23361
+ // absent reads as 'builtin', which is the behaviour that predates the field.
23362
+ //
23363
+ // Deliberately NOT `kind`/PolicyKind: that name and that enum answer which
23364
+ // built-in archetype catalog entry a policy is, which every catalog surface
23365
+ // reads and which a caller may state. This one is a statement the PRODUCER
23366
+ // of a bundle makes about a row, and only the bundle builder ever stamps it
23367
+ // — the CRUD routes neither accept nor set it.
23368
+ //
23369
+ // A device consumes this in exactly one direction: an 'authored' policy
23370
+ // arriving from a control plane marks the rules it targets as not
23371
+ // locally re-assignable. That can only ever ADD a refusal, never relax one,
23372
+ // which is what makes it safe to honour from an unsigned cache — the same
23373
+ // test `prohibitedModels` passes and `reversibleRuleIds` fails.
23374
+ provenance: PolicyProvenance.optional()
23671
23375
  }).meta({ id: "Policy" });
23672
23376
  var PolicyBundle = external_exports.object({
23673
23377
  version: external_exports.string(),
@@ -23719,6 +23423,12 @@ var PolicyBundle = external_exports.object({
23719
23423
  customKeywords: external_exports.array(external_exports.string()),
23720
23424
  fetchedAt: external_exports.iso.datetime()
23721
23425
  }).meta({ id: "PolicyBundle" });
23426
+ var POLICY_BUNDLE_SHAPE_ID = [
23427
+ ...Object.keys(PolicyBundle.shape),
23428
+ ...Object.keys(Policy.shape).map((key) => `policies.${key}`),
23429
+ ...PolicyTarget.options.flatMap((member) => "shape" in member ? Object.keys(member.shape) : []).map((key) => `policies.target.${key}`),
23430
+ ...Object.keys(ExceptionBundleEntry.shape).map((key) => `exceptions.${key}`)
23431
+ ].sort().join(",");
23722
23432
  var OBSERVE_ONLY_CATEGORIES = ["config"];
23723
23433
  var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
23724
23434
  var CATEGORY_PEAK_SEVERITY = {
@@ -23739,9 +23449,11 @@ function severityFloorPolicy(category) {
23739
23449
  const peak = CATEGORY_PEAK_SEVERITY[category];
23740
23450
  return peak === "critical" || peak === "high" ? "warn" : "monitor";
23741
23451
  }
23742
- var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
23743
23452
  var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
23744
23453
  var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
23454
+ var RedactFallback = BuiltinPolicyId.extract(["monitor", "warn", "block"]).meta({
23455
+ id: "RedactFallback"
23456
+ });
23745
23457
  var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
23746
23458
  var BUILTIN_POLICY_SPECS = {
23747
23459
  monitor: {
@@ -23778,6 +23490,42 @@ var BUILTIN_POLICY_SPECS = {
23778
23490
  function builtinPolicyToAction(id) {
23779
23491
  return BUILTIN_POLICY_SPECS[id].action;
23780
23492
  }
23493
+ var PALETTE_WEAKEST_FIRST = [
23494
+ ...new Set(KNOWN_BUILTIN_IDS.map(builtinPolicyToAction))
23495
+ ];
23496
+ var BELOW_PALETTE = ACTION_TAKEN_KEYS.filter(
23497
+ (action) => !PALETTE_WEAKEST_FIRST.includes(action)
23498
+ );
23499
+ var ACTION_STRENGTH_ORDER = [
23500
+ ...BELOW_PALETTE,
23501
+ ...PALETTE_WEAKEST_FIRST
23502
+ ];
23503
+ function actionRank(action) {
23504
+ return ACTION_STRENGTH_ORDER.indexOf(action);
23505
+ }
23506
+ function isActionAtLeast(action, floor) {
23507
+ return actionRank(action) >= actionRank(floor);
23508
+ }
23509
+ function strongerAction(a, b) {
23510
+ return actionRank(a) >= actionRank(b) ? a : b;
23511
+ }
23512
+ function weakestBuiltinAtLeast(floor) {
23513
+ return KNOWN_BUILTIN_IDS.find((id) => isActionAtLeast(builtinPolicyToAction(id), floor)) ?? "block";
23514
+ }
23515
+ var PackPolicyFloor = external_exports.object({
23516
+ /**
23517
+ * The weakest archetype the device may assign. Stated as a BuiltinPolicyId
23518
+ * rather than a raw ActionTaken because that is the vocabulary the user
23519
+ * picks from — a floor a UI cannot name is one it cannot explain.
23520
+ */
23521
+ floor: BuiltinPolicyId,
23522
+ /**
23523
+ * True when the organization AUTHORED a policy governing this pack rather
23524
+ * than stating a minimum: it gave the answer, so the pack is not
23525
+ * re-assignable locally in either direction.
23526
+ */
23527
+ locked: external_exports.boolean()
23528
+ }).describe("PackPolicyFloor");
23781
23529
  var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
23782
23530
  (id) => !BUILTIN_POLICY_SPECS[id].reversible
23783
23531
  );
@@ -23835,6 +23583,402 @@ var PolicyStatsResponse = external_exports.object({
23835
23583
  detectionsGoverned: external_exports.number().int().nonnegative()
23836
23584
  }).meta({ id: "PolicyStatsResponse" });
23837
23585
 
23586
+ // ../../packages/schema/src/zod/vault.ts
23587
+ var POINTER_FORMAT_VERSION = 2;
23588
+ var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
23589
+ var POINTER_TOKEN_PATTERN = new RegExp(
23590
+ `\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
23591
+ );
23592
+ var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
23593
+ function pointerTokenScanner() {
23594
+ return new RegExp(POINTER_TOKEN_PATTERN.source, "g");
23595
+ }
23596
+ var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
23597
+ var ParsedPointer = external_exports.object({
23598
+ category: DetectionCategory,
23599
+ keyVersion: external_exports.number().int().positive(),
23600
+ pointerId: external_exports.string(),
23601
+ tag: external_exports.string()
23602
+ });
23603
+ var VaultEntry = external_exports.object({
23604
+ pointerId: external_exports.string(),
23605
+ // The keyed HMAC of the raw value under `exception.key`, and the epoch it was
23606
+ // derived under. This is what a reveal-to-model grant matches on, and it rotates
23607
+ // independently of the vault encryption key below.
23608
+ valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
23609
+ fingerprintKeyVersion: external_exports.number().int().positive(),
23610
+ // The vault-key epoch this row's ciphertext was sealed under.
23611
+ keyVersion: external_exports.number().int().positive(),
23612
+ // Fixed at first mint and never updated: the same value detected later under a
23613
+ // different rule's category keeps the category it was minted with, so one
23614
+ // value always produces exactly one wire token.
23615
+ category: DetectionCategory,
23616
+ ruleId: external_exports.string(),
23617
+ // Partial-reveal preview for badges and listings. Never the raw value.
23618
+ maskedMatch: external_exports.string(),
23619
+ provider: external_exports.string().optional(),
23620
+ ciphertext: external_exports.string(),
23621
+ nonce: external_exports.string(),
23622
+ authTag: external_exports.string(),
23623
+ // How many times this value has been detected on this machine — the reuse
23624
+ // signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
23625
+ occurrenceCount: external_exports.number().int().nonnegative(),
23626
+ // True when a PERSON asked for this value to be replaced — the surfaced-
23627
+ // secrets strike — rather than a pack enforcing its assignment. One value is
23628
+ // one row however many paths vault it, so this is what tells a policy sweep
23629
+ // that the row carries somebody's own instruction and not just an assignment
23630
+ // that has since been lowered. STICKY and MONOTONIC: a later automatic
23631
+ // vaulting of the same value must never clear it — what the user said about
23632
+ // the value does not expire.
23633
+ userAuthorized: external_exports.boolean(),
23634
+ firstSeen: external_exports.string(),
23635
+ lastSeen: external_exports.string()
23636
+ });
23637
+ var PointerDescriptor = external_exports.object({
23638
+ category: DetectionCategory,
23639
+ provider: external_exports.string().optional(),
23640
+ maskedMatch: external_exports.string(),
23641
+ occurrences: external_exports.number().int().nonnegative(),
23642
+ firstSeen: external_exports.string(),
23643
+ lastSeen: external_exports.string()
23644
+ });
23645
+ var PointerIdentity = external_exports.object({
23646
+ ruleId: external_exports.string(),
23647
+ valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
23648
+ fingerprintKeyVersion: external_exports.number().int().positive()
23649
+ });
23650
+ var DetokenizeTarget = external_exports.enum(["human", "model"]);
23651
+ var VaultDerefReason = external_exports.enum([
23652
+ "display",
23653
+ "explicit-reveal",
23654
+ "view-render",
23655
+ "model-input",
23656
+ "remediation",
23657
+ "purge"
23658
+ ]);
23659
+ var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
23660
+ var VaultDeref = external_exports.object({
23661
+ id: external_exports.guid(),
23662
+ pointerId: external_exports.string(),
23663
+ at: external_exports.string(),
23664
+ target: DetokenizeTarget,
23665
+ reason: VaultDerefReason,
23666
+ outcome: VaultDerefOutcome,
23667
+ // Present only on a model-target crossing that a reveal grant authorized.
23668
+ grantId: external_exports.string().optional(),
23669
+ // How many pointers ONE batched render resolved. 1 for unbatched rows. Named
23670
+ // apart from VaultEntry.occurrenceCount, which counts detections of a value.
23671
+ pointerCount: external_exports.number().int().positive().default(1)
23672
+ });
23673
+ var VaultSightingKind = external_exports.enum([
23674
+ "prompt",
23675
+ "tool-input",
23676
+ "tool-output",
23677
+ "file",
23678
+ "transcript"
23679
+ ]);
23680
+ var VaultSighting = external_exports.object({
23681
+ location: external_exports.string(),
23682
+ kind: VaultSightingKind,
23683
+ firstSeen: external_exports.string(),
23684
+ lastSeen: external_exports.string()
23685
+ });
23686
+ var VaultInventoryEntry = external_exports.object({
23687
+ pointerId: external_exports.string(),
23688
+ category: DetectionCategory,
23689
+ provider: external_exports.string().optional(),
23690
+ maskedMatch: external_exports.string(),
23691
+ occurrences: external_exports.number().int().nonnegative(),
23692
+ firstSeen: external_exports.string(),
23693
+ lastSeen: external_exports.string(),
23694
+ // The active reveal-to-model grant covering this value, when one exists —
23695
+ // the inventory badges it, the row links to revocation.
23696
+ revealGrantId: external_exports.string().nullable(),
23697
+ sightings: external_exports.array(VaultSighting)
23698
+ });
23699
+ var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
23700
+ var DEFAULT_VAULT_DEREFS_LIMIT = 50;
23701
+ var MAX_VAULT_PAGE_LIMIT = 200;
23702
+ var ListVaultInventoryQuery = external_exports.object({
23703
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
23704
+ // Opaque; names the last row of the page just served.
23705
+ cursor: external_exports.string().optional()
23706
+ });
23707
+ var ListVaultInventoryResponse = external_exports.object({
23708
+ // Vaulted values across the whole store, not just this page — cursor-
23709
+ // independent, so paging never changes what the count claims.
23710
+ totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
23711
+ items: external_exports.array(VaultInventoryEntry),
23712
+ // `null` once the last page is reached.
23713
+ nextCursor: external_exports.string().nullable()
23714
+ });
23715
+ var ListVaultReuseQuery = external_exports.object({
23716
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
23717
+ cursor: external_exports.string().optional()
23718
+ });
23719
+ var ListVaultReuseResponse = external_exports.object({
23720
+ // Reused values across the whole store — the number the section's claim
23721
+ // ("values detected in more than one place") is about.
23722
+ totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
23723
+ items: external_exports.array(VaultInventoryEntry),
23724
+ nextCursor: external_exports.string().nullable()
23725
+ });
23726
+ var ListVaultDerefsQuery = external_exports.object({
23727
+ // Include the batched, high-volume reasons (display, view-render). Omitted
23728
+ // hides them and counts them into `hiddenBatched` instead, so the model
23729
+ // crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
23730
+ // over a Server Action, which preserves the type, never as a URL param.
23731
+ includeBatched: external_exports.boolean().optional(),
23732
+ limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
23733
+ cursor: external_exports.string().optional()
23734
+ });
23735
+ var ListVaultDerefsResponse = external_exports.object({
23736
+ items: external_exports.array(VaultDeref),
23737
+ nextCursor: external_exports.string().nullable(),
23738
+ // Display/view-render rows the query hid, over the WHOLE trail rather than
23739
+ // this page — it is the count the "N hidden" line and its toggle speak for.
23740
+ // Always 0 when `includeBatched` was set, since nothing was hidden.
23741
+ hiddenBatched: external_exports.number().int().nonnegative()
23742
+ });
23743
+ var VaultKeyCustody = external_exports.string();
23744
+ var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
23745
+ var VAULT_CONSENT_VERSION = 1;
23746
+ var VaultConsent = external_exports.object({
23747
+ acknowledgedAt: external_exports.iso.datetime(),
23748
+ version: external_exports.number().int().positive()
23749
+ });
23750
+
23751
+ // ../../packages/schema/src/zod/local.ts
23752
+ var WORKSPACE_SETTINGS_SPEC_VERSION = 7;
23753
+ var MODEL_JUDGE_PAYLOAD_VERSION = 1;
23754
+ var HISTORY_SYNC_PAYLOAD_VERSION = 2;
23755
+ var RunMode = external_exports.enum(["standalone", "attached"]);
23756
+ var ControlPlaneConnection = external_exports.object({
23757
+ endpoint: external_exports.string().min(1),
23758
+ // Display name for the deployment, shown instead of the raw endpoint.
23759
+ label: external_exports.string().min(1).optional(),
23760
+ attachedAt: external_exports.iso.datetime()
23761
+ }).meta({ id: "ControlPlaneConnection" });
23762
+ var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
23763
+ var HistoricalAccess = external_exports.enum(["full", "session-only"]);
23764
+ var ModelJudgeConsent = external_exports.object({
23765
+ acknowledgedAt: external_exports.iso.datetime(),
23766
+ payloadVersion: external_exports.number().int().positive()
23767
+ });
23768
+ var HistorySyncConsent = external_exports.object({
23769
+ acknowledgedAt: external_exports.iso.datetime(),
23770
+ payloadVersion: external_exports.number().int().positive(),
23771
+ endpoint: external_exports.string()
23772
+ });
23773
+ function isHistorySyncConsentValid(consent, endpoint) {
23774
+ if (consent === void 0 || endpoint === void 0) return false;
23775
+ return consent.payloadVersion === HISTORY_SYNC_PAYLOAD_VERSION && consent.endpoint === endpoint;
23776
+ }
23777
+ var WorkspaceSettings = external_exports.object({
23778
+ specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
23779
+ runMode: RunMode.default("standalone"),
23780
+ // Present only while attached; a detach clears it. Its presence is what makes
23781
+ // `runMode: 'attached'` mean anything — see isAttached.
23782
+ controlPlane: ControlPlaneConnection.optional(),
23783
+ policy: SimpleDetectionPolicy.default("redact"),
23784
+ // Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
23785
+ historicalAccess: HistoricalAccess.default("session-only"),
23786
+ // In-place egress extraction on the scan paths; disable to stop all Data
23787
+ // Shares writes.
23788
+ dataSharesInPlace: external_exports.boolean().default(true),
23789
+ // Consent to keep a RECOVERABLE encrypted copy of detected values in the local
23790
+ // vault, instead of destroying them. Absent by default: this is a custody
23791
+ // change from one-way redaction, so it is never an assumed grant on upgrade.
23792
+ // Revoking stops future vaulting; it does not erase what is already stored —
23793
+ // purging the vault is the eraser.
23794
+ vaultConsent: VaultConsent.optional(),
23795
+ // Where the vault master key lives.
23796
+ vaultKeyCustody: VaultKeyCustody.default("file"),
23797
+ // How a pointer renders in assistant prose on screen (see VaultInlineReveal).
23798
+ vaultInlineReveal: VaultInlineReveal.default("masked"),
23799
+ // What a `redact` policy degrades to on a FIELD the host cannot rewrite in
23800
+ // place. Not a handling policy: the policy has already resolved to redact,
23801
+ // and this only says what happens when the host offers no channel to carry it
23802
+ // out — Antigravity's PreToolUse has no updatedInput at all, and Codex and
23803
+ // Claude Code decline to mask a field that EXECUTES because masking would
23804
+ // change what runs. Per FIELD rather than per host, so a host that can
23805
+ // rewrite some inputs keeps true redaction on those.
23806
+ //
23807
+ // Spelled in the built-in policy vocabulary rather than as a fresh enum, so
23808
+ // an attached machine's merge is `strongerAction` over the one action ladder
23809
+ // and no second rank order exists to drift from it. 'deny' is a host wire
23810
+ // word and stays out of the stored value.
23811
+ redactFallback: RedactFallback.default("warn"),
23812
+ // Absent until /aka:setup completes; its presence is what "onboarded" means.
23813
+ onboardedAt: external_exports.iso.datetime().optional(),
23814
+ // Records that the user consented to sending findings to the model API for
23815
+ // the /aka:setup judge, along with the payload-shape version they agreed to.
23816
+ // Absent until granted; a stale payloadVersion means the consent no longer
23817
+ // covers the current payload and must be re-granted.
23818
+ modelJudgeConsent: ModelJudgeConsent.optional(),
23819
+ // Records that the user consented to the DEFERRED send — the outbox — along
23820
+ // with the payload shape and the endpoint they agreed to. Since payload v2
23821
+ // that covers both the pre-attach backlog and undelivered captures (which
23822
+ // carry prompt/reply text in `content`); the key name predates the widening.
23823
+ // Absent until granted, and a grant for a different endpoint or an older
23824
+ // payload no longer counts.
23825
+ historySyncConsent: HistorySyncConsent.optional()
23826
+ });
23827
+ function defaultWorkspaceSettings() {
23828
+ return WorkspaceSettings.parse({});
23829
+ }
23830
+ function isAttached(settings) {
23831
+ return settings.runMode === "attached" && settings.controlPlane !== void 0;
23832
+ }
23833
+ function toInventoryRow(input2, id, now) {
23834
+ return {
23835
+ id,
23836
+ objectType: input2.objectType,
23837
+ location: input2.location ?? null,
23838
+ title: input2.title ?? null,
23839
+ hostId: input2.hostId ?? null,
23840
+ attributes: JSON.stringify(input2.attributes),
23841
+ firstSeen: now,
23842
+ lastSeen: now
23843
+ };
23844
+ }
23845
+ function toSourceProjectRow(input2, id, now) {
23846
+ return {
23847
+ id,
23848
+ url: input2.url,
23849
+ name: input2.name ?? null,
23850
+ attributes: JSON.stringify(input2.attributes),
23851
+ firstSeen: now,
23852
+ lastSeen: now
23853
+ };
23854
+ }
23855
+ function toAuditEventRow(input2) {
23856
+ return {
23857
+ id: input2.id,
23858
+ parentId: input2.parentId ?? null,
23859
+ rootSessionId: input2.rootSessionId ?? null,
23860
+ eventType: input2.eventType,
23861
+ hostId: input2.hostId ?? null,
23862
+ harnessId: input2.harnessId ?? null,
23863
+ sourceProjectId: input2.sourceProjectId ?? null,
23864
+ startedAt: isoToEpochMillis(input2.startedAt),
23865
+ endedAt: input2.endedAt ? isoToEpochMillis(input2.endedAt) : null,
23866
+ severity: input2.severity ?? null,
23867
+ priority: input2.priority ?? null,
23868
+ content: input2.content ?? null,
23869
+ contentHash: input2.contentHash ?? null,
23870
+ attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
23871
+ };
23872
+ }
23873
+ function toClassifiedDataRow(input2, id) {
23874
+ return {
23875
+ id,
23876
+ class: input2.class,
23877
+ label: input2.label ?? null,
23878
+ attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
23879
+ };
23880
+ }
23881
+ function toInspectionDefinitionRow(input2, id) {
23882
+ return {
23883
+ id,
23884
+ ruleId: input2.ruleId,
23885
+ name: input2.name,
23886
+ category: input2.category,
23887
+ severity: input2.severity,
23888
+ definition: input2.definition,
23889
+ version: input2.version
23890
+ };
23891
+ }
23892
+ function toInspectionFindingRow(input2) {
23893
+ return {
23894
+ id: input2.id,
23895
+ auditEventId: input2.auditEventId,
23896
+ inspectionDefinitionId: input2.inspectionDefinitionId,
23897
+ classifiedDataId: input2.classifiedDataId ?? null,
23898
+ spanStart: input2.span.start,
23899
+ spanEnd: input2.span.end,
23900
+ maskedMatch: input2.maskedMatch,
23901
+ actionTaken: input2.actionTaken,
23902
+ confidence: input2.confidence,
23903
+ findingKey: input2.findingKey ?? null,
23904
+ firstDetectedAt: input2.firstDetectedAt ? isoToEpochMillis(input2.firstDetectedAt) : null
23905
+ };
23906
+ }
23907
+ function toCaptureAttributes(event) {
23908
+ const metadata = event.metadata;
23909
+ return {
23910
+ source_tool: event.sourceTool,
23911
+ ...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
23912
+ ...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
23913
+ ...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
23914
+ ...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
23915
+ ...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
23916
+ ...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
23917
+ ...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
23918
+ ...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
23919
+ ...metadata?.inspectionMs !== void 0 ? { inspection_ms: metadata.inspectionMs } : {},
23920
+ // `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
23921
+ // has ever populated either), but every legacy metadata key still rides
23922
+ // the bag rather than being silently dropped — CaptureAttributes'
23923
+ // `.catchall(z.unknown())` carries the long tail.
23924
+ ...metadata?.model !== void 0 ? { model: metadata.model } : {},
23925
+ ...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
23926
+ };
23927
+ }
23928
+ function captureDefinitionVersion(finding2) {
23929
+ return `capture/${finding2.category}/${finding2.severity}`;
23930
+ }
23931
+ function toCaptureDefinitionInput(finding2) {
23932
+ return {
23933
+ ruleId: finding2.ruleId,
23934
+ version: captureDefinitionVersion(finding2),
23935
+ name: finding2.ruleId,
23936
+ category: finding2.category,
23937
+ severity: finding2.severity,
23938
+ definition: JSON.stringify({ ruleId: finding2.ruleId })
23939
+ };
23940
+ }
23941
+
23942
+ // ../../packages/schema/src/zod/managed.ts
23943
+ var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
23944
+ var MANAGED_SETTINGS_SPEC_VERSION = 1;
23945
+ var ManagedSettingKey = external_exports.enum([
23946
+ "runMode",
23947
+ "historicalAccess",
23948
+ "vaultConsent",
23949
+ "vaultKeyCustody",
23950
+ "vaultInlineReveal",
23951
+ "modelJudgeConsent",
23952
+ "dataSharesInPlace",
23953
+ "redactFallback"
23954
+ ]).meta({ id: "ManagedSettingKey" });
23955
+ var ManagedSettingsValues = external_exports.object({
23956
+ runMode: external_exports.enum(["standalone", "attached"]).optional(),
23957
+ controlPlane: external_exports.object({
23958
+ endpoint: external_exports.string().min(1),
23959
+ label: external_exports.string().min(1).optional()
23960
+ }).optional(),
23961
+ historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
23962
+ vaultConsent: external_exports.boolean().optional(),
23963
+ vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
23964
+ vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
23965
+ modelJudgeConsent: external_exports.boolean().optional(),
23966
+ dataSharesInPlace: external_exports.boolean().optional(),
23967
+ redactFallback: RedactFallback.optional()
23968
+ }).meta({ id: "ManagedSettingsValues" });
23969
+ var ManagedSettings = external_exports.object({
23970
+ specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
23971
+ // Shown on every locked control, so the user can tell an administrative
23972
+ // decision from a bug. Absent renders as a generic "your organization".
23973
+ organization: external_exports.string().min(1).optional(),
23974
+ // What the administrator pinned.
23975
+ values: ManagedSettingsValues.default({}),
23976
+ // Which of those the user may not change. A key here with no matching value
23977
+ // freezes whatever the user last chose; a value with no lock is a DEFAULT
23978
+ // the user may still override. The two are separable on purpose.
23979
+ lockedFields: external_exports.array(ManagedSettingKey).default([])
23980
+ }).meta({ id: "ManagedSettings" });
23981
+
23838
23982
  // ../../packages/schema/src/zod/project-files.ts
23839
23983
  var ProjectFileInput = external_exports.object({
23840
23984
  path: external_exports.string().min(1),
@@ -24080,10 +24224,12 @@ var DismissRecommendedActionResponse = external_exports.object({ id: external_ex
24080
24224
  var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
24081
24225
 
24082
24226
  // ../../packages/schema/src/zod/settings-action.ts
24227
+ var HistorySyncConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "HistorySyncConsentChoice" });
24228
+ var ModelJudgeConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "ModelJudgeConsentChoice" });
24083
24229
  var SaveSettingsInput = external_exports.object({
24084
24230
  historicalAccess: external_exports.string(),
24085
- modelJudgeConsent: external_exports.boolean(),
24086
- historySyncConsent: external_exports.boolean(),
24231
+ modelJudgeConsent: ModelJudgeConsentChoice,
24232
+ historySyncConsent: HistorySyncConsentChoice,
24087
24233
  vaultConsent: external_exports.string(),
24088
24234
  vaultInlineReveal: external_exports.string()
24089
24235
  });
@@ -24233,9 +24379,9 @@ function deriveReviewReasons(trust, transports) {
24233
24379
  if (transports.includes("http") || transports.includes("ws")) reasons.push("plaintext_transport");
24234
24380
  return reasons;
24235
24381
  }
24236
- function buildReviewInfo(trust, transports) {
24382
+ function buildReviewInfo(trust, transports, decided) {
24237
24383
  const reasons = deriveReviewReasons(trust, transports);
24238
- return { needsReview: reasons.length > 0, reasons };
24384
+ return { needsReview: reasons.length > 0 && !decided, reasons };
24239
24385
  }
24240
24386
  function distinctTransports(transports) {
24241
24387
  return Array.from(new Set(transports));
@@ -24457,8 +24603,8 @@ function readControlPlaneCredential(settingsDir2, connection) {
24457
24603
  }
24458
24604
 
24459
24605
  // ../../packages/persistence/src/database.ts
24460
- import { randomUUID as randomUUID10 } from "crypto";
24461
- import { join as join4, sep } from "path";
24606
+ import { randomUUID as randomUUID11 } from "crypto";
24607
+ import { dirname as dirname2, join as join7, sep } from "path";
24462
24608
  import { DatabaseSync } from "node:sqlite";
24463
24609
 
24464
24610
  // ../../packages/persistence/src/ids.ts
@@ -24723,6 +24869,10 @@ function allRows(stmt, params) {
24723
24869
  if (Array.isArray(params)) return stmt.all(...params);
24724
24870
  return stmt.all(params);
24725
24871
  }
24872
+ function* iterateRows(stmt, params) {
24873
+ const rows = params === void 0 ? stmt.iterate() : Array.isArray(params) ? stmt.iterate(...params) : stmt.iterate(params);
24874
+ for (const row of rows) yield row;
24875
+ }
24726
24876
  function getRow(stmt, params) {
24727
24877
  if (params === void 0) return stmt.get();
24728
24878
  if (Array.isArray(params)) return stmt.get(...params);
@@ -25191,10 +25341,17 @@ function ensureSyncedAtColumn(db, table) {
25191
25341
  if (!columns.includes("sync_claimed_at")) {
25192
25342
  db.exec(`ALTER TABLE ${table} ADD COLUMN sync_claimed_at integer`);
25193
25343
  }
25344
+ if (!columns.includes("outbox_owed")) {
25345
+ db.exec(`ALTER TABLE ${table} ADD COLUMN outbox_owed integer`);
25346
+ }
25194
25347
  db.exec(
25195
25348
  `CREATE INDEX IF NOT EXISTS idx_audit_events_sync
25196
25349
  ON audit_events (event_type, synced_at, sync_claimed_at, started_at)`
25197
25350
  );
25351
+ db.exec(
25352
+ `CREATE INDEX IF NOT EXISTS idx_audit_outbox_owed
25353
+ ON audit_events (event_type, synced_at, sync_claimed_at, started_at) WHERE outbox_owed = 1`
25354
+ );
25198
25355
  db.exec(
25199
25356
  `CREATE INDEX IF NOT EXISTS idx_audit_claimed
25200
25357
  ON audit_events (sync_claimed_at) WHERE sync_claimed_at IS NOT NULL`
@@ -25299,7 +25456,6 @@ function decodeKeysetCursor(cursor) {
25299
25456
  // ../../packages/persistence/src/repositories/activity.ts
25300
25457
  var DAY_MS = 864e5;
25301
25458
  var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
25302
- var LAST_ACTIVITY_EXPR = `max(started_at, coalesce(ended_at, started_at))`;
25303
25459
  function defaultTimeZone() {
25304
25460
  try {
25305
25461
  return Intl.DateTimeFormat().resolvedOptions().timeZone;
@@ -25354,6 +25510,7 @@ var DB_EVENT_TYPE_TO_KIND = {
25354
25510
  error: "error",
25355
25511
  active: "active"
25356
25512
  };
25513
+ var TIMELINE_EVENT_TYPES = Object.keys(DB_EVENT_TYPE_TO_KIND).map((kind) => `'${kind}'`).join(", ");
25357
25514
  function safeParseStringArray(raw) {
25358
25515
  if (!raw) return [];
25359
25516
  const parsed2 = safeJson(raw, null);
@@ -25427,6 +25584,37 @@ var TIMELINE_COLUMNS = `
25427
25584
  json_extract(attributes, '$.targetId') AS target_id,
25428
25585
  json_extract(attributes, '$.internal') AS internal,
25429
25586
  json_extract(attributes, '$.flagged') AS flagged`;
25587
+ var LLM_USAGE_SELECT = `
25588
+ SELECT root_session_id AS sessionId,
25589
+ provider,
25590
+ model,
25591
+ service_tier AS serviceTier,
25592
+ coalesce(sum(input_tokens), 0) AS inputTokens,
25593
+ coalesce(sum(output_tokens), 0) AS outputTokens,
25594
+ coalesce(sum(cache_creation_input_tokens), 0) AS cacheCreationTokens,
25595
+ coalesce(sum(cache_read_input_tokens), 0) AS cacheReadTokens,
25596
+ coalesce(sum(ephemeral_1h_input_tokens), 0) AS ephemeral1hTokens,
25597
+ coalesce(sum(ephemeral_5m_input_tokens), 0) AS ephemeral5mTokens,
25598
+ coalesce(sum(web_search_requests), 0) AS webSearchRequests`;
25599
+ var LLM_USAGE_SCOPE = `event_type = 'llm_call' AND attributes IS NOT NULL AND root_session_id IS NOT NULL`;
25600
+ var LLM_USAGE_GROUP = `GROUP BY root_session_id, provider, model, service_tier`;
25601
+ function usageLeaves(rows) {
25602
+ return rows.map((row) => {
25603
+ const attributes = {
25604
+ input_tokens: row.inputTokens,
25605
+ output_tokens: row.outputTokens,
25606
+ cache_creation_input_tokens: row.cacheCreationTokens,
25607
+ cache_read_input_tokens: row.cacheReadTokens,
25608
+ ephemeral_1h_input_tokens: row.ephemeral1hTokens,
25609
+ ephemeral_5m_input_tokens: row.ephemeral5mTokens,
25610
+ web_search_requests: row.webSearchRequests
25611
+ };
25612
+ if (row.provider !== null) attributes.provider = row.provider;
25613
+ if (row.model !== null) attributes.model = row.model;
25614
+ if (row.serviceTier !== null) attributes.service_tier = row.serviceTier;
25615
+ return { sessionId: row.sessionId, attributes };
25616
+ });
25617
+ }
25430
25618
  var SESSION_ROOT = `event_type = 'session'`;
25431
25619
  var HAS_ACTIVITY = `EXISTS (
25432
25620
  SELECT 1 FROM audit_events c
@@ -25452,16 +25640,17 @@ var SqliteActivityRepository = class {
25452
25640
  const liveThreshold = this.now() - LIVE_ACTIVITY_WINDOW_MS;
25453
25641
  const liveNow = countScalar(
25454
25642
  this.db,
25455
- `SELECT count(*) AS n FROM audit_events s
25643
+ `SELECT count(*) AS n FROM audit_events s INDEXED BY sqlite_autoindex_audit_events_1
25456
25644
  WHERE s.event_type = 'session' AND s.ended_at IS NULL
25457
- AND max(
25458
- s.started_at,
25459
- coalesce(
25460
- (SELECT max(${LAST_ACTIVITY_EXPR}) FROM audit_events e WHERE e.root_session_id = s.id),
25461
- s.started_at
25462
- )
25463
- ) >= ?`,
25464
- [liveThreshold]
25645
+ AND s.id IN (
25646
+ SELECT id FROM audit_events WHERE event_type = 'session' AND started_at >= ?
25647
+ UNION
25648
+ SELECT root_session_id FROM audit_events INDEXED BY idx_audit_started_at
25649
+ WHERE started_at >= ?
25650
+ UNION
25651
+ SELECT root_session_id FROM audit_events INDEXED BY idx_audit_ended_at
25652
+ WHERE ended_at >= ?)`,
25653
+ [liveThreshold, liveThreshold, liveThreshold]
25465
25654
  );
25466
25655
  const toolCallsToday = countScalar(
25467
25656
  this.db,
@@ -25591,7 +25780,7 @@ var SqliteActivityRepository = class {
25591
25780
  this.db.prepare(
25592
25781
  `SELECT ${TIMELINE_COLUMNS}
25593
25782
  FROM audit_events
25594
- WHERE id = ? OR root_session_id = ?
25783
+ WHERE (id = ? OR root_session_id = ?) AND event_type IN (${TIMELINE_EVENT_TYPES})
25595
25784
  ORDER BY started_at ASC, id ASC`
25596
25785
  ),
25597
25786
  [sessionId, sessionId]
@@ -25604,14 +25793,14 @@ var SqliteActivityRepository = class {
25604
25793
  coalesce(sum(output_tokens), 0) AS output,
25605
25794
  coalesce(sum(cache_creation_input_tokens), 0) AS cache_creation,
25606
25795
  coalesce(sum(cache_read_input_tokens), 0) AS cache_read
25607
- FROM audit_events
25796
+ FROM audit_events INDEXED BY idx_audit_session_type
25608
25797
  WHERE root_session_id = ? AND event_type = 'llm_call'`
25609
25798
  ),
25610
25799
  [sessionId]
25611
25800
  ) ?? { input: 0, output: 0, cache_creation: 0, cache_read: 0 };
25612
25801
  const primaryModel = getRow(
25613
25802
  this.db.prepare(
25614
- `SELECT model, provider FROM audit_events
25803
+ `SELECT model, provider FROM audit_events INDEXED BY idx_audit_session_type
25615
25804
  WHERE root_session_id = ? AND event_type = 'llm_call'
25616
25805
  ORDER BY started_at ASC, id ASC
25617
25806
  LIMIT 1`
@@ -25622,7 +25811,7 @@ var SqliteActivityRepository = class {
25622
25811
  this.db.prepare(
25623
25812
  `SELECT coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool')) AS tool,
25624
25813
  count(*) AS n
25625
- FROM audit_events
25814
+ FROM audit_events INDEXED BY idx_audit_session
25626
25815
  WHERE root_session_id = ? AND event_type = 'tool_call'
25627
25816
  GROUP BY coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool'))`
25628
25817
  ),
@@ -25630,7 +25819,7 @@ var SqliteActivityRepository = class {
25630
25819
  );
25631
25820
  const modelRows = allRows(
25632
25821
  this.db.prepare(
25633
- `SELECT DISTINCT model FROM audit_events
25822
+ `SELECT DISTINCT model FROM audit_events INDEXED BY idx_audit_session_type
25634
25823
  WHERE root_session_id = ? AND event_type = 'llm_call' AND model IS NOT NULL AND model <> ''
25635
25824
  ORDER BY model`
25636
25825
  ),
@@ -25639,7 +25828,7 @@ var SqliteActivityRepository = class {
25639
25828
  const derivedModels = modelRows.map((r) => r.model);
25640
25829
  const commits = countScalar(
25641
25830
  this.db,
25642
- `SELECT count(*) AS n FROM audit_events
25831
+ `SELECT count(*) AS n FROM audit_events INDEXED BY idx_audit_session
25643
25832
  WHERE root_session_id = ? AND event_type = 'commit'`,
25644
25833
  [sessionId]
25645
25834
  );
@@ -25675,25 +25864,57 @@ var SqliteActivityRepository = class {
25675
25864
  return Promise.resolve(session);
25676
25865
  }
25677
25866
  /**
25678
- * Cross-session token report — every `llm_call` leaf (optionally windowed to
25679
- * `started_at >= fromMs`) grouped into per-session `SessionTokenReport`s, with
25680
- * USD cost DERIVED at read time via the shared `defaultCostModel` (never
25681
- * stored). `fromMs` lets the Activity page scope the usage panel to its
25682
- * selected time range; omit it for all-time (the CLI/TUI overview). The
25683
- * caller collapses these onto per-model rows with `aggregateTokenUsage`.
25867
+ * Cross-session token report — every `llm_call` in the store (or in a
25868
+ * `started_at >= fromMs` window, the Activity page's range) grouped per
25869
+ * session, with USD cost DERIVED at read time via the shared
25870
+ * `defaultCostModel` (never stored). The caller collapses these onto
25871
+ * per-model rows with `aggregateTokenUsage`.
25872
+ *
25873
+ * Grouped in SQL over `idx_audit_llm_usage` — one entry per call carrying
25874
+ * the members the rollup sums — and priced once per group, which is exact
25875
+ * (see LlmUsageRow). Reading the bags and folding them in JS measured 31 ms
25876
+ * for a seven-day window at 50k calls, and naming the VIRTUAL columns
25877
+ * against the table 40 ms, since each is a json_extract recomputed per row;
25878
+ * the index stores the values once, at write, and answers the same window in
25879
+ * 8.5 ms. `INDEXED BY` is deliberate: with or without ANALYZE statistics the
25880
+ * planner prefers the general event-type index and fetches every row to
25881
+ * recompute the columns it could have read. The index is one every open
25882
+ * store carries, since opening runs the migrations, so the hard requirement
25883
+ * `INDEXED BY` introduces is already met; token-rollup-plans.test.ts pins
25884
+ * the plan. All-time is a scan of the whole index — still one narrow entry
25885
+ * per call, no bag parsed.
25684
25886
  */
25685
25887
  tokenReports(fromMs) {
25686
- const leaves = this.readLlmCallLeaves(fromMs === void 0 ? {} : { fromMs });
25687
- return Promise.resolve(buildTokenReports(leaves, defaultCostModel));
25888
+ const rows = allRows(
25889
+ this.db.prepare(
25890
+ `${LLM_USAGE_SELECT}
25891
+ FROM audit_events INDEXED BY idx_audit_llm_usage
25892
+ WHERE ${LLM_USAGE_SCOPE}${fromMs === void 0 ? "" : " AND started_at >= ?"}
25893
+ ${LLM_USAGE_GROUP}`
25894
+ ),
25895
+ fromMs === void 0 ? void 0 : [fromMs]
25896
+ );
25897
+ return Promise.resolve(buildTokenReports(usageLeaves(rows), defaultCostModel));
25688
25898
  }
25689
25899
  /**
25690
- * One session's token report — its `llm_call` leaves grouped per (provider,
25691
- * model) with derived cost, or `null` when the session made no `llm_call`s
25692
- * (an empty/tool-only session). Feeds the session-detail pane's per-model
25693
- * breakdown + estimated cost.
25900
+ * One session's token report — its `llm_call`s grouped per (provider,
25901
+ * model, tier) with derived cost, or `null` when the session made no
25902
+ * `llm_call`s (an empty/tool-only session). Feeds the session-detail pane's
25903
+ * per-model breakdown + estimated cost. The same rollup as `tokenReports`,
25904
+ * seeking one root through a root-led `llm_call` index; the bag-reading fold
25905
+ * it replaces walked every `llm_call` in the store to find one session's.
25694
25906
  */
25695
25907
  tokenReportForSession(sessionId) {
25696
- const reports = buildTokenReports(this.readLlmCallLeaves({ sessionId }), defaultCostModel);
25908
+ const rows = allRows(
25909
+ this.db.prepare(
25910
+ `${LLM_USAGE_SELECT}
25911
+ FROM audit_events
25912
+ WHERE ${LLM_USAGE_SCOPE} AND root_session_id = ?
25913
+ ${LLM_USAGE_GROUP}`
25914
+ ),
25915
+ [sessionId]
25916
+ );
25917
+ const reports = buildTokenReports(usageLeaves(rows), defaultCostModel);
25697
25918
  return Promise.resolve(reports[0] ?? null);
25698
25919
  }
25699
25920
  /**
@@ -25717,42 +25938,6 @@ var SqliteActivityRepository = class {
25717
25938
  for (const row of rows) seen.add(toHarness(row.harness));
25718
25939
  return Promise.resolve([...seen]);
25719
25940
  }
25720
- /**
25721
- * The raw `llm_call` leaves (session id + parsed attribute bag) for the token
25722
- * rollups, optionally narrowed to one session and/or a `started_at >= fromMs`
25723
- * window. A leaf whose attributes blob is NULL or unparseable is skipped
25724
- * (best-effort read — a corrupt bag never breaks the report). `root_session_id`
25725
- * is the leaf's session (the reconciler sets parent_id = root_session_id).
25726
- */
25727
- readLlmCallLeaves(opts = {}) {
25728
- const conditions = ["event_type = 'llm_call'", "attributes IS NOT NULL"];
25729
- const params = [];
25730
- if (opts.sessionId !== void 0) {
25731
- conditions.push("root_session_id = ?");
25732
- params.push(opts.sessionId);
25733
- }
25734
- if (opts.fromMs !== void 0) {
25735
- conditions.push("started_at >= ?");
25736
- params.push(opts.fromMs);
25737
- }
25738
- const rows = allRows(
25739
- this.db.prepare(
25740
- `SELECT root_session_id AS sessionId, attributes
25741
- FROM audit_events
25742
- WHERE ${conditions.join(" AND ")}`
25743
- ),
25744
- params
25745
- );
25746
- return mapRowsTolerant(
25747
- rows.filter(
25748
- (row) => row.sessionId !== null
25749
- ),
25750
- (row) => ({
25751
- sessionId: row.sessionId,
25752
- attributes: JSON.parse(row.attributes)
25753
- })
25754
- );
25755
- }
25756
25941
  /**
25757
25942
  * Per-session turns/findings/shares + last-activity for a page of session ids,
25758
25943
  * in grouped queries (not one per row). An id with no matching rows still
@@ -25767,20 +25952,23 @@ var SqliteActivityRepository = class {
25767
25952
  const inClause = placeholders(sessionIds.length);
25768
25953
  const lastActivityRows = allRows(
25769
25954
  this.db.prepare(
25770
- `SELECT root_session_id AS id, max(${LAST_ACTIVITY_EXPR}) AS m FROM audit_events
25771
- WHERE root_session_id IN (${inClause})
25772
- GROUP BY root_session_id`
25955
+ `SELECT ids.value AS id,
25956
+ (SELECT max(started_at) FROM audit_events e WHERE e.root_session_id = ids.value) AS ms,
25957
+ (SELECT max(ended_at) FROM audit_events e
25958
+ WHERE e.root_session_id = ids.value AND e.ended_at IS NOT NULL) AS me
25959
+ FROM json_each(?) AS ids`
25773
25960
  ),
25774
- sessionIds
25961
+ [JSON.stringify(sessionIds)]
25775
25962
  );
25776
25963
  for (const row of lastActivityRows) {
25777
- if (row.id === null) continue;
25778
25964
  const entry = result.get(row.id);
25779
- if (entry && row.m !== null) entry.lastActivityMs = row.m;
25965
+ const last = Math.max(row.ms ?? 0, row.me ?? 0);
25966
+ if (entry && last > 0) entry.lastActivityMs = last;
25780
25967
  }
25781
25968
  const turnsRows = allRows(
25782
25969
  this.db.prepare(
25783
- `SELECT root_session_id AS id, count(*) AS n FROM audit_events
25970
+ `SELECT root_session_id AS id, count(*) AS n
25971
+ FROM audit_events INDEXED BY idx_audit_session_prompt
25784
25972
  WHERE root_session_id IN (${inClause}) AND event_type = 'prompt'
25785
25973
  GROUP BY root_session_id`
25786
25974
  ),
@@ -25795,7 +25983,7 @@ var SqliteActivityRepository = class {
25795
25983
  this.db.prepare(
25796
25984
  `SELECT root_session_id AS id,
25797
25985
  count(DISTINCT json_extract(attributes, '$.run_key')) AS n
25798
- FROM audit_events
25986
+ FROM audit_events INDEXED BY idx_audit_session_run_key
25799
25987
  WHERE root_session_id IN (${inClause}) AND event_type = 'llm_call'
25800
25988
  AND json_extract(attributes, '$.run_key') IS NOT NULL
25801
25989
  GROUP BY root_session_id`
@@ -25825,7 +26013,7 @@ var SqliteActivityRepository = class {
25825
26013
  this.db.prepare(
25826
26014
  `SELECT root_session_id AS id,
25827
26015
  count(DISTINCT json_extract(attributes, '$.destination')) AS n
25828
- FROM audit_events
26016
+ FROM audit_events INDEXED BY idx_audit_session_share
25829
26017
  WHERE root_session_id IN (${inClause}) AND event_type = 'share'
25830
26018
  GROUP BY root_session_id`
25831
26019
  ),
@@ -26854,7 +27042,6 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
26854
27042
 
26855
27043
  // ../../packages/persistence/src/repositories/findings.ts
26856
27044
  var PREVIEW_INSTANCES_PER_GROUP = 200;
26857
- var SCAN_BATCH_ROWS = 1e3;
26858
27045
  var DEFAULT_LOCATIONS_LIMIT = 100;
26859
27046
  var LOCATION_RULE_IDS_CAP = 20;
26860
27047
  function compareLocationOrder(a, b) {
@@ -26883,6 +27070,25 @@ function deriveInstanceStatus(row) {
26883
27070
  latestResolutionStatus: row.latest_status
26884
27071
  });
26885
27072
  }
27073
+ function toFlatFindingRow(r) {
27074
+ return {
27075
+ id: r.id,
27076
+ ruleId: r.rule_id,
27077
+ category: r.category,
27078
+ severity: r.severity,
27079
+ maskedMatch: r.masked_match,
27080
+ actionTaken: r.action_taken,
27081
+ confidence: r.confidence,
27082
+ occurredAt: epochMillisToIso(r.occurred_at),
27083
+ sourceTool: r.source_tool,
27084
+ repo: r.repo ?? "",
27085
+ file: r.file ?? "",
27086
+ ...r.tool_name === null ? {} : { toolName: r.tool_name },
27087
+ eventId: r.event_id,
27088
+ ...r.session_id === null ? {} : { sessionId: r.session_id },
27089
+ status: deriveInstanceStatus(r)
27090
+ };
27091
+ }
26886
27092
  function encodeGroupCursor(group) {
26887
27093
  const payload = {
26888
27094
  sev: group.severity,
@@ -26958,7 +27164,7 @@ var SqliteFindingsRepository = class {
26958
27164
  this.db.prepare(
26959
27165
  `SELECT f.id, f.audit_event_id AS event_id, d.rule_id, d.category, d.severity,
26960
27166
  f.masked_match, f.action_taken, f.confidence, e.started_at AS occurred_at,
26961
- json_extract(e.attributes, '$.source_tool') AS source_tool,
27167
+ e.source_tool AS source_tool,
26962
27168
  e.event_type AS kind
26963
27169
  FROM audit_events e
26964
27170
  CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
@@ -27066,56 +27272,11 @@ var SqliteFindingsRepository = class {
27066
27272
  predicate,
27067
27273
  params: sessionParams
27068
27274
  });
27069
- const rows = allRows(
27070
- this.db.prepare(
27071
- `SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
27072
- occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
27073
- kind, finding_key, latest_status
27074
- FROM (
27075
- SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
27076
- d.severity AS severity, f.masked_match AS masked_match,
27077
- f.action_taken AS action_taken, f.confidence AS confidence,
27078
- e.started_at AS occurred_at,
27079
- json_extract(e.attributes, '$.source_tool') AS source_tool,
27080
- json_extract(e.attributes, '$.repo') AS repo,
27081
- json_extract(e.attributes, '$.file_path') AS file,
27082
- json_extract(e.attributes, '$.tool_name') AS tool_name,
27083
- f.audit_event_id AS event_id, e.root_session_id AS session_id,
27084
- e.event_type AS kind, f.finding_key AS finding_key,
27085
- latest.status AS latest_status,
27086
- ROW_NUMBER() OVER (
27087
- PARTITION BY d.rule_id
27088
- ORDER BY e.started_at DESC, f.id DESC
27089
- ) AS rn
27090
- FROM inspection_findings f
27091
- JOIN audit_events e ON e.id = f.audit_event_id
27092
- JOIN inspection_definitions d ON d.id = f.inspection_definition_id
27093
- LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
27094
- ON latest.finding_key = f.finding_key
27095
- ${predicate}
27096
- )
27097
- WHERE rn <= :cap
27098
- ORDER BY occurred_at DESC, id DESC`
27099
- ),
27100
- { cap: PREVIEW_INSTANCES_PER_GROUP, ...sessionParams }
27101
- );
27102
- const groupable = rows.map((r) => ({
27103
- id: r.id,
27104
- ruleId: r.rule_id,
27105
- category: r.category,
27106
- severity: r.severity,
27107
- maskedMatch: r.masked_match,
27108
- actionTaken: r.action_taken,
27109
- confidence: r.confidence,
27110
- occurredAt: epochMillisToIso(r.occurred_at),
27111
- sourceTool: r.source_tool,
27112
- repo: r.repo ?? "",
27113
- file: r.file ?? "",
27114
- ...r.tool_name === null ? {} : { toolName: r.tool_name },
27115
- eventId: r.event_id,
27116
- ...r.session_id === null ? {} : { sessionId: r.session_id },
27117
- status: deriveInstanceStatus(r)
27118
- }));
27275
+ const rows = this.previewRows(aggregates, {
27276
+ sessionId: query.sessionId,
27277
+ from: query.from
27278
+ });
27279
+ const groupable = rows.map(toFlatFindingRow);
27119
27280
  const allGroups = buildFindingGroups(groupable, { aggregates });
27120
27281
  const filterOpts = {
27121
27282
  severity: query.severity,
@@ -27201,8 +27362,10 @@ var SqliteFindingsRepository = class {
27201
27362
  *
27202
27363
  * The scan runs from the top of the scope on every request, not from the
27203
27364
  * cursor: `totals` and `facets` describe the whole filtered scope and must not
27204
- * move as the caller pages. Rows are pulled in batches so memory stays flat
27205
- * while the counting runs, and only the page itself is retained.
27365
+ * move as the caller pages. Rows come off ONE statement, iterated rather
27366
+ * than materialized (`scanFindingRows`), so memory stays flat while the
27367
+ * counting runs — a generator streaming the index order, not a sequence of
27368
+ * fetched batches; only the page itself is retained.
27206
27369
  */
27207
27370
  listFindingInstances(query) {
27208
27371
  const opts = {
@@ -27218,6 +27381,10 @@ var SqliteFindingsRepository = class {
27218
27381
  };
27219
27382
  const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
27220
27383
  const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
27384
+ const isPastCursor = cursor === null ? () => true : (row) => {
27385
+ const rowMs = isoToEpochMillis(row.occurredAt);
27386
+ return rowMs <= cursor.startedAtMs && (rowMs < cursor.startedAtMs || row.id < cursor.id);
27387
+ };
27221
27388
  const accumulator = createInstanceFacetAccumulator(opts);
27222
27389
  const items = [];
27223
27390
  let total = 0;
@@ -27230,6 +27397,7 @@ var SqliteFindingsRepository = class {
27230
27397
  accumulator.add(row);
27231
27398
  if (!matchesInstanceFilters(row, opts)) continue;
27232
27399
  total += 1;
27400
+ if (!isPastCursor(row)) continue;
27233
27401
  if (items.length < limit) {
27234
27402
  items.push(toInstanceDetail(row));
27235
27403
  last = row;
@@ -27238,15 +27406,6 @@ var SqliteFindingsRepository = class {
27238
27406
  }
27239
27407
  }
27240
27408
  const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
27241
- if (cursor !== null) {
27242
- const resumed = this.pageAfter(cursor, opts, limit, query);
27243
- return Promise.resolve({
27244
- totals: { findings: total },
27245
- facets: accumulator.facets(),
27246
- items: resumed.items,
27247
- nextCursor: resumed.nextCursor
27248
- });
27249
- }
27250
27409
  return Promise.resolve({
27251
27410
  totals: { findings: total },
27252
27411
  facets: accumulator.facets(),
@@ -27254,35 +27413,6 @@ var SqliteFindingsRepository = class {
27254
27413
  nextCursor
27255
27414
  });
27256
27415
  }
27257
- /**
27258
- * The page of matching rows strictly after `cursor`. Separate from the
27259
- * counting pass because that one starts at the top of the scope by design;
27260
- * this one narrows the scan with the same keyset predicate the activity list
27261
- * uses, so a later page costs less than the first rather than more.
27262
- */
27263
- pageAfter(cursor, opts, limit, query) {
27264
- const items = [];
27265
- let last;
27266
- let hasMore = false;
27267
- for (const row of this.scanFindingRows({
27268
- sessionId: query.sessionId,
27269
- from: query.from,
27270
- after: cursor
27271
- })) {
27272
- if (!matchesInstanceFilters(row, opts)) continue;
27273
- if (items.length < limit) {
27274
- items.push(toInstanceDetail(row));
27275
- last = row;
27276
- } else {
27277
- hasMore = true;
27278
- break;
27279
- }
27280
- }
27281
- return {
27282
- items,
27283
- nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
27284
- };
27285
- }
27286
27416
  /**
27287
27417
  * The same findings folded by location: repository, then file within it.
27288
27418
  *
@@ -27365,25 +27495,111 @@ var SqliteFindingsRepository = class {
27365
27495
  });
27366
27496
  }
27367
27497
  /**
27368
- * Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
27498
+ * Each group's newest instances, for the table's expanded rows.
27499
+ *
27500
+ * ONE index-ordered scan with early termination, and the shape is the point.
27501
+ * The natural spelling — `ROW_NUMBER() OVER (PARTITION BY rule_id ORDER BY
27502
+ * started_at DESC)` then `WHERE rn <= cap` — sorts EVERY finding in scope
27503
+ * through a temp B-tree to keep a bounded preview of each group, and then
27504
+ * sorts the survivors again for the page order. Both sorts grow with the
27505
+ * store while the answer does not.
27506
+ *
27507
+ * Instead the scan walks `audit_events` newest-first off `idx_audit_started_at`
27508
+ * (or the session or window index the scope names — see `findingScanSql`),
27509
+ * which is already the order the page wants, and keeps rows per rule until
27510
+ * each rule has as many as it can show. The aggregate the caller already holds
27511
+ * says how many that is: `min(instanceCount, PREVIEW_INSTANCES_PER_GROUP)`
27512
+ * per rule, summed, is the number of rows this scan has to find, and it stops
27513
+ * on the last one. That sum is bounded by `rules * PREVIEW_INSTANCES_PER_GROUP`
27514
+ * (8,000 at this repo's 40-rule bench corpus), not by a fixed row count — a
27515
+ * store with many firing rules widens it. The bound that DOES hold
27516
+ * unconditionally is the sorted form's floor: this scan visits at most as
27517
+ * many rows as `ROW_NUMBER() OVER (PARTITION BY rule_id …)` would have
27518
+ * sorted, and stops the moment every rule has its cap, where the sorted form
27519
+ * sorts the whole scope regardless. The true worst case — the rarest rule's
27520
+ * wanted instances sitting at the tail of the scope — is one pass over
27521
+ * everything in scope with a block sort of the id tie-break only, never a
27522
+ * sort of the scope, which is still that floor.
27523
+ *
27524
+ * A row whose rule the aggregate did not see is skipped: the two statements
27525
+ * run without a shared snapshot, so a capture landing between them can add a
27526
+ * rule here that has no counts there, and the counts are what the group is
27527
+ * built from.
27528
+ */
27529
+ previewRows(aggregates, scope) {
27530
+ const wanted = /* @__PURE__ */ new Map();
27531
+ let remaining = 0;
27532
+ for (const [ruleId, agg] of aggregates) {
27533
+ const n = Math.min(agg.instanceCount, PREVIEW_INSTANCES_PER_GROUP);
27534
+ wanted.set(ruleId, n);
27535
+ remaining += n;
27536
+ }
27537
+ const rows = [];
27538
+ if (remaining === 0) return rows;
27539
+ const { sql, params } = this.findingScanSql(scope);
27540
+ const taken = /* @__PURE__ */ new Map();
27541
+ for (const r of iterateRows(this.db.prepare(sql), params)) {
27542
+ const want = wanted.get(r.rule_id);
27543
+ if (want === void 0) continue;
27544
+ const have = taken.get(r.rule_id) ?? 0;
27545
+ if (have >= want) continue;
27546
+ taken.set(r.rule_id, have + 1);
27547
+ rows.push(r);
27548
+ remaining -= 1;
27549
+ if (remaining === 0) break;
27550
+ }
27551
+ return rows;
27552
+ }
27553
+ /**
27554
+ * Every finding in scope as a FlatFindingRow, newest first, streamed.
27369
27555
  *
27370
27556
  * A generator so a caller streams the scope without it ever being an array:
27371
27557
  * the flat list counts and facets the whole filtered scope, which on a large
27372
- * store is far more rows than any page. Each batch advances the same keyset
27373
- * predicate the page read uses, so the scan is a sequence of bounded reads
27374
- * rather than one unbounded result set.
27375
- *
27376
- * The latest-resolution lookup is the CORRELATED form, not the derived table
27377
- * the grouped path joins: only `status` is needed, idx_finding_resolution_key
27378
- * makes it a point lookup per row, and the derived table would re-materialize
27379
- * a window over the whole resolution table once per batch.
27558
+ * store is far more rows than any page. The rows come off ONE statement,
27559
+ * iterated rather than materialized, in the index order `findingScanSql`
27560
+ * arranges — so the scan is a single pass with a block sort of the id
27561
+ * tie-break only, never a sort of the scope, where a sequence of
27562
+ * keyset-bounded batches re-sorted everything below the cursor on every
27563
+ * batch and cost the square of the scope.
27380
27564
  *
27381
- * `scope` carries ONLY what no facet counts. A filter dimension narrowed here
27382
- * would be missing from its own facet, which is computed by excluding that
27383
- * dimension — see listFindingInstances.
27565
+ * `sessionId` and `from` carry ONLY what no facet counts — a filter
27566
+ * dimension narrowed here would be missing from its own facet, which is
27567
+ * computed by excluding that dimension (see listFindingInstances). There is
27568
+ * no `after`/cursor parameter: a keyset page is collected inline from this
27569
+ * same pass (`listFindingInstances`' `isPastCursor`) rather than by a second,
27570
+ * narrower statement, since the counting pass already visits every row a
27571
+ * page-2+ request would otherwise re-seek for.
27384
27572
  */
27385
27573
  *scanFindingRows(scope) {
27386
- const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
27574
+ const { sql, params } = this.findingScanSql(scope);
27575
+ for (const r of iterateRows(this.db.prepare(sql), params)) {
27576
+ yield toFlatFindingRow(r);
27577
+ }
27578
+ }
27579
+ /**
27580
+ * The one statement both instance-level scans run: every finding in scope,
27581
+ * joined to its event and definition, newest first.
27582
+ *
27583
+ * THE PLAN IS THE POINT, and two things in the SQL exist only to pin it —
27584
+ * the same two `recentFindings` documents at length, for the same reason:
27585
+ *
27586
+ * - **`+e.event_type`** makes the capture-kind predicate non-indexable, so
27587
+ * the planner cannot pick `idx_audit_type_t` and then sort. That index
27588
+ * yields `started_at` order per event type, not across the four, so
27589
+ * satisfying the ORDER BY from it would need a merge SQLite does not do.
27590
+ * Freed of it, the planner walks `idx_audit_started_at` backwards — or
27591
+ * `idx_audit_session` for a session scope, which is also `started_at`
27592
+ * ordered within the session — and the order falls out of the index.
27593
+ * - **`CROSS JOIN`** pins `audit_events` as the driving table. With plain
27594
+ * JOINs the planner drives from the findings and sorts everything.
27595
+ *
27596
+ * The latest-resolution lookup is the CORRELATED form: only `status` is
27597
+ * needed, `idx_finding_resolution_key_created` answers it with one backward
27598
+ * index probe per keyed row, and a derived table over the whole resolution
27599
+ * table would be materialized before the first row streamed.
27600
+ */
27601
+ findingScanSql(scope) {
27602
+ const conditions = [`+e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
27387
27603
  const params = [];
27388
27604
  if (scope.sessionId !== void 0 && scope.sessionId !== "") {
27389
27605
  conditions.push("e.root_session_id = ?");
@@ -27397,58 +27613,24 @@ var SqliteFindingsRepository = class {
27397
27613
  d.severity AS severity, f.masked_match AS masked_match,
27398
27614
  f.action_taken AS action_taken, f.confidence AS confidence,
27399
27615
  e.started_at AS occurred_at,
27400
- json_extract(e.attributes, '$.source_tool') AS source_tool,
27401
- json_extract(e.attributes, '$.repo') AS repo,
27402
- json_extract(e.attributes, '$.file_path') AS file,
27403
- json_extract(e.attributes, '$.tool_name') AS tool_name,
27616
+ e.source_tool AS source_tool,
27617
+ e.repo AS repo,
27618
+ e.file_path AS file,
27619
+ e.tool_name AS tool_name,
27404
27620
  f.audit_event_id AS event_id, e.root_session_id AS session_id,
27405
27621
  e.event_type AS kind, f.finding_key AS finding_key,
27406
27622
  ${latestResolutionStatusSql("f")} AS latest_status
27407
- FROM inspection_findings f
27408
- JOIN audit_events e ON e.id = f.audit_event_id
27409
- JOIN inspection_definitions d ON d.id = f.inspection_definition_id
27623
+ FROM audit_events e
27624
+ CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
27625
+ CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
27410
27626
  WHERE ${conditions.join(" AND ")}
27411
- AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
27412
- ORDER BY e.started_at DESC, f.id DESC
27413
- LIMIT ?`;
27414
- let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
27415
- for (; ; ) {
27416
- const rows = allRows(this.db.prepare(sql), [
27417
- ...params,
27418
- after.startedAtMs,
27419
- after.startedAtMs,
27420
- after.id,
27421
- SCAN_BATCH_ROWS
27422
- ]);
27423
- for (const r of rows) {
27424
- yield {
27425
- id: r.id,
27426
- ruleId: r.rule_id,
27427
- category: r.category,
27428
- severity: r.severity,
27429
- maskedMatch: r.masked_match,
27430
- actionTaken: r.action_taken,
27431
- confidence: r.confidence,
27432
- occurredAt: epochMillisToIso(r.occurred_at),
27433
- sourceTool: r.source_tool,
27434
- repo: r.repo ?? "",
27435
- file: r.file ?? "",
27436
- ...r.tool_name === null ? {} : { toolName: r.tool_name },
27437
- eventId: r.event_id,
27438
- ...r.session_id === null ? {} : { sessionId: r.session_id },
27439
- status: deriveInstanceStatus(r)
27440
- };
27441
- }
27442
- if (rows.length < SCAN_BATCH_ROWS) return;
27443
- const lastRow = rows[rows.length - 1];
27444
- if (lastRow === void 0) return;
27445
- after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
27446
- }
27627
+ ORDER BY e.started_at DESC, f.id DESC`;
27628
+ return { sql, params };
27447
27629
  }
27448
27630
  groupAggregates(withSearchText, scope) {
27449
- const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
27450
- group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
27451
- group_concat(DISTINCT 'via ' || json_extract(e.attributes, '$.tool_name')) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
27631
+ const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT e.repo) AS repos,
27632
+ group_concat(DISTINCT e.file_path) AS files,
27633
+ group_concat(DISTINCT 'via ' || e.tool_name) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
27452
27634
  const rows = this.db.prepare(
27453
27635
  `SELECT rule_id,
27454
27636
  sum(tuple_count) AS instance_count,
@@ -27466,7 +27648,7 @@ var SqliteFindingsRepository = class {
27466
27648
  coalesce(latest.status, '') AS status_tuple,
27467
27649
  count(*) AS tuple_count,
27468
27650
  max(e.started_at) AS latest_at,
27469
- group_concat(DISTINCT json_extract(e.attributes, '$.source_tool')) AS source_tools,
27651
+ group_concat(DISTINCT e.source_tool) AS source_tools,
27470
27652
  group_concat(DISTINCT f.action_taken) AS actions_taken
27471
27653
  ${innerSearchColumns}
27472
27654
  FROM inspection_findings f
@@ -27597,6 +27779,8 @@ function isoDay(ms) {
27597
27779
  // ../../packages/persistence/src/repositories/history-sync.ts
27598
27780
  var STRUCTURAL_EVENT_TYPES = ["session", "llm_call", "tool_call"];
27599
27781
  var TYPE_LIST = STRUCTURAL_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
27782
+ var OUTBOX_CAPTURE_EVENT_TYPES = ["prompt", "response", "tool_use"];
27783
+ var CAPTURE_TYPE_LIST = OUTBOX_CAPTURE_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
27600
27784
  var SKIPPED = -1;
27601
27785
  var ROW_COLUMNS = `id,
27602
27786
  parent_id AS parentId,
@@ -27636,6 +27820,20 @@ var SqliteHistorySyncRepository = class {
27636
27820
  ORDER BY (event_type = 'session') DESC, started_at
27637
27821
  LIMIT :limit`
27638
27822
  );
27823
+ this.captureRowsStmt = db.prepare(
27824
+ `SELECT ${ROW_COLUMNS}
27825
+ FROM audit_events
27826
+ WHERE synced_at IS NULL
27827
+ AND sync_claimed_at IS NULL
27828
+ AND outbox_owed = 1
27829
+ AND event_type IN (${CAPTURE_TYPE_LIST})
27830
+ AND started_at < :before
27831
+ ORDER BY started_at
27832
+ LIMIT :limit`
27833
+ );
27834
+ this.markOwedStmt = db.prepare(
27835
+ `UPDATE audit_events SET outbox_owed = 1 WHERE id = :id AND synced_at IS NULL`
27836
+ );
27639
27837
  this.stampStmt = db.prepare(
27640
27838
  `UPDATE audit_events SET synced_at = :at, sync_claimed_at = NULL WHERE id = :id`
27641
27839
  );
@@ -27667,6 +27865,12 @@ var SqliteHistorySyncRepository = class {
27667
27865
  FROM audit_events
27668
27866
  WHERE event_type IN (${TYPE_LIST})`
27669
27867
  );
27868
+ this.captureSkipCountStmt = db.prepare(
27869
+ `SELECT COUNT(*) AS skipped
27870
+ FROM audit_events
27871
+ WHERE synced_at = ${String(SKIPPED)}
27872
+ AND event_type IN (${CAPTURE_TYPE_LIST})`
27873
+ );
27670
27874
  this.fingerprintStmt = db.prepare(
27671
27875
  `SELECT endpoint_fingerprint AS fingerprint, backlog_before AS backlogBefore
27672
27876
  FROM history_sync WHERE id = 1`
@@ -27676,6 +27880,10 @@ var SqliteHistorySyncRepository = class {
27676
27880
  SET endpoint_fingerprint = :fingerprint, backlog_before = :backlogBefore
27677
27881
  WHERE id = 1`
27678
27882
  );
27883
+ this.disownCapturesStmt = db.prepare(
27884
+ `UPDATE audit_events SET outbox_owed = NULL
27885
+ WHERE outbox_owed IS NOT NULL AND event_type IN (${CAPTURE_TYPE_LIST})`
27886
+ );
27679
27887
  this.rearmStmt = db.prepare(
27680
27888
  `UPDATE audit_events SET synced_at = NULL
27681
27889
  WHERE synced_at > 0 AND event_type IN (${TYPE_LIST})`
@@ -27748,6 +27956,10 @@ var SqliteHistorySyncRepository = class {
27748
27956
  closeWindowStmt;
27749
27957
  releaseBoundaryStmt;
27750
27958
  freezeBoundaryStmt;
27959
+ captureRowsStmt;
27960
+ markOwedStmt;
27961
+ captureSkipCountStmt;
27962
+ disownCapturesStmt;
27751
27963
  partitionStmt;
27752
27964
  claimRowStmt;
27753
27965
  releaseRowStmt;
@@ -27781,6 +27993,34 @@ var SqliteHistorySyncRepository = class {
27781
27993
  pendingRows(sessionId, limit, before) {
27782
27994
  return allRows(this.rowsStmt, { sessionId, limit, before });
27783
27995
  }
27996
+ /**
27997
+ * Captures this machine still owes the deployment, oldest first.
27998
+ *
27999
+ * Selected by the `outbox_owed` marker the attached forward path writes, not
28000
+ * by a time window — see captureRowsStmt for why a window could not express
28001
+ * this. `before` is the grace window that leaves a just-recorded capture to
28002
+ * the live path.
28003
+ */
28004
+ pendingCaptureRows(limit, before) {
28005
+ return allRows(this.captureRowsStmt, { limit, before });
28006
+ }
28007
+ /**
28008
+ * Record that a capture is OWED to the deployment.
28009
+ *
28010
+ * Written by the attached forward path when a live send did not confirm
28011
+ * delivery, and read by the drain as the whole of its eligibility test. It is
28012
+ * a fact rather than an inference: the machine was attached, the send did not
28013
+ * land, so the row is owed — which no time window can state, because the same
28014
+ * window that holds the rows a past attachment left owed also holds every
28015
+ * capture recorded while the machine was DETACHED, and those were never
28016
+ * offered to anyone.
28017
+ *
28018
+ * Idempotent, and never un-set: `markSynced` settling the row is what takes it
28019
+ * out of the drain's read.
28020
+ */
28021
+ markCaptureOwed(id) {
28022
+ this.markOwedStmt.run({ id });
28023
+ }
27784
28024
  /** Record delivery. Called only AFTER the far side has accepted the rows. */
27785
28025
  markSynced(ids, atMs) {
27786
28026
  this.stampAll(ids, atMs);
@@ -27864,10 +28104,12 @@ var SqliteHistorySyncRepository = class {
27864
28104
  this.countsStmt,
27865
28105
  { before }
27866
28106
  );
28107
+ const captures = getRow(this.captureSkipCountStmt);
27867
28108
  return {
27868
28109
  pending: row?.pending ?? 0,
27869
28110
  sent: row?.sent ?? 0,
27870
- skipped: row?.skipped ?? 0
28111
+ skipped: row?.skipped ?? 0,
28112
+ capturesSkipped: captures?.skipped ?? 0
27871
28113
  };
27872
28114
  }
27873
28115
  /**
@@ -27908,7 +28150,11 @@ var SqliteHistorySyncRepository = class {
27908
28150
  withTransaction(
27909
28151
  this.db,
27910
28152
  () => {
28153
+ const previous = getRow(this.fingerprintStmt)?.fingerprint;
27911
28154
  this.rearmStmt.run();
28155
+ if (previous !== null && previous !== void 0 && previous !== fingerprint) {
28156
+ this.disownCapturesStmt.run();
28157
+ }
27912
28158
  this.setFingerprintStmt.run({ fingerprint, backlogBefore });
27913
28159
  },
27914
28160
  "IMMEDIATE"
@@ -28105,7 +28351,256 @@ var SqliteInspectionFindingsRepository = class {
28105
28351
  };
28106
28352
 
28107
28353
  // ../../packages/persistence/src/repositories/installed-packs.ts
28108
- import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
28354
+ import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
28355
+
28356
+ // ../../packages/persistence/src/policy-floor.ts
28357
+ import { readFileSync as readFileSync5 } from "fs";
28358
+ import { join as join6 } from "path";
28359
+
28360
+ // ../../packages/persistence/src/local-layout.ts
28361
+ import { renameSync as renameSync3 } from "fs";
28362
+ import { mkdir } from "fs/promises";
28363
+ import { homedir } from "os";
28364
+ import { join as join4 } from "path";
28365
+ function defaultDataDir() {
28366
+ return join4(homedir(), ".aka");
28367
+ }
28368
+ function settingsDir(base = defaultDataDir()) {
28369
+ return join4(base, "settings");
28370
+ }
28371
+ function dataDir(base = defaultDataDir()) {
28372
+ return join4(base, "data");
28373
+ }
28374
+ function dbPath(base = defaultDataDir()) {
28375
+ return join4(dataDir(base), "aka.db");
28376
+ }
28377
+ async function ensureDataDir(dir = defaultDataDir()) {
28378
+ await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
28379
+ tightenDir(dir);
28380
+ }
28381
+ function ensureLayoutDirSync(dir = defaultDataDir()) {
28382
+ ensureDataDirSync(dir);
28383
+ }
28384
+ function migrateLegacyLayout(base = defaultDataDir()) {
28385
+ const moves = [
28386
+ { name: "config.json", dest: settingsDir(base) },
28387
+ { name: "policy-cache.json", dest: dataDir(base) }
28388
+ ];
28389
+ for (const { name: name2, dest } of moves) {
28390
+ try {
28391
+ ensureDataDirSync(dest);
28392
+ const moved = join4(dest, name2);
28393
+ renameSync3(join4(base, name2), moved);
28394
+ tightenFile(moved);
28395
+ } catch {
28396
+ }
28397
+ }
28398
+ }
28399
+
28400
+ // ../../packages/persistence/src/settings.ts
28401
+ import { readFileSync as readFileSync4 } from "fs";
28402
+ import { join as join5 } from "path";
28403
+
28404
+ // ../../packages/persistence/src/file-lock.ts
28405
+ import { randomUUID as randomUUID3 } from "crypto";
28406
+ import {
28407
+ closeSync,
28408
+ existsSync as existsSync2,
28409
+ openSync,
28410
+ readFileSync as readFileSync2,
28411
+ rmSync as rmSync5,
28412
+ statSync as statSync3,
28413
+ writeFileSync as writeFileSync2
28414
+ } from "fs";
28415
+ import { hostname as hostname3 } from "os";
28416
+ var PARK = new Int32Array(new SharedArrayBuffer(4));
28417
+
28418
+ // ../../packages/persistence/src/managed-settings.ts
28419
+ import { readFileSync as readFileSync3 } from "fs";
28420
+ import { posix, win32 } from "path";
28421
+ function managedSettingsPaths(platform2 = process.platform) {
28422
+ if (platform2 === "darwin") {
28423
+ return [
28424
+ posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
28425
+ posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
28426
+ ];
28427
+ }
28428
+ if (platform2 === "win32") {
28429
+ return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
28430
+ }
28431
+ return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
28432
+ }
28433
+ function readManagedSettings(paths = managedSettingsPaths()) {
28434
+ for (const path of paths) {
28435
+ let text;
28436
+ try {
28437
+ text = readFileSync3(path, "utf8");
28438
+ } catch {
28439
+ continue;
28440
+ }
28441
+ const record2 = parseJsonObject(text);
28442
+ if (!record2) continue;
28443
+ const parsed2 = ManagedSettings.safeParse(record2);
28444
+ if (parsed2.success) return parsed2.data;
28445
+ }
28446
+ return null;
28447
+ }
28448
+ function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
28449
+ if (!managed) return settings;
28450
+ const { values } = managed;
28451
+ const merged = { ...settings };
28452
+ if (values.runMode !== void 0) merged.runMode = values.runMode;
28453
+ if (values.controlPlane !== void 0) {
28454
+ merged.controlPlane = {
28455
+ ...values.controlPlane,
28456
+ // The administrator pinned WHICH deployment, not WHEN this machine
28457
+ // joined it. Keep the user's own attach time when the endpoint is
28458
+ // unchanged, so a managed machine does not appear to re-attach on every
28459
+ // read; stamp a fresh one when the administrator moved it.
28460
+ attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
28461
+ };
28462
+ }
28463
+ if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
28464
+ if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
28465
+ if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
28466
+ if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
28467
+ if (values.redactFallback !== void 0) merged.redactFallback = values.redactFallback;
28468
+ if (values.vaultConsent !== void 0) {
28469
+ merged.vaultConsent = values.vaultConsent ? (
28470
+ // Keep an existing valid grant so its acknowledgedAt survives; mint one
28471
+ // at the current version otherwise.
28472
+ settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
28473
+ ) : void 0;
28474
+ }
28475
+ if (values.modelJudgeConsent !== void 0) {
28476
+ merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
28477
+ acknowledgedAt: now().toISOString(),
28478
+ payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
28479
+ } : void 0;
28480
+ }
28481
+ return merged;
28482
+ }
28483
+
28484
+ // ../../packages/persistence/src/settings.ts
28485
+ var SETTINGS_FILENAME = "settings.json";
28486
+ function readWorkspaceSettings(base = defaultDataDir()) {
28487
+ return overlayManagedSettings(readUserSettings(base), readManagedSettings());
28488
+ }
28489
+ function readUserSettings(base) {
28490
+ const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
28491
+ if (!record2) return defaultWorkspaceSettings();
28492
+ try {
28493
+ return WorkspaceSettings.parse(record2);
28494
+ } catch {
28495
+ return defaultWorkspaceSettings();
28496
+ }
28497
+ }
28498
+ function readJson(file2) {
28499
+ let text;
28500
+ try {
28501
+ text = readFileSync4(file2, "utf8");
28502
+ } catch {
28503
+ return null;
28504
+ }
28505
+ return parseJsonObject(text) ?? null;
28506
+ }
28507
+
28508
+ // ../../packages/persistence/src/policy-floor.ts
28509
+ function refusalMessage(pack, attempted, floor, refusal) {
28510
+ switch (refusal) {
28511
+ case "lock":
28512
+ return `refusing to re-assign '${pack}': its policy is set by the connected control plane`;
28513
+ case "disable":
28514
+ return `refusing to disable '${pack}': it is governed by the connected control plane`;
28515
+ case "floor":
28516
+ return `refusing to set '${pack}' to '${attempted ?? "unassigned"}': the connected control plane requires at least '${floor}'`;
28517
+ }
28518
+ }
28519
+ var PolicyFloorError = class extends Error {
28520
+ /** `namespace/packId` of the detection whose write was refused. */
28521
+ pack;
28522
+ /**
28523
+ * The archetype the caller asked for, or null when the write named none —
28524
+ * clearing the assignment, or switching the detection off.
28525
+ */
28526
+ attempted;
28527
+ /** The weakest archetype the control plane permits for this pack. */
28528
+ floor;
28529
+ refusal;
28530
+ constructor(pack, attempted, floor, refusal) {
28531
+ super(refusalMessage(pack, attempted, floor, refusal));
28532
+ this.name = "PolicyFloorError";
28533
+ this.pack = pack;
28534
+ this.attempted = attempted;
28535
+ this.floor = floor;
28536
+ this.refusal = refusal;
28537
+ }
28538
+ };
28539
+ function readCachedPolicyBundle(base = defaultDataDir()) {
28540
+ try {
28541
+ const raw = readFileSync5(join6(dataDir(base), POLICY_CACHE_FILENAME), "utf8");
28542
+ const parsed2 = JSON.parse(raw);
28543
+ if (typeof parsed2 !== "object" || parsed2 === null) return null;
28544
+ return PolicyBundle.parse(parsed2.bundle);
28545
+ } catch {
28546
+ return null;
28547
+ }
28548
+ }
28549
+ function indexEnabled(policies) {
28550
+ const byRuleId = /* @__PURE__ */ new Map();
28551
+ const byCategory = /* @__PURE__ */ new Map();
28552
+ for (const policy of policies) {
28553
+ if (!policy.enabled) continue;
28554
+ if ("ruleId" in policy.target) {
28555
+ if (!byRuleId.has(policy.target.ruleId)) byRuleId.set(policy.target.ruleId, policy.action);
28556
+ } else if (!byCategory.has(policy.target.category)) {
28557
+ byCategory.set(policy.target.category, policy.action);
28558
+ }
28559
+ }
28560
+ return { byRuleId, byCategory };
28561
+ }
28562
+ function hasAuthoredPolicy(policies, rules, byRuleId) {
28563
+ const ruleIds = new Set(rules.map((rule) => rule.id));
28564
+ const categories = new Set(rules.map((rule) => rule.category));
28565
+ const bundleNamesPack = rules.some((rule) => byRuleId.has(rule.id));
28566
+ return policies.some((policy) => {
28567
+ if (!policy.enabled || policy.provenance !== "authored") return false;
28568
+ return "ruleId" in policy.target ? ruleIds.has(policy.target.ruleId) : bundleNamesPack && categories.has(policy.target.category);
28569
+ });
28570
+ }
28571
+ function controlPlanePolicyFloor(rules, base = defaultDataDir()) {
28572
+ const floors = openControlPlaneFloors(base);
28573
+ return floors === null ? null : floors.floorFor(rules);
28574
+ }
28575
+ function openControlPlaneFloors(base = defaultDataDir()) {
28576
+ if (!isAttached(readWorkspaceSettings(base))) return null;
28577
+ const bundle = readCachedPolicyBundle(base);
28578
+ if (bundle === null) return null;
28579
+ const indexes = indexEnabled(bundle.policies);
28580
+ return { floorFor: (rules) => resolveFloor(rules, bundle.policies, indexes) };
28581
+ }
28582
+ function resolveFloor(rules, policies, { byRuleId, byCategory }) {
28583
+ let action = null;
28584
+ for (const rule of rules) {
28585
+ const resolved = byRuleId.get(rule.id) ?? byCategory.get(rule.category);
28586
+ if (resolved === void 0) continue;
28587
+ action = action === null ? resolved : strongerAction(action, resolved);
28588
+ }
28589
+ if (action === null) return null;
28590
+ return {
28591
+ floor: weakestBuiltinAtLeast(action),
28592
+ locked: hasAuthoredPolicy(policies, rules, byRuleId)
28593
+ };
28594
+ }
28595
+ function policyAssignmentRefusal(policyId, floor) {
28596
+ if (floor.locked) return "lock";
28597
+ const effective = policyId ?? DEFAULT_PACK_POLICY_ID;
28598
+ return isActionAtLeast(builtinPolicyToAction(effective), builtinPolicyToAction(floor.floor)) ? null : "floor";
28599
+ }
28600
+ function packEnablementRefusal(enabled, floor) {
28601
+ if (floor === null || enabled) return null;
28602
+ return "disable";
28603
+ }
28109
28604
 
28110
28605
  // ../../packages/persistence/src/semver.ts
28111
28606
  function parse3(version3) {
@@ -28199,8 +28694,19 @@ function ruleIdsOf(rulesJson) {
28199
28694
  return ids;
28200
28695
  }
28201
28696
  var SqliteInstalledPacksRepository = class {
28202
- constructor(db) {
28697
+ /**
28698
+ * `baseDir` is the `~/.aka` LAYOUT BASE, not the data dir — the control-plane
28699
+ * floor needs both halves of it (settings/ says whether this machine is
28700
+ * attached, data/ holds the cached bundle). It is optional because a caller
28701
+ * holding only a DatabaseSync — every test construction site, and any embedder
28702
+ * that opens the store itself — has no layout to point at, and such a caller
28703
+ * gets the pre-existing behaviour: no floor, no lock. Production threads it in
28704
+ * from `openLocalDatabase`, which is the single construction site that owns a
28705
+ * real `~/.aka`.
28706
+ */
28707
+ constructor(db, baseDir) {
28203
28708
  this.db = db;
28709
+ this.baseDir = baseDir;
28204
28710
  this.insertMissingStmt = db.prepare(
28205
28711
  `INSERT INTO installed_packs (id, namespace, pack_id, version, name, rules_json, enabled, created_at, updated_at)
28206
28712
  VALUES (:id, :namespace, :packId, :version, :name, :rulesJson, 1, :now, :now)
@@ -28222,11 +28728,17 @@ var SqliteInstalledPacksRepository = class {
28222
28728
  this.signatureStmt = db.prepare(
28223
28729
  `SELECT namespace, pack_id AS packId, version, rules_json AS rulesJson FROM available_packs`
28224
28730
  );
28731
+ this.packRulesStmt = db.prepare(
28732
+ `SELECT rules_json AS rulesJson FROM installed_packs
28733
+ WHERE namespace = ? AND pack_id = ?`
28734
+ );
28225
28735
  }
28226
28736
  db;
28737
+ baseDir;
28227
28738
  insertMissingStmt;
28228
28739
  upsertAvailableStmt;
28229
28740
  signatureStmt;
28741
+ packRulesStmt;
28230
28742
  /**
28231
28743
  * Record the running binary's detection inventory. Refreshes the
28232
28744
  * available_packs mirror (pruning packs the binary no longer ships) and
@@ -28268,7 +28780,7 @@ var SqliteInstalledPacksRepository = class {
28268
28780
  let behind = false;
28269
28781
  for (const row of rows) {
28270
28782
  const params = {
28271
- id: randomUUID3(),
28783
+ id: randomUUID4(),
28272
28784
  namespace: row.namespace,
28273
28785
  packId: row.packId,
28274
28786
  version: row.version,
@@ -28280,7 +28792,7 @@ var SqliteInstalledPacksRepository = class {
28280
28792
  if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
28281
28793
  this.upsertAvailableStmt.run({
28282
28794
  ...params,
28283
- id: randomUUID3(),
28795
+ id: randomUUID4(),
28284
28796
  recordedBy: meta4?.recordedBy ?? null
28285
28797
  });
28286
28798
  } else {
@@ -28526,9 +29038,65 @@ var SqliteInstalledPacksRepository = class {
28526
29038
  // NOT on the hook path — so, unlike recordInventory, these surface errors to the
28527
29039
  // caller rather than swallowing them. Each returns whether a row matched, so the
28528
29040
  // caller can tell an edit from a no-such-detection.
29041
+ /**
29042
+ * The rules one installed pack owns, reduced to what a floor computation
29043
+ * reads. Display-tolerant parsing on purpose: a pack whose snapshot is
29044
+ * unreadable contributes no rules to a scan either, so it is not a detection
29045
+ * the control plane can be governing, and an empty list correctly imposes no
29046
+ * floor. Enabled state is deliberately not filtered — a disabled pack is one
29047
+ * the user can re-enable, and its assignment stays governed meanwhile.
29048
+ */
29049
+ packFloorRules(namespace, packId) {
29050
+ const row = getRow(this.packRulesStmt, [namespace, packId]);
29051
+ if (!row) return [];
29052
+ return parseRules(row.rulesJson).map((rule) => ({ id: rule.id, category: rule.category }));
29053
+ }
29054
+ /**
29055
+ * What the connected control plane imposes on one installed pack, or null on a
29056
+ * machine that is its own authority (standalone, no cached bundle, or a
29057
+ * repository constructed without a layout base).
29058
+ *
29059
+ * Exposed as a READ so a surface can render the constraint — grey out the
29060
+ * choices below the floor, mark a locked detection as locked — rather than
29061
+ * offer the user a picker whose selections it will then be told it may not
29062
+ * make. The refusal in `setPolicy` does not depend on any surface calling this.
29063
+ */
29064
+ policyFloor(namespace, packId) {
29065
+ if (this.baseDir === void 0) return null;
29066
+ return controlPlanePolicyFloor(this.packFloorRules(namespace, packId), this.baseDir);
29067
+ }
29068
+ /**
29069
+ * The same answer for several packs, keyed `namespace/packId` and carrying an
29070
+ * entry only for a pack the control plane actually governs.
29071
+ *
29072
+ * A surface listing every detection asks per pack, and asking through
29073
+ * `policyFloor` re-reads the settings, re-reads and re-parses the whole cached
29074
+ * bundle and rebuilds its indexes once per pack — the entire cost of one
29075
+ * answer, repeated for each row, on every render. This reads all of that once.
29076
+ * Packs whose rules the snapshot cannot produce simply contribute no entry,
29077
+ * exactly as the single-pack read returns null for them.
29078
+ */
29079
+ policyFloors(packs2) {
29080
+ const floors = /* @__PURE__ */ new Map();
29081
+ if (this.baseDir === void 0) return floors;
29082
+ const source = openControlPlaneFloors(this.baseDir);
29083
+ if (source === null) return floors;
29084
+ for (const pack of packs2) {
29085
+ const floor = source.floorFor(this.packFloorRules(pack.namespace, pack.packId));
29086
+ if (floor !== null) floors.set(`${pack.namespace}/${pack.packId}`, floor);
29087
+ }
29088
+ return floors;
29089
+ }
28529
29090
  /**
28530
29091
  * Assign (or clear, with null) the enforcement policy for one installed pack.
28531
- * `policyId` must be a known built-in id (monitor/warn/redact/block).
29092
+ * `policyId` must be a known built-in id (monitor/warn/redact/block/vault).
29093
+ *
29094
+ * On an ATTACHED machine the organization's bundle is a floor this refuses to
29095
+ * write below, and a detection the organization has authored a policy for is
29096
+ * refused outright — see policy-floor.ts for both, and for why the refusal is
29097
+ * a throw rather than a silently substituted value. This is the one device-local
29098
+ * write path for the assignment, so the check belongs here rather than on any
29099
+ * surface that offers the choice.
28532
29100
  */
28533
29101
  setPolicy(namespace, packId, policyId) {
28534
29102
  if (policyId !== null && !BuiltinPolicyId.safeParse(policyId).success) {
@@ -28536,14 +29104,38 @@ var SqliteInstalledPacksRepository = class {
28536
29104
  `Unknown policy '${policyId}'. Must be one of: ${KNOWN_BUILTIN_IDS.join(", ")}.`
28537
29105
  );
28538
29106
  }
29107
+ const requested = policyId;
29108
+ const floor = this.policyFloor(namespace, packId);
29109
+ if (floor !== null) {
29110
+ const refusal = policyAssignmentRefusal(requested, floor);
29111
+ if (refusal !== null) {
29112
+ throw new PolicyFloorError(`${namespace}/${packId}`, requested, floor.floor, refusal);
29113
+ }
29114
+ }
28539
29115
  const res = this.db.prepare(
28540
29116
  `UPDATE installed_packs SET policy_id = :policyId, updated_at = :now
28541
29117
  WHERE namespace = :namespace AND pack_id = :packId`
28542
29118
  ).run({ policyId, now: Date.now(), namespace, packId });
28543
29119
  return Number(res.changes) > 0;
28544
29120
  }
28545
- /** Enable or disable one installed pack. */
29121
+ /**
29122
+ * Enable or disable one installed pack.
29123
+ *
29124
+ * On an ATTACHED machine a detection the organization's bundle governs at all
29125
+ * may not be switched OFF here — see packEnablementRefusal for why that is not
29126
+ * merely another point below the floor, and why re-enabling stays open. Like
29127
+ * the assignment above, the check belongs at this write path rather than on a
29128
+ * surface: this is the one device-local writer of the column, and a refusal
29129
+ * that lived in a page would leave the CLI free.
29130
+ */
28546
29131
  setEnabled(namespace, packId, enabled) {
29132
+ const floor = this.policyFloor(namespace, packId);
29133
+ if (floor !== null) {
29134
+ const refusal = packEnablementRefusal(enabled, floor);
29135
+ if (refusal !== null) {
29136
+ throw new PolicyFloorError(`${namespace}/${packId}`, null, floor.floor, refusal);
29137
+ }
29138
+ }
28547
29139
  const res = this.db.prepare(
28548
29140
  `UPDATE installed_packs SET enabled = :enabled, updated_at = :now
28549
29141
  WHERE namespace = :namespace AND pack_id = :packId`
@@ -28629,7 +29221,7 @@ var SqliteInventoryRepository = class {
28629
29221
  };
28630
29222
 
28631
29223
  // ../../packages/persistence/src/repositories/inventory-assets.ts
28632
- import { randomUUID as randomUUID4 } from "crypto";
29224
+ import { randomUUID as randomUUID5 } from "crypto";
28633
29225
  var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
28634
29226
  var VALID_HARNESS_IDS = new Set(HarnessId.options);
28635
29227
  var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
@@ -29118,7 +29710,7 @@ var SqliteInventoryAssetsRepository = class {
29118
29710
  `INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
29119
29711
  VALUES (:id, :projectId, :path, :access, :now, :now)
29120
29712
  ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
29121
- ).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
29713
+ ).run({ id: randomUUID5(), projectId, path, access, now: Date.now() });
29122
29714
  }
29123
29715
  return true;
29124
29716
  }
@@ -29139,7 +29731,7 @@ var SqliteInventoryAssetsRepository = class {
29139
29731
  `INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
29140
29732
  VALUES (:id, :assetId, :trust, :now, :now)
29141
29733
  ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
29142
- ).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
29734
+ ).run({ id: randomUUID5(), assetId, trust, now: Date.now() });
29143
29735
  }
29144
29736
  this.configRowsCache = void 0;
29145
29737
  return "ok";
@@ -29436,7 +30028,7 @@ var SqliteInventoryAssetsRepository = class {
29436
30028
  };
29437
30029
 
29438
30030
  // ../../packages/persistence/src/repositories/policies.ts
29439
- import { randomUUID as randomUUID5 } from "crypto";
30031
+ import { randomUUID as randomUUID6 } from "crypto";
29440
30032
  var SqlitePoliciesRepository = class {
29441
30033
  constructor(db) {
29442
30034
  this.db = db;
@@ -29471,7 +30063,7 @@ var SqlitePoliciesRepository = class {
29471
30063
  failOpenTransaction(this.db, () => {
29472
30064
  for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
29473
30065
  stmt.run({
29474
- id: randomUUID5(),
30066
+ id: randomUUID6(),
29475
30067
  target: JSON.stringify({ category }),
29476
30068
  action,
29477
30069
  now: Date.now()
@@ -29491,7 +30083,7 @@ var SqlitePoliciesRepository = class {
29491
30083
  `INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
29492
30084
  VALUES (:id, 'global', :target, :action, 1, :now, :now)
29493
30085
  ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
29494
- ).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
30086
+ ).run({ id: randomUUID6(), target: JSON.stringify({ category }), action, now });
29495
30087
  }
29496
30088
  // Caps every global per-category policy currently set to block/redact down
29497
30089
  // to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
@@ -29559,7 +30151,7 @@ var SqlitePolicyCatalogRepository = class {
29559
30151
  };
29560
30152
 
29561
30153
  // ../../packages/persistence/src/repositories/project-files.ts
29562
- import { randomUUID as randomUUID6 } from "crypto";
30154
+ import { randomUUID as randomUUID7 } from "crypto";
29563
30155
  var SqliteProjectFilesRepository = class {
29564
30156
  constructor(db) {
29565
30157
  this.db = db;
@@ -29591,7 +30183,7 @@ var SqliteProjectFilesRepository = class {
29591
30183
  const stamp = Math.max(now, maxStamp + 1);
29592
30184
  for (const file2 of scan2.files) {
29593
30185
  this.upsertStmt.run({
29594
- id: randomUUID6(),
30186
+ id: randomUUID7(),
29595
30187
  projectId,
29596
30188
  path: file2.path,
29597
30189
  name: file2.name,
@@ -29605,9 +30197,9 @@ var SqliteProjectFilesRepository = class {
29605
30197
  };
29606
30198
 
29607
30199
  // ../../packages/persistence/src/repositories/resolutions.ts
29608
- import { randomUUID as randomUUID7 } from "crypto";
30200
+ import { randomUUID as randomUUID8 } from "crypto";
29609
30201
  var SqliteResolutionsRepository = class {
29610
- constructor(db, now = () => Date.now(), newId = () => randomUUID7()) {
30202
+ constructor(db, now = () => Date.now(), newId = () => randomUUID8()) {
29611
30203
  this.db = db;
29612
30204
  this.now = now;
29613
30205
  this.newId = newId;
@@ -29820,7 +30412,7 @@ var SqliteScanLedgerRepository = class {
29820
30412
  };
29821
30413
 
29822
30414
  // ../../packages/persistence/src/repositories/secret-vault.ts
29823
- import { randomUUID as randomUUID8 } from "crypto";
30415
+ import { randomUUID as randomUUID9 } from "crypto";
29824
30416
  function pageLimit(requested, fallback) {
29825
30417
  if (requested === void 0) return fallback;
29826
30418
  return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
@@ -29866,12 +30458,14 @@ var SELECT_COLUMNS = `
29866
30458
  ciphertext,
29867
30459
  nonce,
29868
30460
  auth_tag AS authTag,
30461
+ user_authorized AS userAuthorized,
29869
30462
  occurrence_count AS occurrenceCount,
29870
30463
  first_seen AS firstSeen,
29871
30464
  last_seen AS lastSeen`;
29872
30465
  function toRow(raw) {
29873
- const { provider, ...rest } = raw;
29874
- return provider === null ? rest : { ...rest, provider };
30466
+ const { provider, userAuthorized, ...rest } = raw;
30467
+ const row = { ...rest, userAuthorized: userAuthorized !== 0 };
30468
+ return provider === null ? row : { ...row, provider };
29875
30469
  }
29876
30470
  var SqliteSecretVaultRepository = class {
29877
30471
  constructor(db) {
@@ -29881,17 +30475,18 @@ var SqliteSecretVaultRepository = class {
29881
30475
  pointer_id, value_fingerprint, fingerprint_key_version, key_version,
29882
30476
  format_version, category, rule_id, masked_match, provider,
29883
30477
  ciphertext, nonce, auth_tag,
29884
- occurrence_count, first_seen, last_seen
30478
+ user_authorized, occurrence_count, first_seen, last_seen
29885
30479
  ) VALUES (
29886
30480
  :pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
29887
30481
  :formatVersion, :category, :ruleId, :maskedMatch, :provider,
29888
30482
  :ciphertext, :nonce, :authTag,
29889
- 1, :now, :now
30483
+ :userAuthorized, 1, :now, :now
29890
30484
  )`
29891
30485
  );
29892
30486
  this.bumpStmt = db.prepare(
29893
30487
  `UPDATE secret_vault
29894
- SET occurrence_count = occurrence_count + 1, last_seen = :now
30488
+ SET occurrence_count = occurrence_count + 1, last_seen = :now,
30489
+ user_authorized = max(user_authorized, :userAuthorized)
29895
30490
  WHERE value_fingerprint = :valueFingerprint`
29896
30491
  );
29897
30492
  this.byPointerStmt = db.prepare(
@@ -29911,6 +30506,7 @@ var SqliteSecretVaultRepository = class {
29911
30506
  SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
29912
30507
  WHERE pointer_id = :pointerId`
29913
30508
  );
30509
+ this.deleteByPointerStmt = db.prepare(`DELETE FROM secret_vault WHERE pointer_id = :pointerId`);
29914
30510
  this.derefStmt = db.prepare(
29915
30511
  `INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
29916
30512
  VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
@@ -29924,6 +30520,7 @@ var SqliteSecretVaultRepository = class {
29924
30520
  listStmt;
29925
30521
  replaceCiphertextStmt;
29926
30522
  refreshFingerprintStmt;
30523
+ deleteByPointerStmt;
29927
30524
  derefStmt;
29928
30525
  /**
29929
30526
  * Vault a value, or record another sighting of one already vaulted. Keyed on
@@ -29932,6 +30529,11 @@ var SqliteSecretVaultRepository = class {
29932
30529
  * pointer, category and ciphertext, so the same secret always resolves to one
29933
30530
  * wire token. `minted` is true only when this call created the row.
29934
30531
  *
30532
+ * `userAuthorized` is the one field a repeat call may still change, and only
30533
+ * upwards: it records that a PERSON asked for this value to be replaced, and
30534
+ * the row is shared with every automatic path that vaults the same value. See
30535
+ * `bumpStmt` for why clearing it is the defect this shape exists to refuse.
30536
+ *
29935
30537
  * The read-then-write runs in one IMMEDIATE transaction so two concurrent
29936
30538
  * writers cannot both decide they are minting.
29937
30539
  */
@@ -29958,13 +30560,18 @@ var SqliteSecretVaultRepository = class {
29958
30560
  ciphertext: input2.ciphertext,
29959
30561
  nonce: input2.nonce,
29960
30562
  authTag: input2.authTag,
30563
+ userAuthorized: input2.userAuthorized === true ? 1 : 0,
29961
30564
  now
29962
30565
  })
29963
30566
  );
29964
30567
  minted = true;
29965
30568
  return;
29966
30569
  }
29967
- this.bumpStmt.run({ valueFingerprint: input2.valueFingerprint, now });
30570
+ this.bumpStmt.run({
30571
+ valueFingerprint: input2.valueFingerprint,
30572
+ userAuthorized: input2.userAuthorized === true ? 1 : 0,
30573
+ now
30574
+ });
29968
30575
  },
29969
30576
  "IMMEDIATE"
29970
30577
  );
@@ -30024,6 +30631,42 @@ var SqliteSecretVaultRepository = class {
30024
30631
  );
30025
30632
  return destroyed;
30026
30633
  }
30634
+ /**
30635
+ * Destroy the named entries and report WHICH ones went — the scoped
30636
+ * counterpart to `purgeAll`, for a caller that has already put those specific
30637
+ * values back where they came from. Ids the store does not hold are absent
30638
+ * from the answer rather than an error, so a set assembled from a stale read
30639
+ * is not a fault. The deref audit is left alone, exactly as the purge leaves
30640
+ * it.
30641
+ *
30642
+ * The ids come back rather than a count because the caller's next act is to
30643
+ * write a purge row per destroyed entry, and a record of destruction has to
30644
+ * be a record of what was really destroyed: a selection is a claim about a
30645
+ * read that has since gone stale, and auditing from it invents a purge for an
30646
+ * entry still sitting in the vault.
30647
+ *
30648
+ * One transaction over the whole set rather than a statement per id: the
30649
+ * caller hands this the result of a restore pass it has completed, and a
30650
+ * fault partway through must leave the vault as it was found rather than
30651
+ * destroying a prefix of it. The vault holds the only copy of what a pointer
30652
+ * stands for, so half a delete is not a state anything can recover from.
30653
+ */
30654
+ deleteByPointerIds(pointerIds) {
30655
+ if (pointerIds.length === 0) return [];
30656
+ const deleted = [];
30657
+ withTransaction(
30658
+ this.db,
30659
+ () => {
30660
+ for (const pointerId of pointerIds) {
30661
+ if (Number(this.deleteByPointerStmt.run({ pointerId }).changes) > 0) {
30662
+ deleted.push(pointerId);
30663
+ }
30664
+ }
30665
+ },
30666
+ "IMMEDIATE"
30667
+ );
30668
+ return deleted;
30669
+ }
30027
30670
  /**
30028
30671
  * Record (or re-stamp) one place a pointer has been written. One row per
30029
30672
  * (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
@@ -30036,7 +30679,7 @@ var SqliteSecretVaultRepository = class {
30036
30679
  VALUES (:id, :pointerId, :location, :kind, :now, :now)
30037
30680
  ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
30038
30681
  ).run({
30039
- id: randomUUID8(),
30682
+ id: randomUUID9(),
30040
30683
  pointerId: entry.pointerId,
30041
30684
  location: entry.location,
30042
30685
  kind: entry.kind,
@@ -30549,15 +31192,15 @@ var SqliteSecurityRepository = class {
30549
31192
  const from = now - RANGE_DAYS[range] * DAY_MS4;
30550
31193
  const rows = allRows(
30551
31194
  this.db.prepare(
30552
- `SELECT json_extract(e.attributes, '$.repo') AS repo, count(*) AS c
31195
+ `SELECT e.repo AS repo, count(*) AS c
30553
31196
  FROM inspection_findings f
30554
31197
  JOIN audit_events e ON e.id = f.audit_event_id
30555
31198
  WHERE e.started_at >= :from AND e.started_at < :to
30556
31199
  AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
30557
- AND json_extract(e.attributes, '$.repo') IS NOT NULL
30558
- AND json_extract(e.attributes, '$.repo') != ''
30559
- GROUP BY repo
30560
- ORDER BY c DESC, repo
31200
+ AND e.repo IS NOT NULL
31201
+ AND e.repo != ''
31202
+ GROUP BY e.repo
31203
+ ORDER BY c DESC, e.repo
30561
31204
  LIMIT :limit`
30562
31205
  ),
30563
31206
  { from, to: now, limit }
@@ -30619,7 +31262,7 @@ var SqliteSecurityRepository = class {
30619
31262
  `SELECT f.finding_key AS finding_key,
30620
31263
  d.rule_id AS rule_id,
30621
31264
  d.severity AS severity,
30622
- json_extract(e.attributes, '$.file_path') AS path,
31265
+ e.file_path AS path,
30623
31266
  COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
30624
31267
  latest.resolved_at AS latest_resolved_at
30625
31268
  FROM ${LATEST_RESOLUTION_BY_KEY_SQL} latest
@@ -30672,7 +31315,7 @@ var SqliteSecurityRepository = class {
30672
31315
  };
30673
31316
 
30674
31317
  // ../../packages/persistence/src/repositories/shares.ts
30675
- import { randomUUID as randomUUID9 } from "crypto";
31318
+ import { randomUUID as randomUUID10 } from "crypto";
30676
31319
  var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
30677
31320
  var IN_CHUNK = 500;
30678
31321
  var KIND_ORDER = ["provider", "internal", "external", "ip"];
@@ -30760,7 +31403,7 @@ function buildSummary(dest, endpoints) {
30760
31403
  callSiteCount,
30761
31404
  transports: distinctTransports(transports),
30762
31405
  dataClasses: distinctDataClasses(dataClasses),
30763
- review: buildReviewInfo(dest.trust, transports),
31406
+ review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
30764
31407
  network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
30765
31408
  endpoints: endpoints.map(toEndpointSummary)
30766
31409
  };
@@ -30787,7 +31430,7 @@ function buildDetail(dest, endpoints, callSites) {
30787
31430
  lastSeen: new Date(lastSeenMs).toISOString(),
30788
31431
  transports: distinctTransports(transports),
30789
31432
  dataClasses: distinctDataClasses(endpoints.map((e) => e.dataClass)),
30790
- review: buildReviewInfo(dest.trust, transports),
31433
+ review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
30791
31434
  network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
30792
31435
  note: dest.note,
30793
31436
  endpoints: endpoints.map((ep) => ({
@@ -30816,7 +31459,11 @@ var SqliteSharesRepository = class {
30816
31459
  FROM share_destination d
30817
31460
  LEFT JOIN share_endpoint e ON e.destination_id = d.id
30818
31461
  AND e.transport IN ${PLAINTEXT_TRANSPORT_SQL}
30819
- WHERE d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL`
31462
+ WHERE (d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL)
31463
+ AND NOT EXISTS (
31464
+ SELECT 1 FROM egress_decision_override o
31465
+ WHERE o.host = d.host OR (o.destination_id = d.id AND o.host IS NULL)
31466
+ )`
30820
31467
  );
30821
31468
  const kindCounts = countBy(
30822
31469
  this.db,
@@ -30928,7 +31575,7 @@ var SqliteSharesRepository = class {
30928
31575
  (id, destination_id, host, decision, created_at, updated_at)
30929
31576
  VALUES (:id, :destinationId, :host, :decision, :now, :now)`
30930
31577
  ).run({
30931
- id: randomUUID9(),
31578
+ id: randomUUID10(),
30932
31579
  destinationId,
30933
31580
  host: dest.host,
30934
31581
  decision,
@@ -31077,7 +31724,7 @@ var SqliteSharesRepository = class {
31077
31724
  let destinationId = destIds.get(hit.host);
31078
31725
  if (destinationId === void 0) {
31079
31726
  destStmt.run({
31080
- id: randomUUID9(),
31727
+ id: randomUUID10(),
31081
31728
  kind: hit.kind,
31082
31729
  name: hit.name,
31083
31730
  host: hit.host,
@@ -31093,7 +31740,7 @@ var SqliteSharesRepository = class {
31093
31740
  let endpointId = endpointIds.get(endpointKey);
31094
31741
  if (endpointId === void 0) {
31095
31742
  endpointStmt.run({
31096
- id: randomUUID9(),
31743
+ id: randomUUID10(),
31097
31744
  destinationId,
31098
31745
  method: hit.method,
31099
31746
  transport: hit.transport,
@@ -31106,7 +31753,7 @@ var SqliteSharesRepository = class {
31106
31753
  endpointIds.set(endpointKey, endpointId);
31107
31754
  }
31108
31755
  siteStmt.run({
31109
- id: randomUUID9(),
31756
+ id: randomUUID10(),
31110
31757
  endpointId,
31111
31758
  project: input2.project,
31112
31759
  projectKey: input2.projectKey,
@@ -31471,6 +32118,7 @@ function purgeSampleData(db) {
31471
32118
  }
31472
32119
 
31473
32120
  // ../../packages/persistence/src/database.ts
32121
+ var CAPTURE_GRAIN = new Set(EventKind.options);
31474
32122
  var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
31475
32123
  "aka.persistence.unsafeTestOnlyRawHandle"
31476
32124
  );
@@ -31518,7 +32166,7 @@ function backupLegacyStore(db, file2) {
31518
32166
  discardStore(file2, backup);
31519
32167
  return backup;
31520
32168
  }
31521
- function openAndInitialize(file2) {
32169
+ function openAndInitialize(file2, base) {
31522
32170
  let db = openWithPragmas(file2);
31523
32171
  try {
31524
32172
  if (isForeignSqliteLineage(db)) {
@@ -31531,7 +32179,7 @@ function openAndInitialize(file2) {
31531
32179
  applyMigrations(db, file2);
31532
32180
  tightenPerms(file2);
31533
32181
  const policies = new SqlitePoliciesRepository(db);
31534
- const installedPacks = new SqliteInstalledPacksRepository(db);
32182
+ const installedPacks = new SqliteInstalledPacksRepository(db, base);
31535
32183
  const repositories = {
31536
32184
  events: new SqliteEventsRepository(db),
31537
32185
  findings: new SqliteFindingsRepository(db),
@@ -31567,7 +32215,7 @@ function openAndInitialize(file2) {
31567
32215
  }
31568
32216
  function openLocalDatabase(dir) {
31569
32217
  ensureDataDirSync(dir);
31570
- const file2 = join4(dir, DB_FILENAME);
32218
+ const file2 = join7(dir, DB_FILENAME);
31571
32219
  reapStalePartials(file2);
31572
32220
  const {
31573
32221
  db,
@@ -31595,7 +32243,13 @@ function openLocalDatabase(dir) {
31595
32243
  inspectionDefinitions,
31596
32244
  inspectionFindings,
31597
32245
  configInventory
31598
- } = openAndInitialize(file2);
32246
+ } = openAndInitialize(
32247
+ file2,
32248
+ // `dir` is always `<base>/data` — every caller resolves it through
32249
+ // `dataDir()` — so its parent is the `~/.aka` base the layout splits into
32250
+ // settings/ and data/, and the pack-policy floor needs both halves.
32251
+ dirname2(dir)
32252
+ );
31599
32253
  function captureRowId(event) {
31600
32254
  return captureId(
31601
32255
  event.metadata?.sessionId ?? null,
@@ -31608,6 +32262,21 @@ function openLocalDatabase(dir) {
31608
32262
  historySync.markSynced([captureRowId(event)], atMs);
31609
32263
  });
31610
32264
  }
32265
+ function markCaptureOwed(event) {
32266
+ failOpenTransaction(db, () => {
32267
+ historySync.markCaptureOwed(captureRowId(event));
32268
+ });
32269
+ }
32270
+ function markAuditEventsDelivered(events2, atMs) {
32271
+ const stampable = events2.filter((event) => !CAPTURE_GRAIN.has(event.eventType));
32272
+ if (stampable.length === 0) return;
32273
+ failOpenTransaction(db, () => {
32274
+ historySync.markSynced(
32275
+ stampable.map((event) => event.id),
32276
+ atMs
32277
+ );
32278
+ });
32279
+ }
31611
32280
  function recordCapture(event, detected) {
31612
32281
  failOpenTransaction(db, () => {
31613
32282
  const sessionId = event.metadata?.sessionId;
@@ -31694,7 +32363,7 @@ function openLocalDatabase(dir) {
31694
32363
  const definitionId = definitionIds.get(`${finding2.ruleId}@${finding2.version}`);
31695
32364
  if (!definitionId) continue;
31696
32365
  inspectionFindings.insertFinding({
31697
- id: randomUUID10(),
32366
+ id: randomUUID11(),
31698
32367
  auditEventId: record2.scanEvent.id,
31699
32368
  inspectionDefinitionId: definitionId,
31700
32369
  span: finding2.span,
@@ -31790,6 +32459,8 @@ function openLocalDatabase(dir) {
31790
32459
  inspectionFindings,
31791
32460
  recordCapture,
31792
32461
  markCaptureDelivered,
32462
+ markCaptureOwed,
32463
+ markAuditEventsDelivered,
31793
32464
  ensureInventory,
31794
32465
  recordConfigScan,
31795
32466
  recordProjectFiles,
@@ -31808,20 +32479,6 @@ function openLocalDatabase(dir) {
31808
32479
  };
31809
32480
  }
31810
32481
 
31811
- // ../../packages/persistence/src/file-lock.ts
31812
- import { randomUUID as randomUUID11 } from "crypto";
31813
- import {
31814
- closeSync,
31815
- existsSync as existsSync2,
31816
- openSync,
31817
- readFileSync as readFileSync2,
31818
- rmSync as rmSync5,
31819
- statSync as statSync3,
31820
- writeFileSync as writeFileSync2
31821
- } from "fs";
31822
- import { hostname as hostname3 } from "os";
31823
- var PARK = new Int32Array(new SharedArrayBuffer(4));
31824
-
31825
32482
  // ../../packages/persistence/src/finding-key.ts
31826
32483
  import { createHash as createHash3 } from "crypto";
31827
32484
  function normalizeFilePath(filePath) {
@@ -31834,13 +32491,13 @@ function computeFindingKey(input2) {
31834
32491
 
31835
32492
  // ../../packages/persistence/src/fingerprint.ts
31836
32493
  import { createHmac, randomBytes } from "crypto";
31837
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
31838
- import { join as join5 } from "path";
32494
+ import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
32495
+ import { join as join8 } from "path";
31839
32496
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
31840
32497
  var EXCEPTION_KEY_FILENAME = "exception.key";
31841
32498
  var KEY_MATERIAL_BYTES = 32;
31842
32499
  function keyFilePath(dataDir2) {
31843
- return join5(dataDir2, EXCEPTION_KEY_FILENAME);
32500
+ return join8(dataDir2, EXCEPTION_KEY_FILENAME);
31844
32501
  }
31845
32502
  function parseKeyFile(raw) {
31846
32503
  const parsed2 = JSON.parse(raw);
@@ -31878,7 +32535,7 @@ var FloorUnreadableError = class extends Error {
31878
32535
  }
31879
32536
  };
31880
32537
  function storedKeyVersionFloor(dataDir2) {
31881
- const file2 = join5(dataDir2, DB_FILENAME);
32538
+ const file2 = join8(dataDir2, DB_FILENAME);
31882
32539
  if (!existsSync3(file2)) return 0;
31883
32540
  let db;
31884
32541
  try {
@@ -31933,7 +32590,7 @@ function occupantMessage(file2, kind) {
31933
32590
  function readFingerprintKey(dataDir2) {
31934
32591
  let raw;
31935
32592
  try {
31936
- raw = readFileSync3(keyFilePath(dataDir2), "utf8");
32593
+ raw = readFileSync6(keyFilePath(dataDir2), "utf8");
31937
32594
  } catch (err) {
31938
32595
  if (err.code === "ENOENT") return null;
31939
32596
  throw err instanceof Error ? err : new Error(String(err));
@@ -31957,143 +32614,12 @@ function fingerprintValue(key, raw) {
31957
32614
 
31958
32615
  // ../../packages/persistence/src/history-preview.ts
31959
32616
  import { existsSync as existsSync4 } from "fs";
31960
- import { join as join6 } from "path";
32617
+ import { join as join9 } from "path";
31961
32618
  import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
31962
32619
 
31963
- // ../../packages/persistence/src/local-layout.ts
31964
- import { renameSync as renameSync3 } from "fs";
31965
- import { mkdir } from "fs/promises";
31966
- import { homedir } from "os";
31967
- import { join as join7 } from "path";
31968
- function defaultDataDir() {
31969
- return join7(homedir(), ".aka");
31970
- }
31971
- function settingsDir(base = defaultDataDir()) {
31972
- return join7(base, "settings");
31973
- }
31974
- function dataDir(base = defaultDataDir()) {
31975
- return join7(base, "data");
31976
- }
31977
- function dbPath(base = defaultDataDir()) {
31978
- return join7(dataDir(base), "aka.db");
31979
- }
31980
- async function ensureDataDir(dir = defaultDataDir()) {
31981
- await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
31982
- tightenDir(dir);
31983
- }
31984
- function ensureLayoutDirSync(dir = defaultDataDir()) {
31985
- ensureDataDirSync(dir);
31986
- }
31987
- function migrateLegacyLayout(base = defaultDataDir()) {
31988
- const moves = [
31989
- { name: "config.json", dest: settingsDir(base) },
31990
- { name: "policy-cache.json", dest: dataDir(base) }
31991
- ];
31992
- for (const { name: name2, dest } of moves) {
31993
- try {
31994
- ensureDataDirSync(dest);
31995
- const moved = join7(dest, name2);
31996
- renameSync3(join7(base, name2), moved);
31997
- tightenFile(moved);
31998
- } catch {
31999
- }
32000
- }
32001
- }
32002
-
32003
- // ../../packages/persistence/src/managed-settings.ts
32004
- import { readFileSync as readFileSync4 } from "fs";
32005
- import { posix, win32 } from "path";
32006
- function managedSettingsPaths(platform2 = process.platform) {
32007
- if (platform2 === "darwin") {
32008
- return [
32009
- posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
32010
- posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
32011
- ];
32012
- }
32013
- if (platform2 === "win32") {
32014
- return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
32015
- }
32016
- return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
32017
- }
32018
- function readManagedSettings(paths = managedSettingsPaths()) {
32019
- for (const path of paths) {
32020
- let text;
32021
- try {
32022
- text = readFileSync4(path, "utf8");
32023
- } catch {
32024
- continue;
32025
- }
32026
- const record2 = parseJsonObject(text);
32027
- if (!record2) continue;
32028
- const parsed2 = ManagedSettings.safeParse(record2);
32029
- if (parsed2.success) return parsed2.data;
32030
- }
32031
- return null;
32032
- }
32033
- function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
32034
- if (!managed) return settings;
32035
- const { values } = managed;
32036
- const merged = { ...settings };
32037
- if (values.runMode !== void 0) merged.runMode = values.runMode;
32038
- if (values.controlPlane !== void 0) {
32039
- merged.controlPlane = {
32040
- ...values.controlPlane,
32041
- // The administrator pinned WHICH deployment, not WHEN this machine
32042
- // joined it. Keep the user's own attach time when the endpoint is
32043
- // unchanged, so a managed machine does not appear to re-attach on every
32044
- // read; stamp a fresh one when the administrator moved it.
32045
- attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
32046
- };
32047
- }
32048
- if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
32049
- if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
32050
- if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
32051
- if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
32052
- if (values.vaultConsent !== void 0) {
32053
- merged.vaultConsent = values.vaultConsent ? (
32054
- // Keep an existing valid grant so its acknowledgedAt survives; mint one
32055
- // at the current version otherwise.
32056
- settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
32057
- ) : void 0;
32058
- }
32059
- if (values.modelJudgeConsent !== void 0) {
32060
- merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
32061
- acknowledgedAt: now().toISOString(),
32062
- payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
32063
- } : void 0;
32064
- }
32065
- return merged;
32066
- }
32067
-
32068
- // ../../packages/persistence/src/settings.ts
32069
- import { readFileSync as readFileSync5 } from "fs";
32070
- import { join as join8 } from "path";
32071
- var SETTINGS_FILENAME = "settings.json";
32072
- function readWorkspaceSettings(base = defaultDataDir()) {
32073
- return overlayManagedSettings(readUserSettings(base), readManagedSettings());
32074
- }
32075
- function readUserSettings(base) {
32076
- const record2 = readJson(join8(settingsDir(base), SETTINGS_FILENAME));
32077
- if (!record2) return defaultWorkspaceSettings();
32078
- try {
32079
- return WorkspaceSettings.parse(record2);
32080
- } catch {
32081
- return defaultWorkspaceSettings();
32082
- }
32083
- }
32084
- function readJson(file2) {
32085
- let text;
32086
- try {
32087
- text = readFileSync5(file2, "utf8");
32088
- } catch {
32089
- return null;
32090
- }
32091
- return parseJsonObject(text) ?? null;
32092
- }
32093
-
32094
32620
  // ../../packages/persistence/src/store-symlinks.ts
32095
32621
  import { existsSync as existsSync5, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
32096
- import { dirname as dirname2, join as join9, resolve } from "path";
32622
+ import { dirname as dirname3, join as join10, resolve } from "path";
32097
32623
 
32098
32624
  // ../../packages/persistence/src/vault/crypto.ts
32099
32625
  import {
@@ -32107,19 +32633,19 @@ import {
32107
32633
  // ../../packages/persistence/src/vault/key-provider.ts
32108
32634
  import { execFileSync } from "child_process";
32109
32635
  import { randomBytes as randomBytes2 } from "crypto";
32110
- import { chmodSync as chmodSync3, readFileSync as readFileSync6, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
32111
- import { join as join10 } from "path";
32636
+ import { chmodSync as chmodSync3, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
32637
+ import { join as join11 } from "path";
32112
32638
 
32113
32639
  // ../../packages/persistence/src/vault/vault.ts
32114
32640
  import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
32115
32641
 
32116
32642
  // ../../packages/persistence/src/warn-era-cap.ts
32117
32643
  import { existsSync as existsSync6, writeFileSync as writeFileSync4 } from "fs";
32118
- import { join as join11 } from "path";
32644
+ import { join as join12 } from "path";
32119
32645
  var MARKER = "warn-era-capped";
32120
32646
  function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
32121
32647
  if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
32122
- const marker = join11(dataDir2, MARKER);
32648
+ const marker = join12(dataDir2, MARKER);
32123
32649
  if (existsSync6(marker)) return { capped: 0, skipped: "already-run" };
32124
32650
  const capped = db.policies.capCategoryActions();
32125
32651
  writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
@@ -32163,6 +32689,307 @@ function toEgressIngestRequest(input2) {
32163
32689
  };
32164
32690
  }
32165
32691
 
32692
+ // ../../packages/remote/src/http.ts
32693
+ import { request as httpRequest } from "http";
32694
+ import { request as httpsRequest } from "https";
32695
+ var DEFAULT_TIMEOUT_MS = 1e4;
32696
+ var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
32697
+ var RemoteRequestError = class extends Error {
32698
+ constructor(status) {
32699
+ super(`control-plane request failed with status ${String(status)}`);
32700
+ this.status = status;
32701
+ this.name = "RemoteRequestError";
32702
+ }
32703
+ status;
32704
+ };
32705
+ var RemoteRouteAbsent = class extends Error {
32706
+ constructor(route) {
32707
+ super(`control plane does not serve ${route}`);
32708
+ this.route = route;
32709
+ this.name = "RemoteRouteAbsent";
32710
+ }
32711
+ route;
32712
+ };
32713
+ var RemoteRequestInvalid = class extends Error {
32714
+ constructor(route, cause) {
32715
+ super(`refusing to send a malformed body to ${route}`);
32716
+ this.cause = cause;
32717
+ this.name = "RemoteRequestInvalid";
32718
+ }
32719
+ cause;
32720
+ };
32721
+ var RemoteResponseInvalid = class extends Error {
32722
+ constructor(route, detail) {
32723
+ super(`control plane answered ${route} with ${detail}`);
32724
+ this.name = "RemoteResponseInvalid";
32725
+ }
32726
+ };
32727
+ var RemoteTransportError = class extends Error {
32728
+ /**
32729
+ * The status the peer sent, when headers arrived and only the BODY was
32730
+ * refused.
32731
+ *
32732
+ * Undefined for the ordinary case this class was written for — no answer at
32733
+ * all. It exists because two paths reject after a status has already been
32734
+ * delivered: an oversized body and an aborted response. Discarding it there
32735
+ * reported a deployment answering 401 with a verbose body as a network
32736
+ * outage, which sends the reader to look at their network instead of their
32737
+ * credential.
32738
+ */
32739
+ constructor(reason, status) {
32740
+ super(`control-plane request did not complete: ${reason}`);
32741
+ this.status = status;
32742
+ this.name = "RemoteTransportError";
32743
+ }
32744
+ status;
32745
+ };
32746
+ async function send(options) {
32747
+ const url2 = new URL(options.url);
32748
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
32749
+ const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
32750
+ const requestOptions = {
32751
+ method: options.method,
32752
+ headers: {
32753
+ // CALLER HEADERS FIRST, so this module's own are not overridable. Spread
32754
+ // last they win, and two of the values below are ones no caller may
32755
+ // replace: `x-api-key` is the credential, and `content-length` is the
32756
+ // byte count that stops a multi-byte body being truncated by the
32757
+ // receiver. `SendOptions.headers` is a free-form record on an exported
32758
+ // function, so "no caller does that today" is not the guarantee to rely
32759
+ // on. The one header any caller actually passes — `if-none-match` on the
32760
+ // conditional GET — is untouched by this order.
32761
+ ...options.headers,
32762
+ // The credential. One header, matching what the deployment authenticates
32763
+ // on; a second copy in an `Authorization` header would be one more place
32764
+ // it can be logged by an intermediary for no gain.
32765
+ //
32766
+ // Spread conditionally rather than assigned as `undefined`: Node's header
32767
+ // handling and `content-length` bookkeeping treat a present-but-undefined
32768
+ // key differently from an absent one, and "the header is not there" is
32769
+ // the property the attach flow needs.
32770
+ ...options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey },
32771
+ accept: "application/json",
32772
+ ...options.body === void 0 ? {} : {
32773
+ "content-type": "application/json",
32774
+ // Byte length, not string length: a multi-byte body sent with a
32775
+ // character count is truncated by the receiver.
32776
+ "content-length": String(Buffer.byteLength(options.body))
32777
+ }
32778
+ }
32779
+ };
32780
+ return new Promise((resolve2, reject) => {
32781
+ let settled = false;
32782
+ const fail = (reason, status) => {
32783
+ if (settled) return;
32784
+ settled = true;
32785
+ reject(new RemoteTransportError(reason, status));
32786
+ };
32787
+ const req = send_(url2, requestOptions, (res) => {
32788
+ const chunks = [];
32789
+ let size = 0;
32790
+ res.on("data", (chunk) => {
32791
+ size += chunk.length;
32792
+ if (size > MAX_RESPONSE_BYTES) {
32793
+ fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
32794
+ res.destroy();
32795
+ req.destroy();
32796
+ return;
32797
+ }
32798
+ chunks.push(chunk);
32799
+ });
32800
+ res.on("aborted", () => {
32801
+ fail("the response was aborted", res.statusCode);
32802
+ });
32803
+ res.on("end", () => {
32804
+ if (settled) return;
32805
+ settled = true;
32806
+ resolve2({
32807
+ status: res.statusCode ?? 0,
32808
+ headers: res.headers,
32809
+ body: Buffer.concat(chunks).toString("utf8")
32810
+ });
32811
+ });
32812
+ });
32813
+ const deadline = setTimeout(() => {
32814
+ fail(`no response within ${String(timeoutMs)}ms`);
32815
+ req.destroy();
32816
+ }, timeoutMs);
32817
+ deadline.unref();
32818
+ req.on("upgrade", (_res, socket) => {
32819
+ fail("the deployment answered with a protocol upgrade");
32820
+ socket.destroy();
32821
+ });
32822
+ req.on("close", () => {
32823
+ fail("the connection closed before a response was read");
32824
+ clearTimeout(deadline);
32825
+ });
32826
+ req.on("error", (err) => {
32827
+ fail(err.message);
32828
+ });
32829
+ if (options.body !== void 0) req.write(options.body);
32830
+ req.end();
32831
+ });
32832
+ }
32833
+
32834
+ // ../../packages/remote/src/client.ts
32835
+ var ROUTES = {
32836
+ events: "/v1/events",
32837
+ auditEvents: "/v1/audit-events",
32838
+ auditEventsBatch: "/v1/audit-events/batch",
32839
+ inventory: "/v1/inventory",
32840
+ storePosture: "/v1/store-posture",
32841
+ policyBundle: "/v1/policy-bundle",
32842
+ whoami: "/v1/plugin/whoami",
32843
+ shares: "/v1/shares",
32844
+ commands: "/v1/plugin/commands"
32845
+ };
32846
+ function ackRoute(id) {
32847
+ return `${ROUTES.commands}/${encodeURIComponent(id)}/ack`;
32848
+ }
32849
+ function headerValue(response, name2) {
32850
+ const raw = response.headers[name2];
32851
+ if (raw === void 0) return void 0;
32852
+ return Array.isArray(raw) ? raw[0] : raw;
32853
+ }
32854
+ function okBody(response) {
32855
+ if (response.status < 200 || response.status >= 300) {
32856
+ throw new RemoteRequestError(response.status);
32857
+ }
32858
+ return response.body;
32859
+ }
32860
+ function parsed(schema, body, route) {
32861
+ let json2;
32862
+ try {
32863
+ json2 = JSON.parse(body);
32864
+ } catch {
32865
+ throw new RemoteResponseInvalid(route, "a body that is not JSON");
32866
+ }
32867
+ const result = schema.safeParse(json2);
32868
+ if (!result.success) {
32869
+ throw new RemoteResponseInvalid(route, "a body this client cannot read");
32870
+ }
32871
+ return result.data;
32872
+ }
32873
+ function withoutTrailingSlashes(endpoint) {
32874
+ let end = endpoint.length;
32875
+ while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
32876
+ return endpoint.slice(0, end);
32877
+ }
32878
+ var SLASH = "/".charCodeAt(0);
32879
+ function createRemoteClient(options) {
32880
+ const base = withoutTrailingSlashes(options.endpoint);
32881
+ const url2 = (route) => `${base}${route}`;
32882
+ const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
32883
+ const sendOne = async (event) => {
32884
+ const validated = RecordAuditEventRequest.safeParse(event);
32885
+ if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
32886
+ const response = await send({
32887
+ ...common,
32888
+ method: "POST",
32889
+ url: url2(ROUTES.auditEvents),
32890
+ body: JSON.stringify(validated.data)
32891
+ });
32892
+ okBody(response);
32893
+ };
32894
+ return {
32895
+ async ingestEvents(batch) {
32896
+ const response = await send({
32897
+ ...common,
32898
+ method: "POST",
32899
+ url: url2(ROUTES.events),
32900
+ body: JSON.stringify(batch)
32901
+ });
32902
+ return parsed(IngestAck, okBody(response), ROUTES.events);
32903
+ },
32904
+ async ingestInventory(context) {
32905
+ const response = await send({
32906
+ ...common,
32907
+ method: "POST",
32908
+ url: url2(ROUTES.inventory),
32909
+ body: JSON.stringify(context)
32910
+ });
32911
+ return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
32912
+ },
32913
+ async recordAuditEvent(event) {
32914
+ await sendOne(event);
32915
+ },
32916
+ async recordAuditEvents(events, opts) {
32917
+ const validated = RecordAuditEventBatch.safeParse({ events });
32918
+ if (!validated.success) {
32919
+ throw new RemoteRequestInvalid(ROUTES.auditEventsBatch, validated.error);
32920
+ }
32921
+ const response = await send({
32922
+ ...common,
32923
+ method: "POST",
32924
+ url: url2(ROUTES.auditEventsBatch),
32925
+ body: JSON.stringify(validated.data)
32926
+ });
32927
+ if (response.status === 404) {
32928
+ if (opts?.fallbackToSingleEvents !== true) {
32929
+ throw new RemoteRouteAbsent(ROUTES.auditEventsBatch);
32930
+ }
32931
+ for (const event of validated.data.events) await sendOne(event);
32932
+ return { accepted: validated.data.events.length };
32933
+ }
32934
+ return parsed(AuditEventBatchAck, okBody(response), ROUTES.auditEventsBatch);
32935
+ },
32936
+ async reportStorePosture(snapshot) {
32937
+ const response = await send({
32938
+ ...common,
32939
+ method: "POST",
32940
+ url: url2(ROUTES.storePosture),
32941
+ body: JSON.stringify(snapshot)
32942
+ });
32943
+ okBody(response);
32944
+ },
32945
+ async getPolicyBundle(etag) {
32946
+ const response = await send({
32947
+ ...common,
32948
+ method: "GET",
32949
+ url: url2(ROUTES.policyBundle),
32950
+ ...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
32951
+ });
32952
+ if (response.status === 304) {
32953
+ return { changed: false, etag: headerValue(response, "etag") ?? etag };
32954
+ }
32955
+ const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
32956
+ return { changed: true, bundle, etag: headerValue(response, "etag") };
32957
+ },
32958
+ async whoami() {
32959
+ const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
32960
+ return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
32961
+ },
32962
+ async recordProjectEgress(request) {
32963
+ const validated = EgressIngestRequest.safeParse(request);
32964
+ if (!validated.success) throw new RemoteRequestInvalid(ROUTES.shares, validated.error);
32965
+ const response = await send({
32966
+ ...common,
32967
+ method: "POST",
32968
+ url: url2(ROUTES.shares),
32969
+ body: JSON.stringify(validated.data)
32970
+ });
32971
+ okBody(response);
32972
+ },
32973
+ async pollCommand() {
32974
+ const response = await send({ ...common, method: "GET", url: url2(ROUTES.commands) });
32975
+ if (response.status === 404) return null;
32976
+ return parsed(DeviceCommandPollResponse, okBody(response), ROUTES.commands).command;
32977
+ },
32978
+ async ackCommand(id, body) {
32979
+ const validated = DeviceCommandAckBody.safeParse(body);
32980
+ const route = ackRoute(id);
32981
+ if (!validated.success) throw new RemoteRequestInvalid(route, validated.error);
32982
+ const response = await send({
32983
+ ...common,
32984
+ method: "POST",
32985
+ url: url2(route),
32986
+ body: JSON.stringify(validated.data)
32987
+ });
32988
+ okBody(response);
32989
+ }
32990
+ };
32991
+ }
32992
+
32166
32993
  // ../../packages/plugin-runtime/src/attached/failure.ts
32167
32994
  function statusOf(err) {
32168
32995
  if (typeof err !== "object" || err === null || !("status" in err)) return null;
@@ -32181,12 +33008,27 @@ function classifyFailure(err) {
32181
33008
  }
32182
33009
  }
32183
33010
 
33011
+ // ../../packages/plugin-runtime/src/attached/with-timeout.ts
33012
+ var REQUEST_TIMEOUT_MS = 2e3;
33013
+ function withTimeout(promise2, ms) {
33014
+ let timer;
33015
+ const timeout = new Promise((_, reject) => {
33016
+ timer = setTimeout(() => {
33017
+ reject(new Error("attached gateway request timed out"));
33018
+ }, ms);
33019
+ });
33020
+ promise2.catch(() => void 0);
33021
+ return Promise.race([promise2, timeout]).finally(() => {
33022
+ clearTimeout(timer);
33023
+ });
33024
+ }
33025
+
32184
33026
  // ../../packages/plugin-runtime/src/attached/forward-drops.ts
32185
- import { readFileSync as readFileSync7 } from "fs";
32186
- import { join as join12 } from "path";
33027
+ import { readFileSync as readFileSync8 } from "fs";
33028
+ import { join as join13 } from "path";
32187
33029
  var FORWARD_DROPS_FILENAME = ATTACHED_FORWARD_DROPS_FILENAME;
32188
33030
  function forwardDropsPath(dataDir2) {
32189
- return join12(dataDir2, FORWARD_DROPS_FILENAME);
33031
+ return join13(dataDir2, FORWARD_DROPS_FILENAME);
32190
33032
  }
32191
33033
  function recordForwardDrops(dataDir2, count, nowMs) {
32192
33034
  if (count <= 0) return;
@@ -32204,7 +33046,7 @@ function recordForwardDrops(dataDir2, count, nowMs) {
32204
33046
  }
32205
33047
  function readForwardDrops(dataDir2) {
32206
33048
  try {
32207
- const parsed2 = JSON.parse(readFileSync7(forwardDropsPath(dataDir2), "utf8"));
33049
+ const parsed2 = JSON.parse(readFileSync8(forwardDropsPath(dataDir2), "utf8"));
32208
33050
  if (typeof parsed2 !== "object" || parsed2 === null) return null;
32209
33051
  const record2 = parsed2;
32210
33052
  if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
@@ -32222,13 +33064,13 @@ function readForwardDrops(dataDir2) {
32222
33064
 
32223
33065
  // ../../packages/plugin-runtime/src/attached/forward-policy.ts
32224
33066
  import { randomUUID as randomUUID15 } from "crypto";
32225
- import { readFileSync as readFileSync13 } from "fs";
33067
+ import { readFileSync as readFileSync14 } from "fs";
32226
33068
  import { readFile, rename, writeFile } from "fs/promises";
32227
- import { join as join21 } from "path";
33069
+ import { join as join22 } from "path";
32228
33070
 
32229
33071
  // ../../packages/plugin-sdk/src/config.ts
32230
33072
  import { existsSync as existsSync7 } from "fs";
32231
- import { join as join13 } from "path";
33073
+ import { join as join14 } from "path";
32232
33074
 
32233
33075
  // ../../packages/plugin-sdk/src/provider-env.ts
32234
33076
  var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
@@ -32282,7 +33124,7 @@ function resolveProvider() {
32282
33124
  function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
32283
33125
  try {
32284
33126
  ensureLayoutDirSync(base);
32285
- const settingsFile = join13(settingsDir(base), "settings.json");
33127
+ const settingsFile = join14(settingsDir(base), "settings.json");
32286
33128
  if (existsSync7(settingsFile)) tightenFile(settingsFile);
32287
33129
  } catch {
32288
33130
  }
@@ -32306,9 +33148,9 @@ function resolveProviderSafe(resolveProviderFn) {
32306
33148
  }
32307
33149
 
32308
33150
  // ../../packages/plugin-sdk/src/config-inventory.ts
32309
- import { readdirSync as readdirSync2, readFileSync as readFileSync9, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
33151
+ import { readdirSync as readdirSync2, readFileSync as readFileSync10, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
32310
33152
  import { homedir as homedir2 } from "os";
32311
- import { basename as basename4, join as join15 } from "path";
33153
+ import { basename as basename4, join as join16 } from "path";
32312
33154
 
32313
33155
  // ../../packages/detections/src/egress/registry.ts
32314
33156
  var EXTRACTOR_VERSION = "1";
@@ -35524,8 +36366,8 @@ function maskText(text) {
35524
36366
  }
35525
36367
 
35526
36368
  // ../../packages/plugin-sdk/src/repo.ts
35527
- import { existsSync as existsSync8, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
35528
- import { basename as basename3, dirname as dirname3, isAbsolute, join as join14, sep as sep2 } from "path";
36369
+ import { existsSync as existsSync8, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
36370
+ import { basename as basename3, dirname as dirname4, isAbsolute, join as join15, sep as sep2 } from "path";
35529
36371
  function resolveRepoIdentity(cwd) {
35530
36372
  try {
35531
36373
  const root = findGitRoot(cwd);
@@ -35575,7 +36417,7 @@ function resolveGitBranch(cwd) {
35575
36417
  try {
35576
36418
  const root = findGitRoot(cwd);
35577
36419
  if (!root) return void 0;
35578
- const dotGit = join14(root, ".git");
36420
+ const dotGit = join15(root, ".git");
35579
36421
  let gitdir;
35580
36422
  try {
35581
36423
  gitdir = statSync6(dotGit).isDirectory() ? dotGit : resolveWorktreeGitdir(root, dotGit);
@@ -35583,7 +36425,7 @@ function resolveGitBranch(cwd) {
35583
36425
  return void 0;
35584
36426
  }
35585
36427
  if (gitdir === void 0) return void 0;
35586
- const head = safeRead(join14(gitdir, "HEAD"));
36428
+ const head = safeRead(join15(gitdir, "HEAD"));
35587
36429
  if (!head) return void 0;
35588
36430
  return /^ref:\s*refs\/heads\/(.+?)\s*$/m.exec(head)?.[1];
35589
36431
  } catch {
@@ -35593,41 +36435,41 @@ function resolveGitBranch(cwd) {
35593
36435
  function resolveWorktreeGitdir(root, dotGitFile) {
35594
36436
  const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGitFile) ?? "")?.[1];
35595
36437
  if (!target) return void 0;
35596
- return isAbsolute(target) ? target : join14(root, target);
36438
+ return isAbsolute(target) ? target : join15(root, target);
35597
36439
  }
35598
36440
  function findGitRoot(start) {
35599
36441
  let dir = start;
35600
36442
  for (; ; ) {
35601
- if (existsSync8(join14(dir, ".git"))) return dir;
35602
- const parent = dirname3(dir);
36443
+ if (existsSync8(join15(dir, ".git"))) return dir;
36444
+ const parent = dirname4(dir);
35603
36445
  if (parent === dir) return void 0;
35604
36446
  dir = parent;
35605
36447
  }
35606
36448
  }
35607
36449
  function resolveGitContext(root) {
35608
- const dotGit = join14(root, ".git");
36450
+ const dotGit = join15(root, ".git");
35609
36451
  try {
35610
36452
  if (statSync6(dotGit).isDirectory()) {
35611
- return { configPath: join14(dotGit, "config"), headRoot: root };
36453
+ return { configPath: join15(dotGit, "config"), headRoot: root };
35612
36454
  }
35613
36455
  } catch {
35614
36456
  return void 0;
35615
36457
  }
35616
36458
  const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
35617
36459
  if (!target) return void 0;
35618
- const gitdir = isAbsolute(target) ? target : join14(root, target);
35619
- if (existsSync8(join14(gitdir, "config"))) {
35620
- return { configPath: join14(gitdir, "config"), headRoot: root };
36460
+ const gitdir = isAbsolute(target) ? target : join15(root, target);
36461
+ if (existsSync8(join15(gitdir, "config"))) {
36462
+ return { configPath: join15(gitdir, "config"), headRoot: root };
35621
36463
  }
35622
- const commonRaw = safeRead(join14(gitdir, "commondir"))?.trim();
36464
+ const commonRaw = safeRead(join15(gitdir, "commondir"))?.trim();
35623
36465
  if (!commonRaw) return void 0;
35624
- const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join14(gitdir, commonRaw);
35625
- const headRoot = basename3(commonGitDir) === ".git" ? dirname3(commonGitDir) : root;
35626
- return { configPath: join14(commonGitDir, "config"), headRoot };
36466
+ const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join15(gitdir, commonRaw);
36467
+ const headRoot = basename3(commonGitDir) === ".git" ? dirname4(commonGitDir) : root;
36468
+ return { configPath: join15(commonGitDir, "config"), headRoot };
35627
36469
  }
35628
36470
  function safeRead(path) {
35629
36471
  try {
35630
- return readFileSync8(path, "utf8");
36472
+ return readFileSync9(path, "utf8");
35631
36473
  } catch {
35632
36474
  return void 0;
35633
36475
  }
@@ -35689,31 +36531,31 @@ function resolveConfigInventory(input2) {
35689
36531
  };
35690
36532
  try {
35691
36533
  const home = input2.homeDir ?? homedir2();
35692
- const claudeDir = join15(home, ".claude");
36534
+ const claudeDir = join16(home, ".claude");
35693
36535
  const repo = resolveRepoIdentity(input2.cwd);
35694
36536
  const repoIdentity = repo?.url ?? input2.cwd;
35695
36537
  const projectSource = `project:${repoIdentity}`;
35696
- collectSettingsHooks(scan2, join15(claudeDir, "settings.json"), "user");
35697
- collectSettingsHooks(scan2, join15(input2.cwd, ".claude", "settings.json"), "project");
35698
- collectSettingsHooks(scan2, join15(input2.cwd, ".claude", "settings.local.json"), "local");
36538
+ collectSettingsHooks(scan2, join16(claudeDir, "settings.json"), "user");
36539
+ collectSettingsHooks(scan2, join16(input2.cwd, ".claude", "settings.json"), "project");
36540
+ collectSettingsHooks(scan2, join16(input2.cwd, ".claude", "settings.local.json"), "local");
35699
36541
  const projectOrigin = { scope: "project", project: repoIdentity };
35700
- collectMcpFile(scan2, join15(input2.cwd, ".mcp.json"), projectOrigin, { recordErrors: true });
35701
- collectUserClaudeJson(scan2, join15(home, ".claude.json"), input2.cwd, repoIdentity);
35702
- collectMcpFile(scan2, join15(claudeDir, "settings.json"), { scope: "user" });
35703
- collectMcpFile(scan2, join15(input2.cwd, ".claude", "settings.json"), projectOrigin);
35704
- collectMcpFile(scan2, join15(input2.cwd, ".claude", "settings.local.json"), {
36542
+ collectMcpFile(scan2, join16(input2.cwd, ".mcp.json"), projectOrigin, { recordErrors: true });
36543
+ collectUserClaudeJson(scan2, join16(home, ".claude.json"), input2.cwd, repoIdentity);
36544
+ collectMcpFile(scan2, join16(claudeDir, "settings.json"), { scope: "user" });
36545
+ collectMcpFile(scan2, join16(input2.cwd, ".claude", "settings.json"), projectOrigin);
36546
+ collectMcpFile(scan2, join16(input2.cwd, ".claude", "settings.local.json"), {
35705
36547
  scope: "local",
35706
36548
  project: repoIdentity
35707
36549
  });
35708
36550
  collectConfigFiles(scan2, claudeDir, input2.cwd);
35709
- collectSkillsDir(scan2, join15(claudeDir, "skills"), { source: "local", scope: "user" });
35710
- collectSkillsDir(scan2, join15(input2.cwd, ".claude", "skills"), {
36551
+ collectSkillsDir(scan2, join16(claudeDir, "skills"), { source: "local", scope: "user" });
36552
+ collectSkillsDir(scan2, join16(input2.cwd, ".claude", "skills"), {
35711
36553
  source: projectSource,
35712
36554
  scope: "project"
35713
36555
  });
35714
36556
  collectInstalledPlugins(scan2, claudeDir);
35715
36557
  collectMarketplaceSkills(scan2, claudeDir);
35716
- collectSkillsDir(scan2, join15(input2.cwd, "skills"), { source: projectSource, scope: "project" });
36558
+ collectSkillsDir(scan2, join16(input2.cwd, "skills"), { source: projectSource, scope: "project" });
35717
36559
  scan2.skills = dedupeSkills(scan2.skills);
35718
36560
  scan2.mcpServers = dedupeMcpServers(scan2.mcpServers);
35719
36561
  } catch (err) {
@@ -35842,7 +36684,7 @@ function projectEntryFor(projects, cwd) {
35842
36684
  return void 0;
35843
36685
  }
35844
36686
  function collectPluginManifestMcp(scan2, installPath, origin) {
35845
- const manifestPath = join15(installPath, ".claude-plugin", "plugin.json");
36687
+ const manifestPath = join16(installPath, ".claude-plugin", "plugin.json");
35846
36688
  const raw = readOptional(manifestPath);
35847
36689
  if (raw === void 0) return;
35848
36690
  try {
@@ -35850,7 +36692,7 @@ function collectPluginManifestMcp(scan2, installPath, origin) {
35850
36692
  if (typeof parsed2 !== "object" || parsed2 === null) return;
35851
36693
  const declared = parsed2.mcpServers;
35852
36694
  if (typeof declared === "string" && declared.length > 0) {
35853
- collectMcpFile(scan2, join15(installPath, declared), origin, { recordErrors: true });
36695
+ collectMcpFile(scan2, join16(installPath, declared), origin, { recordErrors: true });
35854
36696
  } else {
35855
36697
  collectMcpObject(scan2, declared, manifestPath, origin);
35856
36698
  }
@@ -35867,14 +36709,14 @@ var SETTINGS_KEY_LABELS = [
35867
36709
  ["statusLine", "status line"]
35868
36710
  ];
35869
36711
  function collectConfigFiles(scan2, claudeDir, cwd) {
35870
- settingsConfigFile(scan2, join15(claudeDir, "settings.json"), "user", "User settings");
35871
- settingsConfigFile(scan2, join15(cwd, ".claude", "settings.json"), "project", "Project settings");
35872
- settingsConfigFile(scan2, join15(cwd, ".claude", "settings.local.json"), "local", "Local overrides");
35873
- memoryConfigFile(scan2, join15(claudeDir, "CLAUDE.md"), "user", "User memory");
35874
- memoryConfigFile(scan2, join15(cwd, "CLAUDE.md"), "project", "Project memory");
35875
- mcpJsonConfigFile(scan2, join15(cwd, ".mcp.json"));
35876
- dirConfigFile(scan2, join15(cwd, ".claude", "commands"), "Slash commands", "command");
35877
- dirConfigFile(scan2, join15(cwd, ".claude", "agents"), "Subagents", "subagent");
36712
+ settingsConfigFile(scan2, join16(claudeDir, "settings.json"), "user", "User settings");
36713
+ settingsConfigFile(scan2, join16(cwd, ".claude", "settings.json"), "project", "Project settings");
36714
+ settingsConfigFile(scan2, join16(cwd, ".claude", "settings.local.json"), "local", "Local overrides");
36715
+ memoryConfigFile(scan2, join16(claudeDir, "CLAUDE.md"), "user", "User memory");
36716
+ memoryConfigFile(scan2, join16(cwd, "CLAUDE.md"), "project", "Project memory");
36717
+ mcpJsonConfigFile(scan2, join16(cwd, ".mcp.json"));
36718
+ dirConfigFile(scan2, join16(cwd, ".claude", "commands"), "Slash commands", "command");
36719
+ dirConfigFile(scan2, join16(cwd, ".claude", "agents"), "Subagents", "subagent");
35878
36720
  }
35879
36721
  function configFileEntry(path, scope, kind) {
35880
36722
  try {
@@ -35949,7 +36791,7 @@ function countMarkdownFiles(dir, depth) {
35949
36791
  let count = 0;
35950
36792
  for (const dirent of readdirSync2(dir, { withFileTypes: true })) {
35951
36793
  if (dirent.name.startsWith(".")) continue;
35952
- if (dirent.isDirectory()) count += countMarkdownFiles(join15(dir, dirent.name), depth + 1);
36794
+ if (dirent.isDirectory()) count += countMarkdownFiles(join16(dir, dirent.name), depth + 1);
35953
36795
  else if (dirent.name.endsWith(".md")) count += 1;
35954
36796
  }
35955
36797
  return count;
@@ -35962,7 +36804,7 @@ function collectSkillsDir(scan2, dir, origin) {
35962
36804
  return;
35963
36805
  }
35964
36806
  for (const name2 of names) {
35965
- const skillFile = join15(dir, name2, "SKILL.md");
36807
+ const skillFile = join16(dir, name2, "SKILL.md");
35966
36808
  try {
35967
36809
  const raw = readOptional(skillFile);
35968
36810
  if (raw === void 0) continue;
@@ -35971,7 +36813,7 @@ function collectSkillsDir(scan2, dir, origin) {
35971
36813
  name: front.name ?? name2,
35972
36814
  source: origin.source,
35973
36815
  scope: origin.scope,
35974
- location: join15(dir, name2),
36816
+ location: join16(dir, name2),
35975
36817
  updatedAt: statSync7(skillFile).mtime.toISOString()
35976
36818
  };
35977
36819
  const version3 = front.version ?? origin.defaultVersion;
@@ -36002,7 +36844,7 @@ function parseFrontmatter(raw) {
36002
36844
  return out;
36003
36845
  }
36004
36846
  function collectInstalledPlugins(scan2, claudeDir) {
36005
- const manifestPath = join15(claudeDir, "plugins", "installed_plugins.json");
36847
+ const manifestPath = join16(claudeDir, "plugins", "installed_plugins.json");
36006
36848
  const raw = readOptional(manifestPath);
36007
36849
  if (raw === void 0) return;
36008
36850
  let plugins;
@@ -36027,7 +36869,7 @@ function collectInstalledPlugins(scan2, claudeDir) {
36027
36869
  if (typeof installPath !== "string" || seen.has(installPath)) continue;
36028
36870
  seen.add(installPath);
36029
36871
  const version3 = install.version;
36030
- const hooksPath = join15(installPath, "hooks", "hooks.json");
36872
+ const hooksPath = join16(installPath, "hooks", "hooks.json");
36031
36873
  const hooksRaw = readOptional(hooksPath);
36032
36874
  if (hooksRaw !== void 0) {
36033
36875
  try {
@@ -36047,22 +36889,22 @@ function collectInstalledPlugins(scan2, claudeDir) {
36047
36889
  }
36048
36890
  const origin = { source: marketplace, scope: "plugin", pluginName };
36049
36891
  if (typeof version3 === "string") origin.defaultVersion = version3;
36050
- collectSkillsDir(scan2, join15(installPath, "skills"), origin);
36892
+ collectSkillsDir(scan2, join16(installPath, "skills"), origin);
36051
36893
  const mcpOrigin = { scope: "plugin", pluginName, marketplace };
36052
- collectMcpFile(scan2, join15(installPath, ".mcp.json"), mcpOrigin, { recordErrors: true });
36894
+ collectMcpFile(scan2, join16(installPath, ".mcp.json"), mcpOrigin, { recordErrors: true });
36053
36895
  collectPluginManifestMcp(scan2, installPath, mcpOrigin);
36054
36896
  }
36055
36897
  }
36056
36898
  }
36057
36899
  function collectMarketplaceSkills(scan2, claudeDir) {
36058
- for (const mp of readMarketplaces(join15(claudeDir, "plugins", "known_marketplaces.json"))) {
36900
+ for (const mp of readMarketplaces(join16(claudeDir, "plugins", "known_marketplaces.json"))) {
36059
36901
  if (isClaudeOfficialMarketplace(mp.name, mp.repo)) continue;
36060
- collectSkillsDir(scan2, join15(mp.installLocation, "skills"), {
36902
+ collectSkillsDir(scan2, join16(mp.installLocation, "skills"), {
36061
36903
  source: mp.name,
36062
36904
  scope: "plugin"
36063
36905
  });
36064
- collectPluginSkillDirs(scan2, join15(mp.installLocation, "plugins"), mp.name);
36065
- collectPluginSkillDirs(scan2, join15(mp.installLocation, "external_plugins"), mp.name);
36906
+ collectPluginSkillDirs(scan2, join16(mp.installLocation, "plugins"), mp.name);
36907
+ collectPluginSkillDirs(scan2, join16(mp.installLocation, "external_plugins"), mp.name);
36066
36908
  }
36067
36909
  }
36068
36910
  function collectPluginSkillDirs(scan2, pluginsDir, marketplace) {
@@ -36073,7 +36915,7 @@ function collectPluginSkillDirs(scan2, pluginsDir, marketplace) {
36073
36915
  return;
36074
36916
  }
36075
36917
  for (const plugin of plugins) {
36076
- collectSkillsDir(scan2, join15(pluginsDir, plugin, "skills"), {
36918
+ collectSkillsDir(scan2, join16(pluginsDir, plugin, "skills"), {
36077
36919
  source: marketplace,
36078
36920
  scope: "plugin",
36079
36921
  pluginName: plugin
@@ -36127,7 +36969,7 @@ function dedupeMcpServers(servers) {
36127
36969
  }
36128
36970
  function readOptional(path) {
36129
36971
  try {
36130
- return readFileSync9(path, "utf8");
36972
+ return readFileSync10(path, "utf8");
36131
36973
  } catch {
36132
36974
  return void 0;
36133
36975
  }
@@ -36659,11 +37501,11 @@ function createGuardedScanner(partition, gateway, opts) {
36659
37501
 
36660
37502
  // ../../packages/plugin-sdk/src/ignore-layers.ts
36661
37503
  var import_ignore = __toESM(require_ignore(), 1);
36662
- import { readFileSync as readFileSync10 } from "fs";
36663
- import { join as join16 } from "path";
37504
+ import { readFileSync as readFileSync11 } from "fs";
37505
+ import { join as join17 } from "path";
36664
37506
  function readIgnoreLayer(dir, filename, anchorLen) {
36665
37507
  try {
36666
- return { matcher: (0, import_ignore.default)().add(readFileSync10(join16(dir, filename), "utf8")), anchorLen };
37508
+ return { matcher: (0, import_ignore.default)().add(readFileSync11(join17(dir, filename), "utf8")), anchorLen };
36667
37509
  } catch {
36668
37510
  return void 0;
36669
37511
  }
@@ -36725,25 +37567,25 @@ import {
36725
37567
  fstatSync,
36726
37568
  mkdirSync as mkdirSync2,
36727
37569
  openSync as openSync2,
36728
- readFileSync as readFileSync11,
37570
+ readFileSync as readFileSync12,
36729
37571
  readSync,
36730
37572
  writeFileSync as writeFileSync5
36731
37573
  } from "fs";
36732
- import { join as join17 } from "path";
37574
+ import { join as join18 } from "path";
36733
37575
  var TAIL_BYTES = 256 * 1024;
36734
37576
 
36735
37577
  // ../../packages/plugin-sdk/src/nudge.ts
36736
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
36737
- import { join as join18 } from "path";
37578
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
37579
+ import { join as join19 } from "path";
36738
37580
  var SESSION_START_MARKER = "session-start-last";
36739
37581
  function claimSessionStart(dataDir2, sessionId) {
36740
37582
  return claimOncePerSession(dataDir2, SESSION_START_MARKER, sessionId);
36741
37583
  }
36742
37584
  function claimOncePerSession(dataDir2, marker, sessionId) {
36743
37585
  if (!sessionId) return true;
36744
- const path = join18(dataDir2, marker);
37586
+ const path = join19(dataDir2, marker);
36745
37587
  try {
36746
- if (readFileSync12(path, "utf8") === sessionId) return false;
37588
+ if (readFileSync13(path, "utf8") === sessionId) return false;
36747
37589
  } catch {
36748
37590
  }
36749
37591
  try {
@@ -36756,11 +37598,46 @@ function claimOncePerSession(dataDir2, marker, sessionId) {
36756
37598
 
36757
37599
  // ../../packages/plugin-sdk/src/paths.ts
36758
37600
  import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
36759
- import { basename as basename5, dirname as dirname4, sep as sep3 } from "path";
37601
+ import { basename as basename5, dirname as dirname5, sep as sep3 } from "path";
37602
+
37603
+ // ../../packages/plugin-sdk/src/policy-resolver.ts
37604
+ function createPolicyResolver(bundle) {
37605
+ const byRule = /* @__PURE__ */ new Map();
37606
+ const byCategory = /* @__PURE__ */ new Map();
37607
+ let reversible = /* @__PURE__ */ new Set();
37608
+ try {
37609
+ for (const policy of bundle.policies) {
37610
+ if (!policy.enabled) continue;
37611
+ if ("ruleId" in policy.target) {
37612
+ if (!byRule.has(policy.target.ruleId)) byRule.set(policy.target.ruleId, policy.action);
37613
+ } else if (!byCategory.has(policy.target.category)) {
37614
+ byCategory.set(policy.target.category, policy.action);
37615
+ }
37616
+ }
37617
+ reversible = new Set(bundle.reversibleRuleIds ?? []);
37618
+ } catch {
37619
+ byRule.clear();
37620
+ byCategory.clear();
37621
+ reversible = /* @__PURE__ */ new Set();
37622
+ }
37623
+ return {
37624
+ actionFor(ruleId, category) {
37625
+ const byRuleAction = byRule.get(ruleId);
37626
+ if (byRuleAction !== void 0) return byRuleAction;
37627
+ const byCategoryAction = byCategory.get(category);
37628
+ if (byCategoryAction !== void 0) return byCategoryAction;
37629
+ const fallback = DEFAULT_ACTIONS[category];
37630
+ return fallback ?? "log";
37631
+ },
37632
+ isReversible(ruleId) {
37633
+ return reversible.has(ruleId);
37634
+ }
37635
+ };
37636
+ }
36760
37637
 
36761
37638
  // ../../packages/plugin-sdk/src/project-files.ts
36762
37639
  import { existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
36763
- import { basename as basename6, join as join19 } from "path";
37640
+ import { basename as basename6, join as join20 } from "path";
36764
37641
  var SKIP_DIRS = /* @__PURE__ */ new Set([
36765
37642
  ".git",
36766
37643
  "node_modules",
@@ -36857,8 +37734,8 @@ function resolveProjectFiles(cwd, opts = {}) {
36857
37734
  }
36858
37735
  if (entry.isDirectory()) {
36859
37736
  if (SKIP_DIRS.has(entry.name) || isIgnored(dirLayers, dirRel, entry.name, true)) continue;
36860
- const fullPath = join19(dir, entry.name);
36861
- if (existsSync10(join19(fullPath, ".git"))) continue;
37737
+ const fullPath = join20(dir, entry.name);
37738
+ if (existsSync10(join20(fullPath, ".git"))) continue;
36862
37739
  if (depth >= bounds.maxDepth) {
36863
37740
  walk.omitted = true;
36864
37741
  continue;
@@ -36926,7 +37803,6 @@ var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
36926
37803
  // ../../packages/plugin-sdk/src/runtime.ts
36927
37804
  import { randomUUID as randomUUID14 } from "crypto";
36928
37805
  var ENFORCEMENT_CEILING_ENABLED = false;
36929
- var ACTION_PRIORITY = ["block", "redact", "warn", "log", "allow"];
36930
37806
  function startTiming() {
36931
37807
  try {
36932
37808
  return performance.now();
@@ -36963,28 +37839,22 @@ function createPluginRuntime(gateway, settings, opts) {
36963
37839
  bundlesPacked = true;
36964
37840
  }
36965
37841
  const policyMode = settings.policy;
37842
+ const redactFallback = settings.redactFallback;
36966
37843
  const dataDir2 = opts?.dataDir;
36967
- let policies = [];
36968
37844
  let rules = [];
36969
37845
  let scanner;
36970
37846
  let bundleExceptions = [];
36971
37847
  let initialized = false;
36972
- const ruleActionIndex = /* @__PURE__ */ new Map();
36973
- const categoryActionIndex = /* @__PURE__ */ new Map();
36974
- let reversibleRuleIndex = /* @__PURE__ */ new Set();
37848
+ let resolver = createPolicyResolver({
37849
+ version: "",
37850
+ policies: [],
37851
+ customKeywords: [],
37852
+ fetchedAt: ""
37853
+ });
36975
37854
  async function ensureInitialized() {
36976
37855
  if (initialized) return;
36977
37856
  const bundle = await gateway.getPolicyBundle();
36978
- policies = bundle.policies;
36979
- for (const p of policies) {
36980
- if (!p.enabled) continue;
36981
- if ("ruleId" in p.target) {
36982
- if (!ruleActionIndex.has(p.target.ruleId)) ruleActionIndex.set(p.target.ruleId, p.action);
36983
- } else if (!categoryActionIndex.has(p.target.category)) {
36984
- categoryActionIndex.set(p.target.category, p.action);
36985
- }
36986
- }
36987
- reversibleRuleIndex = new Set(bundle.reversibleRuleIds ?? []);
37857
+ resolver = createPolicyResolver(bundle);
36988
37858
  const bundledProbeKeys = new Set(
36989
37859
  getLoadedRules().map(ruleProbeKey).filter((key) => key !== void 0)
36990
37860
  );
@@ -37037,33 +37907,28 @@ function createPluginRuntime(gateway, settings, opts) {
37037
37907
  return cachedKey;
37038
37908
  }
37039
37909
  function resolveAction(ruleId, category) {
37040
- const byRule = ruleActionIndex.get(ruleId);
37041
- if (byRule !== void 0) return byRule;
37042
- const byCategory = categoryActionIndex.get(category);
37043
- if (byCategory !== void 0) return byCategory;
37044
- const fallback = DEFAULT_ACTIONS[category];
37045
- return fallback ?? "log";
37046
- }
37047
- function actionForFinding(finding2, excepted) {
37910
+ return resolver.actionFor(ruleId, category);
37911
+ }
37912
+ function actionForFinding(finding2, excepted, rewritable = true) {
37048
37913
  if (excepted?.has(finding2)) return "allow";
37049
37914
  const action = resolveAction(finding2.ruleId, finding2.category);
37915
+ if (!rewritable && action === "redact") return builtinPolicyToAction(redactFallback);
37050
37916
  if (ENFORCEMENT_CEILING_ENABLED && policyMode === "warn" && (action === "block" || action === "redact")) {
37051
37917
  return "warn";
37052
37918
  }
37053
37919
  return action;
37054
37920
  }
37055
- function decide(findings, text, excepted) {
37921
+ function decide(findings, text, excepted, rewritable = true) {
37056
37922
  if (findings.length === 0) return { action: "log", text, findings: [] };
37057
- const actionFor = (finding2) => actionForFinding(finding2, excepted);
37923
+ const actionFor = (finding2) => actionForFinding(finding2, excepted, rewritable);
37058
37924
  let worst = "log";
37059
37925
  for (const finding2 of findings) {
37060
- const action = actionFor(finding2);
37061
- if (ACTION_PRIORITY.indexOf(action) < ACTION_PRIORITY.indexOf(worst)) worst = action;
37926
+ worst = strongerAction(worst, actionFor(finding2));
37062
37927
  }
37063
37928
  if (worst === "block") return { action: "block", text: null, findings };
37064
37929
  if (worst === "redact") {
37065
37930
  const redactFindings = findings.filter((f) => actionFor(f) === "redact");
37066
- const reversibleFindings = redactFindings.filter((f) => reversibleRuleIndex.has(f.ruleId));
37931
+ const reversibleFindings = redactFindings.filter((f) => resolver.isReversible(f.ruleId));
37067
37932
  return {
37068
37933
  action: "redact",
37069
37934
  text: redact(text, redactFindings),
@@ -37139,7 +38004,7 @@ function createPluginRuntime(gateway, settings, opts) {
37139
38004
  return { excepted: /* @__PURE__ */ new Set(), exceptionIds: [] };
37140
38005
  }
37141
38006
  }
37142
- async function recordBlockedDetections(decision, excepted, ctx, fpCache) {
38007
+ async function recordBlockedDetections(decision, excepted, ctx, fpCache, rewritable = true) {
37143
38008
  const references = [];
37144
38009
  try {
37145
38010
  if (decision.action !== "block" && decision.action !== "redact") return references;
@@ -37147,7 +38012,7 @@ function createPluginRuntime(gateway, settings, opts) {
37147
38012
  if (!key) return references;
37148
38013
  const seen = /* @__PURE__ */ new Set();
37149
38014
  for (const finding2 of decision.findings) {
37150
- const action = actionForFinding(finding2, excepted);
38015
+ const action = actionForFinding(finding2, excepted, rewritable);
37151
38016
  if (action !== "block" && action !== "redact") continue;
37152
38017
  const fp = fingerprintOf(key, finding2, fpCache);
37153
38018
  const pair = `${finding2.ruleId}:${fp}`;
@@ -37174,7 +38039,7 @@ function createPluginRuntime(gateway, settings, opts) {
37174
38039
  }
37175
38040
  return references;
37176
38041
  }
37177
- async function evaluate(text, context, ctx) {
38042
+ async function evaluate(text, context, ctx, rewritable = true) {
37178
38043
  try {
37179
38044
  await ensureInitialized();
37180
38045
  if (!scanner) throw new Error("the runtime initialized without a scanner");
@@ -37183,8 +38048,14 @@ function createPluginRuntime(gateway, settings, opts) {
37183
38048
  const findings = dropShieldedFindings(matched, shielded.spans);
37184
38049
  const fpCache = /* @__PURE__ */ new Map();
37185
38050
  const { excepted, exceptionIds } = await applyExceptions(findings, ctx, fpCache);
37186
- const decision = decide(findings, text, excepted);
37187
- const blockedReferences = await recordBlockedDetections(decision, excepted, ctx, fpCache);
38051
+ const decision = decide(findings, text, excepted, rewritable);
38052
+ const blockedReferences = await recordBlockedDetections(
38053
+ decision,
38054
+ excepted,
38055
+ ctx,
38056
+ fpCache,
38057
+ rewritable
38058
+ );
37188
38059
  if (blockedReferences.length > 0) decision.blockedReferences = blockedReferences;
37189
38060
  return { decision, excepted, exceptionIds };
37190
38061
  } catch {
@@ -37208,12 +38079,16 @@ function createPluginRuntime(gateway, settings, opts) {
37208
38079
  sourceTool: input2.sourceTool,
37209
38080
  metadata: input2.metadata,
37210
38081
  preAuthorizedGrantIds: opts2.preAuthorizedGrantIds
37211
- }
38082
+ },
38083
+ opts2.rewritable
37212
38084
  );
37213
38085
  if (opts2.persist === "with-findings" && decision.findings.length === 0) return decision;
37214
38086
  try {
37215
38087
  const contentHash = contentHashOf(input2.text);
37216
- const storedContent = decision.findings.length > 0 ? redact(input2.text, decision.findings) : input2.text;
38088
+ const maskedFindings = decision.findings.filter(
38089
+ (match) => isActionAtLeast(actionForFinding(match, excepted, opts2.rewritable), "redact")
38090
+ );
38091
+ const storedContent = maskedFindings.length > 0 ? redact(input2.text, maskedFindings) : input2.text;
37217
38092
  const inspectionMs = elapsedMs(timingStartedAt);
37218
38093
  const metadata = exceptionIds.length > 0 || inspectionMs !== void 0 ? {
37219
38094
  ...input2.metadata,
@@ -37250,7 +38125,7 @@ function createPluginRuntime(gateway, settings, opts) {
37250
38125
  severity: match.severity,
37251
38126
  span: match.span,
37252
38127
  maskedMatch,
37253
- actionTaken: actionForFinding(match, excepted),
38128
+ actionTaken: actionForFinding(match, excepted, opts2.rewritable),
37254
38129
  confidence: match.confidence,
37255
38130
  ...findingKey ? { findingKey } : {}
37256
38131
  };
@@ -37290,9 +38165,9 @@ var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
37290
38165
 
37291
38166
  // ../../packages/plugin-sdk/src/throttle.ts
37292
38167
  import { mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
37293
- import { join as join20 } from "path";
38168
+ import { join as join21 } from "path";
37294
38169
  function throttled(dataDir2, markerName, windowMs) {
37295
- const marker = join20(dataDir2, markerName);
38170
+ const marker = join21(dataDir2, markerName);
37296
38171
  try {
37297
38172
  if (Date.now() - statSync8(marker).mtimeMs < windowMs) return true;
37298
38173
  } catch {
@@ -37305,25 +38180,17 @@ function throttled(dataDir2, markerName, windowMs) {
37305
38180
  return false;
37306
38181
  }
37307
38182
 
37308
- // ../../packages/plugin-runtime/src/attached/with-timeout.ts
37309
- var REQUEST_TIMEOUT_MS = 2e3;
37310
- function withTimeout(promise2, ms) {
37311
- let timer;
37312
- const timeout = new Promise((_, reject) => {
37313
- timer = setTimeout(() => {
37314
- reject(new Error("attached gateway request timed out"));
37315
- }, ms);
37316
- });
37317
- promise2.catch(() => void 0);
37318
- return Promise.race([promise2, timeout]).finally(() => {
37319
- clearTimeout(timer);
37320
- });
37321
- }
37322
-
37323
38183
  // ../../packages/plugin-runtime/src/attached/forward-policy.ts
37324
38184
  function isInvalidRequest(err) {
37325
38185
  return typeof err === "object" && err !== null && err.name === "RemoteRequestInvalid";
37326
38186
  }
38187
+ function isRouteAbsent(err) {
38188
+ return typeof err === "object" && err !== null && err.name === "RemoteRouteAbsent";
38189
+ }
38190
+ function isServerRejection(err) {
38191
+ const status = statusOf(err);
38192
+ return status !== null && status >= 400 && status <= 499 && status !== 401 && status !== 403 && status !== 404 && status !== 429;
38193
+ }
37327
38194
  var FORWARD_BUDGET_MS = 1500;
37328
38195
  var DECISION_PATH_BUDGET_MS = 800;
37329
38196
  var BREAKER_FAILURE_THRESHOLD = 3;
@@ -37351,7 +38218,7 @@ function parseBreakerState(raw, nowMs) {
37351
38218
  }
37352
38219
  function createForwardPolicy(deps) {
37353
38220
  const now = deps.now ?? (() => Date.now());
37354
- const file2 = join21(deps.dir, STATE_FILENAME);
38221
+ const file2 = join22(deps.dir, STATE_FILENAME);
37355
38222
  let state = null;
37356
38223
  let loading = null;
37357
38224
  async function readState() {
@@ -37391,6 +38258,20 @@ function createForwardPolicy(deps) {
37391
38258
  } catch {
37392
38259
  current = { ...CLOSED };
37393
38260
  }
38261
+ const restoreOpenedAtMs = (openedAtMs) => persist({
38262
+ consecutiveFailures: current.consecutiveFailures,
38263
+ openedAtMs,
38264
+ lastFailure: current.lastFailure
38265
+ });
38266
+ const recordFailure = (cause) => {
38267
+ const failures = current.consecutiveFailures + 1;
38268
+ const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
38269
+ return persist({
38270
+ consecutiveFailures: failures,
38271
+ openedAtMs: shouldOpen ? now() : null,
38272
+ lastFailure: cause
38273
+ });
38274
+ };
37394
38275
  const at = now();
37395
38276
  if (current.openedAtMs !== null) {
37396
38277
  if (at - current.openedAtMs < BREAKER_COOLDOWN_MS) {
@@ -37409,15 +38290,20 @@ function createForwardPolicy(deps) {
37409
38290
  }
37410
38291
  return { ok: true, value };
37411
38292
  } catch (err) {
37412
- if (isInvalidRequest(err)) return { ok: false, reason: "invalid-request" };
38293
+ if (isInvalidRequest(err)) {
38294
+ if (current.openedAtMs !== null) await restoreOpenedAtMs(current.openedAtMs);
38295
+ return { ok: false, reason: "invalid-request" };
38296
+ }
38297
+ if (isRouteAbsent(err)) {
38298
+ if (current.openedAtMs !== null) await restoreOpenedAtMs(null);
38299
+ return { ok: false, reason: "route-absent" };
38300
+ }
38301
+ if (isServerRejection(err)) {
38302
+ await recordFailure("unreachable");
38303
+ return { ok: false, reason: "rejected" };
38304
+ }
37413
38305
  const reason = classifyFailure(err);
37414
- const failures = current.consecutiveFailures + 1;
37415
- const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
37416
- await persist({
37417
- consecutiveFailures: failures,
37418
- openedAtMs: shouldOpen ? now() : null,
37419
- lastFailure: reason
37420
- });
38306
+ await recordFailure(reason);
37421
38307
  return { ok: false, reason };
37422
38308
  }
37423
38309
  }
@@ -37425,13 +38311,11 @@ function createForwardPolicy(deps) {
37425
38311
  }
37426
38312
 
37427
38313
  // ../../packages/plugin-runtime/src/attached/gateway.ts
37428
- var ACTION_STRENGTH = {
37429
- allow: 0,
37430
- log: 1,
37431
- warn: 2,
37432
- redact: 3,
37433
- block: 4
37434
- };
38314
+ function strongerOf(a, b) {
38315
+ if (a === null) return b;
38316
+ if (b === null) return a;
38317
+ return strongerAction(a, b);
38318
+ }
37435
38319
  function ruleCategoryMap(wireRules, localRules) {
37436
38320
  const map2 = /* @__PURE__ */ new Map();
37437
38321
  for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
@@ -37441,11 +38325,6 @@ function ruleCategoryMap(wireRules, localRules) {
37441
38325
  }
37442
38326
  return map2;
37443
38327
  }
37444
- function strongerOf(a, b) {
37445
- if (a === null) return b;
37446
- if (b === null) return a;
37447
- return ACTION_STRENGTH[a] >= ACTION_STRENGTH[b] ? a : b;
37448
- }
37449
38328
  function policyKey(policy) {
37450
38329
  return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
37451
38330
  }
@@ -37464,7 +38343,7 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
37464
38343
  const floor = floorFor(policy, categoryByRuleId);
37465
38344
  remoteCategoryAction.set(
37466
38345
  policy.target.category,
37467
- floor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[floor] ? floor : policy.action
38346
+ floor !== null && !isActionAtLeast(policy.action, floor) ? floor : policy.action
37468
38347
  );
37469
38348
  }
37470
38349
  for (const policy of localPolicies) {
@@ -37481,7 +38360,7 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
37481
38360
  }
37482
38361
  merged.set(
37483
38362
  key,
37484
- remoteFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[remoteFloor] ? { ...policy, action: remoteFloor } : policy
38363
+ remoteFloor !== null && !isActionAtLeast(policy.action, remoteFloor) ? { ...policy, action: remoteFloor } : policy
37485
38364
  );
37486
38365
  }
37487
38366
  const localCategoryAction = /* @__PURE__ */ new Map();
@@ -37501,13 +38380,13 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
37501
38380
  if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
37502
38381
  }
37503
38382
  const effectiveFloor = strongerOf(floor, localFloor);
37504
- const clamped = effectiveFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[effectiveFloor] ? { ...policy, action: effectiveFloor } : policy;
38383
+ const clamped = effectiveFloor !== null && !isActionAtLeast(policy.action, effectiveFloor) ? { ...policy, action: effectiveFloor } : policy;
37505
38384
  const existing = merged.get(key);
37506
38385
  if (existing === void 0) {
37507
38386
  merged.set(key, clamped);
37508
38387
  continue;
37509
38388
  }
37510
- if (ACTION_STRENGTH[clamped.action] > ACTION_STRENGTH[existing.action]) {
38389
+ if (actionRank(clamped.action) > actionRank(existing.action)) {
37511
38390
  merged.set(key, clamped);
37512
38391
  }
37513
38392
  }
@@ -37540,6 +38419,8 @@ var AttachedDataGateway = class {
37540
38419
  );
37541
38420
  if (forwarded.ok && forwarded.value.accepted + forwarded.value.duplicates > 0) {
37542
38421
  this.deps.local.markCaptureDelivered(record2.event, Date.now());
38422
+ } else {
38423
+ this.deps.local.markCaptureOwed(record2.event);
37543
38424
  }
37544
38425
  }
37545
38426
  async ensureInventory(ctx) {
@@ -37576,9 +38457,10 @@ var AttachedDataGateway = class {
37576
38457
  // a retried tool_call, exactly this path — can never stomp a populated row.
37577
38458
  async recordAuditEvent(event) {
37578
38459
  await this.deps.local.recordAuditEvent(event);
37579
- await this.deps.forward.run(
38460
+ const forwarded = await this.deps.forward.run(
37580
38461
  () => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
37581
38462
  );
38463
+ if (forwarded.ok) this.deps.local.markAuditEventsDelivered([event], Date.now());
37582
38464
  }
37583
38465
  // Attached `llm_call` is written locally by the inner gateway, then routed to
37584
38466
  // the control plane through the existing `recordAuditEvent` ingest (no dedicated
@@ -37587,44 +38469,170 @@ var AttachedDataGateway = class {
37587
38469
  // which would write the event to the local store a second time.
37588
38470
  async recordLlmCall(input2) {
37589
38471
  await this.deps.local.recordLlmCall(input2);
37590
- await this.deps.forward.run(
37591
- () => this.deps.client.recordAuditEvent(
37592
- reKeyForForward(llmAuditEvent(input2), this.remoteInventory)
37593
- )
38472
+ const event = llmAuditEvent(input2);
38473
+ const forwarded = await this.deps.forward.run(
38474
+ () => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
37594
38475
  );
38476
+ if (forwarded.ok) this.deps.local.markAuditEventsDelivered([event], Date.now());
37595
38477
  }
37596
38478
  /**
37597
- * Forward one batch, item by item, under ONE aggregate deadline.
38479
+ * Forward one batch in CHUNKS of AUDIT_EVENT_BATCH_MAX, under ONE aggregate deadline.
38480
+ *
38481
+ * This used to send one HTTP request per event, which is what made the batch
38482
+ * budget bite: at 200ms round-trip a 3s budget admitted ~15 events and threw
38483
+ * away everything after them. The same rows now cross 50 at a time over
38484
+ * `POST /v1/audit-events/batch` — the route the attach-time drain has always
38485
+ * used — so the same budget admits ~750. The wire cap is the server's own
38486
+ * constant, sized against server cost, and the client REFUSES a longer array
38487
+ * client-side, so the chunking here is not a convention.
38488
+ *
38489
+ * Still serial, and still for the original reason: firing N requests at once
38490
+ * would trade a latency problem for a burst the plane's per-key rate limiting
38491
+ * answers with the refusals the breaker then counts. Fewer, fuller requests is
38492
+ * the fix; more concurrent ones is not.
38493
+ *
38494
+ * When the deadline passes the remainder is dropped rather than sent: the
38495
+ * local write has already succeeded, so every caller has a correct result to
38496
+ * return. What is dropped is COUNTED, everywhere it can happen — this path
38497
+ * returns BEFORE `ForwardPolicy.run` is reached, so without the tally in
38498
+ * `forward-drops.ts` a slow-but-answering plane produces no failures, keeps
38499
+ * the breaker closed, renders a healthy block, and discards the tail of every
38500
+ * batch indefinitely. The SAME tally also covers a single that fails inside
38501
+ * the per-item retry below — the breaker opening mid-retry is a failure the
38502
+ * breaker's own state DOES capture, but the events still in this chunk once
38503
+ * that happens are neither delivered nor otherwise counted anywhere, which is
38504
+ * the same invisibility with a different cause.
38505
+ *
38506
+ * `ok` ALONE IS NOT DELIVERY, the same rule `recordCapture` states for the
38507
+ * single-event ack and at fifty times the blast radius here:
38508
+ * `AuditEventBatchAck.accepted` is an aggregate count the wire contract does
38509
+ * not tie to the chunk's own length, so a 2xx answering `{accepted: 30}` for
38510
+ * fifty events is well-formed. Trusting `ok` alone would stamp all fifty as
38511
+ * delivered and never re-offer the twenty the plane did not take. So success
38512
+ * is checked against `chunk.length`; anything short of it falls into the same
38513
+ * per-item pass as a refused chunk, which is the only way to recover the
38514
+ * rows that did not land, since the ack carries no per-row verdict to
38515
+ * resend by.
38516
+ *
38517
+ * That fallback ASSUMES a re-send of an already-landed row is a harmless
38518
+ * no-op rather than a second cost — an assumption this file cannot verify.
38519
+ * `AuditEventBatchAck` carries only `accepted`, unlike its sibling
38520
+ * `IngestAck` (`accepted` + `duplicates`, with `accepted + duplicates ==`
38521
+ * the batch size as the invariant `recordCapture` reads), so whether a
38522
+ * duplicate counts toward THIS route's `accepted` is not expressed
38523
+ * anywhere in this repo. If it follows its sibling's convention and does
38524
+ * NOT, a chunk containing even one already-delivered row — the ordinary
38525
+ * consequence of a lost stamp, which this file already treats as cheap —
38526
+ * answers short forever and enters the per-item pass on every pass it is
38527
+ * offered again. The cost of that is bounded rather than silent: the
38528
+ * pass converges (every row lands and stamps), so it is one wasted round
38529
+ * of singles rather than a stall, and it errs toward an extra resend
38530
+ * rather than toward the lost row the alternative risks.
37598
38531
  *
37599
- * Per-item budgets bound each request and nothing bounded their sum — see
37600
- * BATCH_FORWARD_BUDGET_MS. When the deadline passes the remainder is dropped
37601
- * rather than sent: the local write has already succeeded, so every caller
37602
- * has a correct result to return, and a drop is the outcome this path is
37603
- * built to accept (G8) where a blown hook timeout is not.
38532
+ * BATCH-ATOMIC SETTLEMENT is otherwise the rule: the receiver wraps a chunk in
38533
+ * one transaction, so a full 2xx settles every event in it and a non-2xx
38534
+ * settles none — which is why the whole chunk is stamped together on a FULL
38535
+ * accept and none of it otherwise. THREE reasons do not deserve whole-chunk
38536
+ * treatment, alongside a short accept, and all are re-sent one event at a
38537
+ * time:
37604
38538
  *
37605
- * Serial rather than concurrent on purpose. Firing N requests at once would
37606
- * trade a latency problem for a burst the plane's own per-key rate limiting
37607
- * would answer with the refusals the breaker then counts.
38539
+ * `invalid-request` a chunk the client refused to send at all. One malformed
38540
+ * event would otherwise cost the 49 good ones beside it —
38541
+ * a new way to lose data introduced by the very change
38542
+ * meant to stop losing it.
38543
+ * `route-absent` a deployment that predates the batch route. The
38544
+ * single-event route is the one it serves, and re-sending
38545
+ * here rather than inside the client is what gives each
38546
+ * request its own budget instead of 50 inside one.
38547
+ * `rejected` the deployment's SERVER-side twin of `invalid-request` —
38548
+ * a 4xx body refusal from schema drift on the other side
38549
+ * of the wire. Settlement is batch-atomic on this reason
38550
+ * exactly as on the others, so leaving it out would cost
38551
+ * the whole chunk for one event the DEPLOYMENT considers
38552
+ * malformed, where the per-item form cost only that one.
37608
38553
  *
37609
- * WHAT IS DROPPED IS COUNTED. Every other forward failure ends in
37610
- * `ForwardPolicy.run`'s catch and moves the breaker's file, which is what
37611
- * lets status call the forward unhealthy; this path returns BEFORE `run` is
37612
- * reached, so without the tally in `forward-drops.ts` a slow-but-answering
37613
- * plane produces no failures, keeps the breaker closed, renders a healthy
37614
- * block, and discards the tail of every batch indefinitely.
38554
+ * Every other reason (breaker-open, a refusal, a timeout) applies to the whole
38555
+ * chunk, and re-sending it item by item would just spend the budget failing 50
38556
+ * more times — for those, the blast radius stays exactly what it was before
38557
+ * batching.
37615
38558
  */
37616
38559
  async forwardBatch(inputs, toEvent) {
37617
38560
  const deadline = Date.now() + BATCH_FORWARD_BUDGET_MS;
37618
- for (let i = 0; i < inputs.length; i += 1) {
37619
- const now = Date.now();
37620
- if (now >= deadline) {
37621
- recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
37622
- return;
38561
+ const delivered = [];
38562
+ try {
38563
+ for (let i = 0; i < inputs.length; i += AUDIT_EVENT_BATCH_MAX) {
38564
+ const now = Date.now();
38565
+ if (now >= deadline) {
38566
+ recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
38567
+ return;
38568
+ }
38569
+ const chunk = inputs.slice(i, i + AUDIT_EVENT_BATCH_MAX).map((input2) => toEvent(input2));
38570
+ const forwarded = await this.deps.forward.run(
38571
+ () => this.deps.client.recordAuditEvents(
38572
+ chunk.map((event) => reKeyForForward(event, this.remoteInventory))
38573
+ )
38574
+ );
38575
+ if (forwarded.ok) {
38576
+ if (forwarded.value.accepted === chunk.length) {
38577
+ delivered.push(...chunk);
38578
+ continue;
38579
+ }
38580
+ } else if (
38581
+ // THREE reasons are worth a second pass, one at a time, and they are
38582
+ // the three settled BEFORE the control plane refused anything, or
38583
+ // (for `rejected`) refused the BODY rather than the connection.
38584
+ //
38585
+ // `invalid-request` — the CLIENT refused the body before any request
38586
+ // went out: a defect in one event, not an outage. Re-sending singly
38587
+ // isolates the bad one instead of charging its 49 neighbours for it.
38588
+ //
38589
+ // `route-absent` — the deployment predates the batch route and serves
38590
+ // only the single-event one. The retry IS the compatibility path, and
38591
+ // it has to live HERE rather than inside the client: each single gets
38592
+ // its own FORWARD_BUDGET_MS through `run`, whereas the client's own
38593
+ // fallback would spend 50 sequential round trips inside the ONE
38594
+ // budget wrapping this call — turning a working older deployment into
38595
+ // a timeout, three of those into an open breaker, and every row into
38596
+ // a silent drop while the status surface called an answering
38597
+ // deployment down.
38598
+ //
38599
+ // `rejected` — the deployment's own 4xx refusal of the body, the
38600
+ // server-side twin of `invalid-request`: isolating it the same way
38601
+ // costs one event instead of the whole chunk for a defect the
38602
+ // deployment considers local to one row.
38603
+ //
38604
+ // Every other reason (breaker-open, a refusal, a timeout) applies to
38605
+ // the whole chunk; re-sending it item by item would just spend the
38606
+ // budget failing 50 more times.
38607
+ forwarded.reason !== "invalid-request" && forwarded.reason !== "route-absent" && forwarded.reason !== "rejected"
38608
+ ) {
38609
+ continue;
38610
+ }
38611
+ for (const [j, event] of chunk.entries()) {
38612
+ const at = Date.now();
38613
+ if (at >= deadline) {
38614
+ recordForwardDrops(this.deps.dataDir, inputs.length - i - j, at);
38615
+ return;
38616
+ }
38617
+ const single = await this.deps.forward.run(
38618
+ () => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
38619
+ );
38620
+ if (single.ok) {
38621
+ delivered.push(event);
38622
+ continue;
38623
+ }
38624
+ if (single.reason === "breaker-open") {
38625
+ recordForwardDrops(this.deps.dataDir, inputs.length - i - j, at);
38626
+ return;
38627
+ }
38628
+ recordForwardDrops(this.deps.dataDir, 1, at);
38629
+ }
38630
+ }
38631
+ } finally {
38632
+ try {
38633
+ this.deps.local.markAuditEventsDelivered(delivered, Date.now());
38634
+ } catch {
37623
38635
  }
37624
- const input2 = inputs[i];
37625
- await this.deps.forward.run(
37626
- () => this.deps.client.recordAuditEvent(reKeyForForward(toEvent(input2), this.remoteInventory))
37627
- );
37628
38636
  }
37629
38637
  }
37630
38638
  // Delegated as a BATCH rather than looped over recordLlmCall: the inner
@@ -37667,9 +38675,10 @@ var AttachedDataGateway = class {
37667
38675
  // local store.
37668
38676
  async recordConfigScan(record2) {
37669
38677
  await this.deps.local.recordConfigScan(record2);
37670
- await this.deps.forward.run(
38678
+ const forwarded = await this.deps.forward.run(
37671
38679
  () => this.deps.client.recordAuditEvent(reKeyForForward(record2.scanEvent, this.remoteInventory))
37672
38680
  );
38681
+ if (forwarded.ok) this.deps.local.markAuditEventsDelivered([record2.scanEvent], Date.now());
37673
38682
  }
37674
38683
  async recordBlockedDetection(entry) {
37675
38684
  return this.deps.local.recordBlockedDetection(entry);
@@ -37803,6 +38812,18 @@ var AttachedDataGateway = class {
37803
38812
  // exactly what it did, leaving the whole control inert on every device
37804
38813
  // while every test around it stayed green.
37805
38814
  prohibitedModels: cached2.prohibitedModels
38815
+ // ALSO HONOURED FROM THE CACHE, and not a bundle field at all: each
38816
+ // merged policy's own `provenance`. `mergeRaiseOnly` spreads the policies
38817
+ // it emits, so an 'authored' policy arriving from the control plane
38818
+ // keeps that marker even where the clamp rebuilds it with a stronger
38819
+ // action. The device reads it in exactly one direction — the rules such a
38820
+ // policy targets are not locally re-assignable — so it sits on the
38821
+ // `prohibitedModels` side of the line for the same reason that field
38822
+ // does: it can only ever ADD a refusal, never relax one, and an unsigned
38823
+ // cache therefore has no relaxation to grant by carrying it. Dropping it
38824
+ // would be the silent failure rather than the safe one — the action would
38825
+ // still be enforced while the local override the organization authored
38826
+ // away quietly came back.
37806
38827
  // `rulesComplete` is a STANDALONE-ONLY signal (the user's local installed
37807
38828
  // snapshot) and is taken from the LOCAL bundle only — never from the wire
37808
38829
  // or the on-disk cache. Honoring a cached one would hand the control plane, or
@@ -37842,10 +38863,10 @@ var AttachedDataGateway = class {
37842
38863
  //
37843
38864
  // Implementing these is what actually closes the skipped-local-maintenance
37844
38865
  // gap: the OSS structural guard `hasLocalStoreMaintenance()` is satisfied by
37845
- // any object carrying all five, so the composite qualifies and SessionStart
38866
+ // any object carrying them all, so the composite qualifies and SessionStart
37846
38867
  // runs maintenance on the device's real store.
37847
38868
  //
37848
- // ⚠ Three of the six are SYNCHRONOUS and must stay that way. `handle-session-start`
38869
+ // ⚠ Several of them are SYNCHRONOUS and must stay that way. `handle-session-start`
37849
38870
  // calls `capWarnEraEnforcement` without `await` and uses `staleBinaryNotice`'s
37850
38871
  // return value directly; declaring them `async` here would hand those call
37851
38872
  // sites a Promise and silently break both.
@@ -37868,9 +38889,15 @@ var AttachedDataGateway = class {
37868
38889
  // Delegated like the rest, and SYNCHRONOUS for the reason the note above
37869
38890
  // gives: `recordCapture` calls it after the forward has already settled, on a
37870
38891
  // path that has nothing left to await.
38892
+ markCaptureOwed(event) {
38893
+ this.deps.local.markCaptureOwed(event);
38894
+ }
37871
38895
  markCaptureDelivered(event, atMs) {
37872
38896
  this.deps.local.markCaptureDelivered(event, atMs);
37873
38897
  }
38898
+ markAuditEventsDelivered(events, atMs) {
38899
+ this.deps.local.markAuditEventsDelivered(events, atMs);
38900
+ }
37874
38901
  };
37875
38902
  function reKeyForForward(event, remote) {
37876
38903
  if (remote === null) {
@@ -37913,281 +38940,17 @@ function toolAuditEvent(input2) {
37913
38940
  }
37914
38941
 
37915
38942
  // ../../packages/plugin-runtime/src/attached/history-state.ts
37916
- import { readFileSync as readFileSync14 } from "fs";
37917
- import { join as join22 } from "path";
38943
+ import { readFileSync as readFileSync15 } from "fs";
38944
+ import { join as join23 } from "path";
37918
38945
 
37919
38946
  // ../../packages/plugin-runtime/src/attached/history-sync.ts
37920
38947
  import { createHash as createHash6 } from "crypto";
37921
38948
  import { hostname as hostname5 } from "os";
37922
38949
 
37923
- // ../../packages/remote/src/http.ts
37924
- import { request as httpRequest } from "http";
37925
- import { request as httpsRequest } from "https";
37926
- var DEFAULT_TIMEOUT_MS = 1e4;
37927
- var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
37928
- var RemoteRequestError = class extends Error {
37929
- constructor(status) {
37930
- super(`control-plane request failed with status ${String(status)}`);
37931
- this.status = status;
37932
- this.name = "RemoteRequestError";
37933
- }
37934
- status;
37935
- };
37936
- var RemoteRequestInvalid = class extends Error {
37937
- constructor(route, cause) {
37938
- super(`refusing to send a malformed body to ${route}`);
37939
- this.cause = cause;
37940
- this.name = "RemoteRequestInvalid";
37941
- }
37942
- cause;
37943
- };
37944
- var RemoteResponseInvalid = class extends Error {
37945
- constructor(route, detail) {
37946
- super(`control plane answered ${route} with ${detail}`);
37947
- this.name = "RemoteResponseInvalid";
37948
- }
37949
- };
37950
- var RemoteTransportError = class extends Error {
37951
- /**
37952
- * The status the peer sent, when headers arrived and only the BODY was
37953
- * refused.
37954
- *
37955
- * Undefined for the ordinary case this class was written for — no answer at
37956
- * all. It exists because two paths reject after a status has already been
37957
- * delivered: an oversized body and an aborted response. Discarding it there
37958
- * reported a deployment answering 401 with a verbose body as a network
37959
- * outage, which sends the reader to look at their network instead of their
37960
- * credential.
37961
- */
37962
- constructor(reason, status) {
37963
- super(`control-plane request did not complete: ${reason}`);
37964
- this.status = status;
37965
- this.name = "RemoteTransportError";
37966
- }
37967
- status;
37968
- };
37969
- async function send(options) {
37970
- const url2 = new URL(options.url);
37971
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
37972
- const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
37973
- const requestOptions = {
37974
- method: options.method,
37975
- headers: {
37976
- // CALLER HEADERS FIRST, so this module's own are not overridable. Spread
37977
- // last they win, and two of the values below are ones no caller may
37978
- // replace: `x-api-key` is the credential, and `content-length` is the
37979
- // byte count that stops a multi-byte body being truncated by the
37980
- // receiver. `SendOptions.headers` is a free-form record on an exported
37981
- // function, so "no caller does that today" is not the guarantee to rely
37982
- // on. The one header any caller actually passes — `if-none-match` on the
37983
- // conditional GET — is untouched by this order.
37984
- ...options.headers,
37985
- // The credential. One header, matching what the deployment authenticates
37986
- // on; a second copy in an `Authorization` header would be one more place
37987
- // it can be logged by an intermediary for no gain.
37988
- //
37989
- // Spread conditionally rather than assigned as `undefined`: Node's header
37990
- // handling and `content-length` bookkeeping treat a present-but-undefined
37991
- // key differently from an absent one, and "the header is not there" is
37992
- // the property the attach flow needs.
37993
- ...options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey },
37994
- accept: "application/json",
37995
- ...options.body === void 0 ? {} : {
37996
- "content-type": "application/json",
37997
- // Byte length, not string length: a multi-byte body sent with a
37998
- // character count is truncated by the receiver.
37999
- "content-length": String(Buffer.byteLength(options.body))
38000
- }
38001
- }
38002
- };
38003
- return new Promise((resolve2, reject) => {
38004
- let settled = false;
38005
- const fail = (reason, status) => {
38006
- if (settled) return;
38007
- settled = true;
38008
- reject(new RemoteTransportError(reason, status));
38009
- };
38010
- const req = send_(url2, requestOptions, (res) => {
38011
- const chunks = [];
38012
- let size = 0;
38013
- res.on("data", (chunk) => {
38014
- size += chunk.length;
38015
- if (size > MAX_RESPONSE_BYTES) {
38016
- fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
38017
- res.destroy();
38018
- req.destroy();
38019
- return;
38020
- }
38021
- chunks.push(chunk);
38022
- });
38023
- res.on("aborted", () => {
38024
- fail("the response was aborted", res.statusCode);
38025
- });
38026
- res.on("end", () => {
38027
- if (settled) return;
38028
- settled = true;
38029
- resolve2({
38030
- status: res.statusCode ?? 0,
38031
- headers: res.headers,
38032
- body: Buffer.concat(chunks).toString("utf8")
38033
- });
38034
- });
38035
- });
38036
- const deadline = setTimeout(() => {
38037
- fail(`no response within ${String(timeoutMs)}ms`);
38038
- req.destroy();
38039
- }, timeoutMs);
38040
- deadline.unref();
38041
- req.on("upgrade", (_res, socket) => {
38042
- fail("the deployment answered with a protocol upgrade");
38043
- socket.destroy();
38044
- });
38045
- req.on("close", () => {
38046
- fail("the connection closed before a response was read");
38047
- clearTimeout(deadline);
38048
- });
38049
- req.on("error", (err) => {
38050
- fail(err.message);
38051
- });
38052
- if (options.body !== void 0) req.write(options.body);
38053
- req.end();
38054
- });
38055
- }
38056
-
38057
- // ../../packages/remote/src/client.ts
38058
- var ROUTES = {
38059
- events: "/v1/events",
38060
- auditEvents: "/v1/audit-events",
38061
- auditEventsBatch: "/v1/audit-events/batch",
38062
- inventory: "/v1/inventory",
38063
- storePosture: "/v1/store-posture",
38064
- policyBundle: "/v1/policy-bundle",
38065
- whoami: "/v1/plugin/whoami",
38066
- shares: "/v1/shares"
38067
- };
38068
- function headerValue(response, name2) {
38069
- const raw = response.headers[name2];
38070
- if (raw === void 0) return void 0;
38071
- return Array.isArray(raw) ? raw[0] : raw;
38072
- }
38073
- function okBody(response) {
38074
- if (response.status < 200 || response.status >= 300) {
38075
- throw new RemoteRequestError(response.status);
38076
- }
38077
- return response.body;
38078
- }
38079
- function parsed(schema, body, route) {
38080
- let json2;
38081
- try {
38082
- json2 = JSON.parse(body);
38083
- } catch {
38084
- throw new RemoteResponseInvalid(route, "a body that is not JSON");
38085
- }
38086
- const result = schema.safeParse(json2);
38087
- if (!result.success) {
38088
- throw new RemoteResponseInvalid(route, "a body this client cannot read");
38089
- }
38090
- return result.data;
38091
- }
38092
- function withoutTrailingSlashes(endpoint) {
38093
- let end = endpoint.length;
38094
- while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
38095
- return endpoint.slice(0, end);
38096
- }
38097
- var SLASH = "/".charCodeAt(0);
38098
- function createRemoteClient(options) {
38099
- const base = withoutTrailingSlashes(options.endpoint);
38100
- const url2 = (route) => `${base}${route}`;
38101
- const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
38102
- const sendOne = async (event) => {
38103
- const validated = RecordAuditEventRequest.safeParse(event);
38104
- if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
38105
- const response = await send({
38106
- ...common,
38107
- method: "POST",
38108
- url: url2(ROUTES.auditEvents),
38109
- body: JSON.stringify(validated.data)
38110
- });
38111
- okBody(response);
38112
- };
38113
- return {
38114
- async ingestEvents(batch) {
38115
- const response = await send({
38116
- ...common,
38117
- method: "POST",
38118
- url: url2(ROUTES.events),
38119
- body: JSON.stringify(batch)
38120
- });
38121
- return parsed(IngestAck, okBody(response), ROUTES.events);
38122
- },
38123
- async ingestInventory(context) {
38124
- const response = await send({
38125
- ...common,
38126
- method: "POST",
38127
- url: url2(ROUTES.inventory),
38128
- body: JSON.stringify(context)
38129
- });
38130
- return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
38131
- },
38132
- async recordAuditEvent(event) {
38133
- await sendOne(event);
38134
- },
38135
- async recordAuditEvents(events) {
38136
- const validated = RecordAuditEventBatch.safeParse({ events });
38137
- if (!validated.success) {
38138
- throw new RemoteRequestInvalid(ROUTES.auditEventsBatch, validated.error);
38139
- }
38140
- const response = await send({
38141
- ...common,
38142
- method: "POST",
38143
- url: url2(ROUTES.auditEventsBatch),
38144
- body: JSON.stringify(validated.data)
38145
- });
38146
- if (response.status === 404) {
38147
- for (const event of validated.data.events) await sendOne(event);
38148
- return { accepted: validated.data.events.length };
38149
- }
38150
- return parsed(AuditEventBatchAck, okBody(response), ROUTES.auditEventsBatch);
38151
- },
38152
- async reportStorePosture(snapshot) {
38153
- const response = await send({
38154
- ...common,
38155
- method: "POST",
38156
- url: url2(ROUTES.storePosture),
38157
- body: JSON.stringify(snapshot)
38158
- });
38159
- okBody(response);
38160
- },
38161
- async getPolicyBundle(etag) {
38162
- const response = await send({
38163
- ...common,
38164
- method: "GET",
38165
- url: url2(ROUTES.policyBundle),
38166
- ...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
38167
- });
38168
- if (response.status === 304) {
38169
- return { changed: false, etag: headerValue(response, "etag") ?? etag };
38170
- }
38171
- const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
38172
- return { changed: true, bundle, etag: headerValue(response, "etag") };
38173
- },
38174
- async whoami() {
38175
- const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
38176
- return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
38177
- },
38178
- async recordProjectEgress(request) {
38179
- const validated = EgressIngestRequest.safeParse(request);
38180
- if (!validated.success) throw new RemoteRequestInvalid(ROUTES.shares, validated.error);
38181
- const response = await send({
38182
- ...common,
38183
- method: "POST",
38184
- url: url2(ROUTES.shares),
38185
- body: JSON.stringify(validated.data)
38186
- });
38187
- okBody(response);
38188
- }
38189
- };
38190
- }
38950
+ // ../../packages/plugin-runtime/src/attached/capture-rebuild.ts
38951
+ var CORRELATION_ID = EventMetadata.shape.correlationId;
38952
+ var TRACE_ID = EventMetadata.shape.traceId;
38953
+ var EXCEPTION_ID = EventMetadata.shape.exceptionIds.unwrap().element;
38191
38954
 
38192
38955
  // ../../packages/plugin-runtime/src/attached/history-sync-trigger.ts
38193
38956
  import { spawn } from "child_process";
@@ -38219,7 +38982,7 @@ function spawnDetached(scriptPath) {
38219
38982
  }
38220
38983
 
38221
38984
  // ../../packages/plugin-runtime/src/attached/plugin-block.ts
38222
- import { readFileSync as readFileSync15 } from "fs";
38985
+ import { readFileSync as readFileSync16 } from "fs";
38223
38986
  function createPluginBlock(build, policyStore) {
38224
38987
  return async () => {
38225
38988
  const cached2 = await policyStore.read();
@@ -38238,7 +39001,7 @@ function createPluginBlock(build, policyStore) {
38238
39001
  // ../../packages/plugin-runtime/src/attached/policy-store.ts
38239
39002
  import { randomUUID as randomUUID16 } from "crypto";
38240
39003
  import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
38241
- import { join as join23 } from "path";
39004
+ import { join as join24 } from "path";
38242
39005
 
38243
39006
  // ../../packages/plugin-runtime/src/attached/atomic-publish.ts
38244
39007
  import { rename as rename2 } from "fs/promises";
@@ -38262,7 +39025,7 @@ async function publishByRename(tmp, file2, move = rename2) {
38262
39025
 
38263
39026
  // ../../packages/plugin-runtime/src/attached/policy-store.ts
38264
39027
  function createPolicyStore(dir = dataDir()) {
38265
- const file2 = join23(dir, "policy-cache.json");
39028
+ const file2 = join24(dir, "policy-cache.json");
38266
39029
  async function read() {
38267
39030
  try {
38268
39031
  const raw = await readFile2(file2, "utf8");
@@ -38271,22 +39034,32 @@ function createPolicyStore(dir = dataDir()) {
38271
39034
  const record2 = parsed2;
38272
39035
  const bundle = PolicyBundle.parse(record2.bundle);
38273
39036
  const fetchedAtMs = typeof record2.fetchedAtMs === "number" ? record2.fetchedAtMs : 0;
38274
- const etag = typeof record2.etag === "string" ? record2.etag : void 0;
39037
+ const stored = typeof record2.etag === "string" ? record2.etag : void 0;
39038
+ const replayable = record2.shapeId === POLICY_BUNDLE_SHAPE_ID || knowsMoreThanThisBuild(record2.shapeId);
39039
+ const etag = replayable ? stored : void 0;
38275
39040
  return { bundle, fetchedAtMs, ...etag === void 0 ? {} : { etag } };
38276
39041
  } catch {
38277
39042
  return null;
38278
39043
  }
38279
39044
  }
38280
- async function write(bundle, etag) {
38281
- await ensureDataDir(dir);
38282
- const stored = {
38283
- bundle,
38284
- fetchedAtMs: Date.now(),
38285
- ...etag === void 0 ? {} : { etag }
38286
- };
39045
+ function knowsMoreThanThisBuild(shapeId) {
39046
+ if (typeof shapeId !== "string" || shapeId === "") return false;
39047
+ const theirs = new Set(shapeId.split(","));
39048
+ const ours = new Set(POLICY_BUNDLE_SHAPE_ID.split(","));
39049
+ return theirs.size > ours.size && [...ours].every((key) => theirs.has(key));
39050
+ }
39051
+ async function priorRecord() {
39052
+ try {
39053
+ const parsed2 = JSON.parse(await readFile2(file2, "utf8"));
39054
+ return typeof parsed2 === "object" && parsed2 !== null ? parsed2 : null;
39055
+ } catch {
39056
+ return null;
39057
+ }
39058
+ }
39059
+ async function publishRecord(record2) {
38287
39060
  const tmp = `${file2}.${randomUUID16()}.tmp`;
38288
39061
  try {
38289
- await writeFile2(tmp, JSON.stringify(stored), {
39062
+ await writeFile2(tmp, JSON.stringify(record2), {
38290
39063
  encoding: "utf8",
38291
39064
  mode: DATA_FILE_MODE,
38292
39065
  flag: "wx"
@@ -38297,6 +39070,27 @@ function createPolicyStore(dir = dataDir()) {
38297
39070
  throw err;
38298
39071
  }
38299
39072
  }
39073
+ async function write(bundle, etag) {
39074
+ await ensureDataDir(dir);
39075
+ const prior = await priorRecord();
39076
+ const priorVersion = prior?.bundle?.version;
39077
+ if (prior !== null && knowsMoreThanThisBuild(prior.shapeId) && priorVersion === bundle.version) {
39078
+ await publishRecord({
39079
+ ...prior,
39080
+ fetchedAtMs: Date.now()
39081
+ });
39082
+ return;
39083
+ }
39084
+ await publishRecord({
39085
+ bundle,
39086
+ fetchedAtMs: Date.now(),
39087
+ // Stamped on EVERY write, the 304 arm's included: that arm hands back the
39088
+ // bundle it already holds, and the point of the stamp is to describe the
39089
+ // build that last narrowed those bytes, which is this one.
39090
+ shapeId: POLICY_BUNDLE_SHAPE_ID,
39091
+ ...etag === void 0 ? {} : { etag }
39092
+ });
39093
+ }
38300
39094
  return { read, write, file: file2 };
38301
39095
  }
38302
39096
 
@@ -38462,11 +39256,11 @@ function readStorePosture(dbPath2) {
38462
39256
  // ../../packages/plugin-runtime/src/attached/posture-store.ts
38463
39257
  import { randomUUID as randomUUID17 } from "crypto";
38464
39258
  import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
38465
- import { join as join24 } from "path";
39259
+ import { join as join25 } from "path";
38466
39260
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
38467
39261
  function createPostureStore(dir = settingsDir(), legacyDir) {
38468
- const file2 = join24(dir, "posture-state.json");
38469
- const legacyFile = legacyDir === void 0 ? null : join24(legacyDir, "posture-state.json");
39262
+ const file2 = join25(dir, "posture-state.json");
39263
+ const legacyFile = legacyDir === void 0 ? null : join25(legacyDir, "posture-state.json");
38470
39264
  async function persist(state) {
38471
39265
  await ensureDataDir(dir);
38472
39266
  const tmp = `${file2}.${randomUUID17()}.tmp`;
@@ -38534,8 +39328,8 @@ function createPostureStore(dir = settingsDir(), legacyDir) {
38534
39328
  }
38535
39329
 
38536
39330
  // ../../packages/plugin-runtime/src/attached/sync-state.ts
38537
- import { readFileSync as readFileSync16 } from "fs";
38538
- import { join as join25 } from "path";
39331
+ import { readFileSync as readFileSync17 } from "fs";
39332
+ import { join as join26 } from "path";
38539
39333
 
38540
39334
  // ../../packages/plugin-runtime/src/attached/status.ts
38541
39335
  var REFUSAL_LINES = {
@@ -38877,9 +39671,21 @@ var StandaloneDataGateway = class {
38877
39671
  // for the whole of it, so a member that threw would make that answer a lie
38878
39672
  // the moment a composite delegated to it. A store-level no-op is the honest
38879
39673
  // shape — a standalone machine has nothing delivered to record.
39674
+ markCaptureOwed(event) {
39675
+ this.db.markCaptureOwed(event);
39676
+ }
38880
39677
  markCaptureDelivered(event, atMs) {
38881
39678
  this.db.markCaptureDelivered(event, atMs);
38882
39679
  }
39680
+ // Implemented, not stubbed, for the same reason its sibling above is: the
39681
+ // attached gateway is a DECORATOR over an instance of this class
39682
+ // (`attached/factory.ts` builds one and passes it as `deps.local`), so every
39683
+ // stamp the live forward makes lands here with a non-empty array. This is the
39684
+ // production write path for that feature, not a shape-satisfying no-op — a
39685
+ // machine that is merely standalone simply never calls it.
39686
+ markAuditEventsDelivered(events, atMs) {
39687
+ this.db.markAuditEventsDelivered(events, atMs);
39688
+ }
38883
39689
  staleBinaryNotice(currentVersion) {
38884
39690
  try {
38885
39691
  const newest = this.db.installedPacks.newestRecordedBinary();