Files

外部 Worker SDK

SDK 用于把 Python、JavaScript、Go 或 C ABI 能力接入 Dever。应用先声明 Port,再用外部 Adapter 对接;Dever 管理 Worker 进程和通信。C ABI 运行在独立受管进程中,不是 Dever 主进程的任意 dlopen 或裸指针调用。

开发应用先看 三种语言的完整例子。那里包含同一 Port、三种 Adapter、处理函数和 fake 测试,不需要手写协议或维护另一份接口注册表。

处理函数怎么写

函数名与 Port 操作名对应。输入、输出和业务错误的类型由 Dever 编译器生成;Python/JavaScript/Go SDK 负责解码,C shim 自行处理借用的 wire JSON,所有结果还须通过 Runtime 的 Port 类型校验。

语言 处理函数 加载生成的接口定义
Python 异步函数 Worker.from_manifest(manifest, handlers)
JavaScript 函数接收 AbortSignal;支持 ESM 导出或 CommonJS 属性 Worker.fromManifest(manifest, handlers)
Go 函数接收 context.Context NewFromManifest(manifest),再 RegisterHandlers(handlers)
C/C++ dever_ffi_handler 的同步 C ABI 回调 dever_ffi_serve 读取编译器打包的 contract.json

处理函数返回能用 JSON 表达的值,业务失败返回已声明的 BusinessError。不要往 stdout 写日志;stdout 用于 Worker 通信,日志写 stderr。

Worker 调用不设固定执行倒计时,耗时推理可以持续等待。启动握手、协议写入和终止清理仍有短限时;调用方主动取消或应用停止时仍走既有取消和进程回收机制。SDK 不为每次调用创建容器或内层沙箱。

Python/JavaScript/Go 的协作取消会产生一次 dever.cancelled 错误,已取消调用的结果会被丢弃。如果处理函数一秒内不响应取消,Worker 失败并停止接收新调用。Dever 管理进程与 stdin;自行在 Go 进程内调用 Serve 时,调用方负责关闭或解除自己的 io.Reader 阻塞。C ABI 的同步调用终止边界见后文。

SDK 维护与验证

C ABI 的独立协议/动态库验收:/usr/bin/python3 -B test/external-sdk/test_c_ffi.py --cc /absolute/path/to/cc。测试在自有临时目录编译并调用真实 .so,结束清理;不代表 GPU 或完整发行已验收。

维护 SDK 时,显式指定本机运行环境的绝对路径:

/usr/bin/python3 -B test/external-sdk/check.py --python /usr/bin/python3 --node /absolute/path/to/node --go /absolute/path/to/go

未指定的语言报告 unavailable,不会通过 PATH 猜测。Go 夹具在临时离线缓存中构建。这项检查验证本机 SDK 和类型协议;不能代替发行包或“机器未安装 Python/Node/Go”的运行验收。普通应用测试使用 Port fake,不启动真实 Worker。

编译器通过 ExternalWorkerContract::sdk_manifest() 生成唯一接口定义,包括身份、配置、输入、输出和业务错误。SDK 在 hello 时核对身份,不接受 supervisor 临时指定的 schema。配置在 ready 前解码,只读取 hello.setting。

本目录的三个 example 展示底层 Contract/Worker 注册 API,用于协议夹具。fixture-schema-v1 和 test/external-sdk/typed_contract.json 都是测试数据,不能复制成生产 Port hash。通信帧由 4 字节大端长度和 UTF-8 JSON 组成。

SDK 不依赖第三方包,也不读取环境变量或 PATH。托管 Worker 只读取已打包的 manifest。Python 固定使用 -I -S -B,先加载打包的 SDK,再把 Adapter 与依赖加入 sys.path。

受管 C ABI

复用 external exec "worker/native" {},不引入另一套 FFI 语法或运行器。C/C++ shim 包含 c/dever_ffi.h,与 c/dever_ffi.c 一起预编译为对应平台 ELF;在 invoke 中调用现成库的 C ABI。编译由开发/发行环境显式完成,Dever run/build 不查找宿主编译器。不要把 C++ 异常跨过 C ABI 边界。

  • abi 必须为 DEVER_FFI_ABI_V1;open(context, setting) 在握手时初始化一次,invoke 串行处理操作,close 在正常退出或可处理故障时释放状态。每个 Worker 生命周期可复用已加载状态,但不保证跨独立 Dever invocation 保温。
  • operation 是借用的操作名字节;setting、payload 是借用的 JSON 切片,均不保证 NUL 结尾,只在当前回调有效,不得留存指针。
  • 输出缓冲由 SDK 分配,shim 只写 data[0..capacity] 并设置 size,不得替换指针/容量、释放缓冲或越界。业务值使用 Port 的 wire JSON;Runtime 按已编译 Port 校验实际输出类型、声明错误及深度/大小,不接受动态接口签名。
  • 返回 DEVER_FFI_OK 时输出为 JSON 结果;DEVER_FFI_ERROR 时输出为 {"error":"已声明错误身份","payload":...};其他状态关闭通道并报告运行故障。源码中的 Secret 不得写 stdout/stderr,stdout 仅作协议通信。
  • v1 同步原生调用不承诺协作取消,也没有隐式执行期限;调用方取消或应用退出时,清理宽限结束后由现有进程监管终止未退出的 Worker。崩溃或协议故障直接报错,不在主进程强杀线程,不自动重放失败调用。强制终止不保证执行 close,外部副作用应幂等。

