@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
  // ../../packages/plugin-sdk/src/config.ts
495
- import { existsSync as existsSync5 } from "fs";
496
- import { join as join8 } from "path";
495
+ import { existsSync as existsSync6 } from "fs";
496
+ import { join as join10 } from "path";
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;
@@ -17797,6 +17915,9 @@ var WorkspaceSettings = external_exports.object({
17797
17915
  function defaultWorkspaceSettings() {
17798
17916
  return WorkspaceSettings.parse({});
17799
17917
  }
17918
+ function isAttached(settings) {
17919
+ return settings.runMode === "attached" && settings.controlPlane !== void 0;
17920
+ }
17800
17921
  function toInventoryRow(input, id, now) {
17801
17922
  return {
17802
17923
  id,
@@ -18064,8 +18185,8 @@ function builtinPolicyIsReversible(id) {
18064
18185
  return BUILTIN_POLICY_SPECS[id].reversible;
18065
18186
  }
18066
18187
  function policyIdIsReversible(policyId) {
18067
- const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18068
- const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
18188
+ const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18189
+ const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
18069
18190
  return builtinPolicyIsReversible(id);
18070
18191
  }
18071
18192
  var DEFAULT_ACTIONS = Object.fromEntries(
@@ -18076,8 +18197,8 @@ var BUILTIN_POLICIES = Object.fromEntries(
18076
18197
  );
18077
18198
  var DEFAULT_PACK_POLICY_ID = "monitor";
18078
18199
  function policyIdToAction(policyId) {
18079
- const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18080
- const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
18200
+ const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18201
+ const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
18081
18202
  return BUILTIN_POLICIES[id].action;
18082
18203
  }
18083
18204
  var UsedByItem = external_exports.object({
@@ -18520,53 +18641,6 @@ function reviewSeverityRank(reasons) {
18520
18641
  return Math.min(...reasons.map((r) => REVIEW_SEVERITY_RANK[r]));
18521
18642
  }
18522
18643
 
18523
- // ../../packages/persistence/src/ids.ts
18524
- import { createHash } from "crypto";
18525
- function sha256Hex(input) {
18526
- return createHash("sha256").update(input).digest("hex");
18527
- }
18528
- function inventoryId(objectType, identityKey) {
18529
- return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
18530
- }
18531
- function sourceProjectId(url2) {
18532
- return sha256Hex(canonicalIdentity(["source_project", url2]));
18533
- }
18534
- function classifiedDataId(cls) {
18535
- return sha256Hex(canonicalIdentity(["classified_data", cls]));
18536
- }
18537
- function inspectionDefinitionId(ruleId, version2) {
18538
- return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
18539
- }
18540
- function llmCallId(sessionId, messageId) {
18541
- return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
18542
- }
18543
- function toolCallId(sessionId, toolUseId) {
18544
- return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
18545
- }
18546
- function inspectionFindingId(auditEventId, ruleId, spanStart, spanEnd) {
18547
- return sha256Hex(
18548
- canonicalIdentity([
18549
- "inspection_finding",
18550
- auditEventId,
18551
- ruleId,
18552
- String(spanStart),
18553
- String(spanEnd)
18554
- ])
18555
- );
18556
- }
18557
- var NO_SESSION = "no_session";
18558
- var NO_PATH = "no_path";
18559
- function captureId(sessionId, contentHash, filePath = null) {
18560
- return sha256Hex(
18561
- canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
18562
- );
18563
- }
18564
-
18565
- // ../../packages/persistence/src/internal/snapshot.ts
18566
- import { randomUUID } from "crypto";
18567
- import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
18568
- import { basename, dirname, join } from "path";
18569
-
18570
18644
  // ../../packages/persistence/src/paths.ts
18571
18645
  import {
18572
18646
  chmodSync,
@@ -18692,7 +18766,123 @@ function publishByLink(tmp, file2, data) {
18692
18766
  }
18693
18767
  }
18694
18768
 
18769
+ // ../../packages/persistence/src/control-plane-credential.ts
18770
+ function controlPlaneCredentialPath(settingsDir2) {
18771
+ return join(settingsDir2, ATTACHED_CREDENTIAL_FILENAME);
18772
+ }
18773
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
18774
+ function isSafeEndpoint(endpoint) {
18775
+ let parsed2;
18776
+ try {
18777
+ parsed2 = new URL(endpoint);
18778
+ } catch {
18779
+ return false;
18780
+ }
18781
+ if (parsed2.protocol === "https:") return true;
18782
+ return parsed2.protocol === "http:" && LOOPBACK_HOSTS.has(parsed2.hostname);
18783
+ }
18784
+ function repairOrRefuseMode(file2) {
18785
+ const link = lstatSync2(file2, { throwIfNoEntry: false });
18786
+ if (link === void 0) return "absent";
18787
+ if (link.isSymbolicLink()) return "untrusted";
18788
+ const stat = statSync(file2, { throwIfNoEntry: false });
18789
+ if (stat === void 0) return "absent";
18790
+ const uid = process.getuid?.();
18791
+ if (uid !== void 0 && stat.uid !== uid) return "untrusted";
18792
+ if (process.platform !== "win32" && (stat.mode & 511) !== DATA_FILE_MODE) {
18793
+ try {
18794
+ chmodSync2(file2, DATA_FILE_MODE);
18795
+ } catch {
18796
+ return "untrusted";
18797
+ }
18798
+ }
18799
+ return "ok";
18800
+ }
18801
+ function readControlPlaneCredentialState(settingsDir2, connection) {
18802
+ const file2 = controlPlaneCredentialPath(settingsDir2);
18803
+ let raw;
18804
+ const gate = repairOrRefuseMode(file2);
18805
+ if (gate === "absent") return { usable: false, reason: "absent" };
18806
+ if (gate === "untrusted") return { usable: false, reason: "untrusted-file" };
18807
+ try {
18808
+ raw = readFileSync(file2, "utf8");
18809
+ } catch (err) {
18810
+ const code = err.code;
18811
+ return { usable: false, reason: code === "ENOENT" ? "absent" : "unreadable" };
18812
+ }
18813
+ let parsed2;
18814
+ try {
18815
+ parsed2 = JSON.parse(raw);
18816
+ } catch {
18817
+ return { usable: false, reason: "malformed" };
18818
+ }
18819
+ const result = AttachedCredential.safeParse(parsed2);
18820
+ if (!result.success) return { usable: false, reason: "malformed" };
18821
+ if (!isSafeEndpoint(result.data.endpoint)) {
18822
+ return { usable: false, reason: "unsafe-endpoint" };
18823
+ }
18824
+ if (connection !== void 0 && connection.endpoint !== result.data.endpoint) {
18825
+ return {
18826
+ usable: false,
18827
+ reason: "endpoint-mismatch",
18828
+ credentialEndpoint: result.data.endpoint,
18829
+ settingsEndpoint: connection.endpoint
18830
+ };
18831
+ }
18832
+ return { usable: true, credential: result.data };
18833
+ }
18834
+
18835
+ // ../../packages/persistence/src/database.ts
18836
+ import { randomUUID as randomUUID10 } from "crypto";
18837
+ import { join as join3, sep } from "path";
18838
+ import { DatabaseSync } from "node:sqlite";
18839
+
18840
+ // ../../packages/persistence/src/ids.ts
18841
+ import { createHash } from "crypto";
18842
+ function sha256Hex(input) {
18843
+ return createHash("sha256").update(input).digest("hex");
18844
+ }
18845
+ function inventoryId(objectType, identityKey) {
18846
+ return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
18847
+ }
18848
+ function sourceProjectId(url2) {
18849
+ return sha256Hex(canonicalIdentity(["source_project", url2]));
18850
+ }
18851
+ function classifiedDataId(cls) {
18852
+ return sha256Hex(canonicalIdentity(["classified_data", cls]));
18853
+ }
18854
+ function inspectionDefinitionId(ruleId, version2) {
18855
+ return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
18856
+ }
18857
+ function llmCallId(sessionId, messageId) {
18858
+ return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
18859
+ }
18860
+ function toolCallId(sessionId, toolUseId) {
18861
+ return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
18862
+ }
18863
+ function inspectionFindingId(auditEventId, ruleId, spanStart, spanEnd) {
18864
+ return sha256Hex(
18865
+ canonicalIdentity([
18866
+ "inspection_finding",
18867
+ auditEventId,
18868
+ ruleId,
18869
+ String(spanStart),
18870
+ String(spanEnd)
18871
+ ])
18872
+ );
18873
+ }
18874
+ var NO_SESSION = "no_session";
18875
+ var NO_PATH = "no_path";
18876
+ function captureId(sessionId, contentHash, filePath = null) {
18877
+ return sha256Hex(
18878
+ canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
18879
+ );
18880
+ }
18881
+
18695
18882
  // ../../packages/persistence/src/internal/snapshot.ts
18883
+ import { randomUUID } from "crypto";
18884
+ import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync2 } from "fs";
18885
+ import { basename, dirname, join as join2 } from "path";
18696
18886
  function backupPath(file2, tag) {
18697
18887
  return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
18698
18888
  }
@@ -18702,15 +18892,15 @@ var STAGED_NAME_SUFFIX = `.bak${SNAPSHOT_STAGING_SUFFIX}`;
18702
18892
  var SNAPSHOT_STAGING_COPY = "copy";
18703
18893
  function createSnapshotStaging(backup) {
18704
18894
  const stage = `${backup}${SNAPSHOT_STAGING_SUFFIX}`;
18705
- rmSync2(stage, { recursive: true, force: true });
18895
+ rmSync3(stage, { recursive: true, force: true });
18706
18896
  mkdirOwnerOnlySync(stage);
18707
18897
  tightenDir(stage);
18708
- return { stage, copy: join(stage, SNAPSHOT_STAGING_COPY) };
18898
+ return { stage, copy: join2(stage, SNAPSHOT_STAGING_COPY) };
18709
18899
  }
18710
18900
  function idleMs(entry) {
18711
- for (const candidate of [join(entry, SNAPSHOT_STAGING_COPY), entry]) {
18901
+ for (const candidate of [join2(entry, SNAPSHOT_STAGING_COPY), entry]) {
18712
18902
  try {
18713
- return Date.now() - statSync(candidate).mtimeMs;
18903
+ return Date.now() - statSync2(candidate).mtimeMs;
18714
18904
  } catch {
18715
18905
  }
18716
18906
  }
@@ -18727,11 +18917,11 @@ function reapStalePartials(file2) {
18727
18917
  }
18728
18918
  for (const name of entries) {
18729
18919
  if (!name.startsWith(prefix) || !name.endsWith(STAGED_NAME_SUFFIX)) continue;
18730
- const staging = join(dir, name);
18920
+ const staging = join2(dir, name);
18731
18921
  try {
18732
18922
  const idle = idleMs(staging);
18733
18923
  if (idle !== null && idle > STALE_PARTIAL_MS) {
18734
- rmSync2(staging, { recursive: true, force: true });
18924
+ rmSync3(staging, { recursive: true, force: true });
18735
18925
  }
18736
18926
  } catch {
18737
18927
  }
@@ -18745,13 +18935,13 @@ function snapshotStore(db, backup) {
18745
18935
  renameSync2(copy, backup);
18746
18936
  } catch (error51) {
18747
18937
  try {
18748
- rmSync2(stage, { recursive: true, force: true });
18938
+ rmSync3(stage, { recursive: true, force: true });
18749
18939
  } catch {
18750
18940
  }
18751
18941
  throw error51;
18752
18942
  }
18753
18943
  try {
18754
- rmSync2(stage, { recursive: true, force: true });
18944
+ rmSync3(stage, { recursive: true, force: true });
18755
18945
  } catch {
18756
18946
  }
18757
18947
  }
@@ -18766,7 +18956,7 @@ function moveStoreAside(file2, backup) {
18766
18956
  renameSync2(sidecar, moved);
18767
18957
  undo.push([moved, sidecar]);
18768
18958
  } catch {
18769
- rmSync2(sidecar, { force: true });
18959
+ rmSync3(sidecar, { force: true });
18770
18960
  }
18771
18961
  }
18772
18962
  } catch (error51) {
@@ -18782,14 +18972,14 @@ function moveStoreAside(file2, backup) {
18782
18972
  }
18783
18973
  function discardStore(file2, backup) {
18784
18974
  try {
18785
- rmSync2(file2, { force: true });
18975
+ rmSync3(file2, { force: true });
18786
18976
  for (const sidecar of dbSidecars(file2)) {
18787
- rmSync2(sidecar, { force: true });
18977
+ rmSync3(sidecar, { force: true });
18788
18978
  }
18789
18979
  } catch (error51) {
18790
18980
  if (existsSync(file2)) {
18791
18981
  try {
18792
- rmSync2(backup, { force: true });
18982
+ rmSync3(backup, { force: true });
18793
18983
  } catch {
18794
18984
  }
18795
18985
  }
@@ -19021,10 +19211,31 @@ function applyMigrations(db, file2) {
19021
19211
  if (drained) applyLegacyDropMigration(db, file2);
19022
19212
  }
19023
19213
  }
19214
+ function readLegacyTables(db) {
19215
+ let holdsRows = false;
19216
+ const marks = [];
19217
+ for (const table of ["events", "findings"]) {
19218
+ try {
19219
+ const row = db.prepare(`SELECT count(*) AS n, ifnull(max(rowid), -1) AS hi FROM ${table}`).get();
19220
+ if (row === void 0) {
19221
+ holdsRows = true;
19222
+ marks.push(`${table}:unreadable`);
19223
+ continue;
19224
+ }
19225
+ if (row.n > 0) holdsRows = true;
19226
+ marks.push(`${table}:${String(row.n)}:${String(row.hi)}`);
19227
+ } catch {
19228
+ holdsRows = true;
19229
+ marks.push(`${table}:unreadable`);
19230
+ }
19231
+ }
19232
+ return { holdsRows, mark: marks.join("|") };
19233
+ }
19024
19234
  function applyLegacyDropMigration(db, file2) {
19025
19235
  const migration = SQLITE_MIGRATIONS.find((m) => m.tag === LEGACY_DROP_MIGRATION_TAG);
19026
19236
  if (!migration) return;
19027
- if (file2) {
19237
+ const before = file2 === void 0 ? void 0 : readLegacyTables(db);
19238
+ if (file2 !== void 0 && before?.holdsRows === true) {
19028
19239
  try {
19029
19240
  backupBeforeLegacyDrop(db, file2);
19030
19241
  } catch (error51) {
@@ -19038,6 +19249,12 @@ function applyLegacyDropMigration(db, file2) {
19038
19249
  () => {
19039
19250
  const alreadyDropped = db.prepare("SELECT 1 FROM migration_ledger WHERE tag = ?").get(migration.tag);
19040
19251
  if (alreadyDropped) return;
19252
+ if (before !== void 0 && readLegacyTables(db).mark !== before.mark) {
19253
+ akaWarn(
19254
+ "legacy events/findings rows changed after the pre-drop snapshot decision; deferring the drop so the next open can copy them first."
19255
+ );
19256
+ return;
19257
+ }
19041
19258
  for (const statement of splitStatements(migration.sql)) {
19042
19259
  db.exec(statement);
19043
19260
  }
@@ -19392,8 +19609,8 @@ function safeJson(s, fallback) {
19392
19609
  function parseJsonObject(s) {
19393
19610
  if (s == null) return void 0;
19394
19611
  try {
19395
- const parsed = JSON.parse(s);
19396
- if (typeof parsed === "object" && parsed !== null) return parsed;
19612
+ const parsed2 = JSON.parse(s);
19613
+ if (typeof parsed2 === "object" && parsed2 !== null) return parsed2;
19397
19614
  } catch {
19398
19615
  }
19399
19616
  return void 0;
@@ -19404,16 +19621,16 @@ function encodeKeysetCursor(payload) {
19404
19621
  return Buffer.from(JSON.stringify(payload)).toString("base64url");
19405
19622
  }
19406
19623
  function decodeKeysetCursor(cursor) {
19407
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
19408
- if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
19624
+ const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
19625
+ if (parsed2 !== void 0 && "startedAtMs" in parsed2 && "id" in parsed2 && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
19409
19626
  // resumes from is epoch millis, and a payload carrying ±Infinity or a
19410
19627
  // fraction binds cleanly rather than failing — returning an EMPTY page with
19411
19628
  // a null cursor, which a caller reads as "end of list". That is the one
19412
19629
  // outcome a cursor that does not decode must never produce, since the
19413
19630
  // documented behaviour above is to restart from the top. (`1e999` is valid
19414
19631
  // JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
19415
- Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
19416
- return parsed;
19632
+ Number.isInteger(parsed2.startedAtMs) && typeof parsed2.id === "string") {
19633
+ return parsed2;
19417
19634
  }
19418
19635
  return null;
19419
19636
  }
@@ -19478,18 +19695,18 @@ var DB_EVENT_TYPE_TO_KIND = {
19478
19695
  };
19479
19696
  function safeParseStringArray(raw) {
19480
19697
  if (!raw) return [];
19481
- const parsed = safeJson(raw, null);
19482
- return Array.isArray(parsed) ? parsed : [];
19698
+ const parsed2 = safeJson(raw, null);
19699
+ return Array.isArray(parsed2) ? parsed2 : [];
19483
19700
  }
19484
19701
  var DEFAULT_HARNESS = HARNESS.ClaudeCode;
19485
19702
  function toHarness(raw) {
19486
- const parsed = Harness.safeParse(raw);
19487
- return parsed.success ? parsed.data : DEFAULT_HARNESS;
19703
+ const parsed2 = Harness.safeParse(raw);
19704
+ return parsed2.success ? parsed2.data : DEFAULT_HARNESS;
19488
19705
  }
19489
19706
  function resolveLifecycle(row, lastActivityMs, nowMs) {
19490
19707
  if (row.status) {
19491
- const parsed = SessionStatus.safeParse(row.status);
19492
- if (parsed.success) return { status: parsed.data, endedAtMs: row.ended_at };
19708
+ const parsed2 = SessionStatus.safeParse(row.status);
19709
+ if (parsed2.success) return { status: parsed2.data, endedAtMs: row.ended_at };
19493
19710
  }
19494
19711
  if (row.ended_at !== null) return { status: "completed", endedAtMs: row.ended_at };
19495
19712
  if (lastActivityMs >= nowMs - LIVE_ACTIVITY_WINDOW_MS) {
@@ -20448,9 +20665,9 @@ var SqliteDetectionsRepository = class {
20448
20665
  const ruleIds = /* @__PURE__ */ new Set();
20449
20666
  for (const r of rows) {
20450
20667
  if (intToBool(r.enabled)) active += 1;
20451
- const parsed = parseRules(r.rulesJson);
20452
- rules += parsed.length;
20453
- for (const rule of parsed) {
20668
+ const parsed2 = parseRules(r.rulesJson);
20669
+ rules += parsed2.length;
20670
+ for (const rule of parsed2) {
20454
20671
  if (typeof rule.id === "string") ruleIds.add(rule.id);
20455
20672
  }
20456
20673
  }
@@ -20984,12 +21201,12 @@ function encodeGroupCursor(group) {
20984
21201
  return Buffer.from(JSON.stringify(payload)).toString("base64url");
20985
21202
  }
20986
21203
  function decodeGroupCursor(cursor) {
20987
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
20988
- if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
21204
+ const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
21205
+ if (parsed2 !== void 0 && typeof parsed2.sev === "string" && typeof parsed2.t === "string" && typeof parsed2.id === "string") {
20989
21206
  return {
20990
- severity: parsed.sev,
20991
- latestDetectedAt: parsed.t,
20992
- id: parsed.id
21207
+ severity: parsed2.sev,
21208
+ latestDetectedAt: parsed2.t,
21209
+ id: parsed2.id
20993
21210
  };
20994
21211
  }
20995
21212
  return null;
@@ -22123,16 +22340,16 @@ var SqliteInstalledPacksRepository = class {
22123
22340
  continue;
22124
22341
  }
22125
22342
  for (const entry of raw) {
22126
- const parsed = Rule.safeParse(entry);
22127
- if (parsed.success) {
22128
- out.rules.push(parsed.data);
22129
- out.ruleActions.set(parsed.data.id, action);
22130
- out.ruleVersions.set(parsed.data.id, row.version);
22131
- if (reversible) out.reversibleRules.add(parsed.data.id);
22132
- else out.reversibleRules.delete(parsed.data.id);
22343
+ const parsed2 = Rule.safeParse(entry);
22344
+ if (parsed2.success) {
22345
+ out.rules.push(parsed2.data);
22346
+ out.ruleActions.set(parsed2.data.id, action);
22347
+ out.ruleVersions.set(parsed2.data.id, row.version);
22348
+ if (reversible) out.reversibleRules.add(parsed2.data.id);
22349
+ else out.reversibleRules.delete(parsed2.data.id);
22133
22350
  } else {
22134
22351
  out.invalidRules += 1;
22135
- reject(pack, printableRuleId(entry), firstIssueReason(parsed.error));
22352
+ reject(pack, printableRuleId(entry), firstIssueReason(parsed2.error));
22136
22353
  }
22137
22354
  }
22138
22355
  }
@@ -23522,15 +23739,15 @@ function encodeReuseCursor(payload) {
23522
23739
  return Buffer.from(JSON.stringify(payload)).toString("base64url");
23523
23740
  }
23524
23741
  function decodeReuseCursor(cursor) {
23525
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
23526
- if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
23742
+ const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
23743
+ if (parsed2 !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
23527
23744
  // ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
23528
23745
  // null cursor, which the caller reads as "end of list" — the one outcome a
23529
23746
  // malformed cursor must never produce, since restarting from the top is the
23530
23747
  // documented behaviour and the only recoverable one. (`1e999` is valid JSON
23531
23748
  // and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
23532
- Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
23533
- return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
23749
+ Number.isInteger(parsed2.occurrences) && typeof parsed2.pointerId === "string") {
23750
+ return { occurrences: parsed2.occurrences, pointerId: parsed2.pointerId };
23534
23751
  }
23535
23752
  return null;
23536
23753
  }
@@ -25259,7 +25476,7 @@ function openAndInitialize(file2) {
25259
25476
  }
25260
25477
  function openLocalDatabase(dir) {
25261
25478
  ensureDataDirSync(dir);
25262
- const file2 = join2(dir, DB_FILENAME);
25479
+ const file2 = join3(dir, DB_FILENAME);
25263
25480
  reapStalePartials(file2);
25264
25481
  const {
25265
25482
  db,
@@ -25515,9 +25732,9 @@ import {
25515
25732
  closeSync,
25516
25733
  existsSync as existsSync2,
25517
25734
  openSync,
25518
- readFileSync,
25519
- rmSync as rmSync3,
25520
- statSync as statSync2,
25735
+ readFileSync as readFileSync2,
25736
+ rmSync as rmSync4,
25737
+ statSync as statSync3,
25521
25738
  writeFileSync as writeFileSync2
25522
25739
  } from "fs";
25523
25740
  import { hostname as hostname3 } from "os";
@@ -25535,20 +25752,20 @@ function computeFindingKey(input) {
25535
25752
 
25536
25753
  // ../../packages/persistence/src/fingerprint.ts
25537
25754
  import { createHmac, randomBytes } from "crypto";
25538
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
25539
- import { join as join3 } from "path";
25755
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
25756
+ import { join as join4 } from "path";
25540
25757
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
25541
25758
  var EXCEPTION_KEY_FILENAME = "exception.key";
25542
25759
  var KEY_MATERIAL_BYTES = 32;
25543
25760
  function keyFilePath(dataDir2) {
25544
- return join3(dataDir2, EXCEPTION_KEY_FILENAME);
25761
+ return join4(dataDir2, EXCEPTION_KEY_FILENAME);
25545
25762
  }
25546
25763
  function parseKeyFile(raw) {
25547
- const parsed = JSON.parse(raw);
25548
- if (typeof parsed !== "object" || parsed === null) {
25764
+ const parsed2 = JSON.parse(raw);
25765
+ if (typeof parsed2 !== "object" || parsed2 === null) {
25549
25766
  throw new Error("exception key file is corrupt: not a JSON object");
25550
25767
  }
25551
- const { version: version2, material } = parsed;
25768
+ const { version: version2, material } = parsed2;
25552
25769
  if (typeof version2 !== "number" || !Number.isInteger(version2) || version2 < 1) {
25553
25770
  throw new Error("exception key file is corrupt: bad version");
25554
25771
  }
@@ -25579,7 +25796,7 @@ var FloorUnreadableError = class extends Error {
25579
25796
  }
25580
25797
  };
25581
25798
  function storedKeyVersionFloor(dataDir2) {
25582
- const file2 = join3(dataDir2, DB_FILENAME);
25799
+ const file2 = join4(dataDir2, DB_FILENAME);
25583
25800
  if (!existsSync3(file2)) return 0;
25584
25801
  let db;
25585
25802
  try {
@@ -25634,7 +25851,7 @@ function occupantMessage(file2, kind) {
25634
25851
  function readFingerprintKey(dataDir2) {
25635
25852
  let raw;
25636
25853
  try {
25637
- raw = readFileSync2(keyFilePath(dataDir2), "utf8");
25854
+ raw = readFileSync3(keyFilePath(dataDir2), "utf8");
25638
25855
  } catch (err) {
25639
25856
  if (err.code === "ENOENT") return null;
25640
25857
  throw err instanceof Error ? err : new Error(String(err));
@@ -25660,21 +25877,25 @@ function fingerprintValue(key, raw) {
25660
25877
  import { renameSync as renameSync3 } from "fs";
25661
25878
  import { mkdir } from "fs/promises";
25662
25879
  import { homedir } from "os";
25663
- import { join as join4 } from "path";
25880
+ import { join as join5 } from "path";
25664
25881
  function defaultDataDir() {
25665
- return join4(homedir(), ".aka");
25882
+ return join5(homedir(), ".aka");
25666
25883
  }
25667
25884
  function settingsDir(base = defaultDataDir()) {
25668
- return join4(base, "settings");
25885
+ return join5(base, "settings");
25669
25886
  }
25670
25887
  function dataDir(base = defaultDataDir()) {
25671
- return join4(base, "data");
25888
+ return join5(base, "data");
25672
25889
  }
25673
25890
  function dbPath(base = defaultDataDir()) {
25674
- return join4(dataDir(base), "aka.db");
25891
+ return join5(dataDir(base), "aka.db");
25675
25892
  }
25676
25893
  function keysDir(base = defaultDataDir()) {
25677
- return join4(base, "keys");
25894
+ return join5(base, "keys");
25895
+ }
25896
+ async function ensureDataDir(dir = defaultDataDir()) {
25897
+ await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
25898
+ tightenDir(dir);
25678
25899
  }
25679
25900
  function ensureLayoutDirSync(dir = defaultDataDir()) {
25680
25901
  ensureDataDirSync(dir);
@@ -25687,8 +25908,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
25687
25908
  for (const { name, dest } of moves) {
25688
25909
  try {
25689
25910
  ensureDataDirSync(dest);
25690
- const moved = join4(dest, name);
25691
- renameSync3(join4(base, name), moved);
25911
+ const moved = join5(dest, name);
25912
+ renameSync3(join5(base, name), moved);
25692
25913
  tightenFile(moved);
25693
25914
  } catch {
25694
25915
  }
@@ -25696,7 +25917,7 @@ function migrateLegacyLayout(base = defaultDataDir()) {
25696
25917
  }
25697
25918
 
25698
25919
  // ../../packages/persistence/src/managed-settings.ts
25699
- import { readFileSync as readFileSync3 } from "fs";
25920
+ import { readFileSync as readFileSync4 } from "fs";
25700
25921
  import { posix, win32 } from "path";
25701
25922
  function managedSettingsPaths(platform2 = process.platform) {
25702
25923
  if (platform2 === "darwin") {
@@ -25714,14 +25935,14 @@ function readManagedSettings(paths = managedSettingsPaths()) {
25714
25935
  for (const path of paths) {
25715
25936
  let text;
25716
25937
  try {
25717
- text = readFileSync3(path, "utf8");
25938
+ text = readFileSync4(path, "utf8");
25718
25939
  } catch {
25719
25940
  continue;
25720
25941
  }
25721
25942
  const record2 = parseJsonObject(text);
25722
25943
  if (!record2) continue;
25723
- const parsed = ManagedSettings.safeParse(record2);
25724
- if (parsed.success) return parsed.data;
25944
+ const parsed2 = ManagedSettings.safeParse(record2);
25945
+ if (parsed2.success) return parsed2.data;
25725
25946
  }
25726
25947
  return null;
25727
25948
  }
@@ -25761,14 +25982,14 @@ function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ n
25761
25982
  }
25762
25983
 
25763
25984
  // ../../packages/persistence/src/settings.ts
25764
- import { readFileSync as readFileSync4 } from "fs";
25765
- import { join as join5 } from "path";
25985
+ import { readFileSync as readFileSync5 } from "fs";
25986
+ import { join as join6 } from "path";
25766
25987
  var SETTINGS_FILENAME = "settings.json";
25767
25988
  function readWorkspaceSettings(base = defaultDataDir()) {
25768
25989
  return overlayManagedSettings(readUserSettings(base), readManagedSettings());
25769
25990
  }
25770
25991
  function readUserSettings(base) {
25771
- const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
25992
+ const record2 = readJson(join6(settingsDir(base), SETTINGS_FILENAME));
25772
25993
  if (!record2) return defaultWorkspaceSettings();
25773
25994
  try {
25774
25995
  return WorkspaceSettings.parse(record2);
@@ -25779,13 +26000,64 @@ function readUserSettings(base) {
25779
26000
  function readJson(file2) {
25780
26001
  let text;
25781
26002
  try {
25782
- text = readFileSync4(file2, "utf8");
26003
+ text = readFileSync5(file2, "utf8");
25783
26004
  } catch {
25784
26005
  return null;
25785
26006
  }
25786
26007
  return parseJsonObject(text) ?? null;
25787
26008
  }
25788
26009
 
26010
+ // ../../packages/persistence/src/store-symlinks.ts
26011
+ import { existsSync as existsSync4, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
26012
+ import { dirname as dirname2, join as join7, resolve } from "path";
26013
+ var STORE_DB = "the store database (including the prompt corpus)";
26014
+ var STORE_SETTINGS = "your settings file";
26015
+ function storeContents(home) {
26016
+ return /* @__PURE__ */ new Map([
26017
+ [home, "the store (including the prompt corpus in aka.db)"],
26018
+ [settingsDir(home), STORE_SETTINGS],
26019
+ [dataDir(home), STORE_DB],
26020
+ [keysDir(home), "the vault key"],
26021
+ [join7(settingsDir(home), "settings.json"), STORE_SETTINGS],
26022
+ [dbPath(home), STORE_DB]
26023
+ ]);
26024
+ }
26025
+ function symlinkedStorePaths(home, platform2 = process.platform) {
26026
+ return [...storeContents(home)].flatMap(([path, holds]) => {
26027
+ try {
26028
+ if (!lstatSync3(path).isSymbolicLink()) return [];
26029
+ return [
26030
+ {
26031
+ path,
26032
+ target: linkTarget(path),
26033
+ holds,
26034
+ // existsSync follows the link, so a target that is gone reads as
26035
+ // absent here while lstat above still sees the link itself.
26036
+ missing: !existsSync4(path),
26037
+ mode: targetMode(path, platform2)
26038
+ }
26039
+ ];
26040
+ } catch {
26041
+ return [];
26042
+ }
26043
+ });
26044
+ }
26045
+ function linkTarget(path) {
26046
+ try {
26047
+ return realpathSync(path);
26048
+ } catch {
26049
+ return resolve(dirname2(path), readlinkSync(path));
26050
+ }
26051
+ }
26052
+ function targetMode(path, platform2) {
26053
+ if (platform2 === "win32") return void 0;
26054
+ try {
26055
+ return statSync4(path).mode & 511;
26056
+ } catch {
26057
+ return void 0;
26058
+ }
26059
+ }
26060
+
25789
26061
  // ../../packages/persistence/src/vault/crypto.ts
25790
26062
  import {
25791
26063
  createCipheriv,
@@ -25897,8 +26169,8 @@ function formatPointer(category, keyVersion, pointerId, tag) {
25897
26169
  // ../../packages/persistence/src/vault/key-provider.ts
25898
26170
  import { execFileSync } from "child_process";
25899
26171
  import { randomBytes as randomBytes2 } from "crypto";
25900
- import { chmodSync as chmodSync2, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
25901
- import { join as join6 } from "path";
26172
+ import { chmodSync as chmodSync3, readFileSync as readFileSync6, renameSync as renameSync4, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
26173
+ import { join as join8 } from "path";
25902
26174
  var VAULT_OCCUPANT_REASON = {
25903
26175
  symlink: "the path is a symlink; remove it so a keyring can be created",
25904
26176
  gone: "the path was occupied but holds no keyring (removed while it was being created)",
@@ -25917,11 +26189,11 @@ var KEY_MATERIAL_BYTES2 = 32;
25917
26189
  var KEYCHAIN_SERVICE = "aka-vault";
25918
26190
  var KEYCHAIN_ACCOUNT = "keyring";
25919
26191
  function parseKeyring(raw) {
25920
- const parsed = JSON.parse(raw);
25921
- if (typeof parsed !== "object" || parsed === null) {
26192
+ const parsed2 = JSON.parse(raw);
26193
+ if (typeof parsed2 !== "object" || parsed2 === null) {
25922
26194
  throw new Error("vault key file is corrupt: not a JSON object");
25923
26195
  }
25924
- const { current, keys } = parsed;
26196
+ const { current, keys } = parsed2;
25925
26197
  if (typeof current !== "number" || !Number.isInteger(current) || current < 1) {
25926
26198
  throw new Error("vault key file is corrupt: bad current version");
25927
26199
  }
@@ -25997,28 +26269,28 @@ function claimRotationLock(lock, owner) {
25997
26269
  throw asError(err);
25998
26270
  }
25999
26271
  try {
26000
- writeFileSync3(join6(lock, LOCK_OWNER_FILE), `${owner}
26272
+ writeFileSync3(join8(lock, LOCK_OWNER_FILE), `${owner}
26001
26273
  `, { mode: DATA_FILE_MODE });
26002
26274
  return true;
26003
26275
  } catch (err) {
26004
- rmSync4(lock, { recursive: true, force: true });
26276
+ rmSync5(lock, { recursive: true, force: true });
26005
26277
  throw asError(err);
26006
26278
  }
26007
26279
  }
26008
26280
  function acquireRotationLock(keysDir2) {
26009
- const lock = join6(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
26281
+ const lock = join8(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
26010
26282
  const owner = randomBytes2(16).toString("hex");
26011
26283
  if (claimRotationLock(lock, owner)) return { lock, owner };
26012
26284
  let held;
26013
26285
  try {
26014
- held = statSync3(lock);
26286
+ held = statSync5(lock);
26015
26287
  } catch {
26016
26288
  throw new Error(ROTATION_IN_PROGRESS);
26017
26289
  }
26018
26290
  if (Date.now() - held.mtimeMs < ROTATION_LOCK_STALE_MS) throw new Error(ROTATION_IN_PROGRESS);
26019
26291
  const aside = `${lock}.stale.${owner}`;
26020
26292
  try {
26021
- const now = statSync3(lock);
26293
+ const now = statSync5(lock);
26022
26294
  if (now.ino !== held.ino || now.mtimeMs !== held.mtimeMs) {
26023
26295
  throw new Error(ROTATION_IN_PROGRESS);
26024
26296
  }
@@ -26027,17 +26299,17 @@ function acquireRotationLock(keysDir2) {
26027
26299
  if (err instanceof Error && err.message === ROTATION_IN_PROGRESS) throw err;
26028
26300
  throw new Error(ROTATION_IN_PROGRESS, { cause: err });
26029
26301
  }
26030
- rmSync4(aside, { recursive: true, force: true });
26302
+ rmSync5(aside, { recursive: true, force: true });
26031
26303
  if (!claimRotationLock(lock, owner)) throw new Error(ROTATION_IN_PROGRESS);
26032
26304
  return { lock, owner };
26033
26305
  }
26034
26306
  function releaseRotationLock(lease) {
26035
26307
  try {
26036
- if (readFileSync5(join6(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
26308
+ if (readFileSync6(join8(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
26037
26309
  } catch {
26038
26310
  return;
26039
26311
  }
26040
- rmSync4(lease.lock, { recursive: true, force: true });
26312
+ rmSync5(lease.lock, { recursive: true, force: true });
26041
26313
  }
26042
26314
  function withRotationLock(keysDir2, work) {
26043
26315
  ensureDataDirSync(keysDir2);
@@ -26054,7 +26326,7 @@ var FileKeyProvider = class {
26054
26326
  this.#keysDir = keysDir2;
26055
26327
  }
26056
26328
  get filePath() {
26057
- return join6(this.#keysDir, VAULT_KEY_FILENAME);
26329
+ return join8(this.#keysDir, VAULT_KEY_FILENAME);
26058
26330
  }
26059
26331
  loadOrCreate() {
26060
26332
  return asAsync(() => {
@@ -26084,7 +26356,7 @@ var FileKeyProvider = class {
26084
26356
  #read() {
26085
26357
  let raw;
26086
26358
  try {
26087
- raw = readFileSync5(this.filePath, "utf8");
26359
+ raw = readFileSync6(this.filePath, "utf8");
26088
26360
  } catch (err) {
26089
26361
  if (err.code === "ENOENT") return null;
26090
26362
  throw err instanceof Error ? err : new Error(String(err));
@@ -26141,7 +26413,7 @@ var FileKeyProvider = class {
26141
26413
  };
26142
26414
  function tightenFileMode(file2) {
26143
26415
  try {
26144
- chmodSync2(file2, DATA_FILE_MODE);
26416
+ chmodSync3(file2, DATA_FILE_MODE);
26145
26417
  } catch {
26146
26418
  }
26147
26419
  }
@@ -26406,25 +26678,25 @@ var SecretVault = class {
26406
26678
  * model. Every call that gets as far as an identified row writes an audit row.
26407
26679
  */
26408
26680
  async detokenize(token, opts) {
26409
- const parsed = parsePointer(token);
26410
- if (!parsed) return UNAVAILABLE;
26681
+ const parsed2 = parsePointer(token);
26682
+ if (!parsed2) return UNAVAILABLE;
26411
26683
  let signKey;
26412
26684
  try {
26413
- const epoch = await this.#keys.materialFor(parsed.keyVersion);
26685
+ const epoch = await this.#keys.materialFor(parsed2.keyVersion);
26414
26686
  signKey = deriveSubkeys(epoch.material).sign;
26415
26687
  } catch {
26416
26688
  return UNAVAILABLE;
26417
26689
  }
26418
- if (!verifyPointerTag(signKey, parsed.keyVersion, parsed.pointerId, parsed.category, parsed.tag)) {
26690
+ if (!verifyPointerTag(signKey, parsed2.keyVersion, parsed2.pointerId, parsed2.category, parsed2.tag)) {
26419
26691
  return UNAVAILABLE;
26420
26692
  }
26421
- const pointerId = base32Encode(parsed.pointerId);
26693
+ const pointerId = base32Encode(parsed2.pointerId);
26422
26694
  const row = this.#repo.byPointerId(pointerId);
26423
26695
  if (!row) {
26424
26696
  this.#audit(pointerId, opts, "unavailable");
26425
26697
  return UNAVAILABLE;
26426
26698
  }
26427
- if (row.category !== parsed.category) return UNAVAILABLE;
26699
+ if (row.category !== parsed2.category) return UNAVAILABLE;
26428
26700
  if (opts.target === "model") {
26429
26701
  const grantId = opts.grantId;
26430
26702
  const verify = this.#verifyGrant;
@@ -26461,7 +26733,7 @@ var SecretVault = class {
26461
26733
  // moved the epoch past the one this token names, and a format bump may
26462
26734
  // have moved the constant past the generation this row was sealed
26463
26735
  // under — the AAD follows the row in both cases, never the token.
26464
- bindingInput(row.keyVersion, parsed.pointerId, row.category, row.formatVersion)
26736
+ bindingInput(row.keyVersion, parsed2.pointerId, row.category, row.formatVersion)
26465
26737
  );
26466
26738
  } catch {
26467
26739
  raw = null;
@@ -26679,19 +26951,19 @@ var SecretVault = class {
26679
26951
  // preview. Verifying needs the historical epoch's key, which is why these
26680
26952
  // surfaces are async.
26681
26953
  async #rowFor(token) {
26682
- const parsed = parsePointer(token);
26683
- if (!parsed) return null;
26954
+ const parsed2 = parsePointer(token);
26955
+ if (!parsed2) return null;
26684
26956
  try {
26685
- const epoch = await this.#keys.materialFor(parsed.keyVersion);
26957
+ const epoch = await this.#keys.materialFor(parsed2.keyVersion);
26686
26958
  const signKey = deriveSubkeys(epoch.material).sign;
26687
- if (!verifyPointerTag(signKey, parsed.keyVersion, parsed.pointerId, parsed.category, parsed.tag)) {
26959
+ if (!verifyPointerTag(signKey, parsed2.keyVersion, parsed2.pointerId, parsed2.category, parsed2.tag)) {
26688
26960
  return null;
26689
26961
  }
26690
26962
  } catch {
26691
26963
  return null;
26692
26964
  }
26693
- const row = this.#repo.byPointerId(base32Encode(parsed.pointerId));
26694
- if (row?.category !== parsed.category) return null;
26965
+ const row = this.#repo.byPointerId(base32Encode(parsed2.pointerId));
26966
+ if (row?.category !== parsed2.category) return null;
26695
26967
  return row;
26696
26968
  }
26697
26969
  #audit(pointerId, opts, outcome) {
@@ -26711,13 +26983,13 @@ var SecretVault = class {
26711
26983
  };
26712
26984
 
26713
26985
  // ../../packages/persistence/src/warn-era-cap.ts
26714
- import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
26715
- import { join as join7 } from "path";
26986
+ import { existsSync as existsSync5, writeFileSync as writeFileSync4 } from "fs";
26987
+ import { join as join9 } from "path";
26716
26988
  var MARKER = "warn-era-capped";
26717
26989
  function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
26718
26990
  if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
26719
- const marker = join7(dataDir2, MARKER);
26720
- if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
26991
+ const marker = join9(dataDir2, MARKER);
26992
+ if (existsSync5(marker)) return { capped: 0, skipped: "already-run" };
26721
26993
  const capped = db.policies.capCategoryActions();
26722
26994
  writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
26723
26995
  `, { mode: DATA_FILE_MODE });
@@ -26758,8 +27030,8 @@ function hostOf(url2) {
26758
27030
  }
26759
27031
  }
26760
27032
  function resolveProvider() {
26761
- const parsed = ProviderEnvSchema.safeParse(process.env);
26762
- const env = parsed.success ? parsed.data : ProviderEnvSchema.parse({});
27033
+ const parsed2 = ProviderEnvSchema.safeParse(process.env);
27034
+ const env = parsed2.success ? parsed2.data : ProviderEnvSchema.parse({});
26763
27035
  if (env.CLAUDE_CODE_USE_BEDROCK === true) return { provider: "bedrock" };
26764
27036
  if (env.CLAUDE_CODE_USE_VERTEX === true) return { provider: "vertex" };
26765
27037
  const baseUrl = env.ANTHROPIC_BASE_URL;
@@ -26776,8 +27048,8 @@ function resolveProvider() {
26776
27048
  function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
26777
27049
  try {
26778
27050
  ensureLayoutDirSync(base);
26779
- const settingsFile = join8(settingsDir(base), "settings.json");
26780
- if (existsSync5(settingsFile)) tightenFile(settingsFile);
27051
+ const settingsFile = join10(settingsDir(base), "settings.json");
27052
+ if (existsSync6(settingsFile)) tightenFile(settingsFile);
26781
27053
  } catch {
26782
27054
  }
26783
27055
  migrateLegacyLayout(base);
@@ -26800,9 +27072,9 @@ function resolveProviderSafe(resolveProviderFn) {
26800
27072
  }
26801
27073
 
26802
27074
  // ../../packages/plugin-sdk/src/config-inventory.ts
26803
- import { readdirSync as readdirSync2, readFileSync as readFileSync7, realpathSync, statSync as statSync5 } from "fs";
27075
+ import { readdirSync as readdirSync2, readFileSync as readFileSync8, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
26804
27076
  import { homedir as homedir2 } from "os";
26805
- import { basename as basename3, join as join10 } from "path";
27077
+ import { basename as basename3, join as join12 } from "path";
26806
27078
 
26807
27079
  // ../../packages/detections/src/egress/registry.ts
26808
27080
  var EXTRACTOR_VERSION = "1";
@@ -28579,10 +28851,10 @@ var localhost_ref_default = {
28579
28851
  severity: "low",
28580
28852
  matcher: {
28581
28853
  type: "regex",
28582
- pattern: "\\b(?:localhost|127\\.0\\.0\\.1|0\\.0\\.0\\.0|::1)\\b",
28854
+ 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_])",
28583
28855
  flags: "g"
28584
28856
  },
28585
- examples: ["localhost", "127.0.0.1"]
28857
+ examples: ["localhost", "127.0.0.1", "0.0.0.0", "::1"]
28586
28858
  };
28587
28859
 
28588
28860
  // ../../rules/core-code-context/stack-trace.json
@@ -29891,8 +30163,8 @@ function bundledDetections() {
29891
30163
  }
29892
30164
 
29893
30165
  // ../../packages/plugin-sdk/src/repo.ts
29894
- import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
29895
- import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
30166
+ import { existsSync as existsSync7, readFileSync as readFileSync7, statSync as statSync6 } from "fs";
30167
+ import { basename as basename2, dirname as dirname3, isAbsolute, join as join11, sep as sep2 } from "path";
29896
30168
  function resolveRepo(cwd) {
29897
30169
  try {
29898
30170
  const root = findGitRoot(cwd);
@@ -29907,36 +30179,36 @@ function resolveRepo(cwd) {
29907
30179
  function findGitRoot(start) {
29908
30180
  let dir = start;
29909
30181
  for (; ; ) {
29910
- if (existsSync6(join9(dir, ".git"))) return dir;
29911
- const parent = dirname2(dir);
30182
+ if (existsSync7(join11(dir, ".git"))) return dir;
30183
+ const parent = dirname3(dir);
29912
30184
  if (parent === dir) return void 0;
29913
30185
  dir = parent;
29914
30186
  }
29915
30187
  }
29916
30188
  function resolveGitContext(root) {
29917
- const dotGit = join9(root, ".git");
30189
+ const dotGit = join11(root, ".git");
29918
30190
  try {
29919
- if (statSync4(dotGit).isDirectory()) {
29920
- return { configPath: join9(dotGit, "config"), headRoot: root };
30191
+ if (statSync6(dotGit).isDirectory()) {
30192
+ return { configPath: join11(dotGit, "config"), headRoot: root };
29921
30193
  }
29922
30194
  } catch {
29923
30195
  return void 0;
29924
30196
  }
29925
30197
  const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
29926
30198
  if (!target) return void 0;
29927
- const gitdir = isAbsolute(target) ? target : join9(root, target);
29928
- if (existsSync6(join9(gitdir, "config"))) {
29929
- return { configPath: join9(gitdir, "config"), headRoot: root };
30199
+ const gitdir = isAbsolute(target) ? target : join11(root, target);
30200
+ if (existsSync7(join11(gitdir, "config"))) {
30201
+ return { configPath: join11(gitdir, "config"), headRoot: root };
29930
30202
  }
29931
- const commonRaw = safeRead(join9(gitdir, "commondir"))?.trim();
30203
+ const commonRaw = safeRead(join11(gitdir, "commondir"))?.trim();
29932
30204
  if (!commonRaw) return void 0;
29933
- const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join9(gitdir, commonRaw);
29934
- const headRoot = basename2(commonGitDir) === ".git" ? dirname2(commonGitDir) : root;
29935
- return { configPath: join9(commonGitDir, "config"), headRoot };
30205
+ const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join11(gitdir, commonRaw);
30206
+ const headRoot = basename2(commonGitDir) === ".git" ? dirname3(commonGitDir) : root;
30207
+ return { configPath: join11(commonGitDir, "config"), headRoot };
29936
30208
  }
29937
30209
  function safeRead(path) {
29938
30210
  try {
29939
- return readFileSync6(path, "utf8");
30211
+ return readFileSync7(path, "utf8");
29940
30212
  } catch {
29941
30213
  return void 0;
29942
30214
  }
@@ -29997,7 +30269,7 @@ function buildIngestEvent(input) {
29997
30269
  }
29998
30270
 
29999
30271
  // ../../packages/plugin-sdk/src/isolated-scan.ts
30000
- import { existsSync as existsSync7 } from "fs";
30272
+ import { existsSync as existsSync8 } from "fs";
30001
30273
  import { fileURLToPath } from "url";
30002
30274
  import { Worker } from "worker_threads";
30003
30275
  var ISOLATED_SCAN_BUDGET_MS = 2e3;
@@ -30011,7 +30283,7 @@ function resolveWorkerUrl() {
30011
30283
  for (const name of ["scan-worker.js", "scan-worker.ts"]) {
30012
30284
  const candidate = new URL(name, import.meta.url);
30013
30285
  try {
30014
- if (existsSync7(fileURLToPath(candidate))) {
30286
+ if (existsSync8(fileURLToPath(candidate))) {
30015
30287
  resolvedWorkerUrl = candidate;
30016
30288
  return candidate;
30017
30289
  }
@@ -30196,8 +30468,8 @@ function createIsolatedScanner(data, opts = {}) {
30196
30468
  }
30197
30469
  function enqueue(spec) {
30198
30470
  const next = chain.then(
30199
- () => new Promise((resolve) => {
30200
- spec(resolve);
30471
+ () => new Promise((resolve2) => {
30472
+ spec(resolve2);
30201
30473
  })
30202
30474
  );
30203
30475
  chain = next.then(
@@ -30208,7 +30480,7 @@ function createIsolatedScanner(data, opts = {}) {
30208
30480
  }
30209
30481
  return {
30210
30482
  scan(text, context, scanOpts) {
30211
- return enqueue((resolve) => {
30483
+ return enqueue((resolve2) => {
30212
30484
  runOne(
30213
30485
  {
30214
30486
  budgetMs,
@@ -30221,23 +30493,23 @@ function createIsolatedScanner(data, opts = {}) {
30221
30493
  }),
30222
30494
  reply: (message) => {
30223
30495
  if (message.kind !== "result") return false;
30224
- resolve({ status: "ok", findings: message.findings });
30496
+ resolve2({ status: "ok", findings: message.findings });
30225
30497
  return true;
30226
30498
  }
30227
30499
  },
30228
- resolve
30500
+ resolve2
30229
30501
  );
30230
30502
  });
30231
30503
  },
30232
30504
  probe(rule) {
30233
- return enqueue((resolve) => {
30505
+ return enqueue((resolve2) => {
30234
30506
  runOne(
30235
30507
  {
30236
30508
  budgetMs: probeBudgetMs,
30237
30509
  build: (id) => ({ kind: "probe", id, rule }),
30238
30510
  reply: (message) => {
30239
30511
  if (message.kind !== "probed") return false;
30240
- resolve({
30512
+ resolve2({
30241
30513
  status: "ok",
30242
30514
  verdict: message.verdict,
30243
30515
  worstMs: message.worstMs,
@@ -30246,7 +30518,7 @@ function createIsolatedScanner(data, opts = {}) {
30246
30518
  return true;
30247
30519
  }
30248
30520
  },
30249
- resolve
30521
+ resolve2
30250
30522
  );
30251
30523
  });
30252
30524
  },
@@ -30476,23 +30748,23 @@ function createGuardedScanner(partition, gateway, opts) {
30476
30748
 
30477
30749
  // ../../packages/plugin-sdk/src/ignore-layers.ts
30478
30750
  var import_ignore = __toESM(require_ignore(), 1);
30479
- import { readFileSync as readFileSync8 } from "fs";
30480
- import { join as join11 } from "path";
30751
+ import { readFileSync as readFileSync9 } from "fs";
30752
+ import { join as join13 } from "path";
30481
30753
 
30482
30754
  // ../../packages/plugin-sdk/src/inventory-resolver.ts
30483
30755
  import { arch, hostname as hostname4, platform, release } from "os";
30484
30756
 
30485
30757
  // ../../packages/plugin-sdk/src/nudge.ts
30486
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
30487
- import { join as join12 } from "path";
30758
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
30759
+ import { join as join14 } from "path";
30488
30760
 
30489
30761
  // ../../packages/plugin-sdk/src/paths.ts
30490
- import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
30491
- import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
30762
+ import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
30763
+ import { basename as basename4, dirname as dirname4, sep as sep3 } from "path";
30492
30764
 
30493
30765
  // ../../packages/plugin-sdk/src/project-files.ts
30494
- import { existsSync as existsSync8, readdirSync as readdirSync4 } from "fs";
30495
- import { basename as basename5, join as join13 } from "path";
30766
+ import { existsSync as existsSync9, readdirSync as readdirSync4 } from "fs";
30767
+ import { basename as basename5, join as join15 } from "path";
30496
30768
 
30497
30769
  // ../../packages/plugin-sdk/src/provider-env-antigravity.ts
30498
30770
  var optionalBaseUrl2 = external_exports.preprocess((v) => {
@@ -30863,8 +31135,8 @@ function createPluginRuntime(gateway, settings, opts) {
30863
31135
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
30864
31136
 
30865
31137
  // ../../packages/plugin-sdk/src/throttle.ts
30866
- import { mkdirSync as mkdirSync3, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
30867
- import { join as join14 } from "path";
31138
+ import { mkdirSync as mkdirSync3, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
31139
+ import { join as join16 } from "path";
30868
31140
 
30869
31141
  // ../../packages/plugin-sdk/src/tokenize.ts
30870
31142
  function redactedPlaceholder(category) {
@@ -31171,17 +31443,17 @@ var UNOPENABLE_VAULT = {
31171
31443
 
31172
31444
  // src/protocol/marker.ts
31173
31445
  import { randomBytes as randomBytes4 } from "crypto";
31174
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync10, renameSync as renameSync5, writeFileSync as writeFileSync7 } from "fs";
31175
- import { join as join15 } from "path";
31446
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync11, renameSync as renameSync5, writeFileSync as writeFileSync7 } from "fs";
31447
+ import { join as join17 } from "path";
31176
31448
  var MARKER_FILE = "protocol-marker";
31177
31449
  function mintMarker() {
31178
31450
  return randomBytes4(8).toString("hex");
31179
31451
  }
31180
31452
  function sessionProtocolMarker(dataDir2, sessionId) {
31181
31453
  if (!sessionId) return mintMarker();
31182
- const path = join15(dataDir2, MARKER_FILE);
31454
+ const path = join17(dataDir2, MARKER_FILE);
31183
31455
  try {
31184
- const stored = JSON.parse(readFileSync10(path, "utf8"));
31456
+ const stored = JSON.parse(readFileSync11(path, "utf8"));
31185
31457
  if (stored.sessionId === sessionId && typeof stored.marker === "string" && /^[0-9a-f]{16}$/.test(stored.marker)) {
31186
31458
  return stored.marker;
31187
31459
  }
@@ -31190,7 +31462,7 @@ function sessionProtocolMarker(dataDir2, sessionId) {
31190
31462
  const marker = mintMarker();
31191
31463
  try {
31192
31464
  mkdirSync4(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
31193
- const tmp = join15(dataDir2, `${MARKER_FILE}.tmp`);
31465
+ const tmp = join17(dataDir2, `${MARKER_FILE}.tmp`);
31194
31466
  writeFileSync7(tmp, JSON.stringify({ sessionId, marker }), { mode: DATA_FILE_MODE });
31195
31467
  renameSync5(tmp, path);
31196
31468
  } catch {
@@ -31531,7 +31803,7 @@ function inputFilePath(toolInput) {
31531
31803
 
31532
31804
  // src/hooks/shared.ts
31533
31805
  async function readStdin() {
31534
- return new Promise((resolve) => {
31806
+ return new Promise((resolve2) => {
31535
31807
  let data = "";
31536
31808
  let settled = false;
31537
31809
  const finish = () => {
@@ -31540,7 +31812,7 @@ async function readStdin() {
31540
31812
  clearTimeout(timer);
31541
31813
  process.stdin.removeListener("data", onData);
31542
31814
  process.stdin.removeListener("end", finish);
31543
- resolve(data);
31815
+ resolve2(data);
31544
31816
  };
31545
31817
  const onData = (chunk) => {
31546
31818
  data += chunk;
@@ -31554,8 +31826,8 @@ async function readStdin() {
31554
31826
  }
31555
31827
  function parseJson(raw) {
31556
31828
  try {
31557
- const parsed = JSON.parse(raw);
31558
- return typeof parsed === "object" && parsed !== null ? parsed : null;
31829
+ const parsed2 = JSON.parse(raw);
31830
+ return typeof parsed2 === "object" && parsed2 !== null ? parsed2 : null;
31559
31831
  } catch {
31560
31832
  return null;
31561
31833
  }
@@ -31565,12 +31837,12 @@ function getString(record2, key) {
31565
31837
  return typeof value === "string" ? value : void 0;
31566
31838
  }
31567
31839
  function emit(output) {
31568
- return new Promise((resolve) => {
31840
+ return new Promise((resolve2) => {
31569
31841
  let settled = false;
31570
31842
  const finish = () => {
31571
31843
  if (settled) return;
31572
31844
  settled = true;
31573
- resolve();
31845
+ resolve2();
31574
31846
  };
31575
31847
  process.stdout.on("error", finish);
31576
31848
  process.stdout.write(JSON.stringify(output), finish);
@@ -31586,59 +31858,1249 @@ function baseMetadata(input) {
31586
31858
  }
31587
31859
 
31588
31860
  // src/hooks/store-health.ts
31589
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
31590
- import { join as join16 } from "path";
31861
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
31862
+ import { dirname as dirname5, join as join23 } from "path";
31863
+
31864
+ // ../../packages/plugin-runtime/src/attached/failure.ts
31865
+ function statusOf(err) {
31866
+ if (typeof err !== "object" || err === null || !("status" in err)) return null;
31867
+ const { status } = err;
31868
+ if (typeof status !== "number" || !Number.isInteger(status)) return null;
31869
+ return status >= 100 && status <= 599 ? status : null;
31870
+ }
31871
+ function classifyFailure(err) {
31872
+ switch (statusOf(err)) {
31873
+ case 401:
31874
+ return "unauthorized";
31875
+ case 403:
31876
+ return "forbidden";
31877
+ default:
31878
+ return "unreachable";
31879
+ }
31880
+ }
31591
31881
 
31592
- // ../../packages/plugin-runtime/src/standalone-gateway.ts
31593
- import { randomUUID as randomUUID15 } from "crypto";
31882
+ // ../../packages/plugin-runtime/src/attached/forward-drops.ts
31883
+ import { readFileSync as readFileSync12 } from "fs";
31884
+ import { join as join18 } from "path";
31885
+ var FORWARD_DROPS_FILENAME = "attached-forward-drops.json";
31886
+ function forwardDropsPath(dataDir2) {
31887
+ return join18(dataDir2, FORWARD_DROPS_FILENAME);
31888
+ }
31889
+ function recordForwardDrops(dataDir2, count, nowMs) {
31890
+ if (count <= 0) return;
31891
+ try {
31892
+ ensureDataDirSync(dataDir2);
31893
+ const previous = readForwardDrops(dataDir2);
31894
+ const next = {
31895
+ droppedForwards: (previous?.droppedForwards ?? 0) + count,
31896
+ lastDropAtMs: nowMs
31897
+ };
31898
+ writeOwnerOnlyFileSync(forwardDropsPath(dataDir2), `${JSON.stringify(next)}
31899
+ `);
31900
+ } catch {
31901
+ }
31902
+ }
31903
+ function readForwardDrops(dataDir2) {
31904
+ try {
31905
+ const parsed2 = JSON.parse(readFileSync12(forwardDropsPath(dataDir2), "utf8"));
31906
+ if (typeof parsed2 !== "object" || parsed2 === null) return null;
31907
+ const record2 = parsed2;
31908
+ if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
31909
+ return null;
31910
+ }
31911
+ if (record2.droppedForwards <= 0) return null;
31912
+ if (typeof record2.lastDropAtMs !== "number" || !Number.isFinite(record2.lastDropAtMs)) {
31913
+ return null;
31914
+ }
31915
+ return { droppedForwards: record2.droppedForwards, lastDropAtMs: record2.lastDropAtMs };
31916
+ } catch {
31917
+ return null;
31918
+ }
31919
+ }
31594
31920
 
31595
- // ../../packages/plugin-runtime/src/recorder.ts
31596
- var PLUGIN_RECORDER_BINARY = "plugin";
31921
+ // ../../packages/plugin-runtime/src/attached/forward-policy.ts
31922
+ import { randomUUID as randomUUID15 } from "crypto";
31923
+ import { readFileSync as readFileSync13 } from "fs";
31924
+ import { readFile, rename, writeFile } from "fs/promises";
31925
+ import { join as join19 } from "path";
31926
+
31927
+ // ../../packages/plugin-runtime/src/attached/with-timeout.ts
31928
+ var REQUEST_TIMEOUT_MS = 2e3;
31929
+ function withTimeout(promise2, ms) {
31930
+ let timer;
31931
+ const timeout = new Promise((_, reject) => {
31932
+ timer = setTimeout(() => {
31933
+ reject(new Error("attached gateway request timed out"));
31934
+ }, ms);
31935
+ });
31936
+ promise2.catch(() => void 0);
31937
+ return Promise.race([promise2, timeout]).finally(() => {
31938
+ clearTimeout(timer);
31939
+ });
31940
+ }
31597
31941
 
31598
- // ../../packages/plugin-runtime/src/standalone-gateway.ts
31599
- var StandaloneDataGateway = class {
31600
- db;
31601
- // Kept for the fingerprint key lookup (exception.key lives beside the store).
31602
- dataDir;
31603
- // One notice per gateway — see warnRulesetDiscarded.
31604
- warnedRulesetDiscarded = false;
31605
- constructor(dataDir2, detections = [], meta3) {
31606
- this.db = openLocalDatabase(dataDir2);
31607
- this.dataDir = dataDir2;
31608
- this.db.installedPacks.recordInventory(detections, meta3);
31609
- }
31610
- recordCapture(record2) {
31611
- this.db.recordCapture(record2.event, record2.findings);
31612
- return Promise.resolve();
31942
+ // ../../packages/plugin-runtime/src/attached/forward-policy.ts
31943
+ function isInvalidRequest(err) {
31944
+ return typeof err === "object" && err !== null && err.name === "RemoteRequestInvalid";
31945
+ }
31946
+ var FORWARD_BUDGET_MS = 1500;
31947
+ var DECISION_PATH_BUDGET_MS = 800;
31948
+ var BREAKER_FAILURE_THRESHOLD = 3;
31949
+ var BREAKER_COOLDOWN_MS = 3e4;
31950
+ var CLOSED = { consecutiveFailures: 0, openedAtMs: null, lastFailure: null };
31951
+ var FAILURES = /* @__PURE__ */ new Set([
31952
+ "unauthorized",
31953
+ "forbidden",
31954
+ "unreachable"
31955
+ ]);
31956
+ var FORWARD_STATE_FILENAME = "attached-state.json";
31957
+ var STATE_FILENAME = FORWARD_STATE_FILENAME;
31958
+ function parseBreakerState(raw, nowMs) {
31959
+ try {
31960
+ const parsed2 = JSON.parse(raw);
31961
+ if (typeof parsed2 !== "object" || parsed2 === null) return null;
31962
+ const record2 = parsed2;
31963
+ const failures = typeof record2.consecutiveFailures === "number" && record2.consecutiveFailures >= 0 ? record2.consecutiveFailures : 0;
31964
+ const openedAtMs = typeof record2.openedAtMs === "number" && Number.isFinite(record2.openedAtMs) && record2.openedAtMs <= nowMs ? record2.openedAtMs : null;
31965
+ const lastFailure = typeof record2.lastFailure === "string" && FAILURES.has(record2.lastFailure) ? record2.lastFailure : null;
31966
+ return { consecutiveFailures: failures, openedAtMs, lastFailure };
31967
+ } catch {
31968
+ return null;
31613
31969
  }
31614
- ensureInventory(ctx) {
31615
- return Promise.resolve(this.db.ensureInventory(ctx));
31970
+ }
31971
+ function createForwardPolicy(deps) {
31972
+ const now = deps.now ?? (() => Date.now());
31973
+ const file2 = join19(deps.dir, STATE_FILENAME);
31974
+ let state = null;
31975
+ let loading = null;
31976
+ async function readState() {
31977
+ let raw;
31978
+ try {
31979
+ raw = await readFile(file2, "utf8");
31980
+ } catch {
31981
+ return { ...CLOSED };
31982
+ }
31983
+ return parseBreakerState(raw, now()) ?? { ...CLOSED };
31616
31984
  }
31617
- recordAuditEvent(event) {
31618
- this.db.auditEvents.insertAuditEvent(event);
31619
- return Promise.resolve();
31985
+ async function load() {
31986
+ if (state !== null) return state;
31987
+ loading ??= readState().then((loaded) => {
31988
+ state = loaded;
31989
+ loading = null;
31990
+ return loaded;
31991
+ });
31992
+ return loading;
31620
31993
  }
31621
- // The id is minted inside the repository from the natural key — the plugin can't
31622
- // import @akasecurity/persistence to compute it, so the gateway is the boundary that
31623
- // hands the natural key across. UPSERT-take-MAX → idempotent re-reads that also
31624
- // converge a streaming partial/final split (see insertLlmCall).
31625
- recordLlmCall(input) {
31626
- this.db.auditEvents.insertLlmCall(input);
31627
- return Promise.resolve();
31994
+ async function persist(next) {
31995
+ state = next;
31996
+ try {
31997
+ await ensureDataDir(deps.dir);
31998
+ const tmp = `${file2}.${randomUUID15()}.tmp`;
31999
+ await writeFile(tmp, JSON.stringify(next), { encoding: "utf8", mode: DATA_FILE_MODE });
32000
+ await rename(tmp, file2);
32001
+ } catch {
32002
+ }
31628
32003
  }
31629
- // One reconcile pass = one transaction. All leaves commit together
31630
- // (single lock + WAL fsync); a contended SQLITE_BUSY rolls back and rejects so the
31631
- // reconciler drops the whole pass and recovers it idempotently on the next read.
31632
- recordLlmCalls(inputs) {
31633
- if (inputs.length === 0) return Promise.resolve();
31634
- return new Promise((resolve, reject) => {
32004
+ return {
32005
+ async run(op, opts) {
32006
+ const budget = opts?.decisionPath === true ? DECISION_PATH_BUDGET_MS : FORWARD_BUDGET_MS;
32007
+ let current;
31635
32008
  try {
31636
- this.db.auditEvents.runInTransaction(() => {
31637
- for (const input of inputs) this.db.auditEvents.insertLlmCall(input);
32009
+ current = await load();
32010
+ } catch {
32011
+ current = { ...CLOSED };
32012
+ }
32013
+ const at = now();
32014
+ if (current.openedAtMs !== null) {
32015
+ if (at - current.openedAtMs < BREAKER_COOLDOWN_MS) {
32016
+ return { ok: false, reason: "breaker-open" };
32017
+ }
32018
+ await persist({
32019
+ consecutiveFailures: current.consecutiveFailures,
32020
+ openedAtMs: at,
32021
+ lastFailure: current.lastFailure
31638
32022
  });
31639
- resolve();
31640
- } catch (err) {
31641
- reject(err instanceof Error ? err : new Error(String(err)));
32023
+ }
32024
+ try {
32025
+ const value = await withTimeout(op(), budget);
32026
+ if (current.openedAtMs !== null || current.consecutiveFailures > 0) {
32027
+ await persist({ ...CLOSED });
32028
+ }
32029
+ return { ok: true, value };
32030
+ } catch (err) {
32031
+ if (isInvalidRequest(err)) return { ok: false, reason: "invalid-request" };
32032
+ const reason = classifyFailure(err);
32033
+ const failures = current.consecutiveFailures + 1;
32034
+ const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
32035
+ await persist({
32036
+ consecutiveFailures: failures,
32037
+ openedAtMs: shouldOpen ? now() : null,
32038
+ lastFailure: reason
32039
+ });
32040
+ return { ok: false, reason };
32041
+ }
32042
+ }
32043
+ };
32044
+ }
32045
+
32046
+ // ../../packages/plugin-runtime/src/attached/gateway.ts
32047
+ var ACTION_STRENGTH = {
32048
+ allow: 0,
32049
+ log: 1,
32050
+ warn: 2,
32051
+ redact: 3,
32052
+ block: 4
32053
+ };
32054
+ function ruleCategoryMap(wireRules, localRules) {
32055
+ const map2 = /* @__PURE__ */ new Map();
32056
+ for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
32057
+ for (const rule of localRules ?? []) map2.set(rule.id, rule.category);
32058
+ for (const pack of bundledDetections()) {
32059
+ for (const rule of pack.rules) map2.set(rule.id, rule.category);
32060
+ }
32061
+ return map2;
32062
+ }
32063
+ function strongerOf(a, b) {
32064
+ if (a === null) return b;
32065
+ if (b === null) return a;
32066
+ return ACTION_STRENGTH[a] >= ACTION_STRENGTH[b] ? a : b;
32067
+ }
32068
+ function policyKey(policy) {
32069
+ return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
32070
+ }
32071
+ function floorFor(policy, categoryByRuleId) {
32072
+ const category = "category" in policy.target ? policy.target.category : categoryByRuleId.get(policy.target.ruleId);
32073
+ return category === void 0 ? null : DEFAULT_ACTIONS[category];
32074
+ }
32075
+ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
32076
+ const merged = /* @__PURE__ */ new Map();
32077
+ const disabled = [];
32078
+ const remoteCategoryAction = /* @__PURE__ */ new Map();
32079
+ for (const policy of remotePolicies) {
32080
+ if (!policy.enabled) continue;
32081
+ if (!("category" in policy.target)) continue;
32082
+ if (remoteCategoryAction.has(policy.target.category)) continue;
32083
+ const floor = floorFor(policy, categoryByRuleId);
32084
+ remoteCategoryAction.set(
32085
+ policy.target.category,
32086
+ floor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[floor] ? floor : policy.action
32087
+ );
32088
+ }
32089
+ for (const policy of localPolicies) {
32090
+ if (!policy.enabled) {
32091
+ disabled.push(policy);
32092
+ continue;
32093
+ }
32094
+ const key = policyKey(policy);
32095
+ if (merged.has(key)) continue;
32096
+ let remoteFloor = null;
32097
+ if ("ruleId" in policy.target) {
32098
+ const category = categoryByRuleId.get(policy.target.ruleId);
32099
+ if (category !== void 0) remoteFloor = remoteCategoryAction.get(category) ?? null;
32100
+ }
32101
+ merged.set(
32102
+ key,
32103
+ remoteFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[remoteFloor] ? { ...policy, action: remoteFloor } : policy
32104
+ );
32105
+ }
32106
+ const localCategoryAction = /* @__PURE__ */ new Map();
32107
+ for (const policy of merged.values()) {
32108
+ if ("category" in policy.target) localCategoryAction.set(policy.target.category, policy.action);
32109
+ }
32110
+ for (const policy of remotePolicies) {
32111
+ if (!policy.enabled) {
32112
+ disabled.push(policy);
32113
+ continue;
32114
+ }
32115
+ const key = policyKey(policy);
32116
+ const floor = floorFor(policy, categoryByRuleId);
32117
+ let localFloor = null;
32118
+ if ("ruleId" in policy.target) {
32119
+ const category = categoryByRuleId.get(policy.target.ruleId);
32120
+ if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
32121
+ }
32122
+ const effectiveFloor = strongerOf(floor, localFloor);
32123
+ const clamped = effectiveFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[effectiveFloor] ? { ...policy, action: effectiveFloor } : policy;
32124
+ const existing = merged.get(key);
32125
+ if (existing === void 0) {
32126
+ merged.set(key, clamped);
32127
+ continue;
32128
+ }
32129
+ if (ACTION_STRENGTH[clamped.action] > ACTION_STRENGTH[existing.action]) {
32130
+ merged.set(key, clamped);
32131
+ }
32132
+ }
32133
+ return [...merged.values(), ...disabled];
32134
+ }
32135
+ var AttachedDataGateway = class {
32136
+ constructor(deps) {
32137
+ this.deps = deps;
32138
+ }
32139
+ deps;
32140
+ /**
32141
+ * The control plane's OWN resolution of this session's inventory, captured by
32142
+ * ensureInventory. Null until the first successful forward — and it stays
32143
+ * null for the whole session when the control plane is unreachable, which is fine:
32144
+ * reKeyForForward then leaves the event's ids alone and the control plane resolves
32145
+ * what it can from the descriptors it already has.
32146
+ */
32147
+ remoteInventory = null;
32148
+ // ---------------------------------------------------------------------
32149
+ // Writes: local first, then forward.
32150
+ // ---------------------------------------------------------------------
32151
+ async recordCapture(record2) {
32152
+ await this.deps.local.recordCapture(record2);
32153
+ await this.deps.forward.run(
32154
+ () => this.deps.client.ingestEvents({
32155
+ events: [record2.event],
32156
+ ...record2.dedupe ? { dedupe: record2.dedupe } : {}
32157
+ }),
32158
+ { decisionPath: true }
32159
+ );
32160
+ }
32161
+ async ensureInventory(ctx) {
32162
+ const resolved = await this.deps.local.ensureInventory(ctx);
32163
+ const remote = await this.deps.forward.run(() => this.deps.client.ingestInventory(ctx));
32164
+ this.remoteInventory = remote.ok ? remote.value : null;
32165
+ const snapshot = await (async () => {
32166
+ try {
32167
+ return await this.deps.posture?.prepare() ?? null;
32168
+ } catch {
32169
+ return null;
32170
+ }
32171
+ })();
32172
+ if (snapshot) {
32173
+ try {
32174
+ await withTimeout(
32175
+ this.deps.posture?.send(snapshot) ?? Promise.resolve(),
32176
+ REQUEST_TIMEOUT_MS
32177
+ );
32178
+ } catch {
32179
+ }
32180
+ }
32181
+ return resolved;
32182
+ }
32183
+ // The id is minted CLIENT-side and stored verbatim: the control plane does NOT
32184
+ // re-key it. `pgAuditValues` writes `id: event.id` and carries tenancy in
32185
+ // its own scoping columns, so the device and the forwarded copy
32186
+ // share one id space — which is what makes a re-post idempotent at all.
32187
+ //
32188
+ // Re-posts collapse via `onConflictDoUpdate` on the `id` PK, guarded by
32189
+ // `setWhere eventType = 'session'` (NOT onConflictDoNothing). That guard is
32190
+ // what makes an attached retry safe: a capture-stubbed session row can still
32191
+ // be HEALED by the authoritative root, while a duplicate non-session event —
32192
+ // a retried tool_call, exactly this path — can never stomp a populated row.
32193
+ async recordAuditEvent(event) {
32194
+ await this.deps.local.recordAuditEvent(event);
32195
+ await this.deps.forward.run(
32196
+ () => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
32197
+ );
32198
+ }
32199
+ // Attached `llm_call` is written locally by the inner gateway, then routed to
32200
+ // the control plane through the existing `recordAuditEvent` ingest (no dedicated
32201
+ // client method yet) by pre-building the audit event from the natural key.
32202
+ // The forward goes DIRECTLY to the client rather than through this.recordAuditEvent,
32203
+ // which would write the event to the local store a second time.
32204
+ async recordLlmCall(input) {
32205
+ await this.deps.local.recordLlmCall(input);
32206
+ await this.deps.forward.run(
32207
+ () => this.deps.client.recordAuditEvent(
32208
+ reKeyForForward(llmAuditEvent(input), this.remoteInventory)
32209
+ )
32210
+ );
32211
+ }
32212
+ /**
32213
+ * Forward one batch, item by item, under ONE aggregate deadline.
32214
+ *
32215
+ * Per-item budgets bound each request and nothing bounded their sum — see
32216
+ * BATCH_FORWARD_BUDGET_MS. When the deadline passes the remainder is dropped
32217
+ * rather than sent: the local write has already succeeded, so every caller
32218
+ * has a correct result to return, and a drop is the outcome this path is
32219
+ * built to accept (G8) where a blown hook timeout is not.
32220
+ *
32221
+ * Serial rather than concurrent on purpose. Firing N requests at once would
32222
+ * trade a latency problem for a burst the plane's own per-key rate limiting
32223
+ * would answer with the refusals the breaker then counts.
32224
+ *
32225
+ * WHAT IS DROPPED IS COUNTED. Every other forward failure ends in
32226
+ * `ForwardPolicy.run`'s catch and moves the breaker's file, which is what
32227
+ * lets status call the forward unhealthy; this path returns BEFORE `run` is
32228
+ * reached, so without the tally in `forward-drops.ts` a slow-but-answering
32229
+ * plane produces no failures, keeps the breaker closed, renders a healthy
32230
+ * block, and discards the tail of every batch indefinitely.
32231
+ */
32232
+ async forwardBatch(inputs, toEvent) {
32233
+ const deadline = Date.now() + BATCH_FORWARD_BUDGET_MS;
32234
+ for (let i = 0; i < inputs.length; i += 1) {
32235
+ const now = Date.now();
32236
+ if (now >= deadline) {
32237
+ recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
32238
+ return;
32239
+ }
32240
+ const input = inputs[i];
32241
+ await this.deps.forward.run(
32242
+ () => this.deps.client.recordAuditEvent(reKeyForForward(toEvent(input), this.remoteInventory))
32243
+ );
32244
+ }
32245
+ }
32246
+ // Delegated as a BATCH rather than looped over recordLlmCall: the inner
32247
+ // gateway may write the whole batch in one local transaction, and looping
32248
+ // here would replace that with N separate local writes.
32249
+ async recordLlmCalls(inputs) {
32250
+ await this.deps.local.recordLlmCalls(inputs);
32251
+ await this.forwardBatch(inputs, (input) => llmAuditEvent(input));
32252
+ }
32253
+ // `input.inspections` (secrets detected client-side in the tool's masked
32254
+ // target) ride along on the request's `inspections` field — the control plane
32255
+ // persists each as an inspection_findings row linked to this audit event
32256
+ // (see RecordAuditEventRequest in @akasecurity/schema). The masked
32257
+ // `target` already rides `input.attributes`, so no raw secret leaks either
32258
+ // way — this only stops the FINDING row itself from being dropped.
32259
+ async recordToolCalls(inputs) {
32260
+ await this.deps.local.recordToolCalls(inputs);
32261
+ await this.forwardBatch(inputs, (input) => toolAuditEvent(input));
32262
+ }
32263
+ // Forwarded as a `config_scan` audit event: there is no dedicated
32264
+ // config-scan ingest endpoint, and the audit-event door is the one the
32265
+ // control plane already opens for client-minted, idempotent records.
32266
+ //
32267
+ // ONLY `scanEvent` CROSSES, and unlike `recordCapture` the plane cannot
32268
+ // re-derive the rest. A `ConfigScanRecord` is four things committed together
32269
+ // locally — the inventory `items`, this audit event, and the posture
32270
+ // `definitions`/`findings` that reference it — and three of them stay on the
32271
+ // device. Say that plainly rather than let the asymmetry with `recordCapture`
32272
+ // read as the same argument: there, findings are omitted BECAUSE the plane
32273
+ // re-derives them from `Event.content`; here there is no content to re-derive
32274
+ // from, so what is omitted is simply not sent.
32275
+ //
32276
+ // That is the wire contract as it stands rather than an oversight to patch
32277
+ // here. `items` has no route at all, and `RecordAuditEventRequest.inspections`
32278
+ // is documented as tool-call findings — widening it to carry config-scan
32279
+ // findings is an egress change (a posture finding's `maskedMatch` holds the
32280
+ // matched command) and a decision about what an attached deployment is
32281
+ // entitled to, not a bug fix. An attached machine's config posture therefore
32282
+ // reaches the plane as the event only; the dashboard's own view of it is the
32283
+ // local store.
32284
+ async recordConfigScan(record2) {
32285
+ await this.deps.local.recordConfigScan(record2);
32286
+ await this.deps.forward.run(
32287
+ () => this.deps.client.recordAuditEvent(reKeyForForward(record2.scanEvent, this.remoteInventory))
32288
+ );
32289
+ }
32290
+ async recordBlockedDetection(entry) {
32291
+ return this.deps.local.recordBlockedDetection(entry);
32292
+ }
32293
+ /**
32294
+ * LOCAL-ONLY, deliberately. The shares API is read-plus-decision-override
32295
+ * with no egress ingest endpoint, so there is nothing to forward to; adding a
32296
+ * forward here would be inventing a wire contract that does not exist. The
32297
+ * local write is the whole operation, and its summary is the real one — the
32298
+ * scanner reads a throw as a FAILED WRITE and skips its ledger commit, so
32299
+ * returning the inner gateway's result keeps the retry semantics honest.
32300
+ */
32301
+ async recordProjectEgress(input) {
32302
+ return this.deps.local.recordProjectEgress(input);
32303
+ }
32304
+ // ---------------------------------------------------------------------
32305
+ // Reads and device-local ledgers: pure delegation.
32306
+ // ---------------------------------------------------------------------
32307
+ async configInventoryReport() {
32308
+ return this.deps.local.configInventoryReport();
32309
+ }
32310
+ async readSessionProvider(sessionId) {
32311
+ return this.deps.local.readSessionProvider(sessionId);
32312
+ }
32313
+ async facets() {
32314
+ return this.deps.local.facets();
32315
+ }
32316
+ /**
32317
+ * Delegated UNMODIFIED — including its refusals.
32318
+ *
32319
+ * This is a fail-secure boundary: it decides whether an approved exception
32320
+ * lets a blocked action through. Under local-first the local store owns the
32321
+ * exception ledger, so the honest answer is whatever it says; wrapping this
32322
+ * in a fallback (`catch { return true }`, or defaulting on a timeout) would
32323
+ * turn a store error into a granted bypass. If the inner gateway rejects,
32324
+ * this rejects, and the runtime's own handling decides — which is asserted
32325
+ * end-to-end through runtime.capture rather than here.
32326
+ */
32327
+ async consumeException(id) {
32328
+ return this.deps.local.consumeException(id);
32329
+ }
32330
+ async recentFindings(opts) {
32331
+ return this.deps.local.recentFindings(opts);
32332
+ }
32333
+ async healthSummary() {
32334
+ return this.deps.local.healthSummary();
32335
+ }
32336
+ async activityByDay(days) {
32337
+ return this.deps.local.activityByDay(days);
32338
+ }
32339
+ async tokenReports() {
32340
+ return this.deps.local.tokenReports();
32341
+ }
32342
+ async knownContentHashes() {
32343
+ return this.deps.local.knownContentHashes();
32344
+ }
32345
+ async scanLedger(rulesetHash) {
32346
+ return this.deps.local.scanLedger(rulesetHash);
32347
+ }
32348
+ async recordScanned(entries) {
32349
+ return this.deps.local.recordScanned(entries);
32350
+ }
32351
+ async getRuleProbeVerdict(ruleKey) {
32352
+ return this.deps.local.getRuleProbeVerdict(ruleKey);
32353
+ }
32354
+ async setRuleProbeVerdict(ruleKey, verdict, worstProbeMs2) {
32355
+ return this.deps.local.setRuleProbeVerdict(ruleKey, verdict, worstProbeMs2);
32356
+ }
32357
+ async openAtRestKeysForPath(path) {
32358
+ return this.deps.local.openAtRestKeysForPath(path);
32359
+ }
32360
+ async resolvedAtRestKeysForPath(path) {
32361
+ return this.deps.local.resolvedAtRestKeysForPath(path);
32362
+ }
32363
+ async insertResolution(input) {
32364
+ return this.deps.local.insertResolution(input);
32365
+ }
32366
+ async close() {
32367
+ return this.deps.local.close();
32368
+ }
32369
+ // ---------------------------------------------------------------------
32370
+ // Policy
32371
+ // ---------------------------------------------------------------------
32372
+ async getPolicyBundle() {
32373
+ const local = await this.deps.local.getPolicyBundle();
32374
+ const cached2 = await (async () => {
32375
+ try {
32376
+ return await this.deps.readCachedBundle();
32377
+ } catch {
32378
+ return null;
32379
+ }
32380
+ })();
32381
+ if (cached2 === null) return local;
32382
+ const byRuleId = /* @__PURE__ */ new Map();
32383
+ for (const rule of [...local.rules ?? [], ...cached2.rules ?? []]) {
32384
+ if (!byRuleId.has(rule.id)) byRuleId.set(rule.id, rule);
32385
+ }
32386
+ const rules = [...byRuleId.values()];
32387
+ return {
32388
+ ...local,
32389
+ // The remote version identifies the composed bundle for the poller.
32390
+ version: cached2.version,
32391
+ rules,
32392
+ policies: mergeRaiseOnly(
32393
+ local.policies,
32394
+ cached2.policies,
32395
+ ruleCategoryMap(cached2.rules, local.rules)
32396
+ ),
32397
+ customKeywords: [...local.customKeywords, ...cached2.customKeywords]
32398
+ // `rulesComplete` is a STANDALONE-ONLY signal (the user's local installed
32399
+ // snapshot) and is taken from the LOCAL bundle only — never from the wire
32400
+ // or the on-disk cache. Honoring a cached one would hand the control plane, or
32401
+ // anything able to write policy-cache.json, a kill-switch over the
32402
+ // compiled-in bundled packs: `{ rulesComplete: true, rules: [] }` would
32403
+ // zero local detection. Spread from `local` above, and deliberately not
32404
+ // re-read from `cached` here.
32405
+ //
32406
+ // THREE MORE OF THE CACHED BUNDLE'S FIELDS ARE DROPPED, each on purpose,
32407
+ // and each named here so a reader can tell a decision from an omission:
32408
+ //
32409
+ // `exceptions` — an exception SUPPRESSES a detection, so honoring
32410
+ // one from an unsigned on-disk cache would let
32411
+ // anything able to write that file turn rules off.
32412
+ // Every other field this merge accepts can only
32413
+ // RAISE enforcement; this is the one that cannot,
32414
+ // so it stays local-only until the bundle is
32415
+ // signed. Exceptions remain a device-local ledger.
32416
+ // `reversibleRuleIds` — the Redact & Vault archetype makes a redaction
32417
+ // recoverable, which is a CUSTODY change: it puts
32418
+ // the detected value in the local vault instead of
32419
+ // destroying it. Taking that instruction from the
32420
+ // cache would let a remote party turn one-way
32421
+ // redaction into retention. Dropping it keeps the
32422
+ // one-way behaviour, which the schema itself calls
32423
+ // "the safe direction to default".
32424
+ // `ruleVersions` — remote rules fall back to their own spec version.
32425
+ // Cosmetic rather than protective: it only affects
32426
+ // how a finding is version-attributed, and the two
32427
+ // sides may therefore attribute org rules
32428
+ // differently. Worth carrying once there is a
32429
+ // reader that needs it; nothing reads it today.
32430
+ };
32431
+ }
32432
+ // ---------------------------------------------------------------------
32433
+ // LocalStoreMaintenance — by delegation (D3).
32434
+ //
32435
+ // Implementing these is what actually closes the skipped-local-maintenance
32436
+ // gap: the OSS structural guard `hasLocalStoreMaintenance()` is satisfied by
32437
+ // any object carrying all five, so the composite qualifies and SessionStart
32438
+ // runs maintenance on the device's real store.
32439
+ //
32440
+ // ⚠ Two of the five are SYNCHRONOUS and must stay that way. `handle-session-start`
32441
+ // calls `capWarnEraEnforcement` without `await` and uses `staleBinaryNotice`'s
32442
+ // return value directly; declaring them `async` here would hand those call
32443
+ // sites a Promise and silently break both.
32444
+ // ---------------------------------------------------------------------
32445
+ async sweepTerminalExceptions(retentionMs) {
32446
+ return this.deps.local.sweepTerminalExceptions(retentionMs);
32447
+ }
32448
+ capWarnEraEnforcement(policyMode) {
32449
+ return this.deps.local.capWarnEraEnforcement(policyMode);
32450
+ }
32451
+ async recordProjectFiles(projectId, scan2) {
32452
+ return this.deps.local.recordProjectFiles(projectId, scan2);
32453
+ }
32454
+ async reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot) {
32455
+ return this.deps.local.reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot);
32456
+ }
32457
+ staleBinaryNotice(currentVersion) {
32458
+ return this.deps.local.staleBinaryNotice(currentVersion);
32459
+ }
32460
+ };
32461
+ function reKeyForForward(event, remote) {
32462
+ if (remote === null) {
32463
+ const stripped = { ...event };
32464
+ delete stripped.hostId;
32465
+ delete stripped.harnessId;
32466
+ delete stripped.sourceProjectId;
32467
+ return stripped;
32468
+ }
32469
+ const rekeyed = { ...event };
32470
+ delete rekeyed.hostId;
32471
+ delete rekeyed.harnessId;
32472
+ delete rekeyed.sourceProjectId;
32473
+ if (remote.hostId !== void 0) rekeyed.hostId = remote.hostId;
32474
+ if (remote.harnessId !== void 0) rekeyed.harnessId = remote.harnessId;
32475
+ if (remote.sourceProjectId !== void 0) rekeyed.sourceProjectId = remote.sourceProjectId;
32476
+ return rekeyed;
32477
+ }
32478
+ var BATCH_FORWARD_BUDGET_MS = 3e3;
32479
+ function llmAuditEvent(input) {
32480
+ return {
32481
+ id: llmCallId(input.sessionId, input.messageId),
32482
+ eventType: "llm_call",
32483
+ startedAt: input.startedAt,
32484
+ parentId: input.parentId,
32485
+ rootSessionId: input.rootSessionId,
32486
+ attributes: input.attributes
32487
+ };
32488
+ }
32489
+ function toolAuditEvent(input) {
32490
+ return {
32491
+ id: toolCallId(input.sessionId, input.toolUseId),
32492
+ eventType: "tool_call",
32493
+ startedAt: input.startedAt,
32494
+ parentId: input.parentId,
32495
+ rootSessionId: input.rootSessionId,
32496
+ attributes: input.attributes,
32497
+ inspections: input.inspections
32498
+ };
32499
+ }
32500
+
32501
+ // ../../packages/plugin-runtime/src/attached/policy-store.ts
32502
+ import { randomUUID as randomUUID16 } from "crypto";
32503
+ import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
32504
+ import { join as join20 } from "path";
32505
+
32506
+ // ../../packages/plugin-runtime/src/attached/atomic-publish.ts
32507
+ import { rename as rename2 } from "fs/promises";
32508
+ var RETRYABLE = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY"]);
32509
+ var ATTEMPTS = 5;
32510
+ var delay = (ms) => new Promise((resolve2) => {
32511
+ setTimeout(resolve2, ms);
32512
+ });
32513
+ async function publishByRename(tmp, file2, move = rename2) {
32514
+ for (let attempt = 1; ; attempt += 1) {
32515
+ try {
32516
+ await move(tmp, file2);
32517
+ return;
32518
+ } catch (err) {
32519
+ const code = err.code;
32520
+ if (attempt >= ATTEMPTS || code === void 0 || !RETRYABLE.has(code)) throw err;
32521
+ await delay(attempt * 10);
32522
+ }
32523
+ }
32524
+ }
32525
+
32526
+ // ../../packages/plugin-runtime/src/attached/policy-store.ts
32527
+ function createPolicyStore(dir = dataDir()) {
32528
+ const file2 = join20(dir, "policy-cache.json");
32529
+ async function read() {
32530
+ try {
32531
+ const raw = await readFile2(file2, "utf8");
32532
+ const parsed2 = JSON.parse(raw);
32533
+ if (typeof parsed2 !== "object" || parsed2 === null) return null;
32534
+ const record2 = parsed2;
32535
+ const bundle = PolicyBundle.parse(record2.bundle);
32536
+ const fetchedAtMs = typeof record2.fetchedAtMs === "number" ? record2.fetchedAtMs : 0;
32537
+ const etag = typeof record2.etag === "string" ? record2.etag : void 0;
32538
+ return { bundle, fetchedAtMs, ...etag === void 0 ? {} : { etag } };
32539
+ } catch {
32540
+ return null;
32541
+ }
32542
+ }
32543
+ async function write(bundle, etag) {
32544
+ await ensureDataDir(dir);
32545
+ const stored = {
32546
+ bundle,
32547
+ fetchedAtMs: Date.now(),
32548
+ ...etag === void 0 ? {} : { etag }
32549
+ };
32550
+ const tmp = `${file2}.${randomUUID16()}.tmp`;
32551
+ try {
32552
+ await writeFile2(tmp, JSON.stringify(stored), {
32553
+ encoding: "utf8",
32554
+ mode: DATA_FILE_MODE,
32555
+ flag: "wx"
32556
+ });
32557
+ await publishByRename(tmp, file2);
32558
+ } catch (err) {
32559
+ await rm(tmp, { force: true }).catch(() => void 0);
32560
+ throw err;
32561
+ }
32562
+ }
32563
+ return { read, write, file: file2 };
32564
+ }
32565
+
32566
+ // ../../packages/remote/src/http.ts
32567
+ import { request as httpRequest } from "http";
32568
+ import { request as httpsRequest } from "https";
32569
+ var DEFAULT_TIMEOUT_MS = 1e4;
32570
+ var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
32571
+ var RemoteRequestError = class extends Error {
32572
+ constructor(status) {
32573
+ super(`control-plane request failed with status ${String(status)}`);
32574
+ this.status = status;
32575
+ this.name = "RemoteRequestError";
32576
+ }
32577
+ status;
32578
+ };
32579
+ var RemoteRequestInvalid = class extends Error {
32580
+ constructor(route, cause) {
32581
+ super(`refusing to send a malformed body to ${route}`);
32582
+ this.cause = cause;
32583
+ this.name = "RemoteRequestInvalid";
32584
+ }
32585
+ cause;
32586
+ };
32587
+ var RemoteResponseInvalid = class extends Error {
32588
+ constructor(route, detail) {
32589
+ super(`control plane answered ${route} with ${detail}`);
32590
+ this.name = "RemoteResponseInvalid";
32591
+ }
32592
+ };
32593
+ var RemoteTransportError = class extends Error {
32594
+ /**
32595
+ * The status the peer sent, when headers arrived and only the BODY was
32596
+ * refused.
32597
+ *
32598
+ * Undefined for the ordinary case this class was written for — no answer at
32599
+ * all. It exists because two paths reject after a status has already been
32600
+ * delivered: an oversized body and an aborted response. Discarding it there
32601
+ * reported a deployment answering 401 with a verbose body as a network
32602
+ * outage, which sends the reader to look at their network instead of their
32603
+ * credential.
32604
+ */
32605
+ constructor(reason, status) {
32606
+ super(`control-plane request did not complete: ${reason}`);
32607
+ this.status = status;
32608
+ this.name = "RemoteTransportError";
32609
+ }
32610
+ status;
32611
+ };
32612
+ async function send(options) {
32613
+ const url2 = new URL(options.url);
32614
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
32615
+ const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
32616
+ const requestOptions = {
32617
+ method: options.method,
32618
+ headers: {
32619
+ // CALLER HEADERS FIRST, so this module's own are not overridable. Spread
32620
+ // last they win, and two of the values below are ones no caller may
32621
+ // replace: `x-api-key` is the credential, and `content-length` is the
32622
+ // byte count that stops a multi-byte body being truncated by the
32623
+ // receiver. `SendOptions.headers` is a free-form record on an exported
32624
+ // function, so "no caller does that today" is not the guarantee to rely
32625
+ // on. The one header any caller actually passes — `if-none-match` on the
32626
+ // conditional GET — is untouched by this order.
32627
+ ...options.headers,
32628
+ // The credential. One header, matching what the deployment authenticates
32629
+ // on; a second copy in an `Authorization` header would be one more place
32630
+ // it can be logged by an intermediary for no gain.
32631
+ "x-api-key": options.apiKey,
32632
+ accept: "application/json",
32633
+ ...options.body === void 0 ? {} : {
32634
+ "content-type": "application/json",
32635
+ // Byte length, not string length: a multi-byte body sent with a
32636
+ // character count is truncated by the receiver.
32637
+ "content-length": String(Buffer.byteLength(options.body))
32638
+ }
32639
+ }
32640
+ };
32641
+ return new Promise((resolve2, reject) => {
32642
+ let settled = false;
32643
+ const fail = (reason, status) => {
32644
+ if (settled) return;
32645
+ settled = true;
32646
+ reject(new RemoteTransportError(reason, status));
32647
+ };
32648
+ const req = send_(url2, requestOptions, (res) => {
32649
+ const chunks = [];
32650
+ let size = 0;
32651
+ res.on("data", (chunk) => {
32652
+ size += chunk.length;
32653
+ if (size > MAX_RESPONSE_BYTES) {
32654
+ fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
32655
+ res.destroy();
32656
+ req.destroy();
32657
+ return;
32658
+ }
32659
+ chunks.push(chunk);
32660
+ });
32661
+ res.on("aborted", () => {
32662
+ fail("the response was aborted", res.statusCode);
32663
+ });
32664
+ res.on("end", () => {
32665
+ if (settled) return;
32666
+ settled = true;
32667
+ resolve2({
32668
+ status: res.statusCode ?? 0,
32669
+ headers: res.headers,
32670
+ body: Buffer.concat(chunks).toString("utf8")
32671
+ });
32672
+ });
32673
+ });
32674
+ const deadline = setTimeout(() => {
32675
+ fail(`no response within ${String(timeoutMs)}ms`);
32676
+ req.destroy();
32677
+ }, timeoutMs);
32678
+ deadline.unref();
32679
+ req.on("upgrade", (_res, socket) => {
32680
+ fail("the deployment answered with a protocol upgrade");
32681
+ socket.destroy();
32682
+ });
32683
+ req.on("close", () => {
32684
+ fail("the connection closed before a response was read");
32685
+ clearTimeout(deadline);
32686
+ });
32687
+ req.on("error", (err) => {
32688
+ fail(err.message);
32689
+ });
32690
+ if (options.body !== void 0) req.write(options.body);
32691
+ req.end();
32692
+ });
32693
+ }
32694
+
32695
+ // ../../packages/remote/src/client.ts
32696
+ var ROUTES = {
32697
+ events: "/v1/events",
32698
+ auditEvents: "/v1/audit-events",
32699
+ inventory: "/v1/inventory",
32700
+ storePosture: "/v1/store-posture",
32701
+ policyBundle: "/v1/policy-bundle",
32702
+ whoami: "/v1/plugin/whoami"
32703
+ };
32704
+ function headerValue(response, name) {
32705
+ const raw = response.headers[name];
32706
+ if (raw === void 0) return void 0;
32707
+ return Array.isArray(raw) ? raw[0] : raw;
32708
+ }
32709
+ function okBody(response) {
32710
+ if (response.status < 200 || response.status >= 300) {
32711
+ throw new RemoteRequestError(response.status);
32712
+ }
32713
+ return response.body;
32714
+ }
32715
+ function parsed(schema, body, route) {
32716
+ let json2;
32717
+ try {
32718
+ json2 = JSON.parse(body);
32719
+ } catch {
32720
+ throw new RemoteResponseInvalid(route, "a body that is not JSON");
32721
+ }
32722
+ const result = schema.safeParse(json2);
32723
+ if (!result.success) {
32724
+ throw new RemoteResponseInvalid(route, "a body this client cannot read");
32725
+ }
32726
+ return result.data;
32727
+ }
32728
+ function withoutTrailingSlashes(endpoint) {
32729
+ let end = endpoint.length;
32730
+ while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
32731
+ return endpoint.slice(0, end);
32732
+ }
32733
+ var SLASH = "/".charCodeAt(0);
32734
+ function createRemoteClient(options) {
32735
+ const base = withoutTrailingSlashes(options.endpoint);
32736
+ const url2 = (route) => `${base}${route}`;
32737
+ const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
32738
+ return {
32739
+ async ingestEvents(batch) {
32740
+ const response = await send({
32741
+ ...common,
32742
+ method: "POST",
32743
+ url: url2(ROUTES.events),
32744
+ body: JSON.stringify(batch)
32745
+ });
32746
+ return parsed(IngestAck, okBody(response), ROUTES.events);
32747
+ },
32748
+ async ingestInventory(context) {
32749
+ const response = await send({
32750
+ ...common,
32751
+ method: "POST",
32752
+ url: url2(ROUTES.inventory),
32753
+ body: JSON.stringify(context)
32754
+ });
32755
+ return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
32756
+ },
32757
+ async recordAuditEvent(event) {
32758
+ const validated = RecordAuditEventRequest.safeParse(event);
32759
+ if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
32760
+ const submission = validated.data;
32761
+ const response = await send({
32762
+ ...common,
32763
+ method: "POST",
32764
+ url: url2(ROUTES.auditEvents),
32765
+ body: JSON.stringify(submission)
32766
+ });
32767
+ okBody(response);
32768
+ },
32769
+ async reportStorePosture(snapshot) {
32770
+ const response = await send({
32771
+ ...common,
32772
+ method: "POST",
32773
+ url: url2(ROUTES.storePosture),
32774
+ body: JSON.stringify(snapshot)
32775
+ });
32776
+ okBody(response);
32777
+ },
32778
+ async getPolicyBundle(etag) {
32779
+ const response = await send({
32780
+ ...common,
32781
+ method: "GET",
32782
+ url: url2(ROUTES.policyBundle),
32783
+ ...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
32784
+ });
32785
+ if (response.status === 304) {
32786
+ return { changed: false, etag: headerValue(response, "etag") ?? etag };
32787
+ }
32788
+ const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
32789
+ return { changed: true, bundle, etag: headerValue(response, "etag") };
32790
+ },
32791
+ async whoami() {
32792
+ const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
32793
+ return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
32794
+ }
32795
+ };
32796
+ }
32797
+
32798
+ // ../../packages/plugin-runtime/src/attached/posture-reporter.ts
32799
+ var POSTURE_REPORT_INTERVAL_MS = 60 * 60 * 1e3;
32800
+ function createPostureReporter(deps) {
32801
+ async function prepare() {
32802
+ try {
32803
+ const state = await withTimeout(deps.store.read(), REQUEST_TIMEOUT_MS);
32804
+ if (state === null) return null;
32805
+ const nowMs = deps.now();
32806
+ const elapsed = nowMs - state.lastAttemptedAtMs;
32807
+ if (elapsed >= 0 && elapsed < POSTURE_REPORT_INTERVAL_MS) return null;
32808
+ try {
32809
+ await withTimeout(deps.store.markAttempted(state.deviceId, nowMs), REQUEST_TIMEOUT_MS);
32810
+ } catch {
32811
+ }
32812
+ const { readError, ...measurement } = deps.readStore();
32813
+ if (readError) return null;
32814
+ let plugin;
32815
+ try {
32816
+ plugin = await deps.pluginBlock?.();
32817
+ } catch {
32818
+ plugin = void 0;
32819
+ }
32820
+ return {
32821
+ deviceId: state.deviceId,
32822
+ hostname: deps.hostname(),
32823
+ capturedAt: nowMs,
32824
+ ...measurement,
32825
+ // Omit the key rather than spread an explicit `undefined` —
32826
+ // exactOptionalPropertyTypes distinguishes the two, and the bridge in
32827
+ // factory.ts keys on presence.
32828
+ ...plugin === void 0 ? {} : { plugin }
32829
+ };
32830
+ } catch {
32831
+ return null;
32832
+ }
32833
+ }
32834
+ async function send2(snapshot) {
32835
+ try {
32836
+ await deps.report(snapshot);
32837
+ } catch {
32838
+ }
32839
+ }
32840
+ return { prepare, send: send2 };
32841
+ }
32842
+
32843
+ // ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
32844
+ import { statSync as statSync9 } from "fs";
32845
+ import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
32846
+
32847
+ // ../../packages/plugin-runtime/src/attached/action-counts.ts
32848
+ function emptyActionCounts() {
32849
+ return Object.fromEntries(ACTION_TAKEN_KEYS.map((a) => [a, 0]));
32850
+ }
32851
+ function isActionTaken(value) {
32852
+ return ACTION_TAKEN_KEYS.includes(value);
32853
+ }
32854
+
32855
+ // ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
32856
+ var CAPTURE_EVENT_TYPES_SQL2 = `('prompt','response','code_change','tool_use')`;
32857
+ function isSchemaAbsent(err) {
32858
+ return err instanceof Error && /no such table/i.test(err.message);
32859
+ }
32860
+ function emptyReadout(readError = false) {
32861
+ const byAction = emptyActionCounts();
32862
+ return {
32863
+ storePresent: false,
32864
+ schemaVersion: null,
32865
+ findingsTotal: 0,
32866
+ findingsFirstAt: null,
32867
+ findingsLastAt: null,
32868
+ packs: [],
32869
+ policyCounts: { total: 0, disabled: 0, byAction },
32870
+ readError
32871
+ };
32872
+ }
32873
+ function readStorePosture(dbPath2) {
32874
+ try {
32875
+ statSync9(dbPath2);
32876
+ } catch (err) {
32877
+ const code = err.code;
32878
+ if (code === "ENOENT" || code === "ENOTDIR") return emptyReadout();
32879
+ return emptyReadout(true);
32880
+ }
32881
+ let db = null;
32882
+ let version2 = null;
32883
+ let packs2 = [];
32884
+ let policyCounts = {
32885
+ total: 0,
32886
+ disabled: 0,
32887
+ byAction: emptyActionCounts()
32888
+ };
32889
+ let findingsTotal = 0;
32890
+ let findingsFirstAt = null;
32891
+ let findingsLastAt = null;
32892
+ const currentReadout = () => ({
32893
+ storePresent: true,
32894
+ schemaVersion: version2,
32895
+ findingsTotal,
32896
+ findingsFirstAt,
32897
+ findingsLastAt,
32898
+ packs: packs2,
32899
+ policyCounts,
32900
+ readError: false
32901
+ });
32902
+ try {
32903
+ db = new DatabaseSync3(dbPath2, { readOnly: true });
32904
+ db.exec("PRAGMA busy_timeout = 2000");
32905
+ version2 = db.prepare("PRAGMA user_version").get().user_version;
32906
+ try {
32907
+ const packRows = db.prepare(
32908
+ `SELECT namespace, pack_id, version, enabled, updated_at FROM installed_packs ORDER BY namespace, pack_id`
32909
+ ).all();
32910
+ packs2 = packRows.map((r) => ({
32911
+ packId: `${r.namespace}/${r.pack_id}`,
32912
+ version: r.version,
32913
+ enabled: r.enabled !== 0,
32914
+ updatedAt: r.updated_at == null ? null : String(r.updated_at)
32915
+ }));
32916
+ } catch (err) {
32917
+ if (!isSchemaAbsent(err)) throw err;
32918
+ }
32919
+ try {
32920
+ const policyRows = db.prepare(`SELECT action, enabled FROM policies`).all();
32921
+ const byAction = emptyActionCounts();
32922
+ let disabled = 0;
32923
+ for (const row of policyRows) {
32924
+ if (row.enabled === 0) disabled += 1;
32925
+ if (isActionTaken(row.action)) byAction[row.action] += 1;
32926
+ }
32927
+ policyCounts = { total: policyRows.length, disabled, byAction };
32928
+ } catch (err) {
32929
+ if (!isSchemaAbsent(err)) throw err;
32930
+ }
32931
+ try {
32932
+ const agg = db.prepare(
32933
+ `SELECT count(*) AS n, min(f.first_detected_at) AS firstAt, max(f.first_detected_at) AS lastAt
32934
+ FROM inspection_findings f JOIN audit_events e ON e.id = f.audit_event_id
32935
+ WHERE e.event_type IN ${CAPTURE_EVENT_TYPES_SQL2}`
32936
+ ).get();
32937
+ findingsTotal = agg.n;
32938
+ findingsFirstAt = agg.firstAt;
32939
+ findingsLastAt = agg.lastAt;
32940
+ } catch (err) {
32941
+ if (!isSchemaAbsent(err)) throw err;
32942
+ }
32943
+ return currentReadout();
32944
+ } catch {
32945
+ return emptyReadout(true);
32946
+ } finally {
32947
+ try {
32948
+ db?.close();
32949
+ } catch {
32950
+ }
32951
+ }
32952
+ }
32953
+
32954
+ // ../../packages/plugin-runtime/src/attached/posture-store.ts
32955
+ import { randomUUID as randomUUID17 } from "crypto";
32956
+ import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
32957
+ import { join as join21 } from "path";
32958
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
32959
+ function createPostureStore(dir = settingsDir(), legacyDir) {
32960
+ const file2 = join21(dir, "posture-state.json");
32961
+ const legacyFile = legacyDir === void 0 ? null : join21(legacyDir, "posture-state.json");
32962
+ async function persist(state) {
32963
+ await ensureDataDir(dir);
32964
+ const tmp = `${file2}.${randomUUID17()}.tmp`;
32965
+ try {
32966
+ await writeFile3(tmp, JSON.stringify(state), { encoding: "utf8", mode: DATA_FILE_MODE });
32967
+ await publishByRename(tmp, file2);
32968
+ } catch (err) {
32969
+ await rm2(tmp, { force: true }).catch(() => void 0);
32970
+ throw err;
32971
+ }
32972
+ }
32973
+ async function readFrom(path) {
32974
+ let raw;
32975
+ try {
32976
+ raw = await readFile3(path, "utf8");
32977
+ } catch (err) {
32978
+ const code = err.code;
32979
+ if (code === "ENOENT" || code === "ENOTDIR") return null;
32980
+ throw err;
32981
+ }
32982
+ try {
32983
+ const parsed2 = JSON.parse(raw);
32984
+ if (typeof parsed2 === "object" && parsed2 !== null) {
32985
+ const record2 = parsed2;
32986
+ if (typeof record2.deviceId === "string" && UUID_RE.test(record2.deviceId)) {
32987
+ const stamp = typeof record2.lastAttemptedAtMs === "number" ? record2.lastAttemptedAtMs : typeof record2.lastReportedAtMs === "number" ? record2.lastReportedAtMs : 0;
32988
+ return { deviceId: record2.deviceId, lastAttemptedAtMs: stamp };
32989
+ }
32990
+ }
32991
+ } catch {
32992
+ }
32993
+ return null;
32994
+ }
32995
+ async function read() {
32996
+ const current = await readFrom(file2);
32997
+ if (current) return current;
32998
+ const legacy = legacyFile === null || legacyFile === file2 ? null : await readFrom(legacyFile).catch(() => null);
32999
+ if (legacy) {
33000
+ try {
33001
+ await persist(legacy);
33002
+ } catch {
33003
+ }
33004
+ return legacy;
33005
+ }
33006
+ const fresh = { deviceId: randomUUID17(), lastAttemptedAtMs: 0 };
33007
+ try {
33008
+ await ensureDataDir(dir);
33009
+ if (createOwnerOnlyFileSync(file2, JSON.stringify(fresh))) return fresh;
33010
+ } catch {
33011
+ return null;
33012
+ }
33013
+ const winner = await readFrom(file2).catch(() => null);
33014
+ if (winner) return winner;
33015
+ try {
33016
+ await persist(fresh);
33017
+ } catch {
33018
+ return null;
33019
+ }
33020
+ return fresh;
33021
+ }
33022
+ async function markAttempted(deviceId, atMs) {
33023
+ await persist({ deviceId, lastAttemptedAtMs: atMs });
33024
+ }
33025
+ return { read, markAttempted, file: file2 };
33026
+ }
33027
+
33028
+ // ../../packages/plugin-runtime/src/attached/sync-state.ts
33029
+ import { readFileSync as readFileSync14 } from "fs";
33030
+ import { join as join22 } from "path";
33031
+
33032
+ // ../../packages/plugin-runtime/src/attached/status.ts
33033
+ var REFUSAL_LINES = {
33034
+ unauthorized: "KEY REJECTED \u2014 re-attach with a valid plugin key",
33035
+ forbidden: "ACCESS REFUSED \u2014 key is valid but not permitted; ask your org admin"
33036
+ };
33037
+ var OUTCOME_LINES = {
33038
+ ok: "policy synced",
33039
+ "not-modified": "policy up to date",
33040
+ unauthorized: REFUSAL_LINES.unauthorized,
33041
+ forbidden: REFUSAL_LINES.forbidden,
33042
+ unreachable: "control plane unreachable at last attempt",
33043
+ "invalid-bundle": "control plane sent a policy bundle this build cannot read"
33044
+ };
33045
+
33046
+ // ../../packages/plugin-runtime/src/attached/sync-trigger.ts
33047
+ import { spawn } from "child_process";
33048
+ import { fileURLToPath as fileURLToPath2 } from "url";
33049
+ var SYNC_THROTTLE_MS = 15 * 60 * 1e3;
33050
+
33051
+ // ../../packages/plugin-runtime/src/attached/factory.ts
33052
+ import { hostname as hostname5 } from "os";
33053
+
33054
+ // ../../packages/plugin-runtime/src/standalone-gateway.ts
33055
+ import { randomUUID as randomUUID18 } from "crypto";
33056
+
33057
+ // ../../packages/plugin-runtime/src/recorder.ts
33058
+ var PLUGIN_RECORDER_BINARY = "plugin";
33059
+
33060
+ // ../../packages/plugin-runtime/src/standalone-gateway.ts
33061
+ var StandaloneDataGateway = class {
33062
+ db;
33063
+ // Kept for the fingerprint key lookup (exception.key lives beside the store).
33064
+ dataDir;
33065
+ // One notice per gateway — see warnRulesetDiscarded.
33066
+ warnedRulesetDiscarded = false;
33067
+ constructor(dataDir2, detections = [], meta3) {
33068
+ this.db = openLocalDatabase(dataDir2);
33069
+ this.dataDir = dataDir2;
33070
+ this.db.installedPacks.recordInventory(detections, meta3);
33071
+ }
33072
+ recordCapture(record2) {
33073
+ this.db.recordCapture(record2.event, record2.findings);
33074
+ return Promise.resolve();
33075
+ }
33076
+ ensureInventory(ctx) {
33077
+ return Promise.resolve(this.db.ensureInventory(ctx));
33078
+ }
33079
+ recordAuditEvent(event) {
33080
+ this.db.auditEvents.insertAuditEvent(event);
33081
+ return Promise.resolve();
33082
+ }
33083
+ // The id is minted inside the repository from the natural key — the plugin can't
33084
+ // import @akasecurity/persistence to compute it, so the gateway is the boundary that
33085
+ // hands the natural key across. UPSERT-take-MAX → idempotent re-reads that also
33086
+ // converge a streaming partial/final split (see insertLlmCall).
33087
+ recordLlmCall(input) {
33088
+ this.db.auditEvents.insertLlmCall(input);
33089
+ return Promise.resolve();
33090
+ }
33091
+ // One reconcile pass = one transaction. All leaves commit together
33092
+ // (single lock + WAL fsync); a contended SQLITE_BUSY rolls back and rejects so the
33093
+ // reconciler drops the whole pass and recovers it idempotently on the next read.
33094
+ recordLlmCalls(inputs) {
33095
+ if (inputs.length === 0) return Promise.resolve();
33096
+ return new Promise((resolve2, reject) => {
33097
+ try {
33098
+ this.db.auditEvents.runInTransaction(() => {
33099
+ for (const input of inputs) this.db.auditEvents.insertLlmCall(input);
33100
+ });
33101
+ resolve2();
33102
+ } catch (err) {
33103
+ reject(err instanceof Error ? err : new Error(String(err)));
31642
33104
  }
31643
33105
  });
31644
33106
  }
@@ -31648,12 +33110,12 @@ var StandaloneDataGateway = class {
31648
33110
  // drops the whole pass and recovers it idempotently next time.
31649
33111
  recordToolCalls(inputs) {
31650
33112
  if (inputs.length === 0) return Promise.resolve();
31651
- return new Promise((resolve, reject) => {
33113
+ return new Promise((resolve2, reject) => {
31652
33114
  try {
31653
33115
  this.db.auditEvents.runInTransaction(() => {
31654
33116
  for (const input of inputs) this.writeToolCall(input);
31655
33117
  });
31656
- resolve();
33118
+ resolve2();
31657
33119
  } catch (err) {
31658
33120
  reject(err instanceof Error ? err : new Error(String(err)));
31659
33121
  }
@@ -31795,7 +33257,7 @@ var StandaloneDataGateway = class {
31795
33257
  const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
31796
33258
  const installed = this.installedScanRules();
31797
33259
  const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
31798
- id: randomUUID15(),
33260
+ id: randomUUID18(),
31799
33261
  scope: "global",
31800
33262
  target: { ruleId },
31801
33263
  action,
@@ -31949,19 +33411,66 @@ var StandaloneDataGateway = class {
31949
33411
  }
31950
33412
  };
31951
33413
 
33414
+ // ../../packages/plugin-runtime/src/attached/factory.ts
33415
+ function resolveGatewayForConfig(config2, meta3) {
33416
+ const local = new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
33417
+ try {
33418
+ if (!isAttached(config2.settings)) return local;
33419
+ const connection = config2.settings.controlPlane;
33420
+ if (connection === void 0) return local;
33421
+ const state = readControlPlaneCredentialState(config2.settingsDir, connection);
33422
+ if (!state.usable) return local;
33423
+ const client = createRemoteClient({
33424
+ endpoint: connection.endpoint,
33425
+ apiKey: state.credential.apiKey
33426
+ });
33427
+ const store = createPolicyStore(config2.dataDir);
33428
+ const postureStore = createPostureStore(config2.settingsDir, config2.dataDir);
33429
+ const forward = createForwardPolicy({ dir: config2.dataDir });
33430
+ return new AttachedDataGateway({
33431
+ local,
33432
+ client,
33433
+ dataDir: config2.dataDir,
33434
+ readCachedBundle: () => store.read().then((cached2) => cached2?.bundle ?? null),
33435
+ forward,
33436
+ posture: createPostureReporter({
33437
+ // THROUGH THE BREAKER, and wrapped HERE rather than around
33438
+ // `PostureReporter.send`. The reporter swallows every error by
33439
+ // contract, so a wrap outside it would hand `forward.run` a resolved
33440
+ // promise for a send that failed — recording a SUCCESS, clearing
33441
+ // `consecutiveFailures` and `lastFailure`, and telling `aka status` the
33442
+ // forward recovered when nothing did. Wrapping the raw client call puts
33443
+ // the breaker above the swallow, where it can see the truth.
33444
+ //
33445
+ // What it buys: once the breaker is open — the plane already confirmed
33446
+ // down by the gateway's own writes — this stops paying a request
33447
+ // timeout per throttle interval to re-learn it.
33448
+ report: (snapshot) => forward.run(() => client.reportStorePosture(snapshot)).then(() => void 0),
33449
+ store: postureStore,
33450
+ readStore: () => readStorePosture(config2.dbPath),
33451
+ hostname: () => hostname5(),
33452
+ now: () => Date.now()
33453
+ })
33454
+ });
33455
+ } catch {
33456
+ return local;
33457
+ }
33458
+ }
33459
+
31952
33460
  // ../../packages/plugin-runtime/src/resolve.ts
31953
- var standaloneGatewayFactory = (config2, meta3) => new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
31954
- var defaultGatewayFactory = standaloneGatewayFactory;
33461
+ var configuredGatewayFactory = (config2, meta3) => resolveGatewayForConfig(config2, meta3);
33462
+ var defaultGatewayFactory = configuredGatewayFactory;
31955
33463
  function resolveDataGateway(config2, meta3, gatewayFactory = defaultGatewayFactory) {
31956
33464
  return gatewayFactory(config2, meta3);
31957
33465
  }
31958
33466
 
31959
33467
  // ../../packages/plugin-runtime/src/handle-session-start.ts
31960
- import { randomUUID as randomUUID16 } from "crypto";
33468
+ import { randomUUID as randomUUID19 } from "crypto";
31961
33469
  var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
31962
33470
 
31963
33471
  // src/hooks/store-health.ts
31964
33472
  var STORE_WARNING_MARKER = "store-warning-last-session";
33473
+ var STORE_REDIRECT_MARKER = "store-redirect-last-session";
31965
33474
  function openGatewayOrNull(config2) {
31966
33475
  try {
31967
33476
  return resolveDataGateway(config2);
@@ -31974,17 +33483,64 @@ function storeUnavailableMessage(dbPath2) {
31974
33483
  }
31975
33484
  function claimStoreUnavailableWarning(dataDir2, sessionId) {
31976
33485
  if (!sessionId) return true;
31977
- const path = join16(dataDir2, STORE_WARNING_MARKER);
31978
- try {
31979
- if (readFileSync11(path, "utf8") === sessionId) return false;
31980
- } catch {
33486
+ const dirs = markerDirs(dataDir2);
33487
+ if (alreadyClaimed(dirs, STORE_WARNING_MARKER, sessionId)) return false;
33488
+ recordClaim(dirs, STORE_WARNING_MARKER, sessionId);
33489
+ return true;
33490
+ }
33491
+ function markerDirs(dataDir2) {
33492
+ return [dataDir2, dirname5(dataDir2)];
33493
+ }
33494
+ function alreadyClaimed(dirs, marker, sessionId) {
33495
+ return dirs.some((dir) => {
33496
+ try {
33497
+ return readFileSync15(join23(dir, marker), "utf8") === sessionId;
33498
+ } catch {
33499
+ return false;
33500
+ }
33501
+ });
33502
+ }
33503
+ function recordClaim(dirs, marker, sessionId) {
33504
+ for (const dir of dirs) {
33505
+ try {
33506
+ mkdirSync5(dir, { recursive: true, mode: DATA_DIR_MODE });
33507
+ writeFileSync8(join23(dir, marker), sessionId, { mode: DATA_FILE_MODE });
33508
+ return;
33509
+ } catch {
33510
+ }
31981
33511
  }
33512
+ }
33513
+ function storeRedirectedMessage(paths, platform2 = process.platform) {
33514
+ const where = paths.map(({ path, target, holds, missing, mode }) => {
33515
+ if (missing) return `${path} -> ${target} (which does not exist; ${holds} cannot land there)`;
33516
+ const loose = mode !== void 0 && (mode & 63) !== 0 ? ", NOT owner-only" : "";
33517
+ const inherited = mode === void 0 ? "" : ` (${formatMode(mode)}${loose})`;
33518
+ return `${path} -> ${target}${inherited}, holding ${holds}`;
33519
+ }).join("; ");
33520
+ const subject = paths.length === 1 ? "a store path is a symlink" : `${String(paths.length)} store paths are symlinks`;
33521
+ const anyResolves = paths.some(({ missing }) => !missing);
33522
+ const lead = anyResolves ? `${subject}, so AKA is writing into the target instead: ${where}. ` : `${subject} resolving nowhere, so AKA cannot write there: ${where}. `;
33523
+ const kept = anyResolves && platform2 !== "win32" ? "Permissions are never changed through a symlink, so the store keeps whatever the target already had. " : "";
33524
+ return `[aka] ${lead}${kept}If you did not create that link, treat it as untrusted and run \`aka init\` for the full report.
33525
+ `;
33526
+ }
33527
+ function formatMode(mode) {
33528
+ return `0${mode.toString(8).padStart(3, "0")}`;
33529
+ }
33530
+ function warnIfStoreRedirected(config2, sessionId, write = (message) => void process.stderr.write(message)) {
31982
33531
  try {
31983
- mkdirSync5(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
31984
- writeFileSync8(path, sessionId, { mode: DATA_FILE_MODE });
33532
+ const paths = symlinkedStorePaths(dirname5(config2.dataDir));
33533
+ if (paths.length === 0) return;
33534
+ if (!sessionId) {
33535
+ write(storeRedirectedMessage(paths));
33536
+ return;
33537
+ }
33538
+ const dirs = markerDirs(config2.dataDir);
33539
+ if (alreadyClaimed(dirs, STORE_REDIRECT_MARKER, sessionId)) return;
33540
+ write(storeRedirectedMessage(paths));
33541
+ recordClaim(dirs, STORE_REDIRECT_MARKER, sessionId);
31985
33542
  } catch {
31986
33543
  }
31987
- return true;
31988
33544
  }
31989
33545
 
31990
33546
  // src/hooks/pre-tool-use.ts
@@ -31999,6 +33555,7 @@ async function main() {
31999
33555
  if (fields.length === 0) return;
32000
33556
  const config2 = loadConfig();
32001
33557
  const sessionId = getString(input, "session_id");
33558
+ warnIfStoreRedirected(config2, sessionId);
32002
33559
  const consented = isVaultConsentValid(config2.settings.vaultConsent);
32003
33560
  const vaultGlue = consented ? createVaultGlue() : null;
32004
33561
  const pointerFields = [];