@geonosis/ratchet 2.6.0 → 2.7.0
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/CHANGELOG.md +18 -0
- package/dist/{chunk-Z2T6MHHQ.js → chunk-GJD2676B.js} +69 -59
- package/dist/cli.js +3 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @geonosis/ratchet
|
|
2
2
|
|
|
3
|
+
## 2.7.0
|
|
4
|
+
|
|
5
|
+
## 2.6.1
|
|
6
|
+
|
|
7
|
+
### Patch Changes
|
|
8
|
+
|
|
9
|
+
- e1db50f: `geonosis-ratchet` reads `geonosis.ratchet.json` BEFORE it queues for a heavy slot. The slot
|
|
10
|
+
directory is machine-wide — it bounds heavy work on the box, not per repo — so a second repo's
|
|
11
|
+
`geonosis-verify` holds it legitimately, and a run with nothing to count was spending the resource
|
|
12
|
+
that bounds heavy work in order to discover it had no heavy work, waiting `GEONOSIS_HEAVY_WAIT_MS`
|
|
13
|
+
(20 minutes by default) to then exit 2. Measured 2026-09-07: with during.day's verify holding the
|
|
14
|
+
only slot on a memory-clamped one-slot box, this kit's own
|
|
15
|
+
`registry.test.ts > passes the child bin exit code through verbatim` — which spawns a ratchet into a
|
|
16
|
+
config-less directory expecting an instant refusal — sat for 979,800 ms against a 60,000 ms timeout,
|
|
17
|
+
three runs running. It now refuses in about a second with every slot held, and a bin case holds it
|
|
18
|
+
there: a config-less run must name `geonosis.ratchet.json` and must never say it is waiting.
|
|
19
|
+
- db89b0b: Two Stop-hook defects, both caught by making the instrument go red once. A heavy slot is inherited down a process tree: the holder marks its environment with the slot's path, and a descendant whose mark names a living ancestor's slot takes none of its own — a ratchet inside a verify waited on the slot its parent held, for ever, on a one-slot runner (CI's), and now waits on nobody; a tool that takes twice in one process still gets two. The Stop hook judges a run over a dirty tree: a tree's identity is HEAD plus every change on it, the hook's own files aside; a run launched over that identity is read at the next turn end and not re-run while the tree stands, where before an uncommitted edit meant every turn end launched the tier again and none was judged. The kit's own fast tier now builds first, so the hook never judges bins whose `dist` is older than the `src` under test.
|
|
20
|
+
|
|
3
21
|
## 2.6.0
|
|
4
22
|
|
|
5
23
|
### Minor Changes
|
|
@@ -1,6 +1,51 @@
|
|
|
1
|
+
// src/core/config.ts
|
|
2
|
+
import { existsSync, readFileSync } from "fs";
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
var CONFIG_FILE = "geonosis.ratchet.json";
|
|
5
|
+
var keyOf = (entry) => entry.key ?? entry.counter;
|
|
6
|
+
var loadConfig = (cwd) => {
|
|
7
|
+
const path = resolve(cwd, CONFIG_FILE);
|
|
8
|
+
if (!existsSync(path)) {
|
|
9
|
+
throw new Error(`no ${CONFIG_FILE} in ${cwd} \u2014 the ratchet has nothing to count`);
|
|
10
|
+
}
|
|
11
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
12
|
+
if (!Array.isArray(parsed.counters)) {
|
|
13
|
+
throw new Error(`${CONFIG_FILE} has no "counters" array`);
|
|
14
|
+
}
|
|
15
|
+
for (const entry of parsed.counters) {
|
|
16
|
+
if (typeof entry.counter !== "string") {
|
|
17
|
+
throw new Error(`${CONFIG_FILE}: every counter entry needs a "counter" id`);
|
|
18
|
+
}
|
|
19
|
+
if (entry.tiers !== void 0) {
|
|
20
|
+
const named = Array.isArray(entry.tiers) && entry.tiers.length > 0 && entry.tiers.every((tier) => typeof tier === "string" && tier.length > 0);
|
|
21
|
+
if (!named) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`${CONFIG_FILE}: "${keyOf(entry)}" has "tiers" that is not a non-empty list of tier names`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const keys = parsed.counters.map(keyOf);
|
|
29
|
+
const duplicate = keys.find((key, at) => keys.indexOf(key) !== at);
|
|
30
|
+
if (duplicate !== void 0) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`${CONFIG_FILE}: two counters both write "${duplicate}" \u2014 give one of them a distinct "key"`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return { baseline: parsed.baseline ?? "gate-baseline.json", counters: parsed.counters };
|
|
36
|
+
};
|
|
37
|
+
var resolveBaseline = (cwd) => {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(readFileSync(resolve(cwd, CONFIG_FILE), "utf8"));
|
|
40
|
+
return typeof parsed.baseline === "string" ? parsed.baseline : "gate-baseline.json";
|
|
41
|
+
} catch {
|
|
42
|
+
return "gate-baseline.json";
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
1
46
|
// src/core/envelope.ts
|
|
2
|
-
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
-
import { dirname, join, resolve } from "path";
|
|
47
|
+
import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
48
|
+
import { dirname, join, resolve as resolve2 } from "path";
|
|
4
49
|
import { fileURLToPath } from "url";
|
|
5
50
|
var ENVELOPES_DIR = ".geonosis/envelopes";
|
|
6
51
|
var envelopePath = (root, tool) => join(root, ENVELOPES_DIR, `${tool}.json`);
|
|
@@ -15,7 +60,7 @@ var unbalancedMessage = (envelope, next) => `${envelope.tool}: considered ${enve
|
|
|
15
60
|
var FORBIDDEN_ROOT = "GEONOSIS_ENVELOPES_FORBIDDEN_ROOT";
|
|
16
61
|
var refuseForbiddenRoot = (root) => {
|
|
17
62
|
const forbidden = process.env[FORBIDDEN_ROOT];
|
|
18
|
-
if (forbidden === void 0 ||
|
|
63
|
+
if (forbidden === void 0 || resolve2(forbidden) !== resolve2(root)) return;
|
|
19
64
|
throw new UnbalancedEnvelope(
|
|
20
65
|
`${root} is off limits to envelope writers in this process (${FORBIDDEN_ROOT}) \u2014 a run that writes one into a shared root races every other run reading it, and leaves a file the next one takes for real. Point this at a scratch root of its own: tooling/scratch-dir.ts.`
|
|
21
66
|
);
|
|
@@ -53,7 +98,7 @@ var writeEnvelope = ({
|
|
|
53
98
|
var UNKNOWN = "unknown";
|
|
54
99
|
var versionIn = (dir) => {
|
|
55
100
|
try {
|
|
56
|
-
const manifest = JSON.parse(
|
|
101
|
+
const manifest = JSON.parse(readFileSync2(join(dir, "package.json"), "utf8"));
|
|
57
102
|
return typeof manifest.version === "string" ? manifest.version : void 0;
|
|
58
103
|
} catch {
|
|
59
104
|
return void 0;
|
|
@@ -71,7 +116,7 @@ var versionOf = (moduleUrl) => {
|
|
|
71
116
|
};
|
|
72
117
|
|
|
73
118
|
// src/core/heavy-slot.ts
|
|
74
|
-
import { readFileSync as
|
|
119
|
+
import { readFileSync as readFileSync3, rmSync as rmSync2 } from "fs";
|
|
75
120
|
import { availableParallelism, freemem, tmpdir } from "os";
|
|
76
121
|
import { join as join2 } from "path";
|
|
77
122
|
|
|
@@ -106,7 +151,7 @@ var claimFile = (path, holder) => {
|
|
|
106
151
|
// src/core/heavy-slot.ts
|
|
107
152
|
var availableMemoryMb = () => {
|
|
108
153
|
try {
|
|
109
|
-
const found = /^MemAvailable:\s+(\d+) kB/m.exec(
|
|
154
|
+
const found = /^MemAvailable:\s+(\d+) kB/m.exec(readFileSync3("/proc/meminfo", "utf8"));
|
|
110
155
|
if (found?.[1] !== void 0) return Math.floor(Number(found[1]) / 1024);
|
|
111
156
|
} catch {
|
|
112
157
|
}
|
|
@@ -116,7 +161,7 @@ var DEFAULT_MEMORY_MB_PER_SLOT = 512;
|
|
|
116
161
|
var DEFAULT_HEAVY_SLOTS = Math.max(1, availableParallelism() - 1);
|
|
117
162
|
var perSlotMbDeclaredIn = (root) => {
|
|
118
163
|
try {
|
|
119
|
-
const parsed = JSON.parse(
|
|
164
|
+
const parsed = JSON.parse(readFileSync3(join2(root, "geonosis.json"), "utf8"));
|
|
120
165
|
const declared = parsed.stack?.memoryMbPerSlot;
|
|
121
166
|
return typeof declared === "number" && Number.isFinite(declared) && declared > 0 ? declared : void 0;
|
|
122
167
|
} catch {
|
|
@@ -149,7 +194,7 @@ var heavySlotDir = () => {
|
|
|
149
194
|
var slotPath = (dir, at) => join2(dir, `slot-${at}.json`);
|
|
150
195
|
var holderOf = (path) => {
|
|
151
196
|
try {
|
|
152
|
-
const parsed = JSON.parse(
|
|
197
|
+
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
153
198
|
return typeof parsed.pid === "number" ? {
|
|
154
199
|
command: parsed.command ?? "unknown",
|
|
155
200
|
cwd: parsed.cwd ?? "somewhere",
|
|
@@ -171,6 +216,13 @@ var namedHolders = (dir) => Array.from({ length: heavySlots() }, (_unused, at) =
|
|
|
171
216
|
var describeHolders = (holders) => holders.map((one) => `${one.pid} ${one.command} since ${one.startedAt}`).join(", ");
|
|
172
217
|
var HeavySlotTimeout = class extends Error {
|
|
173
218
|
};
|
|
219
|
+
var HEAVY_SLOT_HELD = "GEONOSIS_HEAVY_SLOT_HELD";
|
|
220
|
+
var inherited = () => {
|
|
221
|
+
const named = process.env[HEAVY_SLOT_HELD];
|
|
222
|
+
if (named === void 0 || named === "") return false;
|
|
223
|
+
const holder = holderOf(named);
|
|
224
|
+
return holder !== void 0 && holder.pid !== process.pid && alive(holder.pid);
|
|
225
|
+
};
|
|
174
226
|
var takeSlot = async ({
|
|
175
227
|
command,
|
|
176
228
|
cwd = process.cwd(),
|
|
@@ -180,6 +232,7 @@ var takeSlot = async ({
|
|
|
180
232
|
`),
|
|
181
233
|
waitMs = Number(process.env.GEONOSIS_HEAVY_WAIT_MS ?? "") || DEFAULT_WAIT_MS
|
|
182
234
|
}) => {
|
|
235
|
+
if (inherited()) return () => void 0;
|
|
183
236
|
const until = Date.now() + waitMs;
|
|
184
237
|
let told = false;
|
|
185
238
|
for (; ; ) {
|
|
@@ -193,8 +246,10 @@ var takeSlot = async ({
|
|
|
193
246
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
194
247
|
};
|
|
195
248
|
if (claim(path, mine)) {
|
|
249
|
+
process.env[HEAVY_SLOT_HELD] = path;
|
|
196
250
|
return () => {
|
|
197
251
|
if (holderOf(path)?.pid === process.pid) rmSync2(path, { force: true });
|
|
252
|
+
if (process.env[HEAVY_SLOT_HELD] === path) delete process.env[HEAVY_SLOT_HELD];
|
|
198
253
|
};
|
|
199
254
|
}
|
|
200
255
|
}
|
|
@@ -213,7 +268,7 @@ var takeSlot = async ({
|
|
|
213
268
|
};
|
|
214
269
|
|
|
215
270
|
// src/core/lock.ts
|
|
216
|
-
import { readFileSync as
|
|
271
|
+
import { readFileSync as readFileSync4, rmSync as rmSync3 } from "fs";
|
|
217
272
|
import { homedir } from "os";
|
|
218
273
|
import { isAbsolute, join as join3 } from "path";
|
|
219
274
|
var heavyLockPath = () => {
|
|
@@ -225,7 +280,7 @@ var heavyLockPath = () => {
|
|
|
225
280
|
};
|
|
226
281
|
var holderOf2 = (path) => {
|
|
227
282
|
try {
|
|
228
|
-
const parsed = JSON.parse(
|
|
283
|
+
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
229
284
|
return typeof parsed.pid === "number" ? {
|
|
230
285
|
cwd: parsed.cwd ?? "somewhere",
|
|
231
286
|
pid: parsed.pid,
|
|
@@ -280,51 +335,6 @@ var acquireExclusive = async ({
|
|
|
280
335
|
}
|
|
281
336
|
};
|
|
282
337
|
|
|
283
|
-
// src/core/config.ts
|
|
284
|
-
import { existsSync, readFileSync as readFileSync4 } from "fs";
|
|
285
|
-
import { resolve as resolve2 } from "path";
|
|
286
|
-
var CONFIG_FILE = "geonosis.ratchet.json";
|
|
287
|
-
var keyOf = (entry) => entry.key ?? entry.counter;
|
|
288
|
-
var loadConfig = (cwd) => {
|
|
289
|
-
const path = resolve2(cwd, CONFIG_FILE);
|
|
290
|
-
if (!existsSync(path)) {
|
|
291
|
-
throw new Error(`no ${CONFIG_FILE} in ${cwd} \u2014 the ratchet has nothing to count`);
|
|
292
|
-
}
|
|
293
|
-
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
294
|
-
if (!Array.isArray(parsed.counters)) {
|
|
295
|
-
throw new Error(`${CONFIG_FILE} has no "counters" array`);
|
|
296
|
-
}
|
|
297
|
-
for (const entry of parsed.counters) {
|
|
298
|
-
if (typeof entry.counter !== "string") {
|
|
299
|
-
throw new Error(`${CONFIG_FILE}: every counter entry needs a "counter" id`);
|
|
300
|
-
}
|
|
301
|
-
if (entry.tiers !== void 0) {
|
|
302
|
-
const named = Array.isArray(entry.tiers) && entry.tiers.length > 0 && entry.tiers.every((tier) => typeof tier === "string" && tier.length > 0);
|
|
303
|
-
if (!named) {
|
|
304
|
-
throw new Error(
|
|
305
|
-
`${CONFIG_FILE}: "${keyOf(entry)}" has "tiers" that is not a non-empty list of tier names`
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
const keys = parsed.counters.map(keyOf);
|
|
311
|
-
const duplicate = keys.find((key, at) => keys.indexOf(key) !== at);
|
|
312
|
-
if (duplicate !== void 0) {
|
|
313
|
-
throw new Error(
|
|
314
|
-
`${CONFIG_FILE}: two counters both write "${duplicate}" \u2014 give one of them a distinct "key"`
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
return { baseline: parsed.baseline ?? "gate-baseline.json", counters: parsed.counters };
|
|
318
|
-
};
|
|
319
|
-
var resolveBaseline = (cwd) => {
|
|
320
|
-
try {
|
|
321
|
-
const parsed = JSON.parse(readFileSync4(resolve2(cwd, CONFIG_FILE), "utf8"));
|
|
322
|
-
return typeof parsed.baseline === "string" ? parsed.baseline : "gate-baseline.json";
|
|
323
|
-
} catch {
|
|
324
|
-
return "gate-baseline.json";
|
|
325
|
-
}
|
|
326
|
-
};
|
|
327
|
-
|
|
328
338
|
// src/core/types.ts
|
|
329
339
|
var CounterError = class extends Error {
|
|
330
340
|
constructor(counter, message) {
|
|
@@ -2216,6 +2226,10 @@ var formatProve = ({ proofs, proven }) => {
|
|
|
2216
2226
|
};
|
|
2217
2227
|
|
|
2218
2228
|
export {
|
|
2229
|
+
CONFIG_FILE,
|
|
2230
|
+
keyOf,
|
|
2231
|
+
loadConfig,
|
|
2232
|
+
resolveBaseline,
|
|
2219
2233
|
ENVELOPES_DIR,
|
|
2220
2234
|
envelopePath,
|
|
2221
2235
|
UnbalancedEnvelope,
|
|
@@ -2233,10 +2247,6 @@ export {
|
|
|
2233
2247
|
takeSlot,
|
|
2234
2248
|
heavyLockPath,
|
|
2235
2249
|
acquireExclusive,
|
|
2236
|
-
CONFIG_FILE,
|
|
2237
|
-
keyOf,
|
|
2238
|
-
loadConfig,
|
|
2239
|
-
resolveBaseline,
|
|
2240
2250
|
CounterError,
|
|
2241
2251
|
runCommand,
|
|
2242
2252
|
runProve,
|
package/dist/cli.js
CHANGED
|
@@ -3,12 +3,13 @@ import {
|
|
|
3
3
|
acquireExclusive,
|
|
4
4
|
formatProve,
|
|
5
5
|
formatReport,
|
|
6
|
+
loadConfig,
|
|
6
7
|
runProve,
|
|
7
8
|
runRatchet,
|
|
8
9
|
takeSlot,
|
|
9
10
|
versionOf,
|
|
10
11
|
writeEnvelope
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-GJD2676B.js";
|
|
12
13
|
|
|
13
14
|
// src/cli.ts
|
|
14
15
|
import process from "process";
|
|
@@ -132,6 +133,7 @@ var measure = async () => {
|
|
|
132
133
|
` : formatProve(proof));
|
|
133
134
|
return proof.proven ? 0 : 2;
|
|
134
135
|
}
|
|
136
|
+
loadConfig(cwd);
|
|
135
137
|
releaseHeavySlot = await takeSlot({
|
|
136
138
|
command: `geonosis-ratchet${tier === void 0 ? "" : ` --tier ${tier}`}`,
|
|
137
139
|
cwd
|
package/dist/index.js
CHANGED