@opennodes/registry 0.1.8 → 0.1.9
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/package.json +1 -1
- package/src/stagec.js +25 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opennodes/registry",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "OpenNodes (ONP) reference registry: node registration with proof-of-resource qualification (Stage A/B/C), transparent ranking, enforceable estimates, catalog importers (OpenRouter, Hugging Face, models.dev, Ollama), MCP surface, and the web app (catalog, chat, advisor, Node Builder)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
package/src/stagec.js
CHANGED
|
@@ -13,12 +13,27 @@ export function startStageC(store, {
|
|
|
13
13
|
livenessIntervalMs = 300_000, // spec: <=5 min; tests shorten this
|
|
14
14
|
blindIntervalMs = 3_600_000,
|
|
15
15
|
failThreshold = 3,
|
|
16
|
+
recoverThreshold = 3, // consecutive liveness successes that lift a liveness suspension
|
|
16
17
|
} = {}) {
|
|
17
18
|
let stopped = false;
|
|
18
19
|
|
|
20
|
+
/**
|
|
21
|
+
* A suspension for liveness is temporary: the node is still probed, and after
|
|
22
|
+
* `recoverThreshold` consecutive successes it returns to the tier it held. Suspensions
|
|
23
|
+
* for any other reason (dispute, fraud, operator request) are never lifted here.
|
|
24
|
+
* Returns the tier to restore to, or null when the node is not eligible.
|
|
25
|
+
*/
|
|
26
|
+
async function livenessSuspension(node) {
|
|
27
|
+
if (node.state !== 'suspended') return null;
|
|
28
|
+
const last = [...(await store.transitionsFor(node.id))].reverse().find((t) => t.to_state === 'suspended');
|
|
29
|
+
if (!last || !String(last.reason ?? '').startsWith('liveness:')) return null;
|
|
30
|
+
return OBSERVED_STATES.includes(last.from_state) ? last.from_state : 'community';
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
async function livenessSweep() {
|
|
20
34
|
for (const node of await store.allNodes()) {
|
|
21
|
-
|
|
35
|
+
const restoreTo = await livenessSuspension(node);
|
|
36
|
+
if (!OBSERVED_STATES.includes(node.state) && !restoreTo) continue;
|
|
22
37
|
const card = await store.latestCard(node.id);
|
|
23
38
|
if (!card) continue;
|
|
24
39
|
let ok = false;
|
|
@@ -37,6 +52,15 @@ export function startStageC(store, {
|
|
|
37
52
|
ratio: recent.length ? Math.round((up / recent.length) * 1000) / 1000 : null,
|
|
38
53
|
});
|
|
39
54
|
|
|
55
|
+
if (restoreTo) {
|
|
56
|
+
let consecutiveOk = 0;
|
|
57
|
+
for (const p of recent) { if (!p.ok) break; consecutiveOk++; }
|
|
58
|
+
if (consecutiveOk >= recoverThreshold) {
|
|
59
|
+
await store.setState(node.id, restoreTo, `reinstated: ${consecutiveOk} consecutive liveness successes`);
|
|
60
|
+
await refreshCard?.(node.id); // it may have restarted with a new card revision
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
40
64
|
let consecutiveFails = 0;
|
|
41
65
|
for (const p of recent) { if (p.ok) break; consecutiveFails++; }
|
|
42
66
|
if (consecutiveFails >= failThreshold) {
|