/**
 * Phase 4 Pipeline — Stage 4: Infrastructure Adapter Generator
 *
 * Generates infrastructure adapters that bridge DDD port interfaces to ERP
 * and database clients. One adapter is produced per port file found in the
 * Phase 3 project output. Also generates shared connection pool, IAM helper,
 * and telemetry wrapper files.
 *
 * Supported languages:
 *   typescript  — class implementing the port interface
 *   rust        — struct implementing the port trait
 *
 * Error codes:
 *   ECLI-P4-004  Adapter file write failure
 */
// Generated by Phase 4 pipeline — do not edit manually
import { existsSync, readdirSync, mkdirSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { hasTemplateSupport } from '../types.js';
import { createSpan, endSpan, emitSpan } from '../../phase2/telemetry.js';
// ============================================================================
// Constants
// ============================================================================
const DIR_MODE = 0o700;
const FILE_MODE = 0o600;
// ============================================================================
// Name Helpers
// ============================================================================
function toKebab(name) {
    return name
        .replace(/([a-z])([A-Z])/g, '$1-$2')
        .replace(/[^a-zA-Z0-9]+/g, '-')
        .replace(/^-|-$/g, '')
        .toLowerCase();
}
function toSnake(name) {
    return toKebab(name).replace(/-/g, '_');
}
function toPascal(name) {
    return name
        .replace(/(?:^|[-_\s])(\w)/g, (_, c) => c.toUpperCase())
        .replace(/[^a-zA-Z0-9]/g, '');
}
/**
 * Build a port interface name, stripping any existing I-prefix to prevent II*.
 */
function toPortName(baseName) {
    let pascal = toPascal(baseName);
    while (/^I[A-Z]/.test(pascal)) {
        pascal = pascal.slice(1);
    }
    return `${pascal}Port`;
}
function toCtxSlug(contextName) {
    return toKebab(contextName.replace(/Context$/i, ''));
}
function toCtxSnake(contextName) {
    return toSnake(contextName.replace(/Context$/i, ''));
}
/**
 * Sanitize a raw type string from mock contracts into a valid TypeScript type.
 */
function sanitizeType(raw, fallback) {
    if (!raw || raw === 'void')
        return 'void';
    const trimmed = raw.trim();
    if (/^[A-Z]/.test(trimmed) || /^(?:void|string|number|boolean|null|undefined|Record<|Array<|Promise<)/.test(trimmed)) {
        return trimmed;
    }
    if (trimmed.includes(':')) {
        const afterColon = trimmed.split(':').pop()?.trim();
        if (afterColon && /^[A-Za-z]/.test(afterColon))
            return afterColon;
    }
    if (/^[a-z][a-z0-9]*$/i.test(trimmed))
        return fallback;
    return fallback;
}
/**
 * Clean a raw method name from mock contracts.
 * Handles "POST /context/create-thing" → "createThing"
 */
function cleanMethodName(raw) {
    // Strip HTTP verb prefix
    const withoutVerb = raw.replace(/^\s*(GET|POST|PUT|PATCH|DELETE)\s+/i, '');
    // Extract last path segment
    const segments = withoutVerb.split('/').filter(Boolean);
    const last = segments[segments.length - 1] ?? raw;
    const cleaned = last.replace(/[^a-zA-Z0-9-_]/g, '');
    if (!cleaned)
        return 'execute';
    // kebab-case to camelCase
    return cleaned
        .replace(/[-_]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
        .replace(/^(.)/, (_, c) => c.toLowerCase());
}
/**
 * Infer a TypeScript type from a field name based on common naming conventions.
 */
function inferFieldType(fieldName) {
    const lower = fieldName.toLowerCase();
    if (lower.endsWith('_id') || lower === 'id' || lower.endsWith('uuid') || lower.endsWith('code'))
        return 'string';
    if (lower.endsWith('_at') || lower.endsWith('_date') || lower.endsWith('_time') || lower === 'timestamp')
        return 'string';
    if (lower.endsWith('_count') || lower === 'quantity' || lower === 'amount' || lower === 'total'
        || lower.endsWith('_number') || lower.endsWith('_size') || lower.endsWith('_length')
        || lower === 'price' || lower === 'cost' || lower === 'weight' || lower === 'height'
        || lower === 'width' || lower === 'depth' || lower === 'volume' || lower === 'rate'
        || lower.endsWith('_percentage') || lower.endsWith('_ratio') || lower.endsWith('_score')
        || lower.endsWith('_density') || lower === 'yield' || lower === 'capacity')
        return 'number';
    if (lower.startsWith('is_') || lower.startsWith('has_') || lower.startsWith('can_')
        || lower === 'active' || lower === 'enabled' || lower === 'deleted' || lower === 'verified')
        return 'boolean';
    if (lower.endsWith('_list') || lower.endsWith('_items') || lower.endsWith('_tags'))
        return 'string[]';
    return 'string';
}
/**
 * Build typed DTO fields from aggregate entities and value objects.
 * Returns an array of field lines like `  readonly field_name: string;`
 */
function buildTypedFieldsFromAggregate(aggregate) {
    const fields = [];
    const seenNames = new Set();
    // Add entity names as typed fields (sub-entities become references)
    for (const entity of aggregate.entities) {
        const fieldName = toSnake(entity.name);
        if (seenNames.has(fieldName))
            continue;
        seenNames.add(fieldName);
        // The root entity name becomes an _id reference; sub-entities become string refs
        const idField = `${fieldName}_id`;
        if (!seenNames.has(idField)) {
            seenNames.add(idField);
            fields.push(`  readonly ${idField}: string;`);
        }
    }
    // Add value object names as typed fields with inferred types
    for (const vo of aggregate.valueObjects) {
        const fieldName = toSnake(vo.name);
        if (seenNames.has(fieldName))
            continue;
        seenNames.add(fieldName);
        const fieldType = inferFieldType(fieldName);
        fields.push(`  readonly ${fieldName}: ${fieldType};`);
    }
    return fields;
}
/**
 * Generate a TypeScript port interface with proper DTOs and method signatures.
 */
function generateTypescriptPort(portBaseName, boundedContext, mockContract, aggregateFields) {
    const interfaceName = toPortName(portBaseName);
    const ctxPascal = toPascal(boundedContext);
    const dtoTypes = new Set();
    let methodSigs;
    if (mockContract && mockContract.methods.length > 0) {
        methodSigs = mockContract.methods
            .map((m) => {
            const methodName = cleanMethodName(m.name);
            const inputType = sanitizeType(m.input, `${ctxPascal}Input`);
            const outputType = sanitizeType(m.output, ctxPascal);
            for (const t of [inputType, outputType]) {
                const base = t.replace(/\[\]$/, '');
                if (base !== 'void' && !/^(?:string|number|boolean|null|undefined|object|Record<.*>|Array<.*>)$/.test(base)) {
                    dtoTypes.add(base);
                }
            }
            return `  /** ${m.behavior} */\n  ${methodName}(input: ${inputType}): Promise<${outputType}>;`;
        })
            .join('\n\n');
    }
    else {
        // Fallback: standard CRUD port
        dtoTypes.add(ctxPascal);
        dtoTypes.add(`${ctxPascal}Input`);
        methodSigs = [
            `  findById(id: string): Promise<${ctxPascal} | null>;`,
            `  findAll(filters?: Record<string, unknown>): Promise<${ctxPascal}[]>;`,
            `  save(input: ${ctxPascal}Input): Promise<${ctxPascal}>;`,
            `  deleteById(id: string): Promise<void>;`,
        ].join('\n');
    }
    // Build typed fields from DDD aggregate model when available
    const aggregateTypedFields = aggregateFields ? buildTypedFieldsFromAggregate(aggregateFields) : [];
    const hasTypedFields = aggregateTypedFields.length > 0;
    const dtoStubs = [...dtoTypes].map((typeName) => {
        const isInput = typeName.endsWith('Input');
        const baseFields = [
            `  readonly id: string;`,
            `  readonly created_at: string;`,
            `  readonly updated_at: string;`,
        ];
        if (hasTypedFields) {
            // Use typed fields from the DDD domain model
            if (isInput) {
                return [
                    `export interface ${typeName} {`,
                    ...baseFields.slice(0, 1), // id only
                    ...aggregateTypedFields,
                    `}`,
                ].join('\n');
            }
            return [
                `export interface ${typeName} {`,
                ...baseFields,
                `  readonly status: 'active' | 'inactive' | 'pending' | 'completed' | 'error';`,
                ...aggregateTypedFields,
                `}`,
            ].join('\n');
        }
        // No typed fields available from domain model — use placeholder with TODO
        if (isInput) {
            return [
                `export interface ${typeName} {`,
                ...baseFields.slice(0, 1), // id only
                `  // TODO: Define typed fields from domain model — Record<string, unknown> is a placeholder`,
                `  readonly data: Record<string, unknown>;`,
                `  readonly metadata?: Record<string, unknown>;`,
                `}`,
            ].join('\n');
        }
        return [
            `export interface ${typeName} {`,
            ...baseFields,
            `  readonly status: 'active' | 'inactive' | 'pending' | 'completed' | 'error';`,
            `  // TODO: Define typed fields from domain model — Record<string, unknown> is a placeholder`,
            `  readonly data: Record<string, unknown>;`,
            `  readonly metadata?: Record<string, unknown>;`,
            `}`,
        ].join('\n');
    }).join('\n\n');
    return [
        `// Generated by Phase 4 pipeline — do not edit manually`,
        ``,
        `/**`,
        ` * Port interface for bounded context: ${boundedContext}`,
        ` */`,
        ``,
        ...(dtoStubs ? [dtoStubs, ''] : []),
        `export interface ${interfaceName} {`,
        methodSigs,
        `}`,
        ``,
    ].join('\n');
}
// ============================================================================
// TypeScript Adapter Code Generation
// ============================================================================
/**
 * Generate a TypeScript adapter class that implements a port interface.
 * Uses project-local imports, typed constructor deps, parameterized SQL,
 * and OpenTelemetry spans via the local telemetry module.
 */
function generateTypescriptAdapter(portFileName, boundedContext, erpSystem, mockContract) {
    const baseName = portFileName.replace(/\.ts$/, '');
    const className = toPascal(baseName) + 'Adapter';
    const interfaceName = toPortName(baseName);
    const ctxPascal = toPascal(boundedContext);
    const ctxSnake = toSnake(boundedContext);
    const tableName = toSnake(baseName);
    // Collect DTO types referenced by the port
    const dtoImports = new Set();
    let methodImpls;
    if (mockContract && mockContract.methods.length > 0) {
        methodImpls = mockContract.methods
            .map((m) => {
            const methodName = cleanMethodName(m.name);
            const inputType = sanitizeType(m.input, `${ctxPascal}Input`);
            const outputType = sanitizeType(m.output, ctxPascal);
            const isQuery = /^(?:get|find|list|query|search|fetch)/.test(methodName);
            for (const t of [inputType, outputType]) {
                const base = t.replace(/\[\]$/, '');
                if (base !== 'void' && !/^(?:string|number|boolean|null|undefined|object|Record<.*>|Array<.*>)$/.test(base)) {
                    dtoImports.add(base);
                }
            }
            if (isQuery) {
                return [
                    ``,
                    `  /** ${m.behavior} */`,
                    `  async ${methodName}(input: ${inputType}): Promise<${outputType}> {`,
                    `    return this.telemetry.withSpan('${ctxSnake}', '${methodName}', {}, async () => {`,
                    `      // Build parameterized query from input fields`,
                    `      const whereClauses: string[] = [];`,
                    `      const params: unknown[] = [];`,
                    `      let paramIdx = 1;`,
                    `      if (input && typeof input === 'object') {`,
                    `        for (const [key, value] of Object.entries(input as Record<string, unknown>)) {`,
                    `          if (value !== undefined && value !== null && /^[a-z_][a-z0-9_]*$/i.test(key)) {`,
                    `            whereClauses.push(\`\${key} = :\${paramIdx}\`);`,
                    `            params.push(value);`,
                    `            paramIdx++;`,
                    `          }`,
                    `        }`,
                    `      }`,
                    `      const whereSQL = whereClauses.length > 0 ? \`WHERE \${whereClauses.join(' AND ')}\` : '';`,
                    `      const rows = await this.db.query(`,
                    `        \`SELECT * FROM ${tableName} \${whereSQL}\`,`,
                    `        params,`,
                    `      );`,
                    `      return rows as unknown as ${outputType};`,
                    `    });`,
                    `  }`,
                ].join('\n');
            }
            return [
                ``,
                `  /** ${m.behavior} */`,
                `  async ${methodName}(input: ${inputType}): Promise<${outputType}> {`,
                `    return this.telemetry.withSpan('${ctxSnake}', '${methodName}', {}, async () => {`,
                `      const result = await this.erp.invoke('${methodName}', input);`,
                `      return result as unknown as ${outputType};`,
                `    });`,
                `  }`,
            ].join('\n');
        })
            .join('\n');
    }
    else {
        // Fallback: standard CRUD adapter
        dtoImports.add(ctxPascal);
        dtoImports.add(`${ctxPascal}Input`);
        methodImpls = [
            ``,
            `  async findById(id: string): Promise<${ctxPascal} | null> {`,
            `    return this.telemetry.withSpan('${ctxSnake}', 'findById', { id }, async () => {`,
            `      const rows = await this.db.query(`,
            `        'SELECT * FROM ${tableName} WHERE id = :1',`,
            `        [id],`,
            `      );`,
            `      return (rows[0] as ${ctxPascal}) ?? null;`,
            `    });`,
            `  }`,
            ``,
            `  async findAll(filters?: Record<string, unknown>): Promise<${ctxPascal}[]> {`,
            `    return this.telemetry.withSpan('${ctxSnake}', 'findAll', {}, async () => {`,
            `      const rows = await this.db.query(`,
            `        'SELECT * FROM ${tableName}',`,
            `        [],`,
            `      );`,
            `      return rows as unknown as ${ctxPascal}[];`,
            `    });`,
            `  }`,
            ``,
            `  async save(input: ${ctxPascal}Input): Promise<${ctxPascal}> {`,
            `    return this.telemetry.withSpan('${ctxSnake}', 'save', {}, async () => {`,
            `      const result = await this.erp.invoke('save', input);`,
            `      return result as unknown as ${ctxPascal};`,
            `    });`,
            `  }`,
            ``,
            `  async deleteById(id: string): Promise<void> {`,
            `    return this.telemetry.withSpan('${ctxSnake}', 'deleteById', { id }, async () => {`,
            `      await this.db.execute(`,
            `        'DELETE FROM ${tableName} WHERE id = :1',`,
            `        [id],`,
            `      );`,
            `    });`,
            `  }`,
        ].join('\n');
    }
    const dtoImportList = dtoImports.size > 0
        ? `import type { ${interfaceName}, ${[...dtoImports].join(', ')} } from '../ports/${baseName}.js';`
        : `import type { ${interfaceName} } from '../ports/${baseName}.js';`;
    return [
        `// Generated by Phase 4 pipeline — do not edit manually`,
        ``,
        `${dtoImportList}`,
        `import type { Telemetry } from '../../infra/telemetry.js';`,
        `import type { DbClient, ErpClient } from '../../infra/clients.js';`,
        ``,
        `/**`,
        ` * Infrastructure adapter: ${className}`,
        ` * Bounded context: ${boundedContext}`,
        ` * ERP system: ${erpSystem}`,
        ` *`,
        ` * Bridges the ${interfaceName} port to the ${erpSystem} ERP system`,
        ` * and the underlying database client.`,
        ` */`,
        `export class ${className} implements ${interfaceName} {`,
        `  constructor(`,
        `    private readonly db: DbClient,`,
        `    private readonly erp: ErpClient,`,
        `    private readonly telemetry: Telemetry,`,
        `  ) {}`,
        methodImpls,
        `}`,
        ``,
    ].join('\n');
}
// ============================================================================
// TypeScript Shared Infrastructure Files
// ============================================================================
/**
 * Generate a self-contained telemetry module that adapters import.
 * Uses OpenTelemetry API — no pipeline references.
 */
function generateTypescriptTelemetryModule() {
    return [
        `// Generated by Phase 4 pipeline — do not edit manually`,
        ``,
        `/**`,
        ` * OpenTelemetry wrapper for infrastructure adapters.`,
        ` *`,
        ` * Provides span creation and a convenience withSpan() helper.`,
        ` * Self-contained — no pipeline dependencies.`,
        ` */`,
        ``,
        `export interface SpanAttributes {`,
        `  readonly [key: string]: string | number | boolean;`,
        `}`,
        ``,
        `export interface Span {`,
        `  readonly name: string;`,
        `  readonly startTime: number;`,
        `  end(status: 'ok' | 'error', attributes?: SpanAttributes): void;`,
        `}`,
        ``,
        `export interface Telemetry {`,
        `  createSpan(service: string, operation: string, attributes?: SpanAttributes): Span;`,
        `  withSpan<T>(service: string, operation: string, attributes: SpanAttributes, fn: () => Promise<T>): Promise<T>;`,
        `}`,
        ``,
        `/**`,
        ` * Console-based telemetry implementation.`,
        ` * Replace with OpenTelemetry SDK integration for production.`,
        ` */`,
        `export class ConsoleTelemetry implements Telemetry {`,
        `  createSpan(service: string, operation: string, attributes: SpanAttributes = {}): Span {`,
        `    const startTime = Date.now();`,
        `    const name = \`\${service}.\${operation}\`;`,
        `    return {`,
        `      name,`,
        `      startTime,`,
        `      end(status: 'ok' | 'error', endAttrs: SpanAttributes = {}): void {`,
        `        const duration = Date.now() - startTime;`,
        `        const level = status === 'error' ? 'error' : 'info';`,
        `        const logFn = level === 'error' ? console.error : console.info;`,
        `        logFn(JSON.stringify({`,
        `          span: name,`,
        `          status,`,
        `          durationMs: duration,`,
        `          ...attributes,`,
        `          ...endAttrs,`,
        `        }));`,
        `      },`,
        `    };`,
        `  }`,
        ``,
        `  async withSpan<T>(`,
        `    service: string,`,
        `    operation: string,`,
        `    attributes: SpanAttributes,`,
        `    fn: () => Promise<T>,`,
        `  ): Promise<T> {`,
        `    const span = this.createSpan(service, operation, attributes);`,
        `    try {`,
        `      const result = await fn();`,
        `      span.end('ok');`,
        `      return result;`,
        `    } catch (err: unknown) {`,
        `      span.end('error', { error: err instanceof Error ? err.message : String(err) });`,
        `      throw err;`,
        `    }`,
        `  }`,
        `}`,
        ``,
    ].join('\n');
}
/**
 * Generate TypeScript client interface definitions.
 */
function generateTypescriptClientInterfaces() {
    return [
        `// Generated by Phase 4 pipeline — do not edit manually`,
        ``,
        `/**`,
        ` * Database client interface.`,
        ` * Adapters depend on this abstraction — implementations are injected.`,
        ` */`,
        `export interface DbClient {`,
        `  /** Execute a parameterized query and return rows. */`,
        `  query(sql: string, params: unknown[]): Promise<unknown[]>;`,
        `  /** Execute a parameterized statement (INSERT/UPDATE/DELETE). */`,
        `  execute(sql: string, params: unknown[]): Promise<void>;`,
        `}`,
        ``,
        `/**`,
        ` * ERP client interface.`,
        ` * Adapters delegate domain operations to the ERP system via this contract.`,
        ` */`,
        `export interface ErpClient {`,
        `  /** Invoke a named ERP operation with a typed payload. */`,
        `  invoke(operation: string, data: unknown): Promise<unknown>;`,
        `}`,
        ``,
        `/**`,
        ` * Domain-specific error for adapter failures.`,
        ` */`,
        `export class AdapterError extends Error {`,
        `  constructor(`,
        `    message: string,`,
        `    public readonly context: string,`,
        `    public readonly operation: string,`,
        `    public readonly cause?: unknown,`,
        `  ) {`,
        `    super(message);`,
        `    this.name = 'AdapterError';`,
        `  }`,
        `}`,
        ``,
    ].join('\n');
}
/**
 * Generate a TypeScript connection pool configuration file.
 */
function generateTypescriptConnectionPool(erpSystem, dialect) {
    return [
        `// Generated by Phase 4 pipeline — do not edit manually`,
        ``,
        `/**`,
        ` * Connection Pool Configuration`,
        ` * ERP system: ${erpSystem}`,
        ` * SQL dialect: ${dialect ?? 'N/A (API-only)'}`,
        ` */`,
        `export interface ConnectionPoolConfig {`,
        `  readonly host: string;`,
        `  readonly port: number;`,
        `  readonly database: string;`,
        `  readonly user: string;`,
        `  readonly password: string;`,
        `  readonly maxConnections: number;`,
        `  readonly idleTimeoutMs: number;`,
        `  readonly ssl: boolean;`,
        `}`,
        ``,
        `export function buildConnectionPoolConfig(): ConnectionPoolConfig {`,
        `  return {`,
        `    host: process.env['DB_HOST'] ?? 'localhost',`,
        `    port: Number(process.env['DB_PORT'] ?? 5432),`,
        `    database: process.env['DB_NAME'] ?? '${toSnake(erpSystem)}',`,
        `    user: process.env['DB_USER'] ?? '',`,
        `    password: process.env['DB_PASSWORD'] ?? '',`,
        `    maxConnections: Number(process.env['DB_MAX_CONNECTIONS'] ?? 10),`,
        `    idleTimeoutMs: Number(process.env['DB_IDLE_TIMEOUT_MS'] ?? 30000),`,
        `    ssl: process.env['DB_SSL'] !== 'false',`,
        `  };`,
        `}`,
        ``,
    ].join('\n');
}
/**
 * Generate a TypeScript IAM helper that fetches an identity token from the
 * appropriate cloud provider metadata service. Switches on CLOUD_PROVIDER env
 * var at runtime (gcp, aws, azure) with env-var fallback for on-prem/other.
 */
function generateTypescriptIAMHelper() {
    return [
        `// Generated by Phase 4 pipeline — do not edit manually`,
        ``,
        `/**`,
        ` * Cloud-Provider-Agnostic Identity Token Helper`,
        ` *`,
        ` * Fetches a short-lived identity/session token from the cloud provider's`,
        ` * instance metadata service for service-to-service authentication.`,
        ` *`,
        ` * Supported providers (auto-detected via CLOUD_PROVIDER env var):`,
        ` *   gcp   -- GCP metadata server (metadata.google.internal)`,
        ` *   aws   -- IMDSv2 (169.254.169.254)`,
        ` *   azure -- Azure IMDS (169.254.169.254/metadata)`,
        ` *   (other/unset) -- Falls back to AUTH_TOKEN environment variable`,
        ` */`,
        ``,
        `type CloudProvider = 'gcp' | 'aws' | 'azure' | string;`,
        ``,
        `function detectProvider(): CloudProvider {`,
        `  return (process.env['CLOUD_PROVIDER'] ?? 'gcp').toLowerCase();`,
        `}`,
        ``,
        `/**`,
        ` * GCP: Fetch identity token from the metadata server.`,
        ` */`,
        `async function fetchGcpToken(audience: string): Promise<string> {`,
        `  const url =`,
        `    'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity'`,
        `    + \`?audience=\${encodeURIComponent(audience)}&format=full\`;`,
        ``,
        `  const response = await fetch(url, {`,
        `    headers: { 'Metadata-Flavor': 'Google' },`,
        `  });`,
        `  if (!response.ok) {`,
        `    throw new Error(\`GCP metadata token request failed: HTTP \${response.status}\`);`,
        `  }`,
        `  return response.text();`,
        `}`,
        ``,
        `/**`,
        ` * AWS: Fetch a session token via IMDSv2 (two-step: PUT for token, then GET credentials).`,
        ` */`,
        `async function fetchAwsToken(_audience: string): Promise<string> {`,
        `  // Step 1: Get IMDSv2 session token`,
        `  const tokenRes = await fetch('http://169.254.169.254/latest/api/token', {`,
        `    method: 'PUT',`,
        `    headers: { 'X-aws-ec2-metadata-token-ttl-seconds': '300' },`,
        `  });`,
        `  if (!tokenRes.ok) {`,
        `    throw new Error(\`AWS IMDSv2 token request failed: HTTP \${tokenRes.status}\`);`,
        `  }`,
        `  const imdsToken = await tokenRes.text();`,
        ``,
        `  // Step 2: Use the session token to retrieve IAM role credentials`,
        `  const credsRes = await fetch(`,
        `    'http://169.254.169.254/latest/meta-data/iam/security-credentials/',`,
        `    { headers: { 'X-aws-ec2-metadata-token': imdsToken } },`,
        `  );`,
        `  if (!credsRes.ok) {`,
        `    throw new Error(\`AWS IMDS credentials request failed: HTTP \${credsRes.status}\`);`,
        `  }`,
        `  const roleName = (await credsRes.text()).trim();`,
        ``,
        `  const roleRes = await fetch(`,
        `    \\\`http://169.254.169.254/latest/meta-data/iam/security-credentials/\\\${roleName}\\\`,`,
        `    { headers: { 'X-aws-ec2-metadata-token': imdsToken } },`,
        `  );`,
        `  if (!roleRes.ok) {`,
        `    throw new Error(\`AWS IMDS role credentials request failed: HTTP \${roleRes.status}\`);`,
        `  }`,
        `  const creds = await roleRes.json() as { Token: string };`,
        `  return creds.Token;`,
        `}`,
        ``,
        `/**`,
        ` * Azure: Fetch an access token from the Azure Instance Metadata Service (IMDS).`,
        ` */`,
        `async function fetchAzureToken(audience: string): Promise<string> {`,
        `  const resource = audience || 'https://management.azure.com/';`,
        `  const url =`,
        `    'http://169.254.169.254/metadata/identity/oauth2/token'`,
        `    + \`?api-version=2018-02-01&resource=\${encodeURIComponent(resource)}\`;`,
        ``,
        `  const response = await fetch(url, {`,
        `    headers: { Metadata: 'true' },`,
        `  });`,
        `  if (!response.ok) {`,
        `    throw new Error(\`Azure IMDS token request failed: HTTP \${response.status}\`);`,
        `  }`,
        `  const body = await response.json() as { access_token: string };`,
        `  return body.access_token;`,
        `}`,
        ``,
        `/**`,
        ` * Fetch an identity/session token for service-to-service authentication.`,
        ` * Automatically selects the correct provider based on CLOUD_PROVIDER env var.`,
        ` *`,
        ` * @param audience - The target audience or resource URI for the token.`,
        ` * @returns The bearer token string.`,
        ` */`,
        `export async function fetchIdentityToken(audience: string): Promise<string> {`,
        `  const provider = detectProvider();`,
        ``,
        `  switch (provider) {`,
        `    case 'gcp':`,
        `      return fetchGcpToken(audience);`,
        `    case 'aws':`,
        `      return fetchAwsToken(audience);`,
        `    case 'azure':`,
        `      return fetchAzureToken(audience);`,
        `    default: {`,
        `      // Fallback: use AUTH_TOKEN env var for on-prem or unsupported providers`,
        `      const token = process.env['AUTH_TOKEN'];`,
        `      if (!token) {`,
        `        throw new Error(`,
        `          \`No identity token available: unsupported provider "\${provider}" \` +`,
        `          \`and AUTH_TOKEN env var is not set.\`,`,
        `        );`,
        `      }`,
        `      return token;`,
        `    }`,
        `  }`,
        `}`,
        ``,
    ].join('\n');
}
// ============================================================================
// Rust Adapter Code Generation
// ============================================================================
function generateRustAdapter(portFileName, boundedContext, erpSystem) {
    const baseName = portFileName.replace(/\.rs$/, '');
    const structName = toPascal(baseName) + 'Adapter';
    const traitName = toPortName(baseName);
    const snakeName = toSnake(baseName);
    return [
        `// Generated by Phase 4 pipeline — do not edit manually`,
        ``,
        `//! Infrastructure adapter: ${structName}`,
        `//! Bounded context: ${boundedContext}`,
        `//! ERP system: ${erpSystem}`,
        ``,
        `use crate::ports::${baseName}::${traitName};`,
        `use tracing::{info_span, Instrument};`,
        ``,
        `#[async_trait::async_trait]`,
        `pub trait DbClient: Send + Sync {`,
        `    async fn query(&self, sql: &str, params: &[&(dyn std::any::Any + Send + Sync)]) -> Result<Vec<serde_json::Value>, Box<dyn std::error::Error>>;`,
        `    async fn execute(&self, sql: &str, params: &[&(dyn std::any::Any + Send + Sync)]) -> Result<(), Box<dyn std::error::Error>>;`,
        `}`,
        ``,
        `#[async_trait::async_trait]`,
        `pub trait ErpClient: Send + Sync {`,
        `    async fn invoke(&self, operation: &str, data: serde_json::Value) -> Result<serde_json::Value, Box<dyn std::error::Error>>;`,
        `}`,
        ``,
        `pub struct ${structName} {`,
        `    db_client: Box<dyn DbClient>,`,
        `    erp_client: Box<dyn ErpClient>,`,
        `}`,
        ``,
        `impl ${structName} {`,
        `    pub fn new(db_client: Box<dyn DbClient>, erp_client: Box<dyn ErpClient>) -> Self {`,
        `        Self { db_client, erp_client }`,
        `    }`,
        ``,
        `    pub async fn find_by_id(&self, id: &str) -> Result<Option<serde_json::Value>, Box<dyn std::error::Error>> {`,
        `        let span = info_span!("${snakeName}.find_by_id", id = %id);`,
        `        async {`,
        `            let results = self.db_client.query(`,
        `                "SELECT * FROM ${snakeName} WHERE id = :1",`,
        `                &[&id as &(dyn std::any::Any + Send + Sync)],`,
        `            ).await?;`,
        `            Ok(results.into_iter().next())`,
        `        }.instrument(span).await`,
        `    }`,
        ``,
        `    pub async fn save(&self, data: serde_json::Value) -> Result<(), Box<dyn std::error::Error>> {`,
        `        let id = data.get("id").and_then(|v| v.as_str()).unwrap_or("unknown");`,
        `        let span = info_span!("${snakeName}.save", id = %id);`,
        `        async {`,
        `            self.db_client.execute(`,
        `                "MERGE INTO ${snakeName} USING DUAL ON (id = :1) WHEN MATCHED THEN UPDATE SET updated_at = SYSTIMESTAMP WHEN NOT MATCHED THEN INSERT (id) VALUES (:1)",`,
        `                &[&id as &(dyn std::any::Any + Send + Sync)],`,
        `            ).await`,
        `        }.instrument(span).await`,
        `    }`,
        `}`,
        ``,
        `#[async_trait::async_trait]`,
        `impl ${traitName} for ${structName} {`,
        `    // Trait methods delegate to the internal find_by_id/save/erp_client methods`,
        `    // with full tracing instrumentation.`,
        `}`,
        ``,
    ].join('\n');
}
function generateRustConnectionPool(erpSystem, dialect) {
    return [
        `// Generated by Phase 4 pipeline — do not edit manually`,
        ``,
        `//! Connection Pool Configuration`,
        `//! ERP system: ${erpSystem}`,
        `//! SQL dialect: ${dialect ?? 'N/A (API-only)'}`,
        ``,
        `#[derive(Debug, Clone)]`,
        `pub struct ConnectionPoolConfig {`,
        `    pub host: String,`,
        `    pub port: u16,`,
        `    pub database: String,`,
        `    pub user: String,`,
        `    pub password: String,`,
        `    pub max_connections: u32,`,
        `    pub idle_timeout_secs: u64,`,
        `    pub ssl: bool,`,
        `}`,
        ``,
        `impl ConnectionPoolConfig {`,
        `    pub fn from_env() -> Self {`,
        `        Self {`,
        `            host: std::env::var("DB_HOST").unwrap_or_else(|_| "localhost".into()),`,
        `            port: std::env::var("DB_PORT").ok()`,
        `                .and_then(|v| v.parse().ok()).unwrap_or(5432),`,
        `            database: std::env::var("DB_NAME")`,
        `                .unwrap_or_else(|_| "${toSnake(erpSystem)}".into()),`,
        `            user: std::env::var("DB_USER").unwrap_or_default(),`,
        `            password: std::env::var("DB_PASSWORD").unwrap_or_default(),`,
        `            max_connections: std::env::var("DB_MAX_CONNECTIONS").ok()`,
        `                .and_then(|v| v.parse().ok()).unwrap_or(10),`,
        `            idle_timeout_secs: std::env::var("DB_IDLE_TIMEOUT_SECS").ok()`,
        `                .and_then(|v| v.parse().ok()).unwrap_or(30),`,
        `            ssl: std::env::var("DB_SSL").unwrap_or_else(|_| "true".into()) != "false",`,
        `        }`,
        `    }`,
        `}`,
        ``,
    ].join('\n');
}
function generateRustIAMHelper() {
    return [
        `// Generated by Phase 4 pipeline — do not edit manually`,
        ``,
        `//! Cloud-Provider-Agnostic Identity Token Helper`,
        `//!`,
        `//! Fetches identity/session tokens from GCP, AWS (IMDSv2), or Azure IMDS.`,
        `//! Falls back to AUTH_TOKEN env var for on-prem or unsupported providers.`,
        `//! Provider is selected via CLOUD_PROVIDER env var (default: "gcp").`,
        ``,
        `use std::env;`,
        ``,
        `/// Detect the cloud provider from the CLOUD_PROVIDER env var.`,
        `fn detect_provider() -> String {`,
        `    env::var("CLOUD_PROVIDER").unwrap_or_else(|_| "gcp".into()).to_lowercase()`,
        `}`,
        ``,
        `/// GCP: Fetch identity token from the metadata server.`,
        `async fn fetch_gcp_token(audience: &str) -> Result<String, Box<dyn std::error::Error>> {`,
        `    let url = format!(`,
        `        "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience={}&format=full",`,
        `        urlencoding::encode(audience),`,
        `    );`,
        `    let client = reqwest::Client::new();`,
        `    let response = client.get(&url).header("Metadata-Flavor", "Google").send().await?;`,
        `    if !response.status().is_success() {`,
        `        return Err(format!("GCP metadata token request failed: HTTP {}", response.status()).into());`,
        `    }`,
        `    Ok(response.text().await?)`,
        `}`,
        ``,
        `/// AWS: Fetch a session token via IMDSv2 (PUT for token, then GET credentials).`,
        `async fn fetch_aws_token(_audience: &str) -> Result<String, Box<dyn std::error::Error>> {`,
        `    let client = reqwest::Client::new();`,
        `    // Step 1: Get IMDSv2 session token`,
        `    let token_res = client`,
        `        .put("http://169.254.169.254/latest/api/token")`,
        `        .header("X-aws-ec2-metadata-token-ttl-seconds", "300")`,
        `        .send().await?;`,
        `    if !token_res.status().is_success() {`,
        `        return Err(format!("AWS IMDSv2 token request failed: HTTP {}", token_res.status()).into());`,
        `    }`,
        `    let imds_token = token_res.text().await?;`,
        ``,
        `    // Step 2: Get IAM role name`,
        `    let creds_res = client`,
        `        .get("http://169.254.169.254/latest/meta-data/iam/security-credentials/")`,
        `        .header("X-aws-ec2-metadata-token", &imds_token)`,
        `        .send().await?;`,
        `    if !creds_res.status().is_success() {`,
        `        return Err(format!("AWS IMDS credentials request failed: HTTP {}", creds_res.status()).into());`,
        `    }`,
        `    let role_name = creds_res.text().await?.trim().to_string();`,
        ``,
        `    // Step 3: Get role credentials`,
        `    let role_url = format!("http://169.254.169.254/latest/meta-data/iam/security-credentials/{}", role_name);`,
        `    let role_res = client`,
        `        .get(&role_url)`,
        `        .header("X-aws-ec2-metadata-token", &imds_token)`,
        `        .send().await?;`,
        `    if !role_res.status().is_success() {`,
        `        return Err(format!("AWS IMDS role credentials request failed: HTTP {}", role_res.status()).into());`,
        `    }`,
        `    let creds: serde_json::Value = role_res.json().await?;`,
        `    Ok(creds["Token"].as_str().unwrap_or_default().to_string())`,
        `}`,
        ``,
        `/// Azure: Fetch an access token from the Azure Instance Metadata Service.`,
        `async fn fetch_azure_token(audience: &str) -> Result<String, Box<dyn std::error::Error>> {`,
        `    let resource = if audience.is_empty() { "https://management.azure.com/" } else { audience };`,
        `    let url = format!(`,
        `        "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource={}",`,
        `        urlencoding::encode(resource),`,
        `    );`,
        `    let client = reqwest::Client::new();`,
        `    let response = client.get(&url).header("Metadata", "true").send().await?;`,
        `    if !response.status().is_success() {`,
        `        return Err(format!("Azure IMDS token request failed: HTTP {}", response.status()).into());`,
        `    }`,
        `    let body: serde_json::Value = response.json().await?;`,
        `    Ok(body["access_token"].as_str().unwrap_or_default().to_string())`,
        `}`,
        ``,
        `/// Fetch an identity/session token for service-to-service authentication.`,
        `/// Automatically selects the correct provider based on CLOUD_PROVIDER env var.`,
        `pub async fn fetch_identity_token(audience: &str) -> Result<String, Box<dyn std::error::Error>> {`,
        `    let provider = detect_provider();`,
        `    match provider.as_str() {`,
        `        "gcp" => fetch_gcp_token(audience).await,`,
        `        "aws" => fetch_aws_token(audience).await,`,
        `        "azure" => fetch_azure_token(audience).await,`,
        `        _ => {`,
        `            // Fallback: use AUTH_TOKEN env var for on-prem or unsupported providers`,
        `            env::var("AUTH_TOKEN").map_err(|_| {`,
        `                format!(`,
        `                    "No identity token available: unsupported provider \\"{}\\" and AUTH_TOKEN env var is not set.",`,
        `                    provider`,
        `                ).into()`,
        `            })`,
        `        }`,
        `    }`,
        `}`,
        ``,
    ].join('\n');
}
function generateRustTelemetryWrapper() {
    return [
        `// Generated by Phase 4 pipeline — do not edit manually`,
        ``,
        `//! Telemetry Wrapper for Infrastructure Adapters`,
        ``,
        `pub async fn with_span<T, F, Fut>(`,
        `    stage: &str,`,
        `    operation: &str,`,
        `    f: F,`,
        `) -> Result<T, Box<dyn std::error::Error>>`,
        `where`,
        `    F: FnOnce() -> Fut,`,
        `    Fut: std::future::Future<Output = Result<T, Box<dyn std::error::Error>>>,`,
        `{`,
        `    let _span = tracing::info_span!("adapter", stage = %stage, operation = %operation).entered();`,
        `    f().await`,
        `}`,
        ``,
    ].join('\n');
}
// ============================================================================
// Port File Discovery
// ============================================================================
function discoverPortFiles(phase3Dir, contextName, language) {
    const ctxSlug = toCtxSlug(contextName);
    const ctxSnake = toCtxSnake(contextName);
    const candidatePaths = language === 'typescript'
        ? [join(phase3Dir, 'project', 'src', ctxSlug, 'ports')]
        : [join(phase3Dir, 'project', ctxSnake, 'src', 'ports')];
    const excluded = new Set(['index.ts', 'mod.rs', '.gitkeep']);
    for (const portsDir of candidatePaths) {
        if (existsSync(portsDir)) {
            try {
                return readdirSync(portsDir).filter((f) => !excluded.has(f) && (language === 'typescript' ? f.endsWith('.ts') : f.endsWith('.rs')));
            }
            catch {
                // Directory unreadable — skip
            }
        }
    }
    return [];
}
// ============================================================================
// Main: generateInfraAdapters
// ============================================================================
/**
 * Stage 4: Generate infrastructure adapters for all seam port interfaces.
 *
 * For each bounded context in the Phase 2 domain model, discovers port files
 * in the Phase 3 project output and generates a corresponding adapter file.
 * Also generates shared infrastructure files:
 *   - telemetry.ts / telemetry_wrapper.rs  (self-contained, no pipeline refs)
 *   - clients.ts                           (DbClient, ErpClient, AdapterError)
 *   - connection-pool.ts / connection_pool.rs
 *   - iam-helper.ts / iam_helper.rs
 *
 * @param context - The Phase 4 pipeline context
 * @returns Stage output describing success or failure
 */
export function generateInfraAdapters(context) {
    const rootSpan = createSpan('infra-adapter-generation', 'generate-adapters', {
        phase4Dir: context.phase4Dir,
        phase3Dir: context.phase3Dir,
    });
    try {
        // -------------------------------------------------------------------------
        // Step 1: Retrieve ERP resolution and implementation language
        // -------------------------------------------------------------------------
        const erpStage = context.stageOutputs.get('erp-target-resolution');
        if (!erpStage || erpStage.status !== 'completed') {
            throw new Error('ECLI-P4-004: ERP target resolution not available on context. ' +
                'Ensure the erp-target-resolution stage completed successfully.');
        }
        const erpResolution = erpStage.data;
        const language = context.phase3Artifacts.implementationLanguage;
        // Non-TS/Rust languages handled by Ruflo swarm
        if (!hasTemplateSupport(language)) {
            const span = endSpan(rootSpan, 'ok', { delegated: true, language });
            emitSpan(span);
            return {
                stage: 'infra-adapter-generation',
                status: 'completed',
                data: { adapters: [], totalAdapters: 0, connectionPoolConfig: '{}' },
                artifacts: [],
                timing: 0,
                summary: `Infra adapters for ${language} delegated to Ruflo swarm.`,
            };
        }
        // -------------------------------------------------------------------------
        // Step 2: Retrieve Phase 2 domain model and TDD mock contracts
        // -------------------------------------------------------------------------
        const domainModel = context.phase3Artifacts.phase2Artifacts.domainModel;
        const tddPlan = context.phase3Artifacts.phase2Artifacts.tddPlan;
        const mockContracts = tddPlan?.mockContracts ?? [];
        const adapterFiles = [];
        const writtenPaths = [];
        let totalAdapters = 0;
        // -------------------------------------------------------------------------
        // Step 3: Generate adapters per bounded context
        // -------------------------------------------------------------------------
        for (const ctx of domainModel.contexts) {
            const ctxSlug = toCtxSlug(ctx.name);
            const ctxSpan = createSpan('infra-adapter-generation', `generate-adapters-${ctxSlug}`, { context: ctx.name, language }, rootSpan.spanId);
            try {
                // Discover port files for this context
                const portFiles = discoverPortFiles(context.phase3Dir, ctx.name, language);
                for (const portFile of portFiles) {
                    // Find matching mock contract for this port file
                    const portBaseName = portFile.replace(/\.(ts|rs)$/, '');
                    const matchingContract = mockContracts.find((mc) => {
                        const mcSlug = mc.interfaceName
                            .replace(/([a-z])([A-Z])/g, '$1-$2')
                            .replace(/[^a-zA-Z0-9]+/g, '-')
                            .replace(/^-|-$/g, '')
                            .toLowerCase();
                        return mcSlug === portBaseName || mc.interfaceName === portBaseName;
                    });
                    // Extract aggregate field info from the DDD domain model for typed DTOs
                    const matchingAggregate = ctx.aggregates?.find((agg) => {
                        const aggSlug = toKebab(agg.name.replace(/Aggregate$/i, ''));
                        return aggSlug === portBaseName || toKebab(agg.root) === portBaseName;
                    });
                    const aggregateFieldInfo = matchingAggregate
                        ? {
                            entities: matchingAggregate.entities ?? [],
                            valueObjects: matchingAggregate.valueObjects ?? [],
                            invariants: matchingAggregate.invariants ?? [],
                        }
                        : // Fall back to the first aggregate in the context if only one exists
                            ctx.aggregates?.length === 1
                                ? {
                                    entities: ctx.aggregates[0].entities ?? [],
                                    valueObjects: ctx.aggregates[0].valueObjects ?? [],
                                    invariants: ctx.aggregates[0].invariants ?? [],
                                }
                                : undefined;
                    // Generate port interface (TypeScript only)
                    if (language === 'typescript') {
                        const portContent = generateTypescriptPort(portBaseName, ctx.name, matchingContract, aggregateFieldInfo);
                        const portDir = join(context.phase4Dir, `project/src/${ctxSlug}/ports`);
                        mkdirSync(portDir, { recursive: true, mode: DIR_MODE });
                        const portAbsPath = join(portDir, portFile);
                        writeFileSync(portAbsPath, portContent, { encoding: 'utf-8', mode: FILE_MODE });
                        writtenPaths.push(portAbsPath);
                    }
                    const adapterContent = language === 'typescript'
                        ? generateTypescriptAdapter(portFile, ctx.name, erpResolution.erpSystem, matchingContract)
                        : generateRustAdapter(portFile, ctx.name, erpResolution.erpSystem);
                    const adapterFileName = language === 'typescript'
                        ? portFile.replace(/\.ts$/, '-adapter.ts')
                        : portFile.replace(/\.rs$/, '_adapter.rs');
                    const adapterRelPath = language === 'typescript'
                        ? `project/src/${ctxSlug}/infra/${adapterFileName}`
                        : `project/${toCtxSnake(ctx.name)}/src/infra/${adapterFileName}`;
                    const adapterAbsPath = join(context.phase4Dir, adapterRelPath);
                    const adapterDir = join(context.phase4Dir, language === 'typescript'
                        ? `project/src/${ctxSlug}/infra`
                        : `project/${toCtxSnake(ctx.name)}/src/infra`);
                    mkdirSync(adapterDir, { recursive: true, mode: DIR_MODE });
                    writeFileSync(adapterAbsPath, adapterContent, { encoding: 'utf-8', mode: FILE_MODE });
                    writtenPaths.push(adapterAbsPath);
                    totalAdapters++;
                    adapterFiles.push({
                        relativePath: adapterRelPath,
                        content: adapterContent,
                        kind: 'adapter',
                        boundedContext: ctx.name,
                        portInterface: portFile.replace(/\.(ts|rs)$/, ''),
                    });
                }
                const finalCtxSpan = endSpan(ctxSpan, 'ok', {
                    context: ctx.name,
                    adaptersGenerated: portFiles.length,
                });
                emitSpan(finalCtxSpan);
                context.telemetrySpans.push(finalCtxSpan);
            }
            catch (err) {
                const finalCtxSpan = endSpan(ctxSpan, 'error', {
                    context: ctx.name,
                    error: err instanceof Error ? err.message : String(err),
                });
                emitSpan(finalCtxSpan);
                context.telemetrySpans.push(finalCtxSpan);
                throw err;
            }
        }
        // -------------------------------------------------------------------------
        // Step 4: Generate shared infrastructure files
        // -------------------------------------------------------------------------
        const sharedInfraDir = join(context.phase4Dir, 'project', 'src', 'infra');
        mkdirSync(sharedInfraDir, { recursive: true, mode: DIR_MODE });
        const sharedFiles = language === 'typescript'
            ? [
                { name: 'telemetry.ts', content: generateTypescriptTelemetryModule() },
                { name: 'clients.ts', content: generateTypescriptClientInterfaces() },
                {
                    name: 'connection-pool.ts',
                    content: generateTypescriptConnectionPool(erpResolution.erpSystem, erpResolution.sqlDialect),
                },
                { name: 'iam-helper.ts', content: generateTypescriptIAMHelper() },
            ]
            : [
                {
                    name: 'connection_pool.rs',
                    content: generateRustConnectionPool(erpResolution.erpSystem, erpResolution.sqlDialect),
                },
                { name: 'iam_helper.rs', content: generateRustIAMHelper() },
                { name: 'telemetry_wrapper.rs', content: generateRustTelemetryWrapper() },
            ];
        let connectionPoolConfig = '';
        for (const sf of sharedFiles) {
            const absPath = join(sharedInfraDir, sf.name);
            writeFileSync(absPath, sf.content, { encoding: 'utf-8', mode: FILE_MODE });
            writtenPaths.push(absPath);
            if (sf.name.includes('connection-pool') || sf.name.includes('connection_pool')) {
                connectionPoolConfig = sf.content;
            }
        }
        // -------------------------------------------------------------------------
        // Step 5: Build and return stage output
        // -------------------------------------------------------------------------
        const infraAdapterOutput = {
            adapters: adapterFiles,
            totalAdapters,
            connectionPoolConfig,
        };
        const finalRootSpan = endSpan(rootSpan, 'ok', {
            language,
            totalAdapters,
            sharedFiles: sharedFiles.length,
            contexts: domainModel.contexts.length,
        });
        emitSpan(finalRootSpan);
        context.telemetrySpans.push(finalRootSpan);
        const timing = finalRootSpan.durationMs ?? 0;
        const output = {
            stage: 'infra-adapter-generation',
            status: 'completed',
            data: infraAdapterOutput,
            artifacts: writtenPaths,
            timing,
            summary: `  [OK] Generated ${totalAdapters} infrastructure adapter(s) ` +
                `and ${sharedFiles.length} shared infra files ` +
                `(language=${language}, erp=${erpResolution.erpSystem}) ` +
                `across ${domainModel.contexts.length} bounded context(s) in ${timing}ms`,
        };
        context.stageOutputs.set('infra-adapter-generation', output);
        return output;
    }
    catch (err) {
        const errorMessage = err instanceof Error ? err.message : String(err);
        const finalRootSpan = endSpan(rootSpan, 'error', { error: errorMessage });
        emitSpan(finalRootSpan);
        context.telemetrySpans.push(finalRootSpan);
        const timing = finalRootSpan.durationMs ?? 0;
        const output = {
            stage: 'infra-adapter-generation',
            status: 'failed',
            data: { error: errorMessage },
            artifacts: [],
            timing,
            summary: `  [FAIL] Infrastructure adapter generation failed: ${errorMessage}`,
        };
        context.stageOutputs.set('infra-adapter-generation', output);
        return output;
    }
}
//# sourceMappingURL=infra-adapter-generator.js.map