fix(fleet): align migration preview and clean CI
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jarvis
2026-07-16 04:10:38 -05:00
parent b7eb724c2b
commit 01d8b16474
3 changed files with 111 additions and 1 deletions

View File

@@ -1136,6 +1136,70 @@ describe('v1-to-v2 migration preview', (): void => {
expect(preview.environment.map(({ agent }) => agent)).toEqual(['coder0']);
});
it.each([
[
'tmux connector with matrix settings',
'connector:\n kind: tmux\n matrix:\n homeserver_url: https://rejected-matrix.example.test\n user_id: "@bot:example.test"\n room_id: "!room:example.test"\n',
'connector.matrix',
'https://rejected-matrix.example.test',
],
[
'tmux connector with discord settings',
'connector:\n kind: tmux\n discord:\n channel_id: rejected-discord-channel\n',
'connector.discord',
'rejected-discord-channel',
],
[
'discord connector with matrix settings',
'connector:\n kind: discord\n discord:\n channel_id: channel\n matrix:\n homeserver_url: https://rejected-matrix.example.test\n user_id: "@bot:example.test"\n room_id: "!room:example.test"\n',
'connector.matrix',
'https://rejected-matrix.example.test',
],
[
'matrix connector with discord settings',
'connector:\n kind: matrix\n matrix:\n homeserver_url: https://matrix.example.test\n user_id: "@bot:example.test"\n room_id: "!room:example.test"\n discord:\n channel_id: rejected-discord-channel\n',
'connector.discord',
'rejected-discord-channel',
],
] as const)(
'blocks %s to match production v1 connector exclusivity',
async (_label, connectorSource, expectedPath, rejectedValue) => {
const preview = await previewV1ToV2Migration({
source: v1Source.replace(/connector:\n[\s\S]*$/, connectorSource),
decisions: decisions(),
observations: observations(),
personaDirs: await semanticDirs(),
});
expect(preview.status).toBe('blocked');
expect(preview.blockers).toContainEqual(
expect.objectContaining({ code: 'invalid-connector', path: expectedPath }),
);
expect(preview).not.toHaveProperty('canonicalRoster');
expect(preview).not.toHaveProperty('canonicalYaml');
expect(JSON.stringify(preview.blockers)).not.toContain(rejectedValue);
},
);
it('blocks whitespace-padded connector kinds to match exact production v1 parsing', async () => {
const rejectedKind = ' discord ';
const preview = await previewV1ToV2Migration({
source: v1Source.replace('kind: discord', `kind: "${rejectedKind}"`),
decisions: decisions(),
observations: observations(),
personaDirs: await semanticDirs(),
});
expect(preview.status).toBe('blocked');
expect(preview.blockers).toContainEqual(
expect.objectContaining({ code: 'invalid-connector', path: 'connector.kind' }),
);
expect(preview).not.toHaveProperty('canonicalRoster');
expect(preview).not.toHaveProperty('canonicalYaml');
expect(preview.evidence).not.toHaveProperty('candidate');
expect(JSON.stringify(preview.blockers)).not.toContain(rejectedKind);
});
it('blocks malformed schema-only remote and connector fields', async () => {
const source = v1Source
.replace('host: build.example.test', 'host: 42')

View File

@@ -1005,10 +1005,48 @@ function validateConnector(
): void {
if (!present) return;
rejectUnknownKeys(connector, new Set(['kind', 'matrix', 'discord']), 'connector', blockers);
const kind = stringValue(connector.kind);
const kind = typeof connector.kind === 'string' ? connector.kind : undefined;
if (!kind || !['tmux', 'discord', 'matrix'].includes(kind)) {
blockers.push(blocker('invalid-connector', 'connector.kind', 'Unsupported connector kind.'));
}
if (kind === 'tmux') {
if (connector.matrix !== undefined) {
blockers.push(
blocker(
'invalid-connector',
'connector.matrix',
'Tmux connector must not define Matrix connector data.',
),
);
}
if (connector.discord !== undefined) {
blockers.push(
blocker(
'invalid-connector',
'connector.discord',
'Tmux connector must not define Discord connector data.',
),
);
}
}
if (kind === 'discord' && connector.matrix !== undefined) {
blockers.push(
blocker(
'invalid-connector',
'connector.matrix',
'Discord connector must not define Matrix connector data.',
),
);
}
if (kind === 'matrix' && connector.discord !== undefined) {
blockers.push(
blocker(
'invalid-connector',
'connector.discord',
'Matrix connector must not define Discord connector data.',
),
);
}
if (kind === 'matrix' && connector.matrix === undefined) {
blockers.push(
blocker('invalid-connector', 'connector.matrix', 'Matrix connector data is required.'),

View File

@@ -0,0 +1,8 @@
{
"extends": ["//"],
"tasks": {
"test": {
"dependsOn": ["^build", "build"]
}
}
}