{"version":3,"file":"pack.d.ts","sourceRoot":"","sources":["../../src/pack/pack.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC,MAAM,WAAW,aAAa;IAC7B,MAAM,EAAE,QAAQ,GAAG,eAAe,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,SAAS,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,eAAe;IAC/B,UAAU,EAAE,CAAC,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,eAAe,CAAC;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AA6ED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,CA0LlE;AA+BD;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAoBzG;AAED,MAAM,WAAW,sBAAsB;IACtC,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;CACf;AAED,sEAAsE;AACtE,wBAAsB,sBAAsB,CAC3C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,eAAe,GACvB,OAAO,CAAC,sBAAsB,CAAC,CAOjC;AAED,qEAAqE;AACrE,wBAAsB,WAAW,CAChC,OAAO,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,QAAQ,EAAE,eAAe,CAAC;IAAC,SAAS,EAAE,sBAAsB,CAAA;CAAE,CAAC,CAQ3E","sourcesContent":["/**\n * Optimization Pack (pack.json v1) — the shareable envelope.\n *\n * See docs/optimization-packs.md. A pack bundles data-type parts (prompts,\n * skills, memory) and code-type parts (components, workflows) described by a\n * single pack.json, with a content-addressed integrity digest so a pack cannot\n * be silently modified in transit.\n *\n * This module parses/validates pack.json and computes/verifies integrity. It is\n * pure (no bundle mutation); import wiring lives elsewhere.\n */\n\nimport { lstat } from \"node:fs/promises\";\nimport { join, sep } from \"node:path\";\nimport { assertEvoAbiId, assertEvoCapability, assertEvoComponentId } from \"../components/manifest.ts\";\nimport { readRegularDirectoryNoFollow, readRegularFileNoFollow, resolveRegularDirectory } from \"../secure-file.ts\";\nimport { canonicalJson, sha256 } from \"../storage.ts\";\n\nexport const EVO_PACK_FORMAT = 1;\n\nexport interface EvoPackPrompt {\n\ttarget: \"system\" | \"append-system\";\n\tfile: string;\n}\n\nexport interface EvoPackSkill {\n\tname: string;\n\tdir: string;\n}\n\nexport interface EvoPackMemory {\n\tfile: string;\n}\n\nexport interface EvoPackComponent {\n\tsurface: string;\n\tabi: string;\n\tid: string;\n\tartifact: string;\n\tcapabilities: string[];\n}\n\nexport interface EvoPackWorkflow {\n\tid: string;\n\ttrigger: string;\n\tabi: string;\n\tartifact: string;\n\tcapabilities: string[];\n}\n\nexport interface EvoPackContents {\n\tprompts: EvoPackPrompt[];\n\tskills: EvoPackSkill[];\n\tmemory: EvoPackMemory[];\n\tcomponents: EvoPackComponent[];\n\tworkflows: EvoPackWorkflow[];\n}\n\nexport interface EvoPackManifest {\n\tpackFormat: 1;\n\tname: string;\n\tversion: string;\n\tauthor?: string;\n\tdescription?: string;\n\tcontents: EvoPackContents;\n\trequiresAbis: string[];\n\trequiresCapabilities: string[];\n\tintegrity?: string;\n}\n\nconst NAME_RE = /^[a-z0-9][a-z0-9._-]*$/;\nconst VERSION_RE = /^[A-Za-z0-9][A-Za-z0-9._+-]*$/;\nconst TRIGGER_RE = /^\\/[a-z0-9][a-z0-9-]*$/;\nconst INTEGRITY_RE = /^sha256:[0-9a-f]{64}$/;\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 asArray(value: unknown, label: string): unknown[] {\n\tif (value === undefined) return [];\n\tif (!Array.isArray(value)) throw new Error(`${label} must be an array`);\n\treturn value;\n}\n\nfunction asString(value: unknown, label: string): string {\n\tif (typeof value !== \"string\" || !value) throw new Error(`${label} must be a non-empty string`);\n\treturn value;\n}\n\nfunction exactKeys(record: Record<string, unknown>, allowed: readonly string[], label: string): void {\n\tconst allowedKeys = new Set(allowed);\n\tfor (const key of Object.keys(record)) {\n\t\tif (!allowedKeys.has(key)) throw new Error(`${label} has unknown key: ${key}`);\n\t}\n}\n\nfunction assertUnique(values: readonly string[], label: string): void {\n\tif (new Set(values).size !== values.length) throw new Error(`${label} must not contain duplicates`);\n}\n\nfunction assertSameSet(actual: readonly string[], expected: readonly string[], label: string): void {\n\tconst sortedActual = [...actual].sort();\n\tconst sortedExpected = [...new Set(expected)].sort();\n\tif (sortedActual.join(\"\\n\") !== sortedExpected.join(\"\\n\")) {\n\t\tthrow new Error(`${label} must exactly match declared code parts: expected [${sortedExpected.join(\", \")}]`);\n\t}\n}\n\n/**\n * A pack-relative path must be relative, forward-only, and contain no `..`\n * segment, absolute prefix, or NUL. This blocks path traversal out of the pack\n * before any file is read.\n */\nfunction assertSafeRelPath(value: string, label: string): void {\n\tif (typeof value !== \"string\" || !value) throw new Error(`${label} must be a non-empty path`);\n\tif (value.includes(\"\\0\")) throw new Error(`${label} contains a NUL byte`);\n\tif (value.includes(\"\\\\\")) throw new Error(`${label} must use forward slashes: ${value}`);\n\tif (value.startsWith(\"/\") || /^[A-Za-z]:/.test(value)) {\n\t\tthrow new Error(`${label} must be relative, got ${value}`);\n\t}\n\tconst segments = value.split(\"/\");\n\tfor (const segment of segments) {\n\t\tif (segment === \"..\") throw new Error(`${label} must not contain '..': ${value}`);\n\t\tif (segment === \"\" || segment === \".\") {\n\t\t\tthrow new Error(`${label} must be a canonical relative path: ${value}`);\n\t\t}\n\t}\n}\n\nfunction assertCapabilities(value: unknown, label: string): string[] {\n\tconst list = asArray(value, label);\n\tconst out: string[] = [];\n\tfor (const [index, entry] of list.entries()) {\n\t\tif (typeof entry !== \"string\") throw new Error(`${label}[${index}] must be a string`);\n\t\tassertEvoCapability(entry, `${label}[${index}]`);\n\t\tout.push(entry);\n\t}\n\tif (new Set(out).size !== out.length) throw new Error(`${label} must not contain duplicates`);\n\treturn out;\n}\n\n/**\n * Parse and validate a pack.json manifest. Fail-closed: any malformed field\n * rejects the whole manifest rather than silently dropping it.\n */\nexport function parseEvoPackManifest(raw: unknown): EvoPackManifest {\n\tconst record = asRecord(raw, \"pack.json\");\n\texactKeys(\n\t\trecord,\n\t\t[\n\t\t\t\"packFormat\",\n\t\t\t\"name\",\n\t\t\t\"version\",\n\t\t\t\"author\",\n\t\t\t\"description\",\n\t\t\t\"contents\",\n\t\t\t\"requiresAbis\",\n\t\t\t\"requiresCapabilities\",\n\t\t\t\"integrity\",\n\t\t],\n\t\t\"pack.json\",\n\t);\n\n\tif (record.packFormat !== EVO_PACK_FORMAT) {\n\t\tthrow new Error(`pack.json packFormat must be ${EVO_PACK_FORMAT}`);\n\t}\n\tconst name = asString(record.name, \"pack.json name\");\n\tif (!NAME_RE.test(name) || name.length > 128) throw new Error(`pack.json name is invalid: ${name}`);\n\tconst version = asString(record.version, \"pack.json version\");\n\tif (!VERSION_RE.test(version) || version.length > 128) throw new Error(`pack.json version is invalid: ${version}`);\n\n\tif (record.author !== undefined && typeof record.author !== \"string\") {\n\t\tthrow new Error(\"pack.json author must be a string\");\n\t}\n\tif (record.description !== undefined && typeof record.description !== \"string\") {\n\t\tthrow new Error(\"pack.json description must be a string\");\n\t}\n\tif (record.integrity !== undefined) {\n\t\tif (typeof record.integrity !== \"string\" || !INTEGRITY_RE.test(record.integrity)) {\n\t\t\tthrow new Error(\"pack.json integrity must be 'sha256:<64 hex>'\");\n\t\t}\n\t}\n\n\tconst contentsRecord = asRecord(record.contents ?? {}, \"pack.json contents\");\n\texactKeys(contentsRecord, [\"prompts\", \"skills\", \"memory\", \"components\", \"workflows\"], \"pack.json contents\");\n\n\tconst prompts: EvoPackPrompt[] = asArray(contentsRecord.prompts, \"contents.prompts\").map((entry, i) => {\n\t\tconst p = asRecord(entry, `contents.prompts[${i}]`);\n\t\texactKeys(p, [\"target\", \"file\"], `contents.prompts[${i}]`);\n\t\tif (p.target !== \"system\" && p.target !== \"append-system\") {\n\t\t\tthrow new Error(`contents.prompts[${i}].target must be 'system' or 'append-system'`);\n\t\t}\n\t\tconst file = asString(p.file, `contents.prompts[${i}].file`);\n\t\tassertSafeRelPath(file, `contents.prompts[${i}].file`);\n\t\treturn { target: p.target, file };\n\t});\n\n\tconst skills: EvoPackSkill[] = asArray(contentsRecord.skills, \"contents.skills\").map((entry, i) => {\n\t\tconst s = asRecord(entry, `contents.skills[${i}]`);\n\t\texactKeys(s, [\"name\", \"dir\"], `contents.skills[${i}]`);\n\t\tconst skillName = asString(s.name, `contents.skills[${i}].name`);\n\t\tconst dir = asString(s.dir, `contents.skills[${i}].dir`);\n\t\tassertSafeRelPath(dir, `contents.skills[${i}].dir`);\n\t\treturn { name: skillName, dir };\n\t});\n\n\tconst memory: EvoPackMemory[] = asArray(contentsRecord.memory, \"contents.memory\").map((entry, i) => {\n\t\tconst m = asRecord(entry, `contents.memory[${i}]`);\n\t\texactKeys(m, [\"file\"], `contents.memory[${i}]`);\n\t\tconst file = asString(m.file, `contents.memory[${i}].file`);\n\t\tassertSafeRelPath(file, `contents.memory[${i}].file`);\n\t\treturn { file };\n\t});\n\n\tconst components: EvoPackComponent[] = asArray(contentsRecord.components, \"contents.components\").map((entry, i) => {\n\t\tconst c = asRecord(entry, `contents.components[${i}]`);\n\t\texactKeys(c, [\"surface\", \"abi\", \"id\", \"artifact\", \"capabilities\"], `contents.components[${i}]`);\n\t\tconst id = asString(c.id, `contents.components[${i}].id`);\n\t\tassertEvoComponentId(id, `contents.components[${i}].id`);\n\t\tconst abi = asString(c.abi, `contents.components[${i}].abi`);\n\t\tassertEvoAbiId(abi, `contents.components[${i}].abi`);\n\t\tconst surface = asString(c.surface, `contents.components[${i}].surface`);\n\t\tassertEvoComponentId(surface, `contents.components[${i}].surface`);\n\t\tif (surface === \"workflow\") {\n\t\t\tthrow new Error(`contents.components[${i}].surface workflow must use contents.workflows`);\n\t\t}\n\t\tconst artifact = asString(c.artifact, `contents.components[${i}].artifact`);\n\t\tassertSafeRelPath(artifact, `contents.components[${i}].artifact`);\n\t\treturn {\n\t\t\tsurface,\n\t\t\tabi,\n\t\t\tid,\n\t\t\tartifact,\n\t\t\tcapabilities: assertCapabilities(c.capabilities, `contents.components[${i}].capabilities`),\n\t\t};\n\t});\n\n\tconst workflows: EvoPackWorkflow[] = asArray(contentsRecord.workflows, \"contents.workflows\").map((entry, i) => {\n\t\tconst w = asRecord(entry, `contents.workflows[${i}]`);\n\t\texactKeys(w, [\"id\", \"trigger\", \"abi\", \"artifact\", \"capabilities\"], `contents.workflows[${i}]`);\n\t\tconst id = asString(w.id, `contents.workflows[${i}].id`);\n\t\tassertEvoComponentId(id, `contents.workflows[${i}].id`);\n\t\tconst trigger = asString(w.trigger, `contents.workflows[${i}].trigger`);\n\t\tif (!TRIGGER_RE.test(trigger)) throw new Error(`contents.workflows[${i}].trigger must look like '/name'`);\n\t\tconst abi = asString(w.abi, `contents.workflows[${i}].abi`);\n\t\tassertEvoAbiId(abi, `contents.workflows[${i}].abi`);\n\t\tconst artifact = asString(w.artifact, `contents.workflows[${i}].artifact`);\n\t\tassertSafeRelPath(artifact, `contents.workflows[${i}].artifact`);\n\t\treturn {\n\t\t\tid,\n\t\t\ttrigger,\n\t\t\tabi,\n\t\t\tartifact,\n\t\t\tcapabilities: assertCapabilities(w.capabilities, `contents.workflows[${i}].capabilities`),\n\t\t};\n\t});\n\n\tconst requiresAbis = asArray(record.requiresAbis, \"pack.json requiresAbis\").map((entry, i) => {\n\t\tconst abi = asString(entry, `requiresAbis[${i}]`);\n\t\tassertEvoAbiId(abi, `requiresAbis[${i}]`);\n\t\treturn abi;\n\t});\n\tassertUnique(requiresAbis, \"pack.json requiresAbis\");\n\tconst requiresCapabilities = assertCapabilities(record.requiresCapabilities, \"pack.json requiresCapabilities\");\n\tassertUnique(\n\t\tprompts.map((entry) => entry.file),\n\t\t\"contents.prompts file references\",\n\t);\n\tassertUnique(\n\t\tskills.map((entry) => entry.name),\n\t\t\"contents.skills names\",\n\t);\n\tassertUnique(\n\t\tskills.map((entry) => entry.dir),\n\t\t\"contents.skills directory references\",\n\t);\n\tassertUnique(\n\t\tmemory.map((entry) => entry.file),\n\t\t\"contents.memory file references\",\n\t);\n\tassertUnique(\n\t\tcomponents.filter((entry) => entry.surface !== \"tool\").map((entry) => entry.surface),\n\t\t\"contents.components singleton surfaces\",\n\t);\n\tassertUnique(\n\t\tcomponents.map((entry) => entry.id),\n\t\t\"contents.components ids\",\n\t);\n\tassertUnique(\n\t\tcomponents.map((entry) => entry.artifact),\n\t\t\"contents.components artifact references\",\n\t);\n\tassertUnique(\n\t\tworkflows.map((entry) => entry.id),\n\t\t\"contents.workflows ids\",\n\t);\n\tassertUnique(\n\t\tworkflows.map((entry) => entry.trigger),\n\t\t\"contents.workflows triggers\",\n\t);\n\tassertUnique(\n\t\tworkflows.map((entry) => entry.artifact),\n\t\t\"contents.workflows artifact references\",\n\t);\n\tconst codeParts = [...components, ...workflows];\n\tassertUnique(\n\t\tcodeParts.map((entry) => entry.id),\n\t\t\"pack code part ids\",\n\t);\n\tassertSameSet(\n\t\trequiresAbis,\n\t\tcodeParts.map((entry) => entry.abi),\n\t\t\"pack.json requiresAbis\",\n\t);\n\tassertSameSet(\n\t\trequiresCapabilities,\n\t\tcodeParts.flatMap((entry) => entry.capabilities),\n\t\t\"pack.json requiresCapabilities\",\n\t);\n\n\treturn {\n\t\tpackFormat: EVO_PACK_FORMAT,\n\t\tname,\n\t\tversion,\n\t\t...(record.author === undefined ? {} : { author: record.author as string }),\n\t\t...(record.description === undefined ? {} : { description: record.description as string }),\n\t\tcontents: { prompts, skills, memory, components, workflows },\n\t\trequiresAbis,\n\t\trequiresCapabilities,\n\t\t...(record.integrity === undefined ? {} : { integrity: record.integrity as string }),\n\t};\n}\n\n/** All pack-relative file references, split into single files and directory roots. */\nfunction referencedPaths(manifest: EvoPackManifest): { files: string[]; dirs: string[] } {\n\tconst files: string[] = [];\n\tconst dirs: string[] = [];\n\tfor (const p of manifest.contents.prompts) files.push(p.file);\n\tfor (const m of manifest.contents.memory) files.push(m.file);\n\tfor (const s of manifest.contents.skills) dirs.push(s.dir);\n\tfor (const c of manifest.contents.components) dirs.push(c.artifact);\n\tfor (const w of manifest.contents.workflows) dirs.push(w.artifact);\n\treturn { files, dirs };\n}\n\nasync function walkFiles(root: string, rel: string, out: string[]): Promise<void> {\n\tconst abs = join(root, rel);\n\tconst info = await lstat(abs);\n\tif (info.isSymbolicLink()) throw new Error(`pack path is a symlink: ${rel}`);\n\tif (info.isDirectory()) {\n\t\tconst entries = await readRegularDirectoryNoFollow(abs, `pack directory ${rel}`);\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.isSymbolicLink()) throw new Error(`pack path is a symlink: ${rel}/${entry.name}`);\n\t\t\tawait walkFiles(root, rel === \"\" ? entry.name : `${rel}/${entry.name}`, out);\n\t\t}\n\t} else if (info.isFile()) {\n\t\tout.push(rel);\n\t} else {\n\t\tthrow new Error(`pack path is neither file nor directory: ${rel}`);\n\t}\n}\n\n/**\n * Content-addressed integrity over the canonical pack: the manifest (minus its\n * own integrity field) plus a sorted map of every referenced file's sha256. Any\n * change to the manifest or any referenced byte changes the digest.\n */\nexport async function computeEvoPackIntegrity(packDir: string, manifest: EvoPackManifest): Promise<string> {\n\tconst root = await resolveRegularDirectory(packDir, \"pack directory\");\n\tconst { files, dirs } = referencedPaths(manifest);\n\tconst collected: string[] = [];\n\tfor (const file of files) await walkFiles(root, file, collected);\n\tfor (const dir of dirs) await walkFiles(root, dir, collected);\n\n\tconst digests: Record<string, string> = {};\n\tfor (const rel of collected) {\n\t\tif (digests[rel] !== undefined) continue;\n\t\tdigests[rel] = sha256(await readRegularFileNoFollow(join(root, rel.split(\"/\").join(sep)), `pack file ${rel}`));\n\t}\n\n\tconst { integrity: _omit, ...manifestWithoutIntegrity } = manifest;\n\tconst canonical = canonicalJson({\n\t\tpackIntegritySchemaVersion: 1,\n\t\tmanifest: manifestWithoutIntegrity,\n\t\tfiles: digests,\n\t});\n\treturn `sha256:${sha256(canonical)}`;\n}\n\nexport interface EvoPackIntegrityResult {\n\tok: boolean;\n\texpected: string | undefined;\n\tactual: string;\n}\n\n/** Verify a pack's declared integrity against its actual contents. */\nexport async function verifyEvoPackIntegrity(\n\tpackDir: string,\n\tmanifest: EvoPackManifest,\n): Promise<EvoPackIntegrityResult> {\n\tconst actual = await computeEvoPackIntegrity(packDir, manifest);\n\treturn {\n\t\tok: manifest.integrity !== undefined && manifest.integrity === actual,\n\t\texpected: manifest.integrity,\n\t\tactual,\n\t};\n}\n\n/** Load, parse, and integrity-check a pack directory's pack.json. */\nexport async function loadEvoPack(\n\tpackDir: string,\n): Promise<{ manifest: EvoPackManifest; integrity: EvoPackIntegrityResult }> {\n\tconst root = await resolveRegularDirectory(packDir, \"pack directory\");\n\tconst raw = JSON.parse(\n\t\t(await readRegularFileNoFollow(join(root, \"pack.json\"), \"pack.json\")).toString(\"utf8\"),\n\t) as unknown;\n\tconst manifest = parseEvoPackManifest(raw);\n\tconst integrity = await verifyEvoPackIntegrity(root, manifest);\n\treturn { manifest, integrity };\n}\n"]}