// Cluster Plugin Package publisher boundary; keep provenance recovery authority explicit. import { assertPluginPackageInstallMatchesLock, type PluginPackageInstallCommit, type PluginPackageInstallCreate, type PluginPackageInstallRecord, type PluginPackageInstallRecoveryCursor, type PluginPackageInstallRecoveryPage, type PluginPackageInstallRepository, type PluginPackageLock, } from '@qinglong/runtime-core/plugin-package-install'; import { normalizePluginPackageStageEvidence, type PluginPackageStageEvidence, } from '@qinglong/runtime-core/plugin-package-installation'; import { createPluginPackagePublisherProvenance, type PluginPackagePublisherProvenance, } from '@qinglong/runtime-core/plugin-package-publisher-provenance'; import { PostgresPluginPackageInstallRepository, } from '@qinglong/cluster-postgres/plugin-package-install'; import { POSTGRES_PLUGIN_PACKAGE_PROVENANCE_RECOVERY_PAGE_LIMIT, PostgresPluginPackagePublisherProvenanceRepository, type PluginPackagePublisherProvenanceRecoveryCursor, } from '@qinglong/cluster-postgres/package-executor'; import type { ClusterPluginPackageStageAuthority } from '../recovery/pluginPackageOciStage'; export const MAX_CLUSTER_PLUGIN_PACKAGE_PROVENANCE_RECOVERY_PAGES = 64; export interface ClusterPluginPackagePublisherProvenanceRecoveryResult { readonly pages: number; readonly scanned: number; readonly created: number; readonly existing: number; readonly remaining: boolean; readonly safeToAdmit: boolean; } function stageEvidence( record: Readonly, ): Readonly { if (record.stageReceipt === null) { throw new TypeError( 'Plugin Package install lacks durable stage evidence for provenance', ); } return normalizePluginPackageStageEvidence({ stageRef: record.stageReceipt.stageRef, artifactDigest: record.stageReceipt.artifactDigest, manifestDigest: record.stageReceipt.manifestDigest, contentDigest: record.stageReceipt.contentDigest, evidenceDigest: record.stageReceipt.evidenceDigest, }); } async function provenanceFor( authority: ClusterPluginPackageStageAuthority, lock: Readonly, record: Readonly, ): Promise> { assertPluginPackageInstallMatchesLock(lock, record); const stage = stageEvidence(record); const signature = await authority.publisherEvidence(lock, stage); return createPluginPackagePublisherProvenance({ projectId: record.projectId, packageName: record.packageName, installationId: record.installationId, lockDigest: record.lockDigest, artifactDigest: stage.artifactDigest, manifestDigest: stage.manifestDigest, contentDigest: stage.contentDigest, stageEvidenceDigest: stage.evidenceDigest, signature, }); } export class ClusterPluginPackageProvenanceInstallRepository implements PluginPackageInstallRepository { constructor( private readonly installs: PostgresPluginPackageInstallRepository, private readonly provenance: PostgresPluginPackagePublisherProvenanceRepository, private readonly authority: ClusterPluginPackageStageAuthority, private readonly trustAuthorityId: string, ) { if ( !(installs instanceof PostgresPluginPackageInstallRepository) || !(provenance instanceof PostgresPluginPackagePublisherProvenanceRepository) || !authority || typeof authority.publisherEvidence !== 'function' || typeof trustAuthorityId !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(trustAuthorityId) ) { throw new TypeError( 'Cluster Plugin Package provenance install repository is invalid', ); } } find( projectId: string, packageName: string, ): Promise | null> { return this.installs.find(projectId, packageName); } findLock( lockDigest: string, ): Promise | null> { return this.installs.findLock(lockDigest); } create(command: Readonly): Promise< Readonly<{ status: 'created' | 'existing'; record: Readonly; }> > { return this.installs.create(command); } async commit(command: Readonly): Promise< Readonly<{ status: 'committed' | 'existing'; record: Readonly; }> > { if (command.record.state !== 'staged') { return this.installs.commit(command); } const lock = await this.installs.findLock(command.record.lockDigest); if (!lock) { throw new TypeError('Plugin Package stage lock is unavailable'); } return this.provenance.commitStage( command, await provenanceFor(this.authority, lock, command.record), this.trustAuthorityId, ); } listRecoveryPage(options: { readonly limit: number; readonly after?: Readonly; }): Promise> { return this.installs.listRecoveryPage(options); } } export async function recoverClusterPluginPackagePublisherProvenance( installs: PostgresPluginPackageInstallRepository, repository: PostgresPluginPackagePublisherProvenanceRepository, authority: ClusterPluginPackageStageAuthority, options: Readonly<{ trustAuthorityId: string; pageSize?: number; maxPages?: number; }>, ): Promise> { const pageSize = options.pageSize ?? 16; const maxPages = options.maxPages ?? 16; if ( !(installs instanceof PostgresPluginPackageInstallRepository) || !(repository instanceof PostgresPluginPackagePublisherProvenanceRepository) || !authority || typeof authority.verify !== 'function' || typeof authority.publisherEvidence !== 'function' || typeof options.trustAuthorityId !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test( options.trustAuthorityId, ) || !Number.isSafeInteger(pageSize) || pageSize < 1 || pageSize > POSTGRES_PLUGIN_PACKAGE_PROVENANCE_RECOVERY_PAGE_LIMIT || !Number.isSafeInteger(maxPages) || maxPages < 1 || maxPages > MAX_CLUSTER_PLUGIN_PACKAGE_PROVENANCE_RECOVERY_PAGES ) { throw new TypeError( 'Cluster Plugin Package provenance recovery configuration is invalid', ); } let after: | Readonly | undefined; const counts = { pages: 0, scanned: 0, created: 0, existing: 0, }; let exhausted = false; while (counts.pages < maxPages) { const page = await repository.listMissingPage({ limit: pageSize, ...(after ? { after } : {}), }); counts.pages += 1; for (const record of page.records) { const lock = await installs.findLock(record.lockDigest); if (!lock || record.stageReceipt === null) { throw new TypeError( 'Cluster Plugin Package provenance recovery source is incomplete', ); } assertPluginPackageInstallMatchesLock(lock, record); await authority.verify(lock, record.stageReceipt); const result = await repository.recordExisting( record, await provenanceFor(authority, lock, record), options.trustAuthorityId, ); counts.scanned += 1; counts[result.status] += 1; } if (!page.truncated) { exhausted = true; break; } after = page.next; } const probe = await repository.listMissingPage({ limit: 1 }); const remaining = !exhausted || probe.records.length > 0; return Object.freeze({ ...counts, remaining, safeToAdmit: !remaining, }); }