这篇把两个容易混在一起的问题拆开:
- NumPy 怎样被编译进
wasm32-wasip1的 CPython Guest? import numpy怎样从嵌入的 Python 文件走到已经静态链接的 C 扩展?
本文使用的源码和产物如下:
| 对象 | 版本 | 用途与现状 |
|---|---|---|
| 当前 Spine | Spine@190ea2e |
当前仓库只有基础 CPython/WASI Guest,没有 NumPy profile |
| 原始 profile 声明 | 只读仓库 agent-python-runtime@702f6b6 |
profiles/numpy-core.lock.json 声明了一个静态原生包 profile |
| 已存在的原始构建证据 | 既有 numpy-core-47070b1e2ae0 构建包 |
是原始仓库提交 47070b1e... 构建出的 NumPy artifact;Spine 当前使用另一份基础 Guest |
因此,下面的构建与导入分析使用原始 profile,运行记录对应既有 artifact;当前 Spine 的实际探针结果单列在第七节。
1. 先看完整产物链
原始仓库通过静态链接,把 NumPy 原生扩展和 CPython 合成同一个 Wasm:
锁定的 NumPy/Cython/setuptools 来源
│
▼
CPython 的 Linux host-build Python
│ 运行 setup.py + Cython
▼
NumPy Python 树 + 19 个 native extension .a
│ │
│ ├─ npymath.a
│ └─ npyrandom.a
▼ ▼
生成 builtin-registry.h runtime.c + CPython .a + WASI 支持库
│ │
└─────────────── clang/wasm32-wasip1 链接 ──► raw.wasm
│
CPython Lib + site-packages/numpy ── wasi-vfs pack ───────────┘
▼
numpy-core Guest artifact
这里有两个平行的“放进去”动作:
- C/C++ 部分通过
clang和链接器进入 Wasm:NumPy 的扩展初始化函数、NumPy 支持库、CPython 和 WASI libc 都变成同一个 reactor 中的代码。 - Python 文件部分通过
wasi-vfs进入 Wasm:numpy/__init__.py、numpy/core/*.py等文件变成 VFS 中的只读文件。
import numpy 需要两者同时存在:只有 Python 文件,找不到底层 _multiarray_umath;只有 .a,又没有 numpy/__init__.py 这层 Python 包。
来源(原始仓库 702f6b6):guest/build/profiles/numpy-core.lock.json:1-95; guest/build/recipes/numpy-static-v1.sh:1-132; guest/build/build-guest.sh:406-540。
2. CPython、WASI 和构建机分别是哪一层
2.1 三个“Python”不要混淆
原始配方里至少有三个执行层:
构建机上的 python3
└─ 启动 shell / Python 构建辅助脚本
CPYTHON_DIR/cross-build/build/python
└─ 构建 NumPy 时运行 setup.py、Cython 和 setuptools
它是交叉构建产生的 Linux host-build interpreter
最终 Wasm 中的 libpython3.14.a
└─ 真正运行用户代码和 NumPy
numpy-static-v1.sh 第 13 行把构建时运行的解释器命名为 HOST_PYTHON,由它执行 NumPy 的构建程序。最终 Guest 启动后,使用链接进 Wasm 的 libpython3.14.a。
扩展最终面向哪个平台,由 WASI SDK 编译器的 --target=wasm32-wasip1 等参数决定。本项目这条配方和 host-build 输入仍以 Linux 构建环境为前提。
2.2 wasm32-wasip1 和 Linux x86_64 描述不同对象
wasm32-wasip1:产物的目标架构和系统 ABI,即 32 位 Wasm + WASI Preview 1。Linux x86_64:原始构建输入和构建机的约束。WASI SDK、工具和交叉构建过程按这个主机环境准备。
构建工具在 Linux x86_64 上运行,输出则面向 wasm32-wasip1;这就是这里的交叉编译关系。
WASI 提供文件访问等系统接口,具体能力由 Host 的实例化配置授予。这个项目再用 wasi-vfs 把 Python 文件嵌入 Wasm,因此 Guest 能看到 /usr/lib/python3.14,但不会因此看到宿主机的同名目录。
2.3 静态库 .a 在这里意味着什么
.a 保存目标文件,供链接器在构建时使用。链接器从归档中取出需要的目标文件,把它们合入最终 Wasm。原始 NumPy profile 的目标是:
- 不依赖
dlopen去加载.so; - 不在 Guest 运行时安装 wheel;
- 把每一个允许的扩展明确列入锁文件、明确链接、明确注册。
3. 真实的 numpy-core profile 锁了什么
原始只读仓库的 guest/build/profiles/numpy-core.lock.json 在提交 702f6b6... 中声明:
| 项目 | 锁定值 |
|---|---|
| profile / kind | numpy-core / static-native-package |
| target | wasm32-wasip1 |
| NumPy | 1.26.0b1 |
| NumPy source commit | 7bc18034031f32e5d03bb646c472dabd1623e9d5 |
| NumPy source archive SHA-256 | 9a34aaef957033ff8a3a865e8f0172eb7de4cf4c2891195a56c13e915fb86014 |
| build-only Cython | 3.0.12 |
| build-only setuptools | 71.1.0 |
| CPython | 3.14.0 |
| wasi-sdk | 33 |
| recipe | numpy-static-v1 |
| 外部 BLAS/LAPACK 顺序 | 空 |
| SVML | 禁用 |
| NumPy 优化 | 禁用(--disable-optimization) |
| 额外 WASI 链接库 | c-printscan-long-double |
| package import root | numpy |
| 最大 Python 包树 | 128 MiB |
profile 还固定了参考来源 https://github.com/bkmashiro/wasi-wheels 的提交 184cce0b537088be76e1e8a06d6fe742e2f29ff4 及其普通构建、静态构建摘要。这些参考摘要用于绑定配方来源,不等于直接下载并信任一个外部预编译 Wasm。
3.1 19 个 native module
这 19 项是 Python 导入名、归档路径和 PyInit_* 初始化符号的三元组。按 NumPy 子树分组如下:
| 子树 | 模块 |
|---|---|
numpy.core(7) |
_multiarray_tests、_multiarray_umath、_operand_flag_tests、_rational_tests、_simd、_struct_ufunc_tests、_umath_tests |
numpy.fft(1) |
_pocketfft_internal |
numpy.linalg(2) |
_umath_linalg、lapack_lite |
numpy.random(9) |
_bounded_integers、_common、_generator、_mt19937、_pcg64、_philox、_sfc64、bit_generator、mtrand |
例如:
{
"name": "numpy.core._multiarray_umath",
"archive": "numpy/core/_multiarray_umath.a",
"init_symbol": "PyInit__multiarray_umath"
}
另外两项为扩展提供链接依赖:
numpy/lib/libnpymath.a
numpy/lib/libnpyrandom.a
因此完整的 NumPy link input 是 19 个扩展归档 + 2 个支持归档 = 21 个。支持归档不会单独出现在 importlib 的模块名列表中。
3.2 构建时选择 profile
原始仓库的 profile registry 把 numpy-core 和 numpy-static-v1 绑定起来,并列出需要的 import roots:agent_runtime、json、numpy、sys。profile 在构建时确定进入 artifact 的内容;运行中的 Guest 直接使用这些已打包组件。
锁文件的 policy 也明确写着:
runtime_package_installation = false
dynamic_native_loading = false
external_prebuilt_artifact = false
4. NumPy 的交叉构建:为什么要把 .so 变成 .a
guest/build/recipes/numpy-static-v1.sh 是理解这件事的关键。
4.1 先准备源码和离线构建环境
配方从已经校验过的下载目录解压 NumPy 和 Cython,然后用 HOST_PYTHON -m venv 创建构建虚拟环境:
HOST_PYTHON -m venv numpy-build-venv
numpy-build-venv/bin/python -m pip install --no-index --no-deps setuptools-71.1.0-py3-none-any.whl
numpy-build-venv/bin/python -m pip install --no-index --no-build-isolation --no-deps cython-source.tar.gz
--no-index 限制 pip 使用本地输入,--no-build-isolation 让构建复用已准备的环境。Cython 和 setuptools 只参与构建;它们的 Python 包不进入最终 NumPy 的运行时 package tree。
4.2 给 NumPy 提供“目标平台”的 sysconfig
配方寻找交叉构建输出中唯一的 _sysconfigdata_*.py,然后设置:
CC = wasi-sdk/bin/clang
AR = wasi-sdk/bin/llvm-ar
REAL_CXX = wasi-sdk/bin/clang++
_PYTHON_SYSCONFIGDATA_NAME = 目标 sysconfigdata 名
PYTHONPATH = CPython Lib + target sysconfig 目录 + WASI build 目录
CFLAGS/CXXFLAGS = --target=wasm32-wasip1 --sysroot=... + CPython 头文件
LDFLAGS = --target=wasm32-wasip1 --sysroot=...
其中 sysconfigdata 告诉 Python 构建系统目标解释器的编译和链接配置;PYTHONPATH 让 host-build Python 能读取目标侧的 CPython 配置。-D__EMSCRIPTEN__=1、-DNPY_NO_SIGNAL 是这份适配配方给 NumPy 的平台兼容开关,不表示目标实际上是 Emscripten。
4.3 fake CXX 的作用
NumPy 的传统构建流程倾向于把扩展链接成平台动态库,例如:
numpy/core/_multiarray_umath.cpython-314-...so
但当前 Guest 设计不让 Python 在运行时 dlopen 这些动态库。配方生成一个 fake_cxx.sh,只拦截输出名以 .so 或 .so.* 结尾的链接动作:
- 解析命令参数,收集其中的
.o目标文件; - 用
llvm-ar rcsD把这些目标文件归档为同名.a; - 创建一个空的
.so占位文件,让 NumPy 的构建流程认为该步骤已完成; - 对其他普通 C++ 输出,交给真正的
clang++。
构建目录会保留空 .so 作为占位文件。脚本先复制完整构建树到 numpy-native,再从最终 Python package tree 删除 .so、.a 等文件。链接步骤选择 numpy-native 中声明的 .a;占位 .so 不进入最终 Python 包。这个技巧把“构建系统以为在生成动态扩展”和“运行时采用静态注册”接起来。
4.4 生成两个树和它们的摘要
NumPy 构建完成后,配方会:
- 找到唯一的
lib.*构建包树; - 把
numpyPython 文件复制到numpy-native/numpy; - 在
numpy-native/numpy/lib/中保存libnpymath.a和libnpyrandom.a; - 再复制一份到
numpy-package/numpy作为最终包树; - 从最终包树删除所有
.a、.so、.pyc和空__pycache__。
native_package_profile.py prepare 随后计算:
- 包树中每个文件的路径、长度和内容摘要;
- 包树的
tree_sha256、文件数和总字节数; - 21 个归档各自的 SHA-256 和字节数;
- 配方、CPython、wasi-sdk、NumPy source identity。
extension-profile.json 记录本轮实际生成的包树摘要、归档摘要和大小。profile 预先约束源版本、模块列表和大小上限;成品树的摘要在构建后计算。
5. 从 .a 到可导入的 Python native module
5.1 先强迫链接器保留 19 个初始化函数
最终链接阶段从锁文件生成:
-Wl,-u,PyInit__multiarray_tests
-Wl,-u,PyInit__multiarray_umath
...
-Wl,-u,PyInit_mtrand
-u,symbol 把符号标成链接器必须解析的未定义符号。即使普通的静态归档提取规则认为某个目标文件暂时没有被引用,这些 PyInit_* 也会把对应目标拉进最终 Wasm。随后链接命令把 19 个 NumPy 归档、2 个支持归档、libpython3.14.a、mpdecimal、HACL、expat、wasi-vfs 和 -lc-printscan-long-double 组合起来。
这一步发生在 Python import 之前:它解决的是“代码是否存在于 Wasm”问题。
5.2 生成 builtin registry
native_package_profile.py registration-header 根据锁文件生成 builtin-registry.h,形状大致是:
extern PyObject *PyInit__multiarray_umath(void);
static int register_selected_builtins(void) {
if (PyImport_AppendInittab(
"numpy.core._multiarray_umath",
PyInit__multiarray_umath) != 0) return -1;
/* 其余 18 项同理 */
return 0;
}
在原始 guest/src/runtime.c 的 ensure_interpreter() 中,编译宏 AGENT_RUNTIME_EXTENSION_PROFILE 打开时,会在 Py_InitializeFromConfig 前调用 register_selected_builtins()。
PyImport_AppendInittab 是 CPython 的内置模块表注册 API。它把一个完整的 Python 模块名绑定到 C 初始化函数;因此 _multiarray_umath 不需要一个运行时可打开的 .so 文件。
5.3 finder 解决 Python 文件和 builtin 的混合布局
NumPy 的包入口和许多模块由 Python 文件组成,例如 numpy/__init__.py、numpy/core/multiarray.py 和 numpy/random/__init__.py。原始 artifact 把它们放在:
/usr/lib/python3.14/site-packages/numpy/
原始 guest/include/wasm_extension_finder.h 注入了一个小型 Python finder:
- 如果
_imp.is_builtin(fullname)为真,就返回BuiltinImporter的 spec; - 否则检查
/usr/lib/python3.14/site-packages/<fullname>/__init__.py; - 再检查
/usr/lib/python3.14/site-packages/<fullname>.py; - 把这个 site-packages 路径放入
sys.path; - 将自定义 finder 放在
PathFinder前面。
于是一次典型的导入是:
import numpy
└─ finder 找到 site-packages/numpy/__init__.py
└─ numpy 的 Python 代码继续请求 numpy.core._multiarray_umath
└─ _imp.is_builtin(...) == true
└─ BuiltinImporter 找到已注册的 PyInit__multiarray_umath
└─ 执行静态链接进 Wasm 的 C 初始化函数
这个流程里有两种不同的“找到”:
numpy这个包由嵌入 VFS 的.py文件找到;_multiarray_umath这个 native module 由 CPython builtin table 找到。
把 .a 文件放进 VFS 并不会自动使 Python 能导入它;把 .py 文件放进 VFS 也不会自动生成 C 扩展。registry、linker 和 finder 三者缺一不可。
5.4 CPython 的搜索路径必须包含两层
原始 runtime 初始化时设置了:
/usr/lib/python3.14
/usr/lib/python3.14/site-packages
第一层放 CPython 标准库,第二层放 agent_runtime 和 NumPy 等包。config.module_search_paths_set = 1 表示程序显式提供搜索路径;site_import = 0 表示不依赖宿主环境自动运行 site.py 来偷偷改变路径。路径和 finder 都被构建逻辑明确写入 artifact。
6. 原始 profile 的真实构建和导入证据
本次核对的既有构建包标识:
numpy-core-47070b1e2ae0/
其 RESULT.READY、dist/manifest.json、dist/import-inventory.json、dist/import-qualification.json 和 SHA256SUMS 给出了可检查的结果:
| 证据 | 实际值 |
|---|---|
| artifact | agent-python-runtime-numpy-core.wasm |
| Wasm SHA-256 | 1b759febddb9dd0d65760c74b21b07c4f04fa992ce761639fbb9baef155926aa |
| Wasm 大小 | 68,174,432 bytes |
| artifact source commit | 47070b1e2ae09e6292f2a5ce0dd9f8c4ee9f6a6c |
| target | wasm32-wasip1 |
| CPython / wasi-sdk | 3.14.0 / 33 |
| NumPy package tree | 442 files,8,593,450 bytes |
| native modules / support archives | 19 / 2 |
| NumPy tree SHA-256 | a14ce5de7729375c6a88a194f5e85c94e38f67ffe40265f71f25bb55b46c5c4f |
导入清单的 probe 是 guest-importlib-find-spec-v1,记录了 numpy 为可发现 root。执行资格测试 guest-import-exec-v1 继续运行了以下操作:
assert numpy.__version__ == "1.26.0b1"
value = numpy.arange(6, dtype=numpy.int64).reshape((2, 3))
assert (value * 2 + 1).tolist() == [[1, 3, 5], [7, 9, 11]]
assert value.flags.c_contiguous
value = numpy.asarray([0.5, -1.25, 3.0], dtype=numpy.dtype("<f8"))
restored = numpy.frombuffer(value.tobytes(order="C"), dtype=numpy.dtype("<f8"))
assert restored.tolist() == [0.5, -1.25, 3.0]
left = numpy.arange(6, dtype=numpy.int64).reshape((2, 3))
right = numpy.arange(6, dtype=numpy.int64).reshape((3, 2))
assert (left @ right).tolist() == [[10, 13], [28, 40]]
构建包的 import-qualification.json 记录 numpy_core_oracle: qualified。另外,原始仓库的 docs/evidence/numpy-core-phase2-mechanism-evidence-v1.json 记录了两次独立进程运行:结果相同,能力调用数为 [0, 0];scripts/review-numpy-core-phase2.py 对这些记录进行检查。
这些运行证据覆盖指定 oracle 中的数组、dtype、buffer roundtrip 和矩阵运算。19 个 native module 全部进入构建清单;逐模块执行覆盖还需要各自的测试记录。
这组运行记录对应 47070b1e... 构建包。当前源码检查使用 702f6b6...,Spine 又使用独立的基础 Guest;三个版本在本章开头分别列出。
7. 和当前 Spine 的逐项对照
7.1 当前 Spine 具备什么
当前 Spine 的输入准备锁 tools/build-inputs.lock.json 固定了 CPython 3.14.0、wasi-sdk 33.0、wasm-tools、wasmtime 和 wasi-vfs。tools/setup-build-inputs.py 在 Linux x86_64 上下载并编译 CPython/WASI 输入;build-guest.sh 再使用已有的 libpython3.14.a 链接自己的 C bridge。
当前构建链是:
CPython Lib
+ guest/bootstrap.py → spine_bootstrap.py
+ guest/plm.py
+ guest/prefix.py
│
▼
临时 $WORK/lib
│
└─ wasi-vfs pack --dir $WORK/lib::/usr/lib/python3.14
│
▼
dist/spine.wasm
当前 guest/runtime.c 只注册项目自己的 _spine builtin,并把 CPython 模块搜索路径设置为 /usr/lib/python3.14。当前链接命令没有 NumPy 的 .a、没有 -Wl,-u,PyInit_* 列表、没有生成的 NumPy registry,也没有 site-packages finder。
7.2 当前 Spine 尚未具备什么
当前仓库没有原始 profile 的这些构建输入或阶段:
原始 numpy-core 所需部件 |
当前 Spine |
|---|---|
guest/build/profiles/numpy-core.lock.json |
没有 |
guest/build/recipes/numpy-static-v1.sh |
没有 |
| NumPy source/Cython/setuptools 的 profile source lock | 没有 |
19 个 NumPy native .a |
没有 |
npymath.a / npyrandom.a |
没有 |
builtin-registry.h 与 19 个 PyInit_* 注册 |
没有 |
/usr/lib/python3.14/site-packages 打包 |
没有 |
| NumPy import finder | 没有 |
| import inventory / qualification sidecar | 没有 |
当前 dist/spine.wasm 的真实观察是:用NumPy 探针执行
go run ./cmd/demo -source learning/deep-probes/numpy_probe.py
得到:
{"python":"3.14.0","error_type":"ModuleNotFoundError","error":"No module named 'numpy'"}
探针得到的导入失败与当前构建配置一致:Spine 没有打包 NumPy,也没有注册相应的 native module。原始 numpy-core 包则包含这些组件。
7.3 当前 prepared/COW 和 NumPy profile 是两条轴
当前 Spine 的 prepared.go、cow_linux.go 和 runner.go 讨论的是 Guest 生命周期和内存复用:
prepared-copy保存初始化后的线性内存并全量复制;- Linux COW 使用私有映射共享初始化页;
- 两者都要求实际 Wasm artifact 先具备所需代码和文件。
它们不会把 NumPy 自动加入现有 artifact。即使把当前 spine.wasm 做成 prepared 或 COW,得到的仍是“没有 NumPy 的 CPython baseline”。包 profile 决定进入产物的代码和文件;prepared/COW 决定实例如何创建与恢复。
8. 从一次 ModuleNotFoundError 反推缺哪一层
遇到 NumPy 导入问题时,不要只看错误字符串,按下面顺序定位:
情况 A:No module named 'numpy'
通常先查 Python 包文件和搜索路径:
- VFS 是否有
/usr/lib/python3.14/site-packages/numpy/__init__.py? sys.path是否含/usr/lib/python3.14/site-packages?- 当前 artifact 的 manifest 标注了哪个 profile?
- manifest 的 profile、package tree 和 import inventory 是否一致?
当前 Spine 的结果正是这一类:它只把 CPython Lib 和项目 bootstrap 放到 /usr/lib/python3.14。
情况 B:numpy 找到了,但 _multiarray_umath 导入失败
Python package tree 可能已经打包,但 native 注册或链接没有闭合:
PyInit__multiarray_umath是否在最终 Wasm 中?-Wl,-u,PyInit__multiarray_umath是否执行?- 生成的 registry 是否在 CPython 初始化前调用?
numpy/core/_multiarray_umath.a是否为空或与 profile 不匹配?- 自定义 finder 是否把 builtin module 交给了
BuiltinImporter?
情况 C:导入成功,但操作失败
应结合执行资格记录和导入清单分别查看:
- import 失败:路径、finder、builtin table 或初始化函数问题;
- operation 失败:NumPy C 代码、dtype/布局、支持库或编译配置问题;
- capability 失败:Host bridge/WASI 能力边界问题。
原始 bundle 把这些阶段分开记录:inventory 证明“可发现”,qualification 证明“导入并执行选定 oracle”。
9. 构建时检查和运行时检查各自证明什么
| 检查 | 它真正证明的内容 |
|---|---|
| source lock SHA-256 | 下载的源码/工具输入与声明一致 |
fake_cxx.sh + .a 存在 |
NumPy 扩展目标可被收集为静态归档 |
native_package_profile.py prepare |
包树、21 个归档和锁定 profile 的身份绑定 |
-Wl,-u,PyInit_* 链接 |
选定的初始化符号被拉入最终 Wasm |
PyImport_AppendInittab |
CPython 知道这些完整模块名对应哪些初始化函数 |
wasi-vfs pack |
Python 文件进入 artifact 的嵌入 VFS |
wasm-tools validate |
Wasm 二进制格式和验证规则通过 |
| import inventory | Guest 的 find_spec 能发现哪些 root |
| import qualification | 固定 Guest 上具体导入和 NumPy 操作成功 |
| 当前 Spine 的 probe | 当前 dist/spine.wasm 的实际行为,不替代原始 profile 证据 |
特别要注意:wasm-tools validate 通过,不等于 import numpy 通过;C 链接成功,也不等于 Python 包文件已经打包。最终判定要落到目标 Guest 的真实运行。
10. 读源码时的最短路线
如果要继续追实现,按这条路线最省力:
- 当前 Spine 的入口:
build-guest.sh→guest/runtime.c。先确认当前 artifact 只含基础 CPython、_spine和项目 Python 文件。 - 当前输入身份:
tools/build-inputs.lock.json→tools/setup-build-inputs.py。它们只覆盖 CPython/WASI 基础输入。 - 原始 profile 锁:只读
agent-python-runtime@702f6b6:guest/build/profiles/numpy-core.lock.json。先读 19+2 的声明,再读 recipe。 - 原始 NumPy 配方:只读
guest/build/recipes/numpy-static-v1.sh。重点看HOST_PYTHON、fake CXX、sysconfig、包树和支持归档。 - 原始最终链接:只读
guest/build/build-guest.sh:453-528。这里把.a、PyInit_*、registry、CPython 和 VFS 拼成 artifact。 - 原始导入机制:只读
guest/include/wasm_extension_finder.h与guest/src/runtime.c:321-376。对照“Python 文件由 finder 找、native 模块由 builtin table 找”。 - 原始结果:读本机 bundle 的
dist/manifest.json、import-inventory.json、import-qualification.json和RESULT.READY,不要只凭 lock 文件推测构建已经成功。
11. 小预测题
- 删除
numpy/core/_multiarray_umath.a,但保留numpy/core/multiarray.py,最可能先在哪一步失败?为什么? - 把
.a归档放到 VFS,却不生成PyImport_AppendInittab注册,Python 能否仅凭文件名找到它? - 只有
PyInit__multiarray_umath被链接进 Wasm、但没有numpy/__init__.py,import numpy会发生什么? - 当前 Spine 把
site-packages加进sys.path,但不链接 NumPy.a,为什么仍不足以支持 NumPy? - 原始配方中的
HOST_PYTHON和最终 Guest 中的libpython3.14.a,哪一个运行numpy.arange? wasm-tools validate通过后,为什么还需要guest-import-exec-v1?
12. 本章边界与来源
本文只写文档,没有把原始 NumPy profile 移植到 Spine,也没有修改原始只读仓库。当前 Spine 的事实基于:
- Spine HEAD
190ea2e238bba454cdcc335e95736c3f27f6be9a; - 当前
build-guest.sh、guest/runtime.c、tools/setup-build-inputs.py和tools/build-inputs.lock.json; - 本次对当前
dist/spine.wasm的 NumPy probe 观察。
原始 profile 的事实基于只读 worktree:
agent-python-runtimeHEAD702f6b6cf48b65f878b057d0c6bce22e852068ee;guest/build/profiles/numpy-core.lock.json;guest/build/recipes/numpy-static-v1.sh;guest/build/native_package_profile.py;guest/build/build-guest.sh、guest/src/runtime.c、guest/include/wasm_extension_finder.h;- 本机实际 bundle
numpy-core-47070b1e2ae0的 manifest、sidecar 和校验文件。
构建输入的许可证、源码 URL 和长摘要以原始仓库的 lock 与 artifact notices 为准;本文只引用必要的身份信息,不把文档中的版本表当成新的下载锁文件。