@akasecurity/cli 0.9.7 → 0.9.8

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 (102) hide show
  1. package/README.md +5 -1
  2. package/dist/cli.js +1594 -504
  3. package/extension/manifest.json +1 -1
  4. package/native-host/host.js +1824 -309
  5. package/native-host/scan-worker.js +118 -0
  6. package/native-host/sync.js +19413 -0
  7. package/package.json +6 -4
  8. package/web-ui/rules/core-code-context/localhost-ref.json +2 -2
  9. package/web-ui/web-ui/.next/BUILD_ID +1 -1
  10. package/web-ui/web-ui/.next/app-build-manifest.json +88 -88
  11. package/web-ui/web-ui/.next/app-path-routes-manifest.json +7 -7
  12. package/web-ui/web-ui/.next/build-manifest.json +2 -2
  13. package/web-ui/web-ui/.next/prerender-manifest.json +3 -3
  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 +2 -2
  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 +2 -2
  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 +1 -1
  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 +2 -2
  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 +2 -2
  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 +1 -1
  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 +1 -1
  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 +7 -7
  63. package/web-ui/web-ui/.next/server/chunks/258.js +1 -1
  64. package/web-ui/web-ui/.next/server/chunks/271.js +2 -2
  65. package/web-ui/web-ui/.next/server/chunks/314.js +783 -0
  66. package/web-ui/web-ui/.next/server/chunks/523.js +1 -1
  67. package/web-ui/web-ui/.next/server/chunks/{477.js → 844.js} +138 -138
  68. package/web-ui/web-ui/.next/server/chunks/967.js +1 -1
  69. package/web-ui/web-ui/.next/server/functions-config-manifest.json +4 -4
  70. package/web-ui/web-ui/.next/server/middleware-manifest.json +5 -5
  71. package/web-ui/web-ui/.next/server/pages/404.html +1 -1
  72. package/web-ui/web-ui/.next/server/pages/500.html +1 -1
  73. package/web-ui/web-ui/.next/server/pages-manifest.json +1 -1
  74. package/web-ui/web-ui/.next/server/server-reference-manifest.js +1 -1
  75. package/web-ui/web-ui/.next/server/server-reference-manifest.json +1 -1
  76. package/web-ui/web-ui/.next/static/chunks/622-0520747def5cc37b.js +62 -0
  77. package/web-ui/web-ui/.next/static/chunks/699-f5e42f0ed71707d9.js +1 -0
  78. package/web-ui/web-ui/.next/static/chunks/app/(app)/activity/{page-2011aeec5114533f.js → page-ee3b9956abc7cf1b.js} +1 -1
  79. package/web-ui/web-ui/.next/static/chunks/app/(app)/data-shares/page-9997c04ce4d4a451.js +1 -0
  80. package/web-ui/web-ui/.next/static/chunks/app/(app)/detections/page-5c9b3e6f6d832127.js +1 -0
  81. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/[id]/page-5f56a9fa25e7af46.js +1 -0
  82. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/new/page-38dcdc2eb2e47f53.js +1 -0
  83. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/page-94d735bc414c9b5c.js +1 -0
  84. package/web-ui/web-ui/.next/static/chunks/app/(app)/findings/{page-5e9249b49d619cf7.js → page-073d0b64e942914f.js} +1 -1
  85. package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/page-71344cf66ee67b51.js +1 -0
  86. package/web-ui/web-ui/.next/static/chunks/app/(app)/scan/page-24429599495731bf.js +1 -0
  87. package/web-ui/web-ui/.next/static/chunks/app/(app)/settings/{page-bc4551c2cd096584.js → page-f633645e1112fcb9.js} +1 -1
  88. package/web-ui/web-ui/.next/static/chunks/app/(app)/updates/{page-d0b9a88e5cff5d9b.js → page-429f377f34456a96.js} +1 -1
  89. package/web-ui/web-ui/.next/static/chunks/app/(app)/vault/{page-de6afbc0036fcb0d.js → page-21c59276737a8b9e.js} +1 -1
  90. package/web-ui/web-ui/dist/scan-worker.js +118 -0
  91. package/web-ui/web-ui/.next/server/chunks/129.js +0 -783
  92. package/web-ui/web-ui/.next/static/chunks/622-e65110f232ad3136.js +0 -62
  93. package/web-ui/web-ui/.next/static/chunks/699-3a550a0d6101e35c.js +0 -1
  94. package/web-ui/web-ui/.next/static/chunks/app/(app)/data-shares/page-e8fbf441cdc78d7e.js +0 -1
  95. package/web-ui/web-ui/.next/static/chunks/app/(app)/detections/page-0cf86550a9f22cf8.js +0 -1
  96. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/[id]/page-3b57fa7a28949fdf.js +0 -1
  97. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/new/page-5524f6b8dd008924.js +0 -1
  98. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/page-5fcea0008a2c0bfc.js +0 -1
  99. package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/page-b1676f9a9b4673f1.js +0 -1
  100. package/web-ui/web-ui/.next/static/chunks/app/(app)/scan/page-3560c03478bda4c7.js +0 -1
  101. /package/web-ui/web-ui/.next/static/{YqH1rSLTmXzavf3MtB9ZX → zjNAa9qOfKutYwuhfySp_}/_buildManifest.js +0 -0
  102. /package/web-ui/web-ui/.next/static/{YqH1rSLTmXzavf3MtB9ZX → zjNAa9qOfKutYwuhfySp_}/_ssgManifest.js +0 -0
