sememe 0.4.21 → 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.
package/README.md CHANGED
@@ -132,7 +132,7 @@ sememe is a local, content-addressed schema store with a verification contract:
132
132
  ## Install (as a consumer, not a contributor)
133
133
 
134
134
  ```bash
135
- npm install github:auroraxo/sememe#v0.4.21 # tagged release; no npm account needed
135
+ npm install github:auroraxo/sememe#v0.4.22 # tagged release; no npm account needed
136
136
  ```
137
137
 
138
138
  ```js
@@ -151,7 +151,7 @@ The **library** refuses the same way: importing `sememe/pull` or `sememe/gap` wi
151
151
  **From an install (not a clone), the CLI is `npx sememe …`** — npm exec resolves the package's own `bin` from `node_modules`; the bare command `sememe` is not on your `PATH` after a plain `npm install`, and `node bin/sememe.mjs` works only inside a clone of the repository (both measured 2026-11-05 from a real tarball install). The consumer journey from the install, verbatim:
152
152
 
153
153
  ```bash
154
- npm install github:auroraxo/sememe#v0.4.21 @solana/web3.js # acquisition needs the peer
154
+ npm install github:auroraxo/sememe#v0.4.22 @solana/web3.js # acquisition needs the peer
155
155
  npx sememe pull whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc --store ./store
156
156
  npx sememe decode whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc 2b04ed0b1ac91e62a40d0100000000000100000000000000af331ba8327fbb35b1c4feff00000000010001010000000601 --store ./store
