test(#1051): preregister mosaic-brain acceptance contract

This commit is contained in:
2026-08-05 17:19:50 -05:00
parent 85d2108e4e
commit 01694f3f98
3 changed files with 750 additions and 0 deletions
@@ -0,0 +1,623 @@
import { afterEach, describe, expect, it } from 'vitest';
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
/**
* Red-first contract checks for stack #1051 / MB-BRAIN-01.
*
* These checks are committed before the implementation. They bind to the
* MC-CRED v1.3 terminal classes and stable reason codes, not to the currently
* deployed resolver behavior. Live grant and read/write round-trip tests remain
* gated on MC-CRED-01; these fixtures contain no credential values.
*/
interface BrainTarget {
readonly estate: string;
readonly host: string;
readonly owner: string;
readonly repo: string;
readonly cloneUrl: string;
}
interface CredentialAssessment {
readonly outcome: 'ok' | 'refused' | 'error' | 'indeterminate';
readonly exitCode: 0 | 10 | 20 | 30;
readonly reasonCode: string;
readonly diagnostic: string;
}
interface ResolverParityAssessment extends CredentialAssessment {
readonly gitReasonCode: string;
readonly apiReasonCode: string;
}
interface MigrationOwner {
readonly name: string;
readonly kind: 'active-lane' | 'durable-team' | 'durable-human' | 'durable-queue';
readonly validated: boolean;
}
interface MigrationCandidate {
readonly source: string;
readonly destination: string;
readonly archive: string;
readonly kind: 'lane' | 'seat';
}
interface MigrationPlan {
readonly status: 'ready' | 'blocked';
readonly candidates: readonly MigrationCandidate[];
readonly reported: readonly { path: string; reason: string }[];
readonly owner: MigrationOwner | null;
}
interface MigrationPublishEvidence {
readonly commit: string;
readonly remoteHead: string;
readonly reachable: boolean;
}
interface MigrationResult {
readonly status: 'migrated' | 'reported' | 'failed';
readonly migrated: readonly MigrationCandidate[];
readonly reported: readonly { path: string; reason: string }[];
readonly publish: MigrationPublishEvidence | null;
}
interface BrainDoctorObservation {
readonly rootExists: boolean;
readonly gitRepository: boolean;
readonly remote: string | null;
readonly branch: string | null;
readonly dirty: boolean | null;
readonly access: CredentialAssessment | null;
}
interface BrainDoctorFinding {
readonly code: string;
readonly repairable: boolean;
readonly reasonCode: string | null;
}
interface BrainDoctorAction {
readonly program: 'git' | 'mosaic';
readonly args: readonly string[];
readonly findingCode: string;
}
interface BrainWritePolicy {
readonly allowed: boolean;
readonly mode: 'append-only' | 'single-writer' | 'seat-writer' | 'refused';
readonly reason: string;
}
interface BrainStoreModule {
deriveBrainTarget(registrySource: string, targetGitUrl: string): BrainTarget;
createBrainSkeleton(root: string): { readonly created: readonly string[] };
assessCredentialResult(source: string): CredentialAssessment;
assessResolverParity(gitSource: string, apiSource: string): ResolverParityAssessment;
discoverBrainMigration(input: {
readonly sourceRoot: string;
readonly brainRoot: string;
readonly seat: string;
readonly lane: string;
readonly laneActive: boolean;
readonly owner?: MigrationOwner;
}): MigrationPlan;
migrateBrainState(
plan: MigrationPlan,
publish: (brainRoot: string, paths: readonly string[]) => MigrationPublishEvidence,
brainRoot: string,
): MigrationResult;
evaluateBrainDoctor(
observation: BrainDoctorObservation,
expectedRemote: string,
): readonly BrainDoctorFinding[];
planBrainDoctorFix(input: {
readonly findings: readonly BrainDoctorFinding[];
readonly target: BrainTarget;
readonly identity: string;
readonly root: string;
}): readonly BrainDoctorAction[];
classifyBrainWrite(input: {
readonly path: string;
readonly actor: string;
readonly seat: string;
readonly boardWriter?: string;
}): BrainWritePolicy;
}
const MODULE_PATH = './brain-store.js';
const tempRoots: string[] = [];
async function loadSut(requirement: string): Promise<BrainStoreModule> {
try {
return (await import(MODULE_PATH)) as BrainStoreModule;
} catch (error: unknown) {
const detail = error instanceof Error ? error.message : String(error);
throw new Error(`${requirement}: brain-store implementation is absent (${detail})`);
}
}
function tempRoot(): string {
const root = mkdtempSync(join(tmpdir(), 'mosaic-brain-contract-'));
tempRoots.push(root);
return root;
}
function registry(): string {
return JSON.stringify({
version: 1,
estates: [
{
name: 'homelab',
readOnlyControlIdentity: 'homelab-read-control',
hosts: [
{
host: 'git.mosaicstack.dev',
provider: 'gitea',
apiBaseUrl: 'https://git.mosaicstack.dev',
tokenPrefix: 'gitea-mosaicstack',
},
],
},
{
name: 'usc',
readOnlyControlIdentity: 'usc-read-control',
hosts: [
{
host: 'git.uscllc.com',
provider: 'gitea',
apiBaseUrl: 'https://git.uscllc.com',
tokenPrefix: 'gitea-usc',
},
],
},
],
});
}
function credentialResult(
outcome: CredentialAssessment['outcome'],
reasonCode: string,
message = 'non-secret diagnostic',
): string {
const exits = { ok: 0, refused: 10, error: 20, indeterminate: 30 } as const;
return JSON.stringify({
schemaVersion: 1,
operation: 'validate',
outcome,
exitCode: exits[outcome],
retryable: false,
subject: {
identity: 'external-seat',
estate: 'homelab',
host: 'git.mosaicstack.dev',
repo: 'mosaicstack/mosaic-brain',
},
mutation: 'none',
reason: { code: reasonCode, message },
evidence: {
providerIdentity: null,
repositoryPermission: null,
writeDifferential: null,
},
audit: { journalId: 'opaque', state: 'sealed' },
});
}
afterEach((): void => {
for (const root of tempRoots.splice(0)) {
rmSync(root, { recursive: true, force: true });
}
});
describe('R2/Q1 — estate and brain discovery have one authority', (): void => {
it('derives the estate from the configured target git host and the brain owner from that target URL', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-02 target-host estate derivation');
expect(
sut.deriveBrainTarget(registry(), 'https://git.mosaicstack.dev/mosaicstack/stack.git'),
).toEqual({
estate: 'homelab',
host: 'git.mosaicstack.dev',
owner: 'mosaicstack',
repo: 'mosaicstack/mosaic-brain',
cloneUrl: 'https://git.mosaicstack.dev/mosaicstack/mosaic-brain.git',
});
});
it('fails closed for an unmapped target host instead of consulting machine or ambient estate values', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-02 unmapped host fail-closed');
const previous = process.env['MOSAIC_ESTATE'];
process.env['MOSAIC_ESTATE'] = 'homelab';
try {
expect(() =>
sut.deriveBrainTarget(registry(), 'https://unmapped.example.invalid/acme/stack.git'),
).toThrow(/estate-host-unmapped/);
} finally {
if (previous === undefined) delete process.env['MOSAIC_ESTATE'];
else process.env['MOSAIC_ESTATE'] = previous;
}
});
});
describe('R6 — brain layout refuses secret material', (): void => {
it('creates the durable layout and exact required gitignore exclusions', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-06 exact secret exclusions');
const root = tempRoot();
sut.createBrainSkeleton(root);
for (const directory of ['agents', 'lanes', 'board', 'specs', 'methods', 'archives']) {
expect(existsSync(join(root, directory)), directory).toBe(true);
}
const rules = readFileSync(join(root, '.gitignore'), 'utf8').trim().split('\n');
expect(rules).toEqual(['*.token', '*.key', '*.pem', '.env', 'credentials.json']);
});
it('never relays broker reason messages that may contain secret-bearing text', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-06 secret-free error path');
const secretMarker = 'DO-NOT-EMIT-CREDENTIAL-MARKER';
const result = sut.assessCredentialResult(
credentialResult('error', 'insecure-credential-source', secretMarker),
);
expect(JSON.stringify(result)).not.toContain(secretMarker);
expect(result.diagnostic).toContain('insecure-credential-source');
});
});
describe('credential caller contract v1.3 terminal classes', (): void => {
it.each([
['ok', 0, 'grant-verified'],
['refused', 10, 'no-token-for-identity'],
['error', 20, 'estate-registry-invalid'],
['indeterminate', 30, 'provider-unavailable'],
['indeterminate', 30, 'identity-not-found'],
['indeterminate', 30, 'credential-rejected'],
] as const)(
'preserves %s/%i and stable reason %s without parsing prose',
async (outcome, exitCode, reasonCode): Promise<void> => {
const sut = await loadSut(`AC-MB-01 terminal class ${outcome}/${exitCode}`);
const result = sut.assessCredentialResult(credentialResult(outcome, reasonCode));
expect(result.outcome).toBe(outcome);
expect(result.exitCode).toBe(exitCode);
expect(result.reasonCode).toBe(reasonCode);
},
);
it('makes a missing or inconsistent decision field indeterminate rather than success or refusal', async (): Promise<void> => {
const sut = await loadSut('AC-MB-01 malformed broker result fail-closed');
const malformed = JSON.parse(credentialResult('ok', 'grant-verified')) as Record<
string,
unknown
>;
malformed['exitCode'] = 10;
const result = sut.assessCredentialResult(JSON.stringify(malformed));
expect(result.outcome).toBe('indeterminate');
expect(result.exitCode).toBe(30);
expect(result.reasonCode).toBe('unexpected-provider-shape');
});
});
describe('R5 — out-of-estate refusal must agree on both resolver axes', (): void => {
it('accepts refusal evidence only when Git and API return the same authoritative refusal', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-05 both-axis refusal');
const refusal = credentialResult('refused', 'no-token-for-identity');
const result = sut.assessResolverParity(refusal, refusal);
expect(result.outcome).toBe('refused');
expect(result.exitCode).toBe(10);
expect(result.reasonCode).toBe('no-token-for-identity');
expect(result.gitReasonCode).toBe('no-token-for-identity');
expect(result.apiReasonCode).toBe('no-token-for-identity');
});
it.each([
[
credentialResult('refused', 'no-token-for-identity'),
credentialResult('ok', 'grant-verified'),
],
[
credentialResult('refused', 'no-token-for-identity'),
credentialResult('refused', 'cross-estate-resolution'),
],
[
credentialResult('refused', 'no-token-for-identity'),
credentialResult('indeterminate', 'provider-unavailable'),
],
])('turns axis disagreement into indeterminate failure', async (git, api): Promise<void> => {
const sut = await loadSut('MB-REQ-05 resolver-axis disagreement');
const result = sut.assessResolverParity(git, api);
expect(result.outcome).toBe('indeterminate');
expect(result.exitCode).toBe(30);
expect(result.reasonCode).toBe('permission-evidence-disagrees');
});
});
describe('R7 — migration is non-destructive, append-only, and explicit', (): void => {
it('detects canonical lane and current-seat state while explicitly reporting unsupported local state', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-07 migration detection/reporting');
const root = tempRoot();
const sourceRoot = join(root, 'local-memory');
const brainRoot = join(root, 'brain');
mkdirSync(join(sourceRoot, 'lanes', 'lane-a'), { recursive: true });
mkdirSync(join(sourceRoot, 'agents', 'seat-a'), { recursive: true });
writeFileSync(join(sourceRoot, 'lanes', 'lane-a', 'finding.md'), 'lane finding\n');
writeFileSync(join(sourceRoot, 'agents', 'seat-a', 'STATE.md'), 'seat state\n');
writeFileSync(join(sourceRoot, 'orphan-state.md'), 'must be reported\n');
const plan = sut.discoverBrainMigration({
sourceRoot,
brainRoot,
seat: 'seat-a',
lane: 'lane-a',
laneActive: true,
owner: { name: 'lane-a', kind: 'active-lane', validated: true },
});
expect(plan.status).toBe('ready');
expect(plan.candidates.map((candidate) => candidate.kind).sort()).toEqual(['lane', 'seat']);
expect(plan.reported).toEqual(
expect.arrayContaining([
expect.objectContaining({ path: join(sourceRoot, 'orphan-state.md') }),
]),
);
});
it('leaves the migration gate blocking when no validated owner exists', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-09 named durable owner gate');
const root = tempRoot();
const sourceRoot = join(root, 'local-memory');
mkdirSync(join(sourceRoot, 'lanes', 'closed-lane'), { recursive: true });
writeFileSync(join(sourceRoot, 'lanes', 'closed-lane', 'finding.md'), 'finding\n');
const plan = sut.discoverBrainMigration({
sourceRoot,
brainRoot: join(root, 'brain'),
seat: 'seat-a',
lane: 'closed-lane',
laneActive: false,
});
expect(plan.status).toBe('blocked');
expect(plan.candidates).toHaveLength(0);
expect(plan.reported).toEqual(
expect.arrayContaining([
expect.objectContaining({ reason: expect.stringMatching(/durable owner/i) }),
]),
);
});
it('publishes collision-safe append-only copies before archiving sources and never overwrites a finding', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-07 append-only publish-before-archive migration');
const root = tempRoot();
const sourceRoot = join(root, 'local-memory');
const brainRoot = join(root, 'brain');
mkdirSync(join(sourceRoot, 'lanes', 'lane-a'), { recursive: true });
writeFileSync(join(sourceRoot, 'lanes', 'lane-a', 'finding.md'), 'new finding\n');
const plan = sut.discoverBrainMigration({
sourceRoot,
brainRoot,
seat: 'seat-a',
lane: 'lane-a',
laneActive: true,
owner: { name: 'lane-a', kind: 'active-lane', validated: true },
});
const candidate = plan.candidates[0];
expect(candidate).toBeDefined();
if (candidate === undefined) return;
mkdirSync(join(brainRoot, 'lanes', 'lane-a', 'findings', 'imports'), { recursive: true });
const preexisting = join(
brainRoot,
'lanes',
'lane-a',
'findings',
'imports',
'existing-finding.md',
);
writeFileSync(preexisting, 'older independent finding\n');
let publishedPaths: readonly string[] = [];
const result = sut.migrateBrainState(
plan,
(_publishedRoot, paths): MigrationPublishEvidence => {
publishedPaths = paths;
return { commit: 'a'.repeat(40), remoteHead: 'a'.repeat(40), reachable: true };
},
brainRoot,
);
expect(result.status).toBe('migrated');
expect(readFileSync(preexisting, 'utf8')).toBe('older independent finding\n');
expect(readFileSync(candidate.destination, 'utf8')).toBe('new finding\n');
expect(readFileSync(candidate.archive, 'utf8')).toBe('new finding\n');
expect(existsSync(candidate.source)).toBe(false);
expect(publishedPaths).toContain(candidate.destination);
expect(publishedPaths).toContain(candidate.archive);
});
it('retains every source and reports failure when remote reachability is not established', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-07 failed-publish source preservation');
const root = tempRoot();
const sourceRoot = join(root, 'local-memory');
const brainRoot = join(root, 'brain');
mkdirSync(join(sourceRoot, 'agents', 'seat-a'), { recursive: true });
const source = join(sourceRoot, 'agents', 'seat-a', 'STATE.md');
writeFileSync(source, 'seat state\n');
const plan = sut.discoverBrainMigration({
sourceRoot,
brainRoot,
seat: 'seat-a',
lane: 'lane-a',
laneActive: true,
owner: { name: 'lane-a', kind: 'active-lane', validated: true },
});
const result = sut.migrateBrainState(
plan,
(): MigrationPublishEvidence => ({
commit: 'a'.repeat(40),
remoteHead: 'b'.repeat(40),
reachable: false,
}),
brainRoot,
);
expect(result.status).toBe('failed');
expect(readFileSync(source, 'utf8')).toBe('seat state\n');
expect(result.reported).toEqual(
expect.arrayContaining([
expect.objectContaining({ reason: expect.stringMatching(/reachab/i) }),
]),
);
});
});
describe('R8 — doctor diagnoses defects and fixes only through approved seams', (): void => {
it('surfaces missing clone, wrong remote, absent write evidence, and uncommitted state as distinct defects', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-08 doctor defect classes');
const expected = 'https://git.mosaicstack.dev/mosaicstack/mosaic-brain.git';
const missing = sut.evaluateBrainDoctor(
{
rootExists: false,
gitRepository: false,
remote: null,
branch: null,
dirty: null,
access: null,
},
expected,
);
expect(missing.map((finding) => finding.code)).toContain('brain-clone-missing');
const defects = sut.evaluateBrainDoctor(
{
rootExists: true,
gitRepository: true,
remote: 'https://git.uscllc.com/usc/mosaic-brain.git',
branch: 'main',
dirty: true,
access: sut.assessCredentialResult(
credentialResult('indeterminate', 'credential-rejected'),
),
},
expected,
);
expect(defects.map((finding) => finding.code)).toEqual(
expect.arrayContaining([
'brain-remote-mismatch',
'brain-write-access-indeterminate',
'brain-uncommitted-state',
]),
);
});
it('plans clone/remote/grant repair with git and mosaic cred only and never emits a token lookup', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-08 approved doctor fix seam');
const target = sut.deriveBrainTarget(
registry(),
'https://git.mosaicstack.dev/mosaicstack/stack.git',
);
const findings: BrainDoctorFinding[] = [
{ code: 'brain-clone-missing', repairable: true, reasonCode: null },
{ code: 'brain-remote-mismatch', repairable: true, reasonCode: null },
{ code: 'brain-write-access-refused', repairable: true, reasonCode: 'permission-denied' },
{ code: 'brain-uncommitted-state', repairable: false, reasonCode: null },
];
const actions = sut.planBrainDoctorFix({
findings,
target,
identity: 'seat-a',
root: '/home/test/.mosaic',
});
const rendered = JSON.stringify(actions);
expect(actions.map((action) => action.program)).toEqual(['git', 'git', 'mosaic']);
expect(rendered).toContain('cred');
expect(rendered).toContain('grant');
expect(rendered).toContain('--estate');
expect(rendered).toContain('homelab');
expect(rendered).not.toMatch(/token|authorization|password/i);
expect(actions.some((action) => action.findingCode === 'brain-uncommitted-state')).toBe(false);
});
it('does not reinterpret error or indeterminate evidence as grantable refusal', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-08 non-refusal fail-closed fix planning');
const target = sut.deriveBrainTarget(
registry(),
'https://git.mosaicstack.dev/mosaicstack/stack.git',
);
for (const finding of [
{
code: 'brain-write-access-error',
repairable: false,
reasonCode: 'estate-registry-invalid',
},
{
code: 'brain-write-access-indeterminate',
repairable: false,
reasonCode: 'provider-unavailable',
},
]) {
expect(
sut.planBrainDoctorFix({
findings: [finding],
target,
identity: 'seat-a',
root: '/home/test/.mosaic',
}),
).toHaveLength(0);
}
});
});
describe('Q2/Q3 doctrine — writes and retention are structurally constrained', (): void => {
it('makes lane findings append-only, board writes single-writer, and seat state seat-owned', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-07 write policy');
expect(
sut.classifyBrainWrite({
path: 'lanes/lane-a/findings/new.md',
actor: 'seat-a',
seat: 'seat-a',
}),
).toMatchObject({ allowed: true, mode: 'append-only' });
expect(
sut.classifyBrainWrite({
path: 'board/assignments.json',
actor: 'tl-mosaic',
seat: 'seat-a',
boardWriter: 'tl-mosaic',
}),
).toMatchObject({ allowed: true, mode: 'single-writer' });
expect(
sut.classifyBrainWrite({
path: 'board/assignments.json',
actor: 'seat-a',
seat: 'seat-a',
boardWriter: 'tl-mosaic',
}),
).toMatchObject({ allowed: false, mode: 'refused' });
expect(
sut.classifyBrainWrite({
path: 'agents/other-seat/STATE.md',
actor: 'seat-a',
seat: 'seat-a',
}),
).toMatchObject({ allowed: false, mode: 'refused' });
});
});