@atbash/sdk 0.9.0-dev.0 → 0.9.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.d.mts CHANGED
@@ -11,8 +11,34 @@ interface ValidatedEndpoint {
11
11
  policy: "default" | "self-hosted";
12
12
  verifyPubKey: string | null;
13
13
  }
14
+ /**
15
+ * Builds the trusted judge host set.
16
+ *
17
+ * The compiled-in default is always trusted: a `prod` build resolves it to
18
+ * atbash.ai, a dev build to whatever DEV_ENDPOINT was set at build time. No
19
+ * dev host is spelled out in source, and dev builds still validate their own
20
+ * default.
21
+ *
22
+ * Exported for tests only. The set is a build-time value and is deliberately
23
+ * never read from the process environment — an env var would let anyone widen
24
+ * the allowlist of an already-shipped artifact, which is the silent-redirection
25
+ * attack the allowlist exists to prevent (F-003). Asserting that requires
26
+ * calling this with the environment set, so it cannot stay module-private.
27
+ *
28
+ * @internal
29
+ */
30
+ declare function buildAllowedJudgeHosts(): ReadonlySet<string>;
14
31
  declare function validateJudgeEndpoint(judge?: JudgeEndpointConfig): ValidatedEndpoint;
15
32
 
33
+ interface ChainConfig {
34
+ readonly network: Network;
35
+ readonly blockchainRid: string;
36
+ readonly nodeUrls: readonly string[];
37
+ }
38
+ declare const PUBLIC_CHAIN: ChainConfig;
39
+ declare const PRIVATE_CHAIN: ChainConfig;
40
+ declare function chainForNetwork(network: Network): ChainConfig;
41
+
16
42
  /**
17
43
  * User-facing types. Two groups:
18
44
  * - Core types — the exact shapes the Rust core emits across the NAPI
@@ -143,6 +169,11 @@ interface LogToolCallResult {
143
169
  }
144
170
  interface JudgeResult {
145
171
  verdict: Verdict;
172
+ /**
173
+ * Canonical executable permission, computed by `canonicalAllow` — see that
174
+ * function for the rule. Never more permissive than `auditToolCall`.
175
+ */
176
+ allow: boolean;
146
177
  actionType: string;
147
178
  reason: string;
148
179
  confidence: number;