157
157
  ```
@@ -2570,6 +2570,116 @@ way shank was. What this fix guarantees is that until that happens, the
2570
2570
  interface is refused by name at every boundary and never crashes the codec
2571
2571
  or pretends to be decodable.
2572
2572
 
2573
+ ## The product SPEAKS the dialect it named: the raw-LE engine is wired (2026-11-12)
2574
+
2575
+ PULS X named the refusal ("naming a dialect is not speaking it") and PULS Y
2576
+ built the speaker — an engine for exactly the grammar the diabloarb file
2577
+ DECLARES (`encoding: "little-endian; u8/u16/u32/u64 unsigned, i64 signed; no
2578
+ padding; no length prefixes except the u8 counters named in each header"`):
2579
+ u8-tag dispatch, dict `types` with declared fixed sizes, counter-prefixed
2580
+ vectors (`Base[n_bases]`, `u8[n_hops]`), one byte-decided optional trailing
2581
+ field. 102/102 committed payloads decoded byte-exactly, and the product
2582
+ boundary stayed untouched — deliberately: capability first, the wiring with
2583
+ the pins it rewrites.
2584
+
2585
+ **This pulse is the wiring** (the card's own declared next step):
2586
+
2587
+ - `src/codec.mjs` routes every decode/encode of a file OUTSIDE the Anchor
2588
+ shape but INSIDE the engine grammar through `src/rawle.mjs`, before the
2589
+ shape gate. The result carries the codec's own fields (`discriminator` is
2590
+ the dispatch prefix in hex; `dialect: 'raw-le'`), and every engine refusal
2591
+ is translated into `CodecError` — one refusal family at the product
2592
+ boundary, never a `RawLeError` leak, never a crash.
2593
+ - `src/dialect.mjs` splits the question "can anything read this file?" from
2594
+ "can the Anchor codec read it?": `unreadableDialectReason`/`assertReadableDialect`
2595
+ pass an engine-readable file and keep the exact named refusal (code
2596
+ `unsupported-idl-dialect`) for everything else; `idlDialect` reports
2597
+ `'raw-le'` for a wired dialect.
2598
+ - The store gates (`src/adopt.mjs` write path, `src/store-read.mjs` read
2599
+ path) use the readable-dialect guard: a raw-LE object adopts under its
2600
+ own program id, reads back, and the CLI loop `sememe adopt` → `sememe
2601
+ decode` works end-to-end offline (measured 2/2). Mislabelling under a
2602
+ foreign program id stays refused via `program_id` — the wiring opens the
2603
+ dialect gate, not the re-labelling gate.
2604
+ - The derivation (`src/discriminators.mjs`) passes the file WITHOUT filling
2605
+ any Anchor sighash table (the PULS X fill defect stays dead — measured
2606
+ filled {instructions: 0, events: 0}); the union (`src/union.mjs`)
2607
+ decodes through the engine like through any source.
2608
+
2609
+ Measured two-tree, byte-identical probe
2610
+ ([`research/rawle-product-2026-11-12.json`](research/rawle-product-2026-11-12.json),
2611
+ pinned in `test/rawle-product-measurement.test.mjs` with a full
2612
+ gh-gated re-derivation): prefix tree = the PULS Y engine commit `05e6643`
2613
+ (engine present, product refuses — 0/102, store loop closed), this tree =
2614
+ 102/102 decoded AND 102/102 byte-exact re-encoded at `decodeInstructionData`,
2615
+ 5/5 product-level refusals as named `CodecError` faces (unknown tag,
2616
+ truncated payload, trailing bytes, unknown instruction on encode, missing
2617
+ argument on encode), derivation passes with NO table, adopt/read/union open,
2618
+ CLI store loop 2/2, Whirlpool control unchanged, a file that defeats BOTH
2619
+ engines keeps the exact PULS X named refusal at codec AND union. 24
2620
+ falsification counts, all zero. The PULS X measurement
2621
+ ([`research/dialect-named-refusal-2026-11-12.json`](research/dialect-named-refusal-2026-11-12.json))
2622
+ was re-derived in its invariant form: its delta now reads crash → decodes
2623
+ through the engine, fill {4,0} → {0,0} — the no-crash, no-fill, no-mislabel
2624
+ invariants pinned on every payload. The PULS Y artifact stays the frozen
2625
+ pre-wiring baseline; its tool now exits 3 NO VERDICT in a wired tree BY
2626
+ DESIGN (stated in the tool header).
2627
+
2628
+ **Tests: 1 new pin file** (`test/rawle-product-measurement.test.mjs`, 12
2629
+ tests) plus the bent faces in `test/dialect-named-refusal.test.mjs` (D1b),
2630
+ `test/rawle-decoder.test.mjs` (G1) and
2631
+ `test/dialect-named-refusal-measurement.test.mjs`. Suite 912 → **933: 929
2632
+ pass / 4 named network skips / 0 fail**. Product code changed; the pin is
2633
+ the next release.
2634
+
2635
+ ## The wiring SHIPS in v0.4.22 — and the ecosystem number it moves is +102 for the user who adopts, not the shipped store (2026-11-12)
2636
+
2637
+ This release carries the raw-LE wiring above to the install channel: the
2638
+ pinned tag now contains `src/rawle.mjs` and every wired boundary, so
2639
+ `npm install github:auroraxo/sememe#v0.4.22` degrades nobody (Whirlpool,
2640
+ shank, name-only anchor controls pin unchanged) and answers the diabloarb
2641
+ dialect where v0.4.21 refused it by name.
2642
+
2643
+ The wiring also prompted the card's declared coverage re-measurement, and
2644
+ the honest answer is TWO-HALVED (`tools/measure-diablo-adoption-coverage.mjs`
2645
+ → `research/diablo-adoption-coverage-2026-11-12.json`, pinned by
2646
+ `test/diablo-adoption-coverage.test.mjs`, 9 tests, gh-gated re-derivation
2647
+ included):
2648
+
2649
+ - **At the shipped store the headline does NOT move.** Re-running the
2650
+ committed 2026-11-11 fixture through the coverage tool on this tree
2651
+ reproduces the committed artifact exactly: **88.5%, DiabLokxis 0 decoded
2652
+ / 102 no-interface.** The wiring changed the codec, not the store, and
2653
+ the diabloarb copy cannot ship inside the store: `cryptodiablo/diabloarb`
2654
+ carries **no license** at the pin `f5005ac4` — live re-checked inside the
2655
+ pin test (GitHub license API 404 at the pin AND at HEAD, repo field
2656
+ null), not narrated. The bytes stay out of the repository and the
2657
+ package; `committedStoreModified: false` (readdir + per-file hash digest
2658
+ before/after).
2659
+ - **For the user who adopts, the 102 are real.** ONE `adopt` of the
2660
+ hash-gated first-party copy (canonical fingerprint
2661
+ `d6d4e8aa…` verified before anything runs) through the product's own
2662
+ write path, into a throwaway copy of the committed store, and the SAME
2663
+ report tool answers **102/102 decoded, 0 refused, 102 byte-exact**:
2664
+ headline **88.5% → 89.0%** of measured traffic, outside the watchlist
2665
+ **62.4% → 64.4%**. Every one of the 102 rides the **adopted** evidence
2666
+ column (`decodedVia.adopted +102`; `pulled` moves by exactly zero) — the
2667
+ same class split the pfeeU, manifest and archer measurements keep, so an
2668
+ adoption can never silently inflate the on-chain-pull number.
2669
+
2670
+ So "102 jetzt zählbar" was half true: countable **yes** — as adoption-path
2671
+ coverage, with a name and a byte-exact check — and **not** as a shipped
2672
+ headline. The acquisition gap at that program is a LICENSE gap, not a
2673
+ decoder gap: the one thing between the user and those 102 payloads is a
2674
+ licensed first-party copy, which is exactly what `sememe adopt` exists to
2675
+ carry while origin stays declared.
2676
+
2677
+ Suite after this release: **954 tests / 950 pass / 4 named network skips /
2678
+ 0 fail** (945 → 954: the 9 new pin tests). CI green on `main`
2679
+ (43287274405) and on the published tag v0.4.22 (43287274209); the release
2680
+ surface re-measured against that tag is clean (newest = latest = pinned,
2681
+ 0 defects — `research/release-surface-2026-11-12b.json`).
2682
+
2573
2683
  ## The remaining 35 publish-nothing programs, classified against foreign sources (2026-11-04)
