@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,16 +492,33 @@ var require_ignore = __commonJS({
492
492
  });
493
493
 
494
494
  // src/hooks/session-start.ts
495
- import { readFileSync as readFileSync12 } from "fs";
495
+ import { readFileSync as readFileSync17 } from "fs";
496
+
497
+ // ../../packages/plugin-runtime/src/attached/failure.ts
498
+ function statusOf(err) {
499
+ if (typeof err !== "object" || err === null || !("status" in err)) return null;
500
+ const { status } = err;
501
+ if (typeof status !== "number" || !Number.isInteger(status)) return null;
502
+ return status >= 100 && status <= 599 ? status : null;
503
+ }
504
+ function classifyFailure(err) {
505
+ switch (statusOf(err)) {
506
+ case 401:
507
+ return "unauthorized";
508
+ case 403:
509
+ return "forbidden";
510
+ default:
511
+ return "unreachable";
512
+ }
513
+ }
496
514
 
497
- // ../../packages/plugin-sdk/src/config.ts
498
- import { existsSync as existsSync5 } from "fs";
499
- import { join as join8 } from "path";
515
+ // ../../packages/plugin-runtime/src/attached/forward-drops.ts
516
+ import { readFileSync as readFileSync7 } from "fs";
517
+ import { join as join10 } from "path";
500
518
 
501
- // ../../packages/persistence/src/database.ts
502
- import { randomUUID as randomUUID10 } from "crypto";
503
- import { join as join2, sep } from "path";
504
- import { DatabaseSync } from "node:sqlite";
519
+ // ../../packages/persistence/src/control-plane-credential.ts
520
+ import { chmodSync as chmodSync2, lstatSync as lstatSync2, readFileSync, rmSync as rmSync2, statSync } from "fs";
521
+ import { join } from "path";
505
522
 
506
523
  // ../../packages/schema/src/drizzle/sqlite-ddl.ts
