开发

托管运行时资源生产

候选资源生产器为托管安装生成固定输入:完整的 Server Python 与依赖树,以及单独的 Ops 工具包。最终用户不需要在系统中安装 Python、uv、Git 或编译器。这是 #2112 的资源生产部分;Desktop 安装包组装与公开发布另行完成。

所有输出都明确标记为未资格验收的候选资源。脚本不接受把构建直接标为生产合格的开关。构建与 CI 冒烟通过不代表完成了签名、 公证、干净机器运行或 Task Scheduler/LaunchAgent 生命周期验收。脚本不会发布资源,也不会推测生产下载地址。

固定输入与构建主机

分别在 Windows x86_64、macOS arm64 和 macOS x86_64 上原生构建,不需要先完成 Linux 构建,也不支持交叉编译。构建主机需要 Git、curl、uv 0.13.5 与锁定的 resource-build 依赖组。该引导环境可导入 Ops 资源/模型契约,不安装 Server 传输层、cryptography、 jieba 或 maturin。先安装完整 server/ops extra 会在固定构建器运行前触发环境中的 Intel cryptography 源码构建。 开发环境的工具和可编辑安装不会复制到资源中。

scripts/managed_resource_interpreters.json 固定 CPython 3.12.16、Astral python-build-standalone 标签 20261026, 以及每个目标平台的完整 install-only 下载地址与 SHA-256:

目标上游产物后缀SHA-256
windows-x86_64x86_64-pc-windows-msvc-install_only.tar.gz590dfcbe3a34c07301e6332e13e2188b98655cd072b08afa45a7998dc4c22bc5
macos-arm64aarch64-apple-darwin-install_only.tar.gzfbf4afef53c2a6b72bb741bc06d5671a9dfaf87bd84e5aa73ac9b3d0d589f14e
macos-x86_64x86_64-apple-darwin-install_only.tar.gza962e47df522889eaa7ff0d6a56203f1079f68aabb65ffdbe8455f362bc769f3

这些文件来自上游 20261026 发布,使用其文档定义的 安装目录树。生产器保留解释器与各依赖在树内的许可说明,并在两个资源中 生成 resource-licenses.json,记录声明、许可文件与实际 wheel 摘要的关系。该索引不是法律合规认证。

准备脚本要求精确的 40 位 Git 源提交与规范化包版本。项目 wheel 直接从 git archive 快照构建,未提交文件、可编辑安装和开发环境 不会混入。构建时使用显式 setuptools-scm 版本覆盖,并核对生成的 wheel 与安装后元数据。inputs.json 记录源提交、源码归档、 uv.lock、pyproject.toml、项目 wheel、解释器、构建工具闭包及所有下载/构建 wheel 的精确身份。执行生产器的 Git 提交与脚本摘要 单独记录,不能与被打包的 Server 源提交混为一谈。matches_committed_files=false 表示执行了工作区中的生产器,不能称为已提交的发布构建器。

运行依赖从该源提交锁定的 ops 与 cli extra 导出;ops 已包含 server。准备阶段按固定解释器的真实 wheel 标签和环境标记选择 兼容产物,再核对原始 uv.lock 中的大小和摘要。只有 jieba==0.42.1 可以使用锁定的 sdist,因为它没有上游 wheel。准备脚本先核对源码 摘要,再用 scripts/managed_resource_build_tools.json 中完整固定版本与摘要的工具闭包构建,不启用构建隔离,也不允许构建后端额外下载依赖。 项目 wheel 是另一个明确的源码构建;Intel macOS 还对精确锁定的 cryptography==50.0.0 使用下述专门源码构建路径。 其他没有兼容 wheel 的依赖会明确失败,不会自动放开源码编译。

准备阶段可以联网。最终资源生产只安装冻结的 requirements.lock 与 wheelhouse,使用离线、仅 wheel 和强制摘要校验。固定工具输入并不 保证任意主机/工具链上的源码构建都产生相同字节,因此必须保留实际 wheel 及摘要。相同冻结输入的重复资源生产必须生成相同归档和清单。

Intel cryptography 源码构建

cryptography 上游从 49.0.0 移除了 x86_64 macOS 支持。生产器保留源锁文件中的 50.0.0,不降级使用旧 wheel。独立的 build_intel_managed_wheel.py 验证精确 sdist 后,使用以下固定原生输入构建:

输入固定身份
Rust1.95.0、x86_64-apple-darwin 分发包,SHA-256 998af750fe2c28f0dbf193edc496c9a7407850486968f40ac11a8c8adfb7cfa6
OpenSSL4.0.3 源码,SHA-256 325b5c806167c13b40b1ffeadfe0248197c00eccc4cf123ec1e28d2d2fd216d9
构建后端maturin==1.14.1 固定 Intel wheel、已有固定 setuptools,以及原生锁定的 cffi/pycparser wheel
编译器/SDKXcode 16.4 build 16F6、macOS SDK 15.5、部署目标 10.13