2574
2684
 
2575
2685
  With Lighthouse decoded under shank, exactly **35 programs (87 payloads)** remained
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sememe",
3
- "version": "0.4.21",
3
+ "version": "0.4.22",
4
4
  "description": "Local, fingerprint-verified Solana program interfaces: pull an IDL once, then decode instructions, accounts and events offline. Zero dependencies on the decode path. Ships derived wire tables for System, ComputeBudget, SPL Token, Token-2022 (incl. its token-metadata fallback), Raydium AMM v4 and the SPL Associated Token Account, pinned to upstream Rust.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/adopt.mjs CHANGED
@@ -39,7 +39,7 @@ import path from 'node:path';
39
39
  import { canonicalJson } from './canonical.mjs';
40
40
  import { writeOnce } from './store-write.mjs';
41
41
  import { originDigestOf, receiptDigestOf } from './receipt-digest.mjs';
42
- import { assertIdlShape, declaredProgram } from './dialect.mjs';
42
+ import { assertIdlShape, assertReadableDialect, declaredProgram } from './dialect.mjs';
43
43
 
44
44
  const sha256 = (data) => createHash('sha256').update(data).digest('hex');
45
45
 
@@ -117,12 +117,13 @@ export async function adopt(programInput, filePath, storeDir, { source = 'publis
117
117
  }
118
118
  throw Error(`${filePath} declares program ${declared}, not ${program} — refusing to re-label a foreign interface`);
119
119
  }
120
- // Gate 2 — the dialect gate runs at the store's WRITE path too: an
121
- // interface the codec cannot read (measured 2026-11-12: diabloarb's
122
- // tag/data dispatch with dict `types`) is refused HERE, not after
123
- // adoption when every decode crashes unnamed. Same reason text as the
124
- // codec's guard (src/dialect.mjs).
125
- assertIdlShape(idl, (reason) => Error(`${filePath}: ${reason}`));
120
+ // Gate 2 — the dialect gate at the store's WRITE path: a dialect NO
121
+ // engine of the product can read is refused HERE by name, before
122
+ // adoption can store an undecodable interface. SINCE THE PULS-Z WIRING
123
+ // a raw-LE file passes (the codec serves it from src/rawle.mjs),
124
+ // everything else keeps the named refusal (measured 2026-11-12:
125
+ // diabloarb's dict `types` was the face of that refusal).
126
+ assertReadableDialect(idl, (reason) => Error(`${filePath}: ${reason}`));
126
127
 
127
128
  const json = Buffer.from(canonicalJson(idl) + '\n');
128
129
  const fingerprint = sha256(json);
package/src/codec.mjs CHANGED
@@ -20,7 +20,8 @@
20
20
 
21
21
  import { encodeBase58, decodeBase58 } from './base58.mjs';
22
22
  import { isShankIdl, isShankDiscriminant, shankDiscriminatorBytes, SHANK_LEB128_TYPES } from './shank.mjs';
23
- import { assertIdlShape } from './dialect.mjs';
23
+ import { assertIdlShape, unsupportedIdlReason } from './dialect.mjs';
24
+ import { rawLeDialectReason, decodeRawLe, encodeRawLe, RawLeError } from './rawle.mjs';
24
25
 
25
26
  const MAX_DEPTH = 32;
26
27
  // Instruction data is bounded by transaction size; a hostile length prefix
