@atbash/sdk 0.12.0-dev.0 → 0.13.2-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
@@ -3313,11 +3313,34 @@ var ENV_MAP = {
3313
3313
  judgeEndpoint: "ATBASH_ENDPOINT",
3314
3314
  // Same variable name the Hermes plugin already documents.
3315
3315
  judgeVerifyPubKey: "ATBASH_JUDGE_VERIFY_PUBKEY",
3316
- blockchainRid: "ATBASH_BLOCKCHAIN_RID",
3316
+ defaultChainNetwork: "ATBASH_DEFAULT_CHAIN_NETWORK",
3317
3317
  provider: "ATBASH_PROVIDER",
3318
3318
  providerModel: "ATBASH_PROVIDER_MODEL",
3319
3319
  debug: "ATBASH_DEBUG"
3320
3320
  };
3321
+ var DEPRECATED_ENV_VARS = ["ATBASH_BLOCKCHAIN_RID"];
3322
+ var DEPRECATED_CONFIG_FIELDS = ["blockchainRid"];
3323
+ var deprecatedWarned = false;
3324
+ function warnDeprecatedEnvVarsOnce(log = console.warn) {
3325
+ if (deprecatedWarned) return;
3326
+ for (const name2 of DEPRECATED_ENV_VARS) {
3327
+ if (process.env[name2]) {
3328
+ deprecatedWarned = true;
3329
+ log(
3330
+ `[atbash] ${name2} is ignored \u2014 each org's chain is resolved from the dashboard; set ATBASH_DEFAULT_CHAIN_NETWORK=private only to pin everything to private`
3331
+ );
3332
+ }
3333
+ }
3334
+ const fileConfig = loadUserConfig();
3335
+ for (const field of DEPRECATED_CONFIG_FIELDS) {
3336
+ if (fileConfig[field]) {
3337
+ deprecatedWarned = true;
3338
+ log(
3339
+ `[atbash] "${field}" in ${getConfigPath()} is ignored \u2014 pass \`chain\` or \`network\` at construction instead`
3340
+ );
3341
+ }
3342
+ }
3343
+ }
3321
3344
  function getConfigDir() {
3322
3345
  const home2 = process.env.HOME || homedir3() || "";
3323
3346
  return join3(home2, ".config", "atbash");
@@ -3359,11 +3382,41 @@ function resolve(key3, flagValue) {
3359
3382
  if (fileVal != null) return String(fileVal);
3360
3383
  return "";
3361
3384
  }
3385
+ function forcedChainNetwork(flagValue) {
3386
+ const raw2 = resolve("defaultChainNetwork", flagValue);
3387
+ if (!raw2) return void 0;
3388
+ if (raw2 !== "public" && raw2 !== "private") {
3389
+ throw new Error(
3390
+ `ATBASH_DEFAULT_CHAIN_NETWORK / defaultChainNetwork must be "public" or "private", got ${JSON.stringify(raw2)} \u2014 unset it to let each org's chain decide.`
3391
+ );
3392
+ }
3393
+ return raw2;
3394
+ }
3362
3395
 
3363
3396
  // src-ts/client.ts
3364
3397
  function generateToolCallId() {
3365
3398
  return `tc-${Date.now()}-${randomHex(4)}`;
3366
3399
  }
3400
+ function resolveConstructorChain(options) {
3401
+ if (options.chain) return options.chain;
3402
+ const hasNodeUrls = options.nodeUrls !== void 0;
3403
+ const hasBrid = options.blockchainRid !== void 0;
3404
+ if (hasNodeUrls !== hasBrid) {
3405
+ throw new Error(
3406
+ 'nodeUrls and blockchainRid must be provided together \u2014 passing one without the other 404s every chain request. Prefer `chain: PUBLIC_CHAIN | PRIVATE_CHAIN` or `network: "public" | "private"`.'
3407
+ );
3408
+ }
3409
+ if (hasNodeUrls && hasBrid) {
3410
+ const brid = options.blockchainRid;
3411
+ const derivedNetwork = options.network ?? (brid === PUBLIC_CHAIN.blockchainRid ? "public" : brid === PRIVATE_CHAIN.blockchainRid ? "private" : "private");
3412
+ return {
3413
+ network: derivedNetwork,
3414
+ blockchainRid: brid,
3415
+ nodeUrls: options.nodeUrls
3416
+ };
3417
+ }
3418
+ return chainForNetwork(options.network ?? forcedChainNetwork() ?? "private");
3419
+ }
3367
3420
  var Atbash = class _Atbash {
3368
3421
  auth;
3369
3422
  endpoint;
@@ -3387,6 +3440,27 @@ var Atbash = class _Atbash {
3387
3440
  * calls don't re-hit the dashboard. Cleared by `clearChainCache()`.
3388
3441
  */
3389
3442
  _chainCache = /* @__PURE__ */ new Map();
3443
+ /**
3444
+ * The chain the constructor settled on. Used only where a lookup returns no
3445
+ * answer — see {@link resolveChainFromMap}.
3446
+ */
3447
+ _defaultChain;
3448
+ /**
3449
+ * True when the caller named a chain outright — `chain`, `network`, or the
3450
+ * paired `blockchainRid` + `nodeUrls`.
3451
+ *
3452
+ * Such a client is never re-pointed: not by the migration switch, and not by
3453
+ * where an org turns out to live. Naming a chain is the caller saying "talk
3454
+ * to this one", and silently routing elsewhere would make the argument a
3455
+ * suggestion. A client that names nothing is the one that follows the org.
3456
+ */
3457
+ _explicitChain;
3458
+ /**
3459
+ * The fleet-wide chain switch, read once at construction. `resolve()` hits
3460
+ * the config file on disk, so re-reading it per call would put a file read
3461
+ * on every judge.
3462
+ */
3463
+ _forcedNetwork;
3390
3464
  /**
3391
3465
  * Short-TTL cache for `/api/ai/exists`. The `registered` field is
3392
3466
  * monotonic (once true, stays true), so most calls in a burst re-fetch
@@ -3419,8 +3493,13 @@ var Atbash = class _Atbash {
3419
3493
  } : { endpoint: options.endpoint }
3420
3494
  );
3421
3495
  this.endpoint = validated.url;
3422
- this.nodeUrls = options.nodeUrls ? [...options.nodeUrls] : DEFAULT_CHROMIA_NODE_URLS;
3423
- this.blockchainRid = options.blockchainRid ?? native.DEFAULT_BLOCKCHAIN_RID;
3496
+ const resolvedChain = resolveConstructorChain(options);
3497
+ this.nodeUrls = [...resolvedChain.nodeUrls];
3498
+ this.blockchainRid = resolvedChain.blockchainRid;
3499
+ this._defaultChain = resolvedChain;
3500
+ this._explicitChain = options.chain !== void 0 || options.network !== void 0 || options.blockchainRid !== void 0 && options.nodeUrls !== void 0;
3501
+ this._forcedNetwork = forcedChainNetwork();
3502
+ warnDeprecatedEnvVarsOnce((msg) => options.logger?.warn?.(msg));
3424
3503
  this.orgName = options.orgName;
3425
3504
  this.verifyPubKey = validated.verifyPubKey ?? void 0;
3426
3505
  this.orgEncryptionPubKey = options.orgEncryptionPubKey;
@@ -3496,10 +3575,11 @@ var Atbash = class _Atbash {
3496
3575
  );
3497
3576
  const agentKey = resolve("agentKey", options.agentKey);
3498
3577
  const auth = agentKey ? native.loadAgent(agentKey) : loadAgentFromFile(options.keyPath);
3499
- const blockchainRid = resolve("blockchainRid", options.blockchainRid) || void 0;
3500
3578
  return new _Atbash(auth.privkey, {
3501
3579
  endpoint: validated.url,
3502
- blockchainRid,
3580
+ chain: options.chain,
3581
+ network: options.network,
3582
+ blockchainRid: options.blockchainRid,
3503
3583
  timeoutMs: options.timeoutMs,
3504
3584
  nodeUrls: options.nodeUrls,
3505
3585
  orgName: options.orgName,
@@ -3540,7 +3620,7 @@ var Atbash = class _Atbash {
3540
3620
  return this.track("checkAgentExists", pk, async () => {
3541
3621
  const query = { pubkey: pk };
3542
3622
  if (network) query.network = network;
3543
- const brid = this.bridFromChainOpts(network ? { network } : void 0);
3623
+ const brid = network ? this.bridFromChainOpts({ network }) : await this.defaultOrgBrid();
3544
3624
  const resp = await this.http.get(
3545
3625
  "/api/ai/exists",
3546
3626
  query,
@@ -3591,7 +3671,7 @@ var Atbash = class _Atbash {
3591
3671
  };
3592
3672
  }
3593
3673
  const toolCallId = generateToolCallId();
3594
- const brid = this.bridFromChainOpts(options.chainOpts);
3674
+ const brid = options.chainOpts?.blockchainRid || options.chainOpts?.network ? this.bridFromChainOpts(options.chainOpts) : await this.defaultOrgBrid() ?? this.blockchainRid;
3595
3675
  const orgKey = options.orgEncryptionPubKey ?? this.orgEncryptionPubKey ?? this._orgKeyFromChain;
3596
3676
  try {
3597
3677
  const signedHex = orgKey ? signEncryptedToolCall(
@@ -3642,7 +3722,11 @@ var Atbash = class _Atbash {
3642
3722
  throw new Error("action is required and cannot be empty.");
3643
3723
  }
3644
3724
  let chainOpts = options.chainOpts;
3645
- if (options.orgName) {
3725
+ if (options.orgName && this._explicitChain) {
3726
+ chainOpts = options.chainOpts ?? { network: this._defaultChain.network };
3727
+ } else if (options.orgName && this.forcedNetwork()) {
3728
+ chainOpts = { network: this.forcedNetwork() };
3729
+ } else if (options.orgName) {
3646
3730
  const cached = this._chainCache.get(options.orgName);
3647
3731
  if (cached) {
3648
3732
  chainOpts = { network: cached.network };
@@ -3876,7 +3960,7 @@ var Atbash = class _Atbash {
3876
3960
  const resp = await this.http.get(
3877
3961
  "/api/v1/judge",
3878
3962
  { tool_call_id: judgmentId, agent_pubkey: pk },
3879
- this.authHeaders()
3963
+ this.authHeaders(await this.defaultOrgBrid())
3880
3964
  );
3881
3965
  await this.raiseIfError(resp);
3882
3966
  const data = await this.json(resp) ?? {};
@@ -3896,7 +3980,11 @@ var Atbash = class _Atbash {
3896
3980
  return this.track(
3897
3981
  "getToolCalls",
3898
3982
  void 0,
3899
- () => this.riskEngineRecords("tool-calls", { limit: maxCount })
3983
+ async () => this.riskEngineRecords(
3984
+ "tool-calls",
3985
+ { limit: maxCount },
3986
+ await this.defaultOrgBrid()
3987
+ )
3900
3988
  );
3901
3989
  }
3902
3990
  async getOrgToolCalls(orgName, maxCount) {
@@ -3913,24 +4001,31 @@ var Atbash = class _Atbash {
3913
4001
  return this.track(
3914
4002
  "getAgentToolCalls",
3915
4003
  agentPubkey,
3916
- () => this.riskEngineRecords("agent-tool-calls", {
3917
- agent: agentPubkey,
3918
- limit: maxCount
3919
- })
4004
+ async () => this.riskEngineRecords(
4005
+ "agent-tool-calls",
4006
+ { agent: agentPubkey, limit: maxCount },
4007
+ await this.defaultOrgBrid()
4008
+ )
3920
4009
  );
3921
4010
  }
3922
4011
  async getToolCallCount() {
3923
4012
  return this.track("getToolCallCount", void 0, async () => {
3924
- const raw2 = await this.riskEngineGet("tool-call-count", {});
4013
+ const raw2 = await this.riskEngineGet(
4014
+ "tool-call-count",
4015
+ {},
4016
+ await this.defaultOrgBrid()
4017
+ );
3925
4018
  const n = Number(raw2);
3926
4019
  return Number.isFinite(n) ? n : 0;
3927
4020
  });
3928
4021
  }
3929
4022
  async getToolCallFull(toolCallId) {
3930
4023
  return this.track("getToolCallFull", void 0, async () => {
3931
- const raw2 = await this.riskEngineGet("tool-call-full", {
3932
- tool_call_id: toolCallId
3933
- });
4024
+ const raw2 = await this.riskEngineGet(
4025
+ "tool-call-full",
4026
+ { tool_call_id: toolCallId },
4027
+ await this.defaultOrgBrid()
4028
+ );
3934
4029
  if (!isRecord(raw2)) return null;
3935
4030
  return toToolCallFull(raw2);
3936
4031
  });
@@ -4016,7 +4111,7 @@ var Atbash = class _Atbash {
4016
4111
  const resp = await this.http.get(
4017
4112
  "/api/insurance",
4018
4113
  { action: "safety-stats" },
4019
- this.authHeaders()
4114
+ this.authHeaders(await this.defaultOrgBrid())
4020
4115
  );
4021
4116
  await this.raiseIfError(resp);
4022
4117
  const data = await this.json(resp) ?? {};
@@ -4072,10 +4167,15 @@ var Atbash = class _Atbash {
4072
4167
  * 2. Per-chain subscription fallback — public + private records
4073
4168
  * are fetched in parallel, with `is_private_blockchain` and
4074
4169
  * `assigned_at` reconciling mixed states.
4075
- * Defaults to the public chain when nothing else resolves.
4170
+ * A lookup that names exactly one chain wins outright. Where it names
4171
+ * neither (a brand-new org) or cannot choose between them, the client's
4172
+ * configured default decides.
4076
4173
  */
4077
4174
  async resolveChainForOrg(orgName) {
4078
4175
  const name2 = orgName.trim();
4176
+ if (this._explicitChain) return this._defaultChain;
4177
+ const forced = this.forcedNetwork();
4178
+ if (forced) return chainForNetwork(forced);
4079
4179
  const cached = this._chainCache.get(name2);
4080
4180
  if (cached) return cached;
4081
4181
  const mapNetwork = await this.getActiveNetworkForOrg(name2);
@@ -4107,7 +4207,7 @@ var Atbash = class _Atbash {
4107
4207
  return PRIVATE_CHAIN;
4108
4208
  }
4109
4209
  if (pubSub && privSub) {
4110
- const chain = privSub.assigned_at > pubSub.assigned_at ? PRIVATE_CHAIN : PUBLIC_CHAIN;
4210
+ const chain = privSub.assigned_at === pubSub.assigned_at ? this._defaultChain : privSub.assigned_at > pubSub.assigned_at ? PRIVATE_CHAIN : PUBLIC_CHAIN;
4111
4211
  this._chainCache.set(orgName, chain);
4112
4212
  return chain;
4113
4213
  }
@@ -4128,8 +4228,8 @@ var Atbash = class _Atbash {
4128
4228
  this.endpoint
4129
4229
  );
4130
4230
  }
4131
- this._chainCache.set(orgName, PUBLIC_CHAIN);
4132
- return PUBLIC_CHAIN;
4231
+ this._chainCache.set(orgName, this._defaultChain);
4232
+ return this._defaultChain;
4133
4233
  }
4134
4234
  /** Drop any cached chain resolutions. Useful in tests. */
4135
4235
  clearChainCache() {
@@ -4180,6 +4280,7 @@ var Atbash = class _Atbash {
4180
4280
  async resolveAgentLookupNetwork(options) {
4181
4281
  if (options.chainOpts?.network) return options.chainOpts.network;
4182
4282
  if (options.chainOpts?.blockchainRid) return void 0;
4283
+ if (this._explicitChain) return this._defaultChain.network;
4183
4284
  const orgName = options.orgName ?? this.orgName;
4184
4285
  if (!orgName) return void 0;
4185
4286
  return (await this.resolveChainForOrg(orgName)).network;
@@ -4245,15 +4346,40 @@ var Atbash = class _Atbash {
4245
4346
  }
4246
4347
  /**
4247
4348
  * BRID for an org — one round-trip to the map, honoring the client's chain
4248
- * cache. Returns undefined when the org is unknown, so the caller falls
4249
- * back to the client default (best-effort discovery).
4349
+ * cache. A "brand-new org" (nothing anywhere names its chain) is not an
4350
+ * error — `resolveChainForOrg` returns the client default for that case and
4351
+ * this helper returns its BRID. A transport failure or non-200 from
4352
+ * `/api/org-network` IS an error and propagates: the caller cannot fall
4353
+ * back to the default chain on outage, because with multi-chain live that
4354
+ * silently reads from the wrong chain. Matches the Python binding's
4355
+ * `_brid_for_org` semantics.
4250
4356
  */
4251
4357
  async bridForOrg(orgName) {
4252
- try {
4253
- return (await this.resolveChainForOrg(orgName)).blockchainRid;
4254
- } catch {
4255
- return void 0;
4256
- }
4358
+ return (await this.resolveChainForOrg(orgName)).blockchainRid;
4359
+ }
4360
+ /**
4361
+ * BRID for the client's configured default org, if it has one.
4362
+ *
4363
+ * Calls that carry no `orgName` argument are not chain-less: they still
4364
+ * belong to `this.orgName`, and that org lives on exactly one chain. Routing
4365
+ * them by the constructor's chain instead means a client configured
4366
+ * `network: "private"` reads the private chain for an org that lives on
4367
+ * public, and gets an empty answer rather than an error. So where an org is
4368
+ * known the org decides the chain, and the constructor's chain is what is
4369
+ * left when no org is known at all — the order `resolveAgentLookupNetwork`
4370
+ * already applies to agent metadata reads, and the order the dashboard
4371
+ * applies in `resolveChainForWallet`.
4372
+ *
4373
+ * Undefined when there is no default org, so callers keep falling back to
4374
+ * the client default.
4375
+ */
4376
+ /** The switch's chain, unless this client named one of its own. */
4377
+ forcedNetwork() {
4378
+ return this._explicitChain ? void 0 : this._forcedNetwork;
4379
+ }
4380
+ async defaultOrgBrid() {
4381
+ if (this._explicitChain || !this.orgName) return void 0;
4382
+ return this.bridForOrg(this.orgName);
4257
4383
  }
4258
4384
  async raiseIfError(resp) {
4259
4385
  if (resp.ok) return;
@@ -4523,21 +4649,26 @@ async function scanMemory(entry, auth, opts) {
4523
4649
  }
4524
4650
  const KNOWN_ACTIONS = ["allow", "block", "hold_for_user_confirm"];
4525
4651
  const action = result.actionType.trim().toLowerCase();
4526
- if (result.verdict !== "No verdict" && !KNOWN_ACTIONS.includes(action)) {
4652
+ if (result.verdict !== "No verdict" && action !== "" && !KNOWN_ACTIONS.includes(action)) {
4527
4653
  throw new Error(
4528
- `memory scan: unrecognized action_type from judge (${result.actionType || "absent"})`
4654
+ `memory scan: unrecognized action_type from judge (${result.actionType})`
4529
4655
  );
4530
4656
  }
4531
- const mapped = native.mapVerdict(
4532
- action,
4533
- result.confidence,
4534
- threshold
4535
- );
4536
- let verdict = mapped;
4537
- if (result.verdict === "BLOCK") {
4538
- verdict = "red";
4539
- } else if (result.verdict === "HOLD" && mapped === "green") {
4540
- verdict = "yellow";
4657
+ let verdict;
4658
+ if (action === "") {
4659
+ verdict = result.verdict === "BLOCK" ? "red" : result.verdict === "HOLD" ? "yellow" : "green";
4660
+ } else {
4661
+ const mapped = native.mapVerdict(
4662
+ action,
4663
+ result.confidence,
4664
+ threshold
4665
+ );
4666
+ verdict = mapped;
4667
+ if (result.verdict === "BLOCK") {
4668
+ verdict = "red";
4669
+ } else if (result.verdict === "HOLD" && mapped === "green") {
4670
+ verdict = "yellow";
4671
+ }
4541
4672
  }
4542
4673
  const parsed = native.parseScoreFromReason(result.reason);
4543
4674
  const score = result.score ?? parsed.score ?? native.defaultScoreForVerdict(verdict);
@@ -43504,10 +43635,13 @@ export {
43504
43635
  KEY_FILENAMES,
43505
43636
  MemoryGuardManager,
43506
43637
  MemoryIntegrityError,
43638
+ PRIVATE_CHAIN,
43639
+ PUBLIC_CHAIN,
43507
43640
  PointerStore,
43508
43641
  SignatureVerificationError,
43509
43642
  bootSyncFailureLine,
43510
43643
  buildAllowedJudgeHosts,
43644
+ chainForNetwork,
43511
43645
  chooseKeyPath,
43512
43646
  claimHashHex,
43513
43647
  classifyMemoryRead,