静态 ELF 可直接打包;动态依赖放在入口旁的 lib/,只接收正常文件,不接收软链接。构建验证目标、SONAME、递归依赖和 ABI 版本;非系统依赖来自该目录;Linux 的解释器/基础系统 ABI 由运行容器提供并须匹配构建目标,不扫描宿主 Python、Node、Go 或编译器。例如 worker/native 对应 worker/lib/libengine.so。编译器同时打包唯一的 contract.json,不手写第二份 Port hash。C++ STL、跨语言对象、回调指针、可变参数及跨进程零拷贝不属于 v1 接口;需要时在 shim 内转为明确的 C 数据与 JSON 值。

开发验收在 test/dever-cli-tests/external_workers/ffi.rs:显式指定绝对路径 DEVER_FFI_CC(C 编译器)、DEVER_PROCESS_RUNTIME(已验证的目标 loader/libc 等运行库目录,仅读取 lib/),运行 cargo test --offline -p dever-cli --test external_workers ffi::real_c_abi_direct_worker_errors_deadlines_and_cancellation -- --ignored --exact。验证真实 .so 打包、Dever 应用执行、业务错误、原生崩溃、阻塞调用、取消和期限处理;测试不替换已安装编译器,不算 GPU/模型验收。原生调用不响应取消、导致协议清理超时时,当前调用报告运行故障,而不是伪装成成功的取消或超时回退。

发行验收还须用 DEVER_RUNTIME_ABI_ARCHIVE 指向实际候选归档并运行 native_runtime_link,尤其是 Bytes wire codec 用例;源码测试通过不能替代配套发行运行时的真实链接与执行。

执行边界与 GPU

Worker 和依赖构建都直接在当前容器中运行,由内核启动真实可执行文件(不以 GNU loader 充当进程入口,保留 Python/Node/Rust 的自身可执行路径和子进程行为)。目标容器须提供匹配的 Linux/glibc 基础 ABI,签名 process-runtime 库保留用于目标依赖核验,不用旧版 libc 替换容器加载器的配套 libc。不再创建 Bubblewrap/user/mount/PID/network namespace、附加 seccomp 或安装 Dever 专用 AppArmor 策略。dever-process 只负责明确的执行路径、运行库和进程组;超时、取消、退出会清理本次进程组。自行 setsid 脱离监管的守护进程不受此保证,Worker 不应自行后台化。

allow file/network/process/gpu 保留为编译期效果声明和握手契约,不是操作系统访问控制。Worker 具有当前容器用户已有的文件、网络和设备权限。旧 adapter.<port>.files / .gpu 挂载授权字段已移除,配置会明确拒绝;真实模型路径和输出目录放入业务 setting,由 Pod volume、UID/GID、NetworkPolicy 和设备分配实施边界。只运行可信代码,不用这套进程监管执行不可信租户程序。

GPU 使用父容器已经分配的设备和驱动,不再由 Dever 重新挂载。除已打包的私有库路径外,仅透传 CUDA_VISIBLE_DEVICES、NVIDIA_VISIBLE_DEVICES、NVIDIA_DRIVER_CAPABILITIES、CUDA_DEVICE_ORDER 与 LD_LIBRARY_PATH;其他宿主环境不自动传入。CUDA/PyTorch 等依赖仍需匹配目标平台。是否有 GPU、是否可加载驱动以及显存是否足够由真实部署决定,不会绕过 Kubernetes 调度或容器权限。设备验收须单独取得 GPU 准入;本次源码改造不等于已部署或模型推理通过。

Python 发行资源

应用容器的系统库基线与编译器开发机不同。不要直接复制开发容器的 Python:解释器和每个动态扩展都必须满足目标 glibc 基线;Python 版本相同不代表系统 ABI 相同。保留真实解释器入口,不改用私有 GNU loader,也不回退到应用容器的系统 Python。

sdk/python-runtime.py 从显式提供并锁定 SHA-256 的 Python Build Standalone install_only_stripped 归档生成输入,不联网下载、不安装服务。参数为 --archive、--sha256、--version、--target、--readelf、--output,默认 --glibc-max 2.17。输出目录必须不存在;工具检查路径、目标 ELF、所有 GLIBC 版本依赖、基础模块及迁移后的解释器身份,保留运行文件和 Python 许可证,不带 pip、第三方 site-packages 或测试缓存。

输出的 python-runtime.json 对应既有作者配置 runtimes.pip,其 source 相对输出根;合入同一作者根后仍由 native-release 校验、打包和签名,Python 继续是独立扩展,不塞进基础包。wheel 标签包含同一 CPython 的原生 ABI、兼容的 abi3 和纯 Python 标签;manylinux 不超过目标 glibc 基线,通用 linux_<架构> 用于本机源码构建产物,不承诺跨系统 ABI 兼容。源码归档的来源、SHA-256 和实际目标环境验收结果归本次发行记录;本地候选目录不能充当正式源或修改现行安装。

