PPysolate SpineREAD THE CODE · UNDERSTAND THE SYSTEM
课程与章节
DEEP DIVE / 01

CPython 与 NumPy 怎样进入 Wasm

静态扩展构建、注册与 Python 导入。

本章目录

这篇把两个容易混在一起的问题拆开:

  1. NumPy 怎样被编译进 wasm32-wasip1 的 CPython Guest?
  2. 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.* 结尾的链接动作:

  1. 解析命令参数,收集其中的 .o 目标文件;
  2. 用 llvm-ar rcsD 把这些目标文件归档为同名 .a;
  3. 创建一个空的 .so 占位文件,让 NumPy 的构建流程认为该步骤已完成;
  4. 对其他普通 C++ 输出,交给真正的 clang++。

构建目录会保留空 .so 作为占位文件。脚本先复制完整构建树到 numpy-native,再从最终 Python package tree 删除 .so、.a 等文件。链接步骤选择 numpy-native 中声明的 .a;占位 .so 不进入最终 Python 包。这个技巧把“构建系统以为在生成动态扩展”和“运行时采用静态注册”接起来。

4.4 生成两个树和它们的摘要

NumPy 构建完成后,配方会:

  • 找到唯一的 lib.* 构建包树;
  • 把 numpy Python 文件复制到 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:

  1. 如果 _imp.is_builtin(fullname) 为真,就返回 BuiltinImporter 的 spec;
  2. 否则检查 /usr/lib/python3.14/site-packages/<fullname>/__init__.py;
  3. 再检查 /usr/lib/python3.14/site-packages/<fullname>.py;
  4. 把这个 site-packages 路径放入 sys.path;
  5. 将自定义 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 包文件和搜索路径:

  1. VFS 是否有 /usr/lib/python3.14/site-packages/numpy/__init__.py?
  2. sys.path 是否含 /usr/lib/python3.14/site-packages?
  3. 当前 artifact 的 manifest 标注了哪个 profile?
  4. manifest 的 profile、package tree 和 import inventory 是否一致?

当前 Spine 的结果正是这一类:它只把 CPython Lib 和项目 bootstrap 放到 /usr/lib/python3.14。

情况 B:numpy 找到了,但 _multiarray_umath 导入失败

Python package tree 可能已经打包,但 native 注册或链接没有闭合:

  1. PyInit__multiarray_umath 是否在最终 Wasm 中?
  2. -Wl,-u,PyInit__multiarray_umath 是否执行?
  3. 生成的 registry 是否在 CPython 初始化前调用?
  4. numpy/core/_multiarray_umath.a 是否为空或与 profile 不匹配?
  5. 自定义 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. 读源码时的最短路线

如果要继续追实现,按这条路线最省力:

  1. 当前 Spine 的入口:build-guest.sh → guest/runtime.c。先确认当前 artifact 只含基础 CPython、_spine 和项目 Python 文件。
  2. 当前输入身份:tools/build-inputs.lock.json → tools/setup-build-inputs.py。它们只覆盖 CPython/WASI 基础输入。
  3. 原始 profile 锁:只读 agent-python-runtime@702f6b6:guest/build/profiles/numpy-core.lock.json。先读 19+2 的声明,再读 recipe。
  4. 原始 NumPy 配方:只读 guest/build/recipes/numpy-static-v1.sh。重点看 HOST_PYTHON、fake CXX、sysconfig、包树和支持归档。
  5. 原始最终链接:只读 guest/build/build-guest.sh:453-528。这里把 .a、PyInit_*、registry、CPython 和 VFS 拼成 artifact。
  6. 原始导入机制:只读 guest/include/wasm_extension_finder.h 与 guest/src/runtime.c:321-376。对照“Python 文件由 finder 找、native 模块由 builtin table 找”。
  7. 原始结果:读本机 bundle 的 dist/manifest.json、import-inventory.json、import-qualification.json 和 RESULT.READY,不要只凭 lock 文件推测构建已经成功。

11. 小预测题

  1. 删除 numpy/core/_multiarray_umath.a,但保留 numpy/core/multiarray.py,最可能先在哪一步失败?为什么?
  2. 把 .a 归档放到 VFS,却不生成 PyImport_AppendInittab 注册,Python 能否仅凭文件名找到它?
  3. 只有 PyInit__multiarray_umath 被链接进 Wasm、但没有 numpy/__init__.py,import numpy 会发生什么?
  4. 当前 Spine 把 site-packages 加进 sys.path,但不链接 NumPy .a,为什么仍不足以支持 NumPy?
  5. 原始配方中的 HOST_PYTHON 和最终 Guest 中的 libpython3.14.a,哪一个运行 numpy.arange?
  6. 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-runtime HEAD 702f6b6cf48b65f878b057d0c6bce22e852068ee;
  • 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 为准;本文只引用必要的身份信息,不把文档中的版本表当成新的下载锁文件。