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
|
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
|
script; the parser is being corrected and the artifact will be rebuilt from
|
||||||
the corrected commit before evidence is accepted.
|
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
|
## 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 凭证。
|
- Renderer 只使用 Main-owned Host API 和产品 Conversation 契约,不读取 Pi wire 类型,也不直接启动 worker、访问本地运行时地址或接触 Provider 凭证。
|
||||||
- 产品保留既有 `niancode` 应用 id、协议、数据目录和项目配置兼容标识;这不代表旧 OpenCode 运行时、路由或资源仍被携带。
|
- 产品保留既有 `niancode` 应用 id、协议、数据目录和项目配置兼容标识;这不代表旧 OpenCode 运行时、路由或资源仍被携带。
|
||||||
- 正式产品界面不提供 share/unshare、revert/unrevert、todos 或全局运行时控制。项目分支只创建新的 Conversation 历史,不表示文件回滚。
|
- 正式产品界面不提供 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>
|
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 验证的准确含义
|
## Provider 验证的准确含义
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,13 @@ import {
|
|||||||
} from '../probe-pi-packaged-runtime.mjs';
|
} from '../probe-pi-packaged-runtime.mjs';
|
||||||
|
|
||||||
const PRODUCT_NAME = 'Makelore';
|
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) {
|
async function pathExists(path) {
|
||||||
try {
|
try {
|
||||||
@@ -171,6 +177,12 @@ export async function collectForbiddenResourcePaths(root, pattern = /opencode/i)
|
|||||||
return matches.sort();
|
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) {
|
async function filesContainingNeedles(root, needles) {
|
||||||
const matches = [];
|
const matches = [];
|
||||||
const visit = async (path) => {
|
const visit = async (path) => {
|
||||||
@@ -270,9 +282,13 @@ export async function verifyPiProductArtifact({ projectRoot, executable }) {
|
|||||||
if (absoluteManifestValues.length > 0) {
|
if (absoluteManifestValues.length > 0) {
|
||||||
throw new Error(`Pi runtime manifest contains absolute paths: ${JSON.stringify(absoluteManifestValues)}`);
|
throw new Error(`Pi runtime manifest contains absolute paths: ${JSON.stringify(absoluteManifestValues)}`);
|
||||||
}
|
}
|
||||||
const forbiddenResourcePaths = await collectForbiddenResourcePaths(resourcesDirectory);
|
const openCodeResourcePaths = classifyOpenCodeResourcePaths(
|
||||||
if (forbiddenResourcePaths.length > 0) {
|
await collectForbiddenResourcePaths(resourcesDirectory),
|
||||||
throw new Error(`Product resources contain OpenCode paths: ${forbiddenResourcePaths.join(', ')}`);
|
);
|
||||||
|
if (openCodeResourcePaths.productOwned.length > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Product-owned resources contain OpenCode paths: ${openCodeResourcePaths.productOwned.join(', ')}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const expectedSkills = await sourceSkillIds(root);
|
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}`);
|
throw new Error(`Packaged coding skills differ: expected ${expectedSkills}, got ${actualSkills}`);
|
||||||
}
|
}
|
||||||
const appAsarContents = await readFile(appAsar);
|
const appAsarContents = await readFile(appAsar);
|
||||||
if (!appAsarContents.includes(Buffer.from(EXTENSION_MARKER))) {
|
const missingExtensionMarkers = EXTENSION_CONTRACT_MARKERS.filter(
|
||||||
throw new Error(`Packaged app.asar does not contain ${EXTENSION_MARKER}`);
|
(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 = [
|
const sourceNeedles = [
|
||||||
@@ -325,9 +346,18 @@ export async function verifyPiProductArtifact({ projectRoot, executable }) {
|
|||||||
nodeEngine: manifest.runtime.nodeEngine,
|
nodeEngine: manifest.runtime.nodeEngine,
|
||||||
},
|
},
|
||||||
packagedClosure,
|
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,
|
skills: actualSkills,
|
||||||
openCodeResourcePaths: [],
|
openCodeResourcePaths: {
|
||||||
|
...openCodeResourcePaths,
|
||||||
|
upstreamDecision: openCodeResourcePaths.upstreamPiProvider.length > 0
|
||||||
|
? 'retained-required-files-from-exact-pinned-pi-production-package'
|
||||||
|
: 'none',
|
||||||
|
},
|
||||||
developmentPathResidue: [],
|
developmentPathResidue: [],
|
||||||
result: 'pass',
|
result: 'pass',
|
||||||
};
|
};
|
||||||
@@ -336,5 +366,5 @@ export async function verifyPiProductArtifact({ projectRoot, executable }) {
|
|||||||
export const PI_PRODUCT_ARTIFACT_DEFAULTS = Object.freeze({
|
export const PI_PRODUCT_ARTIFACT_DEFAULTS = Object.freeze({
|
||||||
platform: hostPlatform(),
|
platform: hostPlatform(),
|
||||||
arch: hostArch(),
|
arch: hostArch(),
|
||||||
extensionMarker: EXTENSION_MARKER,
|
extensionContractMarkers: EXTENSION_CONTRACT_MARKERS,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { afterEach, describe, expect, it } from 'vitest';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
assertNodeEngineCompatible,
|
assertNodeEngineCompatible,
|
||||||
|
classifyOpenCodeResourcePaths,
|
||||||
collectAbsoluteManifestValues,
|
collectAbsoluteManifestValues,
|
||||||
collectForbiddenResourcePaths,
|
collectForbiddenResourcePaths,
|
||||||
defaultProductExecutable,
|
defaultProductExecutable,
|
||||||
@@ -81,6 +82,15 @@ describe('final Pi product artifact verification', () => {
|
|||||||
expect(await collectForbiddenResourcePaths(root)).toEqual([
|
expect(await collectForbiddenResourcePaths(root)).toEqual([
|
||||||
'pi-runtime/node_modules/opencode-ai',
|
'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', () => {
|
it('uses final unpacked-product paths and strictly parses verifier options', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user