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