sememe 0.0.0-stage → 0.4.21

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 (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2667 -2
  3. package/bin/sememe.mjs +556 -0
  4. package/index.mjs +48 -0
  5. package/interface/ata-ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL.json +62 -0
  6. package/interface/native-11111111111111111111111111111111.json +226 -0
  7. package/interface/native-ComputeBudget111111111111111111111111111111.json +72 -0
  8. package/interface/raydium-675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8.json +352 -0
  9. package/interface/token-TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA.json +334 -0
  10. package/interface/token-TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb.json +535 -0
  11. package/interface/token-metadata.json +132 -0
  12. package/package.json +71 -4
  13. package/src/account-list.mjs +308 -0
  14. package/src/account-union.mjs +523 -0
  15. package/src/account.mjs +199 -0
  16. package/src/acquire.mjs +145 -0
  17. package/src/adopt.mjs +205 -0
  18. package/src/ata-interface.mjs +276 -0
  19. package/src/ata.mjs +223 -0
  20. package/src/base58.mjs +96 -0
  21. package/src/canonical.mjs +24 -0
  22. package/src/cli-surface.mjs +300 -0
  23. package/src/codec.mjs +1069 -0
  24. package/src/conformance.mjs +431 -0
  25. package/src/corpus/harvest-compute-budget-2026-10-31.json +19122 -0
  26. package/src/corpus/harvest-system-2026-10-31.json +26090 -0
  27. package/src/decode.mjs +286 -0
  28. package/src/dialect.mjs +145 -0
  29. package/src/discriminators.mjs +177 -0
  30. package/src/drift.mjs +172 -0
  31. package/src/gap.mjs +205 -0
  32. package/src/lighthouse.mjs +168 -0
  33. package/src/lock.mjs +440 -0
  34. package/src/native-audit.mjs +56 -0
  35. package/src/native-corpus.mjs +57 -0
  36. package/src/native-interface.mjs +252 -0
  37. package/src/native.mjs +324 -0
  38. package/src/peer.mjs +95 -0
  39. package/src/program-id.mjs +74 -0
  40. package/src/pull.mjs +117 -0
  41. package/src/rawle.mjs +450 -0
  42. package/src/raydium-interface.mjs +747 -0
  43. package/src/raydium.mjs +391 -0
  44. package/src/receipt-digest.mjs +84 -0
  45. package/src/receipt-name.mjs +128 -0
  46. package/src/receipt-order.mjs +56 -0
  47. package/src/shank.mjs +73 -0
  48. package/src/short-forms.mjs +209 -0
  49. package/src/store-read.mjs +250 -0
  50. package/src/store-write.mjs +46 -0
  51. package/src/token-interface.mjs +394 -0
  52. package/src/token-metadata-interface.mjs +371 -0
  53. package/src/token-metadata.mjs +317 -0
  54. package/src/token.mjs +443 -0
  55. package/src/union.mjs +428 -0
  56. package/vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d/LICENSE.txt +201 -0
  57. package/vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d/compute-budget-interface/src/lib.rs +93 -0
  58. package/vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d/system-interface/src/instruction.rs +1899 -0
  59. package/vendor/lighthouse/4c579479c98635e419b1b167f08be02a71604a71/LICENSE +21 -0
  60. package/vendor/lighthouse/4c579479c98635e419b1b167f08be02a71604a71/lighthouse.json +2769 -0
  61. package/vendor/raydium-amm/91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6/LICENSE +201 -0
  62. package/vendor/raydium-amm/91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6/program/src/instruction.rs +1189 -0
  63. package/vendor/raydium-amm/91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6/program/src/state.rs +1348 -0
  64. package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/LICENSE +201 -0
  65. package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/interface/src/instruction.rs +169 -0
  66. package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/interface/src/lib.rs +12 -0
  67. package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/program/src/processor.rs +308 -0
  68. package/vendor/spl-token/737788ff5bcc3f1f44cfb222f25483187650be76/LICENSE.txt +176 -0
  69. package/vendor/spl-token/737788ff5bcc3f1f44cfb222f25483187650be76/interface/src/instruction.rs +1963 -0
  70. package/vendor/spl-token-2022/5251dcc07ecbcb721d6c4f0231090defadf00311/LICENSE.txt +176 -0
  71. package/vendor/spl-token-2022/5251dcc07ecbcb721d6c4f0231090defadf00311/interface/src/instruction.rs +2868 -0
  72. package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/LICENSE +201 -0
  73. package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/error.rs +58 -0
  74. package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/instruction.rs +596 -0
  75. package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/lib.rs +21 -0
  76. package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/state.rs +229 -0
@@ -0,0 +1,431 @@
1
+ // Conformance of a WHOLE TRANSACTION against the stored interfaces.
2
+ //
3
+ // `src/account-list.mjs` answers "does this account list match this
4
+ // instruction's declaration". This file answers "what does the stored
5
+ // interface say about every instruction of this transaction", using the
6
+ // same verified store the decoder uses.
7
+ //
8
+ // The transaction shape is the RPC `encoding: "json"` shape (getTransaction
9
+ // or getBlock), because that is what a consumer has. Two facts of that shape
10
+ // matter and are easy to get wrong:
11
+ //
12
+ // - `message.accountKeys` holds only addresses. The SIGNER and WRITABLE
13
+ // flags live in `message.header` as COUNTS, in the canonical order
14
+ // [signers-writable, signers-readonly, non-signers-writable,
15
+ // non-signers-readonly]
16
+ // and the loaded (address-lookup-table) addresses follow, writable
17
+ // first. Reading the flags off the key objects — as many tools do when
18
+ // they use the `jsonParsed` encoding — is a DIFFERENT shape; this file
19
+ // handles the `json` shape and says so when the shape is not one it
20
+ // understands.
21
+ // - an instruction's `accounts` are INDICES into that key list, not
22
+ // addresses. Resolving them wrongly turns a signer question into a
23
+ // no-op. An index that does NOT resolve (past the end, negative, a
24
+ // non-integer, or an object from the `jsonParsed` shape this file does
25
+ // not model) is a shape this gate cannot check — it must REFUSE, never
26
+ // fold the position into a null row (measured 2026-11-11, v0.4.16:
27
+ // null rows downgraded the fixture's real `undeclared-signing-account`
28
+ // disagreement to `remaining-accounts`, exit 0).
29
+ //
30
+ // When the shape cannot be understood (no header, no account keys, an
31
+ // encoding this file does not model), the transaction is REFUSED with a
32
+ // named reason instead of being reported as "conforms". An unknown shape
33
+ // must never be reported as a clean bill of health.
34
+
35
+ import { checkAccountList, instructionByDiscriminator, declaredAccounts, summarizeConformance } from './account-list.mjs';
36
+ import { withDerivedDiscriminators } from './discriminators.mjs';
37
+ import { findStoredIdls } from './store-read.mjs';
38
+ import { isProgramId } from './program-id.mjs';
39
+
40
+ export class ConformanceRefusal extends Error {
41
+ constructor(reason, detail = '') {
42
+ super(`conformance refused: ${reason}${detail ? ` — ${detail}` : ''}`);
43
+ this.name = 'ConformanceRefusal';
44
+ this.reason = reason;
45
+ this.detail = detail;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * The canonical key list of a transaction, with each key's signer and
51
+ * writable flags derived from the message header.
52
+ *
53
+ * @returns {{ keys: Array<{address: string, signer: boolean, writable: boolean}>, byIndex: Array }}
54
+ */
55
+ export function transactionKeys(transaction) {
56
+ const message = transaction?.transaction?.message;
57
+ const meta = transaction?.meta ?? {};
58
+ if (!message || !Array.isArray(message.accountKeys)) {
59
+ throw new ConformanceRefusal('unreadable-transaction', 'no transaction.message.accountKeys');
60
+ }
61
+ const header = message.header;
62
+ if (!header || !Number.isSafeInteger(header.numRequiredSignatures)) {
63
+ throw new ConformanceRefusal('unreadable-transaction', 'no message.header (jsonParsed encoding or a truncated response)');
64
+ }
65
+ const readonlySigned = Number.isSafeInteger(header.numReadonlySignedAccounts) ? header.numReadonlySignedAccounts : 0;
66
+ const readonlyUnsigned = Number.isSafeInteger(header.numReadonlyUnsignedAccounts) ? header.numReadonlyUnsignedAccounts : 0;
67
+
68
+ const addresses = message.accountKeys.map((key) => (typeof key === 'string' ? key : key?.pubkey));
69
+ if (addresses.some((address) => typeof address !== 'string' || !address)) {
70
+ throw new ConformanceRefusal('unreadable-transaction', 'account key without an address');
71
+ }
72
+
73
+ const staticCount = addresses.length;
74
+ const loaded = meta.loadedAddresses ?? {};
75
+ const loadedWritable = Array.isArray(loaded.writable) ? loaded.writable : [];
76
+ const loadedReadonly = Array.isArray(loaded.readonly) ? loaded.readonly : [];
77
+ const all = [...addresses, ...loadedWritable, ...loadedReadonly];
78
+
79
+ const keys = all.map((address, index) => {
80
+ let signer = false;
81
+ let writable = false;
82
+ if (index < staticCount) {
83
+ signer = index < header.numRequiredSignatures;
84
+ const signerWritableCount = header.numRequiredSignatures - readonlySigned;
85
+ if (signer) writable = index < signerWritableCount;
86
+ else writable = index < header.numRequiredSignatures + readonlyUnsigned;
87
+ } else {
88
+ writable = index < staticCount + loadedWritable.length;
89
+ }
90
+ return { address, signer, writable };
91
+ });
92
+
93
+ return { keys, byIndex: keys };
94
+ }
95
+
96
+ /** The program id an instruction invokes, resolved against the key list. */
97
+ export function programIdOf(instruction, keys) {
98
+ if (!instruction) return null;
99
+ if (typeof instruction.programId === 'string') return instruction.programId;
100
+ const index = instruction.programIdIndex;
101
+ if (!Number.isSafeInteger(index) || index < 0 || index >= keys.length) return null;
102
+ return keys[index].address;
103
+ }
104
+
105
+ /** Every instruction of the transaction (top level and CPI), with the
106
+ * account list resolved to addresses + flags. */
107
+ export function transactionInstructions(transaction) {
108
+ const message = transaction?.transaction?.message;
109
+ const meta = transaction?.meta ?? {};
110
+ const { keys } = transactionKeys(transaction);
111
+ const out = [];
112
+
113
+ const push = (instruction, depth, position, parentIndex) => {
114
+ const accounts = Array.isArray(instruction?.accounts) ? instruction.accounts : [];
115
+ // Account references this key list CANNOT resolve: out of range,
116
+ // negative, non-integer (a jsonParsed object entry lands here too), or
117
+ // absent from the loaded range. They are collected per instruction so
118
+ // `checkTransaction` can refuse the shape by name — a null row would
119
+ // let `checkAccountList` file them under "signer unknown" (a note) and
120
+ // the gate would exit 0 over an account list it never checked.
121
+ const unresolvedAccounts = [];
122
+ const resolved = accounts.map((index, positionInInstruction) => {
123
+ if (!Number.isSafeInteger(index) || index < 0 || index >= keys.length) {
124
+ unresolvedAccounts.push({ position: positionInInstruction, value: index });
125
+ return { address: null, signer: null, writable: null };
126
+ }
127
+ const key = keys[index];
128
+ return { address: key.address, signer: key.signer, writable: key.writable };
129
+ });
130
+ out.push({
131
+ depth,
132
+ position,
133
+ parentIndex,
134
+ programId: programIdOf(instruction, keys),
135
+ // The RAW program id reference the message carried, kept so a refusal
136
+ // can name the offending value instead of only its resolved shape
137
+ // (src/conformance.mjs `instruction-program-id-unresolvable`).
138
+ programIdRef: instruction?.programId ?? instruction?.programIdIndex ?? null,
139
+ accounts: resolved,
140
+ accountIndices: accounts.slice(),
141
+ unresolvedAccounts,
142
+ dataBase58: instruction?.data ?? '',
143
+ });
144
+ };
145
+
146
+ (message?.instructions ?? []).forEach((instruction, index) => push(instruction, 0, index, null));
147
+ const groups = meta.innerInstructions ?? message?.innerInstructions ?? [];
148
+ for (const group of groups) {
149
+ const parentIndex = group?.index ?? null;
150
+ (group?.instructions ?? []).forEach((instruction, position) => push(instruction, group?.depth ?? 1, position, parentIndex));
151
+ }
152
+ return { instructions: out, keys };
153
+ }
154
+
155
+ /** The 8 discriminator bytes of an instruction's data, or null. */
156
+ export function discriminatorOf(dataBase58) {
157
+ if (typeof dataBase58 !== 'string' || !dataBase58.length) return null;
158
+ // base58 decode without a third-party import: the alphabet is fixed.
159
+ const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
160
+ let value = 0n;
161
+ for (const character of dataBase58) {
162
+ const digit = ALPHABET.indexOf(character);
163
+ if (digit < 0) return null;
164
+ value = value * 58n + BigInt(digit);
165
+ }
166
+ const bytes = [];
167
+ while (value > 0n) { bytes.unshift(Number(value & 0xffn)); value >>= 8n; }
168
+ for (const character of dataBase58) { if (character === '1') bytes.unshift(0); else break; }
169
+ return bytes.length >= 8 ? bytes.slice(0, 8) : null;
170
+ }
171
+
172
+ /**
173
+ * Check every instruction of a transaction against the interfaces stored for
174
+ * the programs it invokes.
175
+ *
176
+ * WHAT THIS REFUSES (measured 2026-11-09 on the published v0.4.12 install):
177
+ * a report over ZERO instructions used to exit 0 — a green CI gate over a
178
+ * check that never ran. Two ways to get there, both silent:
179
+ *
180
+ * * `--only <program>` where the program is never invoked by this
181
+ * transaction -> `rows: []`, `summary.instructions: 0`, exit 0, no
182
+ * disagreement reported, because there was nothing to disagree about;
183
+ * * `--only <not-a-program-id>` -> the same empty green, and the value
184
+ * could not even be a program id.
185
+ *
186
+ * The rule is the one this file already states for an unknown SHAPE: an
187
+ * empty check is not a clean bill of health. When nothing can be checked,
188
+ * the transaction (or the filter) is REFUSED with a named reason that
189
+ * carries the repair — for a filter miss, the program ids the transaction
190
+ * actually invokes, so a typo is visible instead of green.
191
+ *
192
+ * @param {object} transaction an RPC `encoding: "json"` transaction
193
+ * @param {string} storeDir a sememe store directory
194
+ * @param {object} [options]
195
+ * @param {string[]} [options.only] restrict to these program ids
196
+ * @returns {Promise<{ rows: object[], summary: object }>}
197
+ * @throws {ConformanceRefusal} when the check would cover no instruction
198
+ */
199
+ export async function checkTransaction(transaction, storeDir, options = {}) {
200
+ const { only = null } = options;
201
+ const { instructions } = transactionInstructions(transaction);
202
+ const executed = transaction?.meta?.err === null || transaction?.meta?.err === undefined
203
+ ? true
204
+ : false;
205
+
206
+ // Decide what CAN be checked before reading the store: an empty check is a
207
+ // refusal, and the refusal must not depend on which interfaces happen to be
208
+ // stored (that would make the verdict about the store, not about the filter).
209
+ if (only !== null) {
210
+ for (const entry of only) {
211
+ if (!isProgramId(entry)) {
212
+ throw new ConformanceRefusal(
213
+ 'filter-is-not-a-program-id',
214
+ `${JSON.stringify(String(entry))} — a program id is 32 bytes in base58; ` + 'this filter can never match an instruction',
215
+ );
216
+ }
217
+ }
218
+ }
219
+ const invoked = [...new Set(instructions.map((i) => i.programId).filter(Boolean))];
220
+ const unresolvable = instructions
221
+ .map((instruction, index) => ({ instruction, index }))
222
+ .filter(({ instruction }) => !instruction.programId);
223
+
224
+ // An instruction whose program id CANNOT be resolved is not a hole in the
225
+ // report — it is a shape this gate cannot check, and a partial check must
226
+ // never be reported as a check of the transaction it was handed.
227
+ //
228
+ // MEASURED 2026-11-11 (this host, pre-fix tree 94aa00b = the published
229
+ // v0.4.15, order-engine fixture): the loop below used to `continue` over an
230
+ // instruction with an unresolvable programId — no row, no count, no
231
+ // refusal. With the fill instruction's programIdIndex pushed past the end
232
+ // of accountKeys, the gate reported 11 of 12 instructions,
233
+ // `byVerdict {no-stored-interface: 11}`, exit 0 — while the SAME
234
+ // transaction with an intact index exits 1 with
235
+ // `undeclared-signing-account`. The transaction's REAL disagreement was not
236
+ // just unchecked, it was HIDDEN behind a report that looked complete.
237
+ //
238
+ // The order of the refusals matters:
239
+ // * ALL instructions unresolvable keeps its EXISTING reason
240
+ // (`transaction-has-no-resolvable-program-id`) — that refusal already
241
+ // existed and must not be rewritten;
242
+ // * SOME unresolvable is the middle case that was silent, and it is
243
+ // refused here — BEFORE the filter selection, because a filter that
244
+ // selects nothing (`filter-matched-no-instruction`) must not mask a
245
+ // transaction whose shape is broken, and a broken shape must not be
246
+ // reported as "this filter matched nothing";
247
+ // * a filter that is not a program id is still refused first (above) —
248
+ // that is about the CALLER's argument, not about the transaction.
249
+ if (unresolvable.length && unresolvable.length === instructions.length) {
250
+ throw new ConformanceRefusal(
251
+ 'transaction-has-no-resolvable-program-id',
252
+ `all ${instructions.length} instruction(s) lack a program id that resolves inside message.accountKeys — `
253
+ + 'this shape cannot be checked',
254
+ );
255
+ }
256
+ if (unresolvable.length) {
257
+ const described = unresolvable
258
+ .map(({ instruction, index }) => {
259
+ const raw = instruction.programIdRef;
260
+ const shown = raw === undefined || raw === null ? '(none)' : JSON.stringify(raw);
261
+ const where = instruction.depth
262
+ ? `CPI of top-level #${instruction.parentIndex} at position ${instruction.position}`
263
+ : `#${instruction.position ?? index}`;
264
+ return `${where} (programIdIndex ${shown})`;
265
+ })
266
+ .join(', ');
267
+ throw new ConformanceRefusal(
268
+ 'instruction-program-id-unresolvable',
269
+ `${unresolvable.length} of ${instructions.length} instruction(s) have no program id that resolves inside `
270
+ + `message.accountKeys (${described}) — the gate cannot report a transaction it can only check in part; `
271
+ + 'repair: fix programIdIndex (or set programId) on the named instruction(s)',
272
+ );
273
+ }
274
+
275
+ // An ACCOUNT reference the key list cannot resolve is the same defect one
276
+ // level over (the program-id sibling was refused 2026-11-11, v0.4.16).
277
+ //
278
+ // MEASURED 2026-11-11 (this host, pre-fix tree 7b30c68 = the published
279
+ // v0.4.16, order-engine fixture): resolving `keys[index]` past the end
280
+ // returned `{address: null, signer: null, writable: null}`, and
281
+ // `checkAccountList` files a null signer under `unknownSigner` — a NOTE,
282
+ // never a violation. Breaking the ONE signing extra account's index
283
+ // downgraded the fill from `undeclared-signing-account` (exit 1) to
284
+ // `remaining-accounts` (EXIT 0): the transaction's only real disagreement
285
+ // hidden behind a report that looked complete. An object entry (the
286
+ // `jsonParsed` shape this file's header says it does not model) did the
287
+ // same for the WHOLE transaction.
288
+ //
289
+ // The refusal runs BEFORE the store is read and BEFORE the filter
290
+ // selection: it is about the transaction's shape, not about which
291
+ // interface the store happens to hold, and a broken shape must never be
292
+ // reported as "this filter matched nothing".
293
+ const unresolvableAccounts = instructions
294
+ .map((instruction, index) => ({ instruction, index }))
295
+ .filter(({ instruction }) => instruction.unresolvedAccounts.length > 0);
296
+ if (unresolvableAccounts.length) {
297
+ const described = unresolvableAccounts.map(({ instruction, index }) => {
298
+ const where = instruction.depth
299
+ ? `CPI of top-level #${instruction.parentIndex} at position ${instruction.position}`
300
+ : `#${instruction.position ?? index}`;
301
+ const values = instruction.unresolvedAccounts
302
+ .map((entry) => `account position ${entry.position} (value ${JSON.stringify(entry.value)})`)
303
+ .join(', ');
304
+ return `${where} — ${values}`;
305
+ }).join('; ');
306
+ throw new ConformanceRefusal(
307
+ 'instruction-account-index-unresolvable',
308
+ `${unresolvableAccounts.length} of ${instructions.length} instruction(s) carry account indices that do not `
309
+ + `resolve inside message.accountKeys + meta.loadedAddresses (${described}) — the gate cannot report a `
310
+ + 'transaction whose account list it can only check in part; repair: fix the named account index '
311
+ + '(indices are integers into the canonical key list, loaded addresses included)',
312
+ );
313
+ }
314
+
315
+ const eligible = instructions.filter((i) => i.programId && (!only || only.includes(i.programId)));
316
+ if (eligible.length === 0) {
317
+ if (!instructions.length) {
318
+ throw new ConformanceRefusal(
319
+ 'transaction-has-no-instruction',
320
+ 'the transaction exposes no instruction to check — nothing here can be reported as conforming',
321
+ );
322
+ }
323
+ if (!invoked.length) {
324
+ throw new ConformanceRefusal(
325
+ 'transaction-has-no-resolvable-program-id',
326
+ `all ${instructions.length} instruction(s) lack a program id that resolves inside message.accountKeys — `
327
+ + 'this shape cannot be checked',
328
+ );
329
+ }
330
+ if (only) {
331
+ throw new ConformanceRefusal(
332
+ 'filter-matched-no-instruction',
333
+ `--only ${JSON.stringify(only)} selects none of this transaction's instructions; it invokes: ${invoked.join(', ')}`,
334
+ );
335
+ }
336
+ throw new ConformanceRefusal('no-instruction-is-checkable', `the transaction invokes: ${invoked.join(', ')}`);
337
+ }
338
+
339
+ const rows = [];
340
+ const idlCache = new Map();
341
+ for (const instruction of instructions) {
342
+ if (only && !only.includes(instruction.programId)) continue;
343
+ let idl = idlCache.get(instruction.programId);
344
+ if (idl === undefined) {
345
+ try {
346
+ const sources = await findStoredIdls(storeDir, instruction.programId);
347
+ idl = sources.primary?.idl ?? sources.secondary?.idl ?? null;
348
+ } catch (error) {
349
+ // THE READER'S REFUSAL IS NOT A VERDICT ABOUT THE TRANSACTION.
350
+ //
351
+ // findStoredIdls is the ONE store reader that refuses damage by name
352
+ // (src/store-read.mjs): a receipt whose object no longer re-hashes to
353
+ // its fingerprint, a receipt bound to another program, a missing
354
+ // object, a malformed receipt, a rewritten origin. Swallowing that
355
+ // here turned every refusal into `no-stored-interface` and the gate
356
+ // exited 0 — measured 2026-11-10 on the order-engine fixture: a
357
+ // tampered receipt made the transaction's REAL disagreement
358
+ // (undeclared-signing-account) disappear behind 12
359
+ // `no-stored-interface` rows and a green exit.
360
+ //
361
+ // Exactly ONE failure is not damage: the store holds no receipt for
362
+ // this program (code `no-stored-idl`). That is an honest acquisition
363
+ // gap and keeps its own verdict below. A missing store
364
+ // (`no-sememe-store`) and every reader refusal are REFUSED by name,
365
+ // carrying the reader's own message and the repair.
366
+ const code = error?.code ?? null;
367
+ if (code !== 'no-stored-idl') {
368
+ const reason = code === 'no-sememe-store' ? 'no-sememe-store' : 'store-reader-refused';
369
+ throw new ConformanceRefusal(
370
+ reason,
371
+ `${instruction.programId}: ${String(error?.message ?? error)} — ` +
372
+ 'the conformance gate cannot read this program from the store, so it cannot say the transaction conforms; ' +
373
+ 'repair: restore the receipt (re-run "sememe pull") or point --store at a verified store',
374
+ );
375
+ }
376
+ idl = null;
377
+ }
378
+ idlCache.set(instruction.programId, idl);
379
+ }
380
+ const discriminator = discriminatorOf(instruction.dataBase58);
381
+ if (!idl) {
382
+ rows.push({
383
+ program: instruction.programId,
384
+ instruction: null,
385
+ executed,
386
+ depth: instruction.depth,
387
+ verdict: 'no-stored-interface',
388
+ violations: [],
389
+ extras: [],
390
+ notes: [],
391
+ unknownSigner: [],
392
+ positions: { declared: null, observed: instruction.accounts.length },
393
+ });
394
+ continue;
395
+ }
396
+ if (!discriminator) {
397
+ rows.push({
398
+ program: instruction.programId,
399
+ instruction: null,
400
+ executed,
401
+ depth: instruction.depth,
402
+ verdict: 'unknown-discriminator',
403
+ violations: [],
404
+ extras: [],
405
+ notes: [],
406
+ unknownSigner: [],
407
+ positions: { declared: null, observed: instruction.accounts.length },
408
+ });
409
+ continue;
410
+ }
411
+ const { idl: filled } = withDerivedDiscriminators(idl);
412
+ const match = instructionByDiscriminator(filled, discriminator);
413
+ const result = checkAccountList(match, instruction.accounts, {
414
+ program: instruction.programId,
415
+ programId: instruction.programId,
416
+ executed,
417
+ depth: instruction.depth,
418
+ });
419
+ rows.push({
420
+ ...result,
421
+ depth: instruction.depth,
422
+ accountCount: instruction.accounts.length,
423
+ declaredAccountCount: match ? declaredAccounts(match).length : null,
424
+ instructionNames: match ? match.name : null,
425
+ });
426
+ }
427
+
428
+ const summary = summarizeConformance(rows);
429
+
430
+ return { rows, summary };
431
+ }