feat(fleet): inject persona contract at launch (A3b) (#664)
This commit was merged in pull request #664.
This commit is contained in:
106
packages/mosaic/src/fleet/persona-contract.spec.ts
Normal file
106
packages/mosaic/src/fleet/persona-contract.spec.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { readPersonaContractBlock } from './persona-contract.js';
|
||||
|
||||
/**
|
||||
* Persona-contract launch injection (A3b). Asserts the override-aware resolver
|
||||
* is wired so a customized persona in roles.local/ wins at launch (AC-NS-7), and
|
||||
* that any miss (unset/empty/unknown class, missing file) no-ops silently —
|
||||
* never throws — mirroring readFleetCommsBlock's tolerant contract.
|
||||
*/
|
||||
|
||||
const BASELINE_CODER = `# Coder — fleet role definition
|
||||
|
||||
The **coder** persona (\`class: coder\`, \`domain: engineering\`).
|
||||
|
||||
## Mandate
|
||||
|
||||
BASELINE-MANDATE: implement the assigned lane.
|
||||
`;
|
||||
|
||||
const OVERRIDE_CODER = `# Coder — fleet role definition (override)
|
||||
|
||||
The **coder** persona (\`class: coder\`).
|
||||
|
||||
## Mandate
|
||||
|
||||
OVERRIDE-MANDATE: implement the assigned lane, the user's way.
|
||||
`;
|
||||
|
||||
function makeHome(): string {
|
||||
const root = mkdtempSync(join(tmpdir(), 'mosaic-persona-'));
|
||||
return join(root, 'mosaic-home');
|
||||
}
|
||||
|
||||
function seedBaseline(home: string, klass: string, body: string): void {
|
||||
const dir = join(home, 'fleet', 'roles');
|
||||
mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(join(dir, `${klass}.md`), body);
|
||||
}
|
||||
|
||||
function seedOverride(home: string, klass: string, body: string): void {
|
||||
const dir = join(home, 'fleet', 'roles.local');
|
||||
mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(join(dir, `${klass}.md`), body);
|
||||
}
|
||||
|
||||
describe('readPersonaContractBlock — launch-time persona injection (A3b)', () => {
|
||||
let home: string;
|
||||
|
||||
beforeEach(() => {
|
||||
home = makeHome();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// root is the parent of mosaic-home
|
||||
rmSync(join(home, '..'), { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('injects the baseline persona when the class has a fleet/roles/<class>.md', () => {
|
||||
seedBaseline(home, 'coder', BASELINE_CODER);
|
||||
const block = readPersonaContractBlock(home, 'coder');
|
||||
expect(block).toContain('# Persona Contract (coder)');
|
||||
expect(block).toContain('BASELINE-MANDATE');
|
||||
expect(block).toContain('baseline `fleet/roles/` layer');
|
||||
});
|
||||
|
||||
it('OVERRIDE WINS: roles.local/<class>.md content is injected over the baseline (AC-NS-7)', () => {
|
||||
seedBaseline(home, 'coder', BASELINE_CODER);
|
||||
seedOverride(home, 'coder', OVERRIDE_CODER);
|
||||
const block = readPersonaContractBlock(home, 'coder');
|
||||
expect(block).toContain('# Persona Contract (coder)');
|
||||
expect(block).toContain('OVERRIDE-MANDATE'); // override body present
|
||||
expect(block).not.toContain('BASELINE-MANDATE'); // baseline NOT used
|
||||
expect(block).toContain('roles.local'); // layer note names the override layer
|
||||
});
|
||||
|
||||
it('injects an override-only (user-added) persona with no baseline at all', () => {
|
||||
seedOverride(home, 'reviewer', '# Reviewer\n\n(`class: reviewer`)\n\nCUSTOM-ROLE.\n');
|
||||
const block = readPersonaContractBlock(home, 'reviewer');
|
||||
expect(block).toContain('# Persona Contract (reviewer)');
|
||||
expect(block).toContain('CUSTOM-ROLE');
|
||||
});
|
||||
|
||||
it('no-ops (empty string) when the class is undefined', () => {
|
||||
seedBaseline(home, 'coder', BASELINE_CODER);
|
||||
expect(readPersonaContractBlock(home, undefined)).toBe('');
|
||||
});
|
||||
|
||||
it('no-ops (empty string) when the class is empty/whitespace', () => {
|
||||
seedBaseline(home, 'coder', BASELINE_CODER);
|
||||
expect(readPersonaContractBlock(home, '')).toBe('');
|
||||
expect(readPersonaContractBlock(home, ' ')).toBe('');
|
||||
});
|
||||
|
||||
it('no-ops (empty string) for an unknown class with no role file', () => {
|
||||
seedBaseline(home, 'coder', BASELINE_CODER);
|
||||
expect(readPersonaContractBlock(home, 'nonexistent')).toBe('');
|
||||
});
|
||||
|
||||
it('no-ops (empty string, no throw) when no roles directories exist at all', () => {
|
||||
expect(() => readPersonaContractBlock(home, 'coder')).not.toThrow();
|
||||
expect(readPersonaContractBlock(home, 'coder')).toBe('');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user