@atbash/sdk 0.16.6-dev.0 → 0.18.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/browser.mjs CHANGED
@@ -43455,6 +43455,17 @@ var native = {
43455
43455
  defaultChromiaNodeUrls: () => [...DEFAULT_CHROMIA_NODE_URLS_ARR],
43456
43456
  defaultPrivateNodeUrls: () => [...DEFAULT_PRIVATE_NODE_URLS_ARR],
43457
43457
  // Server-only surfaces — throw at runtime, keep types satisfied.
43458
+ // The memory signers are here and not with the other signing shims because
43459
+ // their args are encrypted by `encryptMemoryContent`, which this build does
43460
+ // not have: a browser could sign the transaction but never produce one worth
43461
+ // sending.
43462
+ signAddAgentMemory: stub("signAddAgentMemory"),
43463
+ signRollbackAgentMemory: stub("signRollbackAgentMemory"),
43464
+ extractMemoryEdits: stub("extractMemoryEdits"),
43465
+ applyMemoryEdits: stub("applyMemoryEdits"),
43466
+ memorySlotForPath: stub("memorySlotForPath"),
43467
+ isSafeMemorySlot: stub("isSafeMemorySlot"),
43468
+ defaultAgentHomeDirs: stub("defaultAgentHomeDirs"),
43458
43469
  classifyMemoryWrite: stub("classifyMemoryWrite"),
43459
43470
  classifyMemoryRead: stub("classifyMemoryRead"),
43460
43471
  defaultMemoryWriteToolNames: stub("defaultMemoryWriteToolNames"),
@@ -43531,6 +43542,32 @@ function randomHex(size) {
43531
43542
  return hex;
43532
43543
  }
43533
43544
 
43545
+ // src-ts/auth-bearer.ts
43546
+ init_define_ATBASH_CHROMIA_NODE_URLS();
43547
+ init_define_ATBASH_PRIVATE_NODE_URLS();
43548
+ var BEARER_TTL_MS = 4 * 60 * 1e3;
43549
+ var bearers = /* @__PURE__ */ new Map();
43550
+ function agentAuthBearer(auth, blockchainRid) {
43551
+ const key3 = `${auth.pubkey}:${blockchainRid}`;
43552
+ const now = Date.now();
43553
+ const cached = bearers.get(key3);
43554
+ if (cached && now - cached.issuedAt < BEARER_TTL_MS) return cached.hex;
43555
+ const hex = native.signLogToolCall(
43556
+ `auth-${now.toString(36)}-${randomHex(4)}`,
43557
+ `auth:${now}`,
43558
+ "",
43559
+ "auth-bearer",
43560
+ "",
43561
+ auth.privkey,
43562
+ blockchainRid
43563
+ );
43564
+ bearers.set(key3, { hex, issuedAt: now });
43565
+ return hex;
43566
+ }
43567
+ function agentAuthHeaders(auth, blockchainRid) {
43568
+ return { Authorization: `Bearer ${agentAuthBearer(auth, blockchainRid)}` };
43569
+ }
43570
+
43534
43571
  // src-ts/chain-config.ts
43535
43572
  init_define_ATBASH_CHROMIA_NODE_URLS();
43536
43573
  init_define_ATBASH_PRIVATE_NODE_URLS();
@@ -44590,7 +44627,7 @@ init_define_ATBASH_CHROMIA_NODE_URLS();
44590
44627
  init_define_ATBASH_PRIVATE_NODE_URLS();
44591
44628
  function normalizeVerdict(raw2) {
44592
44629
  if (raw2 === null || raw2 === void 0) return "No verdict";
44593
- const v = String(raw2).toUpperCase();
44630
+ const v = String(raw2).trim().toUpperCase();
44594
44631
  if (v === "ALLOW" || v === "GREEN") return "ALLOW";
44595
44632
  if (v === "HOLD" || v === "YELLOW") return "HOLD";
44596
44633
  if (v === "BLOCK" || v === "RED") return "BLOCK";
@@ -44627,17 +44664,58 @@ function allowSignalsPermit(data) {
44627
44664
  }
44628
44665
  return signals.length === 0 || signals.every((value) => value === true);
44629
44666
  }
44667
+ function explicitRefusal(data) {
44668
+ let hold = false;
44669
+ for (const source of decisionSources(data)) {
44670
+ const verdict = verdictOf(source);
44671
+ if (verdict === "BLOCK" || verdict === "RED") return "BLOCK";
44672
+ if (verdict === "HOLD" || verdict === "YELLOW") hold = true;
44673
+ const actionType = actionTypeOf(source);
44674
+ if (actionType === "block") return "BLOCK";
44675
+ if (actionType === "hold_for_user_confirm") hold = true;
44676
+ }
44677
+ if (!allowSignalsPermit(data)) return "BLOCK";
44678
+ return hold ? "HOLD" : "";
44679
+ }
44680
+ function decisionSources(data) {
44681
+ return [data, isRecord(data.decision) ? data.decision : {}];
44682
+ }
44683
+ function verdictOf(source) {
44684
+ return typeof source.verdict === "string" ? source.verdict.trim().toUpperCase() : "";
44685
+ }
44686
+ function actionTypeRank(actionType) {
44687
+ if (actionType === "block") return 4;
44688
+ if (actionType === "hold_for_user_confirm") return 3;
44689
+ if (actionType === "") return 1;
44690
+ if (actionType === "allow") return 0;
44691
+ return 2;
44692
+ }
44693
+ function actionTypeOf(source) {
44694
+ let winner = "";
44695
+ for (const key3 of ["action_type", "actionType"]) {
44696
+ const raw2 = source[key3];
44697
+ if (raw2 === void 0 || raw2 === null) continue;
44698
+ const actionType = normalizeActionType(raw2);
44699
+ if (actionTypeRank(actionType) > actionTypeRank(winner)) winner = actionType;
44700
+ }
44701
+ return winner;
44702
+ }
44630
44703
  function wireActionType(data) {
44631
- if (data.action_type !== void 0)
44632
- return normalizeActionType(data.action_type);
44633
- return normalizeActionType(data.actionType);
44704
+ for (const source of decisionSources(data)) {
44705
+ const actionType = actionTypeOf(source);
44706
+ if (actionType !== "") return actionType;
44707
+ }
44708
+ return "";
44634
44709
  }
44635
44710
  function isAuditTier(data) {
44636
44711
  const absentVerdict = data.verdict === null || data.verdict === void 0;
44637
44712
  return absentVerdict && data.status === "logged";
44638
44713
  }
44714
+ function isAuditTierBody(data) {
44715
+ return isAuditTier(data);
44716
+ }
44639
44717
  function canonicalAllow(data) {
44640
- if (!allowSignalsPermit(data)) return false;
44718
+ if (explicitRefusal(data) !== "") return false;
44641
44719
  const actionType = wireActionType(data);
44642
44720
  if (actionType && actionType !== "allow") return false;
44643
44721
  if (isAuditTier(data)) return true;
@@ -44682,6 +44760,12 @@ function resolve(_key, flagValue) {
44682
44760
  function forcedChainNetwork(flagValue) {
44683
44761
  return flagValue === "public" || flagValue === "private" ? flagValue : void 0;
44684
44762
  }
44763
+ function agentKeyFromEnv() {
44764
+ return "";
44765
+ }
44766
+ function strandedAgentKey() {
44767
+ return "";
44768
+ }
44685
44769
 
44686
44770
  // src-ts/client.ts
44687
44771
  function generateToolCallId() {
@@ -44720,6 +44804,10 @@ var Atbash = class _Atbash {
44720
44804
  orgEncryptionPubKey;
44721
44805
  /** When true (default), `auditToolCall` denies on any error. */
44722
44806
  failClosed;
44807
+ /** When true (default), an unenforced (audit-tier) answer is refused. */
44808
+ requireEnforcement;
44809
+ /** Overall deadline for one auditToolCall decision, in ms. */
44810
+ decisionTimeoutMs;
44723
44811
  /** Org key learned from the last agent-exists check, for this agent only. */
44724
44812
  _orgKeyFromChain = null;
44725
44813
  debug;
@@ -44761,16 +44849,6 @@ var Atbash = class _Atbash {
44761
44849
  */
44762
44850
  _agentExistsCache = null;
44763
44851
  static AGENT_EXISTS_TTL_MS = 5e3;
44764
- /**
44765
- * Cached bearer token for risk-engine / insurance read calls. Built
44766
- * lazily as a signed `log_tool_call` tx and refreshed every 4 min so
44767
- * server-side replay protection windows never expire it mid-session.
44768
- */
44769
- // Keyed by blockchainRid so bearers for the public and private chains can
44770
- // coexist. The dashboard derives `authNetwork` from the BRID inside the
44771
- // signed envelope, so a bearer signed for chain A cannot authenticate a
44772
- // request routed to chain B — every chain the client talks to needs its own.
44773
- _authBearers = /* @__PURE__ */ new Map();
44774
44852
  /** Guards `logEnvironmentOnce` — hosts construct several clients. */
44775
44853
  static environmentLogged = false;
44776
44854
  constructor(privkey, options = {}) {
@@ -44793,6 +44871,8 @@ var Atbash = class _Atbash {
44793
44871
  this.verifyPubKey = validated.verifyPubKey ?? void 0;
44794
44872
  this.orgEncryptionPubKey = options.orgEncryptionPubKey;
44795
44873
  this.failClosed = options.failClosed !== false;
44874
+ this.requireEnforcement = options.requireEnforcement !== false;
44875
+ this.decisionTimeoutMs = options.decisionTimeoutMs ?? DEFAULT_DECISION_TIMEOUT_MS;
44796
44876
  this.debug = options.debug === true;
44797
44877
  this.logger = options.logger ?? {};
44798
44878
  this.http = new HttpClient(this.endpoint, options.timeoutMs ?? 12e4);
@@ -44856,6 +44936,84 @@ var Atbash = class _Atbash {
44856
44936
  * validated against the trusted allowlist / self-hosted policy; a
44857
44937
  * self-hosted endpoint's `verifyPubKey` becomes the client default.
44858
44938
  */
44939
+ /**
44940
+ * Decide which key this client signs as.
44941
+ *
44942
+ * Most specific wins: a key passed in code, then a key file the host named,
44943
+ * then the environment, then the default key file. `config.json` is absent
44944
+ * from that list on purpose — it is a settings file, writable by anything
44945
+ * that can write the user's home and read by every Atbash integration on the
44946
+ * machine, so letting it name the key meant one stray value silently
44947
+ * re-pointed who each of them signed as.
44948
+ *
44949
+ * A named `keyPath` that does not exist throws rather than falling through.
44950
+ * The caller said which file holds its identity; quietly signing as a
44951
+ * different one is the outcome worth refusing.
44952
+ *
44953
+ * @param options - The `fromConfig` options, read for `agentKey` and `keyPath`.
44954
+ * @returns The resolved agent identity.
44955
+ * @throws When a named key file is missing, or no identity can be found.
44956
+ */
44957
+ static resolveIdentity(options) {
44958
+ if (options.agentKey) return native.loadAgent(options.agentKey);
44959
+ if (options.keyPath) return loadAgentFromFile(options.keyPath);
44960
+ const fromEnv = agentKeyFromEnv();
44961
+ if (fromEnv) return native.loadAgent(fromEnv);
44962
+ try {
44963
+ return loadAgentFromFile();
44964
+ } catch (err) {
44965
+ throw _Atbash.explainStrandedKey(err);
44966
+ }
44967
+ }
44968
+ /**
44969
+ * Add the one thing a machine with no key file needs to hear.
44970
+ *
44971
+ * This is the only path where the operator cannot see what happened: no key
44972
+ * file, and the key they are looking for sits in `config.json`, which no
44973
+ * longer selects the identity. A bare "key file not found" sends them hunting
44974
+ * for a key they already have. Only this branch gets the hint — a caller who
44975
+ * named a `keyPath` is told about that path, where a stranded key is noise.
44976
+ *
44977
+ * @param err - The "key file not found" error from the default lookup.
44978
+ * @returns The same error, or one that also names the key in config.json.
44979
+ */
44980
+ static explainStrandedKey(err) {
44981
+ const stranded = strandedAgentKey();
44982
+ if (!stranded) return err;
44983
+ let identity;
44984
+ try {
44985
+ identity = native.derivePublicKey(stranded);
44986
+ } catch {
44987
+ identity = "(unreadable)";
44988
+ }
44989
+ const message = err instanceof Error ? err.message : String(err);
44990
+ return new Error(
44991
+ `${message}
44992
+ config.json still holds an "agentKey" (${identity}), but a settings file no longer chooses the signing key. Move it into one of the key files above, or set ATBASH_AGENT_KEY.`
44993
+ );
44994
+ }
44995
+ /**
44996
+ * Say so when `config.json` still holds an agent key.
44997
+ *
44998
+ * Nothing signs with it any more, and silence would leave an operator
44999
+ * believing one identity is in force while another is. The pubkey is named,
45000
+ * not the key, so the line is safe in a log.
45001
+ *
45002
+ * @param logger - The host logger, when one was supplied.
45003
+ */
45004
+ static warnOnStrandedKey(logger2) {
45005
+ const stranded = strandedAgentKey();
45006
+ if (!stranded) return;
45007
+ let identity;
45008
+ try {
45009
+ identity = native.derivePublicKey(stranded);
45010
+ } catch {
45011
+ identity = "(unreadable)";
45012
+ }
45013
+ logger2?.warn?.(
45014
+ `[atbash] "agentKey" in config.json is ignored \u2014 settings do not choose the signing key. It holds ${identity}; this client signs as the key file or ATBASH_AGENT_KEY instead. Move it into the key file, or delete the field, to silence this.`
45015
+ );
45016
+ }
44859
45017
  static fromConfig(options = {}) {
44860
45018
  const configuredEndpoint = resolve("judgeEndpoint") || void 0;
44861
45019
  const configuredVerifyPubKey = resolve("judgeVerifyPubKey") || void 0;
@@ -44871,8 +45029,8 @@ var Atbash = class _Atbash {
44871
45029
  verifyPubKey: configuredVerifyPubKey
44872
45030
  } : { endpoint: configuredEndpoint })
44873
45031
  );
44874
- const agentKey = resolve("agentKey", options.agentKey);
44875
- const auth = agentKey ? native.loadAgent(agentKey) : loadAgentFromFile(options.keyPath);
45032
+ const auth = _Atbash.resolveIdentity(options);
45033
+ _Atbash.warnOnStrandedKey(options.logger);
44876
45034
  return new _Atbash(auth.privkey, {
44877
45035
  endpoint: validated.url,
44878
45036
  chain: options.chain,
@@ -44883,6 +45041,8 @@ var Atbash = class _Atbash {
44883
45041
  orgName: options.orgName,
44884
45042
  verifyPubKey: validated.verifyPubKey ?? void 0,
44885
45043
  failClosed: options.failClosed,
45044
+ requireEnforcement: options.requireEnforcement,
45045
+ decisionTimeoutMs: options.decisionTimeoutMs,
44886
45046
  // ATBASH_DEBUG lets an operator turn diagnostics on without editing a
44887
45047
  // host's plugin config, which is usually the harder half.
44888
45048
  debug: options.debug ?? /^(1|true|yes)$/i.test(resolve("debug")),
@@ -45081,34 +45241,16 @@ var Atbash = class _Atbash {
45081
45241
  }
45082
45242
  if (!resp.ok) throw await this.httpError(resp);
45083
45243
  const bodyBytes = Buffer.from(await resp.arrayBuffer());
45084
- const verifyPubKey = options.verifyPubKey ?? this.verifyPubKey;
45244
+ const verifyPubKey = this.effectiveVerifyKey(options.verifyPubKey);
45085
45245
  if (verifyPubKey !== void 0) {
45086
- const sig = resp.headers.get("X-Atbash-Signature");
45087
- if (!sig) {
45088
- throw new SignatureVerificationError(
45089
- "missing X-Atbash-Signature header"
45090
- );
45091
- }
45092
- let ok;
45093
- try {
45094
- ok = native.verifySignature(bodyBytes, sig, verifyPubKey);
45095
- } catch (err) {
45096
- throw new SignatureVerificationError(
45097
- `signature verification threw: ${errorMessage(err)}`
45098
- );
45099
- }
45100
- if (!ok) {
45101
- throw new SignatureVerificationError(
45102
- "signature does not verify against configured verifyPubKey"
45103
- );
45104
- }
45246
+ verifyResponseSignature(bodyBytes, resp, verifyPubKey);
45105
45247
  }
45106
45248
  const data = parseJson(bodyBytes);
45107
45249
  const rawScore = data.score;
45108
45250
  const score = typeof rawScore === "number" && Number.isInteger(rawScore) && rawScore >= 1 && rawScore <= 10 ? rawScore : void 0;
45109
45251
  return {
45110
45252
  verdict: normalizeVerdict(data.verdict),
45111
- allow: canonicalAllow(data),
45253
+ allow: this.executable(data),
45112
45254
  actionType: normalizeActionType(data.action_type),
45113
45255
  reason: String(data.reason ?? ""),
45114
45256
  confidence: Number(data.confidence ?? 0),
@@ -45119,7 +45261,8 @@ var Atbash = class _Atbash {
45119
45261
  enforced: Boolean(data.enforced),
45120
45262
  enforcementMode: String(data.enforcement_mode ?? ""),
45121
45263
  status: String(data.status ?? ""),
45122
- score
45264
+ score,
45265
+ refusal: explicitRefusal(data)
45123
45266
  };
45124
45267
  }
45125
45268
  /* ── audit_tool_call (redact → judge → decision) ───────────────────────── */
@@ -45152,77 +45295,125 @@ var Atbash = class _Atbash {
45152
45295
  }
45153
45296
  try {
45154
45297
  this.logger.info?.("[atbash] judge API called", { tool: toolName });
45155
- const result = await this.judgeAction(actionText, contextText, {
45156
- toolName,
45157
- toolArgsJson: argsJson,
45158
- orgName: this.orgName,
45159
- // Passed through unredacted — caller vouches for the contents.
45160
- resolved: input.resolved
45161
- });
45162
- if (result.verdict === "No verdict") {
45163
- if (result.status !== "logged") {
45164
- return this.failJudge(
45165
- `judge returned no verdict without an audit-tier marker (status: ${result.status || "absent"})`,
45166
- void 0,
45167
- result.toolCallId
45168
- );
45169
- }
45170
- return {
45171
- allow: true,
45172
- verdict: "ALLOW",
45173
- reason: result.reason || "audit tier \u2014 request logged on-chain, no AI enforcement",
45174
- toolCallId: result.toolCallId
45175
- };
45176
- }
45177
- const action = result.actionType;
45178
- if (action === "block") {
45298
+ const result = await withDeadline(
45299
+ this.judgeAction(actionText, contextText, {
45300
+ toolName,
45301
+ toolArgsJson: argsJson,
45302
+ orgName: this.orgName,
45303
+ // Passed through unredacted — caller vouches for the contents.
45304
+ resolved: input.resolved
45305
+ }),
45306
+ this.decisionTimeoutMs
45307
+ );
45308
+ return this.decide(result);
45309
+ } catch (err) {
45310
+ if (err instanceof SignatureVerificationError) {
45311
+ this.failJudge(errorMessage(err), err);
45179
45312
  return {
45180
45313
  allow: false,
45181
- verdict: "BLOCK",
45182
- reason: result.reason,
45183
- toolCallId: result.toolCallId
45314
+ verdict: "ERROR",
45315
+ reason: `The judge's reply failed its signature check (${errorMessage(err)}), so it was not trusted.`
45184
45316
  };
45185
45317
  }
45186
- if (action === "hold_for_user_confirm") {
45187
- return {
45188
- allow: false,
45189
- verdict: "HOLD",
45190
- reason: result.reason || "held for human confirmation",
45191
- toolCallId: result.toolCallId
45192
- };
45318
+ return this.failJudge(errorMessage(err), err);
45319
+ }
45320
+ }
45321
+ /**
45322
+ * The verify key for one call (R4, governor tier-2 review). A per-call key
45323
+ * may only narrow what the client accepts: on a client pinned to a judge
45324
+ * key it must be that same key, never a replacement; on an unpinned client
45325
+ * it adds the check.
45326
+ */
45327
+ effectiveVerifyKey(perCall) {
45328
+ if (perCall === void 0) return this.verifyPubKey;
45329
+ const key3 = perCall.trim().toLowerCase();
45330
+ if (this.verifyPubKey !== void 0 && key3 !== this.verifyPubKey) {
45331
+ throw new SignatureVerificationError(
45332
+ "a per-call verifyPubKey cannot replace the judge key this client is pinned to"
45333
+ );
45334
+ }
45335
+ return key3;
45336
+ }
45337
+ /**
45338
+ * Collapse a judge result into a {@link Decision}. The most restrictive
45339
+ * signal wins (security review 2026-09-30, H1): `action_type`, `verdict`
45340
+ * and the canonical `allow` are each able to deny, and only a result where
45341
+ * all three agree may execute. A present `allow` / `decision.allow` that is
45342
+ * not exactly `true` is the judge declining to grant permission, so it
45343
+ * denies even under `failClosed: false` — that option relaxes *errors*, it
45344
+ * never overrides a refusal.
45345
+ */
45346
+ decide(result) {
45347
+ const toolCallId = result.toolCallId;
45348
+ const deny = (verdict, reason) => ({
45349
+ allow: false,
45350
+ verdict,
45351
+ reason,
45352
+ toolCallId
45353
+ });
45354
+ const notGranted = "the judge response did not grant permission";
45355
+ const held = deny("HOLD", result.reason || "held for human confirmation");
45356
+ const blocked = deny("BLOCK", result.reason || notGranted);
45357
+ if (result.refusal === "BLOCK") return blocked;
45358
+ if (result.verdict === "No verdict") {
45359
+ if (result.actionType === "block") {
45360
+ return blocked;
45361
+ }
45362
+ if (result.actionType === "hold_for_user_confirm") {
45363
+ return held;
45364
+ }
45365
+ if (result.refusal === "HOLD") return held;
45366
+ if (result.status !== "logged") {
45367
+ return this.failJudge(
45368
+ `judge returned no verdict without an audit-tier marker (status: ${result.status || "absent"})`,
45369
+ void 0,
45370
+ toolCallId
45371
+ );
45193
45372
  }
45194
- if (action === "allow") {
45195
- if (result.verdict === "HOLD") {
45196
- return {
45197
- allow: false,
45198
- verdict: "HOLD",
45199
- reason: result.reason,
45200
- toolCallId: result.toolCallId
45201
- };
45202
- }
45203
- if (result.verdict === "BLOCK") {
45204
- return {
45205
- allow: false,
45206
- verdict: "BLOCK",
45207
- reason: result.reason,
45208
- toolCallId: result.toolCallId
45209
- };
45210
- }
45373
+ if (this.requireEnforcement) {
45374
+ this.logger.warn?.(
45375
+ "[atbash] judge did not enforce this action (audit tier) and requireEnforcement is on: denied",
45376
+ { toolCallId }
45377
+ );
45211
45378
  return {
45212
- allow: true,
45213
- verdict: "ALLOW",
45214
- reason: result.reason,
45215
- toolCallId: result.toolCallId
45379
+ allow: false,
45380
+ verdict: "ERROR",
45381
+ reason: "Atbash logged this action but did not check it (audit-tier plan), and this client requires a checked decision. Upgrade the plan, or set requireEnforcement: false for log-only mode.",
45382
+ toolCallId
45216
45383
  };
45217
45384
  }
45218
- return this.failJudge(
45219
- "unrecognized action_type from judge",
45220
- void 0,
45221
- result.toolCallId
45222
- );
45223
- } catch (err) {
45224
- return this.failJudge(errorMessage(err), err);
45385
+ if (result.allow !== true) return deny("BLOCK", notGranted);
45386
+ return {
45387
+ allow: true,
45388
+ verdict: "ALLOW",
45389
+ reason: result.reason || "audit tier \u2014 request logged on-chain, no AI enforcement",
45390
+ toolCallId
45391
+ };
45392
+ }
45393
+ const action = result.actionType;
45394
+ if (action === "block") {
45395
+ return deny("BLOCK", result.reason || "Blocked by your organisation's Atbash policy.");
45396
+ }
45397
+ if (action === "hold_for_user_confirm") {
45398
+ return held;
45225
45399
  }
45400
+ if (result.refusal === "HOLD") return held;
45401
+ if (action === "allow") {
45402
+ if (result.verdict === "HOLD") return held;
45403
+ if (result.verdict === "BLOCK") return blocked;
45404
+ if (result.allow !== true) return deny("BLOCK", notGranted);
45405
+ return {
45406
+ allow: true,
45407
+ verdict: "ALLOW",
45408
+ reason: result.reason,
45409
+ toolCallId
45410
+ };
45411
+ }
45412
+ return this.failJudge(
45413
+ "unrecognized action_type from judge",
45414
+ void 0,
45415
+ toolCallId
45416
+ );
45226
45417
  }
45227
45418
  /**
45228
45419
  * One exit for every judge failure.
@@ -45234,18 +45425,30 @@ var Atbash = class _Atbash {
45234
45425
  */
45235
45426
  failJudge(reason, cause, toolCallId) {
45236
45427
  const api2 = cause instanceof AtbashAPIError ? cause : null;
45428
+ const userReason = userFacingReason(reason, api2);
45237
45429
  const status = api2 ? ` status=${api2.status || "no-response"}` : "";
45430
+ const detail = this.debug ? ` detail=${truncate(reason, 500)}` : "";
45238
45431
  const body = this.debug && api2?.body ? ` body=${truncate(api2.body, 500)}` : "";
45239
45432
  this.logger.warn?.(
45240
- `[atbash] judge API failed \u2014${status} reason=${truncate(reason, 300)}${body}`,
45433
+ `[atbash] judge API failed \u2014${status} reason=${truncate(userReason, 300)}${detail}${body}`,
45241
45434
  {
45242
- reason,
45243
- ...api2 ? { status: api2.status, body: api2.body } : {},
45435
+ reason: userReason,
45436
+ ...api2 ? { status: api2.status } : {},
45437
+ ...this.debug && api2 ? { body: api2.body, detail: reason } : {},
45244
45438
  endpoint: this.endpoint,
45245
45439
  ...toolCallId ? { toolCallId } : {}
45246
45440
  }
45247
45441
  );
45248
- return this.fail(reason, toolCallId);
45442
+ return this.fail(userReason, toolCallId);
45443
+ }
45444
+ /**
45445
+ * The canonical permission for a response body under this client's
45446
+ * settings: {@link canonicalAllow}, minus the audit tier when
45447
+ * `requireEnforcement` is on. Never more permissive than `auditToolCall`.
45448
+ */
45449
+ executable(data) {
45450
+ if (!canonicalAllow(data)) return false;
45451
+ return !(this.requireEnforcement && isAuditTierBody(data));
45249
45452
  }
45250
45453
  fail(reason, toolCallId) {
45251
45454
  return { allow: !this.failClosed, verdict: "ERROR", reason, toolCallId };
@@ -45271,11 +45474,16 @@ var Atbash = class _Atbash {
45271
45474
  this.authHeaders(brid)
45272
45475
  );
45273
45476
  await this.raiseIfError(resp);
45274
- const data = await this.json(resp) ?? {};
45477
+ const bytes = Buffer.from(await resp.arrayBuffer());
45478
+ if (this.verifyPubKey !== void 0) {
45479
+ verifyResponseSignature(bytes, resp, this.verifyPubKey);
45480
+ }
45481
+ const parsed = tryParseJson(bytes.toString("utf-8"));
45482
+ const data = isRecord2(parsed) ? parsed : {};
45275
45483
  return {
45276
45484
  status: normalizeStatus(data.status),
45277
45485
  verdict: normalizeVerdict(data.verdict),
45278
- allow: canonicalAllow(data),
45486
+ allow: this.executable(data),
45279
45487
  reason: String(data.reason ?? ""),
45280
45488
  judgmentId: String(data.judgmentId ?? judgmentId),
45281
45489
  onChain: optBool(data.onChain),
@@ -45602,25 +45810,10 @@ var Atbash = class _Atbash {
45602
45810
  * trips the server's replay window.
45603
45811
  */
45604
45812
  getAuthBearer(brid) {
45605
- const chainBrid = brid ?? this.blockchainRid;
45606
- const now = Date.now();
45607
- const cached = this._authBearers.get(chainBrid);
45608
- if (cached && now - cached.issuedAt < 4 * 60 * 1e3) return cached.hex;
45609
- const nonce = `auth-${now.toString(36)}-${randomHex(4)}`;
45610
- const hex = native.signLogToolCall(
45611
- nonce,
45612
- `auth:${now}`,
45613
- "",
45614
- "auth-bearer",
45615
- "",
45616
- this.auth.privkey,
45617
- chainBrid
45618
- );
45619
- this._authBearers.set(chainBrid, { hex, issuedAt: now });
45620
- return hex;
45813
+ return agentAuthBearer(this.auth, brid ?? this.blockchainRid);
45621
45814
  }
45622
45815
  authHeaders(brid) {
45623
- return { Authorization: `Bearer ${this.getAuthBearer(brid)}` };
45816
+ return agentAuthHeaders(this.auth, brid ?? this.blockchainRid);
45624
45817
  }
45625
45818
  async riskEngineGet(action, params, brid) {
45626
45819
  let resp;
@@ -45750,6 +45943,66 @@ var Atbash = class _Atbash {
45750
45943
  return native.containsEvasionCharacters(text);
45751
45944
  }
45752
45945
  };
45946
+ function userFacingReason(reason, api2) {
45947
+ if (api2 && api2.status > 0) {
45948
+ const where = ` (HTTP ${api2.status}${api2.path ? ` from ${api2.path}` : ""})`;
45949
+ const b = api2.body.toLowerCase();
45950
+ if (b.includes("agent not registered")) {
45951
+ return `Agent not registered with Atbash: onboard it in the Atbash dashboard${where}.`;
45952
+ }
45953
+ if (b.includes("jailed")) {
45954
+ return `This agent is jailed; unjail it in the Atbash dashboard${where}.`;
45955
+ }
45956
+ if (b.includes("agent has no policy") || b.includes("no policy configured")) {
45957
+ return `This agent has no policy; attach one in the Atbash dashboard${where}.`;
45958
+ }
45959
+ if (b.includes("audit tier") || b.includes("verdict disabled") || b.includes("verdict not supported")) {
45960
+ return `The organisation's plan does not include checked decisions${where}.`;
45961
+ }
45962
+ return `The Atbash judge answered with an error${where}, so the action was not approved.`;
45963
+ }
45964
+ const text = api2 ? api2.body || reason : reason;
45965
+ return truncate(text.replace(/https?:\/\/[^\s)]+/g, "the judge"), 300);
45966
+ }
45967
+ var DEFAULT_DECISION_TIMEOUT_MS = 6e4;
45968
+ async function withDeadline(p, ms) {
45969
+ let timer;
45970
+ const deadline = new Promise((_, reject) => {
45971
+ timer = setTimeout(
45972
+ () => reject(
45973
+ new Error(
45974
+ `The judge did not give a decision within ${Math.round(ms / 1e3)} s, so the action was not approved. Check the network, or raise decisionTimeoutMs.`
45975
+ )
45976
+ ),
45977
+ ms
45978
+ );
45979
+ });
45980
+ p.catch(() => void 0);
45981
+ try {
45982
+ return await Promise.race([p, deadline]);
45983
+ } finally {
45984
+ clearTimeout(timer);
45985
+ }
45986
+ }
45987
+ function verifyResponseSignature(bodyBytes, resp, verifyPubKey) {
45988
+ const sig = resp.headers.get("X-Atbash-Signature");
45989
+ if (!sig) {
45990
+ throw new SignatureVerificationError("missing X-Atbash-Signature header");
45991
+ }
45992
+ let ok;
45993
+ try {
45994
+ ok = native.verifySignature(bodyBytes, sig, verifyPubKey);
45995
+ } catch (err) {
45996
+ throw new SignatureVerificationError(
45997
+ `signature verification threw: ${errorMessage(err)}`
45998
+ );
45999
+ }
46000
+ if (!ok) {
46001
+ throw new SignatureVerificationError(
46002
+ "signature does not verify against configured verifyPubKey"
46003
+ );
46004
+ }
46005
+ }
45753
46006
  function baseToolCall(raw2) {
45754
46007
  return {
45755
46008
  toolCallId: String(raw2.tool_call_id ?? ""),
@@ -45951,22 +46204,6 @@ function bootSyncFailureLine(cause) {
45951
46204
  );
45952
46205
  }
45953
46206
 
45954
- // src-ts/memory/_gtv_bytes.ts
45955
- init_define_ATBASH_CHROMIA_NODE_URLS();
45956
- init_define_ATBASH_PRIVATE_NODE_URLS();
45957
- function gtvToFfiSafe(value) {
45958
- if (value instanceof Uint8Array) return Buffer.from(value).toString("hex");
45959
- if (Array.isArray(value)) return value.map(gtvToFfiSafe);
45960
- if (value !== null && typeof value === "object") {
45961
- const out = {};
45962
- for (const [key3, val] of Object.entries(value)) {
45963
- out[key3] = gtvToFfiSafe(val);
45964
- }
45965
- return out;
45966
- }
45967
- return value;
45968
- }
45969
-
45970
46207
  // src-ts/memory/index.ts
45971
46208
  init_define_ATBASH_CHROMIA_NODE_URLS();
45972
46209
  init_define_ATBASH_PRIVATE_NODE_URLS();
@@ -46334,7 +46571,6 @@ export {
46334
46571
  getMemoryHistory,
46335
46572
  getRecentAgentMemory,
46336
46573
  getRollbackHistory,
46337
- gtvToFfiSafe,
46338
46574
  guardMemoryWrite,
46339
46575
  isEnvelope,
46340
46576
  isValidPrivateKey2 as isValidPrivateKey,