@atbash/sdk 0.19.2-dev.0 → 0.20.0-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -166,12 +166,12 @@ var AtbashAPIError = class extends Error {
166
166
  * ran does not carry. The path is the fact that settles it.
167
167
  */
168
168
  path;
169
- constructor(status, body, statusText = "", endpoint = DEFAULT_ENDPOINT, path5) {
170
- super(enrich(status, body, statusText, endpoint, path5));
169
+ constructor(status, body, statusText = "", endpoint = DEFAULT_ENDPOINT, path4) {
170
+ super(enrich(status, body, statusText, endpoint, path4));
171
171
  this.name = "AtbashAPIError";
172
172
  this.status = status;
173
173
  this.body = body;
174
- this.path = path5;
174
+ this.path = path4;
175
175
  }
176
176
  };
177
177
  var SignatureVerificationError = class extends Error {
@@ -180,9 +180,18 @@ var SignatureVerificationError = class extends Error {
180
180
  this.name = "SignatureVerificationError";
181
181
  }
182
182
  };
183
- function enrich(status, body, statusText, endpoint, path5) {
183
+ var JudgeRefusalError = class extends Error {
184
+ constructor(message) {
185
+ super(message);
186
+ this.name = "JudgeRefusalError";
187
+ }
188
+ };
189
+ function isRefusalStatus(status) {
190
+ return status >= 400 && status < 500 && status !== 408;
191
+ }
192
+ function enrich(status, body, statusText, endpoint, path4) {
184
193
  const dashboard = endpoint.replace(/\/+$/, "") || DEFAULT_ENDPOINT;
185
- let msg = `API error ${status}${path5 ? ` from ${path5}` : ""}: ${body || statusText}`;
194
+ let msg = `API error ${status}${path4 ? ` from ${path4}` : ""}: ${body || statusText}`;
186
195
  const lowered = body.toLowerCase();
187
196
  if (lowered.includes("agent not registered")) {
188
197
  msg += `
@@ -203,6 +212,61 @@ function enrich(status, body, statusText, endpoint, path5) {
203
212
  return msg;
204
213
  }
205
214
 
215
+ // src-ts/jail-hold.ts
216
+ var DEFAULTS = { ceilingMs: 3e4, pollMs: 1e3 };
217
+ var holds = /* @__PURE__ */ new Map();
218
+ var blockedAt = /* @__PURE__ */ new Map();
219
+ var BLOCK_MEMORY_MS = 10 * 6e4;
220
+ var config = { ...DEFAULTS };
221
+ function isJailHeld(pubkey) {
222
+ return holds.has(pubkey);
223
+ }
224
+ function recordBlock(pubkey) {
225
+ const now = Date.now();
226
+ for (const [key, at] of blockedAt) if (now - at > BLOCK_MEMORY_MS) blockedAt.delete(key);
227
+ blockedAt.set(pubkey, now);
228
+ }
229
+ function blockedSince(pubkey, startedAt) {
230
+ const at = blockedAt.get(pubkey);
231
+ return at !== void 0 && startedAt <= at;
232
+ }
233
+ function holdJail(pubkey, confirmJailed, onGiveUp) {
234
+ if (holds.has(pubkey)) return;
235
+ const hold = { timer: null };
236
+ holds.set(pubkey, hold);
237
+ const deadline = Date.now() + config.ceilingMs;
238
+ const tick = async () => {
239
+ if (holds.get(pubkey) !== hold) return;
240
+ let jailed = false;
241
+ try {
242
+ jailed = await confirmJailed();
243
+ } catch {
244
+ }
245
+ if (holds.get(pubkey) !== hold) return;
246
+ if (jailed) {
247
+ holds.delete(pubkey);
248
+ return;
249
+ }
250
+ if (Date.now() >= deadline) {
251
+ holds.delete(pubkey);
252
+ onGiveUp?.();
253
+ return;
254
+ }
255
+ const timer = setTimeout(() => void tick(), config.pollMs);
256
+ timer.unref?.();
257
+ hold.timer = timer;
258
+ };
259
+ void tick();
260
+ }
261
+ function releaseJailHolds() {
262
+ for (const hold of holds.values()) if (hold.timer) clearTimeout(hold.timer);
263
+ holds.clear();
264
+ blockedAt.clear();
265
+ }
266
+ function configureJailHold(next) {
267
+ config = { ...DEFAULTS, ...next };
268
+ }
269
+
206
270
  // src-ts/http/client.ts
207
271
  var HttpClient = class {
208
272
  baseUrl;
@@ -211,8 +275,8 @@ var HttpClient = class {
211
275
  this.baseUrl = baseUrl.replace(/\/+$/, "");
212
276
  this.timeoutMs = timeoutMs;
213
277
  }
214
- buildUrl(path5, query) {
215
- const url = new URL(this.baseUrl + path5);
278
+ buildUrl(path4, query) {
279
+ const url = new URL(this.baseUrl + path4);
216
280
  if (query) {
217
281
  for (const [k, v] of Object.entries(query)) {
218
282
  if (v !== void 0 && v !== null && v !== "") {
@@ -222,14 +286,14 @@ var HttpClient = class {
222
286
  }
223
287
  return url.toString();
224
288
  }
225
- async get(path5, query, headers) {
226
- return this.fetch(this.buildUrl(path5, query), {
289
+ async get(path4, query, headers) {
290
+ return this.fetch(this.buildUrl(path4, query), {
227
291
  method: "GET",
228
292
  ...headers && { headers }
229
293
  });
230
294
  }
231
- async post(path5, body, headers) {
232
- return this.fetch(this.buildUrl(path5), {
295
+ async post(path4, body, headers) {
296
+ return this.fetch(this.buildUrl(path4), {
233
297
  method: "POST",
234
298
  headers: { "Content-Type": "application/json", ...headers },
235
299
  body: JSON.stringify(body)
@@ -553,7 +617,7 @@ import {
553
617
  import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
554
618
  import { BatchLogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs";
555
619
  import { createHash } from "crypto";
556
- var SDK_VERSION = true ? "0.19.2-dev.0" : "";
620
+ var SDK_VERSION = true ? "0.20.0-dev.0" : "";
557
621
  var SDK_LANGUAGE = "node";
558
622
  function sdkResourceAttributes() {
559
623
  const attrs = {
@@ -584,8 +648,8 @@ function isTelemetryOptedOut() {
584
648
  const filePath = join2(home2, ".config", "atbash", "telemetry.json");
585
649
  const raw = readFileSync2(filePath, "utf-8").trim();
586
650
  if (!raw) return false;
587
- const config = JSON.parse(raw);
588
- return config.enabled === false;
651
+ const config2 = JSON.parse(raw);
652
+ return config2.enabled === false;
589
653
  } catch {
590
654
  return false;
591
655
  }
@@ -630,26 +694,26 @@ function customCollector() {
630
694
  }
631
695
  return { url: url.replace(/\/+$/, ""), headers: () => headers };
632
696
  }
633
- function setupTelemetry(config) {
634
- if (!config.enabled) return;
697
+ function setupTelemetry(config2) {
698
+ if (!config2.enabled) return;
635
699
  if (isTelemetryOptedOut()) return;
636
- if (config.source && !sourceExplicitlySet && SOURCE_SHAPE.test(config.source)) {
637
- defaultSource = config.source;
700
+ if (config2.source && !sourceExplicitlySet && SOURCE_SHAPE.test(config2.source)) {
701
+ defaultSource = config2.source;
638
702
  sourceExplicitlySet = true;
639
703
  }
640
- if (config.exportIntervalMs !== void 0 && requestedExportIntervalMs === void 0) {
641
- requestedExportIntervalMs = config.exportIntervalMs;
704
+ if (config2.exportIntervalMs !== void 0 && requestedExportIntervalMs === void 0) {
705
+ requestedExportIntervalMs = config2.exportIntervalMs;
642
706
  }
643
707
  if (meterProvider) return;
644
708
  const collector = customCollector();
645
- if (!collector && (!config.endpoint || !config.getAuthHeaders)) return;
709
+ if (!collector && (!config2.endpoint || !config2.getAuthHeaders)) return;
646
710
  if (!collector && /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)(:|\/|$)/i.test(
647
- config.endpoint
711
+ config2.endpoint
648
712
  ))
649
713
  return;
650
714
  const destination = collector ?? {
651
- url: `${config.endpoint.replace(/\/+$/, "")}/api/telemetry`,
652
- headers: () => config.getAuthHeaders()
715
+ url: `${config2.endpoint.replace(/\/+$/, "")}/api/telemetry`,
716
+ headers: () => config2.getAuthHeaders()
653
717
  };
654
718
  const proxyUrl = destination.url;
655
719
  const exporter = new OTLPMetricExporter({
@@ -684,7 +748,7 @@ function setupTelemetry(config) {
684
748
  description: "SDK function execution duration",
685
749
  unit: "ms"
686
750
  });
687
- counterHasher = config.hashAgentKey ?? defaultAgentKeyHasher;
751
+ counterHasher = config2.hashAgentKey ?? defaultAgentKeyHasher;
688
752
  const logExporter = new OTLPLogExporter({
689
753
  // A real collector takes OTLP at its own signal paths; the Atbash proxy
690
754
  // takes a query parameter, because it is one authenticated route.
@@ -703,8 +767,8 @@ function setupTelemetry(config) {
703
767
  });
704
768
  }
705
769
  configureEvents(loggerProvider.getLogger("atbash-sdk"), {
706
- source: sourceOrDefault(config.source),
707
- hashAgentKey: config.hashAgentKey ?? defaultAgentKeyHasher,
770
+ source: sourceOrDefault(config2.source),
771
+ hashAgentKey: config2.hashAgentKey ?? defaultAgentKeyHasher,
708
772
  version: SDK_VERSION
709
773
  });
710
774
  }
@@ -782,13 +846,13 @@ function loadUserConfig() {
782
846
  return {};
783
847
  }
784
848
  }
785
- function saveUserConfig(config) {
849
+ function saveUserConfig(config2) {
786
850
  const dir = getConfigDir();
787
851
  if (!existsSync2(dir)) {
788
852
  mkdirSync(dir, { recursive: true, mode: 448 });
789
853
  }
790
854
  const filePath = getConfigPath();
791
- writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", {
855
+ writeFileSync(filePath, JSON.stringify(config2, null, 2) + "\n", {
792
856
  mode: 384
793
857
  });
794
858
  chmodSync(filePath, 384);
@@ -823,6 +887,7 @@ function forcedChainNetwork(flagValue) {
823
887
  }
824
888
 
825
889
  // src-ts/client.ts
890
+ var AGENT_NOT_REGISTERED = "Agent not registered. Onboard the agent at the dashboard before submitting actions.";
826
891
  function generateToolCallId() {
827
892
  return `tc-${Date.now()}-${randomHex(4)}`;
828
893
  }
@@ -1171,6 +1236,15 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
1171
1236
  * return the signed tx hex. The server broadcasts to chain.
1172
1237
  */
1173
1238
  async logToolCall(action, context = "", options = {}) {
1239
+ return this._logToolCall(action, context, options, false);
1240
+ }
1241
+ /**
1242
+ * `raisePreflightErrors` (the judge path, like Python's
1243
+ * `raise_preflight_errors`): a failed registration check throws instead of
1244
+ * becoming `{ success: false }`, so the caller can tell Atbash's "no"
1245
+ * (not registered, or a 4xx) from an outage.
1246
+ */
1247
+ async _logToolCall(action, context, options, raisePreflightErrors) {
1174
1248
  const start = performance.now();
1175
1249
  recordCall("logToolCall", void 0, this.auth.pubkey);
1176
1250
  let exists;
@@ -1180,18 +1254,20 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
1180
1254
  });
1181
1255
  } catch (err) {
1182
1256
  recordDuration("logToolCall", performance.now() - start, "error");
1257
+ if (raisePreflightErrors) throw err;
1183
1258
  return { success: false, toolCallId: null, error: errorMessage(err) };
1184
1259
  }
1185
1260
  if (!exists) {
1186
1261
  recordDuration("logToolCall", performance.now() - start, "error");
1262
+ if (raisePreflightErrors) throw new JudgeRefusalError(AGENT_NOT_REGISTERED);
1187
1263
  return {
1188
1264
  success: false,
1189
1265
  toolCallId: null,
1190
- error: "Agent not registered. Onboard the agent at the dashboard before submitting actions."
1266
+ error: AGENT_NOT_REGISTERED
1191
1267
  };
1192
1268
  }
1193
1269
  const toolCallId = generateToolCallId();
1194
- const brid = options.chainOpts?.blockchainRid || options.chainOpts?.network ? this.bridFromChainOpts(options.chainOpts) : await this.defaultOrgBrid() ?? this.blockchainRid;
1270
+ const brid = await this.callBrid(options.chainOpts);
1195
1271
  const orgKey = options.orgEncryptionPubKey ?? this.orgEncryptionPubKey ?? this._orgKeyFromChain;
1196
1272
  try {
1197
1273
  const signedHex = orgKey ? signEncryptedToolCall(
@@ -1241,6 +1317,8 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
1241
1317
  if (!action?.trim()) {
1242
1318
  throw new Error("action is required and cannot be empty.");
1243
1319
  }
1320
+ if (isJailHeld(this.auth.pubkey)) return this.localJailBlock();
1321
+ const startedAt = Date.now();
1244
1322
  let chainOpts = options.chainOpts;
1245
1323
  if (options.orgName && this._explicitChain) {
1246
1324
  chainOpts = options.chainOpts ?? { network: this._defaultChain.network };
@@ -1265,12 +1343,13 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
1265
1343
  }
1266
1344
  }
1267
1345
  }
1268
- const brid = this.bridFromChainOpts(chainOpts);
1269
- const logResult = await this.logToolCall(action, context, {
1270
- toolName: options.toolName,
1271
- toolArgsJson: options.toolArgsJson,
1272
- chainOpts
1273
- });
1346
+ const brid = await this.callBrid(chainOpts);
1347
+ const logResult = await this._logToolCall(
1348
+ action,
1349
+ context,
1350
+ { toolName: options.toolName, toolArgsJson: options.toolArgsJson, chainOpts },
1351
+ true
1352
+ );
1274
1353
  if (!logResult.success || !logResult.toolCallId || !logResult.signedHex) {
1275
1354
  throw new Error(logResult.error || "Failed to sign log_tool_call");
1276
1355
  }
@@ -1307,10 +1386,16 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
1307
1386
  if (verifyPubKey !== void 0) {
1308
1387
  verifyResponseSignature(bodyBytes, resp, verifyPubKey);
1309
1388
  }
1310
- const data = parseJson(bodyBytes);
1389
+ const parsed = tryParseJson(bodyBytes.toString("utf-8"));
1390
+ if (!isRecord2(parsed)) {
1391
+ throw new JudgeRefusalError(
1392
+ "The judge's reply could not be read, so the action was not approved."
1393
+ );
1394
+ }
1395
+ const data = parsed;
1311
1396
  const rawScore = data.score;
1312
1397
  const score = typeof rawScore === "number" && Number.isInteger(rawScore) && rawScore >= 1 && rawScore <= 10 ? rawScore : void 0;
1313
- return {
1398
+ const result = {
1314
1399
  verdict: normalizeVerdict(data.verdict),
1315
1400
  allow: this.executable(data),
1316
1401
  actionType: normalizeActionType(data.action_type),
@@ -1326,6 +1411,45 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
1326
1411
  score,
1327
1412
  refusal: explicitRefusal(data)
1328
1413
  };
1414
+ return this.applyJailHold(result, startedAt);
1415
+ }
1416
+ /**
1417
+ * Start a local hold on an enforced, on-chain BLOCK (the verdict that jails).
1418
+ * Refuse any other result that lands while the agent is held, or whose
1419
+ * request was already in flight when the block arrived: the server judged it
1420
+ * on pre-jail state.
1421
+ */
1422
+ applyJailHold(result, startedAt) {
1423
+ const pubkey = this.auth.pubkey;
1424
+ if (result.verdict === "BLOCK" && result.enforced && result.onChain) {
1425
+ recordBlock(pubkey);
1426
+ holdJail(
1427
+ pubkey,
1428
+ () => this.getAgentPolicy(pubkey).then((p) => p.isJailed),
1429
+ () => this.logger.warn?.("[atbash] local jail hold released: the chain did not confirm the jail in time")
1430
+ );
1431
+ return result;
1432
+ }
1433
+ return isJailHeld(pubkey) || blockedSince(pubkey, startedAt) ? this.localJailBlock(result.toolCallId) : result;
1434
+ }
1435
+ /** The server's jailed refusal, produced locally while the agent is held. */
1436
+ localJailBlock(toolCallId = generateToolCallId()) {
1437
+ return {
1438
+ verdict: "BLOCK",
1439
+ allow: false,
1440
+ actionType: "block",
1441
+ reason: "Agent is jailed. All actions are blocked until the organization owner unjails this agent.",
1442
+ confidence: 1,
1443
+ provider: "policy",
1444
+ latencyMs: 0,
1445
+ toolCallId,
1446
+ onChain: false,
1447
+ enforced: true,
1448
+ enforcementMode: "enforce",
1449
+ status: "",
1450
+ score: void 0,
1451
+ refusal: "BLOCK"
1452
+ };
1329
1453
  }
1330
1454
  /* ── audit_tool_call (redact → judge → decision) ───────────────────────── */
1331
1455
  /**
@@ -1377,6 +1501,9 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
1377
1501
  reason: `The judge's reply failed its signature check (${errorMessage(err)}), so it was not trusted.`
1378
1502
  };
1379
1503
  }
1504
+ if (err instanceof JudgeRefusalError || err instanceof AtbashAPIError && isRefusalStatus(err.status)) {
1505
+ return { ...this.failJudge(errorMessage(err), err), allow: false };
1506
+ }
1380
1507
  return this.failJudge(errorMessage(err), err);
1381
1508
  }
1382
1509
  }
