@receiz/sdk 95.0.0 → 96.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { buildReceizIdContinueRequest, createReceizIdIdentity, projectReceizIdentityAccount, readReceizIdentityArtifact, signReceizIdentityLoginProof, verifyReceizIdentityLoginProof, } from "./identity.js";
2
2
  export * from "./identity.js";
3
- export const RECEIZ_SDK_VERSION = "95.0.0";
3
+ export const RECEIZ_SDK_VERSION = "96.1.0";
4
4
  export const RECEIZ_DEFAULT_BASE_URL = "https://receiz.com";
5
5
  export const RECEIZ_WEBHOOK_SIGNATURE_HEADER = "x-receiz-signature";
6
6
  export const RECEIZ_WEBHOOK_TIMESTAMP_HEADER = "x-receiz-timestamp";
@@ -347,6 +347,29 @@ export class ReceizClient {
347
347
  publicLedger: (query = {}) => this.publicWalletLedger(query),
348
348
  actionLedger: (query = {}) => this.request(appendQuery("/api/ledger/actions/public", query)),
349
349
  };
350
+ world = {
351
+ publicSnapshot: () => this.request("/api/world/public"),
352
+ profile: (username, query = {}) => this.request(appendQuery(`/api/world/profile/${encodePathSegment(username)}`, {
353
+ visitorKey: query.visitorKey,
354
+ threadKey: query.threadKey,
355
+ })),
356
+ message: (username, body) => this.request(`/api/world/profile/${encodePathSegment(username)}`, {
357
+ method: "POST",
358
+ body: { ...body, action: body.action ?? "message" },
359
+ }),
360
+ welcome: (username, body = {}) => this.request(`/api/world/profile/${encodePathSegment(username)}`, {
361
+ method: "POST",
362
+ body: { ...body, action: "welcome" },
363
+ }),
364
+ book: (username, body) => this.request(`/api/world/profile/${encodePathSegment(username)}`, {
365
+ method: "POST",
366
+ body: { ...body, action: "book" },
367
+ }),
368
+ streamProfile: (username, body) => this.streamJsonEvents(`/api/world/profile/${encodePathSegment(username)}/stream`, {
369
+ method: "POST",
370
+ body: { ...body, action: body.action === "welcome" ? "welcome" : "message" },
371
+ }),
372
+ };
350
373
  connect = {
351
374
  wallet: () => this.delegated("/api/connect/wallet/me"),
352
375
  record: (body) => this.delegated("/api/connect/record", { method: "POST", body }),
@@ -359,6 +382,20 @@ export class ReceizClient {
359
382
  claimNote: (body) => this.delegated("/api/connect/payments/notes/claim", { method: "POST", body }),
360
383
  downloadNote: (noteId) => this.delegated(`/api/connect/payments/notes/${encodePathSegment(noteId)}/download`),
361
384
  };
385
+ twin = {
386
+ marketMandate: () => this.delegated("/api/connect/twin/market/mandate", { method: "GET" }),
387
+ saveMarketMandate: (body) => this.delegated("/api/connect/twin/market/mandate", { method: "PATCH", body }),
388
+ marketIntents: () => this.delegated("/api/connect/twin/market/intents", { method: "GET" }),
389
+ createMarketIntent: (body) => this.delegated("/api/connect/twin/market/intents", { method: "POST", body }),
390
+ approveMarketIntent: (intentId) => this.delegated(`/api/connect/twin/market/intents/${encodePathSegment(intentId)}/approve`, {
391
+ method: "POST",
392
+ }),
393
+ exportMind: () => this.delegatedBlob("/api/connect/twin/mind/export"),
394
+ importMindSummary: () => this.delegated("/api/connect/twin/mind/import", { method: "GET" }),
395
+ importMind: (file) => this.delegatedTwinMindImport(file),
396
+ approval: () => this.delegated("/api/connect/twin/approval", { method: "GET" }),
397
+ approvePromotion: (body) => this.delegated("/api/connect/twin/approval", { method: "POST", body }),
398
+ };
362
399
  identity = {
363
400
  createReceizId: createReceizIdIdentity,
364
401
  readArtifact: readReceizIdentityArtifact,
@@ -425,6 +462,11 @@ export class ReceizClient {
425
462
  };
426
463
  proofMemory = {
427
464
  createRegister: createReceizProofRegister,
465
+ createMemory: createReceizProofMemory,
466
+ createInMemoryStorage: createReceizInMemoryProofMemoryStorage,
467
+ createLocalStorage: createReceizLocalStorageProofMemoryStorage,
468
+ assertSnapshot: assertReceizProofRegisterSnapshot,
469
+ additionsQuery: receizProofMemoryAdditionsQuery,
428
470
  };
429
471
  constructor(options = {}) {
430
472
  this.baseUrl = trimTrailingSlash(options.baseUrl ?? RECEIZ_DEFAULT_BASE_URL);
@@ -488,6 +530,88 @@ export class ReceizClient {
488
530
  throw new Error("receiz_access_token_required");
489
531
  return this.request(path, { ...options, bearerToken: this.accessToken });
490
532
  }
533
+ async delegatedBlob(path, options = {}) {
534
+ if (!this.accessToken)
535
+ throw new Error("receiz_access_token_required");
536
+ const headers = new Headers(options.headers);
537
+ if (!headers.has("accept"))
538
+ headers.set("accept", "application/octet-stream");
539
+ headers.set("authorization", `Bearer ${this.accessToken}`);
540
+ const response = await this.fetchImpl(`${this.baseUrl}${requestPath(path)}`, {
541
+ method: options.method ?? "GET",
542
+ headers,
543
+ });
544
+ if (!response.ok)
545
+ throw new ReceizHttpError(response.status, await readPayload(response));
546
+ return response.blob();
547
+ }
548
+ async delegatedTwinMindImport(file) {
549
+ const form = new FormData();
550
+ form.set("mind", file);
551
+ return this.delegated("/api/connect/twin/mind/import", { method: "POST", body: form });
552
+ }
553
+ async *streamJsonEvents(path, options = {}) {
554
+ const headers = new Headers(options.headers);
555
+ if (!headers.has("accept"))
556
+ headers.set("accept", "text/event-stream");
557
+ const token = options.bearerToken ?? this.accessToken;
558
+ if (token)
559
+ headers.set("authorization", `Bearer ${token}`);
560
+ let body = options.body;
561
+ if (isJsonRecordBody(body)) {
562
+ if (!headers.has("content-type"))
563
+ headers.set("content-type", "application/json");
564
+ body = JSON.stringify(body);
565
+ }
566
+ const url = path.startsWith("http://") || path.startsWith("https://") ? path : `${this.baseUrl}${requestPath(path)}`;
567
+ const response = await this.fetchImpl(url, {
568
+ method: options.method ?? "GET",
569
+ headers,
570
+ ...(body !== undefined ? { body: body } : {}),
571
+ });
572
+ if (!response.ok)
573
+ throw new ReceizHttpError(response.status, await readPayload(response));
574
+ if (!response.body)
575
+ throw new Error("receiz_stream_unavailable");
576
+ const reader = response.body.getReader();
577
+ const decoder = new TextDecoder();
578
+ let buffer = "";
579
+ const parseEvent = (raw) => {
580
+ const data = raw
581
+ .split("\n")
582
+ .filter((line) => line.startsWith("data:"))
583
+ .map((line) => line.slice(5).trimStart())
584
+ .join("\n")
585
+ .trim();
586
+ if (!data)
587
+ return null;
588
+ return JSON.parse(data);
589
+ };
590
+ try {
591
+ for (;;) {
592
+ const { done, value } = await reader.read();
593
+ if (done)
594
+ break;
595
+ buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
596
+ let boundary = buffer.indexOf("\n\n");
597
+ while (boundary >= 0) {
598
+ const raw = buffer.slice(0, boundary);
599
+ buffer = buffer.slice(boundary + 2);
600
+ const parsed = parseEvent(raw);
601
+ if (parsed)
602
+ yield parsed;
603
+ boundary = buffer.indexOf("\n\n");
604
+ }
605
+ }
606
+ buffer += decoder.decode().replace(/\r\n/g, "\n");
607
+ const parsed = parseEvent(buffer);
608
+ if (parsed)
609
+ yield parsed;
610
+ }
611
+ finally {
612
+ reader.releaseLock();
613
+ }
614
+ }
491
615
  async continueReceizId(identity, options = {}) {
492
616
  const body = await buildReceizIdContinueRequest(identity, options);
493
617
  return this.request("/api/auth/receiz-id/continue", { method: "POST", body });
@@ -863,6 +987,67 @@ function normalizeRegisterEntry(value) {
863
987
  projection: isRecord(projection) ? projection : null,
864
988
  };
865
989
  }
990
+ export function assertReceizProofRegisterSnapshot(value) {
991
+ const record = ensureRecord(value, "ReceizProofRegisterSnapshot");
992
+ const issues = [];
993
+ if (record.schema !== "receiz.sdk.proof_register.v1")
994
+ issues.push("schema must be receiz.sdk.proof_register.v1");
995
+ const ownerId = record.ownerId;
996
+ if (ownerId !== null && ownerId !== undefined && typeof ownerId !== "string") {
997
+ issues.push("ownerId must be a string or null when present");
998
+ }
999
+ const createdAt = ensureString(record, "createdAt", issues);
1000
+ const updatedAt = ensureString(record, "updatedAt", issues);
1001
+ const head = record.head;
1002
+ if (!isRecord(head)) {
1003
+ issues.push("head must be an object");
1004
+ }
1005
+ else {
1006
+ const entryId = head.entryId;
1007
+ if (entryId !== null && entryId !== undefined && typeof entryId !== "string") {
1008
+ issues.push("head.entryId must be a string or null");
1009
+ }
1010
+ const kaiUpulse = head.kaiUpulse;
1011
+ if (kaiUpulse !== null && kaiUpulse !== undefined && typeof kaiUpulse !== "string" && typeof kaiUpulse !== "number") {
1012
+ issues.push("head.kaiUpulse must be a string, number, or null");
1013
+ }
1014
+ const headCreatedAt = head.createdAt;
1015
+ if (headCreatedAt !== null && headCreatedAt !== undefined && typeof headCreatedAt !== "string") {
1016
+ issues.push("head.createdAt must be a string or null");
1017
+ }
1018
+ if (typeof head.count !== "number" || !Number.isInteger(head.count) || head.count < 0) {
1019
+ issues.push("head.count must be a non-negative integer");
1020
+ }
1021
+ }
1022
+ if (!Array.isArray(record.entries))
1023
+ issues.push("entries must be an array");
1024
+ failIfIssues("ReceizProofRegisterSnapshot", issues);
1025
+ const entries = record.entries.map((entry) => normalizeRegisterEntry(entry));
1026
+ return {
1027
+ schema: "receiz.sdk.proof_register.v1",
1028
+ ownerId: typeof ownerId === "string" || ownerId === null ? ownerId : null,
1029
+ createdAt: createdAt ?? "",
1030
+ updatedAt: updatedAt ?? "",
1031
+ head: {
1032
+ entryId: isRecord(head) && typeof head.entryId === "string" ? head.entryId : null,
1033
+ kaiUpulse: isRecord(head) && (typeof head.kaiUpulse === "string" || typeof head.kaiUpulse === "number") ? head.kaiUpulse : null,
1034
+ createdAt: isRecord(head) && typeof head.createdAt === "string" ? head.createdAt : null,
1035
+ count: isRecord(head) && typeof head.count === "number" ? head.count : entries.length,
1036
+ },
1037
+ entries,
1038
+ };
1039
+ }
1040
+ function parseProofMemoryStorageValue(value) {
1041
+ if (value === null || value === undefined)
1042
+ return null;
1043
+ if (typeof value === "string") {
1044
+ const trimmed = value.trim();
1045
+ if (!trimmed)
1046
+ return null;
1047
+ return assertReceizProofRegisterSnapshot(JSON.parse(trimmed));
1048
+ }
1049
+ return assertReceizProofRegisterSnapshot(value);
1050
+ }
866
1051
  function mergeRegisterEntry(existing, incoming) {
867
1052
  return {
868
1053
  id: existing.id,
@@ -922,6 +1107,11 @@ function registerEntryFromWebhookEvent(event) {
922
1107
  },
923
1108
  };
924
1109
  }
1110
+ function errorDetail(error) {
1111
+ if (error instanceof Error)
1112
+ return { name: error.name, message: error.message };
1113
+ return { message: String(error) };
1114
+ }
925
1115
  export class ReceizProofRegister {
926
1116
  ownerId;
927
1117
  createdAt;
@@ -938,6 +1128,16 @@ export class ReceizProofRegister {
938
1128
  this.entriesById.set(normalized.id, existing ? mergeRegisterEntry(existing, normalized) : normalized);
939
1129
  return this;
940
1130
  }
1131
+ admit(value) {
1132
+ if (isReceizAssetManifest(value))
1133
+ return this.admitAssetManifest(value);
1134
+ if (isReceizSportsCardManifest(value))
1135
+ return this.admitSportsCardManifest(value);
1136
+ if (isReceizWebhookEvent(value))
1137
+ return this.admitWebhookEvent(value);
1138
+ const schema = isRecord(value) && typeof value.schema === "string" ? value.schema : "unknown";
1139
+ throw new ReceizValidationError("ReceizProofAdmission", [`unsupported proof object schema ${schema}`]);
1140
+ }
941
1141
  admitAssetManifest(value) {
942
1142
  return this.append(registerEntryFromAssetManifest(assertReceizAssetManifest(value)));
943
1143
  }
@@ -977,6 +1177,193 @@ export class ReceizProofRegister {
977
1177
  export function createReceizProofRegister(input = {}) {
978
1178
  return new ReceizProofRegister(input);
979
1179
  }
1180
+ export class ReceizProofMemory {
1181
+ register;
1182
+ storage;
1183
+ autoPersist;
1184
+ onPersistError;
1185
+ onObservation;
1186
+ persistChain = Promise.resolve();
1187
+ constructor(register, options = {}) {
1188
+ this.register = register;
1189
+ this.storage = options.storage ?? null;
1190
+ this.autoPersist = options.autoPersist ?? true;
1191
+ this.onPersistError = options.onPersistError ?? null;
1192
+ this.onObservation = options.onObservation ?? null;
1193
+ }
1194
+ append(entry) {
1195
+ const startedAtMs = Date.now();
1196
+ try {
1197
+ this.register.append(entry);
1198
+ this.observe("proof_memory.append", startedAtMs, "ok");
1199
+ return this.schedulePersist();
1200
+ }
1201
+ catch (error) {
1202
+ this.observe("proof_memory.append", startedAtMs, "error", errorDetail(error));
1203
+ throw error;
1204
+ }
1205
+ }
1206
+ admit(value) {
1207
+ const startedAtMs = Date.now();
1208
+ try {
1209
+ this.register.admit(value);
1210
+ this.observe("proof_memory.admit", startedAtMs, "ok");
1211
+ return this.schedulePersist();
1212
+ }
1213
+ catch (error) {
1214
+ this.observe("proof_memory.admit", startedAtMs, "error", errorDetail(error));
1215
+ throw error;
1216
+ }
1217
+ }
1218
+ admitAssetManifest(value) {
1219
+ return this.admit(value);
1220
+ }
1221
+ admitSportsCardManifest(value) {
1222
+ return this.admit(value);
1223
+ }
1224
+ admitWebhookEvent(value) {
1225
+ return this.admit(value);
1226
+ }
1227
+ has(entryId) {
1228
+ return this.register.has(entryId);
1229
+ }
1230
+ entries() {
1231
+ return this.register.entries();
1232
+ }
1233
+ snapshot() {
1234
+ return this.register.snapshot();
1235
+ }
1236
+ knownHead(limit) {
1237
+ return receizProofMemoryAdditionsQuery(this.snapshot(), limit);
1238
+ }
1239
+ async persist() {
1240
+ const startedAtMs = Date.now();
1241
+ try {
1242
+ const snapshot = this.snapshot();
1243
+ if (this.storage)
1244
+ await this.storage.write(snapshot);
1245
+ this.observe("proof_memory.persist", startedAtMs, "ok", { persisted: Boolean(this.storage) });
1246
+ return snapshot;
1247
+ }
1248
+ catch (error) {
1249
+ this.observe("proof_memory.persist", startedAtMs, "error", errorDetail(error));
1250
+ throw error;
1251
+ }
1252
+ }
1253
+ async flush() {
1254
+ await this.persistChain;
1255
+ }
1256
+ async clear() {
1257
+ const startedAtMs = Date.now();
1258
+ try {
1259
+ if (this.storage?.remove)
1260
+ await this.storage.remove();
1261
+ this.observe("proof_memory.clear", startedAtMs, "ok", { removed: Boolean(this.storage?.remove) });
1262
+ }
1263
+ catch (error) {
1264
+ this.observe("proof_memory.clear", startedAtMs, "error", errorDetail(error));
1265
+ throw error;
1266
+ }
1267
+ }
1268
+ toJSON() {
1269
+ return this.snapshot();
1270
+ }
1271
+ schedulePersist() {
1272
+ if (!this.autoPersist || !this.storage)
1273
+ return this;
1274
+ this.persistChain = this.persistChain
1275
+ .then(async () => {
1276
+ await this.persist();
1277
+ })
1278
+ .catch((error) => {
1279
+ this.onPersistError?.(error);
1280
+ });
1281
+ return this;
1282
+ }
1283
+ observe(operation, startedAtMs, status, detail) {
1284
+ if (!this.onObservation)
1285
+ return;
1286
+ const endedAtMs = Date.now();
1287
+ const snapshot = this.snapshot();
1288
+ const observation = {
1289
+ schema: "receiz.sdk.observation.v1",
1290
+ operation,
1291
+ status,
1292
+ startedAt: new Date(startedAtMs).toISOString(),
1293
+ endedAt: new Date(endedAtMs).toISOString(),
1294
+ durationMs: Math.max(0, endedAtMs - startedAtMs),
1295
+ ownerId: this.register.ownerId,
1296
+ entryCount: snapshot.head.count,
1297
+ headEntryId: snapshot.head.entryId,
1298
+ headKaiUpulse: snapshot.head.kaiUpulse,
1299
+ };
1300
+ if (detail)
1301
+ observation.detail = detail;
1302
+ try {
1303
+ this.onObservation(observation);
1304
+ }
1305
+ catch {
1306
+ // Observation callbacks are passive evidence only; they must never affect proof admission.
1307
+ }
1308
+ }
1309
+ }
1310
+ export async function createReceizProofMemory(options = {}) {
1311
+ const storedSnapshot = options.storage ? parseProofMemoryStorageValue(await options.storage.read()) : null;
1312
+ const input = storedSnapshot
1313
+ ? {
1314
+ ...storedSnapshot,
1315
+ ownerId: options.ownerId ?? storedSnapshot.ownerId ?? null,
1316
+ }
1317
+ : {
1318
+ ownerId: options.ownerId ?? null,
1319
+ ...(options.createdAt === undefined ? {} : { createdAt: options.createdAt }),
1320
+ ...(options.entries === undefined ? {} : { entries: options.entries }),
1321
+ };
1322
+ return new ReceizProofMemory(createReceizProofRegister(input), options);
1323
+ }
1324
+ export function createReceizInMemoryProofMemoryStorage(initialValue = null) {
1325
+ let storedText = typeof initialValue === "string"
1326
+ ? initialValue
1327
+ : initialValue
1328
+ ? JSON.stringify(assertReceizProofRegisterSnapshot(initialValue))
1329
+ : null;
1330
+ return {
1331
+ read: () => storedText,
1332
+ write: (snapshot) => {
1333
+ storedText = JSON.stringify(assertReceizProofRegisterSnapshot(snapshot));
1334
+ },
1335
+ remove: () => {
1336
+ storedText = null;
1337
+ },
1338
+ readText: () => storedText,
1339
+ };
1340
+ }
1341
+ export function createReceizLocalStorageProofMemoryStorage(key = "receiz:proof-memory:v1", storage = globalThis.localStorage) {
1342
+ if (!storage)
1343
+ throw new Error("receiz_local_storage_unavailable");
1344
+ return {
1345
+ read: () => storage.getItem(key),
1346
+ write: (snapshot) => {
1347
+ storage.setItem(key, JSON.stringify(assertReceizProofRegisterSnapshot(snapshot)));
1348
+ },
1349
+ remove: () => {
1350
+ storage.removeItem(key);
1351
+ },
1352
+ };
1353
+ }
1354
+ export function receizProofMemoryAdditionsQuery(value, limit) {
1355
+ const snapshot = value instanceof ReceizProofRegister
1356
+ ? value.snapshot()
1357
+ : value instanceof ReceizProofMemory
1358
+ ? value.snapshot()
1359
+ : assertReceizProofRegisterSnapshot(value);
1360
+ return {
1361
+ afterEntryId: snapshot.head.entryId,
1362
+ afterKaiUpulse: snapshot.head.kaiUpulse,
1363
+ afterCreatedAt: snapshot.head.createdAt,
1364
+ ...(limit === undefined ? {} : { limit }),
1365
+ };
1366
+ }
980
1367
  function bodyToString(body) {
981
1368
  if (typeof body === "string")
982
1369
  return body;
@@ -12,7 +12,7 @@ const authorizeUrl = receiz.identity.authorizeUrl({
12
12
  clientId: process.env.RECEIZ_CLIENT_ID!,
13
13
  redirectUri: "https://app.example.com/auth/receiz/callback",
14
14
  codeChallenge,
15
- scope: ["openid", "profile", "receiz:verify", "receiz:wallet.transfer"],
15
+ scope: ["openid", "profile", "receiz:verify", "receiz:wallet.transfer", "receiz:twin.read"],
16
16
  state,
17
17
  });
18
18
  ```
@@ -29,3 +29,5 @@ Lifecycle:
29
29
  6. Revoke tokens when the user disconnects your app.
30
30
 
31
31
  Use `sub` from OIDC as the stable external user key. Do not key your user model by mutable email.
32
+
33
+ Public Receiz World reads do not require OIDC. Delegated owner actions, including Twin market mandates, Twin intents, Twin mind import/export, wallet transfers, records, seals, verification, payments, and note actions, require a registered OIDC client and a user-granted access token. Business/developer accounts are the correct place to create and manage those clients, redirect URIs, scopes, and token lifecycle.
@@ -0,0 +1,108 @@
1
+ # CLI And Local Conformance
2
+
3
+ The Receiz SDK ships an executable `receiz` command for local proof inspection, fixture conformance, and starter generation.
4
+
5
+ The CLI is convenience, not authority. It uses the same SDK validators, projections, and durable proof memory APIs that developer apps import from `@receiz/sdk`. It does not create a new conformance system, issuer, verifier, server confirmation, database confirmation, or truth layer.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @receiz/sdk
11
+ ```
12
+
13
+ Run without a local install:
14
+
15
+ ```bash
16
+ npx @receiz/sdk conformance
17
+ ```
18
+
19
+ ## Run Local Fixture Conformance
20
+
21
+ ```bash
22
+ npx @receiz/sdk conformance
23
+ ```
24
+
25
+ This validates the packaged fixtures:
26
+
27
+ - `receiz.asset_manifest.v1`
28
+ - `receiz.sports_arena.card_manifest.v1`
29
+ - `receiz.webhook_event.v1`
30
+
31
+ Each fixture is admitted into durable proof memory, persisted through the same storage interface developer apps use, reopened from the stored proof register, and summarized with its known Kai/proof head.
32
+
33
+ Expected shape:
34
+
35
+ ```json
36
+ {
37
+ "ok": true,
38
+ "schema": "receiz.sdk.cli.conformance.v1",
39
+ "summary": {
40
+ "fixtures": 3,
41
+ "proofMemoryEntries": 3,
42
+ "networkCalls": 0,
43
+ "dbCalls": 0
44
+ }
45
+ }
46
+ ```
47
+
48
+ `networkCalls` and `dbCalls` are zero by design. Local conformance proves the SDK can admit and project proof objects from carried truth without asking a weaker layer whether the proof is true.
49
+
50
+ ## Inspect A Proof Payload
51
+
52
+ ```bash
53
+ npx @receiz/sdk inspect ./receiz-asset-manifest.json
54
+ ```
55
+
56
+ The command validates one JSON payload, projects display-ready rows, admits the payload into in-memory proof memory, and prints the known head:
57
+
58
+ ```json
59
+ {
60
+ "ok": true,
61
+ "schema": "receiz.sdk.cli.inspect.v1",
62
+ "kind": "asset_manifest",
63
+ "knownHead": {
64
+ "afterEntryId": "asset:asset_example_01JZ_RECEIZ",
65
+ "afterKaiUpulse": "123456",
66
+ "afterCreatedAt": "2026-06-25T00:00:00.000Z",
67
+ "limit": 100
68
+ }
69
+ }
70
+ ```
71
+
72
+ The known head is the append-sync boundary. Your app asks for verified additions after this head; it does not rediscover the admitted prefix.
73
+
74
+ ## Generate A Starter
75
+
76
+ ```bash
77
+ npx @receiz/sdk init ./receiz-integration
78
+ ```
79
+
80
+ This writes:
81
+
82
+ ```txt
83
+ receiz-proof-memory.ts
84
+ ```
85
+
86
+ The starter:
87
+
88
+ - opens durable proof memory through `createReceizProofMemory`
89
+ - persists through `createReceizLocalStorageProofMemoryStorage`
90
+ - admits asset manifests, Sports card manifests, and webhook events
91
+ - projects proof objects immediately after admission
92
+ - exposes `knownHead()` for append-only sync
93
+
94
+ It does not include Supabase, service-role keys, or a parallel source of truth.
95
+
96
+ ## Production Rule
97
+
98
+ Use the CLI to prove your local integration shape, then use the typed SDK APIs in your app:
99
+
100
+ 1. Verify stronger proof truth when bytes come from an untrusted transport.
101
+ 2. Validate the manifest or event shape.
102
+ 3. Project display-ready rows from the verified object.
103
+ 4. Admit the proof object into durable proof memory.
104
+ 5. Persist the proof memory snapshot.
105
+ 6. Resume from the known Kai/proof head.
106
+ 7. Append later verified additions without rediscovering known truth.
107
+
108
+ That is the same law Receiz uses internally: first admission only, then append forever.