@engramx/client 0.15.0 → 0.16.0-rc.2

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 (66) hide show
  1. package/README.md +255 -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 +59 -0
  13. package/dist/actionReceipt.js +68 -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 +95 -26
  22. package/dist/cli.js.map +1 -1
  23. package/dist/encryption.d.ts +26 -12
  24. package/dist/encryption.js +13 -15
  25. package/dist/encryption.js.map +1 -1
  26. package/dist/generated/engram.idl.d.ts +6 -0
  27. package/dist/generated/engram.idl.js +1366 -0
  28. package/dist/generated/engram.idl.js.map +1 -0
  29. package/dist/generated/market.idl.d.ts +6 -0
  30. package/dist/generated/market.idl.js +557 -0
  31. package/dist/generated/market.idl.js.map +1 -0
  32. package/dist/generated/registry.idl.d.ts +6 -0
  33. package/dist/generated/registry.idl.js +510 -0
  34. package/dist/generated/registry.idl.js.map +1 -0
  35. package/dist/idl.d.ts +66 -0
  36. package/dist/idl.js +227 -0
  37. package/dist/idl.js.map +1 -0
  38. package/dist/index.d.ts +12 -18
  39. package/dist/index.js +54 -9
  40. package/dist/index.js.map +1 -1
  41. package/dist/ledgerfsHash.js +1 -1
  42. package/dist/ledgerfsHash.js.map +1 -1
  43. package/dist/lineage.js +8 -5
  44. package/dist/lineage.js.map +1 -1
  45. package/dist/memoryArchive.d.ts +384 -0
  46. package/dist/memoryArchive.js +477 -0
  47. package/dist/memoryArchive.js.map +1 -0
  48. package/dist/migrate.js +31 -4
  49. package/dist/migrate.js.map +1 -1
  50. package/dist/openEngram.d.ts +48 -0
  51. package/dist/openEngram.js +161 -0
  52. package/dist/openEngram.js.map +1 -0
  53. package/dist/pair.d.ts +33 -2
  54. package/dist/pair.js +56 -9
  55. package/dist/pair.js.map +1 -1
  56. package/dist/types.d.ts +244 -307
  57. package/dist/version.d.ts +8 -0
  58. package/dist/version.js +45 -0
  59. package/dist/version.js.map +1 -0
  60. package/dist/wire.d.ts +76 -0
  61. package/dist/wire.js +79 -0
  62. package/dist/wire.js.map +1 -0
  63. package/package.json +3 -13
  64. package/dist/bootstrap.d.ts +0 -21
  65. package/dist/bootstrap.js +0 -71
  66. package/dist/bootstrap.js.map +0 -1
