instar 1.3.93 → 1.3.94

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.
@@ -0,0 +1,50 @@
1
+ /**
2
+ * MeshUrlAdvertiser — populate a machine's `lastKnownUrl` so cross-machine
3
+ * routing can actually reach it.
4
+ *
5
+ * THE BUG THIS FIXES (found via real-hardware dogfooding, 2026-05-29):
6
+ * the Multi-Machine Session Pool routes every cross-machine RPC (deliver /
7
+ * transfer / lease) to a peer by reading `registry.machines[id].lastKnownUrl`.
8
+ * But the only writer of that field — `MachineIdentityManager.updateMachineUrl`
9
+ * — had ZERO callers. So in production every peer's URL was null, the peer
10
+ * filters (`!!e.lastKnownUrl`) dropped every peer, and no session could ever be
11
+ * delivered or transferred across machines. The 3-tier tests missed it because
12
+ * they inject peers with mock URLs and never exercise the runtime "where does
13
+ * lastKnownUrl come from" path.
14
+ *
15
+ * A machine's reachable URL is its tunnel URL: a named tunnel's hostname is
16
+ * known from config; a quick tunnel's URL is only known once `tunnel.start()`
17
+ * resolves it. This module turns either into the value to advertise, and writes
18
+ * it into the registry entry — from where the existing git-backed registry sync
19
+ * (RegistrySyncDebouncer) propagates it to peers.
20
+ */
21
+ export interface MeshTunnelConfig {
22
+ enabled?: boolean;
23
+ type?: string;
24
+ hostname?: string;
25
+ }
26
+ /**
27
+ * Resolve the URL this machine should advertise to the mesh.
28
+ *
29
+ * Preference order:
30
+ * 1. A concretely-resolved tunnel URL (quick tunnels only know it at runtime;
31
+ * `tunnel.start()` returns it). This is authoritative when present.
32
+ * 2. A named tunnel's deterministic `https://<hostname>`.
33
+ * 3. null — no reachable URL (tunnel disabled / not configured). A machine
34
+ * with no tunnel is genuinely not reachable cross-machine; advertising
35
+ * nothing is correct (peers will skip it rather than route to a dead URL).
36
+ */
37
+ export declare function resolveAdvertisedMeshUrl(tunnel: MeshTunnelConfig | undefined, resolvedTunnelUrl?: string | null): string | null;
38
+ /** Minimal slice of MachineIdentityManager this module needs (keeps it testable). */
39
+ export interface MeshUrlRecorder {
40
+ getMachineUrl(machineId: string): string | null;
41
+ updateMachineUrl(machineId: string, url: string): void;
42
+ }
43
+ /**
44
+ * Advertise this machine's reachable URL into its own registry entry so peers
45
+ * can route to it. Idempotent (no write when unchanged), and tolerant when the
46
+ * self entry isn't present yet (best-effort — boot ordering can race the
47
+ * registration). Returns true iff it wrote a new value.
48
+ */
49
+ export declare function advertiseSelfMeshUrl(recorder: MeshUrlRecorder, selfMachineId: string, url: string | null, log?: (msg: string) => void): boolean;
50
+ //# sourceMappingURL=MeshUrlAdvertiser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MeshUrlAdvertiser.d.ts","sourceRoot":"","sources":["../../src/core/MeshUrlAdvertiser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,gBAAgB,GAAG,SAAS,EACpC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,GAChC,MAAM,GAAG,IAAI,CASf;AAED,qFAAqF;AACrF,MAAM,WAAW,eAAe;IAC9B,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAChD,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACxD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,eAAe,EACzB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,GAAG,IAAI,EAClB,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC1B,OAAO,CAWT"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * MeshUrlAdvertiser — populate a machine's `lastKnownUrl` so cross-machine
3
+ * routing can actually reach it.
4
+ *
5
+ * THE BUG THIS FIXES (found via real-hardware dogfooding, 2026-05-29):
6
+ * the Multi-Machine Session Pool routes every cross-machine RPC (deliver /
7
+ * transfer / lease) to a peer by reading `registry.machines[id].lastKnownUrl`.
8
+ * But the only writer of that field — `MachineIdentityManager.updateMachineUrl`
9
+ * — had ZERO callers. So in production every peer's URL was null, the peer
10
+ * filters (`!!e.lastKnownUrl`) dropped every peer, and no session could ever be
11
+ * delivered or transferred across machines. The 3-tier tests missed it because
12
+ * they inject peers with mock URLs and never exercise the runtime "where does
13
+ * lastKnownUrl come from" path.
14
+ *
15
+ * A machine's reachable URL is its tunnel URL: a named tunnel's hostname is
16
+ * known from config; a quick tunnel's URL is only known once `tunnel.start()`
17
+ * resolves it. This module turns either into the value to advertise, and writes
18
+ * it into the registry entry — from where the existing git-backed registry sync
19
+ * (RegistrySyncDebouncer) propagates it to peers.
20
+ */
21
+ /**
22
+ * Resolve the URL this machine should advertise to the mesh.
23
+ *
24
+ * Preference order:
25
+ * 1. A concretely-resolved tunnel URL (quick tunnels only know it at runtime;
26
+ * `tunnel.start()` returns it). This is authoritative when present.
27
+ * 2. A named tunnel's deterministic `https://<hostname>`.
28
+ * 3. null — no reachable URL (tunnel disabled / not configured). A machine
29
+ * with no tunnel is genuinely not reachable cross-machine; advertising
30
+ * nothing is correct (peers will skip it rather than route to a dead URL).
31
+ */
32
+ export function resolveAdvertisedMeshUrl(tunnel, resolvedTunnelUrl) {
33
+ if (resolvedTunnelUrl && /^https?:\/\/\S+$/.test(resolvedTunnelUrl.trim())) {
34
+ return resolvedTunnelUrl.trim().replace(/\/+$/, '');
35
+ }
36
+ if (tunnel?.enabled !== false && tunnel?.hostname && tunnel.hostname.trim()) {
37
+ const host = tunnel.hostname.trim().replace(/^https?:\/\//, '').replace(/\/+$/, '');
38
+ if (host)
39
+ return `https://${host}`;
40
+ }
41
+ return null;
42
+ }
43
+ /**
44
+ * Advertise this machine's reachable URL into its own registry entry so peers
45
+ * can route to it. Idempotent (no write when unchanged), and tolerant when the
46
+ * self entry isn't present yet (best-effort — boot ordering can race the
47
+ * registration). Returns true iff it wrote a new value.
48
+ */
49
+ export function advertiseSelfMeshUrl(recorder, selfMachineId, url, log) {
50
+ if (!url || !selfMachineId)
51
+ return false;
52
+ try {
53
+ if (recorder.getMachineUrl(selfMachineId) === url)
54
+ return false; // idempotent
55
+ recorder.updateMachineUrl(selfMachineId, url);
56
+ log?.(` Mesh: advertised self URL → ${url}`);
57
+ return true;
58
+ }
59
+ catch {
60
+ // Self entry not present yet — caller may retry on a later lifecycle event.
61
+ return false;
62
+ }
63
+ }
64
+ //# sourceMappingURL=MeshUrlAdvertiser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MeshUrlAdvertiser.js","sourceRoot":"","sources":["../../src/core/MeshUrlAdvertiser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAQH;;;;;;;;;;GAUG;AACH,MAAM,UAAU,wBAAwB,CACtC,MAAoC,EACpC,iBAAiC;IAEjC,IAAI,iBAAiB,IAAI,kBAAkB,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;QAC3E,OAAO,iBAAiB,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,IAAI,MAAM,EAAE,OAAO,KAAK,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;QAC5E,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACpF,IAAI,IAAI;YAAE,OAAO,WAAW,IAAI,EAAE,CAAC;IACrC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAQD;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,QAAyB,EACzB,aAAqB,EACrB,GAAkB,EAClB,GAA2B;IAE3B,IAAI,CAAC,GAAG,IAAI,CAAC,aAAa;QAAE,OAAO,KAAK,CAAC;IACzC,IAAI,CAAC;QACH,IAAI,QAAQ,CAAC,aAAa,CAAC,aAAa,CAAC,KAAK,GAAG;YAAE,OAAO,KAAK,CAAC,CAAC,aAAa;QAC9E,QAAQ,CAAC,gBAAgB,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;QAC9C,GAAG,EAAE,CAAC,iCAAiC,GAAG,EAAE,CAAC,CAAC;QAC9C,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,4EAA4E;QAC5E,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.93",
3
+ "version": "1.3.94",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-05-29T16:44:14.621Z",
5
- "instarVersion": "1.3.93",
4
+ "generatedAt": "2026-05-29T17:44:10.981Z",
5
+ "instarVersion": "1.3.94",
6
6
  "entryCount": 193,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,38 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- bump: patch -->
4
+
5
+ ## What Changed
6
+
7
+ **Multi-Machine Session Pool — cross-machine routing fix (still dark).** Real
8
+ hardware bring-up of a second machine surfaced a wiring gap: the session pool
9
+ routes every cross-machine action (deliver / transfer / lease) to a peer by its
10
+ `lastKnownUrl` (its tunnel URL), but nothing in the shipped code ever wrote that
11
+ field — so every peer was filtered out and no session could be routed or
12
+ transferred across machines. A machine now advertises its own tunnel URL into
13
+ the mesh registry once the tunnel is up (and re-advertises after a sleep/wake
14
+ restart, since a quick tunnel's URL changes), and a joining machine records the
15
+ URL it paired through. The whole layer remains gated behind
16
+ `multiMachine.sessionPool` (default off / `stage:'dark'`), so single-machine
17
+ agents are unaffected.
18
+
19
+ ## What to Tell Your User
20
+
21
+ Nothing changes yet — this is still off by default. It fixes a foundational gap
22
+ so that, when the session pool is enabled, conversations can actually be placed
23
+ on and moved between machines.
24
+
25
+ ## Summary of New Capabilities
26
+
27
+ - `MeshUrlAdvertiser` (`resolveAdvertisedMeshUrl` + `advertiseSelfMeshUrl`):
28
+ populates a machine's `lastKnownUrl` from its tunnel URL on boot + sleep/wake.
29
+ - `instar join` now records the awake machine's URL on the joining (standby) side.
30
+
31
+ ## Evidence
32
+
33
+ - Unit: `tests/unit/mesh-url-advertiser.test.ts` (resolver + advertiser vs a real
34
+ MachineIdentityManager).
35
+ - Wiring-integrity: `tests/unit/mesh-url-advertisement-wiring.test.ts` (asserts
36
+ the advertiser is actually invoked — the tier that would have caught the
37
+ original zero-callers gap).
38
+ - Side-effects: `upgrades/side-effects/mesh-url-advertisement.md`.
@@ -0,0 +1,66 @@
1
+ # Side-effects review — Mesh URL advertisement (lastKnownUrl population)
2
+
3
+ ## What changed
4
+
5
+ `updateMachineUrl()` (the only writer of `registry.machines[id].lastKnownUrl`)
6
+ had **zero callers** on main. The Multi-Machine Session Pool routes every
7
+ cross-machine RPC — `deliverMessage`, session transfer, lease ops — to a peer by
8
+ reading that peer's `lastKnownUrl`, and filters peers with `!!e.lastKnownUrl`.
9
+ With the field never written, every peer was filtered out and **no session
10
+ could ever be delivered or transferred across machines**. The feature was inert
11
+ across real machines despite green 3-tier tests (which inject mock peer URLs).
12
+
13
+ Found 2026-05-29 bringing up a second real machine (the mini) for the
14
+ session-pool real-hardware proof.
15
+
16
+ This change adds:
17
+ - `src/core/MeshUrlAdvertiser.ts` — `resolveAdvertisedMeshUrl(tunnel, resolvedUrl?)`
18
+ (named → `https://<hostname>`, quick → the runtime-resolved start URL, none →
19
+ null) + `advertiseSelfMeshUrl(recorder, selfId, url)` (idempotent, tolerant of
20
+ a missing self entry).
21
+ - `src/commands/server.ts` — calls the advertiser after `tunnel.start()` on both
22
+ the boot path and the sleep/wake restart path (a quick tunnel gets a new URL
23
+ after a wake, so the previously-advertised URL goes stale).
24
+ - `src/commands/machine.ts` (`joinMesh`) — the joiner records the URL it
25
+ connected through as the awake peer's `lastKnownUrl`, so a standby can route
26
+ back to the awake machine immediately without waiting for a registry sync.
27
+
28
+ ## Blast radius
29
+
30
+ - **Dark-feature-only effect.** The populated `lastKnownUrl` is consumed solely
31
+ by the session-pool mesh code, which is gated behind `multiMachine.sessionPool`
32
+ (default `enabled:false`, `stage:'dark'`). On a single-machine agent there are
33
+ no peers, so nothing changes. Populating the field is otherwise inert.
34
+ - **No new config, no new route, no schema change** → no migration needed.
35
+ Existing agents pick up the behavior at boot on the next release. No
36
+ `PostUpdateMigrator` entry required (the code runs at startup unconditionally
37
+ when multi-machine is enabled and a tunnel URL exists).
38
+ - **Idempotent + fail-safe.** `advertiseSelfMeshUrl` writes only when the URL
39
+ changed and swallows the "self entry not present yet" race (returns false).
40
+ A tunnel-disabled machine advertises nothing (correct — it is genuinely
41
+ unreachable cross-machine; peers skip it rather than route to a dead URL).
42
+ - **No secret exposure.** A tunnel URL is already public (the dashboard link).
43
+ Recording it in the registry adds no new exposure.
44
+
45
+ ## What this does NOT close (scoped, decision-gated follow-up)
46
+
47
+ The **awake machine learning a git-uncredentialed standby's URL** still needs one
48
+ of: (a) the standby having git push creds for the shared agent repo (so its
49
+ advertised entry syncs via the existing git-backed RegistrySyncDebouncer), or
50
+ (b) a signed mesh `announce-url` RPC so a standby can publish its URL to the
51
+ awake machine over authenticated MeshRpc without git. (b) also requires the
52
+ awake side to persist the joiner's identity, which today only happens via the
53
+ **interactive SAS pairing** confirmation — so closing this fully touches the
54
+ pairing security model. That is a genuine design fork (creds vs. new RPC +
55
+ non-interactive registration), not a silent deferral, and is raised explicitly
56
+ rather than hand-resolved on a production identity.
57
+
58
+ ## Tests
59
+
60
+ - `tests/unit/mesh-url-advertiser.test.ts` — resolver (named/quick/disabled/none)
61
+ + advertiser against a real `MachineIdentityManager` (writes, idempotent,
62
+ updates on change, null no-op, missing-entry tolerance).
63
+ - `tests/unit/mesh-url-advertisement-wiring.test.ts` — wiring-integrity: asserts
64
+ the advertiser is invoked on the boot + sleep/wake tunnel paths and the joiner
65
+ records the connect URL. This is the tier that was missing — it would have
66
+ caught the original "zero callers" gap.