507
524
  var SQLITE_MIGRATIONS = [
@@ -16322,6 +16339,125 @@ var ConfigScanRecord = external_exports.object({
16322
16339
  findings: external_exports.array(ConfigPostureFindingInput).optional()
16323
16340
  });
16324
16341
 
16342
+ // ../../packages/schema/src/zod/control-plane.ts
16343
+ var ATTACHED_CREDENTIAL_FILENAME = "control-plane-credential.json";
16344
+ var ATTACHED_CREDENTIAL_SPEC_VERSION = 1;
16345
+ var AttachedCredential = external_exports.object({
16346
+ specVersion: external_exports.literal(ATTACHED_CREDENTIAL_SPEC_VERSION),
16347
+ // The control-plane endpoint this credential was minted against.
16348
+ endpoint: external_exports.string().min(1),
16349
+ // The bearer credential itself. Never logged, never rendered — status
16350
+ // surfaces show `keyPrefix` and nothing else.
16351
+ apiKey: external_exports.string().min(1),
16352
+ // First few characters of the key, safe to display so a user can match the
16353
+ // credential against their organization's key list.
16354
+ keyPrefix: external_exports.string().min(1).max(16).optional(),
16355
+ mintedAt: external_exports.iso.datetime().optional()
16356
+ });
16357
+ var MAX_DATE_MS = 253402300799999;
16358
+ var MAX_INT4 = 2147483647;
16359
+ var StorePosturePack = external_exports.object({
16360
+ packId: external_exports.string().min(1),
16361
+ // 'namespace/packId'
16362
+ version: external_exports.string().min(1),
16363
+ enabled: external_exports.boolean(),
16364
+ // Stringified pass-through of the local store's `installed_packs.updated_at`
16365
+ // — the column format is store-version-dependent (epoch millis vs ISO), so
16366
+ // the wire shape assumes neither.
16367
+ updatedAt: external_exports.string().nullable()
16368
+ }).meta({ id: "StorePosturePack" });
16369
+ var StorePosturePolicyCounts = external_exports.object({
16370
+ total: external_exports.number().int().min(0),
16371
+ disabled: external_exports.number().int().min(0),
16372
+ // Exhaustive per-action map; the builder pre-fills every action with 0.
16373
+ //
16374
+ // Spelled out member-by-member rather than `z.record(ActionTaken, …)`. Zod
16375
+ // enforces exhaustiveness either way, but z.record emits `propertyNames` +
16376
+ // `additionalProperties` into a generated schema document, and a type
16377
+ // generator renders THAT with every key optional — a sender built against
16378
+ // the generated type would typecheck and still be rejected at runtime. An
16379
+ // explicit object emits `properties` + `required`, so generated types
16380
+ // demand all five.
16381
+ //
16382
+ // `satisfies Record<ActionTaken, …>` keeps the link to the enum: adding an
16383
+ // ActionTaken member is a COMPILE error here instead of silent drift.
16384
+ // `.strict()` is load-bearing — it rejects an unknown action key, which a
16385
+ // bare object would silently STRIP, accepting a miscounted map as valid.
16386
+ byAction: external_exports.object({
16387
+ warn: external_exports.number().int().min(0),
16388
+ redact: external_exports.number().int().min(0),
16389
+ block: external_exports.number().int().min(0),
16390
+ allow: external_exports.number().int().min(0),
16391
+ log: external_exports.number().int().min(0)
16392
+ }).strict()
16393
+ }).meta({ id: "StorePosturePolicyCounts" });
16394
+ var StorePosturePlugin = external_exports.object({
16395
+ /** Package name of the reporting plugin. */
16396
+ package: external_exports.string().min(1).max(200),
16397
+ version: external_exports.string().min(1).max(64),
16398
+ /** Version of the bundled core, when the build records one separately. */
16399
+ ossVersion: external_exports.string().max(64).nullable(),
16400
+ /**
16401
+ * `version` of the policy bundle this machine last fetched. Bounded at 200
16402
+ * rather than the 64 a bare sha256 hex digest needs today, so a later
16403
+ * format with an algorithm prefix does not start rejecting the channel.
16404
+ */
16405
+ policyBundleVersion: external_exports.string().max(200).nullable(),
16406
+ /** Epoch millis, on the CLIENT clock, of that fetch. */
16407
+ policyFetchedAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable()
16408
+ }).meta({ id: "StorePosturePlugin" });
16409
+ var StorePostureSnapshot = external_exports.object({
16410
+ deviceId: external_exports.guid(),
16411
+ hostname: external_exports.string().min(1).max(253),
16412
+ // Epoch millis on the CLIENT clock. Bounded by what a receiving store
16413
+ // accepts (see MAX_DATE_MS), not by what a JavaScript Date can hold.
16414
+ capturedAt: external_exports.number().int().min(0).max(MAX_DATE_MS),
16415
+ // False is a measurement, not an error state: "no local store exists on
16416
+ // this machine".
16417
+ storePresent: external_exports.boolean(),
16418
+ schemaVersion: external_exports.number().int().min(0).max(MAX_INT4).nullable(),
16419
+ // PRAGMA user_version
16420
+ findingsTotal: external_exports.number().int().min(0).max(MAX_INT4),
16421
+ // Epoch millis, bounded like `capturedAt` — see MAX_DATE_MS on what that
16422
+ // bound does and does not do. Worth stating for these two specifically:
16423
+ // they are read from the local store's own ROWS rather than from this
16424
+ // machine's clock, so a damaged or hand-edited store is enough to produce
16425
+ // an out-of-range value with no clock skew involved.
16426
+ findingsFirstAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable(),
16427
+ findingsLastAt: external_exports.number().int().min(0).max(MAX_DATE_MS).nullable(),
16428
+ packs: external_exports.array(StorePosturePack).max(500),
16429
+ policyCounts: StorePosturePolicyCounts,
16430
+ // OPTIONAL, not nullable: a reporter that predates this member keeps
16431
+ // getting its 200 without a payload change.
16432
+ plugin: StorePosturePlugin.optional()
16433
+ }).meta({ id: "StorePostureSnapshot" });
16434
+ var CAPTURE_VERSION_PREFIX = "capture/";
16435
+ var RecordAuditEventRequest = AuditEventInput.extend({
16436
+ inspections: external_exports.array(ToolCallInspection).default([])
16437
+ }).refine((v) => v.inspections.every((i) => !i.ruleVersion.startsWith(CAPTURE_VERSION_PREFIX)), {
16438
+ message: `inspections[].ruleVersion must not start with \`${CAPTURE_VERSION_PREFIX}\` \u2014 that namespace is reserved for capture definitions the control plane mints itself`,
16439
+ path: ["inspections"]
16440
+ }).meta({ id: "RecordAuditEventRequest" });
16441
+ var IngestAck = external_exports.object({
16442
+ accepted: external_exports.number().int().nonnegative(),
16443
+ duplicates: external_exports.number().int().nonnegative()
16444
+ });
16445
+ var PRINTABLE = /^[^\p{Cc}\p{Cf}]*$/u;
16446
+ var printable = (max) => external_exports.string().max(max).regex(PRINTABLE, "must not contain control characters");
16447
+ var PluginWhoami = external_exports.object({
16448
+ tenantName: printable(200),
16449
+ userEmail: printable(320),
16450
+ role: printable(64),
16451
+ keyKind: printable(64),
16452
+ serverTime: printable(64)
16453
+ });
16454
+ var ControlPlaneErrorBody = external_exports.object({
16455
+ error: external_exports.object({
16456
+ code: external_exports.string().optional(),
16457
+ message: external_exports.string().optional()
16458
+ }).optional()
16459
+ });
16460
+
16325
16461
  // ../../packages/schema/src/zod/registry.ts
16326
16462
  var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
16327
16463
  var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
@@ -16624,15 +16760,15 @@ function summaryToDetectionListItem(s) {
16624
16760
  }
16625
16761
  function rowToDetectionDetail(row, findingsLast30d, update) {
16626
16762
  const rules = row.rules.flatMap((r) => {
16627
- const parsed = Matcher.safeParse(r.matcher);
16628
- if (!parsed.success) return [];
16763
+ const parsed2 = Matcher.safeParse(r.matcher);
16764
+ if (!parsed2.success) return [];
16629
16765
  return [
16630
16766
  {
16631
16767
  id: r.id,
16632
16768
  name: r.name,
16633
16769
  category: r.category,
16634
16770
  severity: r.severity,
16635
- matcher: parsed.data
16771
+ matcher: parsed2.data
16636
16772
  }
16637
16773
  ];
16638
16774
  });
@@ -17278,8 +17414,8 @@ function toApiAction(dbVal) {
17278
17414
  }
17279
17415
  function toApiCategory(dbVal) {
17280
17416
  if (dbVal === "code_context") return "source_code";
17281
- const parsed = FindingCategory.safeParse(dbVal);
17282
- return parsed.success ? parsed.data : "custom";
17417
+ const parsed2 = FindingCategory.safeParse(dbVal);
17418
+ return parsed2.success ? parsed2.data : "custom";
17283
17419
  }
17284
17420
  function toApiProvider(sourceTool) {
17285
17421
  return TOOL_TO_HARNESS[sourceTool] ?? HARNESS.Api;
@@ -17912,6 +18048,9 @@ var WorkspaceSettings = external_exports.object({
17912
18048
  function defaultWorkspaceSettings() {
17913
18049
  return WorkspaceSettings.parse({});
17914
18050
  }
18051
+ function isAttached(settings) {
18052
+ return settings.runMode === "attached" && settings.controlPlane !== void 0;
18053
+ }
17915
18054
  function toInventoryRow(input, id, now) {
17916
18055
  return {
17917
18056
  id,
@@ -18179,8 +18318,8 @@ function builtinPolicyIsReversible(id) {
18179
18318
  return BUILTIN_POLICY_SPECS[id].reversible;
18180
18319
  }
18181
18320
  function policyIdIsReversible(policyId) {
18182
- const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18183
- const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
18321
+ const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18322
+ const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
18184
18323
  return builtinPolicyIsReversible(id);
18185
18324
  }
18186
18325
  var DEFAULT_ACTIONS = Object.fromEntries(
@@ -18191,8 +18330,8 @@ var BUILTIN_POLICIES = Object.fromEntries(
18191
18330
  );
18192
18331
  var DEFAULT_PACK_POLICY_ID = "monitor";
18193
18332
  function policyIdToAction(policyId) {
18194
- const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18195
- const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
18333
+ const parsed2 = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
18334
+ const id = parsed2.success ? parsed2.data : DEFAULT_PACK_POLICY_ID;
18196
18335
  return BUILTIN_POLICIES[id].action;
18197
18336
  }
18198
18337
  var UsedByItem = external_exports.object({
@@ -18635,53 +18774,6 @@ function reviewSeverityRank(reasons) {
18635
18774
  return Math.min(...reasons.map((r) => REVIEW_SEVERITY_RANK[r]));
18636
18775
  }
18637
18776
 
18638
- // ../../packages/persistence/src/ids.ts
18639
- import { createHash } from "crypto";
18640
- function sha256Hex(input) {
18641
- return createHash("sha256").update(input).digest("hex");
18642
- }
18643
- function inventoryId(objectType, identityKey) {
18644
- return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
18645
- }
18646
- function sourceProjectId(url2) {
18647
- return sha256Hex(canonicalIdentity(["source_project", url2]));
18648
- }
18649
- function classifiedDataId(cls) {
18650
- return sha256Hex(canonicalIdentity(["classified_data", cls]));
18651
- }
18652
- function inspectionDefinitionId(ruleId, version2) {
18653
- return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
18654
- }
18655
- function llmCallId(sessionId, messageId) {
18656
- return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
18657
- }
18658
- function toolCallId(sessionId, toolUseId) {
18659
- return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
18660
- }
18661
- function inspectionFindingId(auditEventId, ruleId, spanStart, spanEnd) {
18662
- return sha256Hex(
18663
- canonicalIdentity([
18664
- "inspection_finding",
18665
- auditEventId,
18666
- ruleId,
18667
- String(spanStart),
18668
- String(spanEnd)
18669
- ])
18670
- );
18671
- }
18672
- var NO_SESSION = "no_session";
18673
- var NO_PATH = "no_path";
18674
- function captureId(sessionId, contentHash, filePath = null) {
18675
- return sha256Hex(
18676
- canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
18677
- );
18678
- }
18679
-
18680
- // ../../packages/persistence/src/internal/snapshot.ts
18681
- import { randomUUID } from "crypto";
18682
- import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
18683
- import { basename, dirname, join } from "path";
18684
-
18685
18777
  // ../../packages/persistence/src/paths.ts
18686
18778
  import {
18687
18779
  chmodSync,
@@ -18729,8 +18821,181 @@ function tightenFile(file2) {
18729
18821
  function tightenPerms(file2) {
18730
18822
  for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
18731
18823
  }
18824
+ function writeExclusiveOwnerOnlySync(file2, data) {
18825
+ writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
18826
+ }
18827
+ function writeOwnerOnlyFileSync(file2, data) {
18828
+ const tmp = `${file2}.${String(process.pid)}.tmp`;
18829
+ try {
18830
+ rmSync(tmp, { force: true });
18831
+ } catch {
18832
+ }
18833
+ try {
18834
+ writeExclusiveOwnerOnlySync(tmp, data);
18835
+ renameSync(tmp, file2);
18836
+ } finally {
18837
+ try {
18838
+ rmSync(tmp, { force: true });
18839
+ } catch {
18840
+ }
18841
+ }
18842
+ tightenFile(file2);
18843
+ }
18844
+ function createOwnerOnlyFileSync(file2, data) {
18845
+ const tmp = `${file2}.${String(process.pid)}.${String(threadId)}.new`;
18846
+ try {
18847
+ rmSync(tmp, { force: true });
18848
+ } catch {
18849
+ }
18850
+ let created;
18851
+ try {
18852
+ writeExclusiveOwnerOnlySync(tmp, data);
18853
+ created = publishByLink(tmp, file2, data);
18854
+ } finally {
18855
+ try {
18856
+ rmSync(tmp, { force: true });
18857
+ } catch {
18858
+ }
18859
+ }
18860
+ if (created) tightenFile(file2);
18861
+ return created;
18862
+ }
18863
+ var LINK_UNSUPPORTED = /* @__PURE__ */ new Set(["EPERM", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EINVAL"]);
18864
+ function publishByLink(tmp, file2, data) {
18865
+ try {
18866
+ linkSync(tmp, file2);
18867
+ return true;
18868
+ } catch (err) {
18869
+ const code = err.code;
18870
+ if (code === "EEXIST") return false;
18871
+ if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
18872
+ }
18873
+ try {
18874
+ writeExclusiveOwnerOnlySync(file2, data);
18875
+ return true;
18876
+ } catch (err) {
18877
+ if (err.code === "EEXIST") return false;
18878
+ throw err;
18879
+ }
18880
+ }
18881
+
18882
+ // ../../packages/persistence/src/control-plane-credential.ts
18883
+ function controlPlaneCredentialPath(settingsDir2) {
18884
+ return join(settingsDir2, ATTACHED_CREDENTIAL_FILENAME);
18885
+ }
18886
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
18887
+ function isSafeEndpoint(endpoint) {
18888
+ let parsed2;
18889
+ try {
18890
+ parsed2 = new URL(endpoint);
18891
+ } catch {
18892
+ return false;
18893
+ }
18894
+ if (parsed2.protocol === "https:") return true;
18895
+ return parsed2.protocol === "http:" && LOOPBACK_HOSTS.has(parsed2.hostname);
18896
+ }
18897
+ function repairOrRefuseMode(file2) {
18898
+ const link = lstatSync2(file2, { throwIfNoEntry: false });
18899
+ if (link === void 0) return "absent";
18900
+ if (link.isSymbolicLink()) return "untrusted";
18901
+ const stat = statSync(file2, { throwIfNoEntry: false });
18902
+ if (stat === void 0) return "absent";
18903
+ const uid = process.getuid?.();
18904
+ if (uid !== void 0 && stat.uid !== uid) return "untrusted";
18905
+ if (process.platform !== "win32" && (stat.mode & 511) !== DATA_FILE_MODE) {
18906
+ try {
18907
+ chmodSync2(file2, DATA_FILE_MODE);
18908
+ } catch {
18909
+ return "untrusted";
18910
+ }
18911
+ }
18912
+ return "ok";
18913
+ }
18914
+ function readControlPlaneCredentialState(settingsDir2, connection) {
18915
+ const file2 = controlPlaneCredentialPath(settingsDir2);
18916
+ let raw;
18917
+ const gate = repairOrRefuseMode(file2);
18918
+ if (gate === "absent") return { usable: false, reason: "absent" };
18919
+ if (gate === "untrusted") return { usable: false, reason: "untrusted-file" };
18920
+ try {
18921
+ raw = readFileSync(file2, "utf8");
18922
+ } catch (err) {
18923
+ const code = err.code;
18924
+ return { usable: false, reason: code === "ENOENT" ? "absent" : "unreadable" };
18925
+ }
18926
+ let parsed2;
18927
+ try {
18928
+ parsed2 = JSON.parse(raw);
18929
+ } catch {
18930
+ return { usable: false, reason: "malformed" };
18931
+ }
18932
+ const result = AttachedCredential.safeParse(parsed2);
18933
+ if (!result.success) return { usable: false, reason: "malformed" };
18934
+ if (!isSafeEndpoint(result.data.endpoint)) {
18935
+ return { usable: false, reason: "unsafe-endpoint" };
18936
+ }
18937
+ if (connection !== void 0 && connection.endpoint !== result.data.endpoint) {
18938
+ return {
18939
+ usable: false,
18940
+ reason: "endpoint-mismatch",
18941
+ credentialEndpoint: result.data.endpoint,
18942
+ settingsEndpoint: connection.endpoint
18943
+ };
18944
+ }
18945
+ return { usable: true, credential: result.data };
18946
+ }
18947
+
18948
+ // ../../packages/persistence/src/database.ts
18949
+ import { randomUUID as randomUUID10 } from "crypto";
18950
+ import { join as join3, sep } from "path";
18951
+ import { DatabaseSync } from "node:sqlite";
18952
+
18953
+ // ../../packages/persistence/src/ids.ts
18954
+ import { createHash } from "crypto";
18955
+ function sha256Hex(input) {
18956
+ return createHash("sha256").update(input).digest("hex");
18957
+ }
18958
+ function inventoryId(objectType, identityKey) {
18959
+ return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
18960
+ }
18961
+ function sourceProjectId(url2) {
18962
+ return sha256Hex(canonicalIdentity(["source_project", url2]));
18963
+ }
18964
+ function classifiedDataId(cls) {
18965
+ return sha256Hex(canonicalIdentity(["classified_data", cls]));
18966
+ }
18967
+ function inspectionDefinitionId(ruleId, version2) {
18968
+ return sha256Hex(canonicalIdentity(["inspection_definition", ruleId, version2]));
18969
+ }
18970
+ function llmCallId(sessionId, messageId) {
18971
+ return sha256Hex(canonicalIdentity(["audit_event_llm_call", sessionId, messageId]));
18972
+ }
18973
+ function toolCallId(sessionId, toolUseId) {
18974
+ return sha256Hex(canonicalIdentity(["audit_event_tool_call", sessionId, toolUseId]));
18975
+ }
18976
+ function inspectionFindingId(auditEventId, ruleId, spanStart, spanEnd) {
18977
+ return sha256Hex(
18978
+ canonicalIdentity([
18979
+ "inspection_finding",
18980
+ auditEventId,
18981
+ ruleId,
18982
+ String(spanStart),
18983
+ String(spanEnd)
18984
+ ])
18985
+ );
18986
+ }
18987
+ var NO_SESSION = "no_session";
18988
+ var NO_PATH = "no_path";
18989
+ function captureId(sessionId, contentHash, filePath = null) {
18990
+ return sha256Hex(
18991
+ canonicalIdentity(["capture", sessionId ?? NO_SESSION, contentHash, filePath ?? NO_PATH])
18992
+ );
18993
+ }
18732
18994
 
18733
18995
  // ../../packages/persistence/src/internal/snapshot.ts
18996
+ import { randomUUID } from "crypto";
18997
+ import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync2 } from "fs";
18998
+ import { basename, dirname, join as join2 } from "path";
18734
18999
  function backupPath(file2, tag) {
18735
19000
  return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
18736
19001
  }
@@ -18740,15 +19005,15 @@ var STAGED_NAME_SUFFIX = `.bak${SNAPSHOT_STAGING_SUFFIX}`;
18740
19005
  var SNAPSHOT_STAGING_COPY = "copy";
18741
19006
  function createSnapshotStaging(backup) {
18742
19007
  const stage = `${backup}${SNAPSHOT_STAGING_SUFFIX}`;
18743
- rmSync2(stage, { recursive: true, force: true });
19008
+ rmSync3(stage, { recursive: true, force: true });
18744
19009
  mkdirOwnerOnlySync(stage);
18745
19010
  tightenDir(stage);
18746
- return { stage, copy: join(stage, SNAPSHOT_STAGING_COPY) };
19011
+ return { stage, copy: join2(stage, SNAPSHOT_STAGING_COPY) };
18747
19012
  }
18748
19013
  function idleMs(entry) {
18749
- for (const candidate of [join(entry, SNAPSHOT_STAGING_COPY), entry]) {
19014
+ for (const candidate of [join2(entry, SNAPSHOT_STAGING_COPY), entry]) {
18750
19015
  try {
18751
- return Date.now() - statSync(candidate).mtimeMs;
19016
+ return Date.now() - statSync2(candidate).mtimeMs;
18752
19017
  } catch {
18753
19018
  }
18754
19019
  }
@@ -18765,11 +19030,11 @@ function reapStalePartials(file2) {
18765
19030
  }
18766
19031
  for (const name of entries) {
18767
19032
  if (!name.startsWith(prefix) || !name.endsWith(STAGED_NAME_SUFFIX)) continue;
18768
- const staging = join(dir, name);
19033
+ const staging = join2(dir, name);
18769
19034
  try {
18770
19035
  const idle = idleMs(staging);
18771
19036
  if (idle !== null && idle > STALE_PARTIAL_MS) {
18772
- rmSync2(staging, { recursive: true, force: true });
19037
+ rmSync3(staging, { recursive: true, force: true });
18773
19038
  }
18774
19039
  } catch {
18775
19040
  }
@@ -18783,13 +19048,13 @@ function snapshotStore(db, backup) {
18783
19048
  renameSync2(copy, backup);
18784
19049
  } catch (error51) {
18785
19050
  try {
18786
- rmSync2(stage, { recursive: true, force: true });
19051
+ rmSync3(stage, { recursive: true, force: true });
18787
19052
  } catch {
18788
19053
  }
18789
19054
  throw error51;
18790
19055
  }
18791
19056
  try {
18792
- rmSync2(stage, { recursive: true, force: true });
19057
+ rmSync3(stage, { recursive: true, force: true });
18793
19058
  } catch {
18794
19059
  }
18795
19060
  }
@@ -18804,7 +19069,7 @@ function moveStoreAside(file2, backup) {
18804
19069
  renameSync2(sidecar, moved);
18805
19070
  undo.push([moved, sidecar]);
18806
19071
  } catch {
18807
- rmSync2(sidecar, { force: true });
19072
+ rmSync3(sidecar, { force: true });
18808
19073
  }
18809
19074
  }
18810
19075
  } catch (error51) {
@@ -18820,14 +19085,14 @@ function moveStoreAside(file2, backup) {
18820
19085
  }
18821
19086
  function discardStore(file2, backup) {
18822
19087
  try {
18823
- rmSync2(file2, { force: true });
19088
+ rmSync3(file2, { force: true });
18824
19089
  for (const sidecar of dbSidecars(file2)) {
18825
- rmSync2(sidecar, { force: true });
19090
+ rmSync3(sidecar, { force: true });
18826
19091
  }
18827
19092
  } catch (error51) {
18828
19093
  if (existsSync(file2)) {
18829
19094
  try {
18830
- rmSync2(backup, { force: true });
19095
+ rmSync3(backup, { force: true });
18831
19096
  } catch {
18832
19097
  }
18833
19098
  }
@@ -19059,10 +19324,31 @@ function applyMigrations(db, file2) {
19059
19324
  if (drained) applyLegacyDropMigration(db, file2);
19060
19325
  }
19061
19326
  }
19327
+ function readLegacyTables(db) {
19328
+ let holdsRows = false;
19329
+ const marks = [];
19330
+ for (const table of ["events", "findings"]) {
19331
+ try {
19332
+ const row = db.prepare(`SELECT count(*) AS n, ifnull(max(rowid), -1) AS hi FROM ${table}`).get();
19333
+ if (row === void 0) {
19334
+ holdsRows = true;
19335
+ marks.push(`${table}:unreadable`);
19336
+ continue;
19337
+ }
19338
+ if (row.n > 0) holdsRows = true;
19339
+ marks.push(`${table}:${String(row.n)}:${String(row.hi)}`);
19340
+ } catch {
19341
+ holdsRows = true;
19342
+ marks.push(`${table}:unreadable`);
19343
+ }
19344
+ }
19345
+ return { holdsRows, mark: marks.join("|") };
19346
+ }
19062
19347
  function applyLegacyDropMigration(db, file2) {
19063
19348
  const migration = SQLITE_MIGRATIONS.find((m) => m.tag === LEGACY_DROP_MIGRATION_TAG);
19064
19349
  if (!migration) return;
19065
- if (file2) {
19350
+ const before = file2 === void 0 ? void 0 : readLegacyTables(db);
19351
+ if (file2 !== void 0 && before?.holdsRows === true) {
19066
19352
  try {
19067
19353
  backupBeforeLegacyDrop(db, file2);
19068
19354
  } catch (error51) {
@@ -19076,6 +19362,12 @@ function applyLegacyDropMigration(db, file2) {
19076
19362
  () => {
19077
19363
  const alreadyDropped = db.prepare("SELECT 1 FROM migration_ledger WHERE tag = ?").get(migration.tag);
19078
19364
  if (alreadyDropped) return;
19365
+ if (before !== void 0 && readLegacyTables(db).mark !== before.mark) {
19366
+ akaWarn(
19367
+ "legacy events/findings rows changed after the pre-drop snapshot decision; deferring the drop so the next open can copy them first."
19368
+ );
19369
+ return;
19370
+ }
19079
19371
  for (const statement of splitStatements(migration.sql)) {
19080
19372
  db.exec(statement);
19081
19373
  }
@@ -19430,8 +19722,8 @@ function safeJson(s, fallback) {
19430
19722
  function parseJsonObject(s) {
19431
19723
  if (s == null) return void 0;
19432
19724
  try {
19433
- const parsed = JSON.parse(s);
19434
- if (typeof parsed === "object" && parsed !== null) return parsed;
19725
+ const parsed2 = JSON.parse(s);
19726
+ if (typeof parsed2 === "object" && parsed2 !== null) return parsed2;
19435
19727
  } catch {
19436
19728
  }
19437
19729
  return void 0;
@@ -19442,16 +19734,16 @@ function encodeKeysetCursor(payload) {
19442
19734
  return Buffer.from(JSON.stringify(payload)).toString("base64url");
19443
19735
  }
19444
19736
  function decodeKeysetCursor(cursor) {
19445
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
19446
- if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
19737
+ const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
19738
+ if (parsed2 !== void 0 && "startedAtMs" in parsed2 && "id" in parsed2 && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
19447
19739
  // resumes from is epoch millis, and a payload carrying ±Infinity or a
19448
19740
  // fraction binds cleanly rather than failing — returning an EMPTY page with
19449
19741
  // a null cursor, which a caller reads as "end of list". That is the one
19450
19742
  // outcome a cursor that does not decode must never produce, since the
19451
19743
  // documented behaviour above is to restart from the top. (`1e999` is valid
19452
19744
  // JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
19453
- Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
19454
- return parsed;
19745
+ Number.isInteger(parsed2.startedAtMs) && typeof parsed2.id === "string") {
19746
+ return parsed2;
19455
19747
  }
19456
19748
  return null;
19457
19749
  }
@@ -19516,18 +19808,18 @@ var DB_EVENT_TYPE_TO_KIND = {
19516
19808
  };
19517
19809
  function safeParseStringArray(raw) {
19518
19810
  if (!raw) return [];
19519
- const parsed = safeJson(raw, null);
19520
- return Array.isArray(parsed) ? parsed : [];
19811
+ const parsed2 = safeJson(raw, null);
19812
+ return Array.isArray(parsed2) ? parsed2 : [];
19521
19813
  }
19522
19814
  var DEFAULT_HARNESS = HARNESS.ClaudeCode;
19523
19815
  function toHarness(raw) {
19524
- const parsed = Harness.safeParse(raw);
19525
- return parsed.success ? parsed.data : DEFAULT_HARNESS;
19816
+ const parsed2 = Harness.safeParse(raw);
19817
+ return parsed2.success ? parsed2.data : DEFAULT_HARNESS;
19526
19818
  }
19527
19819
  function resolveLifecycle(row, lastActivityMs, nowMs) {
19528
19820
  if (row.status) {
19529
- const parsed = SessionStatus.safeParse(row.status);
19530
- if (parsed.success) return { status: parsed.data, endedAtMs: row.ended_at };
19821
+ const parsed2 = SessionStatus.safeParse(row.status);
19822
+ if (parsed2.success) return { status: parsed2.data, endedAtMs: row.ended_at };
19531
19823
  }
19532
19824
  if (row.ended_at !== null) return { status: "completed", endedAtMs: row.ended_at };
19533
19825
  if (lastActivityMs >= nowMs - LIVE_ACTIVITY_WINDOW_MS) {
@@ -20486,9 +20778,9 @@ var SqliteDetectionsRepository = class {
20486
20778
  const ruleIds = /* @__PURE__ */ new Set();
20487
20779
  for (const r of rows) {
20488
20780
  if (intToBool(r.enabled)) active += 1;
20489
- const parsed = parseRules(r.rulesJson);
20490
- rules += parsed.length;
20491
- for (const rule of parsed) {
20781
+ const parsed2 = parseRules(r.rulesJson);
20782
+ rules += parsed2.length;
20783
+ for (const rule of parsed2) {
20492
20784
  if (typeof rule.id === "string") ruleIds.add(rule.id);
20493
20785
  }
20494
20786
  }
@@ -21022,12 +21314,12 @@ function encodeGroupCursor(group) {
21022
21314
  return Buffer.from(JSON.stringify(payload)).toString("base64url");
21023
21315
  }
21024
21316
  function decodeGroupCursor(cursor) {
21025
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
21026
- if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
21317
+ const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
21318
+ if (parsed2 !== void 0 && typeof parsed2.sev === "string" && typeof parsed2.t === "string" && typeof parsed2.id === "string") {
21027
21319
  return {
21028
- severity: parsed.sev,
21029
- latestDetectedAt: parsed.t,
21030
- id: parsed.id
21320
+ severity: parsed2.sev,
21321
+ latestDetectedAt: parsed2.t,
21322
+ id: parsed2.id
21031
21323
  };
21032
21324
  }
21033
21325
  return null;
@@ -22161,16 +22453,16 @@ var SqliteInstalledPacksRepository = class {
22161
22453
  continue;
22162
22454
  }
22163
22455
  for (const entry of raw) {
22164
- const parsed = Rule.safeParse(entry);
22165
- if (parsed.success) {
22166
- out.rules.push(parsed.data);
22167
- out.ruleActions.set(parsed.data.id, action);
22168
- out.ruleVersions.set(parsed.data.id, row.version);
22169
- if (reversible) out.reversibleRules.add(parsed.data.id);
22170
- else out.reversibleRules.delete(parsed.data.id);
22456
+ const parsed2 = Rule.safeParse(entry);
22457
+ if (parsed2.success) {
22458
+ out.rules.push(parsed2.data);
22459
+ out.ruleActions.set(parsed2.data.id, action);
22460
+ out.ruleVersions.set(parsed2.data.id, row.version);
22461
+ if (reversible) out.reversibleRules.add(parsed2.data.id);
22462
+ else out.reversibleRules.delete(parsed2.data.id);
22171
22463
  } else {
22172
22464
  out.invalidRules += 1;
22173
- reject(pack, printableRuleId(entry), firstIssueReason(parsed.error));
22465
+ reject(pack, printableRuleId(entry), firstIssueReason(parsed2.error));
22174
22466
  }
22175
22467
  }
22176
22468
  }
@@ -23560,15 +23852,15 @@ function encodeReuseCursor(payload) {
23560
23852
  return Buffer.from(JSON.stringify(payload)).toString("base64url");
23561
23853
  }
23562
23854
  function decodeReuseCursor(cursor) {
23563
- const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
23564
- if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
23855
+ const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
23856
+ if (parsed2 !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
23565
23857
  // ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
23566
23858
  // null cursor, which the caller reads as "end of list" — the one outcome a
23567
23859
  // malformed cursor must never produce, since restarting from the top is the
23568
23860
  // documented behaviour and the only recoverable one. (`1e999` is valid JSON
23569
23861
  // and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
23570
- Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
23571
- return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
23862
+ Number.isInteger(parsed2.occurrences) && typeof parsed2.pointerId === "string") {
23863
+ return { occurrences: parsed2.occurrences, pointerId: parsed2.pointerId };
23572
23864
  }
23573
23865
  return null;
23574
23866
  }
@@ -25297,7 +25589,7 @@ function openAndInitialize(file2) {
25297
25589
  }
25298
25590
  function openLocalDatabase(dir) {
25299
25591
  ensureDataDirSync(dir);
25300
- const file2 = join2(dir, DB_FILENAME);
25592
+ const file2 = join3(dir, DB_FILENAME);
25301
25593
  reapStalePartials(file2);
25302
25594
  const {
25303
25595
  db,
@@ -25533,9 +25825,9 @@ import {
25533
25825
  closeSync,
25534
25826
  existsSync as existsSync2,
25535
25827
  openSync,
25536
- readFileSync,
25537
- rmSync as rmSync3,
25538
- statSync as statSync2,
25828
+ readFileSync as readFileSync2,
25829
+ rmSync as rmSync4,
25830
+ statSync as statSync3,
25539
25831
  writeFileSync as writeFileSync2
25540
25832
  } from "fs";
25541
25833
  import { hostname as hostname3 } from "os";
@@ -25546,20 +25838,20 @@ import { createHash as createHash3 } from "crypto";
25546
25838
 
25547
25839
  // ../../packages/persistence/src/fingerprint.ts
25548
25840
  import { createHmac, randomBytes } from "crypto";
25549
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
25550
- import { join as join3 } from "path";
25841
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
25842
+ import { join as join4 } from "path";
25551
25843
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
25552
25844
  var EXCEPTION_KEY_FILENAME = "exception.key";
25553
25845
  var KEY_MATERIAL_BYTES = 32;
25554
25846
  function keyFilePath(dataDir2) {
25555
- return join3(dataDir2, EXCEPTION_KEY_FILENAME);
25847
+ return join4(dataDir2, EXCEPTION_KEY_FILENAME);
25556
25848
  }
25557
25849
  function parseKeyFile(raw) {
25558
- const parsed = JSON.parse(raw);
25559
- if (typeof parsed !== "object" || parsed === null) {
25850
+ const parsed2 = JSON.parse(raw);
25851
+ if (typeof parsed2 !== "object" || parsed2 === null) {
25560
25852
  throw new Error("exception key file is corrupt: not a JSON object");
25561
25853
  }
25562
- const { version: version2, material } = parsed;
25854
+ const { version: version2, material } = parsed2;
25563
25855
  if (typeof version2 !== "number" || !Number.isInteger(version2) || version2 < 1) {
25564
25856
  throw new Error("exception key file is corrupt: bad version");
25565
25857
  }
@@ -25575,7 +25867,7 @@ function parseKeyFile(raw) {
25575
25867
  function readFingerprintKey(dataDir2) {
25576
25868
  let raw;
25577
25869
  try {
25578
- raw = readFileSync2(keyFilePath(dataDir2), "utf8");
25870
+ raw = readFileSync3(keyFilePath(dataDir2), "utf8");
25579
25871
  } catch (err) {
25580
25872
  if (err.code === "ENOENT") return null;
25581
25873
  throw err instanceof Error ? err : new Error(String(err));
@@ -25587,18 +25879,25 @@ function readFingerprintKey(dataDir2) {
25587
25879
  import { renameSync as renameSync3 } from "fs";
25588
25880
  import { mkdir } from "fs/promises";
25589
25881
  import { homedir } from "os";
25590
- import { join as join4 } from "path";
25882
+ import { join as join5 } from "path";
25591
25883
  function defaultDataDir() {
25592
- return join4(homedir(), ".aka");
25884
+ return join5(homedir(), ".aka");
25593
25885
  }
25594
25886
  function settingsDir(base = defaultDataDir()) {
25595
- return join4(base, "settings");
25887
+ return join5(base, "settings");
25596
25888
  }
25597
25889
  function dataDir(base = defaultDataDir()) {
25598
- return join4(base, "data");
25890
+ return join5(base, "data");
25599
25891
  }
25600
25892
  function dbPath(base = defaultDataDir()) {
25601
- return join4(dataDir(base), "aka.db");
25893
+ return join5(dataDir(base), "aka.db");
25894
+ }
25895
+ function keysDir(base = defaultDataDir()) {
25896
+ return join5(base, "keys");
25897
+ }
25898
+ async function ensureDataDir(dir = defaultDataDir()) {
25899
+ await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
25900
+ tightenDir(dir);
25602
25901
  }
25603
25902
  function ensureLayoutDirSync(dir = defaultDataDir()) {
25604
25903
  ensureDataDirSync(dir);
@@ -25611,8 +25910,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
25611
25910
  for (const { name, dest } of moves) {
25612
25911
  try {
25613
25912
  ensureDataDirSync(dest);
25614
- const moved = join4(dest, name);
25615
- renameSync3(join4(base, name), moved);
25913
+ const moved = join5(dest, name);
25914
+ renameSync3(join5(base, name), moved);
25616
25915
  tightenFile(moved);
25617
25916
  } catch {
25618
25917
  }
@@ -25620,7 +25919,7 @@ function migrateLegacyLayout(base = defaultDataDir()) {
25620
25919
  }
25621
25920
 
25622
25921
  // ../../packages/persistence/src/managed-settings.ts
25623
- import { readFileSync as readFileSync3 } from "fs";
25922
+ import { readFileSync as readFileSync4 } from "fs";
25624
25923
  import { posix, win32 } from "path";
25625
25924
  function managedSettingsPaths(platform2 = process.platform) {
25626
25925
  if (platform2 === "darwin") {
@@ -25638,14 +25937,14 @@ function readManagedSettings(paths = managedSettingsPaths()) {
25638
25937
  for (const path of paths) {
25639
25938
  let text;
25640
25939
  try {
25641
- text = readFileSync3(path, "utf8");
25940
+ text = readFileSync4(path, "utf8");
25642
25941
  } catch {
25643
25942
  continue;
25644
25943
  }
25645
25944
  const record2 = parseJsonObject(text);
25646
25945
  if (!record2) continue;
25647
- const parsed = ManagedSettings.safeParse(record2);
25648
- if (parsed.success) return parsed.data;
25946
+ const parsed2 = ManagedSettings.safeParse(record2);
25947
+ if (parsed2.success) return parsed2.data;
25649
25948
  }
25650
25949
  return null;
25651
25950
  }
@@ -25685,14 +25984,14 @@ function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ n
25685
25984
  }
25686
25985
 
25687
25986
  // ../../packages/persistence/src/settings.ts
25688
- import { readFileSync as readFileSync4 } from "fs";
25689
- import { join as join5 } from "path";
25987
+ import { readFileSync as readFileSync5 } from "fs";
25988
+ import { join as join6 } from "path";
25690
25989
  var SETTINGS_FILENAME = "settings.json";
25691
25990
  function readWorkspaceSettings(base = defaultDataDir()) {
25692
25991
  return overlayManagedSettings(readUserSettings(base), readManagedSettings());
25693
25992
  }
25694
25993
  function readUserSettings(base) {
25695
- const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
25994
+ const record2 = readJson(join6(settingsDir(base), SETTINGS_FILENAME));
25696
25995
  if (!record2) return defaultWorkspaceSettings();
25697
25996
  try {
25698
25997
  return WorkspaceSettings.parse(record2);
@@ -25703,13 +26002,64 @@ function readUserSettings(base) {
25703
26002
  function readJson(file2) {
25704
26003
  let text;
25705
26004
  try {
25706
- text = readFileSync4(file2, "utf8");
26005
+ text = readFileSync5(file2, "utf8");
25707
26006
  } catch {
25708
26007
  return null;
25709
26008
  }
25710
26009
  return parseJsonObject(text) ?? null;
25711
26010
  }
25712
26011
 
26012
+ // ../../packages/persistence/src/store-symlinks.ts
26013
+ import { existsSync as existsSync4, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
26014
+ import { dirname as dirname2, join as join7, resolve } from "path";
26015
+ var STORE_DB = "the store database (including the prompt corpus)";
26016
+ var STORE_SETTINGS = "your settings file";
26017
+ function storeContents(home) {
26018
+ return /* @__PURE__ */ new Map([
26019
+ [home, "the store (including the prompt corpus in aka.db)"],
26020
+ [settingsDir(home), STORE_SETTINGS],
26021
+ [dataDir(home), STORE_DB],
26022
+ [keysDir(home), "the vault key"],
26023
+ [join7(settingsDir(home), "settings.json"), STORE_SETTINGS],
26024
+ [dbPath(home), STORE_DB]
26025
+ ]);
26026
+ }
26027
+ function symlinkedStorePaths(home, platform2 = process.platform) {
26028
+ return [...storeContents(home)].flatMap(([path, holds]) => {
26029
+ try {
26030
+ if (!lstatSync3(path).isSymbolicLink()) return [];
26031
+ return [
26032
+ {
26033
+ path,
26034
+ target: linkTarget(path),
26035
+ holds,
26036
+ // existsSync follows the link, so a target that is gone reads as
26037
+ // absent here while lstat above still sees the link itself.
26038
+ missing: !existsSync4(path),
26039
+ mode: targetMode(path, platform2)
26040
+ }
26041
+ ];
26042
+ } catch {
26043
+ return [];
26044
+ }
26045
+ });
26046
+ }
26047
+ function linkTarget(path) {
26048
+ try {
26049
+ return realpathSync(path);
26050
+ } catch {
26051
+ return resolve(dirname2(path), readlinkSync(path));
26052
+ }
26053
+ }
26054
+ function targetMode(path, platform2) {
26055
+ if (platform2 === "win32") return void 0;
26056
+ try {
26057
+ return statSync4(path).mode & 511;
26058
+ } catch {
26059
+ return void 0;
26060
+ }
26061
+ }
26062
+
25713
26063
  // ../../packages/persistence/src/vault/crypto.ts
25714
26064
  import {
25715
26065
  createCipheriv,
@@ -25722,26 +26072,73 @@ import {
25722
26072
  // ../../packages/persistence/src/vault/key-provider.ts
25723
26073
  import { execFileSync } from "child_process";
25724
26074
  import { randomBytes as randomBytes2 } from "crypto";
25725
- import { chmodSync as chmodSync2, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
25726
- import { join as join6 } from "path";
26075
+ import { chmodSync as chmodSync3, readFileSync as readFileSync6, renameSync as renameSync4, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
26076
+ import { join as join8 } from "path";
25727
26077
 
25728
26078
  // ../../packages/persistence/src/vault/vault.ts
25729
26079
  import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
25730
26080
 
25731
26081
  // ../../packages/persistence/src/warn-era-cap.ts
25732
- import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
25733
- import { join as join7 } from "path";
26082
+ import { existsSync as existsSync5, writeFileSync as writeFileSync4 } from "fs";
26083
+ import { join as join9 } from "path";
25734
26084
  var MARKER = "warn-era-capped";
25735
26085
  function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
25736
26086
  if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
25737
- const marker = join7(dataDir2, MARKER);
25738
- if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
26087
+ const marker = join9(dataDir2, MARKER);
26088
+ if (existsSync5(marker)) return { capped: 0, skipped: "already-run" };
25739
26089
  const capped = db.policies.capCategoryActions();
25740
26090
  writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
25741
26091
  `, { mode: DATA_FILE_MODE });
25742
26092
  return { capped };
25743
26093
  }
25744
26094
 
26095
+ // ../../packages/plugin-runtime/src/attached/forward-drops.ts
26096
+ var FORWARD_DROPS_FILENAME = "attached-forward-drops.json";
26097
+ function forwardDropsPath(dataDir2) {
26098
+ return join10(dataDir2, FORWARD_DROPS_FILENAME);
26099
+ }
26100
+ function recordForwardDrops(dataDir2, count, nowMs) {
26101
+ if (count <= 0) return;
26102
+ try {
26103
+ ensureDataDirSync(dataDir2);
26104
+ const previous = readForwardDrops(dataDir2);
26105
+ const next = {
26106
+ droppedForwards: (previous?.droppedForwards ?? 0) + count,
26107
+ lastDropAtMs: nowMs
26108
+ };
26109
+ writeOwnerOnlyFileSync(forwardDropsPath(dataDir2), `${JSON.stringify(next)}
26110
+ `);
26111
+ } catch {
26112
+ }
26113
+ }
26114
+ function readForwardDrops(dataDir2) {
26115
+ try {
26116
+ const parsed2 = JSON.parse(readFileSync7(forwardDropsPath(dataDir2), "utf8"));
26117
+ if (typeof parsed2 !== "object" || parsed2 === null) return null;
26118
+ const record2 = parsed2;
26119
+ if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
26120
+ return null;
26121
+ }
26122
+ if (record2.droppedForwards <= 0) return null;
26123
+ if (typeof record2.lastDropAtMs !== "number" || !Number.isFinite(record2.lastDropAtMs)) {
26124
+ return null;
26125
+ }
26126
+ return { droppedForwards: record2.droppedForwards, lastDropAtMs: record2.lastDropAtMs };
26127
+ } catch {
26128
+ return null;
26129
+ }
26130
+ }
26131
+
26132
+ // ../../packages/plugin-runtime/src/attached/forward-policy.ts
26133
+ import { randomUUID as randomUUID15 } from "crypto";
26134
+ import { readFileSync as readFileSync12 } from "fs";
26135
+ import { readFile, rename, writeFile } from "fs/promises";
26136
+ import { join as join18 } from "path";
26137
+
26138
+ // ../../packages/plugin-sdk/src/config.ts
26139
+ import { existsSync as existsSync6 } from "fs";
26140
+ import { join as join11 } from "path";
26141
+
25745
26142
  // ../../packages/plugin-sdk/src/provider-env.ts
25746
26143
  var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
25747
26144
  var booleanish = external_exports.string().optional().transform((v) => {
@@ -25776,8 +26173,8 @@ function hostOf(url2) {
25776
26173
  }
25777
26174
  }
25778
26175
  function resolveProvider() {
25779
- const parsed = ProviderEnvSchema.safeParse(process.env);
25780
- const env = parsed.success ? parsed.data : ProviderEnvSchema.parse({});
26176
+ const parsed2 = ProviderEnvSchema.safeParse(process.env);
26177
+ const env = parsed2.success ? parsed2.data : ProviderEnvSchema.parse({});
25781
26178
  if (env.CLAUDE_CODE_USE_BEDROCK === true) return { provider: "bedrock" };
25782
26179
  if (env.CLAUDE_CODE_USE_VERTEX === true) return { provider: "vertex" };
25783
26180
  const baseUrl = env.ANTHROPIC_BASE_URL;
@@ -25794,8 +26191,8 @@ function resolveProvider() {
25794
26191
  function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
25795
26192
  try {
25796
26193
  ensureLayoutDirSync(base);
25797
- const settingsFile = join8(settingsDir(base), "settings.json");
25798
- if (existsSync5(settingsFile)) tightenFile(settingsFile);
26194
+ const settingsFile = join11(settingsDir(base), "settings.json");
26195
+ if (existsSync6(settingsFile)) tightenFile(settingsFile);
25799
26196
  } catch {
25800
26197
  }
25801
26198
  migrateLegacyLayout(base);
@@ -25818,9 +26215,9 @@ function resolveProviderSafe(resolveProviderFn) {
25818
26215
  }
25819
26216
 
25820
26217
  // ../../packages/plugin-sdk/src/config-inventory.ts
25821
- import { readdirSync as readdirSync2, readFileSync as readFileSync7, realpathSync, statSync as statSync5 } from "fs";
26218
+ import { readdirSync as readdirSync2, readFileSync as readFileSync9, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
25822
26219
  import { homedir as homedir2 } from "os";
25823
- import { basename as basename4, join as join10 } from "path";
26220
+ import { basename as basename4, join as join13 } from "path";
25824
26221
 
25825
26222
  // ../../packages/detections/src/egress/registry.ts
25826
26223
  var EXTRACTOR_VERSION = "1";
@@ -27592,10 +27989,10 @@ var localhost_ref_default = {
27592
27989
  severity: "low",
27593
27990
  matcher: {
27594
27991
  type: "regex",
27595
- pattern: "\\b(?:localhost|127\\.0\\.0\\.1|0\\.0\\.0\\.0|::1)\\b",
27992
+ 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_])",
27596
27993
  flags: "g"
27597
27994
  },
27598
- examples: ["localhost", "127.0.0.1"]
27995
+ examples: ["localhost", "127.0.0.1", "0.0.0.0", "::1"]
27599
27996
  };
27600
27997
 
27601
27998
  // ../../rules/core-code-context/stack-trace.json
@@ -28949,8 +29346,8 @@ function maskText(text) {
28949
29346
  }
28950
29347
 
28951
29348
  // ../../packages/plugin-sdk/src/repo.ts
28952
- import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
28953
- import { basename as basename3, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
29349
+ import { existsSync as existsSync7, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
29350
+ import { basename as basename3, dirname as dirname3, isAbsolute, join as join12, sep as sep2 } from "path";
28954
29351
  function resolveRepoIdentity(cwd) {
28955
29352
  try {
28956
29353
  const root = findGitRoot(cwd);
@@ -29000,15 +29397,15 @@ function resolveGitBranch(cwd) {
29000
29397
  try {
29001
29398
  const root = findGitRoot(cwd);
29002
29399
  if (!root) return void 0;
29003
- const dotGit = join9(root, ".git");
29400
+ const dotGit = join12(root, ".git");
29004
29401
  let gitdir;
29005
29402
  try {
29006
- gitdir = statSync4(dotGit).isDirectory() ? dotGit : resolveWorktreeGitdir(root, dotGit);
29403
+ gitdir = statSync6(dotGit).isDirectory() ? dotGit : resolveWorktreeGitdir(root, dotGit);
29007
29404
  } catch {
29008
29405
  return void 0;
29009
29406
  }
29010
29407
  if (gitdir === void 0) return void 0;
29011
- const head = safeRead(join9(gitdir, "HEAD"));
29408
+ const head = safeRead(join12(gitdir, "HEAD"));
29012
29409
  if (!head) return void 0;
29013
29410
  return /^ref:\s*refs\/heads\/(.+?)\s*$/m.exec(head)?.[1];
29014
29411
  } catch {
@@ -29018,41 +29415,41 @@ function resolveGitBranch(cwd) {
29018
29415
  function resolveWorktreeGitdir(root, dotGitFile) {
29019
29416
  const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGitFile) ?? "")?.[1];
29020
29417
  if (!target) return void 0;
29021
- return isAbsolute(target) ? target : join9(root, target);
29418
+ return isAbsolute(target) ? target : join12(root, target);
29022
29419
  }
29023
29420
  function findGitRoot(start) {
29024
29421
  let dir = start;
29025
29422
  for (; ; ) {
29026
- if (existsSync6(join9(dir, ".git"))) return dir;
29027
- const parent = dirname2(dir);
29423
+ if (existsSync7(join12(dir, ".git"))) return dir;
29424
+ const parent = dirname3(dir);
29028
29425
  if (parent === dir) return void 0;
29029
29426
  dir = parent;
29030
29427
  }
29031
29428
  }
29032
29429
  function resolveGitContext(root) {
29033
- const dotGit = join9(root, ".git");
29430
+ const dotGit = join12(root, ".git");
29034
29431
  try {
29035
- if (statSync4(dotGit).isDirectory()) {
29036
- return { configPath: join9(dotGit, "config"), headRoot: root };
29432
+ if (statSync6(dotGit).isDirectory()) {
29433
+ return { configPath: join12(dotGit, "config"), headRoot: root };
29037
29434
  }
29038
29435
  } catch {
29039
29436
  return void 0;
29040
29437
  }
29041
29438
  const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
29042
29439
  if (!target) return void 0;
29043
- const gitdir = isAbsolute(target) ? target : join9(root, target);
29044
- if (existsSync6(join9(gitdir, "config"))) {
29045
- return { configPath: join9(gitdir, "config"), headRoot: root };
29440
+ const gitdir = isAbsolute(target) ? target : join12(root, target);
29441
+ if (existsSync7(join12(gitdir, "config"))) {
29442
+ return { configPath: join12(gitdir, "config"), headRoot: root };
29046
29443
  }
29047
- const commonRaw = safeRead(join9(gitdir, "commondir"))?.trim();
29444
+ const commonRaw = safeRead(join12(gitdir, "commondir"))?.trim();
29048
29445
  if (!commonRaw) return void 0;
29049
- const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join9(gitdir, commonRaw);
29050
- const headRoot = basename3(commonGitDir) === ".git" ? dirname2(commonGitDir) : root;
29051
- return { configPath: join9(commonGitDir, "config"), headRoot };
29446
+ const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join12(gitdir, commonRaw);
29447
+ const headRoot = basename3(commonGitDir) === ".git" ? dirname3(commonGitDir) : root;
29448
+ return { configPath: join12(commonGitDir, "config"), headRoot };
29052
29449
  }
29053
29450
  function safeRead(path) {
29054
29451
  try {
29055
- return readFileSync6(path, "utf8");
29452
+ return readFileSync8(path, "utf8");
29056
29453
  } catch {
29057
29454
  return void 0;
29058
29455
  }
@@ -29114,31 +29511,31 @@ function resolveConfigInventory(input) {
29114
29511
  };
29115
29512
  try {
29116
29513
  const home = input.homeDir ?? homedir2();
29117
- const claudeDir = join10(home, ".claude");
29514
+ const claudeDir = join13(home, ".claude");
29118
29515
  const repo = resolveRepoIdentity(input.cwd);
29119
29516
  const repoIdentity = repo?.url ?? input.cwd;
29120
29517
  const projectSource = `project:${repoIdentity}`;
29121
- collectSettingsHooks(scan2, join10(claudeDir, "settings.json"), "user");
29122
- collectSettingsHooks(scan2, join10(input.cwd, ".claude", "settings.json"), "project");
29123
- collectSettingsHooks(scan2, join10(input.cwd, ".claude", "settings.local.json"), "local");
29518
+ collectSettingsHooks(scan2, join13(claudeDir, "settings.json"), "user");
29519
+ collectSettingsHooks(scan2, join13(input.cwd, ".claude", "settings.json"), "project");
29520
+ collectSettingsHooks(scan2, join13(input.cwd, ".claude", "settings.local.json"), "local");
29124
29521
  const projectOrigin = { scope: "project", project: repoIdentity };
29125
- collectMcpFile(scan2, join10(input.cwd, ".mcp.json"), projectOrigin, { recordErrors: true });
29126
- collectUserClaudeJson(scan2, join10(home, ".claude.json"), input.cwd, repoIdentity);
29127
- collectMcpFile(scan2, join10(claudeDir, "settings.json"), { scope: "user" });
29128
- collectMcpFile(scan2, join10(input.cwd, ".claude", "settings.json"), projectOrigin);
29129
- collectMcpFile(scan2, join10(input.cwd, ".claude", "settings.local.json"), {
29522
+ collectMcpFile(scan2, join13(input.cwd, ".mcp.json"), projectOrigin, { recordErrors: true });
29523
+ collectUserClaudeJson(scan2, join13(home, ".claude.json"), input.cwd, repoIdentity);
29524
+ collectMcpFile(scan2, join13(claudeDir, "settings.json"), { scope: "user" });
29525
+ collectMcpFile(scan2, join13(input.cwd, ".claude", "settings.json"), projectOrigin);
29526
+ collectMcpFile(scan2, join13(input.cwd, ".claude", "settings.local.json"), {
29130
29527
  scope: "local",
29131
29528
  project: repoIdentity
29132
29529
  });
29133
29530
  collectConfigFiles(scan2, claudeDir, input.cwd);
29134
- collectSkillsDir(scan2, join10(claudeDir, "skills"), { source: "local", scope: "user" });
29135
- collectSkillsDir(scan2, join10(input.cwd, ".claude", "skills"), {
29531
+ collectSkillsDir(scan2, join13(claudeDir, "skills"), { source: "local", scope: "user" });
29532
+ collectSkillsDir(scan2, join13(input.cwd, ".claude", "skills"), {
29136
29533
  source: projectSource,
29137
29534
  scope: "project"
29138
29535
  });
29139
29536
  collectInstalledPlugins(scan2, claudeDir);
29140
29537
  collectMarketplaceSkills(scan2, claudeDir);
29141
- collectSkillsDir(scan2, join10(input.cwd, "skills"), { source: projectSource, scope: "project" });
29538
+ collectSkillsDir(scan2, join13(input.cwd, "skills"), { source: projectSource, scope: "project" });
29142
29539
  scan2.skills = dedupeSkills(scan2.skills);
29143
29540
  scan2.mcpServers = dedupeMcpServers(scan2.mcpServers);
29144
29541
  } catch (err) {
@@ -29173,9 +29570,9 @@ function collectSettingsHooks(scan2, path, scope) {
29173
29570
  const raw = readOptional(path);
29174
29571
  if (raw === void 0) return;
29175
29572
  try {
29176
- const parsed = JSON.parse(raw);
29177
- if (typeof parsed !== "object" || parsed === null) return;
29178
- collectHooksObject(scan2, parsed.hooks, path, scope);
29573
+ const parsed2 = JSON.parse(raw);
29574
+ if (typeof parsed2 !== "object" || parsed2 === null) return;
29575
+ collectHooksObject(scan2, parsed2.hooks, path, scope);
29179
29576
  } catch (err) {
29180
29577
  scan2.errors.push({ source: path, reason: parseErrorReason(err) });
29181
29578
  }
@@ -29221,9 +29618,9 @@ function collectMcpFile(scan2, path, origin, opts) {
29221
29618
  const raw = readOptional(path);
29222
29619
  if (raw === void 0) return;
29223
29620
  try {
29224
- const parsed = JSON.parse(raw);
29225
- if (typeof parsed !== "object" || parsed === null) return;
29226
- collectMcpObject(scan2, parsed.mcpServers, path, origin);
29621
+ const parsed2 = JSON.parse(raw);
29622
+ if (typeof parsed2 !== "object" || parsed2 === null) return;
29623
+ collectMcpObject(scan2, parsed2.mcpServers, path, origin);
29227
29624
  } catch (err) {
29228
29625
  if (opts?.recordErrors ?? false) {
29229
29626
  scan2.errors.push({ source: path, reason: parseErrorReason(err) });
@@ -29234,9 +29631,9 @@ function collectUserClaudeJson(scan2, path, cwd, repoIdentity) {
29234
29631
  const raw = readOptional(path);
29235
29632
  if (raw === void 0) return;
29236
29633
  try {
29237
- const parsed = JSON.parse(raw);
29238
- if (typeof parsed !== "object" || parsed === null) return;
29239
- const rec = parsed;
29634
+ const parsed2 = JSON.parse(raw);
29635
+ if (typeof parsed2 !== "object" || parsed2 === null) return;
29636
+ const rec = parsed2;
29240
29637
  collectMcpObject(scan2, rec.mcpServers, path, { scope: "user" });
29241
29638
  const projects = rec.projects;
29242
29639
  if (typeof projects === "object" && projects !== null) {
@@ -29257,7 +29654,7 @@ function projectEntryFor(projects, cwd) {
29257
29654
  const trimmed = cwd.replace(/\/+$/, "");
29258
29655
  if (trimmed.length > 0) candidates.add(trimmed);
29259
29656
  try {
29260
- candidates.add(realpathSync(cwd));
29657
+ candidates.add(realpathSync2(cwd));
29261
29658
  } catch {
29262
29659
  }
29263
29660
  for (const key of candidates) {
@@ -29267,15 +29664,15 @@ function projectEntryFor(projects, cwd) {
29267
29664
  return void 0;
29268
29665
  }
29269
29666
  function collectPluginManifestMcp(scan2, installPath, origin) {
29270
- const manifestPath = join10(installPath, ".claude-plugin", "plugin.json");
29667
+ const manifestPath = join13(installPath, ".claude-plugin", "plugin.json");
29271
29668
  const raw = readOptional(manifestPath);
29272
29669
  if (raw === void 0) return;
29273
29670
  try {
29274
- const parsed = JSON.parse(raw);
29275
- if (typeof parsed !== "object" || parsed === null) return;
29276
- const declared = parsed.mcpServers;
29671
+ const parsed2 = JSON.parse(raw);
29672
+ if (typeof parsed2 !== "object" || parsed2 === null) return;
29673
+ const declared = parsed2.mcpServers;
29277
29674
  if (typeof declared === "string" && declared.length > 0) {
29278
- collectMcpFile(scan2, join10(installPath, declared), origin, { recordErrors: true });
29675
+ collectMcpFile(scan2, join13(installPath, declared), origin, { recordErrors: true });
29279
29676
  } else {
29280
29677
  collectMcpObject(scan2, declared, manifestPath, origin);
29281
29678
  }
@@ -29292,18 +29689,18 @@ var SETTINGS_KEY_LABELS = [
29292
29689
  ["statusLine", "status line"]
29293
29690
  ];
29294
29691
  function collectConfigFiles(scan2, claudeDir, cwd) {
29295
- settingsConfigFile(scan2, join10(claudeDir, "settings.json"), "user", "User settings");
29296
- settingsConfigFile(scan2, join10(cwd, ".claude", "settings.json"), "project", "Project settings");
29297
- settingsConfigFile(scan2, join10(cwd, ".claude", "settings.local.json"), "local", "Local overrides");
29298
- memoryConfigFile(scan2, join10(claudeDir, "CLAUDE.md"), "user", "User memory");
29299
- memoryConfigFile(scan2, join10(cwd, "CLAUDE.md"), "project", "Project memory");
29300
- mcpJsonConfigFile(scan2, join10(cwd, ".mcp.json"));
29301
- dirConfigFile(scan2, join10(cwd, ".claude", "commands"), "Slash commands", "command");
29302
- dirConfigFile(scan2, join10(cwd, ".claude", "agents"), "Subagents", "subagent");
29692
+ settingsConfigFile(scan2, join13(claudeDir, "settings.json"), "user", "User settings");
29693
+ settingsConfigFile(scan2, join13(cwd, ".claude", "settings.json"), "project", "Project settings");
29694
+ settingsConfigFile(scan2, join13(cwd, ".claude", "settings.local.json"), "local", "Local overrides");
29695
+ memoryConfigFile(scan2, join13(claudeDir, "CLAUDE.md"), "user", "User memory");
29696
+ memoryConfigFile(scan2, join13(cwd, "CLAUDE.md"), "project", "Project memory");
29697
+ mcpJsonConfigFile(scan2, join13(cwd, ".mcp.json"));
29698
+ dirConfigFile(scan2, join13(cwd, ".claude", "commands"), "Slash commands", "command");
29699
+ dirConfigFile(scan2, join13(cwd, ".claude", "agents"), "Subagents", "subagent");
29303
29700
  }
29304
29701
  function configFileEntry(path, scope, kind) {
29305
29702
  try {
29306
- const stat = statSync5(path);
29703
+ const stat = statSync7(path);
29307
29704
  return { name: basename4(path), path, scope, kind, updatedAt: stat.mtime.toISOString() };
29308
29705
  } catch {
29309
29706
  return void 0;
@@ -29315,9 +29712,9 @@ function settingsConfigFile(scan2, path, scope, kind) {
29315
29712
  const raw = readOptional(path);
29316
29713
  if (raw !== void 0) {
29317
29714
  try {
29318
- const parsed = JSON.parse(raw);
29319
- if (typeof parsed === "object" && parsed !== null) {
29320
- const labels = SETTINGS_KEY_LABELS.filter(([key]) => key in parsed).map(
29715
+ const parsed2 = JSON.parse(raw);
29716
+ if (typeof parsed2 === "object" && parsed2 !== null) {
29717
+ const labels = SETTINGS_KEY_LABELS.filter(([key]) => key in parsed2).map(
29321
29718
  ([, label]) => label
29322
29719
  );
29323
29720
  if (labels.length > 0) entry.detail = labels.join(", ");
@@ -29344,8 +29741,8 @@ function mcpJsonConfigFile(scan2, path) {
29344
29741
  const raw = readOptional(path);
29345
29742
  if (raw !== void 0) {
29346
29743
  try {
29347
- const parsed = JSON.parse(raw);
29348
- const servers = parsed?.mcpServers;
29744
+ const parsed2 = JSON.parse(raw);
29745
+ const servers = parsed2?.mcpServers;
29349
29746
  if (typeof servers === "object" && servers !== null) {
29350
29747
  const count = Object.values(servers).filter(
29351
29748
  (v) => typeof v === "object" && v !== null && (str2(v.command) !== void 0 || str2(v.url) !== void 0)
@@ -29374,7 +29771,7 @@ function countMarkdownFiles(dir, depth) {
29374
29771
  let count = 0;
29375
29772
  for (const dirent of readdirSync2(dir, { withFileTypes: true })) {
29376
29773
  if (dirent.name.startsWith(".")) continue;
29377
- if (dirent.isDirectory()) count += countMarkdownFiles(join10(dir, dirent.name), depth + 1);
29774
+ if (dirent.isDirectory()) count += countMarkdownFiles(join13(dir, dirent.name), depth + 1);
29378
29775
  else if (dirent.name.endsWith(".md")) count += 1;
29379
29776
  }
29380
29777
  return count;
@@ -29387,7 +29784,7 @@ function collectSkillsDir(scan2, dir, origin) {
29387
29784
  return;
29388
29785
  }
29389
29786
  for (const name of names) {
29390
- const skillFile = join10(dir, name, "SKILL.md");
29787
+ const skillFile = join13(dir, name, "SKILL.md");
29391
29788
  try {
29392
29789
  const raw = readOptional(skillFile);
29393
29790
  if (raw === void 0) continue;
@@ -29396,8 +29793,8 @@ function collectSkillsDir(scan2, dir, origin) {
29396
29793
  name: front.name ?? name,
29397
29794
  source: origin.source,
29398
29795
  scope: origin.scope,
29399
- location: join10(dir, name),
29400
- updatedAt: statSync5(skillFile).mtime.toISOString()
29796
+ location: join13(dir, name),
29797
+ updatedAt: statSync7(skillFile).mtime.toISOString()
29401
29798
  };
29402
29799
  const version2 = front.version ?? origin.defaultVersion;
29403
29800
  if (version2 !== void 0) entry.version = version2;
@@ -29427,13 +29824,13 @@ function parseFrontmatter(raw) {
29427
29824
  return out;
29428
29825
  }
29429
29826
  function collectInstalledPlugins(scan2, claudeDir) {
29430
- const manifestPath = join10(claudeDir, "plugins", "installed_plugins.json");
29827
+ const manifestPath = join13(claudeDir, "plugins", "installed_plugins.json");
29431
29828
  const raw = readOptional(manifestPath);
29432
29829
  if (raw === void 0) return;
29433
29830
  let plugins;
29434
29831
  try {
29435
- const parsed = JSON.parse(raw);
29436
- const p = parsed?.plugins;
29832
+ const parsed2 = JSON.parse(raw);
29833
+ const p = parsed2?.plugins;
29437
29834
  if (typeof p !== "object" || p === null) return;
29438
29835
  plugins = p;
29439
29836
  } catch (err) {
@@ -29452,15 +29849,15 @@ function collectInstalledPlugins(scan2, claudeDir) {
29452
29849
  if (typeof installPath !== "string" || seen.has(installPath)) continue;
29453
29850
  seen.add(installPath);
29454
29851
  const version2 = install.version;
29455
- const hooksPath = join10(installPath, "hooks", "hooks.json");
29852
+ const hooksPath = join13(installPath, "hooks", "hooks.json");
29456
29853
  const hooksRaw = readOptional(hooksPath);
29457
29854
  if (hooksRaw !== void 0) {
29458
29855
  try {
29459
- const parsed = JSON.parse(hooksRaw);
29460
- if (typeof parsed === "object" && parsed !== null) {
29856
+ const parsed2 = JSON.parse(hooksRaw);
29857
+ if (typeof parsed2 === "object" && parsed2 !== null) {
29461
29858
  collectHooksObject(
29462
29859
  scan2,
29463
- parsed.hooks,
29860
+ parsed2.hooks,
29464
29861
  hooksPath,
29465
29862
  "plugin",
29466
29863
  pluginName
@@ -29472,22 +29869,22 @@ function collectInstalledPlugins(scan2, claudeDir) {
29472
29869
  }
29473
29870
  const origin = { source: marketplace, scope: "plugin", pluginName };
29474
29871
  if (typeof version2 === "string") origin.defaultVersion = version2;
29475
- collectSkillsDir(scan2, join10(installPath, "skills"), origin);
29872
+ collectSkillsDir(scan2, join13(installPath, "skills"), origin);
29476
29873
  const mcpOrigin = { scope: "plugin", pluginName, marketplace };
29477
- collectMcpFile(scan2, join10(installPath, ".mcp.json"), mcpOrigin, { recordErrors: true });
29874
+ collectMcpFile(scan2, join13(installPath, ".mcp.json"), mcpOrigin, { recordErrors: true });
29478
29875
  collectPluginManifestMcp(scan2, installPath, mcpOrigin);
29479
29876
  }
29480
29877
  }
29481
29878
  }
29482
29879
  function collectMarketplaceSkills(scan2, claudeDir) {
29483
- for (const mp of readMarketplaces(join10(claudeDir, "plugins", "known_marketplaces.json"))) {
29880
+ for (const mp of readMarketplaces(join13(claudeDir, "plugins", "known_marketplaces.json"))) {
29484
29881
  if (isClaudeOfficialMarketplace(mp.name, mp.repo)) continue;
29485
- collectSkillsDir(scan2, join10(mp.installLocation, "skills"), {
29882
+ collectSkillsDir(scan2, join13(mp.installLocation, "skills"), {
29486
29883
  source: mp.name,
29487
29884
  scope: "plugin"
29488
29885
  });
29489
- collectPluginSkillDirs(scan2, join10(mp.installLocation, "plugins"), mp.name);
29490
- collectPluginSkillDirs(scan2, join10(mp.installLocation, "external_plugins"), mp.name);
29886
+ collectPluginSkillDirs(scan2, join13(mp.installLocation, "plugins"), mp.name);
29887
+ collectPluginSkillDirs(scan2, join13(mp.installLocation, "external_plugins"), mp.name);
29491
29888
  }
29492
29889
  }
29493
29890
  function collectPluginSkillDirs(scan2, pluginsDir, marketplace) {
@@ -29498,7 +29895,7 @@ function collectPluginSkillDirs(scan2, pluginsDir, marketplace) {
29498
29895
  return;
29499
29896
  }
29500
29897
  for (const plugin of plugins) {
29501
- collectSkillsDir(scan2, join10(pluginsDir, plugin, "skills"), {
29898
+ collectSkillsDir(scan2, join13(pluginsDir, plugin, "skills"), {
29502
29899
  source: marketplace,
29503
29900
  scope: "plugin",
29504
29901
  pluginName: plugin
@@ -29509,10 +29906,10 @@ function readMarketplaces(manifestPath) {
29509
29906
  const raw = readOptional(manifestPath);
29510
29907
  if (raw === void 0) return [];
29511
29908
  try {
29512
- const parsed = JSON.parse(raw);
29513
- if (typeof parsed !== "object" || parsed === null) return [];
29909
+ const parsed2 = JSON.parse(raw);
29910
+ if (typeof parsed2 !== "object" || parsed2 === null) return [];
29514
29911
  const out = [];
29515
- for (const [name, entry] of Object.entries(parsed)) {
29912
+ for (const [name, entry] of Object.entries(parsed2)) {
29516
29913
  const rec = entry;
29517
29914
  const loc = rec?.installLocation;
29518
29915
  if (typeof loc !== "string" || loc.length === 0) continue;
@@ -29552,7 +29949,7 @@ function dedupeMcpServers(servers) {
29552
29949
  }
29553
29950
  function readOptional(path) {
29554
29951
  try {
29555
- return readFileSync7(path, "utf8");
29952
+ return readFileSync9(path, "utf8");
29556
29953
  } catch {
29557
29954
  return void 0;
29558
29955
  }
@@ -29580,17 +29977,17 @@ function offersMaintenance(gateway, member) {
29580
29977
  import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
29581
29978
 
29582
29979
  // ../../packages/plugin-sdk/src/isolated-scan.ts
29583
- import { existsSync as existsSync7 } from "fs";
29980
+ import { existsSync as existsSync8 } from "fs";
29584
29981
  import { fileURLToPath } from "url";
29585
29982
  import { Worker } from "worker_threads";
29586
29983
 
29587
29984
  // ../../packages/plugin-sdk/src/ignore-layers.ts
29588
29985
  var import_ignore = __toESM(require_ignore(), 1);
29589
- import { readFileSync as readFileSync8 } from "fs";
29590
- import { join as join11 } from "path";
29986
+ import { readFileSync as readFileSync10 } from "fs";
29987
+ import { join as join14 } from "path";
29591
29988
  function readIgnoreLayer(dir, filename, anchorLen) {
29592
29989
  try {
29593
- return { matcher: (0, import_ignore.default)().add(readFileSync8(join11(dir, filename), "utf8")), anchorLen };
29990
+ return { matcher: (0, import_ignore.default)().add(readFileSync10(join14(dir, filename), "utf8")), anchorLen };
29594
29991
  } catch {
29595
29992
  return void 0;
29596
29993
  }
@@ -29647,17 +30044,17 @@ function resolveInventoryContext(input) {
29647
30044
  }
29648
30045
 
29649
30046
  // ../../packages/plugin-sdk/src/nudge.ts
29650
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
29651
- import { join as join12 } from "path";
30047
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
30048
+ import { join as join15 } from "path";
29652
30049
  var SESSION_START_MARKER = "session-start-last";
29653
30050
  function claimSessionStart(dataDir2, sessionId) {
29654
30051
  return claimOncePerSession(dataDir2, SESSION_START_MARKER, sessionId);
29655
30052
  }
29656
30053
  function claimOncePerSession(dataDir2, marker, sessionId) {
29657
30054
  if (!sessionId) return true;
29658
- const path = join12(dataDir2, marker);
30055
+ const path = join15(dataDir2, marker);
29659
30056
  try {
29660
- if (readFileSync9(path, "utf8") === sessionId) return false;
30057
+ if (readFileSync11(path, "utf8") === sessionId) return false;
29661
30058
  } catch {
29662
30059
  }
29663
30060
  try {
@@ -29669,12 +30066,12 @@ function claimOncePerSession(dataDir2, marker, sessionId) {
29669
30066
  }
29670
30067
 
29671
30068
  // ../../packages/plugin-sdk/src/paths.ts
29672
- import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
29673
- import { basename as basename5, dirname as dirname3, sep as sep3 } from "path";
30069
+ import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
30070
+ import { basename as basename5, dirname as dirname4, sep as sep3 } from "path";
29674
30071
 
29675
30072
  // ../../packages/plugin-sdk/src/project-files.ts
29676
- import { existsSync as existsSync8, readdirSync as readdirSync4 } from "fs";
29677
- import { basename as basename6, join as join13 } from "path";
30073
+ import { existsSync as existsSync9, readdirSync as readdirSync4 } from "fs";
30074
+ import { basename as basename6, join as join16 } from "path";
29678
30075
  var SKIP_DIRS = /* @__PURE__ */ new Set([
29679
30076
  ".git",
29680
30077
  "node_modules",
@@ -29771,8 +30168,8 @@ function resolveProjectFiles(cwd, opts = {}) {
29771
30168
  }
29772
30169
  if (entry.isDirectory()) {
29773
30170
  if (SKIP_DIRS.has(entry.name) || isIgnored(dirLayers, dirRel, entry.name, true)) continue;
29774
- const fullPath = join13(dir, entry.name);
29775
- if (existsSync8(join13(fullPath, ".git"))) continue;
30171
+ const fullPath = join16(dir, entry.name);
30172
+ if (existsSync9(join16(fullPath, ".git"))) continue;
29776
30173
  if (depth >= bounds.maxDepth) {
29777
30174
  walk.omitted = true;
29778
30175
  continue;
@@ -29844,12 +30241,12 @@ import { randomUUID as randomUUID14 } from "crypto";
29844
30241
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
29845
30242
 
29846
30243
  // ../../packages/plugin-sdk/src/throttle.ts
29847
- import { mkdirSync as mkdirSync3, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
29848
- import { join as join14 } from "path";
30244
+ import { mkdirSync as mkdirSync3, statSync as statSync8, writeFileSync as writeFileSync6 } from "fs";
30245
+ import { join as join17 } from "path";
29849
30246
  function throttled(dataDir2, markerName, windowMs) {
29850
- const marker = join14(dataDir2, markerName);
30247
+ const marker = join17(dataDir2, markerName);
29851
30248
  try {
29852
- if (Date.now() - statSync6(marker).mtimeMs < windowMs) return true;
30249
+ if (Date.now() - statSync8(marker).mtimeMs < windowMs) return true;
29853
30250
  } catch {
29854
30251
  }
29855
30252
  try {
@@ -29860,25 +30257,1173 @@ function throttled(dataDir2, markerName, windowMs) {
29860
30257
  return false;
29861
30258
  }
29862
30259
 
29863
- // ../../packages/plugin-runtime/src/standalone-gateway.ts
29864
- import { randomUUID as randomUUID15 } from "crypto";
29865
-
29866
- // ../../packages/plugin-runtime/src/recorder.ts
29867
- var PLUGIN_RECORDER_BINARY = "plugin";
29868
- function pluginRecordedBy(version2) {
29869
- return `${PLUGIN_RECORDER_BINARY}@${version2}`;
30260
+ // ../../packages/plugin-runtime/src/attached/with-timeout.ts
30261
+ var REQUEST_TIMEOUT_MS = 2e3;
30262
+ function withTimeout(promise2, ms) {
30263
+ let timer;
30264
+ const timeout = new Promise((_, reject) => {
30265
+ timer = setTimeout(() => {
30266
+ reject(new Error("attached gateway request timed out"));
30267
+ }, ms);
30268
+ });
30269
+ promise2.catch(() => void 0);
30270
+ return Promise.race([promise2, timeout]).finally(() => {
30271
+ clearTimeout(timer);
30272
+ });
29870
30273
  }
29871
30274
 
29872
- // ../../packages/plugin-runtime/src/standalone-gateway.ts
29873
- var StandaloneDataGateway = class {
29874
- db;
29875
- // Kept for the fingerprint key lookup (exception.key lives beside the store).
29876
- dataDir;
29877
- // One notice per gateway — see warnRulesetDiscarded.
29878
- warnedRulesetDiscarded = false;
29879
- constructor(dataDir2, detections = [], meta3) {
29880
- this.db = openLocalDatabase(dataDir2);
29881
- this.dataDir = dataDir2;
30275
+ // ../../packages/plugin-runtime/src/attached/forward-policy.ts
30276
+ function isInvalidRequest(err) {
30277
+ return typeof err === "object" && err !== null && err.name === "RemoteRequestInvalid";
30278
+ }
30279
+ var FORWARD_BUDGET_MS = 1500;
30280
+ var DECISION_PATH_BUDGET_MS = 800;
30281
+ var BREAKER_FAILURE_THRESHOLD = 3;
30282
+ var BREAKER_COOLDOWN_MS = 3e4;
30283
+ var CLOSED = { consecutiveFailures: 0, openedAtMs: null, lastFailure: null };
30284
+ var FAILURES = /* @__PURE__ */ new Set([
30285
+ "unauthorized",
30286
+ "forbidden",
30287
+ "unreachable"
30288
+ ]);
30289
+ var FORWARD_STATE_FILENAME = "attached-state.json";
30290
+ var STATE_FILENAME = FORWARD_STATE_FILENAME;
30291
+ function parseBreakerState(raw, nowMs) {
30292
+ try {
30293
+ const parsed2 = JSON.parse(raw);
30294
+ if (typeof parsed2 !== "object" || parsed2 === null) return null;
30295
+ const record2 = parsed2;
30296
+ const failures = typeof record2.consecutiveFailures === "number" && record2.consecutiveFailures >= 0 ? record2.consecutiveFailures : 0;
30297
+ const openedAtMs = typeof record2.openedAtMs === "number" && Number.isFinite(record2.openedAtMs) && record2.openedAtMs <= nowMs ? record2.openedAtMs : null;
30298
+ const lastFailure = typeof record2.lastFailure === "string" && FAILURES.has(record2.lastFailure) ? record2.lastFailure : null;
30299
+ return { consecutiveFailures: failures, openedAtMs, lastFailure };
30300
+ } catch {
30301
+ return null;
30302
+ }
30303
+ }
30304
+ function createForwardPolicy(deps) {
30305
+ const now = deps.now ?? (() => Date.now());
30306
+ const file2 = join18(deps.dir, STATE_FILENAME);
30307
+ let state = null;
30308
+ let loading = null;
30309
+ async function readState() {
30310
+ let raw;
30311
+ try {
30312
+ raw = await readFile(file2, "utf8");
30313
+ } catch {
30314
+ return { ...CLOSED };
30315
+ }
30316
+ return parseBreakerState(raw, now()) ?? { ...CLOSED };
30317
+ }
30318
+ async function load() {
30319
+ if (state !== null) return state;
30320
+ loading ??= readState().then((loaded) => {
30321
+ state = loaded;
30322
+ loading = null;
30323
+ return loaded;
30324
+ });
30325
+ return loading;
30326
+ }
30327
+ async function persist(next) {
30328
+ state = next;
30329
+ try {
30330
+ await ensureDataDir(deps.dir);
30331
+ const tmp = `${file2}.${randomUUID15()}.tmp`;
30332
+ await writeFile(tmp, JSON.stringify(next), { encoding: "utf8", mode: DATA_FILE_MODE });
30333
+ await rename(tmp, file2);
30334
+ } catch {
30335
+ }
30336
+ }
30337
+ return {
30338
+ async run(op, opts) {
30339
+ const budget = opts?.decisionPath === true ? DECISION_PATH_BUDGET_MS : FORWARD_BUDGET_MS;
30340
+ let current;
30341
+ try {
30342
+ current = await load();
30343
+ } catch {
30344
+ current = { ...CLOSED };
30345
+ }
30346
+ const at = now();
30347
+ if (current.openedAtMs !== null) {
30348
+ if (at - current.openedAtMs < BREAKER_COOLDOWN_MS) {
30349
+ return { ok: false, reason: "breaker-open" };
30350
+ }
30351
+ await persist({
30352
+ consecutiveFailures: current.consecutiveFailures,
30353
+ openedAtMs: at,
30354
+ lastFailure: current.lastFailure
30355
+ });
30356
+ }
30357
+ try {
30358
+ const value = await withTimeout(op(), budget);
30359
+ if (current.openedAtMs !== null || current.consecutiveFailures > 0) {
30360
+ await persist({ ...CLOSED });
30361
+ }
30362
+ return { ok: true, value };
30363
+ } catch (err) {
30364
+ if (isInvalidRequest(err)) return { ok: false, reason: "invalid-request" };
30365
+ const reason = classifyFailure(err);
30366
+ const failures = current.consecutiveFailures + 1;
30367
+ const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
30368
+ await persist({
30369
+ consecutiveFailures: failures,
30370
+ openedAtMs: shouldOpen ? now() : null,
30371
+ lastFailure: reason
30372
+ });
30373
+ return { ok: false, reason };
30374
+ }
30375
+ }
30376
+ };
30377
+ }
30378
+
30379
+ // ../../packages/plugin-runtime/src/attached/gateway.ts
30380
+ var ACTION_STRENGTH = {
30381
+ allow: 0,
30382
+ log: 1,
30383
+ warn: 2,
30384
+ redact: 3,
30385
+ block: 4
30386
+ };
30387
+ function ruleCategoryMap(wireRules, localRules) {
30388
+ const map2 = /* @__PURE__ */ new Map();
30389
+ for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
30390
+ for (const rule of localRules ?? []) map2.set(rule.id, rule.category);
30391
+ for (const pack of bundledDetections()) {
30392
+ for (const rule of pack.rules) map2.set(rule.id, rule.category);
30393
+ }
30394
+ return map2;
30395
+ }
30396
+ function strongerOf(a, b) {
30397
+ if (a === null) return b;
30398
+ if (b === null) return a;
30399
+ return ACTION_STRENGTH[a] >= ACTION_STRENGTH[b] ? a : b;
30400
+ }
30401
+ function policyKey(policy) {
30402
+ return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
30403
+ }
30404
+ function floorFor(policy, categoryByRuleId) {
30405
+ const category = "category" in policy.target ? policy.target.category : categoryByRuleId.get(policy.target.ruleId);
30406
+ return category === void 0 ? null : DEFAULT_ACTIONS[category];
30407
+ }
30408
+ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
30409
+ const merged = /* @__PURE__ */ new Map();
30410
+ const disabled = [];
30411
+ const remoteCategoryAction = /* @__PURE__ */ new Map();
30412
+ for (const policy of remotePolicies) {
30413
+ if (!policy.enabled) continue;
30414
+ if (!("category" in policy.target)) continue;
30415
+ if (remoteCategoryAction.has(policy.target.category)) continue;
30416
+ const floor = floorFor(policy, categoryByRuleId);
30417
+ remoteCategoryAction.set(
30418
+ policy.target.category,
30419
+ floor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[floor] ? floor : policy.action
30420
+ );
30421
+ }
30422
+ for (const policy of localPolicies) {
30423
+ if (!policy.enabled) {
30424
+ disabled.push(policy);
30425
+ continue;
30426
+ }
30427
+ const key = policyKey(policy);
30428
+ if (merged.has(key)) continue;
30429
+ let remoteFloor = null;
30430
+ if ("ruleId" in policy.target) {
30431
+ const category = categoryByRuleId.get(policy.target.ruleId);
30432
+ if (category !== void 0) remoteFloor = remoteCategoryAction.get(category) ?? null;
30433
+ }
30434
+ merged.set(
30435
+ key,
30436
+ remoteFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[remoteFloor] ? { ...policy, action: remoteFloor } : policy
30437
+ );
30438
+ }
30439
+ const localCategoryAction = /* @__PURE__ */ new Map();
30440
+ for (const policy of merged.values()) {
30441
+ if ("category" in policy.target) localCategoryAction.set(policy.target.category, policy.action);
30442
+ }
30443
+ for (const policy of remotePolicies) {
30444
+ if (!policy.enabled) {
30445
+ disabled.push(policy);
30446
+ continue;
30447
+ }
30448
+ const key = policyKey(policy);
30449
+ const floor = floorFor(policy, categoryByRuleId);
30450
+ let localFloor = null;
30451
+ if ("ruleId" in policy.target) {
30452
+ const category = categoryByRuleId.get(policy.target.ruleId);
30453
+ if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
30454
+ }
30455
+ const effectiveFloor = strongerOf(floor, localFloor);
30456
+ const clamped = effectiveFloor !== null && ACTION_STRENGTH[policy.action] < ACTION_STRENGTH[effectiveFloor] ? { ...policy, action: effectiveFloor } : policy;
30457
+ const existing = merged.get(key);
30458
+ if (existing === void 0) {
30459
+ merged.set(key, clamped);
30460
+ continue;
30461
+ }
30462
+ if (ACTION_STRENGTH[clamped.action] > ACTION_STRENGTH[existing.action]) {
30463
+ merged.set(key, clamped);
30464
+ }
30465
+ }
30466
+ return [...merged.values(), ...disabled];
30467
+ }
30468
+ var AttachedDataGateway = class {
30469
+ constructor(deps) {
30470
+ this.deps = deps;
30471
+ }
30472
+ deps;
30473
+ /**
30474
+ * The control plane's OWN resolution of this session's inventory, captured by
30475
+ * ensureInventory. Null until the first successful forward — and it stays
30476
+ * null for the whole session when the control plane is unreachable, which is fine:
30477
+ * reKeyForForward then leaves the event's ids alone and the control plane resolves
30478
+ * what it can from the descriptors it already has.
30479
+ */
30480
+ remoteInventory = null;
30481
+ // ---------------------------------------------------------------------
30482
+ // Writes: local first, then forward.
30483
+ // ---------------------------------------------------------------------
30484
+ async recordCapture(record2) {
30485
+ await this.deps.local.recordCapture(record2);
30486
+ await this.deps.forward.run(
30487
+ () => this.deps.client.ingestEvents({
30488
+ events: [record2.event],
30489
+ ...record2.dedupe ? { dedupe: record2.dedupe } : {}
30490
+ }),
30491
+ { decisionPath: true }
30492
+ );
30493
+ }
30494
+ async ensureInventory(ctx) {
30495
+ const resolved = await this.deps.local.ensureInventory(ctx);
30496
+ const remote = await this.deps.forward.run(() => this.deps.client.ingestInventory(ctx));
30497
+ this.remoteInventory = remote.ok ? remote.value : null;
30498
+ const snapshot = await (async () => {
30499
+ try {
30500
+ return await this.deps.posture?.prepare() ?? null;
30501
+ } catch {
30502
+ return null;
30503
+ }
30504
+ })();
30505
+ if (snapshot) {
30506
+ try {
30507
+ await withTimeout(
30508
+ this.deps.posture?.send(snapshot) ?? Promise.resolve(),
30509
+ REQUEST_TIMEOUT_MS
30510
+ );
30511
+ } catch {
30512
+ }
30513
+ }
30514
+ return resolved;
30515
+ }
30516
+ // The id is minted CLIENT-side and stored verbatim: the control plane does NOT
30517
+ // re-key it. `pgAuditValues` writes `id: event.id` and carries tenancy in
30518
+ // its own scoping columns, so the device and the forwarded copy
30519
+ // share one id space — which is what makes a re-post idempotent at all.
30520
+ //
30521
+ // Re-posts collapse via `onConflictDoUpdate` on the `id` PK, guarded by
30522
+ // `setWhere eventType = 'session'` (NOT onConflictDoNothing). That guard is
30523
+ // what makes an attached retry safe: a capture-stubbed session row can still
30524
+ // be HEALED by the authoritative root, while a duplicate non-session event —
30525
+ // a retried tool_call, exactly this path — can never stomp a populated row.
30526
+ async recordAuditEvent(event) {
30527
+ await this.deps.local.recordAuditEvent(event);
30528
+ await this.deps.forward.run(
30529
+ () => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
30530
+ );
30531
+ }
30532
+ // Attached `llm_call` is written locally by the inner gateway, then routed to
30533
+ // the control plane through the existing `recordAuditEvent` ingest (no dedicated
30534
+ // client method yet) by pre-building the audit event from the natural key.
30535
+ // The forward goes DIRECTLY to the client rather than through this.recordAuditEvent,
30536
+ // which would write the event to the local store a second time.
30537
+ async recordLlmCall(input) {
30538
+ await this.deps.local.recordLlmCall(input);
30539
+ await this.deps.forward.run(
30540
+ () => this.deps.client.recordAuditEvent(
30541
+ reKeyForForward(llmAuditEvent(input), this.remoteInventory)
30542
+ )
30543
+ );
30544
+ }
30545
+ /**
30546
+ * Forward one batch, item by item, under ONE aggregate deadline.
30547
+ *
30548
+ * Per-item budgets bound each request and nothing bounded their sum — see
30549
+ * BATCH_FORWARD_BUDGET_MS. When the deadline passes the remainder is dropped
30550
+ * rather than sent: the local write has already succeeded, so every caller
30551
+ * has a correct result to return, and a drop is the outcome this path is
30552
+ * built to accept (G8) where a blown hook timeout is not.
30553
+ *
30554
+ * Serial rather than concurrent on purpose. Firing N requests at once would
30555
+ * trade a latency problem for a burst the plane's own per-key rate limiting
30556
+ * would answer with the refusals the breaker then counts.
30557
+ *
30558
+ * WHAT IS DROPPED IS COUNTED. Every other forward failure ends in
30559
+ * `ForwardPolicy.run`'s catch and moves the breaker's file, which is what
30560
+ * lets status call the forward unhealthy; this path returns BEFORE `run` is
30561
+ * reached, so without the tally in `forward-drops.ts` a slow-but-answering
30562
+ * plane produces no failures, keeps the breaker closed, renders a healthy
30563
+ * block, and discards the tail of every batch indefinitely.
30564
+ */
30565
+ async forwardBatch(inputs, toEvent) {
30566
+ const deadline = Date.now() + BATCH_FORWARD_BUDGET_MS;
30567
+ for (let i = 0; i < inputs.length; i += 1) {
30568
+ const now = Date.now();
30569
+ if (now >= deadline) {
30570
+ recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
30571
+ return;
30572
+ }
30573
+ const input = inputs[i];
30574
+ await this.deps.forward.run(
30575
+ () => this.deps.client.recordAuditEvent(reKeyForForward(toEvent(input), this.remoteInventory))
30576
+ );
30577
+ }
30578
+ }
30579
+ // Delegated as a BATCH rather than looped over recordLlmCall: the inner
30580
+ // gateway may write the whole batch in one local transaction, and looping
30581
+ // here would replace that with N separate local writes.
30582
+ async recordLlmCalls(inputs) {
30583
+ await this.deps.local.recordLlmCalls(inputs);
30584
+ await this.forwardBatch(inputs, (input) => llmAuditEvent(input));
30585
+ }
30586
+ // `input.inspections` (secrets detected client-side in the tool's masked
30587
+ // target) ride along on the request's `inspections` field — the control plane
30588
+ // persists each as an inspection_findings row linked to this audit event
30589
+ // (see RecordAuditEventRequest in @akasecurity/schema). The masked
30590
+ // `target` already rides `input.attributes`, so no raw secret leaks either
30591
+ // way — this only stops the FINDING row itself from being dropped.
30592
+ async recordToolCalls(inputs) {
30593
+ await this.deps.local.recordToolCalls(inputs);
30594
+ await this.forwardBatch(inputs, (input) => toolAuditEvent(input));
30595
+ }
30596
+ // Forwarded as a `config_scan` audit event: there is no dedicated
30597
+ // config-scan ingest endpoint, and the audit-event door is the one the
30598
+ // control plane already opens for client-minted, idempotent records.
30599
+ //
30600
+ // ONLY `scanEvent` CROSSES, and unlike `recordCapture` the plane cannot
30601
+ // re-derive the rest. A `ConfigScanRecord` is four things committed together
30602
+ // locally — the inventory `items`, this audit event, and the posture
30603
+ // `definitions`/`findings` that reference it — and three of them stay on the
30604
+ // device. Say that plainly rather than let the asymmetry with `recordCapture`
30605
+ // read as the same argument: there, findings are omitted BECAUSE the plane
30606
+ // re-derives them from `Event.content`; here there is no content to re-derive
30607
+ // from, so what is omitted is simply not sent.
30608
+ //
30609
+ // That is the wire contract as it stands rather than an oversight to patch
30610
+ // here. `items` has no route at all, and `RecordAuditEventRequest.inspections`
30611
+ // is documented as tool-call findings — widening it to carry config-scan
30612
+ // findings is an egress change (a posture finding's `maskedMatch` holds the
30613
+ // matched command) and a decision about what an attached deployment is
30614
+ // entitled to, not a bug fix. An attached machine's config posture therefore
30615
+ // reaches the plane as the event only; the dashboard's own view of it is the
30616
+ // local store.
30617
+ async recordConfigScan(record2) {
30618
+ await this.deps.local.recordConfigScan(record2);
30619
+ await this.deps.forward.run(
30620
+ () => this.deps.client.recordAuditEvent(reKeyForForward(record2.scanEvent, this.remoteInventory))
30621
+ );
30622
+ }
30623
+ async recordBlockedDetection(entry) {
30624
+ return this.deps.local.recordBlockedDetection(entry);
30625
+ }
30626
+ /**
30627
+ * LOCAL-ONLY, deliberately. The shares API is read-plus-decision-override
30628
+ * with no egress ingest endpoint, so there is nothing to forward to; adding a
30629
+ * forward here would be inventing a wire contract that does not exist. The
30630
+ * local write is the whole operation, and its summary is the real one — the
30631
+ * scanner reads a throw as a FAILED WRITE and skips its ledger commit, so
30632
+ * returning the inner gateway's result keeps the retry semantics honest.
30633
+ */
30634
+ async recordProjectEgress(input) {
30635
+ return this.deps.local.recordProjectEgress(input);
30636
+ }
30637
+ // ---------------------------------------------------------------------
30638
+ // Reads and device-local ledgers: pure delegation.
30639
+ // ---------------------------------------------------------------------
30640
+ async configInventoryReport() {
30641
+ return this.deps.local.configInventoryReport();
30642
+ }
30643
+ async readSessionProvider(sessionId) {
30644
+ return this.deps.local.readSessionProvider(sessionId);
30645
+ }
30646
+ async facets() {
30647
+ return this.deps.local.facets();
30648
+ }
30649
+ /**
30650
+ * Delegated UNMODIFIED — including its refusals.
30651
+ *
30652
+ * This is a fail-secure boundary: it decides whether an approved exception
30653
+ * lets a blocked action through. Under local-first the local store owns the
30654
+ * exception ledger, so the honest answer is whatever it says; wrapping this
30655
+ * in a fallback (`catch { return true }`, or defaulting on a timeout) would
30656
+ * turn a store error into a granted bypass. If the inner gateway rejects,
30657
+ * this rejects, and the runtime's own handling decides — which is asserted
30658
+ * end-to-end through runtime.capture rather than here.
30659
+ */
30660
+ async consumeException(id) {
30661
+ return this.deps.local.consumeException(id);
30662
+ }
30663
+ async recentFindings(opts) {
30664
+ return this.deps.local.recentFindings(opts);
30665
+ }
30666
+ async healthSummary() {
30667
+ return this.deps.local.healthSummary();
30668
+ }
30669
+ async activityByDay(days) {
30670
+ return this.deps.local.activityByDay(days);
30671
+ }
30672
+ async tokenReports() {
30673
+ return this.deps.local.tokenReports();
30674
+ }
30675
+ async knownContentHashes() {
30676
+ return this.deps.local.knownContentHashes();
30677
+ }
30678
+ async scanLedger(rulesetHash) {
30679
+ return this.deps.local.scanLedger(rulesetHash);
30680
+ }
30681
+ async recordScanned(entries) {
30682
+ return this.deps.local.recordScanned(entries);
30683
+ }
30684
+ async getRuleProbeVerdict(ruleKey) {
30685
+ return this.deps.local.getRuleProbeVerdict(ruleKey);
30686
+ }
30687
+ async setRuleProbeVerdict(ruleKey, verdict, worstProbeMs) {
30688
+ return this.deps.local.setRuleProbeVerdict(ruleKey, verdict, worstProbeMs);
30689
+ }
30690
+ async openAtRestKeysForPath(path) {
30691
+ return this.deps.local.openAtRestKeysForPath(path);
30692
+ }
30693
+ async resolvedAtRestKeysForPath(path) {
30694
+ return this.deps.local.resolvedAtRestKeysForPath(path);
30695
+ }
30696
+ async insertResolution(input) {
30697
+ return this.deps.local.insertResolution(input);
30698
+ }
30699
+ async close() {
30700
+ return this.deps.local.close();
30701
+ }
30702
+ // ---------------------------------------------------------------------
30703
+ // Policy
30704
+ // ---------------------------------------------------------------------
30705
+ async getPolicyBundle() {
30706
+ const local = await this.deps.local.getPolicyBundle();
30707
+ const cached2 = await (async () => {
30708
+ try {
30709
+ return await this.deps.readCachedBundle();
30710
+ } catch {
30711
+ return null;
30712
+ }
30713
+ })();
30714
+ if (cached2 === null) return local;
30715
+ const byRuleId = /* @__PURE__ */ new Map();
30716
+ for (const rule of [...local.rules ?? [], ...cached2.rules ?? []]) {
30717
+ if (!byRuleId.has(rule.id)) byRuleId.set(rule.id, rule);
30718
+ }
30719
+ const rules = [...byRuleId.values()];
30720
+ return {
30721
+ ...local,
30722
+ // The remote version identifies the composed bundle for the poller.
30723
+ version: cached2.version,
30724
+ rules,
30725
+ policies: mergeRaiseOnly(
30726
+ local.policies,
30727
+ cached2.policies,
30728
+ ruleCategoryMap(cached2.rules, local.rules)
30729
+ ),
30730
+ customKeywords: [...local.customKeywords, ...cached2.customKeywords]
30731
+ // `rulesComplete` is a STANDALONE-ONLY signal (the user's local installed
30732
+ // snapshot) and is taken from the LOCAL bundle only — never from the wire
30733
+ // or the on-disk cache. Honoring a cached one would hand the control plane, or
30734
+ // anything able to write policy-cache.json, a kill-switch over the
30735
+ // compiled-in bundled packs: `{ rulesComplete: true, rules: [] }` would
30736
+ // zero local detection. Spread from `local` above, and deliberately not
30737
+ // re-read from `cached` here.
30738
+ //
30739
+ // THREE MORE OF THE CACHED BUNDLE'S FIELDS ARE DROPPED, each on purpose,
30740
+ // and each named here so a reader can tell a decision from an omission:
30741
+ //
30742
+ // `exceptions` — an exception SUPPRESSES a detection, so honoring
30743
+ // one from an unsigned on-disk cache would let
30744
+ // anything able to write that file turn rules off.
30745
+ // Every other field this merge accepts can only
30746
+ // RAISE enforcement; this is the one that cannot,
30747
+ // so it stays local-only until the bundle is
30748
+ // signed. Exceptions remain a device-local ledger.
30749
+ // `reversibleRuleIds` — the Redact & Vault archetype makes a redaction
30750
+ // recoverable, which is a CUSTODY change: it puts
30751
+ // the detected value in the local vault instead of
30752
+ // destroying it. Taking that instruction from the
30753
+ // cache would let a remote party turn one-way
30754
+ // redaction into retention. Dropping it keeps the
30755
+ // one-way behaviour, which the schema itself calls
30756
+ // "the safe direction to default".
30757
+ // `ruleVersions` — remote rules fall back to their own spec version.
30758
+ // Cosmetic rather than protective: it only affects
30759
+ // how a finding is version-attributed, and the two
30760
+ // sides may therefore attribute org rules
30761
+ // differently. Worth carrying once there is a
30762
+ // reader that needs it; nothing reads it today.
30763
+ };
30764
+ }
30765
+ // ---------------------------------------------------------------------
30766
+ // LocalStoreMaintenance — by delegation (D3).
30767
+ //
30768
+ // Implementing these is what actually closes the skipped-local-maintenance
30769
+ // gap: the OSS structural guard `hasLocalStoreMaintenance()` is satisfied by
30770
+ // any object carrying all five, so the composite qualifies and SessionStart
30771
+ // runs maintenance on the device's real store.
30772
+ //
30773
+ // ⚠ Two of the five are SYNCHRONOUS and must stay that way. `handle-session-start`
30774
+ // calls `capWarnEraEnforcement` without `await` and uses `staleBinaryNotice`'s
30775
+ // return value directly; declaring them `async` here would hand those call
30776
+ // sites a Promise and silently break both.
30777
+ // ---------------------------------------------------------------------
30778
+ async sweepTerminalExceptions(retentionMs) {
30779
+ return this.deps.local.sweepTerminalExceptions(retentionMs);
30780
+ }
30781
+ capWarnEraEnforcement(policyMode) {
30782
+ return this.deps.local.capWarnEraEnforcement(policyMode);
30783
+ }
30784
+ async recordProjectFiles(projectId, scan2) {
30785
+ return this.deps.local.recordProjectFiles(projectId, scan2);
30786
+ }
30787
+ async reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot) {
30788
+ return this.deps.local.reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot);
30789
+ }
30790
+ staleBinaryNotice(currentVersion) {
30791
+ return this.deps.local.staleBinaryNotice(currentVersion);
30792
+ }
30793
+ };
30794
+ function reKeyForForward(event, remote) {
30795
+ if (remote === null) {
30796
+ const stripped = { ...event };
30797
+ delete stripped.hostId;
30798
+ delete stripped.harnessId;
30799
+ delete stripped.sourceProjectId;
30800
+ return stripped;
30801
+ }
30802
+ const rekeyed = { ...event };
30803
+ delete rekeyed.hostId;
30804
+ delete rekeyed.harnessId;
30805
+ delete rekeyed.sourceProjectId;
30806
+ if (remote.hostId !== void 0) rekeyed.hostId = remote.hostId;
30807
+ if (remote.harnessId !== void 0) rekeyed.harnessId = remote.harnessId;
30808
+ if (remote.sourceProjectId !== void 0) rekeyed.sourceProjectId = remote.sourceProjectId;
30809
+ return rekeyed;
30810
+ }
30811
+ var BATCH_FORWARD_BUDGET_MS = 3e3;
30812
+ function llmAuditEvent(input) {
30813
+ return {
30814
+ id: llmCallId(input.sessionId, input.messageId),
30815
+ eventType: "llm_call",
30816
+ startedAt: input.startedAt,
30817
+ parentId: input.parentId,
30818
+ rootSessionId: input.rootSessionId,
30819
+ attributes: input.attributes
30820
+ };
30821
+ }
30822
+ function toolAuditEvent(input) {
30823
+ return {
30824
+ id: toolCallId(input.sessionId, input.toolUseId),
30825
+ eventType: "tool_call",
30826
+ startedAt: input.startedAt,
30827
+ parentId: input.parentId,
30828
+ rootSessionId: input.rootSessionId,
30829
+ attributes: input.attributes,
30830
+ inspections: input.inspections
30831
+ };
30832
+ }
30833
+
30834
+ // ../../packages/plugin-runtime/src/attached/policy-store.ts
30835
+ import { randomUUID as randomUUID16 } from "crypto";
30836
+ import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
30837
+ import { join as join19 } from "path";
30838
+
30839
+ // ../../packages/plugin-runtime/src/attached/atomic-publish.ts
30840
+ import { rename as rename2 } from "fs/promises";
30841
+ var RETRYABLE = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY"]);
30842
+ var ATTEMPTS = 5;
30843
+ var delay = (ms) => new Promise((resolve2) => {
30844
+ setTimeout(resolve2, ms);
30845
+ });
30846
+ async function publishByRename(tmp, file2, move = rename2) {
30847
+ for (let attempt = 1; ; attempt += 1) {
30848
+ try {
30849
+ await move(tmp, file2);
30850
+ return;
30851
+ } catch (err) {
30852
+ const code = err.code;
30853
+ if (attempt >= ATTEMPTS || code === void 0 || !RETRYABLE.has(code)) throw err;
30854
+ await delay(attempt * 10);
30855
+ }
30856
+ }
30857
+ }
30858
+
30859
+ // ../../packages/plugin-runtime/src/attached/policy-store.ts
30860
+ function createPolicyStore(dir = dataDir()) {
30861
+ const file2 = join19(dir, "policy-cache.json");
30862
+ async function read() {
30863
+ try {
30864
+ const raw = await readFile2(file2, "utf8");
30865
+ const parsed2 = JSON.parse(raw);
30866
+ if (typeof parsed2 !== "object" || parsed2 === null) return null;
30867
+ const record2 = parsed2;
30868
+ const bundle = PolicyBundle.parse(record2.bundle);
30869
+ const fetchedAtMs = typeof record2.fetchedAtMs === "number" ? record2.fetchedAtMs : 0;
30870
+ const etag = typeof record2.etag === "string" ? record2.etag : void 0;
30871
+ return { bundle, fetchedAtMs, ...etag === void 0 ? {} : { etag } };
30872
+ } catch {
30873
+ return null;
30874
+ }
30875
+ }
30876
+ async function write(bundle, etag) {
30877
+ await ensureDataDir(dir);
30878
+ const stored = {
30879
+ bundle,
30880
+ fetchedAtMs: Date.now(),
30881
+ ...etag === void 0 ? {} : { etag }
30882
+ };
30883
+ const tmp = `${file2}.${randomUUID16()}.tmp`;
30884
+ try {
30885
+ await writeFile2(tmp, JSON.stringify(stored), {
30886
+ encoding: "utf8",
30887
+ mode: DATA_FILE_MODE,
30888
+ flag: "wx"
30889
+ });
30890
+ await publishByRename(tmp, file2);
30891
+ } catch (err) {
30892
+ await rm(tmp, { force: true }).catch(() => void 0);
30893
+ throw err;
30894
+ }
30895
+ }
30896
+ return { read, write, file: file2 };
30897
+ }
30898
+
30899
+ // ../../packages/remote/src/http.ts
30900
+ import { request as httpRequest } from "http";
30901
+ import { request as httpsRequest } from "https";
30902
+ var DEFAULT_TIMEOUT_MS = 1e4;
30903
+ var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
30904
+ var RemoteRequestError = class extends Error {
30905
+ constructor(status) {
30906
+ super(`control-plane request failed with status ${String(status)}`);
30907
+ this.status = status;
30908
+ this.name = "RemoteRequestError";
30909
+ }
30910
+ status;
30911
+ };
30912
+ var RemoteRequestInvalid = class extends Error {
30913
+ constructor(route, cause) {
30914
+ super(`refusing to send a malformed body to ${route}`);
30915
+ this.cause = cause;
30916
+ this.name = "RemoteRequestInvalid";
30917
+ }
30918
+ cause;
30919
+ };
30920
+ var RemoteResponseInvalid = class extends Error {
30921
+ constructor(route, detail) {
30922
+ super(`control plane answered ${route} with ${detail}`);
30923
+ this.name = "RemoteResponseInvalid";
30924
+ }
30925
+ };
30926
+ var RemoteTransportError = class extends Error {
30927
+ /**
30928
+ * The status the peer sent, when headers arrived and only the BODY was
30929
+ * refused.
30930
+ *
30931
+ * Undefined for the ordinary case this class was written for — no answer at
30932
+ * all. It exists because two paths reject after a status has already been
30933
+ * delivered: an oversized body and an aborted response. Discarding it there
30934
+ * reported a deployment answering 401 with a verbose body as a network
30935
+ * outage, which sends the reader to look at their network instead of their
30936
+ * credential.
30937
+ */
30938
+ constructor(reason, status) {
30939
+ super(`control-plane request did not complete: ${reason}`);
30940
+ this.status = status;
30941
+ this.name = "RemoteTransportError";
30942
+ }
30943
+ status;
30944
+ };
30945
+ async function send(options) {
30946
+ const url2 = new URL(options.url);
30947
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
30948
+ const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
30949
+ const requestOptions = {
30950
+ method: options.method,
30951
+ headers: {
30952
+ // CALLER HEADERS FIRST, so this module's own are not overridable. Spread
30953
+ // last they win, and two of the values below are ones no caller may
30954
+ // replace: `x-api-key` is the credential, and `content-length` is the
30955
+ // byte count that stops a multi-byte body being truncated by the
30956
+ // receiver. `SendOptions.headers` is a free-form record on an exported
30957
+ // function, so "no caller does that today" is not the guarantee to rely
30958
+ // on. The one header any caller actually passes — `if-none-match` on the
30959
+ // conditional GET — is untouched by this order.
30960
+ ...options.headers,
30961
+ // The credential. One header, matching what the deployment authenticates
30962
+ // on; a second copy in an `Authorization` header would be one more place
30963
+ // it can be logged by an intermediary for no gain.
30964
+ "x-api-key": options.apiKey,
30965
+ accept: "application/json",
30966
+ ...options.body === void 0 ? {} : {
30967
+ "content-type": "application/json",
30968
+ // Byte length, not string length: a multi-byte body sent with a
30969
+ // character count is truncated by the receiver.
30970
+ "content-length": String(Buffer.byteLength(options.body))
30971
+ }
30972
+ }
30973
+ };
30974
+ return new Promise((resolve2, reject) => {
30975
+ let settled = false;
30976
+ const fail = (reason, status) => {
30977
+ if (settled) return;
30978
+ settled = true;
30979
+ reject(new RemoteTransportError(reason, status));
30980
+ };
30981
+ const req = send_(url2, requestOptions, (res) => {
30982
+ const chunks = [];
30983
+ let size = 0;
30984
+ res.on("data", (chunk) => {
30985
+ size += chunk.length;
30986
+ if (size > MAX_RESPONSE_BYTES) {
30987
+ fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
30988
+ res.destroy();
30989
+ req.destroy();
30990
+ return;
30991
+ }
30992
+ chunks.push(chunk);
30993
+ });
30994
+ res.on("aborted", () => {
30995
+ fail("the response was aborted", res.statusCode);
30996
+ });
30997
+ res.on("end", () => {
30998
+ if (settled) return;
30999
+ settled = true;
31000
+ resolve2({
31001
+ status: res.statusCode ?? 0,
31002
+ headers: res.headers,
31003
+ body: Buffer.concat(chunks).toString("utf8")
31004
+ });
31005
+ });
31006
+ });
31007
+ const deadline = setTimeout(() => {
31008
+ fail(`no response within ${String(timeoutMs)}ms`);
31009
+ req.destroy();
31010
+ }, timeoutMs);
31011
+ deadline.unref();
31012
+ req.on("upgrade", (_res, socket) => {
31013
+ fail("the deployment answered with a protocol upgrade");
31014
+ socket.destroy();
31015
+ });
31016
+ req.on("close", () => {
31017
+ fail("the connection closed before a response was read");
31018
+ clearTimeout(deadline);
31019
+ });
31020
+ req.on("error", (err) => {
31021
+ fail(err.message);
31022
+ });
31023
+ if (options.body !== void 0) req.write(options.body);
31024
+ req.end();
31025
+ });
31026
+ }
31027
+
31028
+ // ../../packages/remote/src/client.ts
31029
+ var ROUTES = {
31030
+ events: "/v1/events",
31031
+ auditEvents: "/v1/audit-events",
31032
+ inventory: "/v1/inventory",
31033
+ storePosture: "/v1/store-posture",
31034
+ policyBundle: "/v1/policy-bundle",
31035
+ whoami: "/v1/plugin/whoami"
31036
+ };
31037
+ function headerValue(response, name) {
31038
+ const raw = response.headers[name];
31039
+ if (raw === void 0) return void 0;
31040
+ return Array.isArray(raw) ? raw[0] : raw;
31041
+ }
31042
+ function okBody(response) {
31043
+ if (response.status < 200 || response.status >= 300) {
31044
+ throw new RemoteRequestError(response.status);
31045
+ }
31046
+ return response.body;
31047
+ }
31048
+ function parsed(schema, body, route) {
31049
+ let json2;
31050
+ try {
31051
+ json2 = JSON.parse(body);
31052
+ } catch {
31053
+ throw new RemoteResponseInvalid(route, "a body that is not JSON");
31054
+ }
31055
+ const result = schema.safeParse(json2);
31056
+ if (!result.success) {
31057
+ throw new RemoteResponseInvalid(route, "a body this client cannot read");
31058
+ }
31059
+ return result.data;
31060
+ }
31061
+ function withoutTrailingSlashes(endpoint) {
31062
+ let end = endpoint.length;
31063
+ while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
31064
+ return endpoint.slice(0, end);
31065
+ }
31066
+ var SLASH = "/".charCodeAt(0);
31067
+ function createRemoteClient(options) {
31068
+ const base = withoutTrailingSlashes(options.endpoint);
31069
+ const url2 = (route) => `${base}${route}`;
31070
+ const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
31071
+ return {
31072
+ async ingestEvents(batch) {
31073
+ const response = await send({
31074
+ ...common,
31075
+ method: "POST",
31076
+ url: url2(ROUTES.events),
31077
+ body: JSON.stringify(batch)
31078
+ });
31079
+ return parsed(IngestAck, okBody(response), ROUTES.events);
31080
+ },
31081
+ async ingestInventory(context) {
31082
+ const response = await send({
31083
+ ...common,
31084
+ method: "POST",
31085
+ url: url2(ROUTES.inventory),
31086
+ body: JSON.stringify(context)
31087
+ });
31088
+ return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
31089
+ },
31090
+ async recordAuditEvent(event) {
31091
+ const validated = RecordAuditEventRequest.safeParse(event);
31092
+ if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
31093
+ const submission = validated.data;
31094
+ const response = await send({
31095
+ ...common,
31096
+ method: "POST",
31097
+ url: url2(ROUTES.auditEvents),
31098
+ body: JSON.stringify(submission)
31099
+ });
31100
+ okBody(response);
31101
+ },
31102
+ async reportStorePosture(snapshot) {
31103
+ const response = await send({
31104
+ ...common,
31105
+ method: "POST",
31106
+ url: url2(ROUTES.storePosture),
31107
+ body: JSON.stringify(snapshot)
31108
+ });
31109
+ okBody(response);
31110
+ },
31111
+ async getPolicyBundle(etag) {
31112
+ const response = await send({
31113
+ ...common,
31114
+ method: "GET",
31115
+ url: url2(ROUTES.policyBundle),
31116
+ ...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
31117
+ });
31118
+ if (response.status === 304) {
31119
+ return { changed: false, etag: headerValue(response, "etag") ?? etag };
31120
+ }
31121
+ const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
31122
+ return { changed: true, bundle, etag: headerValue(response, "etag") };
31123
+ },
31124
+ async whoami() {
31125
+ const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
31126
+ return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
31127
+ }
31128
+ };
31129
+ }
31130
+
31131
+ // ../../packages/plugin-runtime/src/attached/posture-reporter.ts
31132
+ var POSTURE_REPORT_INTERVAL_MS = 60 * 60 * 1e3;
31133
+ function createPostureReporter(deps) {
31134
+ async function prepare() {
31135
+ try {
31136
+ const state = await withTimeout(deps.store.read(), REQUEST_TIMEOUT_MS);
31137
+ if (state === null) return null;
31138
+ const nowMs = deps.now();
31139
+ const elapsed = nowMs - state.lastAttemptedAtMs;
31140
+ if (elapsed >= 0 && elapsed < POSTURE_REPORT_INTERVAL_MS) return null;
31141
+ try {
31142
+ await withTimeout(deps.store.markAttempted(state.deviceId, nowMs), REQUEST_TIMEOUT_MS);
31143
+ } catch {
31144
+ }
31145
+ const { readError, ...measurement } = deps.readStore();
31146
+ if (readError) return null;
31147
+ let plugin;
31148
+ try {
31149
+ plugin = await deps.pluginBlock?.();
31150
+ } catch {
31151
+ plugin = void 0;
31152
+ }
31153
+ return {
31154
+ deviceId: state.deviceId,
31155
+ hostname: deps.hostname(),
31156
+ capturedAt: nowMs,
31157
+ ...measurement,
31158
+ // Omit the key rather than spread an explicit `undefined` —
31159
+ // exactOptionalPropertyTypes distinguishes the two, and the bridge in
31160
+ // factory.ts keys on presence.
31161
+ ...plugin === void 0 ? {} : { plugin }
31162
+ };
31163
+ } catch {
31164
+ return null;
31165
+ }
31166
+ }
31167
+ async function send2(snapshot) {
31168
+ try {
31169
+ await deps.report(snapshot);
31170
+ } catch {
31171
+ }
31172
+ }
31173
+ return { prepare, send: send2 };
31174
+ }
31175
+
31176
+ // ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
31177
+ import { statSync as statSync9 } from "fs";
31178
+ import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
31179
+
31180
+ // ../../packages/plugin-runtime/src/attached/action-counts.ts
31181
+ function emptyActionCounts() {
31182
+ return Object.fromEntries(ACTION_TAKEN_KEYS.map((a) => [a, 0]));
31183
+ }
31184
+ function isActionTaken(value) {
31185
+ return ACTION_TAKEN_KEYS.includes(value);
31186
+ }
31187
+
31188
+ // ../../packages/plugin-runtime/src/attached/posture-snapshot.ts
31189
+ var CAPTURE_EVENT_TYPES_SQL2 = `('prompt','response','code_change','tool_use')`;
31190
+ function isSchemaAbsent(err) {
31191
+ return err instanceof Error && /no such table/i.test(err.message);
31192
+ }
31193
+ function emptyReadout(readError = false) {
31194
+ const byAction = emptyActionCounts();
31195
+ return {
31196
+ storePresent: false,
31197
+ schemaVersion: null,
31198
+ findingsTotal: 0,
31199
+ findingsFirstAt: null,
31200
+ findingsLastAt: null,
31201
+ packs: [],
31202
+ policyCounts: { total: 0, disabled: 0, byAction },
31203
+ readError
31204
+ };
31205
+ }
31206
+ function readStorePosture(dbPath2) {
31207
+ try {
31208
+ statSync9(dbPath2);
31209
+ } catch (err) {
31210
+ const code = err.code;
31211
+ if (code === "ENOENT" || code === "ENOTDIR") return emptyReadout();
31212
+ return emptyReadout(true);
31213
+ }
31214
+ let db = null;
31215
+ let version2 = null;
31216
+ let packs2 = [];
31217
+ let policyCounts = {
31218
+ total: 0,
31219
+ disabled: 0,
31220
+ byAction: emptyActionCounts()
31221
+ };
31222
+ let findingsTotal = 0;
31223
+ let findingsFirstAt = null;
31224
+ let findingsLastAt = null;
31225
+ const currentReadout = () => ({
31226
+ storePresent: true,
31227
+ schemaVersion: version2,
31228
+ findingsTotal,
31229
+ findingsFirstAt,
31230
+ findingsLastAt,
31231
+ packs: packs2,
31232
+ policyCounts,
31233
+ readError: false
31234
+ });
31235
+ try {
31236
+ db = new DatabaseSync3(dbPath2, { readOnly: true });
31237
+ db.exec("PRAGMA busy_timeout = 2000");
31238
+ version2 = db.prepare("PRAGMA user_version").get().user_version;
31239
+ try {
31240
+ const packRows = db.prepare(
31241
+ `SELECT namespace, pack_id, version, enabled, updated_at FROM installed_packs ORDER BY namespace, pack_id`
31242
+ ).all();
31243
+ packs2 = packRows.map((r) => ({
31244
+ packId: `${r.namespace}/${r.pack_id}`,
31245
+ version: r.version,
31246
+ enabled: r.enabled !== 0,
31247
+ updatedAt: r.updated_at == null ? null : String(r.updated_at)
31248
+ }));
31249
+ } catch (err) {
31250
+ if (!isSchemaAbsent(err)) throw err;
31251
+ }
31252
+ try {
31253
+ const policyRows = db.prepare(`SELECT action, enabled FROM policies`).all();
31254
+ const byAction = emptyActionCounts();
31255
+ let disabled = 0;
31256
+ for (const row of policyRows) {
31257
+ if (row.enabled === 0) disabled += 1;
31258
+ if (isActionTaken(row.action)) byAction[row.action] += 1;
31259
+ }
31260
+ policyCounts = { total: policyRows.length, disabled, byAction };
31261
+ } catch (err) {
31262
+ if (!isSchemaAbsent(err)) throw err;
31263
+ }
31264
+ try {
31265
+ const agg = db.prepare(
31266
+ `SELECT count(*) AS n, min(f.first_detected_at) AS firstAt, max(f.first_detected_at) AS lastAt
31267
+ FROM inspection_findings f JOIN audit_events e ON e.id = f.audit_event_id
31268
+ WHERE e.event_type IN ${CAPTURE_EVENT_TYPES_SQL2}`
31269
+ ).get();
31270
+ findingsTotal = agg.n;
31271
+ findingsFirstAt = agg.firstAt;
31272
+ findingsLastAt = agg.lastAt;
31273
+ } catch (err) {
31274
+ if (!isSchemaAbsent(err)) throw err;
31275
+ }
31276
+ return currentReadout();
31277
+ } catch {
31278
+ return emptyReadout(true);
31279
+ } finally {
31280
+ try {
31281
+ db?.close();
31282
+ } catch {
31283
+ }
31284
+ }
31285
+ }
31286
+
31287
+ // ../../packages/plugin-runtime/src/attached/posture-store.ts
31288
+ import { randomUUID as randomUUID17 } from "crypto";
31289
+ import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
31290
+ import { join as join20 } from "path";
31291
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
31292
+ function createPostureStore(dir = settingsDir(), legacyDir) {
31293
+ const file2 = join20(dir, "posture-state.json");
31294
+ const legacyFile = legacyDir === void 0 ? null : join20(legacyDir, "posture-state.json");
31295
+ async function persist(state) {
31296
+ await ensureDataDir(dir);
31297
+ const tmp = `${file2}.${randomUUID17()}.tmp`;
31298
+ try {
31299
+ await writeFile3(tmp, JSON.stringify(state), { encoding: "utf8", mode: DATA_FILE_MODE });
31300
+ await publishByRename(tmp, file2);
31301
+ } catch (err) {
31302
+ await rm2(tmp, { force: true }).catch(() => void 0);
31303
+ throw err;
31304
+ }
31305
+ }
31306
+ async function readFrom(path) {
31307
+ let raw;
31308
+ try {
31309
+ raw = await readFile3(path, "utf8");
31310
+ } catch (err) {
31311
+ const code = err.code;
31312
+ if (code === "ENOENT" || code === "ENOTDIR") return null;
31313
+ throw err;
31314
+ }
31315
+ try {
31316
+ const parsed2 = JSON.parse(raw);
31317
+ if (typeof parsed2 === "object" && parsed2 !== null) {
31318
+ const record2 = parsed2;
31319
+ if (typeof record2.deviceId === "string" && UUID_RE.test(record2.deviceId)) {
31320
+ const stamp = typeof record2.lastAttemptedAtMs === "number" ? record2.lastAttemptedAtMs : typeof record2.lastReportedAtMs === "number" ? record2.lastReportedAtMs : 0;
31321
+ return { deviceId: record2.deviceId, lastAttemptedAtMs: stamp };
31322
+ }
31323
+ }
31324
+ } catch {
31325
+ }
31326
+ return null;
31327
+ }
31328
+ async function read() {
31329
+ const current = await readFrom(file2);
31330
+ if (current) return current;
31331
+ const legacy = legacyFile === null || legacyFile === file2 ? null : await readFrom(legacyFile).catch(() => null);
31332
+ if (legacy) {
31333
+ try {
31334
+ await persist(legacy);
31335
+ } catch {
31336
+ }
31337
+ return legacy;
31338
+ }
31339
+ const fresh = { deviceId: randomUUID17(), lastAttemptedAtMs: 0 };
31340
+ try {
31341
+ await ensureDataDir(dir);
31342
+ if (createOwnerOnlyFileSync(file2, JSON.stringify(fresh))) return fresh;
31343
+ } catch {
31344
+ return null;
31345
+ }
31346
+ const winner = await readFrom(file2).catch(() => null);
31347
+ if (winner) return winner;
31348
+ try {
31349
+ await persist(fresh);
31350
+ } catch {
31351
+ return null;
31352
+ }
31353
+ return fresh;
31354
+ }
31355
+ async function markAttempted(deviceId, atMs) {
31356
+ await persist({ deviceId, lastAttemptedAtMs: atMs });
31357
+ }
31358
+ return { read, markAttempted, file: file2 };
31359
+ }
31360
+
31361
+ // ../../packages/plugin-runtime/src/attached/sync-state.ts
31362
+ import { readFileSync as readFileSync13 } from "fs";
31363
+ import { join as join21 } from "path";
31364
+
31365
+ // ../../packages/plugin-runtime/src/attached/status.ts
31366
+ var REFUSAL_LINES = {
31367
+ unauthorized: "KEY REJECTED \u2014 re-attach with a valid plugin key",
31368
+ forbidden: "ACCESS REFUSED \u2014 key is valid but not permitted; ask your org admin"
31369
+ };
31370
+ var OUTCOME_LINES = {
31371
+ ok: "policy synced",
31372
+ "not-modified": "policy up to date",
31373
+ unauthorized: REFUSAL_LINES.unauthorized,
31374
+ forbidden: REFUSAL_LINES.forbidden,
31375
+ unreachable: "control plane unreachable at last attempt",
31376
+ "invalid-bundle": "control plane sent a policy bundle this build cannot read"
31377
+ };
31378
+
31379
+ // ../../packages/plugin-runtime/src/attached/sync-trigger.ts
31380
+ import { spawn } from "child_process";
31381
+ import { fileURLToPath as fileURLToPath2 } from "url";
31382
+ var SYNC_MARKER_NAME = "sync-last-attempt";
31383
+ var SYNC_SCRIPT_NAME = "sync.js";
31384
+ var SYNC_THROTTLE_MS = 15 * 60 * 1e3;
31385
+ function triggerPolicySync(config2, deps = {}) {
31386
+ try {
31387
+ if (!isAttached(config2.settings)) return;
31388
+ const connection = config2.settings.controlPlane;
31389
+ if (connection === void 0) return;
31390
+ if (!readControlPlaneCredentialState(config2.settingsDir, connection).usable) return;
31391
+ const isThrottled = deps.isThrottled ?? ((dir) => throttled(dir, SYNC_MARKER_NAME, SYNC_THROTTLE_MS));
31392
+ if (isThrottled(config2.dataDir)) return;
31393
+ const scriptPath = fileURLToPath2(deps.scriptUrl ?? new URL(SYNC_SCRIPT_NAME, import.meta.url));
31394
+ (deps.spawnChild ?? spawnDetached)(scriptPath);
31395
+ } catch {
31396
+ }
31397
+ }
31398
+ function spawnDetached(scriptPath) {
31399
+ const child = spawn(process.execPath, [scriptPath], { detached: true, stdio: "ignore" });
31400
+ child.on("error", () => {
31401
+ });
31402
+ child.unref();
31403
+ }
31404
+
31405
+ // ../../packages/plugin-runtime/src/attached/factory.ts
31406
+ import { hostname as hostname5 } from "os";
31407
+
31408
+ // ../../packages/plugin-runtime/src/standalone-gateway.ts
31409
+ import { randomUUID as randomUUID18 } from "crypto";
31410
+
31411
+ // ../../packages/plugin-runtime/src/recorder.ts
31412
+ var PLUGIN_RECORDER_BINARY = "plugin";
31413
+ function pluginRecordedBy(version2) {
31414
+ return `${PLUGIN_RECORDER_BINARY}@${version2}`;
31415
+ }
31416
+
31417
+ // ../../packages/plugin-runtime/src/standalone-gateway.ts
31418
+ var StandaloneDataGateway = class {
31419
+ db;
31420
+ // Kept for the fingerprint key lookup (exception.key lives beside the store).
31421
+ dataDir;
31422
+ // One notice per gateway — see warnRulesetDiscarded.
31423
+ warnedRulesetDiscarded = false;
31424
+ constructor(dataDir2, detections = [], meta3) {
31425
+ this.db = openLocalDatabase(dataDir2);
31426
+ this.dataDir = dataDir2;
29882
31427
  this.db.installedPacks.recordInventory(detections, meta3);
29883
31428
  }
29884
31429
  recordCapture(record2) {
@@ -29905,12 +31450,12 @@ var StandaloneDataGateway = class {
29905
31450
  // reconciler drops the whole pass and recovers it idempotently on the next read.
29906
31451
  recordLlmCalls(inputs) {
29907
31452
  if (inputs.length === 0) return Promise.resolve();
29908
- return new Promise((resolve, reject) => {
31453
+ return new Promise((resolve2, reject) => {
29909
31454
  try {
29910
31455
  this.db.auditEvents.runInTransaction(() => {
29911
31456
  for (const input of inputs) this.db.auditEvents.insertLlmCall(input);
29912
31457
  });
29913
- resolve();
31458
+ resolve2();
29914
31459
  } catch (err) {
29915
31460
  reject(err instanceof Error ? err : new Error(String(err)));
29916
31461
  }
@@ -29922,12 +31467,12 @@ var StandaloneDataGateway = class {
29922
31467
  // drops the whole pass and recovers it idempotently next time.
29923
31468
  recordToolCalls(inputs) {
29924
31469
  if (inputs.length === 0) return Promise.resolve();
29925
- return new Promise((resolve, reject) => {
31470
+ return new Promise((resolve2, reject) => {
29926
31471
  try {
29927
31472
  this.db.auditEvents.runInTransaction(() => {
29928
31473
  for (const input of inputs) this.writeToolCall(input);
29929
31474
  });
29930
- resolve();
31475
+ resolve2();
29931
31476
  } catch (err) {
29932
31477
  reject(err instanceof Error ? err : new Error(String(err)));
29933
31478
  }
@@ -30069,7 +31614,7 @@ var StandaloneDataGateway = class {
30069
31614
  const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
30070
31615
  const installed = this.installedScanRules();
30071
31616
  const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
30072
- id: randomUUID15(),
31617
+ id: randomUUID18(),
30073
31618
  scope: "global",
30074
31619
  target: { ruleId },
30075
31620
  action,
@@ -30223,15 +31768,61 @@ var StandaloneDataGateway = class {
30223
31768
  }
30224
31769
  };
30225
31770
 
31771
+ // ../../packages/plugin-runtime/src/attached/factory.ts
31772
+ function resolveGatewayForConfig(config2, meta3) {
31773
+ const local = new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
31774
+ try {
31775
+ if (!isAttached(config2.settings)) return local;
31776
+ const connection = config2.settings.controlPlane;
31777
+ if (connection === void 0) return local;
31778
+ const state = readControlPlaneCredentialState(config2.settingsDir, connection);
31779
+ if (!state.usable) return local;
31780
+ const client = createRemoteClient({
31781
+ endpoint: connection.endpoint,
31782
+ apiKey: state.credential.apiKey
31783
+ });
31784
+ const store = createPolicyStore(config2.dataDir);
31785
+ const postureStore = createPostureStore(config2.settingsDir, config2.dataDir);
31786
+ const forward = createForwardPolicy({ dir: config2.dataDir });
31787
+ return new AttachedDataGateway({
31788
+ local,
31789
+ client,
31790
+ dataDir: config2.dataDir,
31791
+ readCachedBundle: () => store.read().then((cached2) => cached2?.bundle ?? null),
31792
+ forward,
31793
+ posture: createPostureReporter({
31794
+ // THROUGH THE BREAKER, and wrapped HERE rather than around
31795
+ // `PostureReporter.send`. The reporter swallows every error by
31796
+ // contract, so a wrap outside it would hand `forward.run` a resolved
31797
+ // promise for a send that failed — recording a SUCCESS, clearing
31798
+ // `consecutiveFailures` and `lastFailure`, and telling `aka status` the
31799
+ // forward recovered when nothing did. Wrapping the raw client call puts
31800
+ // the breaker above the swallow, where it can see the truth.
31801
+ //
31802
+ // What it buys: once the breaker is open — the plane already confirmed
31803
+ // down by the gateway's own writes — this stops paying a request
31804
+ // timeout per throttle interval to re-learn it.
31805
+ report: (snapshot) => forward.run(() => client.reportStorePosture(snapshot)).then(() => void 0),
31806
+ store: postureStore,
31807
+ readStore: () => readStorePosture(config2.dbPath),
31808
+ hostname: () => hostname5(),
31809
+ now: () => Date.now()
31810
+ })
31811
+ });
31812
+ } catch {
31813
+ return local;
31814
+ }
31815
+ }
31816
+
30226
31817
  // ../../packages/plugin-runtime/src/resolve.ts
30227
- var standaloneGatewayFactory = (config2, meta3) => new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
30228
- var defaultGatewayFactory = standaloneGatewayFactory;
31818
+ var configuredGatewayFactory = (config2, meta3) => resolveGatewayForConfig(config2, meta3);
31819
+ var defaultGatewayFactory = configuredGatewayFactory;
30229
31820
  function resolveDataGateway(config2, meta3, gatewayFactory = defaultGatewayFactory) {
30230
31821
  return gatewayFactory(config2, meta3);
30231
31822
  }
30232
31823
 
30233
31824
  // ../../packages/plugin-runtime/src/handle-session-start.ts
30234
- import { randomUUID as randomUUID16 } from "crypto";
31825
+ import { randomUUID as randomUUID19 } from "crypto";
30235
31826
  var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
30236
31827
  async function handleSessionStart(input, config2 = loadConfig()) {
30237
31828
  const silent = { staleBinaryNotice: null };
@@ -30295,6 +31886,7 @@ async function handleSessionStart(input, config2 = loadConfig()) {
30295
31886
  } catch {
30296
31887
  }
30297
31888
  }
31889
+ triggerPolicySync(config2);
30298
31890
  if (input.harnessVersion !== void 0 && offersMaintenance(gateway, "staleBinaryNotice")) {
30299
31891
  return { staleBinaryNotice: gateway.staleBinaryNotice(input.harnessVersion) };
30300
31892
  }
@@ -30320,7 +31912,7 @@ async function recordConfigInventory(gateway, sessionId, cwd, homeDir) {
30320
31912
  }
30321
31913
  function buildConfigScanEvent(sessionId, scan2) {
30322
31914
  return {
30323
- id: randomUUID16(),
31915
+ id: randomUUID19(),
30324
31916
  eventType: "config_scan",
30325
31917
  startedAt: scan2.scannedAt,
30326
31918
  parentId: sessionId,
@@ -30369,9 +31961,9 @@ function buildSessionRoot(sessionId, input, ctx, resolved, provider, branch) {
30369
31961
  }
30370
31962
 
30371
31963
  // src/history/reconcile-trigger.ts
30372
- import { spawn } from "child_process";
30373
- import { dirname as dirname4, join as join16 } from "path";
30374
- import { fileURLToPath as fileURLToPath2 } from "url";
31964
+ import { spawn as spawn2 } from "child_process";
31965
+ import { dirname as dirname5, join as join23 } from "path";
31966
+ import { fileURLToPath as fileURLToPath3 } from "url";
30375
31967
 
30376
31968
  // src/history/tail.ts
30377
31969
  import { createHash as createHash5 } from "crypto";
@@ -30380,11 +31972,11 @@ import {
30380
31972
  fstatSync,
30381
31973
  mkdirSync as mkdirSync4,
30382
31974
  openSync as openSync2,
30383
- readFileSync as readFileSync10,
31975
+ readFileSync as readFileSync14,
30384
31976
  readSync,
30385
31977
  writeFileSync as writeFileSync7
30386
31978
  } from "fs";
30387
- import { join as join15 } from "path";
31979
+ import { join as join22 } from "path";
30388
31980
  var SAFE_SESSION_ID = /^[A-Za-z0-9._-]+$/;
30389
31981
  function safeSessionId(sessionId) {
30390
31982
  if (SAFE_SESSION_ID.test(sessionId) && sessionId !== "." && sessionId !== "..") {
@@ -30400,8 +31992,8 @@ function triggerReconcile(dataDir2, sessionId, transcriptPath) {
30400
31992
  try {
30401
31993
  const marker = `${RECONCILE_MARKER_PREFIX}-${safeSessionId(sessionId)}`;
30402
31994
  if (throttled(dataDir2, marker, RECONCILE_THROTTLE_MS)) return;
30403
- const here = dirname4(fileURLToPath2(import.meta.url));
30404
- const child = spawn(process.execPath, [join16(here, "reconcile.js"), sessionId, transcriptPath], {
31995
+ const here = dirname5(fileURLToPath3(import.meta.url));
31996
+ const child = spawn2(process.execPath, [join23(here, "reconcile.js"), sessionId, transcriptPath], {
30405
31997
  detached: true,
30406
31998
  stdio: "ignore"
30407
31999
  });
@@ -30412,17 +32004,17 @@ function triggerReconcile(dataDir2, sessionId, transcriptPath) {
30412
32004
 
30413
32005
  // src/protocol/marker.ts
30414
32006
  import { randomBytes as randomBytes4 } from "crypto";
30415
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync11, renameSync as renameSync5, writeFileSync as writeFileSync8 } from "fs";
30416
- import { join as join17 } from "path";
32007
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync15, renameSync as renameSync5, writeFileSync as writeFileSync8 } from "fs";
32008
+ import { join as join24 } from "path";
30417
32009
  var MARKER_FILE = "protocol-marker";
30418
32010
  function mintMarker() {
30419
32011
  return randomBytes4(8).toString("hex");
30420
32012
  }
30421
32013
  function sessionProtocolMarker(dataDir2, sessionId) {
30422
32014
  if (!sessionId) return mintMarker();
30423
- const path = join17(dataDir2, MARKER_FILE);
32015
+ const path = join24(dataDir2, MARKER_FILE);
30424
32016
  try {
30425
- const stored = JSON.parse(readFileSync11(path, "utf8"));
32017
+ const stored = JSON.parse(readFileSync15(path, "utf8"));
30426
32018
  if (stored.sessionId === sessionId && typeof stored.marker === "string" && /^[0-9a-f]{16}$/.test(stored.marker)) {
30427
32019
  return stored.marker;
30428
32020
  }
@@ -30431,7 +32023,7 @@ function sessionProtocolMarker(dataDir2, sessionId) {
30431
32023
  const marker = mintMarker();
30432
32024
  try {
30433
32025
  mkdirSync5(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
30434
- const tmp = join17(dataDir2, `${MARKER_FILE}.tmp`);
32026
+ const tmp = join24(dataDir2, `${MARKER_FILE}.tmp`);
30435
32027
  writeFileSync8(tmp, JSON.stringify({ sessionId, marker }), { mode: DATA_FILE_MODE });
30436
32028
  renameSync5(tmp, path);
30437
32029
  } catch {
@@ -30447,7 +32039,7 @@ function standingBrief(opts) {
30447
32039
 
30448
32040
  // src/hooks/shared.ts
30449
32041
  async function readStdin() {
30450
- return new Promise((resolve) => {
32042
+ return new Promise((resolve2) => {
30451
32043
  let data = "";
30452
32044
  let settled = false;
30453
32045
  const finish = () => {
@@ -30456,7 +32048,7 @@ async function readStdin() {
30456
32048
  clearTimeout(timer);
30457
32049
  process.stdin.removeListener("data", onData);
30458
32050
  process.stdin.removeListener("end", finish);
30459
- resolve(data);
32051
+ resolve2(data);
30460
32052
  };
30461
32053
  const onData = (chunk) => {
30462
32054
  data += chunk;
@@ -30470,8 +32062,8 @@ async function readStdin() {
30470
32062
  }
30471
32063
  function parseJson(raw) {
30472
32064
  try {
30473
- const parsed = JSON.parse(raw);
30474
- return typeof parsed === "object" && parsed !== null ? parsed : null;
32065
+ const parsed2 = JSON.parse(raw);
32066
+ return typeof parsed2 === "object" && parsed2 !== null ? parsed2 : null;
30475
32067
  } catch {
30476
32068
  return null;
30477
32069
  }
@@ -30481,24 +32073,83 @@ function getString(record2, key) {
30481
32073
  return typeof value === "string" ? value : void 0;
30482
32074
  }
30483
32075
  function emit(output) {
30484
- return new Promise((resolve) => {
32076
+ return new Promise((resolve2) => {
30485
32077
  let settled = false;
30486
32078
  const finish = () => {
30487
32079
  if (settled) return;
30488
32080
  settled = true;
30489
- resolve();
32081
+ resolve2();
30490
32082
  };
30491
32083
  process.stdout.on("error", finish);
30492
32084
  process.stdout.write(JSON.stringify(output), finish);
30493
32085
  });
30494
32086
  }
30495
32087
 
32088
+ // src/hooks/store-health.ts
32089
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "fs";
32090
+ import { dirname as dirname6, join as join25 } from "path";
32091
+ var STORE_REDIRECT_MARKER = "store-redirect-last-session";
32092
+ function markerDirs(dataDir2) {
32093
+ return [dataDir2, dirname6(dataDir2)];
32094
+ }
32095
+ function alreadyClaimed(dirs, marker, sessionId) {
32096
+ return dirs.some((dir) => {
32097
+ try {
32098
+ return readFileSync16(join25(dir, marker), "utf8") === sessionId;
32099
+ } catch {
32100
+ return false;
32101
+ }
32102
+ });
32103
+ }
32104
+ function recordClaim(dirs, marker, sessionId) {
32105
+ for (const dir of dirs) {
32106
+ try {
32107
+ mkdirSync6(dir, { recursive: true, mode: DATA_DIR_MODE });
32108
+ writeFileSync9(join25(dir, marker), sessionId, { mode: DATA_FILE_MODE });
32109
+ return;
32110
+ } catch {
32111
+ }
32112
+ }
32113
+ }
32114
+ function storeRedirectedMessage(paths, platform2 = process.platform) {
32115
+ const where = paths.map(({ path, target, holds, missing, mode }) => {
32116
+ if (missing) return `${path} -> ${target} (which does not exist; ${holds} cannot land there)`;
32117
+ const loose = mode !== void 0 && (mode & 63) !== 0 ? ", NOT owner-only" : "";
32118
+ const inherited = mode === void 0 ? "" : ` (${formatMode(mode)}${loose})`;
32119
+ return `${path} -> ${target}${inherited}, holding ${holds}`;
32120
+ }).join("; ");
32121
+ const subject = paths.length === 1 ? "a store path is a symlink" : `${String(paths.length)} store paths are symlinks`;
32122
+ const anyResolves = paths.some(({ missing }) => !missing);
32123
+ const lead = anyResolves ? `${subject}, so AKA is writing into the target instead: ${where}. ` : `${subject} resolving nowhere, so AKA cannot write there: ${where}. `;
32124
+ const kept = anyResolves && platform2 !== "win32" ? "Permissions are never changed through a symlink, so the store keeps whatever the target already had. " : "";
32125
+ return `[aka] ${lead}${kept}If you did not create that link, treat it as untrusted and run \`aka init\` for the full report.
32126
+ `;
32127
+ }
32128
+ function formatMode(mode) {
32129
+ return `0${mode.toString(8).padStart(3, "0")}`;
32130
+ }
32131
+ function warnIfStoreRedirected(config2, sessionId, write = (message2) => void process.stderr.write(message2)) {
32132
+ try {
32133
+ const paths = symlinkedStorePaths(dirname6(config2.dataDir));
32134
+ if (paths.length === 0) return;
32135
+ if (!sessionId) {
32136
+ write(storeRedirectedMessage(paths));
32137
+ return;
32138
+ }
32139
+ const dirs = markerDirs(config2.dataDir);
32140
+ if (alreadyClaimed(dirs, STORE_REDIRECT_MARKER, sessionId)) return;
32141
+ write(storeRedirectedMessage(paths));
32142
+ recordClaim(dirs, STORE_REDIRECT_MARKER, sessionId);
32143
+ } catch {
32144
+ }
32145
+ }
32146
+
30496
32147
  // src/hooks/session-start.ts
30497
32148
  function harnessVersion() {
30498
32149
  const manifestPath = process.argv[2];
30499
32150
  if (!manifestPath) return void 0;
30500
32151
  try {
30501
- const manifest = JSON.parse(readFileSync12(manifestPath, "utf8"));
32152
+ const manifest = JSON.parse(readFileSync17(manifestPath, "utf8"));
30502
32153
  return typeof manifest.version === "string" ? manifest.version : void 0;
30503
32154
  } catch {
30504
32155
  return void 0;
@@ -30524,6 +32175,7 @@ async function main() {
30524
32175
  }
30525
32176
  const transcriptPath = input ? getString(input, "transcript_path") : void 0;
30526
32177
  const config2 = loadConfig();
32178
+ warnIfStoreRedirected(config2, sessionId);
30527
32179
  if (sessionId !== void 0 && transcriptPath !== void 0) {
30528
32180
  triggerReconcile(config2.dataDir, sessionId, transcriptPath);
30529
32181
  }