@@ -0,0 +1,384 @@
1
+ import { Principal } from '@icp-sdk/core/principal';
2
+ import { type Event as LedgerFsEvent, type Hash } from './ledgerfsHash.js';
3
+ export interface MemoryLogChunkRef {
4
+ hash: Uint8Array;
5
+ size: bigint;
6
+ birthSeq: bigint;
7
+ }
8
+ export interface MemoryLogManifest {
9
+ chunks: MemoryLogChunkRef[];
10
+ totalSize: bigint;
11
+ contentRoot: Uint8Array;
12
+ }
13
+ /** Why an imported entry exists — the pre-ledgerfs migration seam. All five classes. */
14
+ export type MemoryLogImportNote = {
15
+ genesis: {
16
+ boundTo: [] | [Uint8Array];
17
+ };
18
+ } | {
19
+ legacyGap: {
20
+ firstSurviving: bigint;
21
+ };
22
+ } | {
23
+ legacyTruncated: {
24
+ storedBytes: bigint;
25
+ prefixHash: Uint8Array;
26
+ };
27
+ } | {
28
+ legacyDelete: null;
29
+ } | {
30
+ legacyUnreconstructable: {
31
+ contentHash: Uint8Array;
32
+ reason: string;
33
+ };
34
+ };
35
+ /** The operation one log entry records. `condense` and `exported` are store-global. */
36
+ export type MemoryLogOp = {
37
+ append: {
38
+ deltaHash: Uint8Array;
39
+ deltaSize: bigint;
40
+ newSize: bigint;
41
+ };
42
+ } | {
43
+ set: {
44
+ manifest: MemoryLogManifest;
45
+ };
46
+ } | {
47
+ restore: {
48
+ toVersion: bigint;
49
+ manifest: MemoryLogManifest;
50
+ };
51
+ } | {
52
+ tombstone: null;
53
+ } | {
54
+ evict: {
55
+ fromVersion: bigint;
56
+ toVersion: bigint;
57
+ chunkCount: bigint;
58
+ byteCount: bigint;
59
+ evictedRoot: Uint8Array;
60
+ };
61
+ } | {
62
+ condense: {
63
+ fromSeq: bigint;
64
+ toSeq: bigint;
65
+ opCount: bigint;
66
+ opsRoot: Uint8Array;
67
+ headAtBoundary: Uint8Array;
68
+ };
69
+ } | {
70
+ exported: {
71
+ fromSeq: bigint;
72
+ toSeq: bigint;
73
+ opsRoot: Uint8Array;
74
+ authorFilter: [] | [Principal];
75
+ };
76
+ } | {
77
+ imported: MemoryLogImportNote;
78
+ };
79
+ /**
80
+ * One entry in the engram's single memory log.
81
+ *
82
+ * `path` is carried verbatim, which is why the whole surface is owner-only: a path is inside the
83
+ * entry's hash preimage, so it cannot be redacted for a lesser caller without breaking the very
84
+ * hash the archive exists to prove.
85
+ */
86
+ export interface MemoryLogEvent {
87
+ seq: bigint;
88
+ path: string;
89
+ version: bigint;
90
+ op: MemoryLogOp;
91
+ /** Attribution — who wrote it. Never authorization. */
92
+ by: Principal;
93
+ /**
94
+ * When the entry was chained, in nanoseconds. ⟨RENAMED 2026-10-05⟩ This said `at`. The engram
95
+ * renamed every wire instant to carry its unit on 2026-10-02 (`at` → `atNs` on this record,
96
+ * `MemoryRangeExport`, `ExportReceiptRow` and `CondenseCheckpointRow`), and the four mirrors in
97
+ * this file kept the old name. `toLedgerFsEvent` then read `e.at`, which a served entry no
98
+ * longer carries, and `sle64(undefined)` throws "TypeError: Cannot convert undefined to a BigInt".
99
+ * So `memoryLogEventHash` and `verifyMemoryArchive` threw on the first real entry. They never
100
+ * returned a wrong verdict, but they could not check a single entry (measured 2026-10-05 with
101
+ * node). The hash contract's own field (`Event.at` in ledgerfsHash.ts) keeps its name, because
102
+ * the hashed body carries the value and never the name.
103
+ */
104
+ atNs: bigint;
105
+ size: bigint;
106
+ contentRoot: Uint8Array;
107
+ /** Hash of the previous entry under the canonical (non-Candid) encoding. */
108
+ prev: Uint8Array;
109
+ }
110
+ /**
111
+ * One page of the memory log.
112
+ *
113
+ * `pageCap` is the width of the window the engram actually covered — its clamp on `max`, which
114
+ * sits at 256 rather than the library's 1024. It is DISCLOSED for exactly one reason: a caller
115
+ * that cannot see the cap cannot know whether it has the whole window. Read it, never `max`.
116
+ *
117
+ * ⚠ ⟨STALE 2026-10-05 — measured, not fixed⟩ This envelope no longer mirrors the wire, and the
118
+ * difference goes beyond a field name. 0.16.0 renamed `getMemoryLogPage` to `listMemoryLog`, and it
119
+ * answers `LogPage = { firstAvailableSeq; head; page : { items; total; withheld } }`, so `events`,
120
+ * `totalEvents` and `pageCap` are not served. `assembleMemoryArchive` walks by `pageCap`, and the
121
+ * served page has no field with that meaning. A caller therefore has to adapt `listMemoryLog` into
122
+ * this shape, and the adaptation decides how wide each window really was. That is a design call
123
+ * for the walk, not a rename. It is left open and named in test/wire-mirror.test.ts, which fails
124
+ * once this envelope matches, so the exception cannot outlive the drift. The entries themselves
125
+ * (`MemoryLogEvent`) do mirror the wire.
126
+ */
127
+ export interface MemoryLogPage {
128
+ events: MemoryLogEvent[];
129
+ /** Entries EVER chained, not the retained count — a condensed prefix never shortens this. */
130
+ totalEvents: bigint;
131
+ /** The floor below which entries are no longer served: a condensed prefix, made visible. */
132
+ firstAvailableSeq: bigint;
133
+ head: Uint8Array;
134
+ pageCap: bigint;
135
+ }
136
+ /** What `exportMemoryRange` writes into the chain and hands back. */
137
+ export interface MemoryExportReceipt {
138
+ /** Chain position of the receipt entry itself — always above every entry it covers. */
139
+ seq: bigint;
140
+ head: Uint8Array;
141
+ fromSeq: bigint;
142
+ toSeq: bigint;
143
+ opsRoot: Uint8Array;
144
+ authorFilter: [] | [Principal];
145
+ /** ⟨RENAMED 2026-10-05⟩ was `at` — see `MemoryLogEvent.atNs`. */
146
+ atNs: bigint;
147
+ }
148
+ /** What `condenseMemoryLog` returns: the checkpoint that replaced the dropped prefix. */
149
+ export interface MemoryCondenseResult {
150
+ seq: bigint;
151
+ head: Uint8Array;
152
+ fromSeq: bigint;
153
+ toSeq: bigint;
154
+ opCount: bigint;
155
+ opsRoot: Uint8Array;
156
+ /** The head attested at the boundary — what a verifier seeds from once the prefix is gone. */
157
+ headAtBoundary: Uint8Array;
158
+ newFirstAvailableSeq: bigint;
159
+ /**
160
+ * SIGNED, and that is the point: a condense drops entries but adds a checkpoint back, so a
161
+ * short condense can cost more history bytes than it frees. A positive delta is a real,
162
+ * representable outcome, not an error.
163
+ */
164
+ historyBytesDelta: bigint;
165
+ }
166
+ /** A receipt or checkpoint named by a preview, enough to go fetch or explain it. */
167
+ export interface MemoryReceiptRef {
168
+ seq: bigint;
169
+ fromSeq: bigint;
170
+ toSeq: bigint;
171
+ }
172
+ /** What a condense WOULD do, and whether the export gate is open. Reads nothing destructive. */
173
+ export interface MemoryCondensePreview {
174
+ firstAvailableSeq: bigint;
175
+ totalEvents: bigint;
176
+ proposedToSeq: bigint;
177
+ opCountInRange: bigint;
178
+ /** True when an unfiltered receipt already covers the whole proposed prefix. */
179
+ unlocked: boolean;
180
+ coveringReceipt: [] | [MemoryReceiptRef];
181
+ /** The nearest miss, so a refusal says what is still needed rather than only "no". */
182
+ bestUnfilteredReceipt: [] | [MemoryReceiptRef];
183
+ /**
184
+ * Author-filtered receipts seen while scanning. They are counted, never credited: a filtered
185
+ * ops root cannot witness that every entry in the range was served.
186
+ */
187
+ filteredReceiptsSeen: bigint;
188
+ maxUnlockableToSeq: bigint;
189
+ scannedFromSeq: bigint;
190
+ /** True ⇒ the scan hit its bound; the answer describes what was scanned, not the whole chain. */
191
+ scanTruncated: boolean;
192
+ }
193
+ export interface MemoryExportReceiptRow {
194
+ seq: bigint;
195
+ /** ⟨RENAMED 2026-10-05⟩ was `at` — see `MemoryLogEvent.atNs`. */
196
+ atNs: bigint;
197
+ by: Principal;
198
+ fromSeq: bigint;
199
+ toSeq: bigint;
200
+ opsRoot: Uint8Array;
201
+ authorFilter: [] | [Principal];
202
+ }
203
+ export interface MemoryCheckpointRow {
204
+ seq: bigint;
205
+ /** ⟨RENAMED 2026-10-05⟩ was `at` — see `MemoryLogEvent.atNs`. */
206
+ atNs: bigint;
207
+ by: Principal;
208
+ fromSeq: bigint;
209
+ toSeq: bigint;
210
+ opCount: bigint;
211
+ opsRoot: Uint8Array;
212
+ headAtBoundary: Uint8Array;
213
+ }
214
+ /**
215
+ * How many log entries one assembled archive may span.
216
+ *
217
+ * A `#set` entry carries a full manifest, and maxFileBytes / chunkSize = 10 MiB / 256 KiB caps a
218
+ * manifest at 40 chunk references (src/engram/LedgerFsInit.mo) — several kilobytes of decoded JS
219
+ * objects for a single entry. Ten thousand of those is already past what a browser tab should
220
+ * hold, and an owner who runs out of memory mid-walk gets no archive AND no receipt.
221
+ *
222
+ * The ceiling is on the RANGE WIDTH, not on the entries kept, so an author-filtered archive is
223
+ * bounded by the same number: the range is what drives the round trips, and the range is what a
224
+ * receipt attests. Splitting costs nothing — each part gets its own receipt, and condense needs
225
+ * only one receipt covering the prefix it drops.
226
+ */
227
+ export declare const ARCHIVE_MAX_RANGE = 10000n;
228
+ /**
229
+ * Entries requested per page before any clamp.
230
+ *
231
+ * Set to the engram's own cap (`MemoryRouter.LOG_PAGE_MAX`, 256) so a full page is never left
232
+ * half-used — the engram clamps the ask to that number anyway and reports the result as
233
+ * `pageCap`. Asking for the cap is safe rather than optimistic because the fallback below it is
234
+ * real: a page that will not fit is retried at half the size, and a walk that had to shrink says
235
+ * so through `smallestPageUsed`.
236
+ */
237
+ export declare const ARCHIVE_PAGE_EVENTS = 256n;
238
+ /**
239
+ * A decoded log entry in the form the frozen hash contract operates on.
240
+ *
241
+ * The store-global sentinel substitution (`#condense`/`#exported` hash with an empty path, zero
242
+ * version, zero size and a zero content root) is NOT applied here — ledgerfsHash owns it, in the
243
+ * one place the Motoko encoder is mirrored. Doing it twice is how the two drift.
244
+ */
245
+ export declare function toLedgerFsEvent(e: MemoryLogEvent): LedgerFsEvent;
246
+ /** The canonical hash of one log entry, recomputed from its decoded fields. */
247
+ export declare const memoryLogEventHash: (e: MemoryLogEvent) => Hash;
248
+ /**
249
+ * Fetches one page.
250
+ *
251
+ * The caller supplies it, and after 2026-08-17 that is the only way it arrives: `EngramClient`
252
+ * used to carry a `getMemoryLogPage` wrapper that satisfied this shape, and the charter diet
253
+ * removed it — `getMemoryLogPage` is owner-only on the canister and the class is the agent SDK.
254
+ * Nothing about the WALK changed. An owner tool passes its own authenticated actor's
255
+ * `getMemoryLogPage` here and gets the same paging, the same halving on an over-large response,
256
+ * and the same refusal to return a short archive; the tests pass their own fetcher and always did.
257
+ *
258
+ * ⟨CORRECTED 2026-10-05⟩ An owner tool can no longer pass the actor's method straight in. 0.16.0
259
+ * serves `listMemoryLog`, not `getMemoryLogPage`, and its reply is not a `MemoryLogPage` (see the
260
+ * note there). Until the walk is adapted, a fetcher has to translate the served page into this
261
+ * shape. That translation is open, not done.
262
+ */
263
+ export type MemoryLogPageFetcher = (startSeq: bigint, max: bigint, authorFilter?: Principal) => Promise<MemoryLogPage>;
264
+ export interface AssembleArchiveOptions {
265
+ /** Default: the engram's retained floor, learned from the first page. */
266
+ fromSeq?: bigint | number;
267
+ /** Default: the last entry ever chained, learned from the first page. */
268
+ toSeq?: bigint | number;
269
+ /** Restrict to one author. A filtered archive can only be checked against a filtered receipt. */
270
+ authorFilter?: Principal;
271
+ /** Starting page size. Only ever shrinks from here — never grows toward `pageCap`. */
272
+ pageEvents?: bigint | number;
273
+ /** Lower the range ceiling for this call. Values above {@link ARCHIVE_MAX_RANGE} are clamped. */
274
+ maxRange?: bigint | number;
275
+ }
276
+ export interface MemoryArchive {
277
+ /** Entries in ascending seq order, deduplicated. */
278
+ events: MemoryLogEvent[];
279
+ fromSeq: bigint;
280
+ toSeq: bigint;
281
+ authorFilter: Principal | null;
282
+ totalEvents: bigint;
283
+ firstAvailableSeq: bigint;
284
+ /** The engram's chain head as of the last page fetched. */
285
+ head: Uint8Array;
286
+ /** The cap the engram disclosed on the last page. */
287
+ pageCap: bigint;
288
+ pagesFetched: number;
289
+ /**
290
+ * The smallest page size the walk had to fall back to. Below the requested size ⇒ at least one
291
+ * page did not fit in a response and was retried smaller. Surfaced because "it worked, slowly"
292
+ * and "it worked" are different facts about the engram's log.
293
+ */
294
+ smallestPageUsed: bigint;
295
+ }
296
+ /**
297
+ * Whether a failed page looks like the engram could not fit the response, as opposed to refusing
298
+ * the call.
299
+ *
300
+ * Deliberately STRICT. Halving on every error would spend six round trips turning an
301
+ * authorization refusal into the same refusal, and would report it under a misleading heading;
302
+ * an unmatched error is rethrown with the cursor attached instead. If a new replica wording slips
303
+ * past this, the archive fails LOUDLY — the walk stops and throws — which is the safe direction:
304
+ * the alternative is an archive that is quietly short.
305
+ */
306
+ export declare function isResponseTooLarge(err: unknown): boolean;
307
+ /**
308
+ * Walk a range of the memory log into one archive, or throw.
309
+ *
310
+ * WHAT IT REFUSES TO DO, in order of how badly each would hurt:
311
+ * - report a short archive as whole (every seq in an unfiltered range is asserted present);
312
+ * - assume it received `max` entries (it advances by the window the engram confirmed via
313
+ * `pageCap`, and by the highest seq actually returned when that is further);
314
+ * - give up on a page the engram could not fit (halves and retries, down to one entry);
315
+ * - start below the retained floor (a condensed prefix is unattestable, so an explicit request
316
+ * for it is refused rather than silently raised);
317
+ * - assemble more than {@link ARCHIVE_MAX_RANGE} entries in one pass.
318
+ *
319
+ * Termination is structural: the cursor advances by at least one seq per successful page, and the
320
+ * page size strictly halves per size failure until it reaches one.
321
+ */
322
+ export declare function assembleMemoryArchive(fetchPage: MemoryLogPageFetcher, options?: AssembleArchiveOptions): Promise<MemoryArchive>;
323
+ /**
324
+ * The part of a receipt or checkpoint verification needs. Both an `exportMemoryRange` receipt and
325
+ * a `condenseMemoryLog` checkpoint satisfy it — they commit to an ops root over the same
326
+ * construction, which is why one verifier covers both.
327
+ */
328
+ export interface OpsRootAttestation {
329
+ fromSeq: bigint;
330
+ toSeq: bigint;
331
+ opsRoot: Uint8Array;
332
+ /** Absent or `[]` ⇒ the attestation covers every author in the range. */
333
+ authorFilter?: [] | [Principal];
334
+ }
335
+ export interface MemoryArchiveVerification {
336
+ /** The single question: does this archive reproduce the root the engram committed to? */
337
+ matches: boolean;
338
+ computedOpsRoot: string;
339
+ attestedOpsRoot: string;
340
+ /** Entries the ops root is built over, after applying the attestation's author filter. */
341
+ eventsCovered: number;
342
+ /** Entries the attestation's range implies, when it can be known (unfiltered only). */
343
+ eventsExpected: number | null;
344
+ /** Every seq in the range present exactly once. Only meaningful for an unfiltered attestation. */
345
+ rangeContiguous: boolean;
346
+ /** Each held entry's `prev` equals the recomputed hash of the entry before it. */
347
+ chainLinked: boolean;
348
+ /** Facts found while checking, in the order found. Empty when everything lined up. */
349
+ notes: string[];
350
+ /**
351
+ * Present only on a mismatch. Written to describe the situation without assigning blame — the
352
+ * likeliest causes are on the reader's side, and an owner told "your engram is corrupt" acts on
353
+ * it.
354
+ */
355
+ explanation: string | null;
356
+ }
357
+ /**
358
+ * Rebuild the ops root over a set of log entries exactly as the engram does: the entries in
359
+ * `[fromSeq, toSeq]` that pass the author filter, in ascending seq order, each hashed with
360
+ * ledgerfsHash and wrapped as an ops leaf.
361
+ *
362
+ * An empty covered set has a DEFINED root — the honest attestation that this author wrote nothing
363
+ * in the range — so it is computed, never special-cased into a failure.
364
+ */
365
+ export declare function recomputeOpsRoot(events: readonly MemoryLogEvent[], range: {
366
+ fromSeq: bigint;
367
+ toSeq: bigint;
368
+ authorFilter?: [] | [Principal];
369
+ }): {
370
+ root: Hash;
371
+ covered: MemoryLogEvent[];
372
+ };
373
+ /**
374
+ * Check an archive against the receipt (or checkpoint) that attests it.
375
+ *
376
+ * The hashing is ./ledgerfsHash.ts throughout — the file the `ledgerfs-goldens` cross-lock pins
377
+ * against the Motoko encoder. Nothing is reimplemented here, because a second implementation of
378
+ * the hash contract is exactly how an owner ends up being told their evidence is bad when the two
379
+ * copies merely drifted.
380
+ *
381
+ * Never throws on a mismatch: a mismatch is a RESULT to read, and the wording of
382
+ * {@link MemoryArchiveVerification.explanation} deliberately does not accuse the engram.
383
+ */
384
+ export declare function verifyMemoryArchive(archive: Pick<MemoryArchive, 'events'>, attestation: OpsRootAttestation): MemoryArchiveVerification;