@shipfox/api-definitions 12.3.0 → 14.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +45 -0
- package/dist/core/entities/index.d.ts +1 -1
- package/dist/core/entities/index.d.ts.map +1 -1
- package/dist/core/entities/index.js +1 -1
- package/dist/core/entities/index.js.map +1 -1
- package/dist/core/entities/integration-context.d.ts +4 -0
- package/dist/core/entities/integration-context.d.ts.map +1 -1
- package/dist/core/entities/integration-context.js.map +1 -1
- package/dist/core/entities/sync-state.d.ts +10 -4
- package/dist/core/entities/sync-state.d.ts.map +1 -1
- package/dist/core/entities/sync-state.js +26 -7
- package/dist/core/entities/sync-state.js.map +1 -1
- package/dist/core/entities/validation-diagnostic.d.ts +8 -0
- package/dist/core/entities/validation-diagnostic.d.ts.map +1 -0
- package/dist/core/entities/validation-diagnostic.js +3 -0
- package/dist/core/entities/validation-diagnostic.js.map +1 -0
- package/dist/core/entities/workflow-definition.d.ts +2 -0
- package/dist/core/entities/workflow-definition.d.ts.map +1 -1
- package/dist/core/entities/workflow-definition.js.map +1 -1
- package/dist/core/errors.d.ts +10 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +9 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/integrations.d.ts +3 -1
- package/dist/core/integrations.d.ts.map +1 -1
- package/dist/core/integrations.js +10 -2
- package/dist/core/integrations.js.map +1 -1
- package/dist/core/parse-definition.d.ts +4 -4
- package/dist/core/parse-definition.d.ts.map +1 -1
- package/dist/core/parse-definition.js +5 -5
- package/dist/core/parse-definition.js.map +1 -1
- package/dist/core/resolve-definition-at-ref.d.ts +66 -0
- package/dist/core/resolve-definition-at-ref.d.ts.map +1 -0
- package/dist/core/resolve-definition-at-ref.js +374 -0
- package/dist/core/resolve-definition-at-ref.js.map +1 -0
- package/dist/core/sync-definitions.d.ts +2 -2
- package/dist/core/sync-definitions.d.ts.map +1 -1
- package/dist/core/sync-definitions.js +5 -5
- package/dist/core/sync-definitions.js.map +1 -1
- package/dist/core/validate-definition.d.ts +3 -3
- package/dist/core/validate-definition.d.ts.map +1 -1
- package/dist/core/validate-definition.js +17 -12
- package/dist/core/validate-definition.js.map +1 -1
- package/dist/core/workflow-model/cron-trigger.d.ts +1 -1
- package/dist/core/workflow-model/cron-trigger.d.ts.map +1 -1
- package/dist/core/workflow-model/cron-trigger.js +9 -5
- package/dist/core/workflow-model/cron-trigger.js.map +1 -1
- package/dist/core/workflow-model/index.d.ts +1 -1
- package/dist/core/workflow-model/index.d.ts.map +1 -1
- package/dist/core/workflow-model/index.js.map +1 -1
- package/dist/core/workflow-model/invalid-workflow-model-error.d.ts +4 -2
- package/dist/core/workflow-model/invalid-workflow-model-error.d.ts.map +1 -1
- package/dist/core/workflow-model/invalid-workflow-model-error.js.map +1 -1
- package/dist/core/workflow-model/normalize-job-listening.d.ts.map +1 -1
- package/dist/core/workflow-model/normalize-job-listening.js +51 -30
- package/dist/core/workflow-model/normalize-job-listening.js.map +1 -1
- package/dist/core/workflow-model/normalize-triggers.d.ts +1 -1
- package/dist/core/workflow-model/normalize-triggers.d.ts.map +1 -1
- package/dist/core/workflow-model/normalize-triggers.js +48 -18
- package/dist/core/workflow-model/normalize-triggers.js.map +1 -1
- package/dist/core/workflow-model/normalize-workflow-document.d.ts +1 -1
- package/dist/core/workflow-model/normalize-workflow-document.d.ts.map +1 -1
- package/dist/core/workflow-model/normalize-workflow-document.js +3 -3
- package/dist/core/workflow-model/normalize-workflow-document.js.map +1 -1
- package/dist/core/workflow-model/parse-interpolation-field.d.ts.map +1 -1
- package/dist/core/workflow-model/parse-interpolation-field.js +1 -15
- package/dist/core/workflow-model/parse-interpolation-field.js.map +1 -1
- package/dist/core/workflow-model/validate-predicate-expression.d.ts +3 -1
- package/dist/core/workflow-model/validate-predicate-expression.d.ts.map +1 -1
- package/dist/core/workflow-model/validate-predicate-expression.js +3 -0
- package/dist/core/workflow-model/validate-predicate-expression.js.map +1 -1
- package/dist/core/workflow-model/validation-issue.d.ts +2 -1
- package/dist/core/workflow-model/validation-issue.d.ts.map +1 -1
- package/dist/core/workflow-model/validation-issue.js +12 -15
- package/dist/core/workflow-model/validation-issue.js.map +1 -1
- package/dist/db/db.d.ts +196 -4
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +3 -1
- package/dist/db/db.js.map +1 -1
- package/dist/db/definition-triggers.js +3 -1
- package/dist/db/definition-triggers.js.map +1 -1
- package/dist/db/definitions.d.ts +17 -0
- package/dist/db/definitions.d.ts.map +1 -1
- package/dist/db/definitions.js +190 -23
- package/dist/db/definitions.js.map +1 -1
- package/dist/db/index.d.ts +2 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +2 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/schema/definitions.d.ts +17 -0
- package/dist/db/schema/definitions.d.ts.map +1 -1
- package/dist/db/schema/definitions.js +8 -0
- package/dist/db/schema/definitions.js.map +1 -1
- package/dist/db/schema/sync-states.d.ts +7 -3
- package/dist/db/schema/sync-states.d.ts.map +1 -1
- package/dist/db/schema/sync-states.js +5 -1
- package/dist/db/schema/sync-states.js.map +1 -1
- package/dist/db/schema/workflows.d.ts +78 -0
- package/dist/db/schema/workflows.d.ts.map +1 -0
- package/dist/db/schema/workflows.js +17 -0
- package/dist/db/schema/workflows.js.map +1 -0
- package/dist/db/sync-states.d.ts +2 -2
- package/dist/db/sync-states.d.ts.map +1 -1
- package/dist/db/sync-states.js +4 -4
- package/dist/db/sync-states.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/metrics/index.d.ts +2 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +3 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/instance.d.ts +4 -0
- package/dist/metrics/instance.d.ts.map +1 -0
- package/dist/metrics/instance.js +19 -0
- package/dist/metrics/instance.js.map +1 -0
- package/dist/presentation/dto/definition.d.ts.map +1 -1
- package/dist/presentation/dto/definition.js +11 -4
- package/dist/presentation/dto/definition.js.map +1 -1
- package/dist/presentation/inter-module.d.ts +9 -1
- package/dist/presentation/inter-module.d.ts.map +1 -1
- package/dist/presentation/inter-module.js +66 -2
- package/dist/presentation/inter-module.js.map +1 -1
- package/dist/presentation/routes/create-definition.d.ts.map +1 -1
- package/dist/presentation/routes/create-definition.js +16 -3
- package/dist/presentation/routes/create-definition.js.map +1 -1
- package/dist/presentation/routes/get-definition.d.ts.map +1 -1
- package/dist/presentation/routes/get-definition.js +18 -4
- package/dist/presentation/routes/get-definition.js.map +1 -1
- package/dist/presentation/routes/project-access.d.ts +1 -0
- package/dist/presentation/routes/project-access.d.ts.map +1 -1
- package/dist/presentation/routes/validate-definition.js +3 -3
- package/dist/presentation/routes/validate-definition.js.map +1 -1
- package/dist/temporal/activities/sync-activities.d.ts +3 -3
- package/dist/temporal/activities/sync-activities.d.ts.map +1 -1
- package/dist/temporal/activities/sync-activities.js +7 -4
- package/dist/temporal/activities/sync-activities.js.map +1 -1
- package/dist/temporal/workflows/definition-sync.js +1 -1
- package/dist/temporal/workflows/definition-sync.js.map +1 -1
- package/dist/temporal/workflows/index.bundle.js +50 -22
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0003_nebulous_nemesis.sql +11 -0
- package/drizzle/0004_workflow_ref_lookup.sql +1 -0
- package/drizzle/meta/0003_snapshot.json +627 -0
- package/drizzle/meta/0004_snapshot.json +649 -0
- package/drizzle/meta/_journal.json +14 -0
- package/drizzle.config.ts +1 -0
- package/package.json +7 -7
- package/src/core/entities/index.ts +1 -1
- package/src/core/entities/integration-context.ts +4 -0
- package/src/core/entities/sync-state.test.ts +49 -0
- package/src/core/entities/sync-state.ts +35 -12
- package/src/core/entities/validation-diagnostic.ts +8 -0
- package/src/core/entities/workflow-definition.ts +2 -0
- package/src/core/errors.ts +26 -0
- package/src/core/index.ts +12 -0
- package/src/core/integrations.test.ts +38 -0
- package/src/core/integrations.ts +10 -1
- package/src/core/parse-definition-diagnostics.test.ts +47 -0
- package/src/core/parse-definition.test.ts +29 -5
- package/src/core/parse-definition.ts +7 -7
- package/src/core/resolve-definition-at-ref.test.ts +792 -0
- package/src/core/resolve-definition-at-ref.ts +551 -0
- package/src/core/sync-definitions.test.ts +61 -2
- package/src/core/sync-definitions.ts +7 -7
- package/src/core/validate-definition.test.ts +31 -22
- package/src/core/validate-definition.ts +21 -13
- package/src/core/workflow-model/cron-trigger.ts +6 -2
- package/src/core/workflow-model/index.ts +1 -0
- package/src/core/workflow-model/invalid-workflow-model-error.ts +5 -1
- package/src/core/workflow-model/normalize-job-listening.ts +55 -23
- package/src/core/workflow-model/normalize-triggers.ts +44 -15
- package/src/core/workflow-model/normalize-workflow-document.test.ts +494 -36
- package/src/core/workflow-model/normalize-workflow-document.ts +8 -4
- package/src/core/workflow-model/parse-interpolation-field.ts +1 -23
- package/src/core/workflow-model/validate-predicate-expression.ts +6 -0
- package/src/core/workflow-model/validation-issue.ts +8 -13
- package/src/db/db.ts +7 -1
- package/src/db/definition-triggers.test.ts +44 -0
- package/src/db/definition-triggers.ts +1 -1
- package/src/db/definitions.test.ts +300 -1
- package/src/db/definitions.ts +282 -28
- package/src/db/index.ts +2 -0
- package/src/db/schema/definitions.test.ts +1 -0
- package/src/db/schema/definitions.ts +11 -0
- package/src/db/schema/sync-states.ts +12 -3
- package/src/db/schema/workflows.ts +23 -0
- package/src/db/sync-states.test.ts +87 -18
- package/src/db/sync-states.ts +6 -6
- package/src/db/workflow-lineage-migration.test.ts +158 -0
- package/src/index.ts +3 -1
- package/src/metrics/index.ts +1 -0
- package/src/metrics/instance.test.ts +50 -0
- package/src/metrics/instance.ts +23 -0
- package/src/presentation/dto/definition.test.ts +89 -1
- package/src/presentation/dto/definition.ts +8 -5
- package/src/presentation/inter-module.test.ts +177 -0
- package/src/presentation/inter-module.ts +77 -4
- package/src/presentation/routes/create-definition.test.ts +38 -0
- package/src/presentation/routes/create-definition.ts +20 -3
- package/src/presentation/routes/get-definition.test.ts +125 -11
- package/src/presentation/routes/get-definition.ts +21 -4
- package/src/presentation/routes/list-definitions.test.ts +1 -1
- package/src/presentation/routes/validate-definition.test.ts +1 -1
- package/src/presentation/routes/validate-definition.ts +3 -3
- package/src/presentation/subscribers/on-integration-connection-available.test.ts +3 -0
- package/src/temporal/activities/sync-activities.test.ts +46 -2
- package/src/temporal/activities/sync-activities.ts +9 -7
- package/src/temporal/workflows/definition-sync.ts +1 -1
- package/test/factories/definition.ts +1 -0
- package/test/globalSetup.ts +2 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/core/entities/validation-warning.d.ts +0 -6
- package/dist/core/entities/validation-warning.d.ts.map +0 -1
- package/dist/core/entities/validation-warning.js +0 -3
- package/dist/core/entities/validation-warning.js.map +0 -1
- package/src/core/entities/validation-warning.ts +0 -5
- package/src/core/parse-definition-warnings.test.ts +0 -33
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import type {AgentValidationCatalog} from '@shipfox/api-agent-dto/inter-module';
|
|
1
2
|
import type {WorkflowDocument} from '@shipfox/workflow-document';
|
|
2
3
|
import {agentValidationCatalog} from '#test/agent-validation-catalog.js';
|
|
3
4
|
import type {IntegrationValidationContext} from '../entities/integration-context.js';
|
|
4
|
-
import {
|
|
5
|
+
import type {WorkflowModel} from '../entities/workflow-model.js';
|
|
6
|
+
import {
|
|
7
|
+
InvalidWorkflowModelError,
|
|
8
|
+
type WorkflowModelValidationIssue,
|
|
9
|
+
} from './invalid-workflow-model-error.js';
|
|
5
10
|
import {DEFAULT_JOB_CHECKOUT} from './normalize-job-checkout.js';
|
|
6
11
|
import {normalizeWorkflowDocument as normalizeWorkflowDocumentBase} from './normalize-workflow-document.js';
|
|
7
12
|
|
|
@@ -33,6 +38,16 @@ function expectInvalid(
|
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
40
|
|
|
41
|
+
function normalizeWithDiagnostics(
|
|
42
|
+
document: WorkflowDocument,
|
|
43
|
+
options?: Omit<Parameters<typeof normalizeWorkflowDocumentBase>[1], 'agentValidationCatalog'> &
|
|
44
|
+
Partial<Pick<Parameters<typeof normalizeWorkflowDocumentBase>[1], 'agentValidationCatalog'>>,
|
|
45
|
+
): {model: WorkflowModel; diagnostics: WorkflowModelValidationIssue[]} {
|
|
46
|
+
const diagnostics: WorkflowModelValidationIssue[] = [];
|
|
47
|
+
const model = normalizeWorkflowDocument(document, {...options, diagnostics});
|
|
48
|
+
return {model, diagnostics};
|
|
49
|
+
}
|
|
50
|
+
|
|
36
51
|
function interpolation(source: string): string {
|
|
37
52
|
return '$'.concat('{{ ', source, ' }}');
|
|
38
53
|
}
|
|
@@ -85,6 +100,13 @@ const integrationValidationContext = {
|
|
|
85
100
|
['linear-main', {id: 'conn_3', provider: 'linear', capabilities: ['agent_tools']}],
|
|
86
101
|
['jira-main', {id: 'conn_4', provider: 'jira', capabilities: ['agent_tools']}],
|
|
87
102
|
]),
|
|
103
|
+
eventCatalogs: new Map([
|
|
104
|
+
['github', new Set(['push', 'pull_request.opened'])],
|
|
105
|
+
['sentry', new Set(['issue.created'])],
|
|
106
|
+
['linear', new Set(['Issue'])],
|
|
107
|
+
['jira', new Set(['jira:issue_created'])],
|
|
108
|
+
]),
|
|
109
|
+
fixedEventProviders: new Set(['webhook']),
|
|
88
110
|
defaultConnectionSlug: 'github-main',
|
|
89
111
|
} satisfies IntegrationValidationContext;
|
|
90
112
|
|
|
@@ -778,6 +800,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
778
800
|
message: 'Provider "github-copilot" is not supported.',
|
|
779
801
|
path: ['jobs', 'fix', 'steps', 0, 'provider'],
|
|
780
802
|
details: {provider: 'github-copilot'},
|
|
803
|
+
severity: 'error',
|
|
804
|
+
scope: 'definition',
|
|
781
805
|
},
|
|
782
806
|
]);
|
|
783
807
|
});
|
|
@@ -828,6 +852,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
828
852
|
'Agent model "not-a-model" is not available for harness "pi" and provider "anthropic".',
|
|
829
853
|
path: ['jobs', 'fix', 'steps', 0, 'model'],
|
|
830
854
|
details: {harness: 'pi', provider: 'anthropic', model: 'not-a-model'},
|
|
855
|
+
severity: 'error',
|
|
856
|
+
scope: 'definition',
|
|
831
857
|
},
|
|
832
858
|
]);
|
|
833
859
|
});
|
|
@@ -901,6 +927,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
901
927
|
'Harness "claude" does not support provider: openai. Supported providers: anthropic.',
|
|
902
928
|
path: ['jobs', 'fix', 'steps', 0, 'provider'],
|
|
903
929
|
details: {harness: 'claude', provider: 'openai', supportedProviders: ['anthropic']},
|
|
930
|
+
severity: 'error',
|
|
931
|
+
scope: 'definition',
|
|
904
932
|
},
|
|
905
933
|
]);
|
|
906
934
|
});
|
|
@@ -939,6 +967,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
939
967
|
'get_search_content',
|
|
940
968
|
],
|
|
941
969
|
},
|
|
970
|
+
severity: 'error',
|
|
971
|
+
scope: 'definition',
|
|
942
972
|
},
|
|
943
973
|
]);
|
|
944
974
|
});
|
|
@@ -981,6 +1011,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
981
1011
|
'Agent step tools require an explicit harness because tool names are harness-specific.',
|
|
982
1012
|
path: ['jobs', 'fix', 'steps', 0, 'tools'],
|
|
983
1013
|
details: {tools: ['Read']},
|
|
1014
|
+
severity: 'error',
|
|
1015
|
+
scope: 'definition',
|
|
984
1016
|
},
|
|
985
1017
|
]);
|
|
986
1018
|
});
|
|
@@ -1068,6 +1100,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
1068
1100
|
thinking: 'off',
|
|
1069
1101
|
supportedLevels: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
1070
1102
|
},
|
|
1103
|
+
severity: 'error',
|
|
1104
|
+
scope: 'definition',
|
|
1071
1105
|
});
|
|
1072
1106
|
});
|
|
1073
1107
|
|
|
@@ -1433,6 +1467,78 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
1433
1467
|
]);
|
|
1434
1468
|
});
|
|
1435
1469
|
|
|
1470
|
+
it('reports missing resolution sources when every until matcher is inert', () => {
|
|
1471
|
+
const document: WorkflowDocument = {
|
|
1472
|
+
name: 'listen forever',
|
|
1473
|
+
jobs: {
|
|
1474
|
+
review: {
|
|
1475
|
+
listening: {
|
|
1476
|
+
on: [{source: 'github', event: 'pull_request_review'}],
|
|
1477
|
+
until: [{source: 'github', event: 'pull_request', filter: 'event.action'}],
|
|
1478
|
+
},
|
|
1479
|
+
steps: [{run: 'echo ok'}],
|
|
1480
|
+
},
|
|
1481
|
+
},
|
|
1482
|
+
};
|
|
1483
|
+
|
|
1484
|
+
const diagnostics: WorkflowModelValidationIssue[] = [];
|
|
1485
|
+
let error: InvalidWorkflowModelError | undefined;
|
|
1486
|
+
try {
|
|
1487
|
+
normalizeWorkflowDocument(document, {diagnostics});
|
|
1488
|
+
expect.fail('Expected InvalidWorkflowModelError');
|
|
1489
|
+
} catch (caught) {
|
|
1490
|
+
expect(caught).toBeInstanceOf(InvalidWorkflowModelError);
|
|
1491
|
+
error = caught as InvalidWorkflowModelError;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
expect(error?.issues).toEqual([
|
|
1495
|
+
expect.objectContaining({
|
|
1496
|
+
code: 'listening-job-missing-resolution-source',
|
|
1497
|
+
path: ['jobs', 'review', 'listening'],
|
|
1498
|
+
}),
|
|
1499
|
+
]);
|
|
1500
|
+
expect(diagnostics).toEqual([
|
|
1501
|
+
expect.objectContaining({
|
|
1502
|
+
code: 'invalid-listener-filter',
|
|
1503
|
+
path: ['jobs', 'review', 'listening', 'until', 0, 'filter'],
|
|
1504
|
+
severity: 'error',
|
|
1505
|
+
scope: 'trigger',
|
|
1506
|
+
}),
|
|
1507
|
+
]);
|
|
1508
|
+
});
|
|
1509
|
+
|
|
1510
|
+
it('drops inert until matchers from the model when a timeout resolves the job', () => {
|
|
1511
|
+
const document: WorkflowDocument = {
|
|
1512
|
+
name: 'listen with timeout',
|
|
1513
|
+
jobs: {
|
|
1514
|
+
review: {
|
|
1515
|
+
listening: {
|
|
1516
|
+
on: [{source: 'github', event: 'pull_request_review'}],
|
|
1517
|
+
until: [{source: 'github', event: 'pull_request', filter: 'event.action'}],
|
|
1518
|
+
timeout: '1h',
|
|
1519
|
+
},
|
|
1520
|
+
steps: [{run: 'echo ok'}],
|
|
1521
|
+
},
|
|
1522
|
+
},
|
|
1523
|
+
};
|
|
1524
|
+
|
|
1525
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
1526
|
+
|
|
1527
|
+
expect(diagnostics).toEqual([
|
|
1528
|
+
expect.objectContaining({
|
|
1529
|
+
code: 'invalid-listener-filter',
|
|
1530
|
+
path: ['jobs', 'review', 'listening', 'until', 0, 'filter'],
|
|
1531
|
+
severity: 'error',
|
|
1532
|
+
scope: 'trigger',
|
|
1533
|
+
}),
|
|
1534
|
+
]);
|
|
1535
|
+
expect(model.jobs[0]?.listening).toMatchObject({
|
|
1536
|
+
on: [{source: 'github', event: 'pull_request_review'}],
|
|
1537
|
+
timeoutMs: 60 * 60 * 1000,
|
|
1538
|
+
});
|
|
1539
|
+
expect(model.jobs[0]?.listening).not.toHaveProperty('until');
|
|
1540
|
+
});
|
|
1541
|
+
|
|
1436
1542
|
it('reports listening timeouts above the run timeout', () => {
|
|
1437
1543
|
const document: WorkflowDocument = {
|
|
1438
1544
|
name: 'too long',
|
|
@@ -1502,7 +1608,6 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
1502
1608
|
['executions root', 'executions.size() > 0', 'context-unavailable-at-predicate-site'],
|
|
1503
1609
|
['matrix root', 'matrix.os == "linux"', 'context-unavailable-at-predicate-site'],
|
|
1504
1610
|
['runner root', 'runner.os == "linux"', 'runner-context-in-server-predicate'],
|
|
1505
|
-
['non-boolean source', 'event.action', 'invalid-listener-filter'],
|
|
1506
1611
|
] as const)('reports invalid listener on filters for %s', (_label, filter, code) => {
|
|
1507
1612
|
const document: WorkflowDocument = {
|
|
1508
1613
|
name: 'invalid listener filter',
|
|
@@ -1531,6 +1636,70 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
1531
1636
|
]);
|
|
1532
1637
|
});
|
|
1533
1638
|
|
|
1639
|
+
it('rejects a listening job whose only on matcher is inert', () => {
|
|
1640
|
+
const document: WorkflowDocument = {
|
|
1641
|
+
name: 'invalid listener filter',
|
|
1642
|
+
jobs: {
|
|
1643
|
+
review: {
|
|
1644
|
+
listening: {
|
|
1645
|
+
on: [{source: 'github', event: 'pull_request_review', filter: 'event.action'}],
|
|
1646
|
+
max_executions: 1,
|
|
1647
|
+
},
|
|
1648
|
+
steps: [{run: 'echo ok'}],
|
|
1649
|
+
},
|
|
1650
|
+
},
|
|
1651
|
+
};
|
|
1652
|
+
|
|
1653
|
+
const error = expectInvalid(document);
|
|
1654
|
+
|
|
1655
|
+
expect(error.issues).toEqual([
|
|
1656
|
+
expect.objectContaining({
|
|
1657
|
+
code: 'listening-job-no-active-matcher',
|
|
1658
|
+
path: ['jobs', 'review', 'listening', 'on'],
|
|
1659
|
+
}),
|
|
1660
|
+
]);
|
|
1661
|
+
});
|
|
1662
|
+
|
|
1663
|
+
it('makes an invalid listener on matcher inert and keeps the others active', () => {
|
|
1664
|
+
const document: WorkflowDocument = {
|
|
1665
|
+
name: 'invalid listener filter',
|
|
1666
|
+
jobs: {
|
|
1667
|
+
review: {
|
|
1668
|
+
listening: {
|
|
1669
|
+
on: [
|
|
1670
|
+
{source: 'github', event: 'pull_request_review', filter: 'event.action'},
|
|
1671
|
+
{
|
|
1672
|
+
source: 'github',
|
|
1673
|
+
event: 'pull_request_review',
|
|
1674
|
+
filter: 'event.action == "submitted"',
|
|
1675
|
+
},
|
|
1676
|
+
],
|
|
1677
|
+
max_executions: 1,
|
|
1678
|
+
},
|
|
1679
|
+
steps: [{run: 'echo ok'}],
|
|
1680
|
+
},
|
|
1681
|
+
},
|
|
1682
|
+
};
|
|
1683
|
+
|
|
1684
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
1685
|
+
|
|
1686
|
+
expect(diagnostics).toEqual([
|
|
1687
|
+
expect.objectContaining({
|
|
1688
|
+
code: 'invalid-listener-filter',
|
|
1689
|
+
path: ['jobs', 'review', 'listening', 'on', 0, 'filter'],
|
|
1690
|
+
details: expect.objectContaining({
|
|
1691
|
+
field: 'listener.on',
|
|
1692
|
+
source: 'event.action',
|
|
1693
|
+
}),
|
|
1694
|
+
severity: 'error',
|
|
1695
|
+
scope: 'trigger',
|
|
1696
|
+
}),
|
|
1697
|
+
]);
|
|
1698
|
+
expect(model.jobs[0]?.listening?.on).toEqual([
|
|
1699
|
+
{source: 'github', event: 'pull_request_review', filter: 'event.action == "submitted"'},
|
|
1700
|
+
]);
|
|
1701
|
+
});
|
|
1702
|
+
|
|
1534
1703
|
it('rejects listener filters that reference jobs without a direct needs edge', () => {
|
|
1535
1704
|
const document: WorkflowDocument = {
|
|
1536
1705
|
name: 'listener missing needs',
|
|
@@ -3174,6 +3343,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3174
3343
|
message: 'Duration must be an integer followed by ms, s, m, h, or d.',
|
|
3175
3344
|
path: ['jobs', 'build', 'execution_timeout'],
|
|
3176
3345
|
details: {source: 'ten minutes'},
|
|
3346
|
+
severity: 'error',
|
|
3347
|
+
scope: 'definition',
|
|
3177
3348
|
},
|
|
3178
3349
|
]);
|
|
3179
3350
|
});
|
|
@@ -3197,6 +3368,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3197
3368
|
message: 'Duration must be between 1s and 24h.',
|
|
3198
3369
|
path: ['jobs', 'build', 'execution_timeout'],
|
|
3199
3370
|
details: {source: '999ms', min_ms: 1000, max_ms: 24 * 60 * 60 * 1000},
|
|
3371
|
+
severity: 'error',
|
|
3372
|
+
scope: 'definition',
|
|
3200
3373
|
},
|
|
3201
3374
|
]);
|
|
3202
3375
|
});
|
|
@@ -3220,6 +3393,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3220
3393
|
message: 'Duration must be between 1s and 24h.',
|
|
3221
3394
|
path: ['jobs', 'build', 'execution_timeout'],
|
|
3222
3395
|
details: {source: '25h', min_ms: 1000, max_ms: 24 * 60 * 60 * 1000},
|
|
3396
|
+
severity: 'error',
|
|
3397
|
+
scope: 'definition',
|
|
3223
3398
|
},
|
|
3224
3399
|
]);
|
|
3225
3400
|
});
|
|
@@ -3243,6 +3418,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3243
3418
|
message: 'Duration must be between 1s and 24h.',
|
|
3244
3419
|
path: ['jobs', 'build', 'execution_timeout'],
|
|
3245
3420
|
details: {source: '30d', min_ms: 1000, max_ms: 24 * 60 * 60 * 1000},
|
|
3421
|
+
severity: 'error',
|
|
3422
|
+
scope: 'definition',
|
|
3246
3423
|
},
|
|
3247
3424
|
]);
|
|
3248
3425
|
});
|
|
@@ -3335,6 +3512,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3335
3512
|
message: 'Step "build-review" must restart from an earlier keyed step; found "producer".',
|
|
3336
3513
|
path: ['jobs', 'build', 'steps', 0, 'gate', 'on_failure'],
|
|
3337
3514
|
details: {stepId: 'build-review', restartFrom: 'producer'},
|
|
3515
|
+
severity: 'error',
|
|
3516
|
+
scope: 'definition',
|
|
3338
3517
|
},
|
|
3339
3518
|
]);
|
|
3340
3519
|
});
|
|
@@ -3363,6 +3542,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3363
3542
|
message: 'Step "build-review" must restart from an earlier keyed step; found "review".',
|
|
3364
3543
|
path: ['jobs', 'build', 'steps', 0, 'gate', 'on_failure'],
|
|
3365
3544
|
details: {stepId: 'build-review', restartFrom: 'review'},
|
|
3545
|
+
severity: 'error',
|
|
3546
|
+
scope: 'definition',
|
|
3366
3547
|
},
|
|
3367
3548
|
]);
|
|
3368
3549
|
});
|
|
@@ -3392,6 +3573,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3392
3573
|
message: 'Step "build-review" must restart from an earlier keyed step; found "producer".',
|
|
3393
3574
|
path: ['jobs', 'build', 'steps', 0, 'gate', 'on_failure'],
|
|
3394
3575
|
details: {stepId: 'build-review', restartFrom: 'producer'},
|
|
3576
|
+
severity: 'error',
|
|
3577
|
+
scope: 'definition',
|
|
3395
3578
|
},
|
|
3396
3579
|
]);
|
|
3397
3580
|
});
|
|
@@ -3415,6 +3598,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3415
3598
|
message: 'Job "test" depends on unknown job "build".',
|
|
3416
3599
|
path: ['jobs', 'test', 'needs'],
|
|
3417
3600
|
details: {job: 'test', dependency: 'build'},
|
|
3601
|
+
severity: 'error',
|
|
3602
|
+
scope: 'definition',
|
|
3418
3603
|
},
|
|
3419
3604
|
]);
|
|
3420
3605
|
});
|
|
@@ -3438,6 +3623,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3438
3623
|
message: 'Job "test" depends on itself.',
|
|
3439
3624
|
path: ['jobs', 'test', 'needs'],
|
|
3440
3625
|
details: {job: 'test'},
|
|
3626
|
+
severity: 'error',
|
|
3627
|
+
scope: 'definition',
|
|
3441
3628
|
},
|
|
3442
3629
|
]);
|
|
3443
3630
|
});
|
|
@@ -3465,6 +3652,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3465
3652
|
message: 'Circular dependency detected among jobs: build, test.',
|
|
3466
3653
|
path: ['jobs'],
|
|
3467
3654
|
details: {cycleSourceNames: ['build', 'test'], cycleJobIds: ['build', 'test']},
|
|
3655
|
+
severity: 'error',
|
|
3656
|
+
scope: 'definition',
|
|
3468
3657
|
},
|
|
3469
3658
|
]);
|
|
3470
3659
|
});
|
|
@@ -3496,6 +3685,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3496
3685
|
message: 'Circular dependency detected among jobs: build, test.',
|
|
3497
3686
|
path: ['jobs'],
|
|
3498
3687
|
details: {cycleSourceNames: ['build', 'test'], cycleJobIds: ['build', 'test']},
|
|
3688
|
+
severity: 'error',
|
|
3689
|
+
scope: 'definition',
|
|
3499
3690
|
},
|
|
3500
3691
|
]);
|
|
3501
3692
|
});
|
|
@@ -3521,6 +3712,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3521
3712
|
message: 'Job keys "build app" and "build-app" resolve to the same stable id "build-app".',
|
|
3522
3713
|
path: ['jobs', 'build-app'],
|
|
3523
3714
|
details: {id: 'build-app', sourceKeys: ['build app', 'build-app']},
|
|
3715
|
+
severity: 'error',
|
|
3716
|
+
scope: 'definition',
|
|
3524
3717
|
},
|
|
3525
3718
|
]);
|
|
3526
3719
|
});
|
|
@@ -3546,7 +3739,7 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3546
3739
|
]);
|
|
3547
3740
|
});
|
|
3548
3741
|
|
|
3549
|
-
it('reports stable trigger id collisions', () => {
|
|
3742
|
+
it('reports stable trigger id collisions with the later trigger inert', () => {
|
|
3550
3743
|
const document: WorkflowDocument = {
|
|
3551
3744
|
name: 'trigger collision',
|
|
3552
3745
|
triggers: {
|
|
@@ -3560,17 +3753,20 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3560
3753
|
},
|
|
3561
3754
|
};
|
|
3562
3755
|
|
|
3563
|
-
const
|
|
3756
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
3564
3757
|
|
|
3565
|
-
expect(
|
|
3758
|
+
expect(diagnostics).toEqual([
|
|
3566
3759
|
{
|
|
3567
3760
|
code: 'duplicate-trigger-id',
|
|
3568
3761
|
message:
|
|
3569
3762
|
'Trigger keys "main_push" and "main push" resolve to the same stable id "main-push".',
|
|
3570
3763
|
path: ['triggers', 'main push'],
|
|
3571
3764
|
details: {id: 'main-push', sourceKeys: ['main_push', 'main push']},
|
|
3765
|
+
severity: 'error',
|
|
3766
|
+
scope: 'trigger',
|
|
3572
3767
|
},
|
|
3573
3768
|
]);
|
|
3769
|
+
expect(model.triggers.map((trigger) => trigger.key)).toEqual(['main_push']);
|
|
3574
3770
|
});
|
|
3575
3771
|
|
|
3576
3772
|
it('reports stable step id collisions inside a job', () => {
|
|
@@ -3591,6 +3787,8 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3591
3787
|
message: 'Steps 0 and 1 in job "build" resolve to the same stable id "build-step-1".',
|
|
3592
3788
|
path: ['jobs', 'build', 'steps', 1],
|
|
3593
3789
|
details: {id: 'build-step-1', indexes: [0, 1]},
|
|
3790
|
+
severity: 'error',
|
|
3791
|
+
scope: 'definition',
|
|
3594
3792
|
},
|
|
3595
3793
|
]);
|
|
3596
3794
|
});
|
|
@@ -3674,17 +3872,6 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3674
3872
|
site: 'ingest',
|
|
3675
3873
|
},
|
|
3676
3874
|
],
|
|
3677
|
-
[
|
|
3678
|
-
'non-boolean shape',
|
|
3679
|
-
'event.ref',
|
|
3680
|
-
'invalid-trigger-filter',
|
|
3681
|
-
{
|
|
3682
|
-
field: 'trigger.filter',
|
|
3683
|
-
source: 'event.ref',
|
|
3684
|
-
contextRoots: ['event'],
|
|
3685
|
-
reason: 'Predicate source must be boolean-shaped.',
|
|
3686
|
-
},
|
|
3687
|
-
],
|
|
3688
3875
|
] as const)('rejects trigger filters with %s', (_label, filter, code, details) => {
|
|
3689
3876
|
const document: WorkflowDocument = {
|
|
3690
3877
|
name: 'invalid trigger filter',
|
|
@@ -3710,11 +3897,50 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3710
3897
|
message: expect.any(String),
|
|
3711
3898
|
path: ['triggers', 'main', 'filter'],
|
|
3712
3899
|
details,
|
|
3900
|
+
severity: 'error',
|
|
3901
|
+
scope: 'definition',
|
|
3713
3902
|
},
|
|
3714
3903
|
]);
|
|
3715
3904
|
});
|
|
3716
3905
|
|
|
3717
|
-
it
|
|
3906
|
+
it('makes a trigger with a non-boolean filter inert', () => {
|
|
3907
|
+
const document: WorkflowDocument = {
|
|
3908
|
+
name: 'invalid trigger filter',
|
|
3909
|
+
triggers: {
|
|
3910
|
+
main: {
|
|
3911
|
+
source: 'github',
|
|
3912
|
+
event: 'push',
|
|
3913
|
+
filter: 'event.ref',
|
|
3914
|
+
},
|
|
3915
|
+
},
|
|
3916
|
+
jobs: {
|
|
3917
|
+
build: {
|
|
3918
|
+
steps: [{run: 'npm run build'}],
|
|
3919
|
+
},
|
|
3920
|
+
},
|
|
3921
|
+
};
|
|
3922
|
+
|
|
3923
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
3924
|
+
|
|
3925
|
+
expect(diagnostics).toEqual([
|
|
3926
|
+
{
|
|
3927
|
+
code: 'invalid-trigger-filter',
|
|
3928
|
+
message: expect.any(String),
|
|
3929
|
+
path: ['triggers', 'main', 'filter'],
|
|
3930
|
+
details: {
|
|
3931
|
+
field: 'trigger.filter',
|
|
3932
|
+
source: 'event.ref',
|
|
3933
|
+
contextRoots: ['event'],
|
|
3934
|
+
reason: 'Predicate source must be boolean-shaped.',
|
|
3935
|
+
},
|
|
3936
|
+
severity: 'error',
|
|
3937
|
+
scope: 'trigger',
|
|
3938
|
+
},
|
|
3939
|
+
]);
|
|
3940
|
+
expect(model.triggers).toEqual([]);
|
|
3941
|
+
});
|
|
3942
|
+
|
|
3943
|
+
it.each(['manual', 'cron'] as const)('makes a %s trigger with a filter inert', (source) => {
|
|
3718
3944
|
const document: WorkflowDocument = {
|
|
3719
3945
|
name: 'unsupported trigger filter',
|
|
3720
3946
|
triggers: {
|
|
@@ -3732,16 +3958,19 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3732
3958
|
},
|
|
3733
3959
|
};
|
|
3734
3960
|
|
|
3735
|
-
const
|
|
3961
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
3736
3962
|
|
|
3737
|
-
expect(
|
|
3963
|
+
expect(diagnostics).toEqual([
|
|
3738
3964
|
{
|
|
3739
3965
|
code: 'invalid-trigger-filter',
|
|
3740
3966
|
message: `A ${source} trigger cannot define a filter because it does not receive an event payload.`,
|
|
3741
3967
|
path: ['triggers', 'main', 'filter'],
|
|
3742
3968
|
details: {source: 'event.ref == "refs/heads/main"', triggerSource: source},
|
|
3969
|
+
severity: 'error',
|
|
3970
|
+
scope: 'trigger',
|
|
3743
3971
|
},
|
|
3744
3972
|
]);
|
|
3973
|
+
expect(model.triggers).toEqual([]);
|
|
3745
3974
|
});
|
|
3746
3975
|
|
|
3747
3976
|
it('maps trigger with values to model inputs', () => {
|
|
@@ -3843,7 +4072,64 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3843
4072
|
]);
|
|
3844
4073
|
});
|
|
3845
4074
|
|
|
3846
|
-
it('
|
|
4075
|
+
it('materializes fire for a manual trigger with the event omitted', () => {
|
|
4076
|
+
const document: WorkflowDocument = {
|
|
4077
|
+
name: 'manual trigger',
|
|
4078
|
+
triggers: {
|
|
4079
|
+
manual: {source: 'manual'},
|
|
4080
|
+
},
|
|
4081
|
+
jobs: {
|
|
4082
|
+
build: {
|
|
4083
|
+
steps: [{run: 'npm run build'}],
|
|
4084
|
+
},
|
|
4085
|
+
},
|
|
4086
|
+
};
|
|
4087
|
+
|
|
4088
|
+
const model = normalizeWorkflowDocument(document);
|
|
4089
|
+
|
|
4090
|
+
expect(model.triggers).toEqual([
|
|
4091
|
+
{
|
|
4092
|
+
id: 'manual',
|
|
4093
|
+
key: 'manual',
|
|
4094
|
+
source: 'manual',
|
|
4095
|
+
event: 'fire',
|
|
4096
|
+
},
|
|
4097
|
+
]);
|
|
4098
|
+
});
|
|
4099
|
+
|
|
4100
|
+
it('materializes tick for a cron trigger with the event omitted', () => {
|
|
4101
|
+
const document: WorkflowDocument = {
|
|
4102
|
+
name: 'nightly trigger',
|
|
4103
|
+
triggers: {
|
|
4104
|
+
nightly: {
|
|
4105
|
+
source: 'cron',
|
|
4106
|
+
config: {schedule: '0 2 * * *'},
|
|
4107
|
+
},
|
|
4108
|
+
},
|
|
4109
|
+
jobs: {
|
|
4110
|
+
build: {
|
|
4111
|
+
steps: [{run: 'npm run build'}],
|
|
4112
|
+
},
|
|
4113
|
+
},
|
|
4114
|
+
};
|
|
4115
|
+
|
|
4116
|
+
const model = normalizeWorkflowDocument(document);
|
|
4117
|
+
|
|
4118
|
+
expect(model.triggers).toEqual([
|
|
4119
|
+
{
|
|
4120
|
+
id: 'nightly',
|
|
4121
|
+
key: 'nightly',
|
|
4122
|
+
source: 'cron',
|
|
4123
|
+
event: 'tick',
|
|
4124
|
+
config: {
|
|
4125
|
+
schedule: '0 2 * * *',
|
|
4126
|
+
timezone: 'UTC',
|
|
4127
|
+
},
|
|
4128
|
+
},
|
|
4129
|
+
]);
|
|
4130
|
+
});
|
|
4131
|
+
|
|
4132
|
+
it('makes a cron trigger with a non-tick event inert', () => {
|
|
3847
4133
|
const document: WorkflowDocument = {
|
|
3848
4134
|
name: 'nightly trigger',
|
|
3849
4135
|
triggers: {
|
|
@@ -3860,19 +4146,58 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3860
4146
|
},
|
|
3861
4147
|
};
|
|
3862
4148
|
|
|
3863
|
-
const
|
|
4149
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
3864
4150
|
|
|
3865
|
-
expect(
|
|
4151
|
+
expect(diagnostics).toEqual([
|
|
3866
4152
|
{
|
|
3867
4153
|
code: 'invalid-cron-event',
|
|
3868
4154
|
message: 'A cron trigger must use event "tick"; found "push".',
|
|
3869
4155
|
path: ['triggers', 'nightly', 'event'],
|
|
3870
4156
|
details: {event: 'push'},
|
|
4157
|
+
severity: 'error',
|
|
4158
|
+
scope: 'trigger',
|
|
4159
|
+
},
|
|
4160
|
+
]);
|
|
4161
|
+
expect(model.triggers).toEqual([]);
|
|
4162
|
+
});
|
|
4163
|
+
|
|
4164
|
+
it('keeps the event absent for integration triggers with the event omitted', () => {
|
|
4165
|
+
const document: WorkflowDocument = {
|
|
4166
|
+
name: 'source subscriptions',
|
|
4167
|
+
triggers: {
|
|
4168
|
+
on_any_deploy: {
|
|
4169
|
+
source: 'deploy_hook',
|
|
4170
|
+
},
|
|
4171
|
+
on_any_github_event: {
|
|
4172
|
+
source: 'github_acme',
|
|
4173
|
+
},
|
|
4174
|
+
},
|
|
4175
|
+
jobs: {
|
|
4176
|
+
build: {
|
|
4177
|
+
steps: [{run: 'npm run build'}],
|
|
4178
|
+
},
|
|
4179
|
+
},
|
|
4180
|
+
};
|
|
4181
|
+
|
|
4182
|
+
const model = normalizeWorkflowDocument(document);
|
|
4183
|
+
|
|
4184
|
+
expect(model.triggers).toEqual([
|
|
4185
|
+
{
|
|
4186
|
+
id: 'on-any-deploy',
|
|
4187
|
+
key: 'on_any_deploy',
|
|
4188
|
+
source: 'deploy_hook',
|
|
4189
|
+
},
|
|
4190
|
+
{
|
|
4191
|
+
id: 'on-any-github-event',
|
|
4192
|
+
key: 'on_any_github_event',
|
|
4193
|
+
source: 'github_acme',
|
|
3871
4194
|
},
|
|
3872
4195
|
]);
|
|
4196
|
+
expect(model.triggers[0]).not.toHaveProperty('event');
|
|
4197
|
+
expect(model.triggers[1]).not.toHaveProperty('event');
|
|
3873
4198
|
});
|
|
3874
4199
|
|
|
3875
|
-
it('
|
|
4200
|
+
it('makes a cron trigger without a schedule inert', () => {
|
|
3876
4201
|
const document: WorkflowDocument = {
|
|
3877
4202
|
name: 'nightly trigger',
|
|
3878
4203
|
triggers: {
|
|
@@ -3888,22 +4213,25 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3888
4213
|
},
|
|
3889
4214
|
};
|
|
3890
4215
|
|
|
3891
|
-
const
|
|
4216
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
3892
4217
|
|
|
3893
|
-
expect(
|
|
4218
|
+
expect(diagnostics).toEqual([
|
|
3894
4219
|
{
|
|
3895
4220
|
code: 'missing-cron-schedule',
|
|
3896
4221
|
message: 'A cron trigger requires a schedule.',
|
|
3897
4222
|
path: ['triggers', 'nightly', 'config', 'schedule'],
|
|
4223
|
+
severity: 'error',
|
|
4224
|
+
scope: 'trigger',
|
|
3898
4225
|
},
|
|
3899
4226
|
]);
|
|
4227
|
+
expect(model.triggers).toEqual([]);
|
|
3900
4228
|
});
|
|
3901
4229
|
|
|
3902
4230
|
it.each([
|
|
3903
4231
|
['malformed', 'not a cron'],
|
|
3904
4232
|
['6-field', '0 0 2 * * *'],
|
|
3905
4233
|
['preset', '@daily'],
|
|
3906
|
-
])('
|
|
4234
|
+
])('makes a cron trigger with an invalid %s schedule inert', (_label, schedule) => {
|
|
3907
4235
|
const document: WorkflowDocument = {
|
|
3908
4236
|
name: 'nightly trigger',
|
|
3909
4237
|
triggers: {
|
|
@@ -3920,19 +4248,22 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3920
4248
|
},
|
|
3921
4249
|
};
|
|
3922
4250
|
|
|
3923
|
-
const
|
|
4251
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
3924
4252
|
|
|
3925
|
-
expect(
|
|
4253
|
+
expect(diagnostics).toEqual([
|
|
3926
4254
|
{
|
|
3927
4255
|
code: 'invalid-cron-schedule',
|
|
3928
4256
|
message: 'Cron trigger schedule must be a valid 5-field cron expression.',
|
|
3929
4257
|
path: ['triggers', 'nightly', 'config', 'schedule'],
|
|
3930
4258
|
details: {schedule},
|
|
4259
|
+
severity: 'error',
|
|
4260
|
+
scope: 'trigger',
|
|
3931
4261
|
},
|
|
3932
4262
|
]);
|
|
4263
|
+
expect(model.triggers).toEqual([]);
|
|
3933
4264
|
});
|
|
3934
4265
|
|
|
3935
|
-
it('
|
|
4266
|
+
it('makes a cron trigger with an invalid timezone inert', () => {
|
|
3936
4267
|
const document: WorkflowDocument = {
|
|
3937
4268
|
name: 'nightly trigger',
|
|
3938
4269
|
triggers: {
|
|
@@ -3952,16 +4283,19 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
3952
4283
|
},
|
|
3953
4284
|
};
|
|
3954
4285
|
|
|
3955
|
-
const
|
|
4286
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
3956
4287
|
|
|
3957
|
-
expect(
|
|
4288
|
+
expect(diagnostics).toEqual([
|
|
3958
4289
|
{
|
|
3959
4290
|
code: 'invalid-cron-timezone',
|
|
3960
4291
|
message: 'Cron trigger timezone must be a valid IANA time zone.',
|
|
3961
4292
|
path: ['triggers', 'nightly', 'config', 'timezone'],
|
|
3962
4293
|
details: {timezone: 'Not/A/Zone'},
|
|
4294
|
+
severity: 'error',
|
|
4295
|
+
scope: 'trigger',
|
|
3963
4296
|
},
|
|
3964
4297
|
]);
|
|
4298
|
+
expect(model.triggers).toEqual([]);
|
|
3965
4299
|
});
|
|
3966
4300
|
|
|
3967
4301
|
it('allows multiple cron triggers', () => {
|
|
@@ -4009,7 +4343,47 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
4009
4343
|
expect(model.triggers).toMatchObject([{id: 'manual', source: 'manual', event: 'fire'}]);
|
|
4010
4344
|
});
|
|
4011
4345
|
|
|
4012
|
-
it('
|
|
4346
|
+
it('keeps other triggers active when a cron trigger is inert', () => {
|
|
4347
|
+
const document: WorkflowDocument = {
|
|
4348
|
+
name: 'partially broken triggers',
|
|
4349
|
+
triggers: {
|
|
4350
|
+
nightly: {
|
|
4351
|
+
source: 'cron',
|
|
4352
|
+
event: 'tick',
|
|
4353
|
+
config: {schedule: 'not a cron'},
|
|
4354
|
+
},
|
|
4355
|
+
on_demand: {source: 'manual', event: 'fire'},
|
|
4356
|
+
},
|
|
4357
|
+
jobs: {
|
|
4358
|
+
build: {
|
|
4359
|
+
steps: [{run: 'npm run build'}],
|
|
4360
|
+
},
|
|
4361
|
+
},
|
|
4362
|
+
};
|
|
4363
|
+
|
|
4364
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
4365
|
+
|
|
4366
|
+
expect(diagnostics).toEqual([
|
|
4367
|
+
{
|
|
4368
|
+
code: 'invalid-cron-schedule',
|
|
4369
|
+
message: 'Cron trigger schedule must be a valid 5-field cron expression.',
|
|
4370
|
+
path: ['triggers', 'nightly', 'config', 'schedule'],
|
|
4371
|
+
details: {schedule: 'not a cron'},
|
|
4372
|
+
severity: 'error',
|
|
4373
|
+
scope: 'trigger',
|
|
4374
|
+
},
|
|
4375
|
+
]);
|
|
4376
|
+
expect(model.triggers).toEqual([
|
|
4377
|
+
{
|
|
4378
|
+
id: 'on-demand',
|
|
4379
|
+
key: 'on_demand',
|
|
4380
|
+
source: 'manual',
|
|
4381
|
+
event: 'fire',
|
|
4382
|
+
},
|
|
4383
|
+
]);
|
|
4384
|
+
});
|
|
4385
|
+
|
|
4386
|
+
it('keeps the first manual trigger active and makes later ones inert', () => {
|
|
4013
4387
|
const document: WorkflowDocument = {
|
|
4014
4388
|
name: 'manual triggers',
|
|
4015
4389
|
triggers: {
|
|
@@ -4023,16 +4397,51 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
4023
4397
|
},
|
|
4024
4398
|
};
|
|
4025
4399
|
|
|
4026
|
-
const
|
|
4400
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
4027
4401
|
|
|
4028
|
-
expect(
|
|
4402
|
+
expect(diagnostics).toEqual([
|
|
4029
4403
|
{
|
|
4030
4404
|
code: 'multiple-manual-triggers',
|
|
4031
|
-
message:
|
|
4032
|
-
|
|
4405
|
+
message:
|
|
4406
|
+
'A workflow may declare at most one manual trigger; found 2: one, two. This trigger is inert because it is not the first manual trigger in document order.',
|
|
4407
|
+
path: ['triggers', 'two'],
|
|
4033
4408
|
details: {manualTriggerKeys: ['one', 'two']},
|
|
4409
|
+
severity: 'error',
|
|
4410
|
+
scope: 'trigger',
|
|
4034
4411
|
},
|
|
4035
4412
|
]);
|
|
4413
|
+
expect(model.triggers.map((trigger) => trigger.key)).toEqual(['one']);
|
|
4414
|
+
});
|
|
4415
|
+
|
|
4416
|
+
it('promotes the first valid manual trigger when an earlier one is inert', () => {
|
|
4417
|
+
const document: WorkflowDocument = {
|
|
4418
|
+
name: 'manual triggers with an inert first trigger',
|
|
4419
|
+
triggers: {
|
|
4420
|
+
broken: {
|
|
4421
|
+
source: 'manual',
|
|
4422
|
+
event: 'fire',
|
|
4423
|
+
filter: 'event.ref == "refs/heads/main"',
|
|
4424
|
+
},
|
|
4425
|
+
working: {source: 'manual', event: 'fire'},
|
|
4426
|
+
},
|
|
4427
|
+
jobs: {
|
|
4428
|
+
build: {
|
|
4429
|
+
steps: [{run: 'npm run build'}],
|
|
4430
|
+
},
|
|
4431
|
+
},
|
|
4432
|
+
};
|
|
4433
|
+
|
|
4434
|
+
const {model, diagnostics} = normalizeWithDiagnostics(document);
|
|
4435
|
+
|
|
4436
|
+
expect(diagnostics).toEqual([
|
|
4437
|
+
expect.objectContaining({
|
|
4438
|
+
code: 'invalid-trigger-filter',
|
|
4439
|
+
path: ['triggers', 'broken', 'filter'],
|
|
4440
|
+
severity: 'error',
|
|
4441
|
+
scope: 'trigger',
|
|
4442
|
+
}),
|
|
4443
|
+
]);
|
|
4444
|
+
expect(model.triggers.map((trigger) => trigger.key)).toEqual(['working']);
|
|
4036
4445
|
});
|
|
4037
4446
|
|
|
4038
4447
|
it('accumulates independent semantic issues in one pass', () => {
|
|
@@ -4985,6 +5394,55 @@ describe('normalizeWorkflowDocument', () => {
|
|
|
4985
5394
|
]);
|
|
4986
5395
|
});
|
|
4987
5396
|
|
|
5397
|
+
it('accepts a managed provider on Claude when the catalog exposes an Anthropic model', () => {
|
|
5398
|
+
const providerId = 'shipfox-managed';
|
|
5399
|
+
const managedCatalog: AgentValidationCatalog = {
|
|
5400
|
+
...agentValidationCatalog,
|
|
5401
|
+
providers: [
|
|
5402
|
+
...agentValidationCatalog.providers,
|
|
5403
|
+
{id: providerId, support_status: 'supported'},
|
|
5404
|
+
],
|
|
5405
|
+
harnesses: agentValidationCatalog.harnesses.map((harness) =>
|
|
5406
|
+
harness.id === 'claude'
|
|
5407
|
+
? {
|
|
5408
|
+
...harness,
|
|
5409
|
+
supported_provider_ids: [...harness.supported_provider_ids, providerId],
|
|
5410
|
+
model_ids_by_provider: {
|
|
5411
|
+
...harness.model_ids_by_provider,
|
|
5412
|
+
[providerId]: ['managed-claude'],
|
|
5413
|
+
},
|
|
5414
|
+
}
|
|
5415
|
+
: harness,
|
|
5416
|
+
),
|
|
5417
|
+
};
|
|
5418
|
+
|
|
5419
|
+
const model = normalizeWorkflowDocument(
|
|
5420
|
+
{
|
|
5421
|
+
name: 'managed Claude provider',
|
|
5422
|
+
jobs: {
|
|
5423
|
+
fix: {
|
|
5424
|
+
steps: [
|
|
5425
|
+
{
|
|
5426
|
+
harness: 'claude',
|
|
5427
|
+
provider: providerId,
|
|
5428
|
+
model: 'managed-claude',
|
|
5429
|
+
prompt: 'Fix it.',
|
|
5430
|
+
},
|
|
5431
|
+
],
|
|
5432
|
+
},
|
|
5433
|
+
},
|
|
5434
|
+
},
|
|
5435
|
+
{agentValidationCatalog: managedCatalog},
|
|
5436
|
+
);
|
|
5437
|
+
|
|
5438
|
+
expect(model.jobs[0]?.steps[0]).toMatchObject({
|
|
5439
|
+
kind: 'agent',
|
|
5440
|
+
harness: 'claude',
|
|
5441
|
+
provider: providerId,
|
|
5442
|
+
model: 'managed-claude',
|
|
5443
|
+
});
|
|
5444
|
+
});
|
|
5445
|
+
|
|
4988
5446
|
it('reports typed interpolation expression errors for trusted known contexts', () => {
|
|
4989
5447
|
const document: WorkflowDocument = {
|
|
4990
5448
|
name: 'bad trusted path',
|