@@ -50,7 +50,7 @@ var require_ignore = __commonJS({
50
50
  var REGEX_SPLITALL_CRLF = /\r?\n/g;
51
51
  var REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/;
52
52
  var REGEX_TEST_TRAILING_SLASH = /\/$/;
53
- var SLASH = "/";
53
+ var SLASH2 = "/";
54
54
  var TMP_KEY_IGNORE = "node-ignore";
55
55
  if (typeof Symbol !== "undefined") {
56
56
  TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
@@ -422,11 +422,11 @@ var require_ignore = __commonJS({
422
422
  if (!REGEX_TEST_TRAILING_SLASH.test(path)) {
423
423
  return this.test(path);
424
424
  }
425
- const slices = path.split(SLASH).filter(Boolean);
425
+ const slices = path.split(SLASH2).filter(Boolean);
426
426
  slices.pop();
427
427
  if (slices.length) {
428
428
  const parent = this._t(
429
- slices.join(SLASH) + SLASH,
429
+ slices.join(SLASH2) + SLASH2,
430
430
  this._testCache,
431
431
  true,
432
432
  slices
@@ -442,14 +442,14 @@ var require_ignore = __commonJS({
442
442
  return cache[path];
443
443
  }
444
444
  if (!slices) {
445
- slices = path.split(SLASH).filter(Boolean);
445
+ slices = path.split(SLASH2).filter(Boolean);
446
446
  }
447
447
  slices.pop();
448
448
  if (!slices.length) {
449
449
  return cache[path] = this._rules.test(path, checkUnignored, MODE_IGNORE);
450
450
  }
451
451
  const parent = this._t(
452
- slices.join(SLASH) + SLASH,
452
+ slices.join(SLASH2) + SLASH2,
453
453
  cache,
454
454
  checkUnignored,
455
455
  slices
@@ -494,14 +494,31 @@ var require_ignore = __commonJS({
494
494
  // src/native-host/host.ts
495
495
  import { homedir as homedir3 } from "os";
496
496
 
497
- // ../../packages/plugin-sdk/src/config.ts
498
- import { existsSync as existsSync5 } from "fs";
499
- import { join as join8 } from "path";
497
+ // ../../packages/plugin-runtime/src/attached/failure.ts
498
+ function statusOf(err) {
499
+ if (typeof err !== "object" || err === null || !("status" in err)) return null;
500
+ const { status } = err;
501
+ if (typeof status !== "number" || !Number.isInteger(status)) return null;
502
+ return status >= 100 && status <= 599 ? status : null;
503
+ }
504
+ function classifyFailure(err) {
505
+ switch (statusOf(err)) {
506
+ case 401:
507
+ return "unauthorized";
508
+ case 403:
509
+ return "forbidden";
510
+ default:
511
+ return "unreachable";
512
+ }
513
+ }
500
514
 
501
- // ../../packages/persistence/src/database.ts
502
- import { randomUUID as randomUUID10 } from "crypto";
503
- import { join as join2, sep } from "path";
504
- import { DatabaseSync } from "node:sqlite";
515
+ // ../../packages/plugin-runtime/src/attached/forward-drops.ts
516
+ import { readFileSync as readFileSync7 } from "fs";
517
+ import { join as join10 } from "path";
518
+
519
+ // ../../packages/persistence/src/control-plane-credential.ts
520
+ import { chmodSync as chmodSync2, lstatSync as lstatSync2, readFileSync, rmSync as rmSync2, statSync } from "fs";
521
+ import { join } from "path";
505
522
 
506
523
  // ../../packages/schema/src/drizzle/sqlite-ddl.ts
507
524
  var SQLITE_MIGRATIONS = [
@@ -16322,6 +16339,125 @@ var ConfigScanRecord = external_exports.object({
16322
16339
  findings: external_exports.array(ConfigPostureFindingInput).optional()
16323
16340
  });
16324
16341
 
16342
+ // ../../packages/schema/src/zod/control-plane.ts
16343
+ var ATTACHED_CREDENTIAL_FILENAME = "control-plane-credential.json";
16344
+ var ATTACHED_CREDENTIAL_SPEC_VERSION = 1;
16345
+ var AttachedCredential = external_exports.object({
16346
+ specVersion: external_exports.literal(ATTACHED_CREDENTIAL_SPEC_VERSION),
16347
+ // The control-plane endpoint this credential was minted against.
16348
+ endpoint: external_exports.string().min(1),
16349
+ // The bearer credential itself. Never logged, never rendered — status
16350
+ // surfaces show `keyPrefix` and nothing else.
16351
+ apiKey: external_exports.string().min(1),
16352
+ // First few characters of the key, safe to display so a user can match the
16353
+ // credential against their organization's key list.
16354
+ keyPrefix: external_exports.string().min(1).max(16).optional(),
16355
+ mintedAt: external_exports.iso.datetime().optional()
16356
+ });
16357
+ var MAX_DATE_MS = 253402300799999;
16358
+ var MAX_INT4 = 2147483647;
16359
+ var StorePosturePack = external_exports.object({
16360
+ packId: external_exports.string().min(1),
16361
+ // 'namespace/packId'
16362
+ version: external_exports.string().min(1),
16363
+ enabled: external_exports.boolean(),
16364
+ // Stringified pass-through of the local store's `installed_packs.updated_at`
16365
+ // — the column format is store-version-dependent (epoch millis vs ISO), so
16366
+ // the wire shape assumes neither.
16367
+ updatedAt: external_exports.string().nullable()
16368
+ }).meta({ id: "StorePosturePack" });
16369
+ var StorePosturePolicyCounts = external_exports.object({
16370
+ total: external_exports.number().int().min(0),
16371
+ disabled: external_exports.number().int().min(0),
16372
+ // Exhaustive per-action map; the builder pre-fills every action with 0.
16373
+ //
16374
+ // Spelled out member-by-member rather than `z.record(ActionTaken, …)`. Zod
16375
+ // enforces exhaustiveness either way, but z.record emits `propertyNames` +
16376
+ // `additionalProperties` into a generated schema document, and a type
16377
+ // generator renders THAT with every key optional — a sender built against
16378
+ // the generated type would typecheck and still be rejected at runtime. An
16379
+ // explicit object emits `properties` + `required`, so generated types
16380
+ // demand all five.
16381
+ //
16382
+ // `satisfies Record<ActionTaken, …>` keeps the link to the enum: adding an
16383
+ // ActionTaken member is a COMPILE error here instead of silent drift.
16384
+ // `.strict()` is load-bearing — it rejects an unknown action key, which a
16385
+ // bare object would silently STRIP, accepting a miscounted map as valid.
16386
+ byAction: external_exports.object({
16387
+ warn: external_exports.number().int().min(0),
16388
+ redact: external_exports.number().int().min(0),
16389
+ block: external_exports.number().int().min(0),
16390
+ allow: external_exports.number().int().min(0),
16391
+ log: external_exports.number().int().min(0)
16392
+ }).strict()
16393
+ }).meta({ id: "StorePosturePolicyCounts" });
16394
+ var StorePosturePlugin = external_exports.object({
16395
+ /** Package name of the reporting plugin. */
16396
+ package: external_exports.string().min(1).max(200),
16397
+ version: external_exports.string().min(1).max(64),
16398
+ /** Version of the bundled core, when the build records one separately. */
16399
+ ossVersion: external_exports.string().max(64).nullable(),
16400
+ /**
16401
+ * `version` of the policy bundle this machine last fetched. Bounded at 200
16402
+ * rather than the 64 a bare sha256 hex digest needs today, so a later
16403
+ * format with an algorithm prefix does not start rejecting the channel.
16404
+ */
16405
+ policyBundleVersion: external_exports.string().max(200).nullable(),
16406
+ /** Epoch millis, on the CLIENT clock, of that fetch. */
16407
+ policyFetchedAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable()
16408
+ }).meta({ id: "StorePosturePlugin" });
16409
+ var StorePostureSnapshot = external_exports.object({
16410
+ deviceId: external_exports.guid(),
16411
+ hostname: external_exports.string().min(1).max(253),
16412
+ // Epoch millis on the CLIENT clock. Bounded by what a receiving store
16413
+ // accepts (see MAX_DATE_MS), not by what a JavaScript Date can hold.
16414
+ capturedAt: external_exports.number().int().min(0).max(MAX_DATE_MS),
16415
+ // False is a measurement, not an error state: "no local store exists on
16416
+ // this machine".
16417
+ storePresent: external_exports.boolean(),
16418
+ schemaVersion: external_exports.number().int().min(0).max(MAX_INT4).nullable(),
16419
+ // PRAGMA user_version
16420
+ findingsTotal: external_exports.number().int().min(0).max(MAX_INT4),
16421
+ // Epoch millis, bounded like `capturedAt` — see MAX_DATE_MS on what that
16422
+ // bound does and does not do. Worth stating for these two specifically:
16423
+ // they are read from the local store's own ROWS rather than from this
16424
+ // machine's clock, so a damaged or hand-edited store is enough to produce
16425
+ // an out-of-range value with no clock skew involved.
16426
+ findingsFirstAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable(),
16427
+ findingsLastAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable(),
16428
+ packs: external_exports.array(StorePosturePack).max(500),
16429
+ policyCounts: StorePosturePolicyCounts,
16430
+ // OPTIONAL, not nullable: a reporter that predates this member keeps
16431
+ // getting its 200 without a payload change.
16432
+ plugin: StorePosturePlugin.optional()
16433
+ }).meta({ id: "StorePostureSnapshot" });
16434
+ var CAPTURE_VERSION_PREFIX = "capture/";
16435
+ var RecordAuditEventRequest = AuditEventInput.extend({
16436
+ inspections: external_exports.array(ToolCallInspection).default([])
16437
+ }).refine((v) => v.inspections.every((i) => !i.ruleVersion.startsWith(CAPTURE_VERSION_PREFIX)), {
16438
+ message: `inspections[].ruleVersion must not start with \`${CAPTURE_VERSION_PREFIX}\` \u2014 that namespace is reserved for capture definitions the control plane mints itself`,
16439
+ path: ["inspections"]
16440
+ }).meta({ id: "RecordAuditEventRequest" });
16441
+ var IngestAck = external_exports.object({
16442
+ accepted: external_exports.number().int().nonnegative(),
16443
+ duplicates: external_exports.number().int().nonnegative()
16444
+ });
16445
+ var PRINTABLE = /^[^\p{Cc}\p{Cf}]*$/u;
16446
+ var printable = (max) => external_exports.string().max(max).regex(PRINTABLE, "must not contain control characters");
16447
+ var PluginWhoami = external_exports.object({
16448
+ tenantName: printable(200),
16449
+ userEmail: printable(320),
16450
+ role: printable(64),
16451
+ keyKind: printable(64),
16452
+ serverTime: printable(64)
16453
+ });
16454
+ var ControlPlaneErrorBody = external_exports.object({
16455
+ error: external_exports.object({
16456
+ code: external_exports.string().optional(),
16457
+ message: external_exports.string().optional()
16458
+ }).optional()
16459
+ });
16460
+
16325
16461
  // ../../packages/schema/src/zod/registry.ts
16326
16462
  var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16327
16463
  var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
@@ -16624,15 +16760,15 @@ function summaryToDetectionListItem(s) {
16624
16760
  }
16625
16761
  function rowToDetectionDetail(row, findingsLast30d, update) {
16626
16762
  const rules = row.rules.flatMap((r) => {
16627
- const parsed = Matcher.safeParse(r.matcher);
16628
- if (!parsed.success) return [];
16763
+ const parsed2 = Matcher.safeParse(r.matcher);
16764
+ if (!parsed2.success) return [];
16629
16765
  return [
16630
16766
  {
16631
16767
  id: r.id,
16632
16768
  name: r.name,
16633
16769
  category: r.category,
16634
16770
  severity: r.severity,
16635
- matcher: parsed.data
16771
+ matcher: parsed2.data
16636
16772
  }
16637
16773
  ];
16638
16774
  });
@@ -17278,8 +17414,8 @@ function toApiAction(dbVal) {
17278
17414
  }
17279
17415
  function toApiCategory(dbVal) {
17280
17416
  if (dbVal === "code_context") return "source_code";
17281
- const parsed = FindingCategory.safeParse(dbVal);
17282
- return parsed.success ? parsed.data : "custom";
17417
+ const parsed2 = FindingCategory.safeParse(dbVal);
17418
+ return parsed2.success ? parsed2.data : "custom";
17283
17419
  }
17284
17420
  function toApiProvider(sourceTool) {
17285
17421
  return TOOL_TO_HARNESS[sourceTool] ?? HARNESS.Api;
@@ -17909,6 +18045,9 @@ var WorkspaceSettings = external_exports.object({
17909
18045
  function defaultWorkspaceSettings() {
17910
18046
  return WorkspaceSettings.parse({});
17911
18047
  }
18048
+ function isAttached(settings) {
18049
+ return settings.runMode === "attached" && settings.controlPlane !== void 0;
18050
+ }
17912
18051
  function toInventoryRow(input, id, now) {
17913
18052
  return {
17914
18053
  id,
@@ -18176,8 +18315,8 @@ function builtinPolicyIsReversible(id) {
18176
18315
  return BUILTIN_POLICY_SPECS[id].reversible;
18177
18316
  }
18178
18317
  function policyIdIsReversible(policyId) {
18179
- const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18180
- const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
18318
+ const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18319
+ const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
18181
18320
  return builtinPolicyIsReversible(id);
18182
18321
  }
18183
18322
  var DEFAULT_ACTIONS = Object.fromEntries(
@@ -18188,8 +18327,8 @@ var BUILTIN_POLICIES = Object.fromEntries(
18188
18327
  );
18189
18328
  var DEFAULT_PACK_POLICY_ID = "monitor";
18190
18329
  function policyIdToAction(policyId) {
18191
- const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18192
- const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
18330
+ const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18331
+ const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
18193
18332
  return BUILTIN_POLICIES[id].action;
18194
18333
  }
18195
18334
  var UsedByItem = external_exports.object({
@@ -18632,53 +18771,6 @@ function reviewSeverityRank(reasons) {
18632
18771
  return Math.min(...reasons.map((r) => REVIEW_SEVERITY_RANK[r]));
18633
18772
  }
18634
18773
 
18635
- // ../../packages/persistence/src/ids.ts
18636
- import { createHash } from "crypto";
18637
- function sha256Hex(input) {
18638
- return createHash("sha256").update(input).digest("hex");
18639
- }
18640
- function inventoryId(objectType, identityKey) {
18641
- return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
18642
- }
18643
- function sourceProjectId(url2) {
18644
- return sha256Hex(canonicalIdentity(["source_project", url2]));
18645
- }
18646
- function classifiedDataId(cls) {
18647
- return sha256Hex(canonicalIdentity(["classified_data", cls]));
18648
- }
18649
- function inspectionDefinitionId(ruleId, version2) {
18650
- return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
18651
- }
18652
- function llmCallId(sessionId, messageId) {
18653
- return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
18654
- }
18655
- function toolCallId(sessionId, toolUseId) {
18656
- return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
18657
- }
18658
- function inspectionFindingId(auditEventId, ruleId, spanStart, spanEnd) {
18659
- return sha256Hex(
18660
- canonicalIdentity([
18661
- "inspection_finding",
18662
- auditEventId,
18663
- ruleId,
18664
- String(spanStart),
18665
- String(spanEnd)
18666
- ])
18667
- );
18668
- }
18669
- var NO_SESSION = "no_session";
18670
- var NO_PATH = "no_path";
18671
- function captureId(sessionId, contentHash, filePath = null) {
18672
- return sha256Hex(
18673
- canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
18674
- );
18675
- }
18676
-
18677
- // ../../packages/persistence/src/internal/snapshot.ts
18678
- import { randomUUID } from "crypto";
18679
- import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
18680
- import { basename, dirname, join } from "path";
18681
-
18682
18774
  // ../../packages/persistence/src/paths.ts
18683
18775
  import {
18684
18776
  chmodSync,
@@ -18729,6 +18821,23 @@ function tightenPerms(file2) {
18729
18821
  function writeExclusiveOwnerOnlySync(file2, data) {
18730
18822
  writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
18731
18823
  }
18824
+ function writeOwnerOnlyFileSync(file2, data) {
18825
+ const tmp = `${file2}.${String(process.pid)}.tmp`;
18826
+ try {
18827
+ rmSync(tmp, { force: true });
18828
+ } catch {
18829
+ }
18830
+ try {
18831
+ writeExclusiveOwnerOnlySync(tmp, data);
18832
+ renameSync(tmp, file2);
18833
+ } finally {
18834
+ try {
18835
+ rmSync(tmp, { force: true });
18836
+ } catch {
18837
+ }
18838
+ }
18839
+ tightenFile(file2);
18840
+ }
18732
18841
  function classifyOccupant(file2) {
18733
18842
  try {
18734
18843
  if (lstatSync(file2).isSymbolicLink()) return { kind: "symlink" };
@@ -18787,7 +18896,123 @@ function publishByLink(tmp, file2, data) {
18787
18896
  }
18788
18897
  }
18789
18898
 
18899
+ // ../../packages/persistence/src/control-plane-credential.ts
18900
+ function controlPlaneCredentialPath(settingsDir2) {
18901
+ return join(settingsDir2, ATTACHED_CREDENTIAL_FILENAME);
18902
+ }
18903
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
18904
+ function isSafeEndpoint(endpoint) {
18905
+ let parsed2;
18906
+ try {
18907
+ parsed2 = new URL(endpoint);
18908
+ } catch {
18909
+ return false;
18910
+ }
18911
+ if (parsed2.protocol === "https:") return true;
18912
+ return parsed2.protocol === "http:" && LOOPBACK_HOSTS.has(parsed2.hostname);
18913
+ }
18914
+ function repairOrRefuseMode(file2) {
18915
+ const link = lstatSync2(file2, { throwIfNoEntry: false });
18916
+ if (link === void 0) return "absent";
18917
+ if (link.isSymbolicLink()) return "untrusted";
18918
+ const stat = statSync(file2, { throwIfNoEntry: false });
18919
+ if (stat === void 0) return "absent";
18920
+ const uid = process.getuid?.();
18921
+ if (uid !== void 0 && stat.uid !== uid) return "untrusted";
18922
+ if (process.platform !== "win32" && (stat.mode & 511) !== DATA_FILE_MODE) {
18923
+ try {
18924
+ chmodSync2(file2, DATA_FILE_MODE);
18925
+ } catch {
18926
+ return "untrusted";
18927
+ }
18928
+ }
18929
+ return "ok";
18930
+ }
18931
+ function readControlPlaneCredentialState(settingsDir2, connection) {
18932
+ const file2 = controlPlaneCredentialPath(settingsDir2);
18933
+ let raw;
18934
+ const gate = repairOrRefuseMode(file2);
18935
+ if (gate === "absent") return { usable: false, reason: "absent" };
18936
+ if (gate === "untrusted") return { usable: false, reason: "untrusted-file" };
18937
+ try {
18938
+ raw = readFileSync(file2, "utf8");
18939
+ } catch (err) {
18940
+ const code = err.code;
18941
+ return { usable: false, reason: code === "ENOENT" ? "absent" : "unreadable" };
18942
+ }
18943
+ let parsed2;
18944
+ try {
18945
+ parsed2 = JSON.parse(raw);
18946
+ } catch {
18947
+ return { usable: false, reason: "malformed" };
18948
+ }
18949
+ const result = AttachedCredential.safeParse(parsed2);
18950
+ if (!result.success) return { usable: false, reason: "malformed" };
18951
+ if (!isSafeEndpoint(result.data.endpoint)) {
18952
+ return { usable: false, reason: "unsafe-endpoint" };
18953
+ }
18954
+ if (connection !== void 0 && connection.endpoint !== result.data.endpoint) {
18955
+ return {
18956
+ usable: false,
18957
+ reason: "endpoint-mismatch",
18958
+ credentialEndpoint: result.data.endpoint,
18959
+ settingsEndpoint: connection.endpoint
18960
+ };
18961
+ }
18962
+ return { usable: true, credential: result.data };
18963
+ }
18964
+
18965
+ // ../../packages/persistence/src/database.ts
18966
+ import { randomUUID as randomUUID10 } from "crypto";
18967
+ import { join as join3, sep } from "path";
18968
+ import { DatabaseSync } from "node:sqlite";
18969
+
18970
+ // ../../packages/persistence/src/ids.ts
18971
+ import { createHash } from "crypto";
18972
+ function sha256Hex(input) {
18973
+ return createHash("sha256").update(input).digest("hex");
18974
+ }
18975
+ function inventoryId(objectType, identityKey) {
18976
+ return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
18977
+ }
18978
+ function sourceProjectId(url2) {
18979
+ return sha256Hex(canonicalIdentity(["source_project", url2]));
18980
+ }
18981
+ function classifiedDataId(cls) {
18982
+ return sha256Hex(canonicalIdentity(["classified_data", cls]));
18983
+ }
18984
+ function inspectionDefinitionId(ruleId, version2) {
18985
+ return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
18986
+ }
18987
+ function llmCallId(sessionId, messageId) {
18988
+ return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
18989
+ }
18990
+ function toolCallId(sessionId, toolUseId) {
18991
+ return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
18992
+ }
18993
+ function inspectionFindingId(auditEventId, ruleId, spanStart, spanEnd) {
18994
+ return sha256Hex(
18995
+ canonicalIdentity([
18996
+ "inspection_finding",
18997
+ auditEventId,
18998
+ ruleId,
18999
+ String(spanStart),
19000
+ String(spanEnd)
19001
+ ])
19002
+ );
19003
+ }
19004
+ var NO_SESSION = "no_session";
19005
+ var NO_PATH = "no_path";
19006
+ function captureId(sessionId, contentHash, filePath = null) {
19007
+ return sha256Hex(
19008
+ canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
19009
+ );
19010
+ }
19011
+
18790
19012
  // ../../packages/persistence/src/internal/snapshot.ts
19013
+ import { randomUUID } from "crypto";
19014
+ import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync2 } from "fs";
19015
+ import { basename, dirname, join as join2 } from "path";
18791
19016
  function backupPath(file2, tag) {
18792
19017
  return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
18793
19018
  }
@@ -18797,15 +19022,15 @@ var STAGED_NAME_SUFFIX = `.bak${SNAPSHOT_STAGING_SUFFIX}`;
18797
19022
  var SNAPSHOT_STAGING_COPY = "copy";
18798
19023
  function createSnapshotStaging(backup) {
18799
19024
  const stage = `${backup}${SNAPSHOT_STAGING_SUFFIX}`;
18800
- rmSync2(stage, { recursive: true, force: true });
19025
+ rmSync3(stage, { recursive: true, force: true });
18801
19026
  mkdirOwnerOnlySync(stage);
18802
19027
  tightenDir(stage);
18803
- return { stage, copy: join(stage, SNAPSHOT_STAGING_COPY) };
19028
+ return { stage, copy: join2(stage, SNAPSHOT_STAGING_COPY) };
18804
19029
  }
18805
19030
  function idleMs(entry) {
18806
- for (const candidate of [join(entry, SNAPSHOT_STAGING_COPY), entry]) {
19031
+ for (const candidate of [join2(entry, SNAPSHOT_STAGING_COPY), entry]) {
18807
19032
  try {
18808
- return Date.now() - statSync(candidate).mtimeMs;
19033
+ return Date.now() - statSync2(candidate).mtimeMs;
18809
19034
  } catch {
18810
19035
  }
18811
19036
  }
@@ -18822,11 +19047,11 @@ function reapStalePartials(file2) {
18822
19047
  }
18823
19048
  for (const name of entries) {
18824
19049
  if (!name.startsWith(prefix) || !name.endsWith(STAGED_NAME_SUFFIX)) continue;
18825
- const staging = join(dir, name);
19050
+ const staging = join2(dir, name);
18826
19051
  try {
18827
19052
  const idle = idleMs(staging);
18828
19053
  if (idle !== null && idle > STALE_PARTIAL_MS) {
18829
- rmSync2(staging, { recursive: true, force: true });
19054
+ rmSync3(staging, { recursive: true, force: true });
18830
19055
  }
18831
19056
  } catch {
18832
19057
  }
@@ -18840,13 +19065,13 @@ function snapshotStore(db, backup) {
18840
19065
  renameSync2(copy, backup);
18841
19066
  } catch (error51) {
18842
19067
  try {
18843
- rmSync2(stage, { recursive: true, force: true });
19068
+ rmSync3(stage, { recursive: true, force: true });
18844
19069
  } catch {
18845
19070
  }
18846
19071
  throw error51;
18847
19072
  }
18848
19073
  try {
18849
- rmSync2(stage, { recursive: true, force: true });
19074
+ rmSync3(stage, { recursive: true, force: true });
18850
19075
  } catch {
18851
19076
  }
18852
19077
  }
@@ -18861,7 +19086,7 @@ function moveStoreAside(file2, backup) {
18861
19086
  renameSync2(sidecar, moved);
18862
19087
  undo.push([moved, sidecar]);
18863
19088
  } catch {
18864
- rmSync2(sidecar, { force: true });
19089
+ rmSync3(sidecar, { force: true });
18865
19090
  }
18866
19091
  }
18867
19092
  } catch (error51) {
@@ -18877,14 +19102,14 @@ function moveStoreAside(file2, backup) {
18877
19102
  }
18878
19103
  function discardStore(file2, backup) {
18879
19104
  try {
18880
- rmSync2(file2, { force: true });
19105
+ rmSync3(file2, { force: true });
18881
19106
  for (const sidecar of dbSidecars(file2)) {
18882
- rmSync2(sidecar, { force: true });
19107
+ rmSync3(sidecar, { force: true });
18883
19108
  }
18884
19109
  } catch (error51) {
18885
19110
  if (existsSync(file2)) {
18886
19111
  try {
18887
- rmSync2(backup, { force: true });
19112
+ rmSync3(backup, { force: true });
18888
19113
  } catch {
18889
19114
  }
18890
19115
  }
@@ -19116,10 +19341,31 @@ function applyMigrations(db, file2) {
19116
19341
  if (drained) applyLegacyDropMigration(db, file2);
19117
19342
  }
19118
19343
  }
19344
+ function readLegacyTables(db) {
19345
+ let holdsRows = false;
19346
+ const marks = [];
19347
+ for (const table of ["events", "findings"]) {
19348
+ try {
19349
+ const row = db.prepare(`SELECT count(*) AS n, ifnull(max(rowid), -1) AS hi FROM ${table}`).get();
19350
+ if (row === void 0) {
19351
+ holdsRows = true;
19352
+ marks.push(`${table}:unreadable`);
19353
+ continue;
19354
+ }
19355
+ if (row.n > 0) holdsRows = true;
19356
+ marks.push(`${table}:${String(row.n)}:${String(row.hi)}`);
19357
+ } catch {
19358
+ holdsRows = true;
19359
+ marks.push(`${table}:unreadable`);
19360
+ }
19361
+ }
19362
+ return { holdsRows, mark: marks.join("|") };
19363
+ }
19119
19364
  function applyLegacyDropMigration(db, file2) {
19120
19365
  const migration = SQLITE_MIGRATIONS.find((m) => m.tag === LEGACY_DROP_MIGRATION_TAG);
19121
19366
  if (!migration) return;
19122
- if (file2) {
19367
+ const before = file2 === void 0 ? void 0 : readLegacyTables(db);
19368
+ if (file2 !== void 0 && before?.holdsRows === true) {
19123
19369
  try {
19124
19370
  backupBeforeLegacyDrop(db, file2);
19125
19371
  } catch (error51) {
@@ -19133,6 +19379,12 @@ function applyLegacyDropMigration(db, file2) {
19133
19379
  () => {
19134
19380
  const alreadyDropped = db.prepare("SELECT 1 FROM migration_ledger WHERE tag = ?").get(migration.tag);
19135
19381
  if (alreadyDropped) return;
19382
+ if (before !== void 0 && readLegacyTables(db).mark !== before.mark) {
19383
+ akaWarn(
19384
+ "legacy events/findings rows changed after the pre-drop snapshot decision; deferring the drop so the next open can copy them first."
19385
+ );
19386
+ return;
19387
+ }
19136
19388
  for (const statement of splitStatements(migration.sql)) {
19137
19389
  db.exec(statement);
19138
19390
  }
@@ -19487,8 +19739,8 @@ function safeJson(s, fallback) {
19487
19739
  function parseJsonObject(s) {
19488
19740
  if (s == null) return void 0;
19489
19741
  try {
19490
- const parsed = JSON.parse(s);
19491
- if (typeof parsed === "object" && parsed !== null) return parsed;
19742
+ const parsed2 = JSON.parse(s);
19743
+ if (typeof parsed2 === "object" && parsed2 !== null) return parsed2;
19492
19744
  } catch {
19493
19745
  }
19494
19746
  return void 0;
@@ -19499,16 +19751,16 @@ function encodeKeysetCursor(payload) {
19499
19751
  return Buffer.from(JSON.stringify(payload)).toString("base64url");
19500
19752
  }
19501
19753
  function decodeKeysetCursor(cursor) {
19502
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
19503
- if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
19754
+ const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
19755
+ if (parsed2 !== void 0 && "startedAtMs" in parsed2 && "id" in parsed2 && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
19504
19756
  // resumes from is epoch millis, and a payload carrying ±Infinity or a
19505
19757
  // fraction binds cleanly rather than failing — returning an EMPTY page with
19506
19758
  // a null cursor, which a caller reads as "end of list". That is the one
19507
19759
  // outcome a cursor that does not decode must never produce, since the
19508
19760
  // documented behaviour above is to restart from the top. (`1e999` is valid
19509
19761
  // JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
19510
- Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
19511
- return parsed;
19762
+ Number.isInteger(parsed2.startedAtMs) && typeof parsed2.id === "string") {
19763
+ return parsed2;
19512
19764
  }
19513
19765
  return null;
19514
19766
  }
@@ -19573,18 +19825,18 @@ var DB_EVENT_TYPE_TO_KIND = {
19573
19825
  };
19574
19826
  function safeParseStringArray(raw) {
19575
19827
  if (!raw) return [];
19576
- const parsed = safeJson(raw, null);
19577
- return Array.isArray(parsed) ? parsed : [];
19828
+ const parsed2 = safeJson(raw, null);
19829
+ return Array.isArray(parsed2) ? parsed2 : [];
19578
19830
  }
19579
19831
  var DEFAULT_HARNESS = HARNESS.ClaudeCode;
19580
19832
  function toHarness(raw) {
19581
- const parsed = Harness.safeParse(raw);
19582
- return parsed.success ? parsed.data : DEFAULT_HARNESS;
19833
+ const parsed2 = Harness.safeParse(raw);
19834
+ return parsed2.success ? parsed2.data : DEFAULT_HARNESS;
19583
19835
  }
19584
19836
  function resolveLifecycle(row, lastActivityMs, nowMs) {
19585
19837
  if (row.status) {
19586
- const parsed = SessionStatus.safeParse(row.status);
19587
- if (parsed.success) return { status: parsed.data, endedAtMs: row.ended_at };
19838
+ const parsed2 = SessionStatus.safeParse(row.status);
19839
+ if (parsed2.success) return { status: parsed2.data, endedAtMs: row.ended_at };
19588
19840
  }
19589
19841
  if (row.ended_at !== null) return { status: "completed", endedAtMs: row.ended_at };
19590
19842
  if (lastActivityMs >= nowMs - LIVE_ACTIVITY_WINDOW_MS) {
@@ -20543,9 +20795,9 @@ var SqliteDetectionsRepository = class {
20543
20795
  const ruleIds = /* @__PURE__ */ new Set();
20544
20796
  for (const r of rows) {
20545
20797
  if (intToBool(r.enabled)) active += 1;
20546
- const parsed = parseRules(r.rulesJson);
20547
- rules += parsed.length;
20548
- for (const rule of parsed) {
20798
+ const parsed2 = parseRules(r.rulesJson);
20799
+ rules += parsed2.length;
20800
+ for (const rule of parsed2) {
20549
20801
  if (typeof rule.id === "string") ruleIds.add(rule.id);
20550
20802
  }
20551
20803
  }
@@ -21079,12 +21331,12 @@ function encodeGroupCursor(group) {
21079
21331
  return Buffer.from(JSON.stringify(payload)).toString("base64url");
21080
21332
  }
21081
21333
  function decodeGroupCursor(cursor) {
21082
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
21083
- if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
21334
+ const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
21335
+ if (parsed2 !== void 0 && typeof parsed2.sev === "string" && typeof parsed2.t === "string" && typeof parsed2.id === "string") {
21084
21336
  return {
21085
- severity: parsed.sev,
21086
- latestDetectedAt: parsed.t,
21087
- id: parsed.id
21337
+ severity: parsed2.sev,
21338
+ latestDetectedAt: parsed2.t,
21339
+ id: parsed2.id
21088
21340
  };
21089
21341
  }
21090
21342
  return null;
@@ -22218,16 +22470,16 @@ var SqliteInstalledPacksRepository = class {
22218
22470
  continue;
22219
22471
  }
22220
22472
  for (const entry of raw) {
22221
- const parsed = Rule.safeParse(entry);
22222
- if (parsed.success) {
22223
- out.rules.push(parsed.data);
22224
- out.ruleActions.set(parsed.data.id, action);
22225
- out.ruleVersions.set(parsed.data.id, row.version);
22226
- if (reversible) out.reversibleRules.add(parsed.data.id);
22227
- else out.reversibleRules.delete(parsed.data.id);
22473
+ const parsed2 = Rule.safeParse(entry);
22474
+ if (parsed2.success) {
22475
+ out.rules.push(parsed2.data);
22476
+ out.ruleActions.set(parsed2.data.id, action);
22477
+ out.ruleVersions.set(parsed2.data.id, row.version);
22478
+ if (reversible) out.reversibleRules.add(parsed2.data.id);
22479
+ else out.reversibleRules.delete(parsed2.data.id);
22228
22480
  } else {
22229
22481
  out.invalidRules += 1;
22230
- reject(pack, printableRuleId(entry), firstIssueReason(parsed.error));
22482
+ reject(pack, printableRuleId(entry), firstIssueReason(parsed2.error));
22231
22483
  }
22232
22484
  }
22233
22485
  }
@@ -23617,15 +23869,15 @@ function encodeReuseCursor(payload) {
23617
23869
  return Buffer.from(JSON.stringify(payload)).toString("base64url");
23618
23870
  }
23619
23871
  function decodeReuseCursor(cursor) {
23620
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
23621
- if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
23872
+ const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
23873
+ if (parsed2 !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
23622
23874
  // ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
23623
23875
  // null cursor, which the caller reads as "end of list" — the one outcome a
23624
23876
  // malformed cursor must never produce, since restarting from the top is the
23625
23877
  // documented behaviour and the only recoverable one. (`1e999` is valid JSON
23626
23878
  // and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
23627
- Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
23628
- return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
23879
+ Number.isInteger(parsed2.occurrences) && typeof parsed2.pointerId === "string") {
23880
+ return { occurrences: parsed2.occurrences, pointerId: parsed2.pointerId };
23629
23881
  }
23630
23882
  return null;
23631
23883
  }
@@ -25354,7 +25606,7 @@ function openAndInitialize(file2) {
25354
25606
  }
25355
25607
  function openLocalDatabase(dir) {
25356
25608
  ensureDataDirSync(dir);
25357
- const file2 = join2(dir, DB_FILENAME);
25609
+ const file2 = join3(dir, DB_FILENAME);
25358
25610
  reapStalePartials(file2);
25359
25611
  const {
25360
25612
  db,
@@ -25590,9 +25842,9 @@ import {
25590
25842
  closeSync,
25591
25843
  existsSync as existsSync2,
25592
25844
  openSync,
25593
- readFileSync,
25594
- rmSync as rmSync3,
25595
- statSync as statSync2,
25845
+ readFileSync as readFileSync2,
25846
+ rmSync as rmSync4,
25847
+ statSync as statSync3,
25596
25848
  writeFileSync as writeFileSync2
25597
25849
  } from "fs";
25598
25850
  import { hostname as hostname3 } from "os";
@@ -25610,20 +25862,20 @@ function computeFindingKey(input) {
25610
25862
 
25611
25863
  // ../../packages/persistence/src/fingerprint.ts
25612
25864
  import { createHmac, randomBytes } from "crypto";
25613
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
25614
- import { join as join3 } from "path";
25865
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
25866
+ import { join as join4 } from "path";
25615
25867
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
25616
25868
  var EXCEPTION_KEY_FILENAME = "exception.key";
25617
25869
  var KEY_MATERIAL_BYTES = 32;
25618
25870
  function keyFilePath(dataDir2) {
25619
- return join3(dataDir2, EXCEPTION_KEY_FILENAME);
25871
+ return join4(dataDir2, EXCEPTION_KEY_FILENAME);
25620
25872
  }
25621
25873
  function parseKeyFile(raw) {
25622
- const parsed = JSON.parse(raw);
25623
- if (typeof parsed !== "object" || parsed === null) {
25874
+ const parsed2 = JSON.parse(raw);
25875
+ if (typeof parsed2 !== "object" || parsed2 === null) {
25624
25876
  throw new Error("exception key file is corrupt: not a JSON object");
25625
25877
  }
25626
- const { version: version2, material } = parsed;
25878
+ const { version: version2, material } = parsed2;
25627
25879
  if (typeof version2 !== "number" || !Number.isInteger(version2) || version2 < 1) {
25628
25880
  throw new Error("exception key file is corrupt: bad version");
25629
25881
  }
@@ -25654,7 +25906,7 @@ var FloorUnreadableError = class extends Error {
25654
25906
  }
25655
25907
  };
25656
25908
  function storedKeyVersionFloor(dataDir2) {
25657
- const file2 = join3(dataDir2, DB_FILENAME);
25909
+ const file2 = join4(dataDir2, DB_FILENAME);
25658
25910
  if (!existsSync3(file2)) return 0;
25659
25911
  let db;
25660
25912
  try {
@@ -25709,7 +25961,7 @@ function occupantMessage(file2, kind) {
25709
25961
  function readFingerprintKey(dataDir2) {
25710
25962
  let raw;
25711
25963
  try {
25712
- raw = readFileSync2(keyFilePath(dataDir2), "utf8");
25964
+ raw = readFileSync3(keyFilePath(dataDir2), "utf8");
25713
25965
  } catch (err) {
25714
25966
  if (err.code === "ENOENT") return null;
25715
25967
  throw err instanceof Error ? err : new Error(String(err));
@@ -25735,18 +25987,22 @@ function fingerprintValue(key, raw) {
25735
25987
  import { renameSync as renameSync3 } from "fs";
25736
25988
  import { mkdir } from "fs/promises";
25737
25989
  import { homedir } from "os";
25738
- import { join as join4 } from "path";
25990
+ import { join as join5 } from "path";
25739
25991
  function defaultDataDir() {
25740
- return join4(homedir(), ".aka");
25992
+ return join5(homedir(), ".aka");
25741
25993
  }
25742
25994
  function settingsDir(base = defaultDataDir()) {
25743
- return join4(base, "settings");
25995
+ return join5(base, "settings");
25744
25996
  }
25745
25997
  function dataDir(base = defaultDataDir()) {
25746
- return join4(base, "data");
25998
+ return join5(base, "data");
25747
25999
  }
25748
26000
  function dbPath(base = defaultDataDir()) {
25749
- return join4(dataDir(base), "aka.db");
26001
+ return join5(dataDir(base), "aka.db");
26002
+ }
26003
+ async function ensureDataDir(dir = defaultDataDir()) {
26004
+ await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
26005
+ tightenDir(dir);
25750
26006
  }
25751
26007
  function ensureLayoutDirSync(dir = defaultDataDir()) {
25752
26008
  ensureDataDirSync(dir);
@@ -25759,8 +26015,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
25759
26015
  for (const { name, dest } of moves) {
25760
26016
  try {
25761
26017
  ensureDataDirSync(dest);
25762
- const moved = join4(dest, name);
25763
- renameSync3(join4(base, name), moved);
26018
+ const moved = join5(dest, name);
26019
+ renameSync3(join5(base, name), moved);
25764
26020
  tightenFile(moved);
25765
26021
  } catch {
25766
26022
  }
@@ -25768,7 +26024,7 @@ function migrateLegacyLayout(base = defaultDataDir()) {
25768
26024
  }
25769
26025
 
25770
26026
  // ../../packages/persistence/src/managed-settings.ts
25771
- import { readFileSync as readFileSync3 } from "fs";
26027
+ import { readFileSync as readFileSync4 } from "fs";
25772
26028
  import { posix, win32 } from "path";
25773
26029
  function managedSettingsPaths(platform2 = process.platform) {
25774
26030
  if (platform2 === "darwin") {
@@ -25786,14 +26042,14 @@ function readManagedSettings(paths = managedSettingsPaths()) {
25786
26042
  for (const path of paths) {
25787
26043
  let text;
25788
26044
  try {
25789
- text = readFileSync3(path, "utf8");
26045
+ text = readFileSync4(path, "utf8");
25790
26046
  } catch {
25791
26047
  continue;
25792
26048
  }
25793
26049
  const record2 = parseJsonObject(text);
25794
26050
  if (!record2) continue;
25795
- const parsed = ManagedSettings.safeParse(record2);
25796
- if (parsed.success) return parsed.data;
26051
+ const parsed2 = ManagedSettings.safeParse(record2);
26052
+ if (parsed2.success) return parsed2.data;
25797
26053
  }
25798
26054
  return null;
25799
26055
  }
@@ -25833,14 +26089,14 @@ function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ n
25833
26089
  }
25834
26090
 
25835
26091
  // ../../packages/persistence/src/settings.ts
25836
- import { readFileSync as readFileSync4 } from "fs";
25837
- import { join as join5 } from "path";
26092
+ import { readFileSync as readFileSync5 } from "fs";
26093
+ import { join as join6 } from "path";
25838
26094
  var SETTINGS_FILENAME = "settings.json";
25839
26095
  function readWorkspaceSettings(base = defaultDataDir()) {
25840
26096
  return overlayManagedSettings(readUserSettings(base), readManagedSettings());
25841
26097
  }
25842
26098
  function readUserSettings(base) {
25843
- const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
26099
+ const record2 = readJson(join6(settingsDir(base), SETTINGS_FILENAME));
25844
26100
  if (!record2) return defaultWorkspaceSettings();
25845
26101
  try {
25846
26102
  return WorkspaceSettings.parse(record2);
@@ -25851,13 +26107,17 @@ function readUserSettings(base) {
25851
26107
  function readJson(file2) {
25852
26108
  let text;
25853
26109
  try {
25854
- text = readFileSync4(file2, "utf8");
26110
+ text = readFileSync5(file2, "utf8");
25855
26111
  } catch {
25856
26112
  return null;
25857
26113
  }
25858
26114
  return parseJsonObject(text) ?? null;
25859
26115
  }
25860
26116
 
26117
+ // ../../packages/persistence/src/store-symlinks.ts
26118
+ import { existsSync as existsSync4, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
26119
+ import { dirname as dirname2, join as join7, resolve } from "path";
26120
+
25861
26121
  // ../../packages/persistence/src/vault/crypto.ts
25862
26122
  import {
25863
26123
  createCipheriv,
@@ -25870,26 +26130,73 @@ import {
25870
26130
  // ../../packages/persistence/src/vault/key-provider.ts
25871
26131
  import { execFileSync } from "child_process";
25872
26132
  import { randomBytes as randomBytes2 } from "crypto";
25873
- import { chmodSync as chmodSync2, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
25874
- import { join as join6 } from "path";
26133
+ import { chmodSync as chmodSync3, readFileSync as readFileSync6, renameSync as renameSync4, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
26134
+ import { join as join8 } from "path";
25875
26135
 
25876
26136
  // ../../packages/persistence/src/vault/vault.ts
25877
26137
  import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
25878
26138
 
25879
26139
  // ../../packages/persistence/src/warn-era-cap.ts
25880
- import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
25881
- import { join as join7 } from "path";
26140
+ import { existsSync as existsSync5, writeFileSync as writeFileSync4 } from "fs";
26141
+ import { join as join9 } from "path";
25882
26142
  var MARKER = "warn-era-capped";
25883
26143
  function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
25884
26144
  if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
25885
- const marker = join7(dataDir2, MARKER);
25886
- if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
26145
+ const marker = join9(dataDir2, MARKER);
26146
+ if (existsSync5(marker)) return { capped: 0, skipped: "already-run" };
25887
26147
  const capped = db.policies.capCategoryActions();
25888
26148
  writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
25889
26149
  `, { mode: DATA_FILE_MODE });
25890
26150
  return { capped };
25891
26151
  }
25892
26152
 
26153
+ // ../../packages/plugin-runtime/src/attached/forward-drops.ts
26154
+ var FORWARD_DROPS_FILENAME = "attached-forward-drops.json";
26155
+ function forwardDropsPath(dataDir2) {
26156
+ return join10(dataDir2, FORWARD_DROPS_FILENAME);
26157
+ }
26158
+ function recordForwardDrops(dataDir2, count, nowMs) {
26159
+ if (count <= 0) return;
26160
+ try {
26161
+ ensureDataDirSync(dataDir2);
26162
+ const previous = readForwardDrops(dataDir2);
26163
+ const next = {
26164
+ droppedForwards: (previous?.droppedForwards ?? 0) + count,
26165
+ lastDropAtMs: nowMs
26166
+ };
26167
+ writeOwnerOnlyFileSync(forwardDropsPath(dataDir2), `${JSON.stringify(next)}
26168
+ `);
26169
+ } catch {
26170
+ }
26171
+ }
26172
+ function readForwardDrops(dataDir2) {
26173
+ try {
26174
+ const parsed2 = JSON.parse(readFileSync7(forwardDropsPath(dataDir2), "utf8"));
26175
+ if (typeof parsed2 !== "object" || parsed2 === null) return null;
26176
+ const record2 = parsed2;
26177
+ if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
26178
+ return null;
26179
+ }
26180
+ if (record2.droppedForwards <= 0) return null;
26181
+ if (typeof record2.lastDropAtMs !== "number" || !Number.isFinite(record2.lastDropAtMs)) {
26182
+ return null;
26183
+ }
26184
+ return { droppedForwards: record2.droppedForwards, lastDropAtMs: record2.lastDropAtMs };
26185
+ } catch {
26186
+ return null;
26187
+ }
26188
+ }
26189
+
26190
+ // ../../packages/plugin-runtime/src/attached/forward-policy.ts
26191
+ import { randomUUID as randomUUID15 } from "crypto";
26192
+ import { readFileSync as readFileSync12 } from "fs";
26193
+ import { readFile, rename, writeFile } from "fs/promises";
26194
+ import { join as join18 } from "path";
26195
+
26196
+ // ../../packages/plugin-sdk/src/config.ts
26197
+ import { existsSync as existsSync6 } from "fs";
26198
+ import { join as join11 } from "path";
26199
+
25893
26200
  // ../../packages/plugin-sdk/src/provider-env.ts
25894
26201
  var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
25895
26202
  var booleanish = external_exports.string().optional().transform((v) => {
@@ -25924,8 +26231,8 @@ function hostOf(url2) {
25924
26231
  }
25925
26232
  }
25926
26233
  function resolveProvider() {
25927
- const parsed = ProviderEnvSchema.safeParse(process.env);
25928
- const env = parsed.success ? parsed.data : ProviderEnvSchema.parse({});
26234
+ const parsed2 = ProviderEnvSchema.safeParse(process.env);
26235
+ const env = parsed2.success ? parsed2.data : ProviderEnvSchema.parse({});
25929
26236
  if (env.CLAUDE_CODE_USE_BEDROCK === true) return { provider: "bedrock" };
25930
26237
  if (env.CLAUDE_CODE_USE_VERTEX === true) return { provider: "vertex" };
25931
26238
  const baseUrl = env.ANTHROPIC_BASE_URL;
@@ -25942,8 +26249,8 @@ function resolveProvider() {
25942
26249
  function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
25943
26250
  try {
25944
26251
  ensureLayoutDirSync(base);
25945
- const settingsFile = join8(settingsDir(base), "settings.json");
25946
- if (existsSync5(settingsFile)) tightenFile(settingsFile);
26252
+ const settingsFile = join11(settingsDir(base), "settings.json");
26253
+ if (existsSync6(settingsFile)) tightenFile(settingsFile);
25947
26254
  } catch {
25948
26255
  }
25949
26256
  migrateLegacyLayout(base);
@@ -25966,9 +26273,9 @@ function resolveProviderSafe(resolveProviderFn) {
25966
26273
  }
25967
26274
 
25968
26275
  // ../../packages/plugin-sdk/src/config-inventory.ts
25969
- import { readdirSync as readdirSync2, readFileSync as readFileSync7, realpathSync, statSync as statSync5 } from "fs";
26276
+ import { readdirSync as readdirSync2, readFileSync as readFileSync9, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
25970
26277
  import { homedir as homedir2 } from "os";
25971
- import { basename as basename4, join as join10 } from "path";
26278
+ import { basename as basename4, join as join13 } from "path";
25972
26279
 
25973
26280
  // ../../packages/detections/src/egress/registry.ts
25974
26281
  var EXTRACTOR_VERSION = "1";
@@ -27827,10 +28134,10 @@ var localhost_ref_default = {
27827
28134
  severity: "low",
27828
28135
  matcher: {
27829
28136
  type: "regex",
27830
- pattern: "\\b(?:localhost|127\\.0\\.0\\.1|0\\.0\\.0\\.0|::1)\\b",
28137
+ pattern: "(?<![A-Za-z0-9_])(?:localhost|127\\.0\\.0\\.1|0\\.0\\.0\\.0|(?<![A-Za-z0-9_]\\[)::1(?!:|\\.[0-9]))(?![A-Za-z0-9_])",
27831
28138
  flags: "g"
27832
28139
  },
27833
- examples: ["localhost", "127.0.0.1"]
28140
+ examples: ["localhost", "127.0.0.1", "0.0.0.0", "::1"]
27834
28141
  };
27835
28142
 
27836
28143
  // ../../rules/core-code-context/stack-trace.json
@@ -29184,8 +29491,8 @@ function maskText(text) {
29184
29491
  }
29185
29492
 
29186
29493
  // ../../packages/plugin-sdk/src/repo.ts
29187
- import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
29188
- import { basename as basename3, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
29494
+ import { existsSync as existsSync7, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
29495
+ import { basename as basename3, dirname as dirname3, isAbsolute, join as join12, sep as sep2 } from "path";
29189
29496
  function resolveRepoIdentity(cwd) {
29190
29497
  try {
29191
29498
  const root = findGitRoot(cwd);
@@ -29235,15 +29542,15 @@ function resolveGitBranch(cwd) {
29235
29542
  try {
29236
29543
  const root = findGitRoot(cwd);
29237
29544
  if (!root) return void 0;
29238
- const dotGit = join9(root, ".git");
29545
+ const dotGit = join12(root, ".git");
29239
29546
  let gitdir;
29240
29547
  try {
29241
- gitdir = statSync4(dotGit).isDirectory() ? dotGit : resolveWorktreeGitdir(root, dotGit);
29548
+ gitdir = statSync6(dotGit).isDirectory() ? dotGit : resolveWorktreeGitdir(root, dotGit);
29242
29549
  } catch {
29243
29550
  return void 0;
29244
29551
  }
29245
29552
  if (gitdir === void 0) return void 0;
29246
- const head = safeRead(join9(gitdir, "HEAD"));
29553
+ const head = safeRead(join12(gitdir, "HEAD"));
29247
29554
  if (!head) return void 0;
29248
29555
  return /^ref:\s*refs\/heads\/(.+?)\s*$/m.exec(head)?.[1];
29249
29556
  } catch {
@@ -29253,41 +29560,41 @@ function resolveGitBranch(cwd) {
29253
29560
  function resolveWorktreeGitdir(root, dotGitFile) {
29254
29561
  const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGitFile) ?? "")?.[1];
29255
29562
  if (!target) return void 0;
29256
- return isAbsolute(target) ? target : join9(root, target);
29563
+ return isAbsolute(target) ? target : join12(root, target);
29257
29564
  }
29258
29565
  function findGitRoot(start) {
29259
29566
  let dir = start;
29260
29567
  for (; ; ) {
29261
- if (existsSync6(join9(dir, ".git"))) return dir;
29262
- const parent = dirname2(dir);
29568
+ if (existsSync7(join12(dir, ".git"))) return dir;
29569
+ const parent = dirname3(dir);
29263
29570
  if (parent === dir) return void 0;
29264
29571
  dir = parent;
29265
29572
  }
29266
29573
  }
29267
29574
  function resolveGitContext(root) {
29268
- const dotGit = join9(root, ".git");
29575
+ const dotGit = join12(root, ".git");
29269
29576
  try {
29270
- if (statSync4(dotGit).isDirectory()) {
29271
- return { configPath: join9(dotGit, "config"), headRoot: root };
29577
+ if (statSync6(dotGit).isDirectory()) {
29578
+ return { configPath: join12(dotGit, "config"), headRoot: root };
29272
29579
  }
29273
29580
  } catch {
29274
29581
  return void 0;
29275
29582
  }
29276
29583
  const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
29277
29584
  if (!target) return void 0;
29278
- const gitdir = isAbsolute(target) ? target : join9(root, target);
29279
- if (existsSync6(join9(gitdir, "config"))) {
29280
- return { configPath: join9(gitdir, "config"), headRoot: root };
29585
+ const gitdir = isAbsolute(target) ? target : join12(root, target);
29586
+ if (existsSync7(join12(gitdir, "config"))) {
29587
+ return { configPath: join12(gitdir, "config"), headRoot: root };
29281
29588
  }
29282
- const commonRaw = safeRead(join9(gitdir, "commondir"))?.trim();
29589
+ const commonRaw = safeRead(join12(gitdir, "commondir"))?.trim();
29283
29590
  if (!commonRaw) return void 0;
29284
- const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join9(gitdir, commonRaw);
29285
- const headRoot = basename3(commonGitDir) === ".git" ? dirname2(commonGitDir) : root;
29286
- return { configPath: join9(commonGitDir, "config"), headRoot };
29591
+ const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join12(gitdir, commonRaw);
29592
+ const headRoot = basename3(commonGitDir) === ".git" ? dirname3(commonGitDir) : root;
29593
+ return { configPath: join12(commonGitDir, "config"), headRoot };
29287
29594
  }
29288
29595
  function safeRead(path) {
29289
29596
  try {
29290
- return readFileSync6(path, "utf8");
29597
+ return readFileSync8(path, "utf8");
29291
29598
  } catch {
29292
29599
  return void 0;
29293
29600
  }
@@ -29349,31 +29656,31 @@ function resolveConfigInventory(input) {
29349
29656
  };
29350
29657
  try {
29351
29658
  const home = input.homeDir ?? homedir2();
29352
- const claudeDir = join10(home, ".claude");
29659
+ const claudeDir = join13(home, ".claude");
29353
29660
  const repo = resolveRepoIdentity(input.cwd);
29354
29661
  const repoIdentity = repo?.url ?? input.cwd;
29355
29662
  const projectSource = `project:${repoIdentity}`;
29356
- collectSettingsHooks(scan2, join10(claudeDir, "settings.json"), "user");
29357
- collectSettingsHooks(scan2, join10(input.cwd, ".claude", "settings.json"), "project");
29358
- collectSettingsHooks(scan2, join10(input.cwd, ".claude", "settings.local.json"), "local");
29663
+ collectSettingsHooks(scan2, join13(claudeDir, "settings.json"), "user");
29664
+ collectSettingsHooks(scan2, join13(input.cwd, ".claude", "settings.json"), "project");
29665
+ collectSettingsHooks(scan2, join13(input.cwd, ".claude", "settings.local.json"), "local");
29359
29666
  const projectOrigin = { scope: "project", project: repoIdentity };
29360
- collectMcpFile(scan2, join10(input.cwd, ".mcp.json"), projectOrigin, { recordErrors: true });
29361
- collectUserClaudeJson(scan2, join10(home, ".claude.json"), input.cwd, repoIdentity);
29362
- collectMcpFile(scan2, join10(claudeDir, "settings.json"), { scope: "user" });
29363
- collectMcpFile(scan2, join10(input.cwd, ".claude", "settings.json"), projectOrigin);
29364
- collectMcpFile(scan2, join10(input.cwd, ".claude", "settings.local.json"), {
29667
+ collectMcpFile(scan2, join13(input.cwd, ".mcp.json"), projectOrigin, { recordErrors: true });
29668
+ collectUserClaudeJson(scan2, join13(home, ".claude.json"), input.cwd, repoIdentity);
29669
+ collectMcpFile(scan2, join13(claudeDir, "settings.json"), { scope: "user" });
29670
+ collectMcpFile(scan2, join13(input.cwd, ".claude", "settings.json"), projectOrigin);
29671
+ collectMcpFile(scan2, join13(input.cwd, ".claude", "settings.local.json"), {
29365
29672
  scope: "local",
29366
29673
  project: repoIdentity
29367
29674
  });
29368
29675
  collectConfigFiles(scan2, claudeDir, input.cwd);
29369
- collectSkillsDir(scan2, join10(claudeDir, "skills"), { source: "local", scope: "user" });
29370
- collectSkillsDir(scan2, join10(input.cwd, ".claude", "skills"), {
29676
+ collectSkillsDir(scan2, join13(claudeDir, "skills"), { source: "local", scope: "user" });
29677
+ collectSkillsDir(scan2, join13(input.cwd, ".claude", "skills"), {
29371
29678
  source: projectSource,
29372
29679
  scope: "project"
29373
29680
  });
29374
29681
  collectInstalledPlugins(scan2, claudeDir);
29375
29682
  collectMarketplaceSkills(scan2, claudeDir);
29376
- collectSkillsDir(scan2, join10(input.cwd, "skills"), { source: projectSource, scope: "project" });
29683
+ collectSkillsDir(scan2, join13(input.cwd, "skills"), { source: projectSource, scope: "project" });
29377
29684
  scan2.skills = dedupeSkills(scan2.skills);
29378
29685
  scan2.mcpServers = dedupeMcpServers(scan2.mcpServers);
29379
29686
  } catch (err) {
@@ -29408,9 +29715,9 @@ function collectSettingsHooks(scan2, path, scope) {
29408
29715
  const raw = readOptional(path);
29409
29716
  if (raw === void 0) return;
29410
29717
  try {
29411
- const parsed = JSON.parse(raw);
29412
- if (typeof parsed !== "object" || parsed === null) return;
29413
- collectHooksObject(scan2, parsed.hooks, path, scope);
29718
+ const parsed2 = JSON.parse(raw);
29719
+ if (typeof parsed2 !== "object" || parsed2 === null) return;
29720
+ collectHooksObject(scan2, parsed2.hooks, path, scope);
29414
29721
  } catch (err) {
29415
29722
  scan2.errors.push({ source: path, reason: parseErrorReason(err) });
29416
29723
  }
@@ -29456,9 +29763,9 @@ function collectMcpFile(scan2, path, origin, opts) {
29456
29763
  const raw = readOptional(path);
29457
29764
  if (raw === void 0) return;
29458
29765
  try {
29459
- const parsed = JSON.parse(raw);
29460
- if (typeof parsed !== "object" || parsed === null) return;
29461
- collectMcpObject(scan2, parsed.mcpServers, path, origin);
29766
+ const parsed2 = JSON.parse(raw);
29767
+ if (typeof parsed2 !== "object" || parsed2 === null) return;
29768
+ collectMcpObject(scan2, parsed2.mcpServers, path, origin);
29462
29769
  } catch (err) {
29463
29770
  if (opts?.recordErrors ?? false) {
29464
29771
  scan2.errors.push({ source: path, reason: parseErrorReason(err) });
@@ -29469,9 +29776,9 @@ function collectUserClaudeJson(scan2, path, cwd, repoIdentity) {
29469
29776
  const raw = readOptional(path);
29470
29777
  if (raw === void 0) return;
29471
29778
  try {
29472
- const parsed = JSON.parse(raw);
29473
- if (typeof parsed !== "object" || parsed === null) return;
29474
- const rec = parsed;
29779
+ const parsed2 = JSON.parse(raw);
29780
+ if (typeof parsed2 !== "object" || parsed2 === null) return;
29781
+ const rec = parsed2;
29475
29782
  collectMcpObject(scan2, rec.mcpServers, path, { scope: "user" });
29476
29783
  const projects = rec.projects;
29477
29784
  if (typeof projects === "object" && projects !== null) {
@@ -29492,7 +29799,7 @@ function projectEntryFor(projects, cwd) {
29492
29799
  const trimmed = cwd.replace(/\/+$/, "");
29493
29800
  if (trimmed.length > 0) candidates.add(trimmed);
29494
29801
  try {
29495
- candidates.add(realpathSync(cwd));
29802
+ candidates.add(realpathSync2(cwd));
29496
29803
  } catch {
29497
29804
  }
29498
29805
  for (const key of candidates) {
@@ -29502,15 +29809,15 @@ function projectEntryFor(projects, cwd) {
29502
29809
  return void 0;
29503
29810
  }
29504
29811
  function collectPluginManifestMcp(scan2, installPath, origin) {
29505
- const manifestPath = join10(installPath, ".claude-plugin", "plugin.json");
29812
+ const manifestPath = join13(installPath, ".claude-plugin", "plugin.json");
29506
29813
  const raw = readOptional(manifestPath);
29507
29814
  if (raw === void 0) return;
29508
29815
  try {
29509
- const parsed = JSON.parse(raw);
29510
- if (typeof parsed !== "object" || parsed === null) return;
29511
- const declared = parsed.mcpServers;
29816
+ const parsed2 = JSON.parse(raw);
29817
+ if (typeof parsed2 !== "object" || parsed2 === null) return;
29818
+ const declared = parsed2.mcpServers;
29512
29819
  if (typeof declared === "string" && declared.length > 0) {
29513
- collectMcpFile(scan2, join10(installPath, declared), origin, { recordErrors: true });
29820
+ collectMcpFile(scan2, join13(installPath, declared), origin, { recordErrors: true });
29514
29821
  } else {
29515
29822
  collectMcpObject(scan2, declared, manifestPath, origin);
29516
29823
  }
@@ -29527,18 +29834,18 @@ var SETTINGS_KEY_LABELS = [
29527
29834
  ["statusLine", "status line"]
29528
29835
  ];
29529
29836
  function collectConfigFiles(scan2, claudeDir, cwd) {
29530
- settingsConfigFile(scan2, join10(claudeDir, "settings.json"), "user", "User settings");
29531
- settingsConfigFile(scan2, join10(cwd, ".claude", "settings.json"), "project", "Project settings");
29532
- settingsConfigFile(scan2, join10(cwd, ".claude", "settings.local.json"), "local", "Local overrides");
29533
- memoryConfigFile(scan2, join10(claudeDir, "CLAUDE.md"), "user", "User memory");
29534
- memoryConfigFile(scan2, join10(cwd, "CLAUDE.md"), "project", "Project memory");
29535
- mcpJsonConfigFile(scan2, join10(cwd, ".mcp.json"));
29536
- dirConfigFile(scan2, join10(cwd, ".claude", "commands"), "Slash commands", "command");
29537
- dirConfigFile(scan2, join10(cwd, ".claude", "agents"), "Subagents", "subagent");
29837
+ settingsConfigFile(scan2, join13(claudeDir, "settings.json"), "user", "User settings");
29838
+ settingsConfigFile(scan2, join13(cwd, ".claude", "settings.json"), "project", "Project settings");
29839
+ settingsConfigFile(scan2, join13(cwd, ".claude", "settings.local.json"), "local", "Local overrides");
29840
+ memoryConfigFile(scan2, join13(claudeDir, "CLAUDE.md"), "user", "User memory");
29841
+ memoryConfigFile(scan2, join13(cwd, "CLAUDE.md"), "project", "Project memory");
29842
+ mcpJsonConfigFile(scan2, join13(cwd, ".mcp.json"));
29843
+ dirConfigFile(scan2, join13(cwd, ".claude", "commands"), "Slash commands", "command");
29844
+ dirConfigFile(scan2, join13(cwd, ".claude", "agents"), "Subagents", "subagent");
29538
29845
  }
29539
29846
  function configFileEntry(path, scope, kind) {
29540
29847
  try {
29541
- const stat = statSync5(path);
29848
+ const stat = statSync7(path);
29542
29849
  return { name: basename4(path), path, scope, kind, updatedAt: stat.mtime.toISOString() };
29543
29850
  } catch {
29544
29851
  return void 0;
@@ -29550,9 +29857,9 @@ function settingsConfigFile(scan2, path, scope, kind) {
29550
29857
  const raw = readOptional(path);
29551
29858
  if (raw !== void 0) {
29552
29859
  try {
29553
- const parsed = JSON.parse(raw);
29554
- if (typeof parsed === "object" && parsed !== null) {
29555
- const labels = SETTINGS_KEY_LABELS.filter(([key]) => key in parsed).map(
29860
+ const parsed2 = JSON.parse(raw);
29861
+ if (typeof parsed2 === "object" && parsed2 !== null) {
29862
+ const labels = SETTINGS_KEY_LABELS.filter(([key]) => key in parsed2).map(
29556
29863
  ([, label]) => label
29557
29864
  );
29558
29865
  if (labels.length > 0) entry.detail = labels.join(", ");
@@ -29579,8 +29886,8 @@ function mcpJsonConfigFile(scan2, path) {
29579
29886
  const raw = readOptional(path);
29580
29887
  if (raw !== void 0) {
29581
29888
  try {
29582
- const parsed = JSON.parse(raw);
29583
- const servers = parsed?.mcpServers;
29889
+ const parsed2 = JSON.parse(raw);
29890
+ const servers = parsed2?.mcpServers;
29584
29891
  if (typeof servers === "object" && servers !== null) {
29585
29892
  const count = Object.values(servers).filter(
29586
29893
  (v) => typeof v === "object" && v !== null && (str2(v.command) !== void 0 || str2(v.url) !== void 0)
@@ -29609,7 +29916,7 @@ function countMarkdownFiles(dir, depth) {
29609
29916
  let count = 0;
29610
29917
  for (const dirent of readdirSync2(dir, { withFileTypes: true })) {
29611
29918
  if (dirent.name.startsWith(".")) continue;
29612
- if (dirent.isDirectory()) count += countMarkdownFiles(join10(dir, dirent.name), depth + 1);
29919
+ if (dirent.isDirectory()) count += countMarkdownFiles(join13(dir, dirent.name), depth + 1);
29613
29920
  else if (dirent.name.endsWith(".md")) count += 1;
29614
29921
  }
29615
29922
  return count;
@@ -29622,7 +29929,7 @@ function collectSkillsDir(scan2, dir, origin) {
29622
29929
  return;
29623
29930
  }
29624
29931
  for (const name of names) {
29625
- const skillFile = join10(dir, name, "SKILL.md");
29932
+ const skillFile = join13(dir, name, "SKILL.md");
29626
29933
  try {
29627
29934
  const raw = readOptional(skillFile);
29628
29935
  if (raw === void 0) continue;
@@ -29631,8 +29938,8 @@ function collectSkillsDir(scan2, dir, origin) {
29631
29938
  name: front.name ?? name,
29632
29939
  source: origin.source,
29633
29940
  scope: origin.scope,
29634
- location: join10(dir, name),
29635
- updatedAt: statSync5(skillFile).mtime.toISOString()
29941
+ location: join13(dir, name),
29942
+ updatedAt: statSync7(skillFile).mtime.toISOString()
29636
29943
  };
29637
29944
  const version2 = front.version ?? origin.defaultVersion;
29638
29945
  if (version2 !== void 0) entry.version = version2;
@@ -29662,13 +29969,13 @@ function parseFrontmatter(raw) {
29662
29969
  return out;
29663
29970
  }
29664
29971
  function collectInstalledPlugins(scan2, claudeDir) {
29665
- const manifestPath = join10(claudeDir, "plugins", "installed_plugins.json");
29972
+ const manifestPath = join13(claudeDir, "plugins", "installed_plugins.json");
29666
29973
  const raw = readOptional(manifestPath);
29667
29974
  if (raw === void 0) return;
29668
29975
  let plugins;
29669
29976
  try {
29670
- const parsed = JSON.parse(raw);
29671
- const p = parsed?.plugins;
29977
+ const parsed2 = JSON.parse(raw);
29978
+ const p = parsed2?.plugins;
29672
29979
  if (typeof p !== "object" || p === null) return;
29673
29980
  plugins = p;
29674
29981
  } catch (err) {
@@ -29687,15 +29994,15 @@ function collectInstalledPlugins(scan2, claudeDir) {
29687
29994
  if (typeof installPath !== "string" || seen.has(installPath)) continue;
29688
29995
  seen.add(installPath);
29689
29996
  const version2 = install.version;
29690
- const hooksPath = join10(installPath, "hooks", "hooks.json");
29997
+ const hooksPath = join13(installPath, "hooks", "hooks.json");
29691
29998
  const hooksRaw = readOptional(hooksPath);
29692
29999
  if (hooksRaw !== void 0) {
29693
30000
  try {
29694
- const parsed = JSON.parse(hooksRaw);
29695
- if (typeof parsed === "object" && parsed !== null) {
30001
+ const parsed2 = JSON.parse(hooksRaw);
30002
+ if (typeof parsed2 === "object" && parsed2 !== null) {
29696
30003
  collectHooksObject(
29697
30004
  scan2,
29698
- parsed.hooks,
30005
+ parsed2.hooks,
29699
30006
  hooksPath,
29700
30007
  "plugin",
29701
30008
  pluginName
@@ -29707,22 +30014,22 @@ function collectInstalledPlugins(scan2, claudeDir) {
29707
30014
  }
29708
30015
  const origin = { source: marketplace, scope: "plugin", pluginName };
29709
30016
  if (typeof version2 === "string") origin.defaultVersion = version2;
29710
- collectSkillsDir(scan2, join10(installPath, "skills"), origin);
30017
+ collectSkillsDir(scan2, join13(installPath, "skills"), origin);
29711
30018
  const mcpOrigin = { scope: "plugin", pluginName, marketplace };
29712
- collectMcpFile(scan2, join10(installPath, ".mcp.json"), mcpOrigin, { recordErrors: true });
30019
+ collectMcpFile(scan2, join13(installPath, ".mcp.json"), mcpOrigin, { recordErrors: true });
29713
30020
  collectPluginManifestMcp(scan2, installPath, mcpOrigin);
29714
30021
  }
29715
30022
  }
29716
30023
  }
29717
30024
  function collectMarketplaceSkills(scan2, claudeDir) {
29718
- for (const mp of readMarketplaces(join10(claudeDir, "plugins", "known_marketplaces.json"))) {
30025
+ for (const mp of readMarketplaces(join13(claudeDir, "plugins", "known_marketplaces.json"))) {
29719
30026
  if (isClaudeOfficialMarketplace(mp.name, mp.repo)) continue;
29720
- collectSkillsDir(scan2, join10(mp.installLocation, "skills"), {
30027
+ collectSkillsDir(scan2, join13(mp.installLocation, "skills"), {
29721
30028
  source: mp.name,
29722
30029
  scope: "plugin"
29723
30030
  });
29724
- collectPluginSkillDirs(scan2, join10(mp.installLocation, "plugins"), mp.name);
29725
- collectPluginSkillDirs(scan2, join10(mp.installLocation, "external_plugins"), mp.name);
30031
+ collectPluginSkillDirs(scan2, join13(mp.installLocation, "plugins"), mp.name);
30032
+ collectPluginSkillDirs(scan2, join13(mp.installLocation, "external_plugins"), mp.name);
29726
30033
  }
29727
30034
  }
29728
30035
  function collectPluginSkillDirs(scan2, pluginsDir, marketplace) {
@@ -29733,7 +30040,7 @@ function collectPluginSkillDirs(scan2, pluginsDir, marketplace) {
29733
30040
  return;
29734
30041
  }
29735
30042
  for (const plugin of plugins) {
29736
- collectSkillsDir(scan2, join10(pluginsDir, plugin, "skills"), {
30043
+ collectSkillsDir(scan2, join13(pluginsDir, plugin, "skills"), {
29737
30044
  source: marketplace,
29738
30045
  scope: "plugin",
29739
30046
  pluginName: plugin
@@ -29744,10 +30051,10 @@ function readMarketplaces(manifestPath) {
29744
30051
  const raw = readOptional(manifestPath);
29745
30052
  if (raw === void 0) return [];
29746
30053
  try {
29747
- const parsed = JSON.parse(raw);
29748
- if (typeof parsed !== "object" || parsed === null) return [];
30054
+ const parsed2 = JSON.parse(raw);
30055
+ if (typeof parsed2 !== "object" || parsed2 === null) return [];
29749
30056
  const out = [];
29750
- for (const [name, entry] of Object.entries(parsed)) {
30057
+ for (const [name, entry] of Object.entries(parsed2)) {
29751
30058
  const rec = entry;
29752
30059
  const loc = rec?.installLocation;
29753
30060
  if (typeof loc !== "string" || loc.length === 0) continue;
@@ -29787,7 +30094,7 @@ function dedupeMcpServers(servers) {
29787
30094
  }
29788
30095
  function readOptional(path) {
29789
30096
  try {
29790
- return readFileSync7(path, "utf8");
30097
+ return readFileSync9(path, "utf8");
29791
30098
  } catch {
29792
30099
  return void 0;
29793
30100
  }
@@ -29835,7 +30142,7 @@ function buildIngestEvent(input) {
29835
30142
  }
29836
30143
 
29837
30144
  // ../../packages/plugin-sdk/src/isolated-scan.ts
29838
- import { existsSync as existsSync7 } from "fs";
30145
+ import { existsSync as existsSync8 } from "fs";
29839
30146
  import { fileURLToPath } from "url";
29840
30147
  import { Worker } from "worker_threads";
29841
30148
  var ISOLATED_SCAN_BUDGET_MS = 2e3;
@@ -29849,7 +30156,7 @@ function resolveWorkerUrl() {
29849
30156
  for (const name of ["scan-worker.js", "scan-worker.ts"]) {
29850
30157
  const candidate = new URL(name, import.meta.url);
29851
30158
  try {
29852
- if (existsSync7(fileURLToPath(candidate))) {
30159
+ if (existsSync8(fileURLToPath(candidate))) {
29853
30160
  resolvedWorkerUrl = candidate;
29854
30161
  return candidate;
29855
30162
  }
@@ -30034,8 +30341,8 @@ function createIsolatedScanner(data, opts = {}) {
30034
30341
  }
30035
30342
  function enqueue(spec) {
30036
30343
  const next = chain.then(
30037
- () => new Promise((resolve) => {
30038
- spec(resolve);
30344
+ () => new Promise((resolve2) => {
30345
+ spec(resolve2);
30039
30346
  })
30040
30347
  );
30041
30348
  chain = next.then(
@@ -30046,7 +30353,7 @@ function createIsolatedScanner(data, opts = {}) {
30046
30353
  }
30047
30354
  return {
30048
30355
  scan(text, context, scanOpts) {
30049
- return enqueue((resolve) => {
30356
+ return enqueue((resolve2) => {
30050
30357
  runOne(
30051
30358
  {
30052
30359
  budgetMs,
@@ -30059,23 +30366,23 @@ function createIsolatedScanner(data, opts = {}) {
30059
30366
  }),
30060
30367
  reply: (message2) => {
30061
30368
  if (message2.kind !== "result") return false;
30062
- resolve({ status: "ok", findings: message2.findings });
30369
+ resolve2({ status: "ok", findings: message2.findings });
30063
30370
  return true;
30064
30371
  }
30065
30372
  },
30066
- resolve
30373
+ resolve2
30067
30374
  );
30068
30375
  });
30069
30376
  },
30070
30377
  probe(rule) {
30071
- return enqueue((resolve) => {
30378
+ return enqueue((resolve2) => {
30072
30379
  runOne(
30073
30380
  {
30074
30381
  budgetMs: probeBudgetMs,
30075
30382
  build: (id) => ({ kind: "probe", id, rule }),
30076
30383
  reply: (message2) => {
30077
30384
  if (message2.kind !== "probed") return false;
30078
- resolve({
30385
+ resolve2({
30079
30386
  status: "ok",
30080
30387
  verdict: message2.verdict,
30081
30388
  worstMs: message2.worstMs,
@@ -30084,7 +30391,7 @@ function createIsolatedScanner(data, opts = {}) {
30084
30391
  return true;
30085
30392
  }
30086
30393
  },
30087
- resolve
30394
+ resolve2
30088
30395
  );
30089
30396
  });
30090
30397
  },
@@ -30314,11 +30621,11 @@ function createGuardedScanner(partition, gateway, opts) {
30314
30621
 
30315
30622
  // ../../packages/plugin-sdk/src/ignore-layers.ts
30316
30623
  var import_ignore = __toESM(require_ignore(), 1);
30317
- import { readFileSync as readFileSync8 } from "fs";
30318
- import { join as join11 } from "path";
30624
+ import { readFileSync as readFileSync10 } from "fs";
30625
+ import { join as join14 } from "path";
30319
30626
  function readIgnoreLayer(dir, filename, anchorLen) {
30320
30627
  try {
30321
- return { matcher: (0, import_ignore.default)().add(readFileSync8(join11(dir, filename), "utf8")), anchorLen };
30628
+ return { matcher: (0, import_ignore.default)().add(readFileSync10(join14(dir, filename), "utf8")), anchorLen };
30322
30629
  } catch {
30323
30630
  return void 0;
30324
30631
  }
@@ -30375,17 +30682,17 @@ function resolveInventoryContext(input) {
30375
30682
  }
30376
30683
 
30377
30684
  // ../../packages/plugin-sdk/src/nudge.ts
30378
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
30379
- import { join as join12 } from "path";
30685
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
30686
+ import { join as join15 } from "path";
30380
30687
  var SESSION_START_MARKER = "session-start-last";
30381
30688
  function claimSessionStart(dataDir2, sessionId) {
30382
30689
  return claimOncePerSession(dataDir2, SESSION_START_MARKER, sessionId);
30383
30690
  }
30384
30691
  function claimOncePerSession(dataDir2, marker, sessionId) {
30385
30692
  if (!sessionId) return true;
30386
- const path = join12(dataDir2, marker);
30693
+ const path = join15(dataDir2, marker);
30387
30694
  try {
30388
- if (readFileSync9(path, "utf8") === sessionId) return false;
30695
+ if (readFileSync11(path, "utf8") === sessionId) return false;
30389
30696
  } catch {
30390
30697
  }
30391
30698
  try {
@@ -30397,12 +30704,12 @@ function claimOncePerSession(dataDir2, marker, sessionId) {
30397
30704
  }
30398
30705
 
30399
30706
  // ../../packages/plugin-sdk/src/paths.ts
30400
- import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
30401
- import { basename as basename5, dirname as dirname3, sep as sep3 } from "path";
30707
+ import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
30708
+ import { basename as basename5, dirname as dirname4, sep as sep3 } from "path";
30402
30709
 
30403
30710
  // ../../packages/plugin-sdk/src/project-files.ts
30404
- import { existsSync as existsSync8, readdirSync as readdirSync4 } from "fs";
30405
- import { basename as basename6, join as join13 } from "path";
30711
+ import { existsSync as existsSync9, readdirSync as readdirSync4 } from "fs";
30712
+ import { basename as basename6, join as join16 } from "path";
30406
30713
  var SKIP_DIRS = /* @__PURE__ */ new Set([
30407
30714
  ".git",
30408
30715
  "node_modules",
@@ -30499,8 +30806,8 @@ function resolveProjectFiles(cwd, opts = {}) {
30499
30806
  }
30500
30807
  if (entry.isDirectory()) {
30501
30808
  if (SKIP_DIRS.has(entry.name) || isIgnored(dirLayers, dirRel, entry.name, true)) continue;
30502
- const fullPath = join13(dir, entry.name);
30503
- if (existsSync8(join13(fullPath, ".git"))) continue;
30809
+ const fullPath = join16(dir, entry.name);
30810
+ if (existsSync9(join16(fullPath, ".git"))) continue;
30504
30811
  if (depth >= bounds.maxDepth) {
30505
30812
  walk.omitted = true;
30506
30813
  continue;
@@ -30908,60 +31215,1221 @@ function createPluginRuntime(gateway, settings, opts) {
30908
31215
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
30909
31216
 
30910
31217
  // ../../packages/plugin-sdk/src/throttle.ts
30911
- import { mkdirSync as mkdirSync3, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
30912
- import { join as join14 } from "path";
30913
-
30914
- // ../../packages/plugin-runtime/src/standalone-gateway.ts
30915
- import { randomUUID as randomUUID15 } from "crypto";
31218
+ import { mkdirSync as mkdirSync3, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
31219
+ import { join as join17 } from "path";
31220
+ function throttled(dataDir2, markerName, windowMs) {
31221
+ const marker = join17(dataDir2, markerName);
31222
+ try {
31223
+ if (Date.now() - statSync8(marker).mtimeMs < windowMs) return true;
31224
+ } catch {
31225
+ }
31226
+ try {
31227
+ mkdirSync3(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
31228
+ writeFileSync6(marker, String(Date.now()), { mode: DATA_FILE_MODE });
31229
+ } catch {
31230
+ }
31231
+ return false;
31232
+ }
30916
31233
 
30917
- // ../../packages/plugin-runtime/src/recorder.ts
30918
- var PLUGIN_RECORDER_BINARY = "plugin";
30919
- function pluginRecordedBy(version2) {
30920
- return `${PLUGIN_RECORDER_BINARY}@${version2}`;
31234
+ // ../../packages/plugin-runtime/src/attached/with-timeout.ts
31235
+ var REQUEST_TIMEOUT_MS = 2e3;
31236
+ function withTimeout(promise2, ms) {
31237
+ let timer;
31238
+ const timeout = new Promise((_, reject) => {
31239
+ timer = setTimeout(() => {
31240
+ reject(new Error("attached gateway request timed out"));
31241
+ }, ms);
31242
+ });
31243
+ promise2.catch(() => void 0);
31244
+ return Promise.race([promise2, timeout]).finally(() => {
31245
+ clearTimeout(timer);
31246
+ });
30921
31247
  }
30922
31248
 
30923
- // ../../packages/plugin-runtime/src/standalone-gateway.ts
30924
- var StandaloneDataGateway = class {
30925
- db;
30926
- // Kept for the fingerprint key lookup (exception.key lives beside the store).
30927
- dataDir;
30928
- // One notice per gateway — see warnRulesetDiscarded.
30929
- warnedRulesetDiscarded = false;
30930
- constructor(dataDir2, detections = [], meta3) {
30931
- this.db = openLocalDatabase(dataDir2);
30932
- this.dataDir = dataDir2;
30933
- this.db.installedPacks.recordInventory(detections, meta3);
30934
- }
30935
- recordCapture(record2) {
30936
- this.db.recordCapture(record2.event, record2.findings);
30937
- return Promise.resolve();
31249
+ // ../../packages/plugin-runtime/src/attached/forward-policy.ts
31250
+ function isInvalidRequest(err) {
31251
+ return typeof err === "object" && err !== null && err.name === "RemoteRequestInvalid";
31252
+ }
31253
+ var FORWARD_BUDGET_MS = 1500;
31254
+ var DECISION_PATH_BUDGET_MS = 800;
31255
+ var BREAKER_FAILURE_THRESHOLD = 3;
31256
+ var BREAKER_COOLDOWN_MS = 3e4;
31257
+ var CLOSED = { consecutiveFailures: 0, openedAtMs: null, lastFailure: null };
31258
+ var FAILURES = /* @__PURE__ */ new Set([
31259
+ "unauthorized",
31260
+ "forbidden",
31261
+ "unreachable"
31262
+ ]);
31263
+ var FORWARD_STATE_FILENAME = "attached-state.json";
31264
+ var STATE_FILENAME = FORWARD_STATE_FILENAME;
31265
+ function parseBreakerState(raw, nowMs) {
31266
+ try {
31267
+ const parsed2 = JSON.parse(raw);
31268
+ if (typeof parsed2 !== "object" || parsed2 === null) return null;
31269
+ const record2 = parsed2;
31270
+ const failures = typeof record2.consecutiveFailures === "number" && record2.consecutiveFailures >= 0 ? record2.consecutiveFailures : 0;
31271
+ const openedAtMs = typeof record2.openedAtMs === "number" && Number.isFinite(record2.openedAtMs) && record2.openedAtMs <= nowMs ? record2.openedAtMs : null;
31272
+ const lastFailure = typeof record2.lastFailure === "string" && FAILURES.has(record2.lastFailure) ? record2.lastFailure : null;
31273
+ return { consecutiveFailures: failures, openedAtMs, lastFailure };
31274
+ } catch {
31275
+ return null;
30938
31276
  }
30939
- ensureInventory(ctx) {
30940
- return Promise.resolve(this.db.ensureInventory(ctx));
31277
+ }
31278
+ function createForwardPolicy(deps) {
31279
+ const now = deps.now ?? (() => Date.now());
31280
+ const file2 = join18(deps.dir, STATE_FILENAME);
31281
+ let state = null;
31282
+ let loading = null;
31283
+ async function readState() {
31284
+ let raw;
31285
+ try {
31286
+ raw = await readFile(file2, "utf8");
31287
+ } catch {
31288
+ return { ...CLOSED };
31289
+ }
31290
+ return parseBreakerState(raw, now()) ?? { ...CLOSED };
30941
31291
  }
30942
- recordAuditEvent(event) {
30943
- this.db.auditEvents.insertAuditEvent(event);
30944
- return Promise.resolve();
31292
+ async function load() {
31293
+ if (state !== null) return state;
31294
+ loading ??= readState().then((loaded) => {
31295
+ state = loaded;
31296
+ loading = null;
31297
+ return loaded;
31298
+ });
31299
+ return loading;
30945
31300
  }
30946
- // The id is minted inside the repository from the natural key — the plugin can't
30947
- // import @akasecurity/persistence to compute it, so the gateway is the boundary that
30948
- // hands the natural key across. UPSERT-take-MAX → idempotent re-reads that also
30949
- // converge a streaming partial/final split (see insertLlmCall).
30950
- recordLlmCall(input) {
30951
- this.db.auditEvents.insertLlmCall(input);
30952
- return Promise.resolve();
31301
+ async function persist(next) {
31302
+ state = next;
31303
+ try {
31304
+ await ensureDataDir(deps.dir);
31305
+ const tmp = `${file2}.${randomUUID15()}.tmp`;
31306
+ await writeFile(tmp, JSON.stringify(next), { encoding: "utf8", mode: DATA_FILE_MODE });
31307
+ await rename(tmp, file2);
31308
+ } catch {
31309
+ }
30953
31310
  }
30954
- // One reconcile pass = one transaction. All leaves commit together
30955
- // (single lock + WAL fsync); a contended SQLITE_BUSY rolls back and rejects so the
30956
- // reconciler drops the whole pass and recovers it idempotently on the next read.
31311
+ return {
31312
+ async run(op, opts) {
31313
+ const budget = opts?.decisionPath === true ? DECISION_PATH_BUDGET_MS : FORWARD_BUDGET_MS;
31314
+ let current;
31315
+ try {
31316
+ current = await load();
31317
+ } catch {
31318
+ current = { ...CLOSED };
31319
+ }
31320
+ const at = now();
31321
+ if (current.openedAtMs !== null) {
31322
+ if (at - current.openedAtMs < BREAKER_COOLDOWN_MS) {
31323
+ return { ok: false, reason: "breaker-open" };
31324
+ }
31325
+ await persist({
31326
+ consecutiveFailures: current.consecutiveFailures,
31327
+ openedAtMs: at,
31328
+ lastFailure: current.lastFailure
31329
+ });
31330
+ }
31331
+ try {
31332
+ const value = await withTimeout(op(), budget);
31333
+ if (current.openedAtMs !== null || current.consecutiveFailures > 0) {
31334
+ await persist({ ...CLOSED });
31335
+ }
31336
+ return { ok: true, value };
31337
+ } catch (err) {
31338
+ if (isInvalidRequest(err)) return { ok: false, reason: "invalid-request" };
31339
+ const reason = classifyFailure(err);
31340
+ const failures = current.consecutiveFailures + 1;
31341
+ const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
31342
+ await persist({
31343
+ consecutiveFailures: failures,
31344
+ openedAtMs: shouldOpen ? now() : null,
31345
+ lastFailure: reason
31346
+ });
31347
+ return { ok: false, reason };
31348
+ }
31349
+ }
31350
+ };
31351
+ }
31352
+
31353
+ // ../../packages/plugin-runtime/src/attached/gateway.ts
31354
+ var ACTION_STRENGTH = {
31355
+ allow: 0,
31356
+ log: 1,
31357
+ warn: 2,
31358
+ redact: 3,
31359
+ block: 4
31360
+ };
31361
+ function ruleCategoryMap(wireRules, localRules) {
31362
+ const map2 = /* @__PURE__ */ new Map();
31363
+ for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
31364
+ for (const rule of localRules ?? []) map2.set(rule.id, rule.category);
31365
+ for (const pack of bundledDetections()) {
31366
+ for (const rule of pack.rules) map2.set(rule.id, rule.category);
31367
+ }
31368
+ return map2;
31369
+ }
31370
+ function strongerOf(a, b) {
31371
+ if (a === null) return b;
31372
+ if (b === null) return a;
31373
+ return ACTION_STRENGTH[a] >= ACTION_STRENGTH[b] ? a : b;
31374
+ }
31375
+ function policyKey(policy) {
31376
+ return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
31377
+ }
31378
+ function floorFor(policy, categoryByRuleId) {
31379
+ const category = "category" in policy.target ? policy.target.category : categoryByRuleId.get(policy.target.ruleId);
31380
+ return category === void 0 ? null : DEFAULT_ACTIONS[category];
31381
+ }
31382
+ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
31383
+ const merged = /* @__PURE__ */ new Map();
31384
+ const disabled = [];
31385
+ const remoteCategoryAction = /* @__PURE__ */ new Map();
31386
+ for (const policy of remotePolicies) {
31387
+ if (!policy.enabled) continue;
31388
+ if (!("category" in policy.target)) continue;
31389
+ if (remoteCategoryAction.has(policy.target.category)) continue;
31390
+ const floor = floorFor(policy, categoryByRuleId);
31391
+ remoteCategoryAction.set(
31392
+ policy.target.category,
31393
+ floor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[floor] ? floor : policy.action
31394
+ );
31395
+ }
31396
+ for (const policy of localPolicies) {
31397
+ if (!policy.enabled) {
31398
+ disabled.push(policy);
31399
+ continue;
31400
+ }
31401
+ const key = policyKey(policy);
31402
+ if (merged.has(key)) continue;
31403
+ let remoteFloor = null;
31404
+ if ("ruleId" in policy.target) {
31405
+ const category = categoryByRuleId.get(policy.target.ruleId);
31406
+ if (category !== void 0) remoteFloor = remoteCategoryAction.get(category) ?? null;
31407
+ }
31408
+ merged.set(
31409
+ key,
31410
+ remoteFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[remoteFloor] ? { ...policy, action: remoteFloor } : policy
31411
+ );
31412
+ }
31413
+ const localCategoryAction = /* @__PURE__ */ new Map();
31414
+ for (const policy of merged.values()) {
31415
+ if ("category" in policy.target) localCategoryAction.set(policy.target.category, policy.action);
31416
+ }
31417
+ for (const policy of remotePolicies) {
31418
+ if (!policy.enabled) {
31419
+ disabled.push(policy);
31420
+ continue;
31421
+ }
31422
+ const key = policyKey(policy);
31423
+ const floor = floorFor(policy, categoryByRuleId);
31424
+ let localFloor = null;
31425
+ if ("ruleId" in policy.target) {
31426
+ const category = categoryByRuleId.get(policy.target.ruleId);
31427
+ if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
31428
+ }
31429
+ const effectiveFloor = strongerOf(floor, localFloor);
31430
+ const clamped = effectiveFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[effectiveFloor] ? { ...policy, action: effectiveFloor } : policy;
31431
+ const existing = merged.get(key);
31432
+ if (existing === void 0) {
31433
+ merged.set(key, clamped);
31434
+ continue;
31435
+ }
31436
+ if (ACTION_STRENGTH[clamped.action] > ACTION_STRENGTH[existing.action]) {
31437
+ merged.set(key, clamped);
31438
+ }
31439
+ }
31440
+ return [...merged.values(), ...disabled];
31441
+ }
31442
+ var AttachedDataGateway = class {
31443
+ constructor(deps) {
31444
+ this.deps = deps;
31445
+ }
31446
+ deps;
31447
+ /**
31448
+ * The control plane's OWN resolution of this session's inventory, captured by
31449
+ * ensureInventory. Null until the first successful forward — and it stays
31450
+ * null for the whole session when the control plane is unreachable, which is fine:
31451
+ * reKeyForForward then leaves the event's ids alone and the control plane resolves
31452
+ * what it can from the descriptors it already has.
31453
+ */
31454
+ remoteInventory = null;
31455
+ // ---------------------------------------------------------------------
31456
+ // Writes: local first, then forward.
31457
+ // ---------------------------------------------------------------------
31458
+ async recordCapture(record2) {
31459
+ await this.deps.local.recordCapture(record2);
31460
+ await this.deps.forward.run(
31461
+ () => this.deps.client.ingestEvents({
31462
+ events: [record2.event],
31463
+ ...record2.dedupe ? { dedupe: record2.dedupe } : {}
31464
+ }),
31465
+ { decisionPath: true }
31466
+ );
31467
+ }
31468
+ async ensureInventory(ctx) {
31469
+ const resolved = await this.deps.local.ensureInventory(ctx);
31470
+ const remote = await this.deps.forward.run(() => this.deps.client.ingestInventory(ctx));
31471
+ this.remoteInventory = remote.ok ? remote.value : null;
31472
+ const snapshot = await (async () => {
31473
+ try {
31474
+ return await this.deps.posture?.prepare() ?? null;
31475
+ } catch {
31476
+ return null;
31477
+ }
31478
+ })();
31479
+ if (snapshot) {
31480
+ try {
31481
+ await withTimeout(
31482
+ this.deps.posture?.send(snapshot) ?? Promise.resolve(),
31483
+ REQUEST_TIMEOUT_MS
31484
+ );
31485
+ } catch {
31486
+ }
31487
+ }
31488
+ return resolved;
31489
+ }
31490
+ // The id is minted CLIENT-side and stored verbatim: the control plane does NOT
31491
+ // re-key it. `pgAuditValues` writes `id: event.id` and carries tenancy in
31492
+ // its own scoping columns, so the device and the forwarded copy
31493
+ // share one id space — which is what makes a re-post idempotent at all.
31494
+ //
31495
+ // Re-posts collapse via `onConflictDoUpdate` on the `id` PK, guarded by
31496
+ // `setWhere eventType = 'session'` (NOT onConflictDoNothing). That guard is
31497
+ // what makes an attached retry safe: a capture-stubbed session row can still
31498
+ // be HEALED by the authoritative root, while a duplicate non-session event —
31499
+ // a retried tool_call, exactly this path — can never stomp a populated row.
31500
+ async recordAuditEvent(event) {
31501
+ await this.deps.local.recordAuditEvent(event);
31502
+ await this.deps.forward.run(
31503
+ () => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
31504
+ );
31505
+ }
31506
+ // Attached `llm_call` is written locally by the inner gateway, then routed to
31507
+ // the control plane through the existing `recordAuditEvent` ingest (no dedicated
31508
+ // client method yet) by pre-building the audit event from the natural key.
31509
+ // The forward goes DIRECTLY to the client rather than through this.recordAuditEvent,
31510
+ // which would write the event to the local store a second time.
31511
+ async recordLlmCall(input) {
31512
+ await this.deps.local.recordLlmCall(input);
31513
+ await this.deps.forward.run(
31514
+ () => this.deps.client.recordAuditEvent(
31515
+ reKeyForForward(llmAuditEvent(input), this.remoteInventory)
31516
+ )
31517
+ );
31518
+ }
31519
+ /**
31520
+ * Forward one batch, item by item, under ONE aggregate deadline.
31521
+ *
31522
+ * Per-item budgets bound each request and nothing bounded their sum — see
31523
+ * BATCH_FORWARD_BUDGET_MS. When the deadline passes the remainder is dropped
31524
+ * rather than sent: the local write has already succeeded, so every caller
31525
+ * has a correct result to return, and a drop is the outcome this path is
31526
+ * built to accept (G8) where a blown hook timeout is not.
31527
+ *
31528
+ * Serial rather than concurrent on purpose. Firing N requests at once would
31529
+ * trade a latency problem for a burst the plane's own per-key rate limiting
31530
+ * would answer with the refusals the breaker then counts.
31531
+ *
31532
+ * WHAT IS DROPPED IS COUNTED. Every other forward failure ends in
31533
+ * `ForwardPolicy.run`'s catch and moves the breaker's file, which is what
31534
+ * lets status call the forward unhealthy; this path returns BEFORE `run` is
31535
+ * reached, so without the tally in `forward-drops.ts` a slow-but-answering
31536
+ * plane produces no failures, keeps the breaker closed, renders a healthy
31537
+ * block, and discards the tail of every batch indefinitely.
31538
+ */
31539
+ async forwardBatch(inputs, toEvent) {
31540
+ const deadline = Date.now() + BATCH_FORWARD_BUDGET_MS;
31541
+ for (let i = 0; i < inputs.length; i += 1) {
31542
+ const now = Date.now();
31543
+ if (now >= deadline) {
31544
+ recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
31545
+ return;
31546
+ }
31547
+ const input = inputs[i];
31548
+ await this.deps.forward.run(
31549
+ () => this.deps.client.recordAuditEvent(reKeyForForward(toEvent(input), this.remoteInventory))
31550
+ );
31551
+ }
31552
+ }
31553
+ // Delegated as a BATCH rather than looped over recordLlmCall: the inner
31554
+ // gateway may write the whole batch in one local transaction, and looping
31555
+ // here would replace that with N separate local writes.
31556
+ async recordLlmCalls(inputs) {
31557
+ await this.deps.local.recordLlmCalls(inputs);
31558
+ await this.forwardBatch(inputs, (input) => llmAuditEvent(input));
31559
+ }
31560
+ // `input.inspections` (secrets detected client-side in the tool's masked
31561
+ // target) ride along on the request's `inspections` field — the control plane
31562
+ // persists each as an inspection_findings row linked to this audit event
31563
+ // (see RecordAuditEventRequest in @akasecurity/schema). The masked
31564
+ // `target` already rides `input.attributes`, so no raw secret leaks either
31565
+ // way — this only stops the FINDING row itself from being dropped.
31566
+ async recordToolCalls(inputs) {
31567
+ await this.deps.local.recordToolCalls(inputs);
31568
+ await this.forwardBatch(inputs, (input) => toolAuditEvent(input));
31569
+ }
31570
+ // Forwarded as a `config_scan` audit event: there is no dedicated
31571
+ // config-scan ingest endpoint, and the audit-event door is the one the
31572
+ // control plane already opens for client-minted, idempotent records.
31573
+ //
31574
+ // ONLY `scanEvent` CROSSES, and unlike `recordCapture` the plane cannot
31575
+ // re-derive the rest. A `ConfigScanRecord` is four things committed together
31576
+ // locally — the inventory `items`, this audit event, and the posture
31577
+ // `definitions`/`findings` that reference it — and three of them stay on the
31578
+ // device. Say that plainly rather than let the asymmetry with `recordCapture`
31579
+ // read as the same argument: there, findings are omitted BECAUSE the plane
31580
+ // re-derives them from `Event.content`; here there is no content to re-derive
31581
+ // from, so what is omitted is simply not sent.
31582
+ //
31583
+ // That is the wire contract as it stands rather than an oversight to patch
31584
+ // here. `items` has no route at all, and `RecordAuditEventRequest.inspections`
31585
+ // is documented as tool-call findings — widening it to carry config-scan
31586
+ // findings is an egress change (a posture finding's `maskedMatch` holds the
31587
+ // matched command) and a decision about what an attached deployment is
31588
+ // entitled to, not a bug fix. An attached machine's config posture therefore
31589
+ // reaches the plane as the event only; the dashboard's own view of it is the
31590
+ // local store.
31591
+ async recordConfigScan(record2) {
31592
+ await this.deps.local.recordConfigScan(record2);
31593
+ await this.deps.forward.run(
31594
+ () => this.deps.client.recordAuditEvent(reKeyForForward(record2.scanEvent, this.remoteInventory))
31595
+ );
31596
+ }
31597
+ async recordBlockedDetection(entry) {
31598
+ return this.deps.local.recordBlockedDetection(entry);
31599
+ }
31600
+ /**
31601
+ * LOCAL-ONLY, deliberately. The shares API is read-plus-decision-override
31602
+ * with no egress ingest endpoint, so there is nothing to forward to; adding a
31603
+ * forward here would be inventing a wire contract that does not exist. The
31604
+ * local write is the whole operation, and its summary is the real one — the
31605
+ * scanner reads a throw as a FAILED WRITE and skips its ledger commit, so
31606
+ * returning the inner gateway's result keeps the retry semantics honest.
31607
+ */
31608
+ async recordProjectEgress(input) {
31609
+ return this.deps.local.recordProjectEgress(input);
31610
+ }
31611
+ // ---------------------------------------------------------------------
31612
+ // Reads and device-local ledgers: pure delegation.
31613
+ // ---------------------------------------------------------------------
31614
+ async configInventoryReport() {
31615
+ return this.deps.local.configInventoryReport();
31616
+ }
31617
+ async readSessionProvider(sessionId) {
31618
+ return this.deps.local.readSessionProvider(sessionId);
31619
+ }
31620
+ async facets() {
31621
+ return this.deps.local.facets();
31622
+ }
31623
+ /**
31624
+ * Delegated UNMODIFIED — including its refusals.
31625
+ *
31626
+ * This is a fail-secure boundary: it decides whether an approved exception
31627
+ * lets a blocked action through. Under local-first the local store owns the
31628
+ * exception ledger, so the honest answer is whatever it says; wrapping this
31629
+ * in a fallback (`catch { return true }`, or defaulting on a timeout) would
31630
+ * turn a store error into a granted bypass. If the inner gateway rejects,
31631
+ * this rejects, and the runtime's own handling decides — which is asserted
31632
+ * end-to-end through runtime.capture rather than here.
31633
+ */
31634
+ async consumeException(id) {
31635
+ return this.deps.local.consumeException(id);
31636
+ }
31637
+ async recentFindings(opts) {
31638
+ return this.deps.local.recentFindings(opts);
31639
+ }
31640
+ async healthSummary() {
31641
+ return this.deps.local.healthSummary();
31642
+ }
31643
+ async activityByDay(days) {
31644
+ return this.deps.local.activityByDay(days);
31645
+ }
31646
+ async tokenReports() {
31647
+ return this.deps.local.tokenReports();
31648
+ }
31649
+ async knownContentHashes() {
31650
+ return this.deps.local.knownContentHashes();
31651
+ }
31652
+ async scanLedger(rulesetHash) {
31653
+ return this.deps.local.scanLedger(rulesetHash);
31654
+ }
31655
+ async recordScanned(entries) {
31656
+ return this.deps.local.recordScanned(entries);
31657
+ }
31658
+ async getRuleProbeVerdict(ruleKey) {
31659
+ return this.deps.local.getRuleProbeVerdict(ruleKey);
31660
+ }
31661
+ async setRuleProbeVerdict(ruleKey, verdict, worstProbeMs2) {
31662
+ return this.deps.local.setRuleProbeVerdict(ruleKey, verdict, worstProbeMs2);
31663
+ }
31664
+ async openAtRestKeysForPath(path) {
31665
+ return this.deps.local.openAtRestKeysForPath(path);
31666
+ }
31667
+ async resolvedAtRestKeysForPath(path) {
31668
+ return this.deps.local.resolvedAtRestKeysForPath(path);
31669
+ }
31670
+ async insertResolution(input) {
31671
+ return this.deps.local.insertResolution(input);
31672
+ }
31673
+ async close() {
31674
+ return this.deps.local.close();
31675
+ }
31676
+ // ---------------------------------------------------------------------
31677
+ // Policy
31678
+ // ---------------------------------------------------------------------
31679
+ async getPolicyBundle() {
31680
+ const local = await this.deps.local.getPolicyBundle();
31681
+ const cached2 = await (async () => {
31682
+ try {
31683
+ return await this.deps.readCachedBundle();
31684
+ } catch {
31685
+ return null;
31686
+ }
31687
+ })();
31688
+ if (cached2 === null) return local;
31689
+ const byRuleId = /* @__PURE__ */ new Map();
31690
+ for (const rule of [...local.rules ?? [], ...cached2.rules ?? []]) {
31691
+ if (!byRuleId.has(rule.id)) byRuleId.set(rule.id, rule);
31692
+ }
31693
+ const rules = [...byRuleId.values()];
31694
+ return {
31695
+ ...local,
31696
+ // The remote version identifies the composed bundle for the poller.
31697
+ version: cached2.version,
31698
+ rules,
31699
+ policies: mergeRaiseOnly(
31700
+ local.policies,
31701
+ cached2.policies,
31702
+ ruleCategoryMap(cached2.rules, local.rules)
31703
+ ),
31704
+ customKeywords: [...local.customKeywords, ...cached2.customKeywords]
31705
+ // `rulesComplete` is a STANDALONE-ONLY signal (the user's local installed
31706
+ // snapshot) and is taken from the LOCAL bundle only — never from the wire
31707
+ // or the on-disk cache. Honoring a cached one would hand the control plane, or
31708
+ // anything able to write policy-cache.json, a kill-switch over the
31709
+ // compiled-in bundled packs: `{ rulesComplete: true, rules: [] }` would
31710
+ // zero local detection. Spread from `local` above, and deliberately not
31711
+ // re-read from `cached` here.
31712
+ //
31713
+ // THREE MORE OF THE CACHED BUNDLE'S FIELDS ARE DROPPED, each on purpose,
31714
+ // and each named here so a reader can tell a decision from an omission:
31715
+ //
31716
+ // `exceptions` — an exception SUPPRESSES a detection, so honoring
31717
+ // one from an unsigned on-disk cache would let
31718
+ // anything able to write that file turn rules off.
31719
+ // Every other field this merge accepts can only
31720
+ // RAISE enforcement; this is the one that cannot,
31721
+ // so it stays local-only until the bundle is
31722
+ // signed. Exceptions remain a device-local ledger.
31723
+ // `reversibleRuleIds` — the Redact & Vault archetype makes a redaction
31724
+ // recoverable, which is a CUSTODY change: it puts
31725
+ // the detected value in the local vault instead of
31726
+ // destroying it. Taking that instruction from the
31727
+ // cache would let a remote party turn one-way
31728
+ // redaction into retention. Dropping it keeps the
31729
+ // one-way behaviour, which the schema itself calls
31730
+ // "the safe direction to default".
31731
+ // `ruleVersions` — remote rules fall back to their own spec version.
31732
+ // Cosmetic rather than protective: it only affects
31733
+ // how a finding is version-attributed, and the two
31734
+ // sides may therefore attribute org rules
31735
+ // differently. Worth carrying once there is a
31736
+ // reader that needs it; nothing reads it today.
31737
+ };
31738
+ }
31739
+ // ---------------------------------------------------------------------
31740
+ // LocalStoreMaintenance — by delegation (D3).
31741
+ //
31742
+ // Implementing these is what actually closes the skipped-local-maintenance
31743
+ // gap: the OSS structural guard `hasLocalStoreMaintenance()` is satisfied by
31744
+ // any object carrying all five, so the composite qualifies and SessionStart
31745
+ // runs maintenance on the device's real store.
31746
+ //
31747
+ // ⚠ Two of the five are SYNCHRONOUS and must stay that way. `handle-session-start`
31748
+ // calls `capWarnEraEnforcement` without `await` and uses `staleBinaryNotice`'s
31749
+ // return value directly; declaring them `async` here would hand those call
31750
+ // sites a Promise and silently break both.
31751
+ // ---------------------------------------------------------------------
31752
+ async sweepTerminalExceptions(retentionMs) {
31753
+ return this.deps.local.sweepTerminalExceptions(retentionMs);
31754
+ }
31755
+ capWarnEraEnforcement(policyMode) {
31756
+ return this.deps.local.capWarnEraEnforcement(policyMode);
31757
+ }
31758
+ async recordProjectFiles(projectId, scan2) {
31759
+ return this.deps.local.recordProjectFiles(projectId, scan2);
31760
+ }
31761
+ async reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot) {
31762
+ return this.deps.local.reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot);
31763
+ }
31764
+ staleBinaryNotice(currentVersion) {
31765
+ return this.deps.local.staleBinaryNotice(currentVersion);
31766
+ }
31767
+ };
31768
+ function reKeyForForward(event, remote) {
31769
+ if (remote === null) {
31770
+ const stripped = { ...event };
31771
+ delete stripped.hostId;
31772
+ delete stripped.harnessId;
31773
+ delete stripped.sourceProjectId;
31774
+ return stripped;
31775
+ }
31776
+ const rekeyed = { ...event };
31777
+ delete rekeyed.hostId;
31778
+ delete rekeyed.harnessId;
31779
+ delete rekeyed.sourceProjectId;
31780
+ if (remote.hostId !== void 0) rekeyed.hostId = remote.hostId;
31781
+ if (remote.harnessId !== void 0) rekeyed.harnessId = remote.harnessId;
31782
+ if (remote.sourceProjectId !== void 0) rekeyed.sourceProjectId = remote.sourceProjectId;
31783
+ return rekeyed;
31784
+ }
31785
+ var BATCH_FORWARD_BUDGET_MS = 3e3;
31786
+ function llmAuditEvent(input) {
31787
+ return {
31788
+ id: llmCallId(input.sessionId, input.messageId),
31789
+ eventType: "llm_call",
31790
+ startedAt: input.startedAt,
31791
+ parentId: input.parentId,
31792
+ rootSessionId: input.rootSessionId,
31793
+ attributes: input.attributes
31794
+ };
31795
+ }
31796
+ function toolAuditEvent(input) {
31797
+ return {
31798
+ id: toolCallId(input.sessionId, input.toolUseId),
31799
+ eventType: "tool_call",
31800
+ startedAt: input.startedAt,
31801
+ parentId: input.parentId,
31802
+ rootSessionId: input.rootSessionId,
31803
+ attributes: input.attributes,
31804
+ inspections: input.inspections
31805
+ };
31806
+ }
31807
+
31808
+ // ../../packages/plugin-runtime/src/attached/policy-store.ts
31809
+ import { randomUUID as randomUUID16 } from "crypto";
31810
+ import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
31811
+ import { join as join19 } from "path";
31812
+
31813
+ // ../../packages/plugin-runtime/src/attached/atomic-publish.ts
31814
+ import { rename as rename2 } from "fs/promises";
31815
+ var RETRYABLE = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY"]);
31816
+ var ATTEMPTS = 5;
31817
+ var delay = (ms) => new Promise((resolve2) => {
31818
+ setTimeout(resolve2, ms);
31819
+ });
31820
+ async function publishByRename(tmp, file2, move = rename2) {
31821
+ for (let attempt = 1; ; attempt += 1) {
31822
+ try {
31823
+ await move(tmp, file2);
31824
+ return;
31825
+ } catch (err) {
31826
+ const code = err.code;
31827
+ if (attempt >= ATTEMPTS || code === void 0 || !RETRYABLE.has(code)) throw err;
31828
+ await delay(attempt * 10);
31829
+ }
31830
+ }
31831
+ }
31832
+
31833
+ // ../../packages/plugin-runtime/src/attached/policy-store.ts
31834
+ function createPolicyStore(dir = dataDir()) {
31835
+ const file2 = join19(dir, "policy-cache.json");
31836
+ async function read() {
31837
+ try {
31838
+ const raw = await readFile2(file2, "utf8");
31839
+ const parsed2 = JSON.parse(raw);
31840
+ if (typeof parsed2 !== "object" || parsed2 === null) return null;
31841
+ const record2 = parsed2;
31842
+ const bundle = PolicyBundle.parse(record2.bundle);
31843
+ const fetchedAtMs = typeof record2.fetchedAtMs === "number" ? record2.fetchedAtMs : 0;
31844
+ const etag = typeof record2.etag === "string" ? record2.etag : void 0;
31845
+ return { bundle, fetchedAtMs, ...etag === void 0 ? {} : { etag } };
31846
+ } catch {
31847
+ return null;
31848
+ }
31849
+ }
31850
+ async function write(bundle, etag) {
31851
+ await ensureDataDir(dir);
31852
+ const stored = {
31853
+ bundle,
31854
+ fetchedAtMs: Date.now(),
31855
+ ...etag === void 0 ? {} : { etag }
31856
+ };
31857
+ const tmp = `${file2}.${randomUUID16()}.tmp`;
31858
+ try {
31859
+ await writeFile2(tmp, JSON.stringify(stored), {
31860
+ encoding: "utf8",
31861
+ mode: DATA_FILE_MODE,
31862
+ flag: "wx"
31863
+ });
31864
+ await publishByRename(tmp, file2);
31865
+ } catch (err) {
31866
+ await rm(tmp, { force: true }).catch(() => void 0);
31867
+ throw err;
31868
+ }
31869
+ }
31870
+ return { read, write, file: file2 };
31871
+ }
31872
+
31873
+ // ../../packages/remote/src/http.ts
31874
+ import { request as httpRequest } from "http";
31875
+ import { request as httpsRequest } from "https";
31876
+ var DEFAULT_TIMEOUT_MS = 1e4;
31877
+ var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
31878
+ var RemoteRequestError = class extends Error {
31879
+ constructor(status) {
31880
+ super(`control-plane request failed with status ${String(status)}`);
31881
+ this.status = status;
31882
+ this.name = "RemoteRequestError";
31883
+ }
31884
+ status;
31885
+ };
31886
+ var RemoteRequestInvalid = class extends Error {
31887
+ constructor(route, cause) {
31888
+ super(`refusing to send a malformed body to ${route}`);
31889
+ this.cause = cause;
31890
+ this.name = "RemoteRequestInvalid";
31891
+ }
31892
+ cause;
31893
+ };
31894
+ var RemoteResponseInvalid = class extends Error {
31895
+ constructor(route, detail) {
31896
+ super(`control plane answered ${route} with ${detail}`);
31897
+ this.name = "RemoteResponseInvalid";
31898
+ }
31899
+ };
31900
+ var RemoteTransportError = class extends Error {
31901
+ /**
31902
+ * The status the peer sent, when headers arrived and only the BODY was
31903
+ * refused.
31904
+ *
31905
+ * Undefined for the ordinary case this class was written for — no answer at
31906
+ * all. It exists because two paths reject after a status has already been
31907
+ * delivered: an oversized body and an aborted response. Discarding it there
31908
+ * reported a deployment answering 401 with a verbose body as a network
31909
+ * outage, which sends the reader to look at their network instead of their
31910
+ * credential.
31911
+ */
31912
+ constructor(reason, status) {
31913
+ super(`control-plane request did not complete: ${reason}`);
31914
+ this.status = status;
31915
+ this.name = "RemoteTransportError";
31916
+ }
31917
+ status;
31918
+ };
31919
+ async function send(options) {
31920
+ const url2 = new URL(options.url);
31921
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
31922
+ const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
31923
+ const requestOptions = {
31924
+ method: options.method,
31925
+ headers: {
31926
+ // CALLER HEADERS FIRST, so this module's own are not overridable. Spread
31927
+ // last they win, and two of the values below are ones no caller may
31928
+ // replace: `x-api-key` is the credential, and `content-length` is the
31929
+ // byte count that stops a multi-byte body being truncated by the
31930
+ // receiver. `SendOptions.headers` is a free-form record on an exported
31931
+ // function, so "no caller does that today" is not the guarantee to rely
31932
+ // on. The one header any caller actually passes — `if-none-match` on the
31933
+ // conditional GET — is untouched by this order.
31934
+ ...options.headers,
31935
+ // The credential. One header, matching what the deployment authenticates
31936
+ // on; a second copy in an `Authorization` header would be one more place
31937
+ // it can be logged by an intermediary for no gain.
31938
+ "x-api-key": options.apiKey,
31939
+ accept: "application/json",
31940
+ ...options.body === void 0 ? {} : {
31941
+ "content-type": "application/json",
31942
+ // Byte length, not string length: a multi-byte body sent with a
31943
+ // character count is truncated by the receiver.
31944
+ "content-length": String(Buffer.byteLength(options.body))
31945
+ }
31946
+ }
31947
+ };
31948
+ return new Promise((resolve2, reject) => {
31949
+ let settled = false;
31950
+ const fail = (reason, status) => {
31951
+ if (settled) return;
31952
+ settled = true;
31953
+ reject(new RemoteTransportError(reason, status));
31954
+ };
31955
+ const req = send_(url2, requestOptions, (res) => {
31956
+ const chunks = [];
31957
+ let size = 0;
31958
+ res.on("data", (chunk) => {
31959
+ size += chunk.length;
31960
+ if (size > MAX_RESPONSE_BYTES) {
31961
+ fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
31962
+ res.destroy();
31963
+ req.destroy();
31964
+ return;
31965
+ }
31966
+ chunks.push(chunk);
31967
+ });
31968
+ res.on("aborted", () => {
31969
+ fail("the response was aborted", res.statusCode);
31970
+ });
31971
+ res.on("end", () => {
31972
+ if (settled) return;
31973
+ settled = true;
31974
+ resolve2({
31975
+ status: res.statusCode ?? 0,
31976
+ headers: res.headers,
31977
+ body: Buffer.concat(chunks).toString("utf8")
31978
+ });
31979
+ });
31980
+ });
31981
+ const deadline = setTimeout(() => {
31982
+ fail(`no response within ${String(timeoutMs)}ms`);
31983
+ req.destroy();
31984
+ }, timeoutMs);
31985
+ deadline.unref();
31986
+ req.on("upgrade", (_res, socket) => {
31987
+ fail("the deployment answered with a protocol upgrade");
31988
+ socket.destroy();
31989
+ });
31990
+ req.on("close", () => {
31991
+ fail("the connection closed before a response was read");
31992
+ clearTimeout(deadline);
31993
+ });
31994
+ req.on("error", (err) => {
31995
+ fail(err.message);
31996
+ });
31997
+ if (options.body !== void 0) req.write(options.body);
31998
+ req.end();
31999
+ });
32000
+ }
32001
+
32002
+ // ../../packages/remote/src/client.ts
32003
+ var ROUTES = {
32004
+ events: "/v1/events",
32005
+ auditEvents: "/v1/audit-events",
32006
+ inventory: "/v1/inventory",
32007
+ storePosture: "/v1/store-posture",
32008
+ policyBundle: "/v1/policy-bundle",
32009
+ whoami: "/v1/plugin/whoami"
32010
+ };
32011
+ function headerValue(response, name) {
32012
+ const raw = response.headers[name];
32013
+ if (raw === void 0) return void 0;
32014
+ return Array.isArray(raw) ? raw[0] : raw;
32015
+ }
32016
+ function okBody(response) {
32017
+ if (response.status < 200 || response.status >= 300) {
32018
+ throw new RemoteRequestError(response.status);
32019
+ }
32020
+ return response.body;
32021
+ }
32022
+ function parsed(schema, body, route) {
32023
+ let json2;
32024
+ try {
32025
+ json2 = JSON.parse(body);
32026
+ } catch {
32027
+ throw new RemoteResponseInvalid(route, "a body that is not JSON");
32028
+ }
32029
+ const result = schema.safeParse(json2);
32030
+ if (!result.success) {
32031
+ throw new RemoteResponseInvalid(route, "a body this client cannot read");
32032
+ }
32033
+ return result.data;
32034
+ }
32035
+ function withoutTrailingSlashes(endpoint) {
32036
+ let end = endpoint.length;
32037
+ while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
32038
+ return endpoint.slice(0, end);
32039
+ }
32040
+ var SLASH = "/".charCodeAt(0);
32041
+ function createRemoteClient(options) {
32042
+ const base = withoutTrailingSlashes(options.endpoint);
32043
+ const url2 = (route) => `${base}${route}`;
32044
+ const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
32045
+ return {
32046
+ async ingestEvents(batch) {
32047
+ const response = await send({
32048
+ ...common,
32049
+ method: "POST",
32050
+ url: url2(ROUTES.events),
32051
+ body: JSON.stringify(batch)
32052
+ });
32053
+ return parsed(IngestAck, okBody(response), ROUTES.events);
32054
+ },
32055
+ async ingestInventory(context) {
32056
+ const response = await send({
32057
+ ...common,
32058
+ method: "POST",
32059
+ url: url2(ROUTES.inventory),
32060
+ body: JSON.stringify(context)
32061
+ });
32062
+ return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
32063
+ },
32064
+ async recordAuditEvent(event) {
32065
+ const validated = RecordAuditEventRequest.safeParse(event);
32066
+ if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
32067
+ const submission = validated.data;
32068
+ const response = await send({
32069
+ ...common,
32070
+ method: "POST",
32071
+ url: url2(ROUTES.auditEvents),
32072
+ body: JSON.stringify(submission)
32073
+ });
32074
+ okBody(response);
32075
+ },
32076
+ async reportStorePosture(snapshot) {
32077
+ const response = await send({
32078
+ ...common,
32079
+ method: "POST",
32080
+ url: url2(ROUTES.storePosture),
32081
+ body: JSON.stringify(snapshot)
32082
+ });
32083
+ okBody(response);
32084
+ },
32085
+ async getPolicyBundle(etag) {
32086
+ const response = await send({
32087
+ ...common,
32088
+ method: "GET",
32089
+ url: url2(ROUTES.policyBundle),
32090
+ ...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
32091
+ });
32092
+ if (response.status === 304) {
32093
+ return { changed: false, etag: headerValue(response, "etag") ?? etag };
32094
+ }
32095
+ const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
32096
+ return { changed: true, bundle, etag: headerValue(response, "etag") };
32097
+ },
32098
+ async whoami() {
32099
+ const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
32100
+ return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
32101
+ }
32102
+ };
32103
+ }
32104
+
32105
+ // ../../packages/plugin-runtime/src/attached/posture-reporter.ts
32106
+ var POSTURE_REPORT_INTERVAL_MS = 60 * 60 * 1e3;
32107
+ function createPostureReporter(deps) {
32108
+ async function prepare() {
32109
+ try {
32110
+ const state = await withTimeout(deps.store.read(), REQUEST_TIMEOUT_MS);
32111
+ if (state === null) return null;
32112
+ const nowMs = deps.now();
32113
+ const elapsed = nowMs - state.lastAttemptedAtMs;
32114
+ if (elapsed >= 0 && elapsed < POSTURE_REPORT_INTERVAL_MS) return null;
32115
+ try {
32116
+ await withTimeout(deps.store.markAttempted(state.deviceId, nowMs), REQUEST_TIMEOUT_MS);
32117
+ } catch {
32118
+ }
32119
+ const { readError, ...measurement } = deps.readStore();
32120
+ if (readError) return null;
32121
+ let plugin;
32122
+ try {
32123
+ plugin = await deps.pluginBlock?.();
32124
+ } catch {
32125
+ plugin = void 0;
32126
+ }
32127
+ return {
32128
+ deviceId: state.deviceId,
32129
+ hostname: deps.hostname(),
32130
+ capturedAt: nowMs,
32131
+ ...measurement,
32132
+ // Omit the key rather than spread an explicit `undefined` —
32133
+ // exactOptionalPropertyTypes distinguishes the two, and the bridge in
32134
+ // factory.ts keys on presence.
32135
+ ...plugin === void 0 ? {} : { plugin }
32136
+ };
32137
+ } catch {
32138
+ return null;
32139
+ }
32140
+ }
32141
+ async function send2(snapshot) {
32142
+ try {
32143
+ await deps.report(snapshot);
32144
+ } catch {
32145
+ }
32146
+ }
32147
+ return { prepare, send: send2 };
32148
+ }
32149
+
32150
+ // ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
32151
+ import { statSync as statSync9 } from "fs";
32152
+ import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
32153
+
32154
+ // ../../packages/plugin-runtime/src/attached/action-counts.ts
32155
+ function emptyActionCounts() {
32156
+ return Object.fromEntries(ACTION_TAKEN_KEYS.map((a) => [a, 0]));
32157
+ }
32158
+ function isActionTaken(value) {
32159
+ return ACTION_TAKEN_KEYS.includes(value);
32160
+ }
32161
+
32162
+ // ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
32163
+ var CAPTURE_EVENT_TYPES_SQL2 = `('prompt','response','code_change','tool_use')`;
32164
+ function isSchemaAbsent(err) {
32165
+ return err instanceof Error && /no such table/i.test(err.message);
32166
+ }
32167
+ function emptyReadout(readError = false) {
32168
+ const byAction = emptyActionCounts();
32169
+ return {
32170
+ storePresent: false,
32171
+ schemaVersion: null,
32172
+ findingsTotal: 0,
32173
+ findingsFirstAt: null,
32174
+ findingsLastAt: null,
32175
+ packs: [],
32176
+ policyCounts: { total: 0, disabled: 0, byAction },
32177
+ readError
32178
+ };
32179
+ }
32180
+ function readStorePosture(dbPath2) {
32181
+ try {
32182
+ statSync9(dbPath2);
32183
+ } catch (err) {
32184
+ const code = err.code;
32185
+ if (code === "ENOENT" || code === "ENOTDIR") return emptyReadout();
32186
+ return emptyReadout(true);
32187
+ }
32188
+ let db = null;
32189
+ let version2 = null;
32190
+ let packs2 = [];
32191
+ let policyCounts = {
32192
+ total: 0,
32193
+ disabled: 0,
32194
+ byAction: emptyActionCounts()
32195
+ };
32196
+ let findingsTotal = 0;
32197
+ let findingsFirstAt = null;
32198
+ let findingsLastAt = null;
32199
+ const currentReadout = () => ({
32200
+ storePresent: true,
32201
+ schemaVersion: version2,
32202
+ findingsTotal,
32203
+ findingsFirstAt,
32204
+ findingsLastAt,
32205
+ packs: packs2,
32206
+ policyCounts,
32207
+ readError: false
32208
+ });
32209
+ try {
32210
+ db = new DatabaseSync3(dbPath2, { readOnly: true });
32211
+ db.exec("PRAGMA busy_timeout = 2000");
32212
+ version2 = db.prepare("PRAGMA user_version").get().user_version;
32213
+ try {
32214
+ const packRows = db.prepare(
32215
+ `SELECT namespace, pack_id, version, enabled, updated_at FROM installed_packs ORDER BY namespace, pack_id`
32216
+ ).all();
32217
+ packs2 = packRows.map((r) => ({
32218
+ packId: `${r.namespace}/${r.pack_id}`,
32219
+ version: r.version,
32220
+ enabled: r.enabled !== 0,
32221
+ updatedAt: r.updated_at == null ? null : String(r.updated_at)
32222
+ }));
32223
+ } catch (err) {
32224
+ if (!isSchemaAbsent(err)) throw err;
32225
+ }
32226
+ try {
32227
+ const policyRows = db.prepare(`SELECT action, enabled FROM policies`).all();
32228
+ const byAction = emptyActionCounts();
32229
+ let disabled = 0;
32230
+ for (const row of policyRows) {
32231
+ if (row.enabled === 0) disabled += 1;
32232
+ if (isActionTaken(row.action)) byAction[row.action] += 1;
32233
+ }
32234
+ policyCounts = { total: policyRows.length, disabled, byAction };
32235
+ } catch (err) {
32236
+ if (!isSchemaAbsent(err)) throw err;
32237
+ }
32238
+ try {
32239
+ const agg = db.prepare(
32240
+ `SELECT count(*) AS n, min(f.first_detected_at) AS firstAt, max(f.first_detected_at) AS lastAt
32241
+ FROM inspection_findings f JOIN audit_events e ON e.id = f.audit_event_id
32242
+ WHERE e.event_type IN ${CAPTURE_EVENT_TYPES_SQL2}`
32243
+ ).get();
32244
+ findingsTotal = agg.n;
32245
+ findingsFirstAt = agg.firstAt;
32246
+ findingsLastAt = agg.lastAt;
32247
+ } catch (err) {
32248
+ if (!isSchemaAbsent(err)) throw err;
32249
+ }
32250
+ return currentReadout();
32251
+ } catch {
32252
+ return emptyReadout(true);
32253
+ } finally {
32254
+ try {
32255
+ db?.close();
32256
+ } catch {
32257
+ }
32258
+ }
32259
+ }
32260
+
32261
+ // ../../packages/plugin-runtime/src/attached/posture-store.ts
32262
+ import { randomUUID as randomUUID17 } from "crypto";
32263
+ import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
32264
+ import { join as join20 } from "path";
32265
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
32266
+ function createPostureStore(dir = settingsDir(), legacyDir) {
32267
+ const file2 = join20(dir, "posture-state.json");
32268
+ const legacyFile = legacyDir === void 0 ? null : join20(legacyDir, "posture-state.json");
32269
+ async function persist(state) {
32270
+ await ensureDataDir(dir);
32271
+ const tmp = `${file2}.${randomUUID17()}.tmp`;
32272
+ try {
32273
+ await writeFile3(tmp, JSON.stringify(state), { encoding: "utf8", mode: DATA_FILE_MODE });
32274
+ await publishByRename(tmp, file2);
32275
+ } catch (err) {
32276
+ await rm2(tmp, { force: true }).catch(() => void 0);
32277
+ throw err;
32278
+ }
32279
+ }
32280
+ async function readFrom(path) {
32281
+ let raw;
32282
+ try {
32283
+ raw = await readFile3(path, "utf8");
32284
+ } catch (err) {
32285
+ const code = err.code;
32286
+ if (code === "ENOENT" || code === "ENOTDIR") return null;
32287
+ throw err;
32288
+ }
32289
+ try {
32290
+ const parsed2 = JSON.parse(raw);
32291
+ if (typeof parsed2 === "object" && parsed2 !== null) {
32292
+ const record2 = parsed2;
32293
+ if (typeof record2.deviceId === "string" && UUID_RE.test(record2.deviceId)) {
32294
+ const stamp = typeof record2.lastAttemptedAtMs === "number" ? record2.lastAttemptedAtMs : typeof record2.lastReportedAtMs === "number" ? record2.lastReportedAtMs : 0;
32295
+ return { deviceId: record2.deviceId, lastAttemptedAtMs: stamp };
32296
+ }
32297
+ }
32298
+ } catch {
32299
+ }
32300
+ return null;
32301
+ }
32302
+ async function read() {
32303
+ const current = await readFrom(file2);
32304
+ if (current) return current;
32305
+ const legacy = legacyFile === null || legacyFile === file2 ? null : await readFrom(legacyFile).catch(() => null);
32306
+ if (legacy) {
32307
+ try {
32308
+ await persist(legacy);
32309
+ } catch {
32310
+ }
32311
+ return legacy;
32312
+ }
32313
+ const fresh = { deviceId: randomUUID17(), lastAttemptedAtMs: 0 };
32314
+ try {
32315
+ await ensureDataDir(dir);
32316
+ if (createOwnerOnlyFileSync(file2, JSON.stringify(fresh))) return fresh;
32317
+ } catch {
32318
+ return null;
32319
+ }
32320
+ const winner = await readFrom(file2).catch(() => null);
32321
+ if (winner) return winner;
32322
+ try {
32323
+ await persist(fresh);
32324
+ } catch {
32325
+ return null;
32326
+ }
32327
+ return fresh;
32328
+ }
32329
+ async function markAttempted(deviceId, atMs) {
32330
+ await persist({ deviceId, lastAttemptedAtMs: atMs });
32331
+ }
32332
+ return { read, markAttempted, file: file2 };
32333
+ }
32334
+
32335
+ // ../../packages/plugin-runtime/src/attached/sync-state.ts
32336
+ import { readFileSync as readFileSync13 } from "fs";
32337
+ import { join as join21 } from "path";
32338
+
32339
+ // ../../packages/plugin-runtime/src/attached/status.ts
32340
+ var REFUSAL_LINES = {
32341
+ unauthorized: "KEY REJECTED \u2014 re-attach with a valid plugin key",
32342
+ forbidden: "ACCESS REFUSED \u2014 key is valid but not permitted; ask your org admin"
32343
+ };
32344
+ var OUTCOME_LINES = {
32345
+ ok: "policy synced",
32346
+ "not-modified": "policy up to date",
32347
+ unauthorized: REFUSAL_LINES.unauthorized,
32348
+ forbidden: REFUSAL_LINES.forbidden,
32349
+ unreachable: "control plane unreachable at last attempt",
32350
+ "invalid-bundle": "control plane sent a policy bundle this build cannot read"
32351
+ };
32352
+
32353
+ // ../../packages/plugin-runtime/src/attached/sync-trigger.ts
32354
+ import { spawn } from "child_process";
32355
+ import { fileURLToPath as fileURLToPath2 } from "url";
32356
+ var SYNC_MARKER_NAME = "sync-last-attempt";
32357
+ var SYNC_SCRIPT_NAME = "sync.js";
32358
+ var SYNC_THROTTLE_MS = 15 * 60 * 1e3;
32359
+ function triggerPolicySync(config2, deps = {}) {
32360
+ try {
32361
+ if (!isAttached(config2.settings)) return;
32362
+ const connection = config2.settings.controlPlane;
32363
+ if (connection === void 0) return;
32364
+ if (!readControlPlaneCredentialState(config2.settingsDir, connection).usable) return;
32365
+ const isThrottled = deps.isThrottled ?? ((dir) => throttled(dir, SYNC_MARKER_NAME, SYNC_THROTTLE_MS));
32366
+ if (isThrottled(config2.dataDir)) return;
32367
+ const scriptPath = fileURLToPath2(deps.scriptUrl ?? new URL(SYNC_SCRIPT_NAME, import.meta.url));
32368
+ (deps.spawnChild ?? spawnDetached)(scriptPath);
32369
+ } catch {
32370
+ }
32371
+ }
32372
+ function spawnDetached(scriptPath) {
32373
+ const child = spawn(process.execPath, [scriptPath], { detached: true, stdio: "ignore" });
32374
+ child.on("error", () => {
32375
+ });
32376
+ child.unref();
32377
+ }
32378
+
32379
+ // ../../packages/plugin-runtime/src/attached/factory.ts
32380
+ import { hostname as hostname5 } from "os";
32381
+
32382
+ // ../../packages/plugin-runtime/src/standalone-gateway.ts
32383
+ import { randomUUID as randomUUID18 } from "crypto";
32384
+
32385
+ // ../../packages/plugin-runtime/src/recorder.ts
32386
+ var PLUGIN_RECORDER_BINARY = "plugin";
32387
+ function pluginRecordedBy(version2) {
32388
+ return `${PLUGIN_RECORDER_BINARY}@${version2}`;
32389
+ }
32390
+
32391
+ // ../../packages/plugin-runtime/src/standalone-gateway.ts
32392
+ var StandaloneDataGateway = class {
32393
+ db;
32394
+ // Kept for the fingerprint key lookup (exception.key lives beside the store).
32395
+ dataDir;
32396
+ // One notice per gateway — see warnRulesetDiscarded.
32397
+ warnedRulesetDiscarded = false;
32398
+ constructor(dataDir2, detections = [], meta3) {
32399
+ this.db = openLocalDatabase(dataDir2);
32400
+ this.dataDir = dataDir2;
32401
+ this.db.installedPacks.recordInventory(detections, meta3);
32402
+ }
32403
+ recordCapture(record2) {
32404
+ this.db.recordCapture(record2.event, record2.findings);
32405
+ return Promise.resolve();
32406
+ }
32407
+ ensureInventory(ctx) {
32408
+ return Promise.resolve(this.db.ensureInventory(ctx));
32409
+ }
32410
+ recordAuditEvent(event) {
32411
+ this.db.auditEvents.insertAuditEvent(event);
32412
+ return Promise.resolve();
32413
+ }
32414
+ // The id is minted inside the repository from the natural key — the plugin can't
32415
+ // import @akasecurity/persistence to compute it, so the gateway is the boundary that
32416
+ // hands the natural key across. UPSERT-take-MAX → idempotent re-reads that also
32417
+ // converge a streaming partial/final split (see insertLlmCall).
32418
+ recordLlmCall(input) {
32419
+ this.db.auditEvents.insertLlmCall(input);
32420
+ return Promise.resolve();
32421
+ }
32422
+ // One reconcile pass = one transaction. All leaves commit together
32423
+ // (single lock + WAL fsync); a contended SQLITE_BUSY rolls back and rejects so the
32424
+ // reconciler drops the whole pass and recovers it idempotently on the next read.
30957
32425
  recordLlmCalls(inputs) {
30958
32426
  if (inputs.length === 0) return Promise.resolve();
30959
- return new Promise((resolve, reject) => {
32427
+ return new Promise((resolve2, reject) => {
30960
32428
  try {
30961
32429
  this.db.auditEvents.runInTransaction(() => {
30962
32430
  for (const input of inputs) this.db.auditEvents.insertLlmCall(input);
30963
32431
  });
30964
- resolve();
32432
+ resolve2();
30965
32433
  } catch (err) {
30966
32434
  reject(err instanceof Error ? err : new Error(String(err)));
30967
32435
  }
@@ -30973,12 +32441,12 @@ var StandaloneDataGateway = class {
30973
32441
  // drops the whole pass and recovers it idempotently next time.
30974
32442
  recordToolCalls(inputs) {
30975
32443
  if (inputs.length === 0) return Promise.resolve();
30976
- return new Promise((resolve, reject) => {
32444
+ return new Promise((resolve2, reject) => {
30977
32445
  try {
30978
32446
  this.db.auditEvents.runInTransaction(() => {
30979
32447
  for (const input of inputs) this.writeToolCall(input);
30980
32448
  });
30981
- resolve();
32449
+ resolve2();
30982
32450
  } catch (err) {
30983
32451
  reject(err instanceof Error ? err : new Error(String(err)));
30984
32452
  }
@@ -31120,7 +32588,7 @@ var StandaloneDataGateway = class {
31120
32588
  const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
31121
32589
  const installed = this.installedScanRules();
31122
32590
  const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
31123
- id: randomUUID15(),
32591
+ id: randomUUID18(),
31124
32592
  scope: "global",
31125
32593
  target: { ruleId },
31126
32594
  action,
@@ -31274,9 +32742,55 @@ var StandaloneDataGateway = class {
31274
32742
  }
31275
32743
  };
31276
32744
 
32745
+ // ../../packages/plugin-runtime/src/attached/factory.ts
32746
+ function resolveGatewayForConfig(config2, meta3) {
32747
+ const local = new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
32748
+ try {
32749
+ if (!isAttached(config2.settings)) return local;
32750
+ const connection = config2.settings.controlPlane;
32751
+ if (connection === void 0) return local;
32752
+ const state = readControlPlaneCredentialState(config2.settingsDir, connection);
32753
+ if (!state.usable) return local;
32754
+ const client = createRemoteClient({
32755
+ endpoint: connection.endpoint,
32756
+ apiKey: state.credential.apiKey
32757
+ });
32758
+ const store = createPolicyStore(config2.dataDir);
32759
+ const postureStore = createPostureStore(config2.settingsDir, config2.dataDir);
32760
+ const forward = createForwardPolicy({ dir: config2.dataDir });
32761
+ return new AttachedDataGateway({
32762
+ local,
32763
+ client,
32764
+ dataDir: config2.dataDir,
32765
+ readCachedBundle: () => store.read().then((cached2) => cached2?.bundle ?? null),
32766
+ forward,
32767
+ posture: createPostureReporter({
32768
+ // THROUGH THE BREAKER, and wrapped HERE rather than around
32769
+ // `PostureReporter.send`. The reporter swallows every error by
32770
+ // contract, so a wrap outside it would hand `forward.run` a resolved
32771
+ // promise for a send that failed — recording a SUCCESS, clearing
32772
+ // `consecutiveFailures` and `lastFailure`, and telling `aka status` the
32773
+ // forward recovered when nothing did. Wrapping the raw client call puts
32774
+ // the breaker above the swallow, where it can see the truth.
32775
+ //
32776
+ // What it buys: once the breaker is open — the plane already confirmed
32777
+ // down by the gateway's own writes — this stops paying a request
32778
+ // timeout per throttle interval to re-learn it.
32779
+ report: (snapshot) => forward.run(() => client.reportStorePosture(snapshot)).then(() => void 0),
32780
+ store: postureStore,
32781
+ readStore: () => readStorePosture(config2.dbPath),
32782
+ hostname: () => hostname5(),
32783
+ now: () => Date.now()
32784
+ })
32785
+ });
32786
+ } catch {
32787
+ return local;
32788
+ }
32789
+ }
32790
+
31277
32791
  // ../../packages/plugin-runtime/src/resolve.ts
31278
- var standaloneGatewayFactory = (config2, meta3) => new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
31279
- var defaultGatewayFactory = standaloneGatewayFactory;
32792
+ var configuredGatewayFactory = (config2, meta3) => resolveGatewayForConfig(config2, meta3);
32793
+ var defaultGatewayFactory = configuredGatewayFactory;
31280
32794
  function resolveDataGateway(config2, meta3, gatewayFactory = defaultGatewayFactory) {
31281
32795
  return gatewayFactory(config2, meta3);
31282
32796
  }
@@ -31299,7 +32813,7 @@ async function handleCapture(input, config2 = loadConfig()) {
31299
32813
  }
31300
32814
 
31301
32815
  // ../../packages/plugin-runtime/src/handle-session-start.ts
31302
- import { randomUUID as randomUUID16 } from "crypto";
32816
+ import { randomUUID as randomUUID19 } from "crypto";
31303
32817
  var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
31304
32818
  async function handleSessionStart(input, config2 = loadConfig()) {
31305
32819
  const silent = { staleBinaryNotice: null };
@@ -31363,6 +32877,7 @@ async function handleSessionStart(input, config2 = loadConfig()) {
31363
32877
  } catch {
31364
32878
  }
31365
32879
  }
32880
+ triggerPolicySync(config2);
31366
32881
  if (input.harnessVersion !== void 0 && offersMaintenance(gateway, "staleBinaryNotice")) {
31367
32882
  return { staleBinaryNotice: gateway.staleBinaryNotice(input.harnessVersion) };
31368
32883
  }
@@ -31388,7 +32903,7 @@ async function recordConfigInventory(gateway, sessionId, cwd, homeDir) {
31388
32903
  }
31389
32904
  function buildConfigScanEvent(sessionId, scan2) {
31390
32905
  return {
31391
- id: randomUUID16(),
32906
+ id: randomUUID19(),
31392
32907
  eventType: "config_scan",
31393
32908
  startedAt: scan2.scannedAt,
31394
32909
  parentId: sessionId,
@@ -31486,10 +33001,10 @@ function writeMessage(stdout, message2) {
31486
33001
  const body = Buffer.from(JSON.stringify(message2), "utf8");
31487
33002
  const header = Buffer.alloc(4);
31488
33003
  header.writeUInt32LE(body.length, 0);
31489
- return new Promise((resolve, reject) => {
33004
+ return new Promise((resolve2, reject) => {
31490
33005
  stdout.write(Buffer.concat([header, body]), (err) => {
31491
33006
  if (err) reject(err);
31492
- else resolve();
33007
+ else resolve2();
31493
33008
  });
31494
33009
  });
31495
33010
  }