Files
makelore/scripts/lib/pi-product-artifact.mjs

838 lines
33 KiB
JavaScript

import { spawn } from 'node:child_process';
import { readFile, readdir, stat } from 'node:fs/promises';
import { createRequire } from 'node:module';
import { arch as hostArch, platform as hostPlatform } from 'node:os';
import {
dirname,
isAbsolute,
join,
relative,
resolve,
sep,
} from 'node:path';
import YAML from 'yaml';
import {
PI_RUNTIME_CLI_ENTRY,
PI_RUNTIME_MANIFEST,
PI_RUNTIME_PACKAGE,
PI_RUNTIME_VERSION,
} from './pi-runtime-bundle.mjs';
import {
inspectPackagedClosure,
packagedResourcesDirectory,
} from '../probe-pi-packaged-runtime.mjs';
const { extractFile, listPackage } = createRequire(import.meta.url)('@electron/asar');
const PRODUCT_NAME = 'Makelore';
const LINUX_EXECUTABLE_NAME = 'niancode';
const EXTENSION_CONTRACT_MARKERS = Object.freeze([
'Makelore runtime bridge rejected the request',
'subagent.dispatch',
'makelore.write-lease',
'MAKELORE_PI_BRIDGE_URL',
]);
const MARKETPLACE_ARTIFACT_MARKERS = Object.freeze({
packageTrust: Object.freeze([
'makelore-plugin-release.v1',
'skill_only',
'platform_hosted',
'plugin_signature_invalid',
'signing key is not trusted',
]),
hostedRuntime: Object.freeze([
'makelore.game-resource',
'/api/plugins/v1/hosted/game-resource/generations',
]),
modelTools: Object.freeze([
'makelore-model-tool.v1',
'model.web-search',
'forced_search',
]),
devicePackages: Object.freeze([
'makelore-device-package.v1',
'/api/coding/device-packages',
'device-parent-workers',
]),
mainRoutes: Object.freeze([
'/api/coding/plugin-marketplace',
'plugin-marketplace\\/install\\/',
'plugin-marketplace\\/update\\/',
]),
effectiveSnapshot: Object.freeze([
'effectiveSkillIds',
'pluginReleaseIds',
]),
renderer: Object.freeze([
'/api/coding/plugin-marketplace/catalog',
'/api/coding/plugin-marketplace/library',
'为你的智能体添加插件,拓展更多能力。',
'acquire',
'install_stable',
'enable_project',
'/project-config/plugins',
]),
});
const FORBIDDEN_PROVIDER_AUTHORITY_MARKERS = Object.freeze([
'MEOWA_API_KEY',
'MEOWA_API_URL',
'MEOWA_GAME_ASSETS_SHARED_SECRET',
'/api/coding/meowa-game-assets',
'https://api.meowa.ai',
'WEB_SEARCH_OPENAI_API_KEY',
'https://api.openai.com/v1/responses',
]);
const CODE_OWNED_PLUGIN_SIGNING_KEYS_SOURCE_MARKER = 'makelore.plugin-trust.code-owned.v1';
const PI_AI_PROVIDER_PREFIX = 'pi-runtime/node_modules/@earendil-works/pi-ai/dist/providers/';
const PI_AI_PROVIDER_ASAR_PREFIX = 'app.asar/node_modules/@earendil-works/pi-ai/dist/providers/';
export const BUNDLED_CODING_PLUGIN_RESOURCE_ROOT = 'resources/coding-plugins';
// Historical path retained only as an artifact-deny rule. Standalone Skills
// must not exist in either source or packaged resources.
const LEGACY_CODING_SKILL_RESOURCE_ROOT = 'resources/coding-skills';
const SDK_ASSET_PATH_PATTERN = /^skills\/[^/]+\/assets\/.+\.(?:js|ts)$/u;
async function pathExists(path) {
try {
await stat(path);
return true;
} catch {
return false;
}
}
async function listResourceFiles(root) {
const files = [];
const visit = async (directory, prefix = '') => {
if (!await pathExists(directory)) return;
for (const entry of await readdir(directory, { withFileTypes: true })) {
const path = join(directory, entry.name);
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
if (entry.isDirectory()) await visit(path, relativePath);
else if (entry.isFile()) files.push(relativePath);
}
};
await visit(root);
return files.sort();
}
function resolveBundledResource(root, baseDirectory, candidate, label) {
if (typeof candidate !== 'string' || candidate.trim().length === 0) {
throw new Error(`Bundled plugin ${label} is missing`);
}
const portableCandidate = candidate.replaceAll('\\', '/');
if (portableCandidate.startsWith('/') || /^[A-Za-z]:\//u.test(portableCandidate)
|| /^[A-Za-z][A-Za-z0-9+.-]*:/u.test(portableCandidate)) {
throw new Error(`Bundled plugin ${label} must be relative`);
}
const resolved = resolve(baseDirectory, candidate);
const relativePath = relative(root, resolved);
if (!relativePath || relativePath === '..' || relativePath.startsWith(`..${sep}`)
|| isAbsolute(relativePath)) {
throw new Error(`Bundled plugin ${label} escapes its package root`);
}
return resolved;
}
async function readJsonResource(path, label) {
try {
return JSON.parse(await readFile(path, 'utf8'));
} catch (error) {
throw new Error(`Bundled plugin ${label} is unreadable: ${error.message}`);
}
}
async function assertMirroredResourceTree(sourceRoot, packagedRoot, label) {
const [sourceFiles, packagedFiles] = await Promise.all([
listResourceFiles(sourceRoot),
listResourceFiles(packagedRoot),
]);
if (JSON.stringify(sourceFiles) !== JSON.stringify(packagedFiles)) {
throw new Error(
`Packaged ${label} files differ: expected ${sourceFiles.join(', ')}, got ${packagedFiles.join(', ')}`,
);
}
for (const file of sourceFiles) {
const [source, packaged] = await Promise.all([
readFile(join(sourceRoot, file)),
readFile(join(packagedRoot, file)),
]);
if (!source.equals(packaged)) throw new Error(`Packaged ${label} file differs: ${file}`);
}
return sourceFiles;
}
function portable(path) {
return path.split(sep).join('/');
}
function normalizeLockedVersion(value) {
return typeof value === 'string' ? value.split('(', 1)[0] : null;
}
function runCommand(executable, args, options = {}) {
return new Promise((resolvePromise, reject) => {
const child = spawn(executable, args, {
cwd: options.cwd,
env: options.env ?? process.env,
stdio: ['ignore', 'pipe', 'pipe'],
windowsHide: true,
});
let stdout = '';
let stderr = '';
child.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
child.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
child.once('error', reject);
child.once('exit', (code, signal) => {
if (code === 0) resolvePromise({ stdout, stderr });
else reject(new Error(
`${executable} exited with code ${code ?? 'null'} signal ${signal ?? 'none'}: ${stderr || stdout}`,
));
});
});
}
export function defaultProductExecutable(projectRoot, currentPlatform = hostPlatform()) {
const root = resolve(projectRoot);
if (currentPlatform === 'win32') {
return join(root, 'release', 'win-unpacked', `${PRODUCT_NAME}.exe`);
}
if (currentPlatform === 'darwin') {
return join(root, 'release', 'mac', `${PRODUCT_NAME}.app`, 'Contents', 'MacOS', PRODUCT_NAME);
}
if (currentPlatform === 'linux') {
return join(root, 'release', 'linux-unpacked', LINUX_EXECUTABLE_NAME);
}
throw new Error(`Unsupported product artifact platform: ${currentPlatform}`);
}
export function assertNodeEngineCompatible(engine, nodeVersion) {
const match = /^>=(\d+)\.(\d+)\.(\d+)$/.exec(engine ?? '');
if (!match) throw new Error(`Unsupported Pi Node engine expression: ${engine ?? 'missing'}`);
const required = match.slice(1).map(Number);
const actualMatch = /^(\d+)\.(\d+)\.(\d+)/.exec(nodeVersion ?? '');
if (!actualMatch) throw new Error(`Invalid packaged Node version: ${nodeVersion ?? 'missing'}`);
const actual = actualMatch.slice(1).map(Number);
for (let index = 0; index < required.length; index += 1) {
if (actual[index] > required[index]) return;
if (actual[index] < required[index]) {
throw new Error(`Packaged Node ${nodeVersion} does not satisfy Pi engine ${engine}`);
}
}
}
export function validatePiArtifactMetadata({
rootPackage,
lockfile,
packagedPackage,
runtimePackage,
manifest,
runtimePlatform,
}) {
const rootDependency = rootPackage.dependencies?.[PI_RUNTIME_PACKAGE];
const packagedDependency = packagedPackage.dependencies?.[PI_RUNTIME_PACKAGE];
const lockDependency = lockfile.importers?.['.']?.dependencies?.[PI_RUNTIME_PACKAGE];
const lockedVersion = normalizeLockedVersion(lockDependency?.version);
const versions = {
expected: PI_RUNTIME_VERSION,
rootDependency,
packagedDependency,
lockSpecifier: lockDependency?.specifier ?? null,
lockedVersion,
runtimePackage: runtimePackage.version,
manifest: manifest.runtime?.version ?? null,
};
const mismatched = Object.entries(versions)
.filter(([key, value]) => key !== 'expected' && value !== PI_RUNTIME_VERSION);
if (mismatched.length > 0) {
throw new Error(`Pi artifact versions do not match: ${JSON.stringify(versions)}`);
}
if (runtimePackage.name !== PI_RUNTIME_PACKAGE) {
throw new Error(`Packaged Pi package name is ${runtimePackage.name ?? 'missing'}`);
}
if (runtimePackage.bin?.pi !== PI_RUNTIME_CLI_ENTRY) {
throw new Error(`Packaged Pi CLI entry is ${runtimePackage.bin?.pi ?? 'missing'}`);
}
if (manifest.runtime?.packageName !== PI_RUNTIME_PACKAGE
|| manifest.runtime?.cliEntry !== PI_RUNTIME_CLI_ENTRY) {
throw new Error(`Packaged Pi manifest identity is invalid: ${JSON.stringify(manifest.runtime)}`);
}
if (manifest.target?.platform !== runtimePlatform.platform
|| manifest.target?.arch !== runtimePlatform.arch) {
throw new Error(
`Packaged Pi target ${manifest.target?.platform ?? 'missing'}-${manifest.target?.arch ?? 'missing'} `
+ `does not match executable ${runtimePlatform.platform}-${runtimePlatform.arch}`,
);
}
assertNodeEngineCompatible(manifest.runtime?.nodeEngine, runtimePlatform.node);
return versions;
}
export function collectAbsoluteManifestValues(value, at = '$', results = []) {
if (typeof value === 'string') {
if (isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value)) results.push({ at, value });
return results;
}
if (Array.isArray(value)) {
value.forEach((entry, index) => collectAbsoluteManifestValues(entry, `${at}[${index}]`, results));
return results;
}
if (value && typeof value === 'object') {
for (const [key, entry] of Object.entries(value)) {
collectAbsoluteManifestValues(entry, `${at}.${key}`, results);
}
}
return results;
}
export async function collectForbiddenResourcePaths(root, pattern = /opencode/i) {
const matches = [];
const visit = async (directory) => {
if (!await pathExists(directory)) return;
for (const entry of await readdir(directory, { withFileTypes: true })) {
const path = join(directory, entry.name);
if (pattern.test(entry.name)) matches.push(portable(relative(root, path)));
if (entry.isDirectory()) await visit(path);
}
};
await visit(root);
return matches.sort();
}
export function classifyOpenCodeResourcePaths(paths) {
const isUpstreamPiProvider = (path) => path.startsWith(PI_AI_PROVIDER_PREFIX)
|| path.startsWith(PI_AI_PROVIDER_ASAR_PREFIX);
const upstreamPiProvider = paths.filter(isUpstreamPiProvider);
const productOwned = paths.filter((path) => !isUpstreamPiProvider(path));
return { productOwned, upstreamPiProvider };
}
export function collectForbiddenAsarPaths(appAsar, pattern = /opencode/i) {
const paths = listPackage(appAsar, { isPack: false }).map((entry) => (
`app.asar/${entry.replace(/^[\\/]+/, '').replaceAll('\\', '/')}`
));
return {
entryCount: paths.length,
matches: paths.filter((entry) => pattern.test(entry)).sort(),
};
}
export function verifyMarketplaceClientArtifact(appAsarContents, productionTrustSource) {
const missing = Object.entries(MARKETPLACE_ARTIFACT_MARKERS).flatMap(([group, markers]) => (
markers
.filter((marker) => !appAsarContents.includes(Buffer.from(marker)))
.map((marker) => `${group}:${marker}`)
));
if (missing.length > 0) {
throw new Error(`Packaged app.asar does not contain Marketplace contract markers: ${missing.join(', ')}`);
}
const forbidden = FORBIDDEN_PROVIDER_AUTHORITY_MARKERS.filter((marker) => (
appAsarContents.includes(Buffer.from(marker))
));
if (forbidden.length > 0) {
throw new Error(`Packaged app.asar still contains provider authority: ${forbidden.join(', ')}`);
}
const hasEmptyCodeOwnedTrust = /(?:CODE_OWNED_PLUGIN_SIGNING_KEYS\s*=\s*)?Object\.freeze\(\s*\{\}\s*(?:as\s+[^)]*)?\)/u.test(productionTrustSource);
if (!hasEmptyCodeOwnedTrust
|| !productionTrustSource.includes(CODE_OWNED_PLUGIN_SIGNING_KEYS_SOURCE_MARKER)
|| productionTrustSource.includes('process.env')
|| productionTrustSource.includes('-----BEGIN PRIVATE KEY-----')
|| productionTrustSource.includes('-----BEGIN ED25519 PRIVATE KEY-----')) {
throw new Error('Marketplace production trust source is not an empty code-owned fail-closed store');
}
return {
schema2SkillOnly: true,
schema2PlatformHosted: true,
legacyMeowaClientAuthorityAbsent: true,
webSearchProviderAuthorityAbsent: true,
productionTrust: 'official-key-absent-fail-closed',
libraryInstallAndEffectiveRoutes: true,
rendererAssets: true,
productionKeyIds: [],
privateKeyMaterialInTrustSource: false,
markers: MARKETPLACE_ARTIFACT_MARKERS,
result: 'pass',
};
}
/**
* Read the package.main-reachable application graph from the packaged app.
* Checkout sources and unrelated app.asar files are not product evidence.
*/
async function readPackagedApplicationGraph(appAsar) {
const entries = new Set(listPackage(appAsar, { isPack: false })
.map((entry) => entry.replace(/^[/\\]+/u, '').replaceAll('\\', '/')));
const readArchiveText = (entry) => {
if (!entries.has(entry)) throw new Error(`Packaged app.asar entry is missing: ${entry}`);
return Buffer.from(extractFile(appAsar, entry.replaceAll('/', sep))).toString('utf8');
};
let packageJson;
try {
packageJson = JSON.parse(readArchiveText('package.json'));
} catch (error) {
const reason = error instanceof Error ? error.message : 'unknown error';
throw new Error(`Packaged app.asar package.json is unreadable: ${reason}`);
}
if (typeof packageJson.main !== 'string' || packageJson.main.trim().length === 0) {
throw new Error('Packaged app.asar package.json.main is missing');
}
const normalizeAsarPath = (value) => {
const result = [];
for (const segment of value.replaceAll('\\', '/').split('/')) {
if (!segment || segment === '.') continue;
if (segment === '..') {
if (result.length === 0) return null;
result.pop();
} else result.push(segment);
}
return result.join('/');
};
const resolveEntry = (from, specifier) => {
if (!specifier.startsWith('.')) return null;
const withoutSuffix = specifier.split(/[?#]/u, 1)[0];
const base = normalizeAsarPath(`${dirname(from).replaceAll('\\', '/')}/${withoutSuffix}`);
if (!base) return null;
const candidates = [base, `${base}.js`, `${base}.mjs`, `${base}.cjs`, `${base}/index.js`];
return candidates.find((candidate) => entries.has(candidate)) ?? null;
};
const main = normalizeAsarPath(packageJson.main);
if (!main || !entries.has(main)) throw new Error('Packaged app.asar package.json.main is not present');
const pending = [main];
const reachable = [];
const seen = new Set();
while (pending.length > 0) {
const entry = pending.shift();
if (!entry || seen.has(entry)) continue;
seen.add(entry);
let source;
try {
source = readArchiveText(entry);
} catch {
continue;
}
reachable.push({ entry, source });
const specifiers = [];
const importPattern = /\b(?:import|export)(?:[^"'`;]*?\bfrom)?\s*['"]([^'"]+)['"]/gu;
const dynamicImportPattern = /\bimport\(\s*['"]([^'"]+)['"]\s*\)/gu;
const requirePattern = /\brequire\(\s*['"]([^'"]+)['"]\s*\)/gu;
const htmlEntryPattern = /['"]([^'"]+\.html(?:[?#][^'"]*)?)['"]/gu;
const htmlAssetPattern = /\b(?:src|href)\s*=\s*['"]([^'"]+)['"]/gu;
for (const match of source.matchAll(importPattern)) specifiers.push(match[1]);
for (const match of source.matchAll(dynamicImportPattern)) specifiers.push(match[1]);
for (const match of source.matchAll(requirePattern)) specifiers.push(match[1]);
for (const match of source.matchAll(htmlEntryPattern)) specifiers.push(match[1]);
if (entry.endsWith('.html')) {
for (const match of source.matchAll(htmlAssetPattern)) specifiers.push(match[1]);
}
for (const specifier of specifiers) {
const resolved = resolveEntry(entry, specifier);
if (resolved) pending.push(resolved);
}
}
return { packageJson, reachable };
}
function findPackagedMarketplaceTrustSource(reachable) {
for (const { source } of reachable) {
const trustAssignment = source.match(
/CODE_OWNED_PLUGIN_SIGNING_KEYS\s*=\s*Object\.freeze\(\s*\{\}\s*(?:as\s+[^)]*)?\)/u,
);
if (trustAssignment) return trustAssignment[0];
const compiledTrust = source.match(
/(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*Object\.freeze\(\s*\{\}\s*\)\s*;(?=[\s\S]{0,512}?sourceMarker\s*:\s*["']makelore\.plugin-trust\.code-owned\.v1["'])/u,
);
if (compiledTrust) {
return `${compiledTrust[0]} sourceMarker: ${CODE_OWNED_PLUGIN_SIGNING_KEYS_SOURCE_MARKER}`;
}
}
throw new Error('Packaged app.asar does not contain the Marketplace trust source');
}
/**
* Read the Marketplace trust source from the packaged application itself.
* Checkout sources are not evidence of what an installed app will trust.
*/
export async function readPackagedMarketplaceTrustSource(appAsar) {
const { reachable } = await readPackagedApplicationGraph(appAsar);
return findPackagedMarketplaceTrustSource(reachable);
}
/**
* Verify Marketplace contract markers only in the package.main-reachable app
* graph. An arbitrary string elsewhere in app.asar is not evidence that the
* installed Main/Renderer contract is present.
*/
export async function verifyPackagedMarketplaceClientArtifact(appAsar) {
const { reachable } = await readPackagedApplicationGraph(appAsar);
const reachableContents = Buffer.from(reachable.map(({ source }) => source).join('\n'));
const trustSource = findPackagedMarketplaceTrustSource(reachable);
if (!reachableContents.includes(Buffer.from(CODE_OWNED_PLUGIN_SIGNING_KEYS_SOURCE_MARKER))) {
throw new Error('Packaged app.asar reachable graph does not contain the Marketplace trust provenance marker');
}
const verifiedTrustSource = `${trustSource}\n${CODE_OWNED_PLUGIN_SIGNING_KEYS_SOURCE_MARKER}`;
return verifyMarketplaceClientArtifact(reachableContents, verifiedTrustSource);
}
export async function verifyPackagedDevicePackageRuntime(appAsar, runtimePlatform) {
const { reachable } = await readPackagedApplicationGraph(appAsar);
const staleImports = reachable
.filter(({ source }) => /\bimport\(\s*["']@earendil-works\/pi-coding-agent["']\s*\)/u.test(source))
.map(({ entry }) => entry);
if (staleImports.length > 0) {
throw new Error(
`Packaged Device Package manager still imports the incomplete app graph: ${staleImports.join(', ')}`,
);
}
if (runtimePlatform.devicePackageManager?.defaultPackageManager !== 'function'
|| runtimePlatform.devicePackageManager?.settingsManager !== 'function') {
throw new Error('Packaged Pi runtime cannot load the Device Package manager');
}
return {
authority: 'bundled-pi-runtime',
appAsarRootImport: false,
exports: runtimePlatform.devicePackageManager,
result: 'pass',
};
}
async function filesContainingNeedles(root, needles) {
const matches = [];
const visit = async (path) => {
const details = await stat(path);
if (details.isDirectory()) {
for (const entry of await readdir(path, { withFileTypes: true })) {
await visit(join(path, entry.name));
}
return;
}
const contents = await readFile(path);
const found = needles.filter((needle) => needle.length > 0 && contents.includes(needle));
if (found.length > 0) matches.push({ path, needles: found.map((value) => value.toString()) });
};
await visit(root);
return matches;
}
async function inspectProductRuntime(executable, resourcesDirectory) {
const script = String.raw`
const { createRequire } = require('node:module');
const path = require('node:path');
const resources = process.env.MAKELORE_PI_PRODUCT_RESOURCES;
const appRequire = createRequire(path.join(resources, 'app.asar', 'package.json'));
const packagedPackage = appRequire('./package.json');
const piRuntime = require(path.join(resources, 'pi-runtime', 'dist', 'index.js'));
if (typeof piRuntime.DefaultPackageManager !== 'function'
|| typeof piRuntime.SettingsManager?.inMemory !== 'function') {
throw new Error('Bundled Pi package manager exports are unavailable');
}
process.stdout.write(JSON.stringify({
platform: process.platform,
arch: process.arch,
node: process.versions.node,
electron: process.versions.electron,
packagedPackage,
devicePackageManager: {
defaultPackageManager: typeof piRuntime.DefaultPackageManager,
settingsManager: typeof piRuntime.SettingsManager.inMemory,
},
}));
`;
const { stdout } = await runCommand(executable, ['-e', script], {
env: {
...process.env,
ELECTRON_RUN_AS_NODE: '1',
MAKELORE_PI_PRODUCT_RESOURCES: resourcesDirectory,
},
});
return JSON.parse(stdout.trim());
}
export async function verifyBundledCodingPluginResources({
projectRoot,
resourcesDirectory,
appAsarContents,
}) {
const root = resolve(projectRoot);
const sourcePluginRoot = join(root, BUNDLED_CODING_PLUGIN_RESOURCE_ROOT);
const packagedPluginRoot = join(resourcesDirectory, BUNDLED_CODING_PLUGIN_RESOURCE_ROOT);
if (!await pathExists(sourcePluginRoot)) {
throw new Error(`Bundled coding plugin resources are missing: ${sourcePluginRoot}`);
}
if (!await pathExists(packagedPluginRoot)) {
throw new Error(`Packaged coding plugin resources are missing: ${packagedPluginRoot}`);
}
const [sourcePackages, packagedPackages] = await Promise.all([
readdir(sourcePluginRoot, { withFileTypes: true }),
readdir(packagedPluginRoot, { withFileTypes: true }),
]);
const sourcePackageNames = sourcePackages
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.sort();
const packagedPackageNames = packagedPackages
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.sort();
if (JSON.stringify(sourcePackageNames) !== JSON.stringify(packagedPackageNames)) {
throw new Error(
`Packaged coding plugin packages differ: expected ${sourcePackageNames.join(', ')}, `
+ `got ${packagedPackageNames.join(', ')}`,
);
}
const packages = [];
const catalogMarkers = [];
for (const packageName of sourcePackageNames) {
const sourcePackageRoot = join(sourcePluginRoot, packageName);
const packagedPackageRoot = join(packagedPluginRoot, packageName);
const packageFiles = await assertMirroredResourceTree(
sourcePackageRoot,
packagedPackageRoot,
`coding plugin ${packageName}`,
);
const sourceManifestPath = join(sourcePackageRoot, 'plugin.json');
const packagedManifestPath = join(packagedPackageRoot, 'plugin.json');
const sourceManifest = await readJsonResource(sourceManifestPath, `${packageName} plugin.json`);
const packagedManifest = await readJsonResource(packagedManifestPath, `${packageName} plugin.json`);
const sourceCapabilityPath = resolveBundledResource(
sourcePackageRoot,
sourcePackageRoot,
sourceManifest.extensions?.['com.makelore']?.capabilityManifest,
`${packageName} capability manifest`,
);
const packagedCapabilityPath = resolveBundledResource(
packagedPackageRoot,
packagedPackageRoot,
packagedManifest.extensions?.['com.makelore']?.capabilityManifest,
`${packageName} capability manifest`,
);
const sourceCapability = await readJsonResource(sourceCapabilityPath, `${packageName} capability manifest`);
const packagedCapability = await readJsonResource(packagedCapabilityPath, `${packageName} capability manifest`);
if (packagedCapability.pluginId !== packagedManifest.name) {
throw new Error(`Packaged coding plugin ${packageName} identity does not match its manifest`);
}
const capabilityRelativePath = portable(relative(packagedPackageRoot, packagedCapabilityPath));
if (!packageFiles.includes(capabilityRelativePath)) {
throw new Error(`Packaged coding plugin capability manifest is not in its resource tree: ${packageName}`);
}
const skills = Array.isArray(packagedCapability.skills) ? packagedCapability.skills : [];
if (skills.length === 0) throw new Error(`Packaged coding plugin has no Skill: ${packageName}`);
const skillPaths = [];
for (const [index, skill] of skills.entries()) {
const skillPath = resolveBundledResource(
packagedPackageRoot,
dirname(packagedCapabilityPath),
skill?.entry,
`${packageName} Skill ${index}`,
);
const relativePath = portable(relative(packagedPackageRoot, skillPath));
if (!packageFiles.includes(relativePath)) {
throw new Error(`Packaged coding plugin Skill is missing: ${packageName}/${relativePath}`);
}
const content = await readFile(skillPath, 'utf8');
if (content.trim().length === 0) throw new Error(`Packaged coding plugin Skill is empty: ${packageName}/${relativePath}`);
skillPaths.push({ id: skill?.id, path: relativePath });
}
const sdkAssetPaths = packageFiles.filter((file) => SDK_ASSET_PATH_PATTERN.test(file));
if (packagedCapability.schemaVersion === 1 && sdkAssetPaths.length === 0) {
throw new Error(`Packaged schema-1 coding plugin has no SDK assets: ${packageName}`);
}
const adapterId = packagedCapability.schemaVersion === 2
? packagedCapability.pluginId
: packagedCapability.adapterId;
const tools = Array.isArray(packagedCapability.tools) ? packagedCapability.tools : [];
const toolNames = tools.map((tool) => tool?.name);
if (typeof adapterId !== 'string' || adapterId.length === 0 || tools.length === 0
|| toolNames.some((name) => typeof name !== 'string' || name.length === 0)
|| new Set(toolNames).size !== toolNames.length) {
throw new Error(`Packaged coding plugin adapter/tool catalog is invalid: ${packageName}`);
}
catalogMarkers.push(packagedManifest.name, adapterId, ...toolNames);
packages.push({
id: packagedManifest.name,
version: packagedManifest.version,
manifestPath: portable(relative(resourcesDirectory, packagedManifestPath)),
capabilityManifestPath: portable(relative(resourcesDirectory, packagedCapabilityPath)),
files: packageFiles.map((file) => portable(join(BUNDLED_CODING_PLUGIN_RESOURCE_ROOT, packageName, file))),
skills: skillPaths,
sdkAssets: sdkAssetPaths.map((file) => portable(join(BUNDLED_CODING_PLUGIN_RESOURCE_ROOT, packageName, file))),
adapterId,
tools: toolNames,
sourceManifest: sourceManifest.name,
sourceCapability: sourceCapability.pluginId,
});
}
const asar = appAsarContents ?? await readFile(join(resourcesDirectory, 'app.asar'));
const missingCatalogMarkers = [...new Set(catalogMarkers)].filter(
(marker) => !asar.includes(Buffer.from(marker)),
);
if (missingCatalogMarkers.length > 0) {
throw new Error(
`Packaged app.asar does not contain the coding plugin adapter/tool catalog: ${missingCatalogMarkers.join(', ')}`,
);
}
const sourceLegacySkillRoot = join(root, LEGACY_CODING_SKILL_RESOURCE_ROOT);
const packagedLegacySkillRoot = join(resourcesDirectory, LEGACY_CODING_SKILL_RESOURCE_ROOT);
if (await pathExists(sourceLegacySkillRoot) || await pathExists(packagedLegacySkillRoot)) {
throw new Error('Standalone coding Skill resources must not be bundled; distribute them through Plugins');
}
return {
root: BUNDLED_CODING_PLUGIN_RESOURCE_ROOT,
packages,
catalog: {
adapters: [...new Set(packages.map(({ adapterId }) => adapterId))],
tools: [...new Set(packages.flatMap(({ tools }) => tools))],
packagedInAsar: true,
},
coreResources: {
root: null,
files: [],
skills: [],
},
result: 'pass',
};
}
export async function verifyPiProductArtifact({ projectRoot, executable }) {
const root = resolve(projectRoot);
const appExecutable = resolve(executable ?? defaultProductExecutable(root));
if (!await pathExists(appExecutable)) throw new Error(`Product executable is missing: ${appExecutable}`);
const resourcesDirectory = packagedResourcesDirectory(appExecutable);
const runtimeRoot = join(resourcesDirectory, 'pi-runtime');
const agentServerPath = join(resourcesDirectory, 'resources', 'pi-agent-server.mjs');
const appAsar = join(resourcesDirectory, 'app.asar');
for (const required of [runtimeRoot, agentServerPath, appAsar]) {
if (!await pathExists(required)) throw new Error(`Product artifact resource is missing: ${required}`);
}
const [rootPackageSource, lockfileSource, runtimePackageSource, manifestSource] = await Promise.all([
readFile(join(root, 'package.json'), 'utf8'),
readFile(join(root, 'pnpm-lock.yaml'), 'utf8'),
readFile(join(runtimeRoot, 'package.json'), 'utf8'),
readFile(join(runtimeRoot, PI_RUNTIME_MANIFEST), 'utf8'),
]);
const rootPackage = JSON.parse(rootPackageSource);
const lockfile = YAML.parse(lockfileSource);
const runtimePackage = JSON.parse(runtimePackageSource);
const manifest = JSON.parse(manifestSource);
const runtimePlatform = await inspectProductRuntime(appExecutable, resourcesDirectory);
const versions = validatePiArtifactMetadata({
rootPackage,
lockfile,
packagedPackage: runtimePlatform.packagedPackage,
runtimePackage,
manifest,
runtimePlatform,
});
const absoluteManifestValues = collectAbsoluteManifestValues(manifest);
if (absoluteManifestValues.length > 0) {
throw new Error(`Pi runtime manifest contains absolute paths: ${JSON.stringify(absoluteManifestValues)}`);
}
const appAsarContents = await readFile(appAsar);
const physicalOpenCodePaths = await collectForbiddenResourcePaths(resourcesDirectory);
const asarOpenCodePaths = collectForbiddenAsarPaths(appAsar);
const openCodeResourcePaths = classifyOpenCodeResourcePaths([
...physicalOpenCodePaths,
...asarOpenCodePaths.matches,
]);
if (openCodeResourcePaths.productOwned.length > 0) {
throw new Error(
`Product-owned resources contain OpenCode paths: ${openCodeResourcePaths.productOwned.join(', ')}`,
);
}
const bundledPluginResources = await verifyBundledCodingPluginResources({
projectRoot: root,
resourcesDirectory,
appAsarContents,
});
const marketplace = await verifyPackagedMarketplaceClientArtifact(appAsar);
const devicePackages = await verifyPackagedDevicePackageRuntime(appAsar, runtimePlatform);
const actualSkills = bundledPluginResources.coreResources.skills;
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 = [
Buffer.from(root),
Buffer.from(portable(root)),
];
const devPathResidue = [
...await filesContainingNeedles(appAsar, sourceNeedles),
...await filesContainingNeedles(runtimeRoot, sourceNeedles),
];
if (devPathResidue.length > 0) {
throw new Error(`Product artifact contains development path residue: ${JSON.stringify(devPathResidue)}`);
}
const packagedClosure = await inspectPackagedClosure(
appExecutable,
resourcesDirectory,
manifest.runtimeAssets,
);
return {
schemaVersion: 1,
artifact: {
executable: appExecutable,
resourcesDirectory,
appAsar,
runtimeRoot,
cliPath: join(runtimeRoot, ...PI_RUNTIME_CLI_ENTRY.split('/')),
agentServerPath,
},
platform: {
platform: runtimePlatform.platform,
arch: runtimePlatform.arch,
electron: runtimePlatform.electron,
node: runtimePlatform.node,
},
versions,
manifest: {
target: manifest.target,
packageCount: manifest.productionPackages.length,
assetCount: manifest.runtimeAssets.length,
nodeEngine: manifest.runtime.nodeEngine,
},
packagedClosure,
bundledPlugins: bundledPluginResources,
marketplace,
devicePackages,
extension: {
contractMarkers: EXTENSION_CONTRACT_MARKERS,
packaged: true,
executionProof: 'smoke:pi:real final-product extension/subagent run',
},
skills: actualSkills,
openCodeResourcePaths: {
...openCodeResourcePaths,
physicalMatches: physicalOpenCodePaths,
asar: asarOpenCodePaths,
upstreamDecision: openCodeResourcePaths.upstreamPiProvider.length > 0
? 'retained-required-files-from-exact-pinned-pi-production-package'
: 'none',
},
developmentPathResidue: [],
result: 'pass',
};
}
export const PI_PRODUCT_ARTIFACT_DEFAULTS = Object.freeze({
platform: hostPlatform(),
arch: hostArch(),
extensionContractMarkers: EXTENSION_CONTRACT_MARKERS,
});