instar 1.3.101 → 1.3.102
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/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +32 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PeerPresencePuller.d.ts +71 -0
- package/dist/core/PeerPresencePuller.d.ts.map +1 -0
- package/dist/core/PeerPresencePuller.js +59 -0
- package/dist/core/PeerPresencePuller.js.map +1 -0
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.102.md +56 -0
- package/upgrades/side-effects/http-presence-transport.md +75 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PeerPresencePuller (Multi-Machine Session Pool — HTTP presence transport).
|
|
3
|
+
*
|
|
4
|
+
* The pool registry's notion of which peer machines are ONLINE was originally
|
|
5
|
+
* fed only by the git-synced MachineHeartbeat: each machine writes its heartbeat
|
|
6
|
+
* into the shared agent repo, peers pull it, and recency ⇒ online. That breaks
|
|
7
|
+
* for a *credential-less standby* — a machine paired into the mesh over HTTP but
|
|
8
|
+
* WITHOUT push access to the shared repo (e.g. a second machine gh-authed to a
|
|
9
|
+
* different account). Its heartbeat never reaches the router, so the router
|
|
10
|
+
* marks it offline and the placement engine refuses to transfer to it, even
|
|
11
|
+
* though the two machines can reach each other perfectly over their tunnels.
|
|
12
|
+
*
|
|
13
|
+
* This puller closes that gap with a pull-based HTTP presence channel: on a
|
|
14
|
+
* cadence, ask each reachable peer for its self-capacity over the signed
|
|
15
|
+
* /mesh/rpc `session-status` command (a read-class command any registered peer
|
|
16
|
+
* may issue) and record the answer into the local pool registry. A peer that
|
|
17
|
+
* answers is, by definition, reachable + alive — so it goes online without ever
|
|
18
|
+
* touching git. Symmetric by design: every mesh machine runs one, so each keeps
|
|
19
|
+
* its own HTTP-sourced view of peer liveness, parallel to (and idempotent with)
|
|
20
|
+
* the git-synced path for credentialed peers.
|
|
21
|
+
*
|
|
22
|
+
* Fully injected (peers, fetch, record, clock) so the loop logic is unit-testable
|
|
23
|
+
* without a live mesh. `pullOnce()` NEVER throws — an unreachable peer simply is
|
|
24
|
+
* not recorded this pass and ages out of `online` naturally via the registry's
|
|
25
|
+
* failover threshold.
|
|
26
|
+
*/
|
|
27
|
+
/** A peer to consider polling: its machine id + resolved tunnel URL (null ⇒ unreachable). */
|
|
28
|
+
export interface PeerPresenceMachine {
|
|
29
|
+
machineId: string;
|
|
30
|
+
url: string | null;
|
|
31
|
+
}
|
|
32
|
+
/** The slice of a peer's self-capacity the puller records (from `session-status`). */
|
|
33
|
+
export interface PeerCapacity {
|
|
34
|
+
selfReportedLastSeen?: string;
|
|
35
|
+
loadAvg?: number;
|
|
36
|
+
}
|
|
37
|
+
export interface PeerPresencePullerDeps {
|
|
38
|
+
/** This machine's id — never pulled (a machine doesn't poll itself). */
|
|
39
|
+
selfMachineId: string;
|
|
40
|
+
/** The current mesh peers (id + resolved URL). Re-read each pass so newly paired peers are picked up. */
|
|
41
|
+
listPeers: () => PeerPresenceMachine[];
|
|
42
|
+
/**
|
|
43
|
+
* Send a signed `session-status` read to a peer and resolve its self-capacity,
|
|
44
|
+
* or `null` if the peer did not answer / rejected. May reject; `pullOnce`
|
|
45
|
+
* treats a rejection identically to a `null` (peer not recorded this pass).
|
|
46
|
+
*/
|
|
47
|
+
fetchPeerCapacity: (machineId: string, url: string) => Promise<PeerCapacity | null>;
|
|
48
|
+
/** Record an observed peer heartbeat into the pool registry (marks it online for the failover window). */
|
|
49
|
+
recordHeartbeat: (obs: {
|
|
50
|
+
machineId: string;
|
|
51
|
+
selfReportedLastSeen: string;
|
|
52
|
+
loadAvg?: number;
|
|
53
|
+
}) => void;
|
|
54
|
+
/** Wall clock — injectable for tests. Defaults to `Date`. */
|
|
55
|
+
now?: () => Date;
|
|
56
|
+
/** Optional structured log line per pass (e.g. for the boot log). */
|
|
57
|
+
log?: (line: string) => void;
|
|
58
|
+
}
|
|
59
|
+
export declare class PeerPresencePuller {
|
|
60
|
+
private readonly d;
|
|
61
|
+
constructor(d: PeerPresencePullerDeps);
|
|
62
|
+
/**
|
|
63
|
+
* One presence pass: poll every reachable peer in parallel and record any that
|
|
64
|
+
* answer into the pool registry. Resolves with the ids recorded this pass.
|
|
65
|
+
* Never rejects.
|
|
66
|
+
*/
|
|
67
|
+
pullOnce(): Promise<{
|
|
68
|
+
recorded: string[];
|
|
69
|
+
}>;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=PeerPresencePuller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PeerPresencePuller.d.ts","sourceRoot":"","sources":["../../src/core/PeerPresencePuller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,6FAA6F;AAC7F,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,sFAAsF;AACtF,MAAM,WAAW,YAAY;IAC3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,wEAAwE;IACxE,aAAa,EAAE,MAAM,CAAC;IACtB,yGAAyG;IACzG,SAAS,EAAE,MAAM,mBAAmB,EAAE,CAAC;IACvC;;;;OAIG;IACH,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IACpF,0GAA0G;IAC1G,eAAe,EAAE,CAAC,GAAG,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,oBAAoB,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACtG,6DAA6D;IAC7D,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,qEAAqE;IACrE,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,qBAAa,kBAAkB;IACjB,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAAD,CAAC,EAAE,sBAAsB;IAEtD;;;;OAIG;IACG,QAAQ,IAAI,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CAoBlD"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PeerPresencePuller (Multi-Machine Session Pool — HTTP presence transport).
|
|
3
|
+
*
|
|
4
|
+
* The pool registry's notion of which peer machines are ONLINE was originally
|
|
5
|
+
* fed only by the git-synced MachineHeartbeat: each machine writes its heartbeat
|
|
6
|
+
* into the shared agent repo, peers pull it, and recency ⇒ online. That breaks
|
|
7
|
+
* for a *credential-less standby* — a machine paired into the mesh over HTTP but
|
|
8
|
+
* WITHOUT push access to the shared repo (e.g. a second machine gh-authed to a
|
|
9
|
+
* different account). Its heartbeat never reaches the router, so the router
|
|
10
|
+
* marks it offline and the placement engine refuses to transfer to it, even
|
|
11
|
+
* though the two machines can reach each other perfectly over their tunnels.
|
|
12
|
+
*
|
|
13
|
+
* This puller closes that gap with a pull-based HTTP presence channel: on a
|
|
14
|
+
* cadence, ask each reachable peer for its self-capacity over the signed
|
|
15
|
+
* /mesh/rpc `session-status` command (a read-class command any registered peer
|
|
16
|
+
* may issue) and record the answer into the local pool registry. A peer that
|
|
17
|
+
* answers is, by definition, reachable + alive — so it goes online without ever
|
|
18
|
+
* touching git. Symmetric by design: every mesh machine runs one, so each keeps
|
|
19
|
+
* its own HTTP-sourced view of peer liveness, parallel to (and idempotent with)
|
|
20
|
+
* the git-synced path for credentialed peers.
|
|
21
|
+
*
|
|
22
|
+
* Fully injected (peers, fetch, record, clock) so the loop logic is unit-testable
|
|
23
|
+
* without a live mesh. `pullOnce()` NEVER throws — an unreachable peer simply is
|
|
24
|
+
* not recorded this pass and ages out of `online` naturally via the registry's
|
|
25
|
+
* failover threshold.
|
|
26
|
+
*/
|
|
27
|
+
export class PeerPresencePuller {
|
|
28
|
+
d;
|
|
29
|
+
constructor(d) {
|
|
30
|
+
this.d = d;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* One presence pass: poll every reachable peer in parallel and record any that
|
|
34
|
+
* answer into the pool registry. Resolves with the ids recorded this pass.
|
|
35
|
+
* Never rejects.
|
|
36
|
+
*/
|
|
37
|
+
async pullOnce() {
|
|
38
|
+
const peers = this.d.listPeers().filter((m) => m.machineId !== this.d.selfMachineId && !!m.url);
|
|
39
|
+
const results = await Promise.all(peers.map(async (m) => {
|
|
40
|
+
let cap = null;
|
|
41
|
+
try {
|
|
42
|
+
cap = await this.d.fetchPeerCapacity(m.machineId, m.url);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
cap = null; // unreachable / rejected → not recorded; ages out of online
|
|
46
|
+
}
|
|
47
|
+
if (!cap)
|
|
48
|
+
return null;
|
|
49
|
+
const seen = cap.selfReportedLastSeen ?? (this.d.now?.() ?? new Date()).toISOString();
|
|
50
|
+
this.d.recordHeartbeat({ machineId: m.machineId, selfReportedLastSeen: seen, loadAvg: cap.loadAvg });
|
|
51
|
+
return m.machineId;
|
|
52
|
+
}));
|
|
53
|
+
const recorded = results.filter((r) => r !== null);
|
|
54
|
+
if (recorded.length && this.d.log)
|
|
55
|
+
this.d.log(`recorded ${recorded.length} peer(s) online over HTTP: ${recorded.join(', ')}`);
|
|
56
|
+
return { recorded };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=PeerPresencePuller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PeerPresencePuller.js","sourceRoot":"","sources":["../../src/core/PeerPresencePuller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAiCH,MAAM,OAAO,kBAAkB;IACA;IAA7B,YAA6B,CAAyB;QAAzB,MAAC,GAAD,CAAC,CAAwB;IAAG,CAAC;IAE1D;;;;OAIG;IACH,KAAK,CAAC,QAAQ;QACZ,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAChG,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAA0B,EAAE;YAC5C,IAAI,GAAG,GAAwB,IAAI,CAAC;YACpC,IAAI,CAAC;gBACH,GAAG,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,GAAa,CAAC,CAAC;YACrE,CAAC;YAAC,MAAM,CAAC;gBACP,GAAG,GAAG,IAAI,CAAC,CAAC,4DAA4D;YAC1E,CAAC;YACD,IAAI,CAAC,GAAG;gBAAE,OAAO,IAAI,CAAC;YACtB,MAAM,IAAI,GAAG,GAAG,CAAC,oBAAoB,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;YACtF,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,oBAAoB,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACrG,OAAO,CAAC,CAAC,SAAS,CAAC;QACrB,CAAC,CAAC,CACH,CAAC;QACF,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;QAChE,IAAI,QAAQ,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG;YAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,QAAQ,CAAC,MAAM,8BAA8B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC9H,OAAO,EAAE,QAAQ,EAAE,CAAC;IACtB,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-05-29T23:
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-05-29T23:18:48.200Z",
|
|
5
|
+
"instarVersion": "1.3.102",
|
|
6
6
|
"entryCount": 193,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- bump: patch -->
|
|
4
|
+
|
|
5
|
+
## What Changed
|
|
6
|
+
|
|
7
|
+
**Multi-machine: a second machine that can't push to your shared agent repo now
|
|
8
|
+
still shows up as online — so work can move to it.** When you run one agent
|
|
9
|
+
across two machines, the pool decides which machine is "online" (and therefore
|
|
10
|
+
eligible to receive a conversation) from a heartbeat. Until now that heartbeat
|
|
11
|
+
travelled through the shared agent git repo: each machine wrote its heartbeat
|
|
12
|
+
into the repo and the others read it. A second machine paired over the network
|
|
13
|
+
but signed into a *different* GitHub account has no way to push to that repo, so
|
|
14
|
+
its heartbeat never arrived — the main machine saw it as permanently offline and
|
|
15
|
+
refused to hand any session to it, even though the two machines could reach each
|
|
16
|
+
other perfectly over their tunnels.
|
|
17
|
+
|
|
18
|
+
There's now a direct presence channel that doesn't touch git: each machine
|
|
19
|
+
periodically asks every reachable peer "are you there?" over the same signed,
|
|
20
|
+
encrypted machine-to-machine channel it already uses for everything else, and
|
|
21
|
+
marks a peer online the moment it answers. A credential-less standby therefore
|
|
22
|
+
becomes a real, transfer-eligible member of the pool purely over its tunnel.
|
|
23
|
+
|
|
24
|
+
## What to Tell Your User
|
|
25
|
+
|
|
26
|
+
If you only run on one machine, nothing changes. If you run across two machines
|
|
27
|
+
and the second one is signed into a different GitHub account (or otherwise can't
|
|
28
|
+
push to the shared repo), it will now correctly show as online in your Machines
|
|
29
|
+
list and become eligible to receive conversations — no extra setup, and no git
|
|
30
|
+
credentials needed on the second machine.
|
|
31
|
+
|
|
32
|
+
## Summary of New Capabilities
|
|
33
|
+
|
|
34
|
+
- `PeerPresencePuller` (new core module): a pull-based HTTP presence channel.
|
|
35
|
+
Each mesh machine asks every reachable peer for its self-capacity over the
|
|
36
|
+
signed `session-status` MeshRpc command and records the answer into its local
|
|
37
|
+
`MachinePoolRegistry` — so a peer that can't push a git-synced heartbeat still
|
|
38
|
+
goes online over its tunnel. Symmetric, additive, and idempotent with the
|
|
39
|
+
existing git-synced heartbeat path; inert on a single-machine agent.
|
|
40
|
+
|
|
41
|
+
## Evidence
|
|
42
|
+
|
|
43
|
+
- `tests/unit/peer-presence-puller.test.ts` — loop semantics with injected fakes
|
|
44
|
+
(records an answering peer; never polls self; skips a peer with no URL; a
|
|
45
|
+
rejecting fetch neither throws nor records; null is not recorded; clock
|
|
46
|
+
fallback; `listPeers` re-read each pass).
|
|
47
|
+
- `tests/unit/peer-presence-wiring.test.ts` — server-boot wiring: a real puller
|
|
48
|
+
is constructed, feeds the signed `session-status` call into
|
|
49
|
+
`machinePoolRegistry.recordHeartbeat`, and is actually started (immediate pass
|
|
50
|
+
+ recurring unref-ed timer).
|
|
51
|
+
- `tests/integration/peer-presence-roundtrip.test.ts` — full chain over a real
|
|
52
|
+
loopback `/mesh/rpc` with real Ed25519 keys: a peer starts OFFLINE +
|
|
53
|
+
placement-ineligible on the router (no git heartbeat) and, after ONE presence
|
|
54
|
+
pass over the signed channel, is ONLINE + placement-eligible — with an
|
|
55
|
+
unreachable peer never marked online.
|
|
56
|
+
- Side-effects: `upgrades/side-effects/http-presence-transport.md`.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Side-effects review — HTTP presence transport (PeerPresencePuller)
|
|
2
|
+
|
|
3
|
+
## What was happening (real-hardware, 2026-05-29)
|
|
4
|
+
|
|
5
|
+
The Multi-Machine Session Pool's live-transfer proof (laptop ↔ Mac mini) got all
|
|
6
|
+
the way to the edge and surfaced one architectural gap: the mesh **forms** fine
|
|
7
|
+
over HTTP (non-interactive code-auth pairing, URLs exchanged, peers reachable at
|
|
8
|
+
their tunnels, split-brain clear), but the router would not transfer a session
|
|
9
|
+
to the mini because it saw the mini as `online: false`.
|
|
10
|
+
|
|
11
|
+
Root cause: the pool registry's notion of which peers are ONLINE was fed ONLY by
|
|
12
|
+
the **git-synced** `MachineHeartbeat` — each machine writes its heartbeat into
|
|
13
|
+
the shared agent repo and peers pull it. The mini is paired into the mesh over
|
|
14
|
+
HTTP but is `gh`-authed to a *different* account, so it has no push access to the
|
|
15
|
+
shared repo. Its heartbeat never reaches the router → the router marks it offline
|
|
16
|
+
→ the placement engine refuses to transfer to an offline machine — even though
|
|
17
|
+
the two machines reach each other perfectly over their tunnels.
|
|
18
|
+
|
|
19
|
+
## The fix
|
|
20
|
+
|
|
21
|
+
A pull-based HTTP presence channel that does not touch git:
|
|
22
|
+
|
|
23
|
+
- **`src/core/PeerPresencePuller.ts`** (new) — on a cadence, ask each reachable
|
|
24
|
+
peer for its self-capacity over the signed §L0 `session-status` MeshRpc command
|
|
25
|
+
and feed the answer into the local `MachinePoolRegistry.recordHeartbeat`. A peer
|
|
26
|
+
that answers is by definition reachable + alive, so it goes `online` purely over
|
|
27
|
+
HTTP. Fully injected (peers, fetch, record, clock) and `pullOnce()` NEVER throws
|
|
28
|
+
— an unreachable peer is simply not recorded this pass and ages out of `online`
|
|
29
|
+
via the registry's existing failover threshold.
|
|
30
|
+
|
|
31
|
+
- **`src/commands/server.ts`** — in the mesh-rpc block (right after the
|
|
32
|
+
SessionRouter is wired, where `meshClient` / `peerUrl` / `machinePoolRegistry`
|
|
33
|
+
are in scope), construct one `PeerPresencePuller` and start it: an immediate
|
|
34
|
+
`pullOnce()` plus a 30 s `unref`-ed interval. `fetchPeerCapacity` issues
|
|
35
|
+
`meshClient.send(peer, { type: 'session-status' }, 0)`; `recordHeartbeat`
|
|
36
|
+
delegates to the real pool registry.
|
|
37
|
+
|
|
38
|
+
`session-status` is a read-class command (RBAC: any *registered* peer — proven by
|
|
39
|
+
`verifyEnvelope`), so the call authenticates off the mutual identity established
|
|
40
|
+
at pairing. No router role, no epoch fence, no new route, no new config.
|
|
41
|
+
|
|
42
|
+
## Blast radius
|
|
43
|
+
|
|
44
|
+
- **Additive + symmetric.** Every mesh machine runs one puller; each maintains
|
|
45
|
+
its own HTTP-sourced view of peer liveness. It runs in PARALLEL with the
|
|
46
|
+
existing git-synced `refreshPool` path — a credentialed peer that both
|
|
47
|
+
git-syncs AND answers HTTP just refreshes twice. `recordHeartbeat` is the single
|
|
48
|
+
arbiter and the router's-clock liveness rule is unchanged, so a peer can never
|
|
49
|
+
be marked *fresher* than its real `routerReceivedAt`.
|
|
50
|
+
- **Inert without a mesh.** A single-machine agent has no peers → `listPeers()` is
|
|
51
|
+
empty → `pullOnce()` is a no-op. Only constructed inside the existing
|
|
52
|
+
mesh-enabled block.
|
|
53
|
+
- **No new config / route / schema / hook / skill → no migration.** Server +
|
|
54
|
+
one new core module; existing agents pick it up on the next release (same shape
|
|
55
|
+
as the native-rebuild / wake-socket fleet fix).
|
|
56
|
+
- **Does NOT replace the git substrate** for ownership/placement *records* (§L0)
|
|
57
|
+
— only the high-frequency presence signal moves to HTTP.
|
|
58
|
+
|
|
59
|
+
## Tests
|
|
60
|
+
|
|
61
|
+
- `tests/unit/peer-presence-puller.test.ts` — loop semantics with injected fakes:
|
|
62
|
+
a peer that answers is recorded (id + loadAvg + selfReportedLastSeen); self is
|
|
63
|
+
never polled; a peer with no URL is skipped; a fetch that REJECTS does not throw
|
|
64
|
+
and does not record (peer ages out); a null result is not recorded; the clock
|
|
65
|
+
fallback fills `selfReportedLastSeen`; `listPeers` is re-read each pass.
|
|
66
|
+
- `tests/unit/peer-presence-wiring.test.ts` — pins the server-boot wiring:
|
|
67
|
+
imports + constructs a real puller, feeds the signed `session-status` call into
|
|
68
|
+
`machinePoolRegistry?.recordHeartbeat`, and actually STARTS it (immediate
|
|
69
|
+
`pullOnce()` + recurring `unref`-ed timer) — guards against the
|
|
70
|
+
"constructed-but-inert" failure the Testing Integrity Standard calls out.
|
|
71
|
+
- `tests/integration/peer-presence-roundtrip.test.ts` — the full chain over a real
|
|
72
|
+
loopback `/mesh/rpc` with real Ed25519 keys: MINI starts OFFLINE +
|
|
73
|
+
placement-ineligible on the router (no git heartbeat); after ONE presence pass
|
|
74
|
+
over the signed channel it is ONLINE + placement-eligible (loadAvg carried over
|
|
75
|
+
HTTP) — and an unreachable peer is never marked online.
|