fix(#1264): preserve portable standalone launch
ci/woodpecker/pr/ci Pipeline failed

This commit is contained in:
goals
2026-08-16 19:01:26 -05:00
parent 9dc90be7e1
commit 3af594590a
11 changed files with 161 additions and 68 deletions
@@ -334,7 +334,22 @@ describe('composeContract — overlay composer', () => {
}
});
it('refuses a USER.md replacement symlink at the point of composition', () => {
it('refuses a fleet USER.md replacement symlink at the point of composition', () => {
mkdirSync(join(fixture.home, 'fleet'), { recursive: true });
writeFileSync(
join(fixture.home, 'fleet', 'roster.yaml'),
[
'version: 1',
'transport: tmux',
'agents:',
' - name: exact-user-seat',
' runtime: pi',
' class: worker',
'',
].join('\n'),
);
process.env['MOSAIC_AGENT_NAME'] = 'exact-user-seat';
process.env['MOSAIC_AGENT_CLASS'] = 'worker';
writeFileSync(join(fixture.home, 'defaults', 'SOUL.md'), '# Generic soul\n');
writeFileSync(join(fixture.home, 'defaults', 'USER.md'), '# Generic user\n');
expect(seedFleetIdentityDefaults(fixture.home)).toEqual(['SOUL.md']);
@@ -351,6 +366,17 @@ describe('composeContract — overlay composer', () => {
expect(readFileSync(external, 'utf8')).toBe('UNSAFE-REPLACEMENT-USER-CONTENT\n');
});
it('preserves tolerant standalone composition when optional USER.md is unreadable', () => {
const userPath = join(fixture.home, 'USER.md');
rmSync(userPath);
mkdirSync(userPath);
const out = composeContract('pi', fixture.home);
expect(out).toContain(AGENTS);
expect(out).not.toContain('# User Profile');
});
it.each(['claude', 'codex', 'opencode', 'pi'] as const)(
'never injects installed TOOLS.md through a target symlink for %s',
(runtime) => {
@@ -51,12 +51,11 @@ function installedEntryExists(path: string): boolean {
}
}
/** Secure point-of-use read for a top-level identity contract. */
export function readOptionalInstalledIdentityContract(
/** Secure fleet point-of-use read for a top-level identity contract. */
export function readInstalledIdentityContractAtPointOfUse(
mosaicHome: string,
entry: (typeof FLEET_IDENTITY_DEFAULTS)[number],
required: boolean = false,
): Buffer | undefined {
): Buffer {
const configuredPath = join(mosaicHome, entry);
try {
// Preserve the launcher's established support for a symlinked Mosaic home,
@@ -68,7 +67,6 @@ export function readOptionalInstalledIdentityContract(
maxBytes: MAX_IDENTITY_CONTRACT_BYTES,
}).content;
} catch (error: unknown) {
if (!required && isFilesystemError(error, 'ENOENT')) return undefined;
throw unsafeIdentityError('installed', configuredPath, error);
}
}
@@ -317,6 +317,23 @@ describe('fleet unattended first start (#1264)', () => {
expect(existsSync(join(fixture.mosaicHome, 'USER.md'))).toBe(false);
});
it.each(['', ' ', '\t'])(
'refuses explicit blank ambient fleet class %j before seeding',
(agentClass: string) => {
const fixture = createGreenfieldFixture();
const result = launchSync(fixture, { agentClass });
const output = outputOf(result);
expect(result.status, output).toBe(1);
expect(output).toContain('Refusing split identity authority');
expect(output).not.toContain('Running setup wizard');
expect(existsSync(fixture.capturePath)).toBe(false);
expect(existsSync(join(fixture.mosaicHome, 'SOUL.md'))).toBe(false);
expect(existsSync(join(fixture.mosaicHome, 'USER.md'))).toBe(false);
},
);
it.each([' unattended-seat', 'unattended-seat ', ''])(
'refuses non-exact ambient fleet name %j before seeding',
(agentName: string) => {
+10 -9
View File
@@ -31,7 +31,7 @@ import { readPersonaContractBlock } from '../fleet/persona-contract.js';
import { canonicalizeRoleClass } from './fleet-personas.js';
import { launchClaudex, type ClaudexHarnessAdapter } from './claudex.js';
import {
readOptionalInstalledIdentityContract,
readInstalledIdentityContractAtPointOfUse,
seedFleetIdentityDefaults,
} from './fleet-first-start-identity.js';
import { runLeaseEnforcementDoctorCheck } from './lease-doctor-check.js';
@@ -236,7 +236,7 @@ function checkRuntime(cmd: string): void {
function assertAmbientFleetClassMatches(canonicalName: string, canonicalClass: string): void {
const configuredClass = process.env['MOSAIC_AGENT_CLASS'];
if (!configuredClass?.trim()) return;
if (configuredClass === undefined) return;
const ambientClass = canonicalizeRoleClass(configuredClass).canonicalClass;
if (ambientClass !== canonicalClass) {
@@ -583,20 +583,21 @@ For required push/merge/issue-close/release actions, execute without routine con
parts.push(readFileSync(join(mosaicHome, 'AGENTS.md'), 'utf-8'));
// USER.md (+ USER.local.md operator overlay, appended directly under the
// profile its base owns).
// profile its base owns). Fleet first start is Linux/systemd-owned and uses
// the no-follow reader at point of use. Standalone launches retain the
// portable tolerant path used on macOS and other supported hosts.
const fleetAgentName = process.env['MOSAIC_AGENT_NAME'];
const user =
readOptionalInstalledIdentityContract(
mosaicHome,
'USER.md',
process.env['MOSAIC_AGENT_NAME'] !== undefined,
)?.toString('utf8') ?? '';
fleetAgentName === undefined
? readOptional(join(mosaicHome, 'USER.md'))
: readInstalledIdentityContractAtPointOfUse(mosaicHome, 'USER.md').toString('utf8');
if (user) parts.push('\n\n# User Profile\n\n' + user);
const userLocal = readOptional(join(mosaicHome, 'USER.local.md'));
if (userLocal.trim()) {
parts.push('\n\n## Operator Overlay (USER.local.md)\n\n' + userLocal);
}
const fleetIdentity = resolveFleetIdentity(mosaicHome, process.env['MOSAIC_AGENT_NAME']);
const fleetIdentity = resolveFleetIdentity(mosaicHome, fleetAgentName);
if (!fleetIdentity.ok) {
throw new Error(`Fleet communications contract unavailable: ${fleetIdentity.error}`);
}