mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-11-11 23:31:18 +00:00
TASK-6e4ff870-a386-4a11-a59c-444ebc23732e: add candidate source policy parent
This commit is contained in:
1 parent
955672c1db
commit
db0d6731d3
2 files changed
+443
No files matched your search
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user