@orchestraworks/cli 0.6.0 → 0.8.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/README.md +1 -1
- package/dist/generated/cli-commands.json +75 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/Artifact.d.ts +14 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/Artifact.js +9 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/ArtifactSummary.d.ts +14 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/ArtifactSummary.js +9 -0
- package/node_modules/@orchestraworks/client/dist/models/Artifact.d.ts +14 -0
- package/node_modules/@orchestraworks/client/dist/models/Artifact.js +10 -1
- package/node_modules/@orchestraworks/client/dist/models/ArtifactSummary.d.ts +14 -0
- package/node_modules/@orchestraworks/client/dist/models/ArtifactSummary.js +10 -0
- package/node_modules/@orchestraworks/client/package.json +1 -1
- package/package.json +4 -4
- package/skills/orchestra/SKILL.md +103 -5
- package/skills/orchestra-fdk/references/authoring.md +485 -0
- package/templates/engineering-team/package.json +1 -1
- package/templates/todo-factory/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ The full guide to every construct, secrets, connections and troubleshooting is [
|
|
|
20
20
|
```bash
|
|
21
21
|
npm install -g @orchestraworks/cli # the CLI, on your path as `orchestra`
|
|
22
22
|
npm install @orchestraworks/cli # the current release
|
|
23
|
-
npm install @orchestraworks/cli@0.
|
|
23
|
+
npm install @orchestraworks/cli@0.8.0 # this exact release
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
Then `import { Factory, Agent, … } from "@orchestraworks/cli"` normally — the package `exports` entry resolves
|
|
@@ -3173,6 +3173,11 @@
|
|
|
3173
3173
|
"kind": "scalar"
|
|
3174
3174
|
}
|
|
3175
3175
|
},
|
|
3176
|
+
"previewStatus": {
|
|
3177
|
+
"schema": {
|
|
3178
|
+
"kind": "scalar"
|
|
3179
|
+
}
|
|
3180
|
+
},
|
|
3176
3181
|
"openCommentCount": {
|
|
3177
3182
|
"schema": {
|
|
3178
3183
|
"kind": "scalar"
|
|
@@ -4440,6 +4445,11 @@
|
|
|
4440
4445
|
"kind": "scalar"
|
|
4441
4446
|
}
|
|
4442
4447
|
},
|
|
4448
|
+
"previewStatus": {
|
|
4449
|
+
"schema": {
|
|
4450
|
+
"kind": "scalar"
|
|
4451
|
+
}
|
|
4452
|
+
},
|
|
4443
4453
|
"openCommentCount": {
|
|
4444
4454
|
"schema": {
|
|
4445
4455
|
"kind": "scalar"
|
|
@@ -5123,6 +5133,11 @@
|
|
|
5123
5133
|
"kind": "scalar"
|
|
5124
5134
|
}
|
|
5125
5135
|
},
|
|
5136
|
+
"previewStatus": {
|
|
5137
|
+
"schema": {
|
|
5138
|
+
"kind": "scalar"
|
|
5139
|
+
}
|
|
5140
|
+
},
|
|
5126
5141
|
"openCommentCount": {
|
|
5127
5142
|
"schema": {
|
|
5128
5143
|
"kind": "scalar"
|
|
@@ -6106,6 +6121,11 @@
|
|
|
6106
6121
|
"kind": "scalar"
|
|
6107
6122
|
}
|
|
6108
6123
|
},
|
|
6124
|
+
"previewStatus": {
|
|
6125
|
+
"schema": {
|
|
6126
|
+
"kind": "scalar"
|
|
6127
|
+
}
|
|
6128
|
+
},
|
|
6109
6129
|
"openCommentCount": {
|
|
6110
6130
|
"schema": {
|
|
6111
6131
|
"kind": "scalar"
|
|
@@ -9705,6 +9725,11 @@
|
|
|
9705
9725
|
"kind": "scalar"
|
|
9706
9726
|
}
|
|
9707
9727
|
},
|
|
9728
|
+
"previewStatus": {
|
|
9729
|
+
"schema": {
|
|
9730
|
+
"kind": "scalar"
|
|
9731
|
+
}
|
|
9732
|
+
},
|
|
9708
9733
|
"openCommentCount": {
|
|
9709
9734
|
"schema": {
|
|
9710
9735
|
"kind": "scalar"
|
|
@@ -73620,6 +73645,11 @@
|
|
|
73620
73645
|
"kind": "scalar"
|
|
73621
73646
|
}
|
|
73622
73647
|
},
|
|
73648
|
+
"previewStatus": {
|
|
73649
|
+
"schema": {
|
|
73650
|
+
"kind": "scalar"
|
|
73651
|
+
}
|
|
73652
|
+
},
|
|
73623
73653
|
"openCommentCount": {
|
|
73624
73654
|
"schema": {
|
|
73625
73655
|
"kind": "scalar"
|
|
@@ -76038,6 +76068,11 @@
|
|
|
76038
76068
|
"kind": "scalar"
|
|
76039
76069
|
}
|
|
76040
76070
|
},
|
|
76071
|
+
"previewStatus": {
|
|
76072
|
+
"schema": {
|
|
76073
|
+
"kind": "scalar"
|
|
76074
|
+
}
|
|
76075
|
+
},
|
|
76041
76076
|
"openCommentCount": {
|
|
76042
76077
|
"schema": {
|
|
76043
76078
|
"kind": "scalar"
|
|
@@ -78541,6 +78576,11 @@
|
|
|
78541
78576
|
"kind": "scalar"
|
|
78542
78577
|
}
|
|
78543
78578
|
},
|
|
78579
|
+
"previewStatus": {
|
|
78580
|
+
"schema": {
|
|
78581
|
+
"kind": "scalar"
|
|
78582
|
+
}
|
|
78583
|
+
},
|
|
78544
78584
|
"openCommentCount": {
|
|
78545
78585
|
"schema": {
|
|
78546
78586
|
"kind": "scalar"
|
|
@@ -80858,6 +80898,11 @@
|
|
|
80858
80898
|
"kind": "scalar"
|
|
80859
80899
|
}
|
|
80860
80900
|
},
|
|
80901
|
+
"previewStatus": {
|
|
80902
|
+
"schema": {
|
|
80903
|
+
"kind": "scalar"
|
|
80904
|
+
}
|
|
80905
|
+
},
|
|
80861
80906
|
"openCommentCount": {
|
|
80862
80907
|
"schema": {
|
|
80863
80908
|
"kind": "scalar"
|
|
@@ -83383,6 +83428,11 @@
|
|
|
83383
83428
|
"kind": "scalar"
|
|
83384
83429
|
}
|
|
83385
83430
|
},
|
|
83431
|
+
"previewStatus": {
|
|
83432
|
+
"schema": {
|
|
83433
|
+
"kind": "scalar"
|
|
83434
|
+
}
|
|
83435
|
+
},
|
|
83386
83436
|
"openCommentCount": {
|
|
83387
83437
|
"schema": {
|
|
83388
83438
|
"kind": "scalar"
|
|
@@ -86243,6 +86293,11 @@
|
|
|
86243
86293
|
"kind": "scalar"
|
|
86244
86294
|
}
|
|
86245
86295
|
},
|
|
86296
|
+
"previewStatus": {
|
|
86297
|
+
"schema": {
|
|
86298
|
+
"kind": "scalar"
|
|
86299
|
+
}
|
|
86300
|
+
},
|
|
86246
86301
|
"openCommentCount": {
|
|
86247
86302
|
"schema": {
|
|
86248
86303
|
"kind": "scalar"
|
|
@@ -88555,6 +88610,11 @@
|
|
|
88555
88610
|
"kind": "scalar"
|
|
88556
88611
|
}
|
|
88557
88612
|
},
|
|
88613
|
+
"previewStatus": {
|
|
88614
|
+
"schema": {
|
|
88615
|
+
"kind": "scalar"
|
|
88616
|
+
}
|
|
88617
|
+
},
|
|
88558
88618
|
"openCommentCount": {
|
|
88559
88619
|
"schema": {
|
|
88560
88620
|
"kind": "scalar"
|
|
@@ -90823,6 +90883,11 @@
|
|
|
90823
90883
|
"kind": "scalar"
|
|
90824
90884
|
}
|
|
90825
90885
|
},
|
|
90886
|
+
"previewStatus": {
|
|
90887
|
+
"schema": {
|
|
90888
|
+
"kind": "scalar"
|
|
90889
|
+
}
|
|
90890
|
+
},
|
|
90826
90891
|
"openCommentCount": {
|
|
90827
90892
|
"schema": {
|
|
90828
90893
|
"kind": "scalar"
|
|
@@ -93158,6 +93223,11 @@
|
|
|
93158
93223
|
"kind": "scalar"
|
|
93159
93224
|
}
|
|
93160
93225
|
},
|
|
93226
|
+
"previewStatus": {
|
|
93227
|
+
"schema": {
|
|
93228
|
+
"kind": "scalar"
|
|
93229
|
+
}
|
|
93230
|
+
},
|
|
93161
93231
|
"openCommentCount": {
|
|
93162
93232
|
"schema": {
|
|
93163
93233
|
"kind": "scalar"
|
|
@@ -95532,6 +95602,11 @@
|
|
|
95532
95602
|
"kind": "scalar"
|
|
95533
95603
|
}
|
|
95534
95604
|
},
|
|
95605
|
+
"previewStatus": {
|
|
95606
|
+
"schema": {
|
|
95607
|
+
"kind": "scalar"
|
|
95608
|
+
}
|
|
95609
|
+
},
|
|
95535
95610
|
"openCommentCount": {
|
|
95536
95611
|
"schema": {
|
|
95537
95612
|
"kind": "scalar"
|
|
@@ -71,6 +71,12 @@ export interface Artifact {
|
|
|
71
71
|
* @memberof Artifact
|
|
72
72
|
*/
|
|
73
73
|
thumbnailUrl?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Whether the machine ingress gateway would route this Preview's port now and that port's url is published: ONLINE when both hold, UNREACHABLE otherwise. Absent when the newest version is not a link into the machine preview zone.
|
|
76
|
+
* @type {ArtifactPreviewStatusEnum}
|
|
77
|
+
* @memberof Artifact
|
|
78
|
+
*/
|
|
79
|
+
previewStatus?: ArtifactPreviewStatusEnum;
|
|
74
80
|
/**
|
|
75
81
|
* How many of its comments are open
|
|
76
82
|
* @type {number}
|
|
@@ -122,6 +128,14 @@ export interface Artifact {
|
|
|
122
128
|
[key: string]: string;
|
|
123
129
|
};
|
|
124
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* @export
|
|
133
|
+
*/
|
|
134
|
+
export declare const ArtifactPreviewStatusEnum: {
|
|
135
|
+
readonly Online: "ONLINE";
|
|
136
|
+
readonly Unreachable: "UNREACHABLE";
|
|
137
|
+
};
|
|
138
|
+
export type ArtifactPreviewStatusEnum = typeof ArtifactPreviewStatusEnum[keyof typeof ArtifactPreviewStatusEnum];
|
|
125
139
|
/**
|
|
126
140
|
* @export
|
|
127
141
|
*/
|
|
@@ -13,6 +13,13 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { ArtifactVersionFromJSON, ArtifactVersionToJSON, } from './ArtifactVersion';
|
|
15
15
|
import { IdentitySetFromJSON, IdentitySetToJSON, } from './IdentitySet';
|
|
16
|
+
/**
|
|
17
|
+
* @export
|
|
18
|
+
*/
|
|
19
|
+
export const ArtifactPreviewStatusEnum = {
|
|
20
|
+
Online: 'ONLINE',
|
|
21
|
+
Unreachable: 'UNREACHABLE'
|
|
22
|
+
};
|
|
16
23
|
/**
|
|
17
24
|
* @export
|
|
18
25
|
*/
|
|
@@ -63,6 +70,7 @@ export function ArtifactFromJSONTyped(json, ignoreDiscriminator) {
|
|
|
63
70
|
'versions': (json['versions'].map(ArtifactVersionFromJSON)),
|
|
64
71
|
'description': json['description'] == null ? undefined : json['description'],
|
|
65
72
|
'thumbnailUrl': json['thumbnailUrl'] == null ? undefined : json['thumbnailUrl'],
|
|
73
|
+
'previewStatus': json['previewStatus'] == null ? undefined : json['previewStatus'],
|
|
66
74
|
'openCommentCount': json['openCommentCount'],
|
|
67
75
|
'workItemId': json['workItemId'] == null ? undefined : json['workItemId'],
|
|
68
76
|
'notifyMode': json['notifyMode'],
|
|
@@ -90,6 +98,7 @@ export function ArtifactToJSONTyped(value, ignoreDiscriminator = false) {
|
|
|
90
98
|
'versions': (value['versions'].map(ArtifactVersionToJSON)),
|
|
91
99
|
'description': value['description'],
|
|
92
100
|
'thumbnailUrl': value['thumbnailUrl'],
|
|
101
|
+
'previewStatus': value['previewStatus'],
|
|
93
102
|
'openCommentCount': value['openCommentCount'],
|
|
94
103
|
'workItemId': value['workItemId'],
|
|
95
104
|
'notifyMode': value['notifyMode'],
|
|
@@ -82,6 +82,12 @@ export interface ArtifactSummary {
|
|
|
82
82
|
* @memberof ArtifactSummary
|
|
83
83
|
*/
|
|
84
84
|
thumbnailUrl?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Whether the machine ingress gateway would route this Preview's port now and that port's url is published: ONLINE when both hold, UNREACHABLE otherwise. Absent when the newest version is not a link into the machine preview zone.
|
|
87
|
+
* @type {ArtifactSummaryPreviewStatusEnum}
|
|
88
|
+
* @memberof ArtifactSummary
|
|
89
|
+
*/
|
|
90
|
+
previewStatus?: ArtifactSummaryPreviewStatusEnum;
|
|
85
91
|
/**
|
|
86
92
|
* How many of its comments are open
|
|
87
93
|
* @type {number}
|
|
@@ -115,6 +121,14 @@ export interface ArtifactSummary {
|
|
|
115
121
|
[key: string]: string;
|
|
116
122
|
};
|
|
117
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* @export
|
|
126
|
+
*/
|
|
127
|
+
export declare const ArtifactSummaryPreviewStatusEnum: {
|
|
128
|
+
readonly Online: "ONLINE";
|
|
129
|
+
readonly Unreachable: "UNREACHABLE";
|
|
130
|
+
};
|
|
131
|
+
export type ArtifactSummaryPreviewStatusEnum = typeof ArtifactSummaryPreviewStatusEnum[keyof typeof ArtifactSummaryPreviewStatusEnum];
|
|
118
132
|
/**
|
|
119
133
|
* Check if a given object implements the ArtifactSummary interface.
|
|
120
134
|
*/
|
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
* Do not edit the class manually.
|
|
13
13
|
*/
|
|
14
14
|
import { IdentitySetFromJSON, IdentitySetToJSON, } from './IdentitySet';
|
|
15
|
+
/**
|
|
16
|
+
* @export
|
|
17
|
+
*/
|
|
18
|
+
export const ArtifactSummaryPreviewStatusEnum = {
|
|
19
|
+
Online: 'ONLINE',
|
|
20
|
+
Unreachable: 'UNREACHABLE'
|
|
21
|
+
};
|
|
15
22
|
/**
|
|
16
23
|
* Check if a given object implements the ArtifactSummary interface.
|
|
17
24
|
*/
|
|
@@ -51,6 +58,7 @@ export function ArtifactSummaryFromJSONTyped(json, ignoreDiscriminator) {
|
|
|
51
58
|
'url': json['url'] == null ? undefined : json['url'],
|
|
52
59
|
'description': json['description'] == null ? undefined : json['description'],
|
|
53
60
|
'thumbnailUrl': json['thumbnailUrl'] == null ? undefined : json['thumbnailUrl'],
|
|
61
|
+
'previewStatus': json['previewStatus'] == null ? undefined : json['previewStatus'],
|
|
54
62
|
'openCommentCount': json['openCommentCount'],
|
|
55
63
|
'workItemId': json['workItemId'] == null ? undefined : json['workItemId'],
|
|
56
64
|
'createdBy': json['createdBy'] == null ? undefined : IdentitySetFromJSON(json['createdBy']),
|
|
@@ -77,6 +85,7 @@ export function ArtifactSummaryToJSONTyped(value, ignoreDiscriminator = false) {
|
|
|
77
85
|
'url': value['url'],
|
|
78
86
|
'description': value['description'],
|
|
79
87
|
'thumbnailUrl': value['thumbnailUrl'],
|
|
88
|
+
'previewStatus': value['previewStatus'],
|
|
80
89
|
'openCommentCount': value['openCommentCount'],
|
|
81
90
|
'workItemId': value['workItemId'],
|
|
82
91
|
'createdBy': IdentitySetToJSON(value['createdBy']),
|
|
@@ -71,6 +71,12 @@ export interface Artifact {
|
|
|
71
71
|
* @memberof Artifact
|
|
72
72
|
*/
|
|
73
73
|
thumbnailUrl?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Whether the machine ingress gateway would route this Preview's port now and that port's url is published: ONLINE when both hold, UNREACHABLE otherwise. Absent when the newest version is not a link into the machine preview zone.
|
|
76
|
+
* @type {ArtifactPreviewStatusEnum}
|
|
77
|
+
* @memberof Artifact
|
|
78
|
+
*/
|
|
79
|
+
previewStatus?: ArtifactPreviewStatusEnum;
|
|
74
80
|
/**
|
|
75
81
|
* How many of its comments are open
|
|
76
82
|
* @type {number}
|
|
@@ -122,6 +128,14 @@ export interface Artifact {
|
|
|
122
128
|
[key: string]: string;
|
|
123
129
|
};
|
|
124
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* @export
|
|
133
|
+
*/
|
|
134
|
+
export declare const ArtifactPreviewStatusEnum: {
|
|
135
|
+
readonly Online: "ONLINE";
|
|
136
|
+
readonly Unreachable: "UNREACHABLE";
|
|
137
|
+
};
|
|
138
|
+
export type ArtifactPreviewStatusEnum = typeof ArtifactPreviewStatusEnum[keyof typeof ArtifactPreviewStatusEnum];
|
|
125
139
|
/**
|
|
126
140
|
* @export
|
|
127
141
|
*/
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Do not edit the class manually.
|
|
14
14
|
*/
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.ArtifactNotifyModeEnum = void 0;
|
|
16
|
+
exports.ArtifactNotifyModeEnum = exports.ArtifactPreviewStatusEnum = void 0;
|
|
17
17
|
exports.instanceOfArtifact = instanceOfArtifact;
|
|
18
18
|
exports.ArtifactFromJSON = ArtifactFromJSON;
|
|
19
19
|
exports.ArtifactFromJSONTyped = ArtifactFromJSONTyped;
|
|
@@ -21,6 +21,13 @@ exports.ArtifactToJSON = ArtifactToJSON;
|
|
|
21
21
|
exports.ArtifactToJSONTyped = ArtifactToJSONTyped;
|
|
22
22
|
const ArtifactVersion_1 = require("./ArtifactVersion");
|
|
23
23
|
const IdentitySet_1 = require("./IdentitySet");
|
|
24
|
+
/**
|
|
25
|
+
* @export
|
|
26
|
+
*/
|
|
27
|
+
exports.ArtifactPreviewStatusEnum = {
|
|
28
|
+
Online: 'ONLINE',
|
|
29
|
+
Unreachable: 'UNREACHABLE'
|
|
30
|
+
};
|
|
24
31
|
/**
|
|
25
32
|
* @export
|
|
26
33
|
*/
|
|
@@ -71,6 +78,7 @@ function ArtifactFromJSONTyped(json, ignoreDiscriminator) {
|
|
|
71
78
|
'versions': (json['versions'].map(ArtifactVersion_1.ArtifactVersionFromJSON)),
|
|
72
79
|
'description': json['description'] == null ? undefined : json['description'],
|
|
73
80
|
'thumbnailUrl': json['thumbnailUrl'] == null ? undefined : json['thumbnailUrl'],
|
|
81
|
+
'previewStatus': json['previewStatus'] == null ? undefined : json['previewStatus'],
|
|
74
82
|
'openCommentCount': json['openCommentCount'],
|
|
75
83
|
'workItemId': json['workItemId'] == null ? undefined : json['workItemId'],
|
|
76
84
|
'notifyMode': json['notifyMode'],
|
|
@@ -98,6 +106,7 @@ function ArtifactToJSONTyped(value, ignoreDiscriminator = false) {
|
|
|
98
106
|
'versions': (value['versions'].map(ArtifactVersion_1.ArtifactVersionToJSON)),
|
|
99
107
|
'description': value['description'],
|
|
100
108
|
'thumbnailUrl': value['thumbnailUrl'],
|
|
109
|
+
'previewStatus': value['previewStatus'],
|
|
101
110
|
'openCommentCount': value['openCommentCount'],
|
|
102
111
|
'workItemId': value['workItemId'],
|
|
103
112
|
'notifyMode': value['notifyMode'],
|
|
@@ -82,6 +82,12 @@ export interface ArtifactSummary {
|
|
|
82
82
|
* @memberof ArtifactSummary
|
|
83
83
|
*/
|
|
84
84
|
thumbnailUrl?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Whether the machine ingress gateway would route this Preview's port now and that port's url is published: ONLINE when both hold, UNREACHABLE otherwise. Absent when the newest version is not a link into the machine preview zone.
|
|
87
|
+
* @type {ArtifactSummaryPreviewStatusEnum}
|
|
88
|
+
* @memberof ArtifactSummary
|
|
89
|
+
*/
|
|
90
|
+
previewStatus?: ArtifactSummaryPreviewStatusEnum;
|
|
85
91
|
/**
|
|
86
92
|
* How many of its comments are open
|
|
87
93
|
* @type {number}
|
|
@@ -115,6 +121,14 @@ export interface ArtifactSummary {
|
|
|
115
121
|
[key: string]: string;
|
|
116
122
|
};
|
|
117
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* @export
|
|
126
|
+
*/
|
|
127
|
+
export declare const ArtifactSummaryPreviewStatusEnum: {
|
|
128
|
+
readonly Online: "ONLINE";
|
|
129
|
+
readonly Unreachable: "UNREACHABLE";
|
|
130
|
+
};
|
|
131
|
+
export type ArtifactSummaryPreviewStatusEnum = typeof ArtifactSummaryPreviewStatusEnum[keyof typeof ArtifactSummaryPreviewStatusEnum];
|
|
118
132
|
/**
|
|
119
133
|
* Check if a given object implements the ArtifactSummary interface.
|
|
120
134
|
*/
|
|
@@ -13,12 +13,20 @@
|
|
|
13
13
|
* Do not edit the class manually.
|
|
14
14
|
*/
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.ArtifactSummaryPreviewStatusEnum = void 0;
|
|
16
17
|
exports.instanceOfArtifactSummary = instanceOfArtifactSummary;
|
|
17
18
|
exports.ArtifactSummaryFromJSON = ArtifactSummaryFromJSON;
|
|
18
19
|
exports.ArtifactSummaryFromJSONTyped = ArtifactSummaryFromJSONTyped;
|
|
19
20
|
exports.ArtifactSummaryToJSON = ArtifactSummaryToJSON;
|
|
20
21
|
exports.ArtifactSummaryToJSONTyped = ArtifactSummaryToJSONTyped;
|
|
21
22
|
const IdentitySet_1 = require("./IdentitySet");
|
|
23
|
+
/**
|
|
24
|
+
* @export
|
|
25
|
+
*/
|
|
26
|
+
exports.ArtifactSummaryPreviewStatusEnum = {
|
|
27
|
+
Online: 'ONLINE',
|
|
28
|
+
Unreachable: 'UNREACHABLE'
|
|
29
|
+
};
|
|
22
30
|
/**
|
|
23
31
|
* Check if a given object implements the ArtifactSummary interface.
|
|
24
32
|
*/
|
|
@@ -58,6 +66,7 @@ function ArtifactSummaryFromJSONTyped(json, ignoreDiscriminator) {
|
|
|
58
66
|
'url': json['url'] == null ? undefined : json['url'],
|
|
59
67
|
'description': json['description'] == null ? undefined : json['description'],
|
|
60
68
|
'thumbnailUrl': json['thumbnailUrl'] == null ? undefined : json['thumbnailUrl'],
|
|
69
|
+
'previewStatus': json['previewStatus'] == null ? undefined : json['previewStatus'],
|
|
61
70
|
'openCommentCount': json['openCommentCount'],
|
|
62
71
|
'workItemId': json['workItemId'] == null ? undefined : json['workItemId'],
|
|
63
72
|
'createdBy': json['createdBy'] == null ? undefined : (0, IdentitySet_1.IdentitySetFromJSON)(json['createdBy']),
|
|
@@ -84,6 +93,7 @@ function ArtifactSummaryToJSONTyped(value, ignoreDiscriminator = false) {
|
|
|
84
93
|
'url': value['url'],
|
|
85
94
|
'description': value['description'],
|
|
86
95
|
'thumbnailUrl': value['thumbnailUrl'],
|
|
96
|
+
'previewStatus': value['previewStatus'],
|
|
87
97
|
'openCommentCount': value['openCommentCount'],
|
|
88
98
|
'workItemId': value['workItemId'],
|
|
89
99
|
'createdBy': (0, IdentitySet_1.IdentitySetToJSON)(value['createdBy']),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orchestraworks/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The Orchestra command line.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -109,13 +109,13 @@
|
|
|
109
109
|
},
|
|
110
110
|
"dependencies": {
|
|
111
111
|
"@agentclientprotocol/sdk": "1.3.0",
|
|
112
|
-
"@orchestraworks/client": "0.
|
|
113
|
-
"@orchestraworks/worker": "0.0.
|
|
112
|
+
"@orchestraworks/client": "0.8.0",
|
|
113
|
+
"@orchestraworks/worker": "0.0.144",
|
|
114
114
|
"commander": "^14.0.0",
|
|
115
115
|
"liquidjs": "^10.28.0"
|
|
116
116
|
},
|
|
117
117
|
"optionalDependencies": {
|
|
118
|
-
"@orchestraworks/local": "0.0.
|
|
118
|
+
"@orchestraworks/local": "0.0.144"
|
|
119
119
|
},
|
|
120
120
|
"bundleDependencies": [
|
|
121
121
|
"@orchestraworks/client"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: orchestra
|
|
3
|
-
description: Use when you need to understand or explain an Orchestra concept - organization, role, agent, version, environment, machine, network rules, secret, reaching a model (subscription, model gateway and gateway key, own key), session, pipeline, work item, workflow rules, trigger, skill, file, plan, or the factory as code - or how the orchestra command line works, what the platform cannot do yet, and where an agent's knowledge belongs. A reference to read, not a list of steps.
|
|
3
|
+
description: Use when you need to understand or explain an Orchestra concept - organization, role, agent, version, environment, machine, network rules, secret, reaching a model (subscription, model gateway and gateway key, own key), session, pipeline, work item and its check gates, workflow rules and the judge, trigger, skill, file, plan, or the factory as code - or how the orchestra command line works, what the platform cannot do yet, and where an agent's knowledge belongs. A reference to read, not a list of steps.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Orchestra: the ideas behind the platform
|
|
@@ -83,7 +83,7 @@ cli/templates/engineering-team/main.ts. Script agents: docs/architecture/api.md:
|
|
|
83
83
|
values), :1398 (no model settings on script or custom); docs/architecture/agent-skills-and-runtime-manifest.md
|
|
84
84
|
§ SCRIPT command and input (program from instructions, input on stdin, no skills); agent-runtime-images.md:862-872
|
|
85
85
|
(bash on Linux, PowerShell on Windows); docs/architecture/events-and-triggers.md:146-154 (a schedule trigger
|
|
86
|
-
targets a pipeline)
|
|
86
|
+
targets a pipeline). -->
|
|
87
87
|
|
|
88
88
|
**What it is.** An *agent* is a worker with a name, standing *instructions*, one environment, a harness, a
|
|
89
89
|
model, and the skills and files it uses. The *harness* is the coding program that runs the agent on a machine:
|
|
@@ -120,8 +120,9 @@ reaches the program on its standard input. It starts like any agent:
|
|
|
120
120
|
to the agent. A schedule trigger targets a pipeline, never an agent directly.
|
|
121
121
|
|
|
122
122
|
A `script` agent cannot use skills or model settings (the platform refuses both with a 400), and it cannot
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
read, write or judge prose the way a model does. It can be a judge of a fixed rule, such as "the description
|
|
124
|
+
has a Release notes section": the program decides and writes the check itself (section 11). A turn runs its
|
|
125
|
+
whole program again, so the program must be safe to run twice. It is billed as an agent: **Settings › Plan & usage** shows the plan.
|
|
125
126
|
|
|
126
127
|
**Cannot yet.**
|
|
127
128
|
- Change an agent's harness after it is created. Make a new agent instead.
|
|
@@ -445,12 +446,90 @@ it, puts the code bundle's file id in it, and assigns it to an Engineer Agent.
|
|
|
445
446
|
|
|
446
447
|
**Cannot yet.** Declare items in the factory code. Create them with `orchestra work-items create`.
|
|
447
448
|
|
|
449
|
+
### Checks and gates
|
|
450
|
+
|
|
451
|
+
<!-- Sources, on develop at b155aff3a8: docs/architecture/work-plane.md:197-205 (a reopen clears every check;
|
|
452
|
+
one vote per writer per key; counts; nothing declares a key ahead), :274-277 (approval as an IAM-restricted
|
|
453
|
+
key; isAssignee; passUsers), :480-486 (what requires reads), :490-493 (POST /decisions writes nothing), :575
|
|
454
|
+
(one key per grant), :634 (.result is the latest write; .writer.* is the latest writer; counts read 0 when
|
|
455
|
+
absent), :656-657 (setCheck, clearCheck; the author holds the key at save), :673 (body {result, summary?,
|
|
456
|
+
evidence?}; owner and admin write every key);
|
|
457
|
+
docs/architecture/iam.md:48-56 (statement shape), :1055 (no managed role but owner and admin holds the
|
|
458
|
+
action); cli/src/constructs/pipeline.ts:218-228 (allowCheck; apply writes no IAM);
|
|
459
|
+
cli/src/engine/pipeline-reconcile.ts:441-455 (the printed notice); cli/src/generated/cli-commands.json,
|
|
460
|
+
operationId setWorkItemCheck (set-check flags: --result, --summary; evidence only through --body);
|
|
461
|
+
platform/src/main/kotlin/ai/livingcomputers/softwarefactory/workplane/controller/WorkplaneWorkItemController.kt:267
|
|
462
|
+
(the item path takes an id or a slug). -->
|
|
463
|
+
|
|
464
|
+
**What it's for.** A gate keeps an item out of a stage until the work passed a test, a review or a person's
|
|
465
|
+
approval.
|
|
466
|
+
|
|
467
|
+
**What it is.** A *check* is a named key on an item, such as `ci`, `review` or `approval`. Each writer has
|
|
468
|
+
one *vote* on a key: `PASS`, `FAIL` or `SKIP`, with an optional summary and evidence links. A writer's new
|
|
469
|
+
vote replaces its own earlier vote. Nobody declares a key ahead: a stage's `requires` names the keys it
|
|
470
|
+
reads. A *gate* is a stage's `requires` list. The platform reads it when something moves the item into the
|
|
471
|
+
stage, and refuses the move when a condition is false. A gate never asks anybody: it reads the stored votes.
|
|
472
|
+
|
|
473
|
+
A stage's `requires` can read these fields of a key:
|
|
474
|
+
|
|
475
|
+
| Field | Value |
|
|
476
|
+
|---|---|
|
|
477
|
+
| `checks.<key>.result` | The result of the latest vote: `PASS`, `FAIL` or `SKIP` |
|
|
478
|
+
| `checks.<key>.pass`, `.fail` | How many writers voted `PASS`, `FAIL` (0 when nobody voted) |
|
|
479
|
+
| `checks.<key>.passUsers`, `.passAgents` | How many people, agents, voted `PASS` |
|
|
480
|
+
| `checks.<key>.writer.isAssignee` | Whether the writer of the latest vote held the item when it voted |
|
|
481
|
+
|
|
482
|
+
`.writer.isAssignee` reads the latest vote only. With two approvers, it cannot prove that neither of them
|
|
483
|
+
is the assignee.
|
|
484
|
+
|
|
485
|
+
**Who can write a key.** IAM decides, one key at a time: the action `work-items:setWorkItemCheck` on the
|
|
486
|
+
resource `work-items/*/checks/<key>`. Owner and admin write every key. No other built-in role holds the
|
|
487
|
+
action, so each agent, service account or group of people that votes needs a policy for its key. A
|
|
488
|
+
policy is a list of statements, such as:
|
|
489
|
+
|
|
490
|
+
```json
|
|
491
|
+
{ "effect": "allow", "actions": ["work-items:setWorkItemCheck"], "resources": ["work-items/*/checks/ci"] }
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
In the factory code, `pipeline.allowCheck(agent, "review")` names that grant. Apply does not write it: it
|
|
495
|
+
prints the statement for an administrator to attach to the agent's membership.
|
|
496
|
+
|
|
497
|
+
**How a vote is written.**
|
|
498
|
+
|
|
499
|
+
- The command: `orchestra work-items set-check <item> <key> --result PASS --summary "…"`. `<item>` is the
|
|
500
|
+
item's id or its slug, such as `bug-12`. Evidence links go in a JSON file passed with `--body @vote.json`.
|
|
501
|
+
- The API: `PUT /work-items/{id}/checks/{key}` with `{result, summary?, evidence?}`.
|
|
502
|
+
- A workflow rule: `setCheck` writes the rule's own vote, and `clearCheck` removes every vote on a key. The
|
|
503
|
+
person who applies the rule must hold the key.
|
|
504
|
+
- A judge: a rule's `evaluate` asks an agent to vote (section 11).
|
|
505
|
+
|
|
506
|
+
**A reopen clears every check.** A move from a closed stage (`DONE` or `CANCELED`) back to an open one
|
|
507
|
+
removes every vote on every key, before the target stage's gate is read.
|
|
508
|
+
|
|
509
|
+
**Not a check writer.** `POST /decisions` reads the text typed into the console's "+ Create" box and
|
|
510
|
+
proposes the fields of a new item. It writes nothing, and it never writes a check.
|
|
511
|
+
|
|
512
|
+
**Recipes.** The orchestra-fdk skill's `references/authoring.md`, "Check gates", has five complete
|
|
513
|
+
examples: a model judge, a CI result, a script judge, a two-person approval and a decision model judge.
|
|
514
|
+
|
|
448
515
|
## 11. Workflow rules and the judge
|
|
449
516
|
|
|
450
517
|
<!-- Sources, on develop at 19459f9ebb: docs/architecture/multi-agent-workflows.md:18-30 (a workflow is a named,
|
|
451
518
|
versioned list of rules for one pipeline); docs/architecture/work-plane.md:364-370 (workflow entries pick which
|
|
452
519
|
version runs on which items), :266-279 (evaluate asks a judge to decide one check; refused when the judge is the
|
|
453
|
-
assignee), :573 (rule actions); AGENTS.md "API Conventions" (no runs, no graph engine).
|
|
520
|
+
assignee), :573 (rule actions); AGENTS.md "API Conventions" (no runs, no graph engine). Judge facts, on develop
|
|
521
|
+
at b155aff3a8: docs/architecture/work-plane.md:279-285 (the judge writes under its own grant, else 403; a gate
|
|
522
|
+
never calls a judge; a failed judge turn writes no SESSION_FAILED, so an inStageFor rule re-runs evaluate), :658
|
|
523
|
+
(evaluate starts or wakes the judge's session); platform/src/main/kotlin/ai/livingcomputers/softwarefactory/
|
|
524
|
+
workplane/service/WorkItemSessionService.kt:188-208 (the turn's text names the item, the criterion and the PUT
|
|
525
|
+
line); worker/script-harness/index.js:682-717 (a script turn's exit code sets the session outcome, not a check).
|
|
526
|
+
A decision model judge, on 3664271a69: https://docs.typesafe.ai/api.md and platform/src/main/kotlin/ai/
|
|
527
|
+
livingcomputers/softwarefactory/integration/typesafe/TypeSafeDecisionClient.kt (choice and noul questions);
|
|
528
|
+
docs/architecture/work-plane.md:496-498 (a decision model picks among options and writes no text), :562-564 (the
|
|
529
|
+
platform's own calls run on its key). The session lookup, on 20b09bc114: WorkItemSessionService.kt:332-392
|
|
530
|
+
(converse finds the judge's link on the item, else startConversation starts a session with a link to that item
|
|
531
|
+
alone); docs/architecture/work-plane.md:193-194 (session keeps the items a link names); docs/architecture/
|
|
532
|
+
fwp.md:1060-1063 (ORCHESTRA_SESSION_ID on every harness process, the script's command included). -->
|
|
454
533
|
|
|
455
534
|
**What it's for.** Rules do the routine moves, so people and agents do not have to: assign the next agent, move
|
|
456
535
|
an item, ask for a review.
|
|
@@ -463,6 +542,25 @@ what it does: move, assign, create an item, or evaluate. There are no workflow r
|
|
|
463
542
|
the judge's session on the item. The judge writes the check, and a stage that requires it then lets the item in.
|
|
464
543
|
The item's own assignee cannot be its judge.
|
|
465
544
|
|
|
545
|
+
A judge can also be a script agent that asks a *decision model*, such as TypeSafe's Jev, with the customer's own
|
|
546
|
+
key. That model picks one of a fixed list of options, or gives the probability of yes, and writes no text. The
|
|
547
|
+
recipe is in section 10's Recipes.
|
|
548
|
+
|
|
549
|
+
**What a judge must know.**
|
|
550
|
+
|
|
551
|
+
- The judge's turn names the item, the key and the criterion, and the `PUT /work-items/<id>/checks/<key>`
|
|
552
|
+
call to make. The judge must make that call itself, for example with `orchestra work-items set-check`.
|
|
553
|
+
The platform does not read the judge's reply or its session outcome into the check.
|
|
554
|
+
- The judge's session is on that one item: a judge of two items has two sessions. A program can find its
|
|
555
|
+
item with `orchestra work-items list --session "$ORCHESTRA_SESSION_ID"` and read none of the turn's text.
|
|
556
|
+
- The judge needs its own grant on the key (section 10). Without it, its write gets a 403, and the gate
|
|
557
|
+
stays shut.
|
|
558
|
+
- A rule that names the item's assignee as the judge is refused, with a note in the item's feed.
|
|
559
|
+
- A failed judge turn writes no `SESSION_FAILED` entry, so no failure rule sees it. To recover, add a rule
|
|
560
|
+
on `inStageFor` that runs `evaluate` again while the check is still absent.
|
|
561
|
+
- A gate does not start a judge. Something must run `evaluate` before the move, usually a rule on entering
|
|
562
|
+
the stage before the gated one.
|
|
563
|
+
|
|
466
564
|
**How it relates.** Rules move items through stages (section 9) and write to checks (section 10).
|
|
467
565
|
|
|
468
566
|
**In the starter.** None yet. Agents and people move items by hand.
|
|
@@ -961,6 +961,491 @@ new Trigger(factory, "bug-intake", {
|
|
|
961
961
|
each to what you declared. A revert made in the app shows in `orchestra fdk diff` as the rules apply
|
|
962
962
|
would write back.
|
|
963
963
|
|
|
964
|
+
## Check gates
|
|
965
|
+
|
|
966
|
+
A *gate* is a stage's `requires` list. It keeps an item out of the stage until its checks say so. A
|
|
967
|
+
*check* is a named key on an item. Each writer has one vote on it: `PASS`, `FAIL` or `SKIP`, with an
|
|
968
|
+
optional summary and evidence links. The orchestra skill's section 10, "Checks and gates", explains the
|
|
969
|
+
model. This section is the setup.
|
|
970
|
+
|
|
971
|
+
**Set up a gate in four steps.**
|
|
972
|
+
|
|
973
|
+
1. **Pick a key**, such as `ci`, `review` or `approval`. Nothing declares it ahead.
|
|
974
|
+
2. **Write the gate.** Add conditions over the key to the stage's `requires`. A condition is
|
|
975
|
+
`{ field, op, value }`, and a count takes a number value: `{ field: "checks.approval.passUsers", op:
|
|
976
|
+
"gte", value: 2 }`. A stage reads `checks.<key>.result`, the counts `.pass`, `.fail`, `.passUsers` and
|
|
977
|
+
`.passAgents`, and `.writer.isAssignee`, which describes the latest vote only.
|
|
978
|
+
3. **Grant the key to each writer.** The action is `work-items:setWorkItemCheck` on the resource
|
|
979
|
+
`work-items/*/checks/<key>`, one key per grant. Only owner and admin hold it by default. For an agent,
|
|
980
|
+
`pipeline.allowCheck(agent, "<key>")` declares the grant, and apply prints the statement for an
|
|
981
|
+
administrator to attach. Apply writes no IAM.
|
|
982
|
+
4. **Make something vote.** A judge agent that a rule's `evaluate` asks, a CI job or a person runs
|
|
983
|
+
`orchestra work-items set-check <item> <key> --result PASS|FAIL|SKIP --summary "…"`. `<item>` is the
|
|
984
|
+
item's id or its slug, such as `feat-12`. Evidence links go in a JSON file passed with
|
|
985
|
+
`--body @vote.json`. A rule can also vote with `setCheck`, or clear every vote with `clearCheck`; the
|
|
986
|
+
person who applies that rule must hold the key.
|
|
987
|
+
|
|
988
|
+
Before you rely on a gate, read the orchestra skill's section 11, "What a judge must know", and the
|
|
989
|
+
reopen rule in its section 10. The examples follow those rules: a rule runs `evaluate` on entering the
|
|
990
|
+
stage before the gated one, the judge votes with `orchestra work-items set-check`, and an `inStageFor`
|
|
991
|
+
rule asks again while the check is absent.
|
|
992
|
+
|
|
993
|
+
The examples build on the `factory` app and the import line of § The construct model. Example 3 runs on its
|
|
994
|
+
`runtime` environment, and Examples 1 and 5 declare their own. Each example imports only the names that
|
|
995
|
+
section and the earlier examples do not import, so all of them paste into one `main.ts`.
|
|
996
|
+
|
|
997
|
+
### Example 1: a model judge on an OpenAI model
|
|
998
|
+
|
|
999
|
+
A `codex` agent judges the `review` key through the model gateway. Its environment, `codexRuntime`, differs
|
|
1000
|
+
from `runtime` in two places:
|
|
1001
|
+
|
|
1002
|
+
- **`env` sets `INITIAL_AGENT_MODE: "agent-full-access"`.** Codex opens each session with a command
|
|
1003
|
+
sandbox that has no network. Without this mode, the judge cannot reach the platform to vote. The
|
|
1004
|
+
egress policy still decides which hosts the judge may reach.
|
|
1005
|
+
- **The egress policy carries the gateway rule**, `routeModelsViaGateway`, with the gateway key's secret.
|
|
1006
|
+
An agent whose model settings turn on the model gateway needs it.
|
|
1007
|
+
|
|
1008
|
+
```ts
|
|
1009
|
+
import {
|
|
1010
|
+
GatewayKey, Pipeline, Workflow, check, clearCheck, entered, evaluate, inStageFor, move, rule,
|
|
1011
|
+
} from "@orchestraworks/cli";
|
|
1012
|
+
|
|
1013
|
+
const gatewayKey = new Secret(factory, "model-gateway-key", {
|
|
1014
|
+
value: SecretValue.fromGatewayKey(new GatewayKey(factory, "model-gateway")),
|
|
1015
|
+
injection: {
|
|
1016
|
+
headers: [{ name: "Authorization", value: "Bearer ${secret}" }],
|
|
1017
|
+
removeHeaders: ["x-api-key"],
|
|
1018
|
+
hosts: ["models.orchestraworks.ai"],
|
|
1019
|
+
},
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
const codexRuntime = new Environment(factory, "codex-runtime", {
|
|
1023
|
+
name: "Codex judge environment",
|
|
1024
|
+
image: Image.fromTag("acme/support-agent:1.0.0"),
|
|
1025
|
+
platform: { os: "linux", arch: "amd64" },
|
|
1026
|
+
resources: { cpu: "2", memory: "4G" },
|
|
1027
|
+
env: { INITIAL_AGENT_MODE: "agent-full-access" },
|
|
1028
|
+
egress: EgressPolicy.denyAll()
|
|
1029
|
+
.applyPreset(egressPreset.softwareDevelopment())
|
|
1030
|
+
.routeModelsViaGateway({ key: gatewayKey }),
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
const reviewer = new Agent(factory, "reviewer", {
|
|
1034
|
+
environment: codexRuntime,
|
|
1035
|
+
harness: "codex",
|
|
1036
|
+
modelSettings: { model: "gpt-6.1-sol", enableModelGateway: true },
|
|
1037
|
+
instructions: `You judge work items. You never change the code or the item.
|
|
1038
|
+
|
|
1039
|
+
When a turn asks you to set a check:
|
|
1040
|
+
1. Read the item: orchestra work-items get <item id>
|
|
1041
|
+
2. Decide against the criterion the turn gives.
|
|
1042
|
+
3. Record the decision. Only this command counts; your reply is not read:
|
|
1043
|
+
orchestra work-items set-check <item id> <key> --result PASS --summary "<one line: why>"
|
|
1044
|
+
Use --result FAIL when the work does not meet the criterion. Use the item id and key the turn names.`,
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
const features = new Pipeline(factory, "features", {
|
|
1048
|
+
itemSlugPrefix: "feat",
|
|
1049
|
+
stages: [
|
|
1050
|
+
{ slug: "building", category: "IN_PROGRESS" },
|
|
1051
|
+
{ slug: "review", category: "IN_PROGRESS" },
|
|
1052
|
+
{ slug: "done", category: "DONE", requires: [{ field: "checks.review.result", op: "is", value: "PASS" }] },
|
|
1053
|
+
],
|
|
1054
|
+
}).allowCheck(reviewer, "review");
|
|
1055
|
+
|
|
1056
|
+
const askReviewer = evaluate({
|
|
1057
|
+
agent: reviewer,
|
|
1058
|
+
check: "review",
|
|
1059
|
+
criterion: "The change does what the item asks, its tests pass, and it adds no unrelated change.",
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
new Workflow(factory, "features-review", {
|
|
1063
|
+
pipeline: features,
|
|
1064
|
+
description: "Asks the reviewer agent to judge each feature in Review and moves the item on its vote.",
|
|
1065
|
+
rules: [
|
|
1066
|
+
rule({
|
|
1067
|
+
name: "Clear the old vote and ask the reviewer on entering Review",
|
|
1068
|
+
when: entered("review"),
|
|
1069
|
+
then: [clearCheck("review"), askReviewer],
|
|
1070
|
+
}),
|
|
1071
|
+
rule({
|
|
1072
|
+
name: "Ask the reviewer again after an hour with no vote",
|
|
1073
|
+
when: inStageFor("review", "1h"),
|
|
1074
|
+
if: { field: "checks.review.result", op: "isAbsent" },
|
|
1075
|
+
then: [askReviewer],
|
|
1076
|
+
}),
|
|
1077
|
+
rule({ name: "Move a passed review to Done", when: check("review").is("PASS"), then: [move("done")] }),
|
|
1078
|
+
rule({ name: "Send a failed review back to Building", when: check("review").is("FAIL"), then: [move("building")] }),
|
|
1079
|
+
],
|
|
1080
|
+
});
|
|
1081
|
+
```
|
|
1082
|
+
|
|
1083
|
+
The first rule clears the vote of an earlier stay, so the second rule sees an absent check. A rule that
|
|
1084
|
+
writes or clears a check needs its author to hold the key, so the person who applies this workflow holds
|
|
1085
|
+
`review`, as owner and admin do. An `inStageFor` rule fires once in each stay in the stage. For more
|
|
1086
|
+
tries, add a second rule with a longer duration.
|
|
1087
|
+
|
|
1088
|
+
**Attach the judge's grant before its first vote.** `allowCheck` only prints the policy statement, and a
|
|
1089
|
+
judge without it gets a 403. An administrator attaches it with the policy and membership commands of
|
|
1090
|
+
Example 2: write the policy for the `review` key, and take the membership id from the `membershipId` field
|
|
1091
|
+
that `orchestra agents get <agent id>` answers. The agent's membership already holds the policies the
|
|
1092
|
+
platform attached at its create, so keep them in `attach.json`.
|
|
1093
|
+
|
|
1094
|
+
### Example 2: a CI result from GitHub Actions
|
|
1095
|
+
|
|
1096
|
+
The CI job runs the tests and votes on the `ci` key as a service account. The FDK neither creates a
|
|
1097
|
+
service account nor writes a policy, so the setup is four commands, run once by an administrator:
|
|
1098
|
+
|
|
1099
|
+
```bash
|
|
1100
|
+
orchestra service-accounts create --name github-ci # note its id, svc_…, and its membershipId
|
|
1101
|
+
orchestra policies create --body @ci-check-policy.json # note its id, pol_…
|
|
1102
|
+
orchestra organization-memberships update <membership id> --body @attach.json
|
|
1103
|
+
orchestra api-keys create --principal-type SERVICE_ACCOUNT --principal-id <svc id> --name github-ci \
|
|
1104
|
+
--output-file github-ci-key.json
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
`ci-check-policy.json`:
|
|
1108
|
+
|
|
1109
|
+
```json
|
|
1110
|
+
{
|
|
1111
|
+
"name": "Write the ci check",
|
|
1112
|
+
"document": {
|
|
1113
|
+
"statements": [
|
|
1114
|
+
{ "effect": "allow", "actions": ["work-items:setWorkItemCheck"], "resources": ["work-items/*/checks/ci"] }
|
|
1115
|
+
]
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
```
|
|
1119
|
+
|
|
1120
|
+
`attach.json` replaces the membership's whole list of directly attached policies. Read the list first with
|
|
1121
|
+
`orchestra organization-memberships get <membership id>` and keep each entry in it:
|
|
1122
|
+
|
|
1123
|
+
```json
|
|
1124
|
+
{ "policyAssignments": [{ "policyId": "<pol id>", "allTeams": true }] }
|
|
1125
|
+
```
|
|
1126
|
+
|
|
1127
|
+
The platform shows a key's token once, in the answer to its create, and stores only its digest. The CLI
|
|
1128
|
+
masks the token on the screen, so the last command writes the whole answer to `github-ci-key.json`. Store
|
|
1129
|
+
its `token` field as the repository secret `CHECKS_CI_KEY`, then delete the file. The gate:
|
|
1130
|
+
|
|
1131
|
+
```ts
|
|
1132
|
+
{ slug: "merged", category: "DONE", requires: [{ field: "checks.ci.result", op: "is", value: "PASS" }] }
|
|
1133
|
+
```
|
|
1134
|
+
|
|
1135
|
+
The job must know the item. This example uses a team convention: the branch name starts with the item's
|
|
1136
|
+
slug, such as `feat-12-export-csv`. Use whatever convention your team keeps, such as a line in the pull
|
|
1137
|
+
request body.
|
|
1138
|
+
|
|
1139
|
+
```yaml
|
|
1140
|
+
name: tests
|
|
1141
|
+
on: pull_request
|
|
1142
|
+
jobs:
|
|
1143
|
+
test:
|
|
1144
|
+
runs-on: ubuntu-latest
|
|
1145
|
+
steps:
|
|
1146
|
+
- uses: actions/checkout@v4
|
|
1147
|
+
- id: test
|
|
1148
|
+
run: npm ci && npm test
|
|
1149
|
+
- name: Vote on the work item's ci check
|
|
1150
|
+
if: always()
|
|
1151
|
+
env:
|
|
1152
|
+
ORCHESTRA_BASE_URL: https://api.orchestraworks.ai
|
|
1153
|
+
ORCHESTRA_API_TOKEN: ${{ secrets.CHECKS_CI_KEY }}
|
|
1154
|
+
HEAD_REF: ${{ github.head_ref }}
|
|
1155
|
+
TEST_OUTCOME: ${{ steps.test.outcome }}
|
|
1156
|
+
run: |
|
|
1157
|
+
item=$(printf '%s' "$HEAD_REF" | grep -oE '^feat-[0-9]+' || true)
|
|
1158
|
+
if [ -z "$item" ]; then echo "The branch name names no work item"; exit 0; fi
|
|
1159
|
+
result=FAIL
|
|
1160
|
+
if [ "$TEST_OUTCOME" = success ]; then result=PASS; fi
|
|
1161
|
+
run_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
|
|
1162
|
+
printf '{"evidence":["%s"]}' "$run_url" > vote.json
|
|
1163
|
+
npx --yes @orchestraworks/cli work-items set-check "$item" ci \
|
|
1164
|
+
--result "$result" --summary "Tests: $TEST_OUTCOME" --body @vote.json
|
|
1165
|
+
```
|
|
1166
|
+
|
|
1167
|
+
Each run replaces the account's earlier vote, so a fix pushed after a `FAIL` turns the check to `PASS`.
|
|
1168
|
+
`ORCHESTRA_BASE_URL` is required: the CLI never sends a token to an address it did not read from you.
|
|
1169
|
+
|
|
1170
|
+
A GitHub event cannot write this check without the CI change. The GitHub connection delivers only
|
|
1171
|
+
`issues`, `issue_comment` and `pull_request` events, and none of them carries a test result.
|
|
1172
|
+
|
|
1173
|
+
### Example 3: a script judge
|
|
1174
|
+
|
|
1175
|
+
A `script` agent's `instructions` are a Bash program, so it judges a fixed rule with no model. Each turn's
|
|
1176
|
+
text reaches the program on standard input, and this program reads none of it. `evaluate` runs the judge in a
|
|
1177
|
+
session on that one item (the orchestra skill's section 11). So the program lists the items of its own
|
|
1178
|
+
session, `ORCHESTRA_SESSION_ID`, decides, and votes with `orchestra`. With no item or more than one, it stops
|
|
1179
|
+
with no vote. The machine image ships `orchestra` and `jq`. `orchestra` sends the agent's own token on each
|
|
1180
|
+
platform call, so the program holds no credential. The command also gets `PLATFORM_BASE_URL` and
|
|
1181
|
+
`ORCHESTRA_ORGANIZATION_ID`.
|
|
1182
|
+
|
|
1183
|
+
```ts
|
|
1184
|
+
import { changed } from "@orchestraworks/cli";
|
|
1185
|
+
|
|
1186
|
+
const notesJudge = new Agent(factory, "notes-judge", {
|
|
1187
|
+
environment: runtime,
|
|
1188
|
+
harness: "script",
|
|
1189
|
+
instructions: `set -euo pipefail
|
|
1190
|
+
items=$(orchestra work-items list --session "$ORCHESTRA_SESSION_ID" --output json)
|
|
1191
|
+
if [ "$(printf '%s' "$items" | jq '.data | length')" != 1 ]; then
|
|
1192
|
+
echo "This session is not on exactly one work item." >&2
|
|
1193
|
+
exit 1
|
|
1194
|
+
fi
|
|
1195
|
+
id=$(printf '%s' "$items" | jq -r '.data[0].id')
|
|
1196
|
+
description=$(printf '%s' "$items" | jq -r '.data[0].description // ""')
|
|
1197
|
+
if printf '%s' "$description" | grep -q '^## Release notes'; then
|
|
1198
|
+
orchestra work-items set-check "$id" notes --result PASS --summary "The description has a Release notes section."
|
|
1199
|
+
else
|
|
1200
|
+
orchestra work-items set-check "$id" notes --result FAIL --summary "Add a '## Release notes' section to the description."
|
|
1201
|
+
fi`,
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
const releases = new Pipeline(factory, "releases", {
|
|
1205
|
+
itemSlugPrefix: "rel",
|
|
1206
|
+
stages: [
|
|
1207
|
+
{ slug: "drafting", category: "TODO" },
|
|
1208
|
+
{ slug: "review", category: "IN_PROGRESS", requires: [{ field: "checks.notes.result", op: "is", value: "PASS" }] },
|
|
1209
|
+
{ slug: "released", category: "DONE" },
|
|
1210
|
+
],
|
|
1211
|
+
}).allowCheck(notesJudge, "notes");
|
|
1212
|
+
|
|
1213
|
+
const askNotesJudge = evaluate({
|
|
1214
|
+
agent: notesJudge,
|
|
1215
|
+
check: "notes",
|
|
1216
|
+
criterion: "The description has a Release notes section.",
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
new Workflow(factory, "releases-notes", {
|
|
1220
|
+
pipeline: releases,
|
|
1221
|
+
description: "Asks the notes judge to check each release's description while it is drafted.",
|
|
1222
|
+
rules: [
|
|
1223
|
+
rule({ name: "Ask the notes judge on entering Drafting", when: entered("drafting"), then: [askNotesJudge] }),
|
|
1224
|
+
rule({
|
|
1225
|
+
name: "Ask the notes judge again after ten minutes with no vote",
|
|
1226
|
+
when: inStageFor("drafting", "10m"),
|
|
1227
|
+
if: { field: "checks.notes.result", op: "isAbsent" },
|
|
1228
|
+
then: [askNotesJudge],
|
|
1229
|
+
}),
|
|
1230
|
+
rule({ name: "Ask the notes judge again on a recheck label", when: changed("labels.recheck"), then: [askNotesJudge] }),
|
|
1231
|
+
],
|
|
1232
|
+
});
|
|
1233
|
+
```
|
|
1234
|
+
|
|
1235
|
+
- **The exit code sets the session outcome, not the check.** Exit 0 after any vote, `FAIL` included.
|
|
1236
|
+
Exit non-zero only when the program could not vote; that turn fails, and the `inStageFor` rule asks
|
|
1237
|
+
again.
|
|
1238
|
+
- **The judge does not see later edits.** A `FAIL` stays until a new turn asks again. So the last rule
|
|
1239
|
+
asks again when a person sets or changes the `recheck` label after a fix.
|
|
1240
|
+
- **A turn runs the whole program again**, so it must be safe to run twice. A second vote replaces the
|
|
1241
|
+
first, so this program is.
|
|
1242
|
+
- **Attach the `notes` grant** the way Example 1 attaches its judge's grant.
|
|
1243
|
+
- **The program reads only the description.** The item's JSON also holds the checks' summaries, and the
|
|
1244
|
+
`FAIL` summary names the section, so a search of the whole JSON would find it there.
|
|
1245
|
+
|
|
1246
|
+
### Example 4: approval by two people
|
|
1247
|
+
|
|
1248
|
+
The `approval` key is granted to people through a role. The gate asks for two people's `PASS`, no `FAIL`,
|
|
1249
|
+
and a latest voter who does not hold the item.
|
|
1250
|
+
|
|
1251
|
+
1. Create the policy with `orchestra policies create --body @approval-policy.json`, the same shape as in
|
|
1252
|
+
example 2 with the resource `work-items/*/checks/approval`. The FDK names a policy by id; it does not
|
|
1253
|
+
create one.
|
|
1254
|
+
2. Declare the role in code:
|
|
1255
|
+
|
|
1256
|
+
```ts
|
|
1257
|
+
import { Policy, Role } from "@orchestraworks/cli";
|
|
1258
|
+
|
|
1259
|
+
const approver = new Role(factory, "approver", {
|
|
1260
|
+
name: "Approver",
|
|
1261
|
+
policies: [Policy.fromId(factory, "pol_…")], // the policy from step 1
|
|
1262
|
+
});
|
|
1263
|
+
```
|
|
1264
|
+
|
|
1265
|
+
3. Give each approver the role at the pipeline's team, or at all teams:
|
|
1266
|
+
`orchestra team-memberships create --membership-id <person's membership id> --team-id <team id>
|
|
1267
|
+
--role-id <role id>`. A member holds one role per scope, so a person who already holds a role at that
|
|
1268
|
+
scope needs one custom role that lists both sets of policies. The FDK declares team memberships for
|
|
1269
|
+
agents and service accounts only, not for people.
|
|
1270
|
+
4. Write the gate:
|
|
1271
|
+
|
|
1272
|
+
```ts
|
|
1273
|
+
{
|
|
1274
|
+
slug: "released",
|
|
1275
|
+
category: "DONE",
|
|
1276
|
+
requires: [
|
|
1277
|
+
{ field: "checks.approval.passUsers", op: "gte", value: 2 },
|
|
1278
|
+
{ field: "checks.approval.fail", op: "is", value: 0 },
|
|
1279
|
+
{ field: "checks.approval.writer.isAssignee", op: "is", value: false },
|
|
1280
|
+
],
|
|
1281
|
+
}
|
|
1282
|
+
```
|
|
1283
|
+
|
|
1284
|
+
An approver votes with `orchestra work-items set-check rel-7 approval --result PASS --summary "…"`.
|
|
1285
|
+
`passUsers` counts people only, so an agent's vote does not count toward the two. One `FAIL` holds the item
|
|
1286
|
+
until that person votes again.
|
|
1287
|
+
|
|
1288
|
+
**The limit of the self-approval guard.** `.writer.isAssignee` reads the latest vote only. If the assignee
|
|
1289
|
+
votes first and a second person votes last, the guard passes. With two approvers, it cannot prove that
|
|
1290
|
+
neither of them holds the item. Keep the assignee out of the approver role when that matters.
|
|
1291
|
+
|
|
1292
|
+
### Example 5: a decision model judge (Jev)
|
|
1293
|
+
|
|
1294
|
+
<!-- Sources, on 74243daf6c: https://docs.typesafe.ai/api.md (the endpoint, the noul and choice questions and
|
|
1295
|
+
answers, a choice's criteria map, back off on 429 and 529); platform/src/main/kotlin/ai/livingcomputers/
|
|
1296
|
+
softwarefactory/integration/typesafe/TypeSafeDecisionClient.kt (that shape as encoded; a choice answer needs a
|
|
1297
|
+
string choice and a number confidence; one attempt) and platform/src/test/resources/typesafe/
|
|
1298
|
+
decision-transcript.json (every field name); platform/…/config/DecisionsProperties.kt:31 (a pinned version, never
|
|
1299
|
+
jev-latest: a new version changes the confidences); docs/architecture/work-plane.md:490-498, 562-564 (POST
|
|
1300
|
+
/decisions writes nothing; the platform's calls, on its key); platform/…/workplane/service/
|
|
1301
|
+
WorkItemSessionService.kt:332-392 (a judge's session is linked to one item); platform/…/common/api/Page.kt:11-16
|
|
1302
|
+
(rows under `data`); cli/src/constructs/environment.ts:717-722 and docs/architecture/egress.md:919-921, 1364-1371
|
|
1303
|
+
(an environment credential is added to a request to its hosts that names none, whatever the agent);
|
|
1304
|
+
cli/src/engine/reconcile.ts:368 and secrets-apply.ts:254-257 (apply reads the variable from its own environment);
|
|
1305
|
+
docs/architecture/commercial-plans-and-credits.md:268, 432 (the individual plan refuses a Secret create);
|
|
1306
|
+
cli/src/constructs/rules.ts:149-152 (entered fires on every entry); agents/runtime/image/Dockerfile:43 (curl, jq);
|
|
1307
|
+
cli/src/constructs/pipeline.ts:31-35 (`next` lists the stage slugs a move may go to);
|
|
1308
|
+
docs/architecture/work-plane.md:650 (a rule's move is judged against `next`); platform/…/workplane/model/
|
|
1309
|
+
WorkItem.kt:344 (a create with no stage lands in the first stage); platform/…/workplane/service/
|
|
1310
|
+
WorkplaneWorkItemService.kt:372 (a move to the item's own stage does nothing); execve(2) (one argument holds at
|
|
1311
|
+
most 128 KiB on Linux) and curl(1) `--data-binary` (`@-` reads the body from standard input as it is). -->
|
|
1312
|
+
|
|
1313
|
+
A *decision model* answers by picking, not by writing: one option from a fixed list, or the probability of yes.
|
|
1314
|
+
TypeSafe's Jev is one. Here a `script` agent asks Jev if a request is ready to build. Jev picks `PASS` or `FAIL`
|
|
1315
|
+
with a confidence, and the agent votes on `ready`.
|
|
1316
|
+
|
|
1317
|
+
- **Bring your own TypeSafe key.** The platform calls Jev only to read the text typed into the console's
|
|
1318
|
+
"+ Create" box, with its own key. That call cannot write a check, and no agent gets that key. Each call this
|
|
1319
|
+
judge makes bills your TypeSafe account.
|
|
1320
|
+
- **Set `TYPESAFE_API_KEY` in the shell that runs `orchestra fdk apply`.** Apply reads the key from that variable
|
|
1321
|
+
and writes it to the Secret. On the `individual` plan, apply refuses a Secret: see the orchestra skill's
|
|
1322
|
+
section 6, "Secret".
|
|
1323
|
+
- **The program never holds the key.** The Secret's recipe sets `Authorization: Bearer` on `api.typesafe.ai`. The
|
|
1324
|
+
environment lists the Secret in `credentials`. So the edge adds the key to each request that any program on
|
|
1325
|
+
this environment's machines sends to `api.typesafe.ai`, and no program holds it. Use this environment for the
|
|
1326
|
+
judge only.
|
|
1327
|
+
- **The request and the answer follow TypeSafe's published reference**, https://docs.typesafe.ai/api.md. The
|
|
1328
|
+
request is `{model, state, questions}`. The program asks one `choice` question, whose `criteria` map each
|
|
1329
|
+
option to a description: here `PASS` and `FAIL`, each described from the criterion. The answer gives the
|
|
1330
|
+
picked option, its confidence and each option's probability. For example: `{"answers": {"ready": {"type":
|
|
1331
|
+
"choice", "choice": "PASS", "confidence": 0.88, "probabilities": {"PASS": 0.94, "FAIL": 0.06}}}}`. The image
|
|
1332
|
+
ships `curl` beside `orchestra` and `jq`.
|
|
1333
|
+
|
|
1334
|
+
```ts
|
|
1335
|
+
const typesafeKey = new Secret(factory, "typesafe-api-key", {
|
|
1336
|
+
value: SecretValue.fromEnv("TYPESAFE_API_KEY"),
|
|
1337
|
+
injection: {
|
|
1338
|
+
headers: [{ name: "Authorization", value: "Bearer ${secret}" }],
|
|
1339
|
+
hosts: ["api.typesafe.ai"],
|
|
1340
|
+
},
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
const decisionRuntime = new Environment(factory, "decision-runtime", {
|
|
1344
|
+
name: "Decision model judge environment",
|
|
1345
|
+
image: Image.fromTag("acme/support-agent:1.0.0"),
|
|
1346
|
+
platform: { os: "linux", arch: "amd64" },
|
|
1347
|
+
resources: { cpu: "2", memory: "4G" },
|
|
1348
|
+
credentials: [{ credential: typesafeKey }],
|
|
1349
|
+
egress: EgressPolicy.denyAll()
|
|
1350
|
+
.applyPreset(egressPreset.softwareDevelopment())
|
|
1351
|
+
.allow("typesafe-api", { hosts: ["api.typesafe.ai"], priority: 300 }),
|
|
1352
|
+
});
|
|
1353
|
+
|
|
1354
|
+
const readyCriterion = "The description says what to build and how to tell that it is done.";
|
|
1355
|
+
const minConfidence = 0.8;
|
|
1356
|
+
const unsureResult: "PASS" | "FAIL" = "FAIL";
|
|
1357
|
+
|
|
1358
|
+
const readyJudge = new Agent(factory, "ready-judge", {
|
|
1359
|
+
environment: decisionRuntime,
|
|
1360
|
+
harness: "script",
|
|
1361
|
+
instructions: `set -euo pipefail
|
|
1362
|
+
items=$(orchestra work-items list --session "$ORCHESTRA_SESSION_ID" --output json)
|
|
1363
|
+
if [ "$(printf '%s' "$items" | jq '.data | length')" != 1 ]; then
|
|
1364
|
+
echo "This session is not on exactly one work item." >&2
|
|
1365
|
+
exit 1
|
|
1366
|
+
fi
|
|
1367
|
+
id=$(printf '%s' "$items" | jq -r '.data[0].id')
|
|
1368
|
+
body=$(printf '%s' "$items" | jq --arg criterion '${readyCriterion}' '{
|
|
1369
|
+
model: "jev-1.13.0",
|
|
1370
|
+
state: (.data[0] | {title, description: (.description // "")}),
|
|
1371
|
+
questions: {ready: {
|
|
1372
|
+
type: "choice",
|
|
1373
|
+
instructions: ("Is this true of the work item? " + $criterion),
|
|
1374
|
+
criteria: {PASS: ("True: " + $criterion), FAIL: ("Not true: " + $criterion)}
|
|
1375
|
+
}}
|
|
1376
|
+
}')
|
|
1377
|
+
answer=$(printf '%s' "$body" |
|
|
1378
|
+
curl -sS --fail --max-time 30 -H 'content-type: application/json' --data-binary @- https://api.typesafe.ai/v1/systemone)
|
|
1379
|
+
choice=$(printf '%s' "$answer" | jq -er '.answers.ready.choice | select(. == "PASS" or . == "FAIL")')
|
|
1380
|
+
confidence=$(printf '%s' "$answer" | jq -e '.answers.ready.confidence | numbers')
|
|
1381
|
+
if jq -en --argjson c "$confidence" '$c >= ${minConfidence}' > /dev/null; then
|
|
1382
|
+
result=$choice
|
|
1383
|
+
summary="The decision model picked $choice with confidence $confidence."
|
|
1384
|
+
else
|
|
1385
|
+
result=${unsureResult}
|
|
1386
|
+
summary="The decision model was unsure: it picked $choice with confidence $confidence."
|
|
1387
|
+
fi
|
|
1388
|
+
orchestra work-items set-check "$id" ready --result "$result" --summary "$summary"`,
|
|
1389
|
+
});
|
|
1390
|
+
|
|
1391
|
+
const requests = new Pipeline(factory, "requests", {
|
|
1392
|
+
itemSlugPrefix: "req",
|
|
1393
|
+
stages: [
|
|
1394
|
+
{ slug: "drafting", category: "TODO", next: ["inbox"] },
|
|
1395
|
+
{ slug: "inbox", category: "TODO" },
|
|
1396
|
+
{ slug: "building", category: "IN_PROGRESS", requires: [{ field: "checks.ready.result", op: "is", value: "PASS" }] },
|
|
1397
|
+
{ slug: "done", category: "DONE" },
|
|
1398
|
+
],
|
|
1399
|
+
}).allowCheck(readyJudge, "ready");
|
|
1400
|
+
|
|
1401
|
+
const askReadyJudge = evaluate({ agent: readyJudge, check: "ready", criterion: readyCriterion });
|
|
1402
|
+
|
|
1403
|
+
new Workflow(factory, "requests-ready", {
|
|
1404
|
+
pipeline: requests,
|
|
1405
|
+
description: "Asks the decision model judge if each request in Inbox is ready, and sends it back on a FAIL.",
|
|
1406
|
+
rules: [
|
|
1407
|
+
rule({
|
|
1408
|
+
name: "Clear the old vote and ask the ready judge on entering Inbox",
|
|
1409
|
+
when: entered("inbox"),
|
|
1410
|
+
then: [clearCheck("ready"), askReadyJudge],
|
|
1411
|
+
}),
|
|
1412
|
+
rule({
|
|
1413
|
+
name: "Ask the ready judge again after ten minutes with no vote",
|
|
1414
|
+
when: inStageFor("inbox", "10m"),
|
|
1415
|
+
if: { field: "checks.ready.result", op: "isAbsent" },
|
|
1416
|
+
then: [askReadyJudge],
|
|
1417
|
+
}),
|
|
1418
|
+
rule({ name: "Send a request that is not ready back to Drafting", when: check("ready").is("FAIL"), then: [move("drafting")] }),
|
|
1419
|
+
],
|
|
1420
|
+
});
|
|
1421
|
+
```
|
|
1422
|
+
|
|
1423
|
+
- **The rules work as Example 1's**, with Drafting and Inbox in place of Building and Review. By default, a new
|
|
1424
|
+
request lands in Drafting, the first stage. Move it to Inbox to ask the judge. No rule moves a passed
|
|
1425
|
+
request: a person moves it to Building.
|
|
1426
|
+
- **A PASS holds until the request next enters Inbox.** A request in Drafting can move only to Inbox, so it
|
|
1427
|
+
reaches Building only through Inbox. Each entry into Inbox clears the vote and asks again. After a fix or an
|
|
1428
|
+
edit, move the request back to Inbox to ask again. A move to the stage the request is in does nothing. So
|
|
1429
|
+
from Inbox, move it to Drafting first.
|
|
1430
|
+
- **The program reads nothing from the turn.** It finds the item as Example 3 does, and the key and the
|
|
1431
|
+
criterion are fixed in it. So no text from the turn reaches the question. The item's title and description
|
|
1432
|
+
go in `state`. A long description can be longer than the limit for one command-line argument. So the
|
|
1433
|
+
program gives the item's JSON and the TypeSafe body to `jq` and `curl` on standard input. The cost is one
|
|
1434
|
+
judge for each criterion. One constant gives the criterion to the rule and to the program. Keep single
|
|
1435
|
+
quotes out of it, because the program quotes it in them.
|
|
1436
|
+
- **The vote.** At a confidence of `minConfidence` or more, the vote is Jev's pick, `answers.ready.choice`. Below
|
|
1437
|
+
it, the vote is `unsureResult`. The summary gives the pick and the confidence, and says when Jev was unsure.
|
|
1438
|
+
- **`FAIL` as the unsure result fails closed.** An unsure answer keeps the gate shut, and the `FAIL` rule sends
|
|
1439
|
+
the request back to Drafting, as in Example 1. Set `unsureResult` to `PASS` only where a wrong pass costs
|
|
1440
|
+
little.
|
|
1441
|
+
- **Pin the model version**, `jev-1.13.0` here. Never use `jev-latest`: a new version changes the confidences,
|
|
1442
|
+
and `minConfidence` then means something else. Choose `minConfidence` from Jev's answers on your own items.
|
|
1443
|
+
- **One call, no retry.** TypeSafe asks a caller to back off on 429 and 529. On an HTTP error or no answer in
|
|
1444
|
+
30 seconds, the program exits non-zero with no vote. It does the same when `choice` is not exactly `PASS` or
|
|
1445
|
+
`FAIL`, or `confidence` is not a number. The `inStageFor` rule then asks again, as in Example 1.
|
|
1446
|
+
- **Attach the `ready` grant** the way Example 1 attaches its judge's grant.
|
|
1447
|
+
- **Example 3's warnings on the exit code and on running twice hold here too.**
|
|
1448
|
+
|
|
964
1449
|
## The six design principles
|
|
965
1450
|
|
|
966
1451
|
These decide the design questions authoring throws up. When something feels like it needs new API
|