fix(mosaic): reject unapproved brain skeleton state

This commit is contained in:
2026-08-05 17:19:51 -05:00
parent bad53564ed
commit 22cedbb506
5 changed files with 86 additions and 17 deletions
@@ -292,6 +292,63 @@ describe('P7 brain provisioning orchestration', (): void => {
expect(requests.some((request) => request.args.includes('grant'))).toBe(false);
});
it('blocks before skeleton publication when the existing checkout is dirty', async (): Promise<void> => {
const provisioner = await loadProvisioner('MB-REQ-06 dirty checkout publication gate');
const root = tempRoot();
const input = baseInput(root);
mkdirSync(join(input.root, '.git'), { recursive: true });
const requests: CommandRequest[] = [];
const result = await provisioner.provisionBrain(input, {
run: (request): CommandResult => {
requests.push(request);
if (request.program === 'mosaic') {
return request.args[2] === 'outside-seat'
? {
status: 10,
stdout: validateResult('refused', 'no-token-for-identity', 'outside-seat'),
stderr: 'refused reason=no-token-for-identity',
}
: { status: 0, stdout: validateResult('ok', 'validation-verified'), stderr: '' };
}
const command = request.args.join(' ');
if (command.includes('ls-remote')) {
return {
status: 128,
stdout: '',
stderr: 'credential helper refused reason=no-token-for-identity',
};
}
if (command.includes('rev-parse --is-inside-work-tree')) {
return { status: 0, stdout: 'true\n', stderr: '' };
}
if (command.includes('remote get-url origin')) {
return {
status: 0,
stdout: 'https://git.example.invalid/durable-owner/mosaic-brain.git\n',
stderr: '',
};
}
if (command.includes('branch --show-current')) {
return { status: 0, stdout: 'main\n', stderr: '' };
}
if (command.includes('status --porcelain')) {
return { status: 0, stdout: '?? .gitignore\n', stderr: '' };
}
return { status: 99, stdout: '', stderr: 'unexpected publication command' };
},
fetch: ownerFetch(),
absentControlName: (): string => 'generated-absent-control',
});
expect(result).toMatchObject({
status: 'blocked',
reasonCode: 'brain-postcondition-failed',
});
expect(requests.some((request) => request.args.includes('commit'))).toBe(false);
expect(requests.some((request) => request.args.includes('push'))).toBe(false);
});
it('clones, seeds, resolves owner, migrates, pushes on each write, and archives source only after reachability', async (): Promise<void> => {
const provisioner = await loadProvisioner('MB-REQ-07 complete migration transaction');
const root = tempRoot();
@@ -175,6 +175,8 @@ export async function provisionBrain(
finding.code === 'brain-not-git-repository' ||
finding.code === 'brain-remote-mismatch' ||
finding.code === 'brain-branch-mismatch' ||
finding.code === 'brain-uncommitted-state' ||
finding.code === 'brain-git-state-indeterminate' ||
finding.code.startsWith('brain-write-access-'),
);
if (blockingCloneFindings.length > 0) {
@@ -359,6 +359,16 @@ describe('R6 — brain layout refuses secret material', (): void => {
);
});
it('refuses existing noncanonical gitignore content instead of publishing it', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-06 unapproved gitignore content');
const root = tempRoot();
const ignore = join(root, '.gitignore');
writeFileSync(ignore, '# token=fixture-value\n*.token\n');
expect(() => sut.createBrainSkeleton(root)).toThrow(/brain-layout-ignore-content-unsafe/);
expect(readFileSync(ignore, 'utf8')).toBe('# token=fixture-value\n*.token\n');
});
it('refuses a symlinked layout directory without writing a tracked placeholder outside the brain', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-06 no-follow brain layout');
const root = tempRoot();
+15 -15
View File
@@ -10,7 +10,6 @@ import {
openSync,
readdirSync,
readFileSync,
renameSync,
rmSync,
unlinkSync,
writeFileSync,
@@ -400,21 +399,22 @@ export function createBrainSkeleton(root: string): {
throw new Error('brain-layout-ignore-unsafe');
}
}
const existing = existsSync(ignorePath)
? readFileSync(ignorePath, 'utf8')
.split(/\r?\n/)
.filter((line: string): boolean => line.length > 0)
: [];
const merged = [...existing];
for (const rule of GITIGNORE_RULES) {
if (!merged.includes(rule)) merged.push(rule);
}
const content = `${merged.join('\n')}\n`;
if (!existsSync(ignorePath) || readFileSync(ignorePath, 'utf8') !== content) {
const content = `${GITIGNORE_RULES.join('\n')}\n`;
if (existsSync(ignorePath)) {
const snapshot = stableSourceSnapshot(ignorePath);
if (!snapshot.content.equals(Buffer.from(content, 'utf8'))) {
throw new Error('brain-layout-ignore-content-unsafe');
}
} else {
const temporary = `${ignorePath}.tmp-${process.pid}-${randomUUID()}`;
writeFileSync(temporary, content, { encoding: 'utf8', mode: 0o644, flag: 'wx' });
syncFile(temporary);
renameSync(temporary, ignorePath);
try {
writeFileSync(temporary, content, { encoding: 'utf8', mode: 0o644, flag: 'wx' });
syncFile(temporary);
linkSync(temporary, ignorePath);
syncFile(ignorePath);
} finally {
rmSync(temporary, { force: true });
}
created.push(ignorePath);
publicationEntries.push({ path: ignorePath, content: new TextEncoder().encode(content) });
}