@afokapu/atdd-bun 0.10.14 → 0.10.16
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.
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
//
|
|
19
19
|
// CONTRACT (atdd.workspace.bun v1.1): reads ATDD_SCAN_ROOTS, writes RAW
|
|
20
20
|
// {rule_id,file,line,col,evidence,source_line} violations to ATDD_VIOLATIONS_REPORT, exits 0.
|
|
21
|
-
// Zero third-party deps
|
|
21
|
+
// Zero third-party deps: Node and Bun builtins only. The plan/_trains interlocking YAML route space is
|
|
22
22
|
// stack-neutral planner data (snake_case, core #1248); the runtime + JOURNEY_MAP + trace are Bun/TS.
|
|
23
23
|
|
|
24
|
+
import { parseInterlocking as parseInterlockingDoc } from "../../lib/interlocking.mjs";
|
|
24
25
|
import { readFileSync, writeFileSync, statSync, readdirSync } from "node:fs";
|
|
25
26
|
import { scanExecution } from "./checks/interlocking_runtime_executes.mjs";
|
|
26
27
|
import { join, sep, resolve } from "node:path";
|
|
@@ -233,68 +234,10 @@ function rel(path, root) {
|
|
|
233
234
|
// ── interlocking YAML parse (CONSUMES core #1248 fields; snake_case planner data) ─────────────
|
|
234
235
|
|
|
235
236
|
function parseInterlocking(text) {
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
if (!idM) return null;
|
|
239
|
-
const interlockingId = idM[1].trim();
|
|
240
|
-
|
|
241
|
-
const routes = [];
|
|
242
|
-
const ri = lines.findIndex((l) => /^routes:\s*(?:#.*)?$/.test(l));
|
|
243
|
-
if (ri >= 0) {
|
|
244
|
-
for (let i = ri + 1; i < lines.length; i++) {
|
|
245
|
-
const l = lines[i];
|
|
246
|
-
if (/^\S/.test(l)) break; // dedent to a new top-level key ends the routes block
|
|
247
|
-
const rm = l.match(/^\s*-\s*route_id:\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/);
|
|
248
|
-
if (rm) {
|
|
249
|
-
routes.push({ routeId: rm[1].trim(), line: i + 1, sourceLine: l, trainId: null, trainPath: null });
|
|
250
|
-
} else if (routes.length) {
|
|
251
|
-
const cur = routes[routes.length - 1];
|
|
252
|
-
const tm = l.match(/^\s*train_id:\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/);
|
|
253
|
-
if (tm) cur.trainId = tm[1].trim();
|
|
254
|
-
const pm = l.match(/^\s*train_path:\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/);
|
|
255
|
-
if (pm) cur.trainPath = pm[1].trim();
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
if (!routes.length) return null;
|
|
260
|
-
|
|
261
|
-
let exposed = false;
|
|
262
|
-
const actions = [];
|
|
263
|
-
const ei = lines.findIndex((l) => /^entrypoint:\s*(?:#.*)?$/.test(l));
|
|
264
|
-
if (ei >= 0) {
|
|
265
|
-
for (let i = ei + 1; i < lines.length; i++) {
|
|
266
|
-
const l = lines[i];
|
|
267
|
-
if (/^\S/.test(l)) break; // dedent ends the entrypoint block
|
|
268
|
-
if (/^\s*exposed:\s*true\b/.test(l)) exposed = true;
|
|
269
|
-
// FLOW STYLE TOO: `actions: [resolve_match, other]`.
|
|
270
|
-
//
|
|
271
|
-
// The interlocking YAML is stack-neutral planner data and flow sequences are
|
|
272
|
-
// ordinary YAML. Python reads it with a real parser and accepts both; this
|
|
273
|
-
// hand-rolled line scanner accepted only block style, so the SAME plan file
|
|
274
|
-
// was judged differently depending on which stack read it — and the verdict
|
|
275
|
-
// was "exposed interlocking is not Station-Master-reachable", a strict
|
|
276
|
-
// blocking finding, against a consumer whose YAML was perfectly valid.
|
|
277
|
-
// Found by running this provider against a Bun consumer written from scratch.
|
|
278
|
-
const flow = l.match(/^\s*actions:\s*\[([^\]]*)\]\s*(?:#.*)?$/);
|
|
279
|
-
if (flow) {
|
|
280
|
-
for (const raw of flow[1].split(",")) {
|
|
281
|
-
const a = raw.trim().replace(/^["']|["']$/g, "");
|
|
282
|
-
if (a) actions.push(a);
|
|
283
|
-
}
|
|
284
|
-
continue;
|
|
285
|
-
}
|
|
286
|
-
if (/^\s*actions:\s*(?:#.*)?$/.test(l)) {
|
|
287
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
288
|
-
const am = lines[j].match(/^\s*-\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/);
|
|
289
|
-
if (am) actions.push(am[1].trim());
|
|
290
|
-
else break;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
237
|
+
const rec = parseInterlockingDoc(text);
|
|
238
|
+
if (!rec) return null;
|
|
296
239
|
const parallelFields = PARALLEL_FIELDS.filter((f) => new RegExp("^\\s*" + f + "\\s*:", "m").test(text));
|
|
297
|
-
return {
|
|
240
|
+
return { ...rec, parallelFields };
|
|
298
241
|
}
|
|
299
242
|
|
|
300
243
|
// ── Station Master JOURNEY_MAP parse (server.ts) ─────────────────────────
|
|
@@ -270,78 +270,9 @@ export function e2eFiles(croot) {
|
|
|
270
270
|
return [...walkFiles(join(croot, "e2e"), isTs)].sort();
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
// Interlocking YAML parse
|
|
274
|
-
//
|
|
275
|
-
export
|
|
276
|
-
const lines = text.split(/\r?\n/);
|
|
277
|
-
const idM = text.match(/^interlocking_id:\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/m);
|
|
278
|
-
if (!idM) return null;
|
|
279
|
-
const interlockingId = idM[1].trim();
|
|
280
|
-
|
|
281
|
-
const routes = [];
|
|
282
|
-
const ri = lines.findIndex((l) => /^routes:\s*(?:#.*)?$/.test(l));
|
|
283
|
-
if (ri >= 0) {
|
|
284
|
-
for (let i = ri + 1; i < lines.length; i++) {
|
|
285
|
-
const l = lines[i];
|
|
286
|
-
if (/^\S/.test(l)) break;
|
|
287
|
-
const rm = l.match(/^\s*-\s*route_id:\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/);
|
|
288
|
-
if (rm) {
|
|
289
|
-
routes.push({
|
|
290
|
-
routeId: rm[1].trim(),
|
|
291
|
-
line: i + 1,
|
|
292
|
-
sourceLine: l,
|
|
293
|
-
trainId: null,
|
|
294
|
-
category: null,
|
|
295
|
-
});
|
|
296
|
-
} else if (routes.length) {
|
|
297
|
-
const cur = routes[routes.length - 1];
|
|
298
|
-
const tm = l.match(/^\s*train_id:\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/);
|
|
299
|
-
if (tm) cur.trainId = tm[1].trim();
|
|
300
|
-
// `category:` only — `category_digit:` is retired (#1421 / #1440) and rejected by
|
|
301
|
-
// core's schema, so there is nothing left to read off a conforming artifact.
|
|
302
|
-
const cm = l.match(/^\s*category:\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/);
|
|
303
|
-
if (cm) cur.category = cm[1].trim();
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
if (!routes.length) return null;
|
|
308
|
-
|
|
309
|
-
let exposed = false;
|
|
310
|
-
const actions = [];
|
|
311
|
-
const ei = lines.findIndex((l) => /^entrypoint:\s*(?:#.*)?$/.test(l));
|
|
312
|
-
if (ei >= 0) {
|
|
313
|
-
for (let i = ei + 1; i < lines.length; i++) {
|
|
314
|
-
const l = lines[i];
|
|
315
|
-
if (/^\S/.test(l)) break;
|
|
316
|
-
if (/^\s*exposed:\s*true\b/.test(l)) exposed = true;
|
|
317
|
-
// FLOW STYLE TOO: `actions: [resolve_match, other]`.
|
|
318
|
-
//
|
|
319
|
-
// The interlocking YAML is stack-neutral planner data and flow sequences are
|
|
320
|
-
// ordinary YAML. Python reads it with a real parser and accepts both; this
|
|
321
|
-
// hand-rolled line scanner accepted only block style, so the SAME plan file
|
|
322
|
-
// was judged differently depending on which stack read it — and the verdict
|
|
323
|
-
// was "exposed interlocking is not Station-Master-reachable", a strict
|
|
324
|
-
// blocking finding, against a consumer whose YAML was perfectly valid.
|
|
325
|
-
// Found by running this provider against a Bun consumer written from scratch.
|
|
326
|
-
const flow = l.match(/^\s*actions:\s*\[([^\]]*)\]\s*(?:#.*)?$/);
|
|
327
|
-
if (flow) {
|
|
328
|
-
for (const raw of flow[1].split(",")) {
|
|
329
|
-
const a = raw.trim().replace(/^["']|["']$/g, "");
|
|
330
|
-
if (a) actions.push(a);
|
|
331
|
-
}
|
|
332
|
-
continue;
|
|
333
|
-
}
|
|
334
|
-
if (/^\s*actions:\s*(?:#.*)?$/.test(l)) {
|
|
335
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
336
|
-
const am = lines[j].match(/^\s*-\s*["']?([^"'#\n]+?)["']?\s*(?:#.*)?$/);
|
|
337
|
-
if (am) actions.push(am[1].trim());
|
|
338
|
-
else break;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
return { interlockingId, routes, exposed, actions, rawText: text };
|
|
344
|
-
}
|
|
273
|
+
// Interlocking YAML parse: interlocking_id + a non-empty routes list (core #1248, snake_case planner data).
|
|
274
|
+
// Registry/projection docs with no routes yield null. One parser for every detector, in lib/.
|
|
275
|
+
export { parseInterlocking } from "../../../lib/interlocking.mjs";
|
|
345
276
|
|
|
346
277
|
export function lineOf(text, pattern, def = 1) {
|
|
347
278
|
const lines = text.split(/\r?\n/);
|
package/integrity.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.10.
|
|
2
|
+
"version": "0.10.16",
|
|
3
3
|
"files": {
|
|
4
4
|
"HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
|
|
5
5
|
"PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
|
|
@@ -329,7 +329,7 @@
|
|
|
329
329
|
"detectors/bun_green_traceability_detector/urn_header.mjs": "004b0ed1f71ec476fb57a959d302da4b5869ff91be049865a806bedca40f55b9",
|
|
330
330
|
"detectors/bun_interlocking_binding/atdd.implementation.yaml": "8df233a7c337d38ab4fc92a309273b91fe713b27507a4b2cfa66ffc7a948d520",
|
|
331
331
|
"detectors/bun_interlocking_binding/checks/interlocking_runtime_executes.mjs": "ea4c32914a007962012e9062cc64063fa939ff0a59c07960c34872d058720955",
|
|
332
|
-
"detectors/bun_interlocking_binding/detect.mjs": "
|
|
332
|
+
"detectors/bun_interlocking_binding/detect.mjs": "ec48b90b14e9c8a4e15bb50cf196af2fc673532f449e884e3c454dc57a2b06b1",
|
|
333
333
|
"detectors/bun_interlocking_binding/fixtures/clean/e2e/interlockings/match-resolution/trace.test.ts": "5dadc041ca9825f404340ca428e14bbff5e00824ca6c25464f21cd2ce56044d8",
|
|
334
334
|
"detectors/bun_interlocking_binding/fixtures/clean/plan/_trains/_interlockings/match-resolution.yaml": "11884309beb9bcceccfeda7917ea73d51a530366db5eb60772b03cf5eeb239cd",
|
|
335
335
|
"detectors/bun_interlocking_binding/fixtures/clean/plan/_trains/train:match:match-resolution-standard.yaml": "a7067661a723e02e0e844e6b90ad187f620dd328e1b9be45163c04db1919ce77",
|
|
@@ -379,7 +379,7 @@
|
|
|
379
379
|
"detectors/bun_interlocking_binding/fixtures/dirty_transcribed_runtime/server.ts": "d5bc34c5b1b2b0a511e0c4604ce6f9c77491dca9aee994be720540f3052babc1",
|
|
380
380
|
"detectors/bun_interlocking_binding/fixtures/dirty_transcribed_runtime/src/trains/interlocking.ts": "9bcfdb4ecff6efd1f658b9fd587342615179924e25468c75daa07c2626d4d8ab",
|
|
381
381
|
"detectors/bun_interlocking_binding/fixtures/dirty_transcribed_runtime/src/trains/runner.ts": "0e4b885e50f12cc2a8c8f6740bf83e7b2e7137a28e1e143586e9a8550ea5bd45",
|
|
382
|
-
"detectors/bun_interlocking_coverage/_shared/interlocking.mjs": "
|
|
382
|
+
"detectors/bun_interlocking_coverage/_shared/interlocking.mjs": "6a3440e53ba13948edfbb1ef8de4c01e4fd022ad6f9d44739f814220cc583417",
|
|
383
383
|
"detectors/bun_interlocking_coverage/atdd.implementation.yaml": "f160517a8138ba3ca05e3d130e6fbfb796e643376f25743fdef4fc210196525b",
|
|
384
384
|
"detectors/bun_interlocking_coverage/checks/_map.json": "0a218ea319cc6258af7bd721c2154cb9561fe67e80d80ef32d08fc77bc9403c3",
|
|
385
385
|
"detectors/bun_interlocking_coverage/checks/interlocking_production_runner.mjs": "136eb0955052f2485740adfb888f5ba6f1414dcdb53d505034b8db40420a611a",
|
|
@@ -968,6 +968,7 @@
|
|
|
968
968
|
"detectors/planner_telemetry_plan/fixtures/dirty/telemetry/commons/response-invocation-accepted/notes.json": "ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356",
|
|
969
969
|
"lib/frontend.mjs": "d6d9b29c3d7f2d162cda03bb34b20a3224a82887b6be3db7cab375510913c9a3",
|
|
970
970
|
"lib/imports.mjs": "3db4979269488fa26f6e7aefded6ece9c646be6587a10e249821a5a208109962",
|
|
971
|
+
"lib/interlocking.mjs": "d0803d080ac7d7477c51be70112df1e85213d339fd3c6b71f75f26145fa8accd",
|
|
971
972
|
"lib/scan.mjs": "8aa21e400ec415e23d49e0cc90fba2744822b8855261c85614e89c7cf35c4700",
|
|
972
973
|
"planner-nodes/ENFORCEMENT_SCOPE.yaml": "d8ec7039c219857f3d77af5d4d84612b533b69c1a5fcef935fbf46470adafed4",
|
|
973
974
|
"planner-nodes/nodes/planner.acceptance.abstract-fields-required.convention.yaml": "bd565693f0ae861f0561ad2f7a91a8c55f9a209c3acd59c5750cfcffd96a85d8",
|
|
@@ -1196,7 +1197,7 @@
|
|
|
1196
1197
|
"src/board.ts": "a3a8efe923858b9b5cf5ca3682dacfceb5557f8303d818687eb1325a15fa1782",
|
|
1197
1198
|
"src/ci.ts": "67e54d2cbfc44a9e42837d5e750af526d6255c539d3c452a4d5fca3226c0d9ac",
|
|
1198
1199
|
"src/cli.ts": "e45f2666334503a91eb65b1b5ee4dd8acab6b99f62d333e2ee2b65963db6358b",
|
|
1199
|
-
"src/delivery.ts": "
|
|
1200
|
+
"src/delivery.ts": "2765868a597261a38b44bb6490da4c17e059c7ef0cc45323ed8475085db46ffe",
|
|
1200
1201
|
"src/docs-capability.ts": "115cf27049a5cf19c133bac5ec237072be6f969a59a2ef6767e054108a21dc5c",
|
|
1201
1202
|
"src/enforce.ts": "41a6f8c058a2a034996c817f224191403cc66ccc4ed4e04bbefc430014c783d8",
|
|
1202
1203
|
"src/hooks.ts": "2feeec2674c1462c94256de02ce59ccc6e3ff671ec4dca1bf9903c32888b600b",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// The interlocking document (plan/_trains/_interlockings/<id>.yaml), read with a YAML parser. Detectors once scanned
|
|
2
|
+
// it line by line and stopped at the first line in column 0 after `routes:`, so a compact list (`routes:` then
|
|
3
|
+
// `- route_id:` at column 0, the same YAML) parsed to no routes and every route rule passed without judging anything
|
|
4
|
+
// (FWS, atdd-maintainer #G3aXIPPW1Mz5). Line numbers, which findings cite, are looked up in the text afterwards.
|
|
5
|
+
|
|
6
|
+
const scalar = (value) => (typeof value === "string" || typeof value === "number" ? String(value).trim() : null);
|
|
7
|
+
const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8
|
+
|
|
9
|
+
/** { interlockingId, routes: [{ routeId, line, sourceLine, trainId, trainPath, category }], exposed, actions, rawText },
|
|
10
|
+
* or null for a document with no interlocking_id or no routes (registries and projections). */
|
|
11
|
+
export function parseInterlocking(text) {
|
|
12
|
+
let doc;
|
|
13
|
+
try { doc = Bun.YAML.parse(text); } catch { return null; }
|
|
14
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) return null;
|
|
15
|
+
const interlockingId = scalar(doc.interlocking_id);
|
|
16
|
+
if (!interlockingId) return null;
|
|
17
|
+
const lines = text.split(/\r?\n/), taken = new Set();
|
|
18
|
+
const lineOf = (routeId) => {
|
|
19
|
+
const pattern = new RegExp(`^\\s*(?:-\\s*)?route_id:\\s*["']?${escape(routeId)}["']?\\s*(?:#.*)?$`);
|
|
20
|
+
const index = lines.findIndex((line, i) => !taken.has(i) && pattern.test(line));
|
|
21
|
+
if (index >= 0) taken.add(index);
|
|
22
|
+
return index;
|
|
23
|
+
};
|
|
24
|
+
const routes = (Array.isArray(doc.routes) ? doc.routes : []).flatMap((route) => {
|
|
25
|
+
const routeId = route && typeof route === "object" ? scalar(route.route_id) : null;
|
|
26
|
+
if (!routeId) return [];
|
|
27
|
+
const index = lineOf(routeId);
|
|
28
|
+
return [{ routeId, line: index >= 0 ? index + 1 : 1, sourceLine: index >= 0 ? lines[index] : "", trainId: scalar(route.train_id), trainPath: scalar(route.train_path), category: scalar(route.category) }];
|
|
29
|
+
});
|
|
30
|
+
if (!routes.length) return null;
|
|
31
|
+
const entrypoint = doc.entrypoint && typeof doc.entrypoint === "object" ? doc.entrypoint : {};
|
|
32
|
+
const actions = (Array.isArray(entrypoint.actions) ? entrypoint.actions : []).map(scalar).filter(Boolean);
|
|
33
|
+
return { interlockingId, routes, exposed: entrypoint.exposed === true, actions, rawText: text };
|
|
34
|
+
}
|
package/package.json
CHANGED
package/src/delivery.ts
CHANGED
|
@@ -234,14 +234,16 @@ export async function loadEvidence(root: string, policy: DeliveryPolicy): Promis
|
|
|
234
234
|
return out;
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
/** A model must come from the stage's list; a model after the first needs a recorded fallback from each one before it
|
|
238
|
-
|
|
237
|
+
/** A model must come from the stage's list; a model after the first needs a recorded fallback from each one before it,
|
|
238
|
+
* except one the stage could not use: under different-model independence a reviewer skips every model that wrote the
|
|
239
|
+
* work it reviews (delivery.reviewer-independent's fix hint). Ineligible is not unavailable, so no fallback records it. */
|
|
240
|
+
function checkModel(file: string, at: string, stage: Stage, role: "writer" | "reviewer", actor: Actor, list: string[], fallbacks: Fallback[], defaultRole: "writer" | "reviewer", fallback: DeliveryPolicy["fallback"], ineligible: string[] = []): PlanFinding[] {
|
|
239
241
|
const out: PlanFinding[] = [];
|
|
240
242
|
const index = list.indexOf(actor.model);
|
|
241
243
|
if (index < 0) return [finding("delivery.model-allowed", file, `${at}: ${role} model '${actor.model}' is not in ${stage}.${role} [${list.join(", ")}]`)];
|
|
242
244
|
// `author` is the 0.9 spelling of `writer`.
|
|
243
245
|
const recorded = fallbacks.filter(entry => ((entry.role === "author" ? "writer" : entry.role) ?? defaultRole) === role).map(entry => entry.from);
|
|
244
|
-
for (const skipped of list.slice(0, index)) if (!recorded.includes(skipped)) out.push(finding("delivery.model-allowed", file, `${at}: ${role} '${actor.model}' is a fallback, but no fallback from '${skipped}' records why it was unavailable`));
|
|
246
|
+
for (const skipped of list.slice(0, index)) if (!recorded.includes(skipped) && !ineligible.includes(skipped)) out.push(finding("delivery.model-allowed", file, `${at}: ${role} '${actor.model}' is a fallback, but no fallback from '${skipped}' records why it was unavailable`));
|
|
245
247
|
for (const from of recorded) if (!list.slice(0, index).includes(from)) out.push(finding("delivery.model-allowed", file, `${at}: ${role} fallback from '${from}' does not precede '${actor.model}' in [${list.join(", ")}]`));
|
|
246
248
|
return out;
|
|
247
249
|
}
|
|
@@ -305,10 +307,11 @@ function checkEvidence(file: string, evidence: Evidence, policy: DeliveryPolicy)
|
|
|
305
307
|
const at = `reviews[${index}] (${review.stage} @ ${review.sha})`, stage = policy.stages[review.stage];
|
|
306
308
|
if (!stage?.reviewer.length) { out.push(finding("delivery.evidence-schema", file, `${at}: ${review.stage} has no reviewer in delivery.stages`)); return; }
|
|
307
309
|
const fallbacks = review.fallback ?? [];
|
|
308
|
-
out.push(...checkFallbacks(file, at, fallbacks, policy.fallback), ...checkModel(file, at, review.stage, "reviewer", review.reviewer, stage.reviewer, fallbacks, "reviewer", policy.fallback));
|
|
309
|
-
if (review.author && stage.writer.length) out.push(...checkModel(file, at, review.stage, "writer", review.author, stage.writer, fallbacks, "reviewer", policy.fallback));
|
|
310
310
|
// Independence is judged against everyone who wrote what this review answers for.
|
|
311
311
|
const covered = coveredStages(review.stage, policy, work), writers = [...work.filter(entry => covered.includes(entry.stage)).map(entry => entry.writer), ...(review.author ? [review.author] : [])];
|
|
312
|
+
const ineligible = stage.independence === "different-model" ? writers.map(writer => writer.model) : [];
|
|
313
|
+
out.push(...checkFallbacks(file, at, fallbacks, policy.fallback), ...checkModel(file, at, review.stage, "reviewer", review.reviewer, stage.reviewer, fallbacks, "reviewer", policy.fallback, ineligible));
|
|
314
|
+
if (review.author && stage.writer.length) out.push(...checkModel(file, at, review.stage, "writer", review.author, stage.writer, fallbacks, "reviewer", policy.fallback));
|
|
312
315
|
if (!writers.length) out.push(finding("delivery.evidence-schema", file, `${at}: names no writer of the work it reviews (${covered.join(", ")}); record each stage's work, or the review's author, so independence can be judged`));
|
|
313
316
|
if (writerRuns.has(review.reviewer.run)) out.push(finding("delivery.reviewer-independent", file, `${at}: reviewer run '${review.reviewer.run}' also wrote in this tranche; a reviewer that edits becomes a writer`));
|
|
314
317
|
if (reviewerRuns.has(review.reviewer.run)) out.push(finding("delivery.reviewer-independent", file, `${at}: reviewer run '${review.reviewer.run}' already reviewed reviews[${reviewerRuns.get(review.reviewer.run)}]; every review is a fresh process`));
|