src/runner/legacy-snapshot.ts
Rama main · réplica de lectura del repositorio original.
import { createHash } from 'node:crypto';
import { join } from 'node:path';
import { readFile } from 'node:fs/promises';
import { humanHelpSkill } from '../tasks/human-help.ts';
import { loadOperation, OPERATION_PROMPT_VERSION } from '../operation/load.ts';
import { SecretStore } from '../control-plane/secrets.ts';
import {
COMMAND_SNAPSHOT_SCHEMA_VERSION,
LEGACY_COMMAND_SNAPSHOT_SCHEMA_VERSION,
canonicalJson,
loadCommandSnapshot,
outputContractForTemplate,
snapshotSha256,
validateCommandSnapshot,
writeCommandSnapshot,
type CommandSnapshot,
} from '../control-plane/index.ts';
import { ControlPlaneError } from '../control-plane/errors.ts';
import { contentDigest } from '../control-plane/common.ts';
import { patchpawPaths } from '../config/paths.ts';
import { TOOLSET_VERSION } from '../control-plane/snapshots.ts';
import type { PausedWorkspace } from './resume.ts';
type LegacyTemplate = 'review' | 'ci' | 'conflict';
const PROVIDER_TYPES = new Set(['zhipu', 'deepseek', 'openrouter', 'kimi', 'qwen']);
const GENERATED_MANIFEST_KEYS = new Set([
'command_id', 'command_revision', 'provider_id', 'model_id',
'snapshot_path', 'snapshot_id', 'snapshot_sha256', 'snapshot_schema_version', 'snapshot_execution_id',
]);
function fail(message: string, field = 'legacy_snapshot'): never {
throw new ControlPlaneError('snapshot_corrupt', message, field);
}
function digest(value: unknown) {
return createHash('sha256').update(canonicalJson(value), 'utf8').digest('hex');
}
function manifestDigest(manifest: Record<string, unknown>) {
return digest(Object.fromEntries(Object.entries(manifest).filter(([key]) => !GENERATED_MANIFEST_KEYS.has(key))));
}
function templateFromManifest(manifest: Record<string, unknown>): LegacyTemplate {
const raw = String(manifest.template_type ?? manifest.task ?? manifest.command ?? '').trim().replace(/^\/+/, '').toLowerCase();
if (raw === 'ci-repair') return 'ci';
if (raw === 'review' || raw === 'ci' || raw === 'conflict') return raw;
fail('Legacy manifest does not identify a supported model task.', 'template_type');
}
function assertManifestBasics(manifest: Record<string, unknown>, repo: string, prNumber: number, candidate: PausedWorkspace) {
if (manifest.repo !== repo || Number(manifest.pr_number) !== prNumber) fail('Legacy manifest does not match the paused PR.', 'manifest');
if (!Number.isSafeInteger(Number(manifest.execution_id)) || Number(manifest.execution_id) !== candidate.execution_id) fail('Legacy manifest execution identity is incomplete.', 'execution_id');
if (typeof manifest.prompt_version !== 'string' || manifest.prompt_version !== OPERATION_PROMPT_VERSION) fail('Legacy prompt version cannot be verified.', 'prompt_version');
if (typeof manifest.toolset_version !== 'string' || manifest.toolset_version !== TOOLSET_VERSION) fail('Legacy toolset version cannot be verified.', 'toolset_version');
if (typeof manifest.initial_head_sha !== 'string' || typeof manifest.base_sha !== 'string' || typeof manifest.current_base_ref !== 'string' || typeof manifest.workspace_path !== 'string') {
fail('Legacy manifest is missing Git/workspace freshness facts.', 'manifest');
}
if (manifest.workspace_path !== candidate.workspace.path) fail('Legacy manifest workspace does not match the paused workspace.', 'workspace_path');
}
function sourceLayout(template: LegacyTemplate) {
const main = template === 'review' ? 'review' : template === 'ci' ? 'ci-repair' : 'conflict';
const auxiliary = template === 'review'
? ['review-json-retry', 'stop-closeout']
: ['repair-completion', 'repair-feedback', 'repair-no-verification', 'repair-verification-empty', 'repair-closeout', 'stop-closeout'];
return { main, auxiliary };
}
function sourceParts(template: LegacyTemplate) {
const layout = sourceLayout(template);
const prompts = [layout.main, 'shared', ...layout.auxiliary].map(slug => ({ kind: 'prompt' as const, slug, role: slug, content: loadOperation(slug) }));
const skill = { kind: 'skill' as const, slug: 'patchpaw-human-help', role: null, content: humanHelpSkill.trim() };
const ordered = [prompts[0], skill, prompts[1], ...prompts.slice(2)];
return ordered.map((part, index) => ({ ...part, position: index + 1, asset_id: `legacy-${part.kind}-${part.slug}`, revision: 1,
sha256: contentDigest(part.content) }));
}
function sourceAssetDigest(parts: ReturnType<typeof sourceParts>) {
return digest(parts.map(part => ({ kind: part.kind, slug: part.slug, sha256: part.sha256 })));
}
function assertProviderManifest(manifest: Record<string, unknown>, snapshot: CommandSnapshot) {
const providerType = String(manifest.provider_type ?? '');
const baseUrl = String(manifest.base_url ?? manifest.provider ?? '');
const model = String(manifest.model_identifier ?? manifest.model ?? '');
if (!PROVIDER_TYPES.has(providerType) || !baseUrl || !model) fail('Legacy provider type, base URL or model identifier is incomplete.', 'provider');
if (providerType !== snapshot.provider.type || baseUrl !== snapshot.provider.base_url || model !== snapshot.provider.model.identifier) {
fail('Legacy snapshot provider facts do not match the legacy manifest.', 'provider');
}
if (String(manifest.credential_ref ?? '') !== snapshot.provider.credential_ref) fail('Legacy credential reference does not match the manifest.', 'credential_ref');
}
function assertSourceMatches(snapshot: CommandSnapshot, manifest: Record<string, unknown>) {
const expected = sourceParts(snapshot.template_type as LegacyTemplate);
const actual = snapshot.composition.parts.map(part => ({ kind: part.kind, slug: part.slug, sha256: part.sha256 }));
const expectedDigest = sourceAssetDigest(expected);
if (canonicalJson(actual) !== canonicalJson(expected.map(part => ({ kind: part.kind, slug: part.slug, sha256: part.sha256 })))) fail('Legacy snapshot source asset set is not the fixed operation/Skill layout.', 'source_asset_digest');
if (String(manifest.source_asset_digest ?? '') !== expectedDigest || snapshot.legacy_verification?.source_asset_digest !== expectedDigest) fail('Legacy source asset digest cannot be verified.', 'source_asset_digest');
for (const [index, part] of snapshot.composition.parts.entries()) {
const source = expected[index];
if (part.content !== source.content || part.sha256 !== source.sha256) fail('Legacy snapshot source asset content changed.', `composition.parts[${index}]`);
}
}
async function assertLegacyEvidence(runtimeHome: string, runId: string, repo: string, prNumber: number, candidate: PausedWorkspace, snapshot: CommandSnapshot, manifest: Record<string, unknown>) {
if (snapshot.schema_version !== LEGACY_COMMAND_SNAPSHOT_SCHEMA_VERSION || snapshot.snapshot_origin !== 'legacy_reconstructed') fail('Legacy snapshot origin or schema is not verifiable.');
assertManifestBasics(manifest, repo, prNumber, candidate);
if (snapshot.execution_id !== `${candidate.run_id}:${candidate.execution_id}`) fail('Legacy snapshot execution identity does not match the paused execution.', 'execution_id');
if (manifest.snapshot_execution_id !== undefined && manifest.snapshot_execution_id !== snapshot.execution_id) fail('Legacy snapshot execution reference does not match the manifest.', 'snapshot_execution_id');
if (manifest.snapshot_sha256 !== undefined && manifest.snapshot_sha256 !== snapshotSha256(snapshot)) fail('Legacy snapshot hash does not match the manifest.', 'snapshot_sha256');
if (manifest.snapshot_schema_version !== undefined && manifest.snapshot_schema_version !== snapshot.schema_version) fail('Legacy snapshot schema reference does not match the manifest.', 'snapshot_schema_version');
if (manifest.snapshot_id !== undefined && manifest.snapshot_id !== snapshot.snapshot_id) fail('Legacy snapshot id does not match the manifest.', 'snapshot_id');
if (snapshot.template_type !== templateFromManifest(manifest)) fail('Legacy snapshot task does not match the manifest.', 'template_type');
assertProviderManifest(manifest, snapshot);
assertSourceMatches(snapshot, manifest);
if (snapshot.legacy_verification?.manifest_sha256 !== manifestDigest(manifest)) fail('Legacy manifest digest cannot be verified.', 'manifest_sha256');
const credentialRef = snapshot.provider.credential_ref;
if (!/^slot:provider\/[A-Za-z0-9-]+$/.test(credentialRef) || !(await new SecretStore(runtimeHome).isConfigured(credentialRef))) fail('Legacy credential slot is unavailable or is not stable.', 'credential_ref');
}
async function readLegacyManifest(runtimeHome: string, runId: string) {
try { return JSON.parse(await readFile(join(patchpawPaths(runtimeHome).runs, runId, 'manifest.json'), 'utf8')) as Record<string, unknown>; }
catch { fail('Legacy run manifest is missing or unreadable.', 'manifest'); }
}
async function reconstruct(runtimeHome: string, runId: string, repo: string, prNumber: number, candidate: PausedWorkspace, manifest: Record<string, unknown>) {
assertManifestBasics(manifest, repo, prNumber, candidate);
const template = templateFromManifest(manifest);
const parts = sourceParts(template);
const expectedDigest = sourceAssetDigest(parts);
if (manifest.source_asset_digest !== expectedDigest) fail('Legacy source asset digest does not match current source assets.', 'source_asset_digest');
const providerType = String(manifest.provider_type ?? '');
const baseUrl = String(manifest.base_url ?? manifest.provider ?? '');
const modelIdentifier = String(manifest.model_identifier ?? manifest.model ?? '');
const credentialRef = String(manifest.credential_ref ?? '');
if (!PROVIDER_TYPES.has(providerType) || !baseUrl || !modelIdentifier || !/^slot:provider\/[A-Za-z0-9-]+$/.test(credentialRef) || !(await new SecretStore(runtimeHome).isConfigured(credentialRef))) {
fail('Legacy provider facts or stable credential slot cannot be verified.', 'provider');
}
const permission = template === 'review' ? 'read_only' as const : 'read_write' as const;
const providerId = `legacy-provider-${contentDigest(`${providerType}:${baseUrl}:${credentialRef}`).slice(0, 24)}`;
const modelId = `legacy-model-${contentDigest(`${providerId}:${modelIdentifier}`).slice(0, 24)}`;
const commandId = `legacy-command-${contentDigest(`${repo}:${prNumber}:${template}`).slice(0, 24)}`;
const snapshot: CommandSnapshot = {
schema_version: LEGACY_COMMAND_SNAPSHOT_SCHEMA_VERSION, snapshot_id: `legacy-snap-${contentDigest(`${runId}:${candidate.execution_id}:${expectedDigest}`).slice(0, 24)}`,
snapshot_origin: 'legacy_reconstructed', execution_id: `${candidate.run_id}:${candidate.execution_id}`,
repository: { id: `legacy-repo-${contentDigest(repo).slice(0, 24)}`, full_name: repo }, target: 'command', template_type: template,
command: { id: commandId, slash_name: template, revision: 1, execution_type: template, permission, enabled: true },
composition: { output_contract: (() => { const contract = outputContractForTemplate(template); return { kind: contract.kind, ...(contract.schemaId ? { schema_id: contract.schemaId } : {}) }; })(), parts },
provider: { id: providerId, type: providerType, display_name: providerType, base_url: baseUrl, revision: 1, credential_ref: credentialRef,
model: { id: modelId, identifier: modelIdentifier, display_name: modelIdentifier, revision: 1 },
request_options: typeof manifest.request_options === 'object' && manifest.request_options ? manifest.request_options as Record<string, string | number | boolean>
: manifest.reasoning_effort ? { reasoning_effort: String(manifest.reasoning_effort) } : {} },
toolset_version: TOOLSET_VERSION, snapshot_created_at: new Date().toISOString(),
legacy_verification: { manifest_sha256: manifestDigest(manifest), source_asset_digest: expectedDigest, credential_ref_verified: true, workspace_freshness_verified: true },
};
validateCommandSnapshot(snapshot, { allowLegacy: true });
await writeCommandSnapshot(runtimeHome, runId, snapshot, { allowLegacy: true });
return loadCommandSnapshot(runtimeHome, runId, { allowLegacy: true });
}
export async function loadOrReconstructLegacySnapshot(runtimeHome: string, runId: string, repo: string, prNumber: number, candidate: PausedWorkspace) {
const manifest = await readLegacyManifest(runtimeHome, candidate.run_id);
let loaded;
try { loaded = await loadCommandSnapshot(runtimeHome, candidate.run_id, { allowLegacy: true }); }
catch (error) {
if (!(error instanceof ControlPlaneError) || error.code !== 'snapshot_missing') throw error;
loaded = await reconstruct(runtimeHome, candidate.run_id, repo, prNumber, candidate, manifest);
}
if (loaded.snapshot.schema_version === COMMAND_SNAPSHOT_SCHEMA_VERSION) return loaded;
await assertLegacyEvidence(runtimeHome, candidate.run_id, repo, prNumber, candidate, loaded.snapshot, manifest);
return loaded;
}