From d2412a25db80819058de3251f683a8ace407b9f6 Mon Sep 17 00:00:00 2001 From: be-coder-07 Date: Wed, 5 Aug 2026 14:07:58 -0500 Subject: [PATCH] fix(mosaic): isolate brain publication index --- .../1051-mosaic-brain-installer.md | 4 +- .../src/commands/brain-provision.spec.ts | 19 +++ .../src/commands/brain-store-runtime.spec.ts | 107 ++++++++++++- .../src/commands/brain-store-runtime.ts | 150 ++++++++++++++---- 4 files changed, 244 insertions(+), 36 deletions(-) diff --git a/docs/scratchpads/1051-mosaic-brain-installer.md b/docs/scratchpads/1051-mosaic-brain-installer.md index fe4d5edb..fee5a78e 100644 --- a/docs/scratchpads/1051-mosaic-brain-installer.md +++ b/docs/scratchpads/1051-mosaic-brain-installer.md @@ -83,13 +83,13 @@ No explicit token ceiling was supplied. Working cap: 55K tokens for implementati - [x] C1 P5→P7 seam receipt read; no brain implementation is in C1. - [x] RED acceptance set committed at `cf11c6c86abae073d8b02b4014cd5447ba67f12a`; author and committer read back as `be-coder-07` and branch reachability was independently verified by `tl-mosaic`. - [x] Moving-contract REDs observed independently for v1.4 mismatch, R8 prerequisite ordering, owner resolver seam/allowlist, tracked skeleton/no-follow behavior, runtime observation/publication, and provider owner resolution. -- [x] Focused implementation includes secure migration, v1.5 write-differential/subject binding, production Git+API refusal parity, provider-backed durable owner resolution that ignores non-admin `active`, required GLPI standing-process policy, P7 provision orchestration, an internal installer command, and installed `mosaic doctor` wiring. Latest focused result: 86/86 (secure config 3, store 43, runtime 12, owner resolver 16, provision 4, provision command 3, installed doctor 5). +- [x] Focused implementation includes secure migration, v1.5 write-differential/subject binding, production Git+API refusal parity, provider-backed durable owner resolution that ignores non-admin `active`, required GLPI standing-process policy, P7 provision orchestration, an internal installer command, and installed `mosaic doctor` wiring. Latest focused result: 87/87 (secure config 3, store 43, runtime 13, owner resolver 16, provision 4, provision command 3, installed doctor 5). - [x] MC-CRED added the required canonical reverse registry seam `ParsedCredentialEstateRegistry.resolveByHost()` at dependency head `6ca8758f`; current local copies are temporary until dependency integration and the 32-line permissive shim has been removed. - [x] Identity gotcha measured: inline `MOSAIC_GIT_IDENTITY=be-coder-07` controls credential resolution but does not override `user.name`/`user.email` inherited from the linked worktree common-dir config (`coder-mos1`). The first local P7 RED commit was immediately amended before push with command-scoped `GIT_AUTHOR_*` + `GIT_COMMITTER_*`; resulting author and committer both read back as `be-coder-07`. Every subsequent authoring command must carry both identity sets and be verified. - [x] R6 migration reports filename- or content-secret-shaped files without copying them; arbitrary legacy content requires an approved scanner bound to the exact source snapshot, and production currently retains/reports when no approved scanner is configured. Symlinked `.gitignore`, layout directories, and nested migration destinations fail closed. - [x] Provider owner lookup uses manual redirect handling, a five-second abort signal, strict JSON content type/shape, and an incrementally enforced 256 KiB response ceiling. - [x] Automatic source deletion is parked per the shared-Git-identity governance ruling; remotely reachable snapshots still leave and report every source. -- [x] Multi-host push-on-write retries non-fast-forward races via fetch/rebase rather than LWW, and unknown publication reachability retains both sources and local copies. +- [x] Multi-host push-on-write uses an isolated temporary Git index, verifies the exact changed-path allowlist plus both author/committer trailers before push, retries non-fast-forward races via identity-scoped fetch/rebase rather than LWW, and retains both sources and local copies when publication reachability is unknown. A real-repository abuse test proves unrelated pre-staged secret-shaped content remains staged but never enters the published commit. - [ ] Installer shell P7 invocation after C1 + MC-CRED integration; production command is registered but the C1 shell has not yet called it. - [ ] Implementation green on merged dependency base. - [ ] Independent code review. diff --git a/packages/mosaic/src/commands/brain-provision.spec.ts b/packages/mosaic/src/commands/brain-provision.spec.ts index 1bb8d96c..1c545c8e 100644 --- a/packages/mosaic/src/commands/brain-provision.spec.ts +++ b/packages/mosaic/src/commands/brain-provision.spec.ts @@ -301,6 +301,7 @@ describe('P7 brain provisioning orchestration', (): void => { writeFileSync(source, 'durable finding\n'); const requests: CommandRequest[] = []; let commitOrdinal = 0; + let approvedPaths: string[] = []; const runner: CommandRunner = (request): CommandResult => { requests.push(request); if (request.program === 'mosaic') { @@ -325,6 +326,24 @@ describe('P7 brain provisioning orchestration', (): void => { mkdirSync(join(input.root, '.git'), { recursive: true }); return { status: 0, stdout: '', stderr: '' }; } + if (command.includes(' add -- ')) { + const separator = request.args.indexOf('--'); + approvedPaths = separator === -1 ? [] : [...request.args.slice(separator + 1)]; + return { status: 0, stdout: '', stderr: '' }; + } + if (command.includes('diff --cached --quiet')) { + return { status: 1, stdout: '', stderr: '' }; + } + if (command.includes('diff-tree')) { + return { status: 0, stdout: `${approvedPaths.join('\0')}\0`, stderr: '' }; + } + if (command.includes('show -s')) { + return { + status: 0, + stdout: 'seat-a\0seat-a@fleet.mosaicstack.dev\0seat-a\0seat-a@fleet.mosaicstack.dev\n', + stderr: '', + }; + } if (command.includes('rev-parse --is-inside-work-tree')) { return { status: 0, stdout: 'true\n', stderr: '' }; } diff --git a/packages/mosaic/src/commands/brain-store-runtime.spec.ts b/packages/mosaic/src/commands/brain-store-runtime.spec.ts index c628007d..a3578449 100644 --- a/packages/mosaic/src/commands/brain-store-runtime.spec.ts +++ b/packages/mosaic/src/commands/brain-store-runtime.spec.ts @@ -1,5 +1,6 @@ import { afterEach, describe, expect, it } from 'vitest'; -import { mkdirSync, mkdtempSync, rmSync } from 'node:fs'; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { execFileSync } from 'node:child_process'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; @@ -46,6 +47,7 @@ interface PublishEvidence { } interface BrainRuntimeModule { + readonly systemCommandRunner: CommandRunner; collectBrainDoctorReport( input: { readonly registrySource: string; @@ -544,6 +546,64 @@ describe('R5 production refusal control', (): void => { }); describe('push-on-write publication', (): void => { + it('never commits or pushes an unrelated pre-staged secret-shaped file', async (): Promise => { + const runtime = await loadRuntime('MB-REQ-06 isolated publication index'); + const root = tempRoot(); + const remote = join(root, 'remote.git'); + const checkout = join(root, 'brain'); + execFileSync('git', ['init', '--bare', '--initial-branch=main', remote]); + execFileSync('git', ['init', '--initial-branch=main', checkout]); + writeFileSync(join(checkout, 'README.md'), 'brain\n'); + execFileSync('git', ['-C', checkout, 'add', 'README.md']); + execFileSync('git', [ + '-C', + checkout, + '-c', + 'user.name=fixture', + '-c', + 'user.email=fixture@example.invalid', + 'commit', + '-m', + 'seed', + ]); + execFileSync('git', ['-C', checkout, 'remote', 'add', 'origin', remote]); + execFileSync('git', ['-C', checkout, 'push', '-u', 'origin', 'main']); + writeFileSync(join(checkout, '.env'), 'TEST_ONLY_SECRET=fixture-value\n'); + execFileSync('git', ['-C', checkout, 'add', '-f', '.env']); + const approved = join(checkout, 'finding.md'); + writeFileSync(approved, 'approved finding\n'); + + const evidence = runtime.publishBrainPaths( + { + root: checkout, + identity: 'seat-a', + paths: [approved], + message: 'append approved finding', + }, + runtime.systemCommandRunner, + ); + + const committed = execFileSync('git', [ + '-C', + checkout, + 'diff-tree', + '--no-commit-id', + '--name-only', + '-r', + evidence.commit, + ]).toString(); + const staged = execFileSync('git', [ + '-C', + checkout, + 'diff', + '--cached', + '--name-only', + ]).toString(); + expect(committed.trim().split('\n')).toEqual(['finding.md']); + expect(staged.trim().split('\n')).toContain('.env'); + expect(evidence.reachable).toBe(true); + }); + it('commits with command-scoped identity, pushes immediately, and proves reachability from origin/main', async (): Promise => { const runtime = await loadRuntime('MB-REQ-07 publish-on-write reachability'); const root = tempRoot(); @@ -558,6 +618,19 @@ describe('push-on-write publication', (): void => { if (command.includes('rev-parse origin/main')) { return { status: 0, stdout: `${remoteHead}\n`, stderr: '' }; } + if (command.includes('diff --cached --quiet')) { + return { status: 1, stdout: '', stderr: '' }; + } + if (command.includes('diff-tree')) { + return { status: 0, stdout: '.gitignore\0lanes/lane-a/.gitkeep\0', stderr: '' }; + } + if (command.includes('show -s')) { + return { + status: 0, + stdout: 'seat-a\0seat-a@fleet.mosaicstack.dev\0seat-a\0seat-a@fleet.mosaicstack.dev\n', + stderr: '', + }; + } return { status: 0, stdout: '', stderr: '' }; }; @@ -632,6 +705,23 @@ describe('push-on-write publication', (): void => { if (command.includes('rev-parse origin/main')) { return { status: 0, stdout: `${remoteHead}\n`, stderr: '' }; } + if (command.includes('diff --cached --quiet')) { + return { status: 1, stdout: '', stderr: '' }; + } + if (command.includes('diff-tree')) { + return { + status: 0, + stdout: 'lanes/lane-a/findings/host-a.md\0', + stderr: '', + }; + } + if (command.includes('show -s')) { + return { + status: 0, + stdout: 'seat-a\0seat-a@fleet.mosaicstack.dev\0seat-a\0seat-a@fleet.mosaicstack.dev\n', + stderr: '', + }; + } return { status: 0, stdout: '', stderr: '' }; }, ); @@ -679,7 +769,7 @@ describe('push-on-write publication', (): void => { ); expect(evidence).toEqual({ commit, remoteHead: commit, reachable: true }); - expect(requests.some((request) => request.args.includes('push'))).toBe(true); + expect(requests.some((request) => request.args.includes('push'))).toBe(false); }); it('does not manufacture reachability when merge-base rejects the new commit', async (): Promise => { @@ -703,6 +793,19 @@ describe('push-on-write publication', (): void => { if (command.includes('rev-parse origin/main')) { return { status: 0, stdout: `${remoteHead}\n`, stderr: '' }; } + if (command.includes('diff --cached --quiet')) { + return { status: 1, stdout: '', stderr: '' }; + } + if (command.includes('diff-tree')) { + return { status: 0, stdout: '.gitignore\0', stderr: '' }; + } + if (command.includes('show -s')) { + return { + status: 0, + stdout: 'seat-a\0seat-a@fleet.mosaicstack.dev\0seat-a\0seat-a@fleet.mosaicstack.dev\n', + stderr: '', + }; + } if (command.includes('merge-base --is-ancestor')) { return { status: 1, stdout: '', stderr: '' }; } diff --git a/packages/mosaic/src/commands/brain-store-runtime.ts b/packages/mosaic/src/commands/brain-store-runtime.ts index 81036249..3f679521 100644 --- a/packages/mosaic/src/commands/brain-store-runtime.ts +++ b/packages/mosaic/src/commands/brain-store-runtime.ts @@ -1,6 +1,7 @@ -import { existsSync } from 'node:fs'; +import { existsSync, mkdtempSync, rmSync } from 'node:fs'; import { spawnSync } from 'node:child_process'; -import { isAbsolute, relative, resolve, sep } from 'node:path'; +import { tmpdir } from 'node:os'; +import { isAbsolute, join, relative, resolve, sep } from 'node:path'; import { assessCredentialResult, deriveBrainTarget, @@ -84,6 +85,15 @@ function runGit(run: CommandRunner, identity: string, args: readonly string[]): return run({ program: 'git', args, env: commandEnv(identity) }); } +function runGitWithEnv( + run: CommandRunner, + identity: string, + args: readonly string[], + env: Readonly>, +): CommandResult { + return run({ program: 'git', args, env: { ...commandEnv(identity), ...env } }); +} + export function collectBrainDoctorReport( input: { readonly registrySource: string; @@ -316,32 +326,6 @@ export function publishBrainPaths( if (input.message.trim().length === 0) throw new Error('brain-publish-message-empty'); const paths = input.paths.map((path: string): string => containedRelative(input.root, path)); - requireSuccess( - runGit(run, input.identity, ['-C', input.root, 'add', '--', ...paths]), - 'brain-git-add', - ); - const commitAttempt = runGit(run, input.identity, [ - '-C', - input.root, - '-c', - `user.name=${input.identity}`, - '-c', - `user.email=${input.identity}@fleet.mosaicstack.dev`, - 'commit', - '-m', - input.message, - ]); - if (commitAttempt.status !== 0) { - const stagedDifference = runGit(run, input.identity, [ - '-C', - input.root, - 'diff', - '--cached', - '--quiet', - '--exit-code', - ]); - if (stagedDifference.status !== 0) throw new Error('brain-git-commit-failed'); - } const readHead = (): string => { const result = runGit(run, input.identity, ['-C', input.root, 'rev-parse', 'HEAD']); requireSuccess(result, 'brain-git-read-commit'); @@ -349,9 +333,100 @@ export function publishBrainPaths( if (!COMMIT.test(value)) throw new Error('brain-git-commit-shape-invalid'); return value; }; - let commit = readHead(); - let pushed = false; - for (let attempt = 0; attempt < 3; attempt += 1) { + const verifyCommitIdentity = (commit: string): void => { + const result = runGit(run, input.identity, [ + '-C', + input.root, + 'show', + '-s', + '--format=%an%x00%ae%x00%cn%x00%ce', + commit, + ]); + requireSuccess(result, 'brain-git-read-commit-identity'); + const expectedEmail = `${input.identity}@fleet.mosaicstack.dev`; + const [author, authorEmail, committer, committerEmail] = result.stdout.trimEnd().split('\0'); + if ( + author !== input.identity || + authorEmail !== expectedEmail || + committer !== input.identity || + committerEmail !== expectedEmail + ) { + throw new Error('brain-git-commit-identity-mismatch'); + } + }; + const verifyCommitPaths = (commit: string): void => { + const changed = runGit(run, input.identity, [ + '-C', + input.root, + 'diff-tree', + '--root', + '--no-commit-id', + '--name-only', + '-r', + '-z', + commit, + ]); + requireSuccess(changed, 'brain-git-read-commit-paths'); + const names = changed.stdout.split('\0').filter(Boolean); + const approved = new Set(paths); + if (names.length === 0 || names.some((name: string): boolean => !approved.has(name))) { + throw new Error('brain-git-commit-paths-unapproved'); + } + }; + + const base = readHead(); + const indexRoot = mkdtempSync(join(tmpdir(), 'mosaic-brain-index-')); + const isolatedEnv = { GIT_INDEX_FILE: join(indexRoot, 'index') }; + let commit = base; + let createdCommit = false; + try { + requireSuccess( + runGitWithEnv(run, input.identity, ['-C', input.root, 'read-tree', base], isolatedEnv), + 'brain-git-isolated-index-init', + ); + requireSuccess( + runGitWithEnv(run, input.identity, ['-C', input.root, 'add', '--', ...paths], isolatedEnv), + 'brain-git-add', + ); + const difference = runGitWithEnv( + run, + input.identity, + ['-C', input.root, 'diff', '--cached', '--quiet', '--exit-code', base, '--', ...paths], + isolatedEnv, + ); + if (difference.status === 1) { + requireSuccess( + runGitWithEnv( + run, + input.identity, + [ + '-C', + input.root, + '-c', + `user.name=${input.identity}`, + '-c', + `user.email=${input.identity}@fleet.mosaicstack.dev`, + 'commit', + '-m', + input.message, + ], + isolatedEnv, + ), + 'brain-git-commit', + ); + commit = readHead(); + verifyCommitPaths(commit); + verifyCommitIdentity(commit); + createdCommit = true; + } else if (difference.status !== 0) { + throw new Error('brain-git-isolated-diff-failed'); + } + } finally { + rmSync(indexRoot, { recursive: true, force: true }); + } + + let pushed = !createdCommit; + for (let attempt = 0; createdCommit && attempt < 3; attempt += 1) { const push = runGit(run, input.identity, ['-C', input.root, 'push', 'origin', 'HEAD:main']); if (push.status === 0) { pushed = true; @@ -364,10 +439,21 @@ export function publishBrainPaths( 'brain-git-fetch-concurrent', ); requireSuccess( - runGit(run, input.identity, ['-C', input.root, 'rebase', 'origin/main']), + runGit(run, input.identity, [ + '-C', + input.root, + '-c', + `user.name=${input.identity}`, + '-c', + `user.email=${input.identity}@fleet.mosaicstack.dev`, + 'rebase', + 'origin/main', + ]), 'brain-git-rebase-concurrent', ); commit = readHead(); + verifyCommitPaths(commit); + verifyCommitIdentity(commit); } if (!pushed) throw new Error('brain-git-push-failed'); requireSuccess(