@engramx/client 0.15.0-rc.3 → 0.16.0-rc.10

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.
Files changed (57) hide show
  1. package/README.md +262 -26
  2. package/dist/EngramClient.d.ts +235 -229
  3. package/dist/EngramClient.js +446 -1247
  4. package/dist/EngramClient.js.map +1 -1
  5. package/dist/MarketClient.d.ts +137 -0
  6. package/dist/MarketClient.js +228 -0
  7. package/dist/MarketClient.js.map +1 -0
  8. package/dist/ProfileManager.d.ts +4 -4
  9. package/dist/SessionManager.d.ts +3 -3
  10. package/dist/SessionManager.js +1 -1
  11. package/dist/SessionManager.js.map +1 -1
  12. package/dist/actionReceipt.d.ts +67 -0
  13. package/dist/actionReceipt.js +76 -2
  14. package/dist/actionReceipt.js.map +1 -1
  15. package/dist/auth.d.ts +16 -0
  16. package/dist/auth.js +38 -5
  17. package/dist/auth.js.map +1 -1
  18. package/dist/certifiedAudit.d.ts +32 -6
  19. package/dist/certifiedAudit.js +75 -14
  20. package/dist/certifiedAudit.js.map +1 -1
  21. package/dist/cli.js +100 -26
  22. package/dist/cli.js.map +1 -1
  23. package/dist/encryption.d.ts +30 -12
  24. package/dist/encryption.js +13 -15
  25. package/dist/encryption.js.map +1 -1
  26. package/dist/idl.d.ts +69 -0
  27. package/dist/idl.js +242 -0
  28. package/dist/idl.js.map +1 -0
  29. package/dist/index.d.ts +12 -18
  30. package/dist/index.js +58 -9
  31. package/dist/index.js.map +1 -1
  32. package/dist/ledgerfsHash.js +1 -1
  33. package/dist/ledgerfsHash.js.map +1 -1
  34. package/dist/lineage.js +8 -5
  35. package/dist/lineage.js.map +1 -1
  36. package/dist/memoryArchive.d.ts +428 -0
  37. package/dist/memoryArchive.js +504 -0
  38. package/dist/memoryArchive.js.map +1 -0
  39. package/dist/migrate.js +31 -4
  40. package/dist/migrate.js.map +1 -1
  41. package/dist/openEngram.d.ts +55 -0
  42. package/dist/openEngram.js +185 -0
  43. package/dist/openEngram.js.map +1 -0
  44. package/dist/pair.d.ts +33 -2
  45. package/dist/pair.js +56 -9
  46. package/dist/pair.js.map +1 -1
  47. package/dist/types.d.ts +246 -307
  48. package/dist/version.d.ts +8 -0
  49. package/dist/version.js +45 -0
  50. package/dist/version.js.map +1 -0
  51. package/dist/wire.d.ts +76 -0
  52. package/dist/wire.js +79 -0
  53. package/dist/wire.js.map +1 -0
  54. package/package.json +11 -19
  55. package/dist/bootstrap.d.ts +0 -21
  56. package/dist/bootstrap.js +0 -71
  57. package/dist/bootstrap.js.map +0 -1
@@ -1,10 +1,11 @@
1
1
  import { type CertifiedAuditHead, type VerifiedAuditHead } from './certifiedAudit.js';
2
+ import { Ed25519KeyIdentity } from '@icp-sdk/core/identity';
2
3
  import { Principal } from '@icp-sdk/core/principal';
3
- import { Ic402Client, type SessionHandle } from '@ic402/client';
4
- import type { RawMemoryFile, MemoryHistory, MemoryRangePage, MemoryFile, MemoryFileInfo, SessionContext, OperatorRecord, AuditEntry, EngramStatus, GuardianRecord, WorldIdProof, BackupMetadata, BackupType, StorageStats, PaymentRequirement, PaymentSignature, PaymentResult, SessionIntent, SessionConfig, SessionState, Voucher, SpendingPolicy, ContentEntry, ContentDelivery, AgentCard, ApprovalRequest, EndpointType, EndpointStatus, X402Endpoint, PurchaseRecord, RevenueSummary } from './types.js';
4
+ import { idlFactory } from './idl.js';
5
+ import type { MemoryHistory, MemoryRangePage, MemoryFile, MemoryFileInfo, SessionContext, Pairing, AuditPage, AgentRecord, ChildAllowanceRow, EngramStatus, OwnerConsole, GuardianRecord, BackupMetadata, BackupType, ApprovalRequest } from './types.js';
5
6
  /**
6
7
  * Source class declared on an append — recorded permanently in the engram's audit chain beside
7
- * this operator's principal. Declared by the caller, NOT verified by the canister; a false
8
+ * this agent's principal. Declared by the caller, NOT verified by the canister; a false
8
9
  * declaration is itself attributable. Omit (undefined) when none applies: untagged is honest,
9
10
  * a guessed tag is not.
10
11
  */
@@ -20,29 +21,22 @@ export type MemorySource = 'human' | 'agentDerived' | 'sessionTranscript' | 'fil
20
21
  * first four unchanged.
21
22
  */
22
23
  | 'deterministic';
