@atbash/sdk 0.10.5-dev.0 → 0.10.7-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
@@ -3051,19 +3051,32 @@ var HttpClient = class {
3051
3051
  };
3052
3052
 
3053
3053
  // src-ts/keyLoader.ts
3054
- import { readFileSync } from "fs";
3054
+ import { existsSync, readFileSync } from "fs";
3055
+
3056
+ // src-ts/key-path.ts
3055
3057
  import { homedir } from "os";
3056
3058
  import { join } from "path";
3057
- var DEFAULT_KEY_PATH_REL = ".config/atbash/guard-client-key";
3058
- function resolveKeyPath(input) {
3059
- if (input) return expandHome(input);
3060
- const home = process.env.HOME || homedir() || "";
3061
- return join(home, DEFAULT_KEY_PATH_REL);
3059
+ var KEY_DIR_REL = ".config/atbash";
3060
+ var KEY_FILENAMES = ["guard-client-key", "atbash-client-key"];
3061
+ function home() {
3062
+ return process.env.HOME || homedir() || "";
3062
3063
  }
3063
3064
  function expandHome(p) {
3064
3065
  if (!p.startsWith("~/")) return p;
3065
- const home = process.env.HOME || homedir() || "";
3066
- return join(home, p.slice(2));
3066
+ return join(home(), p.slice(2));
3067
+ }
3068
+ function keyPathCandidates() {
3069
+ return KEY_FILENAMES.map((name2) => join(home(), KEY_DIR_REL, name2));
3070
+ }
3071
+ function chooseKeyPath(input, exists) {
3072
+ if (input) return expandHome(input);
3073
+ const candidates = keyPathCandidates();
3074
+ return candidates.find(exists) ?? candidates[0];
3075
+ }
3076
+
3077
+ // src-ts/keyLoader.ts
3078
+ function resolveKeyPath(input) {
3079
+ return chooseKeyPath(input, existsSync);
3067
3080
  }
3068
3081
  function readKeyFile(keyPath) {
3069
3082
  const content = String(readFileSync(keyPath, "utf8") || "").trim();
@@ -3093,6 +3106,12 @@ function readKeyFile(keyPath) {
3093
3106
  }
3094
3107
  function loadAgentFromFile(keyPath) {
3095
3108
  const resolved = resolveKeyPath(keyPath);
3109
+ if (!existsSync(resolved)) {
3110
+ const looked = keyPath ? [resolved] : keyPathCandidates();
3111
+ throw new Error(
3112
+ `atbash key file not found. Looked for: ${looked.join(", ")}`
3113
+ );
3114
+ }
3096
3115
  const { privKey } = readKeyFile(resolved);
3097
3116
  return native.loadAgent(privKey);
3098
3117
  }
@@ -3138,8 +3157,8 @@ var durationHistogram = null;
3138
3157
  var defaultSource = "sdk";
3139
3158
  function isTelemetryOptedOut() {
3140
3159
  try {
3141
- const home = process.env.HOME || homedir2() || "";
3142
- const filePath = join2(home, ".config", "atbash", "telemetry.json");
3160
+ const home2 = process.env.HOME || homedir2() || "";
3161
+ const filePath = join2(home2, ".config", "atbash", "telemetry.json");
3143
3162
  const raw2 = readFileSync2(filePath, "utf-8").trim();
3144
3163
  if (!raw2) return false;
3145
3164
  const config2 = JSON.parse(raw2);
@@ -3217,7 +3236,7 @@ async function shutdownTelemetry() {
3217
3236
  // src-ts/userConfig.ts
3218
3237
  import {
3219
3238
  chmodSync,
3220
- existsSync,
3239
+ existsSync as existsSync2,
3221
3240
  mkdirSync,
3222
3241
  readFileSync as readFileSync3,
3223
3242
  writeFileSync
@@ -3230,11 +3249,12 @@ var ENV_MAP = {
3230
3249
  judgeEndpoint: "ATBASH_ENDPOINT",
3231
3250
  blockchainRid: "ATBASH_BLOCKCHAIN_RID",
3232
3251
  provider: "ATBASH_PROVIDER",
3233
- providerModel: "ATBASH_PROVIDER_MODEL"
3252
+ providerModel: "ATBASH_PROVIDER_MODEL",
3253
+ debug: "ATBASH_DEBUG"
3234
3254
  };
3235
3255
  function getConfigDir() {
3236
- const home = process.env.HOME || homedir3() || "";
3237
- return join3(home, ".config", "atbash");
3256
+ const home2 = process.env.HOME || homedir3() || "";
3257
+ return join3(home2, ".config", "atbash");
3238
3258
  }
3239
3259
  function getConfigPath() {
3240
3260
  return join3(getConfigDir(), "config.json");
@@ -3242,7 +3262,7 @@ function getConfigPath() {
3242
3262
  function loadUserConfig() {
3243
3263
  try {
3244
3264
  const p = getConfigPath();
3245
- if (!existsSync(p)) return {};
3265
+ if (!existsSync2(p)) return {};
3246
3266
  const raw2 = readFileSync3(p, "utf-8").trim();
3247
3267
  if (!raw2) return {};
3248
3268
  return JSON.parse(raw2);
@@ -3253,7 +3273,7 @@ function loadUserConfig() {
3253
3273
  }
3254
3274
  function saveUserConfig(config2) {
3255
3275
  const dir = getConfigDir();
3256
- if (!existsSync(dir)) {
3276
+ if (!existsSync2(dir)) {
3257
3277
  mkdirSync(dir, { recursive: true, mode: 448 });
3258
3278
  }
3259
3279
  const filePath = getConfigPath();
@@ -3293,6 +3313,7 @@ var Atbash = class _Atbash {
3293
3313
  failClosed;
3294
3314
  /** Org key learned from the last agent-exists check, for this agent only. */
3295
3315
  _orgKeyFromChain = null;
3316
+ debug;
3296
3317
  logger;
3297
3318
  http;
3298
3319
  /**
@@ -3306,6 +3327,8 @@ var Atbash = class _Atbash {
3306
3327
  * server-side replay protection windows never expire it mid-session.
3307
3328
  */
3308
3329
  _authBearer = null;
3330
+ /** Guards `logEnvironmentOnce` — hosts construct several clients. */
3331
+ static environmentLogged = false;
3309
3332
  constructor(privkey, options = {}) {
3310
3333
  this.auth = native.loadAgent(privkey);
3311
3334
  this.endpoint = (options.endpoint ?? DEFAULT_ENDPOINT).replace(/\/+$/, "") || DEFAULT_ENDPOINT;
@@ -3315,8 +3338,10 @@ var Atbash = class _Atbash {
3315
3338
  this.verifyPubKey = options.verifyPubKey;
3316
3339
  this.orgEncryptionPubKey = options.orgEncryptionPubKey;
3317
3340
  this.failClosed = options.failClosed !== false;
3341
+ this.debug = options.debug === true;
3318
3342
  this.logger = options.logger ?? {};
3319
3343
  this.http = new HttpClient(this.endpoint, options.timeoutMs ?? 3e4);
3344
+ this.logEnvironmentOnce();
3320
3345
  if (this.endpoint !== DEFAULT_ENDPOINT) {
3321
3346
  this.logger.warn?.("[atbash] running on non-default judge endpoint", {
3322
3347
  endpoint: this.endpoint,
@@ -3324,6 +3349,31 @@ var Atbash = class _Atbash {
3324
3349
  });
3325
3350
  }
3326
3351
  }
3352
+ /**
3353
+ * Say which environment this build talks to, once per process.
3354
+ *
3355
+ * The endpoint and both chain RIDs are compiled in, chosen by the npm tag,
3356
+ * and no configuration repoints a released build. So installing the build
3357
+ * for the wrong environment is invisible: the plugin loads, the hook fires,
3358
+ * and every judge call fails because the agent does not exist on the chain
3359
+ * this build targets. Organisation names are not unique across environments
3360
+ * either, so an org resolving is not evidence the build is right.
3361
+ */
3362
+ logEnvironmentOnce() {
3363
+ if (_Atbash.environmentLogged) return;
3364
+ _Atbash.environmentLogged = true;
3365
+ const brief = (rid) => rid ? `${rid.slice(0, 8)}\u2026` : "(unset)";
3366
+ this.logger.info?.(
3367
+ `[atbash] environment \u2014 judge=${this.endpoint} publicChain=${brief(native.DEFAULT_BLOCKCHAIN_RID)} privateChain=${brief(native.DEFAULT_PRIVATE_BLOCKCHAIN_RID)} activeChain=${brief(this.blockchainRid)} responseSignatureCheck=${this.verifyPubKey ? "on" : "off"}`,
3368
+ {
3369
+ judgeEndpoint: this.endpoint,
3370
+ publicBlockchainRid: native.DEFAULT_BLOCKCHAIN_RID,
3371
+ privateBlockchainRid: native.DEFAULT_PRIVATE_BLOCKCHAIN_RID,
3372
+ activeBlockchainRid: this.blockchainRid,
3373
+ responseSignatureCheck: Boolean(this.verifyPubKey)
3374
+ }
3375
+ );
3376
+ }
3327
3377
  /**
3328
3378
  * Construct from resolved config: explicit overrides → env vars → the
3329
3379
  * `~/.config/atbash/config.json` file (see userConfig.resolve). The private
@@ -3347,6 +3397,9 @@ var Atbash = class _Atbash {
3347
3397
  orgName: options.orgName,
3348
3398
  verifyPubKey: validated.verifyPubKey ?? void 0,
3349
3399
  failClosed: options.failClosed,
3400
+ // ATBASH_DEBUG lets an operator turn diagnostics on without editing a
3401
+ // host's plugin config, which is usually the harder half.
3402
+ debug: options.debug ?? /^(1|true|yes)$/i.test(resolve("debug")),
3350
3403
  logger: options.logger
3351
3404
  });
3352
3405
  }
@@ -3446,11 +3499,15 @@ var Atbash = class _Atbash {
3446
3499
  * response bytes via the Rust core's `verifySignature`.
3447
3500
  */
3448
3501
  async judgeAction(action, context = "", options = {}) {
3449
- return this.track(
3450
- "judgeAction",
3451
- this.auth.pubkey,
3452
- () => this._judgeAction(action, context, options)
3453
- );
3502
+ return this.track("judgeAction", this.auth.pubkey, async () => {
3503
+ try {
3504
+ return await this._judgeAction(action, context, options);
3505
+ } catch (err) {
3506
+ if (!isEncryptionStateMismatch(err)) throw err;
3507
+ this._orgKeyFromChain = null;
3508
+ return await this._judgeAction(action, context, options);
3509
+ }
3510
+ });
3454
3511
  }
3455
3512
  async _judgeAction(action, context, options) {
3456
3513
  if (!action?.trim()) {
@@ -3588,8 +3645,9 @@ var Atbash = class _Atbash {
3588
3645
  });
3589
3646
  if (result.verdict === "No verdict") {
3590
3647
  if (result.status !== "logged") {
3591
- return this.fail(
3648
+ return this.failJudge(
3592
3649
  `judge returned no verdict without an audit-tier marker (status: ${result.status || "absent"})`,
3650
+ void 0,
3593
3651
  result.toolCallId
3594
3652
  );
3595
3653
  }
@@ -3641,16 +3699,38 @@ var Atbash = class _Atbash {
3641
3699
  toolCallId: result.toolCallId
3642
3700
  };
3643
3701
  }
3644
- return this.fail(
3702
+ return this.failJudge(
3645
3703
  "unrecognized action_type from judge",
3704
+ void 0,
3646
3705
  result.toolCallId
3647
3706
  );
3648
3707
  } catch (err) {
3649
- const message = errorMessage(err);
3650
- this.logger.warn?.("[atbash] judge API failed", { reason: message });
3651
- return this.fail(message);
3708
+ return this.failJudge(errorMessage(err), err);
3652
3709
  }
3653
3710
  }
3711
+ /**
3712
+ * One exit for every judge failure.
3713
+ *
3714
+ * Status and reason go in the *message*, not only in the meta object: hosts
3715
+ * print the message and drop the meta, which is why this read as a bare
3716
+ * "judge API failed" while the judge was answering with a precise reason.
3717
+ * The response body follows only under `debug`, since it can echo the action.
3718
+ */
3719
+ failJudge(reason, cause, toolCallId) {
3720
+ const api2 = cause instanceof AtbashAPIError ? cause : null;
3721
+ const status = api2 ? ` status=${api2.status || "no-response"}` : "";
3722
+ const body = this.debug && api2?.body ? ` body=${truncate(api2.body, 500)}` : "";
3723
+ this.logger.warn?.(
3724
+ `[atbash] judge API failed \u2014${status} reason=${truncate(reason, 300)}${body}`,
3725
+ {
3726
+ reason,
3727
+ ...api2 ? { status: api2.status, body: api2.body } : {},
3728
+ endpoint: this.endpoint,
3729
+ ...toolCallId ? { toolCallId } : {}
3730
+ }
3731
+ );
3732
+ return this.fail(reason, toolCallId);
3733
+ }
3654
3734
  fail(reason, toolCallId) {
3655
3735
  return { allow: !this.failClosed, verdict: "ERROR", reason, toolCallId };
3656
3736
  }
@@ -4124,6 +4204,10 @@ async function safeText(resp) {
4124
4204
  function errorMessage(err) {
4125
4205
  return err instanceof Error ? err.message : String(err);
4126
4206
  }
4207
+ function isEncryptionStateMismatch(err) {
4208
+ const msg = errorMessage(err);
4209
+ return msg.includes("must be a valid encryption envelope") || msg.includes("must be plaintext");
4210
+ }
4127
4211
  function stringifyArgs(args) {
4128
4212
  if (args === null || args === void 0) return "";
4129
4213
  if (typeof args === "string") return args;
@@ -4134,9 +4218,9 @@ function stringifyArgs(args) {
4134
4218
  }
4135
4219
  }
4136
4220
  var MAX_ACTION_LEN = 4e3;
4137
- function truncate(text) {
4138
- if (text.length <= MAX_ACTION_LEN) return text;
4139
- return text.slice(0, MAX_ACTION_LEN) + "\u2026";
4221
+ function truncate(text, limit = MAX_ACTION_LEN) {
4222
+ if (text.length <= limit) return text;
4223
+ return text.slice(0, limit) + "\u2026";
4140
4224
  }
4141
4225
 
4142
4226
  // src-ts/redact.ts
@@ -4179,6 +4263,17 @@ function verifyJudgeResponseSignature(bodyBytes, signatureHex, pubKeyHex) {
4179
4263
  };
4180
4264
  }
4181
4265
 
4266
+ // src-ts/memory/boot-sync-message.ts
4267
+ var BOOT_SYNC_HINT = "check the chain endpoint and orgName if the cause above does not explain it";
4268
+ function bootSyncFailureLine(cause) {
4269
+ const reason = cause instanceof Error ? cause.message : String(cause);
4270
+ const trimmed = reason.trim();
4271
+ return trimmed ? `[atbash] boot memory sync failed: ${trimmed}` : (
4272
+ // No cause to show: fall back to the advice rather than a bare colon.
4273
+ `[atbash] boot memory sync failed \u2014 ${BOOT_SYNC_HINT}`
4274
+ );
4275
+ }
4276
+
4182
4277
  // src-ts/memory/crypto.ts
4183
4278
  async function deriveMemoryKey(privkey) {
4184
4279
  return native.deriveMemoryKey(privkey);
@@ -4204,6 +4299,19 @@ async function scanMemory(entry, auth, opts) {
4204
4299
  toolArgsJson: opts?.toolArgsJson ?? JSON.stringify({ key: entry.key, source: entry.source }),
4205
4300
  mode: "memory-scan"
4206
4301
  });
4302
+ const knownAction = result.actionType === "allow" || result.actionType === "block" || result.actionType === "hold_for_user_confirm";
4303
+ const missingVerdict = result.verdict === "No verdict" && result.status !== "logged";
4304
+ const unknownAction = result.actionType !== "" && !knownAction;
4305
+ if (missingVerdict || unknownAction) {
4306
+ return {
4307
+ safe: false,
4308
+ verdict: "red",
4309
+ reason: unknownAction ? `judge returned unrecognised action_type "${result.actionType}"` : "judge returned no verdict",
4310
+ confidence: result.confidence,
4311
+ score: native.defaultScoreForVerdict("red"),
4312
+ toolCallId: result.toolCallId
4313
+ };
4314
+ }
4207
4315
  const verdict = native.mapVerdict(
4208
4316
  result.actionType,
4209
4317
  result.confidence,
@@ -42354,6 +42462,10 @@ var index = /* @__PURE__ */ getDefaultExportFromCjs(builtExports);
42354
42462
 
42355
42463
  // src-ts/memory/chain.ts
42356
42464
  var { createClient, encryption: encryption2, newSignatureProvider: newSignatureProvider2, Buffer: PolyBuffer } = index;
42465
+ var FAILOVER_CONFIG = {
42466
+ strategy: "tryNextOnError",
42467
+ attemptsPerEndpoint: 1
42468
+ };
42357
42469
  function toGtxBytes(bytes) {
42358
42470
  return PolyBuffer.from(new Uint8Array(bytes));
42359
42471
  }
@@ -42383,7 +42495,11 @@ function materializeChain(chainOpts) {
42383
42495
  }
42384
42496
  async function buildChainClient(chainOpts) {
42385
42497
  const { nodeUrls, blockchainRid } = materializeChain(chainOpts);
42386
- return createClient({ nodeUrlPool: [...nodeUrls], blockchainRid });
42498
+ return createClient({
42499
+ nodeUrlPool: [...nodeUrls],
42500
+ blockchainRid,
42501
+ failOverConfig: FAILOVER_CONFIG
42502
+ });
42387
42503
  }
42388
42504
  function buildSigner(auth) {
42389
42505
  const privKeyBuf = Buffer.from(auth.privkey, "hex");
@@ -42821,7 +42937,10 @@ var MemoryGuardManager = class {
42821
42937
  await this.pointerStore.set(this.agentPubkeyHex, result.pointer);
42822
42938
  } catch (err) {
42823
42939
  const msg = err instanceof Error ? err.message : String(err);
42824
- this.logger.warn("[atbash] boot memory sync failed \u2014 check chain endpoint / orgName", { error: msg });
42940
+ this.logger.warn(bootSyncFailureLine(err), {
42941
+ error: msg,
42942
+ hint: BOOT_SYNC_HINT
42943
+ });
42825
42944
  }
42826
42945
  }
42827
42946
  /**
@@ -43049,6 +43168,7 @@ function diffMemorySnapshots(before, after) {
43049
43168
  export {
43050
43169
  Atbash,
43051
43170
  AtbashAPIError,
43171
+ BOOT_SYNC_HINT,
43052
43172
  DEFAULT_BLOCKCHAIN_RID,
43053
43173
  DEFAULT_CHROMIA_NODE_URLS,
43054
43174
  DEFAULT_ENDPOINT,
@@ -43056,11 +43176,14 @@ export {
43056
43176
  DEFAULT_MEMORY_READ_TOOL_NAMES,
43057
43177
  DEFAULT_MEMORY_WRITE_TOOL_NAMES,
43058
43178
  EciesDomain,
43179
+ KEY_FILENAMES,
43059
43180
  MemoryGuardManager,
43060
43181
  MemoryIntegrityError,
43061
43182
  PointerStore,
43062
43183
  SignatureVerificationError,
43184
+ bootSyncFailureLine,
43063
43185
  buildAllowedJudgeHosts,
43186
+ chooseKeyPath,
43064
43187
  claimHashHex,
43065
43188
  classifyMemoryRead,
43066
43189
  classifyMemoryWrite,
@@ -43095,6 +43218,7 @@ export {
43095
43218
  isEnvelope,
43096
43219
  isValidPrivateKey,
43097
43220
  keyFingerprintOf,
43221
+ keyPathCandidates,
43098
43222
  loadAgent,
43099
43223
  loadAgentFromFile,
43100
43224
  loadUserConfig,