@@ -1406,6 +1533,15 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
1406
1533
  * never overrides a refusal.
1407
1534
  */
1408
1535
  decide(result) {
1536
+ const inner = this.decideInner(result);
1537
+ if (!result.enforcementMode) return inner;
1538
+ return {
1539
+ ...inner,
1540
+ enforced: result.enforced,
1541
+ enforcementMode: result.enforcementMode
1542
+ };
1543
+ }
1544
+ decideInner(result) {
1409
1545
  const toolCallId = result.toolCallId;
1410
1546
  const deny = (verdict, reason) => ({
1411
1547
  allow: false,
@@ -1519,17 +1655,17 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
1519
1655
  /**
1520
1656
  * Return the current status of a previously submitted judgment.
1521
1657
  *
1522
- * `chainOpts` names which chain the judgment was signed against. The
1523
- * server's GET /api/v1/judge routes to that chain when the SDK sends
1524
- * a `brid` query param; without it, the server falls back to public.
1525
- * Callers on the private chain must pass a `chainOpts` (or configure
1526
- * the client on the private chain) — otherwise polling a POSTed
1527
- * judgment on the private chain 404s at the server.
1658
+ * `chainOpts` names which chain the judgment was signed against; the SDK
1659
+ * sends it as the `brid` query param the server routes by. Without it the
1660
+ * read uses the same chain `judgeAction` signs for (see `callBrid`): the
1661
+ * client's org chain when it has an `orgName`, else the client default.
1662
+ * A judgment signed with an explicit per-call `chainOpts` (or a per-call
1663
+ * `orgName` on another chain) must be polled with that same `chainOpts`.
1528
1664
  */
1529
1665
  async getJudgmentStatus(judgmentId, agentPubkey, chainOpts) {
1530
1666
  const pk = agentPubkey ?? this.auth.pubkey;
1531
- const brid = this.bridFromChainOpts(chainOpts);
1532
1667
  return this.track("getJudgmentStatus", pk, async () => {
1668
+ const brid = await this.callBrid(chainOpts);
1533
1669
  const resp = await this.http.get(
1534
1670
  "/api/v1/judge",
1535
1671
  { tool_call_id: judgmentId, agent_pubkey: pk, brid },
@@ -1867,6 +2003,18 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
1867
2003
  if (chainOpts?.network === "public") return PUBLIC_CHAIN.blockchainRid;
1868
2004
  return this.blockchainRid;
1869
2005
  }
2006
+ /**
2007
+ * The chain one call's transactions are signed for: the per-call override
2008
+ * when given, otherwise the client's org, otherwise the client default.
2009
+ * log_tool_call, judge_action and the judgment-status read all use this, so
2010
+ * a request never pairs transactions signed for two different chains.
2011
+ */
2012
+ async callBrid(chainOpts) {
2013
+ if (chainOpts?.blockchainRid || chainOpts?.network) {
2014
+ return this.bridFromChainOpts(chainOpts);
2015
+ }
2016
+ return await this.defaultOrgBrid() ?? this.blockchainRid;
2017
+ }
1870
2018
  /**
1871
2019
  * Resolve the dashboard chain used by agent metadata/policy reads.
1872
2020
  * Explicit per-call network overrides win; otherwise use the supplied org
@@ -1912,8 +2060,8 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
1912
2060
  async riskEnginePost(body, network, brid) {
1913
2061
  let resp;
1914
2062
  try {
1915
- const path5 = network ? `/api/risk-engine?network=${encodeURIComponent(network)}` : "/api/risk-engine";
1916
- resp = await this.http.post(path5, body, this.authHeaders(brid));
2063
+ const path4 = network ? `/api/risk-engine?network=${encodeURIComponent(network)}` : "/api/risk-engine";
2064
+ resp = await this.http.post(path4, body, this.authHeaders(brid));
1917
2065
  } catch (err) {
1918
2066
  throw this.transportError(err);
1919
2067
  }
@@ -2174,10 +2322,6 @@ function tryParseJson(text) {
2174
2322
  return void 0;
2175
2323
  }
2176
2324
  }
2177
- function parseJson(bytes) {
2178
- const parsed = tryParseJson(bytes.toString("utf-8"));
2179
- return parsed === void 0 ? {} : parsed;
2180
- }
2181
2325
  function requestPath(url) {
2182
2326
  if (!url) return void 0;
2183
2327
  try {
@@ -2456,10 +2600,10 @@ function resolveBlockchainRid(chainOpts) {
2456
2600
  'chainOpts.nodeUrls no longer selects a chain on its own \u2014 memory reaches the chain through the Atbash service, not a node. Set `blockchainRid`, or `network: "public" | "private"`.'
2457
2601
  );
2458
2602
  }
2459
- const config = chainForNetwork(
2603
+ const config2 = chainForNetwork(
2460
2604
  chainOpts?.network ?? forcedChainNetwork() ?? "private"
2461
2605
  );
2462
- return config.blockchainRid;
2606
+ return config2.blockchainRid;
2463
2607
  }
2464
2608
  async function commitMemoryVersion(plaintext, auth, opts) {
2465
2609
  const key = await deriveMemoryKey(auth.privkey);
@@ -2685,7 +2829,34 @@ function classifyMemoryWrite(event, ctx, opts = {}) {
2685
2829
  // src-ts/memory/guard.ts
2686
2830
  import { promises as fs } from "fs";
2687
2831
  import os from "os";
2688
- import path from "path";
2832
+
2833
+ // src-ts/memory/slot.ts
2834
+ import nodePath from "path";
2835
+ function asciiLower(s) {
2836
+ return s.replace(/[A-Z]/g, (c) => String.fromCharCode(c.charCodeAt(0) | 32));
2837
+ }
2838
+ function spelledAs(target, base, p = nodePath) {
2839
+ const resolved = p.resolve(base);
2840
+ if (p.sep !== "\\") return resolved;
2841
+ const prefix = resolved.replace(/[\\/]+$/, "");
2842
+ const t = asciiLower(target);
2843
+ const b = asciiLower(prefix);
2844
+ if (t === b || t.startsWith(b + "\\") || t.startsWith(b + "/")) {
2845
+ return target.slice(0, prefix.length);
2846
+ }
2847
+ return resolved;
2848
+ }
2849
+ function memorySlotOf(target, home2, root, extraHomes, p = nodePath) {
2850
+ const resolved = p.resolve(target);
2851
+ return native.memorySlotForPath(
2852
+ resolved,
2853
+ home2 ? spelledAs(resolved, home2, p) : "",
2854
+ root ? spelledAs(resolved, root, p) : void 0,
2855
+ extraHomes
2856
+ );
2857
+ }
2858
+
2859
+ // src-ts/memory/guard.ts
2689
2860
  function emitDebugProbe(event, ctx, memEntry, logger2) {
2690
2861
  if (!logger2?.info) return;
2691
2862
  const ev = event ?? {};
@@ -2711,7 +2882,7 @@ function emitDebugProbe(event, ctx, memEntry, logger2) {
2711
2882
  }
2712
2883
  function memorySlotFor(key, root, extraHomes) {
2713
2884
  const home2 = process.env.HOME || os.homedir() || "";
2714
- return native.memorySlotForPath(path.resolve(key), home2, root, extraHomes);
2885
+ return memorySlotOf(key, home2, root, extraHomes);
2715
2886
  }
2716
2887
  async function resultingDocument(memEntry, event, ctx) {
2717
2888
  const edits = native.extractMemoryEdits(event, ctx);
@@ -2914,7 +3085,7 @@ async function syncLocalMemory(auth, pointer, opts = {}) {
2914
3085
 
2915
3086
  // src-ts/memory/pointer-store.ts
2916
3087
  import { promises as fs2 } from "fs";
2917
- import path2 from "path";
3088
+ import path from "path";
2918
3089
  var VERSION = 2;
2919
3090
  var ZERO = { activeId: null, checkedAt: 0 };
2920
3091
  var PointerStore = class {
@@ -2967,19 +3138,19 @@ var PointerStore = class {
2967
3138
  this.cache = { version: VERSION, agents: {} };
2968
3139
  }
2969
3140
  async persist(file) {
2970
- await fs2.mkdir(path2.dirname(this.filePath), { recursive: true });
3141
+ await fs2.mkdir(path.dirname(this.filePath), { recursive: true });
2971
3142
  const tmp = `${this.filePath}.${process.pid}.tmp`;
2972
3143
  await fs2.writeFile(tmp, JSON.stringify(file, null, 2), "utf8");
2973
3144
  await fs2.rename(tmp, this.filePath);
2974
3145
  }
2975
3146
  };
2976
3147
  function defaultPointerPath(workspaceDir = process.cwd()) {
2977
- return path2.join(workspaceDir, ".atbash", "memory-pointer.json");
3148
+ return path.join(workspaceDir, ".atbash", "memory-pointer.json");
2978
3149
  }
2979
3150
 
2980
3151
  // src-ts/memory/file-logger.ts
2981
3152
  import { promises as fs3 } from "fs";
2982
- import path3 from "path";
3153
+ import path2 from "path";
2983
3154
  function formatMeta(meta) {
2984
3155
  if (!meta || Object.keys(meta).length === 0) return "";
2985
3156
  try {
@@ -2992,7 +3163,7 @@ function createFileLogger(filePath, upstream) {
2992
3163
  let queue = Promise.resolve();
2993
3164
  let writeError;
2994
3165
  async function ensureDir() {
2995
- await fs3.mkdir(path3.dirname(filePath), { recursive: true });
3166
+ await fs3.mkdir(path2.dirname(filePath), { recursive: true });
2996
3167
  }
2997
3168
  function append(level, message, meta) {
2998
3169
  const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${message}${formatMeta(meta)}
@@ -3018,7 +3189,7 @@ function createFileLogger(filePath, upstream) {
3018
3189
  };
3019
3190
  }
3020
3191
  function defaultPluginLogPath(workspaceDir = process.cwd()) {
3021
- return path3.join(workspaceDir, ".atbash", "plugin.log");
3192
+ return path2.join(workspaceDir, ".atbash", "plugin.log");
3022
3193
  }
3023
3194
 
3024
3195
  // src-ts/memory/read-classifier.ts
@@ -3034,14 +3205,14 @@ function classifyMemoryRead(event, ctx, opts = {}) {
3034
3205
  // src-ts/memory/guard-manager.ts
3035
3206
  import { promises as fs4 } from "fs";
3036
3207
  import os2 from "os";
3037
- import path4 from "path";
3208
+ import path3 from "path";
3038
3209
  var DEFAULT_SYNC_TTL_MS = 3e4;
3039
3210
  var MemoryGuardManager = class {
3040
3211
  constructor(opts) {
3041
3212
  this.opts = opts;
3042
3213
  const workspaceDir = opts.workspaceDir;
3043
3214
  this.workspaceDir = workspaceDir;
3044
- this.memoryFilePath = opts.memoryFilePath ?? path4.join(workspaceDir, "MEMORY.md");
3215
+ this.memoryFilePath = opts.memoryFilePath ?? path3.join(workspaceDir, "MEMORY.md");
3045
3216
  this.pointerStore = new PointerStore(opts.pointerFilePath ?? defaultPointerPath(workspaceDir));
3046
3217
  this.logger = createFileLogger(
3047
3218
  opts.logFilePath ?? defaultPluginLogPath(workspaceDir),
@@ -3256,7 +3427,7 @@ var MemoryGuardManager = class {
3256
3427
  */
3257
3428
  async locate(slot, roots) {
3258
3429
  for (const root of roots) {
3259
- const candidate = path4.join(root, slot);
3430
+ const candidate = path3.join(root, slot);
3260
3431
  try {
3261
3432
  await fs4.access(candidate);
3262
3433
  return candidate;
@@ -3271,7 +3442,7 @@ var MemoryGuardManager = class {
3271
3442
  /** Roots a chain slot may be joined to, most specific first. */
3272
3443
  async memoryRoots() {
3273
3444
  const roots = [];
3274
- if (this.opts.memoryRoot) roots.push(path4.resolve(this.opts.memoryRoot));
3445
+ if (this.opts.memoryRoot) roots.push(path3.resolve(this.opts.memoryRoot));
3275
3446
  const home2 = process.env.HOME || os2.homedir() || "";
3276
3447
  if (home2) {
3277
3448
  const names = [
@@ -3279,7 +3450,7 @@ var MemoryGuardManager = class {
3279
3450
  ...this.opts.agentHomeDirs ?? []
3280
3451
  ];
3281
3452
  for (const name of names) {
3282
- const candidate = path4.join(home2, name);
3453
+ const candidate = path3.join(home2, name);
3283
3454
  try {
3284
3455
  if ((await fs4.stat(candidate)).isDirectory()) roots.push(candidate);
3285
3456
  } catch {
@@ -3417,7 +3588,7 @@ var MemoryGuardManager = class {
3417
3588
  const rec = bag;
3418
3589
  for (const k of KEYS) {
3419
3590
  const v = rec[k];
3420
- if (typeof v === "string" && v) found.add(path4.resolve(this.workspaceDir, v));
3591
+ if (typeof v === "string" && v) found.add(path3.resolve(this.workspaceDir, v));
3421
3592
  }
3422
3593
  }
3423
3594
  }
@@ -3464,12 +3635,7 @@ var MemoryGuardManager = class {
3464
3635
  */
3465
3636
  slotFor(target) {
3466
3637
  const home2 = process.env.HOME || os2.homedir() || "";
3467
- return native.memorySlotForPath(
3468
- path4.resolve(target),
3469
- home2,
3470
- this.opts.memoryRoot,
3471
- this.opts.agentHomeDirs
3472
- );
3638
+ return memorySlotOf(target, home2, this.opts.memoryRoot, this.opts.agentHomeDirs);
3473
3639
  }
3474
3640
  /**
3475
3641
  * Verify every memory file this call reads, each against its own chain slot.
@@ -3618,7 +3784,7 @@ var MemoryGuardManager = class {
3618
3784
  }
3619
3785
  /** Replace `target` in one step, so a reader never sees a half-written file. */
3620
3786
  async writeFileAtomic(target, content) {
3621
- await fs4.mkdir(path4.dirname(target), { recursive: true });
3787
+ await fs4.mkdir(path3.dirname(target), { recursive: true });
3622
3788
  const tmp = `${target}.${process.pid}.tmp`;
3623
3789
  await fs4.writeFile(tmp, content, "utf8");
3624
3790
  await fs4.rename(tmp, target);
@@ -3758,6 +3924,7 @@ export {
3758
3924
  EciesDomain,
3759
3925
  HttpClient,
3760
3926
  HttpTransportError,
3927
+ JudgeRefusalError,
3761
3928
  KEY_FILENAMES,
3762
3929
  MemoryGuardManager,
3763
3930
  MemoryIntegrityError,
@@ -3776,6 +3943,7 @@ export {
3776
3943
  columnAad,
3777
3944
  commitMemoryVersion,
3778
3945
  configureEvents,
3946
+ configureJailHold,
3779
3947
  containsEvasionCharacters,
3780
3948
  containsSecret,
3781
3949
  createFileLogger,
@@ -3804,12 +3972,14 @@ export {
3804
3972
  getRollbackHistory,
3805
3973
  guardMemoryWrite,
3806
3974
  isEnvelope,
3975
+ isJailHeld,
3807
3976
  isValidPrivateKey,
3808
3977
  keyFingerprintOf,
3809
3978
  keyPathCandidates,
3810
3979
  loadAgent,
3811
3980
  loadAgentFromFile,
3812
3981
  loadUserConfig,
3982
+ memorySlotOf,
3813
3983
  normalizeActionForHash,
3814
3984
  normalizeActionType,
3815
3985
  normalizeForMatching,
@@ -3823,6 +3993,7 @@ export {
3823
3993
  recordEvent,
3824
3994
  redactJsonStrings,
3825
3995
  redactSecrets,
3996
+ releaseJailHolds,
3826
3997
  resolve,
3827
3998
  resolveKeyPath,
3828
3999
  rollbackMemory,