grooph 0.4.0 → 0.4.1
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/THIRD-PARTY-NOTICES.md +1 -1
- package/dist/app/assets/{App-CFO6bPna.js → App-48y1Ec5B.js} +4 -4
- package/dist/app/assets/{EmbedApp-B5LWao3x.js → EmbedApp-390Z2d4x.js} +1 -1
- package/dist/app/assets/{ExportPanel-VWus7GM3.js → ExportPanel-Ca3wBB38.js} +1 -1
- package/dist/app/assets/{Import-YEKofx4_.js → Import-ByMI7Uz9.js} +1 -1
- package/dist/app/assets/brakes-BJPDrrJT.js +1 -0
- package/dist/app/assets/{builtins-Cbs5iQtH.js → builtins-D_22fKRF.js} +1 -1
- package/dist/app/assets/{graph-more-BKW9C4-h.js → graph-more-393HTgQE.js} +1 -1
- package/dist/app/assets/{graph-stage-CWh3OWga.js → graph-stage-BkrQGQ-z.js} +1 -1
- package/dist/app/assets/{graph-views-tFUJK-be.js → graph-views-COQF_QTQ.js} +2 -2
- package/dist/app/assets/{index-BoSi6XYh.js → index-CeaKwKuU.js} +2 -2
- package/dist/app/assets/{screens-YUd2HaLm.js → screens-aIjMSzZg.js} +7 -7
- package/dist/app/assets/{templates-DvzLa5sE.js → templates-CoNTzuuz.js} +2 -2
- package/dist/app/assets/{units-DRaa5U2m.js → units-DOIUgOIU.js} +1 -1
- package/dist/app/assets/{views-DCEDX6Qx.js → views-CLwg5VNv.js} +1 -1
- package/dist/app/grooph-app.json +1 -1
- package/dist/app/index.html +2 -2
- package/dist/app/routes.json +20 -20
- package/dist/bundle/grooph.js +320 -42
- package/package.json +1 -1
- package/dist/app/assets/brakes-9E9PsV_Z.js +0 -1
package/dist/bundle/grooph.js
CHANGED
|
@@ -94,7 +94,7 @@ const GLYPH_INK = {
|
|
|
94
94
|
"loop-3": "loop-3"
|
|
95
95
|
};
|
|
96
96
|
const n1 = (n) => (Math.round(n * 10) / 10).toString();
|
|
97
|
-
const count$
|
|
97
|
+
const count$4 = (n, one) => n === 0 ? [] : [`${n} ${one}${n === 1 ? "" : "s"}`];
|
|
98
98
|
function foldUnits(doc, open = []) {
|
|
99
99
|
const units = (doc.groups ?? []).filter((group) => group.from !== void 0);
|
|
100
100
|
const inside = new Map();
|
|
@@ -179,9 +179,9 @@ function unitLine(doc, box) {
|
|
|
179
179
|
return {
|
|
180
180
|
line: [
|
|
181
181
|
box.group.from,
|
|
182
|
-
...count$
|
|
183
|
-
...count$
|
|
184
|
-
...count$
|
|
182
|
+
...count$4(nodes.length, "node"),
|
|
183
|
+
...count$4(nodes.filter((node) => node.kind === "human-gate").length, "human gate"),
|
|
184
|
+
...count$4(box.loops.length, "loop")
|
|
185
185
|
].join(" · "),
|
|
186
186
|
irreversible: nodes.flatMap((node) => node.kind === "agent" ? node.irreversible ?? [] : [])
|
|
187
187
|
};
|
|
@@ -1808,23 +1808,23 @@ function nearestIds(root, path) {
|
|
|
1808
1808
|
}
|
|
1809
1809
|
//#endregion
|
|
1810
1810
|
//#region packages/core/dist/src/schema/proposals.js
|
|
1811
|
-
const count$
|
|
1811
|
+
const count$3 = () => num({
|
|
1812
1812
|
integer: true,
|
|
1813
1813
|
minimum: 0
|
|
1814
1814
|
});
|
|
1815
1815
|
const shapeSchema = obj({
|
|
1816
|
-
agents: count$
|
|
1817
|
-
people: opt(count$
|
|
1818
|
-
checks: count$
|
|
1819
|
-
gates: count$
|
|
1820
|
-
loops: count$
|
|
1816
|
+
agents: count$3(),
|
|
1817
|
+
people: opt(count$3()),
|
|
1818
|
+
checks: count$3(),
|
|
1819
|
+
gates: count$3(),
|
|
1820
|
+
loops: count$3(),
|
|
1821
1821
|
tiers: obj({
|
|
1822
|
-
frontier: count$
|
|
1823
|
-
strong: count$
|
|
1824
|
-
fast: count$
|
|
1825
|
-
unset: count$
|
|
1822
|
+
frontier: count$3(),
|
|
1823
|
+
strong: count$3(),
|
|
1824
|
+
fast: count$3(),
|
|
1825
|
+
unset: count$3()
|
|
1826
1826
|
}),
|
|
1827
|
-
worstCaseRounds: anyOf([count$
|
|
1827
|
+
worstCaseRounds: anyOf([count$3(), nul()], { describe: "integer or null" }),
|
|
1828
1828
|
budgets: arr(str$2())
|
|
1829
1829
|
}, { name: "Shape" });
|
|
1830
1830
|
const candidateFile = obj({ file: str$2({ minLength: 1 }) }, { name: "CandidateFile" });
|
|
@@ -3230,16 +3230,16 @@ function estimateShape(graph) {
|
|
|
3230
3230
|
};
|
|
3231
3231
|
}
|
|
3232
3232
|
const amount = (measure, limit) => measure === "usd" ? `$${limit}` : `${limit} ${measure}`;
|
|
3233
|
-
const count$
|
|
3233
|
+
const count$2 = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
3234
3234
|
function shapeLine(shape) {
|
|
3235
3235
|
const people = shape.people ?? 0;
|
|
3236
|
-
const parts = people === 0 ? [count$
|
|
3237
|
-
if (shape.checks > 0) parts.push(count$
|
|
3238
|
-
if (shape.gates > 0) parts.push(count$
|
|
3236
|
+
const parts = people === 0 ? [count$2(shape.agents, "agent")] : [people === 1 ? "1 person's step" : `${people} people's steps`, ...shape.agents > 0 ? [shape.agents === 1 ? "1 agent's step" : `${shape.agents} agents' steps`] : []];
|
|
3237
|
+
if (shape.checks > 0) parts.push(count$2(shape.checks, "check"));
|
|
3238
|
+
if (shape.gates > 0) parts.push(count$2(shape.gates, "gate"));
|
|
3239
3239
|
if (shape.loops === 0) parts.push("no loop");
|
|
3240
3240
|
else {
|
|
3241
|
-
parts.push(count$
|
|
3242
|
-
parts.push(shape.worstCaseRounds === null ? "no round cap" : `up to ${count$
|
|
3241
|
+
parts.push(count$2(shape.loops, "loop"));
|
|
3242
|
+
parts.push(shape.worstCaseRounds === null ? "no round cap" : `up to ${count$2(shape.worstCaseRounds, "round")}`);
|
|
3243
3243
|
}
|
|
3244
3244
|
parts.push(...shape.budgets);
|
|
3245
3245
|
return parts.join(" · ");
|
|
@@ -6228,7 +6228,7 @@ function planSteps(doc) {
|
|
|
6228
6228
|
}
|
|
6229
6229
|
});
|
|
6230
6230
|
}
|
|
6231
|
-
const count = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
6231
|
+
const count$1 = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
6232
6232
|
const itemsOf = (outlineMarkdown, section) => outlineMarkdown([{
|
|
6233
6233
|
...section,
|
|
6234
6234
|
title: "x"
|
|
@@ -6249,9 +6249,9 @@ function planMarkdown(kit, doc, toFix, pictureFile, documentFile) {
|
|
|
6249
6249
|
return `${[
|
|
6250
6250
|
`# ${name}`,
|
|
6251
6251
|
"",
|
|
6252
|
-
`A plan, kept by grooph: the picture, who does what, and every step in full. grooph runs nothing: a plan is for people to read and follow. The document it is drawn from is \`${documentFile}\`, and that is the one to edit.${notes > 0 ? ` It carries ${count(notes, "note", "notes")} from runs, which ${notes === 1 ? "is" : "are"} in that file and not shown here.` : ""}`,
|
|
6252
|
+
`A plan, kept by grooph: the picture, who does what, and every step in full. grooph runs nothing: a plan is for people to read and follow. The document it is drawn from is \`${documentFile}\`, and that is the one to edit.${notes > 0 ? ` It carries ${count$1(notes, "note", "notes")} from runs, which ${notes === 1 ? "is" : "are"} in that file and not shown here.` : ""}`,
|
|
6253
6253
|
"",
|
|
6254
|
-
`${own > 0 ? `As a plan this is not whole yet: it breaks ${own === 1 ? "a rule a graph itself is held to" : `the rules a graph itself is held to in ${own} places`}, and until ${own === 1 ? "that is" : "those are"} fixed, parts of what follows may be missing or drawn wrong.` : steps.length === 0 ? "This plan has no steps yet." : "As a plan for people to read and follow, this is whole."} ${errors.length === 0 ? `A coding harness could run it as it is: nothing in it is in error.${doc.target?.harness ? ` \`grooph export\` writes its package for ${plain(doc.target.harness)}.` : ""}` : `**To run it in a coding harness, ${count(errors.length, "thing is", "things are")} to be fixed first**${own > 0 && own < errors.length ? ` (${own === 1 ? "that one" : `those ${own}`} among them)` : ""}, listed under "To fix before a harness can run this".`}`,
|
|
6254
|
+
`${own > 0 ? `As a plan this is not whole yet: it breaks ${own === 1 ? "a rule a graph itself is held to" : `the rules a graph itself is held to in ${own} places`}, and until ${own === 1 ? "that is" : "those are"} fixed, parts of what follows may be missing or drawn wrong.` : steps.length === 0 ? "This plan has no steps yet." : "As a plan for people to read and follow, this is whole."} ${errors.length === 0 ? `A coding harness could run it as it is: nothing in it is in error.${doc.target?.harness ? ` \`grooph export\` writes its package for ${plain(doc.target.harness)}.` : ""}` : `**To run it in a coding harness, ${count$1(errors.length, "thing is", "things are")} to be fixed first**${own > 0 && own < errors.length ? ` (${own === 1 ? "that one" : `those ${own}`} among them)` : ""}, listed under "To fix before a harness can run this".`}`,
|
|
6255
6255
|
"",
|
|
6256
6256
|
...oneLine$2(doc.goal ?? "") ? [`**Goal:** ${plain(doc.goal)}`, ""] : [],
|
|
6257
6257
|
``,
|
|
@@ -6263,7 +6263,7 @@ function planMarkdown(kit, doc, toFix, pictureFile, documentFile) {
|
|
|
6263
6263
|
"|---|---|---|---|",
|
|
6264
6264
|
...steps.map((step) => `| ${cell$1(step.name)} | ${step.whose === "nobody" ? " " : step.whose} | ${step.command !== void 0 ? shown$1(step.command) : cell$1(step.does)} | ${cell$1(step.leaves)} |`),
|
|
6265
6265
|
"",
|
|
6266
|
-
`Of ${count(steps.length, "step", "steps")}: ${whose("an agent")} by an agent, ${whose("a person")} by a person, ${whose("a command")} by a command${whose("the lead") > 0 ? `, ${whose("the lead")} by the lead` : ""}${whose("nobody") > 0 ? `; ${whose("nobody")} ${whose("nobody") === 1 ? "ends" : "end"} the run` : ""}.`
|
|
6266
|
+
`Of ${count$1(steps.length, "step", "steps")}: ${whose("an agent")} by an agent, ${whose("a person")} by a person, ${whose("a command")} by a command${whose("the lead") > 0 ? `, ${whose("the lead")} by the lead` : ""}${whose("nobody") > 0 ? `; ${whose("nobody")} ${whose("nobody") === 1 ? "ends" : "end"} the run` : ""}.`
|
|
6267
6267
|
],
|
|
6268
6268
|
...asked.length > 0 ? [
|
|
6269
6269
|
"",
|
|
@@ -9090,13 +9090,25 @@ const saidOf = (reasons, why) => reasons !== void 0 && (reasons === why || reaso
|
|
|
9090
9090
|
const quote$1 = (list) => list.map((item) => `"${item}"`).join(", ");
|
|
9091
9091
|
const markers = (node) => node?.kind === "agent" ? node.irreversible ?? [] : [];
|
|
9092
9092
|
const isBrakeStop = (stop) => stop.kind === "max-iterations" || stop.kind === "budget" || stop.kind === "human";
|
|
9093
|
-
|
|
9093
|
+
const ONE = {
|
|
9094
|
+
dispatches: "dispatch",
|
|
9095
|
+
minutes: "minute",
|
|
9096
|
+
turns: "turn",
|
|
9097
|
+
tokens: "token",
|
|
9098
|
+
rounds: "round"
|
|
9099
|
+
};
|
|
9100
|
+
const count = (n, many) => `${n} ${n === 1 ? ONE[many] ?? many : many}`;
|
|
9101
|
+
const sized = (n, unit) => unit === "" ? `${n}` : count(n, unit);
|
|
9102
|
+
function haltsIn(doc) {
|
|
9094
9103
|
const nodes = new Map(doc.nodes.map((node) => [node.id, node]));
|
|
9095
|
-
|
|
9104
|
+
return (stop) => {
|
|
9096
9105
|
if (stop.then === void 0) return true;
|
|
9097
9106
|
const target = nodes.get(stop.then);
|
|
9098
9107
|
return target?.kind === "human-gate" || target?.kind === "stop" && target.outcome === "halt";
|
|
9099
9108
|
};
|
|
9109
|
+
}
|
|
9110
|
+
function brakesOf(doc, loops) {
|
|
9111
|
+
const halts = haltsIn(doc);
|
|
9100
9112
|
const brakes = new Map();
|
|
9101
9113
|
const least = (now, next) => now === void 0 ? next : Math.min(now, next);
|
|
9102
9114
|
for (const loop of loops) for (const stop of loop.stops) {
|
|
@@ -9108,7 +9120,7 @@ function brakesOf(doc, loops) {
|
|
|
9108
9120
|
] : stop.kind === "budget" ? [
|
|
9109
9121
|
`budget ${stop.measure}`,
|
|
9110
9122
|
"the budget",
|
|
9111
|
-
|
|
9123
|
+
stop.measure,
|
|
9112
9124
|
stop.limit
|
|
9113
9125
|
] : stop.kind === "human" ? [
|
|
9114
9126
|
"human",
|
|
@@ -9142,11 +9154,11 @@ function looser(was, now) {
|
|
|
9142
9154
|
for (const [key, brake] of was) {
|
|
9143
9155
|
const next = now.get(key);
|
|
9144
9156
|
const asked = key === "human";
|
|
9145
|
-
if (next?.any === void 0) said.push(asked ? "removes the stop where a person is asked" : `removes ${brake.name} (${brake.any
|
|
9146
|
-
else if (next.any > brake.any) said.push(asked ? `a person would be asked every ${next.any} rounds, not every ${brake.any}` : `raises ${brake.name} from ${brake.any} to ${next.any
|
|
9147
|
-
else if (brake.halt !== void 0 && next.halt === void 0) said.push(`${brake.name} (${brake.halt
|
|
9148
|
-
else if (brake.halt !== void 0 && next.halt > brake.halt) said.push(`${brake.name} that halts the run would rise from ${brake.halt} to ${next.halt
|
|
9149
|
-
else if (brake.halt !== void 0 && !first(brake) && first(next)) said.push(`${asked ? "a stop that asks a person" : brake.name} of ${next.lead
|
|
9157
|
+
if (next?.any === void 0) said.push(asked ? "removes the stop where a person is asked" : `removes ${brake.name} (${sized(brake.any, brake.unit)})`);
|
|
9158
|
+
else if (next.any > brake.any) said.push(asked ? `a person would be asked every ${next.any} rounds, not every ${brake.any}` : `raises ${brake.name} from ${brake.any} to ${sized(next.any, brake.unit)}`);
|
|
9159
|
+
else if (brake.halt !== void 0 && next.halt === void 0) said.push(`${brake.name} (${sized(brake.halt, brake.unit)}) would no longer halt the run`);
|
|
9160
|
+
else if (brake.halt !== void 0 && next.halt > brake.halt) said.push(`${brake.name} that halts the run would rise from ${brake.halt} to ${sized(next.halt, brake.unit)}`);
|
|
9161
|
+
else if (brake.halt !== void 0 && !first(brake) && first(next)) said.push(`${asked ? "a stop that asks a person" : brake.name} of ${sized(next.lead, brake.unit)} that leads on to ${quote$1([...next.leads])} would fire ${next.lead < next.halt ? "before" : "as soon as"} the one of ${sized(next.halt, brake.unit)} that halts the run`);
|
|
9150
9162
|
else if (first(next)) {
|
|
9151
9163
|
const fresh = [...next.leads].filter((id) => !brake.leads.has(id));
|
|
9152
9164
|
if (fresh.length > 0) said.push(`${brake.name} would lead on to ${quote$1(fresh)}${brake.leads.size > 0 ? `, not to ${quote$1([...brake.leads])}` : ""}`);
|
|
@@ -9154,6 +9166,184 @@ function looser(was, now) {
|
|
|
9154
9166
|
}
|
|
9155
9167
|
return said;
|
|
9156
9168
|
}
|
|
9169
|
+
function stopName(stop) {
|
|
9170
|
+
switch (stop.kind) {
|
|
9171
|
+
case "max-iterations": return `the round cap of ${stop.n}`;
|
|
9172
|
+
case "budget": return `the budget of ${count(stop.limit, stop.measure)}`;
|
|
9173
|
+
case "human": return (stop.every ?? 1) === 1 ? "the stop where a person is asked" : `the stop where a person is asked every ${count(stop.every, "rounds")}`;
|
|
9174
|
+
case "diminishing-returns": return `the stop on diminishing returns over ${count(stop.rounds, "rounds")}`;
|
|
9175
|
+
case "evidence-invalid": return `the stop on evidence invalid for ${count(stop.rounds, "rounds")}`;
|
|
9176
|
+
default: return "the stop on \"bar passed\"";
|
|
9177
|
+
}
|
|
9178
|
+
}
|
|
9179
|
+
const stopSize = (stop) => stop.kind === "max-iterations" ? stop.n : stop.kind === "budget" ? stop.limit : stop.kind === "human" ? stop.every ?? 1 : stop.kind === "bar-passed" ? void 0 : stop.rounds;
|
|
9180
|
+
const stopKind = (stop) => stop.kind === "budget" ? `budget ${stop.measure}` : stop.kind === "diminishing-returns" ? `${stop.kind} ${JSON.stringify([stop.metric ?? null, stop.threshold ?? null])}` : stop.kind;
|
|
9181
|
+
const leadsOnIn = (halts, judged) => (stop) => {
|
|
9182
|
+
if (stop.kind === "human") return false;
|
|
9183
|
+
if (stop.kind === "bar-passed") return !judged && (stop.then === void 0 || !halts(stop));
|
|
9184
|
+
return stop.then !== void 0 && !halts(stop);
|
|
9185
|
+
};
|
|
9186
|
+
function timingOf(stop) {
|
|
9187
|
+
const whole = (size) => Number.isFinite(size) ? Math.max(1, Math.ceil(size)) : 1;
|
|
9188
|
+
switch (stop.kind) {
|
|
9189
|
+
case "max-iterations": return {
|
|
9190
|
+
sort: "cap",
|
|
9191
|
+
from: whole(stop.n)
|
|
9192
|
+
};
|
|
9193
|
+
case "human": return {
|
|
9194
|
+
sort: "person",
|
|
9195
|
+
every: whole(stop.every ?? 1)
|
|
9196
|
+
};
|
|
9197
|
+
case "budget": return {
|
|
9198
|
+
sort: "counted",
|
|
9199
|
+
key: `budget ${stop.measure}`,
|
|
9200
|
+
size: stop.limit,
|
|
9201
|
+
from: 1,
|
|
9202
|
+
lasting: true
|
|
9203
|
+
};
|
|
9204
|
+
case "diminishing-returns": return {
|
|
9205
|
+
sort: "counted",
|
|
9206
|
+
key: `no progress ${JSON.stringify([stop.metric ?? null, stop.threshold ?? null])}`,
|
|
9207
|
+
size: stop.rounds,
|
|
9208
|
+
from: whole(stop.rounds),
|
|
9209
|
+
lasting: false
|
|
9210
|
+
};
|
|
9211
|
+
case "evidence-invalid": return {
|
|
9212
|
+
sort: "counted",
|
|
9213
|
+
key: "invalid evidence",
|
|
9214
|
+
size: stop.rounds,
|
|
9215
|
+
from: whole(stop.rounds),
|
|
9216
|
+
lasting: false
|
|
9217
|
+
};
|
|
9218
|
+
default: return {
|
|
9219
|
+
sort: "counted",
|
|
9220
|
+
key: "bar passed",
|
|
9221
|
+
size: 0,
|
|
9222
|
+
from: 1,
|
|
9223
|
+
lasting: false
|
|
9224
|
+
};
|
|
9225
|
+
}
|
|
9226
|
+
}
|
|
9227
|
+
function tried(stops) {
|
|
9228
|
+
const timing = stops.map(timingOf);
|
|
9229
|
+
const always = [];
|
|
9230
|
+
const capAhead = [];
|
|
9231
|
+
const person = new Map();
|
|
9232
|
+
let [sure, cap, soonest, over] = [
|
|
9233
|
+
false,
|
|
9234
|
+
Infinity,
|
|
9235
|
+
Infinity,
|
|
9236
|
+
Infinity
|
|
9237
|
+
];
|
|
9238
|
+
timing.forEach((mine, at) => {
|
|
9239
|
+
always.push(sure);
|
|
9240
|
+
capAhead.push(cap);
|
|
9241
|
+
if (mine.sort === "person") {
|
|
9242
|
+
if (!person.has(mine.every)) person.set(mine.every, at);
|
|
9243
|
+
sure ||= mine.every === 1;
|
|
9244
|
+
soonest = Math.min(soonest, mine.every);
|
|
9245
|
+
} else if (mine.sort === "cap") {
|
|
9246
|
+
cap = Math.min(cap, mine.from);
|
|
9247
|
+
if (soonest > mine.from) over = Math.min(over, mine.from);
|
|
9248
|
+
}
|
|
9249
|
+
});
|
|
9250
|
+
const kept = new Map();
|
|
9251
|
+
const least = (key) => {
|
|
9252
|
+
let sizes = kept.get(key);
|
|
9253
|
+
if (!sizes) {
|
|
9254
|
+
let size = Infinity;
|
|
9255
|
+
sizes = timing.map((mine) => {
|
|
9256
|
+
const ahead = size;
|
|
9257
|
+
if (mine.sort === "counted" && mine.key === key) size = Math.min(size, mine.size);
|
|
9258
|
+
return ahead;
|
|
9259
|
+
});
|
|
9260
|
+
kept.set(key, sizes);
|
|
9261
|
+
}
|
|
9262
|
+
return sizes;
|
|
9263
|
+
};
|
|
9264
|
+
const dividing = new Map();
|
|
9265
|
+
const divides = (every) => {
|
|
9266
|
+
let place = dividing.get(every);
|
|
9267
|
+
if (place === void 0) {
|
|
9268
|
+
place = Infinity;
|
|
9269
|
+
for (const [other, at] of person) if (every % other === 0) place = Math.min(place, at);
|
|
9270
|
+
dividing.set(every, place);
|
|
9271
|
+
}
|
|
9272
|
+
return place;
|
|
9273
|
+
};
|
|
9274
|
+
return {
|
|
9275
|
+
timing,
|
|
9276
|
+
always,
|
|
9277
|
+
capAhead,
|
|
9278
|
+
over,
|
|
9279
|
+
person,
|
|
9280
|
+
divides,
|
|
9281
|
+
least
|
|
9282
|
+
};
|
|
9283
|
+
}
|
|
9284
|
+
function leadsOnFirst(was, held, now, leadsOn) {
|
|
9285
|
+
const [source, copy] = [tried(was), tried(now)];
|
|
9286
|
+
const caps = source.timing.flatMap((mine, at) => mine.sort === "cap" ? [{
|
|
9287
|
+
from: mine.from,
|
|
9288
|
+
at
|
|
9289
|
+
}] : []).sort((a, b) => a.from - b.from);
|
|
9290
|
+
const found = [];
|
|
9291
|
+
now.forEach((lead, j) => {
|
|
9292
|
+
const mine = copy.timing[j];
|
|
9293
|
+
if (mine.sort === "person" || !leadsOn(lead) || copy.always[j]) return;
|
|
9294
|
+
const [first, last] = [mine.from, Math.min(copy.over, copy.capAhead[j] - 1)];
|
|
9295
|
+
if (first > last || mine.sort === "counted" && !(copy.least(mine.key)[j] > mine.size)) return;
|
|
9296
|
+
const asked = [];
|
|
9297
|
+
let soonest = Infinity;
|
|
9298
|
+
for (const theirs of source.timing) {
|
|
9299
|
+
asked.push(soonest);
|
|
9300
|
+
if (theirs.sort === "person" && !(copy.divides(theirs.every) < j)) soonest = Math.min(soonest, theirs.every);
|
|
9301
|
+
}
|
|
9302
|
+
let early = first;
|
|
9303
|
+
if (mine.sort === "counted") {
|
|
9304
|
+
const due = source.timing.findIndex((theirs) => theirs.sort === "counted" && theirs.key === mine.key && theirs.size <= mine.size);
|
|
9305
|
+
if (due >= 0) early = Math.max(early, asked[due]);
|
|
9306
|
+
}
|
|
9307
|
+
for (const cap of caps) if (cap.from <= early && early < asked[cap.at]) early = asked[cap.at];
|
|
9308
|
+
let same;
|
|
9309
|
+
for (const [i, halt] of was.entries()) {
|
|
9310
|
+
const theirs = source.timing[i];
|
|
9311
|
+
if (!held(halt) || source.always[i] || theirs.sort === "person" && source.divides(theirs.every) < i) continue;
|
|
9312
|
+
const [from, until] = [theirs.sort === "person" ? theirs.every : theirs.from, Math.min(source.over, source.capAhead[i] - 1)];
|
|
9313
|
+
if (from > until || theirs.sort === "counted" && !(source.least(theirs.key)[i] > theirs.size)) continue;
|
|
9314
|
+
if (mine.sort === "counted" && mine.lasting && !(source.least(mine.key)[i] > mine.size)) continue;
|
|
9315
|
+
const asks = (low, high) => low <= high && (theirs.sort !== "person" || Math.ceil(low / theirs.every) * theirs.every <= high);
|
|
9316
|
+
const together = asks(Math.max(first, from), Math.min(last, until)) && !(theirs.sort === "person" && copy.divides(theirs.every) < j) && !(mine.sort === "counted" && !(source.least(mine.key)[i] > mine.size)) && !(theirs.sort === "counted" && !(copy.least(theirs.key)[j] > theirs.size));
|
|
9317
|
+
if (Number.isFinite(early) && early <= last && asks(Math.max(from, early + 1), until)) {
|
|
9318
|
+
if (theirs.sort === "person" && together) found.push({
|
|
9319
|
+
at: j,
|
|
9320
|
+
from: i,
|
|
9321
|
+
lead,
|
|
9322
|
+
halt,
|
|
9323
|
+
before: false
|
|
9324
|
+
});
|
|
9325
|
+
else found.push({
|
|
9326
|
+
at: j,
|
|
9327
|
+
from: i,
|
|
9328
|
+
lead,
|
|
9329
|
+
halt,
|
|
9330
|
+
before: true,
|
|
9331
|
+
...theirs.sort === "person" ? { again: early >= theirs.every } : {}
|
|
9332
|
+
});
|
|
9333
|
+
return;
|
|
9334
|
+
}
|
|
9335
|
+
if (together) same ??= i;
|
|
9336
|
+
}
|
|
9337
|
+
if (same !== void 0) found.push({
|
|
9338
|
+
at: j,
|
|
9339
|
+
from: same,
|
|
9340
|
+
lead,
|
|
9341
|
+
halt: was[same],
|
|
9342
|
+
before: false
|
|
9343
|
+
});
|
|
9344
|
+
});
|
|
9345
|
+
return found;
|
|
9346
|
+
}
|
|
9157
9347
|
const wayName = (way) => way.edge ?? `${way.loop} → ${way.to}`;
|
|
9158
9348
|
function waysRoundUncounted(doc, loop, persons) {
|
|
9159
9349
|
const counted = new Set(loop.back);
|
|
@@ -9187,6 +9377,7 @@ function waysRoundUncounted(doc, loop, persons) {
|
|
|
9187
9377
|
}
|
|
9188
9378
|
function loopLosses(before, after) {
|
|
9189
9379
|
const losses = [];
|
|
9380
|
+
const fired = [];
|
|
9190
9381
|
const still = new Set(after.nodes.map((node) => node.id));
|
|
9191
9382
|
const known = new Set(before.nodes.map((node) => node.id));
|
|
9192
9383
|
const waysWas = waysOf(before);
|
|
@@ -9194,7 +9385,9 @@ function loopLosses(before, after) {
|
|
|
9194
9385
|
const round = new Set(after.nodes.filter((node) => [...reachedFrom(after, waysNow.filter((way) => way.from === node.id && way.to !== node.id).map((way) => way.to))].includes(node.id) || waysNow.some((way) => way.edge !== void 0 && way.from === node.id && way.to === node.id)).map((node) => node.id));
|
|
9195
9386
|
const edgeNow = new Map(after.edges.map((edge) => [edge.id, edge]));
|
|
9196
9387
|
const loopNow = new Map(after.loops.map((loop) => [loop.id, loop]));
|
|
9388
|
+
const loopWas = new Map(before.loops.map((loop) => [loop.id, loop]));
|
|
9197
9389
|
const edgeWas = new Map(before.edges.map((edge) => [edge.id, edge]));
|
|
9390
|
+
const critics = new Set(before.nodes.filter((node) => isCriticFamily(node) && after.nodes.some((other) => other.id === node.id && isCriticFamily(other))).map((node) => node.id));
|
|
9198
9391
|
for (const loop of before.loops) {
|
|
9199
9392
|
if (!(loop.stops.some(isBrakeStop) || loop.bar !== void 0)) continue;
|
|
9200
9393
|
const kept = loopNow.get(loop.id);
|
|
@@ -9202,17 +9395,62 @@ function loopLosses(before, after) {
|
|
|
9202
9395
|
why: "removes a loop with its stops and its bar",
|
|
9203
9396
|
at: [`loop:${loop.id}`]
|
|
9204
9397
|
});
|
|
9205
|
-
const targets = loop.stops.flatMap((stop) => stop.then === void 0 ? [] : [
|
|
9398
|
+
const targets = [...new Set(loop.stops.flatMap((stop) => stop.then === void 0 ? [] : [
|
|
9206
9399
|
`node:${stop.then}.kind`,
|
|
9207
9400
|
`node:${stop.then}.outcome`,
|
|
9208
9401
|
`node:${stop.then}`
|
|
9209
|
-
]);
|
|
9402
|
+
]))];
|
|
9210
9403
|
const was = brakesOf(before, [loop]);
|
|
9211
9404
|
if (kept) {
|
|
9212
9405
|
for (const why of looser(was, brakesOf(after, [kept]))) losses.push({
|
|
9213
9406
|
why,
|
|
9214
9407
|
at: [`loop:${loop.id}.stops`, ...targets]
|
|
9215
9408
|
});
|
|
9409
|
+
const [haltsWas, haltsNow] = [haltsIn(before), haltsIn(after)];
|
|
9410
|
+
const held = (stop) => stop.kind === "human" || isBrakeStop(stop) && haltsWas(stop);
|
|
9411
|
+
const judged = (now, old) => now.members.some((member) => (old?.members.includes(member) ?? false) && critics.has(member));
|
|
9412
|
+
const halting = (halt) => halt.kind === "human" ? stopName(halt) : `${stopName(halt)} that halts the run`;
|
|
9413
|
+
const text = (stop) => JSON.stringify(stop);
|
|
9414
|
+
const [hadAt, keptAt] = [new Map(), new Map()];
|
|
9415
|
+
loop.stops.forEach((stop, at) => void (hadAt.has(text(stop)) || hadAt.set(text(stop), at)));
|
|
9416
|
+
kept.stops.forEach((stop, at) => void keptAt.set(text(stop), at));
|
|
9417
|
+
const gone = new Map();
|
|
9418
|
+
for (const stop of loop.stops) if (!keptAt.has(text(stop))) (gone.get(stopKind(stop)) ?? gone.set(stopKind(stop), []).get(stopKind(stop))).push(stop);
|
|
9419
|
+
const asksNow = kept.stops.filter((stop) => stop.kind === "human" && !hadAt.has(text(stop)));
|
|
9420
|
+
const askedEvery = [...new Set(kept.stops.flatMap((stop) => stop.kind === "human" ? [stop.every ?? 1] : []))];
|
|
9421
|
+
const where = [`loop:${loop.id}.stops`, ...targets];
|
|
9422
|
+
for (const { at: place, from, lead, halt, before: sooner, again } of leadsOnFirst(loop.stops, held, kept.stops, leadsOnIn(haltsNow, judged(kept, loop)))) {
|
|
9423
|
+
const name = stopName(lead);
|
|
9424
|
+
const leading = lead.then === void 0 ? `${name}, which follows the loop's pass edges,` : `${name} that leads on to "${lead.then}"`;
|
|
9425
|
+
const fires = !sooner ? `on the same pass as ${halting(halt)}, where it would be the one obeyed` : halt.kind !== "human" ? `before ${halting(halt)}` : again ? `between two askings of ${stopName(halt)}, after the person has said go on` : `before ${stopName(halt)} first asks`;
|
|
9426
|
+
const stood = hadAt.get(text(lead));
|
|
9427
|
+
const kin = gone.get(stopKind(lead)) ?? [];
|
|
9428
|
+
const sized = kin.find((stop) => stop.then === lead.then && stopSize(stop) !== stopSize(lead));
|
|
9429
|
+
const led = lead.then === void 0 ? void 0 : kin.find((stop) => stopSize(stop) === stopSize(lead) && stop.then !== lead.then);
|
|
9430
|
+
const every = halt.kind === "human" ? halt.every ?? 1 : 1;
|
|
9431
|
+
const asks = stood !== void 0 && halt.kind === "human" && !keptAt.has(text(halt)) && !askedEvery.some((n) => every % n === 0) ? asksNow.find((stop) => stop.then === halt.then) : void 0;
|
|
9432
|
+
const why = stood !== void 0 ? asks ? `a person would be asked every ${asks.every ?? 1} rounds where it was every ${every}: on pass ${every} nobody would be asked, and ${leading} could fire on a pass where a person was asked` : !sooner && stood > from && (keptAt.get(text(halt)) ?? -1) > place ? `${leading} would be moved ahead of ${halting(halt)}, and could fire on the same pass` : `${leading} could fire ${fires}, as it could not before` : sized ? `${leading} would go from ${stopSize(sized)} to ${stopSize(lead)}, and could fire ${fires}` : led ? `${name} would lead on to "${lead.then}"${led.then === void 0 ? "" : `, not to "${led.then}"`}${haltsWas(led) ? ", where it halted the run" : ""}${led === halt ? "" : `, and could fire ${fires}`}` : `${leading} would come into the loop, and could fire ${fires}`;
|
|
9433
|
+
fired.push({
|
|
9434
|
+
why,
|
|
9435
|
+
at: where
|
|
9436
|
+
});
|
|
9437
|
+
}
|
|
9438
|
+
const halt = loop.stops.find(held);
|
|
9439
|
+
for (const other of halt ? after.loops : []) {
|
|
9440
|
+
const shares = (one) => one?.back.some((id) => loop.back.includes(id) || kept.back.includes(id)) ?? false;
|
|
9441
|
+
if (other.id === loop.id || !shares(other)) continue;
|
|
9442
|
+
const old = loopWas.get(other.id);
|
|
9443
|
+
const had = new Set(shares(old) ? old.stops.map(text) : []);
|
|
9444
|
+
const leads = (stop) => leadsOnIn(haltsNow, judged(other, stop.then === void 0 ? old ?? loop : old))(stop);
|
|
9445
|
+
for (const lead of other.stops) {
|
|
9446
|
+
if (!leads(lead) || had.has(text(lead))) continue;
|
|
9447
|
+
const leading = `${stopName(lead)} among its stops, which ${lead.then === void 0 ? "follows that loop's pass edges" : `leads on to "${lead.then}"`},`;
|
|
9448
|
+
fired.push({
|
|
9449
|
+
why: `the loop "${other.id}" would count rounds that "${loop.id}" counts, and ${leading} could fire before ${halting(halt)}`,
|
|
9450
|
+
at: old ? [`loop:${other.id}.stops`, `loop:${other.id}.back`] : [`loop:${other.id}`]
|
|
9451
|
+
});
|
|
9452
|
+
}
|
|
9453
|
+
}
|
|
9216
9454
|
if (loop.bar && kept.bar?.acceptance !== loop.bar.acceptance) losses.push({
|
|
9217
9455
|
why: kept.bar ? "changes the bar's acceptance" : "removes the loop's bar",
|
|
9218
9456
|
at: [`loop:${loop.id}.bar`]
|
|
@@ -9323,7 +9561,10 @@ function loopLosses(before, after) {
|
|
|
9323
9561
|
});
|
|
9324
9562
|
}
|
|
9325
9563
|
}
|
|
9326
|
-
return
|
|
9564
|
+
return {
|
|
9565
|
+
losses,
|
|
9566
|
+
fired
|
|
9567
|
+
};
|
|
9327
9568
|
}
|
|
9328
9569
|
function ownLosses(before, after) {
|
|
9329
9570
|
const losses = [];
|
|
@@ -9689,7 +9930,7 @@ function roundsLeftToAPerson(before, after) {
|
|
|
9689
9930
|
const opened = [...waysRoundUncounted(after, bounded, every).ways].filter(([name, way]) => way.person && !persons.has(name) && !free.ways.has(name));
|
|
9690
9931
|
if (opened.length === 0) continue;
|
|
9691
9932
|
const by = [...new Set(opened.map(([, way]) => way.edge === void 0 ? `the stop where a person is asked, which continues at "${way.to}"` : gates.has(way.from) ? `"${way.when}" at the human gate "${way.from}" (${way.edge})` : `the approval asked on "${way.edge}"`))];
|
|
9692
|
-
const counts = [...brakesOf(after, [kept])].filter(([key]) => key !== "human").map(([, brake]) => `${brake.name} (${brake.halt ?? brake.any
|
|
9933
|
+
const counts = [...brakesOf(after, [kept])].filter(([key]) => key !== "human").map(([, brake]) => `${brake.name} (${sized(brake.halt ?? brake.any, brake.unit)})`);
|
|
9693
9934
|
notes.push(`the loop "${loop.id}": ${counts.length > 0 ? counts.join(" and ") : "its stops"} would count the rounds between two of a person's decisions, and no longer the whole run. A way round its nodes that they do not count is opened each time by ${by.join(", and by ")}`);
|
|
9694
9935
|
}
|
|
9695
9936
|
return notes;
|
|
@@ -9774,7 +10015,36 @@ function onlyTightens(before, after) {
|
|
|
9774
10015
|
}
|
|
9775
10016
|
return names;
|
|
9776
10017
|
}
|
|
10018
|
+
function leadsBroughtIn(before, after) {
|
|
10019
|
+
const names = new Set();
|
|
10020
|
+
const ends = new Set(before.nodes.filter((node) => node.kind === "stop" && node.outcome === "halt").map((node) => node.id));
|
|
10021
|
+
const leads = (stop) => stop.kind === "bar-passed" || stop.then !== void 0 && !ends.has(stop.then);
|
|
10022
|
+
for (const loop of before.loops) {
|
|
10023
|
+
const other = after.loops.find((one) => one.id === loop.id);
|
|
10024
|
+
if (!other) continue;
|
|
10025
|
+
const places = new Map();
|
|
10026
|
+
other.stops.forEach((stop, at) => void (places.get(JSON.stringify(stop)) ?? places.set(JSON.stringify(stop), []).get(JSON.stringify(stop))).push(at));
|
|
10027
|
+
const known = new Set(places.keys());
|
|
10028
|
+
const stood = loop.stops.map((stop) => places.get(JSON.stringify(stop))?.shift());
|
|
10029
|
+
let behind = Infinity;
|
|
10030
|
+
let brought = false;
|
|
10031
|
+
for (let at = loop.stops.length - 1; at >= 0; at -= 1) {
|
|
10032
|
+
const was = stood[at];
|
|
10033
|
+
brought ||= leads(loop.stops[at]) && (was === void 0 ? !known.has(JSON.stringify(loop.stops[at])) : was > behind);
|
|
10034
|
+
if (was !== void 0) behind = Math.min(behind, was);
|
|
10035
|
+
}
|
|
10036
|
+
if (brought) names.add(`loop:${loop.id}.stops`);
|
|
10037
|
+
}
|
|
10038
|
+
return names;
|
|
10039
|
+
}
|
|
9777
10040
|
function brakesLost(before, after) {
|
|
10041
|
+
const brought = leadsBroughtIn(before, after);
|
|
10042
|
+
return brought.size === 0 ? brakesCompared(before, after) : brakesCompared(before, after).map((loss) => loss.at.some((name) => brought.has(name)) ? {
|
|
10043
|
+
...loss,
|
|
10044
|
+
gain: true
|
|
10045
|
+
} : loss);
|
|
10046
|
+
}
|
|
10047
|
+
function brakesCompared(before, after) {
|
|
9778
10048
|
const made = [...comparison(before, after), ...checkLoopLosses(before, after)];
|
|
9779
10049
|
const leaving = checkEdgeLosses(before, after);
|
|
9780
10050
|
if (leaving.length === 0) return made;
|
|
@@ -9785,15 +10055,23 @@ function brakesLost(before, after) {
|
|
|
9785
10055
|
function comparison(before, after) {
|
|
9786
10056
|
const seen = new Set();
|
|
9787
10057
|
const reach = reachLosses(before, after);
|
|
10058
|
+
const loops = loopLosses(before, after);
|
|
10059
|
+
const own = ownLosses(before, after);
|
|
9788
10060
|
const first = [
|
|
9789
|
-
...
|
|
9790
|
-
...
|
|
10061
|
+
...own,
|
|
10062
|
+
...loops.losses,
|
|
9791
10063
|
...reach.losses
|
|
9792
10064
|
];
|
|
9793
10065
|
const named = new Set(first.flatMap((loss) => loss.at));
|
|
9794
10066
|
const told = new Set();
|
|
9795
10067
|
const last = reach.later.filter((loss) => !loss.at.some((name) => named.has(name)) && !told.has(loss.at.join(" ")) && told.add(loss.at.join(" ")));
|
|
9796
|
-
return [
|
|
10068
|
+
return [
|
|
10069
|
+
...own,
|
|
10070
|
+
...loops.losses,
|
|
10071
|
+
...loops.fired,
|
|
10072
|
+
...reach.losses,
|
|
10073
|
+
...last
|
|
10074
|
+
].filter((loss) => {
|
|
9797
10075
|
const key = `${loss.why}\n${loss.at.join(" ")}`;
|
|
9798
10076
|
if (seen.has(key)) return false;
|
|
9799
10077
|
seen.add(key);
|
|
@@ -11504,7 +11782,7 @@ const sameFile$2 = (a, b) => {
|
|
|
11504
11782
|
};
|
|
11505
11783
|
const NOT_JUDGED = {
|
|
11506
11784
|
swapped: "not judged: with a check removed in this copy, no change is called a tightening. If the check that comes in is the same one under another id, these may be built around it:",
|
|
11507
|
-
new: "not judged: an answer a gate did not give,
|
|
11785
|
+
new: "not judged: an answer a gate did not give, a step marked irreversible that the graph did not have, or a stop of a loop that leads on, where it is new, changed or put ahead, lets a person or a run do what it could not before. A change that brings one is named here and not called a tightening, whatever else it does:"
|
|
11508
11786
|
};
|
|
11509
11787
|
function adoptCommand(io, dir, flags) {
|
|
11510
11788
|
const run = readRun(dir);
|
|
@@ -17182,7 +17460,7 @@ function serve(ctx, input = process.stdin, output = process.stdout) {
|
|
|
17182
17460
|
}
|
|
17183
17461
|
//#endregion
|
|
17184
17462
|
//#region packages/cli/dist/src/index.js
|
|
17185
|
-
const VERSION = "0.4.
|
|
17463
|
+
const VERSION = "0.4.1";
|
|
17186
17464
|
async function run(argv, io = stdio, readStdin = () => readFileSync(0, "utf8"), env = {}) {
|
|
17187
17465
|
const [command, ...rest] = argv;
|
|
17188
17466
|
if (command === "help" && rest[0] !== void 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grooph",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Author, check and compile multi-agent loop graphs for a coding harness: templates, a validator with stable rule codes, a compiler to a prompt package, and an MCP server. It never runs agents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ryan Joseph Kamp",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{t as e}from"./index-BoSi6XYh.js";import{Kt as t,Ut as n,Yt as r}from"./share-BzIzB4EY.js";var i=e=>typeof e.when==`object`?e.when.verdict:e.when??`always`;function a(e){let t=new Map(e.nodes.map(e=>[e.id,e.kind])),n=new Set(e.nodes.filter(e=>e.kind===`stop`&&e.outcome===`halt`).map(e=>e.id)),a=new Set(e.nodes.filter(r).map(e=>e.id)),o=e.edges.map(e=>({from:e.from,to:e.to,person:e.approval===!0||t.get(e.from)===`human-gate`,edge:e.id,when:i(e)}));for(let t of e.loops)for(let e of t.stops){if(e.then===void 0)continue;let r=e.kind===`max-iterations`||e.kind===`budget`||e.kind===`human`,i=r&&n.has(e.then),s=e.kind===`bar-passed`?t.members.filter(e=>a.has(e)):[];for(let n of t.members)o.push({from:n,to:e.then,person:e.kind===`human`,loop:t.id,...r?{fires:r}:{},...i?{escalates:i}:{},...s.length>0?{verdictOf:s}:{}})}return o}var o=e=>`${e.loop}\n${e.to}`;function s(e,t){if(t===`every`)return e.person;if(`approval`in t)return e.edge===t.approval;if(`critic`in t&&(e.escalates||e.fires&&t.stops?.has(o(e)))||`critic`in t&&e.verdictOf?.includes(t.critic)&&(t.when===void 0||t.when===`pass`))return!0;let n=`gate`in t?t.gate:t.critic;return e.edge!==void 0&&e.from===n&&(t.when===void 0||e.when===t.when)}function c(e){let t=new Set(e.loops.flatMap(e=>e.back));return e.nodes.filter(n=>!e.edges.some(e=>e.to===n.id&&!t.has(e.id))).map(e=>e.id)}function l(e,t,n){let r=new Set(e.nodes.map(e=>e.id)),i=new Map;for(let t of a(e))n!==void 0&&s(t,n)||!r.has(t.from)||!r.has(t.to)||(i.get(t.from)??i.set(t.from,[]).get(t.from)).push(t.to);let o=new Set(t),c=[...o];for(let e=c.pop();e!==void 0;e=c.pop())for(let t of i.get(e)??[])o.has(t)||(o.add(t),c.push(t));return o}var u=(e,t)=>l(e,c(e),t);function d(e,t){let n=new Map(t.nodes.map(e=>[e.id,e])),s=new Set(t.edges.filter(e=>e.approval===!0).map(e=>e.id)),c=t=>[...new Set(e.edges.filter(e=>e.from===t).map(i))],l=[`every`],u=new Set(a(e).filter(e=>e.fires).map(o));for(let t of e.nodes)t.kind===`human-gate`&&n.get(t.id)?.kind===`human-gate`&&l.push({gate:t.id},...c(t.id).map(e=>({gate:t.id,when:e})));for(let t of e.edges)t.approval===!0&&s.has(t.id)&&l.push({approval:t.id});for(let t of e.nodes){let e=n.get(t.id);if(r(t)&&e&&r(e)&&l.push({critic:t.id},...c(t.id).map(e=>({critic:t.id,when:e}))),t.kind===`check`&&e?.kind===`check`)for(let e of[{check:!0},{check:!0,stops:u}])l.push({critic:t.id,...e},...c(t.id).map(n=>({critic:t.id,when:n,...e})))}return l}function f(e){if(e===`every`)return`a person`;if(`approval`in e)return`the approval on "${e.approval}"`;if(`gate`in e)return e.when===void 0?`the human gate "${e.gate}"`:`"${e.when}" at the human gate "${e.gate}"`;let t=e.check?`check`:`critic`;return e.when===void 0?`the ${t} "${e.critic}"`:`"${e.when}" from the ${t} "${e.critic}"`}var p=(e,t)=>JSON.stringify(e)===JSON.stringify(t),m=(e,t)=>e!==void 0&&(e===t||e.startsWith(`${t}; `)||e.endsWith(`; ${t}`)||e.includes(`; ${t}; `)),h=e=>e.map(e=>`"${e}"`).join(`, `),g=e=>e?.kind===`agent`?e.irreversible??[]:[],_=e=>e.kind===`max-iterations`||e.kind===`budget`||e.kind===`human`;function v(e,t){let n=new Map(e.nodes.map(e=>[e.id,e])),r=e=>{if(e.then===void 0)return!0;let t=n.get(e.then);return t?.kind===`human-gate`||t?.kind===`stop`&&t.outcome===`halt`},i=new Map,a=(e,t)=>e===void 0?t:Math.min(e,t);for(let e of t)for(let t of e.stops){let[e,n,o,s]=t.kind===`max-iterations`?[`cap`,`the round cap`,``,t.n]:t.kind===`budget`?[`budget ${t.measure}`,`the budget`,` ${t.measure}`,t.limit]:t.kind===`human`?[`human`,`the stop where a person is asked`,``,t.every??1]:[void 0,``,``,0];if(e===void 0)continue;let c=i.get(e)??i.set(e,{name:n,unit:o,leads:new Set}).get(e);c.any=a(c.any,s),r(t)?c.halt=a(c.halt,s):(c.lead=a(c.lead,s),c.leads.add(t.then))}return i}function y(e,t){let n=[],r=e=>e.lead!==void 0&&(e.halt===void 0||e.lead<=e.halt);for(let[i,a]of e){let e=t.get(i),o=i===`human`;if(e?.any===void 0)n.push(o?`removes the stop where a person is asked`:`removes ${a.name} (${a.any}${a.unit})`);else if(e.any>a.any)n.push(o?`a person would be asked every ${e.any} rounds, not every ${a.any}`:`raises ${a.name} from ${a.any} to ${e.any}${a.unit}`);else if(a.halt!==void 0&&e.halt===void 0)n.push(`${a.name} (${a.halt}${a.unit}) would no longer halt the run`);else if(a.halt!==void 0&&e.halt>a.halt)n.push(`${a.name} that halts the run would rise from ${a.halt} to ${e.halt}${a.unit}`);else if(a.halt!==void 0&&!r(a)&&r(e))n.push(`${o?`a stop that asks a person`:a.name} of ${e.lead}${a.unit} that leads on to ${h([...e.leads])} would fire ${e.lead<e.halt?`before`:`as soon as`} the one of ${e.halt}${a.unit} that halts the run`);else if(r(e)){let t=[...e.leads].filter(e=>!a.leads.has(e));t.length>0&&n.push(`${a.name} would lead on to ${h(t)}${a.leads.size>0?`, not to ${h([...a.leads])}`:``}`)}}return n}var b=e=>e.edge??`${e.loop} → ${e.to}`;function x(e,t,n){let r=new Set(t.back),i=new Set(e.nodes.filter(e=>e.kind===`human-gate`).map(e=>e.id)),o=a(e).filter(e=>e.from!==e.to&&(e.edge===void 0||!r.has(e.edge))&&(!e.person||n.has(b(e)))),s=n=>n.edge===void 0?[b(n)]:e.loops.filter(e=>e.id!==t.id&&e.back.includes(n.edge)).map(e=>e.id),c=(e,t)=>{let n=new Set(e),r=[...n];for(let e=r.pop();e!==void 0;e=r.pop())for(let i of t(e))n.has(i)||r.push((n.add(i),i));return n},l=o.filter(e=>e.loop===t.id&&t.members.includes(e.to)&&!i.has(e.to)),u=new Map(l.map(e=>[b(e),e])),d=new Map;for(let e of t.members){let t=c([e],e=>o.filter(t=>t.from===e).map(e=>e.to)),n=c([e],e=>o.filter(t=>t.to===e).map(e=>e.from)),r=new Set(l.map(b));for(let e of o)if(t.has(e.from)&&n.has(e.to)){u.set(b(e),e);for(let t of s(e))r.add(t)}d.set(e,r)}return{ways:u,closers:d}}function S(e,t){let n=[],r=new Set(t.nodes.map(e=>e.id)),i=new Set(e.nodes.map(e=>e.id)),o=a(e),s=a(t),c=new Set(t.nodes.filter(e=>[...l(t,s.filter(t=>t.from===e.id&&t.to!==e.id).map(e=>e.to))].includes(e.id)||s.some(t=>t.edge!==void 0&&t.from===e.id&&t.to===e.id)).map(e=>e.id)),u=new Map(t.edges.map(e=>[e.id,e])),d=new Map(t.loops.map(e=>[e.id,e])),f=new Map(e.edges.map(e=>[e.id,e]));for(let a of e.loops){if(!(a.stops.some(_)||a.bar!==void 0))continue;let l=d.get(a.id);l||n.push({why:`removes a loop with its stops and its bar`,at:[`loop:${a.id}`]});let m=a.stops.flatMap(e=>e.then===void 0?[]:[`node:${e.then}.kind`,`node:${e.then}.outcome`,`node:${e.then}`]),g=v(e,[a]);if(l){for(let e of y(g,v(t,[l])))n.push({why:e,at:[`loop:${a.id}.stops`,...m]});a.bar&&l.bar?.acceptance!==a.bar.acceptance&&n.push({why:l.bar?`changes the bar's acceptance`:`removes the loop's bar`,at:[`loop:${a.id}.bar`]})}for(let e of a.back){let r=f.get(e);if(!r)continue;let i=u.has(e)?[u.get(e)]:t.edges.filter(e=>e.from===r.from&&e.to===r.to);for(let e of i){if(l?.back.includes(e.id))continue;let r=t.loops.filter(t=>t.back.includes(e.id));n.push({why:r.length===0?`the rounds that "${e.id}" starts would no longer be counted by any loop`:`the rounds that "${e.id}" starts would be counted by ${h(r.map(e=>e.id))}, not against the stops of "${a.id}"`,at:[`loop:${a.id}.back`,`loop:${a.id}`,...r.flatMap(e=>[`loop:${e.id}`,`loop:${e.id}.back`])]})}}let S=new Set(a.members);for(let r of t.loops){if(r.id===a.id)continue;let t=new Set(e.loops.find(e=>e.id===r.id)?.back??[]);for(let e of r.back){let i=u.get(e),o=f.get(e);!i||t.has(e)&&o?.from===i.from&&o.to===i.to||l?.back.includes(e)||!S.has(i.from)||!S.has(i.to)||n.push({why:`a round between "${i.from}" and "${i.to}", which the loop "${a.id}" bounds, would be counted by "${r.id}" and not against its stops`,at:[`loop:${r.id}`,`loop:${r.id}.back`,`edge:${e}`,`edge:${e}.from`,`edge:${e}.to`]})}}let C=l?a.members.filter(e=>r.has(e)&&!l.members.includes(e)):[];l&&a.members.length>0&&!a.members.some(e=>l.members.includes(e))?n.push({why:`the loop "${a.id}" would keep its stops and bound none of the nodes it did`,at:[`loop:${a.id}.members`,`loop:${a.id}.back`]}):C.length>0&&n.push({why:`the loop "${a.id}" would no longer bound ${h(C)}, which ${C.length===1?`is`:`are`} still in the graph`,at:[`loop:${a.id}.members`]});let w=l?a.members.filter(e=>!r.has(e)):[],T=w.length>0?t.nodes.filter(e=>!i.has(e.id)&&!l.members.includes(e.id)&&e.kind!==`stop`&&c.has(e.id)).map(e=>e.id):[];if(T.length>0&&n.push({why:`removes ${h(w)}, which the loop "${a.id}" bounded, while ${h(T)} would come in on a round it does not count: it may be the same step under another name`,at:[...w.map(e=>`node:${e}`),`loop:${a.id}.members`]}),l&&l.stops.some(e=>e.kind===`budget`&&e.measure===`dispatches`)){let e=new Set(t.loops.filter(e=>e.id!==a.id).flatMap(e=>e.back)),r=s.filter(t=>t.edge!==void 0&&!e.has(t.edge)),o=e=>t=>r.filter(n=>e(n)[0]===t).map(t=>e(t)[1]),c=(e,t)=>{let n=new Set(e),r=[...n];for(let e=r.pop();e!==void 0;e=r.pop())for(let i of t(e))n.has(i)||r.push((n.add(i),i));return n},u=c(l.members,o(e=>[e.from,e.to])),d=c(l.members,o(e=>[e.to,e.from])),f=t.nodes.filter(e=>!i.has(e.id)&&(e.kind===`agent`||e.kind===`check`)&&!l.members.includes(e.id)&&u.has(e.id)&&d.has(e.id)).map(e=>e.id);f.length>0&&n.push({why:`${h(f)} would work on the rounds of the loop "${a.id}" and not be among its members: its budget of dispatches would not count ${f.length===1?`it`:`them`}`,at:[...f.map(e=>`node:${e}`),`loop:${a.id}.members`]})}if(l){let i=new Set(o.filter(e=>e.person).map(b)),s=x(e,a,i),c=[...new Set([...a.members.filter(e=>r.has(e)),...l.members])],m=0,g=x(t,{id:a.id,members:c,back:l.back},i),_=a.members.some(e=>[...g.closers.get(e)??[]].some(t=>!s.closers.get(e)?.has(t))),v=e=>e.edge!==void 0&&f.has(e.edge)&&(f.get(e.edge).from!==e.from||f.get(e.edge).to!==e.to),y=_?[...g.ways].filter(([e,t])=>!s.ways.has(e)||v(t)):[];for(let[,r]of y){let i=r.edge===void 0?void 0:f.get(r.edge),o=r.edge===void 0?[]:t.loops.filter(e=>e.back.includes(r.edge)),s=r.edge===void 0?e.loops.find(e=>e.id===r.loop)?.stops:void 0,c=r.edge===void 0?p(s,d.get(r.loop)?.stops)?[]:[`loop:${r.loop}.stops`,`loop:${r.loop}`]:i===void 0?[`edge:${r.edge}`]:[`from`,`to`,`when`].filter(e=>!p(i[e],u.get(r.edge)[e])).map(e=>`edge:${r.edge}.${e}`);c.length!==0&&(m+=1,n.push({why:r.edge===void 0?`a stop of the loop "${r.loop}" would lead back to "${r.to}": a way round the nodes of "${a.id}" that its stops do not count`:`"${r.edge}" (${r.from} → ${r.to}) would make a way round the nodes of "${a.id}" that its stops do not count${o.length>0?`: ${h(o.map(e=>e.id))} would count it, and each time round "${a.id}" starts afresh`:``}`,at:[...c,...o.flatMap(e=>[`loop:${e.id}`,`loop:${e.id}.back`])]}))}_&&m===0&&n.push({why:`there would be a way round the nodes of "${a.id}" that its stops do not count`,at:[`loop:${a.id}.members`,`loop:${a.id}.back`]})}}return n}function C(e,t){let a=[],o=new Map(t.nodes.map(e=>[e.id,e])),s=new Map(t.edges.map(e=>[e.id,e])),c=new Map(e.edges.map(e=>[e.id,e])),l=new Map(e.nodes.map(e=>[e.id,e]));for(let i of e.nodes){let c=o.get(i.id);if(i.kind===`human-gate`){if(!c)a.push({why:`removes a human gate`,at:[`node:${i.id}`]});else if(c.kind!==`human-gate`)a.push({why:`a human gate becomes another kind of node`,at:[`node:${i.id}.kind`]});else{let e=(i.options??[]).filter(e=>!(c.options??[]).includes(e));e.length>0&&a.push({why:`the gate would no longer offer ${h(e)}`,at:[`node:${i.id}.options`],gain:!0})}}if(g(i).length>0){let e=g(i).filter(e=>!g(c).includes(e));c?e.length>0&&a.push({why:`removes the irreversible marker ${h(e)}`,at:[`node:${i.id}.irreversible`,`node:${i.id}.kind`]}):a.push({why:`removes a node marked irreversible (${g(i).join(`, `)}): what takes its place carries no such mark unless it is given one`,at:[`node:${i.id}`],gain:!0})}if(i.kind===`stop`&&i.outcome===`halt`&&c?.kind===`stop`&&w(c)&&a.push({why:`the stop "${i.id}" would end in success where it halted: what led there to stop the run would now end it well`,at:[`node:${i.id}.outcome`]}),i.kind===`check`){let e=t.nodes.filter(e=>e.kind===`check`&&l.get(e.id)?.kind!==`check`),n=e.map(e=>`"${e.id}": ${e.check.run===void 0?`of kind "${e.check.kind}"`:`runs ${JSON.stringify(e.check.run)}`}, passes on ${JSON.stringify(e.check.pass)}${e.check.threshold===void 0?``:`, threshold ${e.check.threshold}`}`).join(`, and `),r=`while ${e.length===1?`a check`:`checks`} the graph has not ${e.length===1?`comes`:`come`} in (${n}): if that is this check under another id, allowing this`;if(!c&&e.length>0)a.push({why:`removes a check, ${r} name allows every change made to it and every way round it`,at:[`node:${i.id}`],swap:`removes a check`});else if(!c)a.push({why:`removes a check`,at:[`node:${i.id}`]});else if(c.kind!==`check`&&e.length>0)a.push({why:`a check becomes another kind of node, ${r} change allows every change made to it and every way round it`,at:[`node:${i.id}.kind`,`node:${i.id}.check`],swap:`a check becomes another kind of node`});else if(c.kind!==`check`)a.push({why:`a check becomes another kind of node`,at:[`node:${i.id}.kind`,`node:${i.id}.check`]});else{let[e,t]=[i.check,c.check],n=[...new Set([...Object.keys(e),...Object.keys(t)])].filter(n=>!p(e[n],t[n]));n.length>0&&a.push({why:`changes the check's definition (${n.join(`, `)}): what it runs and what counts as a pass may be tightened and not loosened, and a program cannot tell which this is`,at:[`node:${i.id}.check`]})}}if(r(i)){c?r(c)||a.push({why:c.kind===`agent`?`a critic is given another role`:`a critic becomes another kind of node`,at:[`node:${i.id}.role`,`node:${i.id}.kind`]}):a.push({why:`removes a critic`,at:[`node:${i.id}`]});let u=e=>e.edges.filter(e=>e.to===i.id),d=new Set(e.edges.map(e=>e.id));for(let n of c?u(e):[]){let e=s.get(n.id),r=new Set(t.edges.filter(e=>e.from===n.from&&!l.has(e.to)).map(e=>e.to)),c=e&&e.from===n.from&&e.to===i.id?[e]:u(t).filter(e=>!d.has(e.id)&&(!o.has(n.from)||e.from===n.from||r.has(e.from))),f=new Set(c.flatMap(e=>e.evidence??[])),p=(n.evidence??[]).filter(e=>!f.has(e));p.length>0&&a.push({why:`the critic would no longer be handed ${h(p)} by "${n.from}"`,at:[`edge:${n.id}.evidence`,`edge:${n.id}.to`,`edge:${n.id}.from`,`edge:${n.id}`,`node:${n.from}`]})}for(let r of u(t)){let t=e.edges.find(e=>e.id===r.id);n(r)===`shared`&&!(t&&t.to===i.id&&n(t)===`shared`)&&a.push({why:`the critic would share its builder's context`,at:[`edge:${r.id}.isolation`,`edge:${r.id}.to`,`edge:${r.id}`]})}}}for(let n of e.edges){let e=s.get(n.id);if(n.approval===!0){e?e.approval!==!0&&a.push({why:`removes a person's approval from the edge`,at:[`edge:${n.id}.approval`]}):a.push({why:`removes an edge that needs a person's approval`,at:[`edge:${n.id}`,`node:${n.from}`,`node:${n.to}`]});for(let r of e?t.edges:[]){if(r.id===n.id||r.from!==e.from||r.to!==e.to||r.approval===!0)continue;let t=c.get(r.id),i=t===void 0?[`edge:${r.id}`]:[`from`,`to`,`approval`].filter(e=>!p(t[e],r[e])).map(e=>`edge:${r.id}.${e}`);i.length>0&&a.push({why:`"${r.id}" would lead from "${r.from}" to "${r.to}" beside "${n.id}", which needs a person's approval, and need none`,at:i})}}let u=l.get(n.from);u?.kind===`human-gate`&&o.get(n.from)?.kind===`human-gate`&&!t.edges.some(e=>e.from===n.from&&i(e)===i(n))&&a.push({why:`"${i(n)}" at the human gate "${n.from}" would lead nowhere`,at:[`edge:${n.id}`,`edge:${n.id}.from`,`edge:${n.id}.when`,`node:${n.to}`],gain:!0}),e&&i(e)!==i(n)&&(n.approval===!0||u?.kind===`human-gate`?a.push({why:`changes what a person's answer leads to`,at:[`edge:${n.id}.when`]}):u&&r(u)&&a.push({why:`changes what the critic's verdict leads to`,at:[`edge:${n.id}.when`]}))}let u=new Map((t.policies??[]).map(e=>[e.id,e]));for(let t of e.policies??[]){let e=t.kind===`critic-isolation`?`removes critic isolation`:t.kind===`no-self-grading`?`lets a node grade its own work`:t.kind===`no-live-graph-rewrite`?`lets a run rewrite the graph where it could only propose`:void 0,n=u.get(t.id),r=n?[`kind`,`scope`,`params`].filter(e=>!p(n[e],t[e])):[];e===void 0||n&&r.length===0||a.push({why:e,at:n?r.map(e=>`policy:${t.id}.${e}`):[`policy:${t.id}`]})}return a}var w=e=>e.kind===`stop`&&(e.outcome??`success`)===`success`;function T(e,t,n){let r=new Map;for(let e of t)s(e,n)||(r.get(e.to)??r.set(e.to,[]).get(e.to)).push(e.from);let i=new Set(e.nodes.filter(w).map(e=>e.id)),a=[...i];for(let e=a.pop();e!==void 0;e=a.pop())for(let t of r.get(e)??[])i.has(t)||(i.add(t),a.push(t));return i}function E(e,t){let n=[],r=new Set(e.nodes.map(e=>e.id)),o=new Set(t.nodes.map(e=>e.id)),m=u(t,`every`);for(let e of t.nodes)!r.has(e.id)&&g(e).length>0&&m.has(e.id)&&n.push({why:`adds an irreversible step that a run reaches without a person`,at:[`node:${e.id}`]});let _=new Map(e.edges.map(e=>[e.id,e])),v=new Map(t.edges.map(e=>[e.id,e])),y=new Map(e.loops.map(e=>[e.id,e])),b=new Map(t.loops.map(e=>[e.id,e])),x=new Set(c(t)),S=new Set(e.loops.flatMap(e=>e.back)),C=a(t),E=a(e),D=new Set,O=new Set,k=new Map,A=t=>k.get(t)??k.set(t,l(e,[t])).get(t),j=[],M=(e,t,r)=>{let i=`${e!==`every`&&`critic`in e?e.critic:``}\n${t}\n${r.at.join(` `)}`;O.has(i)||(O.add(i),n.push(r))};for(let a of d(e,t)){let c=u(e,a),d=u(t,a),m=`that does not pass ${f(a)}`,g=`${a!==`every`&&`critic`in a?a.critic:``}\n`,k=e.nodes.filter(e=>!o.has(e.id)&&!c.has(e.id)&&!D.has(g+e.id)),N=t.nodes.filter(e=>!r.has(e.id)&&d.has(e.id));if(N.length>0)for(let e of k)D.add(g+e.id),n.push({why:`removes "${e.id}", which a run reached only by passing ${f(a)}, while ${h(N.map(e=>e.id))} would come in with no such need: it may be the same step under another name`,at:[`node:${e.id}`]});let P=0,F=e=>{if(e.edge!==void 0){let t=_.get(e.edge);if(!t)return[`edge:${e.edge}`];let n=[`from`,`to`,`approval`,`when`].filter(n=>!p(t[n],v.get(e.edge)[n]));return n.length>0?n.map(t=>`edge:${e.edge}.${t}`):E.some(t=>t.edge===e.edge&&s(t,a))?[`node:${e.from}.kind`,`node:${e.from}.role`]:void 0}let t=y.get(e.loop);if(!t)return[`loop:${e.loop}`];let n=[`stops`,`members`].filter(n=>!p(t[n],b.get(e.loop)[n]));return n.length>0?n.map(t=>`loop:${e.loop}.${t}`):void 0},I=(e,t)=>{P+=1,M(a,e,t)},L=new Set(t.nodes.filter(e=>r.has(e.id)&&!c.has(e.id)&&d.has(e.id)).map(e=>e.id));for(let e of L.size>0?C:[]){if(s(e,a)||!d.has(e.from)||!L.has(e.to))continue;let t=F(e);t&&(e.edge===void 0?I(e.to,{why:`a stop of the loop would lead on to "${e.to}", a way ${m}`,at:t}):I(e.to,{why:`${_.has(e.edge)?`opens`:`adds`} a way into "${e.to}" ${m}`,at:t}))}let R=a===`every`||!(`when`in a)||a.when===void 0,z=a===`every`?void 0:`gate`in a?a.gate:`critic`in a?a.critic:void 0;for(let n of R?t.nodes:[]){if(!r.has(n.id)||n.id===z)continue;let i=l(e,[n.id],a),o=l(t,[n.id],a),c=new Set([...o].filter(e=>r.has(e)&&!i.has(e)&&A(n.id).has(e)));for(let e of c.size>0?C:[]){if(e.from===e.to||s(e,a)||!o.has(e.from)||!c.has(e.to))continue;let t=F(e);if(!t)continue;let n=e.edge===void 0?`a stop of the loop would lead on from "${e.from}" to "${e.to}", a way ${m}`:`${_.has(e.edge)?`opens`:`adds`} a way from "${e.from}" to "${e.to}" ${m}`;j.push([a,e.to,{why:n,at:t}])}}for(let n of L){if(!x.has(n))continue;let r=`nothing would lead to "${n}", so a run would start there, where every way to it passed ${f(a)}`;for(let i of e.edges){if(i.to!==n||S.has(i.id))continue;let e=v.get(i.id);e&&e.to!==n?I(`start ${n}`,{why:r,at:[`edge:${i.id}.to`]}):e?I(`start ${n}`,{why:r,at:t.loops.filter(e=>e.back.includes(i.id)).flatMap(e=>[`loop:${e.id}.back`,`loop:${e.id}`])}):I(`start ${n}`,{why:r,at:[o.has(i.from)?`edge:${i.id}`:`node:${i.from}`]})}}let B=typeof a==`object`&&`critic`in a&&a.when!==void 0?new Set(e.edges.filter(e=>e.from===a.critic).map(i)):new Set,V=B.size>1&&!B.has(`pass`),H=R||V||typeof a==`object`&&`critic`in a&&a.when===`pass`,U=H?T(e,E,a):new Set,W=H?T(t,C,a):new Set,G=new Set(t.nodes.filter(e=>e.kind!==`stop`&&r.has(e.id)&&!U.has(e.id)&&W.has(e.id)).map(e=>e.id));for(let e of G.size>0?C:[]){if(s(e,a)||!G.has(e.from)||!W.has(e.to))continue;let t=F(e),n=`${a!==`every`&&`critic`in a?a.critic:``}\n`;t&&![...O].some(e=>e.startsWith(n)&&e.endsWith(`\n${t.join(` `)}`))?I(`ends ${e.to}`,{why:`adds a way from "${e.from}" to end in success ${m}`,at:t}):t&&(P+=1)}let K=new Set(e.nodes.filter(w).map(e=>e.id));for(let e of G.size>0?t.nodes:[])w(e)&&r.has(e.id)&&!K.has(e.id)&&W.has(e.id)&&I(`ends ${e.id}`,{why:`a run could end in success at "${e.id}", a way ${m}`,at:[`node:${e.id}.outcome`,`node:${e.id}.kind`]});P===0&&(L.size>0||G.size>0)&&n.push({why:L.size>0?`a run would reach ${h([...L])} by a way ${m}`:`a run could end in success from ${h([...G])} by a way ${m}`,at:[]})}return{losses:n,later:j.map(([,,e])=>e)}}function D(e,t){let n=[],r=new Set(a(e).filter(e=>e.person).map(b)),i=new Set(a(t).filter(e=>e.person).map(b)),o=new Set(t.nodes.map(e=>e.id)),s=new Set(t.nodes.filter(e=>e.kind===`human-gate`).map(e=>e.id));for(let a of e.loops){let c=t.loops.find(e=>e.id===a.id);if(!c||!(a.stops.some(_)||a.bar!==void 0))continue;let l={id:a.id,members:[...new Set([...a.members.filter(e=>o.has(e)),...c.members])],back:c.back},u=x(e,a,r),d=[...x(t,l,i).ways].filter(([e,t])=>t.person&&!r.has(e)&&!u.ways.has(e));if(d.length===0)continue;let f=[...new Set(d.map(([,e])=>e.edge===void 0?`the stop where a person is asked, which continues at "${e.to}"`:s.has(e.from)?`"${e.when}" at the human gate "${e.from}" (${e.edge})`:`the approval asked on "${e.edge}"`))],p=[...v(t,[c])].filter(([e])=>e!==`human`).map(([,e])=>`${e.name} (${e.halt??e.any}${e.unit})`);n.push(`the loop "${a.id}": ${p.length>0?p.join(` and `):`its stops`} would count the rounds between two of a person's decisions, and no longer the whole run. A way round its nodes that they do not count is opened each time by ${f.join(`, and by `)}`)}return n}function O(e,t){let n=[],r=`a program cannot tell which way this goes, so it is held either way, and an honest change (a gate put behind the check's pass, more evidence handed to a builder) costs one --allow too, the price of a rule a program can apply`,a=new Set(e.nodes.filter(e=>e.kind===`check`&&t.nodes.some(t=>t.id===e.id&&t.kind===`check`)).map(e=>e.id)),o=new Set(t.nodes.map(e=>e.id)),s=new Map(e.edges.map(e=>[e.id,e])),c=new Map(t.edges.map(e=>[e.id,e]));for(let t of e.edges){if(!a.has(t.from))continue;let e=c.get(t.id);if(!e){n.push({why:`removes "${t.id}", an edge that leaves the check "${t.from}": ${r}`,at:[o.has(t.to)?`edge:${t.id}`:`node:${t.to}`],either:!0});continue}for(let a of new Set([...Object.keys(t),...Object.keys(e)])){let o=a===`when`?i(t)!==i(e):!p(t[a],e[a]);a!==`id`&&o&&n.push({why:`changes "${t.id}", an edge that leaves the check "${t.from}" (${a}): ${r}`,at:[`edge:${t.id}.${a}`],either:!0})}}for(let e of t.edges){if(!a.has(e.from))continue;let t=s.get(e.id);t?t.from!==e.from&&n.push({why:`moves "${e.id}" to leave the check "${e.from}": ${r}`,at:[`edge:${e.id}.from`],either:!0}):n.push({why:`adds "${e.id}", an edge that leaves the check "${e.from}": ${r}`,at:[`edge:${e.id}`],either:!0})}return n}function k(e,t){let n=[],i=new Map(e.nodes.map(e=>[e.id,e])),a=new Map(e.loops.map(e=>[e.id,e])),o=e=>e?.stops.some(e=>e.kind===`bar-passed`)??!1;for(let e of t.loops){let s=a.get(e.id),c=e=>i.get(e),l=e.members.filter(e=>c(e)?.kind===`check`&&t.nodes.some(t=>t.id===e&&t.kind===`check`)),u=s?.members.some(e=>c(e)!==void 0&&r(c(e))&&t.nodes.some(t=>t.id===e&&r(t)))??!1;if(l.length===0||u)continue;let d=s?`the loop "${e.id}", which the check ${h(l)} judges with no critic`:`the new loop "${e.id}", around the check ${h(l)}`;e.bar!==void 0&&s?.bar===void 0&&n.push({why:`gives ${d}, a bar of its own: the lead would stop on the bar's words, a way out that does not pass the check`,at:s?[`loop:${e.id}.bar`]:[`loop:${e.id}`]}),o(e)&&!o(s)&&n.push({why:`adds a stop on "bar passed" to ${d}: a way out that does not pass the check`,at:s?[`loop:${e.id}.stops`]:[`loop:${e.id}`]})}return n}function A(e,t){let n=new Set,i=(e,t)=>e.nodes.some(e=>e.id===t&&r(e)),a=new Map(t.edges.map(e=>[e.id,e]));for(let r of e.edges){let o=a.get(r.id);if(!o)continue;r.approval!==!0&&o.approval===!0&&n.add(`edge:${r.id}.approval`);let[s,c]=[r.evidence??[],o.evidence??[]];o.to===r.to&&i(e,r.to)&&i(t,r.to)&&s.every(e=>c.includes(e))&&n.add(`edge:${r.id}.evidence`)}return n}function j(e,t){let n=[...M(e,t),...k(e,t)],r=O(e,t);if(r.length===0)return n;let i=new Set(n.flatMap(e=>e.at)),a=A(e,t);return[...n,...r.filter(e=>!(a.has(e.at[0])&&!i.has(e.at[0])))]}function M(e,t){let n=new Set,r=E(e,t),i=[...C(e,t),...S(e,t),...r.losses],a=new Set(i.flatMap(e=>e.at)),o=new Set,s=r.later.filter(e=>!e.at.some(e=>a.has(e))&&!o.has(e.at.join(` `))&&o.add(e.at.join(` `)));return[...i,...s].filter(e=>{let t=`${e.why}\n${e.at.join(` `)}`;return!n.has(t)&&(n.add(t),!0)})}var N=e=>JSON.stringify(e),P=[`fixed`,`propose`,`adaptive`],F=new Set([`grooph`,`id`,`name`,`version`,`lineage`,`notes`,`layout`,`nodes`,`edges`,`loops`,`policies`,`groups`]);function I(e,t){let n=[],r=[[`node`,e.nodes,t.nodes],[`edge`,e.edges,t.edges],[`loop`,e.loops,t.loops],[`policy`,e.policies??[],t.policies??[]],[`group`,e.groups??[],t.groups??[]]];for(let[e,t,i]of r){let r=new Map(i.map(e=>[e.id,e])),a=new Set(t.map(e=>e.id));for(let i of t){let t=r.get(i.id);if(!t){n.push({name:`${e}:${i.id}`,kind:`remove`});continue}for(let r of new Set([...Object.keys(i),...Object.keys(t)]))r!==`id`&&N(i[r])!==N(t[r])&&n.push({name:`${e}:${i.id}.${r}`,kind:`change`})}for(let t of i)a.has(t.id)||n.push({name:`${e}:${t.id}`,kind:`add`})}let i=e,a=t;for(let e of new Set([...Object.keys(i),...Object.keys(a)]))!F.has(e)&&N(i[e])!==N(a[e])&&n.push({name:`graph:${e}`,kind:`change`});return n}function L(e,n){let[r,i]=[t(e),t(n)];return P.indexOf(i)<=P.indexOf(r)?[]:[{why:`the adaptation level would go from "${r}" to "${i}": the lead may change more of the graph during a run`,at:N(e.adaptation)===N(n.adaptation)?[]:[`graph:adaptation`]}]}var R=(e,t)=>[...j(e,t),...L(e,t)];function z(e,t,n={}){let r=I(e,t),i=new Set(r.map(e=>e.name)),a=(e,t,n)=>{for(let a of e){if(t!==`loosens`&&a.either)continue;let e=new Set(a.at.filter(e=>i.has(e)));(e.size!==0||n)&&(r=r.map(n=>e.size>0&&!e.has(n.name)||m(n[t],a.why)||t!==`loosens`&&(m(n.loosens,a.why)||n.kind===`add`&&n.name.startsWith(`loop:`))?n:{...n,[t]:n[t]===void 0?a.why:`${n[t]}; ${a.why}`}))}},o=R(e,t);a(o,`loosens`,!0);let s=R(t,e),c=o.some(e=>e.swap);c?a(s.map(e=>e.swap?{...e,why:e.swap}:e),`unjudged`,!1):(a(s.filter(e=>!e.gain),`tightens`,!1),a(s.filter(e=>e.gain),`unjudged`,!1)),r.sort((e,t)=>Number(t.loosens!==void 0)-Number(e.loosens!==void 0));let l=new Set(n.allow??[]);return{changes:r,refused:r.filter(e=>e.loosens!==void 0&&!l.has(e.name)),unknown:[...l].filter(e=>!i.has(e)),notices:D(e,t),swapped:c}}function B(e,t,n){let r=e=>/^[A-Za-z0-9_.:/@=+-]+$/.test(e)?e:`'${e.replace(/'/g,`'\\''`)}'`;return[`grooph`,`adopt`,r(e),...n===void 0?[]:[`--into`,r(n)],`--write`,...t.flatMap(e=>[`--allow`,r(e)])].join(` `)}var V=e(),H=e=>()=>void navigator.clipboard?.writeText(e).catch(()=>void 0),U=e=>e?.scrollIntoView({block:`start`});function W(e,t,n){if(!n)return(0,V.jsx)(`p`,{className:`field-hint`,"data-brakes":`not-compared`,ref:U,children:`Its brakes were not compared with the graph's: the copy is not a valid graph. Open the copy to see why before you use it.`});let r=z(e,t);if(r.refused.length+r.notices.length===0)return null;let i=r.notices.map(e=>(0,V.jsxs)(`p`,{"data-brakes":`note`,children:[`Note: `,e]},e));return r.refused.length===0?(0,V.jsx)(`div`,{className:`field-hint`,"data-brakes":`said`,ref:U,children:i}):(0,V.jsxs)(`div`,{className:`refusal-hint`,"data-brakes":`proposed`,ref:U,children:[(0,V.jsxs)(`p`,{children:[(0,V.jsx)(`strong`,{children:`This copy loosens a brake the graph has.`}),` It is the run's working copy with this proposal applied; either may have done it, and it is yours to allow or not: read each line before you use the copy.`]}),(0,V.jsx)(`ul`,{className:`brakes-list`,"data-brakes":`loosens`,children:r.refused.map(e=>(0,V.jsxs)(`li`,{"data-change-name":e.name,children:[(0,V.jsx)(`span`,{className:`mono`,children:e.name}),` `,e.loosens]},e.name))}),i]})}function G(e,t,n){let r=z(e,t),i=r.changes.filter(e=>e.tightens!==void 0&&e.loosens===void 0),a=r.changes.filter(e=>e.unjudged!==void 0&&e.loosens===void 0),o=B(`.grooph/${e.id}/runs/${n}`,r.refused.map(e=>e.name)),s=(0,V.jsxs)(V.Fragment,{children:[i.length>0?(0,V.jsxs)(V.Fragment,{children:[(0,V.jsx)(`p`,{children:r.refused.length>0?`It also tightens a brake:`:`It tightens a brake:`}),(0,V.jsx)(`ul`,{className:`brakes-list`,"data-brakes":`tightens`,children:i.map(e=>(0,V.jsxs)(`li`,{"data-change-name":e.name,children:[(0,V.jsx)(`span`,{className:`mono`,children:e.name}),` undoing it: `,e.tightens]},e.name))})]}):null,a.length>0?(0,V.jsxs)(V.Fragment,{children:[(0,V.jsx)(`p`,{children:r.swapped?`With a check removed in this copy, no change is called a tightening. If the check that comes in is the same one under another id, these may be built around it:`:`An answer a gate did not give, or a step marked irreversible that the graph did not have, lets a person or a run do what it could not before. It is named here and not called a tightening:`}),(0,V.jsx)(`ul`,{className:`brakes-list`,"data-brakes":`unjudged`,children:a.map(e=>(0,V.jsxs)(`li`,{"data-change-name":e.name,children:[(0,V.jsx)(`span`,{className:`mono`,children:e.name}),` undoing it: `,e.unjudged]},e.name))})]}):null,r.notices.map(e=>(0,V.jsxs)(`p`,{"data-brakes":`note`,children:[`Note: `,e]},e))]});return r.refused.length===0?{ok:!0,view:i.length+a.length+r.notices.length>0?(0,V.jsx)(`div`,{className:`field-hint`,"data-brakes":`said`,children:s}):null}:{ok:!1,view:(0,V.jsxs)(`div`,{className:`refusal`,role:`alert`,"data-brakes":`refused`,children:[(0,V.jsxs)(`p`,{children:[(0,V.jsx)(`strong`,{children:`Not adopted: this working copy loosens a brake.`}),` A run may tighten a brake and never loosen one. Nothing was saved.`]}),(0,V.jsx)(`ul`,{className:`brakes-list`,"data-brakes":`loosens`,children:r.refused.map(e=>(0,V.jsxs)(`li`,{"data-change-name":e.name,children:[(0,V.jsx)(`span`,{className:`mono`,children:e.name}),` `,e.loosens]},e.name))}),(0,V.jsx)(`p`,{children:`To adopt it on purpose, run this in the project's folder. It names each change above:`}),(0,V.jsx)(`pre`,{className:`issue-lines`,"data-brakes":`command`,children:o}),(0,V.jsx)(`button`,{type:`button`,className:`btn`,onClick:H(o),children:`Copy the command`}),s]})}}export{G as judge,W as loosened};
|