@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.js CHANGED
@@ -2869,10 +2869,13 @@ __export(src_ts_exports, {
2869
2869
  KEY_FILENAMES: () => KEY_FILENAMES,
2870
2870
  MemoryGuardManager: () => MemoryGuardManager,
2871
2871
  MemoryIntegrityError: () => MemoryIntegrityError,
2872
+ PRIVATE_CHAIN: () => PRIVATE_CHAIN,
2873
+ PUBLIC_CHAIN: () => PUBLIC_CHAIN,
2872
2874
  PointerStore: () => PointerStore,
2873
2875
  SignatureVerificationError: () => SignatureVerificationError,
2874
2876
  bootSyncFailureLine: () => bootSyncFailureLine,
2875
2877
  buildAllowedJudgeHosts: () => buildAllowedJudgeHosts,
2878
+ chainForNetwork: () => chainForNetwork,
2876
2879
  chooseKeyPath: () => chooseKeyPath,
2877
2880
  claimHashHex: () => claimHashHex,
2878
2881
  classifyMemoryRead: () => classifyMemoryRead,
@@ -3394,11 +3397,34 @@ var ENV_MAP = {
3394
3397
  judgeEndpoint: "ATBASH_ENDPOINT",
3395
3398
  // Same variable name the Hermes plugin already documents.
3396
3399
  judgeVerifyPubKey: "ATBASH_JUDGE_VERIFY_PUBKEY",
3397
- blockchainRid: "ATBASH_BLOCKCHAIN_RID",
3400
+ defaultChainNetwork: "ATBASH_DEFAULT_CHAIN_NETWORK",
3398
3401
  provider: "ATBASH_PROVIDER",
3399
3402
  providerModel: "ATBASH_PROVIDER_MODEL",
3400
3403
  debug: "ATBASH_DEBUG"
3401
3404
  };
3405
+ var DEPRECATED_ENV_VARS = ["ATBASH_BLOCKCHAIN_RID"];
3406
+ var DEPRECATED_CONFIG_FIELDS = ["blockchainRid"];
3407
+ var deprecatedWarned = false;
3408
+ function warnDeprecatedEnvVarsOnce(log = console.warn) {
3409
+ if (deprecatedWarned) return;
3410
+ for (const name2 of DEPRECATED_ENV_VARS) {
3411
+ if (process.env[name2]) {
3412
+ deprecatedWarned = true;
3413
+ log(
3414
+ `[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`
3415
+ );
3416
+ }
3417
+ }
3418
+ const fileConfig = loadUserConfig();
3419
+ for (const field of DEPRECATED_CONFIG_FIELDS) {
3420
+ if (fileConfig[field]) {
3421
+ deprecatedWarned = true;
3422
+ log(
3423
+ `[atbash] "${field}" in ${getConfigPath()} is ignored \u2014 pass \`chain\` or \`network\` at construction instead`
3424
+ );
3425
+ }
3426
+ }
3427
+ }
3402
3428
  function getConfigDir() {
3403
3429
  const home2 = process.env.HOME || (0, import_node_os3.homedir)() || "";
3404
3430
  return (0, import_node_path3.join)(home2, ".config", "atbash");
@@ -3440,11 +3466,41 @@ function resolve(key3, flagValue) {
3440
3466
  if (fileVal != null) return String(fileVal);
3441
3467
  return "";
3442
3468
  }
3469
+ function forcedChainNetwork(flagValue) {
3470
+ const raw2 = resolve("defaultChainNetwork", flagValue);
3471
+ if (!raw2) return void 0;
3472
+ if (raw2 !== "public" && raw2 !== "private") {
3473
+ throw new Error(
3474
+ `ATBASH_DEFAULT_CHAIN_NETWORK / defaultChainNetwork must be "public" or "private", got ${JSON.stringify(raw2)} \u2014 unset it to let each org's chain decide.`
3475
+ );
3476
+ }
3477
+ return raw2;
3478
+ }
3443
3479
 
3444
3480
  // src-ts/client.ts
3445
3481
  function generateToolCallId() {
3446
3482
  return `tc-${Date.now()}-${randomHex(4)}`;
3447
3483
  }
3484
+ function resolveConstructorChain(options) {
3485
+ if (options.chain) return options.chain;
3486
+ const hasNodeUrls = options.nodeUrls !== void 0;
3487
+ const hasBrid = options.blockchainRid !== void 0;
3488
+ if (hasNodeUrls !== hasBrid) {
3489
+ throw new Error(
3490
+ '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"`.'
3491
+ );
3492
+ }
3493
+ if (hasNodeUrls && hasBrid) {
3494
+ const brid = options.blockchainRid;
3495
+ const derivedNetwork = options.network ?? (brid === PUBLIC_CHAIN.blockchainRid ? "public" : brid === PRIVATE_CHAIN.blockchainRid ? "private" : "private");
3496
+ return {
3497
+ network: derivedNetwork,
3498
+ blockchainRid: brid,
3499
+ nodeUrls: options.nodeUrls
3500
+ };
3501
+ }
3502
+ return chainForNetwork(options.network ?? forcedChainNetwork() ?? "private");
3503
+ }
3448
3504
  var Atbash = class _Atbash {
3449
3505
  auth;
3450
3506
  endpoint;
@@ -3468,6 +3524,27 @@ var Atbash = class _Atbash {
3468
3524
  * calls don't re-hit the dashboard. Cleared by `clearChainCache()`.
3469
3525
  */
3470
3526
  _chainCache = /* @__PURE__ */ new Map();
3527
+ /**
3528
+ * The chain the constructor settled on. Used only where a lookup returns no
3529
+ * answer — see {@link resolveChainFromMap}.
3530
+ */
3531
+ _defaultChain;
3532
+ /**
3533
+ * True when the caller named a chain outright — `chain`, `network`, or the
3534
+ * paired `blockchainRid` + `nodeUrls`.
3535
+ *
3536
+ * Such a client is never re-pointed: not by the migration switch, and not by
3537
+ * where an org turns out to live. Naming a chain is the caller saying "talk
3538
+ * to this one", and silently routing elsewhere would make the argument a
3539
+ * suggestion. A client that names nothing is the one that follows the org.
3540
+ */
3541
+ _explicitChain;
3542
+ /**
3543
+ * The fleet-wide chain switch, read once at construction. `resolve()` hits
3544
+ * the config file on disk, so re-reading it per call would put a file read
3545
+ * on every judge.
3546
+ */
3547
+ _forcedNetwork;
3471
3548
  /**
3472
3549
  * Short-TTL cache for `/api/ai/exists`. The `registered` field is
3473
3550
  * monotonic (once true, stays true), so most calls in a burst re-fetch
@@ -3500,8 +3577,13 @@ var Atbash = class _Atbash {
3500
3577
  } : { endpoint: options.endpoint }
3501
3578
  );
3502
3579
  this.endpoint = validated.url;
3503
- this.nodeUrls = options.nodeUrls ? [...options.nodeUrls] : DEFAULT_CHROMIA_NODE_URLS;
3504
- this.blockchainRid = options.blockchainRid ?? native.DEFAULT_BLOCKCHAIN_RID;
3580
+ const resolvedChain = resolveConstructorChain(options);
3581
+ this.nodeUrls = [...resolvedChain.nodeUrls];
3582
+ this.blockchainRid = resolvedChain.blockchainRid;
3583
+ this._defaultChain = resolvedChain;
3584
+ this._explicitChain = options.chain !== void 0 || options.network !== void 0 || options.blockchainRid !== void 0 && options.nodeUrls !== void 0;
3585
+ this._forcedNetwork = forcedChainNetwork();
3586
+ warnDeprecatedEnvVarsOnce((msg) => options.logger?.warn?.(msg));
3505
3587
  this.orgName = options.orgName;
3506
3588
  this.verifyPubKey = validated.verifyPubKey ?? void 0;
3507
3589
  this.orgEncryptionPubKey = options.orgEncryptionPubKey;
@@ -3577,10 +3659,11 @@ var Atbash = class _Atbash {
3577
3659
  );
3578
3660
  const agentKey = resolve("agentKey", options.agentKey);
3579
3661
  const auth = agentKey ? native.loadAgent(agentKey) : loadAgentFromFile(options.keyPath);
3580
- const blockchainRid = resolve("blockchainRid", options.blockchainRid) || void 0;
3581
3662
  return new _Atbash(auth.privkey, {
3582
3663
  endpoint: validated.url,
3583
- blockchainRid,
3664
+ chain: options.chain,
3665
+ network: options.network,
3666
+ blockchainRid: options.blockchainRid,
3584
3667
  timeoutMs: options.timeoutMs,
3585
3668
  nodeUrls: options.nodeUrls,
3586
3669
  orgName: options.orgName,
@@ -3621,7 +3704,7 @@ var Atbash = class _Atbash {
3621
3704
  return this.track("checkAgentExists", pk, async () => {
3622
3705
  const query = { pubkey: pk };
3623
3706
  if (network) query.network = network;
3624
- const brid = this.bridFromChainOpts(network ? { network } : void 0);
3707
+ const brid = network ? this.bridFromChainOpts({ network }) : await this.defaultOrgBrid();
3625
3708
  const resp = await this.http.get(
3626
3709
  "/api/ai/exists",
3627
3710
  query,
@@ -3672,7 +3755,7 @@ var Atbash = class _Atbash {
3672
3755
  };
3673
3756
  }
3674
3757
  const toolCallId = generateToolCallId();
3675
- const brid = this.bridFromChainOpts(options.chainOpts);
3758
+ const brid = options.chainOpts?.blockchainRid || options.chainOpts?.network ? this.bridFromChainOpts(options.chainOpts) : await this.defaultOrgBrid() ?? this.blockchainRid;
3676
3759
  const orgKey = options.orgEncryptionPubKey ?? this.orgEncryptionPubKey ?? this._orgKeyFromChain;
3677
3760
  try {
3678
3761
  const signedHex = orgKey ? signEncryptedToolCall(
@@ -3723,7 +3806,11 @@ var Atbash = class _Atbash {
3723
3806
  throw new Error("action is required and cannot be empty.");
3724
3807
  }
3725
3808
  let chainOpts = options.chainOpts;
3726
- if (options.orgName) {
3809
+ if (options.orgName && this._explicitChain) {
3810
+ chainOpts = options.chainOpts ?? { network: this._defaultChain.network };
3811
+ } else if (options.orgName && this.forcedNetwork()) {
3812
+ chainOpts = { network: this.forcedNetwork() };
3813
+ } else if (options.orgName) {
3727
3814
  const cached = this._chainCache.get(options.orgName);
3728
3815
  if (cached) {
3729
3816
  chainOpts = { network: cached.network };
@@ -3957,7 +4044,7 @@ var Atbash = class _Atbash {
3957
4044
  const resp = await this.http.get(
3958
4045
  "/api/v1/judge",
3959
4046
  { tool_call_id: judgmentId, agent_pubkey: pk },
3960
- this.authHeaders()
4047
+ this.authHeaders(await this.defaultOrgBrid())
3961
4048
  );
3962
4049
  await this.raiseIfError(resp);
3963
4050
  const data = await this.json(resp) ?? {};
@@ -3977,7 +4064,11 @@ var Atbash = class _Atbash {
3977
4064
  return this.track(
3978
4065
  "getToolCalls",
3979
4066
  void 0,
3980
- () => this.riskEngineRecords("tool-calls", { limit: maxCount })
4067
+ async () => this.riskEngineRecords(
4068
+ "tool-calls",
4069
+ { limit: maxCount },
4070
+ await this.defaultOrgBrid()
4071
+ )
3981
4072
  );
3982
4073
  }
3983
4074
  async getOrgToolCalls(orgName, maxCount) {
@@ -3994,24 +4085,31 @@ var Atbash = class _Atbash {
3994
4085
  return this.track(
3995
4086
  "getAgentToolCalls",
3996
4087
  agentPubkey,
3997
- () => this.riskEngineRecords("agent-tool-calls", {
3998
- agent: agentPubkey,
3999
- limit: maxCount
4000
- })
4088
+ async () => this.riskEngineRecords(
4089
+ "agent-tool-calls",
4090
+ { agent: agentPubkey, limit: maxCount },
4091
+ await this.defaultOrgBrid()
4092
+ )
4001
4093
  );
4002
4094
  }
4003
4095
  async getToolCallCount() {
4004
4096
  return this.track("getToolCallCount", void 0, async () => {
4005
- const raw2 = await this.riskEngineGet("tool-call-count", {});
4097
+ const raw2 = await this.riskEngineGet(
4098
+ "tool-call-count",
4099
+ {},
4100
+ await this.defaultOrgBrid()
4101
+ );
4006
4102
  const n = Number(raw2);
4007
4103
  return Number.isFinite(n) ? n : 0;
4008
4104
  });
4009
4105
  }
4010
4106
  async getToolCallFull(toolCallId) {
4011
4107
  return this.track("getToolCallFull", void 0, async () => {
4012
- const raw2 = await this.riskEngineGet("tool-call-full", {
4013
- tool_call_id: toolCallId
4014
- });
4108
+ const raw2 = await this.riskEngineGet(
4109
+ "tool-call-full",
4110
+ { tool_call_id: toolCallId },
4111
+ await this.defaultOrgBrid()
4112
+ );
4015
4113
  if (!isRecord(raw2)) return null;
4016
4114
  return toToolCallFull(raw2);
4017
4115
  });
@@ -4097,7 +4195,7 @@ var Atbash = class _Atbash {
4097
4195
  const resp = await this.http.get(
4098
4196
  "/api/insurance",
4099
4197
  { action: "safety-stats" },
4100
- this.authHeaders()
4198
+ this.authHeaders(await this.defaultOrgBrid())
4101
4199
  );
4102
4200
  await this.raiseIfError(resp);
4103
4201
  const data = await this.json(resp) ?? {};
@@ -4153,10 +4251,15 @@ var Atbash = class _Atbash {
4153
4251
  * 2. Per-chain subscription fallback — public + private records
4154
4252
  * are fetched in parallel, with `is_private_blockchain` and
4155
4253
  * `assigned_at` reconciling mixed states.
4156
- * Defaults to the public chain when nothing else resolves.
4254
+ * A lookup that names exactly one chain wins outright. Where it names
4255
+ * neither (a brand-new org) or cannot choose between them, the client's
4256
+ * configured default decides.
4157
4257
  */
4158
4258
  async resolveChainForOrg(orgName) {
4159
4259
  const name2 = orgName.trim();
4260
+ if (this._explicitChain) return this._defaultChain;
4261
+ const forced = this.forcedNetwork();
4262
+ if (forced) return chainForNetwork(forced);
4160
4263
  const cached = this._chainCache.get(name2);
4161
4264
  if (cached) return cached;
4162
4265
  const mapNetwork = await this.getActiveNetworkForOrg(name2);
@@ -4188,7 +4291,7 @@ var Atbash = class _Atbash {
4188
4291
  return PRIVATE_CHAIN;
4189
4292
  }
4190
4293
  if (pubSub && privSub) {
4191
- const chain = privSub.assigned_at > pubSub.assigned_at ? PRIVATE_CHAIN : PUBLIC_CHAIN;
4294
+ const chain = privSub.assigned_at === pubSub.assigned_at ? this._defaultChain : privSub.assigned_at > pubSub.assigned_at ? PRIVATE_CHAIN : PUBLIC_CHAIN;
4192
4295
  this._chainCache.set(orgName, chain);
4193
4296
  return chain;
4194
4297
  }
@@ -4209,8 +4312,8 @@ var Atbash = class _Atbash {
4209
4312
  this.endpoint
4210
4313
  );
4211
4314
  }
4212
- this._chainCache.set(orgName, PUBLIC_CHAIN);
4213
- return PUBLIC_CHAIN;
4315
+ this._chainCache.set(orgName, this._defaultChain);
4316
+ return this._defaultChain;
4214
4317
  }
4215
4318
  /** Drop any cached chain resolutions. Useful in tests. */
4216
4319
  clearChainCache() {
@@ -4261,6 +4364,7 @@ var Atbash = class _Atbash {
4261
4364
  async resolveAgentLookupNetwork(options) {
4262
4365
  if (options.chainOpts?.network) return options.chainOpts.network;
4263
4366
  if (options.chainOpts?.blockchainRid) return void 0;
4367
+ if (this._explicitChain) return this._defaultChain.network;
4264
4368
  const orgName = options.orgName ?? this.orgName;
4265
4369
  if (!orgName) return void 0;
4266
4370
  return (await this.resolveChainForOrg(orgName)).network;
@@ -4326,15 +4430,40 @@ var Atbash = class _Atbash {
4326
4430
  }
4327
4431
  /**
4328
4432
  * BRID for an org — one round-trip to the map, honoring the client's chain
4329
- * cache. Returns undefined when the org is unknown, so the caller falls
4330
- * back to the client default (best-effort discovery).
4433
+ * cache. A "brand-new org" (nothing anywhere names its chain) is not an
4434
+ * error — `resolveChainForOrg` returns the client default for that case and
4435
+ * this helper returns its BRID. A transport failure or non-200 from
4436
+ * `/api/org-network` IS an error and propagates: the caller cannot fall
4437
+ * back to the default chain on outage, because with multi-chain live that
4438
+ * silently reads from the wrong chain. Matches the Python binding's
4439
+ * `_brid_for_org` semantics.
4331
4440
  */
4332
4441
  async bridForOrg(orgName) {
4333
- try {
4334
- return (await this.resolveChainForOrg(orgName)).blockchainRid;
4335
- } catch {
4336
- return void 0;
4337
- }
4442
+ return (await this.resolveChainForOrg(orgName)).blockchainRid;
4443
+ }
4444
+ /**
4445
+ * BRID for the client's configured default org, if it has one.
4446
+ *
4447
+ * Calls that carry no `orgName` argument are not chain-less: they still
4448
+ * belong to `this.orgName`, and that org lives on exactly one chain. Routing
4449
+ * them by the constructor's chain instead means a client configured
4450
+ * `network: "private"` reads the private chain for an org that lives on
4451
+ * public, and gets an empty answer rather than an error. So where an org is
4452
+ * known the org decides the chain, and the constructor's chain is what is
4453
+ * left when no org is known at all — the order `resolveAgentLookupNetwork`
4454
+ * already applies to agent metadata reads, and the order the dashboard
4455
+ * applies in `resolveChainForWallet`.
4456
+ *
4457
+ * Undefined when there is no default org, so callers keep falling back to
4458
+ * the client default.
4459
+ */
4460
+ /** The switch's chain, unless this client named one of its own. */
4461
+ forcedNetwork() {
4462
+ return this._explicitChain ? void 0 : this._forcedNetwork;
4463
+ }
4464
+ async defaultOrgBrid() {
4465
+ if (this._explicitChain || !this.orgName) return void 0;
4466
+ return this.bridForOrg(this.orgName);
4338
4467
  }
4339
4468
  async raiseIfError(resp) {
4340
4469
  if (resp.ok) return;
@@ -4604,21 +4733,26 @@ async function scanMemory(entry, auth, opts) {
4604
4733
  }
4605
4734
  const KNOWN_ACTIONS = ["allow", "block", "hold_for_user_confirm"];
4606
4735
  const action = result.actionType.trim().toLowerCase();
4607
- if (result.verdict !== "No verdict" && !KNOWN_ACTIONS.includes(action)) {
4736
+ if (result.verdict !== "No verdict" && action !== "" && !KNOWN_ACTIONS.includes(action)) {
4608
4737
  throw new Error(
4609
- `memory scan: unrecognized action_type from judge (${result.actionType || "absent"})`
4738
+ `memory scan: unrecognized action_type from judge (${result.actionType})`
4610
4739
  );
4611
4740
  }
4612
- const mapped = native.mapVerdict(
4613
- action,
4614
- result.confidence,
4615
- threshold
4616
- );
4617
- let verdict = mapped;
4618
- if (result.verdict === "BLOCK") {
4619
- verdict = "red";
4620
- } else if (result.verdict === "HOLD" && mapped === "green") {
4621
- verdict = "yellow";
4741
+ let verdict;
4742
+ if (action === "") {
4743
+ verdict = result.verdict === "BLOCK" ? "red" : result.verdict === "HOLD" ? "yellow" : "green";
4744
+ } else {
4745
+ const mapped = native.mapVerdict(
4746
+ action,
4747
+ result.confidence,
4748
+ threshold
4749
+ );
4750
+ verdict = mapped;
4751
+ if (result.verdict === "BLOCK") {
4752
+ verdict = "red";
4753
+ } else if (result.verdict === "HOLD" && mapped === "green") {
4754
+ verdict = "yellow";
4755
+ }
4622
4756
  }
4623
4757
  const parsed = native.parseScoreFromReason(result.reason);
4624
4758
  const score = result.score ?? parsed.score ?? native.defaultScoreForVerdict(verdict);
@@ -43586,10 +43720,13 @@ function diffMemorySnapshots(before, after) {
43586
43720
  KEY_FILENAMES,
43587
43721
  MemoryGuardManager,
43588
43722
  MemoryIntegrityError,
43723
+ PRIVATE_CHAIN,
43724
+ PUBLIC_CHAIN,
43589
43725
  PointerStore,
43590
43726
  SignatureVerificationError,
43591
43727
  bootSyncFailureLine,
43592
43728
  buildAllowedJudgeHosts,
43729
+ chainForNetwork,
43593
43730
  chooseKeyPath,
43594
43731
  claimHashHex,
43595
43732
  classifyMemoryRead,