import { cp, mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { dirname, join, relative, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import {
  authorityForCanonicalClass,
  canonicalizeRoleClass,
  extractClassesFromDir,
  extractClassesFromDirSync,
  listPersonaClasses,
  personaStatus,
  resolvePersona,
  resolvePersonaFrom,
  resolvePersonaSync,
} from './fleet-personas.js';
import { loadProfiles, validateProfile, type FleetProfile } from './fleet-profiles.js';

// The real, committed library: packages/mosaic/src/commands -> framework/fleet.
const frameworkFleet = resolve(
  dirname(fileURLToPath(import.meta.url)),
  '..',
  '..',
  'framework',
  'fleet',
);
const realRolesDir = join(frameworkFleet, 'roles');

let tmp: string;
let rolesDir: string;
let overrideDir: string;

// A minimal baseline persona file with an inline `class:` + `domain:` marker.
function baselinePersona(klass: string, domain: string, marker = 'BASELINE'): string {
  return `# ${klass} — fleet role definition

The **${klass}** is the ${marker} definition (\`class: ${klass}\`, \`domain: ${domain}\`).
`;
}

function overridePersona(klass: string, domain: string, marker = 'OVERRIDE'): string {
  return `# ${klass} — fleet role definition (override)

The **${klass}** is the ${marker} definition (\`class: ${klass}\`, \`domain: ${domain}\`).
`;
}

beforeEach(async () => {
  tmp = await mkdtemp(join(tmpdir(), 'h4-personas-'));
  rolesDir = join(tmp, 'roles');
  overrideDir = join(tmp, 'roles.local');
  await mkdir(rolesDir, { recursive: true });
  // Seed two baseline personas. (No override dir yet — created per test.)
  await writeFile(join(rolesDir, 'ceo.md'), baselinePersona('ceo', 'executive'), 'utf8');
  await writeFile(join(rolesDir, 'code.md'), baselinePersona('code', 'engineering'), 'utf8');
});

afterEach(async () => {
  await rm(tmp, { recursive: true, force: true });
});

describe('FCM class canonicalization and authority', () => {
  it.each([
    ['implementer', 'code'],
    ['reviewer', 'review'],
    ['operator-interaction', 'interaction'],
  ])('canonicalizes the approved alias %s to %s', (requested: string, canonical: string) => {
    expect(canonicalizeRoleClass(requested)).toEqual({
      requestedClass: requested,
      canonicalClass: canonical,
    });
  });

  it.each(['worker', 'analyst', 'canary', 'tess', 'ultron', 'constructor'])(
    'does not infer an alias for %s',
    (requested: string) => {
      expect(canonicalizeRoleClass(requested)).toEqual({
        requestedClass: requested,
        canonicalClass: requested,
      });
    },
  );

  it('resolves inherited-property class names without granting protected authority', async () => {
    await mkdir(overrideDir, { recursive: true });
    await writeFile(
      join(overrideDir, 'constructor.md'),
      overridePersona('constructor', 'custom'),
      'utf8',
    );

    const resolved = await resolvePersona('constructor', { rolesDir, overrideDir });
    expect(resolved).toMatchObject({
      requestedClass: 'constructor',
      canonicalClass: 'constructor',
      klass: 'constructor',
      layer: 'override',
    });
    expect(authorityForCanonicalClass('constructor')).toMatchObject({
      mayMerge: false,
      mayIssueValidationCertificate: false,
      mayOrchestrate: false,
    });
  });

  it('canonicalizes before override lookup and lets the canonical override win', async () => {
    await mkdir(overrideDir, { recursive: true });
    await writeFile(
      join(overrideDir, 'implementer.md'),
      overridePersona('implementer', 'legacy'),
      'utf8',
    );
    await writeFile(
      join(overrideDir, 'code.md'),
      overridePersona('code', 'engineering', 'CANONICAL-OVERRIDE'),
      'utf8',
    );

    const resolved = await resolvePersona('implementer', { rolesDir, overrideDir });
    expect(resolved).toMatchObject({
      requestedClass: 'implementer',
      canonicalClass: 'code',
      klass: 'code',
      layer: 'override',
    });
    expect(resolved?.content).toContain('CANONICAL-OVERRIDE');
    expect(resolved?.content).not.toContain('legacy');
  });

  it('keeps sync and async resolution equivalent for aliases and canonical overrides', async () => {
    await mkdir(overrideDir, { recursive: true });
    await writeFile(
      join(overrideDir, 'code.md'),
      overridePersona('code', 'engineering', 'CANONICAL-OVERRIDE'),
      'utf8',
    );

    const asyncResolution = await resolvePersona('implementer', { rolesDir, overrideDir });
    const syncResolution = resolvePersonaSync('implementer', { rolesDir, overrideDir });
    expect(syncResolution).toEqual(asyncResolution);
  });

  it('derives immutable protected authority only from the canonical class', () => {
    expect(authorityForCanonicalClass('merge-gate')).toMatchObject({ mayMerge: true });
    for (const klass of [
      'validator',
      'orchestrator',
      'team-leader',
      'interaction',
      'code',
      'custom-role',
    ]) {
      expect(authorityForCanonicalClass(klass).mayMerge).toBe(false);
    }
    expect(authorityForCanonicalClass('validator')).toMatchObject({
      mayIssueValidationCertificate: true,
      mayMerge: false,
    });
    expect(authorityForCanonicalClass('orchestrator')).toMatchObject({
      mayOrchestrate: true,
      mayIssueLeases: true,
      mayMerge: false,
    });
    expect(authorityForCanonicalClass('team-leader')).toMatchObject({
      leasedCapacityOnly: true,
      mayMutateRoster: false,
      mayAccessCredentials: false,
      mayMerge: false,
    });
    expect(authorityForCanonicalClass('interaction')).toMatchObject({
      requestStatusOnly: true,
      mayOrchestrate: false,
      mayMerge: false,
    });
    expect(Object.isFrozen(authorityForCanonicalClass('merge-gate'))).toBe(true);
  });
});

describe('required FCM role library', () => {
  it.each([
    'code',
    'review',
    'validator',
    'orchestrator',
    'team-leader',
    'enhancer',
    'interaction',
    'merge-gate',
  ])('resolves %s through the real framework role library', async (klass: string) => {
    const resolved = await resolvePersona(klass, {
      rolesDir: realRolesDir,
      overrideDir: join(tmp, 'none'),
    });
    expect(resolved).not.toBeNull();
    expect(resolved?.canonicalClass).toBe(klass);
    expect(resolved?.content.trim()).not.toBe('');
  });
});

describe('extractClassesFromDir (shared extraction)', () => {
  it('records class + domain and distinguishes scanned from missing directories', async () => {
    const base = await extractClassesFromDir(rolesDir);
    expect(base.scanState).toBe('scanned');
    expect(base.classes.has('ceo')).toBe(true);
    expect(base.byClass.get('ceo')?.domain).toBe('executive');

    const missingDir = join(tmp, 'nope');
    const missing = await extractClassesFromDir(missingDir);
    expect(missing.scanState).toBe('missing');
    expect(missing.classes.size).toBe(0);
    expect(extractClassesFromDirSync(missingDir).scanState).toBe('missing');
  });
});

describe('resolvePersona — override wins', () => {
  it('resolves to the override when a class exists in BOTH layers', async () => {
    await mkdir(overrideDir, { recursive: true });
    await writeFile(join(overrideDir, 'ceo.md'), overridePersona('ceo', 'executive'), 'utf8');

    const resolved = await resolvePersona('ceo', { rolesDir, overrideDir });
    expect(resolved?.layer).toBe('override');
    expect(resolved?.content).toContain('OVERRIDE');
    expect(resolved?.file).toBe(join(overrideDir, 'ceo.md'));
  });

  it('resolves to the baseline when no override exists', async () => {
    const resolved = await resolvePersona('code', { rolesDir, overrideDir });
    expect(resolved?.layer).toBe('baseline');
    expect(resolved?.content).toContain('BASELINE');
  });

  it('does not let a LIBRARY-only row shadow a readable baseline persona', async () => {
    await mkdir(overrideDir, { recursive: true });
    await writeFile(
      join(overrideDir, 'LIBRARY.md'),
      '| Persona | Purpose |\n| --- | --- |\n| code | Index only |\n',
      'utf8',
    );

    const resolved = await resolvePersona('code', { rolesDir, overrideDir });
    expect(resolved?.layer).toBe('baseline');
    expect(resolved?.content).toContain('BASELINE');
    expect((await listPersonaClasses({ rolesDir, overrideDir })).has('code')).toBe(true);
    expect(
      (await personaStatus({ rolesDir, overrideDir })).find(({ klass }) => klass === 'code')
        ?.status,
    ).toBe('baseline');
  });

  it('does not let an incidental later class marker shadow a readable baseline persona', async () => {
    await mkdir(overrideDir, { recursive: true });
    await writeFile(
      join(overrideDir, 'mascot.md'),
      '# mascot\n\n(`class: mascot`)\n\nSee also (`class: code`).\n',
      'utf8',
    );

    const resolved = await resolvePersona('code', { rolesDir, overrideDir });
    expect(resolved?.layer).toBe('baseline');
    expect(resolved?.content).toContain('BASELINE');
    expect((await listPersonaClasses({ rolesDir, overrideDir })).has('code')).toBe(true);
    expect((await listPersonaClasses({ rolesDir, overrideDir })).has('mascot')).toBe(true);
    expect(
      (await personaStatus({ rolesDir, overrideDir })).find(({ klass }) => klass === 'code')
        ?.status,
    ).toBe('baseline');
  });

  it('does not advertise an incidental-only class through listing or status APIs', async () => {
    await mkdir(overrideDir, { recursive: true });
    await writeFile(
      join(overrideDir, 'mascot.md'),
      '# mascot\n\n(`class: mascot`)\n\nSee also (`class: phantom`).\n',
      'utf8',
    );

    expect((await listPersonaClasses({ rolesDir, overrideDir })).has('phantom')).toBe(false);
    expect(
      (await personaStatus({ rolesDir, overrideDir })).some(({ klass }) => klass === 'phantom'),
    ).toBe(false);
  });

  it('rejects a canonical filename whose explicit marker names another class', async () => {
    await mkdir(overrideDir, { recursive: true });
    await writeFile(join(overrideDir, 'merge-gate.md'), overridePersona('mascot', 'fun'), 'utf8');
    await writeFile(
      join(rolesDir, 'merge-gate.md'),
      baselinePersona('merge-gate', 'governance'),
      'utf8',
    );

    expect(await resolvePersona('merge-gate', { rolesDir, overrideDir })).toBeNull();
    expect(resolvePersonaSync('merge-gate', { rolesDir, overrideDir })).toBeNull();
    expect((await listPersonaClasses({ rolesDir, overrideDir })).has('merge-gate')).toBe(false);
    expect(
      (await personaStatus({ rolesDir, overrideDir })).some(({ klass }) => klass === 'merge-gate'),
    ).toBe(false);
  });

  it('fails closed if a marker-defined override becomes unreadable after extraction', async () => {
    await mkdir(overrideDir, { recursive: true });
    const overrideFile = join(overrideDir, 'engineering.md');
    await writeFile(overrideFile, overridePersona('code', 'engineering'), 'utf8');
    const [base, over] = await Promise.all([
      extractClassesFromDir(rolesDir),
      extractClassesFromDir(overrideDir),
    ]);
    await rm(overrideFile);
    await mkdir(overrideFile);

    expect(await resolvePersonaFrom('code', { rolesDir, overrideDir, base, over })).toBeNull();
  });

  it('fails closed if a marker-defined override changes identity after extraction', async () => {
    await mkdir(overrideDir, { recursive: true });
    const overrideFile = join(overrideDir, 'engineering.md');
    await writeFile(overrideFile, overridePersona('code', 'engineering'), 'utf8');
    const [base, over] = await Promise.all([
      extractClassesFromDir(rolesDir),
      extractClassesFromDir(overrideDir),
    ]);
    await writeFile(overrideFile, overridePersona('mascot', 'fun'), 'utf8');

    expect(await resolvePersonaFrom('code', { rolesDir, overrideDir, base, over })).toBeNull();

    const classes = await listPersonaClasses({ rolesDir, overrideDir });
    expect(classes.has('code')).toBe(true);
    expect(classes.has('mascot')).toBe(true);
    const status = new Map(
      (await personaStatus({ rolesDir, overrideDir })).map((entry) => [entry.klass, entry]),
    );
    expect(status.get('code')?.status).toBe('baseline');
    expect(status.get('mascot')?.status).toBe('custom');
  });

  it('fails closed if a markerless cached override gains a conflicting marker', async () => {
    await mkdir(overrideDir, { recursive: true });
    const overrideFile = join(overrideDir, 'merge-gate.md');
    await writeFile(overrideFile, '# markerless merge gate\n', 'utf8');
    await writeFile(
      join(rolesDir, 'merge-gate.md'),
      baselinePersona('merge-gate', 'governance'),
      'utf8',
    );
    const [base, over] = await Promise.all([
      extractClassesFromDir(rolesDir),
      extractClassesFromDir(overrideDir),
    ]);
    await writeFile(overrideFile, overridePersona('mascot', 'fun'), 'utf8');

    expect(
      await resolvePersonaFrom('merge-gate', { rolesDir, overrideDir, base, over }),
    ).toBeNull();
  });

  it('fails closed asynchronously when the override directory cannot be scanned', async () => {
    await writeFile(overrideDir, 'not a directory', 'utf8');
    expect(await resolvePersona('code', { rolesDir, overrideDir })).toBeNull();
  });

  it('fails closed synchronously when the override directory cannot be scanned', async () => {
    await writeFile(overrideDir, 'not a directory', 'utf8');
    expect(resolvePersonaSync('code', { rolesDir, overrideDir })).toBeNull();
  });

  it('fails closed asynchronously and synchronously for a dangling override-directory symlink', async () => {
    await symlink(join(tmp, 'missing-override-target'), overrideDir, 'dir');

    expect(await resolvePersona('code', { rolesDir, overrideDir })).toBeNull();
    expect(resolvePersonaSync('code', { rolesDir, overrideDir })).toBeNull();
  });

  it('fails closed asynchronously and synchronously when an override ancestor is a dangling symlink', async () => {
    const danglingAncestor = join(tmp, 'dangling-ancestor');
    await symlink(join(tmp, 'missing-ancestor-target'), danglingAncestor, 'dir');
    overrideDir = join(danglingAncestor, 'nested', 'roles.local');

    expect(await resolvePersona('code', { rolesDir, overrideDir })).toBeNull();
    expect(resolvePersonaSync('code', { rolesDir, overrideDir })).toBeNull();
    expect(await listPersonaClasses({ rolesDir, overrideDir })).toEqual(new Set());
    expect(await personaStatus({ rolesDir, overrideDir })).toEqual([]);
  });

  it('fails closed when dot-dot traversal crosses a dangling override ancestor', async () => {
    const danglingAncestor = join(tmp, 'dangling-dotdot-ancestor');
    await symlink(join(tmp, 'missing-dotdot-target'), danglingAncestor, 'dir');
    overrideDir = `${danglingAncestor}/../roles.local`;

    expect(await resolvePersona('code', { rolesDir, overrideDir })).toBeNull();
    expect(resolvePersonaSync('code', { rolesDir, overrideDir })).toBeNull();
    expect(await listPersonaClasses({ rolesDir, overrideDir })).toEqual(new Set());
    expect(await personaStatus({ rolesDir, overrideDir })).toEqual([]);
  });

  it('fails closed when relative dot-dot traversal crosses a dangling override ancestor', async () => {
    const danglingAncestor = join(tmp, 'relative-dangling-ancestor');
    await symlink(join(tmp, 'missing-relative-target'), danglingAncestor, 'dir');
    overrideDir = `${relative(process.cwd(), danglingAncestor)}/../roles.local`;
    expect(overrideDir.startsWith('/')).toBe(false);

    expect(await resolvePersona('code', { rolesDir, overrideDir })).toBeNull();
    expect(resolvePersonaSync('code', { rolesDir, overrideDir })).toBeNull();
    expect(await listPersonaClasses({ rolesDir, overrideDir })).toEqual(new Set());
    expect(await personaStatus({ rolesDir, overrideDir })).toEqual([]);
  });

  it('revalidates a cached missing override before baseline fallback', async () => {
    const cachedOverrideDir = join(tmp, 'mutable-ancestor', 'roles.local');
    const [base, over] = await Promise.all([
      extractClassesFromDir(rolesDir),
      extractClassesFromDir(cachedOverrideDir),
    ]);
    expect(over.scanState).toBe('missing');
    await symlink(join(tmp, 'missing-mutable-target'), join(tmp, 'mutable-ancestor'), 'dir');

    expect(
      await resolvePersonaFrom('code', {
        rolesDir,
        overrideDir: cachedOverrideDir,
        base,
        over,
      }),
    ).toBeNull();
  });

  it('revalidates a cached scanned override before baseline fallback', async () => {
    await mkdir(overrideDir, { recursive: true });
    const [base, over] = await Promise.all([
      extractClassesFromDir(rolesDir),
      extractClassesFromDir(overrideDir),
    ]);
    expect(over.scanState).toBe('scanned');
    expect(over.classes.size).toBe(0);

    await writeFile(join(overrideDir, 'engineering.md'), overridePersona('code', 'engineering'));

    const resolved = await resolvePersonaFrom('code', {
      rolesDir,
      overrideDir,
      base,
      over,
    });
    expect(resolved?.layer).toBe('override');
    expect(resolved?.file).toBe(join(overrideDir, 'engineering.md'));
  });

  it('falls back to baseline asynchronously and synchronously when override directory is missing', async () => {
    const asyncResolution = await resolvePersona('code', { rolesDir, overrideDir });
    const syncResolution = resolvePersonaSync('code', { rolesDir, overrideDir });
    expect(asyncResolution?.layer).toBe('baseline');
    expect(syncResolution?.layer).toBe('baseline');
  });

  it('fails closed asynchronously when the canonical override exists but is unreadable', async () => {
    await mkdir(overrideDir, { recursive: true });
    await mkdir(join(overrideDir, 'code.md'));

    expect(await resolvePersona('code', { rolesDir, overrideDir })).toBeNull();
  });

  it('does not advertise a shadowed baseline whose canonical override is unreadable', async () => {
    await mkdir(overrideDir, { recursive: true });
    await mkdir(join(overrideDir, 'code.md'));

    expect((await listPersonaClasses({ rolesDir, overrideDir })).has('code')).toBe(false);
    expect(
      (await personaStatus({ rolesDir, overrideDir })).some(({ klass }) => klass === 'code'),
    ).toBe(false);
  });

  it('does not advertise baseline personas when the override directory is unscannable', async () => {
    await writeFile(overrideDir, 'not a directory', 'utf8');

    expect(await listPersonaClasses({ rolesDir, overrideDir })).toEqual(new Set());
    expect(await personaStatus({ rolesDir, overrideDir })).toEqual([]);
  });

  it('does not advertise baseline personas through a dangling override-directory symlink', async () => {
    await symlink(join(tmp, 'missing-override-target'), overrideDir, 'dir');

    expect(await listPersonaClasses({ rolesDir, overrideDir })).toEqual(new Set());
    expect(await personaStatus({ rolesDir, overrideDir })).toEqual([]);
  });

  it('preserves baseline listings when the override directory is missing', async () => {
    expect((await listPersonaClasses({ rolesDir, overrideDir })).has('code')).toBe(true);
    expect(
      (await personaStatus({ rolesDir, overrideDir })).find(({ klass }) => klass === 'code')
        ?.status,
    ).toBe('baseline');
  });

  it('does not advertise an unreadable custom override as a valid persona class or status', async () => {
    await mkdir(overrideDir, { recursive: true });
    await mkdir(join(overrideDir, 'ghost.md'));

    const extracted = await extractClassesFromDir(overrideDir);
    expect(extracted.fileStems.has('ghost')).toBe(true);
    expect(extracted.classes.has('ghost')).toBe(false);
    expect((await listPersonaClasses({ rolesDir, overrideDir })).has('ghost')).toBe(false);
    expect(
      (await personaStatus({ rolesDir, overrideDir })).some(({ klass }) => klass === 'ghost'),
    ).toBe(false);
  });

  it('fails closed synchronously when the canonical override exists but is unreadable', async () => {
    await mkdir(overrideDir, { recursive: true });
    await mkdir(join(overrideDir, 'code.md'));

    expect(resolvePersonaSync('code', { rolesDir, overrideDir })).toBeNull();
  });

  it('returns null for an unknown class', async () => {
    expect(await resolvePersona('does-not-exist', { rolesDir, overrideDir })).toBeNull();
  });
});

describe('custom add — override-only class', () => {
  it('a class present only in roles.local/ appears in listPersonaClasses and resolves', async () => {
    await mkdir(overrideDir, { recursive: true });
    await writeFile(
      join(overrideDir, 'mascot.md'),
      overridePersona('mascot', 'fun', 'CUSTOM'),
      'utf8',
    );

    const classes = await listPersonaClasses({ rolesDir, overrideDir });
    expect(classes.has('mascot')).toBe(true);
    // Baseline classes are still present (union).
    expect(classes.has('ceo')).toBe(true);

    const resolved = await resolvePersona('mascot', { rolesDir, overrideDir });
    expect(resolved?.layer).toBe('override');
    expect(resolved?.content).toContain('CUSTOM');
  });
});

describe('personaStatus classification', () => {
  it('classifies baseline / overridden / custom correctly', async () => {
    await mkdir(overrideDir, { recursive: true });
    // ceo: overridden (both). code: baseline (only base). mascot: custom (only override).
    await writeFile(join(overrideDir, 'ceo.md'), overridePersona('ceo', 'executive'), 'utf8');
    await writeFile(join(overrideDir, 'mascot.md'), overridePersona('mascot', 'fun'), 'utf8');

    const status = await personaStatus({ rolesDir, overrideDir });
    const byClass = new Map(status.map((s) => [s.klass, s]));
    expect(byClass.get('ceo')?.status).toBe('overridden');
    expect(byClass.get('code')?.status).toBe('baseline');
    expect(byClass.get('mascot')?.status).toBe('custom');
    // Domain surfaced.
    expect(byClass.get('ceo')?.domain).toBe('executive');
  });
});

describe('AC-NS-7 — update-survival simulation', () => {
  it('override and custom-added class survive a baseline reseed', async () => {
    // 1. User customizes ceo and adds a brand-new persona in the override layer.
    await mkdir(overrideDir, { recursive: true });
    await writeFile(join(overrideDir, 'ceo.md'), overridePersona('ceo', 'executive'), 'utf8');
    await writeFile(
      join(overrideDir, 'mascot.md'),
      overridePersona('mascot', 'fun', 'CUSTOM'),
      'utf8',
    );

    // 2. Simulate `mosaic update`: REPLACE the baseline roles/ entirely (as the
    //    framework reseed/rsync does), leaving roles.local/ untouched. The reseed
    //    even ships a NEW baseline ceo and adds a brand-new baseline persona.
    await rm(rolesDir, { recursive: true, force: true });
    await mkdir(rolesDir, { recursive: true });
    await writeFile(
      join(rolesDir, 'ceo.md'),
      baselinePersona('ceo', 'executive', 'RESEEDED-BASELINE'),
      'utf8',
    );
    await writeFile(join(rolesDir, 'code.md'), baselinePersona('code', 'engineering'), 'utf8');
    await writeFile(join(rolesDir, 'new-role.md'), baselinePersona('new-role', 'ops'), 'utf8');

    // 3. The override STILL wins (was not clobbered by the reseed).
    const ceo = await resolvePersona('ceo', { rolesDir, overrideDir });
    expect(ceo?.layer).toBe('override');
    expect(ceo?.content).toContain('OVERRIDE');
    expect(ceo?.content).not.toContain('RESEEDED-BASELINE');

    // 4. The custom-added class still exists and resolves.
    const mascot = await resolvePersona('mascot', { rolesDir, overrideDir });
    expect(mascot?.layer).toBe('override');
    expect(mascot?.content).toContain('CUSTOM');

    // 5. New baseline personas from the reseed are now visible too.
    const classes = await listPersonaClasses({ rolesDir, overrideDir });
    expect(classes.has('new-role')).toBe(true);
    expect(classes.has('mascot')).toBe(true);
  });
});

describe('fleet-profiles validation accepts a custom (override-only) persona', () => {
  it('a profile referencing an override-only class validates', async () => {
    // Build a profiles dir + roles using the REAL library plus a custom persona.
    const profilesDir = join(tmp, 'profiles');
    const customRolesDir = join(tmp, 'real-roles');
    const customOverrideDir = join(tmp, 'real-roles.local');
    await mkdir(profilesDir, { recursive: true });
    await cp(realRolesDir, customRolesDir, { recursive: true });
    await mkdir(customOverrideDir, { recursive: true });
    await writeFile(join(customOverrideDir, 'mascot.md'), overridePersona('mascot', 'fun'), 'utf8');

    // A profile whose roster references the custom (override-only) persona.
    const profileYaml = [
      'id: custom-team',
      'title: Custom Team',
      'description: A team that uses a user-added persona.',
      'lead: ceo',
      'floor:',
      '  - ceo',
      'roster:',
      '  - class: ceo',
      '  - class: mascot',
      '    reports_to: ceo',
    ].join('\n');
    await writeFile(join(profilesDir, 'custom-team.yaml'), profileYaml, 'utf8');

    // Override-aware loadProfiles must accept it (would throw if mascot unknown).
    const profiles = await loadProfiles({
      profilesDir,
      rolesDir: customRolesDir,
      overrideDir: customOverrideDir,
    });
    const team = profiles.find((p: FleetProfile) => p.id === 'custom-team');
    expect(team).toBeDefined();

    // And direct validation against the union confirms zero problems.
    const validClasses = await listPersonaClasses({
      rolesDir: customRolesDir,
      overrideDir: customOverrideDir,
    });
    expect(validateProfile(team as FleetProfile, validClasses)).toEqual([]);
  });
});
