{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/bundle/schema.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAGX,cAAc,EAEd,YAAY,EAMZ,MAAM,aAAa,CAAC;AAmXrB,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAkBlD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAyC9D;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CA0ClE","sourcesContent":["import { isAbsolute, normalize } from \"node:path\";\nimport type { EvoCapabilityGrant } from \"../components/capabilities/broker.ts\";\nimport { EVO_CAPABILITY_NAMES } from \"../components/capabilities/protocol.ts\";\nimport type {\n\tBundleManagedSource,\n\tBundleManagedSourceKind,\n\tBundleManifest,\n\tBundleModelRouting,\n\tBundlePolicy,\n\tBundlePolicyLimits,\n\tBundleValidationPolicy,\n\tDeterministicCheck,\n\tEvoComponentSelection,\n\tEvoWorkflowSelection,\n} from \"../types.ts\";\n\nconst DIGEST_PATTERN = /^[a-f0-9]{64}$/;\nconst ASSET_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;\nconst POLICY_KEYS = new Set([\n\t\"schemaVersion\",\n\t\"promptOrder\",\n\t\"stablePromptPaths\",\n\t\"dynamicPromptPaths\",\n\t\"enabledTools\",\n\t\"enabledFeatures\",\n\t\"coreAssets\",\n\t\"limits\",\n\t\"modelRouting\",\n\t\"components\",\n\t\"tools\",\n\t\"workflows\",\n\t\"validation\",\n\t\"managedSources\",\n]);\nconst LIMIT_KEYS = new Set([\"promptBytes\", \"skillBytes\", \"totalBytes\"]);\nconst MODEL_ROUTING_KEYS = new Set([\"worker\", \"reflector\", \"critic\"]);\nconst COMPONENT_SELECTION_KEYS = new Set([\"id\", \"abi\", \"artifactDigest\", \"config\", \"grants\"]);\nconst COMPONENT_SURFACE_PATTERN = /^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/;\nconst COMPONENT_ID_PATTERN = COMPONENT_SURFACE_PATTERN;\nconst COMPONENT_ABI_PATTERN = /^[a-z][a-z0-9-]*\\/v[1-9][0-9]*$/;\nconst WORKFLOW_TRIGGER_PATTERN = /^\\/[a-z0-9][a-z0-9-]*$/;\nconst VALIDATION_KEYS = new Set([\"requiredChecks\"]);\nconst MANAGED_SOURCE_KEYS = new Set([\"kind\", \"sourceRoot\", \"relativePath\", \"targetPath\", \"sourceSha256\"]);\nconst MANAGED_SOURCE_KINDS = new Set<BundleManagedSourceKind>([\n\t\"custom-prompt\",\n\t\"append-prompt\",\n\t\"context\",\n\t\"prompt\",\n\t\"skill\",\n\t\"memory\",\n\t\"preference\",\n]);\nconst DETERMINISTIC_CHECKS = new Set<DeterministicCheck>([\"bundle-compile\"]);\nconst CAPABILITY_NAMES = new Set<string>(EVO_CAPABILITY_NAMES);\n\nfunction asRecord(value: unknown, label: string): Record<string, unknown> {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\tthrow new Error(`${label} must be an object`);\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction rejectUnknownKeys(record: Record<string, unknown>, allowed: Set<string>, label: string): void {\n\tfor (const key of Object.keys(record)) {\n\t\tif (!allowed.has(key)) throw new Error(`${label} has unknown key: ${key}`);\n\t}\n}\n\nfunction parseStringArray(value: unknown, label: string): string[] | undefined {\n\tif (value === undefined) return undefined;\n\tif (!Array.isArray(value) || value.some((entry) => typeof entry !== \"string\" || entry.length === 0)) {\n\t\tthrow new Error(`${label} must be an array of non-empty strings`);\n\t}\n\tconst result = value as string[];\n\tif (new Set(result).size !== result.length) throw new Error(`${label} must not contain duplicates`);\n\treturn [...result];\n}\n\nfunction parsePositiveInteger(value: unknown, label: string): number | undefined {\n\tif (value === undefined) return undefined;\n\tif (!Number.isSafeInteger(value) || (value as number) <= 0) throw new Error(`${label} must be a positive integer`);\n\treturn value as number;\n}\n\nfunction requirePositiveInteger(value: unknown, label: string): number {\n\tconst parsed = parsePositiveInteger(value, label);\n\tif (parsed === undefined) throw new Error(`${label} is required`);\n\treturn parsed;\n}\n\nfunction requirePositiveNumber(value: unknown, label: string): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0) {\n\t\tthrow new Error(`${label} must be a positive finite number`);\n\t}\n\treturn value;\n}\n\nfunction requireStringArray(value: unknown, label: string, allowEmpty: boolean): string[] {\n\tconst parsed = parseStringArray(value, label);\n\tif (!parsed || (!allowEmpty && parsed.length === 0)) {\n\t\tthrow new Error(`${label} must be ${allowEmpty ? \"an\" : \"a non-empty\"} array of non-empty strings`);\n\t}\n\treturn [...parsed].sort();\n}\n\nfunction parseCapabilityGrant(value: unknown, label: string): EvoCapabilityGrant {\n\tconst grant = asRecord(value, label);\n\tif (typeof grant.capability !== \"string\" || !CAPABILITY_NAMES.has(grant.capability)) {\n\t\tthrow new Error(`${label}.capability is unsupported`);\n\t}\n\tif (grant.capability !== \"infer\" && grant.capability !== \"spawn-agent\") {\n\t\trejectUnknownKeys(grant, new Set([\"capability\", \"maxCalls\"]), label);\n\t\treturn {\n\t\t\tcapability: grant.capability,\n\t\t\tmaxCalls: requirePositiveInteger(grant.maxCalls, `${label}.maxCalls`),\n\t\t} as EvoCapabilityGrant;\n\t}\n\trejectUnknownKeys(\n\t\tgrant,\n\t\tnew Set([\n\t\t\t\"capability\",\n\t\t\t\"maxCalls\",\n\t\t\t\"models\",\n\t\t\t\"maxInputTokens\",\n\t\t\t\"maxOutputTokens\",\n\t\t\t\"maxTotalTokens\",\n\t\t\t\"maxCostUsd\",\n\t\t\t\"maxOutputTokensPerCall\",\n\t\t\t\"tools\",\n\t\t]),\n\t\tlabel,\n\t);\n\tif (grant.capability === \"infer\" && grant.tools !== undefined) {\n\t\tthrow new Error(`${label}.tools is only supported for spawn-agent`);\n\t}\n\tconst parsed: EvoCapabilityGrant = {\n\t\tcapability: grant.capability,\n\t\tmaxCalls: requirePositiveInteger(grant.maxCalls, `${label}.maxCalls`),\n\t\tmodels: requireStringArray(grant.models, `${label}.models`, false),\n\t\tmaxInputTokens: requirePositiveInteger(grant.maxInputTokens, `${label}.maxInputTokens`),\n\t\tmaxOutputTokens: requirePositiveInteger(grant.maxOutputTokens, `${label}.maxOutputTokens`),\n\t\tmaxTotalTokens: requirePositiveInteger(grant.maxTotalTokens, `${label}.maxTotalTokens`),\n\t\tmaxCostUsd: requirePositiveNumber(grant.maxCostUsd, `${label}.maxCostUsd`),\n\t\tmaxOutputTokensPerCall: requirePositiveInteger(grant.maxOutputTokensPerCall, `${label}.maxOutputTokensPerCall`),\n\t\t...(grant.tools === undefined ? {} : { tools: requireStringArray(grant.tools, `${label}.tools`, true) }),\n\t};\n\tif (parsed.maxTotalTokens < parsed.maxInputTokens || parsed.maxTotalTokens < parsed.maxOutputTokens) {\n\t\tthrow new Error(`${label}.maxTotalTokens must cover each token sub-budget`);\n\t}\n\tif (parsed.maxOutputTokensPerCall > parsed.maxOutputTokens) {\n\t\tthrow new Error(`${label}.maxOutputTokensPerCall exceeds maxOutputTokens`);\n\t}\n\treturn parsed;\n}\n\nfunction parseCapabilityGrants(value: unknown, label: string): EvoCapabilityGrant[] | undefined {\n\tif (value === undefined) return undefined;\n\tif (!Array.isArray(value)) throw new Error(`${label} must be an array`);\n\tconst grants = value.map((grant, index) => parseCapabilityGrant(grant, `${label}[${index}]`));\n\tif (new Set(grants.map((grant) => grant.capability)).size !== grants.length) {\n\t\tthrow new Error(`${label} must not contain duplicate capabilities`);\n\t}\n\treturn grants.sort((left, right) => left.capability.localeCompare(right.capability));\n}\n\nfunction parseLimits(value: unknown): BundlePolicyLimits | undefined {\n\tif (value === undefined) return undefined;\n\tconst record = asRecord(value, \"policy.limits\");\n\trejectUnknownKeys(record, LIMIT_KEYS, \"policy.limits\");\n\treturn {\n\t\tpromptBytes: parsePositiveInteger(record.promptBytes, \"policy.limits.promptBytes\"),\n\t\tskillBytes: parsePositiveInteger(record.skillBytes, \"policy.limits.skillBytes\"),\n\t\ttotalBytes: parsePositiveInteger(record.totalBytes, \"policy.limits.totalBytes\"),\n\t};\n}\n\nfunction parseModelRouting(value: unknown): BundleModelRouting | undefined {\n\tif (value === undefined) return undefined;\n\tconst record = asRecord(value, \"policy.modelRouting\");\n\trejectUnknownKeys(record, MODEL_ROUTING_KEYS, \"policy.modelRouting\");\n\tconst result: BundleModelRouting = {};\n\tfor (const key of MODEL_ROUTING_KEYS) {\n\t\tconst route = record[key];\n\t\tif (route === undefined) continue;\n\t\tif (typeof route !== \"string\" || route.length === 0 || route.length > 200 || /\\s/.test(route)) {\n\t\t\tthrow new Error(`policy.modelRouting.${key} must be a non-empty provider/model identifier`);\n\t\t}\n\t\tresult[key as keyof BundleModelRouting] = route;\n\t}\n\treturn result;\n}\n\nfunction assertJsonData(value: unknown, label: string): void {\n\tif (value === null || typeof value === \"string\" || typeof value === \"boolean\") return;\n\tif (typeof value === \"number\") {\n\t\tif (!Number.isFinite(value)) throw new Error(`${label} contains a non-finite number`);\n\t\treturn;\n\t}\n\tif (Array.isArray(value)) {\n\t\tvalue.forEach((entry, index) => {\n\t\t\tassertJsonData(entry, `${label}[${index}]`);\n\t\t});\n\t\treturn;\n\t}\n\tif (typeof value !== \"object\" || value === null) throw new Error(`${label} must contain JSON data`);\n\tfor (const [key, entry] of Object.entries(value as Record<string, unknown>)) {\n\t\tif (key.includes(\"\\0\")) throw new Error(`${label} contains an invalid key`);\n\t\tassertJsonData(entry, `${label}.${key}`);\n\t}\n}\n\nfunction parseComponentSelection(value: unknown, label: string): EvoComponentSelection {\n\tconst selection = asRecord(value, label);\n\trejectUnknownKeys(selection, COMPONENT_SELECTION_KEYS, label);\n\tif (typeof selection.id !== \"string\" || !COMPONENT_ID_PATTERN.test(selection.id) || selection.id.length > 128) {\n\t\tthrow new Error(`${label}.id is invalid`);\n\t}\n\tif (typeof selection.abi !== \"string\" || !COMPONENT_ABI_PATTERN.test(selection.abi) || selection.abi.length > 128) {\n\t\tthrow new Error(`${label}.abi is invalid`);\n\t}\n\tif (typeof selection.artifactDigest !== \"string\" || !isDigest(selection.artifactDigest)) {\n\t\tthrow new Error(`${label}.artifactDigest must be a digest`);\n\t}\n\tlet config: Record<string, unknown> | undefined;\n\tif (selection.config !== undefined) {\n\t\tconfig = asRecord(selection.config, `${label}.config`);\n\t\tassertJsonData(config, `${label}.config`);\n\t}\n\tconst grants = parseCapabilityGrants(selection.grants, `${label}.grants`);\n\treturn {\n\t\tid: selection.id,\n\t\tabi: selection.abi,\n\t\tartifactDigest: selection.artifactDigest,\n\t\t...(config ? { config } : {}),\n\t\t...(grants ? { grants } : {}),\n\t};\n}\n\nfunction parseComponents(value: unknown): Record<string, EvoComponentSelection> | undefined {\n\tif (value === undefined) return undefined;\n\tconst record = asRecord(value, \"policy.components\");\n\tconst result: Record<string, EvoComponentSelection> = {};\n\tfor (const surface of Object.keys(record).sort()) {\n\t\tif (!COMPONENT_SURFACE_PATTERN.test(surface) || surface.length > 128) {\n\t\t\tthrow new Error(`policy.components has an invalid surface: ${surface}`);\n\t\t}\n\t\tif (surface === \"tool\" || surface === \"workflow\") {\n\t\t\tthrow new Error(`policy.components.${surface} must use the plural policy field`);\n\t\t}\n\t\tresult[surface] = parseComponentSelection(record[surface], `policy.components.${surface}`);\n\t}\n\treturn result;\n}\n\nfunction parseTools(value: unknown): EvoComponentSelection[] | undefined {\n\tif (value === undefined) return undefined;\n\tif (!Array.isArray(value)) throw new Error(\"policy.tools must be an array\");\n\tconst tools = value.map((entry, index) => {\n\t\tconst tool = parseComponentSelection(entry, `policy.tools[${index}]`);\n\t\tif (tool.abi !== \"tool/v1\") throw new Error(`policy.tools[${index}].abi must be tool/v1`);\n\t\treturn tool;\n\t});\n\tif (new Set(tools.map((tool) => tool.id)).size !== tools.length) {\n\t\tthrow new Error(\"policy.tools must not contain duplicate ids\");\n\t}\n\treturn tools;\n}\n\nfunction parseWorkflows(value: unknown): EvoWorkflowSelection[] | undefined {\n\tif (value === undefined) return undefined;\n\tif (!Array.isArray(value)) throw new Error(\"policy.workflows must be an array\");\n\tconst workflows = value.map((entry, index): EvoWorkflowSelection => {\n\t\tconst label = `policy.workflows[${index}]`;\n\t\tconst record = asRecord(entry, label);\n\t\trejectUnknownKeys(record, new Set([...COMPONENT_SELECTION_KEYS, \"trigger\"]), label);\n\t\tif (typeof record.trigger !== \"string\" || !WORKFLOW_TRIGGER_PATTERN.test(record.trigger)) {\n\t\t\tthrow new Error(`${label}.trigger must look like /name`);\n\t\t}\n\t\tconst selection = parseComponentSelection(\n\t\t\t{\n\t\t\t\tid: record.id,\n\t\t\t\tabi: record.abi,\n\t\t\t\tartifactDigest: record.artifactDigest,\n\t\t\t\t...(record.config === undefined ? {} : { config: record.config }),\n\t\t\t\t...(record.grants === undefined ? {} : { grants: record.grants }),\n\t\t\t},\n\t\t\tlabel,\n\t\t);\n\t\tif (selection.abi !== \"workflow/v1\") throw new Error(`${label}.abi must be workflow/v1`);\n\t\treturn { ...selection, trigger: record.trigger };\n\t});\n\tif (new Set(workflows.map((workflow) => workflow.id)).size !== workflows.length) {\n\t\tthrow new Error(\"policy.workflows must not contain duplicate ids\");\n\t}\n\tif (new Set(workflows.map((workflow) => workflow.trigger)).size !== workflows.length) {\n\t\tthrow new Error(\"policy.workflows must not contain duplicate triggers\");\n\t}\n\treturn workflows;\n}\n\nfunction parseValidation(value: unknown): BundleValidationPolicy | undefined {\n\tif (value === undefined) return undefined;\n\tconst record = asRecord(value, \"policy.validation\");\n\trejectUnknownKeys(record, VALIDATION_KEYS, \"policy.validation\");\n\tconst checks = parseStringArray(record.requiredChecks, \"policy.validation.requiredChecks\");\n\tif (checks?.some((check) => !DETERMINISTIC_CHECKS.has(check as DeterministicCheck))) {\n\t\tthrow new Error(\"policy.validation.requiredChecks contains an unsupported check\");\n\t}\n\treturn { requiredChecks: checks as DeterministicCheck[] | undefined };\n}\n\nfunction assertManagedSourceRelativePath(path: string, label: string): void {\n\tif (path.includes(\"\\\\\") || path.startsWith(\"/\") || path.includes(\"\\0\")) {\n\t\tthrow new Error(`${label} must be a normalized relative path`);\n\t}\n\tconst parts = path.split(\"/\");\n\tif (parts.some((part) => !part || part === \".\" || part === \"..\")) {\n\t\tthrow new Error(`${label} contains an unsafe segment`);\n\t}\n}\n\nfunction parseManagedSources(value: unknown): BundleManagedSource[] | undefined {\n\tif (value === undefined) return undefined;\n\tif (!Array.isArray(value)) throw new Error(\"policy.managedSources must be an array\");\n\tconst sources = value.map((entry, index) => {\n\t\tconst label = `policy.managedSources[${index}]`;\n\t\tconst record = asRecord(entry, label);\n\t\trejectUnknownKeys(record, MANAGED_SOURCE_KEYS, label);\n\t\tif (typeof record.kind !== \"string\" || !MANAGED_SOURCE_KINDS.has(record.kind as BundleManagedSourceKind)) {\n\t\t\tthrow new Error(`${label}.kind is unsupported`);\n\t\t}\n\t\tif (\n\t\t\ttypeof record.sourceRoot !== \"string\" ||\n\t\t\t!record.sourceRoot ||\n\t\t\trecord.sourceRoot.length > 4096 ||\n\t\t\trecord.sourceRoot.includes(\"\\0\") ||\n\t\t\t!isAbsolute(record.sourceRoot) ||\n\t\t\tnormalize(record.sourceRoot) !== record.sourceRoot\n\t\t) {\n\t\t\tthrow new Error(`${label}.sourceRoot must be a canonical absolute path`);\n\t\t}\n\t\tif (typeof record.relativePath !== \"string\") throw new Error(`${label}.relativePath must be a string`);\n\t\tassertManagedSourceRelativePath(record.relativePath, `${label}.relativePath`);\n\t\tif (typeof record.targetPath !== \"string\") throw new Error(`${label}.targetPath must be a string`);\n\t\tassertAssetPath(record.targetPath);\n\t\tif (record.targetPath === \"policy.json\" || record.targetPath === \"bundle.json\") {\n\t\t\tthrow new Error(`${label}.targetPath must reference a managed data asset`);\n\t\t}\n\t\tif (typeof record.sourceSha256 !== \"string\" || !isDigest(record.sourceSha256)) {\n\t\t\tthrow new Error(`${label}.sourceSha256 must be a digest`);\n\t\t}\n\t\tconst kind = record.kind as BundleManagedSourceKind;\n\t\tconst expectedPrefix =\n\t\t\tkind === \"custom-prompt\" || kind === \"append-prompt\" || kind === \"prompt\"\n\t\t\t\t? \"prompts/\"\n\t\t\t\t: kind === \"skill\"\n\t\t\t\t\t? \"skills/\"\n\t\t\t\t\t: \"memory/\";\n\t\tif (!record.targetPath.startsWith(expectedPrefix)) {\n\t\t\tthrow new Error(`${label}.targetPath does not match kind ${kind}`);\n\t\t}\n\t\treturn {\n\t\t\tkind,\n\t\t\tsourceRoot: record.sourceRoot,\n\t\t\trelativePath: record.relativePath,\n\t\t\ttargetPath: record.targetPath,\n\t\t\tsourceSha256: record.sourceSha256,\n\t\t};\n\t});\n\tconst targets = sources.map((source) => source.targetPath);\n\tif (new Set(targets).size !== targets.length) {\n\t\tthrow new Error(\"policy.managedSources must not contain duplicate target paths\");\n\t}\n\tconst origins = sources.map((source) => `${source.sourceRoot}\\0${source.relativePath}`);\n\tif (new Set(origins).size !== origins.length) {\n\t\tthrow new Error(\"policy.managedSources must not contain duplicate source paths\");\n\t}\n\tfor (const singletonKind of [\"custom-prompt\", \"append-prompt\"] as const) {\n\t\tif (sources.filter((source) => source.kind === singletonKind).length > 1) {\n\t\t\tthrow new Error(`policy.managedSources may contain at most one ${singletonKind}`);\n\t\t}\n\t}\n\treturn sources;\n}\n\nexport function isDigest(value: string): boolean {\n\treturn DIGEST_PATTERN.test(value);\n}\n\nexport function assertAssetPath(path: string): void {\n\tif (path.includes(\"\\\\\") || path.startsWith(\"/\") || path.includes(\"\\0\")) {\n\t\tthrow new Error(`Bundle path is not a normalized relative path: ${path}`);\n\t}\n\tconst parts = path.split(\"/\");\n\tif (parts.some((part) => part.length === 0 || part === \".\" || part === \"..\")) {\n\t\tthrow new Error(`Bundle path contains an unsafe segment: ${path}`);\n\t}\n\tconst allowed =\n\t\tpath === \"policy.json\" ||\n\t\tpath === \"bundle.json\" ||\n\t\tpath === \"memory/preferences.json\" ||\n\t\t(parts.length === 2 &&\n\t\t\t(parts[0] === \"prompts\" || parts[0] === \"memory\") &&\n\t\t\tparts[1].endsWith(\".md\") &&\n\t\t\tASSET_NAME_PATTERN.test(parts[1].slice(0, -3))) ||\n\t\t(parts.length === 3 && parts[0] === \"skills\" && ASSET_NAME_PATTERN.test(parts[1]) && parts[2] === \"SKILL.md\");\n\tif (!allowed) throw new Error(`Bundle contains a disallowed data path: ${path}`);\n}\n\nexport function parseBundlePolicy(value: unknown): BundlePolicy {\n\tconst record = asRecord(value, \"policy.json\");\n\trejectUnknownKeys(record, POLICY_KEYS, \"policy.json\");\n\tif (record.schemaVersion !== 1) throw new Error(\"policy.json schemaVersion must be 1\");\n\tconst policy: BundlePolicy = {\n\t\tschemaVersion: 1,\n\t\tpromptOrder: parseStringArray(record.promptOrder, \"policy.promptOrder\"),\n\t\tstablePromptPaths: parseStringArray(record.stablePromptPaths, \"policy.stablePromptPaths\"),\n\t\tdynamicPromptPaths: parseStringArray(record.dynamicPromptPaths, \"policy.dynamicPromptPaths\"),\n\t\tenabledTools: parseStringArray(record.enabledTools, \"policy.enabledTools\"),\n\t\tenabledFeatures: parseStringArray(record.enabledFeatures, \"policy.enabledFeatures\"),\n\t\tcoreAssets: parseStringArray(record.coreAssets, \"policy.coreAssets\"),\n\t\tlimits: parseLimits(record.limits),\n\t\tmodelRouting: parseModelRouting(record.modelRouting),\n\t\tcomponents: parseComponents(record.components),\n\t\ttools: parseTools(record.tools),\n\t\tworkflows: parseWorkflows(record.workflows),\n\t\tvalidation: parseValidation(record.validation),\n\t\tmanagedSources: parseManagedSources(record.managedSources),\n\t};\n\tfor (const path of [\n\t\t...(policy.promptOrder ?? []),\n\t\t...(policy.stablePromptPaths ?? []),\n\t\t...(policy.dynamicPromptPaths ?? []),\n\t\t...(policy.coreAssets ?? []),\n\t]) {\n\t\tassertAssetPath(path);\n\t}\n\tconst stable = new Set(policy.stablePromptPaths ?? []);\n\tif (policy.dynamicPromptPaths?.some((path) => stable.has(path))) {\n\t\tthrow new Error(\"A prompt cannot be both stable and dynamic\");\n\t}\n\tconst codePartIds = [\n\t\t...Object.values(policy.components ?? {}).map((selection) => selection.id),\n\t\t...(policy.tools ?? []).map((selection) => selection.id),\n\t\t...(policy.workflows ?? []).map((selection) => selection.id),\n\t];\n\tif (new Set(codePartIds).size !== codePartIds.length) {\n\t\tthrow new Error(\"policy code selections must not contain duplicate ids\");\n\t}\n\treturn policy;\n}\n\nexport function parseBundleManifest(value: unknown): BundleManifest {\n\tconst record = asRecord(value, \"bundle.json\");\n\trejectUnknownKeys(record, new Set([\"schemaVersion\", \"parentDigest\", \"summary\", \"files\"]), \"bundle.json\");\n\tif (record.schemaVersion !== 1) throw new Error(\"bundle.json schemaVersion must be 1\");\n\tif (record.parentDigest !== null && (typeof record.parentDigest !== \"string\" || !isDigest(record.parentDigest))) {\n\t\tthrow new Error(\"bundle.json parentDigest must be null or a sha256 digest\");\n\t}\n\tif (typeof record.summary !== \"string\" || record.summary.length === 0 || record.summary.length > 240) {\n\t\tthrow new Error(\"bundle.json summary must contain 1-240 characters\");\n\t}\n\tif (!Array.isArray(record.files) || record.files.length === 0)\n\t\tthrow new Error(\"bundle.json files must not be empty\");\n\tconst files = record.files.map((entry, index) => {\n\t\tconst file = asRecord(entry, `bundle.json files[${index}]`);\n\t\trejectUnknownKeys(file, new Set([\"path\", \"sha256\", \"bytes\"]), `bundle.json files[${index}]`);\n\t\tif (typeof file.path !== \"string\") throw new Error(`bundle.json files[${index}].path must be a string`);\n\t\tassertAssetPath(file.path);\n\t\tif (file.path === \"bundle.json\") throw new Error(\"bundle.json cannot include itself in files\");\n\t\tif (typeof file.sha256 !== \"string\" || !isDigest(file.sha256)) {\n\t\t\tthrow new Error(`bundle.json files[${index}].sha256 must be a digest`);\n\t\t}\n\t\tif (!Number.isSafeInteger(file.bytes) || (file.bytes as number) < 0) {\n\t\t\tthrow new Error(`bundle.json files[${index}].bytes must be a non-negative integer`);\n\t\t}\n\t\treturn { path: file.path, sha256: file.sha256, bytes: file.bytes as number };\n\t});\n\tif (files[0]?.path !== \"policy.json\") throw new Error(\"policy.json must be the first manifest file\");\n\tif (new Set(files.map((file) => file.path)).size !== files.length)\n\t\tthrow new Error(\"bundle.json has duplicate files\");\n\tconst sorted = [...files].sort((left, right) => {\n\t\tif (left.path === \"policy.json\") return -1;\n\t\tif (right.path === \"policy.json\") return 1;\n\t\treturn left.path.localeCompare(right.path);\n\t});\n\tif (files.some((file, index) => file.path !== sorted[index].path))\n\t\tthrow new Error(\"bundle.json files are not sorted\");\n\treturn {\n\t\tschemaVersion: 1,\n\t\tparentDigest: record.parentDigest as string | null,\n\t\tsummary: record.summary,\n\t\tfiles,\n\t};\n}\n"]}