TASK-6e4ff870-a386-4a11-a59c-444ebc23732e: add candidate source policy parent

This commit is contained in:
thePR0M3TH3AN committed 2026-10-01 15:21:35 -04:00
1 parent 955672c1db
commit db0d6731d3
2 files changed
+443

No files matched your search

+213
View File
@@ -0,0 +1,213 @@
import { createHash } from 'node:crypto';
import { readFileSync, writeFileSync } from 'node:fs';
import { TorchError } from './errors.mjs';
export const CANDIDATE_SOURCE_PARENT_V2_ADMISSION_SCHEMA = 'torch.dev/candidate-source-parent-admission/v2alpha1';
export const CANDIDATE_SOURCE_PARENT_V2_BINDING_SCHEMA = 'torch.dev/candidate-source-parent-binding/v2alpha1';
export const CANDIDATE_SOURCE_PARENT_V2_MAX_ADMISSION_BYTES = 64 * 1024;
const FD3 = 3;
const FD4 = 4;
const bindings = new WeakMap();
const ADMISSION_KEYS = new Set([
'schema', 'version', 'admissionId', 'leaseId', 'checkId', 'runGeneration', 'guardGeneration',
'policyBytesBase64', 'policyDigest', 'engine', 'subject', 'artifactManifest', 'receiptAdapterAdmission',
]);
const ENGINE_KEYS = new Set(['pre', 'post']);
const ENGINE_OBSERVATION_KEYS = new Set([
'interfaceId', 'moduleId', 'entryByteLength', 'entrySha256', 'dependencyManifestByteLength',
'dependencyManifestSha256', 'node', 'npm', 'checkDefinitionSha256', 'argv',
]);
const TOOL_KEYS = new Set(['realpath', 'entryByteLength', 'entrySha256']);
const SUBJECT_KEYS = new Set([
'projectId', 'productCommit', 'cleanTreeDigest', 'frozenInputManifestSha256', 'inputFileCount', 'inputTotalBytes',
]);
const ARTIFACT_MANIFEST_KEYS = new Set(['schema', 'manifestSha256']);
const SHA256 = /^[a-f0-9]{64}$/;
const COMMIT = /^[a-f0-9]{40}$/;
const ID = /^[a-z0-9][a-z0-9-]*$/;
function fail(message, code, details) {
throw new TorchError(message, { code, details });
}
function exactObject(value, allowed, label) {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
fail(`${label} must be an object`, 'CANDIDATE_PARENT_ADMISSION_INVALID');
}
for (const key of Object.keys(value)) {
if (!allowed.has(key)) fail(`${label} contains an unsupported field`, 'CANDIDATE_PARENT_ADMISSION_INVALID', { field: key });
}
}
function requiredString(value, label, pattern = undefined) {
if (typeof value !== 'string' || value.length === 0 || (pattern && !pattern.test(value))) {
fail(`${label} is invalid`, 'CANDIDATE_PARENT_ADMISSION_INVALID');
}
return value;
}
function requiredCount(value, label, maximum = Number.MAX_SAFE_INTEGER) {
if (!Number.isSafeInteger(value) || value < 0 || value > maximum) {
fail(`${label} is invalid`, 'CANDIDATE_PARENT_ADMISSION_INVALID');
}
return value;
}
function canonicalJson(value) {
if (value === null || typeof value !== 'object') return JSON.stringify(value);
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`;
}
function sha256(bytes) {
return createHash('sha256').update(bytes).digest('hex');
}
function deepFreeze(value) {
if (Buffer.isBuffer(value) || value instanceof Uint8Array) return value;
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
for (const child of Object.values(value)) deepFreeze(child);
Object.freeze(value);
}
return value;
}
function parseToolObservation(value, label) {
exactObject(value, TOOL_KEYS, label);
return {
realpath: requiredString(value.realpath, `${label}.realpath`),
entryByteLength: requiredCount(value.entryByteLength, `${label}.entryByteLength`, 1024 * 1024 * 1024),
entrySha256: requiredString(value.entrySha256, `${label}.entrySha256`, SHA256),
};
}
function parseEngineObservation(value, label) {
exactObject(value, ENGINE_OBSERVATION_KEYS, label);
if (!Array.isArray(value.argv) || value.argv.length > 64 || value.argv.some((arg) => typeof arg !== 'string' || arg.length > 4096)) {
fail(`${label}.argv is invalid`, 'CANDIDATE_PARENT_ADMISSION_INVALID');
}
return {
interfaceId: requiredString(value.interfaceId, `${label}.interfaceId`),
moduleId: requiredString(value.moduleId, `${label}.moduleId`),
entryByteLength: requiredCount(value.entryByteLength, `${label}.entryByteLength`, 1024 * 1024 * 1024),
entrySha256: requiredString(value.entrySha256, `${label}.entrySha256`, SHA256),
dependencyManifestByteLength: requiredCount(value.dependencyManifestByteLength, `${label}.dependencyManifestByteLength`, 16 * 1024 * 1024),
dependencyManifestSha256: requiredString(value.dependencyManifestSha256, `${label}.dependencyManifestSha256`, SHA256),
node: parseToolObservation(value.node, `${label}.node`),
npm: parseToolObservation(value.npm, `${label}.npm`),
checkDefinitionSha256: requiredString(value.checkDefinitionSha256, `${label}.checkDefinitionSha256`, SHA256),
argv: [...value.argv],
};
}
function parseAdmission(bytes) {
if (!bytes.length || bytes.length > CANDIDATE_SOURCE_PARENT_V2_MAX_ADMISSION_BYTES) {
fail('Candidate parent admission is missing or exceeds its fixed bound', 'CANDIDATE_PARENT_ADMISSION_INVALID');
}
let value;
try { value = JSON.parse(bytes.toString('utf8')); } catch {
fail('Candidate parent admission is not JSON', 'CANDIDATE_PARENT_ADMISSION_INVALID');
}
exactObject(value, ADMISSION_KEYS, 'Candidate parent admission');
if (value.schema !== CANDIDATE_SOURCE_PARENT_V2_ADMISSION_SCHEMA || value.version !== 2) {
fail('Candidate parent admission version is unsupported', 'CANDIDATE_PARENT_ADMISSION_UNSUPPORTED');
}
const policyBytes = Buffer.from(requiredString(value.policyBytesBase64, 'policyBytesBase64'), 'base64');
if (!policyBytes.length || policyBytes.length > 1024 * 1024 || policyBytes.toString('base64') !== value.policyBytesBase64) {
fail('Candidate policy transport is invalid', 'CANDIDATE_PARENT_ADMISSION_INVALID');
}
const policyDigest = requiredString(value.policyDigest, 'policyDigest', SHA256);
if (sha256(policyBytes) !== policyDigest) {
fail('Candidate policy transport digest mismatches', 'CANDIDATE_PARENT_ADMISSION_MISMATCH');
}
exactObject(value.engine, ENGINE_KEYS, 'engine observations');
const engine = { pre: parseEngineObservation(value.engine.pre, 'engine.pre'), post: parseEngineObservation(value.engine.post, 'engine.post') };
if (canonicalJson(engine.pre) !== canonicalJson(engine.post)) {
fail('Candidate engine observations drifted', 'CANDIDATE_PARENT_ENGINE_DRIFT');
}
exactObject(value.subject, SUBJECT_KEYS, 'subject observations');
const subject = {
projectId: requiredString(value.subject.projectId, 'subject.projectId', ID),
productCommit: requiredString(value.subject.productCommit, 'subject.productCommit', COMMIT),
cleanTreeDigest: requiredString(value.subject.cleanTreeDigest, 'subject.cleanTreeDigest', SHA256),
frozenInputManifestSha256: requiredString(value.subject.frozenInputManifestSha256, 'subject.frozenInputManifestSha256', SHA256),
inputFileCount: requiredCount(value.subject.inputFileCount, 'subject.inputFileCount', 8192),
inputTotalBytes: requiredCount(value.subject.inputTotalBytes, 'subject.inputTotalBytes', 512 * 1024 * 1024),
};
exactObject(value.artifactManifest, ARTIFACT_MANIFEST_KEYS, 'artifact manifest');
const artifactManifest = {
schema: requiredString(value.artifactManifest.schema, 'artifactManifest.schema'),
manifestSha256: requiredString(value.artifactManifest.manifestSha256, 'artifactManifest.manifestSha256', SHA256),
};
if (value.receiptAdapterAdmission !== null) {
fail('B2 parent cannot admit a receipt adapter', 'CANDIDATE_PARENT_ADAPTER_ADMISSION_FORBIDDEN');
}
return deepFreeze({
admissionId: requiredString(value.admissionId, 'admissionId', ID),
leaseId: requiredString(value.leaseId, 'leaseId', ID),
checkId: requiredString(value.checkId, 'checkId', ID),
runGeneration: requiredCount(value.runGeneration, 'runGeneration'),
guardGeneration: requiredCount(value.guardGeneration, 'guardGeneration'),
policyBytes,
policyDigest,
engine,
subject,
artifactManifest,
receiptAdapterAdmission: null,
});
}
function readPrivateAdmission() {
try {
return readFileSync(FD3);
} catch (error) {
fail('Candidate parent private admission channel is unavailable', 'CANDIDATE_PARENT_NATIVE_BOOTSTRAP_UNAVAILABLE', { cause: error.code ?? 'UNKNOWN' });
}
}
function acknowledge(admission) {
const body = Buffer.from(`${CANDIDATE_SOURCE_PARENT_V2_BINDING_SCHEMA}\n${admission.admissionId}\n${admission.policyDigest}\n`, 'utf8');
try { writeFileSync(FD4, body); } catch (error) {
fail('Candidate parent private acknowledgement channel is unavailable', 'CANDIDATE_PARENT_NATIVE_BOOTSTRAP_UNAVAILABLE', { cause: error.code ?? 'UNKNOWN' });
}
}
/**
* Reads only Work's inherited FD3 admission and returns an opaque, one-shot
* binding. Registered identity/lease authorization happens in CheckService before
* this module starts; this module neither opens a ControlPlane nor accepts caller
* project, root, DB, environment, adapter, or writer selectors.
*/
export function openCandidateSourceParentV2() {
const admission = parseAdmission(readPrivateAdmission());
acknowledge(admission);
const binding = Object.freeze({ schema: CANDIDATE_SOURCE_PARENT_V2_BINDING_SCHEMA });
bindings.set(binding, { admission, consumed: false });
return binding;
}
/** Resolves an issued parent binding once, exposing only safe execution metadata. */
export function resolveCandidateSourceParentV2(input = {}) {
exactObject(input, new Set(['parentBinding']), 'Candidate parent resolution input');
const state = bindings.get(input.parentBinding);
if (!state) fail('Candidate parent binding is not issued by this module', 'CANDIDATE_PARENT_BINDING_REQUIRED');
if (state.consumed) fail('Candidate parent binding is single-use', 'CANDIDATE_PARENT_BINDING_REUSED');
state.consumed = true;
const { admission } = state;
return deepFreeze({
schema: CANDIDATE_SOURCE_PARENT_V2_BINDING_SCHEMA,
checkId: admission.checkId,
leaseId: admission.leaseId,
runGeneration: admission.runGeneration,
guardGeneration: admission.guardGeneration,
policyBytes: Buffer.from(admission.policyBytes),
policyDigest: admission.policyDigest,
engine: structuredClone(admission.engine),
subject: structuredClone(admission.subject),
artifactManifest: structuredClone(admission.artifactManifest),
receiptAdapterAdmission: null,
receiptEligible: false,
});
}
+230
View File
@@ -1,3 +1,4 @@
import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { z } from 'zod';
@@ -5,11 +6,103 @@ import { TorchError } from './errors.mjs';
import { organizationGraphFromConfig, organizationGraphSchema } from './organization.mjs';
export const PROJECT_CONFIG_SCHEMA = 'torch.dev/v1alpha1';
export const CANDIDATE_EXECUTION_POLICY_V2_SCHEMA = 'torch.dev/candidate-execution-policy/v2alpha1';
export const CANDIDATE_EXECUTION_POLICY_V2_VERSION = 2;
export const CANDIDATE_EXECUTION_POLICY_V2_MAX_FRAME_BYTES = 1024 * 1024;
export const CANDIDATE_EXECUTION_POLICY_V2_MAX_SNAPSHOT_BYTES = 64 * 1024 * 1024;
export const CANDIDATE_EXECUTION_POLICY_V2_MAX_SNAPSHOT_TOTAL_BYTES = 512 * 1024 * 1024;
export const CANDIDATE_EXECUTION_POLICY_V2_MAX_RECORDS = 8192;
export const CANDIDATE_EXECUTION_POLICY_V2_MAX_PATH_UTF8_BYTES = 4096;
const text = z.string().trim().min(1);
const id = text.regex(/^[a-z0-9][a-z0-9-]*$/);
const textList = z.array(text);
const launchPolicy = z.record(z.string().regex(/^[a-z][A-Za-z0-9]*$/), text).optional();
const sha256 = z.string().regex(/^[a-f0-9]{64}$/);
const relativePath = z.string().min(1).max(1024).regex(/^(?!\/)(?!.*(?:^|\/)\.\.?\/)[A-Za-z0-9._/@+:*?-]+$/);
const environmentName = z.string().regex(/^[A-Z_][A-Z0-9_]*$/);
const candidateExecutionPolicyV2 = z.object({
schema: z.literal(CANDIDATE_EXECUTION_POLICY_V2_SCHEMA),
version: z.literal(CANDIDATE_EXECUTION_POLICY_V2_VERSION),
check_id: id,
registered_definition_sha256: sha256,
package_json_path: z.literal('package.json'),
package_script_name: z.string().min(1).max(256).regex(/^[A-Za-z0-9:_-]+$/),
package_script_raw: z.string().min(1).max(64 * 1024),
input_rules: z.object({
allowSpecial: z.literal(false),
allowSymlinks: z.literal(false),
exclude: z.array(relativePath).max(CANDIDATE_EXECUTION_POLICY_V2_MAX_RECORDS),
include: z.array(relativePath).min(1).max(CANDIDATE_EXECUTION_POLICY_V2_MAX_RECORDS),
roots: z.array(relativePath).min(1).max(CANDIDATE_EXECUTION_POLICY_V2_MAX_RECORDS),
}).strict(),
environment_allowlist: z.array(environmentName).max(128),
runtime_manifest: z.object({
browser: z.null(),
dependencies: z.array(z.object({
realpath: z.string().min(1).max(CANDIDATE_EXECUTION_POLICY_V2_MAX_PATH_UTF8_BYTES),
bytes: z.number().int().min(0).max(1024 * 1024 * 1024),
sha256,
}).strict()).max(CANDIDATE_EXECUTION_POLICY_V2_MAX_RECORDS),
node: z.object({
entryBytes: z.number().int().min(0).max(1024 * 1024 * 1024),
entrySha256: sha256,
entryUtf8: z.string().max(CANDIDATE_EXECUTION_POLICY_V2_MAX_FRAME_BYTES).optional(),
realpath: z.string().min(1).max(CANDIDATE_EXECUTION_POLICY_V2_MAX_PATH_UTF8_BYTES),
}).strict(),
npm: z.object({
entryBytes: z.number().int().min(0).max(1024 * 1024 * 1024),
entrySha256: sha256,
entryUtf8: z.string().max(CANDIDATE_EXECUTION_POLICY_V2_MAX_FRAME_BYTES).optional(),
realpath: z.string().min(1).max(CANDIDATE_EXECUTION_POLICY_V2_MAX_PATH_UTF8_BYTES),
}).strict(),
packageJson: z.object({
bytes: z.number().int().min(0).max(CANDIDATE_EXECUTION_POLICY_V2_MAX_FRAME_BYTES),
sha256,
utf8: z.string().max(CANDIDATE_EXECUTION_POLICY_V2_MAX_FRAME_BYTES).optional(),
}).strict(),
packageLock: z.null(),
}).strict(),
limits: z.object({
maxFileBytes: z.literal(CANDIDATE_EXECUTION_POLICY_V2_MAX_SNAPSHOT_BYTES),
maxFrameBytes: z.literal(CANDIDATE_EXECUTION_POLICY_V2_MAX_FRAME_BYTES),
maxPathUtf8Bytes: z.literal(CANDIDATE_EXECUTION_POLICY_V2_MAX_PATH_UTF8_BYTES),
maxRecords: z.literal(CANDIDATE_EXECUTION_POLICY_V2_MAX_RECORDS),
maxTotalBytes: z.literal(CANDIDATE_EXECUTION_POLICY_V2_MAX_SNAPSHOT_TOTAL_BYTES),
}).strict(),
}).strict().superRefine((value, context) => {
if (new Set(value.input_rules.include).size !== value.input_rules.include.length) {
context.addIssue({ code: 'custom', path: ['input_rules', 'include'], message: 'input include paths must be unique' });
}
if (new Set(value.input_rules.exclude).size !== value.input_rules.exclude.length) {
context.addIssue({ code: 'custom', path: ['input_rules', 'exclude'], message: 'input exclude paths must be unique' });
}
if (new Set(value.environment_allowlist).size !== value.environment_allowlist.length) {
context.addIssue({ code: 'custom', path: ['environment_allowlist'], message: 'environment names must be unique' });
}
if (new Set(value.input_rules.roots).size !== value.input_rules.roots.length) {
context.addIssue({ code: 'custom', path: ['input_rules', 'roots'], message: 'input roots must be unique' });
}
for (const name of ['node', 'npm']) {
const entry = value.runtime_manifest[name];
if (entry.entryUtf8 !== undefined) {
const bytes = Buffer.from(entry.entryUtf8, 'utf8');
const entryDigest = createHash('sha256').update(bytes).digest('hex');
if (bytes.length !== entry.entryBytes || entryDigest !== entry.entrySha256) {
context.addIssue({ code: 'custom', path: ['runtime_manifest', name, 'entryUtf8'], message: 'runtime entryUtf8 must match its declared entryBytes and entrySha256' });
}
}
}
const packageJson = value.runtime_manifest.packageJson;
if (packageJson.utf8 !== undefined) {
const bytes = Buffer.from(packageJson.utf8, 'utf8');
const packageDigest = createHash('sha256').update(bytes).digest('hex');
if (bytes.length !== packageJson.bytes || packageDigest !== packageJson.sha256) {
context.addIssue({ code: 'custom', path: ['runtime_manifest', 'packageJson', 'utf8'], message: 'package json utf8 must match its declared bytes and sha256' });
}
}
});
const domain = z.object({
id,
@@ -36,6 +129,7 @@ const check = z.object({
command: text,
args: textList.optional(),
source: z.record(z.string(), z.unknown()).optional(),
candidate_execution_policy: candidateExecutionPolicyV2.optional(),
resources: textList.optional(),
snapshot: z.object({
paths: textList.min(1),
@@ -386,3 +480,139 @@ export function planProjectConfigMigration(value) {
blockers: [{ code: 'CONFIG_MIGRATION_UNAVAILABLE', schema: value?.schema ?? null }],
};
}
function policyFail(message, code, details) {
throw new TorchError(message, { code, details });
}
function canonicalJson(value) {
if (value === null || typeof value !== 'object') return JSON.stringify(value);
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`;
}
function fieldFrame(label, value) {
const bytes = Buffer.from(value, 'utf8');
return Buffer.concat([Buffer.from(`${label}:${bytes.length}\n`, 'ascii'), bytes, Buffer.from('\n', 'ascii')]);
}
function digest(bytes) {
return createHash('sha256').update(bytes).digest('hex');
}
function deepFreeze(value) {
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
for (const child of Object.values(value)) deepFreeze(child);
Object.freeze(value);
}
return value;
}
/**
* A data-only, versioned representation of the registered check definition.
* The policy itself is deliberately excluded so its digest cannot self-reference.
*/
export function canonicalRegisteredCheckDefinitionBytesV2(checkDefinition) {
const parsed = z.object({
id,
title: text.optional(),
command: text,
args: textList.optional(),
source: z.record(z.string(), z.unknown()).optional(),
resources: textList.optional(),
snapshot: z.object({ paths: textList.min(1), source_commit_file: text }).strict().optional(),
conditions: z.unknown().optional(),
}).strict().safeParse(checkDefinition);
if (!parsed.success) policyFail('Registered check definition is invalid', 'CANDIDATE_EXECUTION_POLICY_INVALID', formatIssues(parsed.error.issues));
const bytes = Buffer.from(`candidate-check-definition/v2alpha1\n${canonicalJson(parsed.data)}\n`, 'utf8');
if (bytes.length > CANDIDATE_EXECUTION_POLICY_V2_MAX_FRAME_BYTES) {
policyFail('Registered check definition exceeds the policy frame bound', 'CANDIDATE_EXECUTION_POLICY_FRAME_TOO_LARGE');
}
return bytes;
}
export function registeredCheckDefinitionDigestV2(checkDefinition) {
return digest(canonicalRegisteredCheckDefinitionBytesV2(checkDefinition));
}
/**
* Produces the fixed UTF-8/LF v2 policy preimage. It has no filesystem, process,
* ControlPlane, or registration side effects; an issued native parent is required
* before a parsed policy becomes execution authority.
*/
export function canonicalCandidateExecutionPolicyBytesV2(policy) {
const parsed = candidateExecutionPolicyV2.safeParse(policy);
if (!parsed.success) policyFail('Candidate execution policy is invalid', 'CANDIDATE_EXECUTION_POLICY_INVALID', formatIssues(parsed.error.issues));
const value = parsed.data;
const fields = [
['schema', value.schema],
['version', String(value.version)],
['check_id', value.check_id],
['registered_definition_sha256', value.registered_definition_sha256],
['package_json_path', value.package_json_path],
['package_script_name', value.package_script_name],
['package_script_raw', value.package_script_raw],
['input_rules_json', canonicalJson(value.input_rules)],
['environment_allowlist_json', canonicalJson(value.environment_allowlist)],
['runtime_manifest_json', canonicalJson(value.runtime_manifest)],
['limits_json', canonicalJson(value.limits)],
];
const bytes = Buffer.concat([
Buffer.from('candidate-execution-policy/v2alpha1\n', 'ascii'),
...fields.map(([label, fieldValue]) => fieldFrame(label, fieldValue)),
]);
if (bytes.length > CANDIDATE_EXECUTION_POLICY_V2_MAX_FRAME_BYTES) {
policyFail('Candidate execution policy exceeds the policy frame bound', 'CANDIDATE_EXECUTION_POLICY_FRAME_TOO_LARGE');
}
return bytes;
}
export function candidateExecutionPolicyDigestV2(policy) {
return digest(canonicalCandidateExecutionPolicyBytesV2(policy));
}
/**
* Resolves only a policy already present in a parsed project configuration. This
* validates declaration consistency but does not authenticate a caller or issue
* native authority; Work's registered CheckService admission remains that origin.
*/
export function resolveRegisteredCandidateExecutionPolicyV2(input = {}) {
if (!input || typeof input !== 'object' || Array.isArray(input)
|| Object.keys(input).some((key) => !new Set(['config', 'checkId']).has(key))) {
policyFail('Candidate policy resolver input is invalid', 'CANDIDATE_EXECUTION_POLICY_INPUT_INVALID');
}
const { config, checkId } = input;
validateProjectConfig(config);
if (typeof checkId !== 'string' || !/^[a-z0-9][a-z0-9-]*$/.test(checkId)) {
policyFail('Candidate policy check ID is invalid', 'CANDIDATE_EXECUTION_POLICY_INPUT_INVALID');
}
const checkDefinition = config.checks.find((entry) => entry.id === checkId);
if (!checkDefinition) policyFail('Registered check definition is absent', 'CANDIDATE_CHECK_UNREGISTERED', { checkId });
const policy = checkDefinition.candidate_execution_policy;
if (!policy) {
policyFail('Candidate snapshot policy is not registered for this check', 'CANDIDATE_SNAPSHOT_POLICY_UNDECLARED', { checkId });
}
const definition = { ...checkDefinition };
delete definition.candidate_execution_policy;
const definitionDigest = registeredCheckDefinitionDigestV2(definition);
if (policy.check_id !== checkDefinition.id || policy.registered_definition_sha256 !== definitionDigest) {
policyFail('Candidate policy does not bind the registered check definition', 'CANDIDATE_EXECUTION_POLICY_BINDING_MISMATCH');
}
const scriptName = checkDefinition.args?.[1];
const scriptRaw = checkDefinition.source?.type === 'package-script' ? checkDefinition.source.command : undefined;
if (checkDefinition.command !== 'npm' || checkDefinition.args?.[0] !== 'run'
|| typeof scriptName !== 'string' || policy.package_script_name !== scriptName
|| typeof scriptRaw !== 'string' || policy.package_script_raw !== scriptRaw) {
policyFail('Candidate policy does not bind the registered npm package script', 'CANDIDATE_EXECUTION_POLICY_BINDING_MISMATCH');
}
const bytes = canonicalCandidateExecutionPolicyBytesV2(policy);
return deepFreeze({
schema: CANDIDATE_EXECUTION_POLICY_V2_SCHEMA,
checkId,
policy: structuredClone(policy),
bytes,
digest: digest(bytes),
registeredDefinitionBytes: canonicalRegisteredCheckDefinitionBytesV2(definition),
registeredDefinitionDigest: definitionDigest,
});
}