instar 1.3.515 → 1.3.517

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.
@@ -4080,23 +4080,31 @@
4080
4080
  // WS4.2 (multi-machine-seamlessness spec, F7 — 2026-06-12 live incident):
4081
4081
  // a machine with zero running sessions used to render as NOTHING, identical
4082
4082
  // to a broken or unreachable machine. Render an explicit state row per pool
4083
- // machine that has no session tiles: "online no active sessions" when its
4084
- // heartbeat is live, "not reachable last seen <t>" when it is not. Pool
4085
- // disabled or single-machine poolMachinesView is empty zero rows, zero
4086
- // behavior change (single-machine strict no-op, spec invariant 6).
4083
+ // machine that has no session tiles. The three states are computed SERVER-
4084
+ // SIDE in pool.machines[].emptyState (honest derivation from the registry +
4085
+ // the live fan-out) the dashboard just styles + prints them:
4086
+ // - online → "online no active sessions" (idle but healthy)
4087
+ // - offline → "offline since <t>" (known absence)
4088
+ // - unreachable → "unreachable (last seen <t>)" (was online, now silent)
4089
+ // Pool disabled or single-machine → poolMachinesView is empty → zero rows,
4090
+ // zero behavior change (single-machine strict no-op, spec invariant 6).
4087
4091
  function machineStatusInfo(m) {
4088
- if (m.online) return { cls: 'online', text: 'online — no active sessions' };
4089
- const seen = m.selfReportedLastSeen ? new Date(m.selfReportedLastSeen).getTime() : null;
4090
- const rel = seen ? fmtRelTime(seen) : 'unknown';
4091
- return { cls: 'offline', text: `not reachable — last seen ${rel}` };
4092
+ const es = m && m.emptyState;
4093
+ if (es && es.kind && es.text) {
4094
+ // 'online' is calm; 'offline'/'unreachable' both read as offline styling.
4095
+ return { cls: es.kind === 'online' ? 'online' : 'offline', text: es.text };
4096
+ }
4097
+ // No server-computed empty-state present (shouldn't happen for a 0-session
4098
+ // machine, but never fabricate "online" — surface the honest unknown).
4099
+ return { cls: 'offline', text: 'state unknown' };
4092
4100
  }
4093
4101
  function renderMachineStatusStrip(list, allSessions) {
4094
4102
  list.querySelectorAll('.machine-status-row').forEach(el => el.remove());
4095
4103
  if (!poolMachinesView.length) return;
4096
- const busy = new Set(allSessions.map(s => s.machineNickname || s.machineId).filter(Boolean));
4097
4104
  for (const m of poolMachinesView) {
4105
+ // A machine WITH sessions already names itself via its tiles' badges.
4106
+ if (m.sessionCount > 0) continue;
4098
4107
  const label = m.nickname || m.machineId || 'unnamed machine';
4099
- if (busy.has(m.nickname) || busy.has(m.machineId)) continue; // has tiles — badge already names it
4100
4108
  const info = machineStatusInfo(m);
4101
4109
  const row = document.createElement('div');
4102
4110
  row.className = `machine-status-row ${info.cls}`;
@@ -4656,17 +4664,18 @@
4656
4664
  // (2026-06-11: five closed Mac Mini sessions reappeared on the laptop
4657
4665
  // dashboard hours after they were closed).
4658
4666
  remoteSessions = (j.sessions || []).filter(s => s.remote === true && (s.status === 'running' || s.status === 'starting'));
4659
- renderSessionList();
4660
- } catch { /* best-effort peers may be offline; next tick retries */ }
4661
- // WS4.2: refresh the machine inventory on the same cadence so the sessions
4662
- // view can say "online no active sessions" vs "not reachable" per machine.
4663
- try {
4664
- const pool = await apiFetch('/pool');
4665
- poolMachinesView = (pool && pool.enabled && Array.isArray(pool.machines) && pool.machines.length >= 2)
4666
- ? pool.machines
4667
+ // WS4.2 (F7): the pooled sessions response now carries pool.machines —
4668
+ // every registered machine with its session count and, when idle, an
4669
+ // explicit SERVER-COMPUTED empty-state (online / offline / unreachable),
4670
+ // honestly derived from the registry + the live fan-out. Render it so an
4671
+ // idle machine reads "online — no active sessions" and a dark machine
4672
+ // reads "offline since"/"unreachable" — never an indistinguishable blank.
4673
+ // >=2 machines is the multi-machine case; single-machine stays empty (no-op).
4674
+ poolMachinesView = (j.pool && j.pool.enabled && Array.isArray(j.pool.machines) && j.pool.machines.length >= 2)
4675
+ ? j.pool.machines
4667
4676
  : [];
4668
4677
  renderSessionList();
4669
- } catch { /* best-effort — strip simply stays absent until the next tick */ }
4678
+ } catch { /* best-effort — peers may be offline; next tick retries */ }
4670
4679
  }