@@ -171,6 +202,8 @@ interface JudgeResult {
171
202
  interface JudgmentStatus {
172
203
  status: JudgmentState;
173
204
  verdict: Verdict;
205
+ /** Same canonical executable permission as {@link JudgeResult.allow}. */
206
+ allow: boolean;
174
207
  reason: string;
175
208
  judgmentId: string;
176
209
  onChain?: boolean;
@@ -257,6 +290,13 @@ interface AgentPolicy {
257
290
  isCustom: boolean;
258
291
  defaultPolicy: string;
259
292
  }
293
+ /** Options for agent metadata and policy lookups. */
294
+ interface AgentLookupOptions {
295
+ /** Resolve the agent's network from this organization's active network. */
296
+ orgName?: string;
297
+ /** Explicit per-call chain override. `network` selects the dashboard chain. */
298
+ chainOpts?: ChainOpts;
299
+ }
260
300
  /** Optional structured logger. */
261
301
  interface AtbashLogger {
262
302
  info?(...args: unknown[]): void;
@@ -266,7 +306,24 @@ interface AtbashLogger {
266
306
  interface AtbashOptions {
267
307
  endpoint?: string;
268
308
  timeoutMs?: number;
309
+ /**
310
+ * Full chain override — BRID + nodeUrls in one object. Wins over every
311
+ * other chain selector. Prefer this over paired `nodeUrls`/`blockchainRid`
312
+ * for anything but backwards compatibility.
313
+ */
314
+ chain?: ChainConfig;
315
+ /**
316
+ * Preset chain selector — `"public"` or `"private"`. Resolves to the
317
+ * matching `ChainConfig` via `chainForNetwork()`. Overridden by `chain`,
318
+ * overrides env `ATBASH_DEFAULT_CHAIN_NETWORK` and the config file.
319
+ */
320
+ network?: Network;
321
+ /**
322
+ * Explicit node URLs. Must be paired with `blockchainRid`. Passing one
323
+ * without the other throws — a BRID/nodes mismatch 404s every request.
324
+ */
269
325
  nodeUrls?: readonly string[];
326
+ /** Explicit BRID. Must be paired with `nodeUrls`. See {@link nodeUrls}. */
270
327
  blockchainRid?: string;
271
328
  /**
272
329
  * Default org name. When set, `judgeAction` / `auditToolCall` resolve
@@ -293,6 +350,12 @@ interface AtbashOptions {
293
350
  orgEncryptionPubKey?: string;
294
351
  /** When true (default), `auditToolCall` denies on any error. */
295
352
  failClosed?: boolean;
353
+ /**
354
+ * Verbose diagnostics. Off by default. When on, a failed judge call also
355
+ * logs the response body, which is the difference between "judge API failed"
356
+ * and knowing why it failed. Opt-in because that body can echo the action.
357
+ */
358
+ debug?: boolean;
296
359
  logger?: AtbashLogger;
297
360
  }
298
361
  /** Canonical decision returned by `auditToolCall`. */
@@ -327,20 +390,24 @@ interface FromConfigOptions {
327
390
  keyPath?: string;
328
391
  /** Judge endpoint config — validated against the allowlist / self-hosted policy. */
329
392
  judge?: JudgeEndpointConfig;
393
+ /** See {@link AtbashOptions.chain}. */
394
+ chain?: ChainConfig;
395
+ /** See {@link AtbashOptions.network}. */
396
+ network?: Network;
330
397
  blockchainRid?: string;
331
398
  timeoutMs?: number;
332
399
  nodeUrls?: readonly string[];
333
400
  /** Default org name — see {@link AtbashOptions.orgName}. */
334
401
  orgName?: string;
335
402
  failClosed?: boolean;
403
+ /** See {@link AtbashOptions.debug}. */
404
+ debug?: boolean;
336
405
  logger?: AtbashLogger;
337
406
  }
338
407
  /** Options accepted by `judgeAction`. */
339
408
  interface JudgeOptions {
340
409
  toolName?: string;
341
410
  toolArgsJson?: string;
342
- provider?: string;
343
- model?: string;
344
411
  verifyPubKey?: string;
345
412
  /** Per-call override of {@link AtbashOptions.orgEncryptionPubKey}. */
346
413
  orgEncryptionPubKey?: string;
@@ -376,12 +443,6 @@ interface LogToolCallOptions {
376
443
  orgEncryptionPubKey?: string;
377
444
  }
378
445
 
379
- interface ChainConfig {
380
- readonly network: Network;
381
- readonly blockchainRid: string;
382
- readonly nodeUrls: readonly string[];
383
- }
384
-
385
446
  declare class Atbash {
386
447
  readonly auth: AgentAuth;
387
448
  readonly endpoint: string;
@@ -395,6 +456,9 @@ declare class Atbash {
395
456
  readonly orgEncryptionPubKey?: string;
396
457
  /** When true (default), `auditToolCall` denies on any error. */
397
458
  readonly failClosed: boolean;
459
+ /** Org key learned from the last agent-exists check, for this agent only. */
460
+ private _orgKeyFromChain;
461
+ private readonly debug;
398
462
  private readonly logger;
399
463
  private readonly http;
400
464
  /**
@@ -402,13 +466,57 @@ declare class Atbash {
402
466
  * calls don't re-hit the dashboard. Cleared by `clearChainCache()`.
403
467
  */
404
468
  private readonly _chainCache;
469
+ /**
470
+ * The chain the constructor settled on. Used only where a lookup returns no
471
+ * answer — see {@link resolveChainFromMap}.
472
+ */
473
+ private readonly _defaultChain;
474
+ /**
475
+ * True when the caller named a chain outright — `chain`, `network`, or the
476
+ * paired `blockchainRid` + `nodeUrls`.
477
+ *
478
+ * Such a client is never re-pointed: not by the migration switch, and not by
479
+ * where an org turns out to live. Naming a chain is the caller saying "talk
480
+ * to this one", and silently routing elsewhere would make the argument a
481
+ * suggestion. A client that names nothing is the one that follows the org.
482
+ */
483
+ private readonly _explicitChain;
484
+ /**
485
+ * The fleet-wide chain switch, read once at construction. `resolve()` hits
486
+ * the config file on disk, so re-reading it per call would put a file read
487
+ * on every judge.
488
+ */
489
+ private readonly _forcedNetwork;
490
+ /**
491
+ * Short-TTL cache for `/api/ai/exists`. The `registered` field is
492
+ * monotonic (once true, stays true), so most calls in a burst re-fetch
493
+ * data that hasn't changed. The `org_encryption_pubkey` field CAN change
494
+ * — an org toggling encryption mid-session — so the TTL is deliberately
495
+ * short (see `AGENT_EXISTS_TTL_MS`). Keyed by (pubkey, network) so
496
+ * cross-agent / cross-network calls don't collide.
497
+ */
498
+ private _agentExistsCache;
499
+ private static readonly AGENT_EXISTS_TTL_MS;
405
500
  /**
406
501
  * Cached bearer token for risk-engine / insurance read calls. Built
407
502
  * lazily as a signed `log_tool_call` tx and refreshed every 4 min so
408
503
  * server-side replay protection windows never expire it mid-session.
409
504
  */
410
- private _authBearer;
505
+ private _authBearers;
506
+ /** Guards `logEnvironmentOnce` — hosts construct several clients. */
507
+ private static environmentLogged;
411
508
  constructor(privkey: string, options?: AtbashOptions);
509
+ /**
510
+ * Say which environment this build talks to, once per process.
511
+ *
512
+ * The endpoint and both chain RIDs are compiled in, chosen by the npm tag,
513
+ * and no configuration repoints a released build. So installing the build
514
+ * for the wrong environment is invisible: the plugin loads, the hook fires,
515
+ * and every judge call fails because the agent does not exist on the chain
516
+ * this build targets. Organisation names are not unique across environments
517
+ * either, so an org resolving is not evidence the build is right.
518
+ */
519
+ private logEnvironmentOnce;
412
520
  /**
413
521
  * Construct from resolved config: explicit overrides → env vars → the
414
522
  * `~/.config/atbash/config.json` file (see userConfig.resolve). The private
@@ -451,8 +559,27 @@ declare class Atbash {
451
559
  * explicitly false.
452
560
  */
453
561
  auditToolCall(input: ToolCallInput): Promise<Decision>;
562
+ /**
563
+ * One exit for every judge failure.
564
+ *
565
+ * Status and reason go in the *message*, not only in the meta object: hosts
566
+ * print the message and drop the meta, which is why this read as a bare
567
+ * "judge API failed" while the judge was answering with a precise reason.
568
+ * The response body follows only under `debug`, since it can echo the action.
569
+ */
570
+ private failJudge;
454
571
  private fail;
455
- getJudgmentStatus(judgmentId: string, agentPubkey?: string): Promise<JudgmentStatus>;
572
+ /**
573
+ * Return the current status of a previously submitted judgment.
574
+ *
575
+ * `chainOpts` names which chain the judgment was signed against. The
576
+ * server's GET /api/v1/judge routes to that chain when the SDK sends
577
+ * a `brid` query param; without it, the server falls back to public.
578
+ * Callers on the private chain must pass a `chainOpts` (or configure
579
+ * the client on the private chain) — otherwise polling a POSTed
580
+ * judgment on the private chain 404s at the server.
581
+ */
582
+ getJudgmentStatus(judgmentId: string, agentPubkey?: string, chainOpts?: ChainOpts): Promise<JudgmentStatus>;
456
583
  getToolCalls(maxCount: number): Promise<ToolCallRecord[]>;
457
584
  getOrgToolCalls(orgName: string, maxCount: number): Promise<ToolCallRecord[]>;
458
585
  getAgentToolCalls(agentPubkey: string, maxCount: number): Promise<ToolCallRecord[]>;
@@ -461,8 +588,8 @@ declare class Atbash {
461
588
  getOrgTierInfo(orgName: string): Promise<TierInfo | null>;
462
589
  getPendingHeldActions(orgName: string, maxCount: number): Promise<HeldAction[]>;
463
590
  getHeldActionReviews(orgName: string, maxCount: number): Promise<HeldActionReview[]>;
464
- getAgentDetail(agentPubkey: string): Promise<Record<string, unknown>>;
465
- getAgentPolicy(agentPubkey: string): Promise<AgentPolicy>;
591
+ getAgentDetail(agentPubkey: string, options?: AgentLookupOptions): Promise<Record<string, unknown>>;
592
+ getAgentPolicy(agentPubkey: string, options?: AgentLookupOptions): Promise<AgentPolicy>;
466
593
  getSafetyStats(): Promise<Record<string, unknown>>;
467
594
  /**
468
595
  * Org's subscription on a specific chain. The `network` arg selects
@@ -485,7 +612,9 @@ declare class Atbash {
485
612
  * 2. Per-chain subscription fallback — public + private records
486
613
  * are fetched in parallel, with `is_private_blockchain` and
487
614
  * `assigned_at` reconciling mixed states.
488
- * Defaults to the public chain when nothing else resolves.
615
+ * A lookup that names exactly one chain wins outright. Where it names
616
+ * neither (a brand-new org) or cannot choose between them, the client's
617
+ * configured default decides.
489
618
  */
490
619
  resolveChainForOrg(orgName: string): Promise<ChainConfig>;
491
620
  /**
@@ -497,6 +626,8 @@ declare class Atbash {
497
626
  private resolveChainFromMap;
498
627
  /** Drop any cached chain resolutions. Useful in tests. */
499
628
  clearChainCache(): void;
629
+ /** Drop the short-TTL `/api/ai/exists` cache. Useful in tests. */
630
+ clearAgentExistsCache(): void;
500
631
  /**
501
632
  * Wrap an SDK method body in telemetry — records the call at start
502
633
  * and a success/error duration at end. Re-throws on failure so the
@@ -511,6 +642,14 @@ declare class Atbash {
511
642
  * chains; otherwise the client's default.
512
643
  */
513
644
  private bridFromChainOpts;
645
+ /**
646
+ * Resolve the dashboard chain used by agent metadata/policy reads.
647
+ * Explicit per-call network overrides win; otherwise use the supplied org
648
+ * or the client's configured default org. A custom BRID is intentionally
649
+ * left untouched because it cannot be represented by the dashboard's
650
+ * public/private query selector.
651
+ */
652
+ private resolveAgentLookupNetwork;
514
653
  /**
515
654
  * Get-or-create a Bearer token for dashboard reads. The token is a
516
655
  * signed `log_tool_call` op (locally signed, never submitted) — the
@@ -523,10 +662,46 @@ declare class Atbash {
523
662
  private riskEngineGet;
524
663
  private riskEnginePost;
525
664
  private riskEngineRecords;
665
+ /**
666
+ * BRID for an org — one round-trip to the map, honoring the client's chain
667
+ * cache. A "brand-new org" (nothing anywhere names its chain) is not an
668
+ * error — `resolveChainForOrg` returns the client default for that case and
669
+ * this helper returns its BRID. A transport failure or non-200 from
670
+ * `/api/org-network` IS an error and propagates: the caller cannot fall
671
+ * back to the default chain on outage, because with multi-chain live that
672
+ * silently reads from the wrong chain. Matches the Python binding's
673
+ * `_brid_for_org` semantics.
674
+ */
675
+ private bridForOrg;
676
+ /**
677
+ * BRID for the client's configured default org, if it has one.
678
+ *
679
+ * Calls that carry no `orgName` argument are not chain-less: they still
680
+ * belong to `this.orgName`, and that org lives on exactly one chain. Routing
681
+ * them by the constructor's chain instead means a client configured
682
+ * `network: "private"` reads the private chain for an org that lives on
683
+ * public, and gets an empty answer rather than an error. So where an org is
684
+ * known the org decides the chain, and the constructor's chain is what is
685
+ * left when no org is known at all — the order `resolveAgentLookupNetwork`
686
+ * already applies to agent metadata reads, and the order the dashboard
687
+ * applies in `resolveChainForWallet`.
688
+ *
689
+ * Undefined when there is no default org, so callers keep falling back to
690
+ * the client default.
691
+ */
692
+ /** The switch's chain, unless this client named one of its own. */
693
+ private forcedNetwork;
694
+ private defaultOrgBrid;
526
695
  private raiseIfError;
527
696
  /** Wrap a failed HTTP *response* (non-2xx / non-200) as an AtbashAPIError. */
528
697
  private httpError;
529
- /** Wrap a *transport* failure (fetch threw, no response) as an AtbashAPIError. */
698
+ /**
699
+ * Wrap a transport failure (fetch threw, no response) as an AtbashAPIError.
700
+ *
701
+ * `HttpTransportError.kind` names the cause; the message is already
702
+ * human-readable. `debug` echoes the original exception so operators can
703
+ * cross-reference with node / undici logs when a class doesn't match.
704
+ */
530
705
  private transportError;
531
706
  private json;
532
707
  static generateKeypair(): KeyPair;
@@ -552,9 +727,62 @@ declare class SignatureVerificationError extends Error {
552
727
  constructor(message: string);
553
728
  }
554
729
 
730
+ /**
731
+ * Thin typed fetch wrapper.
732
+ *
733
+ * openapi-typescript emits types only (no runtime client), so this is the
734
+ * single hand-written transport — generic `get`/`post` over global `fetch`
735
+ * with a per-request timeout. The endpoint-specific request/response *shapes*
736
+ * are pulled from the generated `schema.ts` at the call sites in client.ts, so
737
+ * the wire contract still lives in spec/openapi.yaml. Methods return the raw
738
+ * `Response` so the caller can read the exact bytes the server signed before
739
+ * any decode (judge signature verification) — mirroring the Python surface's
740
+ * use of raw httpx (DECISIONS 2026-05-22).
741
+ */
742
+ type QueryValue = string | number | boolean | undefined | null;
743
+ declare class HttpClient {
744
+ readonly baseUrl: string;
745
+ readonly timeoutMs: number;
746
+ constructor(baseUrl: string, timeoutMs: number);
747
+ buildUrl(path: string, query?: Record<string, QueryValue>): string;
748
+ get(path: string, query?: Record<string, QueryValue>, headers?: Record<string, string>): Promise<Response>;
749
+ post(path: string, body: unknown, headers?: Record<string, string>): Promise<Response>;
750
+ private fetch;
751
+ }
752
+ /**
753
+ * A transport failure the SDK can act on. Every real cause the platform surfaces
754
+ * lands as one of these — the message names the cause in plain language so a
755
+ * plugin can show it to a user without decoding httpx / fetch internals.
756
+ *
757
+ * `cause` preserves the original error for debug logging; consumers that want
758
+ * the raw exception (e.g. tests) read it there.
759
+ */
760
+ declare class HttpTransportError extends Error {
761
+ readonly kind: "timeout" | "aborted" | "dns" | "connect_refused" | "connection_reset" | "unknown";
762
+ constructor(kind: HttpTransportError["kind"], message: string, options?: {
763
+ cause?: unknown;
764
+ });
765
+ }
766
+
767
+ declare function canonicalAllow(data: Record<string, unknown>): boolean;
768
+
555
769
  /** Normalize wire shapes (verdict casing, status, pubkey) to canonical forms. */
556
770
 
557
771
  declare function normalizeVerdict(raw: unknown): Verdict;
772
+ /**
773
+ * Canonicalize `action_type` at the wire boundary, the way
774
+ * {@link normalizeVerdict} already canonicalizes `verdict`.
775
+ *
776
+ * `verdict` has been normalized here since the beginning and has never
777
+ * drifted between consumers. `action_type` was passed through raw, so every
778
+ * reader invented its own folding policy — `auditToolCall` compared exactly
779
+ * while `memory/scan.ts` trimmed and case-folded, and the same `" ALLOW "`
780
+ * was therefore an error on one path and permission on the other.
781
+ *
782
+ * Trim and case-fold only. Zero-width and homoglyph variants survive
783
+ * untouched, stay outside the known set, and still fail closed.
784
+ */
785
+ declare function normalizeActionType(raw: unknown): string;
558
786
  declare function normalizeStatus(raw: unknown): JudgmentState;
559
787
  /** Wire pubkey may be a hex string, a Buffer/Uint8Array, or `{ data: [...] }`. */
560
788
  declare function pubkeyToHex(val: unknown): string;
@@ -563,9 +791,22 @@ interface AtbashUserConfig {
563
791
  agentKey?: string;
564
792
  orgName?: string;
565
793
  judgeEndpoint?: string;
566
- blockchainRid?: string;
567
- provider?: string;
568
- providerModel?: string;
794
+ /**
795
+ * Response-signing pubkey of a self-hosted judge (66 hex). Setting it makes
796
+ * `fromConfig` use the self-hosted policy for `judgeEndpoint`, which is the
797
+ * only way a non-allowlisted judge host is accepted.
798
+ */
799
+ judgeVerifyPubKey?: string;
800
+ /**
801
+ * `"private"` pins every org to the private chain regardless of where the
802
+ * dashboard says it lives — the migration switch. Leave it unset for the
803
+ * normal mode, where each org's own chain decides. There is no `"public"`
804
+ * value; a caller that wants one specific chain passes `chain` or `network`
805
+ * at construction instead.
806
+ */
807
+ defaultChainNetwork?: Network;
808
+ /** "1" / "true" turns on verbose diagnostics. See AtbashOptions.debug. */
809
+ debug?: string;
569
810
  }
570
811
  declare function getConfigDir(): string;
571
812
  declare function getConfigPath(): string;
@@ -576,6 +817,26 @@ declare function resolve(key: keyof AtbashUserConfig, flagValue?: string): strin
576
817
  declare function resolveKeyPath(input?: string): string;
577
818
  declare function loadAgentFromFile(keyPath?: string): AgentAuth;
578
819
 
820
+ /**
821
+ * Accepted key filenames, in precedence order.
822
+ *
823
+ * `guard-client-key` stays first: it is the name every existing install
824
+ * already has, and changing which file wins would silently switch agent
825
+ * identity for anyone holding both. `atbash-client-key` is accepted because
826
+ * it is the name people actually create — the old one carries retired
827
+ * branding — and hitting ENOENT on a key you just wrote, from a plugin that
828
+ * still reports itself installed, is a miserable first run.
829
+ */
830
+ declare const KEY_FILENAMES: readonly ["guard-client-key", "atbash-client-key"];
831
+ /** Every path checked when no explicit key path is given, in order. */
832
+ declare function keyPathCandidates(): string[];
833
+ /**
834
+ * Pick the key path: an explicit input wins untouched; otherwise the first
835
+ * accepted filename that exists, falling back to the preferred name so the
836
+ * error names something recognisable when nothing is there.
837
+ */
838
+ declare function chooseKeyPath(input: string | undefined, exists: (p: string) => boolean): string;
839
+
579
840
  /**
580
841
  * Canonical secret kinds reported by the Rust core in `SecretMatch.kind`.
581
842
  * Wire is permissive (modelled as a free string in {@link SecretMatch})
@@ -594,6 +855,42 @@ declare function verifyJudgeResponseSignature(bodyBytes: Uint8Array, signatureHe
594
855
  reason?: string;
595
856
  };
596
857
 
858
+ /**
859
+ * The boot memory-sync failure line.
860
+ *
861
+ * Split out of `guard-manager.ts` so it can be asserted without constructing a
862
+ * guard manager, which needs the native addon. The message is the whole point
863
+ * of AT-304: hosts print the message and drop the structured meta, so a cause
864
+ * that lives only in meta never reaches the operator.
865
+ */
866
+ /** Advice, not a diagnosis — appended only when the cause is unhelpful. */
867
+ declare const BOOT_SYNC_HINT = "check the chain endpoint and orgName if the cause above does not explain it";
868
+ /**
869
+ * Lead with the real cause.
870
+ *
871
+ * The previous wording named the chain endpoint and orgName as the things to
872
+ * check, which sent operators to verify configuration that was already correct
873
+ * while the actual cause (a node answering `404 Can't find blockchain with
874
+ * blockchainRID: …` for a chain it does not host) stayed hidden.
875
+ */
876
+ declare function bootSyncFailureLine(cause: unknown): string;
877
+
878
+ /**
879
+ * Make a chain query result crossable at the NAPI boundary.
880
+ *
881
+ * `postchain-client` decodes a Rell `byte_array` into a Node `Buffer`. The
882
+ * core's `parse*` functions take `serde_json::Value`, and NAPI-RS cannot
883
+ * convert a `Buffer` into one — it reaches the methods every `Uint8Array`
884
+ * inherits and fails with "JS functions cannot be represented as a
885
+ * serde_json::Value", which names functions for what is a plain Buffer.
886
+ *
887
+ * The conversion has to happen here because the failure is in the argument
888
+ * conversion, before any core code runs. Of the shapes the core accepts —
889
+ * hex string, `[u8]`, `{ data: [...] }` — hex is the cheapest to produce.
890
+ */
891
+ /** Recursively rewrite byte containers to hex, leaving everything else as-is. */
892
+ declare function gtvToFfiSafe(value: unknown): unknown;
893
+
597
894
  /** Derive the 32-byte AES key from an agent's secp256k1 privkey (hex). Deterministic — same privkey always yields the same key. */
598
895
  declare function deriveMemoryKey(privkey: string): Promise<Buffer>;
599
896
  /** Encrypt UTF-8 plaintext. `ciphertext` has the 16-byte GCM auth tag appended, matching the SDK's on-chain byte layout. */
@@ -608,8 +905,8 @@ declare function decryptMemoryContent(ciphertext: Buffer, nonce: Buffer, key: Bu
608
905
  * Scan a single memory entry for poisoning.
609
906
  *
610
907
  * `auth` is the agent that signs the on-chain audit log for the
611
- * LLM-judge call. The LLM is authoritative; unicode-evasion presence
612
- * is surfaced to the prompt so the LLM can weight suspicion accordingly.
908
+ * LLM-judge call. Unicode-evasion presence is surfaced to the prompt
909
+ * so the LLM can weight suspicion accordingly.
613
910
  */
614
911
  declare function scanMemory(entry: MemoryEntry, auth: AgentAuth, opts?: MemoryScanOptions): Promise<MemoryScanResult>;
615
912
  /**
@@ -623,15 +920,26 @@ declare function scanMemoryBatch(entries: MemoryEntry[], auth: AgentAuth, opts?:
623
920
  interface CommitMemoryOptions {
624
921
  /** Score in [1, 10]. Defaults to 5 (matches the Rell entity default). */
625
922
  score?: number;
923
+ /**
924
+ * Which memory file this commit targets. Defaults to `""` — the
925
+ * un-pathed slot, matching Rell's `file_path: text = ""` default.
926
+ * Pass an explicit filename (e.g. `"AGENTS.md"`) to keep files
927
+ * versioned independently on chain.
928
+ */
929
+ filePath?: string;
626
930
  /** Org name — when set, the SDK resolves which chain the agent lives on. */
627
931
  orgName?: string;
628
932
  /** Atbash service endpoint for org→chain lookup. */
629
933
  endpoint?: string;
934
+ /** Self-hosted judge response-signing pubkey; required with a non-allowlisted `endpoint`. */
935
+ verifyPubKey?: string;
630
936
  chainOpts?: ChainOpts;
631
937
  }
632
938
  interface RollbackMemoryOptions {
633
939
  orgName?: string;
634
940
  endpoint?: string;
941
+ /** Self-hosted judge response-signing pubkey; required with a non-allowlisted `endpoint`. */
942
+ verifyPubKey?: string;
635
943
  chainOpts?: ChainOpts;
636
944
  }
637
945
  /**
@@ -640,9 +948,7 @@ interface RollbackMemoryOptions {
640
948
  * deactivated on-chain.
641
949
  *
642
950
  * The caller is responsible for running `scanMemory` first when
643
- * appropriate — this function does not gate on the verdict. The
644
- * `score` parameter is the only metadata that flows in alongside
645
- * the ciphertext.
951
+ * appropriate — this function does not gate on the verdict.
646
952
  */
647
953
  declare function commitMemoryVersion(plaintext: string, auth: AgentAuth, opts?: CommitMemoryOptions): Promise<void>;
648
954
  /**
@@ -655,6 +961,7 @@ declare function commitMemoryVersion(plaintext: string, auth: AgentAuth, opts?:
655
961
  */
656
962
  interface AgentMemoryEntry {
657
963
  id: number;
964
+ filePath: string;
658
965
  content: string;
659
966
  decryptError?: string;
660
967
  score: number;
@@ -666,6 +973,7 @@ interface AgentMemoryEntry {
666
973
  interface MemoryRollbackEvent {
667
974
  fromId: number;
668
975
  toId: number;
976
+ filePath: string;
669
977
  reason: string;
670
978
  signer: string;
671
979
  createdAt: number;
@@ -674,71 +982,53 @@ interface MemoryRollbackEvent {
674
982
  * Cheap version-pointer probe. Returns just the id of the current
675
983
  * active memory (or null if none). No ciphertext is transferred — the
676
984
  * response is a single integer, so this is safe to call on every
677
- * memory-read hot path. Callers that hold a decrypted local copy can
678
- * compare against a stored pointer and only refetch the full row via
679
- * `getActiveMemory` when the id has changed.
985
+ * memory-read hot path.
680
986
  */
681
- declare function getActiveMemoryId(auth: AgentAuth, chainOpts?: ChainOpts): Promise<number | null>;
987
+ declare function getActiveMemoryId(auth: AgentAuth, chainOpts?: ChainOpts, filePath?: string | null): Promise<number | null>;
682
988
  /**
683
989
  * Recent active memory entries — subset of active versions filtered
684
- * by the chain's `MEMORY_RECENT_WINDOW_MS` (10 days at time of
685
- * writing). Intended for prompt injection at agent runtime, where
686
- * stale memory is worse than missing memory. For a time-unbounded
687
- * view of every currently active version, use `getAllAgentMemory`.
990
+ * by the chain's `MEMORY_RECENT_WINDOW_MS`. For a time-unbounded view
991
+ * of every currently active version, use `getActiveAgentMemory`.
688
992
  */
689
- declare function getActiveMemory(auth: AgentAuth, chainOpts?: ChainOpts): Promise<AgentMemoryEntry[]>;
993
+ declare function getRecentAgentMemory(auth: AgentAuth, chainOpts?: ChainOpts, filePath?: string | null): Promise<AgentMemoryEntry[]>;
690
994
  /**
691
995
  * All currently-active memory entries with no time cutoff. Use this
692
- * when you need every active version regardless of age — e.g., a
693
- * dashboard listing, or a long-running agent whose oldest active
694
- * versions may have fallen outside `getActiveMemory`'s recent window.
996
+ * when you need every active version regardless of age.
695
997
  */
696
- declare function getAllAgentMemory(auth: AgentAuth, chainOpts?: ChainOpts): Promise<AgentMemoryEntry[]>;
998
+ declare function getActiveAgentMemory(auth: AgentAuth, chainOpts?: ChainOpts, filePath?: string | null): Promise<AgentMemoryEntry[]>;
697
999
  /**
698
- * Full version history — active + inactive, most recent first.
699
- * Used by rollback UX to choose a target version.
1000
+ * Full version history — active + inactive, most recent first. Used
1001
+ * by rollback UX to choose a target version.
700
1002
  */
701
- declare function getMemoryHistory(auth: AgentAuth, chainOpts?: ChainOpts): Promise<AgentMemoryEntry[]>;
1003
+ declare function getMemoryHistory(auth: AgentAuth, chainOpts?: ChainOpts, filePath?: string | null): Promise<AgentMemoryEntry[]>;
702
1004
  /**
703
1005
  * Fetch a single memory entry by version id, including its current
704
- * `is_active` state. Useful for inspecting a historical version
705
- * before rolling back to it.
1006
+ * `is_active` state. Version ids are agent-unique on chain (not
1007
+ * per-file), so `id` alone resolves the target row.
706
1008
  */
707
1009
  declare function getMemoryById(id: number, auth: AgentAuth, chainOpts?: ChainOpts): Promise<AgentMemoryEntry>;
708
1010
  /**
709
1011
  * Audit trail of rollback events for this agent, most recent first.
1012
+ * Scope by file with `filePath`; omit for a cross-file view.
710
1013
  */
711
- declare function getRollbackHistory(auth: AgentAuth, chainOpts?: ChainOpts): Promise<MemoryRollbackEvent[]>;
1014
+ declare function getRollbackHistory(auth: AgentAuth, chainOpts?: ChainOpts, filePath?: string | null): Promise<MemoryRollbackEvent[]>;
712
1015
  /**
713
1016
  * Roll back to a previously-committed memory version. The target
714
- * `toId` must exist and be currently inactive. `reason` is required
715
- * and is recorded on-chain in `memory_rollback_log`.
1017
+ * `toId` must exist and be currently inactive. The chain resolves the
1018
+ * target row's `file_path` from `toId` — no file path is passed in.
1019
+ * `reason` is required and is recorded on-chain in `memory_rollback_log`.
716
1020
  */
717
1021
  declare function rollbackMemory(toId: number, reason: string, auth: AgentAuth, opts?: RollbackMemoryOptions): Promise<void>;
718
1022
 
719
- /**
720
- * Classify a plugin tool-call event as a memory write.
721
- *
722
- * Plugins receive `before_tool_call` events from their host runtime
723
- * (openclaw, Claude API, MCP, etc.) with varying argument shapes. This
724
- * module normalizes across shapes and returns a `MemoryEntry` when the
725
- * call is writing to a memory-like path, or `null` when the SDK should
726
- * skip the memory-scan path entirely.
727
- *
728
- * `event` and `ctx` are typed `unknown` so any plugin can pass its
729
- * native hook payloads without adaptation — the classifier probes
730
- * common key names at runtime.
731
- */
732
-
733
1023
  /**
734
1024
  * Tool names that indicate a memory write. Lowercase — matched
735
- * case-insensitively so both OpenClaw (lowercase) and Claude API
736
- * family (TitleCase) hit.
1025
+ * case-insensitively so OpenClaw (lowercase) and Claude API family
1026
+ * (TitleCase) both hit.
737
1027
  */
738
1028
  declare const DEFAULT_MEMORY_WRITE_TOOL_NAMES: ReadonlyArray<string>;
739
1029
  /**
740
1030
  * File path substrings that indicate a memory-shaped target. Callers
741
- * can extend or override this list via `classifyMemoryWrite` options.
1031
+ * extend or override via `classifyMemoryWrite` options.
742
1032
  */
743
1033
  declare const DEFAULT_MEMORY_PATH_PATTERNS: ReadonlyArray<string>;
744
1034
  /**
@@ -781,19 +1071,6 @@ interface ClassifyMemoryWriteOptions {
781
1071
  */
782
1072
  declare function classifyMemoryWrite(event: unknown, ctx: unknown, opts?: ClassifyMemoryWriteOptions): MemoryEntry | null;
783
1073
 
784
- /**
785
- * Plugin-agnostic memory-write guard.
786
- *
787
- * A single call that replaces the plugin's usual memory-write branch:
788
- * classify → scan (Layer 1 regex + Layer 2 LLM) → gate on verdict →
789
- * persist to chain (fire-and-forget when allowed) → return decision.
790
- *
791
- * Plugins call this from their `before_tool_call` hook. When it returns
792
- * `{ handled: false }` the call wasn't a memory write and the plugin
793
- * should fall through to its regular tool-call audit. When
794
- * `{ handled: true }` the plugin returns `decision` directly.
795
- */
796
-
797
1074
  /**
798
1075
  * Minimal logger accepted by `guardMemoryWrite`. Plugins pass their
799
1076
  * host runtime's logger (openclaw's `api.logger`, MCP's console, etc.).
@@ -888,12 +1165,19 @@ interface SyncMemoryOptions {
888
1165
  * `drifted: false` — pointer is still valid; caller can keep serving the local copy.
889
1166
  * `drifted: true` — active id changed on chain; `current` is the fresh decrypted row
890
1167
  * (or `null` if active memory was removed entirely).
1168
+ *
1169
+ * `checked` — whether this call actually queried chain. `false` means the TTL
1170
+ * window was still open and the pointer was trusted without contacting chain, so
1171
+ * `drifted: false` carries no evidence about the current state. Callers that
1172
+ * vouch for content to a third party must not treat an unchecked result as proof.
891
1173
  */
892
1174
  type SyncMemoryResult = {
893
1175
  drifted: false;
1176
+ checked: boolean;
894
1177
  pointer: MemoryPointer;
895
1178
  } | {
896
1179
  drifted: true;
1180
+ checked: true;
897
1181
  current: AgentMemoryEntry | null;
898
1182
  pointer: MemoryPointer;
899
1183
  };
@@ -934,7 +1218,7 @@ declare function createFileLogger(filePath: string, upstream?: UpstreamLogger):
934
1218
  /** Default log-file location — `<workspaceDir>/.atbash/plugin.log`. */
935
1219
  declare function defaultPluginLogPath(workspaceDir?: string): string;
936
1220
 
937
- /** Dedicated memory-read tool names, matched case-insensitively. Extend via options. */
1221
+ /** Dedicated memory-read tool names, matched case-insensitively. */
938
1222
  declare const DEFAULT_MEMORY_READ_TOOL_NAMES: ReadonlyArray<string>;
939
1223
  interface ClassifyMemoryReadOptions {
940
1224
  /** Tool names that always count as memory reads. Merged with defaults. */
@@ -948,15 +1232,49 @@ interface ClassifyMemoryReadOptions {
948
1232
  * Returns `true` when this tool call is a memory read — either a
949
1233
  * dedicated memory-read tool from `readToolNames`, or a generic read
950
1234
  * tool (`read` / `read_file`) targeting a memory-shaped path.
1235
+ *
1236
+ * Caller-supplied `patterns` are MERGED with the defaults (matches
1237
+ * Node's original behavior — extending in one plugin doesn't disable
1238
+ * standard coverage).
951
1239
  */
952
1240
  declare function classifyMemoryRead(event: unknown, ctx: unknown, opts?: ClassifyMemoryReadOptions): boolean;
953
1241
 
954
- /** Decision the manager returns to the plugin's `before_tool_call` handler. */
1242
+ /**
1243
+ * Decision the manager returns to the plugin's `before_tool_call` handler.
1244
+ *
1245
+ * `allow: true` alone is NOT evidence that anything was checked. Read `audited`
1246
+ * to tell the two apart, and route un-audited calls to your own judge — see the
1247
+ * field docs below.
1248
+ */
955
1249
  interface HookDecision {
956
1250
  allow?: boolean;
957
1251
  block?: boolean;
958
1252
  blockReason?: string;
959
1253
  reason?: string;
1254
+ /**
1255
+ * Whether the guard reached an enforcement decision about *this* call.
1256
+ *
1257
+ * Note this describes whether the guard **decided**, not whether it allowed.
1258
+ * Every `block` is `audited: true` — a blocked call is the most thoroughly
1259
+ * checked outcome the guard produces (a red scan, a ciphertext integrity
1260
+ * failure, a rolled-back version), and a host must never re-judge its way past
1261
+ * one.
1262
+ *
1263
+ * Absent or false means the guard reached no decision — it was inside its cache
1264
+ * window, chain was unreachable, the scan never ran, the file it can vouch for
1265
+ * is not the file being read, or it is in observe mode. Those calls are
1266
+ * unaudited: fall through to your own judge exactly as for a `null` return.
1267
+ *
1268
+ * So the host rule is:
1269
+ * `if (d.block) deny; else if (d.audited) allow; else judge it yourself;`
1270
+ *
1271
+ * Treating a bare `allow: true` as a completed audit is what this field exists
1272
+ * to prevent. A host that ignores it and returns the decision verbatim will
1273
+ * execute unaudited tool calls.
1274
+ */
1275
+ audited?: boolean;
1276
+ /** Scan verdict when one was produced (`green` | `yellow` | `red`). Absent when no scan ran. */
1277
+ verdict?: string;
960
1278
  }
961
1279
  interface MemoryGuardManagerOptions {
962
1280
  auth: AgentAuth;
@@ -979,6 +1297,13 @@ interface MemoryGuardManagerOptions {
979
1297
  rollbackMinScore?: number;
980
1298
  /** True → return `{block:true}` on defense triggers. False → log and return `null` (audit-only). Default true. */
981
1299
  enforce?: boolean;
1300
+ /**
1301
+ * Chain targeting for the pointer sync (network, blockchainRid, nodeUrls).
1302
+ * Defaults to the SDK's configured chain. Without this the manager could only
1303
+ * ever talk to the default chain, which left the whole memory-read path
1304
+ * untestable — `syncLocalMemory` already accepted these options.
1305
+ */
1306
+ chainOpts?: ChainOpts;
982
1307
  /** Host-specific tuning of what counts as a memory read. */
983
1308
  memoryReadClassifier?: ClassifyMemoryReadOptions;
984
1309
  /** Passed through to `guardMemoryWrite`. Host memory-write tool names override. */
@@ -1007,7 +1332,29 @@ declare class MemoryGuardManager {
1007
1332
  private readonly rollbackMinScore;
1008
1333
  private readonly enforce;
1009
1334
  private readonly agentPubkeyHex;
1335
+ /**
1336
+ * Memoized org→chain resolution. Reads have to hit the SAME chain
1337
+ * writes did, so an org-scoped guard must resolve `orgName` to
1338
+ * network exactly like `commitMemoryVersion` does. Without this
1339
+ * cache the read path would either (a) hit the SDK-default chain
1340
+ * every time — silently returning "no active memory on chain" when
1341
+ * writes landed on the org's actual chain, or (b) hammer
1342
+ * `/api/org-network` on every read. `undefined` means "not yet
1343
+ * resolved"; a resolved `null` means "no org / use raw chainOpts".
1344
+ */
1345
+ private _resolvedChainOpts;
1346
+ private _resolveChainInflight?;
1010
1347
  constructor(opts: MemoryGuardManagerOptions);
1348
+ /**
1349
+ * Resolve `orgName` → chain once, cache forever. `commitMemoryVersion`
1350
+ * already does this for writes; without the same call on the read
1351
+ * path, a client on the SDK's baked default chain reads from the wrong
1352
+ * chain and reports "no active memory" for an agent whose writes did
1353
+ * land — on the org's actual chain. An explicit `chainOpts.blockchainRid`
1354
+ * still wins (caller vouched for it); everything else honors the
1355
+ * dashboard's `org_networks` map.
1356
+ */
1357
+ private resolveChainOpts;
1011
1358
  /**
1012
1359
  * One-shot chain probe at plugin registration. Refreshes MEMORY.md
1013
1360
  * from chain when drifted and score passes threshold. Fire-and-forget
@@ -1015,12 +1362,33 @@ declare class MemoryGuardManager {
1015
1362
  */
1016
1363
  runBootProbe(): Promise<void>;
1017
1364
  /**
1018
- * Returns a `HookDecision` when the event is a memory read or write
1019
- * (host returns it verbatim to its runtime). Returns `null` when the
1020
- * event isn't memory-related — host falls through to its own audit.
1365
+ * Returns a `HookDecision` when the guard reached a decision about this event.
1366
+ * Returns `null` when it did not — either the event isn't memory-related, or it
1367
+ * is but the guard could not check it. In both cases the host falls through to
1368
+ * its own audit.
1369
+ *
1370
+ * A returned decision carries `audited` (see `HookDecision`). Only
1371
+ * `{ allow: true, audited: true }` means "checked and cleared"; anything else
1372
+ * that allows is a call the host still needs to judge.
1021
1373
  */
1022
1374
  handleBeforeToolCall(event: unknown, ctx: unknown): Promise<HookDecision | null>;
1023
1375
  private mapGuardResult;
1376
+ /**
1377
+ * Whether the pointer state this manager tracks actually describes the file
1378
+ * this call is about to read.
1379
+ *
1380
+ * The classifier fires on nine patterns — including the bare tokens
1381
+ * `"memory/"`, `"CLAUDE.md"` and `"AGENTS.md"` — but the sync path only ever
1382
+ * reads, refreshes, or vouches for `this.memoryFilePath`. Without this check a
1383
+ * read of `/repo/CLAUDE.md` (or any path merely containing `memory/`) would
1384
+ * receive an `audited: true` for a file the guard never opened.
1385
+ *
1386
+ * Conservative on purpose: every path-shaped value found must resolve to the
1387
+ * managed file. If none is found, or any one differs, the answer is no. That
1388
+ * also covers events carrying two different path keys, where the classifier
1389
+ * and the host could otherwise disagree about which one is authoritative.
1390
+ */
1391
+ private vouchesForTarget;
1024
1392
  private handleMemoryRead;
1025
1393
  private writeMemoryAtomic;
1026
1394
  }
@@ -1029,13 +1397,17 @@ declare function createMemoryGuardManager(opts: MemoryGuardManagerOptions): Memo
1029
1397
  /**
1030
1398
  * Atbash SDK Telemetry — OpenTelemetry metrics for usage tracking.
1031
1399
  *
1032
- * Tracks: function call counts, latency, source (CLI/plugin/SDK),
1033
- * and agent identity. ON by default.
1400
+ * Metrics are POSTed to the Atbash-owned `/api/telemetry` proxy, which
1401
+ * verifies the bearer, injects the Honeycomb ingest key server-side, and
1402
+ * forwards to Honeycomb. The ingest credential never enters the SDK.
1403
+ *
1404
+ * Environment opt-out (recommended for air-gapped deployments):
1405
+ * ATBASH_TELEMETRY_DISABLED=1
1034
1406
  *
1035
1407
  * Opt-out: create ~/.config/atbash/telemetry.json with { "enabled": false }
1036
1408
  * The file must be readable by the SDK process. If missing, corrupted, or
1037
- * unreadable → telemetry stays ON. Environment variables cannot disable
1038
- * telemetry (prevents agent bypass via env-var injection).
1409
+ * unreadable, telemetry remains eligible to start unless the environment
1410
+ * opt-out is set.
1039
1411
  */
1040
1412
  type ClientSource = "cli" | "sdk" | "plugin:openclaw" | "plugin:langchain" | "plugin:langgraph" | "plugin:hermes" | "plugin:eliza" | "plugin:crewai" | "plugin:mcp" | "plugin:autogen" | "plugin:jeenai" | (string & {});
1041
1413
  interface TelemetryConfig {
@@ -1045,6 +1417,13 @@ interface TelemetryConfig {
1045
1417
  source?: ClientSource;
1046
1418
  /** Flush interval in ms. Default: 60000 */
1047
1419
  exportIntervalMs?: number;
1420
+ /** Atbash endpoint that hosts /api/telemetry. Required to actually export. */
1421
+ endpoint?: string;
1422
+ /**
1423
+ * Called on every export to obtain fresh auth headers (typically
1424
+ * `{ Authorization: "Bearer <hex>" }`). Required to actually export.
1425
+ */
1426
+ getAuthHeaders?: () => Record<string, string>;
1048
1427
  }
1049
1428
  declare function setupTelemetry(config: TelemetryConfig): void;
1050
1429
  /**
@@ -1068,126 +1447,33 @@ declare function flushTelemetry(): Promise<void>;
1068
1447
  declare function shutdownTelemetry(): Promise<void>;
1069
1448
 
1070
1449
  /**
1071
- * Signs `log_encrypted_tool_call` — the ciphertext-only counterpart of
1072
- * `log_tool_call`.
1073
- *
1074
- * Why this is not in the Rust core like the other signing helpers: the operation
1075
- * takes a `byte_array` argument, and the only consumer today is the dashboard,
1076
- * which loads the browser bundle where Rust is unreachable by construction. This
1077
- * module is plain TypeScript so the node and browser builds share one
1078
- * implementation and cannot drift. The Rust core gets the same operation when the
1079
- * native/Python/Go callers need it — the wire format is pinned by `crypto/ecies.ts`.
1080
- *
1081
- * The contract refuses plaintext once an org registers an encryption key
1082
- * (`log_tool_call` → "Organization requires encrypted payloads"), so for those
1083
- * orgs this is the only way to log a tool call at all.
1084
- */
1085
- /**
1086
- * Commitment to the plaintext claims a caller sends alongside the ciphertext.
1087
- *
1088
- * The judge server reads tool_name / action / context / tool_args_json from the
1089
- * request body and feeds them to policy evaluation, but it holds no org key, so
1090
- * it cannot check them against content_cipher. This hash is a signed operation
1091
- * argument, which makes it the one thing it can check them against.
1092
- *
1093
- * An array, not an object: key order is not part of a JSON array, so the two
1094
- * implementations cannot drift on serialization. Must stay identical to
1095
- * `claimHashHex` in the dashboard (`src/lib/api/judge/action-hash.ts`).
1096
- */
1097
- declare function claimHashHex(toolName: string, action: string, context: string, toolArgsJson: string): string;
1098
- /** Plaintext fields of a tool call, sealed into a single ECIES payload. */
1099
- interface ToolCallPlaintext {
1100
- tool_name: string;
1101
- action: string;
1102
- context: string;
1103
- tool_args_json: string;
1104
- }
1105
- /**
1106
- * Canonical form of an action for the retry-cache hash.
1107
- *
1108
- * Must stay identical to `normalizeActionForHash` in the dashboard
1109
- * (`src/lib/api/judge/on-chain.ts`): both write the same
1110
- * `tool_call_log.normalized_action_hash` column, and `get_resolved_hold_by_action_hash`
1111
- * matches a YELLOW hold retry against it. Diverging here silently breaks
1112
- * hold resolution rather than failing loudly.
1450
+ * Rust core does the work; `browser/encrypted-toolcall.ts` mirrors it for the
1451
+ * browser and must produce the same columns.
1113
1452
  */
1453
+ /** Must match `column_aad` in the core — the label binds a ciphertext to its column. */
1454
+ declare function columnAad(toolCallId: string, column: string): string;
1455
+ /** Byte-identical to the dashboard's copy — diverging breaks hold-retry resolution. */
1114
1456
  declare function normalizeActionForHash(action: string): string;
1115
- /**
1116
- * Sign a `log_encrypted_tool_call` operation.
1117
- *
1118
- * Everything the agent did — action, context, tool name and args — goes into a
1119
- * single ECIES payload readable only with the org's private key. Nothing
1120
- * identifying the action is left in the operation arguments, which are permanent
1121
- * block data.
1122
- *
1123
- * `actionHash` is the one exception, and it is deliberate: it is a SHA-256 over
1124
- * the normalized action, so the chain can match a held action against its retry
1125
- * without being able to read it.
1126
- *
1127
- * @returns hex-encoded signed transaction, ready to POST as `signed_log_tool_call`.
1128
- */
1457
+ /** Lets the judge check the request body against the ciphertext without an org key. */
1458
+ declare function claimHashHex(toolName: string, action: string, context: string, toolArgsJson: string): string;
1459
+ /** @returns hex-encoded signed tx, ready to POST as `signed_log_tool_call`. */
1129
1460
  declare function signEncryptedToolCall(toolCallId: string, action: string, context: string, toolName: string, toolArgsJson: string, orgEncryptionPubKey: string, privkeyHex: string, blockchainRidHex: string): string;
1130
1461
 
1131
1462
  /**
1132
- * ECIES over secp256k1 — encrypts on-chain payloads to an organization's public key.
1133
- *
1134
- * Only the holder of the org's private key can decrypt. Everyone else — including
1135
- * anyone querying the Chromia node directly, and Atbash itself — sees ciphertext.
1136
- * The recipient key is a dedicated encryption keypair the org generates in the
1137
- * dashboard and registers via `org_set_encryption_key`; it is read back with the
1138
- * `get_org_encryption_pubkey` query.
1139
- *
1140
- * ─── WIRE FORMAT (normative) ────────────────────────────────────────────────
1141
- * This exact layout is mirrored in the Atbash dashboard
1142
- * (`src/lib/chromia/ecies.ts`) and must stay byte-for-byte identical: the SDK
1143
- * encrypts tool calls, the dashboard decrypts them.
1144
- *
1145
- * version 1 byte = 0x01
1146
- * ephemeral_pubkey 33 bytes compressed secp256k1 point
1147
- * nonce 12 bytes random, per message
1148
- * ciphertext+tag N bytes AES-256-GCM output (16-byte tag appended)
1149
- *
1150
- * Version 0x01 is FROZEN, not provisional. Records encrypted under it already
1151
- * exist on the deployed chains, and the ledger is immutable — redefining 0x01
1152
- * would make them permanently unreadable, not merely stale. Evolving the format
1153
- * means emitting a NEW version byte and keeping a 0x01 decrypt path, in both
1154
- * repos, forever.
1155
- *
1156
- * Raw bytes, not base64: the on-chain columns are `byte_array`, so encoding to
1157
- * text would add ~33% to what are the largest columns in the schema.
1158
- *
1159
- * Key agreement, per message:
1160
- * shared_x = ECDH(ephemeral_privkey, org_pubkey).x // 32 bytes
1161
- * key = HKDF-SHA256(ikm=shared_x, salt=ephemeral_pubkey, info=domain, len=32)
1162
- * aad = "<domain>|<record_id>"
1163
- *
1164
- * A fresh ephemeral keypair is generated for every message and its private half is
1165
- * discarded immediately. This is what makes the scheme forward-secret with respect
1166
- * to the *sender*: leaking an agent's long-term signing key later does not expose
1167
- * anything it encrypted in the past. (Deriving the shared secret from the agent's
1168
- * static key instead would let anyone recompute every past shared secret, since the
1169
- * org's public key is public by definition.)
1170
- *
1171
- * Three separate bindings, each closing a different substitution:
1172
- * salt = ephemeral pubkey — ties the key to this exact handshake
1173
- * info = domain — a verdict payload cannot be read as a tool call
1174
- * aad = domain|record_id — a payload cannot be lifted onto another row
1175
- */
1176
- /**
1177
- * Cryptographic domain per payload kind. Fed to HKDF `info`, so each kind derives
1178
- * a different key from the same handshake — a verdict payload handed to the
1179
- * tool-call reader fails authentication rather than decoding to an empty struct.
1463
+ * Cryptographic domain per payload kind. Each kind derives a distinct
1464
+ * key from the same handshake — a verdict payload handed to the
1465
+ * tool-call reader fails authentication rather than silently decoding.
1180
1466
  *
1181
- * Must match `EciesDomain` in the dashboard's src/lib/chromia/ecies.ts exactly:
1182
- * the string is an input to key derivation, so any difference makes the two sides
1183
- * mutually unreadable.
1467
+ * Values here are the SHORT domain names the Rust core recognizes.
1468
+ * The full HKDF `info` strings (`atbash:chain-encryption:v1:<kind>`)
1469
+ * live inside the core and never surface at the API boundary.
1184
1470
  */
1185
1471
  declare const EciesDomain: {
1186
- readonly toolCall: "atbash:chain-encryption:v1:toolcall";
1187
- readonly verdict: "atbash:chain-encryption:v1:verdict";
1188
- readonly note: "atbash:chain-encryption:v1:note";
1189
- readonly policy: "atbash:chain-encryption:v1:policy";
1190
- readonly raw: "atbash:chain-encryption:v1:raw";
1472
+ readonly toolCall: "toolcall";
1473
+ readonly verdict: "verdict";
1474
+ readonly note: "note";
1475
+ readonly policy: "policy";
1476
+ readonly raw: "raw";
1191
1477
  };
1192
1478
  type EciesDomain = (typeof EciesDomain)[keyof typeof EciesDomain];
1193
1479
  /**
@@ -1202,9 +1488,9 @@ declare function encryptForOrg(plaintext: string, orgPubKeyHex: string, aad: str
1202
1488
  /**
1203
1489
  * Decrypt a payload produced by {@link encryptForOrg}.
1204
1490
  *
1205
- * Throws if the key is wrong, the `aad` does not match the one used at encrypt
1206
- * time, or the ciphertext was tampered with — GCM authentication makes all three
1207
- * indistinguishable by design.
1491
+ * Throws if the key is wrong, the `aad` does not match the one used at
1492
+ * encrypt time, or the ciphertext was tampered with — GCM authentication
1493
+ * makes all three indistinguishable by design.
1208
1494
  *
1209
1495
  * @param payload Value read from the on-chain `byte_array` column.
1210
1496
  * @param orgPrivKeyHex Org's secp256k1 private key (32 bytes hex).
@@ -1212,12 +1498,36 @@ declare function encryptForOrg(plaintext: string, orgPubKeyHex: string, aad: str
1212
1498
  */
1213
1499
  declare function decryptForOrg(payload: Uint8Array, orgPrivKeyHex: string, aad: string, domain?: EciesDomain): string;
1214
1500
  /**
1215
- * Size in bytes of the encrypted payload for a given plaintext length. Lets
1216
- * callers check against the on-chain column cap (MAX_CONTENT_CIPHER_SIZE)
1217
- * before submitting a transaction the contract would reject.
1501
+ * Size in bytes of the encrypted payload for a given plaintext length.
1502
+ * Lets callers check against the on-chain column cap
1503
+ * (`MAX_CONTENT_CIPHER_SIZE`) before submitting a transaction the
1504
+ * contract would reject.
1218
1505
  */
1219
1506
  declare function encryptedLength(plaintextByteLength: number): number;
1220
1507
 
1508
+ /**
1509
+ * atb1.<key-fingerprint>.<claim-hash>.<base64 ciphertext>
1510
+ *
1511
+ * Normative spec: `core/src/crypto_envelope.rs`. This mirrors it for the browser.
1512
+ */
1513
+ interface Envelope {
1514
+ /** First 8 bytes of the recipient public key, hex. May be empty. */
1515
+ keyFingerprint: string;
1516
+ /** Commitment to the accompanying plaintext claims. May be empty. */
1517
+ claimHash: string;
1518
+ /** Raw ECIES payload. */
1519
+ payload: Uint8Array;
1520
+ }
1521
+ declare function packEnvelope(payload: Uint8Array, keyFingerprint?: string, claimHash?: string): string;
1522
+ /**
1523
+ * Stays true for a truncated envelope that `parseEnvelope` rejects — a severed
1524
+ * ciphertext is not plaintext, so callers must show a placeholder.
1525
+ */
1526
+ declare function isEnvelope(value: string): boolean;
1527
+ /** Null, not a throw — pre-encryption records are plaintext. */
1528
+ declare function parseEnvelope(value: string): Envelope | null;
1529
+ declare function keyFingerprintOf(pubKeyHex: string): string;
1530
+
1221
1531
  declare function isValidPrivateKey(hex: string): boolean;
1222
1532
  declare function derivePublicKey(privkey: string): string;
1223
1533
  declare function generateKeypair(): KeyPair;
@@ -1233,4 +1543,4 @@ declare function containsSecret(text: string): boolean;
1233
1543
  declare function createMemorySnapshot(entries: MemoryEntry[], takenAt: number): MemorySnapshot;
1234
1544
  declare function diffMemorySnapshots(before: MemorySnapshot, after: MemorySnapshot): MemoryDiffResult;
1235
1545
 
1236
- export { type ActionType, type AgentAuth, type AgentMemoryEntry, type AgentPolicy, type AnomalySeverity, type AnomalyType, Atbash, AtbashAPIError, type AtbashLogger, type AtbashOptions, type AtbashUserConfig, type ChainOpts, type ClassifierToolContext, type ClassifierToolEvent, type ClassifyMemoryReadOptions, type ClassifyMemoryWriteOptions, type ClientSource, type CommitMemoryOptions, DEFAULT_BLOCKCHAIN_RID, DEFAULT_CHROMIA_NODE_URLS, DEFAULT_ENDPOINT, DEFAULT_MEMORY_PATH_PATTERNS, DEFAULT_MEMORY_READ_TOOL_NAMES, DEFAULT_MEMORY_WRITE_TOOL_NAMES, type Decision, type DecisionVerdict, EciesDomain, type EncryptedMemory, type FromConfigOptions, type GuardLogger, type GuardMemoryDecision, type GuardMemoryWriteInput, type GuardMemoryWriteResult, type HeldAction, type HeldActionReview, type HookDecision, type JudgeEndpointConfig, type JudgeOptions, type JudgeResult, type JudgmentState, type JudgmentStatus, type KeyPair, type LogToolCallOptions, type LogToolCallResult, type MemoryAnomaly, type MemoryDiffResult, type MemoryEntry, MemoryGuardManager, type MemoryGuardManagerOptions, MemoryIntegrityError, type MemoryPointer, type MemoryRollbackEvent, type MemoryScanOptions, type MemoryScanResult, type MemoryScanVerdict, type MemorySnapshot, type ModifiedEntry, type Network, type OrgSubscription, PointerStore, type Provider, type PubkeyValue, type RedactResult, type RollbackMemoryOptions, type SecretKind, type SecretMatch, SignatureVerificationError, type Subscription, type SyncMemoryOptions, type SyncMemoryResult, type TelemetryConfig, type TierInfo, type ToolCallFull, type ToolCallInput, type ToolCallPlaintext, type ToolCallRecord, type ValidatedEndpoint, type Verdict, type WrappedLogger, claimHashHex, classifyMemoryRead, classifyMemoryWrite, commitMemoryVersion, containsEvasionCharacters, containsSecret, createFileLogger, createMemoryGuardManager, createMemorySnapshot, decryptForOrg, decryptMemoryContent, defaultPluginLogPath, defaultPointerPath, deriveMemoryKey, derivePublicKey, diffMemorySnapshots, encryptForOrg, encryptMemoryContent, encryptedLength, flushTelemetry, generateKeypair, getActiveMemory, getActiveMemoryId, getAllAgentMemory, getConfigDir, getConfigPath, getMemoryById, getMemoryHistory, getRollbackHistory, guardMemoryWrite, isValidPrivateKey, loadAgent, loadAgentFromFile, loadUserConfig, normalizeActionForHash, normalizeForMatching, normalizeStatus, normalizeVerdict, pubkeyToHex, recordCall, recordDuration, redactJsonStrings, redactSecrets, resolve, resolveKeyPath, rollbackMemory, saveUserConfig, scanMemory, scanMemoryBatch, setupTelemetry, shutdownTelemetry, signEncryptedToolCall, signJudgeAction, signLogToolCall, syncLocalMemory, validateJudgeEndpoint, verifyJudgeResponseSignature, verifySignature };
1546
+ export { type ActionType, type AgentAuth, type AgentLookupOptions, type AgentMemoryEntry, type AgentPolicy, type AnomalySeverity, type AnomalyType, Atbash, AtbashAPIError, type AtbashLogger, type AtbashOptions, type AtbashUserConfig, BOOT_SYNC_HINT, type ChainConfig, type ChainOpts, type ClassifierToolContext, type ClassifierToolEvent, type ClassifyMemoryReadOptions, type ClassifyMemoryWriteOptions, type ClientSource, type CommitMemoryOptions, DEFAULT_BLOCKCHAIN_RID, DEFAULT_CHROMIA_NODE_URLS, DEFAULT_ENDPOINT, DEFAULT_MEMORY_PATH_PATTERNS, DEFAULT_MEMORY_READ_TOOL_NAMES, DEFAULT_MEMORY_WRITE_TOOL_NAMES, type Decision, type DecisionVerdict, EciesDomain, type EncryptedMemory, type Envelope, type FromConfigOptions, type GuardLogger, type GuardMemoryDecision, type GuardMemoryWriteInput, type GuardMemoryWriteResult, type HeldAction, type HeldActionReview, type HookDecision, HttpClient, HttpTransportError, type JudgeEndpointConfig, type JudgeOptions, type JudgeResult, type JudgmentState, type JudgmentStatus, KEY_FILENAMES, type KeyPair, type LogToolCallOptions, type LogToolCallResult, type MemoryAnomaly, type MemoryDiffResult, type MemoryEntry, MemoryGuardManager, type MemoryGuardManagerOptions, MemoryIntegrityError, type MemoryPointer, type MemoryRollbackEvent, type MemoryScanOptions, type MemoryScanResult, type MemoryScanVerdict, type MemorySnapshot, type ModifiedEntry, type Network, type OrgSubscription, PRIVATE_CHAIN, PUBLIC_CHAIN, PointerStore, type Provider, type PubkeyValue, type RedactResult, type RollbackMemoryOptions, type SecretKind, type SecretMatch, SignatureVerificationError, type Subscription, type SyncMemoryOptions, type SyncMemoryResult, type TelemetryConfig, type TierInfo, type ToolCallFull, type ToolCallInput, type ToolCallRecord, type ValidatedEndpoint, type Verdict, type WrappedLogger, bootSyncFailureLine, buildAllowedJudgeHosts, canonicalAllow, chainForNetwork, chooseKeyPath, claimHashHex, classifyMemoryRead, classifyMemoryWrite, columnAad, commitMemoryVersion, containsEvasionCharacters, containsSecret, createFileLogger, createMemoryGuardManager, createMemorySnapshot, decryptForOrg, decryptMemoryContent, defaultPluginLogPath, defaultPointerPath, deriveMemoryKey, derivePublicKey, diffMemorySnapshots, encryptForOrg, encryptMemoryContent, encryptedLength, flushTelemetry, generateKeypair, getActiveAgentMemory, getActiveMemoryId, getConfigDir, getConfigPath, getMemoryById, getMemoryHistory, getRecentAgentMemory, getRollbackHistory, gtvToFfiSafe, guardMemoryWrite, isEnvelope, isValidPrivateKey, keyFingerprintOf, keyPathCandidates, loadAgent, loadAgentFromFile, loadUserConfig, normalizeActionForHash, normalizeActionType, normalizeForMatching, normalizeStatus, normalizeVerdict, packEnvelope, parseEnvelope, pubkeyToHex, recordCall, recordDuration, redactJsonStrings, redactSecrets, resolve, resolveKeyPath, rollbackMemory, saveUserConfig, scanMemory, scanMemoryBatch, setupTelemetry, shutdownTelemetry, signEncryptedToolCall, signJudgeAction, signLogToolCall, syncLocalMemory, validateJudgeEndpoint, verifyJudgeResponseSignature, verifySignature };