humanish 0.110.3 → 0.111.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/CHANGELOG.md +29 -0
- package/CONTEXT.md +2 -2
- package/dist/actors/local-agent/claude-session.js +1 -1
- package/dist/actors/local-agent/cli.js +1 -1
- package/dist/actors/scripted-browser/actor.d.ts +2 -2
- package/dist/actors/scripted-browser/actor.js +17 -8
- package/dist/actors/scripted-browser/launch-env.d.ts +12 -0
- package/dist/actors/scripted-browser/launch-env.js +50 -0
- package/dist/analysis/automatic-completion.d.ts +2 -1
- package/dist/analysis/automatic-completion.js +7 -2
- package/dist/cli/commands/migrate.js +1 -1
- package/dist/cli/commands/runs.js +5 -2
- package/dist/cli/commands/study-route-computer-use.js +3 -2
- package/dist/cli/commands/study-route-preview.js +2 -1
- package/dist/cli/commands/study-route-shared-world.js +3 -3
- package/dist/cli/commands/study.js +2 -1
- package/dist/routes/computer-use/live-phase.js +8 -1
- package/dist/routes/computer-use/local-vm.js +2 -1
- package/dist/routes/computer-use/participant-model.js +1 -1
- package/dist/routes/computer-use/plan.js +9 -10
- package/dist/routes/computer-use/preflight.js +1 -1
- package/dist/routes/computer-use/result.js +1 -1
- package/dist/routes/computer-use/route.js +3 -2
- package/dist/routes/computer-use/setup.js +2 -1
- package/dist/routes/computer-use/types.d.ts +4 -4
- package/dist/routes/scripted/plan.js +5 -4
- package/dist/routes/scripted/route.js +6 -2
- package/dist/routes/scripted/scenario.d.ts +1 -1
- package/dist/routes/scripted/scenario.js +3 -3
- package/dist/routes/scripted/surface-sessions.d.ts +2 -0
- package/dist/routes/scripted/surface-sessions.js +2 -1
- package/dist/routes/scripted/types.d.ts +2 -2
- package/dist/routes/shared-world/handoff.js +1 -1
- package/dist/routes/shared-world/participant-specs.js +6 -2
- package/dist/routes/shared-world/plan.js +8 -8
- package/dist/routes/shared-world/route.js +3 -3
- package/dist/routes/shared-world/types.d.ts +1 -1
- package/dist/routes/terminal/bundle.d.ts +3 -3
- package/dist/routes/terminal/ledger.d.ts +2 -2
- package/dist/routes/terminal/lifetime.js +1 -1
- package/dist/routes/terminal/live-finish.js +1 -1
- package/dist/routes/terminal/plan.js +4 -3
- package/dist/routes/terminal/route.js +3 -2
- package/dist/routes/terminal/session.js +1 -0
- package/dist/routes/terminal/types.d.ts +1 -1
- package/dist/run/display.d.ts +10 -0
- package/dist/run/display.js +22 -0
- package/dist/run/reclaim.d.ts +4 -2
- package/dist/run/reclaim.js +61 -18
- package/dist/run/results.d.ts +1 -1
- package/dist/run/run.d.ts +11 -1
- package/dist/run/run.js +24 -5
- package/dist/run/status.d.ts +6 -0
- package/dist/run/status.js +1 -0
- package/dist/run/stored-runs.js +26 -11
- package/dist/run-study.d.ts +5 -3
- package/dist/run-study.js +7 -11
- package/dist/study/adapter-scorer-loader.d.ts +1 -1
- package/dist/study/adapter-scorer-loader.js +3 -3
- package/dist/study/composition-rules.js +49 -39
- package/dist/study/config.d.ts +31 -8
- package/dist/study/config.js +181 -155
- package/dist/study/discover.d.ts +1 -1
- package/dist/study/discover.js +1 -1
- package/dist/study/doctor.js +5 -4
- package/dist/study/keys.d.ts +250 -2
- package/dist/study/keys.js +32 -62
- package/dist/study/{convert.d.ts → migrate/convert.d.ts} +1 -1
- package/dist/study/{convert.js → migrate/convert.js} +28 -31
- package/dist/study/{migrate.d.ts → migrate/migrate.d.ts} +1 -1
- package/dist/study/{migrate.js → migrate/migrate.js} +2 -2
- package/dist/study/{owned-files.js → migrate/owned-files.js} +1 -1
- package/dist/study/migrate/v2.d.ts +78 -0
- package/dist/study/migrate/v2.js +607 -0
- package/dist/study/parse/actors.d.ts +34 -10
- package/dist/study/parse/actors.js +76 -174
- package/dist/study/parse/comms.js +6 -2
- package/dist/study/parse/execution.d.ts +9 -4
- package/dist/study/parse/execution.js +6 -41
- package/dist/study/parse/front.d.ts +36 -0
- package/dist/study/parse/{study-v3.js → front.js} +37 -88
- package/dist/study/parse/subject.d.ts +2 -0
- package/dist/study/parse/subject.js +15 -18
- package/dist/study/persona-resolve.d.ts +2 -4
- package/dist/study/persona-resolve.js +7 -8
- package/dist/study/plan-base.d.ts +1 -1
- package/dist/study/plan-base.js +6 -6
- package/dist/study/plan-participants.d.ts +2 -2
- package/dist/study/plan-participants.js +12 -11
- package/dist/study/plan-types.d.ts +5 -5
- package/dist/study/plan.js +5 -4
- package/dist/study/preflight-probes.js +4 -4
- package/dist/study/preflight.d.ts +1 -1
- package/dist/study/preflight.js +3 -3
- package/dist/study/routing.d.ts +5 -5
- package/dist/study/routing.js +17 -20
- package/dist/study/run-study-options.d.ts +1 -1
- package/dist/study/run-study-options.js +3 -3
- package/dist/study/study-deps.d.ts +1 -1
- package/dist/study/study-fields.d.ts +19 -0
- package/dist/study/study-fields.js +46 -0
- package/dist/study/summary.js +19 -18
- package/dist/study/types.d.ts +66 -104
- package/dist/study/types.js +0 -1
- package/dist/study/url-credentials.js +3 -3
- package/dist/study/validation.d.ts +4 -10
- package/dist/study/validation.js +54 -79
- package/dist/study/warnings.d.ts +3 -3
- package/dist/study/warnings.js +42 -110
- package/dist/substrates/local/runtime-config.js +14 -16
- package/dist/tui-app.js +3 -3
- package/docs/contracts/schemas.md +36 -8
- package/package.json +1 -1
- package/dist/study/parse/study-v3.d.ts +0 -21
- /package/dist/study/{owned-files.d.ts → migrate/owned-files.d.ts} +0 -0
package/dist/study/keys.d.ts
CHANGED
|
@@ -1,3 +1,251 @@
|
|
|
1
|
+
export type KeyShape = {
|
|
2
|
+
readonly [key: string]: true | KeyShape;
|
|
3
|
+
};
|
|
4
|
+
export declare const CAPS: {
|
|
5
|
+
maxUsd: true;
|
|
6
|
+
maxTotalUsd: true;
|
|
7
|
+
maxJobs: true;
|
|
8
|
+
maxMinutes: true;
|
|
9
|
+
};
|
|
10
|
+
export declare const PARTICIPANT_ENTRY: {
|
|
11
|
+
id: true;
|
|
12
|
+
actorType: true;
|
|
13
|
+
surface: true;
|
|
14
|
+
caseGroup: true;
|
|
15
|
+
persona: true;
|
|
16
|
+
device: true;
|
|
17
|
+
instruction: true;
|
|
18
|
+
target: true;
|
|
19
|
+
entry: true;
|
|
20
|
+
host: true;
|
|
21
|
+
reasoningEffort: true;
|
|
22
|
+
stopWhen: {
|
|
23
|
+
any: {
|
|
24
|
+
id: true;
|
|
25
|
+
urlIncludes: true;
|
|
26
|
+
urlPathEquals: true;
|
|
27
|
+
textIncludes: true;
|
|
28
|
+
appStatePathEquals: {
|
|
29
|
+
path: true;
|
|
30
|
+
equals: true;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
dwell: {
|
|
35
|
+
ms: true;
|
|
36
|
+
everyMs: true;
|
|
37
|
+
then: true;
|
|
38
|
+
when: {
|
|
39
|
+
any: {
|
|
40
|
+
id: true;
|
|
41
|
+
urlIncludes: true;
|
|
42
|
+
urlPathEquals: true;
|
|
43
|
+
textIncludes: true;
|
|
44
|
+
appStatePathEquals: {
|
|
45
|
+
path: true;
|
|
46
|
+
equals: true;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
export declare const SUBJECT: {
|
|
53
|
+
source: true;
|
|
54
|
+
exposure: true;
|
|
55
|
+
appUrl: true;
|
|
56
|
+
repos: true;
|
|
57
|
+
env: true;
|
|
58
|
+
envValues: true;
|
|
59
|
+
clone: {
|
|
60
|
+
depth: true;
|
|
61
|
+
fanout: true;
|
|
62
|
+
keep: true;
|
|
63
|
+
};
|
|
64
|
+
localTree: {
|
|
65
|
+
exclude: true;
|
|
66
|
+
keep: true;
|
|
67
|
+
maxArchiveBytes: true;
|
|
68
|
+
};
|
|
69
|
+
product: {
|
|
70
|
+
name: true;
|
|
71
|
+
install: true;
|
|
72
|
+
workdir: true;
|
|
73
|
+
upload: true;
|
|
74
|
+
publicSurfaces: true;
|
|
75
|
+
};
|
|
76
|
+
publicTarget: {
|
|
77
|
+
owner: true;
|
|
78
|
+
authorized: true;
|
|
79
|
+
};
|
|
80
|
+
serve: {
|
|
81
|
+
install: true;
|
|
82
|
+
installTimeoutMs: true;
|
|
83
|
+
build: true;
|
|
84
|
+
buildTimeoutMs: true;
|
|
85
|
+
start: true;
|
|
86
|
+
url: true;
|
|
87
|
+
readyTimeoutMs: true;
|
|
88
|
+
};
|
|
89
|
+
state: {
|
|
90
|
+
seed: {
|
|
91
|
+
name: true;
|
|
92
|
+
command: true;
|
|
93
|
+
when: true;
|
|
94
|
+
timeoutMs: true;
|
|
95
|
+
};
|
|
96
|
+
external: true;
|
|
97
|
+
checkpoint: {
|
|
98
|
+
name: true;
|
|
99
|
+
command: true;
|
|
100
|
+
redact: true;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
export declare const ACTOR: {
|
|
105
|
+
type: true;
|
|
106
|
+
persona: true;
|
|
107
|
+
mission: true;
|
|
108
|
+
model: true;
|
|
109
|
+
maxOutputTokens: true;
|
|
110
|
+
localAgent: true;
|
|
111
|
+
reasoningEffort: true;
|
|
112
|
+
stopWhen: {
|
|
113
|
+
any: {
|
|
114
|
+
id: true;
|
|
115
|
+
urlIncludes: true;
|
|
116
|
+
urlPathEquals: true;
|
|
117
|
+
textIncludes: true;
|
|
118
|
+
appStatePathEquals: {
|
|
119
|
+
path: true;
|
|
120
|
+
equals: true;
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
dwell: {
|
|
125
|
+
ms: true;
|
|
126
|
+
everyMs: true;
|
|
127
|
+
then: true;
|
|
128
|
+
when: {
|
|
129
|
+
any: {
|
|
130
|
+
id: true;
|
|
131
|
+
urlIncludes: true;
|
|
132
|
+
urlPathEquals: true;
|
|
133
|
+
textIncludes: true;
|
|
134
|
+
appStatePathEquals: {
|
|
135
|
+
path: true;
|
|
136
|
+
equals: true;
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
tasks: {
|
|
142
|
+
id: true;
|
|
143
|
+
goal: true;
|
|
144
|
+
success: {
|
|
145
|
+
any: {
|
|
146
|
+
id: true;
|
|
147
|
+
urlIncludes: true;
|
|
148
|
+
urlPathEquals: true;
|
|
149
|
+
textIncludes: true;
|
|
150
|
+
appStatePathEquals: {
|
|
151
|
+
path: true;
|
|
152
|
+
equals: true;
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
export declare const EXECUTION: {
|
|
159
|
+
target: true;
|
|
160
|
+
runtime: {
|
|
161
|
+
version: true;
|
|
162
|
+
};
|
|
163
|
+
runtimeAuth: true;
|
|
164
|
+
concurrency: true;
|
|
165
|
+
timeoutMs: true;
|
|
166
|
+
completionTimeoutMs: true;
|
|
167
|
+
egressAllow: true;
|
|
168
|
+
terminal: {
|
|
169
|
+
stdin: true;
|
|
170
|
+
transport: true;
|
|
171
|
+
};
|
|
172
|
+
desktop: {
|
|
173
|
+
template: true;
|
|
174
|
+
browser: true;
|
|
175
|
+
device: true;
|
|
176
|
+
resolution: true;
|
|
177
|
+
sandboxTimeoutMs: true;
|
|
178
|
+
codexAppServer: true;
|
|
179
|
+
recording: true;
|
|
180
|
+
fidelity: {
|
|
181
|
+
mobileEmulation: true;
|
|
182
|
+
deviceScaleFactor: true;
|
|
183
|
+
touch: true;
|
|
184
|
+
userAgent: true;
|
|
185
|
+
};
|
|
186
|
+
media: {
|
|
187
|
+
camera: {
|
|
188
|
+
source: true;
|
|
189
|
+
};
|
|
190
|
+
microphone: {
|
|
191
|
+
source: true;
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
/** The sections a humanish.lab.v2 file shares with a v3 one. */
|
|
197
|
+
export declare const SHARED_SECTIONS: {
|
|
198
|
+
policies: {
|
|
199
|
+
redactRepos: true;
|
|
200
|
+
redactScreenshots: true;
|
|
201
|
+
allowPublicTargets: true;
|
|
202
|
+
mediaPermission: true;
|
|
203
|
+
allowPrivateRepoAccess: true;
|
|
204
|
+
allowProviderCredentials: true;
|
|
205
|
+
allowPaymentCredentials: true;
|
|
206
|
+
allowGitHubMutation: true;
|
|
207
|
+
};
|
|
208
|
+
review: {
|
|
209
|
+
scoring: true;
|
|
210
|
+
milestones: true;
|
|
211
|
+
vocabulary: true;
|
|
212
|
+
scorer: {
|
|
213
|
+
ref: true;
|
|
214
|
+
};
|
|
215
|
+
analysis: true;
|
|
216
|
+
};
|
|
217
|
+
defaults: {
|
|
218
|
+
open: true;
|
|
219
|
+
};
|
|
220
|
+
comms: {
|
|
221
|
+
email: {
|
|
222
|
+
kind: true;
|
|
223
|
+
connection: true;
|
|
224
|
+
port: true;
|
|
225
|
+
smtp: {
|
|
226
|
+
hostEnv: true;
|
|
227
|
+
portEnv: true;
|
|
228
|
+
port: true;
|
|
229
|
+
userEnv: true;
|
|
230
|
+
user: true;
|
|
231
|
+
passwordEnv: true;
|
|
232
|
+
password: true;
|
|
233
|
+
};
|
|
234
|
+
external: {
|
|
235
|
+
catchBaseUrl: true;
|
|
236
|
+
inboxBaseUrl: true;
|
|
237
|
+
authTokenEnv: true;
|
|
238
|
+
};
|
|
239
|
+
injectEnv: true;
|
|
240
|
+
linkOrigin: true;
|
|
241
|
+
allowedOrigins: true;
|
|
242
|
+
recipients: {
|
|
243
|
+
lane: true;
|
|
244
|
+
address: true;
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
};
|
|
1
249
|
/**
|
|
2
250
|
* Whether a dotted key path, such as `caps.maxUsd` or `participants[0].persona`, names a key a
|
|
3
251
|
* humanish.study.v3 file can set. A path below a leaf is not one: `scenario.ref`, where `scenario`
|
|
@@ -10,7 +258,7 @@ export declare function isStudyKeyPath(path: string): boolean;
|
|
|
10
258
|
* study file reference to these paths.
|
|
11
259
|
*/
|
|
12
260
|
export declare function studyKeyPaths(depth: number): string[];
|
|
13
|
-
/** The first key in `raw` that V2_KEYS does not list, as an error message; undefined if none. */
|
|
14
|
-
export declare function findUnknownV2Key(raw: unknown): string | undefined;
|
|
15
261
|
/** The first key in a humanish.study.v3 document that the format does not have; undefined if none. */
|
|
16
262
|
export declare function findUnknownStudyKey(raw: unknown): string | undefined;
|
|
263
|
+
/** The first key in `raw` that `shape` does not list, as an error message; undefined if none. */
|
|
264
|
+
export declare function findUnknownKey(raw: unknown, shape: KeyShape): string | undefined;
|
package/dist/study/keys.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Every mapping key parseStudy reads, by path. A key that is not listed fails the study as
|
|
2
2
|
// unknown, so a typo stops the run instead of being dropped. `true` marks a leaf: its parser
|
|
3
3
|
// checks the value (scalars, lists, and mappings that check their own keys). Each level satisfies
|
|
4
|
-
// the parsed type's keys, so a field added to a
|
|
4
|
+
// the parsed type's keys, so a field added to a Study* interface must be added here too.
|
|
5
|
+
// migrate/v2.ts builds the humanish.lab.v2 table from the exported sections.
|
|
5
6
|
const STOP_WHEN = {
|
|
6
7
|
any: {
|
|
7
8
|
id: true,
|
|
@@ -17,17 +18,17 @@ const STOP_WHEN = {
|
|
|
17
18
|
const DWELL = {
|
|
18
19
|
ms: true,
|
|
19
20
|
everyMs: true,
|
|
20
|
-
// oxlint-disable-next-line unicorn/no-thenable -- `then` is the documented dwell field of
|
|
21
|
+
// oxlint-disable-next-line unicorn/no-thenable -- `then` is the documented dwell field of a study
|
|
21
22
|
then: true,
|
|
22
23
|
when: STOP_WHEN,
|
|
23
24
|
};
|
|
24
|
-
const CAPS = {
|
|
25
|
+
export const CAPS = {
|
|
25
26
|
maxUsd: true,
|
|
26
27
|
maxTotalUsd: true,
|
|
27
28
|
maxJobs: true,
|
|
28
29
|
maxMinutes: true,
|
|
29
30
|
};
|
|
30
|
-
const PARTICIPANT_ENTRY = {
|
|
31
|
+
export const PARTICIPANT_ENTRY = {
|
|
31
32
|
id: true,
|
|
32
33
|
actorType: true,
|
|
33
34
|
surface: true,
|
|
@@ -42,9 +43,8 @@ const PARTICIPANT_ENTRY = {
|
|
|
42
43
|
stopWhen: STOP_WHEN,
|
|
43
44
|
dwell: DWELL,
|
|
44
45
|
};
|
|
45
|
-
const SUBJECT = {
|
|
46
|
+
export const SUBJECT = {
|
|
46
47
|
source: true,
|
|
47
|
-
topology: true,
|
|
48
48
|
exposure: true,
|
|
49
49
|
appUrl: true,
|
|
50
50
|
repos: true,
|
|
@@ -88,15 +88,8 @@ const SUBJECT = {
|
|
|
88
88
|
},
|
|
89
89
|
},
|
|
90
90
|
};
|
|
91
|
-
const ACTOR = {
|
|
91
|
+
export const ACTOR = {
|
|
92
92
|
type: true,
|
|
93
|
-
count: true,
|
|
94
|
-
lanes: PARTICIPANT_ENTRY,
|
|
95
|
-
// Roster groups are participants with a count; the parser expands them into `lanes[]`.
|
|
96
|
-
roster: {
|
|
97
|
-
...PARTICIPANT_ENTRY,
|
|
98
|
-
count: true,
|
|
99
|
-
},
|
|
100
93
|
persona: true,
|
|
101
94
|
mission: true,
|
|
102
95
|
model: true,
|
|
@@ -106,13 +99,8 @@ const ACTOR = {
|
|
|
106
99
|
stopWhen: STOP_WHEN,
|
|
107
100
|
dwell: DWELL,
|
|
108
101
|
tasks: { id: true, goal: true, success: STOP_WHEN },
|
|
109
|
-
laneFocus: {
|
|
110
|
-
id: true,
|
|
111
|
-
label: true,
|
|
112
|
-
instruction: true,
|
|
113
|
-
},
|
|
114
102
|
};
|
|
115
|
-
const EXECUTION = {
|
|
103
|
+
export const EXECUTION = {
|
|
116
104
|
target: true,
|
|
117
105
|
runtime: { version: true },
|
|
118
106
|
runtimeAuth: true,
|
|
@@ -120,7 +108,6 @@ const EXECUTION = {
|
|
|
120
108
|
timeoutMs: true,
|
|
121
109
|
completionTimeoutMs: true,
|
|
122
110
|
egressAllow: true,
|
|
123
|
-
caps: CAPS,
|
|
124
111
|
terminal: { stdin: true, transport: true },
|
|
125
112
|
desktop: {
|
|
126
113
|
template: true,
|
|
@@ -166,22 +153,25 @@ const EMAIL = {
|
|
|
166
153
|
allowedOrigins: true,
|
|
167
154
|
recipients: { lane: true, address: true },
|
|
168
155
|
};
|
|
169
|
-
|
|
156
|
+
// humanish.study.v3. A list entry with a `count` is a group, so `participants` keys an entry's keys
|
|
157
|
+
// and a group's together: parseStudy checks which form the route takes.
|
|
158
|
+
const STUDY_KEYS = {
|
|
170
159
|
schema: true,
|
|
171
160
|
id: true,
|
|
172
161
|
title: true,
|
|
173
162
|
description: true,
|
|
163
|
+
route: true,
|
|
164
|
+
mode: true,
|
|
174
165
|
subject: SUBJECT,
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
scenario: {
|
|
180
|
-
ref: true,
|
|
181
|
-
mode: true,
|
|
182
|
-
inline: true,
|
|
183
|
-
caps: CAPS,
|
|
166
|
+
actor: ACTOR,
|
|
167
|
+
participants: {
|
|
168
|
+
...PARTICIPANT_ENTRY,
|
|
169
|
+
count: true,
|
|
184
170
|
},
|
|
171
|
+
surfaces: true,
|
|
172
|
+
caps: CAPS,
|
|
173
|
+
execution: EXECUTION,
|
|
174
|
+
scenario: true,
|
|
185
175
|
policies: {
|
|
186
176
|
redactRepos: true,
|
|
187
177
|
redactScreenshots: true,
|
|
@@ -203,32 +193,12 @@ const V2_KEYS = {
|
|
|
203
193
|
defaults: { open: true },
|
|
204
194
|
comms: { email: EMAIL },
|
|
205
195
|
};
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const STUDY_KEYS = {
|
|
213
|
-
schema: true,
|
|
214
|
-
id: true,
|
|
215
|
-
title: true,
|
|
216
|
-
description: true,
|
|
217
|
-
route: true,
|
|
218
|
-
mode: true,
|
|
219
|
-
subject: without(SUBJECT, "topology"),
|
|
220
|
-
actor: without(ACTOR, "count", "lanes", "roster", "laneFocus"),
|
|
221
|
-
// A count, `{ count, instruction }` or a list of entries. studyToV2 checks which form the route
|
|
222
|
-
// takes; an entry with a count is a group.
|
|
223
|
-
participants: { ...PARTICIPANT_ENTRY, count: true },
|
|
224
|
-
surfaces: true,
|
|
225
|
-
caps: CAPS,
|
|
226
|
-
execution: without(EXECUTION, "caps"),
|
|
227
|
-
scenario: true,
|
|
228
|
-
policies: V2_KEYS.policies,
|
|
229
|
-
review: V2_KEYS.review,
|
|
230
|
-
defaults: V2_KEYS.defaults,
|
|
231
|
-
comms: V2_KEYS.comms,
|
|
196
|
+
/** The sections a humanish.lab.v2 file shares with a v3 one. */
|
|
197
|
+
export const SHARED_SECTIONS = {
|
|
198
|
+
policies: STUDY_KEYS.policies,
|
|
199
|
+
review: STUDY_KEYS.review,
|
|
200
|
+
defaults: STUDY_KEYS.defaults,
|
|
201
|
+
comms: STUDY_KEYS.comms,
|
|
232
202
|
};
|
|
233
203
|
/**
|
|
234
204
|
* Whether a dotted key path, such as `caps.maxUsd` or `participants[0].persona`, names a key a
|
|
@@ -262,13 +232,13 @@ export function studyKeyPaths(depth) {
|
|
|
262
232
|
visit(STUDY_KEYS, "", 1);
|
|
263
233
|
return paths;
|
|
264
234
|
}
|
|
265
|
-
/** The first key in `raw` that V2_KEYS does not list, as an error message; undefined if none. */
|
|
266
|
-
export function findUnknownV2Key(raw) {
|
|
267
|
-
return walk(raw, V2_KEYS, "", "study");
|
|
268
|
-
}
|
|
269
235
|
/** The first key in a humanish.study.v3 document that the format does not have; undefined if none. */
|
|
270
236
|
export function findUnknownStudyKey(raw) {
|
|
271
|
-
return
|
|
237
|
+
return findUnknownKey(raw, STUDY_KEYS);
|
|
238
|
+
}
|
|
239
|
+
/** The first key in `raw` that `shape` does not list, as an error message; undefined if none. */
|
|
240
|
+
export function findUnknownKey(raw, shape) {
|
|
241
|
+
return walk(raw, shape, "", "study");
|
|
272
242
|
}
|
|
273
243
|
function walk(value, shape, path, noun) {
|
|
274
244
|
if (Array.isArray(value)) {
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
// Convert a humanish.lab.v2 study file to humanish.study.v3. It edits the parsed YAML document in
|
|
2
2
|
// place and moves each pair node, so the comments on a moved key move with it. The result lists
|
|
3
3
|
// every key it moved and every key it dropped. It refuses a file whose v3 form would parse or plan
|
|
4
|
-
// differently from the v2 source with the dropped keys removed.
|
|
4
|
+
// differently from the v2 source with the dropped keys removed. v2.ts reads the v2 source.
|
|
5
5
|
import { isAlias, isMap, isScalar, isSeq, LineCounter, Pair, parseDocument, Scalar, visit, YAMLMap, YAMLSeq, } from "yaml";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { V2_SCHEMA,
|
|
12
|
-
import { inertFieldPaths } from "./warnings.js";
|
|
13
|
-
import { deleteNodeFieldPath, deletePlainFieldPath, pairIndex, readFieldPath, } from "./field-paths.js";
|
|
6
|
+
import { parseStudy, parseStudyV3 } from "../config.js";
|
|
7
|
+
import { posInt } from "../parse/values.js";
|
|
8
|
+
import { planStudy } from "../plan.js";
|
|
9
|
+
import { STUDY_SCHEMA } from "../types.js";
|
|
10
|
+
import { deleteNodeFieldPath, deletePlainFieldPath, pairIndex, readFieldPath, } from "../field-paths.js";
|
|
11
|
+
import { checkV2, droppedPaths, V2_SCHEMA, v2ShapeReason, v2ToV3Raw } from "./v2.js";
|
|
14
12
|
function isPlainRecord(value) {
|
|
15
13
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16
14
|
}
|
|
@@ -36,10 +34,19 @@ export function convertStudyText(text, cwd) {
|
|
|
36
34
|
return refuse("it is not a YAML mapping.");
|
|
37
35
|
if (raw.schema !== V2_SCHEMA)
|
|
38
36
|
return refuse(`its schema is not ${V2_SCHEMA}.`);
|
|
39
|
-
|
|
37
|
+
// The v2 parser's checks: the sections and the v2-only budget rule, then the checks the formats
|
|
38
|
+
// share on the v3 form of the file, then the rules on v2 shapes the v3 form cannot hold.
|
|
39
|
+
const source = checkV2(raw);
|
|
40
40
|
if (!source.ok)
|
|
41
|
-
return refuse(`it does not parse: ${source.
|
|
42
|
-
const
|
|
41
|
+
return refuse(`it does not parse: ${source.message}`);
|
|
42
|
+
const study = source.value;
|
|
43
|
+
const { route } = study;
|
|
44
|
+
const view = parseStudyV3(v2ToV3Raw(raw, route), { inert: "report" });
|
|
45
|
+
if (!view.ok)
|
|
46
|
+
return refuse(`it does not parse: ${view.error.message}`);
|
|
47
|
+
const shape = v2ShapeReason(study);
|
|
48
|
+
if (shape)
|
|
49
|
+
return refuse(`it does not parse: ${shape}`);
|
|
43
50
|
const actorRaw = Array.isArray(raw.actors) ? raw.actors[0] : undefined;
|
|
44
51
|
if (route === "terminal" && isPlainRecord(actorRaw)) {
|
|
45
52
|
for (const key of ["count", "laneFocus"]) {
|
|
@@ -50,7 +57,7 @@ export function convertStudyText(text, cwd) {
|
|
|
50
57
|
}
|
|
51
58
|
const root = doc.contents;
|
|
52
59
|
const dropped = [];
|
|
53
|
-
for (const path of droppedPaths(
|
|
60
|
+
for (const path of droppedPaths(study, view.inert ?? [])) {
|
|
54
61
|
const value = readFieldPath(raw, path);
|
|
55
62
|
const pairs = deleteNodeFieldPath(root, path);
|
|
56
63
|
if (value === undefined || pairs.length === 0)
|
|
@@ -60,8 +67,7 @@ export function convertStudyText(text, cwd) {
|
|
|
60
67
|
}
|
|
61
68
|
const moved = [];
|
|
62
69
|
// v2 reads some laneFocus values as no focus at all ({}, a valueless key, valueless fields).
|
|
63
|
-
const
|
|
64
|
-
const converted = convertDocument(root, route, focused, moved);
|
|
70
|
+
const converted = convertDocument(root, route, study.focus !== undefined, moved);
|
|
65
71
|
if (!converted.ok)
|
|
66
72
|
return converted;
|
|
67
73
|
const out = doc.toString({ lineWidth: foldWidth(text), flowCollectionPadding: false });
|
|
@@ -69,7 +75,7 @@ export function convertStudyText(text, cwd) {
|
|
|
69
75
|
const lost = lostComment(text, out, dropped);
|
|
70
76
|
if (lost !== undefined)
|
|
71
77
|
return refuse(`it would lose the comment "${lost}".`);
|
|
72
|
-
const check = samePlans(raw, dropped.map((key) => key.path), out, cwd);
|
|
78
|
+
const check = samePlans(raw, route, dropped.map((key) => key.path), out, cwd);
|
|
73
79
|
if (!check.ok)
|
|
74
80
|
return check;
|
|
75
81
|
return { ok: true, conversion: { text: out, route, moved, dropped } };
|
|
@@ -105,16 +111,6 @@ function lostComment(source, output, dropped) {
|
|
|
105
111
|
}
|
|
106
112
|
return undefined;
|
|
107
113
|
}
|
|
108
|
-
// The inert-field table's paths for the route, plus terminal `execution.timeoutMs`, which no
|
|
109
|
-
// terminal code reads. Parents come before their children, so a child under a dropped parent is
|
|
110
|
-
// reported with the parent.
|
|
111
|
-
function droppedPaths(config, route, raw) {
|
|
112
|
-
const paths = inertFieldPaths(config);
|
|
113
|
-
if (route === "terminal" && readFieldPath(raw, "execution.timeoutMs") !== undefined)
|
|
114
|
-
paths.push("execution.timeoutMs");
|
|
115
|
-
const depth = (path) => path.split(".").length;
|
|
116
|
-
return [...new Set(paths)].sort((left, right) => depth(left) - depth(right));
|
|
117
|
-
}
|
|
118
114
|
// Folded text is folded again on output. Most of a file's long lines end near the width its author
|
|
119
115
|
// folded at, so the 90th percentile of their lengths stands for it, between 80 and 120.
|
|
120
116
|
function foldWidth(text) {
|
|
@@ -406,19 +402,20 @@ function numericCount(pair) {
|
|
|
406
402
|
function plain(value) {
|
|
407
403
|
return JSON.parse(JSON.stringify(value));
|
|
408
404
|
}
|
|
409
|
-
|
|
405
|
+
// The YAML rewrite against v2ToV3Raw of the source without the dropped keys: two rewrites written
|
|
406
|
+
// separately, which must parse to the same study and plan the same runs.
|
|
407
|
+
function samePlans(raw, route, dropped, text, cwd) {
|
|
410
408
|
const projected = structuredClone(raw);
|
|
411
409
|
for (const path of dropped)
|
|
412
410
|
deletePlainFieldPath(projected, path);
|
|
413
|
-
const before =
|
|
411
|
+
const before = parseStudy(v2ToV3Raw(projected, route));
|
|
414
412
|
if (!before.ok) {
|
|
415
413
|
return refuse(`it does not parse once the unread keys are dropped: ${before.error.message}`);
|
|
416
414
|
}
|
|
417
|
-
const after =
|
|
415
|
+
const after = parseStudy(parseDocument(text).toJS());
|
|
418
416
|
if (!after.ok)
|
|
419
417
|
return refuse(`its v3 form does not parse: ${after.error.message}`);
|
|
420
|
-
if (JSON.stringify(plain(
|
|
421
|
-
JSON.stringify(plain(before.config))) {
|
|
418
|
+
if (JSON.stringify(plain(after.config)) !== JSON.stringify(plain(before.config))) {
|
|
422
419
|
return refuse("its v3 form parses to a different study.");
|
|
423
420
|
}
|
|
424
421
|
for (const dryRun of [true, false]) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { StudyRoute } from "
|
|
1
|
+
import type { StudyRoute } from "../routing.js";
|
|
2
2
|
import { type DroppedKey, type MovedKey } from "./convert.js";
|
|
3
3
|
/** The schema of `humanish migrate --json`. */
|
|
4
4
|
declare const MIGRATE_SCHEMA = "humanish.migrate-result.v1";
|
|
@@ -14,9 +14,9 @@ import { randomUUID } from "node:crypto";
|
|
|
14
14
|
import { link, lstat, readdir, readFile, realpath, rename, rmdir } from "node:fs/promises";
|
|
15
15
|
import path from "node:path";
|
|
16
16
|
import { parseDocument } from "yaml";
|
|
17
|
-
import { isNodeError } from "
|
|
17
|
+
import { isNodeError } from "../../run/type-guards.js";
|
|
18
18
|
import { convertStudyText, isStudyV3 } from "./convert.js";
|
|
19
|
-
import { otherStudyFiles, STUDY_DIRECTORIES, studyFileStem } from "
|
|
19
|
+
import { otherStudyFiles, STUDY_DIRECTORIES, studyFileStem } from "../files.js";
|
|
20
20
|
import { assertDirectory, bindDirectory, exists, fileIdentity, NO_HARD_LINKS, removeChecked, removeOwned, restoreName, sameFile, sha256, writeOwned, } from "./owned-files.js";
|
|
21
21
|
/** The schema of `humanish migrate --json`. */
|
|
22
22
|
const MIGRATE_SCHEMA = "humanish.migrate-result.v1";
|
|
@@ -6,7 +6,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
6
6
|
import { constants } from "node:fs";
|
|
7
7
|
import { lstat, mkdir, open, readFile, rename, unlink } from "node:fs/promises";
|
|
8
8
|
import path from "node:path";
|
|
9
|
-
import { isNodeError } from "
|
|
9
|
+
import { isNodeError } from "../../run/type-guards.js";
|
|
10
10
|
export function sha256(bytes) {
|
|
11
11
|
return createHash("sha256").update(bytes).digest("hex");
|
|
12
12
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type StudyRoute } from "../routing.js";
|
|
2
|
+
import { type StudyCaps, type StudyConfig, type StudyParticipantEntry } from "../types.js";
|
|
3
|
+
/** The schema of the study format before humanish.study.v3. */
|
|
4
|
+
export declare const V2_SCHEMA = "humanish.lab.v2";
|
|
5
|
+
/** The message for a humanish.lab.v2 document, which the package and the CLI no longer run. */
|
|
6
|
+
export declare const V2_UNSUPPORTED_MESSAGE = "This is a humanish.lab.v2 study, which humanish no longer reads. Convert its file with humanish migrate <path>.";
|
|
7
|
+
type Raw = Record<string, unknown>;
|
|
8
|
+
/** Where each humanish 0.110 StudyConfig field a config still sets went; empty when it sets none. */
|
|
9
|
+
export declare function v2FieldMoves(config: unknown): string[];
|
|
10
|
+
type Read<T> = {
|
|
11
|
+
ok: true;
|
|
12
|
+
value: T;
|
|
13
|
+
} | {
|
|
14
|
+
ok: false;
|
|
15
|
+
message: string;
|
|
16
|
+
};
|
|
17
|
+
/** `laneFocus`: the steer each of a counted group of participants gets. */
|
|
18
|
+
interface V2Focus {
|
|
19
|
+
id?: string;
|
|
20
|
+
label?: string;
|
|
21
|
+
instruction?: string;
|
|
22
|
+
}
|
|
23
|
+
/** The `scenario` object. */
|
|
24
|
+
interface V2Scenario {
|
|
25
|
+
ref?: string;
|
|
26
|
+
mode?: "dry-run" | "live";
|
|
27
|
+
inline?: Raw;
|
|
28
|
+
caps?: StudyCaps;
|
|
29
|
+
}
|
|
30
|
+
/** A v2 file that checkV2 accepted, as the v2 parser reads it. */
|
|
31
|
+
export interface V2Study {
|
|
32
|
+
readonly route: StudyRoute;
|
|
33
|
+
/**
|
|
34
|
+
* The sections that decide the route and that the v2 parser's first cross-section checks read,
|
|
35
|
+
* as a StudyConfig: subject, the actor, execution, policies and comms. Its `route` is the file's
|
|
36
|
+
* route, `shared-world` when `subject.topology` declares one, so routeOf and the composition
|
|
37
|
+
* checks read the declaration as the v2 parser did.
|
|
38
|
+
*/
|
|
39
|
+
readonly composition: StudyConfig;
|
|
40
|
+
readonly topology: "per-lane-worlds" | "shared-world" | undefined;
|
|
41
|
+
readonly count: number | undefined;
|
|
42
|
+
/** `actors[0].lanes`, or the participants `actors[0].roster` expands into. */
|
|
43
|
+
readonly entries: readonly StudyParticipantEntry[] | undefined;
|
|
44
|
+
readonly focus: V2Focus | undefined;
|
|
45
|
+
readonly executionCaps: StudyCaps | undefined;
|
|
46
|
+
readonly scenario: V2Scenario | undefined;
|
|
47
|
+
readonly personas: boolean;
|
|
48
|
+
readonly terminalTimeout: boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Check a humanish.lab.v2 file the way the v2 parser does, up to the checks that read more than
|
|
52
|
+
* one section, and then the budget rule those checks apply first. Each refusal is that parser's
|
|
53
|
+
* message.
|
|
54
|
+
*/
|
|
55
|
+
export declare function checkV2(raw: Raw): Read<V2Study>;
|
|
56
|
+
/**
|
|
57
|
+
* The refusals the v2 parser gives for shapes a v3 file cannot express, once the checks both
|
|
58
|
+
* formats share have passed on the study without them. A file that also breaks a shared check
|
|
59
|
+
* gets that check's message.
|
|
60
|
+
*/
|
|
61
|
+
export declare function v2ShapeReason(study: V2Study): string | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* A v2 file as a plain humanish.study.v3 record, for parseStudyV3 and parseStudy. It reads each key
|
|
64
|
+
* it moves the way the v2 parser reads it (`count` as a positive integer, `laneFocus` through its
|
|
65
|
+
* string fields, `roster` groups expanded, `scenario.ref` and `scenario.mode` as strings), and
|
|
66
|
+
* leaves out every key no v3 file can hold, so it parses whenever the v2 parser would. A caps
|
|
67
|
+
* block keeps the keys its route reads. A shared world keeps a count or no list, which only
|
|
68
|
+
* parseStudyV3's report mode takes.
|
|
69
|
+
*/
|
|
70
|
+
export declare function v2ToV3Raw(raw: Raw, route: StudyRoute): Raw;
|
|
71
|
+
/**
|
|
72
|
+
* The keys migrate drops: the v2-only rows above and `inert`, the fields parseStudyV3 reports the
|
|
73
|
+
* route does not read (v3 paths, mapped back to the file's), then terminal `execution.timeoutMs`,
|
|
74
|
+
* which no terminal code reads. Parents come before their children, so a child under a dropped
|
|
75
|
+
* parent is reported with the parent.
|
|
76
|
+
*/
|
|
77
|
+
export declare function droppedPaths(study: V2Study, inert: readonly string[]): string[];
|
|
78
|
+
export {};
|