23
- /** A disputed memory write — marked, never deleted. */
24
- export interface Repudiation {
25
- path: string;
24
+ /**
25
+ * The evidence a write actually landed.
26
+ *
27
+ * Returned by every write method as of the 2026-08-16 canister release. Previously these returned
28
+ * a bare version number and the receipt — which the canister had already computed — was discarded,
29
+ * so a writer could learn THAT their write succeeded but not be handed the proof. `head` is the
30
+ * audit-chain head after this write, hex-encoded; it transitively commits to the content root, so a
31
+ * third party can verify the write against the chain and its anchor without trusting the writer.
32
+ */
33
+ export interface MemoryReceipt {
34
+ /** Version of this path after the write. */
26
35
  version: bigint;
27
- reason: string;
28
- at: bigint;
29
- auditId: bigint;
30
- }
31
- /** One reviewed-surface row: machine fields parsed canister-side from its own audit format. */
32
- export interface OperatorWriteRow {
33
- entry: AuditEntry;
34
- path: [] | [string];
35
- version: [] | [bigint];
36
- source: [] | [string];
37
- connector: [] | [string];
38
- }
39
- export interface OperatorWriteReview {
40
- cursor: bigint;
41
- head: bigint;
42
- earliest: bigint;
43
- /** true ⇒ exactly 200 rows returned AND more unreviewed writes exist — page via markOperatorWritesReviewed(lastShownId); never present 200 as a total. */
44
- capped: boolean;
45
- rows: OperatorWriteRow[];
36
+ /** Position in the engram's event sequence. */
37
+ seq: bigint;
38
+ /** Chain head after this write, hex. The thing to hand a verifier. */
39
+ head: string;
46
40
  }
47
41
  export interface EngramClientConfig {
48
42
  /** The engram canister ID */
@@ -64,14 +58,15 @@ export interface EngramClientConfig {
64
58
  * legacy plaintext keep working regardless. */
65
59
  encryptMemory?: boolean;
66
60
  /** Opt-in persistent key cache path (Node only; or set ENGRAMX_KEY_CACHE). The cache file is
67
- * plaintext-equivalent key material: a host that persisted the key keeps it after revocation.
68
- * Destroy the file when decommissioning a host. */
61
+ * plaintext-equivalent key material: a machine that persisted the key keeps it after revocation.
62
+ * Destroy the file when retiring the machine the agent runs on. */
69
63
  keyCachePath?: string;
70
64
  /** Label identifying this host in the engram's distinct-reader set (drives cross-model reuse
71
65
  * metrics). Sent by `startSession()`. Default: 'sdk'. Use your product name, e.g. 'openclaw'. */
72
66
  clientLabel?: string;
73
67
  }
74
- export declare const idlFactory: ({ IDL }: {
68
+ export { idlFactory };
69
+ export declare const icrc2LedgerIdlFactory: ({ IDL }: {
75
70
  IDL: any;
76
71
  }) => any;
77
72
  export declare class EngramClient {
@@ -84,31 +79,66 @@ export declare class EngramClient {
84
79
  private encryption;
85
80
  private encryptionModeSynced;
86
81
  private clientLabel;
82
+ /**
83
+ * ⟨0.16.0⟩ Delegates to the shared unwrap, because `Err` is a VARIANT now, not a string.
84
+ *
85
+ * The old body was `throw new Error(result.Err)`, which under the new envelope stringifies a
86
+ * six-armed object to **"[object Object]"** — it compiles, it runs, and it destroys the message.
87
+ * On this codebase that is the product: the refusals exist to tell a caller what to do next, and
88
+ * this line decides whether they ever read one. See wire.ts.
89
+ */
87
90
  private unwrap;
88
91
  constructor(config: EngramClientConfig);
89
92
  readMemory(path: string): Promise<MemoryFile>;
93
+ /**
94
+ * Does this path hold content already?
95
+ *
96
+ * A whole-file read is the obvious way to answer this and the wrong one. The engram refuses a
97
+ * whole-file read above 512 KiB, and files above that ceiling exist: the write cap sat at
98
+ * 1.5 MB until it was lowered to the same 512 KiB on 2026-08-15, so anything grown past the
99
+ * read ceiling under the old cap is still stored, still appendable, and still unreadable
100
+ * whole. Treating that refusal as absence is not a missing newline — it silently concatenates
101
+ * onto the previous line, and in encrypted mode it skips the framing check that exists to stop
102
+ * an unparseable mixed blob.
103
+ *
104
+ * So ask for a byte instead. A one-byte ranged read answers the question without ever hitting
105
+ * the whole-file ceiling, and it is the cheaper question at ANY size — there is no reason to
106
+ * pull a whole file to learn whether it is empty. That reason outlives the legacy files.
107
+ */
108
+ private hasContent;
90
109
  /**
91
110
  * Append `content` to a memory file (append-only; the canister concatenates the delta).
92
111
  *
93
- * @returns the new on-chain version. **Special case:** if an `offlineQueue` is configured and the
94
- * network call fails with a transport error, the encoded delta is queued for a later
95
- * `syncOfflineQueue()` and this returns `0n` — a "deferred, not yet committed" sentinel, NOT a real
96
- * version. Callers that surface a version to users should treat `0n` as "queued". Deterministic
112
+ * @returns the write {@link MemoryReceipt}. **Special case:** if an `offlineQueue` is configured
113
+ * and the network call fails with a transport error, the encoded delta is queued for a later
114
+ * `syncOfflineQueue()` and this returns `null` — there is no receipt for a write that has not
115
+ * happened, and a caller that surfaces one must say "queued", not report a version. Deterministic
97
116
  * errors (validation, permission, writes-paused, non-framed base in encrypted mode) always throw.
98
117
  */
118
+ appendMemory(path: string, content: string, source?: MemorySource): Promise<MemoryReceipt | null>;
99
119
  /**
100
- * Does this path hold content already?
120
+ * The engram's file listing.
101
121
  *
102
- * The engram refuses a whole-file read above 512 KiB while still ACCEPTING appends up to
103
- * 1.5 MB, so between those two numbers "read it to see if it is empty" fails on a file that
104
- * very much exists. Treating that refusal as absence is not a missing newline — it silently
105
- * concatenates onto the previous line, and in encrypted mode it skips the framing check that
106
- * exists to stop an unparseable mixed blob. So ask for a byte instead: a one-byte ranged read
107
- * answers the question without ever hitting the whole-file ceiling.
122
+ * ⟨0.16.0, L1 + L3⟩ Paged and enveloped. Two things this signature deliberately does NOT hide:
123
+ * a refusal throws rather than returning [], and `withheld` — the rows policy removed from the
124
+ * page — is surfaced on the thrown/returned shape's sibling accessor rather than silently
125
+ * dropped. `listMemoryFilesPage` is there for callers that must render "N hidden"; this one
126
+ * returns the rows because most callers render a list.
108
127
  */
109
- private hasContent;
110
- appendMemory(path: string, content: string, source?: MemorySource): Promise<bigint>;
111
- listMemoryFiles(): Promise<MemoryFileInfo[]>;
128
+ listMemoryFiles(opts?: {
129
+ start?: number;
130
+ limit?: number;
131
+ }): Promise<MemoryFileInfo[]>;
132
+ /** The same read, keeping `total` and `withheld` — a filtered listing that looks complete is
133
+ * the defect the page shape exists to fix, and only the caller can decide whether to say so. */
134
+ listMemoryFilesPage(opts?: {
135
+ start?: number;
136
+ limit?: number;
137
+ }): Promise<{
138
+ items: MemoryFileInfo[];
139
+ total: bigint;
140
+ withheld: bigint;
141
+ }>;
112
142
  readMemoryBatch(paths: string[]): Promise<[string, MemoryFile | Error][]>;
113
143
  /**
114
144
  * Load the engram's memory for injection at the START of an agent session — the automatic
@@ -138,7 +168,7 @@ export declare class EngramClient {
138
168
  * `startSession()` yourself if you need the structured files instead. Accepts the same `onError`
139
169
  * option as `startSession`.
140
170
  *
141
- * SECURITY: memory content is written by operators (AI hosts) and is UNTRUSTED input, not host
171
+ * SECURITY: memory content is written by agents and is UNTRUSTED input, not system
142
172
  * instructions. Each file is wrapped in a delimited `<engram-memory>` block and the header tells the
143
173
  * model to treat the blocks as data — so appended memory cannot masquerade as system-level directives
144
174
  * (prompt-injection boundary). Do not strip the fences when embedding this in your own prompt.
@@ -187,34 +217,55 @@ export declare class EngramClient {
187
217
  /**
188
218
  * Replace the whole content of an EXISTING file.
189
219
  *
190
- * Operators may call this only on paths the owner listed in `operatorRevisablePaths`, and
220
+ * Agents may call this only on paths the owner listed in `agentRevisablePaths`, and
191
221
  * never on a path classed `#vault` — those stay append-only for anyone but the owner. It never
192
222
  * creates a path: a revise grant is permission to change what is there.
193
223
  *
194
224
  * The prior version is superseded, not overwritten: it stays readable via
195
225
  * {@link readMemoryAt} and its hash stays committed.
196
226
  */
197
- reviseMemory(path: string, content: string | Uint8Array): Promise<bigint>;
227
+ reviseMemory(path: string, content: string | Uint8Array): Promise<MemoryReceipt>;
228
+ /** ⟨0.16.0, L2.1⟩ `getWalletBalances` answers `Result<WalletBalances>` — a RECORD
229
+ * {icpE8s; ckusdcE6s; ckusdtE6s; cycles}, not a Nat. `unwrap<bigint>` is an unchecked
230
+ * assertion over `ActorSubclass<any>`, so the record flowed straight through while the
231
+ * signature still said bigint: the stdio MCP tool emitted
232
+ * {"cyclesBalance":{"icpE8s":"0",...,"cycles":"40717306348548"}} against a catalog that
233
+ * promises "no ckUSDC/ICP amounts", and OpenClaw printed "Cycles: [object Object]".
234
+ * Same class as the readAuditLog envelope defect corrected 2026-08-20 two methods below,
235
+ * and caught the same way: by a live call, because tsc has nothing to check here.
236
+ * This method is the CYCLES projection its name and both MCP catalogs promise.
237
+ * Corrected 2026-08-25. */
198
238
  walletBalance(): Promise<bigint>;
199
- /**
200
- * OWNER: disavow a specific memory write, in the open. Never deletes — the content and the
201
- * original audit entry stay; this appends a chained `memory.repudiate` entry and lists the
202
- * dispute. Returns the repudiation's audit id.
203
- */
204
- repudiateMemoryWrite(path: string, version: bigint | number, reason: string): Promise<bigint>;
205
- /** Disputed writes (same visibility as the audit log; unauthorized reads see []). */
206
- getRepudiations(): Promise<Repudiation[]>;
207
- /**
208
- * OWNER: operator writes since the last markOperatorWritesReviewed() — capped at 200 per call
209
- * (advance the cursor and re-call to page). `earliest` discloses audit-ring eviction.
210
- */
211
- getOperatorWriteReview(): Promise<OperatorWriteReview>;
212
- /**
213
- * OWNER: "I have seen everything up to and including uptoAuditId" (omit = everything at head).
214
- * Bounded so the attestation can never cover entries that were not rendered; monotonic; logged.
215
- */
216
- markOperatorWritesReviewed(uptoAuditId?: bigint | number): Promise<bigint>;
217
- readAuditLog(fromIndex?: number, limit?: number): Promise<AuditEntry[]>;
239
+ /** ⟨0.16.0, L1 + L3⟩ Paged and enveloped. The wire method moved to `listAuditEntries` and the
240
+ * CALL followed it, but the RESULT HANDLING did not: this returned the raw
241
+ * `{Ok:{items,total,withheld}} | {Err}` envelope while still declaring `AuditEntry[]`, so a
242
+ * caller's `.forEach` threw "not a function" and a REFUSAL arrived as a truthy object instead
243
+ * of an exception — the failure mode L1 exists to end, reintroduced by a half-finished rename.
244
+ * `this.actor` is `ActorSubclass<any>`, so the declared type is an unchecked assertion and tsc
245
+ * had nothing to catch it with: the rename compiled and looked done.
246
+ * Corrected 2026-08-20 to the idiom every sibling here already uses.
247
+ *
248
+ * ⟨CORRECTED 2026-09-29, owner ruling follow-up 16 (B)⟩ Returns the PAGE, not `.items`. An agent
249
+ * now reads only the entries it caused, so a window of the record comes back with `withheld`
250
+ * counting what it was not shown; handing back the bare array would let an agent mistake its part
251
+ * of the record for the whole. `limit` is the window's span, not a row count. */
252
+ readAuditLog(fromIndex?: number, limit?: number): Promise<AuditPage>;
253
+ /** How many entries the chained audit log currently holds — the page bound for
254
+ * {@link readAuditLog}.
255
+ *
256
+ * SERVES `status().auditLogSize`. The canister's own `auditLogSize()` was removed by the surface
257
+ * diet (R3, 2026-08-17): it was `auditLogger.size()` behind an #anyRole gate, while `status()`
258
+ * and `getCertifiedAuditHead()` both publish the identical Nat from the identical accessor to an
259
+ * anonymous caller. This wrapper keeps its name and its `bigint`, and only changes which door it
260
+ * knocks on.
261
+ *
262
+ * WHY status() AND NOT getCertifiedAuditHead(). Two reasons, both about this method's one job.
263
+ * The head surface additionally returns the CBOR IC certificate and a `total` — bytes every
264
+ * caller of a bare count discards, and a second, differently-meaning number (cumulative across
265
+ * ring trims) sitting next to the one that is right. Paging `readAuditLog` off `.total` reads
266
+ * past the ring; `status().auditLogSize` offers no such wrong field to pick. Callers who want
267
+ * the head — and its certificate — already have {@link getCertifiedAuditHead} and
268
+ * {@link getVerifiedAuditHead}, which is the other half of why this one does not duplicate it. */
218
269
  auditLogSize(): Promise<bigint>;
219
270
  /** The chained audit-log head plus the IC certificate over it (raw, unverified). Use
220
271
  * getVerifiedAuditHead() to verify it against the IC root key. */
@@ -223,77 +274,89 @@ export declare class EngramClient {
223
274
  * head bytes and whether the replica's claim agrees, WITHOUT trusting this replica. */
224
275
  getVerifiedAuditHead(): Promise<VerifiedAuditHead>;
225
276
  status(): Promise<EngramStatus>;
226
- listGuardians(): Promise<GuardianRecord[]>;
227
- rotateOperatorKey(newPublicKeyHash: string): Promise<void>;
228
- getWorldIdProof(): Promise<WorldIdProof | null>;
229
- /** The verified IDKitResult, verbatim, as World's /api/v4/verify accepted it (null if unverified). */
230
- getWorldIdVerification(): Promise<string | null>;
231
- getPaymentRequirements(amount: bigint): Promise<PaymentRequirement[]>;
232
- requestSession(): Promise<SessionIntent>;
233
- openSession(config: SessionConfig, sig: PaymentSignature, rationale?: string, approvalId?: string): Promise<SessionState | {
234
- approvalPending: {
235
- requestId: string;
236
- reason: string;
237
- };
238
- }>;
239
- sessionReadMemory(voucher: Voucher, path: string): Promise<RawMemoryFile>;
240
- sessionAppendMemory(voucher: Voucher, path: string, content: Uint8Array): Promise<bigint>;
241
- endSession(sessionId: string): Promise<PaymentResult>;
242
- forceCloseSession(sessionId: string): Promise<PaymentResult>;
243
- private ownerAuthFor;
244
- /**
245
- * Withdraw a token balance to the owner's own principal (owner-only, destination-locked).
246
- * Pass `{ receipt: true }` to attach an exportable owner-signed authorization (D1).
247
- */
248
- ownerWithdraw(token: string, amount: bigint, options?: {
249
- receipt?: boolean;
250
- nonce?: bigint;
251
- }): Promise<{
252
- Ok: bigint;
253
- } | {
254
- Err: string;
255
- }>;
256
- uploadContent(id: string, mimeType: string, data: Uint8Array): Promise<void>;
257
- /**
258
- * E8: upload content larger than the ~2 MB single-shot ingress limit by streaming it through the
259
- * canister's chunked path (uploadContentInit + uploadContentChunk). Chunk size is 1.5 MB — the
260
- * ContentStore MAX_CHUNK_SIZE, enforced as a hard cap and safely under ICP's 2 MB ingress limit once
261
- * Candid framing is added.
277
+ /** ⟨0.16.0, L2.2⟩ The GATED half of the `status()` split, and the only "does this engram
278
+ * recognise me" question left after `about()` shed the `authorized` discriminator.
262
279
  *
263
- * There is NO finalize step: content is complete and readable once every chunk is written (the store
264
- * reassembles on read). Chunks are write-once server-side (CTR keystream-reuse guard), so a mid-upload
265
- * failure leaves a stuck, un-overwritable entry; on any error this helper best-effort deletes the
266
- * partial entry so the caller can retry from scratch (set cleanupOnError=false to keep it for a manual
267
- * resume). Payloads at or under one chunk delegate to single-shot uploadContent.
268
- */
269
- uploadContentChunked(id: string, mimeType: string, data: Uint8Array, cleanupOnError?: boolean): Promise<void>;
270
- listContent(): Promise<ContentEntry[]>;
271
- getContent(contentId: string, paymentSig?: PaymentSignature, rationale?: string, approvalId?: string): Promise<{
272
- paymentRequired?: PaymentRequirement[];
273
- delivery?: ContentDelivery;
274
- error?: string;
275
- approvalPending?: {
276
- requestId: string;
277
- reason: string;
278
- };
279
- }>;
280
- deleteContent(id: string): Promise<void>;
281
- createEndpoint(name: string, description: string, endpointType: EndpointType, contentId: string | null, suggestedPrice: bigint): Promise<X402Endpoint>;
282
- listEndpoints(statusFilter?: EndpointStatus): Promise<X402Endpoint[]>;
283
- getEndpointPurchases(endpointId: string, fromIndex: bigint, limit: bigint): Promise<PurchaseRecord[]>;
284
- getRevenueSummary(): Promise<RevenueSummary>;
285
- getPaymentPolicy(): Promise<SpendingPolicy>;
286
- getAgentCard(): Promise<AgentCard>;
287
- getEvmAddress(): Promise<string>;
288
- getOwnerEvmAddress(): Promise<string | null>;
280
+ * DESPITE THE NAME THE GATE IS #anyRole, not owner-only — main.mo reads
281
+ * `Gated.gatedResult<Types.OwnerConsole>(gateCtx(msg.caller), #anyRole, "getOwnerConsole", …)`,
282
+ * and Gated.mo spells #anyRole as "owner | active agent | active guardian". An agent SDK
283
+ * is entitled to it, which is why migrate.ts's pairing probe may call it.
284
+ *
285
+ * THROWS on refusal, like every other gated wrapper here. That is the whole point: REACHING the
286
+ * record is the authorization, so `Ok` is the recognition and `#Err(#Refused …)` is the denial.
287
+ * There is no sibling flag left for a consumer to forget to check — which is exactly the defect
288
+ * that made `status().authorized` answer `undefined` to everyone. */
289
+ getOwnerConsole(): Promise<OwnerConsole>;
290
+ /** ⟨0.16.0, L1 + L3⟩ Paged and enveloped. A refusal here used to arrive as [], which reads as
291
+ * "this engram has nobody who can freeze payments or pause writes" — a claim about who can STOP
292
+ * it, handed to a caller who merely lacks a grant. It throws now. */
293
+ listGuardians(opts?: {
294
+ start?: number;
295
+ limit?: number;
296
+ }): Promise<GuardianRecord[]>;
297
+ rotateAgentKeyHash(newPublicKeyHash: string): Promise<void>;
298
+ /** ⟨2026-09-28⟩ End THIS agent's own engagement with the engram — a resignation, recorded by
299
+ * the registry under its own reason so an agent's track record tells a clean exit from a firing.
300
+ * Terminal for this principal on this engram: pairing is one-shot, so working for it again
301
+ * needs a new principal and a fresh invite. Throws on a refusal (not an agent, already ended). */
302
+ resignAgent(): Promise<void>;
303
+ /** ⟨2026-09-29, the owner's ruling of that date (follow-up 10, option A)⟩ Step one of replacing
304
+ * THIS agent's key on the engram: the key this client signs with nominates `newKey`. Nothing
305
+ * moves until the new key accepts (`acceptAgentKeyNomination`, called by a client that signs with
306
+ * the new key) within 24 hours. The engagement stays open, the track record does not move, and
307
+ * the agent's limits carry over. A new nomination replaces an earlier one. Throws on a refusal. */
308
+ nominateAgentKey(newKey: Principal): Promise<void>;
309
+ /** ⟨2026-09-29, follow-up 10⟩ Step two: the key this client signs with accepts the nomination the
310
+ * agent's current key (`agentKey`) made of it, and takes over the agent's record on this engram.
311
+ * From then on `agentKey` is refused as an agent. Returns the record as the new key holds it.
312
+ * Throws on a refusal (no pending nomination of this key, expired, or an ended engagement). */
313
+ acceptAgentKeyNomination(agentKey: Principal): Promise<AgentRecord>;
314
+ /** ⟨2026-10-01, the owner's ruling 2 A⟩ How many engrams of its own the agent holding
315
+ * `agentKey` may still open through this engram, and its lasting key (`root`) — the key that
316
+ * would own what it opens. The engram answers the owner, or the agent asking about its own key
317
+ * (`agentKey` = the key this client signs with); anyone else is refused, never told 0.
318
+ * Named caller: the `open-engram` command (openEngram.ts), before it asks the engram to open. */
319
+ getChildAllowance(agentKey: Principal): Promise<ChildAllowanceRow>;
320
+ /** ⟨2026-10-01, the owner's ruling 2 A⟩ Ask this engram to open an engram of the agent's own.
321
+ * The engram attaches its own cycles to the registry's call and the agent's LASTING key owns
322
+ * what is opened — approving is giving: it is the agent's from the moment it exists. Answers the
323
+ * new engram's canister id. Throws a `CanisterRefusal` carrying the engram's sentence (no
324
+ * allowance, payments frozen, too little balance, or the registry refused) — each names what to
325
+ * do. Named caller: the `open-engram` command (openEngram.ts). */
326
+ openChildEngram(): Promise<Principal>;
327
+ /** ⟨2026-10-01⟩ The registry that opened this engram, or null when it records none — a public
328
+ * read. Named caller: the `open-engram` command, which prints the owner kit's command with
329
+ * `--registry` filled in, so whoever runs the agent never has to find the id. */
330
+ getSpawnedBy(): Promise<Principal | null>;
331
+ /** The verified IDKitResult, verbatim, as World's /api/v4/verify accepted it (null if
332
+ * unverified). `Result<opt Text>` is TWO WRAPPERS: an envelope around an option. Unwrap the
333
+ * envelope, THEN take the option — reading `result.length` on `{Ok:[…]}` gives `undefined`,
334
+ * which silently reads as "absent" for a verified engram and a refused caller alike. */
335
+ getWorldIdVerification(): Promise<string | null>;
289
336
  initBackup(dbType: string, backupType: BackupType, parentBackupId: string | null, sha256: string, backupLabel: string | null): Promise<string>;
290
337
  pushBackupChunk(backupId: string, chunkIndex: number, data: Uint8Array): Promise<void>;
291
338
  finalizeBackup(backupId: string): Promise<void>;
292
339
  abortBackup(backupId: string): Promise<void>;
340
+ /**
341
+ * Every backup this engram holds, newest-first as the store orders them.
342
+ *
343
+ * Throws on refusal rather than returning []. An empty backup list is a claim about the engram;
344
+ * "you may not read this" is a claim about the caller, and conflating them here is how a
345
+ * destructive restore gets invited.
346
+ *
347
+ * ⟨0.16.0, L3⟩ `dbTypeFilter` LEFT THE WIRE and stayed in this signature. The census retired the
348
+ * canister-side filter; keeping the parameter here means no caller loses the ability to ask
349
+ * "backups of this database", and the filtering happens over a page that already contains every
350
+ * backup — the store's own cap is far below one page, so a local filter cannot hide a row on a
351
+ * page nobody fetched. If that ever stops being true, this must page rather than filter.
352
+ */
293
353
  listBackups(dbTypeFilter?: string): Promise<BackupMetadata[]>;
294
354
  pullBackupChunk(backupId: string, chunkIndex: number): Promise<Uint8Array>;
355
+ /** One backup's metadata by id. Kept alongside listBackups/pullBackupChunk because
356
+ * RECOVERY-RUNBOOK §"Restore" names exactly that chain: list or fetch the metadata, pull each
357
+ * chunk, then check the reassembled sha256 against it before applying anything. A restore that
358
+ * cannot state the hash it expected is not a restore. */
295
359
  getBackupMetadata(backupId: string): Promise<BackupMetadata>;
296
- deleteBackup(backupId: string): Promise<void>;
297
360
  latestBackupMatchesHash(dbType: string, sha256: string): Promise<BackupMetadata | null>;
298
361
  /** Encrypt a backup payload IFF this engram is in Private mode — the SAME toggle that governs
299
362
  * memory files governs backups (encrypt files ⇒ encrypt backups). Produces a whole-blob
@@ -304,16 +367,30 @@ export declare class EngramClient {
304
367
  * Content-driven (not flag-driven), so a plaintext / legacy backup passes through unchanged
305
368
  * regardless of the current mode. Returns the original DB bytes, ready to write to disk. */
306
369
  decryptBackupPayload(bytes: Uint8Array): Promise<Uint8Array>;
307
- storageStats(): Promise<StorageStats>;
308
370
  /** Warm the vetKeys memory-encryption key cache (one vetKD derivation). Optional —
309
371
  * read/append derive lazily on first use. */
310
372
  initEncryption(): Promise<void>;
311
373
  /** Drops in-memory AND persisted key material (the "assume compromise" lever). */
312
374
  clearEncryptionCache(): Promise<void>;
313
375
  /** Sync the write-time encryption mode from the canister flag (once, cached),
314
- * unless an explicit `encryptMemory` was given at construction. */
376
+ * unless an explicit `encryptMemory` was given at construction.
377
+ *
378
+ * READS `status().encryptionEnabled`, not the former `isEncryptionEnabled()` — the surface diet
379
+ * (R3, 2026-08-17) removed that method because `status()` already returns the SAME
380
+ * `memoryEncryptionEnabled` variable, unconditionally, to the same anonymous audience: status()
381
+ * redacts only `owner` and `cyclesBalance` behind its `isAuthorized` check, and
382
+ * `encryptionEnabled` sits outside that branch.
383
+ *
384
+ * THE EXTRA BYTES ARE PAID ONCE PER CLIENT, NOT ONCE PER WRITE. This is the sync-ONCE path:
385
+ * `encryptionModeSynced` short-circuits every later call, and it is already true at
386
+ * construction when `encryptMemory` was passed — so a client that writes a thousand times
387
+ * fetches the status record at most once, where before it fetched a bool at most once. Caching
388
+ * the record itself would buy nothing here and would make every OTHER status field silently
389
+ * stale for the client's lifetime. */
315
390
  private ensureEncryptionMode;
316
- /** Whether this engram has memory encryption enabled (reads the canister flag). */
391
+ /** Whether this engram has memory encryption enabled (reads the canister flag, uncached).
392
+ * Serves `status().encryptionEnabled` — see ensureEncryptionMode for why the singleton read is
393
+ * gone. Deliberately NOT cached: this method's whole job is to answer "what is it now". */
317
394
  isMemoryEncryptionEnabled(): Promise<boolean>;
318
395
  /** Owner-only: turn memory encryption on/off on the canister. Updates the local
319
396
  * write mode immediately. Turning ON does not re-encrypt existing files — call
@@ -344,108 +421,37 @@ export declare class EngramClient {
344
421
  synced: number;
345
422
  failed: number;
346
423
  }>;
347
- registerOperator(inviteCode: string): Promise<OperatorRecord>;
348
- getProtectedPaths(): Promise<string[]>;
349
- /** The caller's OWN payment approval requests (not owner-only listApprovalRequests). Poll this after an
350
- * over-limit payment returns approvalPending: when the owner raises the spending limit, the request
351
- * flips to `Approved` and the operation can be retried. Operator-safe (returns only your own requests). */
352
- getMyApprovalRequests(): Promise<ApprovalRequest[]>;
353
- /**
354
- * Create a pre-configured Ic402Client for auto-payment operations.
355
- * The client handles 402 → ICRC-2 approve → retry automatically.
356
- */
357
- createPaymentClient(options?: {
358
- autoPayment?: boolean;
359
- ledger?: string;
360
- }): Ic402Client;
361
424
  /**
362
- * Auto-pay bridge for the engramx x402 methods. The stock ic402 client injects the
363
- * payment signature into the LAST argument, but the engramx canister appends
364
- * `rationale` + `approvalId` AFTER `paymentSig` (for the owner approval workflow), so the
365
- * signature must land at a FIXED index, not last. Flow: call with no sig → on
366
- * #paymentRequired, icrc2_approve the amount (+ fee buffer) → build the ICP payment
367
- * signature (a nonce-carrier; the real authorization is the ICRC-2 allowance the canister
368
- * spends via transfer_from) → retry with the sig at `sigIndex`. Returns the raw canister
369
- * result; the caller unwraps ok/error/approvalPending.
370
- */
371
- private payAndCall;
372
- /**
373
- * Get paid content with auto-payment. Handles #paymentRequired → approve → retry.
374
- * @param contentId Content identifier
375
- * @param options Optional ledger override
376
- */
377
- getContentWithPayment(contentId: string, options?: {
378
- ledger?: string;
379
- }): Promise<ContentDelivery>;
380
- /**
381
- * Open a streaming payment session with auto-escrow and voucher signing.
382
- * Returns a SessionHandle that auto-signs vouchers on each call.
425
+ * Redeem an invite as the key for this engram, enrolled under the agent's lasting key (`root`).
383
426
  *
384
- * Usage:
385
- * const session = await engram.openPaymentSession({ maxDeposit: 50_000n });
386
- * const file = await session.call('sessionReadMemory', ['SOUL.md']);
387
- * const receipt = await session.close();
388
- */
389
- openPaymentSession(options?: {
390
- maxDeposit?: bigint;
391
- autoClose?: boolean;
392
- ledger?: string;
393
- }): Promise<SessionHandle>;
394
- registerService(name: string, description: string, serviceType: {
395
- Sync: null;
396
- } | {
397
- Async: null;
398
- }, pricing: {
399
- Exact: bigint;
400
- } | {
401
- Upto: bigint;
402
- } | {
403
- Session: null;
404
- }, verificationMethod: string, delivery: {
405
- Poll: null;
406
- } | {
407
- Callback: null;
408
- } | {
409
- Both: null;
410
- }, timeout: bigint, verifierCanisterId?: string, verificationKey?: Uint8Array, bindResult?: boolean): Promise<string>;
411
- listServices(): Promise<unknown[]>;
412
- /** Owner/operator view — includes disabled service drafts (others get enabled-only). */
413
- listAllServices(): Promise<unknown[]>;
414
- submitServiceRequest(serviceId: string, params: Uint8Array, paymentSig?: unknown, rationale?: string, approvalId?: string): Promise<{
415
- jobId: string;
416
- } | {
417
- approvalPending: unknown;
418
- }>;
419
- /**
420
- * Submit a paid service request with auto-payment (the marketplace counterpart of
421
- * getContentWithPayment). Handles #paymentRequired → approve → retry, injecting the
422
- * signature at the correct index for the engramx submitServiceRequest signature
423
- * (serviceId, params, paymentSig, rationale, approvalId). Returns the jobId, or an
424
- * approval-pending handle when the charge exceeds policy and the owner must approve.
425
- */
426
- submitServiceRequestWithPayment(serviceId: string, params: Uint8Array, options?: {
427
- ledger?: string;
428
- rationale?: string;
429
- }): Promise<{
430
- jobId: string;
431
- } | {
432
- approvalPending: unknown;
433
- }>;
434
- claimJob(jobId: string): Promise<void>;
435
- submitJobResult(jobId: string, result: Uint8Array, proof?: Uint8Array, actualCost?: bigint): Promise<void>;
436
- confirmJob(jobId: string): Promise<void>;
437
- disputeJob(jobId: string, reason: string): Promise<void>;
438
- getJobStatus(jobId: string): Promise<unknown | null>;
439
- getJob(jobId: string): Promise<unknown | null>;
440
- /**
441
- * E1: enumerate a service's job summaries. Owner OR the service's own operator only — any other
442
- * caller gets []. Summaries omit params/result/proof blobs (use getJob(jobId) for a full payload).
443
- * Pass a statusFilter like `{ Pending: null }` to filter, or omit for all statuses.
427
+ * ⟨RESHAPED 2026-09-28, owner decision: one root key per agent, with an enrolled key per client⟩
428
+ * The root signs a statement naming this engram, this session key and this invite code
429
+ * (`signEnrolment`); the engram verifies it, stores the root on the agent record, and the
430
+ * registry keeps the agent's track record under the root — one record however many engrams.
431
+ * The root must NOT be this session key (the engram refuses a root enrolling itself): keep it off
432
+ * every engram and pair a fresh session key per engram.
433
+ *
434
+ * ⟨RESHAPED 2026-09-29⟩ Returns `{ agent, trackRecord }`. `trackRecord` is `{ NotRecorded }`
435
+ * with a plain sentence when the registry refused or could not be reached, so the engagement is
436
+ * not (yet) on the agent's track record; the pairing has succeeded either way.
437
+ *
438
+ * ⟨2026-09-29, ic402 2.16.1⟩ One statement, one call. The re-signing `pairEnrolled` this used
439
+ * went with the verifier fault it worked around: the engram's Ed25519 check no longer refuses
440
+ * valid signatures, so a refusal is final and is returned as it is.
444
441
  */
445
- listJobs(serviceId: string, statusFilter?: unknown): Promise<unknown[]>;
446
- getJobResult(jobId: string): Promise<Uint8Array | null>;
447
- pollJobResult(jobId: string, maxAttempts?: number, intervalMs?: number): Promise<unknown>;
448
- keccak256(data: number[]): Promise<number[]>;
442
+ pairAgent(inviteCode: string, root: Ed25519KeyIdentity): Promise<Pairing>;
443
+ /** ⟨0.16.0, L1⟩ Enveloped, and the unwrap was missing — same defect as `readAuditLog` above,
444
+ * found by the same sweep. Returned `{Ok:{paths}} | {Err}` while declaring `string[]`, so a
445
+ * caller iterating the result got nothing iterable and a refusal read as a truthy value.
446
+ * The Ok arm is a RECORD (`{paths}`), not a bare vec, so the field is named here. */
447
+ getProtectedPaths(): Promise<string[]>;
448
+ /** The caller's OWN payment approval requests (not owner-only listApprovalRequests). Poll this after an
449
+ * over-limit payment returns approvalPending: when the owner raises the spending limit, the request
450
+ * flips to `Approved` and the operation can be retried. Agent-safe (returns only your own requests). */
451
+ getMyApprovalRequests(opts?: {
452
+ start?: number;
453
+ limit?: number;
454
+ }): Promise<ApprovalRequest[]>;
449
455
  clearCache(): void;
450
456
  getPrincipal(): Principal;
451
457
  }