feat(mosaic): provision per-estate durable brain

This commit is contained in:
2026-08-05 17:19:50 -05:00
parent c6329ec91e
commit 2451c2f21a
16 changed files with 4423 additions and 81 deletions
+3 -3
View File
@@ -104,18 +104,18 @@ In scope now: estate derivation, secret exclusion, non-destructive migration, do
### Acceptance criteria
1. `AC-MB-01`: Contract tests observe RED before implementation and then distinguish `ok/0`, `refused/10`, `error/20`, and `indeterminate/30`, preserving stable reason codes including `identity-not-found`, `credential-rejected`, and `provider-unavailable`.
1. `AC-MB-01`: Contract tests observe RED before implementation and then distinguish `ok/0`, `refused/10`, `error/20`, and `indeterminate/30`, preserving v1.5 diagnoses including refused `provider-identity-mismatch`/`credential-rejected` and indeterminate `identity-not-measured`/`provider-unavailable`. A scope-forbidden `/user` result with confirmed in-scope repository capability is never represented as a dead credential. `identity-not-found` remains reserved for a future visibility-authorized inventory operation and is not an expected `validate` result.
2. `AC-MB-02`: Estate resolution uses the configured target git host and one registry; unknown, mismatched, and host-machine-derived inputs fail closed.
3. `AC-MB-03`: A clean fixture contains the required layout and exact secret exclusions, and seeded secret-shaped files remain ignored without their values appearing in output.
4. `AC-MB-04`: Migration moves lane-durable and seat-state content into collision-safe archive/ledger paths, preserves source on any incomplete move, never overwrites an existing finding, never deletes by age/size, and reports unresolved items explicitly.
5. `AC-MB-05`: Doctor detects all four R8 defect classes; `--fix` repairs eligible classes through the approved P7/broker seam and leaves unresolved credential-dependent states visible.
6. `AC-MB-06`: Git-axis and API-axis refusal must both be authoritative `refused` outcomes with matching stable reason codes; any disagreement yields `indeterminate`.
7. `AC-MB-07`: Independent code review and security review pass at the exact head, and HOMELAB Woodpecker instance `mosaic` is terminal green before integration.
8. `AC-MB-08`: Integration into `next` is reported only as **believed-fixed, pending validation AND pending promotion to `main`**; issue #1051 remains open for #1037 promotion and W-jarvis validation.
8. `AC-MB-08`: After reviewed merge to `main`, report only **believed-fixed, pending jarvis validation**; issue #1051 remains open until W-jarvis validates the installed result.
### Constraints and risks
- MC-CRED-01 contract v1.3 is the caller boundary; no independent credential/token lookup is permitted.
- MC-CRED-01 contract v1.5 is the caller boundary; no independent credential/token lookup is permitted. Identity is established from governed mint-time binding and provider evidence when measurable, never a credential filename. Runtime validation does not widen a least-privilege token merely to make `/user` observable.
- C1 owns installer phase sequencing. This slice consumes P5/P7 ordering without renumbering or duplicating the phase machine.
- Lane content is findings, so last-writer-wins is data loss. Append-only names and explicit collision handling are mandatory.
- A created-but-empty brain beside unbacked local doctrine fails the objective; migration is a primary acceptance gate.
+26 -10
View File
@@ -10,9 +10,9 @@ Codify estate-derived, repository-backed `~/.mosaic` support with secret exclusi
- Provider issue: HOMELAB `git.mosaicstack.dev`, `GET /api/v1/repos/mosaicstack/stack/issues/1051`, `application/json;charset=utf-8`.
- Issue requirements: R1R8 read directly on 2026-08-05.
- MC-CRED caller contract: v1.3, SHA-256 `8cfa4853d2b0b0e8cc9e792fa8411310e16d7704c06e0af9d9a57155131d8086` at intake.
- MC-CRED caller contract: v1.5, SHA-256 `4cecba3386b37431d4a075205c6dfe43555c7673922fed61b84f43cac1a6ae92` at the 2026-08-05 re-derivation. Earlier moving bindings were v1.5 `710d22d61a93a4b9c70fc55506a023a675a110417fa7a6e72dc051c0d9fe8237`, v1.4 `27f20158561ae8292f3bfc926b5e97f398de93db6a1cf65fcc215d08811d39af`/`d12ad4595b7aef078e392988a07ab5cb00244440775c9c733dc825746d7ac67b`, and v1.3 `8cfa4853d2b0b0e8cc9e792fa8411310e16d7704c06e0af9d9a57155131d8086`.
- Fleet doctrine: SHA-256 `026b43322e0551ef15b646a9f30d3a6aef58c662a810b732be2a03b1ecf7d36e` at intake.
- Base: HOMELAB provider `next` = `4df478cdd150fdf8d52ea109f02ade5d85017acd`; `main` = `5916aeefd6ed12bcac086c6834c7f6c4ae38e1bc`; provider branch objects matched fetched refs and `main` is reachable from `next`.
- Intake base was HOMELAB provider `next` = `4df478cdd150fdf8d52ea109f02ade5d85017acd`; `main` = `5916aeefd6ed12bcac086c6834c7f6c4ae38e1bc`. On 2026-08-05 `mos-claude` ruled that L0 trunk-based gate 15 requires all three lanes to retarget to `main`; `next` remains a non-merging integration branch. Never weaken or patch `pr-merge.sh`.
## Scope
@@ -33,6 +33,15 @@ Codify estate-derived, repository-backed `~/.mosaic` support with secret exclusi
- No phase renumbering; C1 owns the phase machine and provides the P5→P7 seam.
- No age/size reaping or deletion.
## Owner authority ruling and resolver seam
- Binding addendum: `/home/hermes/agent-work/tl-mosaic/CHARTER-MB-BRAIN-01-ADDENDUM.md`; re-read after compaction.
- HOMELAB durable lane-archive owner and user-namespace brain owner are the human provider account selected by local estate policy (operator ruling: `jason.woltje`) with a required GLPI queue as the standing remediation process. The brain target is therefore `<policy-owner>/mosaic-brain` on the estate host, not `<installer-source-org>/mosaic-brain`. Framework source remains operator-agnostic: the actual login and queue are local policy, not hardcoded open-source context.
- Provider lookup is anonymous because the ruled owner is public. It requires exact allowlisted login plus a same-invocation public known-good control, private 404 control, and generated absent 404 control. It sends no Authorization header and never widens token scope.
- Provider `active` is deliberately ignored: non-admin reads return false for demonstrably active accounts. Resolvability + exact login + public visibility are the gate.
- Private and absent principals both return anonymous 404. The fail-closed reason is `owner-not-resolvable`, never owner-not-found.
- Caller `owner` strings and `validated=true` are ignored. Migration consumes only an injected source-of-truth resolver result. Owner grammar is NFKC-stable, ASCII allowlisted, exact-policy matched, and mission-seat class is excluded.
## Plan
1. Pre-register acceptance tests and observe each requirement RED for its own missing behavior.
@@ -42,7 +51,7 @@ Codify estate-derived, repository-backed `~/.mosaic` support with secret exclusi
5. Integrate the helper into C1's P7 seam and `mosaic doctor` after C1 lands/rebase.
6. Run focused, package, installer, lint, typecheck, format, and situational security tests.
7. Run independent code and security reviews in parallel; remediate and re-review.
8. Push after HOMELAB queue guard, open PR to `next`, and wait for merge order C1 → MC-CRED → MB-BRAIN.
8. Push after HOMELAB queue guard, open the reviewed PR to `main`, and preserve merge order C1 → MC-CRED → MB-BRAIN. Do not modify the merge guard; `next` is non-merging integration only.
9. Re-take CI measurement at the rebased exact head; do not rework code solely because base evidence moved.
## Acceptance interpretation registered before results
@@ -51,7 +60,7 @@ Codify estate-derived, repository-backed `~/.mosaic` support with secret exclusi
- `refused/10`: complete authoritative denial only.
- `error/20`: local contract/control failure; never reinterpret as denial.
- `indeterminate/30`: incomplete/disagreeing evidence; fail closed, never resolve permissively.
- Both Git and API axes must return authoritative `refused` with the same stable reason code for R5. Any axis disagreement is `indeterminate`.
- Both Git and API axes must return authoritative `refused` with the same stable reason code for R5. Any axis disagreement is `indeterminate`. A provider `/user` login mismatch is first-class `provider-identity-mismatch`; credential filenames never establish principal identity.
- Migration success requires the durable object to contain the moved item and no overwrite; incomplete moves retain the source and are reported.
- Secret exclusion is tested through both exact ignore rules and seeded secret-shaped controls; output is scanned without printing secret values.
@@ -61,7 +70,7 @@ No explicit token ceiling was supplied. Working cap: 55K tokens for implementati
## Risks
- C1 and MC-CRED branches have not merged into `next`; integration edits must wait for their exact interfaces or be confined to stable contract seams.
- C1 and MC-CRED branches have not merged into `main`; integration edits must wait for their exact interfaces or be confined to stable contract seams.
- A broker runtime test before MC-CRED lands would either fail for an irrelevant reason or pressure a hand-rolled workaround; contract fixtures are allowed, live capability claims are not.
- Migration can lose data through overwrite, cross-device move failure, or partial copy. Implementation must stage, verify resulting bytes, and retain/report source on incomplete transfer.
- `~/.mosaic` is a git repo, while current working state may live under multiple local roots; detection must be explicit and cannot treat age/size as ownership.
@@ -70,15 +79,22 @@ No explicit token ceiling was supplied. Working cap: 55K tokens for implementati
- [x] Charter receipt accepted by `tl-mosaic`.
- [x] Issue #1051 R1R8 read directly from provider.
- [x] Contract re-derived at v1.3.
- [x] Contract re-derived through v1.3, moving v1.4, and v1.5 before R5 integration. v1.5 separates in-scope repository capability from `/user` identity measurement: 401 is `credential-rejected`/refused, 403/404 may become `identity-not-measured` only after in-scope capability succeeds, and 200 login mismatch is refused. `identity-not-found` is not reachable from `validate`.
- [x] C1 P5→P7 seam receipt read; no brain implementation is in C1.
- [ ] RED acceptance set committed.
- [ ] Implementation green.
- [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: 76/76 (store 38, runtime 12, owner resolver 14, 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 now reports secret-shaped files without copying them; symlinked `.gitignore`, layout directories, and nested migration destinations fail closed.
- [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.
- [ ] 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.
- [ ] Independent security review.
- [ ] HOMELAB CI terminal green at exact head.
- [ ] Integrated to `next` after C1 and MC-CRED.
- [ ] Reviewed PR retargeted to `main` after C1 and MC-CRED; `next` remains non-merging integration only.
## Completion language
Only: **believed-fixed, pending validation AND pending promotion to `main`**. Issue #1051 remains open; #1037 is the promotion vehicle and W-jarvis is the external validator.
After reviewed merge to `main`, only: **believed-fixed, pending jarvis validation**. Issue #1051 remains open until W-jarvis validates the installed result.
+5
View File
@@ -23,6 +23,7 @@ import { registerSkillCommand } from './commands/skill.js';
import { registerLaunchCommands } from './commands/launch.js';
import { registerLeaseCapabilityProbe } from './commands/lease-activation-probe.js';
import { registerInstallOrderingGuardCommand } from './commands/install-ordering-guard.js';
import { registerBrainProvisionCommand } from './commands/brain-provision-command.js';
import { registerAuthCommand } from './commands/auth.js';
import { registerFederationCommand } from './commands/federation.js';
import { registerGatewayCommand } from './commands/gateway.js';
@@ -85,6 +86,10 @@ registerLeaseCapabilityProbe(program);
registerInstallOrderingGuardCommand(program);
// ─── durable brain P7 provisioner (hidden; #1051) ───────────────────────
registerBrainProvisionCommand(program);
// ─── login ──────────────────────────────────────────────────────────────
program
@@ -0,0 +1,272 @@
import { afterEach, describe, expect, it } from 'vitest';
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
interface CommandRequest {
readonly program: 'git' | 'mosaic';
readonly args: readonly string[];
readonly env: Readonly<Record<string, string>>;
}
interface CommandResult {
readonly status: number;
readonly stdout: string;
readonly stderr: string;
}
interface InstalledDoctorResult {
readonly status: 'ok' | 'warn' | 'error';
readonly findings: readonly {
readonly code: string;
readonly reasonCode: string | null;
}[];
readonly lines: readonly string[];
}
interface BrainDoctorModule {
runInstalledBrainDoctorCheck(
options: {
readonly mosaicHome: string;
readonly home: string;
readonly identity?: string;
readonly fix: boolean;
},
run: (request: CommandRequest) => CommandResult,
): InstalledDoctorResult;
}
const MODULE_PATH = './brain-doctor-check.js';
const roots: string[] = [];
async function loadDoctor(requirement: string): Promise<BrainDoctorModule> {
try {
return (await import(MODULE_PATH)) as BrainDoctorModule;
} catch (error: unknown) {
const detail = error instanceof Error ? error.message : String(error);
throw new Error(`${requirement}: installed brain doctor check is absent (${detail})`);
}
}
function tempRoot(): string {
const root = mkdtempSync(join(tmpdir(), 'mosaic-brain-doctor-'));
roots.push(root);
return root;
}
function installConfig(root: string): { readonly home: string; readonly mosaicHome: string } {
const home = join(root, 'home');
const mosaicHome = join(home, '.config', 'mosaic');
mkdirSync(join(mosaicHome, 'cred'), { recursive: true });
mkdirSync(join(mosaicHome, 'brain'), { recursive: true });
writeFileSync(
join(mosaicHome, 'cred', 'estates.json'),
JSON.stringify({
version: 1,
estates: [
{
name: 'homelab',
readOnlyControlIdentity: 'read-control',
hosts: [
{
host: 'git.example.invalid',
provider: 'gitea',
apiBaseUrl: 'https://git.example.invalid',
tokenPrefix: 'gitea-example',
},
],
},
],
}),
{ mode: 0o600 },
);
writeFileSync(
join(mosaicHome, 'brain', 'owners.json'),
JSON.stringify({
version: 1,
estates: [
{
estate: 'homelab',
laneArchiveOwners: [{ kind: 'provider-user', login: 'durable-owner' }],
standingProcess: { kind: 'glpi-queue', queue: 'mosaic-brain-remediation' },
controls: { publicIdentity: 'public-control', privateIdentity: 'private-control' },
},
],
}),
{ mode: 0o600 },
);
writeFileSync(
join(mosaicHome, '.install-manifest.json'),
JSON.stringify({
version: 2,
status: 'committed',
sourceRepo: 'https://git.example.invalid/example/stack.git',
}),
{ mode: 0o600 },
);
return { home, mosaicHome };
}
function validateResult(outcome: 'ok' | 'refused' | 'indeterminate', reasonCode: string): string {
const exitCode = outcome === 'ok' ? 0 : outcome === 'refused' ? 10 : 30;
return JSON.stringify({
schemaVersion: 1,
operation: 'validate',
outcome,
exitCode,
retryable: false,
subject: {
identity: 'seat-a',
estate: 'homelab',
host: 'git.example.invalid',
repo: 'durable-owner/mosaic-brain',
},
mutation: 'none',
reason: { code: reasonCode, message: 'non-secret' },
evidence: {
providerIdentity:
outcome === 'ok'
? {
login: 'seat-a',
endpoint: 'GET /api/v1/user',
contentType: 'application/json',
}
: null,
repositoryPermission:
outcome === 'ok'
? {
requested: 'write',
effective: 'write',
endpoint: 'GET /api/v1/repos/durable-owner/mosaic-brain',
contentType: 'application/json',
}
: null,
writeDifferential:
outcome === 'ok'
? {
state: 'can-write',
credentialBinding: 'same-resolution',
transportPrincipal: 'seat-a',
authenticatedReceivePack: 'advertised',
readOnlyControl: {
identity: 'read-control',
providerPermission: 'read',
receivePack: 'refused',
},
unauthenticatedReceivePack: 'refused',
artifactCreated: false,
proves: 'non-secret evidence',
doesNotProve: 'branch update acceptance',
}
: null,
},
audit: { journalId: 'opaque', state: 'sealed' },
});
}
afterEach((): void => {
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
});
describe('installed mosaic doctor brain checks', (): void => {
it('derives the target from the committed install manifest and surfaces a missing clone plus refusal', async (): Promise<void> => {
const doctor = await loadDoctor('MB-REQ-08 installed doctor missing clone');
const config = installConfig(tempRoot());
const requests: CommandRequest[] = [];
const result = doctor.runInstalledBrainDoctorCheck(
{ ...config, identity: 'seat-a', fix: false },
(request): CommandResult => {
requests.push(request);
return {
status: 10,
stdout: validateResult('refused', 'no-token-for-identity'),
stderr: 'refused reason=no-token-for-identity',
};
},
);
expect(result.status).toBe('warn');
expect(result.findings.map((finding) => finding.code)).toEqual(
expect.arrayContaining(['brain-clone-missing', 'brain-write-access-refused']),
);
expect(result.lines.join('\n')).toMatch(/brain-clone-missing/);
expect(result.lines.join('\n')).toMatch(/no-token-for-identity/);
expect(requests[0]?.args).toContain('durable-owner/mosaic-brain');
});
it('fails closed without an explicit identity and performs no command', async (): Promise<void> => {
const doctor = await loadDoctor('MB-REQ-08 explicit identity');
const config = installConfig(tempRoot());
let calls = 0;
const result = doctor.runInstalledBrainDoctorCheck(
{ ...config, fix: false },
(): CommandResult => {
calls += 1;
return { status: 0, stdout: '', stderr: '' };
},
);
expect(result).toMatchObject({
status: 'error',
findings: [{ code: 'brain-identity-required', reasonCode: 'identity-required' }],
});
expect(calls).toBe(0);
});
it('treats identity-not-measured as an error, not no-write refusal and not a repairable grant case', async (): Promise<void> => {
const doctor = await loadDoctor('MB-REQ-08 identity measurement axis');
const config = installConfig(tempRoot());
const requests: CommandRequest[] = [];
const result = doctor.runInstalledBrainDoctorCheck(
{ ...config, identity: 'seat-a', fix: true },
(request): CommandResult => {
requests.push(request);
return {
status: 30,
stdout: validateResult('indeterminate', 'identity-not-measured'),
stderr: 'identity not measured',
};
},
);
expect(result.status).toBe('error');
expect(result.findings).toEqual(
expect.arrayContaining([
expect.objectContaining({
code: 'brain-write-access-indeterminate',
reasonCode: 'identity-not-measured',
}),
]),
);
expect(requests.some((request) => request.args.includes('grant'))).toBe(false);
});
it('is wired into the top-level mosaic doctor path before the shell audit runs', (): void => {
const launch = readFileSync(join(process.cwd(), 'src', 'commands', 'launch.ts'), 'utf8');
expect(launch).toContain('runInstalledBrainDoctorCheck');
expect(launch).toContain('defaultInstalledBrainDoctorOptions');
expect(launch).toContain('systemCommandRunner');
expect(launch).toMatch(/brainCheckFailed[\s\S]*runDoctorScriptAndExit/);
});
it('reports a missing or unsafe registry/manifest as configuration error rather than defaulting estate', async (): Promise<void> => {
const doctor = await loadDoctor('MB-REQ-02 missing mapping fail-closed');
const root = tempRoot();
const home = join(root, 'home');
const mosaicHome = join(home, '.config', 'mosaic');
mkdirSync(mosaicHome, { recursive: true });
const result = doctor.runInstalledBrainDoctorCheck(
{ home, mosaicHome, identity: 'seat-a', fix: false },
(): CommandResult => ({ status: 0, stdout: '', stderr: '' }),
);
expect(result.status).toBe('error');
expect(result.findings[0]?.code).toMatch(/brain-(estate-registry|install-manifest)-/);
expect(result.lines.join('\n')).not.toMatch(/homelab|usc/);
});
});
@@ -0,0 +1,161 @@
import { homedir } from 'node:os';
import { join } from 'node:path';
import { z } from 'zod';
import { readRegularFileSecure } from '../fleet/secure-file.js';
import { resolveBrainOwnerPolicy } from './brain-owner-resolver.js';
import { deriveBrainTarget } from './brain-store.js';
import {
collectBrainDoctorReport,
repairBrainDoctor,
type CommandRunner,
type DoctorRuntimeReport,
} from './brain-store-runtime.js';
const MAX_CONFIG_BYTES = 256 * 1024;
const IDENTITY = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
const manifestSchema = z
.object({
version: z.literal(2),
status: z.literal('committed'),
sourceRepo: z.string().min(1),
})
.passthrough();
export interface InstalledDoctorFinding {
readonly code: string;
readonly reasonCode: string | null;
}
export interface InstalledDoctorResult {
readonly status: 'ok' | 'warn' | 'error';
readonly findings: readonly InstalledDoctorFinding[];
readonly lines: readonly string[];
}
function configurationError(code: string, reasonCode = code): InstalledDoctorResult {
return {
status: 'error',
findings: [{ code, reasonCode }],
lines: [`[mosaic-doctor] [ERROR] ${code}`],
};
}
function readUtf8(path: string, root: string): string {
const snapshot = readRegularFileSecure(path, { root, maxBytes: MAX_CONFIG_BYTES });
try {
return new TextDecoder('utf-8', { fatal: true }).decode(snapshot.content);
} catch {
throw new Error('config-not-utf8');
}
}
function renderReport(report: DoctorRuntimeReport): InstalledDoctorResult {
const findings = report.findings.map(
(finding): InstalledDoctorFinding => ({
code: finding.code,
reasonCode: finding.reasonCode,
}),
);
const hard = findings.some(
(finding): boolean =>
finding.code.endsWith('-error') ||
finding.code.endsWith('-indeterminate') ||
finding.code === 'brain-not-git-repository',
);
const status: InstalledDoctorResult['status'] =
findings.length === 0 ? 'ok' : hard ? 'error' : 'warn';
const severity = status === 'error' ? 'ERROR' : status === 'warn' ? 'WARN' : 'OK';
const lines =
findings.length === 0
? ['[mosaic-doctor] [OK] mosaic-brain ready']
: findings.map(
(finding): string =>
`[mosaic-doctor] [${severity}] ${finding.code}${
finding.reasonCode === null ? '' : ` reason=${finding.reasonCode}`
}`,
);
return { status, findings, lines };
}
export function runInstalledBrainDoctorCheck(
options: {
readonly mosaicHome: string;
readonly home: string;
readonly identity?: string;
readonly fix: boolean;
},
run: CommandRunner,
): InstalledDoctorResult {
if (options.identity === undefined || !IDENTITY.test(options.identity)) {
return configurationError('brain-identity-required', 'identity-required');
}
const registryPath = join(options.mosaicHome, 'cred', 'estates.json');
const manifestPath = join(options.mosaicHome, '.install-manifest.json');
const ownerPolicyPath = join(options.mosaicHome, 'brain', 'owners.json');
let registrySource: string;
try {
registrySource = readUtf8(registryPath, options.mosaicHome);
} catch {
return configurationError('brain-estate-registry-unavailable');
}
let manifestSource: string;
try {
manifestSource = readUtf8(manifestPath, options.mosaicHome);
} catch {
return configurationError('brain-install-manifest-unavailable');
}
let manifestRaw: unknown;
try {
manifestRaw = JSON.parse(manifestSource);
} catch {
return configurationError('brain-install-manifest-invalid');
}
const manifest = manifestSchema.safeParse(manifestRaw);
if (!manifest.success) return configurationError('brain-install-manifest-invalid');
let ownerPolicySource: string;
try {
ownerPolicySource = readUtf8(ownerPolicyPath, options.mosaicHome);
} catch {
return configurationError('brain-owner-policy-unavailable');
}
let preliminaryTarget: ReturnType<typeof deriveBrainTarget>;
try {
preliminaryTarget = deriveBrainTarget(registrySource, manifest.data.sourceRepo, 'policy-probe');
} catch {
return configurationError('brain-estate-registry-invalid');
}
const ownerPolicy = resolveBrainOwnerPolicy(ownerPolicySource, preliminaryTarget.estate);
if (ownerPolicy === undefined) return configurationError('brain-owner-policy-invalid');
const input = {
registrySource,
targetGitUrl: manifest.data.sourceRepo,
brainNamespace: ownerPolicy.brainNamespace,
identity: options.identity,
root: join(options.home, '.mosaic'),
};
try {
return renderReport(
options.fix ? repairBrainDoctor(input, run) : collectBrainDoctorReport(input, run),
);
} catch {
return configurationError('brain-estate-registry-invalid');
}
}
export function defaultInstalledBrainDoctorOptions(fix: boolean): {
readonly mosaicHome: string;
readonly home: string;
readonly identity?: string;
readonly fix: boolean;
} {
const home = homedir();
const identity = process.env['MOSAIC_GIT_IDENTITY'];
return {
mosaicHome: process.env['MOSAIC_HOME'] ?? join(home, '.config', 'mosaic'),
home,
...(identity === undefined ? {} : { identity }),
fix,
};
}
@@ -0,0 +1,304 @@
import { describe, expect, it } from 'vitest';
/**
* Red-first owner-authority resolver contract for #1051.
*
* Fixtures are operator-agnostic. The HOMELAB owner name belongs in the local
* estate policy, never in framework source. Anonymous lookup is intentional:
* the ruled owner class is PUBLIC and least-privilege seats may lack read:user.
*/
interface MigrationOwnerResolution {
readonly verdict: 'resolved' | 'refused' | 'not-measured';
readonly reasonCode: string;
readonly principal: {
readonly name: string;
readonly kind: 'durable-human';
} | null;
readonly authority: {
readonly system: 'gitea';
readonly endpoint: string;
readonly contentType: 'application/json';
} | null;
}
type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
interface OwnerResolverModule {
resolveProviderDurableOwner(
input: {
readonly estateRegistrySource: string;
readonly ownerPolicySource: string;
readonly host: string;
readonly requestedOwner: string;
},
dependencies: {
readonly fetch: FetchLike;
readonly absentControlName: () => string;
},
): Promise<MigrationOwnerResolution>;
}
const MODULE_PATH = './brain-owner-resolver.js';
async function loadResolver(requirement: string): Promise<OwnerResolverModule> {
try {
return (await import(MODULE_PATH)) as OwnerResolverModule;
} catch (error: unknown) {
const detail = error instanceof Error ? error.message : String(error);
throw new Error(`${requirement}: brain owner resolver is absent (${detail})`);
}
}
function estateRegistry(): string {
return JSON.stringify({
version: 1,
estates: [
{
name: 'homelab',
readOnlyControlIdentity: 'read-control',
hosts: [
{
host: 'git.example.invalid',
provider: 'gitea',
apiBaseUrl: 'https://git.example.invalid',
tokenPrefix: 'gitea-example',
},
],
},
],
});
}
function ownerPolicy(): string {
return JSON.stringify({
version: 1,
estates: [
{
estate: 'homelab',
laneArchiveOwners: [{ kind: 'provider-user', login: 'durable-owner' }],
standingProcess: { kind: 'glpi-queue', queue: 'mosaic-brain-remediation' },
controls: {
publicIdentity: 'public-control',
privateIdentity: 'private-control',
},
},
],
});
}
function jsonResponse(status: number, body: unknown): Response {
return new Response(JSON.stringify(body), {
status,
headers: { 'content-type': 'application/json; charset=utf-8' },
});
}
function publicUser(login: string, active = false): Response {
return jsonResponse(200, {
id: 42,
login,
visibility: 'public',
active,
});
}
function identityFromUrl(input: string | URL | Request): string {
const value = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
return decodeURIComponent(new URL(value).pathname.split('/').at(-1) ?? '');
}
function controlledFetch(
overrides: Readonly<Record<string, Response>> = {},
calls: Array<{ identity: string; authorization: string | null }> = [],
): FetchLike {
return async (input: string | URL | Request, init?: RequestInit): Promise<Response> => {
const identity = identityFromUrl(input);
const headers = new Headers(init?.headers);
calls.push({ identity, authorization: headers.get('authorization') });
const override = overrides[identity];
if (override !== undefined) return override.clone();
if (identity === 'public-control') return publicUser('public-control');
if (identity === 'private-control' || identity === 'generated-absent-control') {
return jsonResponse(404, { message: 'not found' });
}
if (identity === 'durable-owner') return publicUser('durable-owner', false);
return jsonResponse(404, { message: 'not found' });
};
}
describe('provider-backed durable owner resolver', (): void => {
it('resolves an allowlisted PUBLIC owner by exact login with public/private/absent controls and ignores active=false', async (): Promise<void> => {
const resolver = await loadResolver('MB-REQ-09 provider owner resolution');
const calls: Array<{ identity: string; authorization: string | null }> = [];
const result = await resolver.resolveProviderDurableOwner(
{
estateRegistrySource: estateRegistry(),
ownerPolicySource: ownerPolicy(),
host: 'git.example.invalid',
requestedOwner: 'user:durable-owner',
},
{
fetch: controlledFetch({}, calls),
absentControlName: (): string => 'generated-absent-control',
},
);
expect(result).toEqual({
verdict: 'resolved',
reasonCode: 'owner-verified',
principal: { name: 'user:durable-owner', kind: 'durable-human' },
authority: {
system: 'gitea',
endpoint: 'GET /api/v1/users/durable-owner',
contentType: 'application/json',
},
});
expect(calls.map((call) => call.identity)).toEqual([
'public-control',
'private-control',
'generated-absent-control',
'durable-owner',
]);
expect(calls.every((call) => call.authorization === null)).toBe(true);
});
it('requires the GLPI standing remediation queue in the local estate policy', async (): Promise<void> => {
const resolver = await loadResolver('MB-REQ-09 standing process policy');
const raw = JSON.parse(ownerPolicy()) as { estates: Array<Record<string, unknown>> };
delete raw.estates[0]?.['standingProcess'];
let fetchCalls = 0;
const result = await resolver.resolveProviderDurableOwner(
{
estateRegistrySource: estateRegistry(),
ownerPolicySource: JSON.stringify(raw),
host: 'git.example.invalid',
requestedOwner: 'user:durable-owner',
},
{
fetch: async (): Promise<Response> => {
fetchCalls += 1;
return publicUser('durable-owner');
},
absentControlName: (): string => 'generated-absent-control',
},
);
expect(result).toMatchObject({ verdict: 'refused', reasonCode: 'owner-policy-invalid' });
expect(fetchCalls).toBe(0);
});
it('rejects a provider-valid but unlisted principal before provider lookup', async (): Promise<void> => {
const resolver = await loadResolver('MB-REQ-09 provider-valid unlisted owner refusal');
const calls: Array<{ identity: string; authorization: string | null }> = [];
const result = await resolver.resolveProviderDurableOwner(
{
estateRegistrySource: estateRegistry(),
ownerPolicySource: ownerPolicy(),
host: 'git.example.invalid',
requestedOwner: 'user:other-public-user',
},
{
fetch: controlledFetch({ 'other-public-user': publicUser('other-public-user') }, calls),
absentControlName: (): string => 'generated-absent-control',
},
);
expect(result).toMatchObject({ verdict: 'refused', reasonCode: 'owner-not-allowlisted' });
expect(calls).toHaveLength(0);
});
it.each([
['user:durableowner', 'owner-name-invalid'],
[' user:durable-owner ', 'owner-name-invalid'],
['user:durable.owner', 'owner-not-allowlisted'],
['user:durable owner', 'owner-name-invalid'],
['user:urable-owner', 'owner-name-invalid'],
['user:be-coder-07@mission-seat', 'owner-name-invalid'],
] as const)(
'rejects non-canonical, unlisted, or transient-seat presentation %s before lookup',
async (name, reasonCode): Promise<void> => {
const resolver = await loadResolver('MB-REQ-09 owner allowlist grammar');
let fetchCalls = 0;
const result = await resolver.resolveProviderDurableOwner(
{
estateRegistrySource: estateRegistry(),
ownerPolicySource: ownerPolicy(),
host: 'git.example.invalid',
requestedOwner: name,
},
{
fetch: async (): Promise<Response> => {
fetchCalls += 1;
return publicUser('durable-owner');
},
absentControlName: (): string => 'generated-absent-control',
},
);
expect(result).toMatchObject({ verdict: 'refused', reasonCode });
expect(fetchCalls).toBe(0);
},
);
it('fails closed as not-resolvable rather than claiming a private-or-absent owner does not exist', async (): Promise<void> => {
const resolver = await loadResolver('MB-REQ-09 private/absent ambiguity');
const result = await resolver.resolveProviderDurableOwner(
{
estateRegistrySource: estateRegistry(),
ownerPolicySource: ownerPolicy(),
host: 'git.example.invalid',
requestedOwner: 'user:durable-owner',
},
{
fetch: controlledFetch({ 'durable-owner': jsonResponse(404, { message: 'hidden' }) }),
absentControlName: (): string => 'generated-absent-control',
},
);
expect(result).toMatchObject({
verdict: 'not-measured',
reasonCode: 'owner-not-resolvable',
principal: null,
});
expect(JSON.stringify(result)).not.toMatch(/owner-not-found|does-not-exist/);
});
it.each([
['public control hidden', { 'public-control': jsonResponse(404, {}) }],
['public control login mismatch', { 'public-control': publicUser('other') }],
['private control unexpectedly public', { 'private-control': publicUser('private-control') }],
[
'generated absent control unexpectedly resolves',
{ 'generated-absent-control': publicUser('generated-absent-control') },
],
] as const)(
'makes the whole result not-measured when %s',
async (_caseName, overrides): Promise<void> => {
const resolver = await loadResolver('MB-REQ-09 owner resolver controls');
const result = await resolver.resolveProviderDurableOwner(
{
estateRegistrySource: estateRegistry(),
ownerPolicySource: ownerPolicy(),
host: 'git.example.invalid',
requestedOwner: 'user:durable-owner',
},
{
fetch: controlledFetch(overrides),
absentControlName: (): string => 'generated-absent-control',
},
);
expect(result).toMatchObject({
verdict: 'not-measured',
reasonCode: 'owner-control-invalid',
});
},
);
});
@@ -0,0 +1,255 @@
import { z } from 'zod';
import { parseCredentialEstateRegistry } from '../credentials/estate-registry.js';
import type { MigrationOwnerResolution } from './brain-store.js';
const MAX_BODY_BYTES = 256 * 1024;
const LOGIN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*$/;
const REQUESTED_OWNER = /^user:(.+)$/;
const ownerPolicySchema = z
.object({
version: z.literal(1),
estates: z
.array(
z
.object({
estate: z.string().min(1),
laneArchiveOwners: z
.array(
z
.object({
kind: z.literal('provider-user'),
login: z.string().min(1),
})
.strict(),
)
.min(1),
standingProcess: z
.object({
kind: z.literal('glpi-queue'),
queue: z.string().regex(/^[a-z0-9][a-z0-9-]*$/),
})
.strict(),
controls: z
.object({
publicIdentity: z.string().min(1),
privateIdentity: z.string().min(1),
})
.strict(),
})
.strict(),
)
.min(1),
})
.strict();
const providerUserSchema = z
.object({
id: z.number().int(),
login: z.string().min(1),
visibility: z.literal('public'),
})
.passthrough();
export type OwnerFetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
function unresolved(reasonCode: string): MigrationOwnerResolution {
return {
verdict: 'not-measured',
reasonCode,
principal: null,
authority: null,
};
}
function refused(reasonCode: string): MigrationOwnerResolution {
return {
verdict: 'refused',
reasonCode,
principal: null,
authority: null,
};
}
function exactCanonicalLogin(value: string): boolean {
return value.normalize('NFKC') === value && LOGIN.test(value);
}
async function boundedJson(response: Response): Promise<unknown> {
const contentType = response.headers.get('content-type') ?? '';
if (!contentType.toLowerCase().startsWith('application/json')) {
throw new Error('owner-unexpected-content-type');
}
const declared = response.headers.get('content-length');
if (declared !== null) {
const size = Number.parseInt(declared, 10);
if (Number.isFinite(size) && size > MAX_BODY_BYTES) {
throw new Error('owner-unexpected-provider-shape');
}
}
const body = new Uint8Array(await response.arrayBuffer());
if (body.byteLength > MAX_BODY_BYTES) throw new Error('owner-unexpected-provider-shape');
try {
return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(body));
} catch {
throw new Error('owner-unexpected-provider-shape');
}
}
async function readPublicIdentity(
origin: string,
identity: string,
fetchImpl: OwnerFetch,
): Promise<{ readonly status: number; readonly user: unknown }> {
let response: Response;
try {
response = await fetchImpl(`${origin}/api/v1/users/${encodeURIComponent(identity)}`, {
method: 'GET',
headers: {
Accept: 'application/json',
'User-Agent': 'mosaic-brain-owner/1',
},
});
} catch {
throw new Error('owner-provider-unavailable');
}
return { status: response.status, user: await boundedJson(response) };
}
function publicIdentityMatches(value: unknown, identity: string): boolean {
const parsed = providerUserSchema.safeParse(value);
return parsed.success && parsed.data.login === identity;
}
export interface BrainOwnerPolicyBinding {
readonly brainNamespace: string;
readonly publicControl: string;
readonly privateControl: string;
readonly standingQueue: string;
}
export function resolveBrainOwnerPolicy(
ownerPolicySource: string,
estate: string,
): BrainOwnerPolicyBinding | undefined {
let rawPolicy: unknown;
try {
rawPolicy = JSON.parse(ownerPolicySource);
} catch {
return undefined;
}
const policy = ownerPolicySchema.safeParse(rawPolicy);
if (!policy.success) return undefined;
const estatePolicies = policy.data.estates.filter(
(candidate): boolean => candidate.estate === estate,
);
if (estatePolicies.length !== 1) return undefined;
const estatePolicy = estatePolicies[0];
if (estatePolicy === undefined || estatePolicy.laneArchiveOwners.length !== 1) return undefined;
const brainNamespace = estatePolicy.laneArchiveOwners[0]?.login;
if (brainNamespace === undefined || !exactCanonicalLogin(brainNamespace)) return undefined;
return {
brainNamespace,
publicControl: estatePolicy.controls.publicIdentity,
privateControl: estatePolicy.controls.privateIdentity,
standingQueue: estatePolicy.standingProcess.queue,
};
}
export function parseRequestedOwner(requestedOwner: string): string | null {
if (requestedOwner.normalize('NFKC') !== requestedOwner) return null;
const match = REQUESTED_OWNER.exec(requestedOwner);
const login = match?.[1];
if (login === undefined || !exactCanonicalLogin(login)) return null;
return login;
}
export async function resolveProviderDurableOwner(
input: {
readonly estateRegistrySource: string;
readonly ownerPolicySource: string;
readonly host: string;
readonly requestedOwner: string;
},
dependencies: {
readonly fetch: OwnerFetch;
readonly absentControlName: () => string;
},
): Promise<MigrationOwnerResolution> {
const requestedLogin = parseRequestedOwner(input.requestedOwner);
if (requestedLogin === null) return refused('owner-name-invalid');
const target = parseCredentialEstateRegistry(input.estateRegistrySource).resolveByHost(
input.host,
);
if (target === undefined) return refused('estate-host-unmapped');
const policy = resolveBrainOwnerPolicy(input.ownerPolicySource, target.estate);
if (policy === undefined) return refused('owner-policy-invalid');
if (policy.brainNamespace !== requestedLogin) return refused('owner-not-allowlisted');
const publicControl = policy.publicControl;
const privateControl = policy.privateControl;
const absentControl = dependencies.absentControlName();
if (
!exactCanonicalLogin(publicControl) ||
!exactCanonicalLogin(privateControl) ||
!exactCanonicalLogin(absentControl) ||
new Set([publicControl, privateControl, absentControl, requestedLogin]).size !== 4
) {
return refused('owner-policy-invalid');
}
try {
const publicResult = await readPublicIdentity(
target.host.apiBaseUrl,
publicControl,
dependencies.fetch,
);
if (publicResult.status !== 200 || !publicIdentityMatches(publicResult.user, publicControl)) {
return unresolved('owner-control-invalid');
}
const privateResult = await readPublicIdentity(
target.host.apiBaseUrl,
privateControl,
dependencies.fetch,
);
if (privateResult.status !== 404) return unresolved('owner-control-invalid');
const absentResult = await readPublicIdentity(
target.host.apiBaseUrl,
absentControl,
dependencies.fetch,
);
if (absentResult.status !== 404) return unresolved('owner-control-invalid');
const ownerResult = await readPublicIdentity(
target.host.apiBaseUrl,
requestedLogin,
dependencies.fetch,
);
if (ownerResult.status === 401 || ownerResult.status === 403 || ownerResult.status === 404) {
return unresolved('owner-not-resolvable');
}
if (ownerResult.status !== 200) return unresolved('owner-provider-unavailable');
if (!publicIdentityMatches(ownerResult.user, requestedLogin)) {
return unresolved('owner-provider-identity-mismatch');
}
return {
verdict: 'resolved',
reasonCode: 'owner-verified',
principal: { name: `user:${requestedLogin}`, kind: 'durable-human' },
authority: {
system: 'gitea',
endpoint: `GET /api/v1/users/${requestedLogin}`,
contentType: 'application/json',
},
};
} catch (error: unknown) {
const reason = error instanceof Error ? error.message : 'owner-provider-unavailable';
if (reason === 'owner-unexpected-content-type') return unresolved(reason);
if (reason === 'owner-unexpected-provider-shape') return unresolved(reason);
return unresolved('owner-provider-unavailable');
}
}
@@ -0,0 +1,122 @@
import { afterEach, describe, expect, it } from 'vitest';
import { Command } from 'commander';
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
interface BrainProvisionCommandModule {
readonly BRAIN_PROVISION_COMMAND: string;
registerBrainProvisionCommand(program: Command): void;
executeBrainProvisionCommand(
options: {
readonly mosaicHome: string;
readonly home: string;
readonly identity: string;
readonly refusalIdentity: string;
readonly targetUrl: string;
readonly owner: string;
readonly lane: string;
readonly sourceRoot?: string;
readonly brainRoot?: string;
readonly ownerPolicy?: string;
readonly registry?: string;
},
dependencies: {
readonly run: () => never;
readonly fetch: typeof fetch;
readonly absentControlName: () => string;
},
): Promise<{
readonly status: 'provisioned' | 'blocked' | 'failed';
readonly reasonCode: string;
}>;
}
const MODULE_PATH = './brain-provision-command.js';
const roots: string[] = [];
async function loadCommand(requirement: string): Promise<BrainProvisionCommandModule> {
try {
return (await import(MODULE_PATH)) as BrainProvisionCommandModule;
} catch (error: unknown) {
const detail = error instanceof Error ? error.message : String(error);
throw new Error(`${requirement}: brain provision command is absent (${detail})`);
}
}
function tempRoot(): string {
const root = mkdtempSync(join(tmpdir(), 'mosaic-brain-command-'));
roots.push(root);
return root;
}
afterEach((): void => {
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
});
describe('internal P7 brain provision command', (): void => {
it('registers only explicit non-secret contract inputs and no credential/token lookup switches', async (): Promise<void> => {
const module = await loadCommand('MB-REQ-03 broker-only provision command');
const program = new Command();
module.registerBrainProvisionCommand(program);
const command = program.commands.find(
(candidate) => candidate.name() === module.BRAIN_PROVISION_COMMAND,
);
expect(command).toBeDefined();
const flags = command?.options.map((option) => option.flags) ?? [];
expect(flags.join(' ')).toContain('--identity');
expect(flags.join(' ')).toContain('--target-url');
expect(flags.join(' ')).toContain('--refusal-identity');
expect(flags.join(' ')).toContain('--owner-policy');
expect(flags.join(' ')).toContain('--owner');
expect(flags.join(' ')).toContain('--lane');
expect(flags.join(' ')).not.toMatch(/token|password|authorization|grant-authority/i);
});
it('is registered by the shipped CLI', async (): Promise<void> => {
const module = await loadCommand('MB-REQ-10 shipped P7 command');
const cli = readFileSync(join(process.cwd(), 'src', 'cli.ts'), 'utf8');
expect(cli).toContain('registerBrainProvisionCommand');
expect(cli).toContain(`registerBrainProvisionCommand(program)`);
expect(module.BRAIN_PROVISION_COMMAND).toBe('__brain-provision');
});
it('fails closed before commands when the local owner policy is absent', async (): Promise<void> => {
const module = await loadCommand('MB-REQ-09 owner policy required');
const root = tempRoot();
const home = join(root, 'home');
const mosaicHome = join(home, '.config', 'mosaic');
mkdirSync(join(mosaicHome, 'cred'), { recursive: true });
writeFileSync(
join(mosaicHome, 'cred', 'estates.json'),
JSON.stringify({ version: 1, estates: [] }),
{ mode: 0o600 },
);
let commands = 0;
const result = await module.executeBrainProvisionCommand(
{
mosaicHome,
home,
identity: 'seat-a',
refusalIdentity: 'outside-seat',
targetUrl: 'https://git.example.invalid/example/stack.git',
owner: 'user:durable-owner',
lane: 'lane-a',
},
{
run: (): never => {
commands += 1;
throw new Error('must not run');
},
fetch,
absentControlName: (): string => 'generated-absent-control',
},
);
expect(result).toMatchObject({ status: 'failed', reasonCode: 'owner-policy-unavailable' });
expect(commands).toBe(0);
});
});
@@ -0,0 +1,139 @@
import { randomUUID } from 'node:crypto';
import { homedir } from 'node:os';
import { join } from 'node:path';
import type { Command } from 'commander';
import { readRegularFileSecure } from '../fleet/secure-file.js';
import { provisionBrain, type ProvisionResult } from './brain-provision.js';
import { systemCommandRunner, type CommandRunner } from './brain-store-runtime.js';
import type { OwnerFetch } from './brain-owner-resolver.js';
const MAX_POLICY_BYTES = 256 * 1024;
export const BRAIN_PROVISION_COMMAND = '__brain-provision';
interface BrainProvisionCommandOptions {
readonly mosaicHome: string;
readonly home: string;
readonly identity: string;
readonly refusalIdentity: string;
readonly targetUrl: string;
readonly owner: string;
readonly lane: string;
readonly sourceRoot?: string;
readonly brainRoot?: string;
readonly ownerPolicy?: string;
readonly registry?: string;
}
interface BrainProvisionCommandDependencies {
readonly run: CommandRunner;
readonly fetch: OwnerFetch;
readonly absentControlName: () => string;
}
function configFailure(reasonCode: string): ProvisionResult {
return {
status: 'failed',
reasonCode,
findings: [{ code: `brain-${reasonCode}`, reasonCode }],
owner: null,
migration: null,
};
}
function readConfig(path: string, root: string): string {
const content = readRegularFileSecure(path, { root, maxBytes: MAX_POLICY_BYTES }).content;
return new TextDecoder('utf-8', { fatal: true }).decode(content);
}
export async function executeBrainProvisionCommand(
options: BrainProvisionCommandOptions,
dependencies: BrainProvisionCommandDependencies,
): Promise<ProvisionResult> {
const registry = options.registry ?? join(options.mosaicHome, 'cred', 'estates.json');
const ownerPolicy = options.ownerPolicy ?? join(options.mosaicHome, 'brain', 'owners.json');
let estateRegistrySource: string;
try {
estateRegistrySource = readConfig(registry, options.mosaicHome);
} catch {
return configFailure('estate-registry-unavailable');
}
let ownerPolicySource: string;
try {
ownerPolicySource = readConfig(ownerPolicy, options.mosaicHome);
} catch {
return configFailure('owner-policy-unavailable');
}
try {
return await provisionBrain(
{
estateRegistrySource,
ownerPolicySource,
targetGitUrl: options.targetUrl,
requestedOwner: options.owner,
identity: options.identity,
refusalIdentity: options.refusalIdentity,
root: options.brainRoot ?? join(options.home, '.mosaic'),
sourceRoot: options.sourceRoot ?? join(options.mosaicHome, 'memory'),
seat: options.identity,
lane: options.lane,
laneActive: false,
},
dependencies,
);
} catch {
return configFailure('brain-provision-exception');
}
}
export function registerBrainProvisionCommand(program: Command): void {
program
.command(BRAIN_PROVISION_COMMAND, { hidden: true })
.description('Internal installer P7 durable-brain provisioner')
.requiredOption('--identity <name>', 'explicit fleet identity')
.requiredOption('--target-url <url>', 'configured target git URL')
.requiredOption('--refusal-identity <name>', 'explicit out-of-estate negative control')
.requiredOption('--owner <owner>', 'policy-bound durable owner candidate')
.requiredOption('--lane <name>', 'source lane to migrate')
.option('--mosaic-home <path>', 'installed Mosaic home')
.option('--home <path>', 'principal home')
.option('--source-root <path>', 'legacy memory root')
.option('--brain-root <path>', 'per-estate brain checkout root')
.option('--owner-policy <path>', 'durable-owner allowlist policy')
.option('--registry <path>', 'estate registry path')
.action(async (raw: Record<string, string | undefined>): Promise<void> => {
const home = raw['home'] ?? homedir();
const mosaicHome =
raw['mosaicHome'] ?? process.env['MOSAIC_HOME'] ?? join(home, '.config', 'mosaic');
const result = await executeBrainProvisionCommand(
{
mosaicHome,
home,
identity: raw['identity']!,
targetUrl: raw['targetUrl']!,
refusalIdentity: raw['refusalIdentity']!,
owner: raw['owner']!,
lane: raw['lane']!,
...(raw['sourceRoot'] === undefined ? {} : { sourceRoot: raw['sourceRoot'] }),
...(raw['brainRoot'] === undefined ? {} : { brainRoot: raw['brainRoot'] }),
...(raw['ownerPolicy'] === undefined ? {} : { ownerPolicy: raw['ownerPolicy'] }),
...(raw['registry'] === undefined ? {} : { registry: raw['registry'] }),
},
{
run: systemCommandRunner,
fetch,
absentControlName: (): string => `mosaic-absent-${randomUUID()}`,
},
);
process.stdout.write(
`${JSON.stringify({
status: result.status,
reasonCode: result.reasonCode,
findings: result.findings,
owner: result.owner,
migration: result.migration,
})}\n`,
);
if (result.status !== 'provisioned') process.exitCode = result.status === 'blocked' ? 30 : 20;
});
}
@@ -0,0 +1,458 @@
import { afterEach, describe, expect, it } from 'vitest';
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
interface CommandRequest {
readonly program: 'git' | 'mosaic';
readonly args: readonly string[];
readonly env: Readonly<Record<string, string>>;
}
interface CommandResult {
readonly status: number;
readonly stdout: string;
readonly stderr: string;
}
type CommandRunner = (request: CommandRequest) => CommandResult;
type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
interface ProvisionResult {
readonly status: 'provisioned' | 'blocked' | 'failed';
readonly reasonCode: string;
readonly findings: readonly { code: string; reasonCode: string | null }[];
readonly owner: {
readonly verdict: 'resolved' | 'refused' | 'not-measured';
readonly reasonCode: string;
} | null;
readonly migration: {
readonly status: 'migrated' | 'reported' | 'failed';
readonly reported: readonly { path: string; reason: string }[];
} | null;
}
interface ProvisionModule {
provisionBrain(
input: {
readonly estateRegistrySource: string;
readonly ownerPolicySource: string;
readonly targetGitUrl: string;
readonly requestedOwner: string;
readonly identity: string;
readonly refusalIdentity: string;
readonly root: string;
readonly sourceRoot: string;
readonly seat: string;
readonly lane: string;
readonly laneActive: boolean;
},
dependencies: {
readonly run: CommandRunner;
readonly fetch: FetchLike;
readonly absentControlName: () => string;
},
): Promise<ProvisionResult>;
}
const MODULE_PATH = './brain-provision.js';
const roots: string[] = [];
async function loadProvisioner(requirement: string): Promise<ProvisionModule> {
try {
return (await import(MODULE_PATH)) as ProvisionModule;
} catch (error: unknown) {
const detail = error instanceof Error ? error.message : String(error);
throw new Error(`${requirement}: brain provisioner is absent (${detail})`);
}
}
function tempRoot(): string {
const root = mkdtempSync(join(tmpdir(), 'mosaic-brain-provision-'));
roots.push(root);
return root;
}
function estateRegistry(): string {
return JSON.stringify({
version: 1,
estates: [
{
name: 'homelab',
readOnlyControlIdentity: 'read-control',
hosts: [
{
host: 'git.example.invalid',
provider: 'gitea',
apiBaseUrl: 'https://git.example.invalid',
tokenPrefix: 'gitea-example',
},
],
},
],
});
}
function ownerPolicy(): string {
return JSON.stringify({
version: 1,
estates: [
{
estate: 'homelab',
laneArchiveOwners: [{ kind: 'provider-user', login: 'durable-owner' }],
standingProcess: { kind: 'glpi-queue', queue: 'mosaic-brain-remediation' },
controls: { publicIdentity: 'public-control', privateIdentity: 'private-control' },
},
],
});
}
function validateResult(
outcome: 'ok' | 'refused' | 'indeterminate',
reasonCode: string,
identity = 'seat-a',
): string {
const exitCode = outcome === 'ok' ? 0 : outcome === 'refused' ? 10 : 30;
return JSON.stringify({
schemaVersion: 1,
operation: 'validate',
outcome,
exitCode,
retryable: false,
subject: {
identity,
estate: 'homelab',
host: 'git.example.invalid',
repo: 'durable-owner/mosaic-brain',
},
mutation: 'none',
reason: { code: reasonCode, message: 'non-secret' },
evidence: {
providerIdentity:
outcome === 'ok'
? {
login: identity,
endpoint: 'GET /api/v1/user',
contentType: 'application/json',
}
: null,
repositoryPermission:
outcome === 'ok'
? {
requested: 'write',
effective: 'write',
endpoint: 'GET /api/v1/repos/durable-owner/mosaic-brain',
contentType: 'application/json',
}
: null,
writeDifferential:
outcome === 'ok'
? {
state: 'can-write',
credentialBinding: 'same-resolution',
transportPrincipal: identity,
authenticatedReceivePack: 'advertised',
readOnlyControl: {
identity: 'read-control',
providerPermission: 'read',
receivePack: 'refused',
},
unauthenticatedReceivePack: 'refused',
artifactCreated: false,
proves: 'non-secret evidence',
doesNotProve: 'branch update acceptance',
}
: null,
},
audit: { journalId: 'opaque', state: 'sealed' },
});
}
function publicUser(login: string): Response {
return new Response(JSON.stringify({ id: 1, login, visibility: 'public', active: false }), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}
function ownerFetch(ownerStatus = 200): FetchLike {
return async (input): Promise<Response> => {
const raw = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
const identity = decodeURIComponent(new URL(raw).pathname.split('/').at(-1) ?? '');
if (identity === 'public-control') return publicUser(identity);
if (identity === 'private-control' || identity === 'generated-absent-control') {
return new Response(JSON.stringify({ message: 'hidden or absent' }), {
status: 404,
headers: { 'content-type': 'application/json' },
});
}
if (identity === 'durable-owner' && ownerStatus === 200) return publicUser(identity);
return new Response(JSON.stringify({ message: 'hidden or absent' }), {
status: ownerStatus,
headers: { 'content-type': 'application/json' },
});
};
}
function baseInput(root: string): {
readonly estateRegistrySource: string;
readonly ownerPolicySource: string;
readonly targetGitUrl: string;
readonly requestedOwner: string;
readonly identity: string;
readonly refusalIdentity: string;
readonly root: string;
readonly sourceRoot: string;
readonly seat: string;
readonly lane: string;
readonly laneActive: boolean;
} {
return {
estateRegistrySource: estateRegistry(),
ownerPolicySource: ownerPolicy(),
targetGitUrl: 'https://git.example.invalid/example/stack.git',
requestedOwner: 'user:durable-owner',
identity: 'seat-a',
refusalIdentity: 'outside-seat',
root: join(root, 'brain'),
sourceRoot: join(root, 'local-memory'),
seat: 'seat-a',
lane: 'lane-a',
laneActive: false,
};
}
afterEach((): void => {
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
});
describe('P7 brain provisioning orchestration', (): void => {
it('requires the P5 write-capability postcondition and never grants or clones on refusal', async (): Promise<void> => {
const provisioner = await loadProvisioner('MB-REQ-10 P5 before P7');
const root = tempRoot();
const requests: CommandRequest[] = [];
const result = await provisioner.provisionBrain(baseInput(root), {
run: (request): CommandResult => {
requests.push(request);
return {
status: 10,
stdout: validateResult('refused', 'no-token-for-identity'),
stderr: 'refused reason=no-token-for-identity',
};
},
fetch: ownerFetch(),
absentControlName: (): string => 'generated-absent-control',
});
expect(result).toMatchObject({
status: 'blocked',
reasonCode: 'credential-postcondition-failed',
});
expect(requests).toHaveLength(1);
expect(requests[0]?.program).toBe('mosaic');
expect(requests[0]?.args.slice(0, 3)).toEqual(['cred', 'validate', 'seat-a']);
expect(requests.some((request) => request.args.includes('grant'))).toBe(false);
expect(requests.some((request) => request.args.includes('clone'))).toBe(false);
});
it('blocks before clone when the out-of-estate Git and API axes disagree', async (): Promise<void> => {
const provisioner = await loadProvisioner('MB-REQ-05 P7 refusal control gate');
const root = tempRoot();
const requests: CommandRequest[] = [];
const result = await provisioner.provisionBrain(baseInput(root), {
run: (request): CommandResult => {
requests.push(request);
if (request.program === 'mosaic' && request.args[2] === 'outside-seat') {
return {
status: 10,
stdout: validateResult('refused', 'no-token-for-identity', 'outside-seat'),
stderr: 'refused reason=no-token-for-identity',
};
}
if (request.program === 'mosaic') {
return { status: 0, stdout: validateResult('ok', 'validation-verified'), stderr: '' };
}
if (request.args.includes('ls-remote')) {
return { status: 0, stdout: 'refs are visible', stderr: '' };
}
return { status: 99, stdout: '', stderr: 'unexpected command' };
},
fetch: ownerFetch(),
absentControlName: (): string => 'generated-absent-control',
});
expect(result).toMatchObject({
status: 'blocked',
reasonCode: 'refusal-control-failed',
});
expect(requests.some((request) => request.args.includes('clone'))).toBe(false);
expect(requests.some((request) => request.args.includes('grant'))).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();
const input = baseInput(root);
mkdirSync(join(input.sourceRoot, 'lanes', 'lane-a'), { recursive: true });
const source = join(input.sourceRoot, 'lanes', 'lane-a', 'finding.md');
writeFileSync(source, 'durable finding\n');
const requests: CommandRequest[] = [];
let commitOrdinal = 0;
const runner: CommandRunner = (request): CommandResult => {
requests.push(request);
if (request.program === 'mosaic') {
if (request.args[2] === 'outside-seat') {
return {
status: 10,
stdout: validateResult('refused', 'no-token-for-identity', 'outside-seat'),
stderr: 'refused reason=no-token-for-identity',
};
}
return { 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 (request.args[0] === 'clone') {
mkdirSync(join(input.root, '.git'), { recursive: true });
return { status: 0, stdout: '', stderr: '' };
}
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: '', stderr: '' };
}
if (command.includes('rev-parse HEAD')) {
commitOrdinal += 1;
return {
status: 0,
stdout: `${commitOrdinal === 1 ? 'a' : 'c'.repeat(1)}`.repeat(40) + '\n',
stderr: '',
};
}
if (command.includes('rev-parse origin/main')) {
const value = commitOrdinal === 1 ? 'b' : 'd';
return { status: 0, stdout: `${value.repeat(40)}\n`, stderr: '' };
}
return { status: 0, stdout: '', stderr: '' };
};
const result = await provisioner.provisionBrain(input, {
run: runner,
fetch: ownerFetch(),
absentControlName: (): string => 'generated-absent-control',
});
expect(result).toMatchObject({
status: 'provisioned',
reasonCode: 'brain-provisioned',
owner: { verdict: 'resolved', reasonCode: 'owner-verified' },
migration: { status: 'migrated' },
});
expect(existsSync(source)).toBe(false);
const imported = result.migration?.reported ?? [];
expect(imported).toEqual([]);
const laneImports = join(input.root, 'lanes', 'lane-a', 'findings', 'imports');
const archiveImports = join(input.root, 'archives', 'imports', 'lane');
expect(existsSync(laneImports)).toBe(true);
expect(existsSync(archiveImports)).toBe(true);
expect(
requests.filter((request) => request.program === 'git' && request.args.includes('push')),
).toHaveLength(2);
expect(requests.some((request) => request.args.includes('grant'))).toBe(false);
});
it('keeps every source and reports the owner ambiguity when the public owner cannot be resolved', async (): Promise<void> => {
const provisioner = await loadProvisioner('MB-REQ-07 owner-blocked detection/reporting');
const root = tempRoot();
const input = baseInput(root);
mkdirSync(input.root, { recursive: true });
mkdirSync(join(input.root, '.git'), { recursive: true });
mkdirSync(join(input.sourceRoot, 'lanes', 'lane-a'), { recursive: true });
const source = join(input.sourceRoot, 'lanes', 'lane-a', 'finding.md');
writeFileSync(source, 'retain me\n');
let commitOrdinal = 0;
const result = await provisioner.provisionBrain(input, {
run: (request): CommandResult => {
if (request.program === 'mosaic') {
if (request.args[2] === 'outside-seat') {
return {
status: 10,
stdout: validateResult('refused', 'no-token-for-identity', 'outside-seat'),
stderr: 'refused reason=no-token-for-identity',
};
}
return { 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: '', stderr: '' };
}
if (command.includes('rev-parse HEAD')) {
commitOrdinal += 1;
return { status: 0, stdout: `${'a'.repeat(40)}\n`, stderr: '' };
}
if (command.includes('rev-parse origin/main')) {
return { status: 0, stdout: `${'b'.repeat(40)}\n`, stderr: '' };
}
return { status: 0, stdout: '', stderr: '' };
},
fetch: ownerFetch(404),
absentControlName: (): string => 'generated-absent-control',
});
expect(result).toMatchObject({
status: 'blocked',
reasonCode: 'owner-not-resolvable',
owner: { verdict: 'not-measured', reasonCode: 'owner-not-resolvable' },
migration: { status: 'reported' },
});
expect(readFileSync(source, 'utf8')).toBe('retain me\n');
expect(result.migration?.reported).toEqual(
expect.arrayContaining([
expect.objectContaining({ path: source, reason: expect.stringMatching(/owner/i) }),
]),
);
expect(JSON.stringify(result)).not.toMatch(/owner-not-found|does-not-exist/);
expect(commitOrdinal).toBe(0);
});
});
@@ -0,0 +1,247 @@
import { lstatSync } from 'node:fs';
import { parseRequestedOwner, resolveProviderDurableOwner } from './brain-owner-resolver.js';
import {
createBrainSkeleton,
deriveBrainTarget,
discoverBrainMigration,
migrateBrainState,
type MigrationResult,
type MigrationOwnerResolution,
} from './brain-store.js';
import {
collectBrainDoctorReport,
collectBrainRefusalControl,
publishBrainPaths,
type CommandRequest,
type CommandResult,
type CommandRunner,
} from './brain-store-runtime.js';
import type { OwnerFetch } from './brain-owner-resolver.js';
export interface ProvisionResult {
readonly status: 'provisioned' | 'blocked' | 'failed';
readonly reasonCode: string;
readonly findings: readonly {
readonly code: string;
readonly reasonCode: string | null;
}[];
readonly owner: Pick<MigrationOwnerResolution, 'verdict' | 'reasonCode'> | null;
readonly migration: Pick<MigrationResult, 'status' | 'reported'> | null;
}
function commandEnv(identity: string): Readonly<Record<string, string>> {
return { MOSAIC_GIT_IDENTITY: identity, GIT_TERMINAL_PROMPT: '0' };
}
function findingView(
findings: readonly { readonly code: string; readonly reasonCode: string | null }[],
): readonly { readonly code: string; readonly reasonCode: string | null }[] {
return findings.map((finding): { readonly code: string; readonly reasonCode: string | null } => ({
code: finding.code,
reasonCode: finding.reasonCode,
}));
}
function blocked(
reasonCode: string,
findings: readonly { readonly code: string; readonly reasonCode: string | null }[],
owner: MigrationOwnerResolution | null = null,
migration: MigrationResult | null = null,
): ProvisionResult {
return {
status: 'blocked',
reasonCode,
findings: findingView(findings),
owner: owner === null ? null : { verdict: owner.verdict, reasonCode: owner.reasonCode },
migration:
migration === null ? null : { status: migration.status, reported: migration.reported },
};
}
function failed(
reasonCode: string,
findings: readonly { readonly code: string; readonly reasonCode: string | null }[],
owner: MigrationOwnerResolution | null = null,
migration: MigrationResult | null = null,
): ProvisionResult {
return {
...blocked(reasonCode, findings, owner, migration),
status: 'failed',
};
}
export async function provisionBrain(
input: {
readonly estateRegistrySource: string;
readonly ownerPolicySource: string;
readonly targetGitUrl: string;
readonly requestedOwner: string;
readonly identity: string;
readonly refusalIdentity: string;
readonly root: string;
readonly sourceRoot: string;
readonly seat: string;
readonly lane: string;
readonly laneActive: boolean;
},
dependencies: {
readonly run: CommandRunner;
readonly fetch: OwnerFetch;
readonly absentControlName: () => string;
},
): Promise<ProvisionResult> {
const brainNamespace = parseRequestedOwner(input.requestedOwner);
if (brainNamespace === null) return blocked('owner-name-invalid', []);
const target = deriveBrainTarget(input.estateRegistrySource, input.targetGitUrl, brainNamespace);
const doctorInput = {
registrySource: input.estateRegistrySource,
targetGitUrl: input.targetGitUrl,
brainNamespace,
identity: input.identity,
root: input.root,
};
let report = collectBrainDoctorReport(doctorInput, dependencies.run);
if (report.access.outcome !== 'ok') {
return blocked('credential-postcondition-failed', report.findings);
}
const refusalControl = collectBrainRefusalControl(
{
registrySource: input.estateRegistrySource,
targetGitUrl: input.targetGitUrl,
brainNamespace,
refusalIdentity: input.refusalIdentity,
},
dependencies.run,
);
if (!refusalControl.observed) {
return blocked('refusal-control-failed', [
...report.findings,
{
code: 'brain-refusal-control-indeterminate',
reasonCode: refusalControl.reasonCode,
},
]);
}
const owner = await resolveProviderDurableOwner(
{
estateRegistrySource: input.estateRegistrySource,
ownerPolicySource: input.ownerPolicySource,
host: target.host,
requestedOwner: input.requestedOwner,
},
{
fetch: dependencies.fetch,
absentControlName: dependencies.absentControlName,
},
);
if (owner.verdict !== 'resolved') {
const plan = discoverBrainMigration(
{
sourceRoot: input.sourceRoot,
brainRoot: input.root,
seat: input.seat,
lane: input.lane,
laneActive: input.laneActive,
},
(): MigrationOwnerResolution => owner,
);
const migration = migrateBrainState(
plan,
(): never => {
throw new Error('blocked owner cannot publish');
},
input.root,
);
return blocked(owner.reasonCode, report.findings, owner, migration);
}
if (report.findings.some((finding): boolean => finding.code === 'brain-clone-missing')) {
const clone: CommandRequest = {
program: 'git',
args: ['clone', '--branch', 'main', '--single-branch', target.cloneUrl, input.root],
env: commandEnv(input.identity),
};
const cloneResult: CommandResult = dependencies.run(clone);
if (cloneResult.status !== 0) return failed('brain-clone-failed', report.findings);
report = collectBrainDoctorReport(doctorInput, dependencies.run);
}
const blockingCloneFindings = report.findings.filter(
(finding): boolean =>
finding.code === 'brain-clone-missing' ||
finding.code === 'brain-not-git-repository' ||
finding.code === 'brain-remote-mismatch' ||
finding.code === 'brain-branch-mismatch' ||
finding.code.startsWith('brain-write-access-'),
);
if (blockingCloneFindings.length > 0) {
return blocked('brain-postcondition-failed', report.findings);
}
let skeletonPaths: readonly string[];
try {
const skeleton = createBrainSkeleton(input.root);
skeletonPaths = skeleton.created.filter((path: string): boolean => lstatSync(path).isFile());
} catch {
return failed('brain-skeleton-failed', report.findings);
}
if (skeletonPaths.length > 0) {
try {
const evidence = publishBrainPaths(
{
root: input.root,
identity: input.identity,
paths: skeletonPaths,
message: 'chore: seed durable brain layout',
},
dependencies.run,
);
if (!evidence.reachable) return failed('brain-skeleton-not-reachable', report.findings);
} catch {
return failed('brain-skeleton-publish-failed', report.findings);
}
}
const plan = discoverBrainMigration(
{
sourceRoot: input.sourceRoot,
brainRoot: input.root,
seat: input.seat,
lane: input.lane,
laneActive: input.laneActive,
},
(): MigrationOwnerResolution => owner,
);
const migration = migrateBrainState(
plan,
(brainRoot: string, paths: readonly string[]) =>
publishBrainPaths(
{
root: brainRoot,
identity: input.identity,
paths,
message: `migrate: archive ${input.lane} working memory`,
},
dependencies.run,
),
input.root,
);
if (migration.status === 'failed') {
return failed('brain-migration-publish-failed', report.findings, owner, migration);
}
report = collectBrainDoctorReport(doctorInput, dependencies.run);
if (report.findings.length > 0) {
return blocked('brain-final-postcondition-failed', report.findings, owner, migration);
}
return {
status: 'provisioned',
reasonCode: 'brain-provisioned',
findings: [],
owner: { verdict: owner.verdict, reasonCode: owner.reasonCode },
migration: { status: migration.status, reported: migration.reported },
};
}
@@ -0,0 +1,715 @@
import { afterEach, describe, expect, it } from 'vitest';
import { mkdirSync, mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
/**
* Red-first integration seam for #1051.
*
* Live broker grants and remote writes are deliberately not exercised here.
* The injected runner records exact commands and returns contract objects; the
* production path must call `mosaic cred`, never resolve a token itself.
*/
interface CommandRequest {
readonly program: 'git' | 'mosaic';
readonly args: readonly string[];
readonly cwd?: string;
readonly env: Readonly<Record<string, string>>;
}
interface CommandResult {
readonly status: number;
readonly stdout: string;
readonly stderr: string;
}
type CommandRunner = (request: CommandRequest) => CommandResult;
interface DoctorRuntimeReport {
readonly findings: readonly { code: string; repairable: boolean; reasonCode: string | null }[];
readonly access: {
readonly outcome: 'ok' | 'refused' | 'error' | 'indeterminate';
readonly exitCode: 0 | 10 | 20 | 30;
readonly reasonCode: string;
};
readonly refusalControl: {
readonly observed: boolean;
readonly reasonCode: string | null;
};
}
interface PublishEvidence {
readonly commit: string;
readonly remoteHead: string;
readonly reachable: boolean;
}
interface BrainRuntimeModule {
collectBrainDoctorReport(
input: {
readonly registrySource: string;
readonly targetGitUrl: string;
readonly brainNamespace: string;
readonly identity: string;
readonly root: string;
},
run: CommandRunner,
): DoctorRuntimeReport;
repairBrainDoctor(
input: {
readonly registrySource: string;
readonly targetGitUrl: string;
readonly brainNamespace: string;
readonly identity: string;
readonly root: string;
},
run: CommandRunner,
): DoctorRuntimeReport;
collectBrainRefusalControl(
input: {
readonly registrySource: string;
readonly targetGitUrl: string;
readonly brainNamespace: string;
readonly refusalIdentity: string;
},
run: CommandRunner,
): {
readonly observed: boolean;
readonly reasonCode: string | null;
readonly gitReasonCode: string;
readonly apiReasonCode: string;
};
publishBrainPaths(
input: {
readonly root: string;
readonly identity: string;
readonly paths: readonly string[];
readonly message: string;
},
run: CommandRunner,
): PublishEvidence;
}
const MODULE_PATH = './brain-store-runtime.js';
const roots: string[] = [];
async function loadRuntime(requirement: string): Promise<BrainRuntimeModule> {
try {
return (await import(MODULE_PATH)) as BrainRuntimeModule;
} catch (error: unknown) {
const detail = error instanceof Error ? error.message : String(error);
throw new Error(`${requirement}: brain-store runtime is absent (${detail})`);
}
}
function tempRoot(): string {
const root = mkdtempSync(join(tmpdir(), 'mosaic-brain-runtime-'));
roots.push(root);
return root;
}
function registry(): string {
return JSON.stringify({
version: 1,
estates: [
{
name: 'homelab',
readOnlyControlIdentity: 'read-control',
hosts: [
{
host: 'git.mosaicstack.dev',
provider: 'gitea',
apiBaseUrl: 'https://git.mosaicstack.dev',
tokenPrefix: 'gitea-mosaicstack',
},
],
},
],
});
}
function validateResult(
outcome: 'ok' | 'refused' | 'error' | 'indeterminate',
reasonCode: string,
): 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: 'synthetic-no-token',
estate: 'homelab',
host: 'git.mosaicstack.dev',
repo: 'mosaicstack/mosaic-brain',
},
mutation: 'none',
reason: { code: reasonCode, message: 'non-secret' },
evidence: {
providerIdentity:
outcome === 'ok'
? {
login: 'synthetic-no-token',
endpoint: 'GET /api/v1/user',
contentType: 'application/json',
}
: null,
repositoryPermission:
outcome === 'ok'
? {
requested: 'write',
effective: 'write',
endpoint: 'GET /api/v1/repos/mosaicstack/mosaic-brain',
contentType: 'application/json',
}
: null,
writeDifferential:
outcome === 'ok'
? {
state: 'can-write',
credentialBinding: 'same-resolution',
transportPrincipal: 'synthetic-no-token',
authenticatedReceivePack: 'advertised',
readOnlyControl: {
identity: 'read-control',
providerPermission: 'read',
receivePack: 'refused',
},
unauthenticatedReceivePack: 'refused',
artifactCreated: false,
proves: 'non-secret evidence',
doesNotProve: 'branch update acceptance',
}
: null,
},
audit: { journalId: 'opaque', state: 'sealed' },
});
}
function requestHasSecretShape(request: CommandRequest): boolean {
return JSON.stringify(request).match(/authorization|password|\.token|token-dir/i) !== null;
}
afterEach((): void => {
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
});
describe('doctor runtime observation', (): void => {
it('runs a synthetic no-token positive control through mosaic cred even when the clone is missing', async (): Promise<void> => {
const runtime = await loadRuntime('MB-REQ-05 synthetic refusal control');
const root = join(tempRoot(), 'missing-brain');
const requests: CommandRequest[] = [];
const runner: CommandRunner = (request): CommandResult => {
requests.push(request);
return {
status: 10,
stdout: validateResult('refused', 'no-token-for-identity'),
stderr: 'refused reason=no-token-for-identity',
};
};
const report = runtime.collectBrainDoctorReport(
{
registrySource: registry(),
targetGitUrl: 'https://git.mosaicstack.dev/mosaicstack/stack.git',
brainNamespace: 'mosaicstack',
identity: 'synthetic-no-token',
root,
},
runner,
);
expect(requests).toHaveLength(1);
expect(requests[0]).toMatchObject({
program: 'mosaic',
args: [
'cred',
'validate',
'synthetic-no-token',
'--estate',
'homelab',
'--host',
'git.mosaicstack.dev',
'--repo',
'mosaicstack/mosaic-brain',
'--require',
'write',
'--json',
],
});
expect(requests.some(requestHasSecretShape)).toBe(false);
expect(report.refusalControl).toEqual({
observed: true,
reasonCode: 'no-token-for-identity',
});
expect(report.findings.map((finding) => finding.code)).toEqual(
expect.arrayContaining(['brain-clone-missing', 'brain-write-access-refused']),
);
});
it('treats process/object terminal-class disagreement as indeterminate', async (): Promise<void> => {
const runtime = await loadRuntime('MB-REQ-08 process/object disagreement');
const root = join(tempRoot(), 'missing-brain');
const report = runtime.collectBrainDoctorReport(
{
registrySource: registry(),
targetGitUrl: 'https://git.mosaicstack.dev/mosaicstack/stack.git',
brainNamespace: 'mosaicstack',
identity: 'synthetic-no-token',
root,
},
(): CommandResult => ({
status: 0,
stdout: validateResult('refused', 'no-token-for-identity'),
stderr: '',
}),
);
expect(report.access).toMatchObject({
outcome: 'indeterminate',
exitCode: 30,
reasonCode: 'unexpected-provider-shape',
});
expect(report.refusalControl.observed).toBe(false);
});
it('rejects a self-consistent broker object for a different declared subject', async (): Promise<void> => {
const runtime = await loadRuntime('MB-REQ-04 caller subject binding');
const root = join(tempRoot(), 'missing-brain');
const report = runtime.collectBrainDoctorReport(
{
registrySource: registry(),
targetGitUrl: 'https://git.mosaicstack.dev/mosaicstack/stack.git',
brainNamespace: 'mosaicstack',
identity: 'seat-a',
root,
},
(): CommandResult => ({
status: 0,
stdout: validateResult('ok', 'validation-verified'),
stderr: '',
}),
);
expect(report.access).toMatchObject({
outcome: 'indeterminate',
exitCode: 30,
reasonCode: 'unexpected-provider-shape',
});
});
it('reads wrong remote and dirty state from git while preserving credential identity binding', async (): Promise<void> => {
const runtime = await loadRuntime('MB-REQ-08 real git observation seam');
const root = join(tempRoot(), 'brain');
mkdirSync(join(root, '.git'), { recursive: true });
const requests: CommandRequest[] = [];
const runner: CommandRunner = (request): CommandResult => {
requests.push(request);
if (request.program === 'mosaic') {
const source = validateResult('ok', 'validation-verified').replaceAll(
'synthetic-no-token',
'seat-a',
);
return { status: 0, stdout: source, 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 {
status: 0,
stdout: 'https://git.uscllc.com/usc/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: '?? uncommitted.md\n', 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.map((finding) => finding.code)).toEqual(
expect.arrayContaining(['brain-remote-mismatch', 'brain-uncommitted-state']),
);
expect(requests.filter((request) => request.program === 'git')).toHaveLength(4);
for (const request of requests) {
expect(request.env['MOSAIC_GIT_IDENTITY']).toBe('seat-a');
expect(request.env['GIT_TERMINAL_PROMPT']).toBe('0');
}
});
it('repairs write refusal through mosaic cred, revalidates, then clones and verifies the resulting object', async (): Promise<void> => {
const runtime = await loadRuntime('MB-REQ-08 broker-only doctor repair');
const root = join(tempRoot(), 'brain');
const requests: CommandRequest[] = [];
let validationCount = 0;
const runner: CommandRunner = (request): CommandResult => {
requests.push(request);
if (request.program === 'mosaic' && request.args[1] === 'validate') {
validationCount += 1;
if (validationCount === 1) {
return {
status: 10,
stdout: validateResult('refused', 'no-token-for-identity').replaceAll(
'synthetic-no-token',
'seat-a',
),
stderr: 'refused reason=no-token-for-identity',
};
}
const source = validateResult('ok', 'validation-verified').replaceAll(
'synthetic-no-token',
'seat-a',
);
return { status: 0, stdout: source, stderr: '' };
}
if (request.program === 'mosaic' && request.args[1] === 'grant') {
return { status: 0, stdout: '{"outcome":"ok"}\n', stderr: '' };
}
if (request.program === 'git' && request.args[0] === 'clone') {
mkdirSync(join(root, '.git'), { recursive: true });
return { status: 0, stdout: '', 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 {
status: 0,
stdout: 'https://git.mosaicstack.dev/mosaicstack/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: '', stderr: '' };
}
return { status: 99, stdout: '', stderr: 'unexpected command' };
};
const report = runtime.repairBrainDoctor(
{
registrySource: registry(),
targetGitUrl: 'https://git.mosaicstack.dev/mosaicstack/stack.git',
brainNamespace: 'mosaicstack',
identity: 'seat-a',
root,
},
runner,
);
expect(report.findings).toEqual([]);
const sequence = requests.map(
(request) => `${request.program}:${request.args[1] ?? request.args[0]}`,
);
expect(sequence.slice(0, 4)).toEqual([
'mosaic:validate',
'mosaic:grant',
'mosaic:validate',
'git:--branch',
]);
expect(requests.some(requestHasSecretShape)).toBe(false);
});
it('does not clone when broker revalidation remains refused after a grant attempt', async (): Promise<void> => {
const runtime = await loadRuntime('MB-REQ-08 failed grant remains visible');
const root = join(tempRoot(), 'brain');
const requests: CommandRequest[] = [];
const report = runtime.repairBrainDoctor(
{
registrySource: registry(),
targetGitUrl: 'https://git.mosaicstack.dev/mosaicstack/stack.git',
brainNamespace: 'mosaicstack',
identity: 'synthetic-no-token',
root,
},
(request): CommandResult => {
requests.push(request);
if (request.program === 'mosaic' && request.args[1] === 'grant') {
return { status: 10, stdout: '{"outcome":"refused"}\n', stderr: 'refused' };
}
return {
status: 10,
stdout: validateResult('refused', 'no-token-for-identity'),
stderr: 'refused reason=no-token-for-identity',
};
},
);
expect(report.findings.map((finding) => finding.code)).toEqual(
expect.arrayContaining(['brain-clone-missing', 'brain-write-access-refused']),
);
expect(requests.some((request) => request.program === 'git')).toBe(false);
});
});
describe('R5 production refusal control', (): void => {
it('requires matching refusal on both Git transport and broker API axes', async (): Promise<void> => {
const runtime = await loadRuntime('MB-REQ-05 production both-axis refusal');
const requests: CommandRequest[] = [];
const result = runtime.collectBrainRefusalControl(
{
registrySource: registry(),
targetGitUrl: 'https://git.mosaicstack.dev/mosaicstack/stack.git',
brainNamespace: 'mosaicstack',
refusalIdentity: 'synthetic-no-token',
},
(request): CommandResult => {
requests.push(request);
if (request.program === 'mosaic') {
return {
status: 10,
stdout: validateResult('refused', 'no-token-for-identity'),
stderr: 'refused reason=no-token-for-identity',
};
}
return {
status: 128,
stdout: '',
stderr: 'credential helper refused reason=no-token-for-identity',
};
},
);
expect(result).toEqual({
observed: true,
reasonCode: 'no-token-for-identity',
gitReasonCode: 'no-token-for-identity',
apiReasonCode: 'no-token-for-identity',
});
expect(requests.map((request) => request.program)).toEqual(['mosaic', 'git']);
expect(requests[1]?.args).toEqual([
'ls-remote',
'https://git.mosaicstack.dev/mosaicstack/mosaic-brain.git',
'HEAD',
]);
expect(
requests.every((request) => request.env['MOSAIC_GIT_IDENTITY'] === 'synthetic-no-token'),
).toBe(true);
expect(requests.some(requestHasSecretShape)).toBe(false);
});
it('fails when the API refuses but Git transport accepts the out-of-estate identity', async (): Promise<void> => {
const runtime = await loadRuntime('MB-REQ-05 production axis disagreement');
const result = runtime.collectBrainRefusalControl(
{
registrySource: registry(),
targetGitUrl: 'https://git.mosaicstack.dev/mosaicstack/stack.git',
brainNamespace: 'mosaicstack',
refusalIdentity: 'synthetic-no-token',
},
(request): CommandResult =>
request.program === 'mosaic'
? {
status: 10,
stdout: validateResult('refused', 'no-token-for-identity'),
stderr: 'refused reason=no-token-for-identity',
}
: { status: 0, stdout: 'refs are visible', stderr: '' },
);
expect(result).toMatchObject({
observed: false,
reasonCode: 'permission-evidence-disagrees',
gitReasonCode: 'transport-accepted',
apiReasonCode: 'no-token-for-identity',
});
});
});
describe('push-on-write publication', (): void => {
it('commits with command-scoped identity, pushes immediately, and proves reachability from origin/main', async (): Promise<void> => {
const runtime = await loadRuntime('MB-REQ-07 publish-on-write reachability');
const root = tempRoot();
const requests: CommandRequest[] = [];
const commit = 'a'.repeat(40);
const remoteHead = 'b'.repeat(40);
const runner: CommandRunner = (request): CommandResult => {
requests.push(request);
const command = request.args.join(' ');
if (command.includes('rev-parse HEAD'))
return { status: 0, stdout: `${commit}\n`, stderr: '' };
if (command.includes('rev-parse origin/main')) {
return { status: 0, stdout: `${remoteHead}\n`, stderr: '' };
}
return { status: 0, stdout: '', stderr: '' };
};
const evidence = runtime.publishBrainPaths(
{
root,
identity: 'seat-a',
paths: [join(root, '.gitignore'), join(root, 'lanes', 'lane-a', '.gitkeep')],
message: 'migrate lane-a state',
},
runner,
);
expect(evidence).toEqual({ commit, remoteHead, reachable: true });
const rendered = requests.map((request) => `${request.program} ${request.args.join(' ')}`);
expect(rendered).toEqual(
expect.arrayContaining([
expect.stringMatching(/git -C .* add -- \.gitignore lanes\/lane-a\/\.gitkeep/),
expect.stringMatching(
/git -C .* -c user\.name=seat-a -c user\.email=seat-a@fleet\.mosaicstack\.dev commit/,
),
expect.stringMatching(/git -C .* push origin HEAD:main/),
expect.stringMatching(/git -C .* fetch origin main/),
expect.stringMatching(/git -C .* merge-base --is-ancestor/),
]),
);
const pushIndex = rendered.findIndex((command) => command.includes(' push origin HEAD:main'));
const fetchIndex = rendered.findIndex((command) => command.includes(' fetch origin main'));
expect(pushIndex).toBeGreaterThan(-1);
expect(fetchIndex).toBeGreaterThan(pushIndex);
expect(rendered.join('\n')).not.toMatch(/timer|cron|interval/);
expect(requests.some(requestHasSecretShape)).toBe(false);
});
it('rebases and retries a rejected concurrent append-only push instead of choosing last-writer-wins', async (): Promise<void> => {
const runtime = await loadRuntime('MB-REQ-07 append-only multi-host conflict');
const root = tempRoot();
const requests: CommandRequest[] = [];
const firstCommit = 'a'.repeat(40);
const rebasedCommit = 'c'.repeat(40);
const remoteHead = 'd'.repeat(40);
let pushes = 0;
let rebased = false;
const evidence = runtime.publishBrainPaths(
{
root,
identity: 'seat-a',
paths: [join(root, 'lanes', 'lane-a', 'findings', 'host-a.md')],
message: 'append host-a finding',
},
(request): CommandResult => {
requests.push(request);
const command = request.args.join(' ');
if (command.includes('push origin HEAD:main')) {
pushes += 1;
return pushes === 1
? { status: 1, stdout: '', stderr: 'non-fast-forward' }
: { status: 0, stdout: '', stderr: '' };
}
if (command.includes('rebase origin/main')) {
rebased = true;
return { status: 0, stdout: '', stderr: '' };
}
if (command.includes('rev-parse HEAD')) {
return {
status: 0,
stdout: `${rebased ? rebasedCommit : firstCommit}\n`,
stderr: '',
};
}
if (command.includes('rev-parse origin/main')) {
return { status: 0, stdout: `${remoteHead}\n`, stderr: '' };
}
return { status: 0, stdout: '', stderr: '' };
},
);
const rendered = requests.map((request) => request.args.join(' '));
const firstPush = rendered.findIndex((command) => command.includes('push origin HEAD:main'));
const rebase = rendered.findIndex((command) => command.includes('rebase origin/main'));
const secondPush = rendered
.map((command): boolean => command.includes('push origin HEAD:main'))
.lastIndexOf(true);
expect(firstPush).toBeGreaterThan(-1);
expect(rebase).toBeGreaterThan(firstPush);
expect(secondPush).toBeGreaterThan(rebase);
expect(pushes).toBe(2);
expect(evidence).toEqual({ commit: rebasedCommit, remoteHead, reachable: true });
});
it('re-proves reachability without inventing a commit when content was already published', async (): Promise<void> => {
const runtime = await loadRuntime('MB-REQ-07 idempotent publication read-back');
const root = tempRoot();
const commit = 'e'.repeat(40);
const requests: CommandRequest[] = [];
const evidence = runtime.publishBrainPaths(
{
root,
identity: 'seat-a',
paths: [join(root, 'lanes', 'lane-a', 'already-present.md')],
message: 'append existing finding',
},
(request): CommandResult => {
requests.push(request);
const command = request.args.join(' ');
if (command.includes(' commit ')) {
return { status: 1, stdout: '', stderr: 'nothing to commit' };
}
if (command.includes('diff --cached --quiet')) {
return { status: 0, stdout: '', stderr: '' };
}
if (command.includes('rev-parse HEAD') || command.includes('rev-parse origin/main')) {
return { status: 0, stdout: `${commit}\n`, stderr: '' };
}
return { status: 0, stdout: '', stderr: '' };
},
);
expect(evidence).toEqual({ commit, remoteHead: commit, reachable: true });
expect(requests.some((request) => request.args.includes('push'))).toBe(true);
});
it('does not manufacture reachability when merge-base rejects the new commit', async (): Promise<void> => {
const runtime = await loadRuntime('MB-REQ-07 publication reachability negative control');
const root = tempRoot();
const commit = 'a'.repeat(40);
const remoteHead = 'b'.repeat(40);
const evidence = runtime.publishBrainPaths(
{
root,
identity: 'seat-a',
paths: [join(root, '.gitignore')],
message: 'seed brain',
},
(request): CommandResult => {
const command = request.args.join(' ');
if (command.includes('rev-parse HEAD')) {
return { status: 0, stdout: `${commit}\n`, stderr: '' };
}
if (command.includes('rev-parse origin/main')) {
return { status: 0, stdout: `${remoteHead}\n`, stderr: '' };
}
if (command.includes('merge-base --is-ancestor')) {
return { status: 1, stdout: '', stderr: '' };
}
return { status: 0, stdout: '', stderr: '' };
},
);
expect(evidence).toEqual({ commit, remoteHead, reachable: false });
});
});
@@ -0,0 +1,393 @@
import { existsSync } from 'node:fs';
import { spawnSync } from 'node:child_process';
import { isAbsolute, relative, resolve, sep } from 'node:path';
import {
assessCredentialResult,
deriveBrainTarget,
evaluateBrainDoctor,
planBrainDoctorFix,
type BrainDoctorFinding,
type BrainDoctorObservation,
type CredentialAssessment,
} from './brain-store.js';
const COMMIT = /^[0-9a-f]{40}$/;
export interface CommandRequest {
readonly program: 'git' | 'mosaic';
readonly args: readonly string[];
readonly cwd?: string;
readonly env: Readonly<Record<string, string>>;
}
export interface CommandResult {
readonly status: number;
readonly stdout: string;
readonly stderr: string;
}
export type CommandRunner = (request: CommandRequest) => CommandResult;
export const systemCommandRunner: CommandRunner = (request: CommandRequest): CommandResult => {
const result = spawnSync(request.program, request.args, {
cwd: request.cwd,
env: { ...process.env, ...request.env },
encoding: 'utf8',
maxBuffer: 1024 * 1024,
});
return {
status: result.status ?? 127,
stdout: result.stdout ?? '',
stderr: result.stderr ?? result.error?.message ?? '',
};
};
export interface DoctorRuntimeReport {
readonly findings: readonly BrainDoctorFinding[];
readonly access: CredentialAssessment;
readonly refusalControl: {
readonly observed: boolean;
readonly reasonCode: string | null;
};
}
export interface BrainRefusalControlResult {
readonly observed: boolean;
readonly reasonCode: string | null;
readonly gitReasonCode: string;
readonly apiReasonCode: string;
}
export interface PublishEvidence {
readonly commit: string;
readonly remoteHead: string;
readonly reachable: boolean;
}
function commandEnv(identity: string): Readonly<Record<string, string>> {
return {
MOSAIC_GIT_IDENTITY: identity,
GIT_TERMINAL_PROMPT: '0',
};
}
function integrationFailure(): CredentialAssessment {
return {
outcome: 'indeterminate',
exitCode: 30,
reasonCode: 'unexpected-provider-shape',
diagnostic: 'indeterminate: unexpected-provider-shape',
};
}
function runGit(run: CommandRunner, identity: string, args: readonly string[]): CommandResult {
return run({ program: 'git', args, env: commandEnv(identity) });
}
export function collectBrainDoctorReport(
input: {
readonly registrySource: string;
readonly targetGitUrl: string;
readonly brainNamespace: string;
readonly identity: string;
readonly root: string;
},
run: CommandRunner,
): DoctorRuntimeReport {
const target = deriveBrainTarget(input.registrySource, input.targetGitUrl, input.brainNamespace);
const validation = run({
program: 'mosaic',
args: [
'cred',
'validate',
input.identity,
'--estate',
target.estate,
'--host',
target.host,
'--repo',
target.repo,
'--require',
'write',
'--json',
],
env: commandEnv(input.identity),
});
let access = assessCredentialResult(validation.stdout, {
identity: input.identity,
estate: target.estate,
host: target.host,
repo: target.repo,
});
if (validation.status !== access.exitCode) access = integrationFailure();
const rootExists = existsSync(input.root);
let gitRepository = false;
let remote: string | null = null;
let branch: string | null = null;
let dirty: boolean | null = null;
if (rootExists) {
const repository = runGit(run, input.identity, [
'-C',
input.root,
'rev-parse',
'--is-inside-work-tree',
]);
gitRepository = repository.status === 0 && repository.stdout.trim() === 'true';
if (gitRepository) {
const remoteResult = runGit(run, input.identity, [
'-C',
input.root,
'remote',
'get-url',
'origin',
]);
const branchResult = runGit(run, input.identity, [
'-C',
input.root,
'branch',
'--show-current',
]);
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;
}
}
const observation: BrainDoctorObservation = {
rootExists,
gitRepository,
remote,
branch,
dirty,
access,
};
const refusalMarker = `refused reason=${access.reasonCode}`;
const refusalObserved =
validation.status === 10 &&
access.outcome === 'refused' &&
access.reasonCode === 'no-token-for-identity' &&
validation.stderr.includes(refusalMarker);
return {
findings: evaluateBrainDoctor(observation, target.cloneUrl),
access,
refusalControl: {
observed: refusalObserved,
reasonCode: refusalObserved ? access.reasonCode : null,
},
};
}
export function collectBrainRefusalControl(
input: {
readonly registrySource: string;
readonly targetGitUrl: string;
readonly brainNamespace: string;
readonly refusalIdentity: string;
},
run: CommandRunner,
): BrainRefusalControlResult {
const target = deriveBrainTarget(input.registrySource, input.targetGitUrl, input.brainNamespace);
if (!/^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(input.refusalIdentity)) {
return {
observed: false,
reasonCode: 'permission-evidence-disagrees',
gitReasonCode: 'invalid-control-identity',
apiReasonCode: 'invalid-control-identity',
};
}
const apiResult = run({
program: 'mosaic',
args: [
'cred',
'validate',
input.refusalIdentity,
'--estate',
target.estate,
'--host',
target.host,
'--repo',
target.repo,
'--require',
'write',
'--json',
],
env: commandEnv(input.refusalIdentity),
});
let api = assessCredentialResult(apiResult.stdout, {
identity: input.refusalIdentity,
estate: target.estate,
host: target.host,
repo: target.repo,
});
if (apiResult.status !== api.exitCode) api = integrationFailure();
const gitResult = runGit(run, input.refusalIdentity, ['ls-remote', target.cloneUrl, 'HEAD']);
const marker = /(?:^|\s)reason=([a-z0-9-]+)(?:\s|$)/.exec(gitResult.stderr)?.[1];
const stableRefusals = new Set([
'identity-required',
'estate-required',
'estate-host-mismatch',
'cross-estate-resolution',
'no-token-for-identity',
'tea-login-missing',
'tea-login-host-mismatch',
'provider-identity-mismatch',
'credential-rejected',
'permission-denied',
'organization-membership-required',
'team-membership-required',
]);
const gitReasonCode =
gitResult.status === 0
? 'transport-accepted'
: marker !== undefined && stableRefusals.has(marker) && gitResult.stdout.length === 0
? marker
: 'transport-indeterminate';
const observed =
api.outcome === 'refused' &&
gitReasonCode !== 'transport-accepted' &&
gitReasonCode !== 'transport-indeterminate' &&
gitReasonCode === api.reasonCode;
return {
observed,
reasonCode: observed ? api.reasonCode : 'permission-evidence-disagrees',
gitReasonCode,
apiReasonCode: api.reasonCode,
};
}
export function repairBrainDoctor(
input: {
readonly registrySource: string;
readonly targetGitUrl: string;
readonly brainNamespace: string;
readonly identity: string;
readonly root: string;
},
run: CommandRunner,
): DoctorRuntimeReport {
const target = deriveBrainTarget(input.registrySource, input.targetGitUrl, input.brainNamespace);
let report = collectBrainDoctorReport(input, run);
const actions = planBrainDoctorFix({
findings: report.findings,
target,
identity: input.identity,
root: input.root,
});
for (const action of actions) {
if (action.program === 'mosaic') {
run({ program: 'mosaic', args: action.args, env: commandEnv(input.identity) });
report = collectBrainDoctorReport(input, run);
if (report.access.outcome !== 'ok') return report;
continue;
}
const result = runGit(run, input.identity, action.args);
if (result.status !== 0) return collectBrainDoctorReport(input, run);
}
return collectBrainDoctorReport(input, run);
}
function requireSuccess(result: CommandResult, operation: string): void {
if (result.status !== 0) throw new Error(`${operation}-failed`);
}
function containedRelative(root: string, path: string): string {
if (isAbsolute(path) === false) throw new Error('brain-publish-path-must-be-absolute');
const absoluteRoot = resolve(root);
const absolutePath = resolve(path);
if (absolutePath === absoluteRoot || !absolutePath.startsWith(`${absoluteRoot}${sep}`)) {
throw new Error('brain-publish-path-escaped-root');
}
return relative(absoluteRoot, absolutePath).split(sep).join('/');
}
export function publishBrainPaths(
input: {
readonly root: string;
readonly identity: string;
readonly paths: readonly string[];
readonly message: string;
},
run: CommandRunner,
): PublishEvidence {
if (input.paths.length === 0) throw new Error('brain-publish-paths-empty');
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');
const value = result.stdout.trim();
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 push = runGit(run, input.identity, ['-C', input.root, 'push', 'origin', 'HEAD:main']);
if (push.status === 0) {
pushed = true;
break;
}
const concurrentUpdate = /non-fast-forward|fetch first|\[rejected\]/i.test(push.stderr);
if (!concurrentUpdate || attempt === 2) throw new Error('brain-git-push-failed');
requireSuccess(
runGit(run, input.identity, ['-C', input.root, 'fetch', 'origin', 'main']),
'brain-git-fetch-concurrent',
);
requireSuccess(
runGit(run, input.identity, ['-C', input.root, 'rebase', 'origin/main']),
'brain-git-rebase-concurrent',
);
commit = readHead();
}
if (!pushed) throw new Error('brain-git-push-failed');
requireSuccess(
runGit(run, input.identity, ['-C', input.root, 'fetch', 'origin', 'main']),
'brain-git-fetch-readback',
);
const reachableResult = runGit(run, input.identity, [
'-C',
input.root,
'merge-base',
'--is-ancestor',
commit,
'origin/main',
]);
if (reachableResult.status !== 0 && reachableResult.status !== 1) {
throw new Error('brain-git-reachability-check-failed');
}
const remoteResult = runGit(run, input.identity, ['-C', input.root, 'rev-parse', 'origin/main']);
requireSuccess(remoteResult, 'brain-git-read-remote-head');
const remoteHead = remoteResult.stdout.trim();
if (!COMMIT.test(remoteHead)) throw new Error('brain-git-remote-head-shape-invalid');
return { commit, remoteHead, reachable: reachableResult.status === 0 };
}
+341 -64
View File
@@ -1,5 +1,14 @@
import { afterEach, describe, expect, it } from 'vitest';
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import {
existsSync,
mkdtempSync,
mkdirSync,
readFileSync,
readdirSync,
rmSync,
symlinkSync,
writeFileSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
@@ -7,7 +16,7 @@ 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
* MC-CRED v1.5 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.
*/
@@ -32,10 +41,23 @@ interface ResolverParityAssessment extends CredentialAssessment {
readonly apiReasonCode: string;
}
interface MigrationOwner {
readonly name: string;
readonly kind: 'active-lane' | 'durable-team' | 'durable-human' | 'durable-queue';
readonly validated: boolean;
interface MigrationOwnerResolution {
readonly verdict: 'resolved' | 'refused' | 'not-measured';
readonly reasonCode: string;
readonly principal: {
readonly name: string;
readonly kind:
| 'active-lane'
| 'durable-team'
| 'durable-human'
| 'durable-queue'
| 'mission-seat';
} | null;
readonly authority: {
readonly system: 'gitea' | 'glpi' | 'mosaic-mission-state';
readonly endpoint: string;
readonly contentType: 'application/json';
} | null;
}
interface MigrationCandidate {
@@ -49,7 +71,7 @@ interface MigrationPlan {
readonly status: 'ready' | 'blocked';
readonly candidates: readonly MigrationCandidate[];
readonly reported: readonly { path: string; reason: string }[];
readonly owner: MigrationOwner | null;
readonly owner: MigrationOwnerResolution['principal'];
}
interface MigrationPublishEvidence {
@@ -93,18 +115,24 @@ interface BrainWritePolicy {
}
interface BrainStoreModule {
deriveBrainTarget(registrySource: string, targetGitUrl: string): BrainTarget;
deriveBrainTarget(
registrySource: string,
targetGitUrl: string,
brainNamespace: 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;
discoverBrainMigration(
input: {
readonly sourceRoot: string;
readonly brainRoot: string;
readonly seat: string;
readonly lane: string;
readonly laneActive: boolean;
},
resolveOwner?: (lane: string) => MigrationOwnerResolution,
): MigrationPlan;
migrateBrainState(
plan: MigrationPlan,
publish: (brainRoot: string, paths: readonly string[]) => MigrationPublishEvidence,
@@ -182,6 +210,11 @@ function credentialResult(
outcome: CredentialAssessment['outcome'],
reasonCode: string,
message = 'non-secret diagnostic',
providerLogin: string | null = outcome === 'ok'
? 'external-seat'
: reasonCode === 'provider-identity-mismatch'
? 'Mos'
: null,
): string {
const exits = { ok: 0, refused: 10, error: 20, indeterminate: 30 } as const;
return JSON.stringify({
@@ -199,9 +232,41 @@ function credentialResult(
mutation: 'none',
reason: { code: reasonCode, message },
evidence: {
providerIdentity: null,
repositoryPermission: null,
writeDifferential: null,
providerIdentity:
providerLogin === null
? null
: {
login: providerLogin,
endpoint: 'GET /api/v1/user',
contentType: 'application/json',
},
repositoryPermission:
outcome === 'ok'
? {
requested: 'write',
effective: 'write',
endpoint: 'GET /api/v1/repos/mosaicstack/mosaic-brain',
contentType: 'application/json',
}
: null,
writeDifferential:
outcome === 'ok'
? {
state: 'can-write',
credentialBinding: 'same-resolution',
transportPrincipal: 'external-seat',
authenticatedReceivePack: 'advertised',
readOnlyControl: {
identity: 'homelab-read-control',
providerPermission: 'read',
receivePack: 'refused',
},
unauthenticatedReceivePack: 'refused',
artifactCreated: false,
proves: 'non-secret evidence',
doesNotProve: 'branch update acceptance',
}
: null,
},
audit: { journalId: 'opaque', state: 'sealed' },
});
@@ -218,13 +283,17 @@ describe('R2/Q1 — estate and brain discovery have one authority', (): void =>
const sut = await loadSut('MB-REQ-02 target-host estate derivation');
expect(
sut.deriveBrainTarget(registry(), 'https://git.mosaicstack.dev/mosaicstack/stack.git'),
sut.deriveBrainTarget(
registry(),
'https://git.mosaicstack.dev/mosaicstack/stack.git',
'durable-owner',
),
).toEqual({
estate: 'homelab',
host: 'git.mosaicstack.dev',
owner: 'mosaicstack',
repo: 'mosaicstack/mosaic-brain',
cloneUrl: 'https://git.mosaicstack.dev/mosaicstack/mosaic-brain.git',
owner: 'durable-owner',
repo: 'durable-owner/mosaic-brain',
cloneUrl: 'https://git.mosaicstack.dev/durable-owner/mosaic-brain.git',
});
});
@@ -234,7 +303,11 @@ describe('R2/Q1 — estate and brain discovery have one authority', (): void =>
process.env['MOSAIC_ESTATE'] = 'homelab';
try {
expect(() =>
sut.deriveBrainTarget(registry(), 'https://unmapped.example.invalid/acme/stack.git'),
sut.deriveBrainTarget(
registry(),
'https://unmapped.example.invalid/acme/stack.git',
'durable-owner',
),
).toThrow(/estate-host-unmapped/);
} finally {
if (previous === undefined) delete process.env['MOSAIC_ESTATE'];
@@ -252,11 +325,41 @@ describe('R6 — brain layout refuses secret material', (): void => {
for (const directory of ['agents', 'lanes', 'board', 'specs', 'methods', 'archives']) {
expect(existsSync(join(root, directory)), directory).toBe(true);
expect(
existsSync(join(root, directory, '.gitkeep')),
`${directory} tracked placeholder`,
).toBe(true);
}
const rules = readFileSync(join(root, '.gitignore'), 'utf8').trim().split('\n');
expect(rules).toEqual(['*.token', '*.key', '*.pem', '.env', 'credentials.json']);
});
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();
const brain = join(root, 'brain');
const outside = join(root, 'outside');
mkdirSync(brain);
mkdirSync(outside);
symlinkSync(outside, join(brain, 'agents'));
expect(() => sut.createBrainSkeleton(brain)).toThrow(/brain-layout-directory-unsafe/);
expect(existsSync(join(outside, '.gitkeep'))).toBe(false);
});
it('refuses a symlinked gitignore instead of reading and tracking its external target', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-06 no-follow gitignore');
const root = tempRoot();
const brain = join(root, 'brain');
const outside = join(root, 'outside-secret');
mkdirSync(brain);
writeFileSync(outside, 'DO-NOT-TRACK\n');
symlinkSync(outside, join(brain, '.gitignore'));
expect(() => sut.createBrainSkeleton(brain)).toThrow(/brain-layout-ignore-unsafe/);
expect(readFileSync(outside, 'utf8')).toBe('DO-NOT-TRACK\n');
});
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';
@@ -270,14 +373,16 @@ describe('R6 — brain layout refuses secret material', (): void => {
});
});
describe('credential caller contract v1.3 terminal classes', (): void => {
describe('credential caller contract v1.5 terminal classes', (): void => {
it.each([
['ok', 0, 'grant-verified'],
['ok', 0, 'validation-verified'],
['refused', 10, 'no-token-for-identity'],
['refused', 10, 'provider-identity-mismatch'],
['error', 20, 'estate-registry-invalid'],
['indeterminate', 30, 'provider-unavailable'],
['indeterminate', 30, 'identity-not-found'],
['indeterminate', 30, 'credential-rejected'],
['indeterminate', 30, 'identity-not-visible'],
['indeterminate', 30, 'identity-not-measured'],
['refused', 10, 'credential-rejected'],
] as const)(
'preserves %s/%i and stable reason %s without parsing prose',
async (outcome, exitCode, reasonCode): Promise<void> => {
@@ -293,7 +398,7 @@ describe('credential caller contract v1.3 terminal classes', (): void => {
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<
const malformed = JSON.parse(credentialResult('ok', 'validation-verified')) as Record<
string,
unknown
>;
@@ -305,6 +410,66 @@ describe('credential caller contract v1.3 terminal classes', (): void => {
expect(result.exitCode).toBe(30);
expect(result.reasonCode).toBe('unexpected-provider-shape');
});
it('makes an apparent write ok indeterminate when the side-effect-free differential is absent', async (): Promise<void> => {
const sut = await loadSut('AC-MB-01 required write differential');
const raw = JSON.parse(credentialResult('ok', 'validation-verified')) as {
evidence: { writeDifferential: unknown };
};
raw.evidence.writeDifferential = null;
const result = sut.assessCredentialResult(JSON.stringify(raw));
expect(result).toMatchObject({
outcome: 'indeterminate',
exitCode: 30,
reasonCode: 'readback-missing',
});
});
it('rejects the superseded indeterminate/credential-rejected pairing from before the v1.4 correction', async (): Promise<void> => {
const sut = await loadSut('AC-MB-01 credential-rejected stable class');
const legacy = JSON.parse(credentialResult('indeterminate', 'provider-unavailable')) as Record<
string,
unknown
>;
legacy['reason'] = {
code: 'credential-rejected',
message: 'superseded classification',
};
const result = sut.assessCredentialResult(JSON.stringify(legacy));
expect(result.outcome).toBe('indeterminate');
expect(result.exitCode).toBe(30);
expect(result.reasonCode).toBe('unexpected-provider-shape');
});
it('preserves scope-limited identity as not-measured rather than a dead credential refusal', async (): Promise<void> => {
const sut = await loadSut('AC-MB-01 scope-limited identity is not dead credential');
const result = sut.assessCredentialResult(
credentialResult('indeterminate', 'identity-not-measured'),
);
expect(result).toMatchObject({
outcome: 'indeterminate',
exitCode: 30,
reasonCode: 'identity-not-measured',
});
});
it('refuses an apparent ok whose provider /user read-back names a different principal', async (): Promise<void> => {
const sut = await loadSut('AC-MB-01 provider identity MISMATCH is first-class');
const result = sut.assessCredentialResult(
credentialResult('ok', 'validation-verified', 'looks successful', 'Mos'),
);
expect(result.outcome).toBe('refused');
expect(result.exitCode).toBe(10);
expect(result.reasonCode).toBe('provider-identity-mismatch');
});
});
describe('R5 — out-of-estate refusal must agree on both resolver axes', (): void => {
@@ -324,7 +489,7 @@ describe('R5 — out-of-estate refusal must agree on both resolver axes', (): vo
it.each([
[
credentialResult('refused', 'no-token-for-identity'),
credentialResult('ok', 'grant-verified'),
credentialResult('ok', 'validation-verified'),
],
[
credentialResult('refused', 'no-token-for-identity'),
@@ -346,8 +511,19 @@ describe('R5 — out-of-estate refusal must agree on both resolver axes', (): vo
});
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 activeLaneOwner = (): MigrationOwnerResolution => ({
verdict: 'resolved',
reasonCode: 'active-lane-owner-verified',
principal: { name: 'lane:lane-a', kind: 'active-lane' },
authority: {
system: 'mosaic-mission-state',
endpoint: 'file:///var/lib/mosaic/missions/lane-a.json',
contentType: 'application/json',
},
});
it('uses an injected authoritative owner resolver for unit mechanics without claiming live owner validation', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-07 injected owner-resolver seam');
const root = tempRoot();
const sourceRoot = join(root, 'local-memory');
const brainRoot = join(root, 'brain');
@@ -357,16 +533,13 @@ describe('R7 — migration is non-destructive, append-only, and explicit', (): v
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 },
});
const plan = sut.discoverBrainMigration(
{ sourceRoot, brainRoot, seat: 'seat-a', lane: 'lane-a', laneActive: true },
activeLaneOwner,
);
expect(plan.status).toBe('ready');
expect(plan.owner).toEqual({ name: 'lane:lane-a', kind: 'active-lane' });
expect(plan.candidates.map((candidate) => candidate.kind).sort()).toEqual(['lane', 'seat']);
expect(plan.reported).toEqual(
expect.arrayContaining([
@@ -375,20 +548,22 @@ describe('R7 — migration is non-destructive, append-only, and explicit', (): v
);
});
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');
it('ignores a caller-asserted owner string/validated flag and leaves the gate blocking', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-09 caller owner assertion cannot satisfy 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({
const callerAssertion = {
sourceRoot,
brainRoot: join(root, 'brain'),
seat: 'seat-a',
lane: 'closed-lane',
laneActive: false,
});
owner: { name: 'some-string', kind: 'durable-team', validated: true },
};
const plan = sut.discoverBrainMigration(callerAssertion);
expect(plan.status).toBe('blocked');
expect(plan.candidates).toHaveLength(0);
@@ -399,6 +574,74 @@ describe('R7 — migration is non-destructive, append-only, and explicit', (): v
);
});
it.each([
['mission seat', { name: 'user:be-coder-07', kind: 'mission-seat' }],
['unicode dash', { name: 'team:platformmaintainers', kind: 'durable-team' }],
['padded', { name: ' team:platform-maintainers ', kind: 'durable-team' }],
['dot presentation', { name: 'team:platform.maintainers', kind: 'durable-team' }],
['space presentation', { name: 'team:platform maintainers', kind: 'durable-team' }],
] as const)(
'rejects %s owner evidence through the injected resolver allowlist',
async (_caseName, principal): Promise<void> => {
const sut = await loadSut('MB-REQ-09 durable-owner allowlist');
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');
let resolverCalls = 0;
const plan = sut.discoverBrainMigration(
{
sourceRoot,
brainRoot: join(root, 'brain'),
seat: 'seat-a',
lane: 'closed-lane',
laneActive: false,
},
(): MigrationOwnerResolution => {
resolverCalls += 1;
return {
verdict: 'resolved',
reasonCode: 'owner-verified',
principal,
authority: {
system: 'gitea',
endpoint: 'GET /api/v1/teams/1',
contentType: 'application/json',
},
};
},
);
expect(resolverCalls).toBe(1);
expect(plan.status).toBe('blocked');
expect(plan.candidates).toHaveLength(0);
},
);
it('reports secret-shaped legacy files without ever copying them into the brain', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-06 migration secret exclusion');
const root = tempRoot();
const sourceRoot = join(root, 'local-memory');
const laneRoot = join(sourceRoot, 'lanes', 'lane-a');
const brainRoot = join(root, 'brain');
mkdirSync(laneRoot, { recursive: true });
for (const name of ['access.token', 'private.key', 'client.pem', '.env', 'credentials.json']) {
writeFileSync(join(laneRoot, name), 'DO-NOT-MIGRATE\n');
}
const plan = sut.discoverBrainMigration(
{ sourceRoot, brainRoot, seat: 'seat-a', lane: 'lane-a', laneActive: true },
activeLaneOwner,
);
expect(plan.status).toBe('ready');
expect(plan.candidates).toHaveLength(0);
expect(plan.reported).toHaveLength(5);
expect(plan.reported.every((entry) => /secret/i.test(entry.reason))).toBe(true);
expect(existsSync(brainRoot)).toBe(false);
});
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();
@@ -406,14 +649,10 @@ describe('R7 — migration is non-destructive, append-only, and explicit', (): v
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 plan = sut.discoverBrainMigration(
{ sourceRoot, brainRoot, seat: 'seat-a', lane: 'lane-a', laneActive: true },
activeLaneOwner,
);
const candidate = plan.candidates[0];
expect(candidate).toBeDefined();
if (candidate === undefined) return;
@@ -447,6 +686,39 @@ describe('R7 — migration is non-destructive, append-only, and explicit', (): v
expect(publishedPaths).toContain(candidate.archive);
});
it('refuses nested symlink destinations without copying a migration outside the brain', async (): Promise<void> => {
const sut = await loadSut('MB-REQ-07 migration destination no-follow');
const root = tempRoot();
const sourceRoot = join(root, 'local-memory');
const brainRoot = join(root, 'brain');
const outside = join(root, 'outside');
mkdirSync(join(sourceRoot, 'lanes', 'lane-a'), { recursive: true });
mkdirSync(join(brainRoot, 'lanes'), { recursive: true });
mkdirSync(outside);
const source = join(sourceRoot, 'lanes', 'lane-a', 'finding.md');
writeFileSync(source, 'lane state\n');
symlinkSync(outside, join(brainRoot, 'lanes', 'lane-a'));
const plan = sut.discoverBrainMigration(
{ sourceRoot, brainRoot, seat: 'seat-a', lane: 'lane-a', laneActive: true },
activeLaneOwner,
);
let publishCalls = 0;
const result = sut.migrateBrainState(
plan,
(): MigrationPublishEvidence => {
publishCalls += 1;
return { commit: 'a'.repeat(40), remoteHead: 'a'.repeat(40), reachable: true };
},
brainRoot,
);
expect(result.status).toBe('failed');
expect(readFileSync(source, 'utf8')).toBe('lane state\n');
expect(readdirSync(outside)).toEqual([]);
expect(publishCalls).toBe(0);
});
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();
@@ -455,14 +727,10 @@ describe('R7 — migration is non-destructive, append-only, and explicit', (): v
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 plan = sut.discoverBrainMigration(
{ sourceRoot, brainRoot, seat: 'seat-a', lane: 'lane-a', laneActive: true },
activeLaneOwner,
);
const result = sut.migrateBrainState(
plan,
@@ -476,6 +744,9 @@ describe('R7 — migration is non-destructive, append-only, and explicit', (): v
expect(result.status).toBe('failed');
expect(readFileSync(source, 'utf8')).toBe('seat state\n');
expect(plan.candidates[0]).toBeDefined();
expect(existsSync(plan.candidates[0]!.destination)).toBe(true);
expect(existsSync(plan.candidates[0]!.archive)).toBe(true);
expect(result.reported).toEqual(
expect.arrayContaining([
expect.objectContaining({ reason: expect.stringMatching(/reachab/i) }),
@@ -496,11 +767,13 @@ describe('R8 — doctor diagnoses defects and fixes only through approved seams'
remote: null,
branch: null,
dirty: null,
access: null,
access: sut.assessCredentialResult(credentialResult('refused', 'no-token-for-identity')),
},
expected,
);
expect(missing.map((finding) => finding.code)).toContain('brain-clone-missing');
expect(missing.map((finding) => finding.code)).toEqual(
expect.arrayContaining(['brain-clone-missing', 'brain-write-access-refused']),
);
const defects = sut.evaluateBrainDoctor(
{
@@ -529,6 +802,7 @@ describe('R8 — doctor diagnoses defects and fixes only through approved seams'
const target = sut.deriveBrainTarget(
registry(),
'https://git.mosaicstack.dev/mosaicstack/stack.git',
'durable-owner',
);
const findings: BrainDoctorFinding[] = [
{ code: 'brain-clone-missing', repairable: true, reasonCode: null },
@@ -545,7 +819,9 @@ describe('R8 — doctor diagnoses defects and fixes only through approved seams'
});
const rendered = JSON.stringify(actions);
expect(actions.map((action) => action.program)).toEqual(['git', 'git', 'mosaic']);
expect(actions.map((action) => action.program)).toEqual(['mosaic', 'git', 'git']);
expect(actions[0]?.findingCode).toBe('brain-write-access-refused');
expect(actions[1]?.findingCode).toBe('brain-clone-missing');
expect(rendered).toContain('cred');
expect(rendered).toContain('grant');
expect(rendered).toContain('--estate');
@@ -559,6 +835,7 @@ describe('R8 — doctor diagnoses defects and fixes only through approved seams'
const target = sut.deriveBrainTarget(
registry(),
'https://git.mosaicstack.dev/mosaicstack/stack.git',
'durable-owner',
);
for (const finding of [
+955
View File
@@ -0,0 +1,955 @@
import {
closeSync,
constants as fsConstants,
copyFileSync,
existsSync,
fsyncSync,
lstatSync,
linkSync,
mkdirSync,
openSync,
readdirSync,
readFileSync,
renameSync,
rmSync,
unlinkSync,
writeFileSync,
} from 'node:fs';
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
import { createHash, randomUUID } from 'node:crypto';
import { z } from 'zod';
import { parseCredentialEstateRegistry } from '../credentials/estate-registry.js';
const SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
const COMMIT = /^[0-9a-f]{40}$/;
const GITIGNORE_RULES = ['*.token', '*.key', '*.pem', '.env', 'credentials.json'] as const;
const BRAIN_DIRECTORIES = ['agents', 'lanes', 'board', 'specs', 'methods', 'archives'] as const;
const TERMINAL_EXITS = {
ok: 0,
refused: 10,
error: 20,
indeterminate: 30,
} as const;
const STABLE_REASON_CLASSES: Readonly<Record<string, CredentialOutcome>> = {
'identity-required': 'refused',
'estate-required': 'refused',
'estate-host-mismatch': 'refused',
'cross-estate-resolution': 'refused',
'no-token-for-identity': 'refused',
'tea-login-missing': 'refused',
'tea-login-host-mismatch': 'refused',
'provider-identity-mismatch': 'refused',
'credential-rejected': 'refused',
'permission-denied': 'refused',
'organization-membership-required': 'refused',
'team-membership-required': 'refused',
'invalid-input': 'error',
'estate-registry-invalid': 'error',
'insecure-credential-source': 'error',
'journal-unavailable': 'error',
'internal-invariant': 'error',
'provider-unavailable': 'indeterminate',
'identity-not-visible': 'indeterminate',
'identity-not-measured': 'indeterminate',
'identity-not-found': 'indeterminate',
'unexpected-content-type': 'indeterminate',
'unexpected-provider-shape': 'indeterminate',
'scope-not-evaluable': 'indeterminate',
'permission-evidence-disagrees': 'indeterminate',
'transport-principal-mismatch': 'indeterminate',
'read-only-control-invalid': 'indeterminate',
'readback-missing': 'indeterminate',
'mutation-state-unknown': 'indeterminate',
};
export interface BrainTarget {
readonly estate: string;
readonly host: string;
readonly owner: string;
readonly repo: string;
readonly cloneUrl: string;
}
export type CredentialOutcome = keyof typeof TERMINAL_EXITS;
export interface CredentialAssessment {
readonly outcome: CredentialOutcome;
readonly exitCode: 0 | 10 | 20 | 30;
readonly reasonCode: string;
readonly diagnostic: string;
}
export interface ResolverParityAssessment extends CredentialAssessment {
readonly gitReasonCode: string;
readonly apiReasonCode: string;
}
export interface MigrationOwnerResolution {
readonly verdict: 'resolved' | 'refused' | 'not-measured';
readonly reasonCode: string;
readonly principal: {
readonly name: string;
readonly kind:
| 'active-lane'
| 'durable-team'
| 'durable-human'
| 'durable-queue'
| 'mission-seat';
} | null;
readonly authority: {
readonly system: 'gitea' | 'glpi' | 'mosaic-mission-state';
readonly endpoint: string;
readonly contentType: 'application/json';
} | null;
}
export interface MigrationCandidate {
readonly source: string;
readonly destination: string;
readonly archive: string;
readonly kind: 'lane' | 'seat';
}
export interface MigrationReport {
readonly path: string;
readonly reason: string;
}
export interface MigrationPlan {
readonly status: 'ready' | 'blocked';
readonly candidates: readonly MigrationCandidate[];
readonly reported: readonly MigrationReport[];
readonly owner: MigrationOwnerResolution['principal'];
}
export interface MigrationPublishEvidence {
readonly commit: string;
readonly remoteHead: string;
readonly reachable: boolean;
}
export interface MigrationResult {
readonly status: 'migrated' | 'reported' | 'failed';
readonly migrated: readonly MigrationCandidate[];
readonly reported: readonly MigrationReport[];
readonly publish: MigrationPublishEvidence | null;
}
export interface BrainDoctorObservation {
readonly rootExists: boolean;
readonly gitRepository: boolean;
readonly remote: string | null;
readonly branch: string | null;
readonly dirty: boolean | null;
readonly access: CredentialAssessment | null;
}
export interface BrainDoctorFinding {
readonly code: string;
readonly repairable: boolean;
readonly reasonCode: string | null;
}
export interface BrainDoctorAction {
readonly program: 'git' | 'mosaic';
readonly args: readonly string[];
readonly findingCode: string;
}
export interface BrainWritePolicy {
readonly allowed: boolean;
readonly mode: 'append-only' | 'single-writer' | 'seat-writer' | 'refused';
readonly reason: string;
}
interface ParsedGitTarget {
readonly host: string;
readonly owner: string;
}
const providerIdentitySchema = z
.object({
login: z.string().min(1),
endpoint: z.literal('GET /api/v1/user'),
contentType: z.string().min(1),
})
.passthrough();
const repositoryPermissionSchema = z
.object({
requested: z.literal('write'),
effective: z.enum(['write', 'admin']),
endpoint: z.string().min(1),
contentType: z.string().min(1),
})
.passthrough();
const writeDifferentialSchema = z
.object({
state: z.literal('can-write'),
credentialBinding: z.literal('same-resolution'),
transportPrincipal: z.string().min(1),
authenticatedReceivePack: z.literal('advertised'),
readOnlyControl: z
.object({
identity: z.string().min(1),
providerPermission: z.literal('read'),
receivePack: z.literal('refused'),
})
.passthrough(),
unauthenticatedReceivePack: z.literal('refused'),
artifactCreated: z.literal(false),
proves: z.string().min(1),
doesNotProve: z.string().min(1),
})
.passthrough();
const credentialResultSchema = z
.object({
schemaVersion: z.literal(1),
operation: z.literal('validate'),
outcome: z.enum(['ok', 'refused', 'error', 'indeterminate']),
exitCode: z.number().int(),
subject: z
.object({
identity: z.string().min(1),
estate: z.string().min(1),
host: z.string().min(1),
repo: z.string().min(1),
})
.strict(),
mutation: z.enum(['none', 'not-started', 'applied', 'unknown']),
reason: z
.object({
code: z.string().min(1),
message: z.string(),
})
.passthrough(),
evidence: z
.object({
providerIdentity: providerIdentitySchema.nullable(),
repositoryPermission: repositoryPermissionSchema.nullable(),
writeDifferential: writeDifferentialSchema.nullable(),
})
.passthrough(),
audit: z
.object({
journalId: z.string().nullable(),
state: z.enum(['not-started', 'open', 'sealed']),
})
.passthrough(),
})
.passthrough();
function safeName(value: string, label: string): string {
if (!SAFE_NAME.test(value)) {
throw new Error(`invalid-${label}`);
}
return value;
}
function parseGitTarget(targetGitUrl: string): ParsedGitTarget {
let host = '';
let pathname = '';
if (/^[^@\s]+@[^:\s]+:.+$/.test(targetGitUrl)) {
const separator = targetGitUrl.indexOf(':');
const authority = targetGitUrl.slice(0, separator);
host = authority.slice(authority.lastIndexOf('@') + 1);
pathname = targetGitUrl.slice(separator + 1);
} else {
let parsed: URL;
try {
parsed = new URL(targetGitUrl);
} catch {
throw new Error('target-git-url-invalid');
}
if (!['https:', 'ssh:'].includes(parsed.protocol) || parsed.password !== '') {
throw new Error('target-git-url-invalid');
}
if (parsed.protocol === 'https:' && parsed.username !== '') {
throw new Error('target-git-url-contains-credential');
}
host = parsed.hostname;
pathname = parsed.pathname;
}
const parts = pathname
.replace(/^\/+/, '')
.replace(/\.git$/, '')
.split('/')
.filter((part: string): boolean => part.length > 0);
if (host.length === 0 || parts.length !== 2) {
throw new Error('target-git-url-invalid');
}
return { host: host.toLowerCase(), owner: safeName(parts[0] ?? '', 'repository-owner') };
}
export function deriveBrainTarget(
registrySource: string,
targetGitUrl: string,
brainNamespace: string,
): BrainTarget {
const target = parseGitTarget(targetGitUrl);
const resolved = parseCredentialEstateRegistry(registrySource).resolveByHost(target.host);
if (resolved === undefined) {
throw new Error(`estate-host-unmapped: ${target.host}`);
}
const owner = safeName(brainNamespace, 'brain-namespace');
const repo = `${owner}/mosaic-brain`;
return {
estate: resolved.estate,
host: target.host,
owner,
repo,
cloneUrl: `${resolved.host.apiBaseUrl}/${repo}.git`,
};
}
function syncFile(path: string): void {
const descriptor = openSync(path, 'r');
try {
fsyncSync(descriptor);
} finally {
closeSync(descriptor);
}
}
export function createBrainSkeleton(root: string): { readonly created: readonly string[] } {
const created: string[] = [];
if (existsSync(root)) {
const rootStatus = lstatSync(root);
if (!rootStatus.isDirectory() || rootStatus.isSymbolicLink()) {
throw new Error('brain-layout-root-unsafe');
}
}
for (const directory of BRAIN_DIRECTORIES) {
const path = join(root, directory);
if (existsSync(path)) {
const status = lstatSync(path);
if (!status.isDirectory() || status.isSymbolicLink()) {
throw new Error('brain-layout-directory-unsafe');
}
}
}
mkdirSync(root, { recursive: true });
for (const directory of BRAIN_DIRECTORIES) {
const path = join(root, directory);
if (!existsSync(path)) {
mkdirSync(path, { recursive: true });
created.push(path);
}
const placeholder = join(path, '.gitkeep');
if (!existsSync(placeholder)) {
writeFileSync(placeholder, '', { encoding: 'utf8', mode: 0o644, flag: 'wx' });
syncFile(placeholder);
created.push(placeholder);
} else if (!lstatSync(placeholder).isFile() || lstatSync(placeholder).isSymbolicLink()) {
throw new Error('brain-layout-placeholder-unsafe');
}
}
const ignorePath = join(root, '.gitignore');
if (existsSync(ignorePath)) {
const ignoreStatus = lstatSync(ignorePath);
if (!ignoreStatus.isFile() || ignoreStatus.isSymbolicLink()) {
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 temporary = `${ignorePath}.tmp-${process.pid}-${randomUUID()}`;
writeFileSync(temporary, content, { encoding: 'utf8', mode: 0o644, flag: 'wx' });
syncFile(temporary);
renameSync(temporary, ignorePath);
created.push(ignorePath);
}
return { created };
}
function indeterminate(reasonCode: string): CredentialAssessment {
return {
outcome: 'indeterminate',
exitCode: 30,
reasonCode,
diagnostic: `indeterminate: ${reasonCode}`,
};
}
export function assessCredentialResult(
source: string,
expectedSubject?: {
readonly identity: string;
readonly estate: string;
readonly host: string;
readonly repo: string;
},
): CredentialAssessment {
let raw: unknown;
try {
raw = JSON.parse(source);
} catch {
return indeterminate('unexpected-provider-shape');
}
const parsed = credentialResultSchema.safeParse(raw);
if (!parsed.success) return indeterminate('unexpected-provider-shape');
if (
expectedSubject !== undefined &&
(parsed.data.subject.identity !== expectedSubject.identity ||
parsed.data.subject.estate !== expectedSubject.estate ||
parsed.data.subject.host !== expectedSubject.host ||
parsed.data.subject.repo !== expectedSubject.repo)
) {
return indeterminate('unexpected-provider-shape');
}
const expectedExit = TERMINAL_EXITS[parsed.data.outcome];
if (parsed.data.exitCode !== expectedExit) {
return indeterminate('unexpected-provider-shape');
}
const stableClass = STABLE_REASON_CLASSES[parsed.data.reason.code];
if (stableClass !== undefined && stableClass !== parsed.data.outcome) {
return indeterminate('unexpected-provider-shape');
}
const identity = parsed.data.evidence.providerIdentity;
if (identity !== null) {
if (!identity.contentType.toLowerCase().startsWith('application/json')) {
return indeterminate('unexpected-content-type');
}
if (identity.login !== parsed.data.subject.identity) {
return {
outcome: 'refused',
exitCode: 10,
reasonCode: 'provider-identity-mismatch',
diagnostic: 'refused: provider-identity-mismatch',
};
}
}
if (parsed.data.outcome === 'ok') {
const permission = parsed.data.evidence.repositoryPermission;
const differential = parsed.data.evidence.writeDifferential;
if (
identity === null ||
permission === null ||
differential === null ||
!permission.contentType.toLowerCase().startsWith('application/json') ||
differential.transportPrincipal !== parsed.data.subject.identity ||
parsed.data.mutation !== 'none' ||
parsed.data.audit.state !== 'sealed' ||
parsed.data.audit.journalId === null
) {
return indeterminate('readback-missing');
}
}
if (
parsed.data.reason.code === 'provider-identity-mismatch' &&
(identity === null || identity.login === parsed.data.subject.identity)
) {
return indeterminate('unexpected-provider-shape');
}
return {
outcome: parsed.data.outcome,
exitCode: expectedExit,
reasonCode: parsed.data.reason.code,
diagnostic: `${parsed.data.outcome}: ${parsed.data.reason.code}`,
};
}
export function assessResolverParity(
gitSource: string,
apiSource: string,
): ResolverParityAssessment {
const git = assessCredentialResult(gitSource);
const api = assessCredentialResult(apiSource);
if (git.outcome === 'refused' && api.outcome === 'refused' && git.reasonCode === api.reasonCode) {
return {
...git,
gitReasonCode: git.reasonCode,
apiReasonCode: api.reasonCode,
};
}
return {
...indeterminate('permission-evidence-disagrees'),
gitReasonCode: git.reasonCode,
apiReasonCode: api.reasonCode,
};
}
function filesBelow(root: string): string[] {
if (!existsSync(root)) return [];
const result: string[] = [];
const walk = (directory: string): void => {
for (const entry of readdirSync(directory, { withFileTypes: true })) {
const path = join(directory, entry.name);
if (entry.isDirectory()) walk(path);
else if (entry.isFile()) result.push(path);
else result.push(path);
}
};
walk(root);
return result.sort((left: string, right: string): number => left.localeCompare(right));
}
function ownerIsValid(
resolution: MigrationOwnerResolution,
lane: string,
laneActive: boolean,
): boolean {
if (
resolution.verdict !== 'resolved' ||
resolution.principal === null ||
resolution.authority === null ||
resolution.authority.endpoint.length === 0 ||
resolution.authority.contentType !== 'application/json'
) {
return false;
}
const principal = resolution.principal;
const normalized = principal.name.normalize('NFKC');
if (normalized !== principal.name) return false;
const grammars: Readonly<Record<string, RegExp>> = {
'active-lane': /^lane:[a-z0-9][a-z0-9-]*$/,
'durable-team': /^team:[a-z0-9][a-z0-9-]*$/,
'durable-human': /^user:[a-z0-9][a-z0-9-]*$/,
'durable-queue': /^queue:[a-z0-9][a-z0-9-]*$/,
};
const grammar = grammars[principal.kind];
if (grammar === undefined || !grammar.test(normalized)) return false;
if (principal.kind === 'active-lane') {
return laneActive && principal.name === `lane:${lane}`;
}
return true;
}
function migrationDigest(sourceRoot: string, path: string): string {
const stat = lstatSync(path);
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('migration-source-not-regular');
const key = relative(sourceRoot, path).split(sep).join('/');
return createHash('sha256').update(key).update('\0').update(readFileSync(path)).digest('hex');
}
function migrationCandidate(
sourceRoot: string,
brainRoot: string,
path: string,
kind: 'lane' | 'seat',
seat: string,
lane: string,
): MigrationCandidate {
const digest = migrationDigest(sourceRoot, path).slice(0, 16);
const name = `${digest}-${basename(path)}`;
const destination =
kind === 'lane'
? join(brainRoot, 'lanes', lane, 'findings', 'imports', name)
: join(brainRoot, 'agents', seat, 'state', 'imports', name);
return {
source: path,
destination,
archive: join(brainRoot, 'archives', 'imports', kind, name),
kind,
};
}
function secretShapedPath(path: string): boolean {
const name = basename(path);
return (
name === '.env' ||
name === 'credentials.json' ||
name.endsWith('.token') ||
name.endsWith('.key') ||
name.endsWith('.pem')
);
}
function reportAll(paths: readonly string[], reason: string): MigrationReport[] {
return paths.map((path: string): MigrationReport => ({ path, reason }));
}
export function discoverBrainMigration(
input: {
readonly sourceRoot: string;
readonly brainRoot: string;
readonly seat: string;
readonly lane: string;
readonly laneActive: boolean;
},
resolveOwner?: (lane: string) => MigrationOwnerResolution,
): MigrationPlan {
const seat = safeName(input.seat, 'seat');
const lane = safeName(input.lane, 'lane');
const laneRoot = join(input.sourceRoot, 'lanes', lane);
const seatRoot = join(input.sourceRoot, 'agents', seat);
const laneFiles = filesBelow(laneRoot);
const seatFiles = filesBelow(seatRoot);
const selected = new Set([...laneFiles, ...seatFiles]);
const all = filesBelow(input.sourceRoot);
const unsupported = all.filter((path: string): boolean => !selected.has(path));
let ownerResolution: MigrationOwnerResolution = {
verdict: 'not-measured',
reasonCode: 'owner-resolver-unavailable',
principal: null,
authority: null,
};
if (resolveOwner !== undefined) {
try {
ownerResolution = resolveOwner(lane);
} catch {
ownerResolution = {
verdict: 'not-measured',
reasonCode: 'owner-resolver-failed',
principal: null,
authority: null,
};
}
}
if (!ownerIsValid(ownerResolution, lane, input.laneActive)) {
return {
status: 'blocked',
candidates: [],
reported: reportAll(
all,
'Migration requires a source-of-truth-resolved named durable owner; caller assertions are not evidence.',
),
owner: null,
};
}
const candidates: MigrationCandidate[] = [];
const reported: MigrationReport[] = reportAll(
unsupported,
'Ownership or supported migration shape was not established; retained and reported.',
);
for (const path of laneFiles) {
if (secretShapedPath(path)) {
reported.push({
path,
reason: 'Secret-shaped state is forbidden in the brain; retained and reported.',
});
continue;
}
try {
candidates.push(
migrationCandidate(input.sourceRoot, input.brainRoot, path, 'lane', seat, lane),
);
} catch {
reported.push({ path, reason: 'Lane state was not a regular file; retained and reported.' });
}
}
for (const path of seatFiles) {
if (secretShapedPath(path)) {
reported.push({
path,
reason: 'Secret-shaped state is forbidden in the brain; retained and reported.',
});
continue;
}
try {
candidates.push(
migrationCandidate(input.sourceRoot, input.brainRoot, path, 'seat', seat, lane),
);
} catch {
reported.push({ path, reason: 'Seat state was not a regular file; retained and reported.' });
}
}
return { status: 'ready', candidates, reported, owner: ownerResolution.principal };
}
function isContained(root: string, path: string): boolean {
const absoluteRoot = resolve(root);
const absolutePath = resolve(path);
return absolutePath === absoluteRoot || absolutePath.startsWith(`${absoluteRoot}${sep}`);
}
function assertSafeDestinationAncestors(root: string, destination: string): void {
if (!isContained(root, destination)) throw new Error('migration-destination-escaped-brain');
if (!existsSync(root)) mkdirSync(root, { recursive: true });
const rootStatus = lstatSync(root);
if (!rootStatus.isDirectory() || rootStatus.isSymbolicLink()) {
throw new Error('migration-destination-ancestor-unsafe');
}
const parts = relative(root, dirname(destination)).split(sep).filter(Boolean);
let cursor = root;
for (const part of parts) {
cursor = join(cursor, part);
if (!existsSync(cursor)) continue;
const status = lstatSync(cursor);
if (!status.isDirectory() || status.isSymbolicLink()) {
throw new Error('migration-destination-ancestor-unsafe');
}
}
}
function copyVerified(source: string, destination: string, brainRoot: string): boolean {
assertSafeDestinationAncestors(brainRoot, destination);
mkdirSync(dirname(destination), { recursive: true });
assertSafeDestinationAncestors(brainRoot, destination);
if (existsSync(destination)) {
const status = lstatSync(destination);
if (!status.isFile() || status.isSymbolicLink()) {
throw new Error('append-only-destination-unsafe');
}
const sourceDigest = createHash('sha256').update(readFileSync(source)).digest('hex');
const destinationDigest = createHash('sha256').update(readFileSync(destination)).digest('hex');
if (sourceDigest !== destinationDigest) throw new Error('append-only-collision');
return false;
}
const temporary = `${destination}.tmp-${process.pid}-${randomUUID()}`;
try {
copyFileSync(source, temporary, fsConstants.COPYFILE_EXCL);
const temporaryStatus = lstatSync(temporary);
if (!temporaryStatus.isFile() || temporaryStatus.isSymbolicLink()) {
throw new Error('migration-copy-target-unsafe');
}
syncFile(temporary);
const sourceDigest = createHash('sha256').update(readFileSync(source)).digest('hex');
const copiedDigest = createHash('sha256').update(readFileSync(temporary)).digest('hex');
if (sourceDigest !== copiedDigest) throw new Error('migration-copy-verification-failed');
assertSafeDestinationAncestors(brainRoot, destination);
linkSync(temporary, destination);
syncFile(destination);
return true;
} finally {
rmSync(temporary, { force: true });
}
}
export function migrateBrainState(
plan: MigrationPlan,
publish: (brainRoot: string, paths: readonly string[]) => MigrationPublishEvidence,
brainRoot: string,
): MigrationResult {
if (plan.status !== 'ready' || plan.candidates.length === 0) {
return {
status: 'reported',
migrated: [],
reported: plan.reported,
publish: null,
};
}
const created: string[] = [];
const published: string[] = [];
let publicationAttempted = false;
let evidence: MigrationPublishEvidence;
try {
for (const candidate of plan.candidates) {
if (
!isContained(brainRoot, candidate.destination) ||
!isContained(brainRoot, candidate.archive)
) {
throw new Error('migration-destination-escaped-brain');
}
if (copyVerified(candidate.source, candidate.destination, brainRoot)) {
created.push(candidate.destination);
}
if (copyVerified(candidate.source, candidate.archive, brainRoot)) {
created.push(candidate.archive);
}
published.push(candidate.destination, candidate.archive);
}
publicationAttempted = true;
evidence = publish(brainRoot, published);
if (!COMMIT.test(evidence.commit) || !COMMIT.test(evidence.remoteHead) || !evidence.reachable) {
throw new Error('remote reachability was not established');
}
} catch (error: unknown) {
// Once publication is attempted its remote mutation state may be unknown.
// Keep the local copies so the checkout does not silently diverge from a
// commit that may already be reachable; sources always remain intact.
if (!publicationAttempted) {
for (const path of created.reverse()) rmSync(path, { force: true });
}
const detail = error instanceof Error ? error.message : 'migration failed';
return {
status: 'failed',
migrated: [],
reported: [
...plan.reported,
...plan.candidates.map(
(candidate: MigrationCandidate): MigrationReport => ({
path: candidate.source,
reason: `Migration retained source: ${detail}`,
}),
),
],
publish: null,
};
}
const migrated: MigrationCandidate[] = [];
const removalReports: MigrationReport[] = [];
for (const candidate of plan.candidates) {
try {
unlinkSync(candidate.source);
migrated.push(candidate);
} catch {
removalReports.push({
path: candidate.source,
reason:
'Published migration is reachable but source cleanup failed; retained and reported.',
});
}
}
return {
status: removalReports.length === 0 ? 'migrated' : 'reported',
migrated,
reported: [...plan.reported, ...removalReports],
publish: evidence,
};
}
function accessFinding(access: CredentialAssessment | null): BrainDoctorFinding | null {
if (access === null) {
return {
code: 'brain-write-access-indeterminate',
repairable: false,
reasonCode: 'readback-missing',
};
}
if (access.outcome === 'ok') return null;
return {
code: `brain-write-access-${access.outcome}`,
repairable:
access.outcome === 'refused' &&
['permission-denied', 'no-token-for-identity'].includes(access.reasonCode),
reasonCode: access.reasonCode,
};
}
export function evaluateBrainDoctor(
observation: BrainDoctorObservation,
expectedRemote: string,
): readonly BrainDoctorFinding[] {
const access = accessFinding(observation.access);
if (!observation.rootExists) {
return [
{ code: 'brain-clone-missing', repairable: true, reasonCode: null },
...(access === null ? [] : [access]),
];
}
const findings: BrainDoctorFinding[] = [];
if (!observation.gitRepository) {
findings.push({ code: 'brain-not-git-repository', repairable: true, reasonCode: null });
return findings;
}
if (observation.remote !== expectedRemote) {
findings.push({ code: 'brain-remote-mismatch', repairable: true, reasonCode: null });
}
if (observation.branch !== 'main') {
findings.push({ code: 'brain-branch-mismatch', repairable: false, reasonCode: null });
}
if (observation.dirty === true) {
findings.push({ code: 'brain-uncommitted-state', repairable: false, reasonCode: null });
}
if (access !== null) findings.push(access);
return findings;
}
export function planBrainDoctorFix(input: {
readonly findings: readonly BrainDoctorFinding[];
readonly target: BrainTarget;
readonly identity: string;
readonly root: string;
}): readonly BrainDoctorAction[] {
safeName(input.identity, 'identity');
const actions: BrainDoctorAction[] = [];
const priority: Readonly<Record<string, number>> = {
'brain-write-access-refused': 0,
'brain-clone-missing': 1,
'brain-remote-mismatch': 2,
};
const ordered = [...input.findings].sort(
(left: BrainDoctorFinding, right: BrainDoctorFinding): number =>
(priority[left.code] ?? 99) - (priority[right.code] ?? 99),
);
for (const finding of ordered) {
if (!finding.repairable) continue;
if (finding.code === 'brain-clone-missing') {
actions.push({
program: 'git',
args: ['clone', '--branch', 'main', '--single-branch', input.target.cloneUrl, input.root],
findingCode: finding.code,
});
} else if (finding.code === 'brain-remote-mismatch') {
actions.push({
program: 'git',
args: ['-C', input.root, 'remote', 'set-url', 'origin', input.target.cloneUrl],
findingCode: finding.code,
});
} else if (finding.code === 'brain-write-access-refused') {
actions.push({
program: 'mosaic',
args: [
'cred',
'grant',
input.identity,
'--estate',
input.target.estate,
'--host',
input.target.host,
'--repo',
input.target.repo,
'--permission',
'write',
'--json',
],
findingCode: finding.code,
});
}
}
return actions;
}
function safeRelativePath(path: string): string[] | null {
if (isAbsolute(path) || path.includes('\\')) return null;
const parts = path.split('/').filter((part: string): boolean => part.length > 0);
if (parts.length === 0 || parts.some((part: string): boolean => part === '.' || part === '..')) {
return null;
}
return parts;
}
export function classifyBrainWrite(input: {
readonly path: string;
readonly actor: string;
readonly seat: string;
readonly boardWriter?: string;
}): BrainWritePolicy {
const parts = safeRelativePath(input.path);
if (parts === null || !SAFE_NAME.test(input.actor) || !SAFE_NAME.test(input.seat)) {
return { allowed: false, mode: 'refused', reason: 'invalid-write-subject' };
}
if (parts[0] === 'lanes' && parts.length >= 3) {
return { allowed: true, mode: 'append-only', reason: 'lane-content-is-findings' };
}
if (parts[0] === 'board') {
if (input.boardWriter !== undefined && input.actor === input.boardWriter) {
return { allowed: true, mode: 'single-writer', reason: 'named-board-writer' };
}
return { allowed: false, mode: 'refused', reason: 'board-writer-mismatch' };
}
if (parts[0] === 'agents' && parts.length >= 3) {
if (parts[1] === input.seat && input.actor === input.seat) {
return { allowed: true, mode: 'seat-writer', reason: 'seat-owned-state' };
}
return { allowed: false, mode: 'refused', reason: 'seat-writer-mismatch' };
}
return { allowed: false, mode: 'refused', reason: 'unsupported-write-path' };
}
+27 -4
View File
@@ -29,6 +29,11 @@ import { readPersonaContractBlock } from '../fleet/persona-contract.js';
import { canonicalizeRoleClass } from './fleet-personas.js';
import { launchClaudex, type ClaudexHarnessAdapter } from './claudex.js';
import { runLeaseEnforcementDoctorCheck } from './lease-doctor-check.js';
import {
defaultInstalledBrainDoctorOptions,
runInstalledBrainDoctorCheck,
} from './brain-doctor-check.js';
import { systemCommandRunner } from './brain-store-runtime.js';
const MOSAIC_HOME = process.env['MOSAIC_HOME'] ?? join(homedir(), '.config', 'mosaic');
const MAX_INSTALLED_TOOLS_BYTES = 256 * 1024;
@@ -1257,8 +1262,11 @@ export function registerLaunchCommands(program: Command): void {
});
}
// `doctor` — the framework drift audit (bash script) PLUS the #869
// Point-1 C5 lease-enforcement activation check (TS, reusing C1's
// `doctor` — the framework drift audit (bash script), the #869
// Point-1 C5 lease-enforcement activation check, and the #1051 per-estate
// durable brain check. Both TS checks run before the bash audit and can
// force a non-zero result for hard/indeterminate failures.
// The lease check reuses C1's
// `leaseEnforcementActivatable()` and C3's `checkBrokerSupervisorHealth()`).
// Kept out of the generic `directCommands` loop above because this check
// must run and report BEFORE the bash script's own exit, and must be able
@@ -1267,14 +1275,29 @@ export function registerLaunchCommands(program: Command): void {
// undiagnosed (see lease-doctor-check.ts docstring).
program
.command('doctor')
.description('Health audit — detect drift, missing files, and #869 lease-activation gaps')
.description('Health audit — detect drift, lease gaps, and per-estate brain defects')
.allowUnknownOption(true)
.allowExcessArguments(true)
.action(async (_opts: unknown, cmd: Command) => {
checkMosaicHome();
const leaseCheck = await runLeaseEnforcementDoctorCheck();
const leaseCheckFailed = printLeaseDoctorCheck(leaseCheck);
runDoctorScriptAndExit(fwScript('mosaic-doctor'), cmd.args, leaseCheckFailed);
const fix = cmd.args.includes('--fix');
const brainCheck = runInstalledBrainDoctorCheck(
defaultInstalledBrainDoctorOptions(fix),
systemCommandRunner,
);
for (const line of brainCheck.lines) {
(brainCheck.status === 'ok' ? console.log : console.error)(line);
}
const brainCheckFailed =
brainCheck.status === 'error' ||
(brainCheck.status === 'warn' && cmd.args.includes('--fail-on-warn'));
runDoctorScriptAndExit(
fwScript('mosaic-doctor'),
cmd.args,
leaseCheckFailed || brainCheckFailed,
);
});
}