所有下载地址与摘要固定在 scripts/managed_resource_intel_build.json。Rust 仅安装到一次性构建目录,OpenSSL 在该目录内静态构建, 资源不引用或携带 Homebrew 库。外部 Cargo crate 必须在固定源码的 Cargo.lock 中具有 crates.io 校验和,cargo vendor --locked 核对获取结果,随后使用 vendored 依赖和 Cargo 离线模式。后端仅从离线固定摘要 wheel 安装,其裸 maturin 命令必须解析至该 wheel 在任务构建环境中的精确二进制文件。继承的目标前缀 OpenSSL 设置及 Python/后端/编译器选项无法重定向固定输入。 生成的 wheel 保留 OpenSSL 与 Rust 依赖许可文件。 otool 拒绝非系统动态依赖及运行时搜索路径;三个平台安装后的探测都会实际执行 AES-GCM 往返和 Ed25519 签名/验签。 源码、工具、SDK 身份和链接检查结果写入 inputs.json 后再冻结摘要。

Intel 构建主机须已安装该 Xcode,并为当前进程设置 DEVELOPER_DIR=/Applications/Xcode_16.4.app/Contents/Developer;CI 仅设置环境变量, 不修改系统全局 Xcode 选择。该构建针对上游已停止支持的平台,编译和冒烟通过不会恢复上游支持,也不能证明生产维护或安全资格。 Intel CI 任务必须实际通过,不会因没有 wheel 而跳过;不同编译器/SDK 身份之间不宣称源码构建字节一致。

生成候选资源

使用当前已提交的生产器检出,再指定确切的包源码提交和版本。以下 macOS arm64 命令可直接执行,冻结当前提交并赋予明确的候选版本:

uv sync --locked --no-dev --group resource-build --python 3.12 --no-build-package cryptography --no-build-package jieba
package_source_sha="$(git rev-parse HEAD)"
uv run --no-sync python scripts/prepare_managed_resources.py \
  --target macos-arm64 \
  --source-ref "$package_source_sha" \
  --server-version "0.0.0+candidate.$package_source_sha" \
  --output build/resource-inputs \
  --download-cache build/resource-downloads

源提交必须存在于当前检出中,且具有兼容的 uv.lock 和包构建契约。Intel macOS 使用 macos-x86_64。Windows PowerShell 对应命令:

uv sync --locked --no-dev --group resource-build --python 3.12 --no-build-package cryptography --no-build-package jieba
$packageSourceSha = (git rev-parse HEAD).Trim()
uv run --no-sync python scripts/prepare_managed_resources.py --target windows-x86_64 --source-ref $packageSourceSha --server-version "0.0.0+candidate.$packageSourceSha" --output build/resource-inputs --download-cache build/resource-downloads

--runtime-archive 可复用已经下载的解释器归档,仍必须满足同一个固定摘要。 可选下载缓存位于显式指定的任务目录,按摘要寻址,每次命中都会重新校验。已经存在但损坏或未完成的输入必须先检查,不会直接复用。

保存准备脚本输出的 inputs_sha256,再传给离线生产器:

uv run --no-sync python scripts/produce_managed_resources.py \
  --inputs build/resource-inputs \
  --inputs-sha256 <准备阶段输出的摘要> \
  --server-url https://candidate.invalid/server.tar.gz \
  --output build/resource-candidate
uv run --no-sync python scripts/ci_managed_resource_smoke.py \
  --candidate build/resource-candidate --serve --evidence-dir build/resource-evidence

candidate.invalid 是保留的测试域名,不能作为真实下载位置。验证 HTTP 交付时,应由操作者提供受控的固定 HTTPS 候选地址;生产器不负责 上传或查找地址。冒烟工具把实际候选归档放入仍会校验摘要的本地缓存,再使用真实 ResourceCatalog 和 ResourceStore 回读,不宣称发生了 HTTP 下载。输入与输出目录不能互相包含;已有输出绝不覆盖。失败或未完成的构建保留供检查,重试应使用新目录。大型输入、输出和缓存不可提交。

冒烟失败时,会在临时目录清理前,将有界的测试 stdout/stderr 和异常类型摘要保存到显式证据目录。每个日志最多保留末尾 64 KiB, 不在控制台内联打印原始异常内容或子进程 stdout。未指定 --evidence-dir 时,默认持久目录是候选目录旁的 <候选目录名>-evidence。

输出与启动契约

