sememe 0.0.0-stage → 0.4.22

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 +2777 -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 +206 -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 +1118 -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 +180 -0
  29. package/src/discriminators.mjs +192 -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 +454 -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 +253 -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
package/src/lock.mjs ADDED
@@ -0,0 +1,440 @@
1
+ // sememe.lock — the fingerprint lockfile the README's contract promised and
2
+ // the tree never shipped.
3
+ //
4
+ // What this module is NOT: not distribution, not an update path, not a second
5
+ // acquisition channel. It records what a store ANSWERED with, so a project
6
+ // can commit one document that says "my decode calls answered with these
7
+ // fingerprints" and have CI refuse silent drift. The store reader
8
+ // (src/store-read.mjs) already refuses a tampered object on every read; the
9
+ // lock adds the cross-time check no single read can make: the store's answer
10
+ // is still THE answer it was pinned at.
11
+ //
12
+ // The document is canonical JSON with a trailing newline (the same
13
+ // serialization `pull`/`adopt` fingerprint their objects with), so diffs are
14
+ // stable and the file is reproducible from the store alone. Refusals are
15
+ // named, never guessed around:
16
+ // lock-v1-parse the file is not JSON
17
+ // lock-v1-shape the document is not a v1 lock (programs map etc.)
18
+ // lock-v1-fingerprint a pinned fingerprint is not sha256:<64 hex>
19
+ // lock-pins-nothing the lock pins ZERO programs, so it can never detect
20
+ // drift — a green gate over a check that never ran
21
+ // (measured 2026-11-09: `sememe lock` on a store with
22
+ // no receipts wrote `{"lockVersion":1,"programs":{}}`
23
+ // and exited 0, and `sememe verify --mode error` over
24
+ // that lock exited 0 with `pinned: 0, violations: 0`
25
+ // against a real 18-program store)
26
+ // Violations checkLock reports (each with the program named):
27
+ // drifted the store answers the program with a DIFFERENT fingerprint
28
+ // missing the store has no verified receipt for a pinned program
29
+ // unverified an object fails the store reader's own re-hash check. The
30
+ // entry carries a `scope`: `pinned` (the lock names this
31
+ // program) or `extra` (the lock does NOT — the store gained the
32
+ // program later, and its receipt is damaged). The scope is part
33
+ // of the repair, so the two are never reported alike.
34
+ // relabelled a pinned or stored DECLARED origin changed after the fact —
35
+ // the receipt says "declared" about bytes whose label no longer
36
+ // matches what was pinned (see src/receipt-digest.mjs)
37
+ // unbound the store declares an origin the lock does NOT pin, so the
38
+ // gate cannot say whether that label changed. A lock written
39
+ // before origin pinning (every existing user's committed lock)
40
+ // pins no origin; a legacy receipt carries no self-digest, so
41
+ // nothing covers the label at all. Reported instead of passing,
42
+ // because "verified" over a claim that was never checked is the
43
+ // green lie this gate exists to prevent. Repair: `sememe lock`.
44
+ // Extra store programs are reported, never a violation: the lock pins
45
+ // minimums, not store equality — a pull that ADDS a program must not fail
46
+ // someone else's CI. writeLock refuses to write a lock over a store it cannot
47
+ // fully verify: every receipt is re-checked through the store reader first,
48
+ // so a lock is a snapshot of VERIFIED answers, never of filenames. The lock
49
+ // pins each program's DECLARED origin next to its fingerprint: the fingerprint
50
+ // binds the bytes, the origin pins the label the store answered with, and
51
+ // neither may change silently.
52
+
53
+ import { writeFile, readFile, readdir } from 'node:fs/promises';
54
+ import path from 'node:path';
55
+ import { canonicalJson } from './canonical.mjs';
56
+ import { readVerified } from './store-read.mjs';
57
+ import { compareReceiptNames } from './receipt-order.mjs';
58
+ import { classifyReceiptFiles, isReceiptFile } from './receipt-name.mjs';
59
+
60
+ const LOCK_VERSION = 1;
61
+ const HEX64 = /^[a-f0-9]{64}$/;
62
+
63
+ /** The program a receipt filename belongs to. Base58 program ids contain no
64
+ * `-`, so the first dash-terminated run is the program; the source label may
65
+ * contain dashes, the fingerprint never does (it is the last segment). */
66
+ function programOfReceiptName(name) {
67
+ return name.split('-')[0];
68
+ }
69
+
70
+ /** Newest-selection rule, identical to `findStoredIdl`: sort by the ONE
71
+ * shared ordering (numeric slot, filename tiebreak), take last. A plain
72
+ * string sort here would pin "99999999" over "123456789" — two answers from
73
+ * one store, measured 2026-11-07 (test/slot-order-store.test.mjs O2). */
74
+ async function newestReceipt(storeDir, program) {
75
+ const receipts = path.join(storeDir, 'receipts');
76
+ const names = (await readdir(receipts)).filter((name) => name.startsWith(`${program}-`) && isReceiptFile(name));
77
+ if (!names.length) return null;
78
+ names.sort(compareReceiptNames);
79
+ return readVerified(storeDir, program, names[names.length - 1]);
80
+ }
81
+
82
+ /** Every receipt of `program`, re-checked through the store reader.
83
+ *
84
+ * Measured 2026-11-07 (this host): a store may hold SEVERAL receipts for one
85
+ * program — a pull plus a published copy, or two published copies. `writeLock`
86
+ * re-checked only the NEWEST of them, so a DAMAGED second copy was skipped in
87
+ * silence: `sememe lock` exited 0 and `sememe verify` reported 0 violations
88
+ * over a store whose union decode refused to decode. The README's claim
89
+ * ("every receipt is re-checked through the store reader first, so a store the
90
+ * reader refuses produces NO lock, never a lock over damage") was therefore
91
+ * true of one receipt out of several.
92
+ *
93
+ * Every receipt is checked here, so a lock is a snapshot of VERIFIED answers
94
+ * over the WHOLE store, not of the receipt that happened to sort last.
95
+ *
96
+ * ONE helper for BOTH lock paths on purpose: `writeLock` (refuse to write) and
97
+ * `checkLock` (name the damage) must agree about what a clean store is. Two
98
+ * separate loops is how the store ended up "locked clean" and "verified
99
+ * damaged" in the same run. */
100
+ async function verifyEveryReceipt(storeDir, program, names) {
101
+ for (const name of names) {
102
+ try {
103
+ await readVerified(storeDir, program, name);
104
+ } catch (error) {
105
+ // The failure KIND decides which bucket checkLock reports it in: a
106
+ // rewritten declared origin is a relabel, not an unreadable object.
107
+ return {
108
+ ok: false,
109
+ receipt: name,
110
+ reason: String(error?.message ?? error).slice(0, 300),
111
+ code: error?.code ?? null,
112
+ };
113
+ }
114
+ }
115
+ return { ok: true, receipt: null, reason: null, code: null };
116
+ }
117
+
118
+ /** Snapshot every verified receipt of `storeDir` into a lock document.
119
+ * Returns the document; `outPath` receives exactly its canonical bytes. */
120
+ export async function writeLock(storeDir, outPath) {
121
+ const receipts = path.join(storeDir, 'receipts');
122
+ let names;
123
+ try { names = await readdir(receipts); }
124
+ catch (error) {
125
+ if (error?.code === 'ENOENT') {
126
+ throw Error(`no sememe store at ${storeDir}: run "sememe pull" first`);
127
+ }
128
+ throw error;
129
+ }
130
+ const programs = [...new Set(names.filter(isReceiptFile).map(programOfReceiptName))].sort();
131
+ // A lock that pins NOTHING is not a lock: it can never detect a drifted
132
+ // fingerprint, a missing receipt or a rewritten origin. Measured
133
+ // 2026-11-09: `sememe lock` over a store whose receipts/ directory holds
134
+ // no receipt wrote `{"lockVersion":1,"programs":{}}` and exited 0 — and
135
+ // `sememe verify --mode error` over that lock exited 0 with
136
+ // `pinned: 0, violations: 0` against a REAL 18-program store, reporting
137
+ // every program as an `extra` (extras are never violations). The gate was
138
+ // green over a check that never ran, and it LOOKED informative. So a lock
139
+ // over an empty store is REFUSED and no file is written: the repair is to
140
+ // pull a program first, not to commit a lock that certifies nothing.
141
+ if (programs.length === 0) {
142
+ throw Error(
143
+ `the store at ${storeDir} pins nothing: it holds no receipt, so a lock over it could never detect drift `
144
+ + `(lock-pins-nothing) — run "sememe pull <program-id> --store ${storeDir}" first, then lock again`,
145
+ );
146
+ }
147
+ const doc = { lockVersion: LOCK_VERSION, programs: {} };
148
+ for (const program of programs) {
149
+ // Full verification through the store reader: a tampered object or a
150
+ // dangling receipt must fail the LOCK, not silently enter it — and that
151
+ // holds for EVERY receipt of the program, not only the newest one.
152
+ const candidates = names.filter((name) => name.startsWith(`${program}-`) && isReceiptFile(name));
153
+ const verdict = await verifyEveryReceipt(storeDir, program, candidates);
154
+ if (!verdict.ok) {
155
+ // Refuse the lock: a store the reader refuses must produce NO lock.
156
+ throw Error(verdict.reason);
157
+ }
158
+ const { idl, receipt, receiptName } = await newestReceipt(storeDir, program);
159
+ doc.programs[program] = {
160
+ fingerprint: receipt.fingerprint,
161
+ source: receipt.source ?? 'unknown',
162
+ slot: receipt.slot ?? null,
163
+ receipt: receiptName,
164
+ instructions: Array.isArray(idl.instructions) ? idl.instructions.length : 0,
165
+ // The DECLARED origin, pinned beside the fingerprint. Absent for an
166
+ // on-chain receipt (it names no file) and for a legacy adopted receipt
167
+ // without one; `null` never appears — a pinned origin is a string.
168
+ ...(receipt.origin === undefined || receipt.origin === null ? {} : {
169
+ origin: receipt.origin,
170
+ originStatus: receipt.originStatus ?? 'declared',
171
+ }),
172
+ };
173
+ }
174
+ const bytes = Buffer.from(`${canonicalJson(doc)}\n`);
175
+ if (outPath !== undefined && outPath !== null) {
176
+ await writeFile(outPath, bytes);
177
+ }
178
+ return doc;
179
+ }
180
+
181
+ /** Parse and shape-check a lock document. Every failure names its reason. */
182
+ export async function readLock(lockPath) {
183
+ let raw;
184
+ try { raw = await readFile(lockPath, 'utf8'); }
185
+ catch (error) {
186
+ throw Error(`sememe lock file ${lockPath} is unreadable: ${String(error?.message ?? error).slice(0, 140)}`);
187
+ }
188
+ let doc;
189
+ try { doc = JSON.parse(raw); }
190
+ catch {
191
+ throw Error(`sememe lock file ${lockPath} is not JSON (lock-v1-parse)`);
192
+ }
193
+ if (!doc || typeof doc !== 'object' || Array.isArray(doc)) {
194
+ throw Error(`sememe lock file ${lockPath} is not a lock document (lock-v1-shape)`);
195
+ }
196
+ if (doc.lockVersion !== LOCK_VERSION) {
197
+ throw Error(`sememe lock file ${lockPath} has unsupported lockVersion ${JSON.stringify(doc.lockVersion)} — expected ${LOCK_VERSION} (lock-v1-shape)`);
198
+ }
199
+ if (!doc.programs || typeof doc.programs !== 'object' || Array.isArray(doc.programs)) {
200
+ throw Error(`sememe lock file ${lockPath} has no programs map (lock-v1-shape)`);
201
+ }
202
+ // A lock that pins ZERO programs certifies nothing: there is no pinned
203
+ // answer a drifted store could contradict, so every store would pass it.
204
+ // Measured 2026-11-09 (this host, published v0.4.13 AND main): an empty
205
+ // `programs` map made `sememe verify --mode error` exit 0 with
206
+ // `pinned: 0, violations: 0` against a real 18-program store — a green CI
207
+ // gate over a check that never ran. The refusal is about the DOCUMENT, not
208
+ // about the store, so it applies in `--mode warn` too: warn mode exists to
209
+ // downgrade STORE violations, not to certify a lock that pins nothing.
210
+ if (Object.keys(doc.programs).length === 0) {
211
+ throw Error(
212
+ `sememe lock file ${lockPath} pins no program (lock-pins-nothing): a lock over zero answers can never `
213
+ + `detect drift — re-create it with "sememe lock" over a store that holds at least one receipt`,
214
+ );
215
+ }
216
+ for (const [program, entry] of Object.entries(doc.programs)) {
217
+ if (!entry || typeof entry !== 'object') {
218
+ throw Error(`sememe lock file ${lockPath} pins ${program} with a non-object entry (lock-v1-shape)`);
219
+ }
220
+ const fingerprint = typeof entry.fingerprint === 'string' ? entry.fingerprint.replace(/^sha256:/, '') : '';
221
+ if (!HEX64.test(fingerprint)) {
222
+ throw Error(`sememe lock file ${lockPath} pins ${program} with a malformed fingerprint ${JSON.stringify(entry.fingerprint)} (lock-v1-fingerprint)`);
223
+ }
224
+ if (entry.origin !== undefined && typeof entry.origin !== 'string') {
225
+ throw Error(`sememe lock file ${lockPath} pins ${program} with a malformed origin ${JSON.stringify(entry.origin)} (lock-v1-origin)`);
226
+ }
227
+ }
228
+ return doc;
229
+ }
230
+
231
+ /** Re-check a store against a lock document. Returns the full report.
232
+ * `mode: 'error'` (the CI-gate default) throws after the report is complete
233
+ * when any violation exists; `mode: 'warn'` never throws on violations. */
234
+ export async function checkLock(storeDir, lockPath, { mode = 'error' } = {}) {
235
+ const doc = await readLock(lockPath);
236
+ const pinnedPrograms = Object.keys(doc.programs).sort();
237
+
238
+ let receiptNames = [];
239
+ try { receiptNames = (await readdir(path.join(storeDir, 'receipts'))); }
240
+ catch (error) {
241
+ if (error?.code === 'ENOENT') {
242
+ throw Error(`no sememe store at ${storeDir}: run "sememe pull" first`);
243
+ }
244
+ throw error;
245
+ }
246
+ // The same ONE ordering the writer and both readers use, so "newest" means
247
+ // the same receipt on every side of a lock comparison.
248
+ // Stray files (sememe's own uncommitted .tmp- debris from a crash, or a
249
+ // file that carries no slot segment) are partitioned off: they are not
250
+ // receipts and reading them as receipts would fail the gate over a clean
251
+ // store (measured 2026-11-07). Malformed receipts (a slot segment but bad
252
+ // hex/suffix) stay in `receiptNames` and are refused by name — they are
253
+ // damage to the store, not uncommitted writes.
254
+ const { receipts: validReceipts, stray } = classifyReceiptFiles(receiptNames);
255
+ validReceipts.sort(compareReceiptNames);
256
+ const storePrograms = [...new Set(validReceipts.map(programOfReceiptName))].sort();
257
+
258
+ const report = {
259
+ lock: path.resolve(lockPath),
260
+ store: path.resolve(storeDir),
261
+ mode,
262
+ pinned: pinnedPrograms.length,
263
+ verified: [],
264
+ drifted: [],
265
+ missing: [],
266
+ unverified: [],
267
+ // A declared origin that is no longer the one that was pinned, or a
268
+ // receipt whose own declared label no longer matches its digest.
269
+ relabelled: [],
270
+ // A declared origin the lock does not pin at all: the gate cannot
271
+ // certify a label it never recorded.
272
+ unbound: [],
273
+ extra: storePrograms.filter((program) => !doc.programs[program]),
274
+ // Non-receipt files found in receipts/ (sememe's own .tmp- crash debris,
275
+ // foreign files): reported by name and kind, never a violation — an
276
+ // uncommitted write is safe to ignore.
277
+ stray,
278
+ };
279
+
280
+ for (const program of pinnedPrograms) {
281
+ const pinnedFingerprint = doc.programs[program].fingerprint.replace(/^sha256:/, '');
282
+ const candidates = validReceipts.filter((name) => name.startsWith(`${program}-`));
283
+ if (!candidates.length) {
284
+ report.missing.push(program);
285
+ continue;
286
+ }
287
+ const newest = candidates[candidates.length - 1];
288
+ // Every receipt of this program is re-checked, not only the newest: a
289
+ // damaged SECOND copy is a damaged store, and reporting it as clean is the
290
+ // green lie this gate exists to prevent. The SAME helper the writer uses,
291
+ // so "lock" and "verify" cannot disagree about the store.
292
+ const verdict = await verifyEveryReceipt(storeDir, program, candidates);
293
+ if (!verdict.ok) {
294
+ if (verdict.code === 'receipt-relabelled') {
295
+ // The declared origin in the receipt no longer matches its own digest:
296
+ // the label was rewritten after the receipt was written. Reported as
297
+ // its own violation kind so a caller can see that the BYTES are fine
298
+ // and the PROVENANCE claim is not.
299
+ report.relabelled.push({ program, receipt: verdict.receipt, reason: verdict.reason, scope: 'receipt' });
300
+ } else {
301
+ report.unverified.push({ program, receipt: verdict.receipt, reason: verdict.reason });
302
+ }
303
+ continue;
304
+ }
305
+ let source;
306
+ try {
307
+ source = await readVerified(storeDir, program, newest);
308
+ } catch (error) {
309
+ if (error?.code === 'receipt-relabelled') {
310
+ report.relabelled.push({
311
+ program,
312
+ receipt: newest,
313
+ reason: String(error?.message ?? error).slice(0, 300),
314
+ scope: 'receipt',
315
+ });
316
+ } else {
317
+ report.unverified.push({ program, receipt: newest, reason: String(error?.message ?? error).slice(0, 200) });
318
+ }
319
+ continue;
320
+ }
321
+ const storeFingerprint = source.receipt.fingerprint.replace(/^sha256:/, '');
322
+ if (storeFingerprint !== pinnedFingerprint) {
323
+ report.drifted.push({
324
+ program,
325
+ pinned: `sha256:${pinnedFingerprint}`,
326
+ store: `sha256:${storeFingerprint}`,
327
+ storeReceipt: newest,
328
+ });
329
+ continue;
330
+ }
331
+ // Fingerprint matches: now the DECLARED ORIGIN must still be the pinned
332
+ // one. The receipt's own digests catch an edited receipt; this catches a
333
+ // receipt that was rewritten AND re-digested after the lock was written —
334
+ // the layer a self-digest alone cannot cover, because the attacker holds
335
+ // the same hashing function.
336
+ const pinnedOrigin = doc.programs[program].origin;
337
+ const storeOrigin = source.receipt.origin ?? null;
338
+ if (typeof pinnedOrigin === 'string') {
339
+ if (storeOrigin !== pinnedOrigin) {
340
+ report.relabelled.push({
341
+ program,
342
+ receipt: newest,
343
+ pinned: pinnedOrigin,
344
+ store: storeOrigin,
345
+ reason: `the store no longer declares origin ${JSON.stringify(pinnedOrigin)} for ${program} — the store answers ${storeOrigin === null ? 'no origin' : JSON.stringify(storeOrigin)}`,
346
+ scope: 'lock',
347
+ });
348
+ continue;
349
+ }
350
+ } else if (storeOrigin !== null) {
351
+ // The store answers with a DECLARED origin this lock does not pin. For
352
+ // a legacy receipt there is no self-digest either, so this label is
353
+ // covered by nothing: a rewrite of it would look exactly like the
354
+ // untouched case. Say so, and name the one command that binds it —
355
+ // writeLock already pins `receipt.origin`, so the repair really works.
356
+ report.unbound.push({
357
+ program,
358
+ receipt: newest,
359
+ store: storeOrigin,
360
+ reason: `the store declares origin ${JSON.stringify(storeOrigin)} for ${program}, but sememe.lock does not pin it — this lock predates origin pinning, so a rewritten origin here cannot be detected; re-run "sememe lock" to bind it`,
361
+ scope: 'lock',
362
+ });
363
+ continue;
364
+ }
365
+ report.verified.push(program);
366
+ }
367
+
368
+ // EXTRA programs are re-checked too. The lock pins MINIMUMS, so a program
369
+ // the store gained is still only reported (presence is never a violation —
370
+ // that contract is G1 in test/extra-program-damage.test.mjs and must not
371
+ // move). But DAMAGE is not presence: measured 2026-11-07 on this tree
372
+ // (pre-fix 61df87b), a store whose LATER-PULLED extra program carried an
373
+ // object that no longer re-hashed to its receipt gave
374
+ //
375
+ // sememe lock -> EXIT 1, no lock written (writeLock re-checks extras)
376
+ // sememe verify -> EXIT 0, violations 0 (checkLock never read them)
377
+ //
378
+ // Two commands, two verdicts about ONE store, and a green CI gate over a
379
+ // program the store reader refuses: `sememe decode` on that same program
380
+ // exits 1 with "object and receipt disagree, refusing to decode". The
381
+ // shared helper existed; only the PINNED loop ever called it — the same
382
+ // "one store, two strengths" class this repo killed for the newest-receipt
383
+ // path on 2026-11-07, one level down.
384
+ //
385
+ // So extras run through the SAME helper, and only their FAILURES are
386
+ // reported (as `unverified`/`relabelled` with scope `extra`, so pin drift is
387
+ // not misread as the cause). What is NOT checked for an extra: binding it
388
+ // against the lock's origin pin — an unpinned program has no pin to bind
389
+ // to, and demanding one would turn "someone pulled a program" into a CI
390
+ // failure, which is exactly the equality rule the lock rejects.
391
+ for (const program of report.extra) {
392
+ const candidates = validReceipts.filter((name) => name.startsWith(`${program}-`));
393
+ if (!candidates.length) continue; // no receipt left to check
394
+ const verdict = await verifyEveryReceipt(storeDir, program, candidates);
395
+ if (verdict.ok) continue;
396
+ const entry = {
397
+ program,
398
+ receipt: verdict.receipt,
399
+ reason: verdict.reason,
400
+ scope: 'extra',
401
+ };
402
+ if (verdict.code === 'receipt-relabelled') {
403
+ report.relabelled.push({ ...entry, scope: 'extra' });
404
+ } else {
405
+ report.unverified.push(entry);
406
+ }
407
+ }
408
+
409
+ const violations = report.drifted.length + report.missing.length
410
+ + report.unverified.length + report.relabelled.length + report.unbound.length;
411
+ report.violations = violations;
412
+ if (mode === 'error' && violations > 0) {
413
+ const parts = [];
414
+ if (report.drifted.length) {
415
+ parts.push(`drifted: ${report.drifted.map((d) => `${d.program} pinned ${d.pinned} vs store ${d.store}`).join('; ')}`);
416
+ }
417
+ if (report.missing.length) parts.push(`missing: ${report.missing.join(', ')}`);
418
+ if (report.unverified.length) {
419
+ // The scope is part of the repair: `extra` means the LOCK is fine and
420
+ // the STORE holds a damaged receipt for a program the lock never pinned
421
+ // (re-pull or drop that receipt) — while a pinned name means the pin no
422
+ // longer describes the store (re-run `sememe lock`). One list, two very
423
+ // different repairs; the refusal must not make them look alike.
424
+ // For an extra, the RECEIPT is named too: it is the file in the store
425
+ // that has to be re-pulled, and its name carries the fingerprint the
426
+ // receipt claims — the number a reader needs to look the damage up.
427
+ parts.push(`unverified: ${report.unverified.map((u) => (u.scope === 'extra'
428
+ ? `${u.program} (extra, receipt ${u.receipt})`
429
+ : u.program)).join(', ')}`);
430
+ }
431
+ if (report.relabelled.length) {
432
+ parts.push(`relabelled (declared origin changed): ${report.relabelled.map((r) => (r.scope === 'extra' ? `${r.program} (extra)` : r.program)).join(', ')}`);
433
+ }
434
+ if (report.unbound.length) {
435
+ parts.push(`unbound (declared origin not pinned by the lock): ${report.unbound.map((r) => r.program).join(', ')} — re-run "sememe lock"`);
436
+ }
437
+ throw Error(`store does not match sememe.lock (${report.drifted.length} drifted, ${report.missing.length} missing, ${report.unverified.length} unverified, ${report.relabelled.length} relabelled, ${report.unbound.length} unbound) — ${parts.join(' | ')}`);
438
+ }
439
+ return report;
440
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * native-audit.mjs — THE DERIVATION AUDIT, callable by a consumer.
3
+ *
4
+ * tools/native-interface.mjs --check answers "is the committed interface artifact exactly
5
+ * what the vendored upstream Rust derives to today?" — but as a subprocess in sememe's own
6
+ * checkout. A consumer that installed the package has no checkout and no tools/ directory
7
+ * (deliberately: the qualification harness does not ship), so this module carries the same
8
+ * verdict as an import: re-derive both pinned native interfaces from vendor/ and compare
9
+ * against the shipped interface/ artifacts byte-for-byte.
10
+ *
11
+ * This is the same audit sememe's own suite runs (test/native-interface.test.mjs), moved to
12
+ * a package surface. It exists so a consumer can falsify the table it was handed instead of
13
+ * trusting it: the verdict is derived in the consumer's process from the shipped upstream
14
+ * bytes, not read from a log the producer wrote about itself.
15
+ */
16
+ import { readFileSync } from 'node:fs';
17
+ import path from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+ import { deriveAll } from './native-interface.mjs';
20
+
21
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
22
+ const INTERFACE_DIR = path.resolve(HERE, '..', 'interface');
23
+
24
+ /**
25
+ * Re-derive every pinned native interface from the vendored upstream sources and compare
26
+ * with the shipped artifacts.
27
+ *
28
+ * Returns `{ ok, checked, stale, missing, error }` where `checked` is the number of
29
+ * artifacts compared and `stale` names each artifact whose bytes differ from a fresh
30
+ * derivation. `ok === false` with a non-empty `stale` is the interesting failure: the
31
+ * derivation rules or the vendored bytes moved without the committed artifact following.
32
+ */
33
+ export function auditNativeInterface() {
34
+ const stale = [];
35
+ const missing = [];
36
+ let checked = 0;
37
+ let docs;
38
+ try {
39
+ docs = deriveAll();
40
+ } catch (error) {
41
+ return { ok: false, checked, stale, missing, error: `derivation failed: ${error.message}` };
42
+ }
43
+ for (const doc of docs) {
44
+ const file = path.join(INTERFACE_DIR, `native-${doc.program}.json`);
45
+ let current;
46
+ try {
47
+ current = readFileSync(file, 'utf8');
48
+ } catch {
49
+ missing.push(path.basename(file));
50
+ continue;
51
+ }
52
+ checked += 1;
53
+ if (current !== `${JSON.stringify(doc, null, 2)}\n`) stale.push(path.basename(file));
54
+ }
55
+ return { ok: stale.length === 0 && missing.length === 0, checked, stale, missing, error: null };
56
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * native-corpus.mjs — sememe's REAL MAINNET NATIVE-PROGRAM CORPUS, shipped with the package.
3
+ *
4
+ * The two harvests here are the measured traffic the native decoder was qualified against
5
+ * (2026-10-31): every System and ComputeBudget payload pulled from live mainnet blocks,
6
+ * with the bytes the chain actually carried. A decoder can be checked against invented
7
+ * bytes; these are not invented — each carries its own provenance (slot, block, index)
8
+ * inside the harvest document.
9
+ *
10
+ * Why this ships instead of staying a test fixture: sememe's first in-tree consumer
11
+ * (solana-tx-remix) falsifies its own native path against the SAME corpus — "the table I
12
+ * consume names the chain's traffic" is a claim about the consumer, and the bytes it is
13
+ * judged on must be the producer's, not a stale copy. A copy in the consumer's tree is
14
+ * exactly the drift this package exists to end. The corpus is addressed by its sha256, so
15
+ * the consumer can pin and re-hash what it was given.
16
+ *
17
+ * `default` returns the whole document; `payloads(program)` flattens the wire bytes to
18
+ * Buffers. Neither decodes anything: reading is this module's whole job.
19
+ */
20
+ import { readFileSync } from 'node:fs';
21
+ import { createHash } from 'node:crypto';
22
+ import path from 'node:path';
23
+ import { fileURLToPath } from 'node:url';
24
+
25
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
26
+
27
+ export const SYSTEM_PROGRAM = '11111111111111111111111111111111';
28
+ export const COMPUTE_BUDGET_PROGRAM = 'ComputeBudget111111111111111111111111111111';
29
+
30
+ export const CORPUS = {
31
+ system: { file: 'harvest-system-2026-10-31.json', sha256: 'sha256:75c7052ebc8e1f18a0e2cb3fb1fb70b9963141f86b5994b3f1e64b4d9c64fd7d' },
32
+ computeBudget: { file: 'harvest-compute-budget-2026-10-31.json', sha256: 'sha256:25354bf24977ee52371cf307db2edab9fa7108662d68018db2aba63ba8172295' },
33
+ };
34
+
35
+ /** One harvest document, byte-hashed on every read. */
36
+ export function corpus(which) {
37
+ const entry = CORPUS[which];
38
+ if (!entry) throw Error(`unknown corpus ${String(which)}; expected one of ${Object.keys(CORPUS).join(', ')}`);
39
+ const raw = readFileSync(path.join(HERE, 'corpus', entry.file));
40
+ const actual = `sha256:${createHash('sha256').update(raw).digest('hex')}`;
41
+ if (actual !== entry.sha256) {
42
+ throw Error(`corpus ${which} re-hashes to ${actual}, expected ${entry.sha256} — the shipped bytes are not the qualified ones`);
43
+ }
44
+ return JSON.parse(raw);
45
+ }
46
+
47
+ /** Every wire payload in one harvest, as Buffers (payload.dataHex). */
48
+ export function payloads(which) {
49
+ const document = corpus(which);
50
+ const out = [];
51
+ for (const transaction of document.transactions ?? []) {
52
+ for (const payload of transaction.payloads ?? []) {
53
+ if (typeof payload.dataHex === 'string') out.push(Buffer.from(payload.dataHex, 'hex'));
54
+ }
55
+ }
56
+ return out;
57
+ }