@mmmbuto/nexuscrew 0.8.49 → 0.8.50

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.
@@ -11,7 +11,7 @@
11
11
  <meta name="apple-mobile-web-app-title" content="NexusCrew" />
12
12
  <link rel="manifest" href="/manifest.json" />
13
13
  <title>NexusCrew</title>
14
- <script type="module" crossorigin src="/assets/index-BZ1dY5k6.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-kj-l-RDc.js"></script>
15
15
  <link rel="stylesheet" crossorigin href="/assets/index-43DFO1EH.css">
16
16
  </head>
17
17
  <body>
@@ -1 +1 @@
1
- {"version":"0.8.49"}
1
+ {"version":"0.8.50"}
@@ -96,4 +96,28 @@ function summarizeStops(results) {
96
96
  return { stoppedAny, quarantinedAny, allClosed: stoppedAny && !quarantinedAny };
97
97
  }
98
98
 
99
- module.exports = { LEASE_MS, GRACE_MS, nextReadySlot, prepareRotation, abortPrepared, commitRotation, settleGrace, quarantineSlot, stopWasDemonstrated, summarizeStops };
99
+ // Esito di un giro di verifica del pool. `verified` richiede TUTTI gli slot:
100
+ // e' una scelta deliberata, perche' rotateRotatableReverse si rifiuta di
101
+ // commutare dentro un pool non interamente provato. La conseguenza pero' va
102
+ // detta, non dedotta: finche' il pool non e' verificato la rotazione
103
+ // automatica resta SPENTA e il watcher non viene nemmeno armato. Un pool
104
+ // "unverifiable" non e' un'etichetta descrittiva, e' autoriparazione assente.
105
+ function summarizePoolVerification(results, slotCount) {
106
+ const list = Array.isArray(results) ? results : [];
107
+ const total = Number.isSafeInteger(slotCount) && slotCount > 0 ? slotCount : 0;
108
+ const verifiedSlots = [...new Set(list.filter((r) => r && r.proven === true)
109
+ .map((r) => r.slot).filter(Number.isSafeInteger))].sort((a, b) => a - b);
110
+ const failures = list.filter((r) => r && r.proven !== true)
111
+ .map((r) => ({ slot: r.slot, code: typeof r.code === 'string' && /^[a-z0-9-]{1,64}$/.test(r.code) ? r.code : 'reverse-slot-verify-failed' }));
112
+ // Zero slot dichiarati non e' "tutto verificato": senza un pool da provare
113
+ // non c'e' nulla da cui ruotare.
114
+ const verified = total > 0 && verifiedSlots.length === total;
115
+ return {
116
+ verifiedSlots,
117
+ failures,
118
+ verification: verified ? 'verified' : 'unverifiable',
119
+ rotationActive: verified,
120
+ };
121
+ }
122
+
123
+ module.exports = { LEASE_MS, GRACE_MS, nextReadySlot, prepareRotation, abortPrepared, commitRotation, settleGrace, quarantineSlot, stopWasDemonstrated, summarizeStops, summarizePoolVerification };
package/lib/server.js CHANGED
@@ -267,6 +267,10 @@ function createServer(opts = {}) {
267
267
  async function verifyRotatablePool(node) {
268
268
  const pool = node?.reversePool;
269
269
  if (!pool || !node.shared || !node.token || !node.nodeId) return { verified: false, code: 'reverse-pool-not-ready' };
270
+ // Fermarsi al primo slot che non si prova nasconde quali altri fossero
271
+ // sani: l'esito e' identico (servono tutti per 'verified'), ma la diagnosi
272
+ // no. Chi guarda deve poter distinguere "un solo slot non risponde" da
273
+ // "non risponde nessuno", perche' sono due guasti diversi.
270
274
  const proven = [];
271
275
  for (let slot = 0; slot < pool.slots.length; slot += 1) {
272
276
  const candidate = pool.slots[slot];
@@ -275,9 +279,9 @@ function createServer(opts = {}) {
275
279
  await startRotatableReverse(node, { slot, generation: candidate.generation });
276
280
  temporary = slot !== pool.activeSlot;
277
281
  await federation.verifyHubPoolSlot({ node, slot, generation: candidate.generation, fetchImpl: healthFetch });
278
- proven.push(slot);
279
- } catch (_) {
280
- break;
282
+ proven.push({ slot, proven: true });
283
+ } catch (error) {
284
+ proven.push({ slot, proven: false, code: error && error.code });
281
285
  } finally {
282
286
  if (temporary) await stopRotatableReverse(node.name, { remotePort: candidate.port, generation: candidate.generation });
283
287
  }
@@ -285,15 +289,27 @@ function createServer(opts = {}) {
285
289
  const current = nodesStore.loadStoreStrict(nodesPath);
286
290
  const fresh = nodesStore.getNode(current, node.name);
287
291
  if (!fresh?.reversePool) return { verified: false, code: 'reverse-pool-missing' };
288
- const verifiedSlots = [...new Set(proven)].sort((a, b) => a - b);
289
- const verification = verifiedSlots.length === fresh.reversePool.slots.length ? 'verified' : 'unverifiable';
292
+ const outcome = reverseRotation.summarizePoolVerification(proven, fresh.reversePool.slots.length);
293
+ const { verifiedSlots, verification, failures } = outcome;
290
294
  const updatedPool = { ...fresh.reversePool, verifiedSlots, verification };
291
295
  nodesStore.atomicWriteStore(nodesPath, nodesStore.setNodeReversePool(current, fresh.name, updatedPool));
292
296
  diagnostics.record(verification === 'verified' ? 'info' : 'warn', 'reverse-pool',
293
297
  verification === 'verified' ? 'REVERSE_POOL_VERIFIED' : 'REVERSE_POOL_UNVERIFIABLE',
294
- verification === 'verified' ? 'Reverse pool verified' : 'Reverse pool could not be fully verified', { node: fresh.name, verifiedSlots: verifiedSlots.length });
298
+ verification === 'verified' ? 'Reverse pool verified' : 'Reverse pool could not be fully verified',
299
+ { node: fresh.name, verifiedSlots: verifiedSlots.length, slots: fresh.reversePool.slots.length,
300
+ ...(failures.length ? { failedSlots: failures } : {}) });
301
+ // Un pool non verificato non e' solo un'etichetta: la rotazione automatica
302
+ // RIFIUTA di partire senza tutti gli slot provati (scelta deliberata, vedi
303
+ // rotateRotatableReverse) e il watcher non viene nemmeno armato. Detto
304
+ // altrimenti: l'autoriparazione del canale reverse resta SPENTA, e finora
305
+ // nessuno lo diceva. Va dichiarato, non dedotto da un aggettivo.
306
+ if (verification !== 'verified') {
307
+ diagnostics.record('warn', 'reverse-pool', 'REVERSE_POOL_ROTATION_INACTIVE',
308
+ 'Automatic reverse rotation is not active: the pool is not fully verified',
309
+ { node: fresh.name, verifiedSlots: verifiedSlots.length, slots: fresh.reversePool.slots.length });
310
+ }
295
311
  if (verification === 'verified') ensureReverseWatcher(fresh.name);
296
- return { verified: verification === 'verified', verification, verifiedSlots };
312
+ return { verified: verification === 'verified', verification, verifiedSlots, ...(failures.length ? { failures } : {}) };
297
313
  }
298
314
  async function settleRotatableGrace(name, generation) {
299
315
  const current = nodesStore.loadStoreStrict(nodesPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmmbuto/nexuscrew",
3
- "version": "0.8.49",
3
+ "version": "0.8.50",
4
4
  "description": "Faithful browser tmux client — attach to live sessions over a real PTY, localhost-only, mobile-easy",
5
5
  "main": "lib/server.js",
6
6
  "bin": {