4671
4680
  function startPoolSessionsPolling() {
4672
4681
  if (poolPollTimer) return;
@@ -0,0 +1,200 @@
1
+ /**
2
+ * HybridLogicalClock — a pure, dependency-injected total-order clock (WS2
3
+ * replicated-store foundation, Component 1).
4
+ *
5
+ * Spec: docs/specs/multi-machine-replicated-store-foundation.md §3 (the whole
6
+ * section), §10.2 (the maxDriftMs clamp invariant), §13 build order step 1,
7
+ * §15 risk-5/risk-6 (maxDriftMs sourcing — BLOCKER-5).
8
+ *
9
+ * HLC combines physical wall-clock time (so order tracks real time and is
10
+ * human-readable) with a logical counter (so causality survives clock skew and
11
+ * equal-millisecond ties). It is the load-bearing total order WS2 merges rely
12
+ * on: "merges order by HLC, never raw wall-clock" (master spec line 229).
13
+ *
14
+ * Design contract (§3.6 Purity + testability):
15
+ * - Imports NOTHING but its injected seams. No `fs`, no `Date` (only via the
16
+ * injected `now`), no network. Every operation is a pure function of
17
+ * `(last, input, now())`.
18
+ * - The clock (`now`), the `node` id, and persistence (`persist.load/save`)
19
+ * are constructor deps, so every dangerous property — monotonicity, the
20
+ * skew bound, restart-monotonicity — is unit-testable with in-memory fakes.
21
+ *
22
+ * Posture (§11): machine-local-by-design. Each machine has its own clock; the
23
+ * clocks CONVERGE via receive() but are never shared state. The HLC TIMESTAMPS
24
+ * are replicated (one per record, §4); the clock OBJECT is not.
25
+ */
26
+ /**
27
+ * An HLC value. Carried on each replicated record as the `hlc` field (§4).
28
+ * The triple `(physical, logical, node)` is a globally-unique, totally-ordered
29
+ * stamp (node ids are unique across the pool, so distinct-machine stamps are
30
+ * never "equal" in sort position — §3.3).
31
+ */
32
+ export interface HlcTimestamp {
33
+ /** Physical time in ms since epoch (the LARGEST seen, not necessarily now). */
34
+ physical: number;
35
+ /** Logical counter — breaks ties at equal physical and advances under skew. */
36
+ logical: number;
37
+ /** Node (machine) id of the clock that STAMPED this timestamp. The
38
+ * tie-breaker of LAST resort and the carrier of the origin tag (§7). */
39
+ node: string;
40
+ }
41
+ /**
42
+ * The persistence seam (§3.5). Injected so crash-safety is unit-testable with
43
+ * an in-memory fake. `save` is expected to be ATOMIC (temp + rename) in the
44
+ * real implementation; the clock calls it on EVERY advance.
45
+ */
46
+ export interface HlcPersistence {
47
+ /** Load the last durable stamp, or null if fresh (first boot). */
48
+ load(): HlcTimestamp | null;
49
+ /** Persist the last issued stamp atomically. Called on every advance. */
50
+ save(t: HlcTimestamp): void;
51
+ }
52
+ /** Constructor config (§3.2). */
53
+ export interface HybridLogicalClockConfig {
54
+ /** This machine's id — the `node` stamped on every timestamp this clock issues. */
55
+ node: string;
56
+ /** INJECTED physical-time source (ms since epoch). Tests pass a fake clock. */
57
+ now: () => number;
58
+ /**
59
+ * FIXED bounded-drift ceiling (§3.4, BLOCKER-5). Default 5 minutes, CLAMPED
60
+ * to [60s, 15min]. NOT derived from any "measured pool skew" — no such numeric
61
+ * quantity exists today (ClockSkewStatus is a 3-value categorical enum).
62
+ */
63
+ maxDriftMs?: number;
64
+ /** Persistence seam (§3.5). Omitted ⇒ the clock keeps `last` only in memory. */
65
+ persist?: HlcPersistence;
66
+ /**
67
+ * Optional structured logger for the at-most-once breadcrumbs the spec
68
+ * mandates (a regressed-wall-clock-on-load note, §3.5; a clamp note). Defaults
69
+ * to a no-op so the primitive stays import-free.
70
+ */
71
+ log?: (event: string, detail: Record<string, unknown>) => void;
72
+ }
73
+ /**
74
+ * A rejected receive (§3.4). Returned (never thrown into the hot path silently)
75
+ * when a remote timestamp exceeds the pool-relative reference by more than the
76
+ * clamped maxDriftMs. The local clock is NOT advanced and the caller MUST
77
+ * quarantine the record (§5, failure-class `skew-suspicious`).
78
+ */
79
+ export interface SkewRejection {
80
+ rejected: true;
81
+ reason: 'skew-ahead';
82
+ /** The remote timestamp that was rejected. */
83
+ remote: HlcTimestamp;
84
+ /** The pool-relative reference R the remote was measured against. */
85
+ reference: number;
86
+ /** The clamped drift ceiling that R + ceiling was exceeded by. */
87
+ maxDriftMs: number;
88
+ }
89
+ /** The result of receive(): either the merged stamp, or a typed rejection. */
90
+ export type ReceiveResult = {
91
+ rejected: false;
92
+ hlc: HlcTimestamp;
93
+ } | SkewRejection;
94
+ /** Options for a single receive() call (§3.4 pool-relative reference). */
95
+ export interface ReceiveOptions {
96
+ /**
97
+ * An OBSERVED pool-relative physical-time floor (e.g. the observed-pool-median
98
+ * physical time carried in the capacity heartbeat, §3.4). The drift check
99
+ * references `R = max(last.physical, poolReference ?? 0)`, NOT the bare local
100
+ * `now()` — so a receiver whose own NTP is behind does not falsely reject
101
+ * ahead-but-honest peers. Omitted ⇒ R = last.physical.
102
+ */
103
+ poolReference?: number;
104
+ }
105
+ /** The default bounded-drift ceiling: 5 minutes (§3.4). */
106
+ export declare const DEFAULT_MAX_DRIFT_MS: number;
107
+ /** The floor of the maxDriftMs clamp (§3.4 / §10.2): 60 seconds. */
108
+ export declare const MIN_MAX_DRIFT_MS: number;
109
+ /** The ceiling of the maxDriftMs clamp (§3.4 / §10.2): 15 minutes. */
110
+ export declare const MAX_MAX_DRIFT_MS: number;
111
+ /**
112
+ * Clamp a configured maxDriftMs to the [60s, 15min] window (§3.4, §10.2).
113
+ *
114
+ * This is the helper `validateStateSyncInvariants` (§10.2) uses for the
115
+ * `maxDriftMs` knob: a value below the floor would start rejecting ordinary NTP
116
+ * jitter; a value above the ceiling would defeat the fast-clock defense. The
117
+ * spec's §10.2 invariant REJECTS an out-of-range value at config resolution;
118
+ * this clamp helper is the in-clock guard so the primitive is correct even if a
119
+ * caller hands it a raw value. A non-finite/undefined input falls back to the
120
+ * default.
121
+ */
122
+ export declare function clampMaxDriftMs(value: number | undefined): number;
123
+ /** Type guard: is a receive() result a skew rejection? */
124
+ export declare function isSkewRejection(r: ReceiveResult): r is SkewRejection;
125
+ /**
126
+ * Serialize an HlcTimestamp to the compact wire/disk form (§3.5).
127
+ *
128
+ * Default form is a 3-field JSON object (carried on each record as `hlc`). The
129
+ * string form `"<physical>:<logical>:<node>"` is for embedding in a key — and
130
+ * because a node id MAY contain a `:`, the node is the LAST segment and parse
131
+ * splits on the FIRST TWO colons only (physical and logical are numeric and
132
+ * colon-free), so the round-trip is lossless for any node id.
133
+ */
134
+ export declare function serializeHlc(t: HlcTimestamp): string;
135
+ /** Serialize to the compact key-string form `"<physical>:<logical>:<node>"`. */
136
+ export declare function serializeHlcKey(t: HlcTimestamp): string;
137
+ /**
138
+ * Parse the JSON object form (§3.5). Throws on malformed input — a record whose
139
+ * `hlc` cannot be parsed is a schema reject upstream, never a silent default.
140
+ */
141
+ export declare function parseHlc(input: string): HlcTimestamp;
142
+ /** Parse the key-string form `"<physical>:<logical>:<node>"` (§3.5). */
143
+ export declare function parseHlcKey(input: string): HlcTimestamp;
144
+ /**
145
+ * Validate + narrow an unknown value to an HlcTimestamp. The single chokepoint
146
+ * both parsers and receive() funnel untrusted input through (§3.5): physical and
147
+ * logical must be finite non-negative integers; node must be a non-empty string.
148
+ */
149
+ export declare function coerceHlc(raw: unknown): HlcTimestamp;
150
+ export declare class HybridLogicalClock {
151
+ private readonly node;
152
+ private readonly now;
153
+ private readonly maxDriftMs;
154
+ private readonly persist?;
155
+ private readonly log;
156
+ /** The largest stamp this clock has issued (the floor for every advance). */
157
+ private last;
158
+ constructor(config: HybridLogicalClockConfig);
159
+ /** The largest stamp issued so far (a copy — never the internal reference). */
160
+ current(): HlcTimestamp;
161
+ /** The clamped drift ceiling this clock enforces (§3.4). */
162
+ getMaxDriftMs(): number;
163
+ /**
164
+ * tick() — local-event advance (§3.2.1). Called when THIS machine AUTHORS a
165
+ * record. `pt = max(now(), last.physical)`; `logical = (pt === last.physical)
166
+ * ? last.logical + 1 : 0`. Persists and returns the new stamp.
167
+ *
168
+ * Monotonicity guarantee: the returned stamp is strictly greater (by compare)
169
+ * than every previous tick()/receive() result on this clock — even if the wall
170
+ * clock jumps backward, because physical never regresses.
171
+ */
172
+ tick(): HlcTimestamp;
173
+ /**
174
+ * receive(remote) — merge an inbound peer stamp (§3.2.2 + §3.4). Runs the
175
+ * bounded-drift check FIRST; on rejection the local clock does NOT advance and
176
+ * a typed SkewRejection is returned (the caller quarantines the record).
177
+ *
178
+ * Otherwise the canonical HLC merge (Kulkarni et al.):
179
+ * pt = max(now(), last.physical, remote.physical)
180
+ * - pt === last.physical === remote.physical → max(last.logical, remote.logical)+1
181
+ * - pt === last.physical → last.logical + 1
182
+ * - pt === remote.physical → remote.logical + 1
183
+ * - else → 0
184
+ *
185
+ * Monotonic vs BOTH local and remote: the receiving clock can never go
186
+ * backward, and a received record's causal position is preserved.
187
+ */
188
+ receive(remote: HlcTimestamp, options?: ReceiveOptions): ReceiveResult;
189
+ /** Advance `last` and persist atomically (§3.5 — persist on EVERY advance). */
190
+ private commit;
191
+ /**
192
+ * compare(a, b) — the STRICT TOTAL ORDER (§3.3, static + pure). Compares
193
+ * physical, then logical, then node id (lexicographic). Returns 0 ONLY for an
194
+ * identical triple — because node ids are unique, two distinct-machine stamps
195
+ * are NEVER "equal" in sort position. This totality is what makes a
196
+ * deterministic merge across the pool possible.
197
+ */
198
+ static compare(a: HlcTimestamp, b: HlcTimestamp): -1 | 0 | 1;
199
+ }
200
+ //# sourceMappingURL=HybridLogicalClock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HybridLogicalClock.d.ts","sourceRoot":"","sources":["../../src/core/HybridLogicalClock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,288 @@
1
+ /**
2
+ * HybridLogicalClock — a pure, dependency-injected total-order clock (WS2
3
+ * replicated-store foundation, Component 1).
4
+ *
5
+ * Spec: docs/specs/multi-machine-replicated-store-foundation.md §3 (the whole
6
+ * section), §10.2 (the maxDriftMs clamp invariant), §13 build order step 1,
7
+ * §15 risk-5/risk-6 (maxDriftMs sourcing — BLOCKER-5).
8
+ *
9
+ * HLC combines physical wall-clock time (so order tracks real time and is
10
+ * human-readable) with a logical counter (so causality survives clock skew and
11
+ * equal-millisecond ties). It is the load-bearing total order WS2 merges rely
12
+ * on: "merges order by HLC, never raw wall-clock" (master spec line 229).
13
+ *
14
+ * Design contract (§3.6 Purity + testability):
15
+ * - Imports NOTHING but its injected seams. No `fs`, no `Date` (only via the
16
+ * injected `now`), no network. Every operation is a pure function of
17
+ * `(last, input, now())`.
18
+ * - The clock (`now`), the `node` id, and persistence (`persist.load/save`)
19
+ * are constructor deps, so every dangerous property — monotonicity, the
20
+ * skew bound, restart-monotonicity — is unit-testable with in-memory fakes.
21
+ *
22
+ * Posture (§11): machine-local-by-design. Each machine has its own clock; the
23
+ * clocks CONVERGE via receive() but are never shared state. The HLC TIMESTAMPS
24
+ * are replicated (one per record, §4); the clock OBJECT is not.
25
+ */
26
+ /** The default bounded-drift ceiling: 5 minutes (§3.4). */
27
+ export const DEFAULT_MAX_DRIFT_MS = 5 * 60 * 1000;
28
+ /** The floor of the maxDriftMs clamp (§3.4 / §10.2): 60 seconds. */
29
+ export const MIN_MAX_DRIFT_MS = 60 * 1000;
30
+ /** The ceiling of the maxDriftMs clamp (§3.4 / §10.2): 15 minutes. */
31
+ export const MAX_MAX_DRIFT_MS = 15 * 60 * 1000;
32
+ /**
33
+ * Clamp a configured maxDriftMs to the [60s, 15min] window (§3.4, §10.2).
34
+ *
35
+ * This is the helper `validateStateSyncInvariants` (§10.2) uses for the
36
+ * `maxDriftMs` knob: a value below the floor would start rejecting ordinary NTP
37
+ * jitter; a value above the ceiling would defeat the fast-clock defense. The
38
+ * spec's §10.2 invariant REJECTS an out-of-range value at config resolution;
39
+ * this clamp helper is the in-clock guard so the primitive is correct even if a
40
+ * caller hands it a raw value. A non-finite/undefined input falls back to the
41
+ * default.
42
+ */
43
+ export function clampMaxDriftMs(value) {
44
+ if (value === undefined || !Number.isFinite(value))
45
+ return DEFAULT_MAX_DRIFT_MS;
46
+ if (value < MIN_MAX_DRIFT_MS)
47
+ return MIN_MAX_DRIFT_MS;
48
+ if (value > MAX_MAX_DRIFT_MS)
49
+ return MAX_MAX_DRIFT_MS;
50
+ return value;
51
+ }
52
+ /** Type guard: is a receive() result a skew rejection? */
53
+ export function isSkewRejection(r) {
54
+ return r.rejected === true;
55
+ }
56
+ /**
57
+ * Serialize an HlcTimestamp to the compact wire/disk form (§3.5).
58
+ *
59
+ * Default form is a 3-field JSON object (carried on each record as `hlc`). The
60
+ * string form `"<physical>:<logical>:<node>"` is for embedding in a key — and
61
+ * because a node id MAY contain a `:`, the node is the LAST segment and parse
62
+ * splits on the FIRST TWO colons only (physical and logical are numeric and
63
+ * colon-free), so the round-trip is lossless for any node id.
64
+ */
65
+ export function serializeHlc(t) {
66
+ return JSON.stringify({ physical: t.physical, logical: t.logical, node: t.node });
67
+ }
68
+ /** Serialize to the compact key-string form `"<physical>:<logical>:<node>"`. */
69
+ export function serializeHlcKey(t) {
70
+ return `${t.physical}:${t.logical}:${t.node}`;
71
+ }
72
+ /**
73
+ * Parse the JSON object form (§3.5). Throws on malformed input — a record whose
74
+ * `hlc` cannot be parsed is a schema reject upstream, never a silent default.
75
+ */
76
+ export function parseHlc(input) {
77
+ let raw;
78
+ try {
79
+ raw = JSON.parse(input);
80
+ }
81
+ catch {
82
+ throw new Error(`parseHlc: not valid JSON: ${truncateForError(input)}`);
83
+ }
84
+ return coerceHlc(raw);
85
+ }
86
+ /** Parse the key-string form `"<physical>:<logical>:<node>"` (§3.5). */
87
+ export function parseHlcKey(input) {
88
+ if (typeof input !== 'string') {
89
+ throw new Error('parseHlcKey: input is not a string');
90
+ }
91
+ // Split on the FIRST TWO colons only — the node id may contain colons.
92
+ const firstColon = input.indexOf(':');
93
+ const secondColon = firstColon < 0 ? -1 : input.indexOf(':', firstColon + 1);
94
+ if (firstColon < 0 || secondColon < 0) {
95
+ throw new Error(`parseHlcKey: expected "<physical>:<logical>:<node>", got: ${truncateForError(input)}`);
96
+ }
97
+ const physical = Number(input.slice(0, firstColon));
98
+ const logical = Number(input.slice(firstColon + 1, secondColon));
99
+ const node = input.slice(secondColon + 1);
100
+ return coerceHlc({ physical, logical, node });
101
+ }
102
+ /**
103
+ * Validate + narrow an unknown value to an HlcTimestamp. The single chokepoint
104
+ * both parsers and receive() funnel untrusted input through (§3.5): physical and
105
+ * logical must be finite non-negative integers; node must be a non-empty string.
106
+ */
107
+ export function coerceHlc(raw) {
108
+ if (typeof raw !== 'object' || raw === null) {
109
+ throw new Error('coerceHlc: not an object');
110
+ }
111
+ const obj = raw;
112
+ const { physical, logical, node } = obj;
113
+ if (typeof physical !== 'number' || !Number.isInteger(physical) || physical < 0) {
114
+ throw new Error(`coerceHlc: physical must be a non-negative integer, got ${String(physical)}`);
115
+ }
116
+ if (typeof logical !== 'number' || !Number.isInteger(logical) || logical < 0) {
117
+ throw new Error(`coerceHlc: logical must be a non-negative integer, got ${String(logical)}`);
118
+ }
119
+ if (typeof node !== 'string' || node.length === 0) {
120
+ throw new Error('coerceHlc: node must be a non-empty string');
121
+ }
122
+ return { physical, logical, node };
123
+ }
124
+ function truncateForError(s) {
125
+ const str = String(s);
126
+ return str.length > 80 ? `${str.slice(0, 80)}…` : str;
127
+ }
128
+ export class HybridLogicalClock {
129
+ node;
130
+ now;
131
+ maxDriftMs;
132
+ persist;
133
+ log;
134
+ /** The largest stamp this clock has issued (the floor for every advance). */
135
+ last;
136
+ constructor(config) {
137
+ if (typeof config.node !== 'string' || config.node.length === 0) {
138
+ throw new Error('HybridLogicalClock: node id must be a non-empty string');
139
+ }
140
+ this.node = config.node;
141
+ this.now = config.now;
142
+ this.maxDriftMs = clampMaxDriftMs(config.maxDriftMs);
143
+ this.persist = config.persist;
144
+ this.log = config.log ?? (() => { });
145
+ const loaded = this.persist?.load() ?? null;
146
+ // Validate the durable stamp through the same chokepoint a parser uses. A
147
+ // CORRUPT (non-null but malformed) load must NOT crash construction (§3.5,
148
+ // "Distrust Temporary Success"): a poisoned/partially-written persistence row
149
+ // would otherwise brick every consumer that boots the clock. We fail TOWARD a
150
+ // fresh-but-monotonic clock (monotonic relative to wall time) and log once,
151
+ // mirroring the missing-file (null) path — never throw out of the constructor.
152
+ let safeLoaded;
153
+ if (loaded === null) {
154
+ safeLoaded = null;
155
+ }
156
+ else {
157
+ try {
158
+ safeLoaded = coerceHlc(loaded);
159
+ }
160
+ catch (err) {
161
+ // @silent-fallback-ok: a corrupt persisted stamp degrades to the same
162
+ // fresh-but-monotonic clock as the missing-file path (safeLoaded=null
163
+ // below) rather than throwing out of construction — fail toward a usable
164
+ // clock, the safe direction. The discard is logged with context
165
+ // ('hlc-load-corrupt'); it is not a DegradationReporter case.
166
+ this.log('hlc-load-corrupt', {
167
+ error: err instanceof Error ? err.message : String(err),
168
+ });
169
+ safeLoaded = null;
170
+ }
171
+ }
172
+ if (safeLoaded === null) {
173
+ // Fresh (no durable stamp, or a corrupt one we just discarded): start at
174
+ // { physical: now(), logical: 0, node } (§3.5).
175
+ this.last = { physical: this.now(), logical: 0, node: this.node };
176
+ }
177
+ else {
178
+ // Seed last from the durable stamp so a restart cannot rewind below it.
179
+ // We trust our OWN durable past over a regressed wall clock: if the loaded
180
+ // physical is ahead of now() by more than maxDriftMs (a backward wall-clock
181
+ // jump across the restart) we honor the durable floor and log once (§3.5).
182
+ this.last = { physical: safeLoaded.physical, logical: safeLoaded.logical, node: this.node };
183
+ const wall = this.now();
184
+ if (safeLoaded.physical - wall > this.maxDriftMs) {
185
+ this.log('hlc-load-ahead-of-wall', {
186
+ loadedPhysical: safeLoaded.physical,
187
+ wall,
188
+ maxDriftMs: this.maxDriftMs,
189
+ });
190
+ }
191
+ }
192
+ }
193
+ /** The largest stamp issued so far (a copy — never the internal reference). */
194
+ current() {
195
+ return { ...this.last };
196
+ }
197
+ /** The clamped drift ceiling this clock enforces (§3.4). */
198
+ getMaxDriftMs() {
199
+ return this.maxDriftMs;
200
+ }
201
+ /**
202
+ * tick() — local-event advance (§3.2.1). Called when THIS machine AUTHORS a
203
+ * record. `pt = max(now(), last.physical)`; `logical = (pt === last.physical)
204
+ * ? last.logical + 1 : 0`. Persists and returns the new stamp.
205
+ *
206
+ * Monotonicity guarantee: the returned stamp is strictly greater (by compare)
207
+ * than every previous tick()/receive() result on this clock — even if the wall
208
+ * clock jumps backward, because physical never regresses.
209
+ */
210
+ tick() {
211
+ const pt = Math.max(this.now(), this.last.physical);
212
+ const logical = pt === this.last.physical ? this.last.logical + 1 : 0;
213
+ const next = { physical: pt, logical, node: this.node };
214
+ this.commit(next);
215
+ return next;
216
+ }
217
+ /**
218
+ * receive(remote) — merge an inbound peer stamp (§3.2.2 + §3.4). Runs the
219
+ * bounded-drift check FIRST; on rejection the local clock does NOT advance and
220
+ * a typed SkewRejection is returned (the caller quarantines the record).
221
+ *
222
+ * Otherwise the canonical HLC merge (Kulkarni et al.):
223
+ * pt = max(now(), last.physical, remote.physical)
224
+ * - pt === last.physical === remote.physical → max(last.logical, remote.logical)+1
225
+ * - pt === last.physical → last.logical + 1
226
+ * - pt === remote.physical → remote.logical + 1
227
+ * - else → 0
228
+ *
229
+ * Monotonic vs BOTH local and remote: the receiving clock can never go
230
+ * backward, and a received record's causal position is preserved.
231
+ */
232
+ receive(remote, options = {}) {
233
+ // Narrow untrusted input through the same chokepoint a parser uses.
234
+ const r = coerceHlc(remote);
235
+ // §3.4 — POOL-RELATIVE reference, never the bare local now(). A slow receiver
236
+ // must not quarantine a legitimately-ahead peer just because its own clock lags.
237
+ const reference = Math.max(this.last.physical, options.poolReference ?? 0);
238
+ if (r.physical - reference > this.maxDriftMs) {
239
+ // Clock NOT advanced — a fast peer cannot drag us into the future.
240
+ return {
241
+ rejected: true,
242
+ reason: 'skew-ahead',
243
+ remote: r,
244
+ reference,
245
+ maxDriftMs: this.maxDriftMs,
246
+ };
247
+ }
248
+ const pt = Math.max(this.now(), this.last.physical, r.physical);
249
+ let logical;
250
+ if (pt === this.last.physical && pt === r.physical) {
251
+ logical = Math.max(this.last.logical, r.logical) + 1;
252
+ }
253
+ else if (pt === this.last.physical) {
254
+ logical = this.last.logical + 1;
255
+ }
256
+ else if (pt === r.physical) {
257
+ logical = r.logical + 1;
258
+ }
259
+ else {
260
+ logical = 0;
261
+ }
262
+ const next = { physical: pt, logical, node: this.node };
263
+ this.commit(next);
264
+ return { rejected: false, hlc: next };
265
+ }
266
+ /** Advance `last` and persist atomically (§3.5 — persist on EVERY advance). */
267
+ commit(next) {
268
+ this.last = next;
269
+ this.persist?.save({ ...next });
270
+ }
271
+ /**
272
+ * compare(a, b) — the STRICT TOTAL ORDER (§3.3, static + pure). Compares
273
+ * physical, then logical, then node id (lexicographic). Returns 0 ONLY for an
274
+ * identical triple — because node ids are unique, two distinct-machine stamps
275
+ * are NEVER "equal" in sort position. This totality is what makes a
276
+ * deterministic merge across the pool possible.
277
+ */
278
+ static compare(a, b) {
279
+ if (a.physical !== b.physical)
280
+ return a.physical < b.physical ? -1 : 1;
281
+ if (a.logical !== b.logical)
282
+ return a.logical < b.logical ? -1 : 1;
283
+ if (a.node !== b.node)
284
+ return a.node < b.node ? -1 : 1;
285
+ return 0;
286
+ }
287
+ }
288
+ //# sourceMappingURL=HybridLogicalClock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HybridLogicalClock.js","sourceRoot":"","sources":["../../src/core/HybridLogicalClock.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"PostUpdateMigrator.d.ts","sourceRoot":"","sources":["../../src/core/PostUpdateMigrator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PostUpdateMigrator.d.ts","sourceRoot":"","sources":["../../src/core/PostUpdateMigrator.ts"],"names":[],"mappings":"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"}
@@ -4093,6 +4093,7 @@ Beyond the one-awake-machine model: with the pool enabled I run conversations ac
4093
4093
 
4094
4094
  - **See the pool:** the **Machines tab** in the dashboard, or \`GET /pool\` (Bearer-auth) → which machine is the router ("dispatcher") + every machine's nickname, hardware, online status, load, and clock-skew status.
4095
4095
  - **Every session, every machine:** the dashboard sessions list shows ALL sessions across the pool, each tagged with the machine it runs on. API: \`GET /sessions?scope=pool\` → \`{ sessions: [...each with machineId/machineNickname...], pool: { peersOk, failed } }\`. An unreachable peer degrades to a \`failed\` entry — local sessions always answer.
4096
+ - **Idle vs broken machine (WS4.2):** the same \`pool.machines[]\` carries an explicit per-machine state so an idle machine never reads as broken. A machine with ZERO sessions gets \`pool.machines[].emptyState\` = \`online — no active sessions\` (heartbeat-fresh, just idle) / \`offline since <t>\` (known offline) / \`unreachable (last seen <t>)\` (was online, now not answering — the \`failed\` case). Honest derivation from the registry online flag + last-seen + the live fan-out — never a fabricated "looks fine". The dashboard sessions view renders these per-machine; a machine WITH sessions gets no empty-state (its tiles already name it). Single-machine install = just the lone self row.
4096
4097
  - **Post-transfer closeout (automatic):** when a topic moves to another machine, the OLD machine's session for it is closed automatically (immediately on an explicit "move", or within ~2 reaper ticks for any other path) — no duplicate sessions doing duplicate work. The close is recorded in the reap-log with reason "topic moved to <machine>"; protected sessions are never auto-closed.
4097
4098
  - **Quota-aware placement (automatic):** capacity heartbeats carry each machine's LLM-account quota state, and placement avoids machines whose account is currently rate-limited/blocked (no more topics placed onto a silent machine). A hard pin still wins (flagged \`pinned-machine-quota-blocked\`); if EVERY machine is blocked, placement proceeds least-loaded with \`all-machines-quota-blocked\` flagged. \`GET /pool\` shows each machine's \`quotaState\`.
4098
4099
  - **Machine nicknames** are the user-facing handle (auto-assigned, editable). Rename via \`PATCH /pool/machines/:machineId\` with \`{"nickname":"the mini"}\`, or inline on the Machines tab.
@@ -4150,6 +4151,20 @@ Beyond the one-awake-machine model: with the pool enabled I run conversations ac
4150
4151
  patched = true;
4151
4152
  result.upgraded.push('CLAUDE.md: added pool-wide session visibility line');
4152
4153
  }
4154
+ // WS4.2 (MULTI-MACHINE-SEAMLESSNESS-SPEC §WS4.2, F7): agents that already
4155
+ // carry the pool section predate the per-machine empty-state — without it
4156
+ // they will read an idle peer's blank row as a broken/missing machine (the
4157
+ // 2026-06-12 incident). Append the sub-line so they explain "online — no
4158
+ // active sessions" vs "offline since" vs "unreachable" correctly from the
4159
+ // pooled sessions response. Idempotent via the unique `pool.machines[].emptyState`
4160
+ // content-sniff (a sub-line of the already-tracked pool section).
4161
+ if (content.includes('Multi-Machine Session Pool (active-active') && !content.includes('pool.machines[].emptyState')) {
4162
+ const ws42line = `
4163
+ - **Idle vs broken machine (WS4.2):** the same \`pool.machines[]\` carries an explicit per-machine state so an idle machine never reads as broken. A machine with ZERO sessions gets \`pool.machines[].emptyState\` = \`online — no active sessions\` (heartbeat-fresh, just idle) / \`offline since <t>\` (known offline) / \`unreachable (last seen <t>)\` (was online, now not answering — the \`failed\` case). Honest derivation from the registry online flag + last-seen + the live fan-out — never a fabricated "looks fine". The dashboard sessions view renders these per-machine; a machine WITH sessions gets no empty-state (its tiles already name it). Single-machine install = just the lone self row.`;
4164
+ content += '\n' + ws42line + '\n';
4165
+ patched = true;
4166
+ result.upgraded.push('CLAUDE.md: added WS4.2 per-machine empty-state line');
4167
+ }
4153
4168
  // Post-transfer closeout awareness (2026-06-05): agents that already carry
4154
4169
  // the pool section must learn that a moved topic's old session now closes
4155
4170
  // automatically (so they explain a disappeared session correctly instead of