sememe 0.4.25 → 0.4.27
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 +39 -4
- package/bin/sememe.mjs +29 -0
- package/package.json +2 -1
- package/src/cli-surface.mjs +4 -0
- package/src/codec.mjs +3 -3
- package/src/hadron.mjs +228 -0
- package/src/wire-layout.mjs +12 -8
- package/vendor/hadron/0c1bca1d8c9bc2abaa1b3d651d450c814cdd6328/LICENSE-APACHE +190 -0
- package/vendor/hadron/0c1bca1d8c9bc2abaa1b3d651d450c814cdd6328/LICENSE-MIT +21 -0
- package/vendor/hadron/0c1bca1d8c9bc2abaa1b3d651d450c814cdd6328/NOTICE +33 -0
- package/vendor/hadron/0c1bca1d8c9bc2abaa1b3d651d450c814cdd6328/hadron.json +954 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A local meaning layer for Solana bytes. `sememe pull <program>` fetches and verifies a program interface; every process on the machine can then decode instructions, accounts, and events offline.
|
|
4
4
|
|
|
5
|
-
**Status: v0.4.
|
|
5
|
+
**Status: v0.4.27 — published on the npm registry AND as a git tag. Pull/store plus an offline decoder for instructions, Anchor event self-CPIs and account state, verified against real mainnet programs; union decode across a program's on-chain IDL and its adopted first-party published copy (`sememe adopt` + `sememe decode --union`); the fingerprint lockfile ships (`sememe lock` + `sememe verify`); the decode path installs zero runtime dependencies. The ecosystem-wide measured coverage is 88.8% (18,967/21,350 payloads across 6 finalized blocks, 2026-11-13; 96.4% on the curated watchlist, 63.8% outside it; byte-exact re-encode on 93.1% of decoded). The honest limits ride with every number: zero external users so far — publish is not consumption — one machine, no audit.**
|
|
6
6
|
|
|
7
7
|
Everything below the fold is what you run. The full measurement and qualification history — every number, artifact, defect found and fixed, and every honest limit — was moved verbatim to [docs/measured-history.md](https://github.com/auroraxo/sememe/blob/main/docs/measured-history.md) on 2026-11-13; nothing was deleted.
|
|
8
8
|
|
|
@@ -56,7 +56,7 @@ Measured on that sample (2026-11-04): 1,000 checked transactions, 6,337 payloads
|
|
|
56
56
|
## Install (as a consumer, not a contributor)
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
npm install github:auroraxo/sememe#v0.4.
|
|
59
|
+
npm install github:auroraxo/sememe#v0.4.27 # tagged release; no npm account needed
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
```js
|
|
@@ -75,14 +75,14 @@ The **library** refuses the same way: importing `sememe/pull` or `sememe/gap` wi
|
|
|
75
75
|
**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:
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
|
-
npm install github:auroraxo/sememe#v0.4.
|
|
78
|
+
npm install github:auroraxo/sememe#v0.4.27 @solana/web3.js # acquisition needs the peer
|
|
79
79
|
npx sememe pull whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc --store ./store
|
|
80
80
|
npx sememe decode whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc 2b04ed0b1ac91e62a40d0100000000000100000000000000af331ba8327fbb35b1c4feff00000000010001010000000601 --store ./store
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
`./store` is the local content-addressed store the error `no sememe store at ./store: run "sememe pull" first` tells you to create — the `pull` above is that creation step; the vendored programs (System, ComputeBudget, SPL Token, Token-2022, Raydium AMM v4, SPL Associated Token Account) need no store at all: `npx sememe decode-native 11111111111111111111111111111111 <payload>` answers offline from the pinned upstream tables. If you ever point the decoder at a store directory that does not exist yet, the refusal names the fix; `npx sememe` is the form of that fix that runs where you are. Measured 2026-11-05 from the packed tarball of this tree: install (zero deps) → `npx sememe pull` (live mainnet) → `npx sememe decode` (byte-exact, live-pulled object byte-identical to the fixture store object `0b886e37…660f`).
|
|
84
84
|
|
|
85
|
-
Publishing to the npm registry changed on 2026-11-12: the `sememe` name is now live on registry.npmjs.org (first shipped as 0.4.21; the measured registry install of `sememe@0.4.22` into an empty app decoded the Whirlpool fixture payload `swap_v2`, `roundTripByteExact: true`). `npm install sememe` and `npm install github:auroraxo/sememe#v0.4.
|
|
85
|
+
Publishing to the npm registry changed on 2026-11-12: the `sememe` name is now live on registry.npmjs.org (first shipped as 0.4.21; the measured registry install of `sememe@0.4.22` into an empty app decoded the Whirlpool fixture payload `swap_v2`, `roundTripByteExact: true`). `npm install sememe` and `npm install github:auroraxo/sememe#v0.4.27` deliver the same product files from the same code: the files allowlist already ships none of this repository's tests, fixtures or qualification harness (pinned by `test/package.test.mjs`), so the git tag remains the release artifact — `test/package.test.mjs` (the contract above), `tools/e2e-package-consumer.mjs` (a real install into an empty directory) and `test/install-surface.test.mjs` (the shipped README's install commands run verbatim from the packed tarball) keep both channels honest.
|
|
86
86
|
|
|
87
87
|
## Working now: pin the answers (`sememe lock` + `sememe verify`, 2026-11-05)
|
|
88
88
|
|
|
@@ -110,6 +110,41 @@ npx sememe verify --store ./store --lock ./sememe.lock
|
|
|
110
110
|
|
|
111
111
|
What the lock does NOT do: it is not a freshness check against the chain (the pin says "this is what I answered", not "this is what the program is now"); it does not cover vendored programs (System/ComputeBudget/SPL Token/Token-2022/Raydium AMM v4/SPL Associated Token Account have no receipts, they are pinned from upstream sources instead); and it cannot stop you from re-pulling — it makes every re-pull that changes an answer visible.
|
|
112
112
|
|
|
113
|
+
## Working now: when the on-chain interface copy is complete but incomplete on the wire (`sememe adopt-hadron`, 2026-11-14)
|
|
114
|
+
|
|
115
|
+
Most interface gaps are "no interface exists at all" — the ATA, Lighthouse and Manifest cases, closed by a derived wire table or a vendored first-party copy. HADRo (`HADRoNbLovyqhCsocfYQYB7QdfCAAinN9HTePvBCVDQ8`) is the other kind: the on-chain metadata IDL declares **all 38 instructions and every type**, and it is still incomplete **on the wire**. It lacks exactly two argument declarations — `sequence: u64`, last on `setCurve` and `setRiskCurve` — and the wire carries those 8 bytes after the points.
|
|
116
|
+
|
|
117
|
+
Measured on the 144 committed chain-accepted payloads of those two instructions:
|
|
118
|
+
|
|
119
|
+
| from the on-chain copy alone | |
|
|
120
|
+
|---|---|
|
|
121
|
+
| decoded | 144/144, 0 refused |
|
|
122
|
+
| payloads ending in unexplained bytes | 144/144 (8 bytes each) |
|
|
123
|
+
| **re-encoding byte-exactly** | **0/144** |
|
|
124
|
+
| refused under `--trailing error` | 144/144 |
|
|
125
|
+
|
|
126
|
+
So a consumer had no path to byte-exactness on real mainnet traffic, and `--trailing error` refused every one of those payloads outright.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
npx sememe adopt-hadron --store ./store
|
|
130
|
+
npx sememe decode --union HADRoNbLovyqhCsocfYQYB7QdfCAAinN9HTePvBCVDQ8 <payload> --store ./store
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`adopt-hadron` adopts the program's **first-party published copy** (`Hadron-Fi/hadron-tx-verifier`, Apache-2.0 AND MIT) from a sha256-pinned vendored file, hash-checked **before** any write, through the product's own write path. Afterwards the union reproduces **144/144 payloads byte-exactly**, 0 unexplained bytes, 0 refusals under `--trailing error` — and *names* the disagreement with the on-chain copy instead of silently picking one (`verdict: layout_drift`, drift path `sequence`, arbitration basis `byte_evidence_full_roundtrip`).
|
|
134
|
+
|
|
135
|
+
Neither half of this was sufficient alone, and that was measured rather than argued (`research/hadron-adoption-2026-11-14.json`, four faces over the same 144 payloads):
|
|
136
|
+
|
|
137
|
+
| face | decoded | refused | byte-exact |
|
|
138
|
+
|---|---|---|---|
|
|
139
|
+
| before the header-count rule + on-chain IDL | 72 | 72 | 0 |
|
|
140
|
+
| before the header-count rule + first-party IDL | 72 | 72 | 0 |
|
|
141
|
+
| after the header-count rule + on-chain IDL | 144 | 0 | 0 |
|
|
142
|
+
| after the header-count rule + first-party IDL | 144 | 0 | **144** |
|
|
143
|
+
|
|
144
|
+
The second row is the falsifier for "just adopt the source IDL": the first-party copy declares `points` as a Borsh vector too, so on its own it refuses the same 72 payloads. The third row is the falsifier for "the header rule alone is the fix": it stops the refusals but drops the 8-byte tail, so nothing round-trips. Only the composition reproduces every byte.
|
|
145
|
+
|
|
146
|
+
What it does **not** do: plain `sememe decode` keeps the on-chain receipt as its default answer (measured unchanged on both sides of the adoption — the adopted receipt sorts at slot 0, so it never wins the single-source read), and the receipt's origin stays `declared`: a commit is content-addressed, not signed, and nothing here proves the bytes describe the deployed program. Chain proof stays with the decode — every payload must re-encode byte-exactly or the union refuses. See [docs/measured-history.md](https://github.com/auroraxo/sememe/blob/main/docs/measured-history.md) and [research/hadron-adoption-2026-11-14.json](research/hadron-adoption-2026-11-14.json).
|
|
147
|
+
|
|
113
148
|
## Where the numbers come from
|
|
114
149
|
|
|
115
150
|
Every claim above is pinned by a test and backed by a committed artifact. If you want to know **why** a number is what it is, and what was measured wrong before it was measured right:
|
package/bin/sememe.mjs
CHANGED
|
@@ -52,6 +52,18 @@ const usage = `usage:
|
|
|
52
52
|
(L2TExMFK…, 1,214 payloads in the committed 6-block window) from the
|
|
53
53
|
vendored first-party copy (Jac0xb/lighthouse, MIT), hash-checked against
|
|
54
54
|
its pin BEFORE any write; the receipt's origin stays declared.
|
|
55
|
+
sememe adopt-hadron [--store <directory>]
|
|
56
|
+
the measured case where the ON-CHAIN interface copy is complete but
|
|
57
|
+
incomplete ON THE WIRE: HADRo (HADRoNbLovy…) declares every instruction
|
|
58
|
+
and type, yet lacks the trailing "sequence: u64" of setCurve and
|
|
59
|
+
setRiskCurve, so 144 committed chain-accepted payloads decode with 8
|
|
60
|
+
unexplained trailing bytes and 0 of them re-encode byte-exactly
|
|
61
|
+
(research/hadron-adoption-2026-11-14.json). This adopts the first-party
|
|
62
|
+
published copy (Hadron-Fi/hadron-tx-verifier, Apache-2.0 AND MIT) from a
|
|
63
|
+
sha256-pinned vendored file, hash-checked BEFORE any write. Afterwards
|
|
64
|
+
"sememe decode --union" reproduces 144/144 payloads byte-exactly and
|
|
65
|
+
NAMES the disagreement with the on-chain copy (drift path "sequence").
|
|
66
|
+
Plain "sememe decode" keeps the on-chain receipt as its answer.
|
|
55
67
|
sememe lock [--store <directory>] [--out <file>]
|
|
56
68
|
snapshot the WHOLE store into a sememe.lock document: for every program,
|
|
57
69
|
the exact fingerprint its newest verified receipt answered with, plus
|
|
@@ -279,6 +291,23 @@ try {
|
|
|
279
291
|
path: result.receipt.path,
|
|
280
292
|
receiptPath: result.receipt.receiptPath,
|
|
281
293
|
}, null, 2));
|
|
294
|
+
} else if (command === 'adopt-hadron') {
|
|
295
|
+
const store = option('--store', path.join(os.homedir(), '.sememe'));
|
|
296
|
+
const { adoptHadron } = await import('../src/hadron.mjs');
|
|
297
|
+
const { adopt: adoptFn } = await import('../src/adopt.mjs');
|
|
298
|
+
const result = await adoptHadron(store, { adopt: adoptFn });
|
|
299
|
+
console.log(JSON.stringify({
|
|
300
|
+
program: result.receipt.program,
|
|
301
|
+
source: result.receipt.source,
|
|
302
|
+
origin: result.receipt.origin,
|
|
303
|
+
originStatus: result.receipt.originStatus,
|
|
304
|
+
originNote: result.receipt.originNote,
|
|
305
|
+
fingerprint: result.receipt.fingerprint,
|
|
306
|
+
instructionCount: result.receipt.instructionCount,
|
|
307
|
+
path: result.receipt.path,
|
|
308
|
+
receiptPath: result.receipt.receiptPath,
|
|
309
|
+
note: 'the on-chain receipt still answers plain "sememe decode"; use "sememe decode --union" to let the two sources arbitrate by bytes',
|
|
310
|
+
}, null, 2));
|
|
282
311
|
} else if (command === 'decode') {
|
|
283
312
|
if (!program) throw Error('decode needs a program id');
|
|
284
313
|
const payload = args.shift();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sememe",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.27",
|
|
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",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"./raydium": "./src/raydium.mjs",
|
|
25
25
|
"./ata": "./src/ata.mjs",
|
|
26
26
|
"./lighthouse": "./src/lighthouse.mjs",
|
|
27
|
+
"./hadron": "./src/hadron.mjs",
|
|
27
28
|
"./gap": "./src/gap.mjs"
|
|
28
29
|
},
|
|
29
30
|
"files": [
|
package/src/cli-surface.mjs
CHANGED
|
@@ -100,6 +100,10 @@ export const COMMAND_SPECS = {
|
|
|
100
100
|
flags: { '--store': 'value' },
|
|
101
101
|
positionals: 0,
|
|
102
102
|
},
|
|
103
|
+
'adopt-hadron': {
|
|
104
|
+
flags: { '--store': 'value' },
|
|
105
|
+
positionals: 0,
|
|
106
|
+
},
|
|
103
107
|
decode: {
|
|
104
108
|
flags: { '--store': 'value', '--trailing': 'value', '--absent': 'value', '--union': 'boolean' },
|
|
105
109
|
positionals: 2,
|
package/src/codec.mjs
CHANGED
|
@@ -365,7 +365,7 @@ function decodeAt(type, cur, ctx, path, depth) {
|
|
|
365
365
|
// keeps the strict prefix codec byte for byte.
|
|
366
366
|
if (ctx.__countFromHeader && ctx.__countFromHeader.vectorPath === path) {
|
|
367
367
|
const length = ctx.__countFromHeader.count;
|
|
368
|
-
const elementSize = ctx.__countFromHeader.
|
|
368
|
+
const elementSize = ctx.__countFromHeader.elementBytes;
|
|
369
369
|
const needed = length * elementSize;
|
|
370
370
|
if (needed > cur.remaining) {
|
|
371
371
|
// The measured count does not fit the remaining bytes: this is NOT a
|
|
@@ -1167,7 +1167,7 @@ export function decodeInstructionData(idl, data, { allowTrailing, trailing, abse
|
|
|
1167
1167
|
countArg: headerRule.countArg,
|
|
1168
1168
|
vectorArg: headerRule.vectorArg,
|
|
1169
1169
|
vectorPath: `${instruction.name}.${headerRule.vectorArg}`,
|
|
1170
|
-
|
|
1170
|
+
elementBytes: headerRule.elementBytes,
|
|
1171
1171
|
instruction: instruction.name,
|
|
1172
1172
|
};
|
|
1173
1173
|
}
|
|
@@ -1365,7 +1365,7 @@ export function encodeInstructionData(idl, name, args, { absent } = {}) {
|
|
|
1365
1365
|
countArg: headerRule.countArg,
|
|
1366
1366
|
vectorArg: headerRule.vectorArg,
|
|
1367
1367
|
vectorPath: `${instruction.name}.${headerRule.vectorArg}`,
|
|
1368
|
-
|
|
1368
|
+
elementBytes: headerRule.elementBytes,
|
|
1369
1369
|
instruction: instruction.name,
|
|
1370
1370
|
};
|
|
1371
1371
|
}
|
package/src/hadron.mjs
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// The HADRo acquisition: a pinned, hash-verified first-party published IDL for
|
|
2
|
+
// the ONE program sememe can decode but NOT fully re-encode from the on-chain
|
|
3
|
+
// copy (measured 2026-11-14, HADRoNbLovyqhCsocfYQYB7QdfCAAinN9HTePvBCVDQ8,
|
|
4
|
+
// research/hadron-adoption-2026-11-14.json).
|
|
5
|
+
//
|
|
6
|
+
// THE MEASURED GAP (this is the reason the file exists, not a preference):
|
|
7
|
+
//
|
|
8
|
+
// The on-chain metadata IDL (pulled at slot 468262003) is COMPLETE on
|
|
9
|
+
// instructions and types, but it is missing exactly two argument declarations
|
|
10
|
+
// — `sequence: u64` as the last argument of setCurve and setRiskCurve. The
|
|
11
|
+
// wire carries those 8 bytes after the points, so with the stored copy alone:
|
|
12
|
+
//
|
|
13
|
+
// 144/144 committed chain-accepted payloads decode (0 refused)
|
|
14
|
+
// 144/144 end with 8 UNEXPLAINED trailing bytes
|
|
15
|
+
// 0/144 round-trip byte-exact (the tail is dropped)
|
|
16
|
+
// 144/144 are REFUSED by --trailing error
|
|
17
|
+
//
|
|
18
|
+
// A user who wants byte-exactness has no fix available on that path: the bytes
|
|
19
|
+
// are real, the IDL simply does not declare them. `sememe adopt-hadron` puts
|
|
20
|
+
// the first-party published copy (Hadron-Fi/hadron-tx-verifier, which declares
|
|
21
|
+
// the two `sequence: u64` tails) into the store beside the on-chain receipt, so
|
|
22
|
+
// `sememe decode --union` arbitrates BY BYTES: the published copy then
|
|
23
|
+
// reproduces 144/144 payloads byte-exact with 0 trailing bytes, and the drift
|
|
24
|
+
// against the on-chain copy is reported by NAME (paths: ["sequence"]) instead
|
|
25
|
+
// of silently choosing one source.
|
|
26
|
+
//
|
|
27
|
+
// WHAT THE PIN DOES NOT CLAIM (src/adopt.mjs says this on every surface): the
|
|
28
|
+
// receipt's origin stays `declared` — the vendored bytes are bound to a commit
|
|
29
|
+
// by sha256, but a commit is content-addressed, not signed, and nothing here
|
|
30
|
+
// proves the bytes describe the DEPLOYED program. Chain proof stays with the
|
|
31
|
+
// decode: every payload must re-encode byte-exactly or union refuses.
|
|
32
|
+
//
|
|
33
|
+
// WHAT SHIPPING THE COPY DOES NOT CHANGE: plain `sememe decode` still reads
|
|
34
|
+
// the on-chain receipt (slot 0 sorts before every real slot — the rule
|
|
35
|
+
// src/adopt.mjs measures and pins). Adoption widens what `--union` can
|
|
36
|
+
// arbitrate; it never silently rewrites the default single-source answer.
|
|
37
|
+
|
|
38
|
+
import { readFileSync } from 'node:fs';
|
|
39
|
+
import { createHash } from 'node:crypto';
|
|
40
|
+
import path from 'node:path';
|
|
41
|
+
import { fileURLToPath } from 'node:url';
|
|
42
|
+
|
|
43
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
44
|
+
export const REPO_ROOT = path.resolve(HERE, '..');
|
|
45
|
+
|
|
46
|
+
export const HADRON_PROGRAM = 'HADRoNbLovyqhCsocfYQYB7QdfCAAinN9HTePvBCVDQ8';
|
|
47
|
+
|
|
48
|
+
export const HADRON_PIN = {
|
|
49
|
+
repo: 'Hadron-Fi/hadron-tx-verifier',
|
|
50
|
+
commit: '0c1bca1d8c9bc2abaa1b3d651d450c814cdd6328',
|
|
51
|
+
// Dual-licensed: the repo carries LICENSE-APACHE + LICENSE-MIT + NOTICE, all
|
|
52
|
+
// three vendored beside the IDL so a redistributor of the npm tarball has
|
|
53
|
+
// the terms and the attribution NOTICE in the same directory as the bytes.
|
|
54
|
+
license: 'Apache-2.0 AND MIT',
|
|
55
|
+
// sha256 of the bytes AS PUBLISHED at that commit, read via the GitHub
|
|
56
|
+
// contents API with `?ref=<commit>` — not from a mutable working tree.
|
|
57
|
+
files: [
|
|
58
|
+
{
|
|
59
|
+
file: 'idl/hadron.json',
|
|
60
|
+
role: 'first-party published IDL: the copy that declares the two sequence:u64 tails the on-chain metadata IDL lacks',
|
|
61
|
+
sha256: '219f44fb63729232b3685fbde49a817b0c8321a0203dcca68c88370fbed1279e',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
file: 'LICENSE-APACHE',
|
|
65
|
+
role: 'Apache-2.0 terms the vendored copy ships under',
|
|
66
|
+
sha256: 'e4cbaf2699b8d6509727edfae292eabe12249f8a31d4dc978de59e15d12ec717',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
file: 'LICENSE-MIT',
|
|
70
|
+
role: 'MIT terms the vendored copy ships under',
|
|
71
|
+
sha256: '81382b018e2877fc1f037cc427ecb62333e653ddd05e6e1ff141b184735c7d92',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
file: 'NOTICE',
|
|
75
|
+
role: 'attribution NOTICE (fork of multisig-verifier, Squads Labs; Hadron modifications)',
|
|
76
|
+
sha256: 'e39dc25965a71c43d8e475edf3b123f6028239ba612026065d43a5f124dfc36f',
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
// The FULL provenance string, as documentation. It is 96 characters, so
|
|
80
|
+
// `sememe adopt`'s own label sanitizer would TRUNCATE it to 64 (that is
|
|
81
|
+
// where the existing lighthouse origin loses its commit tail and its file
|
|
82
|
+
// path — a pre-existing shape this module does not copy). The label
|
|
83
|
+
// actually recorded is hadronOriginLabel() below, which FITS in 64 and
|
|
84
|
+
// therefore loses nothing.
|
|
85
|
+
origin: 'github.com/Hadron-Fi/hadron-tx-verifier@0c1bca1d8c9bc2abaa1b3d651d450c814cdd6328:idl/hadron.json',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/** The two arguments this acquisition exists to supply. They are the entire
|
|
89
|
+
* difference between an IDL that reports the wire's 8-byte tail as unexplained
|
|
90
|
+
* bytes and one that names it — measured, see research/hadron-adoption-*.json. */
|
|
91
|
+
export const HADRON_MISSING_ARGS = [
|
|
92
|
+
{ instruction: 'setCurve', arg: 'sequence', type: 'u64' },
|
|
93
|
+
{ instruction: 'setRiskCurve', arg: 'sequence', type: 'u64' },
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
export function vendoredHadronPath(file, root = REPO_ROOT) {
|
|
97
|
+
return path.join(root, 'vendor', 'hadron', HADRON_PIN.commit, path.basename(file));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function sha256File(filePath) {
|
|
101
|
+
return createHash('sha256').update(readFileSync(filePath)).digest('hex');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export class HadronPinError extends Error {
|
|
105
|
+
constructor(reason, detail) {
|
|
106
|
+
super(detail ? `${reason}: ${detail}` : reason);
|
|
107
|
+
this.name = 'HadronPinError';
|
|
108
|
+
this.reason = reason;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Read the vendored IDL and verify it against the pin. Every refusal is NAMED
|
|
114
|
+
* so a caller can tally failure faces without parsing prose.
|
|
115
|
+
*
|
|
116
|
+
* Returns { idl, path, sha256, bytes, declaredMissing } on success, where
|
|
117
|
+
* `declaredMissing` is the list of HADRON_MISSING_ARGS entries this copy
|
|
118
|
+
* actually declares — the measured reason for adopting it.
|
|
119
|
+
*/
|
|
120
|
+
export function loadVendoredHadron(root = REPO_ROOT, pin = HADRON_PIN) {
|
|
121
|
+
const pinned = pin.files.find((f) => f.file.endsWith('hadron.json'));
|
|
122
|
+
const vendoredPath = path.join(root, 'vendor', 'hadron', pin.commit, path.basename(pinned.file));
|
|
123
|
+
let raw;
|
|
124
|
+
try {
|
|
125
|
+
raw = readFileSync(vendoredPath);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
throw new HadronPinError(
|
|
128
|
+
'hadron-vendor-missing',
|
|
129
|
+
`${vendoredPath} is not present (${error.code ?? error.message})`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
const now = createHash('sha256').update(raw).digest('hex');
|
|
133
|
+
if (now !== pinned.sha256) {
|
|
134
|
+
throw new HadronPinError(
|
|
135
|
+
'hadron-vendor-hash-mismatch',
|
|
136
|
+
`pinned ${pinned.sha256}, hashed now ${now} — refusing to adopt bytes that are not the reviewed copy`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
let idl;
|
|
140
|
+
try {
|
|
141
|
+
idl = JSON.parse(raw.toString('utf8'));
|
|
142
|
+
} catch (error) {
|
|
143
|
+
throw new HadronPinError('hadron-vendor-unparseable', String(error.message ?? error));
|
|
144
|
+
}
|
|
145
|
+
if (!idl || typeof idl !== 'object' || Array.isArray(idl)) {
|
|
146
|
+
throw new HadronPinError('hadron-vendor-unparseable', 'not an IDL object');
|
|
147
|
+
}
|
|
148
|
+
// Address binding: this file claims HADRo by its own `address`, the same
|
|
149
|
+
// field the on-chain receipt binds. A file declaring another program is
|
|
150
|
+
// never re-labelled (the rule src/adopt.mjs measures: a declared program
|
|
151
|
+
// that differs from the receipt's program is a re-labelling, refused).
|
|
152
|
+
if (idl.address !== HADRON_PROGRAM) {
|
|
153
|
+
throw new HadronPinError(
|
|
154
|
+
'hadron-address-disagrees',
|
|
155
|
+
`vendored file declares ${idl.address ?? 'nothing'}, pin binds ${HADRON_PROGRAM}`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (!Array.isArray(idl.instructions) || idl.instructions.length === 0) {
|
|
159
|
+
throw new HadronPinError('hadron-no-instructions', 'not an interface copy');
|
|
160
|
+
}
|
|
161
|
+
// THE MEASURED REASON FOR SHIPPING THIS FILE: each of the two arguments the
|
|
162
|
+
// on-chain copy lacks must be present, last, and typed u64. A copy that lost
|
|
163
|
+
// them would still hash correctly against nothing in particular — the pin
|
|
164
|
+
// covers BYTES, this check covers the CLAIM those bytes make.
|
|
165
|
+
const byName = new Map(idl.instructions.map((instruction) => [instruction.name, instruction]));
|
|
166
|
+
const declaredMissing = [];
|
|
167
|
+
for (const expected of HADRON_MISSING_ARGS) {
|
|
168
|
+
const instruction = byName.get(expected.instruction);
|
|
169
|
+
if (!instruction) {
|
|
170
|
+
throw new HadronPinError('hadron-instruction-missing', `${expected.instruction} is not in the vendored copy`);
|
|
171
|
+
}
|
|
172
|
+
const args = Array.isArray(instruction.args) ? instruction.args : [];
|
|
173
|
+
const last = args[args.length - 1];
|
|
174
|
+
if (last?.name === expected.arg && last?.type === expected.type) {
|
|
175
|
+
declaredMissing.push({ ...expected });
|
|
176
|
+
}
|
|
177
|
+
// A copy WITHOUT the argument is still adopted honestly — it just cannot
|
|
178
|
+
// claim the reason this module exists, so `declaredMissing` stays short
|
|
179
|
+
// and the adopting caller reports the shortfall instead of the promise.
|
|
180
|
+
}
|
|
181
|
+
if (declaredMissing.length !== HADRON_MISSING_ARGS.length) {
|
|
182
|
+
throw new HadronPinError(
|
|
183
|
+
'hadron-tail-args-absent',
|
|
184
|
+
`this copy declares ${declaredMissing.length}/${HADRON_MISSING_ARGS.length} of the sequence:u64 tails `
|
|
185
|
+
+ `the acquisition exists for (${HADRON_MISSING_ARGS.map((a) => `${a.instruction}.${a.arg}`).join(', ')}) `
|
|
186
|
+
+ '— adopting it would promise byte-exactness it cannot deliver',
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
return { idl, path: vendoredPath, sha256: now, bytes: raw.length, declaredMissing };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The origin label adopt records.
|
|
194
|
+
*
|
|
195
|
+
* `sememe adopt` sanitizes the caller's label to a filename-safe form and
|
|
196
|
+
* caps it at 64 characters (src/adopt.mjs, `safeLabel`). A label longer than
|
|
197
|
+
* that is TRUNCATED, silently: the obvious label for this acquisition
|
|
198
|
+
* (`Hadron-Fi/hadron-tx-verifier@<40-hex commit>:idl/hadron.json`) is 96
|
|
199
|
+
* characters and comes back as a 64-char prefix that has LOST the tail of the
|
|
200
|
+
* commit hash and the whole file path — the same thing that already happens
|
|
201
|
+
* to the 105-character lighthouse origin. A commit truncated to 35 hex
|
|
202
|
+
* characters is not a commit, so this label drops the file path and the owner
|
|
203
|
+
* on purpose and keeps the FULL 40-hex commit: 59 characters, recorded whole.
|
|
204
|
+
* The owner and the file are in HADRON_PIN, beside the sha256 of every
|
|
205
|
+
* vendored byte — and the commit here is the directory the bytes live in.
|
|
206
|
+
*/
|
|
207
|
+
export function hadronOriginLabel() {
|
|
208
|
+
return `hadron-tx-verifier@${HADRON_PIN.commit}`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Adopt the verified first-party copy into a store through the PRODUCT's one
|
|
213
|
+
* write path (src/adopt.mjs — the same path `sememe adopt` uses).
|
|
214
|
+
*
|
|
215
|
+
* Order is the point: the pin is checked FIRST, so a tampered, missing or
|
|
216
|
+
* tail-less vendored file means NOTHING was written. The receipt's origin
|
|
217
|
+
* stays `declared`: the pin binds the BYTES to a commit, not the bytes to the
|
|
218
|
+
* deployed program.
|
|
219
|
+
*/
|
|
220
|
+
export async function adoptHadron(storeDir, { adopt } = {}) {
|
|
221
|
+
if (typeof adopt !== 'function') throw Error('adoptHadron needs the product write path (src/adopt.mjs)');
|
|
222
|
+
const loaded = loadVendoredHadron();
|
|
223
|
+
const receipt = await adopt(HADRON_PROGRAM, loaded.path, storeDir, {
|
|
224
|
+
source: 'published',
|
|
225
|
+
origin: hadronOriginLabel(),
|
|
226
|
+
});
|
|
227
|
+
return { receipt, loaded };
|
|
228
|
+
}
|
package/src/wire-layout.mjs
CHANGED
|
@@ -49,21 +49,25 @@
|
|
|
49
49
|
// tail after the points is 8 bytes for all 144 (an undeclared field the IDL
|
|
50
50
|
// does not name — reported as trailing, never invented).
|
|
51
51
|
|
|
52
|
-
const POINT_BYTES = 21;
|
|
53
|
-
|
|
54
52
|
/**
|
|
55
53
|
* The measured count-from-header table, keyed by program address.
|
|
56
54
|
*
|
|
57
55
|
* Every entry is a program+instruction pair whose wire layout was measured
|
|
58
56
|
* against chain-accepted payloads with a committed artifact; nothing here is
|
|
59
|
-
* inferred from an IDL claim or from a shape match.
|
|
57
|
+
* inferred from an IDL claim or from a shape match. `elementBytes` is the
|
|
58
|
+
* measured size of ONE vector element — a property of the element TYPE the
|
|
59
|
+
* measurement walked, never a global constant: CurvePointInput is 21 bytes
|
|
60
|
+
* (research/hadro-vec-wire-2026-11-13.json: 144/144 payloads fit n x 21),
|
|
61
|
+
* SpreadTriggerInput is 66 (research/live-layout-scan-hadro-witness-
|
|
62
|
+
* 2026-11-14.json: 2/2 chain-accepted live payloads numTriggers=5 x 66 =
|
|
63
|
+
* 330 body bytes, trailingBytes 0).
|
|
60
64
|
*/
|
|
61
65
|
export const COUNT_FROM_HEADER = {
|
|
62
66
|
HADRoNbLovyqhCsocfYQYB7QdfCAAinN9HTePvBCVDQ8: {
|
|
63
|
-
source: 'research/hadro-vec-wire-2026-11-13.json',
|
|
64
67
|
instructions: {
|
|
65
|
-
setCurve: { countArg: 'numPoints', vectorArg: 'points' },
|
|
66
|
-
setRiskCurve: { countArg: 'numPoints', vectorArg: 'points' },
|
|
68
|
+
setCurve: { countArg: 'numPoints', vectorArg: 'points', elementBytes: 21, source: 'research/hadro-vec-wire-2026-11-13.json' },
|
|
69
|
+
setRiskCurve: { countArg: 'numPoints', vectorArg: 'points', elementBytes: 21, source: 'research/hadro-vec-wire-2026-11-13.json' },
|
|
70
|
+
updateSpreadConfig: { countArg: 'numTriggers', vectorArg: 'triggers', elementBytes: 66, source: 'research/live-layout-scan-hadro-witness-2026-11-14.json' },
|
|
67
71
|
},
|
|
68
72
|
},
|
|
69
73
|
};
|
|
@@ -79,10 +83,10 @@ export const COUNT_FROM_HEADER = {
|
|
|
79
83
|
*
|
|
80
84
|
* @param {object} idl the IDL being decoded (must carry `address`)
|
|
81
85
|
* @param {string} name the instruction name being decoded/encoded
|
|
82
|
-
* @returns {{countArg: string, vectorArg: string,
|
|
86
|
+
* @returns {{countArg: string, vectorArg: string, elementBytes: number, source: string}|null}
|
|
83
87
|
*/
|
|
84
88
|
export function countFromHeader(idl, name) {
|
|
85
89
|
const entry = COUNT_FROM_HEADER[idl?.address]?.instructions?.[name];
|
|
86
90
|
if (!entry) return null;
|
|
87
|
-
return { ...entry
|
|
91
|
+
return { ...entry };
|
|
88
92
|
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to the Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by the Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding any notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
Copyright 2025 Squads Labs, Inc.
|
|
179
|
+
|
|
180
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
181
|
+
you may not use this file except in compliance with the License.
|
|
182
|
+
You may obtain a copy of the License at
|
|
183
|
+
|
|
184
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
185
|
+
|
|
186
|
+
Unless required by applicable law or agreed to in writing, software
|
|
187
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
188
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
189
|
+
See the License for the specific language governing permissions and
|
|
190
|
+
limitations under the License.
|