0.1.12: retire a consumer the mesh stops asking for, after five stable passes, and wait for a person over the bound

The hourly release let a mistaken bulk unassignment drain one consumer an hour
with nobody deciding. Per novox/hq ADR 0230 a consumer is retired (disabled,
marked, data kept) only after the same unasked set in five consecutive passes;
more than 3, or more than half of those held, waits for approve or reject.
Deletion is a separate act of a person, of a retired consumer only.
This commit is contained in:
jochen
2026-10-06 13:47:51 +02:00
parent a6b7b566b0
commit e0453316b8
6 changed files with 675 additions and 135 deletions
+7 -55
View File
@@ -185,7 +185,7 @@ test("provisioner removes nothing while its contributions file cannot be used (n
removed.push(p.as);
},
},
{ receives, everyMs: 20, releaseEveryMs: 300 },
{ receives, everyMs: 20 },
);
await waitFor(() => created.length === 2, 2000);
@@ -203,65 +203,17 @@ test("provisioner removes nothing while its contributions file cannot be used (n
await new Promise((r) => setTimeout(r, 100));
}
// The file read and saying so still withdraws — within the brake (to-be 45 Phase 2): two at once is
// over its bound, so nothing goes at first, then one at a time as the brake lets them go.
// The file read and saying so is a result — but two of two held is over the bound (ADR 0230):
// nothing is retired without a person, and approving that exact set retires both.
await writeFile(receives, doc([]));
await new Promise((r) => setTimeout(r, 150));
assert.deepEqual(removed, [], "two consumers were withdrawn at once");
await waitFor(() => removed.length === 2, 3000);
for (let i = 0; i < 200 && (await stop.retirement()).waiting === null; i++) await new Promise((r) => setTimeout(r, 10));
assert.notEqual((await stop.retirement()).waiting, null, "the retirement never waited for a person");
assert.deepEqual(removed, [], "two of two consumers were retired without a person");
await stop.approve(["mesh_anchor_mail", "mesh_anchor_forge"], { why: "both unassigned on purpose", by: "test" });
assert.deepEqual([...removed].sort(), ["mesh_anchor_forge", "mesh_anchor_mail"]);
stop();
});
test("provisioner brakes a withdrawal of more than one consumer, and keeps one asked for again (to-be 45 Phase 2)", async () => {
const dir = await mkdtemp(join(tmpdir(), "prov-"));
const created: string[] = [];
const removed: string[] = [];
const receives = join(dir, "postgres-database.json");
const doc = (given: unknown[]): string =>
JSON.stringify({ contributions: 1, requirement: "postgres-database", given });
const seven: unknown[] = [];
for (let i = 1; i <= 7; i++) {
await writeFile(join(dir, `c${i}.secret`), `pw-${i}\n`);
seven.push({ from: `c${i}`, node: "anchor", as: `consumer${i}`, secret: join(dir, `c${i}.secret`) });
}
await writeFile(receives, doc(seven));
const said: string[] = [];
const was = console.error;
console.error = (...args: unknown[]) => said.push(args.join(" "));
const stop = runProvisioner(
"postgres-database",
{
async create(p) {
created.push(p.as);
},
async remove(p) {
removed.push(p.as);
},
},
{ receives, everyMs: 10, releaseEveryMs: 60_000 },
);
try {
await waitFor(() => created.length === 7, 2000);
// A file read whole that names nobody — the shape of 241 its first fix does not catch.
await writeFile(receives, doc([]));
await new Promise((r) => setTimeout(r, 200));
assert.deepEqual(removed, [], "a withdrawal of seven at once was not braked");
assert.ok(said.some((l) => l.includes("WITHDRAWAL BRAKED")), "the brake was not said");
// The file is right again: nothing was withdrawn, and nothing will be.
await writeFile(receives, doc(seven));
await new Promise((r) => setTimeout(r, 100));
assert.deepEqual(removed, []);
// One of seven goes as asked: within the bound.
await writeFile(receives, doc(seven.slice(1)));
await waitFor(() => removed.length === 1, 2000);
assert.deepEqual(removed, ["consumer1"]);
} finally {
stop();
console.error = was;
}
});
test("provisioner applies again what the backend no longer holds, and trusts memory without holds", async () => {
const dir = await mkdtemp(join(tmpdir(), "prov-holds-"));
await writeFile(join(dir, "webapp.secret"), "minted-pw\n");