fix: verify packaged PI ownership boundaries
This commit is contained in:
@@ -101,6 +101,23 @@
|
||||
exposed that pnpm `10.33.4` forwards the conventional `--` separator to the
|
||||
script; the parser is being corrected and the artifact will be rebuilt from
|
||||
the corrected commit before evidence is accepted.
|
||||
- The rebuilt `13efe84` Windows artifact passed the existing verifier. The new
|
||||
verifier then found OpenCode-named files only inside the exact pinned
|
||||
`@earendil-works/pi-ai/dist/providers` production package. Pi statically
|
||||
imports those provider modules from `providers/all.js` and
|
||||
`models.generated.js`; deleting them would corrupt the pinned closure. The
|
||||
verifier is being narrowed to reject product-owned/legacy OpenCode resource
|
||||
paths while reporting this upstream Pi package exception explicitly.
|
||||
- A one-sample verifier probe then showed the source filename
|
||||
`makelore-runtime-v3.mjs` is not retained literally by the production bundle.
|
||||
The final verifier now checks four stable extension contract markers inside
|
||||
`app.asar`; executable extension/subagent behavior remains owned by
|
||||
`smoke:pi:real`. With both corrections applied, the final-product verifier
|
||||
probe passed against the existing unpacked Windows artifact, including exact
|
||||
Pi `0.84.2`, Node `24.18.1`, 130 expected production packages, six assets,
|
||||
four Skills, empty product-owned OpenCode paths, the explicit upstream Pi
|
||||
exception list, empty development-path residue, and actual `get_state`/
|
||||
lifecycle execution.
|
||||
|
||||
## Follow-ups
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## 发布边界
|
||||
|
||||
- 正式包固定使用仓库锁定的 Pi 版本和生产依赖闭包。Electron Main 从安装目录中的 `resources/pi-runtime` 解析运行时、扩展、Manifest 与编码 Skills;缺失、版本漂移、开发路径泄漏或 OpenCode 命名资源残留均阻断发布。
|
||||
- 正式包固定使用仓库锁定的 Pi 版本和生产依赖闭包。Electron Main 从安装目录中的 `resources/pi-runtime` 解析运行时、扩展、Manifest 与编码 Skills;缺失、版本漂移、开发路径泄漏或产品自有的 OpenCode runtime/资源残留均阻断发布。锁定的 `@earendil-works/pi-ai` 生产包会静态导入其内置 `providers/opencode*` 模块;这些上游文件必须在报告中单独列出,不能冒充旧 Makelore OpenCode runtime,也不能为制造“零命中”而破坏 Pi 闭包。
|
||||
- Renderer 只使用 Main-owned Host API 和产品 Conversation 契约,不读取 Pi wire 类型,也不直接启动 worker、访问本地运行时地址或接触 Provider 凭证。
|
||||
- 产品保留既有 `niancode` 应用 id、协议、数据目录和项目配置兼容标识;这不代表旧 OpenCode 运行时、路由或资源仍被携带。
|
||||
- 正式产品界面不提供 share/unshare、revert/unrevert、todos 或全局运行时控制。项目分支只创建新的 Conversation 历史,不表示文件回滚。
|
||||
@@ -36,7 +36,7 @@ pnpm run smoke:pi:real -- --app-exe <final-product-executable> --samples 5 --rep
|
||||
pnpm run perf:pi:release -- --app-exe <final-product-executable> --samples 5 --report <performance-report.json>
|
||||
```
|
||||
|
||||
产物验证必须确认版本与 Node engine、生产依赖闭包、扩展、Manifest、编码 Skills、`resolve/get_state`、无 OpenCode 资源以及无构建工作区绝对路径。Smoke 必须从最终产品可执行文件启动最终 `resources/pi-runtime`,覆盖 session、prompt、tool、abort、settle、reopen、双 worker 重叠与隔离、子 Agent 和 shutdown。性能报告必须记录 p50/p95/max/样本数组、RSS、Main→Renderer IPC/提交延迟和 Git commit,并覆盖 Spec 17.3 的十个场景。
|
||||
产物验证必须确认版本与 Node engine、生产依赖闭包、扩展、Manifest、编码 Skills、`resolve/get_state`、无产品自有 OpenCode runtime/资源、上游 Pi provider 例外清单,以及无构建工作区绝对路径。Smoke 必须从最终产品可执行文件启动最终 `resources/pi-runtime`,覆盖 session、prompt、tool、abort、settle、reopen、双 worker 重叠与隔离、子 Agent 和 shutdown。性能报告必须记录 p50/p95/max/样本数组、RSS、Main→Renderer IPC/提交延迟和 Git commit,并覆盖 Spec 17.3 的十个场景。
|
||||
|
||||
## Provider 验证的准确含义
|
||||
|
||||
|
||||
@@ -23,7 +23,13 @@ import {
|
||||
} from '../probe-pi-packaged-runtime.mjs';
|
||||
|
||||
const PRODUCT_NAME = 'Makelore';
|
||||
const EXTENSION_MARKER = 'makelore-runtime-v3.mjs';
|
||||
const EXTENSION_CONTRACT_MARKERS = Object.freeze([
|
||||
'Makelore runtime bridge rejected the request',
|
||||
'subagent.dispatch',
|
||||
'makelore.write-lease',
|
||||
'MAKELORE_PI_BRIDGE_URL',
|
||||
]);
|
||||
const PI_AI_PROVIDER_PREFIX = 'pi-runtime/node_modules/@earendil-works/pi-ai/dist/providers/';
|
||||
|
||||
async function pathExists(path) {
|
||||
try {
|
||||
@@ -171,6 +177,12 @@ export async function collectForbiddenResourcePaths(root, pattern = /opencode/i)
|
||||
return matches.sort();
|
||||
}
|
||||
|
||||
export function classifyOpenCodeResourcePaths(paths) {
|
||||
const upstreamPiProvider = paths.filter((path) => path.startsWith(PI_AI_PROVIDER_PREFIX));
|
||||
const productOwned = paths.filter((path) => !path.startsWith(PI_AI_PROVIDER_PREFIX));
|
||||
return { productOwned, upstreamPiProvider };
|
||||
}
|
||||
|
||||
async function filesContainingNeedles(root, needles) {
|
||||
const matches = [];
|
||||
const visit = async (path) => {
|
||||
@@ -270,9 +282,13 @@ export async function verifyPiProductArtifact({ projectRoot, executable }) {
|
||||
if (absoluteManifestValues.length > 0) {
|
||||
throw new Error(`Pi runtime manifest contains absolute paths: ${JSON.stringify(absoluteManifestValues)}`);
|
||||
}
|
||||
const forbiddenResourcePaths = await collectForbiddenResourcePaths(resourcesDirectory);
|
||||
if (forbiddenResourcePaths.length > 0) {
|
||||
throw new Error(`Product resources contain OpenCode paths: ${forbiddenResourcePaths.join(', ')}`);
|
||||
const openCodeResourcePaths = classifyOpenCodeResourcePaths(
|
||||
await collectForbiddenResourcePaths(resourcesDirectory),
|
||||
);
|
||||
if (openCodeResourcePaths.productOwned.length > 0) {
|
||||
throw new Error(
|
||||
`Product-owned resources contain OpenCode paths: ${openCodeResourcePaths.productOwned.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
const expectedSkills = await sourceSkillIds(root);
|
||||
@@ -281,8 +297,13 @@ export async function verifyPiProductArtifact({ projectRoot, executable }) {
|
||||
throw new Error(`Packaged coding skills differ: expected ${expectedSkills}, got ${actualSkills}`);
|
||||
}
|
||||
const appAsarContents = await readFile(appAsar);
|
||||
if (!appAsarContents.includes(Buffer.from(EXTENSION_MARKER))) {
|
||||
throw new Error(`Packaged app.asar does not contain ${EXTENSION_MARKER}`);
|
||||
const missingExtensionMarkers = EXTENSION_CONTRACT_MARKERS.filter(
|
||||
(marker) => !appAsarContents.includes(Buffer.from(marker)),
|
||||
);
|
||||
if (missingExtensionMarkers.length > 0) {
|
||||
throw new Error(
|
||||
`Packaged app.asar does not contain Pi extension contract markers: ${missingExtensionMarkers.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
const sourceNeedles = [
|
||||
@@ -325,9 +346,18 @@ export async function verifyPiProductArtifact({ projectRoot, executable }) {
|
||||
nodeEngine: manifest.runtime.nodeEngine,
|
||||
},
|
||||
packagedClosure,
|
||||
extension: { marker: EXTENSION_MARKER, packaged: true },
|
||||
extension: {
|
||||
contractMarkers: EXTENSION_CONTRACT_MARKERS,
|
||||
packaged: true,
|
||||
executionProof: 'smoke:pi:real final-product extension/subagent run',
|
||||
},
|
||||
skills: actualSkills,
|
||||
openCodeResourcePaths: [],
|
||||
openCodeResourcePaths: {
|
||||
...openCodeResourcePaths,
|
||||
upstreamDecision: openCodeResourcePaths.upstreamPiProvider.length > 0
|
||||
? 'retained-required-files-from-exact-pinned-pi-production-package'
|
||||
: 'none',
|
||||
},
|
||||
developmentPathResidue: [],
|
||||
result: 'pass',
|
||||
};
|
||||
@@ -336,5 +366,5 @@ export async function verifyPiProductArtifact({ projectRoot, executable }) {
|
||||
export const PI_PRODUCT_ARTIFACT_DEFAULTS = Object.freeze({
|
||||
platform: hostPlatform(),
|
||||
arch: hostArch(),
|
||||
extensionMarker: EXTENSION_MARKER,
|
||||
extensionContractMarkers: EXTENSION_CONTRACT_MARKERS,
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import { afterEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
assertNodeEngineCompatible,
|
||||
classifyOpenCodeResourcePaths,
|
||||
collectAbsoluteManifestValues,
|
||||
collectForbiddenResourcePaths,
|
||||
defaultProductExecutable,
|
||||
@@ -81,6 +82,15 @@ describe('final Pi product artifact verification', () => {
|
||||
expect(await collectForbiddenResourcePaths(root)).toEqual([
|
||||
'pi-runtime/node_modules/opencode-ai',
|
||||
]);
|
||||
expect(classifyOpenCodeResourcePaths([
|
||||
'app.asar.unpacked/resources/opencode-runtime',
|
||||
'pi-runtime/node_modules/@earendil-works/pi-ai/dist/providers/opencode.js',
|
||||
])).toEqual({
|
||||
productOwned: ['app.asar.unpacked/resources/opencode-runtime'],
|
||||
upstreamPiProvider: [
|
||||
'pi-runtime/node_modules/@earendil-works/pi-ai/dist/providers/opencode.js',
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('uses final unpacked-product paths and strictly parses verifier options', () => {
|
||||
|
||||
Reference in New Issue
Block a user