@receiz/sdk 94.0.0 → 96.0.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 = "94.0.0";
3
+ export const RECEIZ_SDK_VERSION = "96.0.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";
@@ -19,12 +19,34 @@ export const RECEIZ_ASSET_MANIFEST_SCHEMA = {
19
19
  },
20
20
  proof: {
21
21
  type: "object",
22
- required: ["kind"],
22
+ required: [
23
+ "kind",
24
+ "payloadVersion",
25
+ "createdAtMs",
26
+ "ts",
27
+ "code",
28
+ "slug",
29
+ "verifyPath",
30
+ "verifyUrl",
31
+ "kaiPulseEternal",
32
+ "kaiKlok",
33
+ "receizClaimId",
34
+ "sigilClaimSeed",
35
+ ],
23
36
  additionalProperties: true,
24
37
  properties: {
25
- kind: { type: "string", minLength: 1 },
38
+ kind: { const: "receiz.proof_bundle" },
39
+ payloadVersion: { enum: ["v2", "v1"] },
40
+ createdAtMs: { type: "number" },
41
+ ts: { type: "string", minLength: 1 },
42
+ code: { type: "string", minLength: 1 },
43
+ slug: { type: "string", minLength: 1 },
44
+ verifyPath: { type: "string", minLength: 1 },
26
45
  verifyUrl: { type: "string" },
27
- kaiPulseEternal: { type: ["number", "string"] },
46
+ kaiPulseEternal: { type: "string", minLength: 1 },
47
+ kaiKlok: { type: "string", minLength: 1 },
48
+ receizClaimId: { type: "string", minLength: 1 },
49
+ sigilClaimSeed: { type: "string", minLength: 1 },
28
50
  },
29
51
  },
30
52
  owner: { type: ["object", "null"], additionalProperties: true },
@@ -208,6 +230,79 @@ function failIfIssues(label, issues) {
208
230
  if (issues.length > 0)
209
231
  throw new ReceizValidationError(label, issues);
210
232
  }
