{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/discovery/service.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdtemp, rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { EvoCapabilityGrant } from \"../components/capabilities/broker.ts\";\nimport { type EvoPackImportPreflight, type EvoPackImportResult, importEvoPack } from \"../pack/import.ts\";\nimport { type EvoPackManifest, loadEvoPack } from \"../pack/pack.ts\";\nimport type { EvoPaths } from \"../paths.ts\";\nimport { canonicalJson } from \"../storage.ts\";\nimport { type EvoDiscoveredPack, EvoPackDiscoveryClient } from \"./client.ts\";\nimport {\n\ttype EvoPackRegistryEntry,\n\ttype EvoPackRegistryEntryTrust,\n\ttype EvoRawFileSource,\n\ttype EvoTrustedRegistrySigner,\n\tparseEvoRawFileSource,\n} from \"./registry.ts\";\nimport { EvoPackDiscoveryTransport } from \"./transport.ts\";\n\ntype EvoTrustedPackRegistryEntryTrust = Extract<EvoPackRegistryEntryTrust, { trusted: true }>;\n\ninterface EvoTrustedDiscoveredPack extends Omit<EvoDiscoveredPack, \"trust\"> {\n\ttrust: EvoTrustedPackRegistryEntryTrust;\n}\n\nexport interface EvoPackRegistryServiceOptions {\n\tregistrySources: readonly EvoRawFileSource[];\n\ttransport: EvoPackDiscoveryTransport;\n\ttrustedSigners: readonly EvoTrustedRegistrySigner[];\n\ttemporaryDirectory?: string;\n}\n\nexport interface EvoPackRegistrySearchOptions {\n\tquery?: string;\n\ttrustedOnly?: boolean;\n\tlimit?: number;\n\tincludeTrustedManifests?: boolean;\n\tsignal?: AbortSignal;\n}\n\nexport interface EvoPackRegistrySearchResult extends EvoDiscoveredPack {\n\tmanifest?: EvoPackManifest;\n}\n\nexport interface EvoPackRegistryInstallOptions {\n\tname: string;\n\tversion?: string;\n\t/** Pin the content identity shown by a prior trusted inspection. */\n\texpectedIntegrity: string;\n\tpaths: EvoPaths;\n\tparentDigest: string;\n\tgrantsByComponent?: Readonly<Record<string, readonly EvoCapabilityGrant[]>>;\n\tsignal?: AbortSignal;\n\t/** Runs after full pack preflight and before any pack-owned artifact or proposal is persisted. */\n\tbeforeStage?: (context: EvoPackRegistryBeforeStageContext) => Promise<void>;\n\t/** Sandbox mode for the post-stage executable validation of imported components. */\n\tsandbox?: boolean;\n}\n\nexport interface EvoPackRegistryInspection {\n\tregistrySource: EvoRawFileSource;\n\tpackSource: EvoRawFileSource;\n\tentry: EvoPackRegistryEntry;\n\ttrust: EvoTrustedPackRegistryEntryTrust;\n\tmanifest: EvoPackManifest;\n}\n\nexport interface EvoPackRegistryBeforeStageContext extends EvoPackRegistryInspection {\n\tpackDirectory: string;\n\tfileCount: number;\n\ttotalBytes: number;\n\tpreflight: EvoPackImportPreflight;\n}\n\nexport interface EvoPackRegistryInstallResult {\n\tregistrySource: EvoRawFileSource;\n\tpackSource: EvoRawFileSource;\n\ttrust: EvoTrustedPackRegistryEntryTrust;\n\tfileCount: number;\n\ttotalBytes: number;\n\timported: EvoPackImportResult;\n}\n\nfunction compareText(left: string, right: string): number {\n\treturn left < right ? -1 : left > right ? 1 : 0;\n}\n\nfunction positiveLimit(value: number | undefined): number | undefined {\n\tif (value === undefined) return undefined;\n\tif (!Number.isSafeInteger(value) || value <= 0 || value > 10_000) {\n\t\tthrow new Error(\"registry search limit must be an integer from 1 to 10000\");\n\t}\n\treturn value;\n}\n\nfunction matchesQuery(pack: EvoDiscoveredPack, query: string): boolean {\n\tconst needle = query.toLocaleLowerCase(\"en-US\");\n\treturn [pack.entry.name, pack.entry.version, pack.entry.author, pack.entry.description]\n\t\t.filter((value): value is string => value !== undefined)\n\t\t.some((value) => value.toLocaleLowerCase(\"en-US\").includes(needle));\n}\n\n/** Read-only registry search plus trusted, fully verified staging installs. */\nexport class EvoPackRegistryService {\n\tprivate readonly registrySources: readonly EvoRawFileSource[];\n\tprivate readonly transport: EvoPackDiscoveryTransport;\n\tprivate readonly trustedSigners: readonly EvoTrustedRegistrySigner[];\n\tprivate readonly temporaryDirectory: string;\n\n\tconstructor(options: EvoPackRegistryServiceOptions) {\n\t\tif (!(options.transport instanceof EvoPackDiscoveryTransport)) {\n\t\t\tthrow new Error(\"registry service transport must be an EvoPackDiscoveryTransport\");\n\t\t}\n\t\tif (!Array.isArray(options.registrySources) || options.registrySources.length === 0) {\n\t\t\tthrow new Error(\"registry service requires at least one registry source\");\n\t\t}\n\t\tthis.registrySources = options.registrySources.map((source) => parseEvoRawFileSource(source));\n\t\tthis.transport = options.transport;\n\t\tthis.trustedSigners = [...options.trustedSigners];\n\t\tthis.temporaryDirectory = options.temporaryDirectory ?? tmpdir();\n\t}\n\n\tprivate client(signal?: AbortSignal): EvoPackDiscoveryClient {\n\t\treturn new EvoPackDiscoveryClient({\n\t\t\treadRegistryIndex: (source) => this.transport.readRegistryIndex(source, signal),\n\t\t\tfetchPackManifest: (source) => this.transport.fetchPackManifest(source, signal),\n\t\t\ttrustedSigners: this.trustedSigners,\n\t\t});\n\t}\n\n\tprivate async selectTrusted(\n\t\tname: string,\n\t\tversion?: string,\n\t\tsignal?: AbortSignal,\n\t): Promise<{ client: EvoPackDiscoveryClient; selected: EvoTrustedDiscoveredPack }> {\n\t\tsignal?.throwIfAborted();\n\t\tconst client = this.client(signal);\n\t\tconst discovered = await client.discover(this.registrySources);\n\t\tconst matches = discovered.filter(\n\t\t\t(pack) => pack.entry.name === name && (version === undefined || pack.entry.version === version),\n\t\t);\n\t\tif (matches.length === 0) {\n\t\t\tthrow new Error(`pack registry has no matching pack: ${name}${version === undefined ? \"\" : `@${version}`}`);\n\t\t}\n\t\tif (matches.length > 1) {\n\t\t\tthrow new Error(`pack registry selection is ambiguous; specify an exact version for ${name}`);\n\t\t}\n\t\tconst selected = matches[0];\n\t\tif (!selected.trust.trusted || selected.trust.status !== \"trusted\") {\n\t\t\tthrow new Error(\n\t\t\t\t`refusing to install untrusted registry entry ${selected.entry.name}@${selected.entry.version}: ${selected.trust.status}; trusted inspection is also required`,\n\t\t\t);\n\t\t}\n\t\treturn { client, selected: { ...selected, trust: selected.trust } };\n\t}\n\n\tasync search(options: EvoPackRegistrySearchOptions = {}): Promise<EvoPackRegistrySearchResult[]> {\n\t\tconst limit = positiveLimit(options.limit);\n\t\tconst query = options.query?.trim();\n\t\toptions.signal?.throwIfAborted();\n\t\tconst client = this.client(options.signal);\n\t\tconst discovered = await client.discover(this.registrySources);\n\t\tconst filtered = discovered.filter(\n\t\t\t(pack) =>\n\t\t\t\t(!options.trustedOnly || pack.trust.trusted) &&\n\t\t\t\t(query === undefined || query.length === 0 || matchesQuery(pack, query)),\n\t\t);\n\t\tfiltered.sort(\n\t\t\t(left, right) =>\n\t\t\t\tcompareText(left.entry.name, right.entry.name) ||\n\t\t\t\tcompareText(left.entry.version, right.entry.version) ||\n\t\t\t\tcompareText(left.registrySource.rawUrl, right.registrySource.rawUrl),\n\t\t);\n\t\tconst selected = limit === undefined ? filtered : filtered.slice(0, limit);\n\t\tif (!options.includeTrustedManifests) return selected;\n\t\tconst results: EvoPackRegistrySearchResult[] = [];\n\t\tfor (const pack of selected) {\n\t\t\toptions.signal?.throwIfAborted();\n\t\t\tresults.push(pack.trust.trusted ? { ...pack, manifest: await client.fetchPackManifest(pack) } : pack);\n\t\t}\n\t\treturn results;\n\t}\n\n\t/** Fetch and cross-check one exactly selected, trusted manifest without downloading its contents. */\n\tasync inspect(name: string, version?: string, signal?: AbortSignal): Promise<EvoPackRegistryInspection> {\n\t\tconst { client, selected } = await this.selectTrusted(name, version, signal);\n\t\tconst manifest = await client.fetchPackManifest(selected);\n\t\treturn {\n\t\t\tregistrySource: selected.registrySource,\n\t\t\tpackSource: selected.entry.source,\n\t\t\tentry: selected.entry,\n\t\t\ttrust: selected.trust,\n\t\t\tmanifest,\n\t\t};\n\t}\n\n\tasync install(options: EvoPackRegistryInstallOptions): Promise<EvoPackRegistryInstallResult> {\n\t\tconst { client, selected } = await this.selectTrusted(options.name, options.version, options.signal);\n\t\tif (selected.entry.integrity !== options.expectedIntegrity) {\n\t\t\tthrow new Error(\n\t\t\t\t`registry entry changed after inspection: expected ${options.expectedIntegrity}, got ${selected.entry.integrity}`,\n\t\t\t);\n\t\t}\n\t\tconst manifest = await client.fetchPackManifest(selected);\n\t\toptions.signal?.throwIfAborted();\n\t\tconst temporaryRoot = await mkdtemp(join(this.temporaryDirectory, \"evo-pack-registry-\"));\n\t\ttry {\n\t\t\tconst materialized = await this.transport.materializePack({\n\t\t\t\tsource: selected.entry.source,\n\t\t\t\texpectedManifest: manifest,\n\t\t\t\tdestination: join(temporaryRoot, \"pack\"),\n\t\t\t\tsignal: options.signal,\n\t\t\t});\n\t\t\tconst loaded = await loadEvoPack(materialized.directory);\n\t\t\tif (!loaded.integrity.ok || loaded.integrity.actual !== selected.entry.integrity) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`downloaded pack integrity does not match registry entry ${selected.entry.name}@${selected.entry.version}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (canonicalJson(loaded.manifest) !== canonicalJson(manifest)) {\n\t\t\t\tthrow new Error(\"downloaded pack manifest changed after verification\");\n\t\t\t}\n\t\t\toptions.signal?.throwIfAborted();\n\t\t\tconst beforeStage = options.beforeStage;\n\t\t\tconst imported = await importEvoPack({\n\t\t\t\tpaths: options.paths,\n\t\t\t\tparentDigest: options.parentDigest,\n\t\t\t\tpackDir: materialized.directory,\n\t\t\t\texpectedIntegrity: options.expectedIntegrity,\n\t\t\t\t...(options.sandbox === undefined ? {} : { sandbox: options.sandbox }),\n\t\t\t\t...(options.grantsByComponent === undefined ? {} : { grantsByComponent: options.grantsByComponent }),\n\t\t\t\t...(beforeStage === undefined\n\t\t\t\t\t? {}\n\t\t\t\t\t: {\n\t\t\t\t\t\t\tbeforeStage: (preflight: EvoPackImportPreflight) =>\n\t\t\t\t\t\t\t\tbeforeStage({\n\t\t\t\t\t\t\t\t\tregistrySource: selected.registrySource,\n\t\t\t\t\t\t\t\t\tpackSource: selected.entry.source,\n\t\t\t\t\t\t\t\t\tentry: selected.entry,\n\t\t\t\t\t\t\t\t\ttrust: selected.trust,\n\t\t\t\t\t\t\t\t\tmanifest: preflight.manifest,\n\t\t\t\t\t\t\t\t\tpackDirectory: preflight.packDirectory,\n\t\t\t\t\t\t\t\t\tfileCount: materialized.fileCount,\n\t\t\t\t\t\t\t\t\ttotalBytes: materialized.totalBytes,\n\t\t\t\t\t\t\t\t\tpreflight,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t}),\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tregistrySource: selected.registrySource,\n\t\t\t\tpackSource: selected.entry.source,\n\t\t\t\ttrust: selected.trust,\n\t\t\t\tfileCount: materialized.fileCount,\n\t\t\t\ttotalBytes: materialized.totalBytes,\n\t\t\t\timported,\n\t\t\t};\n\t\t} finally {\n\t\t\tawait rm(temporaryRoot, { recursive: true, force: true });\n\t\t}\n\t}\n}\n"]}