@wavehouse/chtypes 0.5.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +34 -38
  3. package/dist/abi1/buildinfo.d.ts +40 -0
  4. package/dist/abi1/buildinfo.d.ts.map +1 -0
  5. package/dist/abi1/buildinfo.js +83 -0
  6. package/dist/abi1/buildinfo.js.map +1 -0
  7. package/dist/abi1/calls.gen.d.ts +55 -0
  8. package/dist/abi1/calls.gen.d.ts.map +1 -0
  9. package/dist/abi1/calls.gen.js +110 -0
  10. package/dist/abi1/calls.gen.js.map +1 -0
  11. package/dist/abi1/decls.gen.d.ts +115 -0
  12. package/dist/abi1/decls.gen.d.ts.map +1 -0
  13. package/dist/abi1/decls.gen.js +1613 -0
  14. package/dist/abi1/decls.gen.js.map +1 -0
  15. package/dist/abi1/errmap.gen.d.ts +18 -0
  16. package/dist/abi1/errmap.gen.d.ts.map +1 -0
  17. package/dist/abi1/errmap.gen.js +66 -0
  18. package/dist/abi1/errmap.gen.js.map +1 -0
  19. package/dist/abi1/errors.d.ts +107 -0
  20. package/dist/abi1/errors.d.ts.map +1 -0
  21. package/dist/abi1/errors.js +134 -0
  22. package/dist/abi1/errors.js.map +1 -0
  23. package/dist/abi1/handles.d.ts +31 -0
  24. package/dist/abi1/handles.d.ts.map +1 -0
  25. package/dist/abi1/handles.js +76 -0
  26. package/dist/abi1/handles.js.map +1 -0
  27. package/dist/abi1/index.d.ts +17 -0
  28. package/dist/abi1/index.d.ts.map +1 -0
  29. package/dist/abi1/index.js +17 -0
  30. package/dist/abi1/index.js.map +1 -0
  31. package/dist/abi1/libc.d.ts +54 -0
  32. package/dist/abi1/libc.d.ts.map +1 -0
  33. package/dist/abi1/libc.gen.d.ts +12 -0
  34. package/dist/abi1/libc.gen.d.ts.map +1 -0
  35. package/dist/abi1/libc.gen.js +75 -0
  36. package/dist/abi1/libc.gen.js.map +1 -0
  37. package/dist/abi1/libc.js +109 -0
  38. package/dist/abi1/libc.js.map +1 -0
  39. package/dist/abi1/loader.d.ts +99 -0
  40. package/dist/abi1/loader.d.ts.map +1 -0
  41. package/dist/abi1/loader.js +261 -0
  42. package/dist/abi1/loader.js.map +1 -0
  43. package/dist/abi1/raw.d.ts +104 -0
  44. package/dist/abi1/raw.d.ts.map +1 -0
  45. package/dist/abi1/raw.js +296 -0
  46. package/dist/abi1/raw.js.map +1 -0
  47. package/dist/abi1/strictjson.d.ts +19 -0
  48. package/dist/abi1/strictjson.d.ts.map +1 -0
  49. package/dist/abi1/strictjson.js +78 -0
  50. package/dist/abi1/strictjson.js.map +1 -0
  51. package/dist/abi1/vocab.gen.d.ts +169 -0
  52. package/dist/abi1/vocab.gen.d.ts.map +1 -0
  53. package/dist/abi1/vocab.gen.js +306 -0
  54. package/dist/abi1/vocab.gen.js.map +1 -0
  55. package/dist/cli.d.ts +25 -26
  56. package/dist/cli.d.ts.map +1 -1
  57. package/dist/cli.js +206 -218
  58. package/dist/cli.js.map +1 -1
  59. package/dist/documents.d.ts +195 -0
  60. package/dist/documents.d.ts.map +1 -0
  61. package/dist/documents.js +389 -0
  62. package/dist/documents.js.map +1 -0
  63. package/dist/env.d.ts +16 -0
  64. package/dist/env.d.ts.map +1 -0
  65. package/dist/env.js +57 -0
  66. package/dist/env.js.map +1 -0
  67. package/dist/index.d.ts +19 -24
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +17 -23
  70. package/dist/index.js.map +1 -1
  71. package/dist/json.d.ts +11 -89
  72. package/dist/json.d.ts.map +1 -1
  73. package/dist/json.js +16 -180
  74. package/dist/json.js.map +1 -1
  75. package/dist/library.d.ts +53 -309
  76. package/dist/library.d.ts.map +1 -1
  77. package/dist/library.js +77 -315
  78. package/dist/library.js.map +1 -1
  79. package/dist/ocifetch/constants.gen.d.ts +84 -0
  80. package/dist/ocifetch/constants.gen.d.ts.map +1 -0
  81. package/dist/ocifetch/constants.gen.js +92 -0
  82. package/dist/ocifetch/constants.gen.js.map +1 -0
  83. package/dist/ocifetch/dsse.d.ts +114 -0
  84. package/dist/ocifetch/dsse.d.ts.map +1 -0
  85. package/dist/ocifetch/dsse.js +331 -0
  86. package/dist/ocifetch/dsse.js.map +1 -0
  87. package/dist/ocifetch/ensure.d.ts +65 -0
  88. package/dist/ocifetch/ensure.d.ts.map +1 -0
  89. package/dist/ocifetch/ensure.js +775 -0
  90. package/dist/ocifetch/ensure.js.map +1 -0
  91. package/dist/ocifetch/errors.d.ts +92 -0
  92. package/dist/ocifetch/errors.d.ts.map +1 -0
  93. package/dist/ocifetch/errors.js +101 -0
  94. package/dist/ocifetch/errors.js.map +1 -0
  95. package/dist/ocifetch/goldens.d.ts +44 -0
  96. package/dist/ocifetch/goldens.d.ts.map +1 -0
  97. package/dist/ocifetch/goldens.js +62 -0
  98. package/dist/ocifetch/goldens.js.map +1 -0
  99. package/dist/ocifetch/http.d.ts +124 -0
  100. package/dist/ocifetch/http.d.ts.map +1 -0
  101. package/dist/ocifetch/http.js +549 -0
  102. package/dist/ocifetch/http.js.map +1 -0
  103. package/dist/ocifetch/index.d.ts +17 -0
  104. package/dist/ocifetch/index.d.ts.map +1 -0
  105. package/dist/ocifetch/index.js +14 -0
  106. package/dist/ocifetch/index.js.map +1 -0
  107. package/dist/ocifetch/layout.d.ts +126 -0
  108. package/dist/ocifetch/layout.d.ts.map +1 -0
  109. package/dist/ocifetch/layout.js +375 -0
  110. package/dist/ocifetch/layout.js.map +1 -0
  111. package/dist/ocifetch/localverify.d.ts +43 -0
  112. package/dist/ocifetch/localverify.d.ts.map +1 -0
  113. package/dist/ocifetch/localverify.js +164 -0
  114. package/dist/ocifetch/localverify.js.map +1 -0
  115. package/dist/ocifetch/lock.d.ts +39 -0
  116. package/dist/ocifetch/lock.d.ts.map +1 -0
  117. package/dist/ocifetch/lock.js +140 -0
  118. package/dist/ocifetch/lock.js.map +1 -0
  119. package/dist/ocifetch/oci.d.ts +100 -0
  120. package/dist/ocifetch/oci.d.ts.map +1 -0
  121. package/dist/ocifetch/oci.js +323 -0
  122. package/dist/ocifetch/oci.js.map +1 -0
  123. package/dist/ocifetch/referrers.d.ts +55 -0
  124. package/dist/ocifetch/referrers.d.ts.map +1 -0
  125. package/dist/ocifetch/referrers.js +132 -0
  126. package/dist/ocifetch/referrers.js.map +1 -0
  127. package/dist/ocifetch/types.d.ts +167 -0
  128. package/dist/ocifetch/types.d.ts.map +1 -0
  129. package/dist/ocifetch/types.js +88 -0
  130. package/dist/ocifetch/types.js.map +1 -0
  131. package/dist/ocifetch/unpack.d.ts +55 -0
  132. package/dist/ocifetch/unpack.d.ts.map +1 -0
  133. package/dist/ocifetch/unpack.js +205 -0
  134. package/dist/ocifetch/unpack.js.map +1 -0
  135. package/dist/registry.d.ts +38 -387
  136. package/dist/registry.d.ts.map +1 -1
  137. package/dist/registry.js +94 -819
  138. package/dist/registry.js.map +1 -1
  139. package/dist/schema.d.ts +77 -588
  140. package/dist/schema.d.ts.map +1 -1
  141. package/dist/schema.js +87 -549
  142. package/dist/schema.js.map +1 -1
  143. package/dist/settings.d.ts +27 -36
  144. package/dist/settings.d.ts.map +1 -1
  145. package/dist/settings.js +75 -25
  146. package/dist/settings.js.map +1 -1
  147. package/dist/setup.d.ts +37 -0
  148. package/dist/setup.d.ts.map +1 -0
  149. package/dist/setup.js +60 -0
  150. package/dist/setup.js.map +1 -0
  151. package/dist/tar.d.ts +27 -11
  152. package/dist/tar.d.ts.map +1 -1
  153. package/dist/tar.js +54 -32
  154. package/dist/tar.js.map +1 -1
  155. package/package.json +2 -2
  156. package/dist/discover.d.ts +0 -142
  157. package/dist/discover.d.ts.map +0 -1
  158. package/dist/discover.js +0 -288
  159. package/dist/discover.js.map +0 -1
  160. package/dist/error-codes.d.ts +0 -80
  161. package/dist/error-codes.d.ts.map +0 -1
  162. package/dist/error-codes.js +0 -139
  163. package/dist/error-codes.js.map +0 -1
  164. package/dist/errors.d.ts +0 -284
  165. package/dist/errors.d.ts.map +0 -1
  166. package/dist/errors.js +0 -321
  167. package/dist/errors.js.map +0 -1
  168. package/dist/fetch.d.ts +0 -382
  169. package/dist/fetch.d.ts.map +0 -1
  170. package/dist/fetch.js +0 -1498
  171. package/dist/fetch.js.map +0 -1
  172. package/dist/ffi.d.ts +0 -410
  173. package/dist/ffi.d.ts.map +0 -1
  174. package/dist/ffi.js +0 -1154
  175. package/dist/ffi.js.map +0 -1
  176. package/dist/format.d.ts +0 -128
  177. package/dist/format.d.ts.map +0 -1
  178. package/dist/format.js +0 -132
  179. package/dist/format.js.map +0 -1
  180. package/dist/numeric.d.ts +0 -20
  181. package/dist/numeric.d.ts.map +0 -1
  182. package/dist/numeric.js +0 -107
  183. package/dist/numeric.js.map +0 -1
  184. package/dist/paths.d.ts +0 -55
  185. package/dist/paths.d.ts.map +0 -1
  186. package/dist/paths.js +0 -87
  187. package/dist/paths.js.map +0 -1
  188. package/dist/results.d.ts +0 -495
  189. package/dist/results.d.ts.map +0 -1
  190. package/dist/results.js +0 -406
  191. package/dist/results.js.map +0 -1
  192. package/dist/transform.d.ts +0 -73
  193. package/dist/transform.d.ts.map +0 -1
  194. package/dist/transform.js +0 -533
  195. package/dist/transform.js.map +0 -1
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The TS loader's trap and its fix (plan §3.3, "THE TS TRAP").
3
+ *
4
+ * ffi-rs opens a library through libloading with `RTLD_LAZY | RTLD_LOCAL`
5
+ * (measured: v0 `ts/src/registry.ts`'s own header comment states this of
6
+ * ffi-rs 1.3.7, and nothing in ffi-rs's public API — `open`, `define`,
7
+ * `load` — takes a dlopen mode flag). `RTLD_LAZY` defers symbol binding
8
+ * until first use, so a library with an UNRESOLVED external symbol (the
9
+ * `unbound` stub variant) would `dlopen` successfully under ffi-rs and only
10
+ * fail later, if ever, when something actually calls the missing function —
11
+ * which step 2 of the loader (D3/§3.2) requires NEVER happen: a v1 loader
12
+ * must refuse an artifact that cannot bind completely, at open time.
13
+ *
14
+ * The fix: do the REAL `dlopen(path, RTLD_NOW | RTLD_LOCAL)` ourselves,
15
+ * through libc directly, before ffi-rs ever touches the path. `RTLD_NOW`
16
+ * binds every relocation immediately, so `unbound` fails exactly where it
17
+ * must.
18
+ *
19
+ * This file is HAND-WRITTEN and declares nothing itself: the actual libc
20
+ * declarations and the symbol-lookup primitive live in the GENERATED
21
+ * `./libc.gen.ts` (scripts/abi-v1/emit/ts.py — see its module docstring).
22
+ * That split is the lead's 2026-10-02 ruling: check-no-hand-decls.py's
23
+ * `dlsym(` rule exists so every raw symbol lookup lives in generated code,
24
+ * exempt by construction, rather than being worked around in a hand file
25
+ * with a renamed table key or a call site shaped to dodge the text match.
26
+ * This file calls the generated exports only, by their own names.
27
+ */
28
+ import { type JsExternal } from 'ffi-rs';
29
+ export declare const RTLD_NOW_LOCAL: number;
30
+ /** A NUL-terminated, ASCII/UTF-8-safe C string at `ptr`, read via the generated `cStringLength` (strlen) + a zero-copy view. Never call this on a pointer that may carry non-UTF-8 or embedded-NUL bytes (an ABI document body): it is for libc's own always-ASCII strings and `chs_build_info`'s guaranteed-ASCII, NUL-terminated JSON only. */
31
+ declare function readCString(ptr: JsExternal): string;
32
+ /** `dlopen(path, RTLD_NOW | RTLD_LOCAL)` through the generated primitive (plan §3.3(b)). Returns the raw OS handle, or null on failure — call `lastDlError()` immediately after a null to get libc's own message, before any other libc call. */
33
+ export declare function rawDlopen(path: string): JsExternal | null;
34
+ /** The loader's symbol-presence check (plan §3.3(c)), through the generated `resolveSymbol` helper: the raw symbol address, or null if `name` is not exported by `handle`'s image (this file never calls through the returned pointer — only ffi-rs's own by-name `define`/`load`, on an already-opened path, does that). */
35
+ export declare function rawDlsym(handle: JsExternal, name: string): JsExternal | null;
36
+ /** libc's own diagnostic for the most recent failing open/resolve call, read ONCE (POSIX clears it on read) and only ever called right after that failure. */
37
+ export declare function lastDlError(): string;
38
+ /**
39
+ * The glibc version string (`gnu_get_libc_version()`), or null when the
40
+ * symbol is absent — which means this is musl, not glibc (plan step 1:
41
+ * "Absent → musl → refuse"). Linux only; a caller on darwin never calls
42
+ * this (step 1 is a no-op there).
43
+ */
44
+ export declare function glibcVersionString(): string | null;
45
+ /**
46
+ * Dotted-integer version comparison (`"2.29"` vs `"2.5"`, never lexical):
47
+ * negative if `a` < `b`, positive if `a` > `b`, 0 if equal. Missing trailing
48
+ * components compare as 0 (`"2"` == `"2.0"`).
49
+ */
50
+ export declare function compareDottedVersions(a: string, b: string): number;
51
+ /** Open the target library's path through ffi-rs, under `libraryKey`, for typed calls (plan §3.3(d)). A harmless re-open when `path` is already the image our own `rawDlopen` just bound: dlopen (and the libloading ffi-rs uses underneath) dedupes by canonical path, returning the same already-mapped, already-bound image rather than re-relocating it. Declares no symbol of its own — `open()` only registers a path under a key for a later `define()` — so it is unaffected by the generated/hand-written split above. */
52
+ export declare function ffiOpen(libraryKey: string, path: string): void;
53
+ export { readCString };
54
+ //# sourceMappingURL=libc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"libc.d.ts","sourceRoot":"","sources":["../../src/abi1/libc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAwB,KAAK,UAAU,EAAQ,MAAM,QAAQ,CAAC;AAwBrE,eAAO,MAAM,cAAc,QAAwB,CAAC;AAEpD,iVAAiV;AACjV,iBAAS,WAAW,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAI5C;AAED,iPAAiP;AACjP,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAEzD;AAED,6TAA6T;AAC7T,wBAAgB,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAE5E;AAED,8JAA8J;AAC9J,wBAAgB,WAAW,IAAI,MAAM,CAGpC;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,IAAI,CAGlD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAWlE;AAED,mgBAAmgB;AACngB,wBAAgB,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAK9D;AAID,OAAO,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { type JsExternal } from 'ffi-rs';
2
+ /** dlopen(path, flags) against the process's own libc. Null on failure. */
3
+ export declare function dlopen(path: string, flags: number): JsExternal | null;
4
+ /** THE one symbol-lookup helper: dlsym(handle, name). Null if `name` is not exported by `handle`'s image. */
5
+ export declare function resolveSymbol(handle: JsExternal, name: string): JsExternal | null;
6
+ /** dlerror(). Null if there is no pending error (POSIX clears it on read, so call this ONCE, right after a failure). */
7
+ export declare function dlerror(): JsExternal | null;
8
+ /** gnu_get_libc_version(). Null if the symbol does not resolve at all (musl) or the call itself returns null. */
9
+ export declare function gnuGetLibcVersion(): JsExternal | null;
10
+ /** strlen(ptr): the length of a NUL-terminated C string, for a byte-safe read via createExternalBuffer. */
11
+ export declare function cStringLength(ptr: JsExternal): number;
12
+ //# sourceMappingURL=libc.gen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"libc.gen.d.ts","sourceRoot":"","sources":["../../src/abi1/libc.gen.ts"],"names":[],"mappings":"AAeA,OAAO,EAAmC,KAAK,UAAU,EAAQ,MAAM,QAAQ,CAAC;AA0ChF,2EAA2E;AAC3E,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAGrE;AAED,6GAA6G;AAC7G,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAGjF;AAED,wHAAwH;AACxH,wBAAgB,OAAO,IAAI,UAAU,GAAG,IAAI,CAG3C;AAED,iHAAiH;AACjH,wBAAgB,iBAAiB,IAAI,UAAU,GAAG,IAAI,CAOrD;AAED,2GAA2G;AAC3G,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAErD"}
@@ -0,0 +1,75 @@
1
+ /* GENERATED by scripts/abi-v1/gen.py from spec/abi-v1/abi.json (CHS_ABI_FINGERPRINT sha256:856e5cb7543db291b4db716662bf6e487b2258973b959bbf5d58b4bd46d73285) — DO NOT EDIT */
2
+ /*
3
+ * The libc primitives ts/src/abi1/libc.ts needs for plan §3.3's TS-trap fix: dlopen,
4
+ * dlsym, dlerror, gnu_get_libc_version and strlen, declared through ffi-rs's define()
5
+ * against the PROCESS'S OWN libc, by their real C names. This file carries the generated
6
+ * banner, so scripts/abi-v1/check-no-hand-decls.py exempts it outright — the check's own
7
+ * intent (every raw symbol lookup lives in generated code) is satisfied for real here,
8
+ * never worked around with a renamed key or a disguised call site in a hand file. See
9
+ * scripts/abi-v1/emit/ts.py's module docstring.
10
+ *
11
+ * `resolveSymbol` is THE ONE symbol-lookup helper hand code uses (dlsym plus the null
12
+ * check); the other four exports are the raw primitives dlopen/dlerror/gnu_get_libc_version/
13
+ * strlen need, each with its own null handling where the C function can return NULL.
14
+ */
15
+ import { DataType, define, isNullPointer, open } from 'ffi-rs';
16
+ const { External, I32, U64, String: Str } = DataType;
17
+ const LIBC_KEY = 'chtypes_abi1_libc';
18
+ function libcPath() {
19
+ if (process.platform === 'darwin')
20
+ return '/usr/lib/libSystem.B.dylib';
21
+ if (process.platform === 'linux')
22
+ return 'libc.so.6';
23
+ throw new Error(`chtypes abi1: unsupported platform ${process.platform}; only linux and darwin are v1 platforms`);
24
+ }
25
+ let opened = false;
26
+ function ensureOpen() {
27
+ if (opened)
28
+ return;
29
+ open({ library: LIBC_KEY, path: libcPath() });
30
+ opened = true;
31
+ }
32
+ let fns = null;
33
+ function libc() {
34
+ ensureOpen();
35
+ if (fns === null) {
36
+ fns = define({
37
+ dlopen: { library: LIBC_KEY, retType: External, paramsType: [Str, I32] },
38
+ dlsym: { library: LIBC_KEY, retType: External, paramsType: [External, Str] },
39
+ dlerror: { library: LIBC_KEY, retType: External, paramsType: [] },
40
+ gnu_get_libc_version: { library: LIBC_KEY, retType: External, paramsType: [] },
41
+ strlen: { library: LIBC_KEY, retType: U64, paramsType: [External] },
42
+ });
43
+ }
44
+ return fns;
45
+ }
46
+ /** dlopen(path, flags) against the process's own libc. Null on failure. */
47
+ export function dlopen(path, flags) {
48
+ const h = libc().dlopen([path, flags]);
49
+ return isNullPointer(h) ? null : h;
50
+ }
51
+ /** THE one symbol-lookup helper: dlsym(handle, name). Null if `name` is not exported by `handle`'s image. */
52
+ export function resolveSymbol(handle, name) {
53
+ const p = libc().dlsym([handle, name]);
54
+ return isNullPointer(p) ? null : p;
55
+ }
56
+ /** dlerror(). Null if there is no pending error (POSIX clears it on read, so call this ONCE, right after a failure). */
57
+ export function dlerror() {
58
+ const p = libc().dlerror([]);
59
+ return isNullPointer(p) ? null : p;
60
+ }
61
+ /** gnu_get_libc_version(). Null if the symbol does not resolve at all (musl) or the call itself returns null. */
62
+ export function gnuGetLibcVersion() {
63
+ try {
64
+ const p = libc().gnu_get_libc_version([]);
65
+ return isNullPointer(p) ? null : p;
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ }
71
+ /** strlen(ptr): the length of a NUL-terminated C string, for a byte-safe read via createExternalBuffer. */
72
+ export function cStringLength(ptr) {
73
+ return Number(libc().strlen([ptr]));
74
+ }
75
+ //# sourceMappingURL=libc.gen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"libc.gen.js","sourceRoot":"","sources":["../../src/abi1/libc.gen.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * The TS loader's trap and its fix (plan §3.3, "THE TS TRAP").
3
+ *
4
+ * ffi-rs opens a library through libloading with `RTLD_LAZY | RTLD_LOCAL`
5
+ * (measured: v0 `ts/src/registry.ts`'s own header comment states this of
6
+ * ffi-rs 1.3.7, and nothing in ffi-rs's public API — `open`, `define`,
7
+ * `load` — takes a dlopen mode flag). `RTLD_LAZY` defers symbol binding
8
+ * until first use, so a library with an UNRESOLVED external symbol (the
9
+ * `unbound` stub variant) would `dlopen` successfully under ffi-rs and only
10
+ * fail later, if ever, when something actually calls the missing function —
11
+ * which step 2 of the loader (D3/§3.2) requires NEVER happen: a v1 loader
12
+ * must refuse an artifact that cannot bind completely, at open time.
13
+ *
14
+ * The fix: do the REAL `dlopen(path, RTLD_NOW | RTLD_LOCAL)` ourselves,
15
+ * through libc directly, before ffi-rs ever touches the path. `RTLD_NOW`
16
+ * binds every relocation immediately, so `unbound` fails exactly where it
17
+ * must.
18
+ *
19
+ * This file is HAND-WRITTEN and declares nothing itself: the actual libc
20
+ * declarations and the symbol-lookup primitive live in the GENERATED
21
+ * `./libc.gen.ts` (scripts/abi-v1/emit/ts.py — see its module docstring).
22
+ * That split is the lead's 2026-10-02 ruling: check-no-hand-decls.py's
23
+ * `dlsym(` rule exists so every raw symbol lookup lives in generated code,
24
+ * exempt by construction, rather than being worked around in a hand file
25
+ * with a renamed table key or a call site shaped to dodge the text match.
26
+ * This file calls the generated exports only, by their own names.
27
+ */
28
+ import { createExternalBuffer, open } from 'ffi-rs';
29
+ import { cStringLength, dlerror, dlopen, gnuGetLibcVersion, resolveSymbol } from './libc.gen.js';
30
+ /**
31
+ * RTLD_NOW | RTLD_LOCAL, read from this platform's own headers, not from
32
+ * memory (plan: "The RTLD constants differ between Linux and darwin; get
33
+ * them from the platform, not from memory"):
34
+ *
35
+ * - glibc (x86_64-linux-gnu/bits/dlfcn.h) and musl (dlfcn.h) AGREE:
36
+ * RTLD_LAZY=1, RTLD_NOW=2, RTLD_LOCAL=0, RTLD_GLOBAL=0x100.
37
+ * RTLD_LOCAL is the absence of RTLD_GLOBAL, not a bit of its own.
38
+ * - darwin (measured from this Mac's SDK,
39
+ * `$(xcrun --show-sdk-path)/usr/include/dlfcn.h`):
40
+ * RTLD_LAZY=0x1, RTLD_NOW=0x2, RTLD_LOCAL=0x4, RTLD_GLOBAL=0x8.
41
+ * RTLD_LOCAL is a REAL bit here, and omitting it would ask for the
42
+ * default (which Apple's dlopen(3) documents as RTLD_GLOBAL) — the
43
+ * opposite of what D2's per-image symbol scope requires.
44
+ *
45
+ * So `RTLD_NOW | RTLD_LOCAL` is numerically 2 on Linux and 6 on darwin:
46
+ * computed here from named bits, never copied as a bare "2".
47
+ */
48
+ const RTLD = process.platform === 'darwin' ? { NOW: 0x2, LOCAL: 0x4 } : { NOW: 0x2, LOCAL: 0x0 };
49
+ export const RTLD_NOW_LOCAL = RTLD.NOW | RTLD.LOCAL;
50
+ /** A NUL-terminated, ASCII/UTF-8-safe C string at `ptr`, read via the generated `cStringLength` (strlen) + a zero-copy view. Never call this on a pointer that may carry non-UTF-8 or embedded-NUL bytes (an ABI document body): it is for libc's own always-ASCII strings and `chs_build_info`'s guaranteed-ASCII, NUL-terminated JSON only. */
51
+ function readCString(ptr) {
52
+ const len = cStringLength(ptr);
53
+ if (len === 0)
54
+ return '';
55
+ return Buffer.from(createExternalBuffer(ptr, len)).toString('utf8');
56
+ }
57
+ /** `dlopen(path, RTLD_NOW | RTLD_LOCAL)` through the generated primitive (plan §3.3(b)). Returns the raw OS handle, or null on failure — call `lastDlError()` immediately after a null to get libc's own message, before any other libc call. */
58
+ export function rawDlopen(path) {
59
+ return dlopen(path, RTLD_NOW_LOCAL);
60
+ }
61
+ /** The loader's symbol-presence check (plan §3.3(c)), through the generated `resolveSymbol` helper: the raw symbol address, or null if `name` is not exported by `handle`'s image (this file never calls through the returned pointer — only ffi-rs's own by-name `define`/`load`, on an already-opened path, does that). */
62
+ export function rawDlsym(handle, name) {
63
+ return resolveSymbol(handle, name);
64
+ }
65
+ /** libc's own diagnostic for the most recent failing open/resolve call, read ONCE (POSIX clears it on read) and only ever called right after that failure. */
66
+ export function lastDlError() {
67
+ const p = dlerror();
68
+ return p === null ? '(no dlerror message)' : readCString(p);
69
+ }
70
+ /**
71
+ * The glibc version string (`gnu_get_libc_version()`), or null when the
72
+ * symbol is absent — which means this is musl, not glibc (plan step 1:
73
+ * "Absent → musl → refuse"). Linux only; a caller on darwin never calls
74
+ * this (step 1 is a no-op there).
75
+ */
76
+ export function glibcVersionString() {
77
+ const ptr = gnuGetLibcVersion();
78
+ return ptr === null ? null : readCString(ptr);
79
+ }
80
+ /**
81
+ * Dotted-integer version comparison (`"2.29"` vs `"2.5"`, never lexical):
82
+ * negative if `a` < `b`, positive if `a` > `b`, 0 if equal. Missing trailing
83
+ * components compare as 0 (`"2"` == `"2.0"`).
84
+ */
85
+ export function compareDottedVersions(a, b) {
86
+ const as = a.split('.').map((s) => Number.parseInt(s, 10));
87
+ const bs = b.split('.').map((s) => Number.parseInt(s, 10));
88
+ const n = Math.max(as.length, bs.length);
89
+ for (let i = 0; i < n; i++) {
90
+ const av = as[i] ?? 0;
91
+ const bv = bs[i] ?? 0;
92
+ if (Number.isNaN(av) || Number.isNaN(bv))
93
+ return Number.NaN;
94
+ if (av !== bv)
95
+ return av - bv;
96
+ }
97
+ return 0;
98
+ }
99
+ /** Open the target library's path through ffi-rs, under `libraryKey`, for typed calls (plan §3.3(d)). A harmless re-open when `path` is already the image our own `rawDlopen` just bound: dlopen (and the libloading ffi-rs uses underneath) dedupes by canonical path, returning the same already-mapped, already-bound image rather than re-relocating it. Declares no symbol of its own — `open()` only registers a path under a key for a later `define()` — so it is unaffected by the generated/hand-written split above. */
100
+ export function ffiOpen(libraryKey, path) {
101
+ // Once per key: a load that failed after this point (a cross-check mismatch, a missing symbol) and is retried reaches here again for the same image.
102
+ if (ffiOpened.has(libraryKey))
103
+ return;
104
+ open({ library: libraryKey, path });
105
+ ffiOpened.add(libraryKey);
106
+ }
107
+ const ffiOpened = new Set();
108
+ export { readCString };
109
+ //# sourceMappingURL=libc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"libc.js","sourceRoot":"","sources":["../../src/abi1/libc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * The ABI v1 loader (plan §3, steps 1-7). Hand-written (the plan's "about 200
3
+ * lines per binding" FIRM piece); everything it reaches for — `rawDlopen`/
4
+ * `rawDlsym`/`glibcVersionString` (`./libc.ts`), `defineRawFunctions`/
5
+ * `rawCall` (`./raw.ts`), `DESCRIBED_SYMBOLS`/`SYMBOL`/`ABI_FINGERPRINT`/
6
+ * `CROSS_CHECK` (`./decls.gen.ts`) — is generated or generic, so this file
7
+ * never spells a `chs_` name itself.
8
+ *
9
+ * STEPS, IN ORDER (plan §3.2; `spec/abi-v1/sdk.json`'s `loader.refusals`
10
+ * names each reason):
11
+ *
12
+ * 1. glibc (Linux only; darwin skips it). BEFORE dlopen.
13
+ * 2. `dlopen(path, RTLD_NOW | RTLD_LOCAL)`, through `./libc.ts` (plan
14
+ * §3.3(b): the real fix for ffi-rs's own `RTLD_LAZY` open).
15
+ * 3. `chs_abi_version`: present (`dlsym`, plan §3.3(c)) and, once present,
16
+ * `=== 1` (a real call — only now safe through ffi-rs's own by-name
17
+ * `open`/`define`, plan §3.3(d), since step 2 already proved the image
18
+ * binds completely under `RTLD_NOW`).
19
+ * 4. `chs_build_info`: present, parses (ASCII, no duplicate keys at any
20
+ * nesting depth, `schema === 1`), and its `abi_fingerprint` matches
21
+ * this binding's own compiled-in `ABI_FINGERPRINT` byte for byte.
22
+ * 5. the nine `CROSS_CHECK` fields (`spec/abi-v1/sdk.json`) against
23
+ * `LoadInput.predicate`.
24
+ * 6. every OTHER described symbol, present (`dlsym` only — no call).
25
+ * 7. `chs_initialize(zone)`, then `chs_set_defaults(defaults)` when there are
26
+ * any, once per image, from the process setup the caller passes in
27
+ * (`LoadInput.timezone` / `defaults`). A failure here is the CALL's own
28
+ * error, mapped by the status table — never a loader refusal reason.
29
+ *
30
+ * No `dlclose`, ever (plan: "v0 measured a segfault on reopen"). Images are
31
+ * deduplicated by realpath + `dev:ino` (plan §3.2), re-implemented here
32
+ * rather than imported from any v0 file (plan §3.4: this directory touches
33
+ * no v0 file).
34
+ *
35
+ * `openUnverified` (plan §3.1) is the one path that skips steps 1 and 5: for
36
+ * core's own local builds and the (not-yet-built) linked mode, gated on BOTH
37
+ * an explicit flag and `CHTYPES_ALLOW_UNVERIFIED_LIBRARY=1`, never the
38
+ * default.
39
+ */
40
+ import { type BuildInfo } from './buildinfo.js';
41
+ import { Calls } from './calls.gen.js';
42
+ import { type RawApi } from './raw.js';
43
+ /** The predicate a loaded artifact is checked against: the verified signed statement's own fields, passed through verbatim (plan §3.1 — "predicate is passed verbatim, as the seam promises"). Only the fields this loader reads are typed; a real predicate carries more, and the fetch layer's own `ArtifactPredicate` satisfies this shape as it is. */
44
+ export interface Predicate {
45
+ readonly abi: number;
46
+ readonly abi_fingerprint: string;
47
+ readonly clickhouse_version: string;
48
+ readonly channel: string;
49
+ readonly build: string;
50
+ readonly os: string;
51
+ readonly arch: string;
52
+ readonly core_commit: string;
53
+ readonly inputs_sha256: string;
54
+ readonly glibc_floor?: string | undefined;
55
+ }
56
+ /** The process setup step 7 applies to every image: the image zone and the default settings. */
57
+ export interface ImageSetup {
58
+ /** The image zone `chs_initialize` is given (an IANA name); absent or empty means UTC. */
59
+ readonly timezone?: string;
60
+ /** Seeded once with `chs_set_defaults` when there are any. */
61
+ readonly defaults?: Readonly<Record<string, string>>;
62
+ }
63
+ /** The loader's own input, decoupled from the fetch layer's `Resolved` type: the adapter in `../registry.ts` joins them and passes the predicate verbatim. */
64
+ export interface LoadInput extends ImageSetup {
65
+ readonly libraryPath: string;
66
+ readonly predicate: Predicate;
67
+ readonly platform: string;
68
+ }
69
+ /** One opened, verified ABI v1 image: the resolved raw table, its typed call wrappers, and the decoded build info. */
70
+ export declare class LoadedImage {
71
+ readonly path: string;
72
+ readonly raw: RawApi;
73
+ readonly calls: Calls;
74
+ readonly buildInfo: BuildInfo;
75
+ constructor(path: string, raw: RawApi, buildInfo: BuildInfo);
76
+ get version(): string;
77
+ }
78
+ /**
79
+ * Load, verify and open one ABI v1 artifact (plan §3.2, steps 1-7). Returns
80
+ * the SAME `LoadedImage` for a path, a hardlink, a symlink or a second
81
+ * spelling of an already-loaded image: steps 2-4, 6 and 7 run once per image.
82
+ * An already-open image is still checked against every new signed statement a
83
+ * request brings (steps 1 and 5; step 4's fingerprint is the binding's own
84
+ * constant, which the first load already matched): a mismatch refuses that
85
+ * request and leaves the image open for the requests it did match.
86
+ */
87
+ export declare function openAbi1(input: LoadInput): LoadedImage;
88
+ /**
89
+ * Load `path` with NO predicate: skips step 1 (nothing to check a floor
90
+ * against) and step 5 (nothing to cross-check), still runs 2, 3, 4, 6 and 7.
91
+ * Refuses with a `UsageError` unless BOTH `options.allow` is true AND
92
+ * `CHTYPES_ALLOW_UNVERIFIED_LIBRARY=1` is set; warns loudly, once per path,
93
+ * when it proceeds. Never the default path — a local, trusted build is its
94
+ * only caller.
95
+ */
96
+ export declare function openUnverified(path: string, options: {
97
+ readonly allow: boolean;
98
+ } & ImageSetup): LoadedImage;
99
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/abi1/loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAIH,OAAO,EAAE,KAAK,SAAS,EAAmB,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAIvC,OAAO,EAAsB,KAAK,MAAM,EAAW,MAAM,UAAU,CAAC;AAEpE,2VAA2V;AAC3V,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C;AAED,gGAAgG;AAChG,MAAM,WAAW,UAAU;IACzB,0FAA0F;IAC1F,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACtD;AAED,8JAA8J;AAC9J,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,sHAAsH;AACtH,qBAAa,WAAW;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAE9B,YAAY,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAK1D;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;CACF;AAsID;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,WAAW,CAkBtD;AA0BD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,UAAU,GAAG,WAAW,CAe3G"}
@@ -0,0 +1,261 @@
1
+ /**
2
+ * The ABI v1 loader (plan §3, steps 1-7). Hand-written (the plan's "about 200
3
+ * lines per binding" FIRM piece); everything it reaches for — `rawDlopen`/
4
+ * `rawDlsym`/`glibcVersionString` (`./libc.ts`), `defineRawFunctions`/
5
+ * `rawCall` (`./raw.ts`), `DESCRIBED_SYMBOLS`/`SYMBOL`/`ABI_FINGERPRINT`/
6
+ * `CROSS_CHECK` (`./decls.gen.ts`) — is generated or generic, so this file
7
+ * never spells a `chs_` name itself.
8
+ *
9
+ * STEPS, IN ORDER (plan §3.2; `spec/abi-v1/sdk.json`'s `loader.refusals`
10
+ * names each reason):
11
+ *
12
+ * 1. glibc (Linux only; darwin skips it). BEFORE dlopen.
13
+ * 2. `dlopen(path, RTLD_NOW | RTLD_LOCAL)`, through `./libc.ts` (plan
14
+ * §3.3(b): the real fix for ffi-rs's own `RTLD_LAZY` open).
15
+ * 3. `chs_abi_version`: present (`dlsym`, plan §3.3(c)) and, once present,
16
+ * `=== 1` (a real call — only now safe through ffi-rs's own by-name
17
+ * `open`/`define`, plan §3.3(d), since step 2 already proved the image
18
+ * binds completely under `RTLD_NOW`).
19
+ * 4. `chs_build_info`: present, parses (ASCII, no duplicate keys at any
20
+ * nesting depth, `schema === 1`), and its `abi_fingerprint` matches
21
+ * this binding's own compiled-in `ABI_FINGERPRINT` byte for byte.
22
+ * 5. the nine `CROSS_CHECK` fields (`spec/abi-v1/sdk.json`) against
23
+ * `LoadInput.predicate`.
24
+ * 6. every OTHER described symbol, present (`dlsym` only — no call).
25
+ * 7. `chs_initialize(zone)`, then `chs_set_defaults(defaults)` when there are
26
+ * any, once per image, from the process setup the caller passes in
27
+ * (`LoadInput.timezone` / `defaults`). A failure here is the CALL's own
28
+ * error, mapped by the status table — never a loader refusal reason.
29
+ *
30
+ * No `dlclose`, ever (plan: "v0 measured a segfault on reopen"). Images are
31
+ * deduplicated by realpath + `dev:ino` (plan §3.2), re-implemented here
32
+ * rather than imported from any v0 file (plan §3.4: this directory touches
33
+ * no v0 file).
34
+ *
35
+ * `openUnverified` (plan §3.1) is the one path that skips steps 1 and 5: for
36
+ * core's own local builds and the (not-yet-built) linked mode, gated on BOTH
37
+ * an explicit flag and `CHTYPES_ALLOW_UNVERIFIED_LIBRARY=1`, never the
38
+ * default.
39
+ */
40
+ import { realpathSync, statSync } from 'node:fs';
41
+ import { decodeBuildInfo } from './buildinfo.js';
42
+ import { Calls } from './calls.gen.js';
43
+ import { ABI_FINGERPRINT, CROSS_CHECK, DESCRIBED_SYMBOLS, SYMBOL } from './decls.gen.js';
44
+ import { ArtifactIncompatibleError, LoaderCorruptError, UsageError, usageError } from './errors.js';
45
+ import { compareDottedVersions, ffiOpen, glibcVersionString, lastDlError, rawDlopen, rawDlsym } from './libc.js';
46
+ import { defineRawFunctions, rawCall } from './raw.js';
47
+ /** One opened, verified ABI v1 image: the resolved raw table, its typed call wrappers, and the decoded build info. */
48
+ export class LoadedImage {
49
+ path;
50
+ raw;
51
+ calls;
52
+ buildInfo;
53
+ constructor(path, raw, buildInfo) {
54
+ this.path = path;
55
+ this.raw = raw;
56
+ this.calls = new Calls(raw);
57
+ this.buildInfo = buildInfo;
58
+ }
59
+ get version() {
60
+ return this.buildInfo.clickhouseVersion;
61
+ }
62
+ }
63
+ function refuseIncompatible(path, reason, want, got) {
64
+ throw new ArtifactIncompatibleError({ reason, path, want, got });
65
+ }
66
+ function refuseCorrupt(path, reason, want, got) {
67
+ throw new LoaderCorruptError({ reason, path, want, got });
68
+ }
69
+ // --------------------------------------------------------------- image cache
70
+ const loadedByKey = new Map();
71
+ /** The typed call table per image, so a load retried after a late refusal declares it once. */
72
+ const rawTables = new Map();
73
+ const keyBySpelling = new Map();
74
+ /** The key `dlopen` will dedupe this path to (realpath + dev:ino, or an already-known spelling's key), per plan §3.2. */
75
+ function resolveImageKey(path) {
76
+ let key;
77
+ let resolved;
78
+ try {
79
+ const st = statSync(path, { bigint: true });
80
+ key = `${st.dev}:${st.ino}`;
81
+ resolved = realpathSync(path);
82
+ }
83
+ catch (err) {
84
+ throw new ArtifactIncompatibleError({
85
+ reason: 'dlopen',
86
+ path,
87
+ got: `cannot stat this path: ${err instanceof Error ? err.message : String(err)}`,
88
+ });
89
+ }
90
+ const known = keyBySpelling.get(path) ?? keyBySpelling.get(resolved);
91
+ const realKey = known ?? key;
92
+ keyBySpelling.set(path, realKey);
93
+ keyBySpelling.set(resolved, realKey);
94
+ return realKey;
95
+ }
96
+ // ------------------------------------------------------------------- step 1
97
+ function checkGlibc(path, predicate, platform) {
98
+ if (!platform.startsWith('linux'))
99
+ return; // darwin: skip (plan §3.2 step 1)
100
+ const actual = glibcVersionString();
101
+ if (actual === null) {
102
+ refuseIncompatible(path, 'no_glibc', predicate.glibc_floor, '(musl, or gnu_get_libc_version did not resolve)');
103
+ }
104
+ const floor = predicate.glibc_floor;
105
+ if (floor === undefined) {
106
+ refuseIncompatible(path, 'predicate_malformed', 'a glibc_floor field', '(absent)');
107
+ }
108
+ if (compareDottedVersions(actual, floor) < 0) {
109
+ refuseIncompatible(path, 'glibc_floor', floor, actual);
110
+ }
111
+ }
112
+ // ------------------------------------------------------------------- step 2
113
+ function openImage(path) {
114
+ const handle = rawDlopen(path);
115
+ if (handle === null)
116
+ refuseIncompatible(path, 'dlopen', undefined, lastDlError());
117
+ return handle;
118
+ }
119
+ // ------------------------------------------------------------------- step 5
120
+ /** `abi_fingerprint` -> `abiFingerprint`: the decoded `BuildInfo`'s own spelling of a build-info key. */
121
+ function camelOf(key) {
122
+ return key.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
123
+ }
124
+ function crossCheckField(got, want, compare) {
125
+ if (compare === 'int')
126
+ return Number(got) === Number(want);
127
+ return String(got) === String(want);
128
+ }
129
+ function checkCrossFields(path, buildInfo, predicate) {
130
+ const pred = predicate;
131
+ const info = buildInfo;
132
+ for (const field of CROSS_CHECK) {
133
+ const got = info[camelOf(field.buildInfo)];
134
+ const want = pred[field.predicate];
135
+ if (!crossCheckField(got, want, field.compare)) {
136
+ refuseCorrupt(path, `build_info_mismatch:${field.buildInfo}`, JSON.stringify(want), JSON.stringify(got));
137
+ }
138
+ }
139
+ }
140
+ // ---------------------------------------------------------------- the whole
141
+ /**
142
+ * Steps 2-4 and 6, shared by the verified and the unverified path: open the
143
+ * image `RTLD_NOW`, prove the generation, read and decode the build info and
144
+ * compare its fingerprint, resolve every other described symbol for presence.
145
+ */
146
+ function openAndCheck(path, key) {
147
+ const osHandle = openImage(path); // step 2
148
+ // step 3: chs_abi_version present, then (through ffi-rs, now safe) === 1.
149
+ if (rawDlsym(osHandle, SYMBOL.ABI_VERSION) === null) {
150
+ refuseIncompatible(path, 'not_v1', '1', '(the generation handshake is not exported)');
151
+ }
152
+ ffiOpen(key, path); // plan §3.3(d): only now, after RTLD_NOW already bound the image.
153
+ let raw = rawTables.get(key);
154
+ if (raw === undefined) {
155
+ raw = defineRawFunctions(key);
156
+ rawTables.set(key, raw);
157
+ }
158
+ const abiVersionCall = rawCall(raw, SYMBOL.ABI_VERSION, []);
159
+ const abiVersion = abiVersionCall.outcome === 'value' ? Number(abiVersionCall.value) : Number.NaN;
160
+ if (abiVersion !== 1)
161
+ refuseIncompatible(path, 'abi_version', '1', String(abiVersion));
162
+ // step 4: the build info present, decoded (malformed is corrupt), fingerprint matches.
163
+ if (rawDlsym(osHandle, SYMBOL.BUILD_INFO) === null) {
164
+ refuseIncompatible(path, `missing_symbol:${SYMBOL.BUILD_INFO}`, undefined, `(${SYMBOL.BUILD_INFO} is not exported)`);
165
+ }
166
+ const buildInfoCall = rawCall(raw, SYMBOL.BUILD_INFO, []);
167
+ const buildInfoText = buildInfoCall.outcome === 'value' ? buildInfoCall.value : null;
168
+ if (buildInfoText === null)
169
+ refuseCorrupt(path, 'build_info_malformed', 'a JSON object', 'null');
170
+ const buildInfo = decodeBuildInfo(path, buildInfoText);
171
+ if (buildInfo.abiFingerprint !== ABI_FINGERPRINT) {
172
+ refuseIncompatible(path, 'fingerprint', ABI_FINGERPRINT, buildInfo.abiFingerprint);
173
+ }
174
+ // step 6: every other described symbol, present.
175
+ for (const sym of DESCRIBED_SYMBOLS) {
176
+ if (sym === SYMBOL.ABI_VERSION || sym === SYMBOL.BUILD_INFO)
177
+ continue; // already proved present above
178
+ if (rawDlsym(osHandle, sym) === null) {
179
+ refuseIncompatible(path, `missing_symbol:${sym}`, undefined, `(${sym} is not exported)`);
180
+ }
181
+ }
182
+ return { raw, buildInfo };
183
+ }
184
+ /**
185
+ * Load, verify and open one ABI v1 artifact (plan §3.2, steps 1-7). Returns
186
+ * the SAME `LoadedImage` for a path, a hardlink, a symlink or a second
187
+ * spelling of an already-loaded image: steps 2-4, 6 and 7 run once per image.
188
+ * An already-open image is still checked against every new signed statement a
189
+ * request brings (steps 1 and 5; step 4's fingerprint is the binding's own
190
+ * constant, which the first load already matched): a mismatch refuses that
191
+ * request and leaves the image open for the requests it did match.
192
+ */
193
+ export function openAbi1(input) {
194
+ const path = input.libraryPath;
195
+ const key = resolveImageKey(path);
196
+ const cached = loadedByKey.get(key);
197
+ if (cached !== undefined) {
198
+ checkGlibc(path, input.predicate, input.platform); // step 1
199
+ checkCrossFields(path, cached.buildInfo, input.predicate); // step 5
200
+ return cached;
201
+ }
202
+ checkGlibc(path, input.predicate, input.platform); // step 1
203
+ const { raw, buildInfo } = openAndCheck(path, key); // steps 2-4, 6
204
+ checkCrossFields(path, buildInfo, input.predicate); // step 5, before step 7 touches the image
205
+ const image = new LoadedImage(path, raw, buildInfo);
206
+ initializeImage(image, input); // step 7
207
+ loadedByKey.set(key, image);
208
+ return image;
209
+ }
210
+ /** Step 7: `chs_initialize(zone)`, then `chs_set_defaults` when there are defaults. A failure is the call's own error, mapped by the status table; the zone asked for is named in the message either way. */
211
+ function initializeImage(image, setup) {
212
+ const timezone = setup.timezone ?? '';
213
+ try {
214
+ image.calls.initialize(Buffer.from(timezone, 'utf8'));
215
+ }
216
+ catch (err) {
217
+ if (err instanceof UsageError) {
218
+ // The library names the spelling it already holds; name the one asked for too.
219
+ throw usageError(`a different image zone was asked for (${JSON.stringify(timezone === '' ? 'UTC' : timezone)}) than the one this image already holds: ${err.messageBytes.toString('utf8')}`);
220
+ }
221
+ throw err;
222
+ }
223
+ const defaults = setup.defaults;
224
+ if (defaults !== undefined && Object.keys(defaults).length > 0) {
225
+ image.calls.setDefaults(Buffer.from(JSON.stringify(defaults), 'utf8'));
226
+ }
227
+ }
228
+ // ----------------------------------------------------------------- unverified
229
+ const UNVERIFIED_ENV = 'CHTYPES_ALLOW_UNVERIFIED_LIBRARY';
230
+ /**
231
+ * Load `path` with NO predicate: skips step 1 (nothing to check a floor
232
+ * against) and step 5 (nothing to cross-check), still runs 2, 3, 4, 6 and 7.
233
+ * Refuses with a `UsageError` unless BOTH `options.allow` is true AND
234
+ * `CHTYPES_ALLOW_UNVERIFIED_LIBRARY=1` is set; warns loudly, once per path,
235
+ * when it proceeds. Never the default path — a local, trusted build is its
236
+ * only caller.
237
+ */
238
+ export function openUnverified(path, options) {
239
+ if (!options.allow || process.env[UNVERIFIED_ENV] !== '1') {
240
+ throw usageError(`opening ${path} unverified requires BOTH allow: true and ${UNVERIFIED_ENV}=1`);
241
+ }
242
+ warnUnverifiedOnce(path);
243
+ const key = resolveImageKey(path);
244
+ const cached = loadedByKey.get(key);
245
+ if (cached !== undefined)
246
+ return cached;
247
+ const { raw, buildInfo } = openAndCheck(path, key);
248
+ const image = new LoadedImage(path, raw, buildInfo);
249
+ initializeImage(image, options);
250
+ loadedByKey.set(key, image);
251
+ return image;
252
+ }
253
+ const warnedPaths = new Set();
254
+ function warnUnverifiedOnce(path) {
255
+ if (warnedPaths.has(path))
256
+ return;
257
+ warnedPaths.add(path);
258
+ console.warn(`chtypes: ${path} was opened UNVERIFIED (${UNVERIFIED_ENV}=1): no predicate was checked. ` +
259
+ 'Never use this path for an artifact that came from anywhere but a local, trusted build.');
260
+ }
261
+ //# sourceMappingURL=loader.js.map