mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-17 20:32:25 +03:00
The eighteen commits main carries beyond the cycle branch, and what each one
became here:
already in release/v3.8.51 by its own PR (no-op, verified by content):
b090b601a5 / 026e1cadaa deps: nanoid 3.3.18 equal, dompurify 3.4.14 newer
918fba5e39 .gitignore: /_tasks already anchored
5f0a394091 #10026 hide health-check-excluded models — same helper, 5 call sites
c68cda7dfb #11075 shared passthrough providers — superseded by #11071/#11078
superseded, one piece kept:
ca23eed77c #10055 memoize models.dev pricing — the cycle memoizes on the
catalog cache version already; only the resetDbInstance() hook is
ported, wired to that memo
applied as-is:
8778ea7d18 stamp dist/BUILD_SHA before the npm provenance gate (#11721)
aa52351113 decouple the Bun image from the release manifest (#11724)
925feb27b8 let the bun digest artifact be absent (#11740)
b65ef333da size the install-upgrade gate to a measured run
0ce21232db #11845 converge install/upgrade schemas (migration renumbered in
the next commit: 163 collides with 163_radar_feed_cache_generated_at)
b7c07edad8 #11855 install-upgrade gate on disk, not tmpfs
8e2fb04329 #11864 drop *.nft.json from the npm tarball (413)
dea6bb8b6b #11877 publish npm from a hosted runner (provenance 422)
handled by the sync script that follows (CHANGELOG protocol):
b4ec7807ab Release v3.8.50 — squash of content this branch already carries
5458026c21 / c44c0a29e8 CHANGELOG aggregation, stats and top-25
applied separately (its own commit, ten files):
65e81158ab #11088 Ollama capability routing — a 5,094-file squash from a
stale base; only the Ollama files are the change
Every cherry-pick that touched a file this branch had also changed was
resolved by hand and re-run through the tests both sides own for it.
143 lines
5.4 KiB
TypeScript
143 lines
5.4 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
// @ts-expect-error — plain .mjs gate script, no type declarations by design
|
|
import {
|
|
assertNoDiskExhaustion,
|
|
evaluateConvergence,
|
|
} from "../../scripts/check/check-install-upgrade.mjs";
|
|
|
|
/**
|
|
* The whole point of this gate is that the two directions of schema divergence are NOT
|
|
* equivalent, and the cheap symmetric check ("do the table sets match?") would either
|
|
* block every release on harmless residue or let a real upgrade bug through.
|
|
*
|
|
* Measured on 2026-07-30: a real 3.8.48 install on VPS .16 upgraded to 3.8.49 ended with
|
|
* 117 tables while a clean 3.8.49 install had 116 — the extra one being `cache_metrics`.
|
|
*/
|
|
|
|
test("converged schemas pass", () => {
|
|
const v = evaluateConvergence({
|
|
freshTables: ["a", "b", "c"],
|
|
upgradedTables: ["c", "b", "a"],
|
|
});
|
|
assert.equal(v.ok, true);
|
|
assert.deepEqual(v.failures, []);
|
|
assert.deepEqual(v.onlyFresh, []);
|
|
assert.deepEqual(v.onlyUpgraded, []);
|
|
});
|
|
|
|
test("a table only a CLEAN install creates is ALWAYS a failure — upgraders lack structure", () => {
|
|
const v = evaluateConvergence({
|
|
freshTables: ["a", "b", "new_feature_table"],
|
|
upgradedTables: ["a", "b"],
|
|
});
|
|
assert.equal(v.ok, false);
|
|
assert.deepEqual(v.onlyFresh, ["new_feature_table"]);
|
|
assert.match(v.failures[0], /CLEAN install creates but an UPGRADE does not/);
|
|
assert.match(v.failures[0], /new_feature_table/);
|
|
});
|
|
|
|
test("that direction cannot be silenced by the residual allowlist", () => {
|
|
const v = evaluateConvergence({
|
|
freshTables: ["a", "missing_on_upgrade"],
|
|
upgradedTables: ["a"],
|
|
// Even if someone lists it here, the dangerous direction must still fail.
|
|
residualAllowlist: { missing_on_upgrade: "please ignore me" },
|
|
});
|
|
assert.equal(v.ok, false);
|
|
assert.deepEqual(v.onlyFresh, ["missing_on_upgrade"]);
|
|
});
|
|
|
|
test("known residue (cache_metrics, the real 3.8.48→3.8.49 finding) passes but is reported", () => {
|
|
const v = evaluateConvergence({
|
|
freshTables: ["a", "b"],
|
|
upgradedTables: ["a", "b", "cache_metrics"],
|
|
residualAllowlist: { cache_metrics: "measured 2026-07-30 on VPS .16" },
|
|
});
|
|
assert.equal(v.ok, true, "allowlisted residue must not block a release");
|
|
assert.deepEqual(v.onlyUpgraded, ["cache_metrics"], "still surfaced so it stays visible");
|
|
assert.deepEqual(v.unknownResidue, []);
|
|
});
|
|
|
|
test("UNKNOWN residue fails — a new divergence must not hide behind the allowlist", () => {
|
|
const v = evaluateConvergence({
|
|
freshTables: ["a"],
|
|
upgradedTables: ["a", "cache_metrics", "surprise_table"],
|
|
residualAllowlist: { cache_metrics: "known" },
|
|
});
|
|
assert.equal(v.ok, false);
|
|
assert.deepEqual(v.unknownResidue, ["surprise_table"]);
|
|
assert.match(v.failures[0], /surprise_table/);
|
|
assert.doesNotMatch(
|
|
v.failures[0],
|
|
/cache_metrics/,
|
|
"the known one must not be re-reported as new"
|
|
);
|
|
});
|
|
|
|
test("both directions at once report both failures", () => {
|
|
const v = evaluateConvergence({
|
|
freshTables: ["shared", "only_fresh"],
|
|
upgradedTables: ["shared", "only_upgraded"],
|
|
});
|
|
assert.equal(v.ok, false);
|
|
assert.equal(v.failures.length, 2);
|
|
assert.deepEqual(v.onlyFresh, ["only_fresh"]);
|
|
assert.deepEqual(v.unknownResidue, ["only_upgraded"]);
|
|
});
|
|
|
|
test("accepts Sets as well as arrays (the gate passes Sets from sqlite_master)", () => {
|
|
const v = evaluateConvergence({
|
|
freshTables: new Set(["a", "b"]),
|
|
upgradedTables: new Set(["a", "b"]),
|
|
});
|
|
assert.equal(v.ok, true);
|
|
});
|
|
|
|
test("empty/missing inputs do not crash", () => {
|
|
const v = evaluateConvergence({ freshTables: undefined, upgradedTables: undefined });
|
|
assert.equal(v.ok, true);
|
|
assert.deepEqual(v.onlyFresh, []);
|
|
});
|
|
|
|
// ─── ENOSPC guard ──────────────────────────────────────────────────────────────
|
|
//
|
|
// The v3.8.50 publish run (CI 33104507735) failed with "15 tables a CLEAN install creates
|
|
// but an UPGRADE does not". None of them was missing: the Phase B upgrade install had hit
|
|
// `npm warn tar TAR_ENTRY_ERROR ENOSPC: no space left on device` 5611 times, npm still
|
|
// exited 0, the truncated `omniroute serve` "exited with code 0 before serving", and the
|
|
// database therefore still held the 3.8.49 schema. npm reporting disk exhaustion as a
|
|
// warning is what let a full disk masquerade as a schema defect.
|
|
|
|
test("npm ENOSPC warnings are raised as an install failure, not ignored", () => {
|
|
const enospc = "npm warn tar TAR_ENTRY_ERROR ENOSPC: no space left on device, write\n".repeat(3);
|
|
assert.throws(
|
|
() => assertNoDiskExhaustion(enospc, "upgrade install"),
|
|
(err: Error) => {
|
|
assert.match(err.message, /upgrade install/);
|
|
assert.match(err.message, /ran out of disk space/);
|
|
assert.match(err.message, /3 ENOSPC error/);
|
|
// The operator must not go looking for a migration that is not missing.
|
|
assert.match(err.message, /NOT a schema divergence/);
|
|
return true;
|
|
}
|
|
);
|
|
});
|
|
|
|
test("a clean install log does not trip the disk guard", () => {
|
|
assert.doesNotThrow(() =>
|
|
assertNoDiskExhaustion(
|
|
"npm warn deprecated boolean@3.2.0: Package no longer supported.\nadded 900 packages\n",
|
|
"clean install"
|
|
)
|
|
);
|
|
});
|
|
|
|
test("the guard also catches the bare kernel message without the ENOSPC code", () => {
|
|
assert.throws(
|
|
() => assertNoDiskExhaustion("Error: no space left on device", "clean install"),
|
|
/ran out of disk space/
|
|
);
|
|
});
|