@akasecurity/ai-tc-claude-code 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.
package/scripts/query.js CHANGED
@@ -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
@@ -491,10 +491,9 @@ var require_ignore = __commonJS({
491
491
  }
492
492
  });
493
493
 
494
- // ../../packages/persistence/src/database.ts
495
- import { randomUUID as randomUUID10 } from "crypto";
496
- import { join as join2, sep } from "path";
497
- import { DatabaseSync } from "node:sqlite";
494
+ // ../../packages/persistence/src/control-plane-credential.ts
495
+ import { chmodSync as chmodSync2, lstatSync as lstatSync2, readFileSync, rmSync as rmSync2, statSync } from "fs";
496
+ import { join } from "path";
498
497
 
499
498
  // ../../packages/schema/src/drizzle/sqlite-ddl.ts
500
499
  var SQLITE_MIGRATIONS = [
@@ -16258,6 +16257,125 @@ var ConfigScanRecord = external_exports.object({
16258
16257
  findings: external_exports.array(ConfigPostureFindingInput).optional()
16259
16258
  });
16260
16259
 
16260
+ // ../../packages/schema/src/zod/control-plane.ts
16261
+ var ATTACHED_CREDENTIAL_FILENAME = "control-plane-credential.json";
16262
+ var ATTACHED_CREDENTIAL_SPEC_VERSION = 1;
16263
+ var AttachedCredential = external_exports.object({
16264
+ specVersion: external_exports.literal(ATTACHED_CREDENTIAL_SPEC_VERSION),
16265
+ // The control-plane endpoint this credential was minted against.
16266
+ endpoint: external_exports.string().min(1),
16267
+ // The bearer credential itself. Never logged, never rendered — status
16268
+ // surfaces show `keyPrefix` and nothing else.
16269
+ apiKey: external_exports.string().min(1),
16270
+ // First few characters of the key, safe to display so a user can match the
16271
+ // credential against their organization's key list.
16272
+ keyPrefix: external_exports.string().min(1).max(16).optional(),
16273
+ mintedAt: external_exports.iso.datetime().optional()
16274
+ });
16275
+ var MAX_DATE_MS = 253402300799999;
16276
+ var MAX_INT4 = 2147483647;
16277
+ var StorePosturePack = external_exports.object({
16278
+ packId: external_exports.string().min(1),
16279
+ // 'namespace/packId'
16280
+ version: external_exports.string().min(1),
16281
+ enabled: external_exports.boolean(),
16282
+ // Stringified pass-through of the local store's `installed_packs.updated_at`
16283
+ // — the column format is store-version-dependent (epoch millis vs ISO), so
16284
+ // the wire shape assumes neither.
16285
+ updatedAt: external_exports.string().nullable()
16286
+ }).meta({ id: "StorePosturePack" });
16287
+ var StorePosturePolicyCounts = external_exports.object({
16288
+ total: external_exports.number().int().min(0),
16289
+ disabled: external_exports.number().int().min(0),
16290
+ // Exhaustive per-action map; the builder pre-fills every action with 0.
16291
+ //
16292
+ // Spelled out member-by-member rather than `z.record(ActionTaken, …)`. Zod
16293
+ // enforces exhaustiveness either way, but z.record emits `propertyNames` +
16294
+ // `additionalProperties` into a generated schema document, and a type
16295
+ // generator renders THAT with every key optional — a sender built against
16296
+ // the generated type would typecheck and still be rejected at runtime. An
16297
+ // explicit object emits `properties` + `required`, so generated types
16298
+ // demand all five.
16299
+ //
16300
+ // `satisfies Record<ActionTaken, …>` keeps the link to the enum: adding an
16301
+ // ActionTaken member is a COMPILE error here instead of silent drift.
16302
+ // `.strict()` is load-bearing — it rejects an unknown action key, which a
16303
+ // bare object would silently STRIP, accepting a miscounted map as valid.
16304
+ byAction: external_exports.object({
16305
+ warn: external_exports.number().int().min(0),
16306
+ redact: external_exports.number().int().min(0),
16307
+ block: external_exports.number().int().min(0),
16308
+ allow: external_exports.number().int().min(0),
16309
+ log: external_exports.number().int().min(0)
16310
+ }).strict()
16311
+ }).meta({ id: "StorePosturePolicyCounts" });
16312
+ var StorePosturePlugin = external_exports.object({
16313
+ /** Package name of the reporting plugin. */
16314
+ package: external_exports.string().min(1).max(200),
16315
+ version: external_exports.string().min(1).max(64),
16316
+ /** Version of the bundled core, when the build records one separately. */
16317
+ ossVersion: external_exports.string().max(64).nullable(),
16318
+ /**
16319
+ * `version` of the policy bundle this machine last fetched. Bounded at 200
16320
+ * rather than the 64 a bare sha256 hex digest needs today, so a later
16321
+ * format with an algorithm prefix does not start rejecting the channel.
16322
+ */
16323
+ policyBundleVersion: external_exports.string().max(200).nullable(),
16324
+ /** Epoch millis, on the CLIENT clock, of that fetch. */
16325
+ policyFetchedAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable()
16326
+ }).meta({ id: "StorePosturePlugin" });
16327
+ var StorePostureSnapshot = external_exports.object({
16328
+ deviceId: external_exports.guid(),
16329
+ hostname: external_exports.string().min(1).max(253),
16330
+ // Epoch millis on the CLIENT clock. Bounded by what a receiving store
16331
+ // accepts (see MAX_DATE_MS), not by what a JavaScript Date can hold.
16332
+ capturedAt: external_exports.number().int().min(0).max(MAX_DATE_MS),
16333
+ // False is a measurement, not an error state: "no local store exists on
16334
+ // this machine".
16335
+ storePresent: external_exports.boolean(),
16336
+ schemaVersion: external_exports.number().int().min(0).max(MAX_INT4).nullable(),
16337
+ // PRAGMA user_version
16338
+ findingsTotal: external_exports.number().int().min(0).max(MAX_INT4),
16339
+ // Epoch millis, bounded like `capturedAt` — see MAX_DATE_MS on what that
16340
+ // bound does and does not do. Worth stating for these two specifically:
16341
+ // they are read from the local store's own ROWS rather than from this
16342
+ // machine's clock, so a damaged or hand-edited store is enough to produce
16343
+ // an out-of-range value with no clock skew involved.
16344
+ findingsFirstAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable(),
16345
+ findingsLastAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable(),
16346
+ packs: external_exports.array(StorePosturePack).max(500),
16347
+ policyCounts: StorePosturePolicyCounts,
16348
+ // OPTIONAL, not nullable: a reporter that predates this member keeps
16349
+ // getting its 200 without a payload change.
16350
+ plugin: StorePosturePlugin.optional()
16351
+ }).meta({ id: "StorePostureSnapshot" });
16352
+ var CAPTURE_VERSION_PREFIX = "capture/";
16353
+ var RecordAuditEventRequest = AuditEventInput.extend({
16354
+ inspections: external_exports.array(ToolCallInspection).default([])
16355
+ }).refine((v) => v.inspections.every((i) => !i.ruleVersion.startsWith(CAPTURE_VERSION_PREFIX)), {
16356
+ message: `inspections[].ruleVersion must not start with \`${CAPTURE_VERSION_PREFIX}\` \u2014 that namespace is reserved for capture definitions the control plane mints itself`,
16357
+ path: ["inspections"]
16358
+ }).meta({ id: "RecordAuditEventRequest" });
16359
+ var IngestAck = external_exports.object({
16360
+ accepted: external_exports.number().int().nonnegative(),
16361
+ duplicates: external_exports.number().int().nonnegative()
16362
+ });
16363
+ var PRINTABLE = /^[^\p{Cc}\p{Cf}]*$/u;
16364
+ var printable = (max) => external_exports.string().max(max).regex(PRINTABLE, "must not contain control characters");
16365
+ var PluginWhoami = external_exports.object({
16366
+ tenantName: printable(200),
16367
+ userEmail: printable(320),
16368
+ role: printable(64),
16369
+ keyKind: printable(64),
16370
+ serverTime: printable(64)
16371
+ });
16372
+ var ControlPlaneErrorBody = external_exports.object({
16373
+ error: external_exports.object({
16374
+ code: external_exports.string().optional(),
16375
+ message: external_exports.string().optional()
16376
+ }).optional()
16377
+ });
16378
+
16261
16379
  // ../../packages/schema/src/zod/registry.ts
16262
16380
  var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16263
16381
  var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
@@ -16560,15 +16678,15 @@ function summaryToDetectionListItem(s) {
16560
16678
  }
16561
16679
  function rowToDetectionDetail(row, findingsLast30d, update) {
16562
16680
  const rules = row.rules.flatMap((r) => {
16563
- const parsed = Matcher.safeParse(r.matcher);
16564
- if (!parsed.success) return [];
16681
+ const parsed2 = Matcher.safeParse(r.matcher);
16682
+ if (!parsed2.success) return [];
16565
16683
  return [
16566
16684
  {
16567
16685
  id: r.id,
16568
16686
  name: r.name,
16569
16687
  category: r.category,
16570
16688
  severity: r.severity,
16571
- matcher: parsed.data
16689
+ matcher: parsed2.data
16572
16690
  }
16573
16691
  ];
16574
16692
  });
@@ -17214,8 +17332,8 @@ function toApiAction(dbVal) {
17214
17332
  }
17215
17333
  function toApiCategory(dbVal) {
17216
17334
  if (dbVal === "code_context") return "source_code";
17217
- const parsed = FindingCategory.safeParse(dbVal);
17218
- return parsed.success ? parsed.data : "custom";
17335
+ const parsed2 = FindingCategory.safeParse(dbVal);
17336
+ return parsed2.success ? parsed2.data : "custom";
17219
17337
  }
17220
17338
  function toApiProvider(sourceTool) {
17221
17339
  return TOOL_TO_HARNESS[sourceTool] ?? HARNESS.Api;
@@ -17842,6 +17960,9 @@ var WorkspaceSettings = external_exports.object({
17842
17960
  function defaultWorkspaceSettings() {
17843
17961
  return WorkspaceSettings.parse({});
17844
17962
  }
17963
+ function isAttached(settings) {
17964
+ return settings.runMode === "attached" && settings.controlPlane !== void 0;
17965
+ }
17845
17966
  function toInventoryRow(input, id, now) {
17846
17967
  return {
17847
17968
  id,
@@ -18109,8 +18230,8 @@ function builtinPolicyIsReversible(id) {
18109
18230
  return BUILTIN_POLICY_SPECS[id].reversible;
18110
18231
  }
18111
18232
  function policyIdIsReversible(policyId) {
18112
- const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18113
- const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
18233
+ const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18234
+ const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
18114
18235
  return builtinPolicyIsReversible(id);
18115
18236
  }
18116
18237
  var DEFAULT_ACTIONS = Object.fromEntries(
@@ -18122,12 +18243,12 @@ var BUILTIN_POLICIES = Object.fromEntries(
18122
18243
  var DEFAULT_PACK_POLICY_ID = "monitor";
18123
18244
  function policyDisplayName(policyId) {
18124
18245
  const id = policyId ?? DEFAULT_PACK_POLICY_ID;
18125
- const parsed = BuiltinPolicyId.safeParse(id);
18126
- return parsed.success ? BUILTIN_POLICIES[parsed.data].name : id;
18246
+ const parsed2 = BuiltinPolicyId.safeParse(id);
18247
+ return parsed2.success ? BUILTIN_POLICIES[parsed2.data].name : id;
18127
18248
  }
18128
18249
  function policyIdToAction(policyId) {
18129
- const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18130
- const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
18250
+ const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18251
+ const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
18131
18252
  return BUILTIN_POLICIES[id].action;
18132
18253
  }
18133
18254
  var UsedByItem = external_exports.object({
@@ -18570,53 +18691,6 @@ function reviewSeverityRank(reasons) {
18570
18691
  return Math.min(...reasons.map((r) => REVIEW_SEVERITY_RANK[r]));
18571
18692
  }
18572
18693
 
18573
- // ../../packages/persistence/src/ids.ts
18574
- import { createHash } from "crypto";
18575
- function sha256Hex(input) {
18576
- return createHash("sha256").update(input).digest("hex");
18577
- }
18578
- function inventoryId(objectType, identityKey) {
18579
- return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
18580
- }
18581
- function sourceProjectId(url2) {
18582
- return sha256Hex(canonicalIdentity(["source_project", url2]));
18583
- }
18584
- function classifiedDataId(cls) {
18585
- return sha256Hex(canonicalIdentity(["classified_data", cls]));
18586
- }
18587
- function inspectionDefinitionId(ruleId, version2) {
18588
- return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
18589
- }
18590
- function llmCallId(sessionId, messageId) {
18591
- return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
18592
- }
18593
- function toolCallId(sessionId, toolUseId) {
18594
- return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
18595
- }
18596
- function inspectionFindingId(auditEventId, ruleId, spanStart, spanEnd) {
18597
- return sha256Hex(
18598
- canonicalIdentity([
18599
- "inspection_finding",
18600
- auditEventId,
18601
- ruleId,
18602
- String(spanStart),
18603
- String(spanEnd)
18604
- ])
18605
- );
18606
- }
18607
- var NO_SESSION = "no_session";
18608
- var NO_PATH = "no_path";
18609
- function captureId(sessionId, contentHash, filePath = null) {
18610
- return sha256Hex(
18611
- canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
18612
- );
18613
- }
18614
-
18615
- // ../../packages/persistence/src/internal/snapshot.ts
18616
- import { randomUUID } from "crypto";
18617
- import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
18618
- import { basename, dirname, join } from "path";
18619
-
18620
18694
  // ../../packages/persistence/src/paths.ts
18621
18695
  import {
18622
18696
  chmodSync,
@@ -18664,8 +18738,181 @@ function tightenFile(file2) {
18664
18738
  function tightenPerms(file2) {
18665
18739
  for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
18666
18740
  }
18741
+ function writeExclusiveOwnerOnlySync(file2, data) {
18742
+ writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
18743
+ }
18744
+ function writeOwnerOnlyFileSync(file2, data) {
18745
+ const tmp = `${file2}.${String(process.pid)}.tmp`;
18746
+ try {
18747
+ rmSync(tmp, { force: true });
18748
+ } catch {
18749
+ }
18750
+ try {
18751
+ writeExclusiveOwnerOnlySync(tmp, data);
18752
+ renameSync(tmp, file2);
18753
+ } finally {
18754
+ try {
18755
+ rmSync(tmp, { force: true });
18756
+ } catch {
18757
+ }
18758
+ }
18759
+ tightenFile(file2);
18760
+ }
18761
+ function createOwnerOnlyFileSync(file2, data) {
18762
+ const tmp = `${file2}.${String(process.pid)}.${String(threadId)}.new`;
18763
+ try {
18764
+ rmSync(tmp, { force: true });
18765
+ } catch {
18766
+ }
18767
+ let created;
18768
+ try {
18769
+ writeExclusiveOwnerOnlySync(tmp, data);
18770
+ created = publishByLink(tmp, file2, data);
18771
+ } finally {
18772
+ try {
18773
+ rmSync(tmp, { force: true });
18774
+ } catch {
18775
+ }
18776
+ }
18777
+ if (created) tightenFile(file2);
18778
+ return created;
18779
+ }
18780
+ var LINK_UNSUPPORTED = /* @__PURE__ */ new Set(["EPERM", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EINVAL"]);
18781
+ function publishByLink(tmp, file2, data) {
18782
+ try {
18783
+ linkSync(tmp, file2);
18784
+ return true;
18785
+ } catch (err) {
18786
+ const code = err.code;
18787
+ if (code === "EEXIST") return false;
18788
+ if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
18789
+ }
18790
+ try {
18791
+ writeExclusiveOwnerOnlySync(file2, data);
18792
+ return true;
18793
+ } catch (err) {
18794
+ if (err.code === "EEXIST") return false;
18795
+ throw err;
18796
+ }
18797
+ }
18798
+
18799
+ // ../../packages/persistence/src/control-plane-credential.ts
18800
+ function controlPlaneCredentialPath(settingsDir2) {
18801
+ return join(settingsDir2, ATTACHED_CREDENTIAL_FILENAME);
18802
+ }
18803
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
18804
+ function isSafeEndpoint(endpoint) {
18805
+ let parsed2;
18806
+ try {
18807
+ parsed2 = new URL(endpoint);
18808
+ } catch {
18809
+ return false;
18810
+ }
18811
+ if (parsed2.protocol === "https:") return true;
18812
+ return parsed2.protocol === "http:" && LOOPBACK_HOSTS.has(parsed2.hostname);
18813
+ }
18814
+ function repairOrRefuseMode(file2) {
18815
+ const link = lstatSync2(file2, { throwIfNoEntry: false });
18816
+ if (link === void 0) return "absent";
18817
+ if (link.isSymbolicLink()) return "untrusted";
18818
+ const stat = statSync(file2, { throwIfNoEntry: false });
18819
+ if (stat === void 0) return "absent";
18820
+ const uid = process.getuid?.();
18821
+ if (uid !== void 0 && stat.uid !== uid) return "untrusted";
18822
+ if (process.platform !== "win32" && (stat.mode & 511) !== DATA_FILE_MODE) {
18823
+ try {
18824
+ chmodSync2(file2, DATA_FILE_MODE);
18825
+ } catch {
18826
+ return "untrusted";
18827
+ }
18828
+ }
18829
+ return "ok";
18830
+ }
18831
+ function readControlPlaneCredentialState(settingsDir2, connection) {
18832
+ const file2 = controlPlaneCredentialPath(settingsDir2);
18833
+ let raw;
18834
+ const gate = repairOrRefuseMode(file2);
18835
+ if (gate === "absent") return { usable: false, reason: "absent" };
18836
+ if (gate === "untrusted") return { usable: false, reason: "untrusted-file" };
18837
+ try {
18838
+ raw = readFileSync(file2, "utf8");
18839
+ } catch (err) {
18840
+ const code = err.code;
18841
+ return { usable: false, reason: code === "ENOENT" ? "absent" : "unreadable" };
18842
+ }
18843
+ let parsed2;
18844
+ try {
18845
+ parsed2 = JSON.parse(raw);
18846
+ } catch {
18847
+ return { usable: false, reason: "malformed" };
18848
+ }
18849
+ const result = AttachedCredential.safeParse(parsed2);
18850
+ if (!result.success) return { usable: false, reason: "malformed" };
18851
+ if (!isSafeEndpoint(result.data.endpoint)) {
18852
+ return { usable: false, reason: "unsafe-endpoint" };
18853
+ }
18854
+ if (connection !== void 0 && connection.endpoint !== result.data.endpoint) {
18855
+ return {
18856
+ usable: false,
18857
+ reason: "endpoint-mismatch",
18858
+ credentialEndpoint: result.data.endpoint,
18859
+ settingsEndpoint: connection.endpoint
18860
+ };
18861
+ }
18862
+ return { usable: true, credential: result.data };
18863
+ }
18864
+
18865
+ // ../../packages/persistence/src/database.ts
18866
+ import { randomUUID as randomUUID10 } from "crypto";
18867
+ import { join as join3, sep } from "path";
18868
+ import { DatabaseSync } from "node:sqlite";
18869
+
18870
+ // ../../packages/persistence/src/ids.ts
18871
+ import { createHash } from "crypto";
18872
+ function sha256Hex(input) {
18873
+ return createHash("sha256").update(input).digest("hex");
18874
+ }
18875
+ function inventoryId(objectType, identityKey) {
18876
+ return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
18877
+ }
18878
+ function sourceProjectId(url2) {
18879
+ return sha256Hex(canonicalIdentity(["source_project", url2]));
18880
+ }
18881
+ function classifiedDataId(cls) {
18882
+ return sha256Hex(canonicalIdentity(["classified_data", cls]));
18883
+ }
18884
+ function inspectionDefinitionId(ruleId, version2) {
18885
+ return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
18886
+ }
18887
+ function llmCallId(sessionId, messageId) {
18888
+ return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
18889
+ }
18890
+ function toolCallId(sessionId, toolUseId) {
18891
+ return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
18892
+ }
18893
+ function inspectionFindingId(auditEventId, ruleId, spanStart, spanEnd) {
18894
+ return sha256Hex(
18895
+ canonicalIdentity([
18896
+ "inspection_finding",
18897
+ auditEventId,
18898
+ ruleId,
18899
+ String(spanStart),
18900
+ String(spanEnd)
18901
+ ])
18902
+ );
18903
+ }
18904
+ var NO_SESSION = "no_session";
18905
+ var NO_PATH = "no_path";
18906
+ function captureId(sessionId, contentHash, filePath = null) {
18907
+ return sha256Hex(
18908
+ canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
18909
+ );
18910
+ }
18667
18911
 
18668
18912
  // ../../packages/persistence/src/internal/snapshot.ts
18913
+ import { randomUUID } from "crypto";
18914
+ import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync2 } from "fs";
18915
+ import { basename, dirname, join as join2 } from "path";
18669
18916
  function backupPath(file2, tag) {
18670
18917
  return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
18671
18918
  }
@@ -18675,15 +18922,15 @@ var STAGED_NAME_SUFFIX = `.bak${SNAPSHOT_STAGING_SUFFIX}`;
18675
18922
  var SNAPSHOT_STAGING_COPY = "copy";
18676
18923
  function createSnapshotStaging(backup) {
18677
18924
  const stage = `${backup}${SNAPSHOT_STAGING_SUFFIX}`;
18678
- rmSync2(stage, { recursive: true, force: true });
18925
+ rmSync3(stage, { recursive: true, force: true });
18679
18926
  mkdirOwnerOnlySync(stage);
18680
18927
  tightenDir(stage);
18681
- return { stage, copy: join(stage, SNAPSHOT_STAGING_COPY) };
18928
+ return { stage, copy: join2(stage, SNAPSHOT_STAGING_COPY) };
18682
18929
  }
18683
18930
  function idleMs(entry) {
18684
- for (const candidate of [join(entry, SNAPSHOT_STAGING_COPY), entry]) {
18931
+ for (const candidate of [join2(entry, SNAPSHOT_STAGING_COPY), entry]) {
18685
18932
  try {
18686
- return Date.now() - statSync(candidate).mtimeMs;
18933
+ return Date.now() - statSync2(candidate).mtimeMs;
18687
18934
  } catch {
18688
18935
  }
18689
18936
  }
@@ -18700,11 +18947,11 @@ function reapStalePartials(file2) {
18700
18947
  }
18701
18948
  for (const name of entries) {
18702
18949
  if (!name.startsWith(prefix) || !name.endsWith(STAGED_NAME_SUFFIX)) continue;
18703
- const staging = join(dir, name);
18950
+ const staging = join2(dir, name);
18704
18951
  try {
18705
18952
  const idle = idleMs(staging);
18706
18953
  if (idle !== null && idle > STALE_PARTIAL_MS) {
18707
- rmSync2(staging, { recursive: true, force: true });
18954
+ rmSync3(staging, { recursive: true, force: true });
18708
18955
  }
18709
18956
  } catch {
18710
18957
  }
@@ -18718,13 +18965,13 @@ function snapshotStore(db, backup) {
18718
18965
  renameSync2(copy, backup);
18719
18966
  } catch (error51) {
18720
18967
  try {
18721
- rmSync2(stage, { recursive: true, force: true });
18968
+ rmSync3(stage, { recursive: true, force: true });
18722
18969
  } catch {
18723
18970
  }
18724
18971
  throw error51;
18725
18972
  }
18726
18973
  try {
18727
- rmSync2(stage, { recursive: true, force: true });
18974
+ rmSync3(stage, { recursive: true, force: true });
18728
18975
  } catch {
18729
18976
  }
18730
18977
  }
@@ -18739,7 +18986,7 @@ function moveStoreAside(file2, backup) {
18739
18986
  renameSync2(sidecar, moved);
18740
18987
  undo.push([moved, sidecar]);
18741
18988
  } catch {
18742
- rmSync2(sidecar, { force: true });
18989
+ rmSync3(sidecar, { force: true });
18743
18990
  }
18744
18991
  }
18745
18992
  } catch (error51) {
@@ -18755,14 +19002,14 @@ function moveStoreAside(file2, backup) {
18755
19002
  }
18756
19003
  function discardStore(file2, backup) {
18757
19004
  try {
18758
- rmSync2(file2, { force: true });
19005
+ rmSync3(file2, { force: true });
18759
19006
  for (const sidecar of dbSidecars(file2)) {
18760
- rmSync2(sidecar, { force: true });
19007
+ rmSync3(sidecar, { force: true });
18761
19008
  }
18762
19009
  } catch (error51) {
18763
19010
  if (existsSync(file2)) {
18764
19011
  try {
18765
- rmSync2(backup, { force: true });
19012
+ rmSync3(backup, { force: true });
18766
19013
  } catch {
18767
19014
  }
18768
19015
  }
@@ -18994,10 +19241,31 @@ function applyMigrations(db, file2) {
18994
19241
  if (drained) applyLegacyDropMigration(db, file2);
18995
19242
  }
18996
19243
  }
19244
+ function readLegacyTables(db) {
19245
+ let holdsRows = false;
19246
+ const marks = [];
19247
+ for (const table2 of ["events", "findings"]) {
19248
+ try {
19249
+ const row = db.prepare(`SELECT count(*) AS n, ifnull(max(rowid), -1) AS hi FROM ${table2}`).get();
19250
+ if (row === void 0) {
19251
+ holdsRows = true;
19252
+ marks.push(`${table2}:unreadable`);
19253
+ continue;
19254
+ }
19255
+ if (row.n > 0) holdsRows = true;
19256
+ marks.push(`${table2}:${String(row.n)}:${String(row.hi)}`);
19257
+ } catch {
19258
+ holdsRows = true;
19259
+ marks.push(`${table2}:unreadable`);
19260
+ }
19261
+ }
19262
+ return { holdsRows, mark: marks.join("|") };
19263
+ }
18997
19264
  function applyLegacyDropMigration(db, file2) {
18998
19265
  const migration = SQLITE_MIGRATIONS.find((m) => m.tag === LEGACY_DROP_MIGRATION_TAG);
18999
19266
  if (!migration) return;
19000
- if (file2) {
19267
+ const before = file2 === void 0 ? void 0 : readLegacyTables(db);
19268
+ if (file2 !== void 0 && before?.holdsRows === true) {
19001
19269
  try {
19002
19270
  backupBeforeLegacyDrop(db, file2);
19003
19271
  } catch (error51) {
@@ -19011,6 +19279,12 @@ function applyLegacyDropMigration(db, file2) {
19011
19279
  () => {
19012
19280
  const alreadyDropped = db.prepare("SELECT 1 FROM migration_ledger WHERE tag = ?").get(migration.tag);
19013
19281
  if (alreadyDropped) return;
19282
+ if (before !== void 0 && readLegacyTables(db).mark !== before.mark) {
19283
+ akaWarn(
19284
+ "legacy events/findings rows changed after the pre-drop snapshot decision; deferring the drop so the next open can copy them first."
19285
+ );
19286
+ return;
19287
+ }
19014
19288
  for (const statement of splitStatements(migration.sql)) {
19015
19289
  db.exec(statement);
19016
19290
  }
@@ -19365,8 +19639,8 @@ function safeJson(s, fallback) {
19365
19639
  function parseJsonObject(s) {
19366
19640
  if (s == null) return void 0;
19367
19641
  try {
19368
- const parsed = JSON.parse(s);
19369
- if (typeof parsed === "object" && parsed !== null) return parsed;
19642
+ const parsed2 = JSON.parse(s);
19643
+ if (typeof parsed2 === "object" && parsed2 !== null) return parsed2;
19370
19644
  } catch {
19371
19645
  }
19372
19646
  return void 0;
@@ -19377,16 +19651,16 @@ function encodeKeysetCursor(payload) {
19377
19651
  return Buffer.from(JSON.stringify(payload)).toString("base64url");
19378
19652
  }
19379
19653
  function decodeKeysetCursor(cursor) {
19380
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
19381
- if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
19654
+ const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
19655
+ if (parsed2 !== void 0 && "startedAtMs" in parsed2 && "id" in parsed2 && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
19382
19656
  // resumes from is epoch millis, and a payload carrying ±Infinity or a
19383
19657
  // fraction binds cleanly rather than failing — returning an EMPTY page with
19384
19658
  // a null cursor, which a caller reads as "end of list". That is the one
19385
19659
  // outcome a cursor that does not decode must never produce, since the
19386
19660
  // documented behaviour above is to restart from the top. (`1e999` is valid
19387
19661
  // JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
19388
- Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
19389
- return parsed;
19662
+ Number.isInteger(parsed2.startedAtMs) && typeof parsed2.id === "string") {
19663
+ return parsed2;
19390
19664
  }
19391
19665
  return null;
19392
19666
  }
@@ -19451,18 +19725,18 @@ var DB_EVENT_TYPE_TO_KIND = {
19451
19725
  };
19452
19726
  function safeParseStringArray(raw) {
19453
19727
  if (!raw) return [];
19454
- const parsed = safeJson(raw, null);
19455
- return Array.isArray(parsed) ? parsed : [];
19728
+ const parsed2 = safeJson(raw, null);
19729
+ return Array.isArray(parsed2) ? parsed2 : [];
19456
19730
  }
19457
19731
  var DEFAULT_HARNESS = HARNESS.ClaudeCode;
19458
19732
  function toHarness(raw) {
19459
- const parsed = Harness.safeParse(raw);
19460
- return parsed.success ? parsed.data : DEFAULT_HARNESS;
19733
+ const parsed2 = Harness.safeParse(raw);
19734
+ return parsed2.success ? parsed2.data : DEFAULT_HARNESS;
19461
19735
  }
19462
19736
  function resolveLifecycle(row, lastActivityMs, nowMs) {
19463
19737
  if (row.status) {
19464
- const parsed = SessionStatus.safeParse(row.status);
19465
- if (parsed.success) return { status: parsed.data, endedAtMs: row.ended_at };
19738
+ const parsed2 = SessionStatus.safeParse(row.status);
19739
+ if (parsed2.success) return { status: parsed2.data, endedAtMs: row.ended_at };
19466
19740
  }
19467
19741
  if (row.ended_at !== null) return { status: "completed", endedAtMs: row.ended_at };
19468
19742
  if (lastActivityMs >= nowMs - LIVE_ACTIVITY_WINDOW_MS) {
@@ -20421,9 +20695,9 @@ var SqliteDetectionsRepository = class {
20421
20695
  const ruleIds = /* @__PURE__ */ new Set();
20422
20696
  for (const r of rows) {
20423
20697
  if (intToBool(r.enabled)) active += 1;
20424
- const parsed = parseRules(r.rulesJson);
20425
- rules += parsed.length;
20426
- for (const rule of parsed) {
20698
+ const parsed2 = parseRules(r.rulesJson);
20699
+ rules += parsed2.length;
20700
+ for (const rule of parsed2) {
20427
20701
  if (typeof rule.id === "string") ruleIds.add(rule.id);
20428
20702
  }
20429
20703
  }
@@ -20957,12 +21231,12 @@ function encodeGroupCursor(group) {
20957
21231
  return Buffer.from(JSON.stringify(payload)).toString("base64url");
20958
21232
  }
20959
21233
  function decodeGroupCursor(cursor) {
20960
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
20961
- if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
21234
+ const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
21235
+ if (parsed2 !== void 0 && typeof parsed2.sev === "string" && typeof parsed2.t === "string" && typeof parsed2.id === "string") {
20962
21236
  return {
20963
- severity: parsed.sev,
20964
- latestDetectedAt: parsed.t,
20965
- id: parsed.id
21237
+ severity: parsed2.sev,
21238
+ latestDetectedAt: parsed2.t,
21239
+ id: parsed2.id
20966
21240
  };
20967
21241
  }
20968
21242
  return null;
@@ -22096,16 +22370,16 @@ var SqliteInstalledPacksRepository = class {
22096
22370
  continue;
22097
22371
  }
22098
22372
  for (const entry of raw) {
22099
- const parsed = Rule.safeParse(entry);
22100
- if (parsed.success) {
22101
- out.rules.push(parsed.data);
22102
- out.ruleActions.set(parsed.data.id, action);
22103
- out.ruleVersions.set(parsed.data.id, row.version);
22104
- if (reversible) out.reversibleRules.add(parsed.data.id);
22105
- else out.reversibleRules.delete(parsed.data.id);
22373
+ const parsed2 = Rule.safeParse(entry);
22374
+ if (parsed2.success) {
22375
+ out.rules.push(parsed2.data);
22376
+ out.ruleActions.set(parsed2.data.id, action);
22377
+ out.ruleVersions.set(parsed2.data.id, row.version);
22378
+ if (reversible) out.reversibleRules.add(parsed2.data.id);
22379
+ else out.reversibleRules.delete(parsed2.data.id);
22106
22380
  } else {
22107
22381
  out.invalidRules += 1;
22108
- reject(pack, printableRuleId(entry), firstIssueReason(parsed.error));
22382
+ reject(pack, printableRuleId(entry), firstIssueReason(parsed2.error));
22109
22383
  }
22110
22384
  }
22111
22385
  }
@@ -23495,15 +23769,15 @@ function encodeReuseCursor(payload) {
23495
23769
  return Buffer.from(JSON.stringify(payload)).toString("base64url");
23496
23770
  }
23497
23771
  function decodeReuseCursor(cursor) {
23498
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
23499
- if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
23772
+ const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
23773
+ if (parsed2 !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
23500
23774
  // ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
23501
23775
  // null cursor, which the caller reads as "end of list" — the one outcome a
23502
23776
  // malformed cursor must never produce, since restarting from the top is the
23503
23777
  // documented behaviour and the only recoverable one. (`1e999` is valid JSON
23504
23778
  // and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
23505
- Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
23506
- return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
23779
+ Number.isInteger(parsed2.occurrences) && typeof parsed2.pointerId === "string") {
23780
+ return { occurrences: parsed2.occurrences, pointerId: parsed2.pointerId };
23507
23781
  }
23508
23782
  return null;
23509
23783
  }
@@ -25232,7 +25506,7 @@ function openAndInitialize(file2) {
25232
25506
  }
25233
25507
  function openLocalDatabase(dir) {
25234
25508
  ensureDataDirSync(dir);
25235
- const file2 = join2(dir, DB_FILENAME);
25509
+ const file2 = join3(dir, DB_FILENAME);
25236
25510
  reapStalePartials(file2);
25237
25511
  const {
25238
25512
  db,
@@ -25468,9 +25742,9 @@ import {
25468
25742
  closeSync,
25469
25743
  existsSync as existsSync2,
25470
25744
  openSync,
25471
- readFileSync,
25472
- rmSync as rmSync3,
25473
- statSync as statSync2,
25745
+ readFileSync as readFileSync2,
25746
+ rmSync as rmSync4,
25747
+ statSync as statSync3,
25474
25748
  writeFileSync as writeFileSync2
25475
25749
  } from "fs";
25476
25750
  import { hostname as hostname3 } from "os";
@@ -25481,20 +25755,20 @@ import { createHash as createHash3 } from "crypto";
25481
25755
 
25482
25756
  // ../../packages/persistence/src/fingerprint.ts
25483
25757
  import { createHmac, randomBytes } from "crypto";
25484
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
25485
- import { join as join3 } from "path";
25758
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
25759
+ import { join as join4 } from "path";
25486
25760
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
25487
25761
  var EXCEPTION_KEY_FILENAME = "exception.key";
25488
25762
  var KEY_MATERIAL_BYTES = 32;
25489
25763
  function keyFilePath(dataDir2) {
25490
- return join3(dataDir2, EXCEPTION_KEY_FILENAME);
25764
+ return join4(dataDir2, EXCEPTION_KEY_FILENAME);
25491
25765
  }
25492
25766
  function parseKeyFile(raw) {
25493
- const parsed = JSON.parse(raw);
25494
- if (typeof parsed !== "object" || parsed === null) {
25767
+ const parsed2 = JSON.parse(raw);
25768
+ if (typeof parsed2 !== "object" || parsed2 === null) {
25495
25769
  throw new Error("exception key file is corrupt: not a JSON object");
25496
25770
  }
25497
- const { version: version2, material } = parsed;
25771
+ const { version: version2, material } = parsed2;
25498
25772
  if (typeof version2 !== "number" || !Number.isInteger(version2) || version2 < 1) {
25499
25773
  throw new Error("exception key file is corrupt: bad version");
25500
25774
  }
@@ -25510,7 +25784,7 @@ function parseKeyFile(raw) {
25510
25784
  function readFingerprintKey(dataDir2) {
25511
25785
  let raw;
25512
25786
  try {
25513
- raw = readFileSync2(keyFilePath(dataDir2), "utf8");
25787
+ raw = readFileSync3(keyFilePath(dataDir2), "utf8");
25514
25788
  } catch (err) {
25515
25789
  if (err.code === "ENOENT") return null;
25516
25790
  throw err instanceof Error ? err : new Error(String(err));
@@ -25522,18 +25796,22 @@ function readFingerprintKey(dataDir2) {
25522
25796
  import { renameSync as renameSync3 } from "fs";
25523
25797
  import { mkdir } from "fs/promises";
25524
25798
  import { homedir } from "os";
25525
- import { join as join4 } from "path";
25799
+ import { join as join5 } from "path";
25526
25800
  function defaultDataDir() {
25527
- return join4(homedir(), ".aka");
25801
+ return join5(homedir(), ".aka");
25528
25802
  }
25529
25803
  function settingsDir(base = defaultDataDir()) {
25530
- return join4(base, "settings");
25804
+ return join5(base, "settings");
25531
25805
  }
25532
25806
  function dataDir(base = defaultDataDir()) {
25533
- return join4(base, "data");
25807
+ return join5(base, "data");
25534
25808
  }
25535
25809
  function dbPath(base = defaultDataDir()) {
25536
- return join4(dataDir(base), "aka.db");
25810
+ return join5(dataDir(base), "aka.db");
25811
+ }
25812
+ async function ensureDataDir(dir = defaultDataDir()) {
25813
+ await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
25814
+ tightenDir(dir);
25537
25815
  }
25538
25816
  function ensureLayoutDirSync(dir = defaultDataDir()) {
25539
25817
  ensureDataDirSync(dir);
@@ -25546,8 +25824,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
25546
25824
  for (const { name, dest } of moves) {
25547
25825
  try {
25548
25826
  ensureDataDirSync(dest);
25549
- const moved = join4(dest, name);
25550
- renameSync3(join4(base, name), moved);
25827
+ const moved = join5(dest, name);
25828
+ renameSync3(join5(base, name), moved);
25551
25829
  tightenFile(moved);
25552
25830
  } catch {
25553
25831
  }
@@ -25555,7 +25833,7 @@ function migrateLegacyLayout(base = defaultDataDir()) {
25555
25833
  }
25556
25834
 
25557
25835
  // ../../packages/persistence/src/managed-settings.ts
25558
- import { readFileSync as readFileSync3 } from "fs";
25836
+ import { readFileSync as readFileSync4 } from "fs";
25559
25837
  import { posix, win32 } from "path";
25560
25838
  function managedSettingsPaths(platform2 = process.platform) {
25561
25839
  if (platform2 === "darwin") {
@@ -25573,14 +25851,14 @@ function readManagedSettings(paths = managedSettingsPaths()) {
25573
25851
  for (const path of paths) {
25574
25852
  let text;
25575
25853
  try {
25576
- text = readFileSync3(path, "utf8");
25854
+ text = readFileSync4(path, "utf8");
25577
25855
  } catch {
25578
25856
  continue;
25579
25857
  }
25580
25858
  const record2 = parseJsonObject(text);
25581
25859
  if (!record2) continue;
25582
- const parsed = ManagedSettings.safeParse(record2);
25583
- if (parsed.success) return parsed.data;
25860
+ const parsed2 = ManagedSettings.safeParse(record2);
25861
+ if (parsed2.success) return parsed2.data;
25584
25862
  }
25585
25863
  return null;
25586
25864
  }
@@ -25620,14 +25898,14 @@ function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ n
25620
25898
  }
25621
25899
 
25622
25900
  // ../../packages/persistence/src/settings.ts
25623
- import { readFileSync as readFileSync4 } from "fs";
25624
- import { join as join5 } from "path";
25901
+ import { readFileSync as readFileSync5 } from "fs";
25902
+ import { join as join6 } from "path";
25625
25903
  var SETTINGS_FILENAME = "settings.json";
25626
25904
  function readWorkspaceSettings(base = defaultDataDir()) {
25627
25905
  return overlayManagedSettings(readUserSettings(base), readManagedSettings());
25628
25906
  }
25629
25907
  function readUserSettings(base) {
25630
- const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
25908
+ const record2 = readJson(join6(settingsDir(base), SETTINGS_FILENAME));
25631
25909
  if (!record2) return defaultWorkspaceSettings();
25632
25910
  try {
25633
25911
  return WorkspaceSettings.parse(record2);
@@ -25638,13 +25916,17 @@ function readUserSettings(base) {
25638
25916
  function readJson(file2) {
25639
25917
  let text;
25640
25918
  try {
25641
- text = readFileSync4(file2, "utf8");
25919
+ text = readFileSync5(file2, "utf8");
25642
25920
  } catch {
25643
25921
  return null;
25644
25922
  }
25645
25923
  return parseJsonObject(text) ?? null;
25646
25924
  }
25647
25925
 
25926
+ // ../../packages/persistence/src/store-symlinks.ts
25927
+ import { existsSync as existsSync4, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
25928
+ import { dirname as dirname2, join as join7, resolve } from "path";
25929
+
25648
25930
  // ../../packages/persistence/src/vault/crypto.ts
25649
25931
  import {
25650
25932
  createCipheriv,
@@ -25657,29 +25939,92 @@ import {
25657
25939
  // ../../packages/persistence/src/vault/key-provider.ts
25658
25940
  import { execFileSync } from "child_process";
25659
25941
  import { randomBytes as randomBytes2 } from "crypto";
25660
- import { chmodSync as chmodSync2, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
25661
- import { join as join6 } from "path";
25942
+ import { chmodSync as chmodSync3, readFileSync as readFileSync6, renameSync as renameSync4, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
25943
+ import { join as join8 } from "path";
25662
25944
 
25663
25945
  // ../../packages/persistence/src/vault/vault.ts
25664
25946
  import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
25665
25947
 
25666
25948
  // ../../packages/persistence/src/warn-era-cap.ts
25667
- import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
25668
- import { join as join7 } from "path";
25949
+ import { existsSync as existsSync5, writeFileSync as writeFileSync4 } from "fs";
25950
+ import { join as join9 } from "path";
25669
25951
  var MARKER = "warn-era-capped";
25670
25952
  function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
25671
25953
  if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
25672
- const marker = join7(dataDir2, MARKER);
25673
- if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
25954
+ const marker = join9(dataDir2, MARKER);
25955
+ if (existsSync5(marker)) return { capped: 0, skipped: "already-run" };
25674
25956
  const capped = db.policies.capCategoryActions();
25675
25957
  writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
25676
25958
  `, { mode: DATA_FILE_MODE });
25677
25959
  return { capped };
25678
25960
  }
25679
25961
 
25962
+ // ../../packages/plugin-runtime/src/attached/failure.ts
25963
+ function statusOf(err) {
25964
+ if (typeof err !== "object" || err === null || !("status" in err)) return null;
25965
+ const { status } = err;
25966
+ if (typeof status !== "number" || !Number.isInteger(status)) return null;
25967
+ return status >= 100 && status <= 599 ? status : null;
25968
+ }
25969
+ function classifyFailure(err) {
25970
+ switch (statusOf(err)) {
25971
+ case 401:
25972
+ return "unauthorized";
25973
+ case 403:
25974
+ return "forbidden";
25975
+ default:
25976
+ return "unreachable";
25977
+ }
25978
+ }
25979
+
25980
+ // ../../packages/plugin-runtime/src/attached/forward-drops.ts
25981
+ import { readFileSync as readFileSync7 } from "fs";
25982
+ import { join as join10 } from "path";
25983
+ var FORWARD_DROPS_FILENAME = "attached-forward-drops.json";
25984
+ function forwardDropsPath(dataDir2) {
25985
+ return join10(dataDir2, FORWARD_DROPS_FILENAME);
25986
+ }
25987
+ function recordForwardDrops(dataDir2, count, nowMs) {
25988
+ if (count <= 0) return;
25989
+ try {
25990
+ ensureDataDirSync(dataDir2);
25991
+ const previous = readForwardDrops(dataDir2);
25992
+ const next = {
25993
+ droppedForwards: (previous?.droppedForwards ?? 0) + count,
25994
+ lastDropAtMs: nowMs
25995
+ };
25996
+ writeOwnerOnlyFileSync(forwardDropsPath(dataDir2), `${JSON.stringify(next)}
25997
+ `);
25998
+ } catch {
25999
+ }
26000
+ }
26001
+ function readForwardDrops(dataDir2) {
26002
+ try {
26003
+ const parsed2 = JSON.parse(readFileSync7(forwardDropsPath(dataDir2), "utf8"));
26004
+ if (typeof parsed2 !== "object" || parsed2 === null) return null;
26005
+ const record2 = parsed2;
26006
+ if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
26007
+ return null;
26008
+ }
26009
+ if (record2.droppedForwards <= 0) return null;
26010
+ if (typeof record2.lastDropAtMs !== "number" || !Number.isFinite(record2.lastDropAtMs)) {
26011
+ return null;
26012
+ }
26013
+ return { droppedForwards: record2.droppedForwards, lastDropAtMs: record2.lastDropAtMs };
26014
+ } catch {
26015
+ return null;
26016
+ }
26017
+ }
26018
+
26019
+ // ../../packages/plugin-runtime/src/attached/forward-policy.ts
26020
+ import { randomUUID as randomUUID15 } from "crypto";
26021
+ import { readFileSync as readFileSync12 } from "fs";
26022
+ import { readFile, rename, writeFile } from "fs/promises";
26023
+ import { join as join18 } from "path";
26024
+
25680
26025
  // ../../packages/plugin-sdk/src/config.ts
25681
- import { existsSync as existsSync5 } from "fs";
25682
- import { join as join8 } from "path";
26026
+ import { existsSync as existsSync6 } from "fs";
26027
+ import { join as join11 } from "path";
25683
26028
 
25684
26029
  // ../../packages/plugin-sdk/src/provider-env.ts
25685
26030
  var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
@@ -25715,8 +26060,8 @@ function hostOf(url2) {
25715
26060
  }
25716
26061
  }
25717
26062
  function resolveProvider() {
25718
- const parsed = ProviderEnvSchema.safeParse(process.env);
25719
- const env = parsed.success ? parsed.data : ProviderEnvSchema.parse({});
26063
+ const parsed2 = ProviderEnvSchema.safeParse(process.env);
26064
+ const env = parsed2.success ? parsed2.data : ProviderEnvSchema.parse({});
25720
26065
  if (env.CLAUDE_CODE_USE_BEDROCK === true) return { provider: "bedrock" };
25721
26066
  if (env.CLAUDE_CODE_USE_VERTEX === true) return { provider: "vertex" };
25722
26067
  const baseUrl = env.ANTHROPIC_BASE_URL;
@@ -25733,8 +26078,8 @@ function resolveProvider() {
25733
26078
  function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
25734
26079
  try {
25735
26080
  ensureLayoutDirSync(base);
25736
- const settingsFile = join8(settingsDir(base), "settings.json");
25737
- if (existsSync5(settingsFile)) tightenFile(settingsFile);
26081
+ const settingsFile = join11(settingsDir(base), "settings.json");
26082
+ if (existsSync6(settingsFile)) tightenFile(settingsFile);
25738
26083
  } catch {
25739
26084
  }
25740
26085
  migrateLegacyLayout(base);
@@ -25757,9 +26102,9 @@ function resolveProviderSafe(resolveProviderFn) {
25757
26102
  }
25758
26103
 
25759
26104
  // ../../packages/plugin-sdk/src/config-inventory.ts
25760
- import { readdirSync as readdirSync2, readFileSync as readFileSync7, realpathSync, statSync as statSync5 } from "fs";
26105
+ import { readdirSync as readdirSync2, readFileSync as readFileSync9, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
25761
26106
  import { homedir as homedir2 } from "os";
25762
- import { basename as basename3, join as join10 } from "path";
26107
+ import { basename as basename3, join as join13 } from "path";
25763
26108
 
25764
26109
  // ../../packages/detections/src/egress/registry.ts
25765
26110
  var EXTRACTOR_VERSION = "1";
@@ -27237,10 +27582,10 @@ var localhost_ref_default = {
27237
27582
  severity: "low",
27238
27583
  matcher: {
27239
27584
  type: "regex",
27240
- pattern: "\\b(?:localhost|127\\.0\\.0\\.1|0\\.0\\.0\\.0|::1)\\b",
27585
+ 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_])",
27241
27586
  flags: "g"
27242
27587
  },
27243
- examples: ["localhost", "127.0.0.1"]
27588
+ examples: ["localhost", "127.0.0.1", "0.0.0.0", "::1"]
27244
27589
  };
27245
27590
 
27246
27591
  // ../../rules/core-code-context/stack-trace.json
@@ -28542,36 +28887,36 @@ function bundledDetections() {
28542
28887
  }
28543
28888
 
28544
28889
  // ../../packages/plugin-sdk/src/repo.ts
28545
- import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
28546
- import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
28890
+ import { existsSync as existsSync7, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
28891
+ import { basename as basename2, dirname as dirname3, isAbsolute, join as join12, sep as sep2 } from "path";
28547
28892
 
28548
28893
  // ../../packages/plugin-sdk/src/events.ts
28549
28894
  import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
28550
28895
 
28551
28896
  // ../../packages/plugin-sdk/src/isolated-scan.ts
28552
- import { existsSync as existsSync7 } from "fs";
28897
+ import { existsSync as existsSync8 } from "fs";
28553
28898
  import { fileURLToPath } from "url";
28554
28899
  import { Worker } from "worker_threads";
28555
28900
 
28556
28901
  // ../../packages/plugin-sdk/src/ignore-layers.ts
28557
28902
  var import_ignore = __toESM(require_ignore(), 1);
28558
- import { readFileSync as readFileSync8 } from "fs";
28559
- import { join as join11 } from "path";
28903
+ import { readFileSync as readFileSync10 } from "fs";
28904
+ import { join as join14 } from "path";
28560
28905
 
28561
28906
  // ../../packages/plugin-sdk/src/inventory-resolver.ts
28562
28907
  import { arch, hostname as hostname4, platform, release } from "os";
28563
28908
 
28564
28909
  // ../../packages/plugin-sdk/src/nudge.ts
28565
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
28566
- import { join as join12 } from "path";
28910
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
28911
+ import { join as join15 } from "path";
28567
28912
 
28568
28913
  // ../../packages/plugin-sdk/src/paths.ts
28569
- import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
28570
- import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
28914
+ import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
28915
+ import { basename as basename4, dirname as dirname4, sep as sep3 } from "path";
28571
28916
 
28572
28917
  // ../../packages/plugin-sdk/src/project-files.ts
28573
- import { existsSync as existsSync8, readdirSync as readdirSync4 } from "fs";
28574
- import { basename as basename5, join as join13 } from "path";
28918
+ import { existsSync as existsSync9, readdirSync as readdirSync4 } from "fs";
28919
+ import { basename as basename5, join as join16 } from "path";
28575
28920
 
28576
28921
  // ../../packages/plugin-sdk/src/provider-env-antigravity.ts
28577
28922
  var optionalBaseUrl2 = external_exports.preprocess((v) => {
@@ -28606,41 +28951,1168 @@ import { randomUUID as randomUUID14 } from "crypto";
28606
28951
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
28607
28952
 
28608
28953
  // ../../packages/plugin-sdk/src/throttle.ts
28609
- import { mkdirSync as mkdirSync3, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
28610
- import { join as join14 } from "path";
28611
-
28612
- // ../../packages/plugin-runtime/src/standalone-gateway.ts
28613
- import { randomUUID as randomUUID15 } from "crypto";
28614
-
28615
- // ../../packages/plugin-runtime/src/recorder.ts
28616
- var PLUGIN_RECORDER_BINARY = "plugin";
28954
+ import { mkdirSync as mkdirSync3, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
28955
+ import { join as join17 } from "path";
28956
+
28957
+ // ../../packages/plugin-runtime/src/attached/with-timeout.ts
28958
+ var REQUEST_TIMEOUT_MS = 2e3;
28959
+ function withTimeout(promise2, ms) {
28960
+ let timer;
28961
+ const timeout = new Promise((_, reject) => {
28962
+ timer = setTimeout(() => {
28963
+ reject(new Error("attached gateway request timed out"));
28964
+ }, ms);
28965
+ });
28966
+ promise2.catch(() => void 0);
28967
+ return Promise.race([promise2, timeout]).finally(() => {
28968
+ clearTimeout(timer);
28969
+ });
28970
+ }
28617
28971
 
28618
- // ../../packages/plugin-runtime/src/standalone-gateway.ts
28619
- var StandaloneDataGateway = class {
28620
- db;
28621
- // Kept for the fingerprint key lookup (exception.key lives beside the store).
28622
- dataDir;
28623
- // One notice per gateway — see warnRulesetDiscarded.
28624
- warnedRulesetDiscarded = false;
28625
- constructor(dataDir2, detections = [], meta3) {
28626
- this.db = openLocalDatabase(dataDir2);
28627
- this.dataDir = dataDir2;
28628
- this.db.installedPacks.recordInventory(detections, meta3);
28972
+ // ../../packages/plugin-runtime/src/attached/forward-policy.ts
28973
+ function isInvalidRequest(err) {
28974
+ return typeof err === "object" && err !== null && err.name === "RemoteRequestInvalid";
28975
+ }
28976
+ var FORWARD_BUDGET_MS = 1500;
28977
+ var DECISION_PATH_BUDGET_MS = 800;
28978
+ var BREAKER_FAILURE_THRESHOLD = 3;
28979
+ var BREAKER_COOLDOWN_MS = 3e4;
28980
+ var CLOSED = { consecutiveFailures: 0, openedAtMs: null, lastFailure: null };
28981
+ var FAILURES = /* @__PURE__ */ new Set([
28982
+ "unauthorized",
28983
+ "forbidden",
28984
+ "unreachable"
28985
+ ]);
28986
+ var FORWARD_STATE_FILENAME = "attached-state.json";
28987
+ var STATE_FILENAME = FORWARD_STATE_FILENAME;
28988
+ function parseBreakerState(raw, nowMs) {
28989
+ try {
28990
+ const parsed2 = JSON.parse(raw);
28991
+ if (typeof parsed2 !== "object" || parsed2 === null) return null;
28992
+ const record2 = parsed2;
28993
+ const failures = typeof record2.consecutiveFailures === "number" && record2.consecutiveFailures >= 0 ? record2.consecutiveFailures : 0;
28994
+ const openedAtMs = typeof record2.openedAtMs === "number" && Number.isFinite(record2.openedAtMs) && record2.openedAtMs <= nowMs ? record2.openedAtMs : null;
28995
+ const lastFailure = typeof record2.lastFailure === "string" && FAILURES.has(record2.lastFailure) ? record2.lastFailure : null;
28996
+ return { consecutiveFailures: failures, openedAtMs, lastFailure };
28997
+ } catch {
28998
+ return null;
28629
28999
  }
28630
- recordCapture(record2) {
28631
- this.db.recordCapture(record2.event, record2.findings);
28632
- return Promise.resolve();
29000
+ }
29001
+ function createForwardPolicy(deps) {
29002
+ const now = deps.now ?? (() => Date.now());
29003
+ const file2 = join18(deps.dir, STATE_FILENAME);
29004
+ let state = null;
29005
+ let loading = null;
29006
+ async function readState() {
29007
+ let raw;
29008
+ try {
29009
+ raw = await readFile(file2, "utf8");
29010
+ } catch {
29011
+ return { ...CLOSED };
29012
+ }
29013
+ return parseBreakerState(raw, now()) ?? { ...CLOSED };
28633
29014
  }
28634
- ensureInventory(ctx) {
28635
- return Promise.resolve(this.db.ensureInventory(ctx));
29015
+ async function load() {
29016
+ if (state !== null) return state;
29017
+ loading ??= readState().then((loaded) => {
29018
+ state = loaded;
29019
+ loading = null;
29020
+ return loaded;
29021
+ });
29022
+ return loading;
28636
29023
  }
28637
- recordAuditEvent(event) {
28638
- this.db.auditEvents.insertAuditEvent(event);
28639
- return Promise.resolve();
29024
+ async function persist(next) {
29025
+ state = next;
29026
+ try {
29027
+ await ensureDataDir(deps.dir);
29028
+ const tmp = `${file2}.${randomUUID15()}.tmp`;
29029
+ await writeFile(tmp, JSON.stringify(next), { encoding: "utf8", mode: DATA_FILE_MODE });
29030
+ await rename(tmp, file2);
29031
+ } catch {
29032
+ }
28640
29033
  }
28641
- // The id is minted inside the repository from the natural key — the plugin can't
28642
- // import @akasecurity/persistence to compute it, so the gateway is the boundary that
28643
- // hands the natural key across. UPSERT-take-MAX idempotent re-reads that also
29034
+ return {
29035
+ async run(op, opts) {
29036
+ const budget = opts?.decisionPath === true ? DECISION_PATH_BUDGET_MS : FORWARD_BUDGET_MS;
29037
+ let current;
29038
+ try {
29039
+ current = await load();
29040
+ } catch {
29041
+ current = { ...CLOSED };
29042
+ }
29043
+ const at = now();
29044
+ if (current.openedAtMs !== null) {
29045
+ if (at - current.openedAtMs < BREAKER_COOLDOWN_MS) {
29046
+ return { ok: false, reason: "breaker-open" };
29047
+ }
29048
+ await persist({
29049
+ consecutiveFailures: current.consecutiveFailures,
29050
+ openedAtMs: at,
29051
+ lastFailure: current.lastFailure
29052
+ });
29053
+ }
29054
+ try {
29055
+ const value = await withTimeout(op(), budget);
29056
+ if (current.openedAtMs !== null || current.consecutiveFailures > 0) {
29057
+ await persist({ ...CLOSED });
29058
+ }
29059
+ return { ok: true, value };
29060
+ } catch (err) {
29061
+ if (isInvalidRequest(err)) return { ok: false, reason: "invalid-request" };
29062
+ const reason = classifyFailure(err);
29063
+ const failures = current.consecutiveFailures + 1;
29064
+ const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
29065
+ await persist({
29066
+ consecutiveFailures: failures,
29067
+ openedAtMs: shouldOpen ? now() : null,
29068
+ lastFailure: reason
29069
+ });
29070
+ return { ok: false, reason };
29071
+ }
29072
+ }
29073
+ };
29074
+ }
29075
+
29076
+ // ../../packages/plugin-runtime/src/attached/gateway.ts
29077
+ var ACTION_STRENGTH = {
29078
+ allow: 0,
29079
+ log: 1,
29080
+ warn: 2,
29081
+ redact: 3,
29082
+ block: 4
29083
+ };
29084
+ function ruleCategoryMap(wireRules, localRules) {
29085
+ const map2 = /* @__PURE__ */ new Map();
29086
+ for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
29087
+ for (const rule of localRules ?? []) map2.set(rule.id, rule.category);
29088
+ for (const pack of bundledDetections()) {
29089
+ for (const rule of pack.rules) map2.set(rule.id, rule.category);
29090
+ }
29091
+ return map2;
29092
+ }
29093
+ function strongerOf(a, b) {
29094
+ if (a === null) return b;
29095
+ if (b === null) return a;
29096
+ return ACTION_STRENGTH[a] >= ACTION_STRENGTH[b] ? a : b;
29097
+ }
29098
+ function policyKey(policy) {
29099
+ return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
29100
+ }
29101
+ function floorFor(policy, categoryByRuleId) {
29102
+ const category = "category" in policy.target ? policy.target.category : categoryByRuleId.get(policy.target.ruleId);
29103
+ return category === void 0 ? null : DEFAULT_ACTIONS[category];
29104
+ }
29105
+ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
29106
+ const merged = /* @__PURE__ */ new Map();
29107
+ const disabled = [];
29108
+ const remoteCategoryAction = /* @__PURE__ */ new Map();
29109
+ for (const policy of remotePolicies) {
29110
+ if (!policy.enabled) continue;
29111
+ if (!("category" in policy.target)) continue;
29112
+ if (remoteCategoryAction.has(policy.target.category)) continue;
29113
+ const floor = floorFor(policy, categoryByRuleId);
29114
+ remoteCategoryAction.set(
29115
+ policy.target.category,
29116
+ floor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[floor] ? floor : policy.action
29117
+ );
29118
+ }
29119
+ for (const policy of localPolicies) {
29120
+ if (!policy.enabled) {
29121
+ disabled.push(policy);
29122
+ continue;
29123
+ }
29124
+ const key = policyKey(policy);
29125
+ if (merged.has(key)) continue;
29126
+ let remoteFloor = null;
29127
+ if ("ruleId" in policy.target) {
29128
+ const category = categoryByRuleId.get(policy.target.ruleId);
29129
+ if (category !== void 0) remoteFloor = remoteCategoryAction.get(category) ?? null;
29130
+ }
29131
+ merged.set(
29132
+ key,
29133
+ remoteFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[remoteFloor] ? { ...policy, action: remoteFloor } : policy
29134
+ );
29135
+ }
29136
+ const localCategoryAction = /* @__PURE__ */ new Map();
29137
+ for (const policy of merged.values()) {
29138
+ if ("category" in policy.target) localCategoryAction.set(policy.target.category, policy.action);
29139
+ }
29140
+ for (const policy of remotePolicies) {
29141
+ if (!policy.enabled) {
29142
+ disabled.push(policy);
29143
+ continue;
29144
+ }
29145
+ const key = policyKey(policy);
29146
+ const floor = floorFor(policy, categoryByRuleId);
29147
+ let localFloor = null;
29148
+ if ("ruleId" in policy.target) {
29149
+ const category = categoryByRuleId.get(policy.target.ruleId);
29150
+ if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
29151
+ }
29152
+ const effectiveFloor = strongerOf(floor, localFloor);
29153
+ const clamped = effectiveFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[effectiveFloor] ? { ...policy, action: effectiveFloor } : policy;
29154
+ const existing = merged.get(key);
29155
+ if (existing === void 0) {
29156
+ merged.set(key, clamped);
29157
+ continue;
29158
+ }
29159
+ if (ACTION_STRENGTH[clamped.action] > ACTION_STRENGTH[existing.action]) {
29160
+ merged.set(key, clamped);
29161
+ }
29162
+ }
29163
+ return [...merged.values(), ...disabled];
29164
+ }
29165
+ var AttachedDataGateway = class {
29166
+ constructor(deps) {
29167
+ this.deps = deps;
29168
+ }
29169
+ deps;
29170
+ /**
29171
+ * The control plane's OWN resolution of this session's inventory, captured by
29172
+ * ensureInventory. Null until the first successful forward — and it stays
29173
+ * null for the whole session when the control plane is unreachable, which is fine:
29174
+ * reKeyForForward then leaves the event's ids alone and the control plane resolves
29175
+ * what it can from the descriptors it already has.
29176
+ */
29177
+ remoteInventory = null;
29178
+ // ---------------------------------------------------------------------
29179
+ // Writes: local first, then forward.
29180
+ // ---------------------------------------------------------------------
29181
+ async recordCapture(record2) {
29182
+ await this.deps.local.recordCapture(record2);
29183
+ await this.deps.forward.run(
29184
+ () => this.deps.client.ingestEvents({
29185
+ events: [record2.event],
29186
+ ...record2.dedupe ? { dedupe: record2.dedupe } : {}
29187
+ }),
29188
+ { decisionPath: true }
29189
+ );
29190
+ }
29191
+ async ensureInventory(ctx) {
29192
+ const resolved = await this.deps.local.ensureInventory(ctx);
29193
+ const remote = await this.deps.forward.run(() => this.deps.client.ingestInventory(ctx));
29194
+ this.remoteInventory = remote.ok ? remote.value : null;
29195
+ const snapshot = await (async () => {
29196
+ try {
29197
+ return await this.deps.posture?.prepare() ?? null;
29198
+ } catch {
29199
+ return null;
29200
+ }
29201
+ })();
29202
+ if (snapshot) {
29203
+ try {
29204
+ await withTimeout(
29205
+ this.deps.posture?.send(snapshot) ?? Promise.resolve(),
29206
+ REQUEST_TIMEOUT_MS
29207
+ );
29208
+ } catch {
29209
+ }
29210
+ }
29211
+ return resolved;
29212
+ }
29213
+ // The id is minted CLIENT-side and stored verbatim: the control plane does NOT
29214
+ // re-key it. `pgAuditValues` writes `id: event.id` and carries tenancy in
29215
+ // its own scoping columns, so the device and the forwarded copy
29216
+ // share one id space — which is what makes a re-post idempotent at all.
29217
+ //
29218
+ // Re-posts collapse via `onConflictDoUpdate` on the `id` PK, guarded by
29219
+ // `setWhere eventType = 'session'` (NOT onConflictDoNothing). That guard is
29220
+ // what makes an attached retry safe: a capture-stubbed session row can still
29221
+ // be HEALED by the authoritative root, while a duplicate non-session event —
29222
+ // a retried tool_call, exactly this path — can never stomp a populated row.
29223
+ async recordAuditEvent(event) {
29224
+ await this.deps.local.recordAuditEvent(event);
29225
+ await this.deps.forward.run(
29226
+ () => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
29227
+ );
29228
+ }
29229
+ // Attached `llm_call` is written locally by the inner gateway, then routed to
29230
+ // the control plane through the existing `recordAuditEvent` ingest (no dedicated
29231
+ // client method yet) by pre-building the audit event from the natural key.
29232
+ // The forward goes DIRECTLY to the client rather than through this.recordAuditEvent,
29233
+ // which would write the event to the local store a second time.
29234
+ async recordLlmCall(input) {
29235
+ await this.deps.local.recordLlmCall(input);
29236
+ await this.deps.forward.run(
29237
+ () => this.deps.client.recordAuditEvent(
29238
+ reKeyForForward(llmAuditEvent(input), this.remoteInventory)
29239
+ )
29240
+ );
29241
+ }
29242
+ /**
29243
+ * Forward one batch, item by item, under ONE aggregate deadline.
29244
+ *
29245
+ * Per-item budgets bound each request and nothing bounded their sum — see
29246
+ * BATCH_FORWARD_BUDGET_MS. When the deadline passes the remainder is dropped
29247
+ * rather than sent: the local write has already succeeded, so every caller
29248
+ * has a correct result to return, and a drop is the outcome this path is
29249
+ * built to accept (G8) where a blown hook timeout is not.
29250
+ *
29251
+ * Serial rather than concurrent on purpose. Firing N requests at once would
29252
+ * trade a latency problem for a burst the plane's own per-key rate limiting
29253
+ * would answer with the refusals the breaker then counts.
29254
+ *
29255
+ * WHAT IS DROPPED IS COUNTED. Every other forward failure ends in
29256
+ * `ForwardPolicy.run`'s catch and moves the breaker's file, which is what
29257
+ * lets status call the forward unhealthy; this path returns BEFORE `run` is
29258
+ * reached, so without the tally in `forward-drops.ts` a slow-but-answering
29259
+ * plane produces no failures, keeps the breaker closed, renders a healthy
29260
+ * block, and discards the tail of every batch indefinitely.
29261
+ */
29262
+ async forwardBatch(inputs, toEvent) {
29263
+ const deadline = Date.now() + BATCH_FORWARD_BUDGET_MS;
29264
+ for (let i = 0; i < inputs.length; i += 1) {
29265
+ const now = Date.now();
29266
+ if (now >= deadline) {
29267
+ recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
29268
+ return;
29269
+ }
29270
+ const input = inputs[i];
29271
+ await this.deps.forward.run(
29272
+ () => this.deps.client.recordAuditEvent(reKeyForForward(toEvent(input), this.remoteInventory))
29273
+ );
29274
+ }
29275
+ }
29276
+ // Delegated as a BATCH rather than looped over recordLlmCall: the inner
29277
+ // gateway may write the whole batch in one local transaction, and looping
29278
+ // here would replace that with N separate local writes.
29279
+ async recordLlmCalls(inputs) {
29280
+ await this.deps.local.recordLlmCalls(inputs);
29281
+ await this.forwardBatch(inputs, (input) => llmAuditEvent(input));
29282
+ }
29283
+ // `input.inspections` (secrets detected client-side in the tool's masked
29284
+ // target) ride along on the request's `inspections` field — the control plane
29285
+ // persists each as an inspection_findings row linked to this audit event
29286
+ // (see RecordAuditEventRequest in @akasecurity/schema). The masked
29287
+ // `target` already rides `input.attributes`, so no raw secret leaks either
29288
+ // way — this only stops the FINDING row itself from being dropped.
29289
+ async recordToolCalls(inputs) {
29290
+ await this.deps.local.recordToolCalls(inputs);
29291
+ await this.forwardBatch(inputs, (input) => toolAuditEvent(input));
29292
+ }
29293
+ // Forwarded as a `config_scan` audit event: there is no dedicated
29294
+ // config-scan ingest endpoint, and the audit-event door is the one the
29295
+ // control plane already opens for client-minted, idempotent records.
29296
+ //
29297
+ // ONLY `scanEvent` CROSSES, and unlike `recordCapture` the plane cannot
29298
+ // re-derive the rest. A `ConfigScanRecord` is four things committed together
29299
+ // locally — the inventory `items`, this audit event, and the posture
29300
+ // `definitions`/`findings` that reference it — and three of them stay on the
29301
+ // device. Say that plainly rather than let the asymmetry with `recordCapture`
29302
+ // read as the same argument: there, findings are omitted BECAUSE the plane
29303
+ // re-derives them from `Event.content`; here there is no content to re-derive
29304
+ // from, so what is omitted is simply not sent.
29305
+ //
29306
+ // That is the wire contract as it stands rather than an oversight to patch
29307
+ // here. `items` has no route at all, and `RecordAuditEventRequest.inspections`
29308
+ // is documented as tool-call findings — widening it to carry config-scan
29309
+ // findings is an egress change (a posture finding's `maskedMatch` holds the
29310
+ // matched command) and a decision about what an attached deployment is
29311
+ // entitled to, not a bug fix. An attached machine's config posture therefore
29312
+ // reaches the plane as the event only; the dashboard's own view of it is the
29313
+ // local store.
29314
+ async recordConfigScan(record2) {
29315
+ await this.deps.local.recordConfigScan(record2);
29316
+ await this.deps.forward.run(
29317
+ () => this.deps.client.recordAuditEvent(reKeyForForward(record2.scanEvent, this.remoteInventory))
29318
+ );
29319
+ }
29320
+ async recordBlockedDetection(entry) {
29321
+ return this.deps.local.recordBlockedDetection(entry);
29322
+ }
29323
+ /**
29324
+ * LOCAL-ONLY, deliberately. The shares API is read-plus-decision-override
29325
+ * with no egress ingest endpoint, so there is nothing to forward to; adding a
29326
+ * forward here would be inventing a wire contract that does not exist. The
29327
+ * local write is the whole operation, and its summary is the real one — the
29328
+ * scanner reads a throw as a FAILED WRITE and skips its ledger commit, so
29329
+ * returning the inner gateway's result keeps the retry semantics honest.
29330
+ */
29331
+ async recordProjectEgress(input) {
29332
+ return this.deps.local.recordProjectEgress(input);
29333
+ }
29334
+ // ---------------------------------------------------------------------
29335
+ // Reads and device-local ledgers: pure delegation.
29336
+ // ---------------------------------------------------------------------
29337
+ async configInventoryReport() {
29338
+ return this.deps.local.configInventoryReport();
29339
+ }
29340
+ async readSessionProvider(sessionId) {
29341
+ return this.deps.local.readSessionProvider(sessionId);
29342
+ }
29343
+ async facets() {
29344
+ return this.deps.local.facets();
29345
+ }
29346
+ /**
29347
+ * Delegated UNMODIFIED — including its refusals.
29348
+ *
29349
+ * This is a fail-secure boundary: it decides whether an approved exception
29350
+ * lets a blocked action through. Under local-first the local store owns the
29351
+ * exception ledger, so the honest answer is whatever it says; wrapping this
29352
+ * in a fallback (`catch { return true }`, or defaulting on a timeout) would
29353
+ * turn a store error into a granted bypass. If the inner gateway rejects,
29354
+ * this rejects, and the runtime's own handling decides — which is asserted
29355
+ * end-to-end through runtime.capture rather than here.
29356
+ */
29357
+ async consumeException(id) {
29358
+ return this.deps.local.consumeException(id);
29359
+ }
29360
+ async recentFindings(opts) {
29361
+ return this.deps.local.recentFindings(opts);
29362
+ }
29363
+ async healthSummary() {
29364
+ return this.deps.local.healthSummary();
29365
+ }
29366
+ async activityByDay(days) {
29367
+ return this.deps.local.activityByDay(days);
29368
+ }
29369
+ async tokenReports() {
29370
+ return this.deps.local.tokenReports();
29371
+ }
29372
+ async knownContentHashes() {
29373
+ return this.deps.local.knownContentHashes();
29374
+ }
29375
+ async scanLedger(rulesetHash) {
29376
+ return this.deps.local.scanLedger(rulesetHash);
29377
+ }
29378
+ async recordScanned(entries) {
29379
+ return this.deps.local.recordScanned(entries);
29380
+ }
29381
+ async getRuleProbeVerdict(ruleKey) {
29382
+ return this.deps.local.getRuleProbeVerdict(ruleKey);
29383
+ }
29384
+ async setRuleProbeVerdict(ruleKey, verdict, worstProbeMs) {
29385
+ return this.deps.local.setRuleProbeVerdict(ruleKey, verdict, worstProbeMs);
29386
+ }
29387
+ async openAtRestKeysForPath(path) {
29388
+ return this.deps.local.openAtRestKeysForPath(path);
29389
+ }
29390
+ async resolvedAtRestKeysForPath(path) {
29391
+ return this.deps.local.resolvedAtRestKeysForPath(path);
29392
+ }
29393
+ async insertResolution(input) {
29394
+ return this.deps.local.insertResolution(input);
29395
+ }
29396
+ async close() {
29397
+ return this.deps.local.close();
29398
+ }
29399
+ // ---------------------------------------------------------------------
29400
+ // Policy
29401
+ // ---------------------------------------------------------------------
29402
+ async getPolicyBundle() {
29403
+ const local = await this.deps.local.getPolicyBundle();
29404
+ const cached2 = await (async () => {
29405
+ try {
29406
+ return await this.deps.readCachedBundle();
29407
+ } catch {
29408
+ return null;
29409
+ }
29410
+ })();
29411
+ if (cached2 === null) return local;
29412
+ const byRuleId = /* @__PURE__ */ new Map();
29413
+ for (const rule of [...local.rules ?? [], ...cached2.rules ?? []]) {
29414
+ if (!byRuleId.has(rule.id)) byRuleId.set(rule.id, rule);
29415
+ }
29416
+ const rules = [...byRuleId.values()];
29417
+ return {
29418
+ ...local,
29419
+ // The remote version identifies the composed bundle for the poller.
29420
+ version: cached2.version,
29421
+ rules,
29422
+ policies: mergeRaiseOnly(
29423
+ local.policies,
29424
+ cached2.policies,
29425
+ ruleCategoryMap(cached2.rules, local.rules)
29426
+ ),
29427
+ customKeywords: [...local.customKeywords, ...cached2.customKeywords]
29428
+ // `rulesComplete` is a STANDALONE-ONLY signal (the user's local installed
29429
+ // snapshot) and is taken from the LOCAL bundle only — never from the wire
29430
+ // or the on-disk cache. Honoring a cached one would hand the control plane, or
29431
+ // anything able to write policy-cache.json, a kill-switch over the
29432
+ // compiled-in bundled packs: `{ rulesComplete: true, rules: [] }` would
29433
+ // zero local detection. Spread from `local` above, and deliberately not
29434
+ // re-read from `cached` here.
29435
+ //
29436
+ // THREE MORE OF THE CACHED BUNDLE'S FIELDS ARE DROPPED, each on purpose,
29437
+ // and each named here so a reader can tell a decision from an omission:
29438
+ //
29439
+ // `exceptions` — an exception SUPPRESSES a detection, so honoring
29440
+ // one from an unsigned on-disk cache would let
29441
+ // anything able to write that file turn rules off.
29442
+ // Every other field this merge accepts can only
29443
+ // RAISE enforcement; this is the one that cannot,
29444
+ // so it stays local-only until the bundle is
29445
+ // signed. Exceptions remain a device-local ledger.
29446
+ // `reversibleRuleIds` — the Redact & Vault archetype makes a redaction
29447
+ // recoverable, which is a CUSTODY change: it puts
29448
+ // the detected value in the local vault instead of
29449
+ // destroying it. Taking that instruction from the
29450
+ // cache would let a remote party turn one-way
29451
+ // redaction into retention. Dropping it keeps the
29452
+ // one-way behaviour, which the schema itself calls
29453
+ // "the safe direction to default".
29454
+ // `ruleVersions` — remote rules fall back to their own spec version.
29455
+ // Cosmetic rather than protective: it only affects
29456
+ // how a finding is version-attributed, and the two
29457
+ // sides may therefore attribute org rules
29458
+ // differently. Worth carrying once there is a
29459
+ // reader that needs it; nothing reads it today.
29460
+ };
29461
+ }
29462
+ // ---------------------------------------------------------------------
29463
+ // LocalStoreMaintenance — by delegation (D3).
29464
+ //
29465
+ // Implementing these is what actually closes the skipped-local-maintenance
29466
+ // gap: the OSS structural guard `hasLocalStoreMaintenance()` is satisfied by
29467
+ // any object carrying all five, so the composite qualifies and SessionStart
29468
+ // runs maintenance on the device's real store.
29469
+ //
29470
+ // ⚠ Two of the five are SYNCHRONOUS and must stay that way. `handle-session-start`
29471
+ // calls `capWarnEraEnforcement` without `await` and uses `staleBinaryNotice`'s
29472
+ // return value directly; declaring them `async` here would hand those call
29473
+ // sites a Promise and silently break both.
29474
+ // ---------------------------------------------------------------------
29475
+ async sweepTerminalExceptions(retentionMs) {
29476
+ return this.deps.local.sweepTerminalExceptions(retentionMs);
29477
+ }
29478
+ capWarnEraEnforcement(policyMode) {
29479
+ return this.deps.local.capWarnEraEnforcement(policyMode);
29480
+ }
29481
+ async recordProjectFiles(projectId, scan2) {
29482
+ return this.deps.local.recordProjectFiles(projectId, scan2);
29483
+ }
29484
+ async reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot) {
29485
+ return this.deps.local.reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot);
29486
+ }
29487
+ staleBinaryNotice(currentVersion) {
29488
+ return this.deps.local.staleBinaryNotice(currentVersion);
29489
+ }
29490
+ };
29491
+ function reKeyForForward(event, remote) {
29492
+ if (remote === null) {
29493
+ const stripped = { ...event };
29494
+ delete stripped.hostId;
29495
+ delete stripped.harnessId;
29496
+ delete stripped.sourceProjectId;
29497
+ return stripped;
29498
+ }
29499
+ const rekeyed = { ...event };
29500
+ delete rekeyed.hostId;
29501
+ delete rekeyed.harnessId;
29502
+ delete rekeyed.sourceProjectId;
29503
+ if (remote.hostId !== void 0) rekeyed.hostId = remote.hostId;
29504
+ if (remote.harnessId !== void 0) rekeyed.harnessId = remote.harnessId;
29505
+ if (remote.sourceProjectId !== void 0) rekeyed.sourceProjectId = remote.sourceProjectId;
29506
+ return rekeyed;
29507
+ }
29508
+ var BATCH_FORWARD_BUDGET_MS = 3e3;
29509
+ function llmAuditEvent(input) {
29510
+ return {
29511
+ id: llmCallId(input.sessionId, input.messageId),
29512
+ eventType: "llm_call",
29513
+ startedAt: input.startedAt,
29514
+ parentId: input.parentId,
29515
+ rootSessionId: input.rootSessionId,
29516
+ attributes: input.attributes
29517
+ };
29518
+ }
29519
+ function toolAuditEvent(input) {
29520
+ return {
29521
+ id: toolCallId(input.sessionId, input.toolUseId),
29522
+ eventType: "tool_call",
29523
+ startedAt: input.startedAt,
29524
+ parentId: input.parentId,
29525
+ rootSessionId: input.rootSessionId,
29526
+ attributes: input.attributes,
29527
+ inspections: input.inspections
29528
+ };
29529
+ }
29530
+
29531
+ // ../../packages/plugin-runtime/src/attached/policy-store.ts
29532
+ import { randomUUID as randomUUID16 } from "crypto";
29533
+ import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
29534
+ import { join as join19 } from "path";
29535
+
29536
+ // ../../packages/plugin-runtime/src/attached/atomic-publish.ts
29537
+ import { rename as rename2 } from "fs/promises";
29538
+ var RETRYABLE = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY"]);
29539
+ var ATTEMPTS = 5;
29540
+ var delay = (ms) => new Promise((resolve2) => {
29541
+ setTimeout(resolve2, ms);
29542
+ });
29543
+ async function publishByRename(tmp, file2, move = rename2) {
29544
+ for (let attempt = 1; ; attempt += 1) {
29545
+ try {
29546
+ await move(tmp, file2);
29547
+ return;
29548
+ } catch (err) {
29549
+ const code = err.code;
29550
+ if (attempt >= ATTEMPTS || code === void 0 || !RETRYABLE.has(code)) throw err;
29551
+ await delay(attempt * 10);
29552
+ }
29553
+ }
29554
+ }
29555
+
29556
+ // ../../packages/plugin-runtime/src/attached/policy-store.ts
29557
+ function createPolicyStore(dir = dataDir()) {
29558
+ const file2 = join19(dir, "policy-cache.json");
29559
+ async function read() {
29560
+ try {
29561
+ const raw = await readFile2(file2, "utf8");
29562
+ const parsed2 = JSON.parse(raw);
29563
+ if (typeof parsed2 !== "object" || parsed2 === null) return null;
29564
+ const record2 = parsed2;
29565
+ const bundle = PolicyBundle.parse(record2.bundle);
29566
+ const fetchedAtMs = typeof record2.fetchedAtMs === "number" ? record2.fetchedAtMs : 0;
29567
+ const etag = typeof record2.etag === "string" ? record2.etag : void 0;
29568
+ return { bundle, fetchedAtMs, ...etag === void 0 ? {} : { etag } };
29569
+ } catch {
29570
+ return null;
29571
+ }
29572
+ }
29573
+ async function write(bundle, etag) {
29574
+ await ensureDataDir(dir);
29575
+ const stored = {
29576
+ bundle,
29577
+ fetchedAtMs: Date.now(),
29578
+ ...etag === void 0 ? {} : { etag }
29579
+ };
29580
+ const tmp = `${file2}.${randomUUID16()}.tmp`;
29581
+ try {
29582
+ await writeFile2(tmp, JSON.stringify(stored), {
29583
+ encoding: "utf8",
29584
+ mode: DATA_FILE_MODE,
29585
+ flag: "wx"
29586
+ });
29587
+ await publishByRename(tmp, file2);
29588
+ } catch (err) {
29589
+ await rm(tmp, { force: true }).catch(() => void 0);
29590
+ throw err;
29591
+ }
29592
+ }
29593
+ return { read, write, file: file2 };
29594
+ }
29595
+
29596
+ // ../../packages/remote/src/http.ts
29597
+ import { request as httpRequest } from "http";
29598
+ import { request as httpsRequest } from "https";
29599
+ var DEFAULT_TIMEOUT_MS = 1e4;
29600
+ var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
29601
+ var RemoteRequestError = class extends Error {
29602
+ constructor(status) {
29603
+ super(`control-plane request failed with status ${String(status)}`);
29604
+ this.status = status;
29605
+ this.name = "RemoteRequestError";
29606
+ }
29607
+ status;
29608
+ };
29609
+ var RemoteRequestInvalid = class extends Error {
29610
+ constructor(route, cause) {
29611
+ super(`refusing to send a malformed body to ${route}`);
29612
+ this.cause = cause;
29613
+ this.name = "RemoteRequestInvalid";
29614
+ }
29615
+ cause;
29616
+ };
29617
+ var RemoteResponseInvalid = class extends Error {
29618
+ constructor(route, detail) {
29619
+ super(`control plane answered ${route} with ${detail}`);
29620
+ this.name = "RemoteResponseInvalid";
29621
+ }
29622
+ };
29623
+ var RemoteTransportError = class extends Error {
29624
+ /**
29625
+ * The status the peer sent, when headers arrived and only the BODY was
29626
+ * refused.
29627
+ *
29628
+ * Undefined for the ordinary case this class was written for — no answer at
29629
+ * all. It exists because two paths reject after a status has already been
29630
+ * delivered: an oversized body and an aborted response. Discarding it there
29631
+ * reported a deployment answering 401 with a verbose body as a network
29632
+ * outage, which sends the reader to look at their network instead of their
29633
+ * credential.
29634
+ */
29635
+ constructor(reason, status) {
29636
+ super(`control-plane request did not complete: ${reason}`);
29637
+ this.status = status;
29638
+ this.name = "RemoteTransportError";
29639
+ }
29640
+ status;
29641
+ };
29642
+ async function send(options) {
29643
+ const url2 = new URL(options.url);
29644
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
29645
+ const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
29646
+ const requestOptions = {
29647
+ method: options.method,
29648
+ headers: {
29649
+ // CALLER HEADERS FIRST, so this module's own are not overridable. Spread
29650
+ // last they win, and two of the values below are ones no caller may
29651
+ // replace: `x-api-key` is the credential, and `content-length` is the
29652
+ // byte count that stops a multi-byte body being truncated by the
29653
+ // receiver. `SendOptions.headers` is a free-form record on an exported
29654
+ // function, so "no caller does that today" is not the guarantee to rely
29655
+ // on. The one header any caller actually passes — `if-none-match` on the
29656
+ // conditional GET — is untouched by this order.
29657
+ ...options.headers,
29658
+ // The credential. One header, matching what the deployment authenticates
29659
+ // on; a second copy in an `Authorization` header would be one more place
29660
+ // it can be logged by an intermediary for no gain.
29661
+ "x-api-key": options.apiKey,
29662
+ accept: "application/json",
29663
+ ...options.body === void 0 ? {} : {
29664
+ "content-type": "application/json",
29665
+ // Byte length, not string length: a multi-byte body sent with a
29666
+ // character count is truncated by the receiver.
29667
+ "content-length": String(Buffer.byteLength(options.body))
29668
+ }
29669
+ }
29670
+ };
29671
+ return new Promise((resolve2, reject) => {
29672
+ let settled = false;
29673
+ const fail = (reason, status) => {
29674
+ if (settled) return;
29675
+ settled = true;
29676
+ reject(new RemoteTransportError(reason, status));
29677
+ };
29678
+ const req = send_(url2, requestOptions, (res) => {
29679
+ const chunks = [];
29680
+ let size = 0;
29681
+ res.on("data", (chunk) => {
29682
+ size += chunk.length;
29683
+ if (size > MAX_RESPONSE_BYTES) {
29684
+ fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
29685
+ res.destroy();
29686
+ req.destroy();
29687
+ return;
29688
+ }
29689
+ chunks.push(chunk);
29690
+ });
29691
+ res.on("aborted", () => {
29692
+ fail("the response was aborted", res.statusCode);
29693
+ });
29694
+ res.on("end", () => {
29695
+ if (settled) return;
29696
+ settled = true;
29697
+ resolve2({
29698
+ status: res.statusCode ?? 0,
29699
+ headers: res.headers,
29700
+ body: Buffer.concat(chunks).toString("utf8")
29701
+ });
29702
+ });
29703
+ });
29704
+ const deadline = setTimeout(() => {
29705
+ fail(`no response within ${String(timeoutMs)}ms`);
29706
+ req.destroy();
29707
+ }, timeoutMs);
29708
+ deadline.unref();
29709
+ req.on("upgrade", (_res, socket) => {
29710
+ fail("the deployment answered with a protocol upgrade");
29711
+ socket.destroy();
29712
+ });
29713
+ req.on("close", () => {
29714
+ fail("the connection closed before a response was read");
29715
+ clearTimeout(deadline);
29716
+ });
29717
+ req.on("error", (err) => {
29718
+ fail(err.message);
29719
+ });
29720
+ if (options.body !== void 0) req.write(options.body);
29721
+ req.end();
29722
+ });
29723
+ }
29724
+
29725
+ // ../../packages/remote/src/client.ts
29726
+ var ROUTES = {
29727
+ events: "/v1/events",
29728
+ auditEvents: "/v1/audit-events",
29729
+ inventory: "/v1/inventory",
29730
+ storePosture: "/v1/store-posture",
29731
+ policyBundle: "/v1/policy-bundle",
29732
+ whoami: "/v1/plugin/whoami"
29733
+ };
29734
+ function headerValue(response, name) {
29735
+ const raw = response.headers[name];
29736
+ if (raw === void 0) return void 0;
29737
+ return Array.isArray(raw) ? raw[0] : raw;
29738
+ }
29739
+ function okBody(response) {
29740
+ if (response.status < 200 || response.status >= 300) {
29741
+ throw new RemoteRequestError(response.status);
29742
+ }
29743
+ return response.body;
29744
+ }
29745
+ function parsed(schema, body, route) {
29746
+ let json2;
29747
+ try {
29748
+ json2 = JSON.parse(body);
29749
+ } catch {
29750
+ throw new RemoteResponseInvalid(route, "a body that is not JSON");
29751
+ }
29752
+ const result = schema.safeParse(json2);
29753
+ if (!result.success) {
29754
+ throw new RemoteResponseInvalid(route, "a body this client cannot read");
29755
+ }
29756
+ return result.data;
29757
+ }
29758
+ function withoutTrailingSlashes(endpoint) {
29759
+ let end = endpoint.length;
29760
+ while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
29761
+ return endpoint.slice(0, end);
29762
+ }
29763
+ var SLASH = "/".charCodeAt(0);
29764
+ function createRemoteClient(options) {
29765
+ const base = withoutTrailingSlashes(options.endpoint);
29766
+ const url2 = (route) => `${base}${route}`;
29767
+ const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
29768
+ return {
29769
+ async ingestEvents(batch) {
29770
+ const response = await send({
29771
+ ...common,
29772
+ method: "POST",
29773
+ url: url2(ROUTES.events),
29774
+ body: JSON.stringify(batch)
29775
+ });
29776
+ return parsed(IngestAck, okBody(response), ROUTES.events);
29777
+ },
29778
+ async ingestInventory(context) {
29779
+ const response = await send({
29780
+ ...common,
29781
+ method: "POST",
29782
+ url: url2(ROUTES.inventory),
29783
+ body: JSON.stringify(context)
29784
+ });
29785
+ return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
29786
+ },
29787
+ async recordAuditEvent(event) {
29788
+ const validated = RecordAuditEventRequest.safeParse(event);
29789
+ if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
29790
+ const submission = validated.data;
29791
+ const response = await send({
29792
+ ...common,
29793
+ method: "POST",
29794
+ url: url2(ROUTES.auditEvents),
29795
+ body: JSON.stringify(submission)
29796
+ });
29797
+ okBody(response);
29798
+ },
29799
+ async reportStorePosture(snapshot) {
29800
+ const response = await send({
29801
+ ...common,
29802
+ method: "POST",
29803
+ url: url2(ROUTES.storePosture),
29804
+ body: JSON.stringify(snapshot)
29805
+ });
29806
+ okBody(response);
29807
+ },
29808
+ async getPolicyBundle(etag) {
29809
+ const response = await send({
29810
+ ...common,
29811
+ method: "GET",
29812
+ url: url2(ROUTES.policyBundle),
29813
+ ...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
29814
+ });
29815
+ if (response.status === 304) {
29816
+ return { changed: false, etag: headerValue(response, "etag") ?? etag };
29817
+ }
29818
+ const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
29819
+ return { changed: true, bundle, etag: headerValue(response, "etag") };
29820
+ },
29821
+ async whoami() {
29822
+ const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
29823
+ return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
29824
+ }
29825
+ };
29826
+ }
29827
+
29828
+ // ../../packages/plugin-runtime/src/attached/posture-reporter.ts
29829
+ var POSTURE_REPORT_INTERVAL_MS = 60 * 60 * 1e3;
29830
+ function createPostureReporter(deps) {
29831
+ async function prepare() {
29832
+ try {
29833
+ const state = await withTimeout(deps.store.read(), REQUEST_TIMEOUT_MS);
29834
+ if (state === null) return null;
29835
+ const nowMs = deps.now();
29836
+ const elapsed = nowMs - state.lastAttemptedAtMs;
29837
+ if (elapsed >= 0 && elapsed < POSTURE_REPORT_INTERVAL_MS) return null;
29838
+ try {
29839
+ await withTimeout(deps.store.markAttempted(state.deviceId, nowMs), REQUEST_TIMEOUT_MS);
29840
+ } catch {
29841
+ }
29842
+ const { readError, ...measurement } = deps.readStore();
29843
+ if (readError) return null;
29844
+ let plugin;
29845
+ try {
29846
+ plugin = await deps.pluginBlock?.();
29847
+ } catch {
29848
+ plugin = void 0;
29849
+ }
29850
+ return {
29851
+ deviceId: state.deviceId,
29852
+ hostname: deps.hostname(),
29853
+ capturedAt: nowMs,
29854
+ ...measurement,
29855
+ // Omit the key rather than spread an explicit `undefined` —
29856
+ // exactOptionalPropertyTypes distinguishes the two, and the bridge in
29857
+ // factory.ts keys on presence.
29858
+ ...plugin === void 0 ? {} : { plugin }
29859
+ };
29860
+ } catch {
29861
+ return null;
29862
+ }
29863
+ }
29864
+ async function send2(snapshot) {
29865
+ try {
29866
+ await deps.report(snapshot);
29867
+ } catch {
29868
+ }
29869
+ }
29870
+ return { prepare, send: send2 };
29871
+ }
29872
+
29873
+ // ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
29874
+ import { statSync as statSync9 } from "fs";
29875
+ import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
29876
+
29877
+ // ../../packages/plugin-runtime/src/attached/action-counts.ts
29878
+ function emptyActionCounts() {
29879
+ return Object.fromEntries(ACTION_TAKEN_KEYS.map((a) => [a, 0]));
29880
+ }
29881
+ function isActionTaken(value) {
29882
+ return ACTION_TAKEN_KEYS.includes(value);
29883
+ }
29884
+
29885
+ // ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
29886
+ var CAPTURE_EVENT_TYPES_SQL2 = `('prompt','response','code_change','tool_use')`;
29887
+ function isSchemaAbsent(err) {
29888
+ return err instanceof Error && /no such table/i.test(err.message);
29889
+ }
29890
+ function emptyReadout(readError = false) {
29891
+ const byAction = emptyActionCounts();
29892
+ return {
29893
+ storePresent: false,
29894
+ schemaVersion: null,
29895
+ findingsTotal: 0,
29896
+ findingsFirstAt: null,
29897
+ findingsLastAt: null,
29898
+ packs: [],
29899
+ policyCounts: { total: 0, disabled: 0, byAction },
29900
+ readError
29901
+ };
29902
+ }
29903
+ function readStorePosture(dbPath2) {
29904
+ try {
29905
+ statSync9(dbPath2);
29906
+ } catch (err) {
29907
+ const code = err.code;
29908
+ if (code === "ENOENT" || code === "ENOTDIR") return emptyReadout();
29909
+ return emptyReadout(true);
29910
+ }
29911
+ let db = null;
29912
+ let version2 = null;
29913
+ let packs = [];
29914
+ let policyCounts = {
29915
+ total: 0,
29916
+ disabled: 0,
29917
+ byAction: emptyActionCounts()
29918
+ };
29919
+ let findingsTotal = 0;
29920
+ let findingsFirstAt = null;
29921
+ let findingsLastAt = null;
29922
+ const currentReadout = () => ({
29923
+ storePresent: true,
29924
+ schemaVersion: version2,
29925
+ findingsTotal,
29926
+ findingsFirstAt,
29927
+ findingsLastAt,
29928
+ packs,
29929
+ policyCounts,
29930
+ readError: false
29931
+ });
29932
+ try {
29933
+ db = new DatabaseSync3(dbPath2, { readOnly: true });
29934
+ db.exec("PRAGMA busy_timeout = 2000");
29935
+ version2 = db.prepare("PRAGMA user_version").get().user_version;
29936
+ try {
29937
+ const packRows = db.prepare(
29938
+ `SELECT namespace, pack_id, version, enabled, updated_at FROM installed_packs ORDER BY namespace, pack_id`
29939
+ ).all();
29940
+ packs = packRows.map((r) => ({
29941
+ packId: `${r.namespace}/${r.pack_id}`,
29942
+ version: r.version,
29943
+ enabled: r.enabled !== 0,
29944
+ updatedAt: r.updated_at == null ? null : String(r.updated_at)
29945
+ }));
29946
+ } catch (err) {
29947
+ if (!isSchemaAbsent(err)) throw err;
29948
+ }
29949
+ try {
29950
+ const policyRows = db.prepare(`SELECT action, enabled FROM policies`).all();
29951
+ const byAction = emptyActionCounts();
29952
+ let disabled = 0;
29953
+ for (const row of policyRows) {
29954
+ if (row.enabled === 0) disabled += 1;
29955
+ if (isActionTaken(row.action)) byAction[row.action] += 1;
29956
+ }
29957
+ policyCounts = { total: policyRows.length, disabled, byAction };
29958
+ } catch (err) {
29959
+ if (!isSchemaAbsent(err)) throw err;
29960
+ }
29961
+ try {
29962
+ const agg = db.prepare(
29963
+ `SELECT count(*) AS n, min(f.first_detected_at) AS firstAt, max(f.first_detected_at) AS lastAt
29964
+ FROM inspection_findings f JOIN audit_events e ON e.id = f.audit_event_id
29965
+ WHERE e.event_type IN ${CAPTURE_EVENT_TYPES_SQL2}`
29966
+ ).get();
29967
+ findingsTotal = agg.n;
29968
+ findingsFirstAt = agg.firstAt;
29969
+ findingsLastAt = agg.lastAt;
29970
+ } catch (err) {
29971
+ if (!isSchemaAbsent(err)) throw err;
29972
+ }
29973
+ return currentReadout();
29974
+ } catch {
29975
+ return emptyReadout(true);
29976
+ } finally {
29977
+ try {
29978
+ db?.close();
29979
+ } catch {
29980
+ }
29981
+ }
29982
+ }
29983
+
29984
+ // ../../packages/plugin-runtime/src/attached/posture-store.ts
29985
+ import { randomUUID as randomUUID17 } from "crypto";
29986
+ import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
29987
+ import { join as join20 } from "path";
29988
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
29989
+ function createPostureStore(dir = settingsDir(), legacyDir) {
29990
+ const file2 = join20(dir, "posture-state.json");
29991
+ const legacyFile = legacyDir === void 0 ? null : join20(legacyDir, "posture-state.json");
29992
+ async function persist(state) {
29993
+ await ensureDataDir(dir);
29994
+ const tmp = `${file2}.${randomUUID17()}.tmp`;
29995
+ try {
29996
+ await writeFile3(tmp, JSON.stringify(state), { encoding: "utf8", mode: DATA_FILE_MODE });
29997
+ await publishByRename(tmp, file2);
29998
+ } catch (err) {
29999
+ await rm2(tmp, { force: true }).catch(() => void 0);
30000
+ throw err;
30001
+ }
30002
+ }
30003
+ async function readFrom(path) {
30004
+ let raw;
30005
+ try {
30006
+ raw = await readFile3(path, "utf8");
30007
+ } catch (err) {
30008
+ const code = err.code;
30009
+ if (code === "ENOENT" || code === "ENOTDIR") return null;
30010
+ throw err;
30011
+ }
30012
+ try {
30013
+ const parsed2 = JSON.parse(raw);
30014
+ if (typeof parsed2 === "object" && parsed2 !== null) {
30015
+ const record2 = parsed2;
30016
+ if (typeof record2.deviceId === "string" && UUID_RE.test(record2.deviceId)) {
30017
+ const stamp = typeof record2.lastAttemptedAtMs === "number" ? record2.lastAttemptedAtMs : typeof record2.lastReportedAtMs === "number" ? record2.lastReportedAtMs : 0;
30018
+ return { deviceId: record2.deviceId, lastAttemptedAtMs: stamp };
30019
+ }
30020
+ }
30021
+ } catch {
30022
+ }
30023
+ return null;
30024
+ }
30025
+ async function read() {
30026
+ const current = await readFrom(file2);
30027
+ if (current) return current;
30028
+ const legacy = legacyFile === null || legacyFile === file2 ? null : await readFrom(legacyFile).catch(() => null);
30029
+ if (legacy) {
30030
+ try {
30031
+ await persist(legacy);
30032
+ } catch {
30033
+ }
30034
+ return legacy;
30035
+ }
30036
+ const fresh = { deviceId: randomUUID17(), lastAttemptedAtMs: 0 };
30037
+ try {
30038
+ await ensureDataDir(dir);
30039
+ if (createOwnerOnlyFileSync(file2, JSON.stringify(fresh))) return fresh;
30040
+ } catch {
30041
+ return null;
30042
+ }
30043
+ const winner = await readFrom(file2).catch(() => null);
30044
+ if (winner) return winner;
30045
+ try {
30046
+ await persist(fresh);
30047
+ } catch {
30048
+ return null;
30049
+ }
30050
+ return fresh;
30051
+ }
30052
+ async function markAttempted(deviceId, atMs) {
30053
+ await persist({ deviceId, lastAttemptedAtMs: atMs });
30054
+ }
30055
+ return { read, markAttempted, file: file2 };
30056
+ }
30057
+
30058
+ // ../../packages/plugin-runtime/src/attached/sync-state.ts
30059
+ import { readFileSync as readFileSync13 } from "fs";
30060
+ import { join as join21 } from "path";
30061
+
30062
+ // ../../packages/plugin-runtime/src/attached/status.ts
30063
+ var REFUSAL_LINES = {
30064
+ unauthorized: "KEY REJECTED \u2014 re-attach with a valid plugin key",
30065
+ forbidden: "ACCESS REFUSED \u2014 key is valid but not permitted; ask your org admin"
30066
+ };
30067
+ var OUTCOME_LINES = {
30068
+ ok: "policy synced",
30069
+ "not-modified": "policy up to date",
30070
+ unauthorized: REFUSAL_LINES.unauthorized,
30071
+ forbidden: REFUSAL_LINES.forbidden,
30072
+ unreachable: "control plane unreachable at last attempt",
30073
+ "invalid-bundle": "control plane sent a policy bundle this build cannot read"
30074
+ };
30075
+
30076
+ // ../../packages/plugin-runtime/src/attached/sync-trigger.ts
30077
+ import { spawn } from "child_process";
30078
+ import { fileURLToPath as fileURLToPath2 } from "url";
30079
+ var SYNC_THROTTLE_MS = 15 * 60 * 1e3;
30080
+
30081
+ // ../../packages/plugin-runtime/src/attached/factory.ts
30082
+ import { hostname as hostname5 } from "os";
30083
+
30084
+ // ../../packages/plugin-runtime/src/standalone-gateway.ts
30085
+ import { randomUUID as randomUUID18 } from "crypto";
30086
+
30087
+ // ../../packages/plugin-runtime/src/recorder.ts
30088
+ var PLUGIN_RECORDER_BINARY = "plugin";
30089
+
30090
+ // ../../packages/plugin-runtime/src/standalone-gateway.ts
30091
+ var StandaloneDataGateway = class {
30092
+ db;
30093
+ // Kept for the fingerprint key lookup (exception.key lives beside the store).
30094
+ dataDir;
30095
+ // One notice per gateway — see warnRulesetDiscarded.
30096
+ warnedRulesetDiscarded = false;
30097
+ constructor(dataDir2, detections = [], meta3) {
30098
+ this.db = openLocalDatabase(dataDir2);
30099
+ this.dataDir = dataDir2;
30100
+ this.db.installedPacks.recordInventory(detections, meta3);
30101
+ }
30102
+ recordCapture(record2) {
30103
+ this.db.recordCapture(record2.event, record2.findings);
30104
+ return Promise.resolve();
30105
+ }
30106
+ ensureInventory(ctx) {
30107
+ return Promise.resolve(this.db.ensureInventory(ctx));
30108
+ }
30109
+ recordAuditEvent(event) {
30110
+ this.db.auditEvents.insertAuditEvent(event);
30111
+ return Promise.resolve();
30112
+ }
30113
+ // The id is minted inside the repository from the natural key — the plugin can't
30114
+ // import @akasecurity/persistence to compute it, so the gateway is the boundary that
30115
+ // hands the natural key across. UPSERT-take-MAX → idempotent re-reads that also
28644
30116
  // converge a streaming partial/final split (see insertLlmCall).
28645
30117
  recordLlmCall(input) {
28646
30118
  this.db.auditEvents.insertLlmCall(input);
@@ -28651,12 +30123,12 @@ var StandaloneDataGateway = class {
28651
30123
  // reconciler drops the whole pass and recovers it idempotently on the next read.
28652
30124
  recordLlmCalls(inputs) {
28653
30125
  if (inputs.length === 0) return Promise.resolve();
28654
- return new Promise((resolve, reject) => {
30126
+ return new Promise((resolve2, reject) => {
28655
30127
  try {
28656
30128
  this.db.auditEvents.runInTransaction(() => {
28657
30129
  for (const input of inputs) this.db.auditEvents.insertLlmCall(input);
28658
30130
  });
28659
- resolve();
30131
+ resolve2();
28660
30132
  } catch (err) {
28661
30133
  reject(err instanceof Error ? err : new Error(String(err)));
28662
30134
  }
@@ -28668,12 +30140,12 @@ var StandaloneDataGateway = class {
28668
30140
  // drops the whole pass and recovers it idempotently next time.
28669
30141
  recordToolCalls(inputs) {
28670
30142
  if (inputs.length === 0) return Promise.resolve();
28671
- return new Promise((resolve, reject) => {
30143
+ return new Promise((resolve2, reject) => {
28672
30144
  try {
28673
30145
  this.db.auditEvents.runInTransaction(() => {
28674
30146
  for (const input of inputs) this.writeToolCall(input);
28675
30147
  });
28676
- resolve();
30148
+ resolve2();
28677
30149
  } catch (err) {
28678
30150
  reject(err instanceof Error ? err : new Error(String(err)));
28679
30151
  }
@@ -28815,7 +30287,7 @@ var StandaloneDataGateway = class {
28815
30287
  const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
28816
30288
  const installed = this.installedScanRules();
28817
30289
  const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
28818
- id: randomUUID15(),
30290
+ id: randomUUID18(),
28819
30291
  scope: "global",
28820
30292
  target: { ruleId },
28821
30293
  action,
@@ -28969,15 +30441,61 @@ var StandaloneDataGateway = class {
28969
30441
  }
28970
30442
  };
28971
30443
 
30444
+ // ../../packages/plugin-runtime/src/attached/factory.ts
30445
+ function resolveGatewayForConfig(config2, meta3) {
30446
+ const local = new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
30447
+ try {
30448
+ if (!isAttached(config2.settings)) return local;
30449
+ const connection = config2.settings.controlPlane;
30450
+ if (connection === void 0) return local;
30451
+ const state = readControlPlaneCredentialState(config2.settingsDir, connection);
30452
+ if (!state.usable) return local;
30453
+ const client = createRemoteClient({
30454
+ endpoint: connection.endpoint,
30455
+ apiKey: state.credential.apiKey
30456
+ });
30457
+ const store = createPolicyStore(config2.dataDir);
30458
+ const postureStore = createPostureStore(config2.settingsDir, config2.dataDir);
30459
+ const forward = createForwardPolicy({ dir: config2.dataDir });
30460
+ return new AttachedDataGateway({
30461
+ local,
30462
+ client,
30463
+ dataDir: config2.dataDir,
30464
+ readCachedBundle: () => store.read().then((cached2) => cached2?.bundle ?? null),
30465
+ forward,
30466
+ posture: createPostureReporter({
30467
+ // THROUGH THE BREAKER, and wrapped HERE rather than around
30468
+ // `PostureReporter.send`. The reporter swallows every error by
30469
+ // contract, so a wrap outside it would hand `forward.run` a resolved
30470
+ // promise for a send that failed — recording a SUCCESS, clearing
30471
+ // `consecutiveFailures` and `lastFailure`, and telling `aka status` the
30472
+ // forward recovered when nothing did. Wrapping the raw client call puts
30473
+ // the breaker above the swallow, where it can see the truth.
30474
+ //
30475
+ // What it buys: once the breaker is open — the plane already confirmed
30476
+ // down by the gateway's own writes — this stops paying a request
30477
+ // timeout per throttle interval to re-learn it.
30478
+ report: (snapshot) => forward.run(() => client.reportStorePosture(snapshot)).then(() => void 0),
30479
+ store: postureStore,
30480
+ readStore: () => readStorePosture(config2.dbPath),
30481
+ hostname: () => hostname5(),
30482
+ now: () => Date.now()
30483
+ })
30484
+ });
30485
+ } catch {
30486
+ return local;
30487
+ }
30488
+ }
30489
+
28972
30490
  // ../../packages/plugin-runtime/src/resolve.ts
28973
- var standaloneGatewayFactory = (config2, meta3) => new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
28974
- var defaultGatewayFactory = standaloneGatewayFactory;
30491
+ var configuredGatewayFactory = (config2, meta3) => resolveGatewayForConfig(config2, meta3);
30492
+ var defaultGatewayFactory = configuredGatewayFactory;
28975
30493
  function resolveDataGateway(config2, meta3, gatewayFactory = defaultGatewayFactory) {
28976
30494
  return gatewayFactory(config2, meta3);
28977
30495
  }
28978
30496
 
28979
30497
  // ../../packages/plugin-runtime/src/handle-session-start.ts
28980
- import { randomUUID as randomUUID16 } from "crypto";
30498
+ import { randomUUID as randomUUID19 } from "crypto";
28981
30499
  var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
28982
30500
 
28983
30501
  // src/present.ts
@@ -29098,7 +30616,7 @@ function fenced(body) {
29098
30616
 
29099
30617
  // ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
29100
30618
  import { writeFileSync as writeFileSync7 } from "fs";
29101
- import { join as join15 } from "path";
30619
+ import { join as join22 } from "path";
29102
30620
 
29103
30621
  // ../../packages/setup-wizard/src/triage/merge.ts
29104
30622
  var RANK = Object.fromEntries(
@@ -29106,9 +30624,9 @@ var RANK = Object.fromEntries(
29106
30624
  );
29107
30625
 
29108
30626
  // ../../packages/setup-wizard/src/triage/plan-file.ts
29109
- import { mkdtempSync, readFileSync as readFileSync10, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
30627
+ import { mkdtempSync, readFileSync as readFileSync14, rmdirSync, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
29110
30628
  import { tmpdir } from "os";
29111
- import { basename as basename6, dirname as dirname4, join as join16 } from "path";
30629
+ import { basename as basename6, dirname as dirname5, join as join23 } from "path";
29112
30630
  var SuppressionEntrySchema = external_exports.object({
29113
30631
  ruleId: external_exports.string(),
29114
30632
  category: DetectionCategory,
@@ -29151,8 +30669,8 @@ var PersistedPlanSchema = external_exports.object({
29151
30669
 
29152
30670
  // src/command-registry.ts
29153
30671
  import { readdirSync as readdirSync5 } from "fs";
29154
- import { fileURLToPath as fileURLToPath2 } from "url";
29155
- var COMMANDS_DIR = fileURLToPath2(new URL("../commands", import.meta.url));
30672
+ import { fileURLToPath as fileURLToPath3 } from "url";
30673
+ var COMMANDS_DIR = fileURLToPath3(new URL("../commands", import.meta.url));
29156
30674
 
29157
30675
  // src/render.ts
29158
30676
  var SEVERITY_WEIGHT = { critical: 4, high: 3, medium: 2, low: 1 };