@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.
@@ -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
@@ -492,13 +492,12 @@ var require_ignore = __commonJS({
492
492
  });
493
493
 
494
494
  // src/remediation/entry.ts
495
- import { readFileSync as readFileSync14 } from "fs";
496
- import { fileURLToPath as fileURLToPath3 } from "url";
495
+ import { readFileSync as readFileSync18 } from "fs";
496
+ import { fileURLToPath as fileURLToPath4 } from "url";
497
497
 
498
- // ../../packages/persistence/src/database.ts
499
- import { randomUUID as randomUUID10 } from "crypto";
500
- import { join as join2, sep } from "path";
501
- import { DatabaseSync } from "node:sqlite";
498
+ // ../../packages/persistence/src/control-plane-credential.ts
499
+ import { chmodSync as chmodSync2, lstatSync as lstatSync2, readFileSync, rmSync as rmSync2, statSync } from "fs";
500
+ import { join } from "path";
502
501
 
503
502
  // ../../packages/schema/src/drizzle/sqlite-ddl.ts
504
503
  var SQLITE_MIGRATIONS = [
@@ -16202,6 +16201,125 @@ var ConfigScanRecord = external_exports.object({
16202
16201
  findings: external_exports.array(ConfigPostureFindingInput).optional()
16203
16202
  });
16204
16203
 
16204
+ // ../../packages/schema/src/zod/control-plane.ts
16205
+ var ATTACHED_CREDENTIAL_FILENAME = "control-plane-credential.json";
16206
+ var ATTACHED_CREDENTIAL_SPEC_VERSION = 1;
16207
+ var AttachedCredential = external_exports.object({
16208
+ specVersion: external_exports.literal(ATTACHED_CREDENTIAL_SPEC_VERSION),
16209
+ // The control-plane endpoint this credential was minted against.
16210
+ endpoint: external_exports.string().min(1),
16211
+ // The bearer credential itself. Never logged, never rendered — status
16212
+ // surfaces show `keyPrefix` and nothing else.
16213
+ apiKey: external_exports.string().min(1),
16214
+ // First few characters of the key, safe to display so a user can match the
16215
+ // credential against their organization's key list.
16216
+ keyPrefix: external_exports.string().min(1).max(16).optional(),
16217
+ mintedAt: external_exports.iso.datetime().optional()
16218
+ });
16219
+ var MAX_DATE_MS = 253402300799999;
16220
+ var MAX_INT4 = 2147483647;
16221
+ var StorePosturePack = external_exports.object({
16222
+ packId: external_exports.string().min(1),
16223
+ // 'namespace/packId'
16224
+ version: external_exports.string().min(1),
16225
+ enabled: external_exports.boolean(),
16226
+ // Stringified pass-through of the local store's `installed_packs.updated_at`
16227
+ // — the column format is store-version-dependent (epoch millis vs ISO), so
16228
+ // the wire shape assumes neither.
16229
+ updatedAt: external_exports.string().nullable()
16230
+ }).meta({ id: "StorePosturePack" });
16231
+ var StorePosturePolicyCounts = external_exports.object({
16232
+ total: external_exports.number().int().min(0),
16233
+ disabled: external_exports.number().int().min(0),
16234
+ // Exhaustive per-action map; the builder pre-fills every action with 0.
16235
+ //
16236
+ // Spelled out member-by-member rather than `z.record(ActionTaken, …)`. Zod
16237
+ // enforces exhaustiveness either way, but z.record emits `propertyNames` +
16238
+ // `additionalProperties` into a generated schema document, and a type
16239
+ // generator renders THAT with every key optional — a sender built against
16240
+ // the generated type would typecheck and still be rejected at runtime. An
16241
+ // explicit object emits `properties` + `required`, so generated types
16242
+ // demand all five.
16243
+ //
16244
+ // `satisfies Record<ActionTaken, …>` keeps the link to the enum: adding an
16245
+ // ActionTaken member is a COMPILE error here instead of silent drift.
16246
+ // `.strict()` is load-bearing — it rejects an unknown action key, which a
16247
+ // bare object would silently STRIP, accepting a miscounted map as valid.
16248
+ byAction: external_exports.object({
16249
+ warn: external_exports.number().int().min(0),
16250
+ redact: external_exports.number().int().min(0),
16251
+ block: external_exports.number().int().min(0),
16252
+ allow: external_exports.number().int().min(0),
16253
+ log: external_exports.number().int().min(0)
16254
+ }).strict()
16255
+ }).meta({ id: "StorePosturePolicyCounts" });
16256
+ var StorePosturePlugin = external_exports.object({
16257
+ /** Package name of the reporting plugin. */
16258
+ package: external_exports.string().min(1).max(200),
16259
+ version: external_exports.string().min(1).max(64),
16260
+ /** Version of the bundled core, when the build records one separately. */
16261
+ ossVersion: external_exports.string().max(64).nullable(),
16262
+ /**
16263
+ * `version` of the policy bundle this machine last fetched. Bounded at 200
16264
+ * rather than the 64 a bare sha256 hex digest needs today, so a later
16265
+ * format with an algorithm prefix does not start rejecting the channel.
16266
+ */
16267
+ policyBundleVersion: external_exports.string().max(200).nullable(),
16268
+ /** Epoch millis, on the CLIENT clock, of that fetch. */
16269
+ policyFetchedAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable()
16270
+ }).meta({ id: "StorePosturePlugin" });
16271
+ var StorePostureSnapshot = external_exports.object({
16272
+ deviceId: external_exports.guid(),
16273
+ hostname: external_exports.string().min(1).max(253),
16274
+ // Epoch millis on the CLIENT clock. Bounded by what a receiving store
16275
+ // accepts (see MAX_DATE_MS), not by what a JavaScript Date can hold.
16276
+ capturedAt: external_exports.number().int().min(0).max(MAX_DATE_MS),
16277
+ // False is a measurement, not an error state: "no local store exists on
16278
+ // this machine".
16279
+ storePresent: external_exports.boolean(),
16280
+ schemaVersion: external_exports.number().int().min(0).max(MAX_INT4).nullable(),
16281
+ // PRAGMA user_version
16282
+ findingsTotal: external_exports.number().int().min(0).max(MAX_INT4),
16283
+ // Epoch millis, bounded like `capturedAt` — see MAX_DATE_MS on what that
16284
+ // bound does and does not do. Worth stating for these two specifically:
16285
+ // they are read from the local store's own ROWS rather than from this
16286
+ // machine's clock, so a damaged or hand-edited store is enough to produce
16287
+ // an out-of-range value with no clock skew involved.
16288
+ findingsFirstAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable(),
16289
+ findingsLastAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable(),
16290
+ packs: external_exports.array(StorePosturePack).max(500),
16291
+ policyCounts: StorePosturePolicyCounts,
16292
+ // OPTIONAL, not nullable: a reporter that predates this member keeps
16293
+ // getting its 200 without a payload change.
16294
+ plugin: StorePosturePlugin.optional()
16295
+ }).meta({ id: "StorePostureSnapshot" });
16296
+ var CAPTURE_VERSION_PREFIX = "capture/";
16297
+ var RecordAuditEventRequest = AuditEventInput.extend({
16298
+ inspections: external_exports.array(ToolCallInspection).default([])
16299
+ }).refine((v) => v.inspections.every((i) => !i.ruleVersion.startsWith(CAPTURE_VERSION_PREFIX)), {
16300
+ message: `inspections[].ruleVersion must not start with \`${CAPTURE_VERSION_PREFIX}\` \u2014 that namespace is reserved for capture definitions the control plane mints itself`,
16301
+ path: ["inspections"]
16302
+ }).meta({ id: "RecordAuditEventRequest" });
16303
+ var IngestAck = external_exports.object({
16304
+ accepted: external_exports.number().int().nonnegative(),
16305
+ duplicates: external_exports.number().int().nonnegative()
16306
+ });
16307
+ var PRINTABLE = /^[^\p{Cc}\p{Cf}]*$/u;
16308
+ var printable = (max) => external_exports.string().max(max).regex(PRINTABLE, "must not contain control characters");
16309
+ var PluginWhoami = external_exports.object({
16310
+ tenantName: printable(200),
16311
+ userEmail: printable(320),
16312
+ role: printable(64),
16313
+ keyKind: printable(64),
16314
+ serverTime: printable(64)
16315
+ });
16316
+ var ControlPlaneErrorBody = external_exports.object({
16317
+ error: external_exports.object({
16318
+ code: external_exports.string().optional(),
16319
+ message: external_exports.string().optional()
16320
+ }).optional()
16321
+ });
16322
+
16205
16323
  // ../../packages/schema/src/zod/registry.ts
16206
16324
  var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16207
16325
  var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
@@ -16504,15 +16622,15 @@ function summaryToDetectionListItem(s) {
16504
16622
  }
16505
16623
  function rowToDetectionDetail(row, findingsLast30d, update) {
16506
16624
  const rules = row.rules.flatMap((r) => {
16507
- const parsed = Matcher.safeParse(r.matcher);
16508
- if (!parsed.success) return [];
16625
+ const parsed2 = Matcher.safeParse(r.matcher);
16626
+ if (!parsed2.success) return [];
16509
16627
  return [
16510
16628
  {
16511
16629
  id: r.id,
16512
16630
  name: r.name,
16513
16631
  category: r.category,
16514
16632
  severity: r.severity,
16515
- matcher: parsed.data
16633
+ matcher: parsed2.data
16516
16634
  }
16517
16635
  ];
16518
16636
  });
@@ -17158,8 +17276,8 @@ function toApiAction(dbVal) {
17158
17276
  }
17159
17277
  function toApiCategory(dbVal) {
17160
17278
  if (dbVal === "code_context") return "source_code";
17161
- const parsed = FindingCategory.safeParse(dbVal);
17162
- return parsed.success ? parsed.data : "custom";
17279
+ const parsed2 = FindingCategory.safeParse(dbVal);
17280
+ return parsed2.success ? parsed2.data : "custom";
17163
17281
  }
17164
17282
  function toApiProvider(sourceTool) {
17165
17283
  return TOOL_TO_HARNESS[sourceTool] ?? HARNESS.Api;
@@ -17796,6 +17914,9 @@ var WorkspaceSettings = external_exports.object({
17796
17914
  function defaultWorkspaceSettings() {
17797
17915
  return WorkspaceSettings.parse({});
17798
17916
  }
17917
+ function isAttached(settings) {
17918
+ return settings.runMode === "attached" && settings.controlPlane !== void 0;
17919
+ }
17799
17920
  function toInventoryRow(input, id, now) {
17800
17921
  return {
17801
17922
  id,
@@ -18063,8 +18184,8 @@ function builtinPolicyIsReversible(id) {
18063
18184
  return BUILTIN_POLICY_SPECS[id].reversible;
18064
18185
  }
18065
18186
  function policyIdIsReversible(policyId) {
18066
- const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18067
- const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
18187
+ const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18188
+ const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
18068
18189
  return builtinPolicyIsReversible(id);
18069
18190
  }
18070
18191
  var DEFAULT_ACTIONS = Object.fromEntries(
@@ -18075,8 +18196,8 @@ var BUILTIN_POLICIES = Object.fromEntries(
18075
18196
  );
18076
18197
  var DEFAULT_PACK_POLICY_ID = "monitor";
18077
18198
  function policyIdToAction(policyId) {
18078
- const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18079
- const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
18199
+ const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18200
+ const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
18080
18201
  return BUILTIN_POLICIES[id].action;
18081
18202
  }
18082
18203
  var UsedByItem = external_exports.object({
@@ -18519,53 +18640,6 @@ function reviewSeverityRank(reasons) {
18519
18640
  return Math.min(...reasons.map((r) => REVIEW_SEVERITY_RANK[r]));
18520
18641
  }
18521
18642
 
18522
- // ../../packages/persistence/src/ids.ts
18523
- import { createHash } from "crypto";
18524
- function sha256Hex(input) {
18525
- return createHash("sha256").update(input).digest("hex");
18526
- }
18527
- function inventoryId(objectType, identityKey) {
18528
- return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
18529
- }
18530
- function sourceProjectId(url2) {
18531
- return sha256Hex(canonicalIdentity(["source_project", url2]));
18532
- }
18533
- function classifiedDataId(cls) {
18534
- return sha256Hex(canonicalIdentity(["classified_data", cls]));
18535
- }
18536
- function inspectionDefinitionId(ruleId, version2) {
18537
- return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
18538
- }
18539
- function llmCallId(sessionId, messageId) {
18540
- return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
18541
- }
18542
- function toolCallId(sessionId, toolUseId) {
18543
- return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
18544
- }
18545
- function inspectionFindingId(auditEventId, ruleId, spanStart, spanEnd) {
18546
- return sha256Hex(
18547
- canonicalIdentity([
18548
- "inspection_finding",
18549
- auditEventId,
18550
- ruleId,
18551
- String(spanStart),
18552
- String(spanEnd)
18553
- ])
18554
- );
18555
- }
18556
- var NO_SESSION = "no_session";
18557
- var NO_PATH = "no_path";
18558
- function captureId(sessionId, contentHash, filePath = null) {
18559
- return sha256Hex(
18560
- canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
18561
- );
18562
- }
18563
-
18564
- // ../../packages/persistence/src/internal/snapshot.ts
18565
- import { randomUUID } from "crypto";
18566
- import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
18567
- import { basename, dirname, join } from "path";
18568
-
18569
18643
  // ../../packages/persistence/src/paths.ts
18570
18644
  import {
18571
18645
  chmodSync,
@@ -18691,7 +18765,123 @@ function publishByLink(tmp, file2, data) {
18691
18765
  }
18692
18766
  }
18693
18767
 
18768
+ // ../../packages/persistence/src/control-plane-credential.ts
18769
+ function controlPlaneCredentialPath(settingsDir2) {
18770
+ return join(settingsDir2, ATTACHED_CREDENTIAL_FILENAME);
18771
+ }
18772
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
18773
+ function isSafeEndpoint(endpoint) {
18774
+ let parsed2;
18775
+ try {
18776
+ parsed2 = new URL(endpoint);
18777
+ } catch {
18778
+ return false;
18779
+ }
18780
+ if (parsed2.protocol === "https:") return true;
18781
+ return parsed2.protocol === "http:" && LOOPBACK_HOSTS.has(parsed2.hostname);
18782
+ }
18783
+ function repairOrRefuseMode(file2) {
18784
+ const link = lstatSync2(file2, { throwIfNoEntry: false });
18785
+ if (link === void 0) return "absent";
18786
+ if (link.isSymbolicLink()) return "untrusted";
18787
+ const stat = statSync(file2, { throwIfNoEntry: false });
18788
+ if (stat === void 0) return "absent";
18789
+ const uid = process.getuid?.();
18790
+ if (uid !== void 0 && stat.uid !== uid) return "untrusted";
18791
+ if (process.platform !== "win32" && (stat.mode & 511) !== DATA_FILE_MODE) {
18792
+ try {
18793
+ chmodSync2(file2, DATA_FILE_MODE);
18794
+ } catch {
18795
+ return "untrusted";
18796
+ }
18797
+ }
18798
+ return "ok";
18799
+ }
18800
+ function readControlPlaneCredentialState(settingsDir2, connection) {
18801
+ const file2 = controlPlaneCredentialPath(settingsDir2);
18802
+ let raw;
18803
+ const gate = repairOrRefuseMode(file2);
18804
+ if (gate === "absent") return { usable: false, reason: "absent" };
18805
+ if (gate === "untrusted") return { usable: false, reason: "untrusted-file" };
18806
+ try {
18807
+ raw = readFileSync(file2, "utf8");
18808
+ } catch (err) {
18809
+ const code = err.code;
18810
+ return { usable: false, reason: code === "ENOENT" ? "absent" : "unreadable" };
18811
+ }
18812
+ let parsed2;
18813
+ try {
18814
+ parsed2 = JSON.parse(raw);
18815
+ } catch {
18816
+ return { usable: false, reason: "malformed" };
18817
+ }
18818
+ const result = AttachedCredential.safeParse(parsed2);
18819
+ if (!result.success) return { usable: false, reason: "malformed" };
18820
+ if (!isSafeEndpoint(result.data.endpoint)) {
18821
+ return { usable: false, reason: "unsafe-endpoint" };
18822
+ }
18823
+ if (connection !== void 0 && connection.endpoint !== result.data.endpoint) {
18824
+ return {
18825
+ usable: false,
18826
+ reason: "endpoint-mismatch",
18827
+ credentialEndpoint: result.data.endpoint,
18828
+ settingsEndpoint: connection.endpoint
18829
+ };
18830
+ }
18831
+ return { usable: true, credential: result.data };
18832
+ }
18833
+
18834
+ // ../../packages/persistence/src/database.ts
18835
+ import { randomUUID as randomUUID10 } from "crypto";
18836
+ import { join as join3, sep } from "path";
18837
+ import { DatabaseSync } from "node:sqlite";
18838
+
18839
+ // ../../packages/persistence/src/ids.ts
18840
+ import { createHash } from "crypto";
18841
+ function sha256Hex(input) {
18842
+ return createHash("sha256").update(input).digest("hex");
18843
+ }
18844
+ function inventoryId(objectType, identityKey) {
18845
+ return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
18846
+ }
18847
+ function sourceProjectId(url2) {
18848
+ return sha256Hex(canonicalIdentity(["source_project", url2]));
18849
+ }
18850
+ function classifiedDataId(cls) {
18851
+ return sha256Hex(canonicalIdentity(["classified_data", cls]));
18852
+ }
18853
+ function inspectionDefinitionId(ruleId, version2) {
18854
+ return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
18855
+ }
18856
+ function llmCallId(sessionId, messageId) {
18857
+ return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
18858
+ }
18859
+ function toolCallId(sessionId, toolUseId) {
18860
+ return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
18861
+ }
18862
+ function inspectionFindingId(auditEventId, ruleId, spanStart, spanEnd) {
18863
+ return sha256Hex(
18864
+ canonicalIdentity([
18865
+ "inspection_finding",
18866
+ auditEventId,
18867
+ ruleId,
18868
+ String(spanStart),
18869
+ String(spanEnd)
18870
+ ])
18871
+ );
18872
+ }
18873
+ var NO_SESSION = "no_session";
18874
+ var NO_PATH = "no_path";
18875
+ function captureId(sessionId, contentHash, filePath = null) {
18876
+ return sha256Hex(
18877
+ canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
18878
+ );
18879
+ }
18880
+
18694
18881
  // ../../packages/persistence/src/internal/snapshot.ts
18882
+ import { randomUUID } from "crypto";
18883
+ import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync2 } from "fs";
18884
+ import { basename, dirname, join as join2 } from "path";
18695
18885
  function backupPath(file2, tag) {
18696
18886
  return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
18697
18887
  }
@@ -18701,15 +18891,15 @@ var STAGED_NAME_SUFFIX = `.bak${SNAPSHOT_STAGING_SUFFIX}`;
18701
18891
  var SNAPSHOT_STAGING_COPY = "copy";
18702
18892
  function createSnapshotStaging(backup) {
18703
18893
  const stage = `${backup}${SNAPSHOT_STAGING_SUFFIX}`;
18704
- rmSync2(stage, { recursive: true, force: true });
18894
+ rmSync3(stage, { recursive: true, force: true });
18705
18895
  mkdirOwnerOnlySync(stage);
18706
18896
  tightenDir(stage);
18707
- return { stage, copy: join(stage, SNAPSHOT_STAGING_COPY) };
18897
+ return { stage, copy: join2(stage, SNAPSHOT_STAGING_COPY) };
18708
18898
  }
18709
18899
  function idleMs(entry) {
18710
- for (const candidate of [join(entry, SNAPSHOT_STAGING_COPY), entry]) {
18900
+ for (const candidate of [join2(entry, SNAPSHOT_STAGING_COPY), entry]) {
18711
18901
  try {
18712
- return Date.now() - statSync(candidate).mtimeMs;
18902
+ return Date.now() - statSync2(candidate).mtimeMs;
18713
18903
  } catch {
18714
18904
  }
18715
18905
  }
@@ -18726,11 +18916,11 @@ function reapStalePartials(file2) {
18726
18916
  }
18727
18917
  for (const name of entries) {
18728
18918
  if (!name.startsWith(prefix) || !name.endsWith(STAGED_NAME_SUFFIX)) continue;
18729
- const staging = join(dir, name);
18919
+ const staging = join2(dir, name);
18730
18920
  try {
18731
18921
  const idle = idleMs(staging);
18732
18922
  if (idle !== null && idle > STALE_PARTIAL_MS) {
18733
- rmSync2(staging, { recursive: true, force: true });
18923
+ rmSync3(staging, { recursive: true, force: true });
18734
18924
  }
18735
18925
  } catch {
18736
18926
  }
@@ -18744,13 +18934,13 @@ function snapshotStore(db, backup) {
18744
18934
  renameSync2(copy, backup);
18745
18935
  } catch (error51) {
18746
18936
  try {
18747
- rmSync2(stage, { recursive: true, force: true });
18937
+ rmSync3(stage, { recursive: true, force: true });
18748
18938
  } catch {
18749
18939
  }
18750
18940
  throw error51;
18751
18941
  }
18752
18942
  try {
18753
- rmSync2(stage, { recursive: true, force: true });
18943
+ rmSync3(stage, { recursive: true, force: true });
18754
18944
  } catch {
18755
18945
  }
18756
18946
  }
@@ -18765,7 +18955,7 @@ function moveStoreAside(file2, backup) {
18765
18955
  renameSync2(sidecar, moved);
18766
18956
  undo.push([moved, sidecar]);
18767
18957
  } catch {
18768
- rmSync2(sidecar, { force: true });
18958
+ rmSync3(sidecar, { force: true });
18769
18959
  }
18770
18960
  }
18771
18961
  } catch (error51) {
@@ -18781,14 +18971,14 @@ function moveStoreAside(file2, backup) {
18781
18971
  }
18782
18972
  function discardStore(file2, backup) {
18783
18973
  try {
18784
- rmSync2(file2, { force: true });
18974
+ rmSync3(file2, { force: true });
18785
18975
  for (const sidecar of dbSidecars(file2)) {
18786
- rmSync2(sidecar, { force: true });
18976
+ rmSync3(sidecar, { force: true });
18787
18977
  }
18788
18978
  } catch (error51) {
18789
18979
  if (existsSync(file2)) {
18790
18980
  try {
18791
- rmSync2(backup, { force: true });
18981
+ rmSync3(backup, { force: true });
18792
18982
  } catch {
18793
18983
  }
18794
18984
  }
@@ -19020,10 +19210,31 @@ function applyMigrations(db, file2) {
19020
19210
  if (drained) applyLegacyDropMigration(db, file2);
19021
19211
  }
19022
19212
  }
19213
+ function readLegacyTables(db) {
19214
+ let holdsRows = false;
19215
+ const marks = [];
19216
+ for (const table2 of ["events", "findings"]) {
19217
+ try {
19218
+ const row = db.prepare(`SELECT count(*) AS n, ifnull(max(rowid), -1) AS hi FROM ${table2}`).get();
19219
+ if (row === void 0) {
19220
+ holdsRows = true;
19221
+ marks.push(`${table2}:unreadable`);
19222
+ continue;
19223
+ }
19224
+ if (row.n > 0) holdsRows = true;
19225
+ marks.push(`${table2}:${String(row.n)}:${String(row.hi)}`);
19226
+ } catch {
19227
+ holdsRows = true;
19228
+ marks.push(`${table2}:unreadable`);
19229
+ }
19230
+ }
19231
+ return { holdsRows, mark: marks.join("|") };
19232
+ }
19023
19233
  function applyLegacyDropMigration(db, file2) {
19024
19234
  const migration = SQLITE_MIGRATIONS.find((m) => m.tag === LEGACY_DROP_MIGRATION_TAG);
19025
19235
  if (!migration) return;
19026
- if (file2) {
19236
+ const before = file2 === void 0 ? void 0 : readLegacyTables(db);
19237
+ if (file2 !== void 0 && before?.holdsRows === true) {
19027
19238
  try {
19028
19239
  backupBeforeLegacyDrop(db, file2);
19029
19240
  } catch (error51) {
@@ -19037,6 +19248,12 @@ function applyLegacyDropMigration(db, file2) {
19037
19248
  () => {
19038
19249
  const alreadyDropped = db.prepare("SELECT 1 FROM migration_ledger WHERE tag = ?").get(migration.tag);
19039
19250
  if (alreadyDropped) return;
19251
+ if (before !== void 0 && readLegacyTables(db).mark !== before.mark) {
19252
+ akaWarn(
19253
+ "legacy events/findings rows changed after the pre-drop snapshot decision; deferring the drop so the next open can copy them first."
19254
+ );
19255
+ return;
19256
+ }
19040
19257
  for (const statement of splitStatements(migration.sql)) {
19041
19258
  db.exec(statement);
19042
19259
  }
@@ -19391,8 +19608,8 @@ function safeJson(s, fallback) {
19391
19608
  function parseJsonObject(s) {
19392
19609
  if (s == null) return void 0;
19393
19610
  try {
19394
- const parsed = JSON.parse(s);
19395
- if (typeof parsed === "object" && parsed !== null) return parsed;
19611
+ const parsed2 = JSON.parse(s);
19612
+ if (typeof parsed2 === "object" && parsed2 !== null) return parsed2;
19396
19613
  } catch {
19397
19614
  }
19398
19615
  return void 0;
@@ -19403,16 +19620,16 @@ function encodeKeysetCursor(payload) {
19403
19620
  return Buffer.from(JSON.stringify(payload)).toString("base64url");
19404
19621
  }
19405
19622
  function decodeKeysetCursor(cursor) {
19406
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
19407
- if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
19623
+ const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
19624
+ if (parsed2 !== void 0 && "startedAtMs" in parsed2 && "id" in parsed2 && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
19408
19625
  // resumes from is epoch millis, and a payload carrying ±Infinity or a
19409
19626
  // fraction binds cleanly rather than failing — returning an EMPTY page with
19410
19627
  // a null cursor, which a caller reads as "end of list". That is the one
19411
19628
  // outcome a cursor that does not decode must never produce, since the
19412
19629
  // documented behaviour above is to restart from the top. (`1e999` is valid
19413
19630
  // JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
19414
- Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
19415
- return parsed;
19631
+ Number.isInteger(parsed2.startedAtMs) && typeof parsed2.id === "string") {
19632
+ return parsed2;
19416
19633
  }
19417
19634
  return null;
19418
19635
  }
@@ -19477,18 +19694,18 @@ var DB_EVENT_TYPE_TO_KIND = {
19477
19694
  };
19478
19695
  function safeParseStringArray(raw) {
19479
19696
  if (!raw) return [];
19480
- const parsed = safeJson(raw, null);
19481
- return Array.isArray(parsed) ? parsed : [];
19697
+ const parsed2 = safeJson(raw, null);
19698
+ return Array.isArray(parsed2) ? parsed2 : [];
19482
19699
  }
19483
19700
  var DEFAULT_HARNESS = HARNESS.ClaudeCode;
19484
19701
  function toHarness(raw) {
19485
- const parsed = Harness.safeParse(raw);
19486
- return parsed.success ? parsed.data : DEFAULT_HARNESS;
19702
+ const parsed2 = Harness.safeParse(raw);
19703
+ return parsed2.success ? parsed2.data : DEFAULT_HARNESS;
19487
19704
  }
19488
19705
  function resolveLifecycle(row, lastActivityMs, nowMs) {
19489
19706
  if (row.status) {
19490
- const parsed = SessionStatus.safeParse(row.status);
19491
- if (parsed.success) return { status: parsed.data, endedAtMs: row.ended_at };
19707
+ const parsed2 = SessionStatus.safeParse(row.status);
19708
+ if (parsed2.success) return { status: parsed2.data, endedAtMs: row.ended_at };
19492
19709
  }
19493
19710
  if (row.ended_at !== null) return { status: "completed", endedAtMs: row.ended_at };
19494
19711
  if (lastActivityMs >= nowMs - LIVE_ACTIVITY_WINDOW_MS) {
@@ -20447,9 +20664,9 @@ var SqliteDetectionsRepository = class {
20447
20664
  const ruleIds = /* @__PURE__ */ new Set();
20448
20665
  for (const r of rows) {
20449
20666
  if (intToBool(r.enabled)) active += 1;
20450
- const parsed = parseRules(r.rulesJson);
20451
- rules += parsed.length;
20452
- for (const rule of parsed) {
20667
+ const parsed2 = parseRules(r.rulesJson);
20668
+ rules += parsed2.length;
20669
+ for (const rule of parsed2) {
20453
20670
  if (typeof rule.id === "string") ruleIds.add(rule.id);
20454
20671
  }
20455
20672
  }
@@ -20983,12 +21200,12 @@ function encodeGroupCursor(group) {
20983
21200
  return Buffer.from(JSON.stringify(payload)).toString("base64url");
20984
21201
  }
20985
21202
  function decodeGroupCursor(cursor) {
20986
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
20987
- if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
21203
+ const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
21204
+ if (parsed2 !== void 0 && typeof parsed2.sev === "string" && typeof parsed2.t === "string" && typeof parsed2.id === "string") {
20988
21205
  return {
20989
- severity: parsed.sev,
20990
- latestDetectedAt: parsed.t,
20991
- id: parsed.id
21206
+ severity: parsed2.sev,
21207
+ latestDetectedAt: parsed2.t,
21208
+ id: parsed2.id
20992
21209
  };
20993
21210
  }
20994
21211
  return null;
@@ -22122,16 +22339,16 @@ var SqliteInstalledPacksRepository = class {
22122
22339
  continue;
22123
22340
  }
22124
22341
  for (const entry of raw) {
22125
- const parsed = Rule.safeParse(entry);
22126
- if (parsed.success) {
22127
- out.rules.push(parsed.data);
22128
- out.ruleActions.set(parsed.data.id, action);
22129
- out.ruleVersions.set(parsed.data.id, row.version);
22130
- if (reversible) out.reversibleRules.add(parsed.data.id);
22131
- else out.reversibleRules.delete(parsed.data.id);
22342
+ const parsed2 = Rule.safeParse(entry);
22343
+ if (parsed2.success) {
22344
+ out.rules.push(parsed2.data);
22345
+ out.ruleActions.set(parsed2.data.id, action);
22346
+ out.ruleVersions.set(parsed2.data.id, row.version);
22347
+ if (reversible) out.reversibleRules.add(parsed2.data.id);
22348
+ else out.reversibleRules.delete(parsed2.data.id);
22132
22349
  } else {
22133
22350
  out.invalidRules += 1;
22134
- reject(pack, printableRuleId(entry), firstIssueReason(parsed.error));
22351
+ reject(pack, printableRuleId(entry), firstIssueReason(parsed2.error));
22135
22352
  }
22136
22353
  }
22137
22354
  }
@@ -23521,15 +23738,15 @@ function encodeReuseCursor(payload) {
23521
23738
  return Buffer.from(JSON.stringify(payload)).toString("base64url");
23522
23739
  }
23523
23740
  function decodeReuseCursor(cursor) {
23524
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
23525
- if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
23741
+ const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
23742
+ if (parsed2 !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
23526
23743
  // ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
23527
23744
  // null cursor, which the caller reads as "end of list" — the one outcome a
23528
23745
  // malformed cursor must never produce, since restarting from the top is the
23529
23746
  // documented behaviour and the only recoverable one. (`1e999` is valid JSON
23530
23747
  // and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
23531
- Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
23532
- return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
23748
+ Number.isInteger(parsed2.occurrences) && typeof parsed2.pointerId === "string") {
23749
+ return { occurrences: parsed2.occurrences, pointerId: parsed2.pointerId };
23533
23750
  }
23534
23751
  return null;
23535
23752
  }
@@ -25258,7 +25475,7 @@ function openAndInitialize(file2) {
25258
25475
  }
25259
25476
  function openLocalDatabase(dir) {
25260
25477
  ensureDataDirSync(dir);
25261
- const file2 = join2(dir, DB_FILENAME);
25478
+ const file2 = join3(dir, DB_FILENAME);
25262
25479
  reapStalePartials(file2);
25263
25480
  const {
25264
25481
  db,
@@ -25514,9 +25731,9 @@ import {
25514
25731
  closeSync,
25515
25732
  existsSync as existsSync2,
25516
25733
  openSync,
25517
- readFileSync,
25518
- rmSync as rmSync3,
25519
- statSync as statSync2,
25734
+ readFileSync as readFileSync2,
25735
+ rmSync as rmSync4,
25736
+ statSync as statSync3,
25520
25737
  writeFileSync as writeFileSync2
25521
25738
  } from "fs";
25522
25739
  import { hostname as hostname3 } from "os";
@@ -25534,20 +25751,20 @@ function computeFindingKey(input) {
25534
25751
 
25535
25752
  // ../../packages/persistence/src/fingerprint.ts
25536
25753
  import { createHmac, randomBytes } from "crypto";
25537
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
25538
- import { join as join3 } from "path";
25754
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
25755
+ import { join as join4 } from "path";
25539
25756
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
25540
25757
  var EXCEPTION_KEY_FILENAME = "exception.key";
25541
25758
  var KEY_MATERIAL_BYTES = 32;
25542
25759
  function keyFilePath(dataDir2) {
25543
- return join3(dataDir2, EXCEPTION_KEY_FILENAME);
25760
+ return join4(dataDir2, EXCEPTION_KEY_FILENAME);
25544
25761
  }
25545
25762
  function parseKeyFile(raw) {
25546
- const parsed = JSON.parse(raw);
25547
- if (typeof parsed !== "object" || parsed === null) {
25763
+ const parsed2 = JSON.parse(raw);
25764
+ if (typeof parsed2 !== "object" || parsed2 === null) {
25548
25765
  throw new Error("exception key file is corrupt: not a JSON object");
25549
25766
  }
25550
- const { version: version2, material } = parsed;
25767
+ const { version: version2, material } = parsed2;
25551
25768
  if (typeof version2 !== "number" || !Number.isInteger(version2) || version2 < 1) {
25552
25769
  throw new Error("exception key file is corrupt: bad version");
25553
25770
  }
@@ -25578,7 +25795,7 @@ var FloorUnreadableError = class extends Error {
25578
25795
  }
25579
25796
  };
25580
25797
  function storedKeyVersionFloor(dataDir2) {
25581
- const file2 = join3(dataDir2, DB_FILENAME);
25798
+ const file2 = join4(dataDir2, DB_FILENAME);
25582
25799
  if (!existsSync3(file2)) return 0;
25583
25800
  let db;
25584
25801
  try {
@@ -25633,7 +25850,7 @@ function occupantMessage(file2, kind) {
25633
25850
  function readFingerprintKey(dataDir2) {
25634
25851
  let raw;
25635
25852
  try {
25636
- raw = readFileSync2(keyFilePath(dataDir2), "utf8");
25853
+ raw = readFileSync3(keyFilePath(dataDir2), "utf8");
25637
25854
  } catch (err) {
25638
25855
  if (err.code === "ENOENT") return null;
25639
25856
  throw err instanceof Error ? err : new Error(String(err));
@@ -25659,21 +25876,25 @@ function fingerprintValue(key, raw) {
25659
25876
  import { renameSync as renameSync3 } from "fs";
25660
25877
  import { mkdir } from "fs/promises";
25661
25878
  import { homedir } from "os";
25662
- import { join as join4 } from "path";
25879
+ import { join as join5 } from "path";
25663
25880
  function defaultDataDir() {
25664
- return join4(homedir(), ".aka");
25881
+ return join5(homedir(), ".aka");
25665
25882
  }
25666
25883
  function settingsDir(base = defaultDataDir()) {
25667
- return join4(base, "settings");
25884
+ return join5(base, "settings");
25668
25885
  }
25669
25886
  function dataDir(base = defaultDataDir()) {
25670
- return join4(base, "data");
25887
+ return join5(base, "data");
25671
25888
  }
25672
25889
  function dbPath(base = defaultDataDir()) {
25673
- return join4(dataDir(base), "aka.db");
25890
+ return join5(dataDir(base), "aka.db");
25674
25891
  }
25675
25892
  function keysDir(base = defaultDataDir()) {
25676
- return join4(base, "keys");
25893
+ return join5(base, "keys");
25894
+ }
25895
+ async function ensureDataDir(dir = defaultDataDir()) {
25896
+ await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
25897
+ tightenDir(dir);
25677
25898
  }
25678
25899
  function ensureLayoutDirSync(dir = defaultDataDir()) {
25679
25900
  ensureDataDirSync(dir);
@@ -25686,8 +25907,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
25686
25907
  for (const { name, dest } of moves) {
25687
25908
  try {
25688
25909
  ensureDataDirSync(dest);
25689
- const moved = join4(dest, name);
25690
- renameSync3(join4(base, name), moved);
25910
+ const moved = join5(dest, name);
25911
+ renameSync3(join5(base, name), moved);
25691
25912
  tightenFile(moved);
25692
25913
  } catch {
25693
25914
  }
@@ -25695,7 +25916,7 @@ function migrateLegacyLayout(base = defaultDataDir()) {
25695
25916
  }
25696
25917
 
25697
25918
  // ../../packages/persistence/src/managed-settings.ts
25698
- import { readFileSync as readFileSync3 } from "fs";
25919
+ import { readFileSync as readFileSync4 } from "fs";
25699
25920
  import { posix, win32 } from "path";
25700
25921
  function managedSettingsPaths(platform2 = process.platform) {
25701
25922
  if (platform2 === "darwin") {
@@ -25713,14 +25934,14 @@ function readManagedSettings(paths = managedSettingsPaths()) {
25713
25934
  for (const path of paths) {
25714
25935
  let text;
25715
25936
  try {
25716
- text = readFileSync3(path, "utf8");
25937
+ text = readFileSync4(path, "utf8");
25717
25938
  } catch {
25718
25939
  continue;
25719
25940
  }
25720
25941
  const record2 = parseJsonObject(text);
25721
25942
  if (!record2) continue;
25722
- const parsed = ManagedSettings.safeParse(record2);
25723
- if (parsed.success) return parsed.data;
25943
+ const parsed2 = ManagedSettings.safeParse(record2);
25944
+ if (parsed2.success) return parsed2.data;
25724
25945
  }
25725
25946
  return null;
25726
25947
  }
@@ -25760,14 +25981,14 @@ function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ n
25760
25981
  }
25761
25982
 
25762
25983
  // ../../packages/persistence/src/settings.ts
25763
- import { readFileSync as readFileSync4 } from "fs";
25764
- import { join as join5 } from "path";
25984
+ import { readFileSync as readFileSync5 } from "fs";
25985
+ import { join as join6 } from "path";
25765
25986
  var SETTINGS_FILENAME = "settings.json";
25766
25987
  function readWorkspaceSettings(base = defaultDataDir()) {
25767
25988
  return overlayManagedSettings(readUserSettings(base), readManagedSettings());
25768
25989
  }
25769
25990
  function readUserSettings(base) {
25770
- const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
25991
+ const record2 = readJson(join6(settingsDir(base), SETTINGS_FILENAME));
25771
25992
  if (!record2) return defaultWorkspaceSettings();
25772
25993
  try {
25773
25994
  return WorkspaceSettings.parse(record2);
@@ -25778,13 +25999,17 @@ function readUserSettings(base) {
25778
25999
  function readJson(file2) {
25779
26000
  let text;
25780
26001
  try {
25781
- text = readFileSync4(file2, "utf8");
26002
+ text = readFileSync5(file2, "utf8");
25782
26003
  } catch {
25783
26004
  return null;
25784
26005
  }
25785
26006
  return parseJsonObject(text) ?? null;
25786
26007
  }
25787
26008
 
26009
+ // ../../packages/persistence/src/store-symlinks.ts
26010
+ import { existsSync as existsSync4, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
26011
+ import { dirname as dirname2, join as join7, resolve } from "path";
26012
+
25788
26013
  // ../../packages/persistence/src/vault/crypto.ts
25789
26014
  import {
25790
26015
  createCipheriv,
@@ -25896,8 +26121,8 @@ function formatPointer(category, keyVersion, pointerId, tag) {
25896
26121
  // ../../packages/persistence/src/vault/key-provider.ts
25897
26122
  import { execFileSync } from "child_process";
25898
26123
  import { randomBytes as randomBytes2 } from "crypto";
25899
- import { chmodSync as chmodSync2, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
25900
- import { join as join6 } from "path";
26124
+ import { chmodSync as chmodSync3, readFileSync as readFileSync6, renameSync as renameSync4, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
26125
+ import { join as join8 } from "path";
25901
26126
  var VAULT_OCCUPANT_REASON = {
25902
26127
  symlink: "the path is a symlink; remove it so a keyring can be created",
25903
26128
  gone: "the path was occupied but holds no keyring (removed while it was being created)",
@@ -25916,11 +26141,11 @@ var KEY_MATERIAL_BYTES2 = 32;
25916
26141
  var KEYCHAIN_SERVICE = "aka-vault";
25917
26142
  var KEYCHAIN_ACCOUNT = "keyring";
25918
26143
  function parseKeyring(raw) {
25919
- const parsed = JSON.parse(raw);
25920
- if (typeof parsed !== "object" || parsed === null) {
26144
+ const parsed2 = JSON.parse(raw);
26145
+ if (typeof parsed2 !== "object" || parsed2 === null) {
25921
26146
  throw new Error("vault key file is corrupt: not a JSON object");
25922
26147
  }
25923
- const { current, keys } = parsed;
26148
+ const { current, keys } = parsed2;
25924
26149
  if (typeof current !== "number" || !Number.isInteger(current) || current < 1) {
25925
26150
  throw new Error("vault key file is corrupt: bad current version");
25926
26151
  }
@@ -25996,28 +26221,28 @@ function claimRotationLock(lock, owner) {
25996
26221
  throw asError(err);
25997
26222
  }
25998
26223
  try {
25999
- writeFileSync3(join6(lock, LOCK_OWNER_FILE), `${owner}
26224
+ writeFileSync3(join8(lock, LOCK_OWNER_FILE), `${owner}
26000
26225
  `, { mode: DATA_FILE_MODE });
26001
26226
  return true;
26002
26227
  } catch (err) {
26003
- rmSync4(lock, { recursive: true, force: true });
26228
+ rmSync5(lock, { recursive: true, force: true });
26004
26229
  throw asError(err);
26005
26230
  }
26006
26231
  }
26007
26232
  function acquireRotationLock(keysDir2) {
26008
- const lock = join6(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
26233
+ const lock = join8(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
26009
26234
  const owner = randomBytes2(16).toString("hex");
26010
26235
  if (claimRotationLock(lock, owner)) return { lock, owner };
26011
26236
  let held;
26012
26237
  try {
26013
- held = statSync3(lock);
26238
+ held = statSync5(lock);
26014
26239
  } catch {
26015
26240
  throw new Error(ROTATION_IN_PROGRESS);
26016
26241
  }
26017
26242
  if (Date.now() - held.mtimeMs < ROTATION_LOCK_STALE_MS) throw new Error(ROTATION_IN_PROGRESS);
26018
26243
  const aside = `${lock}.stale.${owner}`;
26019
26244
  try {
26020
- const now = statSync3(lock);
26245
+ const now = statSync5(lock);
26021
26246
  if (now.ino !== held.ino || now.mtimeMs !== held.mtimeMs) {
26022
26247
  throw new Error(ROTATION_IN_PROGRESS);
26023
26248
  }
@@ -26026,17 +26251,17 @@ function acquireRotationLock(keysDir2) {
26026
26251
  if (err instanceof Error && err.message === ROTATION_IN_PROGRESS) throw err;
26027
26252
  throw new Error(ROTATION_IN_PROGRESS, { cause: err });
26028
26253
  }
26029
- rmSync4(aside, { recursive: true, force: true });
26254
+ rmSync5(aside, { recursive: true, force: true });
26030
26255
  if (!claimRotationLock(lock, owner)) throw new Error(ROTATION_IN_PROGRESS);
26031
26256
  return { lock, owner };
26032
26257
  }
26033
26258
  function releaseRotationLock(lease) {
26034
26259
  try {
26035
- if (readFileSync5(join6(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
26260
+ if (readFileSync6(join8(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
26036
26261
  } catch {
26037
26262
  return;
26038
26263
  }
26039
- rmSync4(lease.lock, { recursive: true, force: true });
26264
+ rmSync5(lease.lock, { recursive: true, force: true });
26040
26265
  }
26041
26266
  function withRotationLock(keysDir2, work) {
26042
26267
  ensureDataDirSync(keysDir2);
@@ -26053,7 +26278,7 @@ var FileKeyProvider = class {
26053
26278
  this.#keysDir = keysDir2;
26054
26279
  }
26055
26280
  get filePath() {
26056
- return join6(this.#keysDir, VAULT_KEY_FILENAME);
26281
+ return join8(this.#keysDir, VAULT_KEY_FILENAME);
26057
26282
  }
26058
26283
  loadOrCreate() {
26059
26284
  return asAsync(() => {
@@ -26083,7 +26308,7 @@ var FileKeyProvider = class {
26083
26308
  #read() {
26084
26309
  let raw;
26085
26310
  try {
26086
- raw = readFileSync5(this.filePath, "utf8");
26311
+ raw = readFileSync6(this.filePath, "utf8");
26087
26312
  } catch (err) {
26088
26313
  if (err.code === "ENOENT") return null;
26089
26314
  throw err instanceof Error ? err : new Error(String(err));
@@ -26140,7 +26365,7 @@ var FileKeyProvider = class {
26140
26365
  };
26141
26366
  function tightenFileMode(file2) {
26142
26367
  try {
26143
- chmodSync2(file2, DATA_FILE_MODE);
26368
+ chmodSync3(file2, DATA_FILE_MODE);
26144
26369
  } catch {
26145
26370
  }
26146
26371
  }
@@ -26405,25 +26630,25 @@ var SecretVault = class {
26405
26630
  * model. Every call that gets as far as an identified row writes an audit row.
26406
26631
  */
26407
26632
  async detokenize(token, opts) {
26408
- const parsed = parsePointer(token);
26409
- if (!parsed) return UNAVAILABLE;
26633
+ const parsed2 = parsePointer(token);
26634
+ if (!parsed2) return UNAVAILABLE;
26410
26635
  let signKey;
26411
26636
  try {
26412
- const epoch = await this.#keys.materialFor(parsed.keyVersion);
26637
+ const epoch = await this.#keys.materialFor(parsed2.keyVersion);
26413
26638
  signKey = deriveSubkeys(epoch.material).sign;
26414
26639
  } catch {
26415
26640
  return UNAVAILABLE;
26416
26641
  }
26417
- if (!verifyPointerTag(signKey, parsed.keyVersion, parsed.pointerId, parsed.category, parsed.tag)) {
26642
+ if (!verifyPointerTag(signKey, parsed2.keyVersion, parsed2.pointerId, parsed2.category, parsed2.tag)) {
26418
26643
  return UNAVAILABLE;
26419
26644
  }
26420
- const pointerId = base32Encode(parsed.pointerId);
26645
+ const pointerId = base32Encode(parsed2.pointerId);
26421
26646
  const row = this.#repo.byPointerId(pointerId);
26422
26647
  if (!row) {
26423
26648
  this.#audit(pointerId, opts, "unavailable");
26424
26649
  return UNAVAILABLE;
26425
26650
  }
26426
- if (row.category !== parsed.category) return UNAVAILABLE;
26651
+ if (row.category !== parsed2.category) return UNAVAILABLE;
26427
26652
  if (opts.target === "model") {
26428
26653
  const grantId = opts.grantId;
26429
26654
  const verify = this.#verifyGrant;
@@ -26460,7 +26685,7 @@ var SecretVault = class {
26460
26685
  // moved the epoch past the one this token names, and a format bump may
26461
26686
  // have moved the constant past the generation this row was sealed
26462
26687
  // under — the AAD follows the row in both cases, never the token.
26463
- bindingInput(row.keyVersion, parsed.pointerId, row.category, row.formatVersion)
26688
+ bindingInput(row.keyVersion, parsed2.pointerId, row.category, row.formatVersion)
26464
26689
  );
26465
26690
  } catch {
26466
26691
  raw = null;
@@ -26678,19 +26903,19 @@ var SecretVault = class {
26678
26903
  // preview. Verifying needs the historical epoch's key, which is why these
26679
26904
  // surfaces are async.
26680
26905
  async #rowFor(token) {
26681
- const parsed = parsePointer(token);
26682
- if (!parsed) return null;
26906
+ const parsed2 = parsePointer(token);
26907
+ if (!parsed2) return null;
26683
26908
  try {
26684
- const epoch = await this.#keys.materialFor(parsed.keyVersion);
26909
+ const epoch = await this.#keys.materialFor(parsed2.keyVersion);
26685
26910
  const signKey = deriveSubkeys(epoch.material).sign;
26686
- if (!verifyPointerTag(signKey, parsed.keyVersion, parsed.pointerId, parsed.category, parsed.tag)) {
26911
+ if (!verifyPointerTag(signKey, parsed2.keyVersion, parsed2.pointerId, parsed2.category, parsed2.tag)) {
26687
26912
  return null;
26688
26913
  }
26689
26914
  } catch {
26690
26915
  return null;
26691
26916
  }
26692
- const row = this.#repo.byPointerId(base32Encode(parsed.pointerId));
26693
- if (row?.category !== parsed.category) return null;
26917
+ const row = this.#repo.byPointerId(base32Encode(parsed2.pointerId));
26918
+ if (row?.category !== parsed2.category) return null;
26694
26919
  return row;
26695
26920
  }
26696
26921
  #audit(pointerId, opts, outcome) {
@@ -26710,13 +26935,13 @@ var SecretVault = class {
26710
26935
  };
26711
26936
 
26712
26937
  // ../../packages/persistence/src/warn-era-cap.ts
26713
- import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
26714
- import { join as join7 } from "path";
26938
+ import { existsSync as existsSync5, writeFileSync as writeFileSync4 } from "fs";
26939
+ import { join as join9 } from "path";
26715
26940
  var MARKER = "warn-era-capped";
26716
26941
  function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
26717
26942
  if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
26718
- const marker = join7(dataDir2, MARKER);
26719
- if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
26943
+ const marker = join9(dataDir2, MARKER);
26944
+ if (existsSync5(marker)) return { capped: 0, skipped: "already-run" };
26720
26945
  const capped = db.policies.capCategoryActions();
26721
26946
  writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
26722
26947
  `, { mode: DATA_FILE_MODE });
@@ -26724,8 +26949,8 @@ function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
26724
26949
  }
26725
26950
 
26726
26951
  // ../../packages/plugin-sdk/src/config.ts
26727
- import { existsSync as existsSync5 } from "fs";
26728
- import { join as join8 } from "path";
26952
+ import { existsSync as existsSync6 } from "fs";
26953
+ import { join as join10 } from "path";
26729
26954
 
26730
26955
  // ../../packages/plugin-sdk/src/provider-env.ts
26731
26956
  var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
@@ -26761,8 +26986,8 @@ function hostOf(url2) {
26761
26986
  }
26762
26987
  }
26763
26988
  function resolveProvider() {
26764
- const parsed = ProviderEnvSchema.safeParse(process.env);
26765
- const env = parsed.success ? parsed.data : ProviderEnvSchema.parse({});
26989
+ const parsed2 = ProviderEnvSchema.safeParse(process.env);
26990
+ const env = parsed2.success ? parsed2.data : ProviderEnvSchema.parse({});
26766
26991
  if (env.CLAUDE_CODE_USE_BEDROCK === true) return { provider: "bedrock" };
26767
26992
  if (env.CLAUDE_CODE_USE_VERTEX === true) return { provider: "vertex" };
26768
26993
  const baseUrl = env.ANTHROPIC_BASE_URL;
@@ -26779,8 +27004,8 @@ function resolveProvider() {
26779
27004
  function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
26780
27005
  try {
26781
27006
  ensureLayoutDirSync(base);
26782
- const settingsFile = join8(settingsDir(base), "settings.json");
26783
- if (existsSync5(settingsFile)) tightenFile(settingsFile);
27007
+ const settingsFile = join10(settingsDir(base), "settings.json");
27008
+ if (existsSync6(settingsFile)) tightenFile(settingsFile);
26784
27009
  } catch {
26785
27010
  }
26786
27011
  migrateLegacyLayout(base);
@@ -26803,9 +27028,9 @@ function resolveProviderSafe(resolveProviderFn) {
26803
27028
  }
26804
27029
 
26805
27030
  // ../../packages/plugin-sdk/src/config-inventory.ts
26806
- import { readdirSync as readdirSync2, readFileSync as readFileSync7, realpathSync, statSync as statSync5 } from "fs";
27031
+ import { readdirSync as readdirSync2, readFileSync as readFileSync8, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
26807
27032
  import { homedir as homedir2 } from "os";
26808
- import { basename as basename3, join as join10 } from "path";
27033
+ import { basename as basename3, join as join12 } from "path";
26809
27034
 
26810
27035
  // ../../packages/detections/src/egress/registry.ts
26811
27036
  var EXTRACTOR_VERSION = "1";
@@ -28582,10 +28807,10 @@ var localhost_ref_default = {
28582
28807
  severity: "low",
28583
28808
  matcher: {
28584
28809
  type: "regex",
28585
- pattern: "\\b(?:localhost|127\\.0\\.0\\.1|0\\.0\\.0\\.0|::1)\\b",
28810
+ 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_])",
28586
28811
  flags: "g"
28587
28812
  },
28588
- examples: ["localhost", "127.0.0.1"]
28813
+ examples: ["localhost", "127.0.0.1", "0.0.0.0", "::1"]
28589
28814
  };
28590
28815
 
28591
28816
  // ../../rules/core-code-context/stack-trace.json
@@ -29894,8 +30119,8 @@ function bundledDetections() {
29894
30119
  }
29895
30120
 
29896
30121
  // ../../packages/plugin-sdk/src/repo.ts
29897
- import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
29898
- import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
30122
+ import { existsSync as existsSync7, readFileSync as readFileSync7, statSync as statSync6 } from "fs";
30123
+ import { basename as basename2, dirname as dirname3, isAbsolute, join as join11, sep as sep2 } from "path";
29899
30124
  function resolveRepo(cwd) {
29900
30125
  try {
29901
30126
  const root = findGitRoot(cwd);
@@ -29917,36 +30142,36 @@ function resolveWorktreeRoot(cwd) {
29917
30142
  function findGitRoot(start) {
29918
30143
  let dir = start;
29919
30144
  for (; ; ) {
29920
- if (existsSync6(join9(dir, ".git"))) return dir;
29921
- const parent = dirname2(dir);
30145
+ if (existsSync7(join11(dir, ".git"))) return dir;
30146
+ const parent = dirname3(dir);
29922
30147
  if (parent === dir) return void 0;
29923
30148
  dir = parent;
29924
30149
  }
29925
30150
  }
29926
30151
  function resolveGitContext(root) {
29927
- const dotGit = join9(root, ".git");
30152
+ const dotGit = join11(root, ".git");
29928
30153
  try {
29929
- if (statSync4(dotGit).isDirectory()) {
29930
- return { configPath: join9(dotGit, "config"), headRoot: root };
30154
+ if (statSync6(dotGit).isDirectory()) {
30155
+ return { configPath: join11(dotGit, "config"), headRoot: root };
29931
30156
  }
29932
30157
  } catch {
29933
30158
  return void 0;
29934
30159
  }
29935
30160
  const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
29936
30161
  if (!target) return void 0;
29937
- const gitdir = isAbsolute(target) ? target : join9(root, target);
29938
- if (existsSync6(join9(gitdir, "config"))) {
29939
- return { configPath: join9(gitdir, "config"), headRoot: root };
30162
+ const gitdir = isAbsolute(target) ? target : join11(root, target);
30163
+ if (existsSync7(join11(gitdir, "config"))) {
30164
+ return { configPath: join11(gitdir, "config"), headRoot: root };
29940
30165
  }
29941
- const commonRaw = safeRead(join9(gitdir, "commondir"))?.trim();
30166
+ const commonRaw = safeRead(join11(gitdir, "commondir"))?.trim();
29942
30167
  if (!commonRaw) return void 0;
29943
- const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join9(gitdir, commonRaw);
29944
- const headRoot = basename2(commonGitDir) === ".git" ? dirname2(commonGitDir) : root;
29945
- return { configPath: join9(commonGitDir, "config"), headRoot };
30168
+ const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join11(gitdir, commonRaw);
30169
+ const headRoot = basename2(commonGitDir) === ".git" ? dirname3(commonGitDir) : root;
30170
+ return { configPath: join11(commonGitDir, "config"), headRoot };
29946
30171
  }
29947
30172
  function safeRead(path) {
29948
30173
  try {
29949
- return readFileSync6(path, "utf8");
30174
+ return readFileSync7(path, "utf8");
29950
30175
  } catch {
29951
30176
  return void 0;
29952
30177
  }
@@ -30007,7 +30232,7 @@ function buildIngestEvent(input) {
30007
30232
  }
30008
30233
 
30009
30234
  // ../../packages/plugin-sdk/src/isolated-scan.ts
30010
- import { existsSync as existsSync7 } from "fs";
30235
+ import { existsSync as existsSync8 } from "fs";
30011
30236
  import { fileURLToPath } from "url";
30012
30237
  import { Worker } from "worker_threads";
30013
30238
  var ISOLATED_SCAN_BUDGET_MS = 2e3;
@@ -30021,7 +30246,7 @@ function resolveWorkerUrl() {
30021
30246
  for (const name of ["scan-worker.js", "scan-worker.ts"]) {
30022
30247
  const candidate = new URL(name, import.meta.url);
30023
30248
  try {
30024
- if (existsSync7(fileURLToPath(candidate))) {
30249
+ if (existsSync8(fileURLToPath(candidate))) {
30025
30250
  resolvedWorkerUrl = candidate;
30026
30251
  return candidate;
30027
30252
  }
@@ -30206,8 +30431,8 @@ function createIsolatedScanner(data, opts = {}) {
30206
30431
  }
30207
30432
  function enqueue(spec) {
30208
30433
  const next = chain.then(
30209
- () => new Promise((resolve2) => {
30210
- spec(resolve2);
30434
+ () => new Promise((resolve3) => {
30435
+ spec(resolve3);
30211
30436
  })
30212
30437
  );
30213
30438
  chain = next.then(
@@ -30218,7 +30443,7 @@ function createIsolatedScanner(data, opts = {}) {
30218
30443
  }
30219
30444
  return {
30220
30445
  scan(text, context, scanOpts) {
30221
- return enqueue((resolve2) => {
30446
+ return enqueue((resolve3) => {
30222
30447
  runOne(
30223
30448
  {
30224
30449
  budgetMs,
@@ -30231,23 +30456,23 @@ function createIsolatedScanner(data, opts = {}) {
30231
30456
  }),
30232
30457
  reply: (message) => {
30233
30458
  if (message.kind !== "result") return false;
30234
- resolve2({ status: "ok", findings: message.findings });
30459
+ resolve3({ status: "ok", findings: message.findings });
30235
30460
  return true;
30236
30461
  }
30237
30462
  },
30238
- resolve2
30463
+ resolve3
30239
30464
  );
30240
30465
  });
30241
30466
  },
30242
30467
  probe(rule) {
30243
- return enqueue((resolve2) => {
30468
+ return enqueue((resolve3) => {
30244
30469
  runOne(
30245
30470
  {
30246
30471
  budgetMs: probeBudgetMs,
30247
30472
  build: (id) => ({ kind: "probe", id, rule }),
30248
30473
  reply: (message) => {
30249
30474
  if (message.kind !== "probed") return false;
30250
- resolve2({
30475
+ resolve3({
30251
30476
  status: "ok",
30252
30477
  verdict: message.verdict,
30253
30478
  worstMs: message.worstMs,
@@ -30256,7 +30481,7 @@ function createIsolatedScanner(data, opts = {}) {
30256
30481
  return true;
30257
30482
  }
30258
30483
  },
30259
- resolve2
30484
+ resolve3
30260
30485
  );
30261
30486
  });
30262
30487
  },
@@ -30486,19 +30711,19 @@ function createGuardedScanner(partition, gateway, opts) {
30486
30711
 
30487
30712
  // ../../packages/plugin-sdk/src/ignore-layers.ts
30488
30713
  var import_ignore = __toESM(require_ignore(), 1);
30489
- import { readFileSync as readFileSync8 } from "fs";
30490
- import { join as join11 } from "path";
30714
+ import { readFileSync as readFileSync9 } from "fs";
30715
+ import { join as join13 } from "path";
30491
30716
 
30492
30717
  // ../../packages/plugin-sdk/src/inventory-resolver.ts
30493
30718
  import { arch, hostname as hostname4, platform, release } from "os";
30494
30719
 
30495
30720
  // ../../packages/plugin-sdk/src/nudge.ts
30496
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
30497
- import { join as join12 } from "path";
30721
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
30722
+ import { join as join14 } from "path";
30498
30723
 
30499
30724
  // ../../packages/plugin-sdk/src/paths.ts
30500
- import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
30501
- import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
30725
+ import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
30726
+ import { basename as basename4, dirname as dirname4, sep as sep3 } from "path";
30502
30727
 
30503
30728
  // ../../packages/plugin-sdk/src/posture.ts
30504
30729
  function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
@@ -30511,8 +30736,8 @@ function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
30511
30736
  }
30512
30737
 
30513
30738
  // ../../packages/plugin-sdk/src/project-files.ts
30514
- import { existsSync as existsSync8, readdirSync as readdirSync4 } from "fs";
30515
- import { basename as basename5, join as join13 } from "path";
30739
+ import { existsSync as existsSync9, readdirSync as readdirSync4 } from "fs";
30740
+ import { basename as basename5, join as join15 } from "path";
30516
30741
 
30517
30742
  // ../../packages/plugin-sdk/src/provider-env-antigravity.ts
30518
30743
  var optionalBaseUrl2 = external_exports.preprocess((v) => {
@@ -30893,8 +31118,8 @@ function createPluginRuntime(gateway, settings, opts) {
30893
31118
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
30894
31119
 
30895
31120
  // ../../packages/plugin-sdk/src/throttle.ts
30896
- import { mkdirSync as mkdirSync3, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
30897
- import { join as join14 } from "path";
31121
+ import { mkdirSync as mkdirSync3, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
31122
+ import { join as join16 } from "path";
30898
31123
 
30899
31124
  // ../../packages/plugin-sdk/src/tokenize.ts
30900
31125
  function redactedPlaceholder(category) {
@@ -31237,7 +31462,7 @@ function routeRemediationOption(option, handlers) {
31237
31462
 
31238
31463
  // ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
31239
31464
  import { writeFileSync as writeFileSync7 } from "fs";
31240
- import { join as join15 } from "path";
31465
+ import { join as join17 } from "path";
31241
31466
  var GENERIC_CONSOLE_PATH = "rotate via the provider's own console";
31242
31467
  var CONSOLE_PATHS = {
31243
31468
  anthropic: "console.anthropic.com \u2192 Settings \u2192 API keys",
@@ -31337,7 +31562,7 @@ function generateRotationChecklist(input) {
31337
31562
  try {
31338
31563
  const target = resolveRotationChecklistTarget(input.cwd);
31339
31564
  targetDirectory = target.directory;
31340
- const filePath = join15(target.directory, "rotation-checklist.md");
31565
+ const filePath = join17(target.directory, "rotation-checklist.md");
31341
31566
  writeRotationChecklist(input.entries, target.directory);
31342
31567
  return {
31343
31568
  status: "written",
@@ -31448,9 +31673,9 @@ var RANK = Object.fromEntries(
31448
31673
  );
31449
31674
 
31450
31675
  // ../../packages/setup-wizard/src/triage/plan-file.ts
31451
- import { mkdtempSync, readFileSync as readFileSync10, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
31676
+ import { mkdtempSync, readFileSync as readFileSync11, rmdirSync, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
31452
31677
  import { tmpdir } from "os";
31453
- import { basename as basename6, dirname as dirname4, join as join16 } from "path";
31678
+ import { basename as basename6, dirname as dirname5, join as join18 } from "path";
31454
31679
  var SuppressionEntrySchema = external_exports.object({
31455
31680
  ruleId: external_exports.string(),
31456
31681
  category: DetectionCategory,
@@ -31654,10 +31879,1200 @@ function renderRemediationDecision(findings, moreCount, registry2) {
31654
31879
  }
31655
31880
 
31656
31881
  // src/remediation/surfaced-redact.ts
31882
+ import { readFileSync as readFileSync17 } from "fs";
31883
+
31884
+ // ../../packages/plugin-runtime/src/attached/failure.ts
31885
+ function statusOf(err) {
31886
+ if (typeof err !== "object" || err === null || !("status" in err)) return null;
31887
+ const { status } = err;
31888
+ if (typeof status !== "number" || !Number.isInteger(status)) return null;
31889
+ return status >= 100 && status <= 599 ? status : null;
31890
+ }
31891
+ function classifyFailure(err) {
31892
+ switch (statusOf(err)) {
31893
+ case 401:
31894
+ return "unauthorized";
31895
+ case 403:
31896
+ return "forbidden";
31897
+ default:
31898
+ return "unreachable";
31899
+ }
31900
+ }
31901
+
31902
+ // ../../packages/plugin-runtime/src/attached/forward-drops.ts
31903
+ import { readFileSync as readFileSync12 } from "fs";
31904
+ import { join as join19 } from "path";
31905
+ var FORWARD_DROPS_FILENAME = "attached-forward-drops.json";
31906
+ function forwardDropsPath(dataDir2) {
31907
+ return join19(dataDir2, FORWARD_DROPS_FILENAME);
31908
+ }
31909
+ function recordForwardDrops(dataDir2, count, nowMs) {
31910
+ if (count <= 0) return;
31911
+ try {
31912
+ ensureDataDirSync(dataDir2);
31913
+ const previous = readForwardDrops(dataDir2);
31914
+ const next = {
31915
+ droppedForwards: (previous?.droppedForwards ?? 0) + count,
31916
+ lastDropAtMs: nowMs
31917
+ };
31918
+ writeOwnerOnlyFileSync(forwardDropsPath(dataDir2), `${JSON.stringify(next)}
31919
+ `);
31920
+ } catch {
31921
+ }
31922
+ }
31923
+ function readForwardDrops(dataDir2) {
31924
+ try {
31925
+ const parsed2 = JSON.parse(readFileSync12(forwardDropsPath(dataDir2), "utf8"));
31926
+ if (typeof parsed2 !== "object" || parsed2 === null) return null;
31927
+ const record2 = parsed2;
31928
+ if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
31929
+ return null;
31930
+ }
31931
+ if (record2.droppedForwards <= 0) return null;
31932
+ if (typeof record2.lastDropAtMs !== "number" || !Number.isFinite(record2.lastDropAtMs)) {
31933
+ return null;
31934
+ }
31935
+ return { droppedForwards: record2.droppedForwards, lastDropAtMs: record2.lastDropAtMs };
31936
+ } catch {
31937
+ return null;
31938
+ }
31939
+ }
31940
+
31941
+ // ../../packages/plugin-runtime/src/attached/forward-policy.ts
31942
+ import { randomUUID as randomUUID15 } from "crypto";
31657
31943
  import { readFileSync as readFileSync13 } from "fs";
31944
+ import { readFile, rename, writeFile } from "fs/promises";
31945
+ import { join as join20 } from "path";
31946
+
31947
+ // ../../packages/plugin-runtime/src/attached/with-timeout.ts
31948
+ var REQUEST_TIMEOUT_MS = 2e3;
31949
+ function withTimeout(promise2, ms) {
31950
+ let timer;
31951
+ const timeout = new Promise((_, reject) => {
31952
+ timer = setTimeout(() => {
31953
+ reject(new Error("attached gateway request timed out"));
31954
+ }, ms);
31955
+ });
31956
+ promise2.catch(() => void 0);
31957
+ return Promise.race([promise2, timeout]).finally(() => {
31958
+ clearTimeout(timer);
31959
+ });
31960
+ }
31961
+
31962
+ // ../../packages/plugin-runtime/src/attached/forward-policy.ts
31963
+ function isInvalidRequest(err) {
31964
+ return typeof err === "object" && err !== null && err.name === "RemoteRequestInvalid";
31965
+ }
31966
+ var FORWARD_BUDGET_MS = 1500;
31967
+ var DECISION_PATH_BUDGET_MS = 800;
31968
+ var BREAKER_FAILURE_THRESHOLD = 3;
31969
+ var BREAKER_COOLDOWN_MS = 3e4;
31970
+ var CLOSED = { consecutiveFailures: 0, openedAtMs: null, lastFailure: null };
31971
+ var FAILURES = /* @__PURE__ */ new Set([
31972
+ "unauthorized",
31973
+ "forbidden",
31974
+ "unreachable"
31975
+ ]);
31976
+ var FORWARD_STATE_FILENAME = "attached-state.json";
31977
+ var STATE_FILENAME = FORWARD_STATE_FILENAME;
31978
+ function parseBreakerState(raw, nowMs) {
31979
+ try {
31980
+ const parsed2 = JSON.parse(raw);
31981
+ if (typeof parsed2 !== "object" || parsed2 === null) return null;
31982
+ const record2 = parsed2;
31983
+ const failures = typeof record2.consecutiveFailures === "number" && record2.consecutiveFailures >= 0 ? record2.consecutiveFailures : 0;
31984
+ const openedAtMs = typeof record2.openedAtMs === "number" && Number.isFinite(record2.openedAtMs) && record2.openedAtMs <= nowMs ? record2.openedAtMs : null;
31985
+ const lastFailure = typeof record2.lastFailure === "string" && FAILURES.has(record2.lastFailure) ? record2.lastFailure : null;
31986
+ return { consecutiveFailures: failures, openedAtMs, lastFailure };
31987
+ } catch {
31988
+ return null;
31989
+ }
31990
+ }
31991
+ function createForwardPolicy(deps) {
31992
+ const now = deps.now ?? (() => Date.now());
31993
+ const file2 = join20(deps.dir, STATE_FILENAME);
31994
+ let state = null;
31995
+ let loading = null;
31996
+ async function readState() {
31997
+ let raw;
31998
+ try {
31999
+ raw = await readFile(file2, "utf8");
32000
+ } catch {
32001
+ return { ...CLOSED };
32002
+ }
32003
+ return parseBreakerState(raw, now()) ?? { ...CLOSED };
32004
+ }
32005
+ async function load() {
32006
+ if (state !== null) return state;
32007
+ loading ??= readState().then((loaded) => {
32008
+ state = loaded;
32009
+ loading = null;
32010
+ return loaded;
32011
+ });
32012
+ return loading;
32013
+ }
32014
+ async function persist(next) {
32015
+ state = next;
32016
+ try {
32017
+ await ensureDataDir(deps.dir);
32018
+ const tmp = `${file2}.${randomUUID15()}.tmp`;
32019
+ await writeFile(tmp, JSON.stringify(next), { encoding: "utf8", mode: DATA_FILE_MODE });
32020
+ await rename(tmp, file2);
32021
+ } catch {
32022
+ }
32023
+ }
32024
+ return {
32025
+ async run(op, opts) {
32026
+ const budget = opts?.decisionPath === true ? DECISION_PATH_BUDGET_MS : FORWARD_BUDGET_MS;
32027
+ let current;
32028
+ try {
32029
+ current = await load();
32030
+ } catch {
32031
+ current = { ...CLOSED };
32032
+ }
32033
+ const at = now();
32034
+ if (current.openedAtMs !== null) {
32035
+ if (at - current.openedAtMs < BREAKER_COOLDOWN_MS) {
32036
+ return { ok: false, reason: "breaker-open" };
32037
+ }
32038
+ await persist({
32039
+ consecutiveFailures: current.consecutiveFailures,
32040
+ openedAtMs: at,
32041
+ lastFailure: current.lastFailure
32042
+ });
32043
+ }
32044
+ try {
32045
+ const value = await withTimeout(op(), budget);
32046
+ if (current.openedAtMs !== null || current.consecutiveFailures > 0) {
32047
+ await persist({ ...CLOSED });
32048
+ }
32049
+ return { ok: true, value };
32050
+ } catch (err) {
32051
+ if (isInvalidRequest(err)) return { ok: false, reason: "invalid-request" };
32052
+ const reason = classifyFailure(err);
32053
+ const failures = current.consecutiveFailures + 1;
32054
+ const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
32055
+ await persist({
32056
+ consecutiveFailures: failures,
32057
+ openedAtMs: shouldOpen ? now() : null,
32058
+ lastFailure: reason
32059
+ });
32060
+ return { ok: false, reason };
32061
+ }
32062
+ }
32063
+ };
32064
+ }
32065
+
32066
+ // ../../packages/plugin-runtime/src/attached/gateway.ts
32067
+ var ACTION_STRENGTH = {
32068
+ allow: 0,
32069
+ log: 1,
32070
+ warn: 2,
32071
+ redact: 3,
32072
+ block: 4
32073
+ };
32074
+ function ruleCategoryMap(wireRules, localRules) {
32075
+ const map2 = /* @__PURE__ */ new Map();
32076
+ for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
32077
+ for (const rule of localRules ?? []) map2.set(rule.id, rule.category);
32078
+ for (const pack of bundledDetections()) {
32079
+ for (const rule of pack.rules) map2.set(rule.id, rule.category);
32080
+ }
32081
+ return map2;
32082
+ }
32083
+ function strongerOf(a, b) {
32084
+ if (a === null) return b;
32085
+ if (b === null) return a;
32086
+ return ACTION_STRENGTH[a] >= ACTION_STRENGTH[b] ? a : b;
32087
+ }
32088
+ function policyKey(policy) {
32089
+ return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
32090
+ }
32091
+ function floorFor(policy, categoryByRuleId) {
32092
+ const category = "category" in policy.target ? policy.target.category : categoryByRuleId.get(policy.target.ruleId);
32093
+ return category === void 0 ? null : DEFAULT_ACTIONS[category];
32094
+ }
32095
+ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
32096
+ const merged = /* @__PURE__ */ new Map();
32097
+ const disabled = [];
32098
+ const remoteCategoryAction = /* @__PURE__ */ new Map();
32099
+ for (const policy of remotePolicies) {
32100
+ if (!policy.enabled) continue;
32101
+ if (!("category" in policy.target)) continue;
32102
+ if (remoteCategoryAction.has(policy.target.category)) continue;
32103
+ const floor = floorFor(policy, categoryByRuleId);
32104
+ remoteCategoryAction.set(
32105
+ policy.target.category,
32106
+ floor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[floor] ? floor : policy.action
32107
+ );
32108
+ }
32109
+ for (const policy of localPolicies) {
32110
+ if (!policy.enabled) {
32111
+ disabled.push(policy);
32112
+ continue;
32113
+ }
32114
+ const key = policyKey(policy);
32115
+ if (merged.has(key)) continue;
32116
+ let remoteFloor = null;
32117
+ if ("ruleId" in policy.target) {
32118
+ const category = categoryByRuleId.get(policy.target.ruleId);
32119
+ if (category !== void 0) remoteFloor = remoteCategoryAction.get(category) ?? null;
32120
+ }
32121
+ merged.set(
32122
+ key,
32123
+ remoteFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[remoteFloor] ? { ...policy, action: remoteFloor } : policy
32124
+ );
32125
+ }
32126
+ const localCategoryAction = /* @__PURE__ */ new Map();
32127
+ for (const policy of merged.values()) {
32128
+ if ("category" in policy.target) localCategoryAction.set(policy.target.category, policy.action);
32129
+ }
32130
+ for (const policy of remotePolicies) {
32131
+ if (!policy.enabled) {
32132
+ disabled.push(policy);
32133
+ continue;
32134
+ }
32135
+ const key = policyKey(policy);
32136
+ const floor = floorFor(policy, categoryByRuleId);
32137
+ let localFloor = null;
32138
+ if ("ruleId" in policy.target) {
32139
+ const category = categoryByRuleId.get(policy.target.ruleId);
32140
+ if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
32141
+ }
32142
+ const effectiveFloor = strongerOf(floor, localFloor);
32143
+ const clamped = effectiveFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[effectiveFloor] ? { ...policy, action: effectiveFloor } : policy;
32144
+ const existing = merged.get(key);
32145
+ if (existing === void 0) {
32146
+ merged.set(key, clamped);
32147
+ continue;
32148
+ }
32149
+ if (ACTION_STRENGTH[clamped.action] > ACTION_STRENGTH[existing.action]) {
32150
+ merged.set(key, clamped);
32151
+ }
32152
+ }
32153
+ return [...merged.values(), ...disabled];
32154
+ }
32155
+ var AttachedDataGateway = class {
32156
+ constructor(deps) {
32157
+ this.deps = deps;
32158
+ }
32159
+ deps;
32160
+ /**
32161
+ * The control plane's OWN resolution of this session's inventory, captured by
32162
+ * ensureInventory. Null until the first successful forward — and it stays
32163
+ * null for the whole session when the control plane is unreachable, which is fine:
32164
+ * reKeyForForward then leaves the event's ids alone and the control plane resolves
32165
+ * what it can from the descriptors it already has.
32166
+ */
32167
+ remoteInventory = null;
32168
+ // ---------------------------------------------------------------------
32169
+ // Writes: local first, then forward.
32170
+ // ---------------------------------------------------------------------
32171
+ async recordCapture(record2) {
32172
+ await this.deps.local.recordCapture(record2);
32173
+ await this.deps.forward.run(
32174
+ () => this.deps.client.ingestEvents({
32175
+ events: [record2.event],
32176
+ ...record2.dedupe ? { dedupe: record2.dedupe } : {}
32177
+ }),
32178
+ { decisionPath: true }
32179
+ );
32180
+ }
32181
+ async ensureInventory(ctx) {
32182
+ const resolved = await this.deps.local.ensureInventory(ctx);
32183
+ const remote = await this.deps.forward.run(() => this.deps.client.ingestInventory(ctx));
32184
+ this.remoteInventory = remote.ok ? remote.value : null;
32185
+ const snapshot = await (async () => {
32186
+ try {
32187
+ return await this.deps.posture?.prepare() ?? null;
32188
+ } catch {
32189
+ return null;
32190
+ }
32191
+ })();
32192
+ if (snapshot) {
32193
+ try {
32194
+ await withTimeout(
32195
+ this.deps.posture?.send(snapshot) ?? Promise.resolve(),
32196
+ REQUEST_TIMEOUT_MS
32197
+ );
32198
+ } catch {
32199
+ }
32200
+ }
32201
+ return resolved;
32202
+ }
32203
+ // The id is minted CLIENT-side and stored verbatim: the control plane does NOT
32204
+ // re-key it. `pgAuditValues` writes `id: event.id` and carries tenancy in
32205
+ // its own scoping columns, so the device and the forwarded copy
32206
+ // share one id space — which is what makes a re-post idempotent at all.
32207
+ //
32208
+ // Re-posts collapse via `onConflictDoUpdate` on the `id` PK, guarded by
32209
+ // `setWhere eventType = 'session'` (NOT onConflictDoNothing). That guard is
32210
+ // what makes an attached retry safe: a capture-stubbed session row can still
32211
+ // be HEALED by the authoritative root, while a duplicate non-session event —
32212
+ // a retried tool_call, exactly this path — can never stomp a populated row.
32213
+ async recordAuditEvent(event) {
32214
+ await this.deps.local.recordAuditEvent(event);
32215
+ await this.deps.forward.run(
32216
+ () => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
32217
+ );
32218
+ }
32219
+ // Attached `llm_call` is written locally by the inner gateway, then routed to
32220
+ // the control plane through the existing `recordAuditEvent` ingest (no dedicated
32221
+ // client method yet) by pre-building the audit event from the natural key.
32222
+ // The forward goes DIRECTLY to the client rather than through this.recordAuditEvent,
32223
+ // which would write the event to the local store a second time.
32224
+ async recordLlmCall(input) {
32225
+ await this.deps.local.recordLlmCall(input);
32226
+ await this.deps.forward.run(
32227
+ () => this.deps.client.recordAuditEvent(
32228
+ reKeyForForward(llmAuditEvent(input), this.remoteInventory)
32229
+ )
32230
+ );
32231
+ }
32232
+ /**
32233
+ * Forward one batch, item by item, under ONE aggregate deadline.
32234
+ *
32235
+ * Per-item budgets bound each request and nothing bounded their sum — see
32236
+ * BATCH_FORWARD_BUDGET_MS. When the deadline passes the remainder is dropped
32237
+ * rather than sent: the local write has already succeeded, so every caller
32238
+ * has a correct result to return, and a drop is the outcome this path is
32239
+ * built to accept (G8) where a blown hook timeout is not.
32240
+ *
32241
+ * Serial rather than concurrent on purpose. Firing N requests at once would
32242
+ * trade a latency problem for a burst the plane's own per-key rate limiting
32243
+ * would answer with the refusals the breaker then counts.
32244
+ *
32245
+ * WHAT IS DROPPED IS COUNTED. Every other forward failure ends in
32246
+ * `ForwardPolicy.run`'s catch and moves the breaker's file, which is what
32247
+ * lets status call the forward unhealthy; this path returns BEFORE `run` is
32248
+ * reached, so without the tally in `forward-drops.ts` a slow-but-answering
32249
+ * plane produces no failures, keeps the breaker closed, renders a healthy
32250
+ * block, and discards the tail of every batch indefinitely.
32251
+ */
32252
+ async forwardBatch(inputs, toEvent) {
32253
+ const deadline = Date.now() + BATCH_FORWARD_BUDGET_MS;
32254
+ for (let i = 0; i < inputs.length; i += 1) {
32255
+ const now = Date.now();
32256
+ if (now >= deadline) {
32257
+ recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
32258
+ return;
32259
+ }
32260
+ const input = inputs[i];
32261
+ await this.deps.forward.run(
32262
+ () => this.deps.client.recordAuditEvent(reKeyForForward(toEvent(input), this.remoteInventory))
32263
+ );
32264
+ }
32265
+ }
32266
+ // Delegated as a BATCH rather than looped over recordLlmCall: the inner
32267
+ // gateway may write the whole batch in one local transaction, and looping
32268
+ // here would replace that with N separate local writes.
32269
+ async recordLlmCalls(inputs) {
32270
+ await this.deps.local.recordLlmCalls(inputs);
32271
+ await this.forwardBatch(inputs, (input) => llmAuditEvent(input));
32272
+ }
32273
+ // `input.inspections` (secrets detected client-side in the tool's masked
32274
+ // target) ride along on the request's `inspections` field — the control plane
32275
+ // persists each as an inspection_findings row linked to this audit event
32276
+ // (see RecordAuditEventRequest in @akasecurity/schema). The masked
32277
+ // `target` already rides `input.attributes`, so no raw secret leaks either
32278
+ // way — this only stops the FINDING row itself from being dropped.
32279
+ async recordToolCalls(inputs) {
32280
+ await this.deps.local.recordToolCalls(inputs);
32281
+ await this.forwardBatch(inputs, (input) => toolAuditEvent(input));
32282
+ }
32283
+ // Forwarded as a `config_scan` audit event: there is no dedicated
32284
+ // config-scan ingest endpoint, and the audit-event door is the one the
32285
+ // control plane already opens for client-minted, idempotent records.
32286
+ //
32287
+ // ONLY `scanEvent` CROSSES, and unlike `recordCapture` the plane cannot
32288
+ // re-derive the rest. A `ConfigScanRecord` is four things committed together
32289
+ // locally — the inventory `items`, this audit event, and the posture
32290
+ // `definitions`/`findings` that reference it — and three of them stay on the
32291
+ // device. Say that plainly rather than let the asymmetry with `recordCapture`
32292
+ // read as the same argument: there, findings are omitted BECAUSE the plane
32293
+ // re-derives them from `Event.content`; here there is no content to re-derive
32294
+ // from, so what is omitted is simply not sent.
32295
+ //
32296
+ // That is the wire contract as it stands rather than an oversight to patch
32297
+ // here. `items` has no route at all, and `RecordAuditEventRequest.inspections`
32298
+ // is documented as tool-call findings — widening it to carry config-scan
32299
+ // findings is an egress change (a posture finding's `maskedMatch` holds the
32300
+ // matched command) and a decision about what an attached deployment is
32301
+ // entitled to, not a bug fix. An attached machine's config posture therefore
32302
+ // reaches the plane as the event only; the dashboard's own view of it is the
32303
+ // local store.
32304
+ async recordConfigScan(record2) {
32305
+ await this.deps.local.recordConfigScan(record2);
32306
+ await this.deps.forward.run(
32307
+ () => this.deps.client.recordAuditEvent(reKeyForForward(record2.scanEvent, this.remoteInventory))
32308
+ );
32309
+ }
32310
+ async recordBlockedDetection(entry) {
32311
+ return this.deps.local.recordBlockedDetection(entry);
32312
+ }
32313
+ /**
32314
+ * LOCAL-ONLY, deliberately. The shares API is read-plus-decision-override
32315
+ * with no egress ingest endpoint, so there is nothing to forward to; adding a
32316
+ * forward here would be inventing a wire contract that does not exist. The
32317
+ * local write is the whole operation, and its summary is the real one — the
32318
+ * scanner reads a throw as a FAILED WRITE and skips its ledger commit, so
32319
+ * returning the inner gateway's result keeps the retry semantics honest.
32320
+ */
32321
+ async recordProjectEgress(input) {
32322
+ return this.deps.local.recordProjectEgress(input);
32323
+ }
32324
+ // ---------------------------------------------------------------------
32325
+ // Reads and device-local ledgers: pure delegation.
32326
+ // ---------------------------------------------------------------------
32327
+ async configInventoryReport() {
32328
+ return this.deps.local.configInventoryReport();
32329
+ }
32330
+ async readSessionProvider(sessionId) {
32331
+ return this.deps.local.readSessionProvider(sessionId);
32332
+ }
32333
+ async facets() {
32334
+ return this.deps.local.facets();
32335
+ }
32336
+ /**
32337
+ * Delegated UNMODIFIED — including its refusals.
32338
+ *
32339
+ * This is a fail-secure boundary: it decides whether an approved exception
32340
+ * lets a blocked action through. Under local-first the local store owns the
32341
+ * exception ledger, so the honest answer is whatever it says; wrapping this
32342
+ * in a fallback (`catch { return true }`, or defaulting on a timeout) would
32343
+ * turn a store error into a granted bypass. If the inner gateway rejects,
32344
+ * this rejects, and the runtime's own handling decides — which is asserted
32345
+ * end-to-end through runtime.capture rather than here.
32346
+ */
32347
+ async consumeException(id) {
32348
+ return this.deps.local.consumeException(id);
32349
+ }
32350
+ async recentFindings(opts) {
32351
+ return this.deps.local.recentFindings(opts);
32352
+ }
32353
+ async healthSummary() {
32354
+ return this.deps.local.healthSummary();
32355
+ }
32356
+ async activityByDay(days) {
32357
+ return this.deps.local.activityByDay(days);
32358
+ }
32359
+ async tokenReports() {
32360
+ return this.deps.local.tokenReports();
32361
+ }
32362
+ async knownContentHashes() {
32363
+ return this.deps.local.knownContentHashes();
32364
+ }
32365
+ async scanLedger(rulesetHash) {
32366
+ return this.deps.local.scanLedger(rulesetHash);
32367
+ }
32368
+ async recordScanned(entries) {
32369
+ return this.deps.local.recordScanned(entries);
32370
+ }
32371
+ async getRuleProbeVerdict(ruleKey) {
32372
+ return this.deps.local.getRuleProbeVerdict(ruleKey);
32373
+ }
32374
+ async setRuleProbeVerdict(ruleKey, verdict, worstProbeMs2) {
32375
+ return this.deps.local.setRuleProbeVerdict(ruleKey, verdict, worstProbeMs2);
32376
+ }
32377
+ async openAtRestKeysForPath(path) {
32378
+ return this.deps.local.openAtRestKeysForPath(path);
32379
+ }
32380
+ async resolvedAtRestKeysForPath(path) {
32381
+ return this.deps.local.resolvedAtRestKeysForPath(path);
32382
+ }
32383
+ async insertResolution(input) {
32384
+ return this.deps.local.insertResolution(input);
32385
+ }
32386
+ async close() {
32387
+ return this.deps.local.close();
32388
+ }
32389
+ // ---------------------------------------------------------------------
32390
+ // Policy
32391
+ // ---------------------------------------------------------------------
32392
+ async getPolicyBundle() {
32393
+ const local = await this.deps.local.getPolicyBundle();
32394
+ const cached2 = await (async () => {
32395
+ try {
32396
+ return await this.deps.readCachedBundle();
32397
+ } catch {
32398
+ return null;
32399
+ }
32400
+ })();
32401
+ if (cached2 === null) return local;
32402
+ const byRuleId = /* @__PURE__ */ new Map();
32403
+ for (const rule of [...local.rules ?? [], ...cached2.rules ?? []]) {
32404
+ if (!byRuleId.has(rule.id)) byRuleId.set(rule.id, rule);
32405
+ }
32406
+ const rules = [...byRuleId.values()];
32407
+ return {
32408
+ ...local,
32409
+ // The remote version identifies the composed bundle for the poller.
32410
+ version: cached2.version,
32411
+ rules,
32412
+ policies: mergeRaiseOnly(
32413
+ local.policies,
32414
+ cached2.policies,
32415
+ ruleCategoryMap(cached2.rules, local.rules)
32416
+ ),
32417
+ customKeywords: [...local.customKeywords, ...cached2.customKeywords]
32418
+ // `rulesComplete` is a STANDALONE-ONLY signal (the user's local installed
32419
+ // snapshot) and is taken from the LOCAL bundle only — never from the wire
32420
+ // or the on-disk cache. Honoring a cached one would hand the control plane, or
32421
+ // anything able to write policy-cache.json, a kill-switch over the
32422
+ // compiled-in bundled packs: `{ rulesComplete: true, rules: [] }` would
32423
+ // zero local detection. Spread from `local` above, and deliberately not
32424
+ // re-read from `cached` here.
32425
+ //
32426
+ // THREE MORE OF THE CACHED BUNDLE'S FIELDS ARE DROPPED, each on purpose,
32427
+ // and each named here so a reader can tell a decision from an omission:
32428
+ //
32429
+ // `exceptions` — an exception SUPPRESSES a detection, so honoring
32430
+ // one from an unsigned on-disk cache would let
32431
+ // anything able to write that file turn rules off.
32432
+ // Every other field this merge accepts can only
32433
+ // RAISE enforcement; this is the one that cannot,
32434
+ // so it stays local-only until the bundle is
32435
+ // signed. Exceptions remain a device-local ledger.
32436
+ // `reversibleRuleIds` — the Redact & Vault archetype makes a redaction
32437
+ // recoverable, which is a CUSTODY change: it puts
32438
+ // the detected value in the local vault instead of
32439
+ // destroying it. Taking that instruction from the
32440
+ // cache would let a remote party turn one-way
32441
+ // redaction into retention. Dropping it keeps the
32442
+ // one-way behaviour, which the schema itself calls
32443
+ // "the safe direction to default".
32444
+ // `ruleVersions` — remote rules fall back to their own spec version.
32445
+ // Cosmetic rather than protective: it only affects
32446
+ // how a finding is version-attributed, and the two
32447
+ // sides may therefore attribute org rules
32448
+ // differently. Worth carrying once there is a
32449
+ // reader that needs it; nothing reads it today.
32450
+ };
32451
+ }
32452
+ // ---------------------------------------------------------------------
32453
+ // LocalStoreMaintenance — by delegation (D3).
32454
+ //
32455
+ // Implementing these is what actually closes the skipped-local-maintenance
32456
+ // gap: the OSS structural guard `hasLocalStoreMaintenance()` is satisfied by
32457
+ // any object carrying all five, so the composite qualifies and SessionStart
32458
+ // runs maintenance on the device's real store.
32459
+ //
32460
+ // ⚠ Two of the five are SYNCHRONOUS and must stay that way. `handle-session-start`
32461
+ // calls `capWarnEraEnforcement` without `await` and uses `staleBinaryNotice`'s
32462
+ // return value directly; declaring them `async` here would hand those call
32463
+ // sites a Promise and silently break both.
32464
+ // ---------------------------------------------------------------------
32465
+ async sweepTerminalExceptions(retentionMs) {
32466
+ return this.deps.local.sweepTerminalExceptions(retentionMs);
32467
+ }
32468
+ capWarnEraEnforcement(policyMode) {
32469
+ return this.deps.local.capWarnEraEnforcement(policyMode);
32470
+ }
32471
+ async recordProjectFiles(projectId, scan2) {
32472
+ return this.deps.local.recordProjectFiles(projectId, scan2);
32473
+ }
32474
+ async reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot) {
32475
+ return this.deps.local.reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot);
32476
+ }
32477
+ staleBinaryNotice(currentVersion) {
32478
+ return this.deps.local.staleBinaryNotice(currentVersion);
32479
+ }
32480
+ };
32481
+ function reKeyForForward(event, remote) {
32482
+ if (remote === null) {
32483
+ const stripped = { ...event };
32484
+ delete stripped.hostId;
32485
+ delete stripped.harnessId;
32486
+ delete stripped.sourceProjectId;
32487
+ return stripped;
32488
+ }
32489
+ const rekeyed = { ...event };
32490
+ delete rekeyed.hostId;
32491
+ delete rekeyed.harnessId;
32492
+ delete rekeyed.sourceProjectId;
32493
+ if (remote.hostId !== void 0) rekeyed.hostId = remote.hostId;
32494
+ if (remote.harnessId !== void 0) rekeyed.harnessId = remote.harnessId;
32495
+ if (remote.sourceProjectId !== void 0) rekeyed.sourceProjectId = remote.sourceProjectId;
32496
+ return rekeyed;
32497
+ }
32498
+ var BATCH_FORWARD_BUDGET_MS = 3e3;
32499
+ function llmAuditEvent(input) {
32500
+ return {
32501
+ id: llmCallId(input.sessionId, input.messageId),
32502
+ eventType: "llm_call",
32503
+ startedAt: input.startedAt,
32504
+ parentId: input.parentId,
32505
+ rootSessionId: input.rootSessionId,
32506
+ attributes: input.attributes
32507
+ };
32508
+ }
32509
+ function toolAuditEvent(input) {
32510
+ return {
32511
+ id: toolCallId(input.sessionId, input.toolUseId),
32512
+ eventType: "tool_call",
32513
+ startedAt: input.startedAt,
32514
+ parentId: input.parentId,
32515
+ rootSessionId: input.rootSessionId,
32516
+ attributes: input.attributes,
32517
+ inspections: input.inspections
32518
+ };
32519
+ }
32520
+
32521
+ // ../../packages/plugin-runtime/src/attached/policy-store.ts
32522
+ import { randomUUID as randomUUID16 } from "crypto";
32523
+ import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
32524
+ import { join as join21 } from "path";
32525
+
32526
+ // ../../packages/plugin-runtime/src/attached/atomic-publish.ts
32527
+ import { rename as rename2 } from "fs/promises";
32528
+ var RETRYABLE = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY"]);
32529
+ var ATTEMPTS = 5;
32530
+ var delay = (ms) => new Promise((resolve3) => {
32531
+ setTimeout(resolve3, ms);
32532
+ });
32533
+ async function publishByRename(tmp, file2, move = rename2) {
32534
+ for (let attempt = 1; ; attempt += 1) {
32535
+ try {
32536
+ await move(tmp, file2);
32537
+ return;
32538
+ } catch (err) {
32539
+ const code = err.code;
32540
+ if (attempt >= ATTEMPTS || code === void 0 || !RETRYABLE.has(code)) throw err;
32541
+ await delay(attempt * 10);
32542
+ }
32543
+ }
32544
+ }
32545
+
32546
+ // ../../packages/plugin-runtime/src/attached/policy-store.ts
32547
+ function createPolicyStore(dir = dataDir()) {
32548
+ const file2 = join21(dir, "policy-cache.json");
32549
+ async function read() {
32550
+ try {
32551
+ const raw = await readFile2(file2, "utf8");
32552
+ const parsed2 = JSON.parse(raw);
32553
+ if (typeof parsed2 !== "object" || parsed2 === null) return null;
32554
+ const record2 = parsed2;
32555
+ const bundle = PolicyBundle.parse(record2.bundle);
32556
+ const fetchedAtMs = typeof record2.fetchedAtMs === "number" ? record2.fetchedAtMs : 0;
32557
+ const etag = typeof record2.etag === "string" ? record2.etag : void 0;
32558
+ return { bundle, fetchedAtMs, ...etag === void 0 ? {} : { etag } };
32559
+ } catch {
32560
+ return null;
32561
+ }
32562
+ }
32563
+ async function write(bundle, etag) {
32564
+ await ensureDataDir(dir);
32565
+ const stored = {
32566
+ bundle,
32567
+ fetchedAtMs: Date.now(),
32568
+ ...etag === void 0 ? {} : { etag }
32569
+ };
32570
+ const tmp = `${file2}.${randomUUID16()}.tmp`;
32571
+ try {
32572
+ await writeFile2(tmp, JSON.stringify(stored), {
32573
+ encoding: "utf8",
32574
+ mode: DATA_FILE_MODE,
32575
+ flag: "wx"
32576
+ });
32577
+ await publishByRename(tmp, file2);
32578
+ } catch (err) {
32579
+ await rm(tmp, { force: true }).catch(() => void 0);
32580
+ throw err;
32581
+ }
32582
+ }
32583
+ return { read, write, file: file2 };
32584
+ }
32585
+
32586
+ // ../../packages/remote/src/http.ts
32587
+ import { request as httpRequest } from "http";
32588
+ import { request as httpsRequest } from "https";
32589
+ var DEFAULT_TIMEOUT_MS = 1e4;
32590
+ var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
32591
+ var RemoteRequestError = class extends Error {
32592
+ constructor(status) {
32593
+ super(`control-plane request failed with status ${String(status)}`);
32594
+ this.status = status;
32595
+ this.name = "RemoteRequestError";
32596
+ }
32597
+ status;
32598
+ };
32599
+ var RemoteRequestInvalid = class extends Error {
32600
+ constructor(route2, cause) {
32601
+ super(`refusing to send a malformed body to ${route2}`);
32602
+ this.cause = cause;
32603
+ this.name = "RemoteRequestInvalid";
32604
+ }
32605
+ cause;
32606
+ };
32607
+ var RemoteResponseInvalid = class extends Error {
32608
+ constructor(route2, detail) {
32609
+ super(`control plane answered ${route2} with ${detail}`);
32610
+ this.name = "RemoteResponseInvalid";
32611
+ }
32612
+ };
32613
+ var RemoteTransportError = class extends Error {
32614
+ /**
32615
+ * The status the peer sent, when headers arrived and only the BODY was
32616
+ * refused.
32617
+ *
32618
+ * Undefined for the ordinary case this class was written for — no answer at
32619
+ * all. It exists because two paths reject after a status has already been
32620
+ * delivered: an oversized body and an aborted response. Discarding it there
32621
+ * reported a deployment answering 401 with a verbose body as a network
32622
+ * outage, which sends the reader to look at their network instead of their
32623
+ * credential.
32624
+ */
32625
+ constructor(reason, status) {
32626
+ super(`control-plane request did not complete: ${reason}`);
32627
+ this.status = status;
32628
+ this.name = "RemoteTransportError";
32629
+ }
32630
+ status;
32631
+ };
32632
+ async function send(options) {
32633
+ const url2 = new URL(options.url);
32634
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
32635
+ const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
32636
+ const requestOptions = {
32637
+ method: options.method,
32638
+ headers: {
32639
+ // CALLER HEADERS FIRST, so this module's own are not overridable. Spread
32640
+ // last they win, and two of the values below are ones no caller may
32641
+ // replace: `x-api-key` is the credential, and `content-length` is the
32642
+ // byte count that stops a multi-byte body being truncated by the
32643
+ // receiver. `SendOptions.headers` is a free-form record on an exported
32644
+ // function, so "no caller does that today" is not the guarantee to rely
32645
+ // on. The one header any caller actually passes — `if-none-match` on the
32646
+ // conditional GET — is untouched by this order.
32647
+ ...options.headers,
32648
+ // The credential. One header, matching what the deployment authenticates
32649
+ // on; a second copy in an `Authorization` header would be one more place
32650
+ // it can be logged by an intermediary for no gain.
32651
+ "x-api-key": options.apiKey,
32652
+ accept: "application/json",
32653
+ ...options.body === void 0 ? {} : {
32654
+ "content-type": "application/json",
32655
+ // Byte length, not string length: a multi-byte body sent with a
32656
+ // character count is truncated by the receiver.
32657
+ "content-length": String(Buffer.byteLength(options.body))
32658
+ }
32659
+ }
32660
+ };
32661
+ return new Promise((resolve3, reject) => {
32662
+ let settled = false;
32663
+ const fail2 = (reason, status) => {
32664
+ if (settled) return;
32665
+ settled = true;
32666
+ reject(new RemoteTransportError(reason, status));
32667
+ };
32668
+ const req = send_(url2, requestOptions, (res) => {
32669
+ const chunks = [];
32670
+ let size = 0;
32671
+ res.on("data", (chunk) => {
32672
+ size += chunk.length;
32673
+ if (size > MAX_RESPONSE_BYTES) {
32674
+ fail2(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
32675
+ res.destroy();
32676
+ req.destroy();
32677
+ return;
32678
+ }
32679
+ chunks.push(chunk);
32680
+ });
32681
+ res.on("aborted", () => {
32682
+ fail2("the response was aborted", res.statusCode);
32683
+ });
32684
+ res.on("end", () => {
32685
+ if (settled) return;
32686
+ settled = true;
32687
+ resolve3({
32688
+ status: res.statusCode ?? 0,
32689
+ headers: res.headers,
32690
+ body: Buffer.concat(chunks).toString("utf8")
32691
+ });
32692
+ });
32693
+ });
32694
+ const deadline = setTimeout(() => {
32695
+ fail2(`no response within ${String(timeoutMs)}ms`);
32696
+ req.destroy();
32697
+ }, timeoutMs);
32698
+ deadline.unref();
32699
+ req.on("upgrade", (_res, socket) => {
32700
+ fail2("the deployment answered with a protocol upgrade");
32701
+ socket.destroy();
32702
+ });
32703
+ req.on("close", () => {
32704
+ fail2("the connection closed before a response was read");
32705
+ clearTimeout(deadline);
32706
+ });
32707
+ req.on("error", (err) => {
32708
+ fail2(err.message);
32709
+ });
32710
+ if (options.body !== void 0) req.write(options.body);
32711
+ req.end();
32712
+ });
32713
+ }
32714
+
32715
+ // ../../packages/remote/src/client.ts
32716
+ var ROUTES = {
32717
+ events: "/v1/events",
32718
+ auditEvents: "/v1/audit-events",
32719
+ inventory: "/v1/inventory",
32720
+ storePosture: "/v1/store-posture",
32721
+ policyBundle: "/v1/policy-bundle",
32722
+ whoami: "/v1/plugin/whoami"
32723
+ };
32724
+ function headerValue(response, name) {
32725
+ const raw = response.headers[name];
32726
+ if (raw === void 0) return void 0;
32727
+ return Array.isArray(raw) ? raw[0] : raw;
32728
+ }
32729
+ function okBody(response) {
32730
+ if (response.status < 200 || response.status >= 300) {
32731
+ throw new RemoteRequestError(response.status);
32732
+ }
32733
+ return response.body;
32734
+ }
32735
+ function parsed(schema, body, route2) {
32736
+ let json2;
32737
+ try {
32738
+ json2 = JSON.parse(body);
32739
+ } catch {
32740
+ throw new RemoteResponseInvalid(route2, "a body that is not JSON");
32741
+ }
32742
+ const result = schema.safeParse(json2);
32743
+ if (!result.success) {
32744
+ throw new RemoteResponseInvalid(route2, "a body this client cannot read");
32745
+ }
32746
+ return result.data;
32747
+ }
32748
+ function withoutTrailingSlashes(endpoint) {
32749
+ let end = endpoint.length;
32750
+ while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
32751
+ return endpoint.slice(0, end);
32752
+ }
32753
+ var SLASH = "/".charCodeAt(0);
32754
+ function createRemoteClient(options) {
32755
+ const base = withoutTrailingSlashes(options.endpoint);
32756
+ const url2 = (route2) => `${base}${route2}`;
32757
+ const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
32758
+ return {
32759
+ async ingestEvents(batch) {
32760
+ const response = await send({
32761
+ ...common,
32762
+ method: "POST",
32763
+ url: url2(ROUTES.events),
32764
+ body: JSON.stringify(batch)
32765
+ });
32766
+ return parsed(IngestAck, okBody(response), ROUTES.events);
32767
+ },
32768
+ async ingestInventory(context) {
32769
+ const response = await send({
32770
+ ...common,
32771
+ method: "POST",
32772
+ url: url2(ROUTES.inventory),
32773
+ body: JSON.stringify(context)
32774
+ });
32775
+ return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
32776
+ },
32777
+ async recordAuditEvent(event) {
32778
+ const validated = RecordAuditEventRequest.safeParse(event);
32779
+ if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
32780
+ const submission = validated.data;
32781
+ const response = await send({
32782
+ ...common,
32783
+ method: "POST",
32784
+ url: url2(ROUTES.auditEvents),
32785
+ body: JSON.stringify(submission)
32786
+ });
32787
+ okBody(response);
32788
+ },
32789
+ async reportStorePosture(snapshot) {
32790
+ const response = await send({
32791
+ ...common,
32792
+ method: "POST",
32793
+ url: url2(ROUTES.storePosture),
32794
+ body: JSON.stringify(snapshot)
32795
+ });
32796
+ okBody(response);
32797
+ },
32798
+ async getPolicyBundle(etag) {
32799
+ const response = await send({
32800
+ ...common,
32801
+ method: "GET",
32802
+ url: url2(ROUTES.policyBundle),
32803
+ ...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
32804
+ });
32805
+ if (response.status === 304) {
32806
+ return { changed: false, etag: headerValue(response, "etag") ?? etag };
32807
+ }
32808
+ const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
32809
+ return { changed: true, bundle, etag: headerValue(response, "etag") };
32810
+ },
32811
+ async whoami() {
32812
+ const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
32813
+ return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
32814
+ }
32815
+ };
32816
+ }
32817
+
32818
+ // ../../packages/plugin-runtime/src/attached/posture-reporter.ts
32819
+ var POSTURE_REPORT_INTERVAL_MS = 60 * 60 * 1e3;
32820
+ function createPostureReporter(deps) {
32821
+ async function prepare() {
32822
+ try {
32823
+ const state = await withTimeout(deps.store.read(), REQUEST_TIMEOUT_MS);
32824
+ if (state === null) return null;
32825
+ const nowMs = deps.now();
32826
+ const elapsed = nowMs - state.lastAttemptedAtMs;
32827
+ if (elapsed >= 0 && elapsed < POSTURE_REPORT_INTERVAL_MS) return null;
32828
+ try {
32829
+ await withTimeout(deps.store.markAttempted(state.deviceId, nowMs), REQUEST_TIMEOUT_MS);
32830
+ } catch {
32831
+ }
32832
+ const { readError, ...measurement } = deps.readStore();
32833
+ if (readError) return null;
32834
+ let plugin;
32835
+ try {
32836
+ plugin = await deps.pluginBlock?.();
32837
+ } catch {
32838
+ plugin = void 0;
32839
+ }
32840
+ return {
32841
+ deviceId: state.deviceId,
32842
+ hostname: deps.hostname(),
32843
+ capturedAt: nowMs,
32844
+ ...measurement,
32845
+ // Omit the key rather than spread an explicit `undefined` —
32846
+ // exactOptionalPropertyTypes distinguishes the two, and the bridge in
32847
+ // factory.ts keys on presence.
32848
+ ...plugin === void 0 ? {} : { plugin }
32849
+ };
32850
+ } catch {
32851
+ return null;
32852
+ }
32853
+ }
32854
+ async function send2(snapshot) {
32855
+ try {
32856
+ await deps.report(snapshot);
32857
+ } catch {
32858
+ }
32859
+ }
32860
+ return { prepare, send: send2 };
32861
+ }
32862
+
32863
+ // ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
32864
+ import { statSync as statSync9 } from "fs";
32865
+ import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
32866
+
32867
+ // ../../packages/plugin-runtime/src/attached/action-counts.ts
32868
+ function emptyActionCounts() {
32869
+ return Object.fromEntries(ACTION_TAKEN_KEYS.map((a) => [a, 0]));
32870
+ }
32871
+ function isActionTaken(value) {
32872
+ return ACTION_TAKEN_KEYS.includes(value);
32873
+ }
32874
+
32875
+ // ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
32876
+ var CAPTURE_EVENT_TYPES_SQL2 = `('prompt','response','code_change','tool_use')`;
32877
+ function isSchemaAbsent(err) {
32878
+ return err instanceof Error && /no such table/i.test(err.message);
32879
+ }
32880
+ function emptyReadout(readError = false) {
32881
+ const byAction = emptyActionCounts();
32882
+ return {
32883
+ storePresent: false,
32884
+ schemaVersion: null,
32885
+ findingsTotal: 0,
32886
+ findingsFirstAt: null,
32887
+ findingsLastAt: null,
32888
+ packs: [],
32889
+ policyCounts: { total: 0, disabled: 0, byAction },
32890
+ readError
32891
+ };
32892
+ }
32893
+ function readStorePosture(dbPath2) {
32894
+ try {
32895
+ statSync9(dbPath2);
32896
+ } catch (err) {
32897
+ const code = err.code;
32898
+ if (code === "ENOENT" || code === "ENOTDIR") return emptyReadout();
32899
+ return emptyReadout(true);
32900
+ }
32901
+ let db = null;
32902
+ let version2 = null;
32903
+ let packs2 = [];
32904
+ let policyCounts = {
32905
+ total: 0,
32906
+ disabled: 0,
32907
+ byAction: emptyActionCounts()
32908
+ };
32909
+ let findingsTotal = 0;
32910
+ let findingsFirstAt = null;
32911
+ let findingsLastAt = null;
32912
+ const currentReadout = () => ({
32913
+ storePresent: true,
32914
+ schemaVersion: version2,
32915
+ findingsTotal,
32916
+ findingsFirstAt,
32917
+ findingsLastAt,
32918
+ packs: packs2,
32919
+ policyCounts,
32920
+ readError: false
32921
+ });
32922
+ try {
32923
+ db = new DatabaseSync3(dbPath2, { readOnly: true });
32924
+ db.exec("PRAGMA busy_timeout = 2000");
32925
+ version2 = db.prepare("PRAGMA user_version").get().user_version;
32926
+ try {
32927
+ const packRows = db.prepare(
32928
+ `SELECT namespace, pack_id, version, enabled, updated_at FROM installed_packs ORDER BY namespace, pack_id`
32929
+ ).all();
32930
+ packs2 = packRows.map((r) => ({
32931
+ packId: `${r.namespace}/${r.pack_id}`,
32932
+ version: r.version,
32933
+ enabled: r.enabled !== 0,
32934
+ updatedAt: r.updated_at == null ? null : String(r.updated_at)
32935
+ }));
32936
+ } catch (err) {
32937
+ if (!isSchemaAbsent(err)) throw err;
32938
+ }
32939
+ try {
32940
+ const policyRows = db.prepare(`SELECT action, enabled FROM policies`).all();
32941
+ const byAction = emptyActionCounts();
32942
+ let disabled = 0;
32943
+ for (const row of policyRows) {
32944
+ if (row.enabled === 0) disabled += 1;
32945
+ if (isActionTaken(row.action)) byAction[row.action] += 1;
32946
+ }
32947
+ policyCounts = { total: policyRows.length, disabled, byAction };
32948
+ } catch (err) {
32949
+ if (!isSchemaAbsent(err)) throw err;
32950
+ }
32951
+ try {
32952
+ const agg = db.prepare(
32953
+ `SELECT count(*) AS n, min(f.first_detected_at) AS firstAt, max(f.first_detected_at) AS lastAt
32954
+ FROM inspection_findings f JOIN audit_events e ON e.id = f.audit_event_id
32955
+ WHERE e.event_type IN ${CAPTURE_EVENT_TYPES_SQL2}`
32956
+ ).get();
32957
+ findingsTotal = agg.n;
32958
+ findingsFirstAt = agg.firstAt;
32959
+ findingsLastAt = agg.lastAt;
32960
+ } catch (err) {
32961
+ if (!isSchemaAbsent(err)) throw err;
32962
+ }
32963
+ return currentReadout();
32964
+ } catch {
32965
+ return emptyReadout(true);
32966
+ } finally {
32967
+ try {
32968
+ db?.close();
32969
+ } catch {
32970
+ }
32971
+ }
32972
+ }
32973
+
32974
+ // ../../packages/plugin-runtime/src/attached/posture-store.ts
32975
+ import { randomUUID as randomUUID17 } from "crypto";
32976
+ import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
32977
+ import { join as join22 } from "path";
32978
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
32979
+ function createPostureStore(dir = settingsDir(), legacyDir) {
32980
+ const file2 = join22(dir, "posture-state.json");
32981
+ const legacyFile = legacyDir === void 0 ? null : join22(legacyDir, "posture-state.json");
32982
+ async function persist(state) {
32983
+ await ensureDataDir(dir);
32984
+ const tmp = `${file2}.${randomUUID17()}.tmp`;
32985
+ try {
32986
+ await writeFile3(tmp, JSON.stringify(state), { encoding: "utf8", mode: DATA_FILE_MODE });
32987
+ await publishByRename(tmp, file2);
32988
+ } catch (err) {
32989
+ await rm2(tmp, { force: true }).catch(() => void 0);
32990
+ throw err;
32991
+ }
32992
+ }
32993
+ async function readFrom(path) {
32994
+ let raw;
32995
+ try {
32996
+ raw = await readFile3(path, "utf8");
32997
+ } catch (err) {
32998
+ const code = err.code;
32999
+ if (code === "ENOENT" || code === "ENOTDIR") return null;
33000
+ throw err;
33001
+ }
33002
+ try {
33003
+ const parsed2 = JSON.parse(raw);
33004
+ if (typeof parsed2 === "object" && parsed2 !== null) {
33005
+ const record2 = parsed2;
33006
+ if (typeof record2.deviceId === "string" && UUID_RE.test(record2.deviceId)) {
33007
+ const stamp = typeof record2.lastAttemptedAtMs === "number" ? record2.lastAttemptedAtMs : typeof record2.lastReportedAtMs === "number" ? record2.lastReportedAtMs : 0;
33008
+ return { deviceId: record2.deviceId, lastAttemptedAtMs: stamp };
33009
+ }
33010
+ }
33011
+ } catch {
33012
+ }
33013
+ return null;
33014
+ }
33015
+ async function read() {
33016
+ const current = await readFrom(file2);
33017
+ if (current) return current;
33018
+ const legacy = legacyFile === null || legacyFile === file2 ? null : await readFrom(legacyFile).catch(() => null);
33019
+ if (legacy) {
33020
+ try {
33021
+ await persist(legacy);
33022
+ } catch {
33023
+ }
33024
+ return legacy;
33025
+ }
33026
+ const fresh = { deviceId: randomUUID17(), lastAttemptedAtMs: 0 };
33027
+ try {
33028
+ await ensureDataDir(dir);
33029
+ if (createOwnerOnlyFileSync(file2, JSON.stringify(fresh))) return fresh;
33030
+ } catch {
33031
+ return null;
33032
+ }
33033
+ const winner = await readFrom(file2).catch(() => null);
33034
+ if (winner) return winner;
33035
+ try {
33036
+ await persist(fresh);
33037
+ } catch {
33038
+ return null;
33039
+ }
33040
+ return fresh;
33041
+ }
33042
+ async function markAttempted(deviceId, atMs) {
33043
+ await persist({ deviceId, lastAttemptedAtMs: atMs });
33044
+ }
33045
+ return { read, markAttempted, file: file2 };
33046
+ }
33047
+
33048
+ // ../../packages/plugin-runtime/src/attached/sync-state.ts
33049
+ import { readFileSync as readFileSync14 } from "fs";
33050
+ import { join as join23 } from "path";
33051
+
33052
+ // ../../packages/plugin-runtime/src/attached/status.ts
33053
+ var REFUSAL_LINES = {
33054
+ unauthorized: "KEY REJECTED \u2014 re-attach with a valid plugin key",
33055
+ forbidden: "ACCESS REFUSED \u2014 key is valid but not permitted; ask your org admin"
33056
+ };
33057
+ var OUTCOME_LINES = {
33058
+ ok: "policy synced",
33059
+ "not-modified": "policy up to date",
33060
+ unauthorized: REFUSAL_LINES.unauthorized,
33061
+ forbidden: REFUSAL_LINES.forbidden,
33062
+ unreachable: "control plane unreachable at last attempt",
33063
+ "invalid-bundle": "control plane sent a policy bundle this build cannot read"
33064
+ };
33065
+
33066
+ // ../../packages/plugin-runtime/src/attached/sync-trigger.ts
33067
+ import { spawn } from "child_process";
33068
+ import { fileURLToPath as fileURLToPath3 } from "url";
33069
+ var SYNC_THROTTLE_MS = 15 * 60 * 1e3;
33070
+
33071
+ // ../../packages/plugin-runtime/src/attached/factory.ts
33072
+ import { hostname as hostname5 } from "os";
31658
33073
 
31659
33074
  // ../../packages/plugin-runtime/src/standalone-gateway.ts
31660
- import { randomUUID as randomUUID15 } from "crypto";
33075
+ import { randomUUID as randomUUID18 } from "crypto";
31661
33076
 
31662
33077
  // ../../packages/plugin-runtime/src/recorder.ts
31663
33078
  var PLUGIN_RECORDER_BINARY = "plugin";
@@ -31698,12 +33113,12 @@ var StandaloneDataGateway = class {
31698
33113
  // reconciler drops the whole pass and recovers it idempotently on the next read.
31699
33114
  recordLlmCalls(inputs) {
31700
33115
  if (inputs.length === 0) return Promise.resolve();
31701
- return new Promise((resolve2, reject) => {
33116
+ return new Promise((resolve3, reject) => {
31702
33117
  try {
31703
33118
  this.db.auditEvents.runInTransaction(() => {
31704
33119
  for (const input of inputs) this.db.auditEvents.insertLlmCall(input);
31705
33120
  });
31706
- resolve2();
33121
+ resolve3();
31707
33122
  } catch (err) {
31708
33123
  reject(err instanceof Error ? err : new Error(String(err)));
31709
33124
  }
@@ -31715,12 +33130,12 @@ var StandaloneDataGateway = class {
31715
33130
  // drops the whole pass and recovers it idempotently next time.
31716
33131
  recordToolCalls(inputs) {
31717
33132
  if (inputs.length === 0) return Promise.resolve();
31718
- return new Promise((resolve2, reject) => {
33133
+ return new Promise((resolve3, reject) => {
31719
33134
  try {
31720
33135
  this.db.auditEvents.runInTransaction(() => {
31721
33136
  for (const input of inputs) this.writeToolCall(input);
31722
33137
  });
31723
- resolve2();
33138
+ resolve3();
31724
33139
  } catch (err) {
31725
33140
  reject(err instanceof Error ? err : new Error(String(err)));
31726
33141
  }
@@ -31862,7 +33277,7 @@ var StandaloneDataGateway = class {
31862
33277
  const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
31863
33278
  const installed = this.installedScanRules();
31864
33279
  const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
31865
- id: randomUUID15(),
33280
+ id: randomUUID18(),
31866
33281
  scope: "global",
31867
33282
  target: { ruleId },
31868
33283
  action,
@@ -32016,29 +33431,75 @@ var StandaloneDataGateway = class {
32016
33431
  }
32017
33432
  };
32018
33433
 
33434
+ // ../../packages/plugin-runtime/src/attached/factory.ts
33435
+ function resolveGatewayForConfig(config2, meta3) {
33436
+ const local = new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
33437
+ try {
33438
+ if (!isAttached(config2.settings)) return local;
33439
+ const connection = config2.settings.controlPlane;
33440
+ if (connection === void 0) return local;
33441
+ const state = readControlPlaneCredentialState(config2.settingsDir, connection);
33442
+ if (!state.usable) return local;
33443
+ const client = createRemoteClient({
33444
+ endpoint: connection.endpoint,
33445
+ apiKey: state.credential.apiKey
33446
+ });
33447
+ const store = createPolicyStore(config2.dataDir);
33448
+ const postureStore = createPostureStore(config2.settingsDir, config2.dataDir);
33449
+ const forward = createForwardPolicy({ dir: config2.dataDir });
33450
+ return new AttachedDataGateway({
33451
+ local,
33452
+ client,
33453
+ dataDir: config2.dataDir,
33454
+ readCachedBundle: () => store.read().then((cached2) => cached2?.bundle ?? null),
33455
+ forward,
33456
+ posture: createPostureReporter({
33457
+ // THROUGH THE BREAKER, and wrapped HERE rather than around
33458
+ // `PostureReporter.send`. The reporter swallows every error by
33459
+ // contract, so a wrap outside it would hand `forward.run` a resolved
33460
+ // promise for a send that failed — recording a SUCCESS, clearing
33461
+ // `consecutiveFailures` and `lastFailure`, and telling `aka status` the
33462
+ // forward recovered when nothing did. Wrapping the raw client call puts
33463
+ // the breaker above the swallow, where it can see the truth.
33464
+ //
33465
+ // What it buys: once the breaker is open — the plane already confirmed
33466
+ // down by the gateway's own writes — this stops paying a request
33467
+ // timeout per throttle interval to re-learn it.
33468
+ report: (snapshot) => forward.run(() => client.reportStorePosture(snapshot)).then(() => void 0),
33469
+ store: postureStore,
33470
+ readStore: () => readStorePosture(config2.dbPath),
33471
+ hostname: () => hostname5(),
33472
+ now: () => Date.now()
33473
+ })
33474
+ });
33475
+ } catch {
33476
+ return local;
33477
+ }
33478
+ }
33479
+
32019
33480
  // ../../packages/plugin-runtime/src/resolve.ts
32020
- var standaloneGatewayFactory = (config2, meta3) => new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
32021
- var defaultGatewayFactory = standaloneGatewayFactory;
33481
+ var configuredGatewayFactory = (config2, meta3) => resolveGatewayForConfig(config2, meta3);
33482
+ var defaultGatewayFactory = configuredGatewayFactory;
32022
33483
  function resolveDataGateway(config2, meta3, gatewayFactory = defaultGatewayFactory) {
32023
33484
  return gatewayFactory(config2, meta3);
32024
33485
  }
32025
33486
 
32026
33487
  // ../../packages/plugin-runtime/src/handle-session-start.ts
32027
- import { randomUUID as randomUUID16 } from "crypto";
33488
+ import { randomUUID as randomUUID19 } from "crypto";
32028
33489
  var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
32029
33490
 
32030
33491
  // src/history/transcripts.ts
32031
- import { readdirSync as readdirSync6, readFileSync as readFileSync11 } from "fs";
33492
+ import { readdirSync as readdirSync6, readFileSync as readFileSync15 } from "fs";
32032
33493
  import { homedir as homedir3 } from "os";
32033
- import { join as join17 } from "path";
33494
+ import { join as join24 } from "path";
32034
33495
  function transcriptsDir(home) {
32035
- return join17(home ?? homedir3(), ".claude", "projects");
33496
+ return join24(home ?? homedir3(), ".claude", "projects");
32036
33497
  }
32037
33498
  var DAY_MS5 = 24 * 60 * 60 * 1e3;
32038
33499
 
32039
33500
  // src/remediation/redact.ts
32040
- import { readFileSync as readFileSync12, realpathSync as realpathSync3, renameSync as renameSync5, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
32041
- import { isAbsolute as isAbsolute2, relative, resolve } from "path";
33501
+ import { readFileSync as readFileSync16, realpathSync as realpathSync4, renameSync as renameSync5, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "fs";
33502
+ import { isAbsolute as isAbsolute2, relative, resolve as resolve2 } from "path";
32042
33503
  var REDACTED_PLACEHOLDER = "[REDACTED:SECRET]";
32043
33504
  var REPLACE_PATTERN_SEQUENCE = /\$[$&`'<0-9]/;
32044
33505
  function replacementFor(rawValue, replacements) {
@@ -32053,7 +33514,7 @@ function platformRedactionScope(home) {
32053
33514
  }
32054
33515
  function realPathOrNull(path) {
32055
33516
  try {
32056
- return realpathSync3(path);
33517
+ return realpathSync4(path);
32057
33518
  } catch {
32058
33519
  return null;
32059
33520
  }
@@ -32065,7 +33526,7 @@ function isWithinRoot(realTarget, root) {
32065
33526
  return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
32066
33527
  }
32067
33528
  function resolveRedactableArtifact(filePath, scope) {
32068
- const realTarget = realPathOrNull(resolve(filePath));
33529
+ const realTarget = realPathOrNull(resolve2(filePath));
32069
33530
  if (realTarget === null) return null;
32070
33531
  return scope.artifactRoots.some((root) => isWithinRoot(realTarget, root)) ? realTarget : null;
32071
33532
  }
@@ -32085,7 +33546,7 @@ function redactLeakedKeysDetailed(targets, scope = platformRedactionScope(), rep
32085
33546
  for (const [filePath, fileTargets] of byFile) {
32086
33547
  let content;
32087
33548
  try {
32088
- content = readFileSync12(filePath, "utf8");
33549
+ content = readFileSync16(filePath, "utf8");
32089
33550
  } catch {
32090
33551
  continue;
32091
33552
  }
@@ -32119,7 +33580,7 @@ function redactLeakedKeysDetailed(targets, scope = platformRedactionScope(), rep
32119
33580
  renameSync5(tmpPath, filePath);
32120
33581
  } catch {
32121
33582
  try {
32122
- rmSync6(tmpPath, { force: true, recursive: true });
33583
+ rmSync7(tmpPath, { force: true, recursive: true });
32123
33584
  } catch {
32124
33585
  }
32125
33586
  continue;
@@ -32208,7 +33669,7 @@ async function redactSurfacedSecrets(findings, overrides = {}) {
32208
33669
  for (const [filePath, fileFindings] of byFile) {
32209
33670
  let content;
32210
33671
  try {
32211
- content = readFileSync13(filePath, "utf8");
33672
+ content = readFileSync17(filePath, "utf8");
32212
33673
  } catch {
32213
33674
  unrecovered.push(...fileFindings);
32214
33675
  continue;
@@ -32369,11 +33830,11 @@ async function route(frameText, rawOption, rawPosture) {
32369
33830
  break;
32370
33831
  }
32371
33832
  }
32372
- if (process.argv[1] && fileURLToPath3(import.meta.url) === process.argv[1]) {
33833
+ if (process.argv[1] && fileURLToPath4(import.meta.url) === process.argv[1]) {
32373
33834
  try {
32374
33835
  const argv = process.argv.slice(2);
32375
33836
  const optionIndex = argv.indexOf("--option");
32376
- const frameText = readFileSync14(0, "utf8");
33837
+ const frameText = readFileSync18(0, "utf8");
32377
33838
  if (optionIndex === -1) {
32378
33839
  present(frameText);
32379
33840
  } else {