pi-system-one 1.2.0 → 1.2.3
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 +20 -1
- package/demo.gif +0 -0
- package/package.json +7 -2
- package/skills/system-one/SKILL.md +147 -9
- package/src/render.ts +70 -5
- package/src/tool.ts +740 -52
- package/tests/packaging.test.ts +13 -0
- package/tests/properties.test.ts +565 -0
- package/tests/render.test.ts +47 -0
- package/tests/tool.test.ts +1263 -62
package/tests/tool.test.ts
CHANGED
|
@@ -11,6 +11,25 @@ import {
|
|
|
11
11
|
} from "../src/tool.ts";
|
|
12
12
|
|
|
13
13
|
describe("system_one tool", () => {
|
|
14
|
+
/**
|
|
15
|
+
* A tool whose provider records the request it was handed, so a test can
|
|
16
|
+
* assert the exact shape that reached the wire after normalization.
|
|
17
|
+
*/
|
|
18
|
+
function recordingTool() {
|
|
19
|
+
let captured: any = null;
|
|
20
|
+
const stub = {
|
|
21
|
+
id: "stub",
|
|
22
|
+
async evaluate(request: any) {
|
|
23
|
+
captured = request;
|
|
24
|
+
return { answers: {}, metadata: { provider: "stub" } };
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
tool: buildSystemOneTool({ provider: stub as never }),
|
|
29
|
+
request: () => captured,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
14
33
|
it("delegates a mixed batch and preserves details", async () => {
|
|
15
34
|
const tool = buildSystemOneTool({
|
|
16
35
|
provider: new MockSystemOneProvider({
|
|
@@ -303,6 +322,66 @@ describe("system_one tool", () => {
|
|
|
303
322
|
});
|
|
304
323
|
}
|
|
305
324
|
|
|
325
|
+
it("decodes questions serialized as a JSON string (provider quirk)", () => {
|
|
326
|
+
const raw = {
|
|
327
|
+
state: "hi",
|
|
328
|
+
questions: JSON.stringify({
|
|
329
|
+
t: { type: "choice", instructions: "W?", criteria: { a: null } },
|
|
330
|
+
}),
|
|
331
|
+
};
|
|
332
|
+
const prepared = prepareSystemOneArgs(raw);
|
|
333
|
+
assert.deepEqual(prepared, {
|
|
334
|
+
state: "hi",
|
|
335
|
+
questions: {
|
|
336
|
+
t: { type: "choice", instructions: "W?", criteria: { a: null } },
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it("decodes a question serialized as a JSON string inside an array", () => {
|
|
342
|
+
const q = { type: "choice", instructions: "W?", criteria: { a: null } };
|
|
343
|
+
// Every entry stringified, and the whole argument stringified too:
|
|
344
|
+
// both layers decode before normalization.
|
|
345
|
+
for (const questions of [[JSON.stringify(q)], JSON.stringify([q])]) {
|
|
346
|
+
assert.deepEqual(prepareSystemOneArgs({ state: "hi", questions }), {
|
|
347
|
+
state: "hi",
|
|
348
|
+
questions: { q1: q },
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("still rejects strings that are not JSON objects or arrays", async () => {
|
|
354
|
+
const tool = buildSystemOneTool({
|
|
355
|
+
provider: {
|
|
356
|
+
id: "stub",
|
|
357
|
+
async evaluate() {
|
|
358
|
+
return { answers: {}, metadata: { provider: "stub" } };
|
|
359
|
+
},
|
|
360
|
+
} as never,
|
|
361
|
+
});
|
|
362
|
+
for (const questions of [
|
|
363
|
+
"foo",
|
|
364
|
+
"{not json",
|
|
365
|
+
"{{{{",
|
|
366
|
+
"[[[",
|
|
367
|
+
"123",
|
|
368
|
+
"true",
|
|
369
|
+
"null",
|
|
370
|
+
'"hello"',
|
|
371
|
+
]) {
|
|
372
|
+
await assert.rejects(
|
|
373
|
+
tool.execute(
|
|
374
|
+
"id-rej-string",
|
|
375
|
+
{ state: "hi", questions } as never,
|
|
376
|
+
undefined,
|
|
377
|
+
undefined,
|
|
378
|
+
{} as never,
|
|
379
|
+
),
|
|
380
|
+
/"questions" must resolve to a question map/,
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
306
385
|
it("folds options array to null-valued criteria record", () => {
|
|
307
386
|
assert.deepEqual(
|
|
308
387
|
prepareSystemOneArgs({
|
|
@@ -348,7 +427,7 @@ describe("system_one tool", () => {
|
|
|
348
427
|
);
|
|
349
428
|
});
|
|
350
429
|
|
|
351
|
-
it("unwraps single
|
|
430
|
+
it("unwraps a single question and drops its extra fields", async () => {
|
|
352
431
|
const prepared = prepareSystemOneArgs({
|
|
353
432
|
state: "hi",
|
|
354
433
|
questions: {
|
|
@@ -365,12 +444,12 @@ describe("system_one tool", () => {
|
|
|
365
444
|
type: "choice",
|
|
366
445
|
instructions: "W?",
|
|
367
446
|
criteria: { a: null },
|
|
368
|
-
foo: "bar",
|
|
369
447
|
},
|
|
370
448
|
},
|
|
371
449
|
});
|
|
372
450
|
// The permissive boundary schema lets this through (older Pi must be
|
|
373
|
-
// able to reach execute)
|
|
451
|
+
// able to reach execute), and the extra field is dropped rather than
|
|
452
|
+
// turned into a retry the model has to learn from.
|
|
374
453
|
assert.equal(Value.Check(systemOneParams, prepared), true);
|
|
375
454
|
let calls = 0;
|
|
376
455
|
const tool = buildSystemOneTool({
|
|
@@ -382,17 +461,14 @@ describe("system_one tool", () => {
|
|
|
382
461
|
},
|
|
383
462
|
} as never,
|
|
384
463
|
});
|
|
385
|
-
await
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
{} as never,
|
|
392
|
-
),
|
|
393
|
-
/question "q" has unknown field "foo"/,
|
|
464
|
+
await tool.execute(
|
|
465
|
+
"id-foo",
|
|
466
|
+
prepared as never,
|
|
467
|
+
undefined,
|
|
468
|
+
undefined,
|
|
469
|
+
{} as never,
|
|
394
470
|
);
|
|
395
|
-
assert.equal(calls,
|
|
471
|
+
assert.equal(calls, 1);
|
|
396
472
|
});
|
|
397
473
|
|
|
398
474
|
it("lets unknown types through the schema so execute rejects them clearly", async () => {
|
|
@@ -861,21 +937,6 @@ describe("system_one tool", () => {
|
|
|
861
937
|
{ state: "hi", questions: null },
|
|
862
938
|
/questions is required/,
|
|
863
939
|
],
|
|
864
|
-
[
|
|
865
|
-
"unknown field",
|
|
866
|
-
{
|
|
867
|
-
state: "hi",
|
|
868
|
-
questions: {
|
|
869
|
-
t: {
|
|
870
|
-
type: "choice",
|
|
871
|
-
instructions: "W?",
|
|
872
|
-
criteria: { a: null },
|
|
873
|
-
confidence: 0.9,
|
|
874
|
-
},
|
|
875
|
-
},
|
|
876
|
-
},
|
|
877
|
-
/question "t" has unknown field "confidence"/,
|
|
878
|
-
],
|
|
879
940
|
[
|
|
880
941
|
"noul criteria as array",
|
|
881
942
|
{
|
|
@@ -1044,11 +1105,6 @@ describe("system_one tool", () => {
|
|
|
1044
1105
|
provider: new MockSystemOneProvider({ answers: {} }),
|
|
1045
1106
|
});
|
|
1046
1107
|
assert.ok(tool.promptSnippet && tool.promptSnippet.length > 0);
|
|
1047
|
-
for (const guideline of tool.promptGuidelines ?? []) {
|
|
1048
|
-
assert.match(guideline, /system_one/);
|
|
1049
|
-
}
|
|
1050
|
-
assert.match(tool.description, /instead of answering directly/);
|
|
1051
|
-
assert.match(tool.description, /calibrated probabilities/);
|
|
1052
1108
|
});
|
|
1053
1109
|
|
|
1054
1110
|
it("keeps discovery and type selection mechanical in one layer", () => {
|
|
@@ -1075,6 +1131,245 @@ describe("system_one tool", () => {
|
|
|
1075
1131
|
});
|
|
1076
1132
|
});
|
|
1077
1133
|
|
|
1134
|
+
describe("copy-on-write normalization", () => {
|
|
1135
|
+
const canonicalRequest = () => ({
|
|
1136
|
+
state: { incident: "latency" },
|
|
1137
|
+
questions: {
|
|
1138
|
+
rollback: { type: "noul", instructions: "Roll back?" },
|
|
1139
|
+
owner: {
|
|
1140
|
+
type: "choice",
|
|
1141
|
+
instructions: "Which team?",
|
|
1142
|
+
criteria: { frontend: null, backend: null },
|
|
1143
|
+
},
|
|
1144
|
+
},
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
it("returns canonical requests, maps, and questions by reference", () => {
|
|
1148
|
+
const args = canonicalRequest();
|
|
1149
|
+
const out = prepareSystemOneArgs(args) as typeof args;
|
|
1150
|
+
assert.equal(out, args);
|
|
1151
|
+
assert.equal(out.questions, args.questions);
|
|
1152
|
+
assert.equal(out.questions.rollback, args.questions.rollback);
|
|
1153
|
+
assert.equal(out.questions.owner, args.questions.owner);
|
|
1154
|
+
// Key order is not canonicalized: identity means "already final".
|
|
1155
|
+
const reordered = {
|
|
1156
|
+
state: "s",
|
|
1157
|
+
questions: {
|
|
1158
|
+
q: { criteria: { a: null }, instructions: "I?", type: "choice" },
|
|
1159
|
+
},
|
|
1160
|
+
};
|
|
1161
|
+
assert.equal(prepareSystemOneArgs(reordered), reordered);
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
it("structurally shares unaffected siblings on partial repair", () => {
|
|
1165
|
+
const args = {
|
|
1166
|
+
state: "s",
|
|
1167
|
+
questions: {
|
|
1168
|
+
fine: { type: "noul", instructions: "OK?" },
|
|
1169
|
+
sloppy: { type: "Choice", prompt: "Pick?", options: ["a", "b"] },
|
|
1170
|
+
},
|
|
1171
|
+
};
|
|
1172
|
+
const out = prepareSystemOneArgs(args) as typeof args;
|
|
1173
|
+
assert.notEqual(out, args);
|
|
1174
|
+
assert.notEqual(out.questions, args.questions);
|
|
1175
|
+
assert.notEqual(out.questions.sloppy, args.questions.sloppy);
|
|
1176
|
+
assert.equal(out.questions.fine, args.questions.fine);
|
|
1177
|
+
assert.deepEqual(out.questions.sloppy, {
|
|
1178
|
+
type: "choice",
|
|
1179
|
+
instructions: "Pick?",
|
|
1180
|
+
criteria: { a: null, b: null },
|
|
1181
|
+
});
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
it("is idempotent and allocates nothing new on the second pass", () => {
|
|
1185
|
+
const once = prepareSystemOneArgs(canonicalRequest()) as Record<
|
|
1186
|
+
string,
|
|
1187
|
+
unknown
|
|
1188
|
+
>;
|
|
1189
|
+
const twice = prepareSystemOneArgs(once);
|
|
1190
|
+
assert.deepEqual(twice, once);
|
|
1191
|
+
assert.equal(twice, once);
|
|
1192
|
+
// Repair once, then the repaired output is itself canonical.
|
|
1193
|
+
const repairedOnce = prepareSystemOneArgs({
|
|
1194
|
+
state: "s",
|
|
1195
|
+
questions: [{ name: "q", type: "boolean", prompt: "Y?" }],
|
|
1196
|
+
});
|
|
1197
|
+
assert.equal(prepareSystemOneArgs(repairedOnce), repairedOnce);
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
it("never mutates its input", () => {
|
|
1201
|
+
const deepFreeze = (v: unknown): void => {
|
|
1202
|
+
if (typeof v === "object" && v !== null && !Object.isFrozen(v)) {
|
|
1203
|
+
Object.freeze(v);
|
|
1204
|
+
for (const k of Object.keys(v)) {
|
|
1205
|
+
deepFreeze((v as Record<string, unknown>)[k]);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
const sloppy = {
|
|
1210
|
+
context: { incident: "latency" },
|
|
1211
|
+
question: [
|
|
1212
|
+
{ name: "a", type: "Rating", prompt: "Rate?", levels: ["1", "5"] },
|
|
1213
|
+
{ id: "b", type: "noul", instructions: "OK?", extra: 1 },
|
|
1214
|
+
],
|
|
1215
|
+
};
|
|
1216
|
+
deepFreeze(sloppy);
|
|
1217
|
+
const out = prepareSystemOneArgs(sloppy);
|
|
1218
|
+
assert.deepEqual(out, {
|
|
1219
|
+
state: { incident: "latency" },
|
|
1220
|
+
questions: {
|
|
1221
|
+
a: {
|
|
1222
|
+
type: "score",
|
|
1223
|
+
instructions: "Rate?",
|
|
1224
|
+
criteria: ["1", "5"],
|
|
1225
|
+
},
|
|
1226
|
+
b: { type: "noul", instructions: "OK?" },
|
|
1227
|
+
},
|
|
1228
|
+
});
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
it("repairs every non-canonical shape exactly (no false sharing)", () => {
|
|
1232
|
+
// [input, expected output, sharesInputRef]: the last two entries are
|
|
1233
|
+
// already builder fixed points, so sharing them is correct.
|
|
1234
|
+
const cases: Array<[unknown, unknown, boolean]> = [
|
|
1235
|
+
// Alias type spelling.
|
|
1236
|
+
[
|
|
1237
|
+
{
|
|
1238
|
+
state: "s",
|
|
1239
|
+
questions: { q: { type: "Yes/No", instructions: "Y?" } },
|
|
1240
|
+
},
|
|
1241
|
+
{
|
|
1242
|
+
state: "s",
|
|
1243
|
+
questions: { q: { type: "noul", instructions: "Y?" } },
|
|
1244
|
+
},
|
|
1245
|
+
false,
|
|
1246
|
+
],
|
|
1247
|
+
// Alias field names.
|
|
1248
|
+
[
|
|
1249
|
+
{
|
|
1250
|
+
state: "s",
|
|
1251
|
+
questions: { q: { type: "choice", prompt: "P?", choices: ["a"] } },
|
|
1252
|
+
},
|
|
1253
|
+
{
|
|
1254
|
+
state: "s",
|
|
1255
|
+
questions: {
|
|
1256
|
+
q: { type: "choice", instructions: "P?", criteria: { a: null } },
|
|
1257
|
+
},
|
|
1258
|
+
},
|
|
1259
|
+
false,
|
|
1260
|
+
],
|
|
1261
|
+
// Junk keys dropped.
|
|
1262
|
+
[
|
|
1263
|
+
{
|
|
1264
|
+
state: "s",
|
|
1265
|
+
questions: {
|
|
1266
|
+
q: {
|
|
1267
|
+
type: "noul",
|
|
1268
|
+
instructions: "Y?",
|
|
1269
|
+
description: "d",
|
|
1270
|
+
title: "t",
|
|
1271
|
+
},
|
|
1272
|
+
},
|
|
1273
|
+
},
|
|
1274
|
+
{
|
|
1275
|
+
state: "s",
|
|
1276
|
+
questions: { q: { type: "noul", instructions: "Y?" } },
|
|
1277
|
+
},
|
|
1278
|
+
false,
|
|
1279
|
+
],
|
|
1280
|
+
// Undefined-valued lingering alias key is still dropped.
|
|
1281
|
+
[
|
|
1282
|
+
{
|
|
1283
|
+
state: "s",
|
|
1284
|
+
questions: {
|
|
1285
|
+
q: { type: "noul", instructions: "Y?", prompt: undefined },
|
|
1286
|
+
},
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
state: "s",
|
|
1290
|
+
questions: { q: { type: "noul", instructions: "Y?" } },
|
|
1291
|
+
},
|
|
1292
|
+
false,
|
|
1293
|
+
],
|
|
1294
|
+
// Own undefined instructions key is preserved (builder copies it).
|
|
1295
|
+
[
|
|
1296
|
+
{
|
|
1297
|
+
state: "s",
|
|
1298
|
+
questions: { q: { type: "noul", instructions: undefined } },
|
|
1299
|
+
},
|
|
1300
|
+
{
|
|
1301
|
+
state: "s",
|
|
1302
|
+
questions: { q: { type: "noul", instructions: undefined } },
|
|
1303
|
+
},
|
|
1304
|
+
true,
|
|
1305
|
+
],
|
|
1306
|
+
// noul null criteria reads as omitted.
|
|
1307
|
+
[
|
|
1308
|
+
{
|
|
1309
|
+
state: "s",
|
|
1310
|
+
questions: {
|
|
1311
|
+
q: { type: "noul", instructions: "Y?", criteria: null },
|
|
1312
|
+
},
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
state: "s",
|
|
1316
|
+
questions: { q: { type: "noul", instructions: "Y?" } },
|
|
1317
|
+
},
|
|
1318
|
+
false,
|
|
1319
|
+
],
|
|
1320
|
+
// Singular question alias + array form + naming metadata.
|
|
1321
|
+
[
|
|
1322
|
+
{ context: "s", question: { type: "boolean", prompt: "Y?" } },
|
|
1323
|
+
{
|
|
1324
|
+
state: "s",
|
|
1325
|
+
questions: { q: { type: "noul", instructions: "Y?" } },
|
|
1326
|
+
},
|
|
1327
|
+
false,
|
|
1328
|
+
],
|
|
1329
|
+
// Unknown top-level keys dropped; missing state preserved as-is.
|
|
1330
|
+
[
|
|
1331
|
+
{
|
|
1332
|
+
state: "s",
|
|
1333
|
+
questions: { q: { type: "noul", instructions: "Y?" } },
|
|
1334
|
+
extra: 1,
|
|
1335
|
+
},
|
|
1336
|
+
{
|
|
1337
|
+
state: "s",
|
|
1338
|
+
questions: { q: { type: "noul", instructions: "Y?" } },
|
|
1339
|
+
},
|
|
1340
|
+
false,
|
|
1341
|
+
],
|
|
1342
|
+
// Empty choice array still folds (every() on [] is true).
|
|
1343
|
+
[
|
|
1344
|
+
{
|
|
1345
|
+
state: "s",
|
|
1346
|
+
questions: {
|
|
1347
|
+
q: { type: "choice", instructions: "P?", criteria: [] },
|
|
1348
|
+
},
|
|
1349
|
+
},
|
|
1350
|
+
{
|
|
1351
|
+
state: "s",
|
|
1352
|
+
questions: {
|
|
1353
|
+
q: { type: "choice", instructions: "P?", criteria: {} },
|
|
1354
|
+
},
|
|
1355
|
+
},
|
|
1356
|
+
false,
|
|
1357
|
+
],
|
|
1358
|
+
// Non-string type passes through untouched (fixed point).
|
|
1359
|
+
[
|
|
1360
|
+
{ state: "s", questions: { q: { type: 5, instructions: "Y?" } } },
|
|
1361
|
+
{ state: "s", questions: { q: { type: 5, instructions: "Y?" } } },
|
|
1362
|
+
true,
|
|
1363
|
+
],
|
|
1364
|
+
];
|
|
1365
|
+
for (const [input, expected, sharesRef] of cases) {
|
|
1366
|
+
const out = prepareSystemOneArgs(input);
|
|
1367
|
+
assert.deepEqual(out, expected);
|
|
1368
|
+
assert.equal(out === input, sharesRef);
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1078
1373
|
describe("backend minima guardrails", () => {
|
|
1079
1374
|
function stubTool() {
|
|
1080
1375
|
let calls = 0;
|
|
@@ -1227,18 +1522,92 @@ describe("system_one tool", () => {
|
|
|
1227
1522
|
assert.equal(calls(), 0);
|
|
1228
1523
|
});
|
|
1229
1524
|
|
|
1230
|
-
it("
|
|
1525
|
+
it("folds object score criteria into an ordered rubric", async () => {
|
|
1526
|
+
const { tool, request } = recordingTool();
|
|
1527
|
+
await tool.execute(
|
|
1528
|
+
"id-fold-score",
|
|
1529
|
+
{
|
|
1530
|
+
state: "hi",
|
|
1531
|
+
questions: {
|
|
1532
|
+
s: {
|
|
1533
|
+
type: "score",
|
|
1534
|
+
instructions: "How?",
|
|
1535
|
+
criteria: { low: "bad", high: "good" },
|
|
1536
|
+
},
|
|
1537
|
+
},
|
|
1538
|
+
} as never,
|
|
1539
|
+
undefined,
|
|
1540
|
+
undefined,
|
|
1541
|
+
{} as never,
|
|
1542
|
+
);
|
|
1543
|
+
assert.deepEqual(request().questions.s.criteria, [
|
|
1544
|
+
"low: bad",
|
|
1545
|
+
"high: good",
|
|
1546
|
+
]);
|
|
1547
|
+
});
|
|
1548
|
+
|
|
1549
|
+
it("keeps a score level's label and description without duplicating", async () => {
|
|
1550
|
+
const { tool, request } = recordingTool();
|
|
1551
|
+
await tool.execute(
|
|
1552
|
+
"id-level-self-describing",
|
|
1553
|
+
{
|
|
1554
|
+
state: "hi",
|
|
1555
|
+
questions: {
|
|
1556
|
+
s: {
|
|
1557
|
+
type: "score",
|
|
1558
|
+
instructions: "How bad?",
|
|
1559
|
+
criteria: {
|
|
1560
|
+
low: "Bad: minor and reversible",
|
|
1561
|
+
high: "",
|
|
1562
|
+
moderate: "moderate",
|
|
1563
|
+
},
|
|
1564
|
+
},
|
|
1565
|
+
},
|
|
1566
|
+
} as never,
|
|
1567
|
+
undefined,
|
|
1568
|
+
undefined,
|
|
1569
|
+
{} as never,
|
|
1570
|
+
);
|
|
1571
|
+
assert.deepEqual(request().questions.s.criteria, [
|
|
1572
|
+
"low: Bad: minor and reversible",
|
|
1573
|
+
"high",
|
|
1574
|
+
"moderate",
|
|
1575
|
+
]);
|
|
1576
|
+
});
|
|
1577
|
+
|
|
1578
|
+
it("unwraps a single wrapper key holding the rubric", async () => {
|
|
1579
|
+
const { tool, request } = recordingTool();
|
|
1580
|
+
await tool.execute(
|
|
1581
|
+
"id-fold-score-wrapper",
|
|
1582
|
+
{
|
|
1583
|
+
state: "hi",
|
|
1584
|
+
questions: {
|
|
1585
|
+
s: {
|
|
1586
|
+
type: "score",
|
|
1587
|
+
instructions: "How?",
|
|
1588
|
+
criteria: { item: ["low", "high"] },
|
|
1589
|
+
},
|
|
1590
|
+
},
|
|
1591
|
+
} as never,
|
|
1592
|
+
undefined,
|
|
1593
|
+
undefined,
|
|
1594
|
+
{} as never,
|
|
1595
|
+
);
|
|
1596
|
+
assert.deepEqual(request().questions.s.criteria, ["low", "high"]);
|
|
1597
|
+
});
|
|
1598
|
+
|
|
1599
|
+
it("rejects a one-level object rubric rather than unwrapping it", async () => {
|
|
1231
1600
|
const { tool, calls } = stubTool();
|
|
1232
1601
|
await assert.rejects(
|
|
1233
1602
|
tool.execute(
|
|
1234
|
-
"id-
|
|
1603
|
+
"id-fold-score-single",
|
|
1235
1604
|
{
|
|
1236
1605
|
state: "hi",
|
|
1237
1606
|
questions: {
|
|
1238
1607
|
s: {
|
|
1239
1608
|
type: "score",
|
|
1240
1609
|
instructions: "How?",
|
|
1241
|
-
criteria: {
|
|
1610
|
+
criteria: { only: "meh" },
|
|
1242
1611
|
},
|
|
1243
1612
|
},
|
|
1244
1613
|
} as never,
|
|
@@ -1251,47 +1620,879 @@ describe("system_one tool", () => {
|
|
|
1251
1620
|
assert.equal(calls(), 0);
|
|
1252
1621
|
});
|
|
1253
1622
|
|
|
1254
|
-
it("
|
|
1255
|
-
const { tool,
|
|
1256
|
-
await
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1623
|
+
it("lifts bare noul outcomes into criteria", async () => {
|
|
1624
|
+
const { tool, request } = recordingTool();
|
|
1625
|
+
await tool.execute(
|
|
1626
|
+
"id-lift-noul",
|
|
1627
|
+
{
|
|
1628
|
+
state: "hi",
|
|
1629
|
+
questions: {
|
|
1630
|
+
n: {
|
|
1631
|
+
type: "noul",
|
|
1632
|
+
instructions: "Is it?",
|
|
1633
|
+
true: "it is",
|
|
1634
|
+
false: "it is not",
|
|
1263
1635
|
},
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
/choice question "t" needs criteria as an object/,
|
|
1636
|
+
},
|
|
1637
|
+
} as never,
|
|
1638
|
+
undefined,
|
|
1639
|
+
undefined,
|
|
1640
|
+
{} as never,
|
|
1270
1641
|
);
|
|
1271
|
-
assert.
|
|
1642
|
+
assert.deepEqual(request().questions.n, {
|
|
1643
|
+
type: "noul",
|
|
1644
|
+
instructions: "Is it?",
|
|
1645
|
+
criteria: { true: "it is", false: "it is not" },
|
|
1646
|
+
});
|
|
1272
1647
|
});
|
|
1273
1648
|
|
|
1274
|
-
it("
|
|
1275
|
-
const { tool,
|
|
1649
|
+
it("merges choice labels spilled out of criteria", async () => {
|
|
1650
|
+
const { tool, request } = recordingTool();
|
|
1276
1651
|
await tool.execute(
|
|
1277
|
-
"id-
|
|
1652
|
+
"id-spilled-choice",
|
|
1278
1653
|
{
|
|
1279
1654
|
state: "hi",
|
|
1280
1655
|
questions: {
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1656
|
+
ownership: {
|
|
1657
|
+
type: "choice",
|
|
1658
|
+
instructions: "Who?",
|
|
1659
|
+
criteria: { sre_oncall: "" },
|
|
1660
|
+
account_team: "",
|
|
1661
|
+
ingest_service_owner: "",
|
|
1286
1662
|
},
|
|
1287
|
-
n: { type: "noul", instructions: "Is it?" },
|
|
1288
1663
|
},
|
|
1289
1664
|
} as never,
|
|
1290
1665
|
undefined,
|
|
1291
1666
|
undefined,
|
|
1292
1667
|
{} as never,
|
|
1293
1668
|
);
|
|
1294
|
-
assert.
|
|
1669
|
+
assert.deepEqual(Object.keys(request().questions.ownership.criteria), [
|
|
1670
|
+
"sre_oncall",
|
|
1671
|
+
"account_team",
|
|
1672
|
+
"ingest_service_owner",
|
|
1673
|
+
]);
|
|
1674
|
+
});
|
|
1675
|
+
|
|
1676
|
+
it("uses spilled labels as criteria when criteria is absent", async () => {
|
|
1677
|
+
const { tool, request } = recordingTool();
|
|
1678
|
+
await tool.execute(
|
|
1679
|
+
"id-spilled-only",
|
|
1680
|
+
{
|
|
1681
|
+
state: "hi",
|
|
1682
|
+
questions: {
|
|
1683
|
+
pick: {
|
|
1684
|
+
type: "choice",
|
|
1685
|
+
instructions: "Which?",
|
|
1686
|
+
billing: null,
|
|
1687
|
+
onboarding: null,
|
|
1688
|
+
},
|
|
1689
|
+
},
|
|
1690
|
+
} as never,
|
|
1691
|
+
undefined,
|
|
1692
|
+
undefined,
|
|
1693
|
+
{} as never,
|
|
1694
|
+
);
|
|
1695
|
+
assert.deepEqual(request().questions.pick.criteria, {
|
|
1696
|
+
billing: null,
|
|
1697
|
+
onboarding: null,
|
|
1698
|
+
});
|
|
1699
|
+
});
|
|
1700
|
+
|
|
1701
|
+
it("drops an empty sibling that is a noul question's own name", async () => {
|
|
1702
|
+
const { tool, request } = recordingTool();
|
|
1703
|
+
await tool.execute(
|
|
1704
|
+
"id-noul-selfname",
|
|
1705
|
+
{
|
|
1706
|
+
state: "hi",
|
|
1707
|
+
questions: {
|
|
1708
|
+
q: {
|
|
1709
|
+
type: "noul",
|
|
1710
|
+
instructions: "Is it?",
|
|
1711
|
+
is_coordinated_credential_stuffing_run: "",
|
|
1712
|
+
},
|
|
1713
|
+
},
|
|
1714
|
+
} as never,
|
|
1715
|
+
undefined,
|
|
1716
|
+
undefined,
|
|
1717
|
+
{} as never,
|
|
1718
|
+
);
|
|
1719
|
+
assert.deepEqual(request().questions.q, {
|
|
1720
|
+
type: "noul",
|
|
1721
|
+
instructions: "Is it?",
|
|
1722
|
+
});
|
|
1723
|
+
});
|
|
1724
|
+
|
|
1725
|
+
it("drops a NON-empty extra field rather than failing the call", async () => {
|
|
1726
|
+
const { tool, request } = recordingTool();
|
|
1727
|
+
await tool.execute(
|
|
1728
|
+
"id-nonempty-extra",
|
|
1729
|
+
{
|
|
1730
|
+
state: "hi",
|
|
1731
|
+
questions: {
|
|
1732
|
+
q: {
|
|
1733
|
+
type: "noul",
|
|
1734
|
+
instructions: "Is it?",
|
|
1735
|
+
confidence: 0.9,
|
|
1736
|
+
},
|
|
1737
|
+
},
|
|
1738
|
+
} as never,
|
|
1739
|
+
undefined,
|
|
1740
|
+
undefined,
|
|
1741
|
+
{} as never,
|
|
1742
|
+
);
|
|
1743
|
+
assert.deepEqual(request().questions.q, {
|
|
1744
|
+
type: "noul",
|
|
1745
|
+
instructions: "Is it?",
|
|
1746
|
+
});
|
|
1747
|
+
});
|
|
1748
|
+
|
|
1749
|
+
it("salvages the type from an extra key that names the kind", async () => {
|
|
1750
|
+
const { tool, request } = recordingTool();
|
|
1751
|
+
await tool.execute(
|
|
1752
|
+
"id-salvage-type",
|
|
1753
|
+
{
|
|
1754
|
+
state: "hi",
|
|
1755
|
+
questions: {
|
|
1756
|
+
q: {
|
|
1757
|
+
approach: "yes_no",
|
|
1758
|
+
name: "finishes_in_window",
|
|
1759
|
+
instructions: "Will it fit?",
|
|
1760
|
+
},
|
|
1761
|
+
},
|
|
1762
|
+
} as never,
|
|
1763
|
+
undefined,
|
|
1764
|
+
undefined,
|
|
1765
|
+
{} as never,
|
|
1766
|
+
);
|
|
1767
|
+
assert.deepEqual(request().questions.q, {
|
|
1768
|
+
type: "noul",
|
|
1769
|
+
instructions: "Will it fit?",
|
|
1770
|
+
});
|
|
1771
|
+
});
|
|
1772
|
+
|
|
1773
|
+
it("unwraps a single-key wrapper around an array of questions", async () => {
|
|
1774
|
+
const { tool, request } = recordingTool();
|
|
1775
|
+
await tool.execute(
|
|
1776
|
+
"id-wrapped-array",
|
|
1777
|
+
{
|
|
1778
|
+
state: "hi",
|
|
1779
|
+
questions: {
|
|
1780
|
+
item: [
|
|
1781
|
+
{ name: "plan_it", type: "noul", instructions: "Should we?" },
|
|
1782
|
+
{
|
|
1783
|
+
name: "how_soon",
|
|
1784
|
+
type: "score",
|
|
1785
|
+
instructions: "How soon?",
|
|
1786
|
+
criteria: ["now", "later"],
|
|
1787
|
+
},
|
|
1788
|
+
],
|
|
1789
|
+
},
|
|
1790
|
+
} as never,
|
|
1791
|
+
undefined,
|
|
1792
|
+
undefined,
|
|
1793
|
+
{} as never,
|
|
1794
|
+
);
|
|
1795
|
+
assert.deepEqual(Object.keys(request().questions), [
|
|
1796
|
+
"plan_it",
|
|
1797
|
+
"how_soon",
|
|
1798
|
+
]);
|
|
1799
|
+
});
|
|
1800
|
+
|
|
1801
|
+
it("drops string entries beside a real question", async () => {
|
|
1802
|
+
const { tool, request } = recordingTool();
|
|
1803
|
+
await tool.execute(
|
|
1804
|
+
"id-string-entry",
|
|
1805
|
+
{
|
|
1806
|
+
state: "hi",
|
|
1807
|
+
questions: {
|
|
1808
|
+
refund_decision: {
|
|
1809
|
+
type: "choice",
|
|
1810
|
+
instructions: "Which?",
|
|
1811
|
+
criteria: { deny: null },
|
|
1812
|
+
},
|
|
1813
|
+
deny: "refund it",
|
|
1814
|
+
"PR #419 (feat: add metrics endpoint)": "merge it",
|
|
1815
|
+
},
|
|
1816
|
+
} as never,
|
|
1817
|
+
undefined,
|
|
1818
|
+
undefined,
|
|
1819
|
+
{} as never,
|
|
1820
|
+
);
|
|
1821
|
+
assert.deepEqual(Object.keys(request().questions), ["refund_decision"]);
|
|
1822
|
+
});
|
|
1823
|
+
|
|
1824
|
+
it("still rejects a question map of nothing but strings", async () => {
|
|
1825
|
+
const { tool, calls } = stubTool();
|
|
1826
|
+
await assert.rejects(
|
|
1827
|
+
tool.execute(
|
|
1828
|
+
"id-all-strings",
|
|
1829
|
+
{
|
|
1830
|
+
state: "hi",
|
|
1831
|
+
questions: { deny: "refund it", allow: "let it go" },
|
|
1832
|
+
} as never,
|
|
1833
|
+
undefined,
|
|
1834
|
+
undefined,
|
|
1835
|
+
{} as never,
|
|
1836
|
+
),
|
|
1837
|
+
/must be an object with "type"/,
|
|
1838
|
+
);
|
|
1839
|
+
assert.equal(calls(), 0);
|
|
1840
|
+
});
|
|
1841
|
+
|
|
1842
|
+
it("merges a name-to-array question map", async () => {
|
|
1843
|
+
const { tool, request } = recordingTool();
|
|
1844
|
+
await tool.execute(
|
|
1845
|
+
"id-array-map",
|
|
1846
|
+
{
|
|
1847
|
+
state: "hi",
|
|
1848
|
+
questions: {
|
|
1849
|
+
fixes: [
|
|
1850
|
+
{ type: "noul", instructions: "Is it safe?" },
|
|
1851
|
+
{ type: "noul", instructions: "Is it cheap?" },
|
|
1852
|
+
],
|
|
1853
|
+
risks: [
|
|
1854
|
+
{
|
|
1855
|
+
type: "score",
|
|
1856
|
+
instructions: "How bad?",
|
|
1857
|
+
criteria: ["low", "high"],
|
|
1858
|
+
},
|
|
1859
|
+
],
|
|
1860
|
+
},
|
|
1861
|
+
} as never,
|
|
1862
|
+
undefined,
|
|
1863
|
+
undefined,
|
|
1864
|
+
{} as never,
|
|
1865
|
+
);
|
|
1866
|
+
assert.deepEqual(Object.keys(request().questions), [
|
|
1867
|
+
"fixes",
|
|
1868
|
+
"fixes_2",
|
|
1869
|
+
"risks",
|
|
1870
|
+
]);
|
|
1871
|
+
});
|
|
1872
|
+
|
|
1873
|
+
it("unwraps one question filed under the wrapper's own name", async () => {
|
|
1874
|
+
const { tool, request } = recordingTool();
|
|
1875
|
+
await tool.execute(
|
|
1876
|
+
"id-named-questions",
|
|
1877
|
+
{
|
|
1878
|
+
state: "hi",
|
|
1879
|
+
questions: {
|
|
1880
|
+
questions: {
|
|
1881
|
+
type: "score",
|
|
1882
|
+
instructions: "How clear?",
|
|
1883
|
+
criteria: ["low", "high"],
|
|
1884
|
+
},
|
|
1885
|
+
},
|
|
1886
|
+
} as never,
|
|
1887
|
+
undefined,
|
|
1888
|
+
undefined,
|
|
1889
|
+
{} as never,
|
|
1890
|
+
);
|
|
1891
|
+
assert.deepEqual(Object.keys(request().questions), ["q"]);
|
|
1892
|
+
assert.equal(request().questions.q.type, "score");
|
|
1893
|
+
});
|
|
1894
|
+
|
|
1895
|
+
it("peels a wrapper nested more than one level deep", async () => {
|
|
1896
|
+
const { tool, request } = recordingTool();
|
|
1897
|
+
await tool.execute(
|
|
1898
|
+
"id-nested-wrapper",
|
|
1899
|
+
{
|
|
1900
|
+
state: "hi",
|
|
1901
|
+
questions: {
|
|
1902
|
+
impact: {
|
|
1903
|
+
type: "score",
|
|
1904
|
+
instructions: "How bad?",
|
|
1905
|
+
criteria: {
|
|
1906
|
+
item: { item: ["negligible", "moderate", "severe"] },
|
|
1907
|
+
},
|
|
1908
|
+
},
|
|
1909
|
+
},
|
|
1910
|
+
} as never,
|
|
1911
|
+
undefined,
|
|
1912
|
+
undefined,
|
|
1913
|
+
{} as never,
|
|
1914
|
+
);
|
|
1915
|
+
assert.deepEqual(request().questions.impact.criteria, [
|
|
1916
|
+
"negligible",
|
|
1917
|
+
"moderate",
|
|
1918
|
+
"severe",
|
|
1919
|
+
]);
|
|
1920
|
+
});
|
|
1921
|
+
|
|
1922
|
+
it("still rejects a one-level rubric nested in a wrapper", async () => {
|
|
1923
|
+
const { tool, calls } = stubTool();
|
|
1924
|
+
await assert.rejects(
|
|
1925
|
+
tool.execute(
|
|
1926
|
+
"id-nested-one-level",
|
|
1927
|
+
{
|
|
1928
|
+
state: "hi",
|
|
1929
|
+
questions: {
|
|
1930
|
+
impact: {
|
|
1931
|
+
type: "score",
|
|
1932
|
+
instructions: "How bad?",
|
|
1933
|
+
criteria: { item: { only: "meh" } },
|
|
1934
|
+
},
|
|
1935
|
+
},
|
|
1936
|
+
} as never,
|
|
1937
|
+
undefined,
|
|
1938
|
+
undefined,
|
|
1939
|
+
{} as never,
|
|
1940
|
+
),
|
|
1941
|
+
/score question "impact" needs criteria as an ordered array/,
|
|
1942
|
+
);
|
|
1943
|
+
assert.equal(calls(), 0);
|
|
1944
|
+
});
|
|
1945
|
+
|
|
1946
|
+
it("transposes parallel arrays into named questions", async () => {
|
|
1947
|
+
const { tool, request } = recordingTool();
|
|
1948
|
+
await tool.execute(
|
|
1949
|
+
"id-parallel-arrays",
|
|
1950
|
+
{
|
|
1951
|
+
state: "hi",
|
|
1952
|
+
questions: {
|
|
1953
|
+
item: ["refund", "risk", "followup"],
|
|
1954
|
+
type: ["choice", "score", "noul"],
|
|
1955
|
+
instructions: [
|
|
1956
|
+
"Which action?",
|
|
1957
|
+
"How risky?",
|
|
1958
|
+
"Will they follow up?",
|
|
1959
|
+
],
|
|
1960
|
+
criteria: [{ refund: null, deny: null }, ["low", "high"], null],
|
|
1961
|
+
},
|
|
1962
|
+
} as never,
|
|
1963
|
+
undefined,
|
|
1964
|
+
undefined,
|
|
1965
|
+
{} as never,
|
|
1966
|
+
);
|
|
1967
|
+
const asked = request().questions;
|
|
1968
|
+
assert.deepEqual(Object.keys(asked), ["refund", "risk", "followup"]);
|
|
1969
|
+
assert.equal(asked.refund.type, "choice");
|
|
1970
|
+
assert.deepEqual(asked.risk.criteria, ["low", "high"]);
|
|
1971
|
+
assert.equal(asked.followup.type, "noul");
|
|
1972
|
+
});
|
|
1973
|
+
|
|
1974
|
+
it("unwraps a question under the wrapper name beside other junk keys", async () => {
|
|
1975
|
+
const { tool, request } = recordingTool();
|
|
1976
|
+
await tool.execute(
|
|
1977
|
+
"id-alias-siblings",
|
|
1978
|
+
{
|
|
1979
|
+
state: "hi",
|
|
1980
|
+
questions: {
|
|
1981
|
+
policy: "",
|
|
1982
|
+
questions: {
|
|
1983
|
+
type: "choice",
|
|
1984
|
+
instructions: "Which policy?",
|
|
1985
|
+
criteria: { a: null, b: null },
|
|
1986
|
+
},
|
|
1987
|
+
},
|
|
1988
|
+
} as never,
|
|
1989
|
+
undefined,
|
|
1990
|
+
undefined,
|
|
1991
|
+
{} as never,
|
|
1992
|
+
);
|
|
1993
|
+
assert.deepEqual(Object.keys(request().questions), ["q"]);
|
|
1994
|
+
assert.equal(request().questions.q.type, "choice");
|
|
1995
|
+
});
|
|
1996
|
+
|
|
1997
|
+
it("drops a question FIELD that landed in the name slot", async () => {
|
|
1998
|
+
const { tool, request } = recordingTool();
|
|
1999
|
+
await tool.execute(
|
|
2000
|
+
"id-field-in-name-slot",
|
|
2001
|
+
{
|
|
2002
|
+
state: "hi",
|
|
2003
|
+
questions: {
|
|
2004
|
+
pick: {
|
|
2005
|
+
type: "choice",
|
|
2006
|
+
instructions: "Which library?",
|
|
2007
|
+
criteria: { date_fns: null, dayjs: null },
|
|
2008
|
+
},
|
|
2009
|
+
criteria: { stray: true },
|
|
2010
|
+
instructions: "stray",
|
|
2011
|
+
},
|
|
2012
|
+
} as never,
|
|
2013
|
+
undefined,
|
|
2014
|
+
undefined,
|
|
2015
|
+
{} as never,
|
|
2016
|
+
);
|
|
2017
|
+
assert.deepEqual(Object.keys(request().questions), ["pick"]);
|
|
2018
|
+
});
|
|
2019
|
+
|
|
2020
|
+
it("splices a question map that landed in a name slot", async () => {
|
|
2021
|
+
const { tool, request } = recordingTool();
|
|
2022
|
+
await tool.execute(
|
|
2023
|
+
"id-nested-map",
|
|
2024
|
+
{
|
|
2025
|
+
state: "hi",
|
|
2026
|
+
questions: {
|
|
2027
|
+
"Which single library should this CLI adopt?": {
|
|
2028
|
+
which_date_library: {
|
|
2029
|
+
type: "choice",
|
|
2030
|
+
instructions: "Which one?",
|
|
2031
|
+
criteria: { date_fns: null, dayjs: null },
|
|
2032
|
+
},
|
|
2033
|
+
},
|
|
2034
|
+
},
|
|
2035
|
+
} as never,
|
|
2036
|
+
undefined,
|
|
2037
|
+
undefined,
|
|
2038
|
+
{} as never,
|
|
2039
|
+
);
|
|
2040
|
+
assert.deepEqual(Object.keys(request().questions), [
|
|
2041
|
+
"which_date_library",
|
|
2042
|
+
]);
|
|
2043
|
+
assert.equal(request().questions.which_date_library.type, "choice");
|
|
2044
|
+
});
|
|
2045
|
+
|
|
2046
|
+
it("does not mistake a question for a nested map", async () => {
|
|
2047
|
+
const { tool, request } = recordingTool();
|
|
2048
|
+
await tool.execute(
|
|
2049
|
+
"id-not-nested",
|
|
2050
|
+
{
|
|
2051
|
+
state: "hi",
|
|
2052
|
+
questions: {
|
|
2053
|
+
pick: {
|
|
2054
|
+
type: "choice",
|
|
2055
|
+
instructions: "Which one?",
|
|
2056
|
+
criteria: { a: null, b: null },
|
|
2057
|
+
},
|
|
2058
|
+
},
|
|
2059
|
+
} as never,
|
|
2060
|
+
undefined,
|
|
2061
|
+
undefined,
|
|
2062
|
+
{} as never,
|
|
2063
|
+
);
|
|
2064
|
+
assert.deepEqual(Object.keys(request().questions), ["pick"]);
|
|
2065
|
+
});
|
|
2066
|
+
|
|
2067
|
+
it("takes description as the instructions when there are none", async () => {
|
|
2068
|
+
const { tool, request } = recordingTool();
|
|
2069
|
+
await tool.execute(
|
|
2070
|
+
"id-description-instructions",
|
|
2071
|
+
{
|
|
2072
|
+
state: "hi",
|
|
2073
|
+
questions: {
|
|
2074
|
+
q: {
|
|
2075
|
+
type: "score",
|
|
2076
|
+
description: "Rate the documentation quality.",
|
|
2077
|
+
criteria: ["low", "high"],
|
|
2078
|
+
},
|
|
2079
|
+
},
|
|
2080
|
+
} as never,
|
|
2081
|
+
undefined,
|
|
2082
|
+
undefined,
|
|
2083
|
+
{} as never,
|
|
2084
|
+
);
|
|
2085
|
+
assert.equal(
|
|
2086
|
+
request().questions.q.instructions,
|
|
2087
|
+
"Rate the documentation quality.",
|
|
2088
|
+
);
|
|
2089
|
+
});
|
|
2090
|
+
|
|
2091
|
+
it("keeps real instructions ahead of a description", async () => {
|
|
2092
|
+
const { tool, request } = recordingTool();
|
|
2093
|
+
await tool.execute(
|
|
2094
|
+
"id-description-loser",
|
|
2095
|
+
{
|
|
2096
|
+
state: "hi",
|
|
2097
|
+
questions: {
|
|
2098
|
+
q: {
|
|
2099
|
+
type: "noul",
|
|
2100
|
+
instructions: "Is it?",
|
|
2101
|
+
description: "some metadata",
|
|
2102
|
+
},
|
|
2103
|
+
},
|
|
2104
|
+
} as never,
|
|
2105
|
+
undefined,
|
|
2106
|
+
undefined,
|
|
2107
|
+
{} as never,
|
|
2108
|
+
);
|
|
2109
|
+
assert.deepEqual(request().questions.q, {
|
|
2110
|
+
type: "noul",
|
|
2111
|
+
instructions: "Is it?",
|
|
2112
|
+
});
|
|
2113
|
+
});
|
|
2114
|
+
|
|
2115
|
+
it("splices a question map nested more than one level deep", async () => {
|
|
2116
|
+
const { tool, request } = recordingTool();
|
|
2117
|
+
await tool.execute(
|
|
2118
|
+
"id-nested-two-deep",
|
|
2119
|
+
{
|
|
2120
|
+
state: "hi",
|
|
2121
|
+
questions: {
|
|
2122
|
+
no_audit: {
|
|
2123
|
+
no_audit: {
|
|
2124
|
+
q1: {
|
|
2125
|
+
type: "choice",
|
|
2126
|
+
instructions: "Which action?",
|
|
2127
|
+
criteria: { keep: null, replace: null },
|
|
2128
|
+
},
|
|
2129
|
+
},
|
|
2130
|
+
},
|
|
2131
|
+
},
|
|
2132
|
+
} as never,
|
|
2133
|
+
undefined,
|
|
2134
|
+
undefined,
|
|
2135
|
+
{} as never,
|
|
2136
|
+
);
|
|
2137
|
+
assert.deepEqual(Object.keys(request().questions), ["q1"]);
|
|
2138
|
+
assert.equal(request().questions.q1.type, "choice");
|
|
2139
|
+
});
|
|
2140
|
+
|
|
2141
|
+
it("lifts a state nested inside a question to the top level", async () => {
|
|
2142
|
+
const { tool, request } = recordingTool();
|
|
2143
|
+
await tool.execute(
|
|
2144
|
+
"id-lift-state",
|
|
2145
|
+
{
|
|
2146
|
+
questions: [
|
|
2147
|
+
{
|
|
2148
|
+
type: "score",
|
|
2149
|
+
instructions: "How clear?",
|
|
2150
|
+
criteria: ["low", "high"],
|
|
2151
|
+
state: { readme: "## Usage\nRun it." },
|
|
2152
|
+
},
|
|
2153
|
+
],
|
|
2154
|
+
} as never,
|
|
2155
|
+
undefined,
|
|
2156
|
+
undefined,
|
|
2157
|
+
{} as never,
|
|
2158
|
+
);
|
|
2159
|
+
assert.deepEqual(request().state, { readme: "## Usage\nRun it." });
|
|
2160
|
+
assert.equal(
|
|
2161
|
+
Object.hasOwn(request().questions.q1, "state"),
|
|
2162
|
+
false,
|
|
2163
|
+
"the state must be removed from the question it was found in",
|
|
2164
|
+
);
|
|
2165
|
+
});
|
|
2166
|
+
|
|
2167
|
+
it("lifts context and evidence aliases too", async () => {
|
|
2168
|
+
const { tool, request } = recordingTool();
|
|
2169
|
+
await tool.execute(
|
|
2170
|
+
"id-lift-evidence",
|
|
2171
|
+
{
|
|
2172
|
+
questions: {
|
|
2173
|
+
q: {
|
|
2174
|
+
type: "noul",
|
|
2175
|
+
instructions: "Is it?",
|
|
2176
|
+
evidence: { signal: "weak" },
|
|
2177
|
+
},
|
|
2178
|
+
},
|
|
2179
|
+
} as never,
|
|
2180
|
+
undefined,
|
|
2181
|
+
undefined,
|
|
2182
|
+
{} as never,
|
|
2183
|
+
);
|
|
2184
|
+
assert.deepEqual(request().state, { signal: "weak" });
|
|
2185
|
+
});
|
|
2186
|
+
|
|
2187
|
+
it("keeps a top-level state ahead of a nested one", async () => {
|
|
2188
|
+
const { tool, request } = recordingTool();
|
|
2189
|
+
await tool.execute(
|
|
2190
|
+
"id-lift-state-priority",
|
|
2191
|
+
{
|
|
2192
|
+
state: "the real state",
|
|
2193
|
+
questions: {
|
|
2194
|
+
q: { type: "noul", instructions: "Is it?", state: "stray" },
|
|
2195
|
+
},
|
|
2196
|
+
} as never,
|
|
2197
|
+
undefined,
|
|
2198
|
+
undefined,
|
|
2199
|
+
{} as never,
|
|
2200
|
+
);
|
|
2201
|
+
assert.equal(request().state, "the real state");
|
|
2202
|
+
});
|
|
2203
|
+
|
|
2204
|
+
it("leaves a criteria key named state alone — that is an option label", async () => {
|
|
2205
|
+
const { tool, request } = recordingTool();
|
|
2206
|
+
await tool.execute(
|
|
2207
|
+
"id-criteria-state-label",
|
|
2208
|
+
{
|
|
2209
|
+
state: "s",
|
|
2210
|
+
questions: {
|
|
2211
|
+
q: {
|
|
2212
|
+
type: "choice",
|
|
2213
|
+
instructions: "Which level?",
|
|
2214
|
+
criteria: { state: null, federal: null },
|
|
2215
|
+
},
|
|
2216
|
+
},
|
|
2217
|
+
} as never,
|
|
2218
|
+
undefined,
|
|
2219
|
+
undefined,
|
|
2220
|
+
{} as never,
|
|
2221
|
+
);
|
|
2222
|
+
assert.deepEqual(Object.keys(request().questions.q.criteria), [
|
|
2223
|
+
"state",
|
|
2224
|
+
"federal",
|
|
2225
|
+
]);
|
|
2226
|
+
});
|
|
2227
|
+
|
|
2228
|
+
it("does not mutate its input when lifting", () => {
|
|
2229
|
+
const deepFreeze = (v: unknown): void => {
|
|
2230
|
+
if (typeof v === "object" && v !== null && !Object.isFrozen(v)) {
|
|
2231
|
+
Object.freeze(v);
|
|
2232
|
+
for (const k of Object.keys(v)) {
|
|
2233
|
+
deepFreeze((v as Record<string, unknown>)[k]);
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
};
|
|
2237
|
+
const input = {
|
|
2238
|
+
questions: {
|
|
2239
|
+
q: { type: "noul", instructions: "Is it?", state: "s" },
|
|
2240
|
+
},
|
|
2241
|
+
};
|
|
2242
|
+
deepFreeze(input);
|
|
2243
|
+
const out = prepareSystemOneArgs(input);
|
|
2244
|
+
assert.deepEqual(out, {
|
|
2245
|
+
state: "s",
|
|
2246
|
+
questions: { q: { type: "noul", instructions: "Is it?" } },
|
|
2247
|
+
});
|
|
2248
|
+
});
|
|
2249
|
+
|
|
2250
|
+
it("strips a nested state even when a top-level one is supplied", async () => {
|
|
2251
|
+
// Regression: the lift used to early-return when a real top-level state
|
|
2252
|
+
// existed, leaving `state` inside the question where the provider could
|
|
2253
|
+
// see it. A question must never carry what it is judged against.
|
|
2254
|
+
const { tool, request } = recordingTool();
|
|
2255
|
+
await tool.execute(
|
|
2256
|
+
"id-lift-strip-always",
|
|
2257
|
+
{
|
|
2258
|
+
state: "the real state",
|
|
2259
|
+
questions: {
|
|
2260
|
+
q: { type: "noul", instructions: "Is it?", state: { stray: 1 } },
|
|
2261
|
+
},
|
|
2262
|
+
} as never,
|
|
2263
|
+
undefined,
|
|
2264
|
+
undefined,
|
|
2265
|
+
{} as never,
|
|
2266
|
+
);
|
|
2267
|
+
assert.equal(request().state, "the real state");
|
|
2268
|
+
assert.deepEqual(request().questions.q, {
|
|
2269
|
+
type: "noul",
|
|
2270
|
+
instructions: "Is it?",
|
|
2271
|
+
});
|
|
2272
|
+
});
|
|
2273
|
+
|
|
2274
|
+
it("strips every state alias in one pass (idempotent)", async () => {
|
|
2275
|
+
const freeze = (v: unknown): void => {
|
|
2276
|
+
if (typeof v === "object" && v !== null && !Object.isFrozen(v)) {
|
|
2277
|
+
Object.freeze(v);
|
|
2278
|
+
for (const k of Object.keys(v)) {
|
|
2279
|
+
freeze((v as Record<string, unknown>)[k]);
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
};
|
|
2283
|
+
const input = {
|
|
2284
|
+
questions: {
|
|
2285
|
+
q: {
|
|
2286
|
+
type: "noul",
|
|
2287
|
+
instructions: "Is it?",
|
|
2288
|
+
state: { a: 1 },
|
|
2289
|
+
context: { b: 2 },
|
|
2290
|
+
evidence: { c: 3 },
|
|
2291
|
+
},
|
|
2292
|
+
},
|
|
2293
|
+
};
|
|
2294
|
+
freeze(input);
|
|
2295
|
+
const once = prepareSystemOneArgs(input);
|
|
2296
|
+
const twice = prepareSystemOneArgs(once);
|
|
2297
|
+
assert.deepEqual(twice, once);
|
|
2298
|
+
assert.deepEqual(once, {
|
|
2299
|
+
state: { a: 1 },
|
|
2300
|
+
questions: { q: { type: "noul", instructions: "Is it?" } },
|
|
2301
|
+
});
|
|
2302
|
+
});
|
|
2303
|
+
|
|
2304
|
+
it("drops true/false from a question that is not a noul", async () => {
|
|
2305
|
+
// Regression: they are lifted into `criteria` on a noul question and
|
|
2306
|
+
// mean nothing elsewhere, but the copy-on-write fast path used to ship
|
|
2307
|
+
// them to the provider on a choice.
|
|
2308
|
+
const { tool, request } = recordingTool();
|
|
2309
|
+
await tool.execute(
|
|
2310
|
+
"id-true-false-choice",
|
|
2311
|
+
{
|
|
2312
|
+
state: "hi",
|
|
2313
|
+
questions: {
|
|
2314
|
+
q: {
|
|
2315
|
+
type: "choice",
|
|
2316
|
+
instructions: "Which?",
|
|
2317
|
+
criteria: { a: null },
|
|
2318
|
+
true: "yes",
|
|
2319
|
+
false: "no",
|
|
2320
|
+
},
|
|
2321
|
+
},
|
|
2322
|
+
} as never,
|
|
2323
|
+
undefined,
|
|
2324
|
+
undefined,
|
|
2325
|
+
{} as never,
|
|
2326
|
+
);
|
|
2327
|
+
assert.deepEqual(request().questions.q, {
|
|
2328
|
+
type: "choice",
|
|
2329
|
+
instructions: "Which?",
|
|
2330
|
+
criteria: { a: null },
|
|
2331
|
+
});
|
|
2332
|
+
});
|
|
2333
|
+
|
|
2334
|
+
it("stays idempotent when an array mixes junk with questions", () => {
|
|
2335
|
+
// Regression: the array branch kept non-question entries while the
|
|
2336
|
+
// record branch dropped them, so `["junk", {a question}]` normalized to
|
|
2337
|
+
// different maps on the first and second pass.
|
|
2338
|
+
const input = {
|
|
2339
|
+
state: "S",
|
|
2340
|
+
questions: ["junk", { type: "noul", instructions: "Is it?" }, "junk"],
|
|
2341
|
+
};
|
|
2342
|
+
assert.deepEqual(prepareSystemOneArgs(input), {
|
|
2343
|
+
state: "S",
|
|
2344
|
+
questions: { q2: { type: "noul", instructions: "Is it?" } },
|
|
2345
|
+
});
|
|
2346
|
+
const once = prepareSystemOneArgs(input);
|
|
2347
|
+
assert.deepEqual(prepareSystemOneArgs(once), once);
|
|
2348
|
+
});
|
|
2349
|
+
|
|
2350
|
+
it("does not unwrap an array of non-questions", () => {
|
|
2351
|
+
// Regression: `{q1: [...]}` is a question map whose one entry is not a
|
|
2352
|
+
// question. Unwrapping it renamed the junk on every pass.
|
|
2353
|
+
const input = { state: "S", questions: { q1: ["text", "text"] } };
|
|
2354
|
+
const once = prepareSystemOneArgs(input);
|
|
2355
|
+
assert.deepEqual(prepareSystemOneArgs(once), once);
|
|
2356
|
+
assert.deepEqual(once, {
|
|
2357
|
+
state: "S",
|
|
2358
|
+
questions: { q1: ["text", "text"] },
|
|
2359
|
+
});
|
|
2360
|
+
});
|
|
2361
|
+
|
|
2362
|
+
it("rejects score questions with a one-element array rubric", async () => {
|
|
2363
|
+
const { tool, calls } = stubTool();
|
|
2364
|
+
await assert.rejects(
|
|
2365
|
+
tool.execute(
|
|
2366
|
+
"id-min-8",
|
|
2367
|
+
{
|
|
2368
|
+
state: "hi",
|
|
2369
|
+
questions: {
|
|
2370
|
+
s: {
|
|
2371
|
+
type: "score",
|
|
2372
|
+
instructions: "How?",
|
|
2373
|
+
criteria: ["lonely"],
|
|
2374
|
+
},
|
|
2375
|
+
},
|
|
2376
|
+
} as never,
|
|
2377
|
+
undefined,
|
|
2378
|
+
undefined,
|
|
2379
|
+
{} as never,
|
|
2380
|
+
),
|
|
2381
|
+
/score question "s" needs criteria as an ordered array/,
|
|
2382
|
+
);
|
|
2383
|
+
assert.equal(calls(), 0);
|
|
2384
|
+
});
|
|
2385
|
+
|
|
2386
|
+
it("rejects choice questions with non-string array criteria", async () => {
|
|
2387
|
+
const { tool, calls } = stubTool();
|
|
2388
|
+
await assert.rejects(
|
|
2389
|
+
tool.execute(
|
|
2390
|
+
"id-min-9",
|
|
2391
|
+
{
|
|
2392
|
+
state: "hi",
|
|
2393
|
+
questions: {
|
|
2394
|
+
t: { type: "choice", instructions: "W?", criteria: [1, 2] },
|
|
2395
|
+
},
|
|
2396
|
+
} as never,
|
|
2397
|
+
undefined,
|
|
2398
|
+
undefined,
|
|
2399
|
+
{} as never,
|
|
2400
|
+
),
|
|
2401
|
+
/choice question "t" needs criteria as an object/,
|
|
2402
|
+
);
|
|
2403
|
+
assert.equal(calls(), 0);
|
|
2404
|
+
});
|
|
2405
|
+
|
|
2406
|
+
it("lets boundary-valid questions through (1 choice, 2 levels)", async () => {
|
|
2407
|
+
const { tool, calls } = stubTool();
|
|
2408
|
+
await tool.execute(
|
|
2409
|
+
"id-min-5",
|
|
2410
|
+
{
|
|
2411
|
+
state: "hi",
|
|
2412
|
+
questions: {
|
|
2413
|
+
c: { type: "choice", instructions: "W?", criteria: { a: null } },
|
|
2414
|
+
s: {
|
|
2415
|
+
type: "score",
|
|
2416
|
+
instructions: "How?",
|
|
2417
|
+
criteria: ["low", "high"],
|
|
2418
|
+
},
|
|
2419
|
+
n: { type: "noul", instructions: "Is it?" },
|
|
2420
|
+
},
|
|
2421
|
+
} as never,
|
|
2422
|
+
undefined,
|
|
2423
|
+
undefined,
|
|
2424
|
+
{} as never,
|
|
2425
|
+
);
|
|
2426
|
+
assert.equal(calls(), 1);
|
|
2427
|
+
});
|
|
2428
|
+
});
|
|
2429
|
+
});
|
|
2430
|
+
|
|
2431
|
+
describe("discarded question reporting", () => {
|
|
2432
|
+
function toolWithAnswers(answers: Record<string, unknown>) {
|
|
2433
|
+
const stub = {
|
|
2434
|
+
id: "stub",
|
|
2435
|
+
async evaluate() {
|
|
2436
|
+
return { answers, metadata: { provider: "stub" } };
|
|
2437
|
+
},
|
|
2438
|
+
};
|
|
2439
|
+
return buildSystemOneTool({ provider: stub as never });
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
async function renderOf(args: unknown): Promise<string> {
|
|
2443
|
+
const tool = toolWithAnswers({
|
|
2444
|
+
kept: { type: "noul", noul: 0.7 },
|
|
2445
|
+
});
|
|
2446
|
+
const result = await tool.execute(
|
|
2447
|
+
"id",
|
|
2448
|
+
args as never,
|
|
2449
|
+
undefined,
|
|
2450
|
+
undefined,
|
|
2451
|
+
{} as never,
|
|
2452
|
+
);
|
|
2453
|
+
return (result.content as Array<{ text: string }>)[0].text;
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
// A real defect the scenario suite could not see: the model sends a
|
|
2457
|
+
// two-question batch, one of which is filed under the wrapper's own name.
|
|
2458
|
+
// The unwrap returned that question and dropped its sibling silently, so
|
|
2459
|
+
// the model received a confident answer to half of what it asked.
|
|
2460
|
+
it("reports a question abandoned by the wrapper-alias unwrap", async () => {
|
|
2461
|
+
const text = await renderOf({
|
|
2462
|
+
state: "an outage and a refund",
|
|
2463
|
+
questions: {
|
|
2464
|
+
questions: {
|
|
2465
|
+
type: "choice",
|
|
2466
|
+
instructions: "Which?",
|
|
2467
|
+
criteria: { a: null },
|
|
2468
|
+
},
|
|
2469
|
+
risk: { type: "noul", instructions: "Is there a risk?" },
|
|
2470
|
+
},
|
|
2471
|
+
});
|
|
2472
|
+
assert.match(text, /NOTE:/);
|
|
2473
|
+
assert.match(text, /discarded/);
|
|
2474
|
+
assert.match(text, /risk/);
|
|
2475
|
+
// The kept answer is still complete and still first.
|
|
2476
|
+
assert.match(text, /kept:[\s\S]*noul: 0\.7/);
|
|
2477
|
+
});
|
|
2478
|
+
|
|
2479
|
+
it("reports a name-slot entry dropped beside a real question", async () => {
|
|
2480
|
+
const text = await renderOf({
|
|
2481
|
+
state: "S",
|
|
2482
|
+
questions: {
|
|
2483
|
+
q1: { type: "noul", instructions: "Is it?" },
|
|
2484
|
+
stray_label: "refund it",
|
|
2485
|
+
},
|
|
2486
|
+
});
|
|
2487
|
+
assert.match(text, /NOTE:/);
|
|
2488
|
+
assert.match(text, /stray_label/);
|
|
2489
|
+
});
|
|
2490
|
+
|
|
2491
|
+
it("adds no note when nothing was discarded", async () => {
|
|
2492
|
+
const text = await renderOf({
|
|
2493
|
+
state: "S",
|
|
2494
|
+
questions: { q1: { type: "noul", instructions: "Is it?" } },
|
|
1295
2495
|
});
|
|
2496
|
+
assert.doesNotMatch(text, /NOTE:/);
|
|
1296
2497
|
});
|
|
1297
2498
|
});
|