diff --git a/docs/scratchpads/1051-mosaic-brain-installer.md b/docs/scratchpads/1051-mosaic-brain-installer.md index 212434ce..91ce6e5a 100644 --- a/docs/scratchpads/1051-mosaic-brain-installer.md +++ b/docs/scratchpads/1051-mosaic-brain-installer.md @@ -83,14 +83,16 @@ 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: 88/88 (secure config 4, store 43, runtime 13, 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: 93/93 (secure config 4, store 43, runtime 18, 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] Security-critical owner policy/registry reads have direct controls for principal UID ownership, file/ancestor permissions, and descriptor-safe regular-file reads. - [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 uses an isolated temporary Git index populated from approved in-memory blobs rather than pathname re-reads, verifies each committed blob ID, the exact changed-path allowlist, and 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 destination-path substitution cannot change committed bytes and unrelated pre-staged secret-shaped content remains staged but never enters the published commit. +- [x] Multi-host push-on-write uses an isolated temporary Git index populated from approved in-memory blobs rather than pathname re-reads, verifies each committed blob ID, the exact changed-path allowlist, and both author/committer trailers before push, then reconciles only approved paths into the real checkout index. Real-repository controls prove a clean checkout remains clean, a concurrent non-fast-forward fetch/rebase/push remains clean and preserves both findings, destination-path substitution cannot change committed bytes, and unrelated pre-staged secret-shaped content remains staged but never enters the published commit. +- [x] Doctor Git observations preserve three states: `clean`, `dirty`, and `unmeasurable`; failed remote, branch, or status measurements emit hard `brain-git-state-indeterminate` findings rather than mismatch or ready. The boolean-literal guard sweep covered all MB-BRAIN production files in the 20-file PR population: its only remaining `=== false` guard is the non-nullable `isAbsolute()` predicate; no nullable boolean measurement guards remain. +- [x] Author-run Review 10 and focused 88/88 evidence were declared void when blocker fixes changed the head; neither is an independent gate pass. - [ ] 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-store-runtime.spec.ts b/packages/mosaic/src/commands/brain-store-runtime.spec.ts index 6c7a6c5e..2b658fb6 100644 --- a/packages/mosaic/src/commands/brain-store-runtime.spec.ts +++ b/packages/mosaic/src/commands/brain-store-runtime.spec.ts @@ -361,6 +361,72 @@ describe('doctor runtime observation', (): void => { } }); + it.each(['remote', 'branch', 'status'] as const)( + 'reports %s measurement failure as indeterminate rather than healthy or mismatched', + async (failedMeasurement): Promise => { + const runtime = await loadRuntime('MB-REQ-08 three-state git measurements'); + const root = join(tempRoot(), 'brain'); + mkdirSync(join(root, '.git'), { recursive: true }); + const runner: CommandRunner = (request): CommandResult => { + if (request.program === 'mosaic') { + return { + status: 0, + stdout: validateResult('ok', 'validation-verified').replaceAll( + 'synthetic-no-token', + 'seat-a', + ), + stderr: '', + }; + } + const command = request.args.join(' '); + if (command.includes('rev-parse --is-inside-work-tree')) { + return { status: 0, stdout: 'true\n', stderr: '' }; + } + if (command.includes('remote get-url origin')) { + return failedMeasurement === 'remote' + ? { status: 1, stdout: '', stderr: 'measurement failed' } + : { + status: 0, + stdout: 'https://git.mosaicstack.dev/mosaicstack/mosaic-brain.git\n', + stderr: '', + }; + } + if (command.includes('branch --show-current')) { + return failedMeasurement === 'branch' + ? { status: 1, stdout: '', stderr: 'measurement failed' } + : { status: 0, stdout: 'main\n', stderr: '' }; + } + if (command.includes('status --porcelain')) { + return failedMeasurement === 'status' + ? { status: 1, stdout: '', stderr: 'measurement failed' } + : { status: 0, stdout: '', stderr: '' }; + } + return { status: 99, stdout: '', stderr: 'unexpected command' }; + }; + + const report = runtime.collectBrainDoctorReport( + { + registrySource: registry(), + targetGitUrl: 'https://git.mosaicstack.dev/mosaicstack/stack.git', + brainNamespace: 'mosaicstack', + identity: 'seat-a', + root, + }, + runner, + ); + + expect(report.findings).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + code: 'brain-git-state-indeterminate', + reasonCode: `${failedMeasurement}-unmeasurable`, + }), + ]), + ); + expect(report.findings.some((finding) => finding.code.endsWith('-mismatch'))).toBe(false); + }, + ); + it('repairs write refusal through mosaic cred, revalidates, then clones and verifies the resulting object', async (): Promise => { const runtime = await loadRuntime('MB-REQ-08 broker-only doctor repair'); const root = join(tempRoot(), 'brain'); @@ -612,6 +678,105 @@ describe('push-on-write publication', (): void => { expect(evidence.reachable).toBe(true); }); + it('leaves a genuinely clean checkout clean after normal publication', async (): Promise => { + const runtime = await loadRuntime('MB-REQ-07 normal publication index reconciliation'); + 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']); + const finding = join(checkout, 'finding.md'); + writeFileSync(finding, 'approved finding\n'); + + runtime.publishBrainPaths( + { + root: checkout, + identity: 'seat-a', + entries: [{ path: finding, content: new TextEncoder().encode('approved finding\n') }], + message: 'append approved finding', + }, + runtime.systemCommandRunner, + ); + + expect(execFileSync('git', ['-C', checkout, 'status', '--porcelain']).toString()).toBe(''); + }); + + it('rebases a clean concurrent append-only push and leaves the checkout clean', async (): Promise => { + const runtime = await loadRuntime('MB-REQ-07 real concurrent publication reconciliation'); + const root = tempRoot(); + const remote = join(root, 'remote.git'); + const checkout = join(root, 'brain-a'); + const concurrent = join(root, 'brain-b'); + 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']); + execFileSync('git', ['clone', remote, concurrent]); + writeFileSync(join(concurrent, 'remote-finding.md'), 'concurrent finding\n'); + execFileSync('git', ['-C', concurrent, 'add', 'remote-finding.md']); + execFileSync('git', [ + '-C', + concurrent, + '-c', + 'user.name=other-seat', + '-c', + 'user.email=other-seat@fleet.mosaicstack.dev', + 'commit', + '-m', + 'append concurrent finding', + ]); + execFileSync('git', ['-C', concurrent, 'push', 'origin', 'main']); + const finding = join(checkout, 'local-finding.md'); + writeFileSync(finding, 'local finding\n'); + + const evidence = runtime.publishBrainPaths( + { + root: checkout, + identity: 'seat-a', + entries: [{ path: finding, content: new TextEncoder().encode('local finding\n') }], + message: 'append local finding', + }, + runtime.systemCommandRunner, + ); + + expect(evidence.reachable).toBe(true); + expect(execFileSync('git', ['-C', checkout, 'status', '--porcelain']).toString()).toBe(''); + expect(execFileSync('git', ['-C', checkout, 'show', 'HEAD:remote-finding.md']).toString()).toBe( + 'concurrent finding\n', + ); + expect(execFileSync('git', ['-C', checkout, 'show', 'HEAD:local-finding.md']).toString()).toBe( + 'local finding\n', + ); + }); + 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(); diff --git a/packages/mosaic/src/commands/brain-store-runtime.ts b/packages/mosaic/src/commands/brain-store-runtime.ts index f8113aa8..6bfa29b3 100644 --- a/packages/mosaic/src/commands/brain-store-runtime.ts +++ b/packages/mosaic/src/commands/brain-store-runtime.ts @@ -140,7 +140,7 @@ export function collectBrainDoctorReport( let gitRepository = false; let remote: string | null = null; let branch: string | null = null; - let dirty: boolean | null = null; + let worktreeState: BrainDoctorObservation['worktreeState'] = 'unmeasurable'; if (rootExists) { const repository = runGit(run, input.identity, [ '-C', @@ -166,7 +166,9 @@ export function collectBrainDoctorReport( const statusResult = runGit(run, input.identity, ['-C', input.root, 'status', '--porcelain']); if (remoteResult.status === 0) remote = remoteResult.stdout.trim(); if (branchResult.status === 0) branch = branchResult.stdout.trim(); - if (statusResult.status === 0) dirty = statusResult.stdout.trim().length > 0; + if (statusResult.status === 0) { + worktreeState = statusResult.stdout.trim().length > 0 ? 'dirty' : 'clean'; + } } } @@ -175,7 +177,7 @@ export function collectBrainDoctorReport( gitRepository, remote, branch, - dirty, + worktreeState, access, }; const refusalMarker = `refused reason=${access.reasonCode}`; @@ -385,6 +387,23 @@ export function publishBrainPaths( if (result.stdout.trim() !== expected) throw new Error('brain-git-commit-content-mismatch'); } }; + const reconcileRealIndex = (): void => { + for (const [path, objectId] of expectedObjects) { + requireSuccess( + runGit(run, input.identity, [ + '-C', + input.root, + 'update-index', + '--add', + '--cacheinfo', + '100644', + objectId, + path, + ]), + 'brain-git-reconcile-checkout-index', + ); + } + }; const verifyCommitPaths = (commit: string): void => { const changed = runGit(run, input.identity, [ '-C', @@ -476,6 +495,7 @@ export function publishBrainPaths( verifyCommitPaths(commit); verifyCommitObjects(commit); verifyCommitIdentity(commit); + reconcileRealIndex(); createdCommit = true; } else if (difference.status !== 0) { throw new Error('brain-git-isolated-diff-failed'); diff --git a/packages/mosaic/src/commands/brain-store.spec.ts b/packages/mosaic/src/commands/brain-store.spec.ts index dfaa1bba..2d8a81d3 100644 --- a/packages/mosaic/src/commands/brain-store.spec.ts +++ b/packages/mosaic/src/commands/brain-store.spec.ts @@ -93,7 +93,7 @@ interface BrainDoctorObservation { readonly gitRepository: boolean; readonly remote: string | null; readonly branch: string | null; - readonly dirty: boolean | null; + readonly worktreeState: 'clean' | 'dirty' | 'unmeasurable'; readonly access: CredentialAssessment | null; } @@ -991,7 +991,7 @@ describe('R8 — doctor diagnoses defects and fixes only through approved seams' gitRepository: false, remote: null, branch: null, - dirty: null, + worktreeState: 'unmeasurable', access: sut.assessCredentialResult(credentialResult('refused', 'no-token-for-identity')), }, expected, @@ -1006,7 +1006,7 @@ describe('R8 — doctor diagnoses defects and fixes only through approved seams' gitRepository: true, remote: 'https://git.uscllc.com/usc/mosaic-brain.git', branch: 'main', - dirty: true, + worktreeState: 'dirty', access: sut.assessCredentialResult( credentialResult('indeterminate', 'credential-rejected'), ), diff --git a/packages/mosaic/src/commands/brain-store.ts b/packages/mosaic/src/commands/brain-store.ts index a314d96e..77599016 100644 --- a/packages/mosaic/src/commands/brain-store.ts +++ b/packages/mosaic/src/commands/brain-store.ts @@ -178,7 +178,7 @@ export interface BrainDoctorObservation { readonly gitRepository: boolean; readonly remote: string | null; readonly branch: string | null; - readonly dirty: boolean | null; + readonly worktreeState: 'clean' | 'dirty' | 'unmeasurable'; readonly access: CredentialAssessment | null; } @@ -1079,13 +1079,31 @@ export function evaluateBrainDoctor( findings.push({ code: 'brain-not-git-repository', repairable: true, reasonCode: null }); return findings; } - if (observation.remote !== expectedRemote) { + if (observation.remote === null) { + findings.push({ + code: 'brain-git-state-indeterminate', + repairable: false, + reasonCode: 'remote-unmeasurable', + }); + } else if (observation.remote !== expectedRemote) { findings.push({ code: 'brain-remote-mismatch', repairable: true, reasonCode: null }); } - if (observation.branch !== 'main') { + if (observation.branch === null) { + findings.push({ + code: 'brain-git-state-indeterminate', + repairable: false, + reasonCode: 'branch-unmeasurable', + }); + } else if (observation.branch !== 'main') { findings.push({ code: 'brain-branch-mismatch', repairable: false, reasonCode: null }); } - if (observation.dirty === true) { + if (observation.worktreeState === 'unmeasurable') { + findings.push({ + code: 'brain-git-state-indeterminate', + repairable: false, + reasonCode: 'status-unmeasurable', + }); + } else if (observation.worktreeState === 'dirty') { findings.push({ code: 'brain-uncommitted-state', repairable: false, reasonCode: null }); } if (access !== null) findings.push(access);