233
+ function asSdkHex(value, length) {
234
+ if (typeof value !== "string")
235
+ return null;
236
+ const normalized = value.trim().toLowerCase();
237
+ const pattern = new RegExp(`^[0-9a-f]{${length}}$`);
238
+ return pattern.test(normalized) ? normalized : null;
239
+ }
240
+ function validateOptionalSdkHex(record, key, length, issues, path) {
241
+ if (record[key] === undefined)
242
+ return;
243
+ if (!asSdkHex(record[key], length))
244
+ issues.push(`${path}.${key} must be ${length} lowercase hex characters`);
245
+ }
246
+ function validateReceizProofBundleShape(value, path, issues) {
247
+ if (!isRecord(value)) {
248
+ issues.push(`${path} must be an object`);
249
+ return null;
250
+ }
251
+ if (value.kind !== "receiz.proof_bundle")
252
+ issues.push(`${path}.kind must be receiz.proof_bundle`);
253
+ if (value.payloadVersion !== "v2" && value.payloadVersion !== "v1") {
254
+ issues.push(`${path}.payloadVersion must be v2 or v1`);
255
+ }
256
+ const createdAtMs = value.createdAtMs;
257
+ if (typeof createdAtMs !== "number" || !Number.isFinite(createdAtMs)) {
258
+ issues.push(`${path}.createdAtMs must be a finite number`);
259
+ }
260
+ for (const key of ["ts", "code", "slug", "verifyPath", "kaiPulseEternal", "kaiKlok", "receizClaimId", "sigilClaimSeed"]) {
261
+ const fieldValue = value[key];
262
+ if (typeof fieldValue !== "string" || !fieldValue.trim())
263
+ issues.push(`${path}.${key} must be a non-empty string`);
264
+ }
265
+ if (typeof value.verifyUrl !== "string")
266
+ issues.push(`${path}.verifyUrl must be a string`);
267
+ if (value.tsDisplay !== undefined && (typeof value.tsDisplay !== "string" || !value.tsDisplay.trim())) {
268
+ issues.push(`${path}.tsDisplay must be a non-empty string when present`);
269
+ }
270
+ if (value.tzMinutesEast !== undefined && (typeof value.tzMinutesEast !== "number" || !Number.isFinite(value.tzMinutesEast))) {
271
+ issues.push(`${path}.tzMinutesEast must be a finite number when present`);
272
+ }
273
+ validateOptionalSdkHex(value, "signerKeyId", 12, issues, path);
274
+ validateOptionalSdkHex(value, "anchorId", 8, issues, path);
275
+ validateOptionalSdkHex(value, "groth16ProofDigest", 64, issues, path);
276
+ validateOptionalSdkHex(value, "artifactSha256Basis", 64, issues, path);
277
+ if (value.zkPoseidonHash !== undefined && (typeof value.zkPoseidonHash !== "string" || !value.zkPoseidonHash.trim())) {
278
+ issues.push(`${path}.zkPoseidonHash must be a non-empty string when present`);
279
+ }
280
+ if (value.signatureV3 !== undefined && !isRecord(value.signatureV3)) {
281
+ issues.push(`${path}.signatureV3 must be an object when present`);
282
+ }
283
+ if (value.signatureV4 !== undefined && !isRecord(value.signatureV4)) {
284
+ issues.push(`${path}.signatureV4 must be an object when present`);
285
+ }
286
+ if (value.wireproof !== undefined) {
287
+ const wireproof = value.wireproof;
288
+ if (!isRecord(wireproof)) {
289
+ issues.push(`${path}.wireproof must be an object when present`);
290
+ }
291
+ else {
292
+ if (wireproof.schema !== "wireproof.v1")
293
+ issues.push(`${path}.wireproof.schema must be wireproof.v1`);
294
+ if (!asSdkHex(wireproof.anchorId, 8))
295
+ issues.push(`${path}.wireproof.anchorId must be 8 lowercase hex characters`);
296
+ if (!asSdkHex(wireproof.claimHashSha256, 64)) {
297
+ issues.push(`${path}.wireproof.claimHashSha256 must be 64 lowercase hex characters`);
298
+ }
299
+ if (typeof wireproof.verifierPath !== "string" || !wireproof.verifierPath.trim()) {
300
+ issues.push(`${path}.wireproof.verifierPath must be a non-empty string`);
301
+ }
302
+ }
303
+ }
304
+ return value;
305
+ }
211
306
  const WEBHOOK_EVENT_TYPES = new Set([
212
307
  "asset.created",
213
308
  "asset.transferred",
@@ -330,6 +425,11 @@ export class ReceizClient {
330
425
  };
331
426
  proofMemory = {
332
427
  createRegister: createReceizProofRegister,
428
+ createMemory: createReceizProofMemory,
429
+ createInMemoryStorage: createReceizInMemoryProofMemoryStorage,
430
+ createLocalStorage: createReceizLocalStorageProofMemoryStorage,
431
+ assertSnapshot: assertReceizProofRegisterSnapshot,
432
+ additionsQuery: receizProofMemoryAdditionsQuery,
333
433
  };
334
434
  constructor(options = {}) {
335
435
  this.baseUrl = trimTrailingSlash(options.baseUrl ?? RECEIZ_DEFAULT_BASE_URL);
@@ -441,6 +541,21 @@ export async function loadReceizOpenApi(baseUrl = RECEIZ_DEFAULT_BASE_URL, fetch
441
541
  throw new ReceizHttpError(response.status, await readPayload(response));
442
542
  return response.json();
443
543
  }
544
+ export function assertReceizProofBundle(value) {
545
+ const issues = [];
546
+ const proof = validateReceizProofBundleShape(value, "proof", issues);
547
+ failIfIssues("ReceizProofBundle", issues);
548
+ return proof;
549
+ }
550
+ export function isReceizProofBundle(value) {
551
+ try {
552
+ assertReceizProofBundle(value);
553
+ return true;
554
+ }
555
+ catch {
556
+ return false;
557
+ }
558
+ }
444
559
  export function assertReceizAssetManifest(value) {
445
560
  const record = ensureRecord(value, "ReceizAssetManifest");
446
561
  const issues = [];
@@ -449,8 +564,7 @@ export function assertReceizAssetManifest(value) {
449
564
  ensureString(record, "assetId", issues);
450
565
  ensureString(record, "assetType", issues);
451
566
  const proof = record.proof;
452
- if (!isRecord(proof) || typeof proof.kind !== "string" || !proof.kind.trim())
453
- issues.push("proof.kind must be a non-empty string");
567
+ validateReceizProofBundleShape(proof, "proof", issues);
454
568
  ensureOptionalRecord(record, "owner", issues);
455
569
  ensureOptionalRecord(record, "media", issues);
456
570
  ensureOptionalRecord(record, "settlement", issues);
@@ -754,6 +868,67 @@ function normalizeRegisterEntry(value) {
754
868
  projection: isRecord(projection) ? projection : null,
755
869
  };
756
870
  }
871
+ export function assertReceizProofRegisterSnapshot(value) {
872
+ const record = ensureRecord(value, "ReceizProofRegisterSnapshot");
873
+ const issues = [];
874
+ if (record.schema !== "receiz.sdk.proof_register.v1")
875
+ issues.push("schema must be receiz.sdk.proof_register.v1");
876
+ const ownerId = record.ownerId;
877
+ if (ownerId !== null && ownerId !== undefined && typeof ownerId !== "string") {
878
+ issues.push("ownerId must be a string or null when present");
879
+ }
880
+ const createdAt = ensureString(record, "createdAt", issues);
881
+ const updatedAt = ensureString(record, "updatedAt", issues);
882
+ const head = record.head;
883
+ if (!isRecord(head)) {
884
+ issues.push("head must be an object");
885
+ }
886
+ else {
887
+ const entryId = head.entryId;
888
+ if (entryId !== null && entryId !== undefined && typeof entryId !== "string") {
889
+ issues.push("head.entryId must be a string or null");
890
+ }
891
+ const kaiUpulse = head.kaiUpulse;
892
+ if (kaiUpulse !== null && kaiUpulse !== undefined && typeof kaiUpulse !== "string" && typeof kaiUpulse !== "number") {
893
+ issues.push("head.kaiUpulse must be a string, number, or null");
894
+ }
895
+ const headCreatedAt = head.createdAt;
896
+ if (headCreatedAt !== null && headCreatedAt !== undefined && typeof headCreatedAt !== "string") {
897
+ issues.push("head.createdAt must be a string or null");
898
+ }
899
+ if (typeof head.count !== "number" || !Number.isInteger(head.count) || head.count < 0) {
900
+ issues.push("head.count must be a non-negative integer");
901
+ }
902
+ }
903
+ if (!Array.isArray(record.entries))
904
+ issues.push("entries must be an array");
905
+ failIfIssues("ReceizProofRegisterSnapshot", issues);
906
+ const entries = record.entries.map((entry) => normalizeRegisterEntry(entry));
907
+ return {
908
+ schema: "receiz.sdk.proof_register.v1",
909
+ ownerId: typeof ownerId === "string" || ownerId === null ? ownerId : null,
910
+ createdAt: createdAt ?? "",
911
+ updatedAt: updatedAt ?? "",
912
+ head: {
913
+ entryId: isRecord(head) && typeof head.entryId === "string" ? head.entryId : null,
914
+ kaiUpulse: isRecord(head) && (typeof head.kaiUpulse === "string" || typeof head.kaiUpulse === "number") ? head.kaiUpulse : null,
915
+ createdAt: isRecord(head) && typeof head.createdAt === "string" ? head.createdAt : null,
916
+ count: isRecord(head) && typeof head.count === "number" ? head.count : entries.length,
917
+ },
918
+ entries,
919
+ };
920
+ }
921
+ function parseProofMemoryStorageValue(value) {
922
+ if (value === null || value === undefined)
923
+ return null;
924
+ if (typeof value === "string") {
925
+ const trimmed = value.trim();
926
+ if (!trimmed)
927
+ return null;
928
+ return assertReceizProofRegisterSnapshot(JSON.parse(trimmed));
929
+ }
930
+ return assertReceizProofRegisterSnapshot(value);
931
+ }
757
932
  function mergeRegisterEntry(existing, incoming) {
758
933
  return {
759
934
  id: existing.id,
@@ -813,6 +988,11 @@ function registerEntryFromWebhookEvent(event) {
813
988
  },
814
989
  };
815
990
  }
991
+ function errorDetail(error) {
992
+ if (error instanceof Error)
993
+ return { name: error.name, message: error.message };
994
+ return { message: String(error) };
995
+ }
816
996
  export class ReceizProofRegister {
817
997
  ownerId;
818
998
  createdAt;
@@ -829,6 +1009,16 @@ export class ReceizProofRegister {
829
1009
  this.entriesById.set(normalized.id, existing ? mergeRegisterEntry(existing, normalized) : normalized);
830
1010
  return this;
831
1011
  }
1012
+ admit(value) {
1013
+ if (isReceizAssetManifest(value))
1014
+ return this.admitAssetManifest(value);
1015
+ if (isReceizSportsCardManifest(value))
1016
+ return this.admitSportsCardManifest(value);
1017
+ if (isReceizWebhookEvent(value))
1018
+ return this.admitWebhookEvent(value);
1019
+ const schema = isRecord(value) && typeof value.schema === "string" ? value.schema : "unknown";
1020
+ throw new ReceizValidationError("ReceizProofAdmission", [`unsupported proof object schema ${schema}`]);
1021
+ }
832
1022
  admitAssetManifest(value) {
833
1023
  return this.append(registerEntryFromAssetManifest(assertReceizAssetManifest(value)));
834
1024
  }
@@ -868,6 +1058,193 @@ export class ReceizProofRegister {
868
1058
  export function createReceizProofRegister(input = {}) {
869
1059
  return new ReceizProofRegister(input);
870
1060
  }
1061
+ export class ReceizProofMemory {
1062
+ register;
1063
+ storage;
1064
+ autoPersist;
1065
+ onPersistError;
1066
+ onObservation;
1067
+ persistChain = Promise.resolve();
1068
+ constructor(register, options = {}) {
1069
+ this.register = register;
1070
+ this.storage = options.storage ?? null;
1071
+ this.autoPersist = options.autoPersist ?? true;
1072
+ this.onPersistError = options.onPersistError ?? null;
1073
+ this.onObservation = options.onObservation ?? null;
1074
+ }
1075
+ append(entry) {
1076
+ const startedAtMs = Date.now();
1077
+ try {
1078
+ this.register.append(entry);
1079
+ this.observe("proof_memory.append", startedAtMs, "ok");
1080
+ return this.schedulePersist();
1081
+ }
1082
+ catch (error) {
1083
+ this.observe("proof_memory.append", startedAtMs, "error", errorDetail(error));
1084
+ throw error;
1085
+ }
1086
+ }
1087
+ admit(value) {
1088
+ const startedAtMs = Date.now();
1089
+ try {
1090
+ this.register.admit(value);
1091
+ this.observe("proof_memory.admit", startedAtMs, "ok");
1092
+ return this.schedulePersist();
1093
+ }
1094
+ catch (error) {
1095
+ this.observe("proof_memory.admit", startedAtMs, "error", errorDetail(error));
1096
+ throw error;
1097
+ }
1098
+ }
1099
+ admitAssetManifest(value) {
1100
+ return this.admit(value);
1101
+ }
1102
+ admitSportsCardManifest(value) {
1103
+ return this.admit(value);
1104
+ }
1105
+ admitWebhookEvent(value) {
1106
+ return this.admit(value);
1107
+ }
1108
+ has(entryId) {
1109
+ return this.register.has(entryId);
1110
+ }
1111
+ entries() {
1112
+ return this.register.entries();
1113
+ }
1114
+ snapshot() {
1115
+ return this.register.snapshot();
1116
+ }
1117
+ knownHead(limit) {
1118
+ return receizProofMemoryAdditionsQuery(this.snapshot(), limit);
1119
+ }
1120
+ async persist() {
1121
+ const startedAtMs = Date.now();
1122
+ try {
1123
+ const snapshot = this.snapshot();
1124
+ if (this.storage)
1125
+ await this.storage.write(snapshot);
1126
+ this.observe("proof_memory.persist", startedAtMs, "ok", { persisted: Boolean(this.storage) });
1127
+ return snapshot;
1128
+ }
1129
+ catch (error) {
1130
+ this.observe("proof_memory.persist", startedAtMs, "error", errorDetail(error));
1131
+ throw error;
1132
+ }
1133
+ }
1134
+ async flush() {
1135
+ await this.persistChain;
1136
+ }
1137
+ async clear() {
1138
+ const startedAtMs = Date.now();
1139
+ try {
1140
+ if (this.storage?.remove)
1141
+ await this.storage.remove();
1142
+ this.observe("proof_memory.clear", startedAtMs, "ok", { removed: Boolean(this.storage?.remove) });
1143
+ }
1144
+ catch (error) {
1145
+ this.observe("proof_memory.clear", startedAtMs, "error", errorDetail(error));
1146
+ throw error;
1147
+ }
1148
+ }
1149
+ toJSON() {
1150
+ return this.snapshot();
1151
+ }
1152
+ schedulePersist() {
1153
+ if (!this.autoPersist || !this.storage)
1154
+ return this;
1155
+ this.persistChain = this.persistChain
1156
+ .then(async () => {
1157
+ await this.persist();
1158
+ })
1159
+ .catch((error) => {
1160
+ this.onPersistError?.(error);
1161
+ });
1162
+ return this;
1163
+ }
1164
+ observe(operation, startedAtMs, status, detail) {
1165
+ if (!this.onObservation)
1166
+ return;
1167
+ const endedAtMs = Date.now();
1168
+ const snapshot = this.snapshot();
1169
+ const observation = {
1170
+ schema: "receiz.sdk.observation.v1",
1171
+ operation,
1172
+ status,
1173
+ startedAt: new Date(startedAtMs).toISOString(),
1174
+ endedAt: new Date(endedAtMs).toISOString(),
1175
+ durationMs: Math.max(0, endedAtMs - startedAtMs),
1176
+ ownerId: this.register.ownerId,
1177
+ entryCount: snapshot.head.count,
1178
+ headEntryId: snapshot.head.entryId,
1179
+ headKaiUpulse: snapshot.head.kaiUpulse,
1180
+ };
1181
+ if (detail)
1182
+ observation.detail = detail;
1183
+ try {
1184
+ this.onObservation(observation);
1185
+ }
1186
+ catch {
1187
+ // Observation callbacks are passive evidence only; they must never affect proof admission.
1188
+ }
1189
+ }
1190
+ }
1191
+ export async function createReceizProofMemory(options = {}) {
1192
+ const storedSnapshot = options.storage ? parseProofMemoryStorageValue(await options.storage.read()) : null;
1193
+ const input = storedSnapshot
1194
+ ? {
1195
+ ...storedSnapshot,
1196
+ ownerId: options.ownerId ?? storedSnapshot.ownerId ?? null,
1197
+ }
1198
+ : {
1199
+ ownerId: options.ownerId ?? null,
1200
+ ...(options.createdAt === undefined ? {} : { createdAt: options.createdAt }),
1201
+ ...(options.entries === undefined ? {} : { entries: options.entries }),
1202
+ };
1203
+ return new ReceizProofMemory(createReceizProofRegister(input), options);
1204
+ }
1205
+ export function createReceizInMemoryProofMemoryStorage(initialValue = null) {
1206
+ let storedText = typeof initialValue === "string"
1207
+ ? initialValue
1208
+ : initialValue
1209
+ ? JSON.stringify(assertReceizProofRegisterSnapshot(initialValue))
1210
+ : null;
1211
+ return {
1212
+ read: () => storedText,
1213
+ write: (snapshot) => {
1214
+ storedText = JSON.stringify(assertReceizProofRegisterSnapshot(snapshot));
1215
+ },
1216
+ remove: () => {
1217
+ storedText = null;
1218
+ },
1219
+ readText: () => storedText,
1220
+ };
1221
+ }
1222
+ export function createReceizLocalStorageProofMemoryStorage(key = "receiz:proof-memory:v1", storage = globalThis.localStorage) {
1223
+ if (!storage)
1224
+ throw new Error("receiz_local_storage_unavailable");
1225
+ return {
1226
+ read: () => storage.getItem(key),
1227
+ write: (snapshot) => {
1228
+ storage.setItem(key, JSON.stringify(assertReceizProofRegisterSnapshot(snapshot)));
1229
+ },
1230
+ remove: () => {
1231
+ storage.removeItem(key);
1232
+ },
1233
+ };
1234
+ }
1235
+ export function receizProofMemoryAdditionsQuery(value, limit) {
1236
+ const snapshot = value instanceof ReceizProofRegister
1237
+ ? value.snapshot()
1238
+ : value instanceof ReceizProofMemory
1239
+ ? value.snapshot()
1240
+ : assertReceizProofRegisterSnapshot(value);
1241
+ return {
1242
+ afterEntryId: snapshot.head.entryId,
1243
+ afterKaiUpulse: snapshot.head.kaiUpulse,
1244
+ afterCreatedAt: snapshot.head.createdAt,
1245
+ ...(limit === undefined ? {} : { limit }),
1246
+ };
1247
+ }
871
1248
  function bodyToString(body) {
872
1249
  if (typeof body === "string")
873
1250
  return body;
@@ -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.