输出用途
server/server.tar.gz可重复、无链接的 Server 目录树,包含自身 Python 和完整安装依赖闭包。
server/catalog.json、server/catalog.sha256精确的 ResourceCatalog/ResourceSpec 字节及摘要;清单逐文件记录路径、大小、SHA-256 和执行位。
server/resource.json完整来源/输入溯源与生成的资源规格。
ops/经现有 build_ops_bundle.py 构建的独立 runtime/、packages/、entry.py 与兼容的 bundle.json。
ops.tar.gz、ops-resource.json可重复 Ops 归档及完整文件大小/摘要/执行位清单,包含 bundle.json。
ops-provision.jsonDesktop 分发输入,固定精确的 ops/bundle.json 和 server/catalog.json 摘要,使用 binding_mode=managed_catalog。
candidate.json未验收状态、源码/包/输出绑定、生产器身份,以及在 Windows 上执行时的真实路径测量。

资格引用以 unqualified-candidate:sha256: 开头,绑定冻结的源码/包输入与实际输出,不构成平台验收证据。跟随不可信产物提供的校验和不是 信任根。Desktop 打包器须在受信任分发中固定描述文件,并把独立维护工具部署至受保护的当前用户 PowerContext/Ops/bundle,与 Desktop 安装和托管 Server 分离。catalog 打包位置为 resource_dir/server/catalog.json。生成上述目录本身不会安装 Desktop 或部署维护工具。

Server 用精确的内置解释器运行 -I -B -m powercontext.service.launcher。Windows catalog 选择 pythonw.exe,并同时清点配套的 python.exe;Ops 使用控制台解释器,继续采用 -I -S -B entry.py 和原有清单格式。安装器生成的控制台包装器含绝对构建路径,无法迁移, 因此在冻结前删除。包含构建输入时间戳的可变 uv 安装器缓存记录也会移除,再同步修正发行版 RECORD。 可导入模块、原生库、wheel 数据和许可文件保留;人工 CLI 可调用内置模块的 main 入口。

解释器归档中的链接不会直接解压为链接。仅将指向树内普通文件的安全相对别名物化为独立普通文件,并记录来源。目录别名、外部/绝对链接、 循环、特殊文件、歧义路径和 reparse point 都被拒绝。归档顺序、时间戳、权限、所有者和 gzip/tar 元数据统一,验证冻结资源时禁用字节码生成。

Windows 生产会按普通 Win32 UTF-16 文件/目录上限核对当前用户真实的托管最终目录、staging/resource-<32 位十六进制> 目录及独立 Ops 最终目录。Server 还调用消费者实际使用的目标路径校验器,不启用系统级长路径设置。短构建路径通过不代表其他用户目录也合格;消费者仍拒绝 不支持的真实安装路径。

CI 与证据范围

.github/workflows/managed-resource-candidates.yml 在 windows-2025、macos-15(arm64)和 macos-15-intel 上实际生产。 每个矩阵任务按精确源提交取得固定输入,迁移同一组摘要输入后构建两次,比较归档与清单字节、拒绝输出覆盖/版本不符,并通过真实 catalog/归档消费者回读。 安装后的 Server 在隔离模式下导入依赖,通过精确 service launcher 启动一次性 SQLite Server 并验证就绪。独立 Ops 冒烟使用被污染的 Python 环境变量,检查模块来源,并在 Server Python 缺失时通过原生服务管理器的只读命令检查状态,不注册任何服务。 CI 在成功时保存明确标记为未资格验收的候选资源。失败时保留有界诊断、输入身份和测试证据,不把部分运行时或下载缓存作为失败诊断上传; 任务失败不能算合格资源。

本地聚焦检查:

uv run --no-sync pytest -q tests/test_managed_resource_producer.py tests/test_ops_resources.py

没有显式准备的原生输入时,开销较大的原生生产测试跳过。将 POWERCONTEXT_RESOURCE_TEST_INPUTS 设置为准备目录,将 POWERCONTEXT_RESOURCE_TEST_INPUTS_SHA256 设置为可信的 inputs.json 摘要,即可启用同一路径。 POWERCONTEXT_RESOURCE_TEST_OUTPUT 可指定首个保留候选目录,该目录不能已经存在。合成 Windows 归档/路径测试仅证明布局与拒绝逻辑, 不能证明 Windows 上实际执行成功。

正式组装/发布仍需验证 Windows 签名、macOS 签名与公证、没有开发工具的干净标准用户 Windows 11/macOS 机器、登录/重启/服务生命周期, 以及修复和移除时保留配置与数据。签名会改变解释器或原生库字节。必须先完成真实签名,再冻结合格产物的文件清单、归档、catalog、bundle 清单和部署描述文件摘要,并重新生成、验证整个身份链。不得用未签名候选摘要证明另一组签名产物。

On this page