完整公开发行

正式附件使用 sdk/release-assets.py --editor-vsix <VSIX> 导出,默认执行完整性检查。必须包含独立基础包、x86_64/ARM64 的 Python/Node/Go 运行扩展、宿主 Python/npm 构建扩展、ARM64 原生目标扩展、同版本编辑器扩展,以及签名清单和引导文件。缺任一项就失败;部分资源只允许通过显式的 --partial-test-fixture 用于定向测试,不能当作完整公开版本。Go 扩展由 test/go-managed/make_pack.go 导出完整 std,不能只导出样例依赖。

.tar.zst 是 tar 文件集合经过 Zstandard 压缩,减少附件体积;它不是另一个运行环境。安装器使用已验签的引导程序解压,不要求使用者额外安装 zstd。Python、Node、Go 及构建工具继续按需单独下载,不并入基础包。

当前发行序列经项目所有者确认从 0.0.1 重新开始,旧 Release 与附件在替代包验收后清理,不删除源代码提交历史。资源格式固定为现行 process-runtime,不再根据版本号推断旧沙箱布局。旧版 0.1.x 安装不能通过普通升级降到 0.0.1;须在新的安装根验收后明确切换,保留项目和凭据。不要关闭签名、同版本身份检查或防降级检查来绕过此规则。后续版本变更由项目所有者确认,不因每次修改自动递增版本号。

定向测试:python3 -B -m unittest discover -s test/onboarding -p test_python_runtime.py -v。这些测试验证打包约束,不代替在目标容器中执行签名应用、真实模型和取消清理验收;x86_64 验收也不证明 ARM64 已验收。

跨架构作者验证可显式传入 --runner /absolute/qemu-aarch64 --sysroot /absolute/arm64-sysroot,仍执行相同解释器身份和基础模块探测,不跳过 ELF 检查;须记录为 QEMU 验收,而不是原生 ARM64 硬件验收。这两个参数不进入应用运行配置。

依赖与跨平台构建

dever lib install <project> 按现有锁恢复依赖,不重新选版本、不修改配置或锁。dever-lock-v6 记录 Python extras/wheel METADATA、npm 嵌套依赖及 peers/optional/bundled 依赖、Go 模块校验证据。原始包保留来源与文件名,构建记录保留完整输入,包括 npm 剪除可选构建依赖前的依赖图。

丢失的构建结果由同一受管构建链重建,必须与记录的摘要一致。Python 原生 wheel 安装到配套解释器中,必须满足签名声明的标签、扩展 ABI 和动态库依赖要求。

生成 ARM64 应用时,先运行 dever lib update <project> --target linux-aarch64,再运行 dever build <project> --target linux-aarch64 --output <new-file>。Port 和 Worker 源码无需改写;锁、解释器、原生依赖和进程运行库资源必须匹配目标平台。缺少目标资源时失败,不借用宿主运行环境。run/test 仍在本机执行。

Go pack 的 build.host 描述编译器、链接器与分析器的运行平台;build.goos/goarch 和标准库对象头描述输出平台。工具在清空的环境中执行,目标必须预先正确配置。

第三方源码构建限制

只有显式准备 Lib 时才构建 Python sdist 或运行 npm 安装钩子。构建使用签名工具包,在独立临时工作目录内直接运行受管子进程;不再提供内层文件/网络隔离,因此只运行可信依赖及构建钩子。缺少工具时不回退宿主 PATH。

直接执行 GCC/binutils 时,工具依赖的非 glibc 动态库从已验签的 SDK 在本次临时目录内建立硬链接,供既有受管库路径加载;不依赖宿主另装 libbfd 等作者工具库,不复制额外库数据,也不替换宿主 glibc/loader。Python 原生扩展和 npm 原生 addon 必须分别通过真实源码构建验收,纯 Python/JS 回退不算通过。

Python PEP 517 的构建依赖单独锁定,每个 hook 使用新进程,最终 wheel 按保留的元数据核对。npm 的配套运行器处理 preinstall/install/postinstall、依赖 bin 和 node-gyp。

构建记录绑定源码、目标运行环境、工具、构建前端、依赖图和输出摘要。Worker 准备阶段离线恢复结果,不重跑安装钩子。npm .node 产物须通过目标检查、动态加载检查和独立 Node 加载探测,才能进入 Worker 资源目录。不匹配平台或 ABI 的其他变体保留在锁定原始包中,不加入运行目录。运行时非系统依赖来自 Worker 目录,系统 ABI 由目标容器提供,不能借用编译器 sysroot。

生成的原生 addon 验证失败,整个构建就失败;使用纯 JS 备用路径不能证明原生构建成功。当前限制:

  • 构建进程最多 5 分钟,stdout/stderr 各最多 16 MiB。
  • 每 250 毫秒检查临时目录,限制 512 MiB、65,536 个条目;这是定期检查,检查间隔内可能短暂超出。
  • 最终 Worker 最多 256 MiB、65,536 个条目。失败时清理本次拥有的进程。