import type { ConnectionId, CredentialBindingValue, ScopeId } from "@executor-js/sdk"; export type SourceCredentialSlot = | { readonly kind: "secret"; readonly slot: string; readonly label: string; readonly optional?: boolean; } | { readonly kind: "connection"; readonly slot: string; readonly label: string; readonly optional?: boolean; }; export type SourceCredentialBindingRef = { readonly slot: string; readonly scopeId: ScopeId; readonly value: CredentialBindingValue; }; const scopeRank = (ranks: ReadonlyMap, scopeId: ScopeId): number => ranks.get(scopeId) ?? Number.MAX_SAFE_INTEGER; export const effectiveSourceCredentialBinding = ( rows: readonly SourceCredentialBindingRef[], slot: string, targetScope: ScopeId, ranks: ReadonlyMap, ): SourceCredentialBindingRef | null => rows .filter( (row) => row.slot === slot && scopeRank(ranks, row.scopeId) >= scopeRank(ranks, targetScope), ) .sort((a, b) => scopeRank(ranks, a.scopeId) - scopeRank(ranks, b.scopeId))[0] ?? null; const liveConnectionSet = ( values?: ReadonlySet | readonly ConnectionId[], ): ReadonlySet | undefined => (values ? new Set(Array.from(values, String)) : undefined); export const missingSourceCredentialLabels = (input: { readonly slots: readonly SourceCredentialSlot[]; readonly bindings: readonly SourceCredentialBindingRef[]; readonly targetScope: ScopeId; readonly scopeRanks: ReadonlyMap; readonly liveConnectionIds?: ReadonlySet | readonly ConnectionId[]; }): string[] => { const liveConnections = liveConnectionSet(input.liveConnectionIds); const missing: string[] = []; for (const slot of input.slots) { if (slot.optional) continue; const binding = effectiveSourceCredentialBinding( input.bindings, slot.slot, input.targetScope, input.scopeRanks, ); if (slot.kind === "secret" && binding?.value.kind !== "secret") { missing.push(slot.label); continue; } if (slot.kind === "connection") { if (binding?.value.kind !== "connection") { missing.push(slot.label); continue; } if (liveConnections && !liveConnections.has(binding.value.connectionId)) { missing.push(slot.label); } } } return missing; };