@@ -925,6 +926,43 @@ export function decodeInstructionData(idl, data, { allowTrailing, trailing, abse
925
926
  throw new CodecError(`unknown absent policy ${JSON.stringify(absent)}`, null);
926
927
  }
927
928
  if (!(data instanceof Uint8Array)) throw new CodecError('instruction data must be a Uint8Array/Buffer', null);
929
+ // The raw little-endian dialect is WIRED at the product boundary (12.11.
930
+ // PULS Z). Until PULS X the diabloarb interface crashed the codec unnamed
931
+ // (TypeError over the dict `types`, 102/102 payloads); until this wiring
932
+ // the same interface was refused by name — honest, but naming a dialect is
933
+ // not speaking it (PULS Y: the engine decoded 102/102 byte-exactly, and
934
+ // the card's own next step was this adapter). The branch runs BEFORE the
935
+ // shape gate: a raw-LE IDL is exactly the shape the dialect gate names as
936
+ // unsupported, so the engine must answer first. When the bytes defeat the
937
+ // declared grammar the RawLeError is translated into the codec's error
938
+ // family — one refusal family at the product boundary, never a RawLeError
939
+ // leaking past it. The message text stays the engine's own words.
940
+ if (unsupportedIdlReason(idl) && rawLeDialectReason(idl) === null) {
941
+ try {
942
+ const raw = decodeRawLe(idl, data);
943
+ const { kind, instruction, tag, args, absent, consumed, total, trailing: rawTrailing } = raw;
944
+ const bytes = data instanceof Uint8Array ? data : Buffer.from(data);
945
+ return {
946
+ kind: kind ?? 'instruction',
947
+ instruction,
948
+ tag,
949
+ // The dispatch prefix in hex, like the codec's own `discriminator`
950
+ // field — here the prefix is the u8 tag the engine dispatched on,
951
+ // not an Anchor sighash. Consumers that render the prefix (CLI,
952
+ // src/decode.mjs) therefore see the real wire bytes.
953
+ discriminator: Buffer.from(data.subarray(0, 1)).toString('hex'),
954
+ args,
955
+ absent: absent ?? [],
956
+ consumed,
957
+ total,
958
+ trailing: rawTrailing ?? { count: Math.max(0, bytes.length - consumed), hex: '' },
959
+ dialect: 'raw-le',
960
+ };
961
+ } catch (error) {
962
+ if (!(error instanceof RawLeError)) throw error;
963
+ throw new CodecError(error.message, error.path);
964
+ }
965
+ }
928
966
  const ctx = context(idl);
929
967
  const index = instructionIndex(idl);
930
968
  const prefixLength = Math.min(8, data.length);
@@ -998,6 +1036,17 @@ export function decodeInstructionData(idl, data, { allowTrailing, trailing, abse
998
1036
  * layout, and byte-exactness is the only evidence this function offers.
999
1037
  */
1000
1038
  export function encodeInstructionData(idl, name, args, { absent } = {}) {
1039
+ // Same dialect branch as decodeInstructionData: a raw-LE interface is
1040
+ // served by its engine, byte for byte, and every refusal comes back in
1041
+ // the codec's own error family.
1042
+ if (unsupportedIdlReason(idl) && rawLeDialectReason(idl) === null) {
1043
+ try {
1044
+ return encodeRawLe(idl, name, args, { absent });
1045
+ } catch (error) {
1046
+ if (!(error instanceof RawLeError)) throw error;
1047
+ throw new CodecError(error.message, error.path);
1048
+ }
1049
+ }
1001
1050
  const ctx = context(idl);
1002
1051
  const instruction = instructionByName(idl, name);
1003
1052
  const bytes = discriminatorBytes(instruction) ?? (() => {
package/src/dialect.mjs CHANGED
@@ -43,6 +43,7 @@
43
43
  // shank take the same paths as before, byte for byte.
44
44
 
45
45
  import { isShankDiscriminant } from './shank.mjs';
46
+ import { rawLeDialectReason } from './rawle.mjs';
46
47
 
47
48
  const isPlainObject = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
48
49
 
@@ -107,7 +108,12 @@ export function unsupportedIdlReason(idl) {
107
108
  * lists.
108
109
  */
109
110
  export function idlDialect(idl) {
110
- if (unsupportedIdlReason(idl)) return 'unsupported';
111
+ // A raw-LE file is SUPPORTED now (PULS Z wiring) but is not one of the
112
+ // three Anchor-family dialects this classifier enumerates; it reports
113
+ // its own name so callers can tell a wired dialect from a refused one.
114
+ if (unsupportedIdlReason(idl)) {
115
+ return rawLeDialectReason(idl) === null ? 'raw-le' : 'unsupported';
116
+ }
111
117
  const instructions = idl.instructions;
112
118
  if (instructions.length === 0) return 'anchor';
113
119
  for (const instruction of instructions) {
@@ -143,3 +149,32 @@ export function assertIdlShape(idl, makeError) {
143
149
  }
144
150
  throw error;
145
151
  }
152
+
153
+ /**
154
+ * The refusal reason ONLY when no engine in the product can read the file.
155
+ * Since 12.11. PULS Z the raw little-endian engine (src/rawle.mjs) is wired
156
+ * into the codec: an IDL the Anchor codec refuses by SHAPE but the engine
157
+ * claims grammar for is decodable, so the store write gate, the store read
158
+ * gate and the discriminator derivation must not refuse it. Every other
159
+ * unsupported shape keeps the exact named refusal of `unsupportedIdlReason`.
160
+ * The codec's own dispatch (src/codec.mjs) asks the same two questions in
161
+ * the same order: is it outside the Anchor shape, and is it inside the
162
+ * engine grammar?
163
+ */
164
+ export function unreadableDialectReason(idl) {
165
+ const reason = unsupportedIdlReason(idl);
166
+ if (!reason) return null;
167
+ return rawLeDialectReason(idl) === null ? null : reason;
168
+ }
169
+
170
+ /** Like assertIdlShape, but raw-LE-readable files pass — the engine speaks
171
+ * them now (PULS Z). Same named refusal and same error code otherwise. */
172
+ export function assertReadableDialect(idl, makeError) {
173
+ const reason = unreadableDialectReason(idl);
174
+ if (!reason) return;
175
+ const error = makeError(reason);
176
+ if (error && typeof error === 'object') {
177
+ error.code = UNSUPPORTED_DIALECT_CODE;
178
+ }
179
+ throw error;
180
+ }
@@ -28,7 +28,7 @@
28
28
 
29
29
  import { createHash } from 'node:crypto';
30
30
  import { isShankDiscriminant } from './shank.mjs';
31
- import { unsupportedIdlReason } from './dialect.mjs';
31
+ import { unreadableDialectReason, idlDialect } from './dialect.mjs';
32
32
 
33
33
  const sha256 = (text) => createHash('sha256').update(text).digest();
34
34
 
@@ -119,14 +119,29 @@ export function verifyDerivedDiscriminators(idl) {
119
119
  * @returns {{ idl: object, filled: { instructions: number, events: number }, conflicts: object[] }}
120
120
  */
121
121
  export function withDerivedDiscriminators(idl) {
122
- // A dialect the codec cannot read gets NO derived table at all. Measured
123
- // 2026-11-12 on the diabloarb interface: the derivation filled all four
124
- // tag-dispatch instructions with Anchor sighashes that match no wire — a
125
- // table that looks authoritative and decodes nothing. The refusal is named
126
- // (see src/dialect.mjs) and identical to the codec's own.
127
- const refusal = unsupportedIdlReason(idl);
122
+ // A dialect NO engine in the product can read gets NO derived table at
123
+ // all. Measured 2026-11-12 on the diabloarb interface: the derivation
124
+ // filled all four tag-dispatch instructions with Anchor sighashes that
125
+ // match no wire — a table that looks authoritative and decodes nothing.
126
+ // The refusal is named (see src/dialect.mjs) and identical to the
127
+ // codec's own. SINCE THE PULS-Z WIRING the raw little-endian engine is
128
+ // a product engine: a raw-LE interface carries its own tag dispatch (a
129
+ // u8 `tag`, not a sighash) and passes this gate to the codec, which
130
+ // serves it from src/rawle.mjs — no Anchor table is derived for it and
131
+ // none is needed.
132
+ const refusal = unreadableDialectReason(idl);
128
133
  if (refusal) throw new Error(refusal);
129
134
  const copy = structuredClone(idl);
135
+ // A raw-LE file (PULS Z wiring) dispatches on the u8 tag its own file
136
+ // declares — Anchor sighash derivation for it is the measured PULS X
137
+ // defect one gate change later: filled {instructions: 4, events: 0}, a
138
+ // table that looks authoritative and matches no wire byte. The honest
139
+ // table for this dialect is NO derived table, and the codec serves the
140
+ // file from src/rawle.mjs. The file still passes the gate (an engine
141
+ // reads it); only the Anchor filling is withheld.
142
+ if (idlDialect(idl) === 'raw-le') {
143
+ return { idl: copy, filled: { instructions: 0, events: 0 }, conflicts: [] };
144
+ }
130
145
  let instructions = 0;
131
146
  let events = 0;
132
147
  const conflicts = [];
package/src/rawle.mjs CHANGED
@@ -42,15 +42,19 @@
42
42
  // A remainder that is neither exactly the option size nor zero is a
43
43
  // named refusal (trailing bytes the layout does not explain).
44
44
  //
45
- // NOT WIRED INTO THE PRODUCT. No call site in codec/union/adopt/store-read
46
- // or the CLI routes through this engine yet — PULS X's named refusal stays
47
- // the product's answer at every boundary, byte for byte, and the dialect
48
- // gate is untouched. That is deliberate: making support the product answer
49
- // changes 19 pinned faces of the PULS X measurement (its artifacts and
50
- // tools assert the refusal of THIS interface), and the release it belongs
51
- // to must state the change. The engine + measurement first, the wiring
52
- // with the pins it rewrites — this is the same order every pulse in this
53
- // repo has worked in.
45
+ // WIRED INTO THE PRODUCT SINCE PULS Z (2026-11-12): src/codec.mjs routes
46
+ // every decode/encode of a file OUTSIDE the Anchor shape but INSIDE this
47
+ // grammar through this engine — instruction decode/encode, the union
48
+ // probe, store adopt/read, and the discriminator derivation gate (an
49
+ // engine-readable file passes it; no Anchor table is derived for it).
50
+ // RawLeError is translated into CodecError at the boundary, so the
51
+ // product keeps ONE refusal family. Everything else — every anchor/shank/
52
+ // name-only file — takes the exact same codec paths as before, byte for
53
+ // byte, and files no engine can read keep PULS X's named refusal.
54
+ // The face this wiring turns: diabloarb's 102 sample payloads, refused
55
+ // by name until now, decode byte-exactly through the product entry. The
56
+ // pins it bends are the PULS X refusal faces for THIS interface, and the
57
+ // measurement that proves them is tools/measure-rawle-product.mjs.
54
58
  //
55
59
  // Byte-exactness note: decoded integers come back as Number (<= 4 bytes)
56
60
  // or BigInt (8 bytes), exactly like src/codec.mjs's own convention, and
@@ -33,7 +33,7 @@ import { canonicalJson } from './canonical.mjs';
33
33
  import { compareReceiptNames } from './receipt-order.mjs';
34
34
  import { classifyReceiptFiles, isReceiptFile } from './receipt-name.mjs';
35
35
  import { receiptIntegrity } from './receipt-digest.mjs';
36
- import { assertIdlShape, declaredProgram } from './dialect.mjs';
36
+ import { assertIdlShape, assertReadableDialect, declaredProgram } from './dialect.mjs';
37
37
 
38
38
  export { canonicalJson } from './canonical.mjs';
39
39
 
@@ -80,11 +80,14 @@ export async function readVerified(storeDir, program, receiptName) {
80
80
  }
81
81
  if (!Array.isArray(idl.instructions)) throw Error(`stored object for ${program} has no instructions`);
82
82
  // Same two checks as the write path (src/adopt.mjs), applied on every
83
- // read: a dialect the codec cannot read is refused by NAME before any
84
- // decode can crash unnamed, and a declared program that differs from the
85
- // receipt's program is a re-labelling no matter which field carries the
86
- // declaration (`address`, `metadata.address` or `program_id`).
87
- assertIdlShape(idl, (reason) => {
83
+ // read: a dialect NO engine of the product can read is refused by NAME
84
+ // before any decode can crash unnamed, and a declared program that
85
+ // differs from the receipt's program is a re-labelling no matter which
86
+ // field carries the declaration (`address`, `metadata.address` or
87
+ // `program_id`). Since the PULS-Z wiring a stored raw-LE object reads
88
+ // through the engine (src/rawle.mjs) — a store that refused it by name
89
+ // now decodes instead, which is the point of the wiring.
90
+ assertReadableDialect(idl, (reason) => {
88
91
  const error = Error(`stored object ${fingerprint} for ${program}: ${reason}`);
89
92
  error.code = 'unsupported-idl-dialect';
90
93
  return error;