@openscout/protocol 0.2.98 → 0.2.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.
@@ -0,0 +1,231 @@
1
+ import type { AdvertiseScope, MetadataMap, ScoutId } from "./common.js";
2
+ /**
3
+ * Machines — the physical (or virtual) boxes Scout knows about.
4
+ *
5
+ * A machine is NOT a node. A node is a Scout broker runtime; a machine is the
6
+ * computer it runs on. The distinction matters because most machines Scout can
7
+ * see are not running Scout at all: a tailnet peer, a Mac advertising itself on
8
+ * the LAN, a Linux box that only answers ARP. Modelling them as nodes would
9
+ * either invent fake nodes or make them invisible, and both were wrong.
10
+ *
11
+ * A machine record is assembled from **evidence**, never asserted. Four
12
+ * channels report independently and none of them is authoritative:
13
+ *
14
+ * | channel | tells us |
15
+ * | --------- | ----------------------------------------------- |
16
+ * | `scout` | a broker node lives here (id, mesh, urls) |
17
+ * | `tailnet` | reachable over Tailscale (stable peer id, IPs) |
18
+ * | `lan` | seen on this network (mDNS advert, ARP neighbor)|
19
+ * | `host` | terminal hosts present (herdr, tmux, zellij) |
20
+ *
21
+ * The join between them is `machineIdentityKeys` + `groupMachineEvidence`.
22
+ * Everything else in this module is a pure derivation over a group, so the
23
+ * whole model is testable without a network, a broker, or a database.
24
+ */
25
+ export declare const MACHINE_ID_PREFIX = "mach-";
26
+ export type MachineEvidenceKind = "scout" | "tailnet" | "lan" | "host";
27
+ export type MachinePlatform = "macos" | "linux" | "windows" | "ios" | "android" | "unknown";
28
+ /**
29
+ * What a machine can do *for Scout*, as observed — never as configured. A
30
+ * capability is present because something answered, so an operator reading
31
+ * `herdr` on a row knows a herdr probe actually succeeded there.
32
+ */
33
+ export type MachineCapability = "scout-broker" | "scout-web" | "scout-pairing" | "herdr" | "tmux" | "zellij" | "ssh" | "vnc" | "smb" | "http";
34
+ export type MachineRouteKind = "loopback" | "lan" | "tailnet" | "mesh" | "relay";
35
+ /** One way to reach a machine. Ordered by preference in `deriveMachineRoutes`. */
36
+ export interface MachineRoute {
37
+ kind: MachineRouteKind;
38
+ host: string;
39
+ port?: number;
40
+ url?: string;
41
+ lastSeenAt?: number;
42
+ }
43
+ export type MachineTerminalHost = "herdr" | "tmux" | "zellij";
44
+ export type LanDiscoveryMethod = "mdns" | "arp";
45
+ interface MachineEvidenceBase {
46
+ kind: MachineEvidenceKind;
47
+ observedAt: number;
48
+ }
49
+ /** A Scout broker node registered with, or discovered by, this broker. */
50
+ export interface ScoutMachineEvidence extends MachineEvidenceBase {
51
+ kind: "scout";
52
+ nodeId: ScoutId;
53
+ meshId?: ScoutId;
54
+ nodeName: string;
55
+ hostName?: string;
56
+ brokerUrl?: string;
57
+ webUrl?: string;
58
+ advertiseScope?: AdvertiseScope;
59
+ tailnetName?: string;
60
+ isSelf?: boolean;
61
+ }
62
+ /** A `tailscale status` peer (or self). Present whether or not it runs Scout. */
63
+ export interface TailnetMachineEvidence extends MachineEvidenceBase {
64
+ kind: "tailnet";
65
+ peerId: string;
66
+ hostName?: string;
67
+ dnsName?: string;
68
+ tailnetName?: string;
69
+ addresses: string[];
70
+ online: boolean;
71
+ os?: string;
72
+ tags?: string[];
73
+ isSelf?: boolean;
74
+ }
75
+ /** A local-network sighting: an mDNS service instance or an ARP neighbor. */
76
+ export interface LanMachineEvidence extends MachineEvidenceBase {
77
+ kind: "lan";
78
+ method: LanDiscoveryMethod;
79
+ hostName?: string;
80
+ instanceName?: string;
81
+ /** Full service type as browsed, e.g. `_openscout._tcp`. */
82
+ serviceType?: string;
83
+ addresses: string[];
84
+ port?: number;
85
+ macAddress?: string;
86
+ /** Best-effort OUI vendor. Null whenever the prefix is not in the table. */
87
+ vendor?: string;
88
+ interfaceName?: string;
89
+ txt?: MetadataMap;
90
+ }
91
+ /** A terminal host observed on a machine Scout can probe. */
92
+ export interface HostMachineEvidence extends MachineEvidenceBase {
93
+ kind: "host";
94
+ host: MachineTerminalHost;
95
+ /** Machine this was probed on — host probes are never self-identifying. */
96
+ hostName?: string;
97
+ nodeId?: ScoutId;
98
+ sessionCount: number;
99
+ runningSessionCount?: number;
100
+ version?: string;
101
+ }
102
+ export type MachineEvidence = ScoutMachineEvidence | TailnetMachineEvidence | LanMachineEvidence | HostMachineEvidence;
103
+ /**
104
+ * Identity keys, split by how long they stay true.
105
+ *
106
+ * `durable` keys name the machine itself — a Tailscale node id, a MAC, a Scout
107
+ * node id, a hostname. They are persisted with the record and re-join evidence
108
+ * across passes, days apart.
109
+ *
110
+ * `transient` keys are only true right now. A LAN IP is the important one: it
111
+ * is what joins an ARP row to an mDNS advert *inside one scan*, and it is
112
+ * exactly what DHCP hands to a different machine next week. Persisting it
113
+ * would silently fuse two machines, so it never leaves the pass that saw it.
114
+ */
115
+ export interface MachineIdentityKeys {
116
+ durable: string[];
117
+ transient: string[];
118
+ }
119
+ /** mDNS service types Scout browses, and what each one proves. */
120
+ export declare const MACHINE_SERVICE_CAPABILITIES: Readonly<Record<string, MachineCapability>>;
121
+ /** Browsed for inventory. `_workstation._tcp` proves nothing but presence. */
122
+ export declare const MACHINE_LAN_SERVICE_TYPES: readonly string[];
123
+ export declare function normalizeMachineHostName(value: string | null | undefined): string;
124
+ /**
125
+ * Lowercase colon-form MAC. Returns "" for entries that identify nothing —
126
+ * broadcast, all-zero, or an incomplete ARP row.
127
+ *
128
+ * BSD `arp` drops leading zeros (`3c:22:fb:1:2:3`), so octets are padded
129
+ * rather than the string being scrubbed of separators: scrubbing turns that
130
+ * nine-character form into a rejected MAC instead of a valid one.
131
+ */
132
+ export declare function normalizeMacAddress(value: string | null | undefined): string;
133
+ export declare function normalizeMachineAddress(value: string | null | undefined): string;
134
+ export declare function isLoopbackAddress(value: string): boolean;
135
+ /** Tailscale's CGNAT range (100.64.0.0/10) and its IPv6 ULA prefix. */
136
+ export declare function isTailnetAddress(value: string): boolean;
137
+ /**
138
+ * The keys one piece of evidence claims. Two pieces of evidence describe the
139
+ * same machine when they share any key — see `groupMachineEvidence`.
140
+ */
141
+ export declare function machineIdentityKeys(evidence: MachineEvidence): MachineIdentityKeys;
142
+ export interface MachineEvidenceGroup {
143
+ /** Durable keys only — what gets persisted so the next pass re-joins here. */
144
+ identityKeys: string[];
145
+ evidence: MachineEvidence[];
146
+ /**
147
+ * Indices of every seed this group attached to, ascending. More than one
148
+ * means fresh evidence proved two records were always the same machine —
149
+ * the first is the survivor, the rest are absorbed by the caller.
150
+ */
151
+ seedIndices: number[];
152
+ }
153
+ /**
154
+ * Union-find over identity keys. Evidence lands in the same group when it
155
+ * shares any key, directly or through a chain — an ARP row and an mDNS advert
156
+ * share an IP; the advert and a tailnet peer share a hostname; so all three
157
+ * describe one machine even though ARP and Tailscale share nothing at all.
158
+ *
159
+ * `seeds` are the durable key sets of machines already on record. They enter
160
+ * the union as members, which is what lets a machine seen only by ARP today
161
+ * and only by Tailscale tomorrow stay one record: the seed bridges the passes.
162
+ */
163
+ export declare function groupMachineEvidence(evidence: readonly MachineEvidence[], seeds?: readonly (readonly string[])[]): MachineEvidenceGroup[];
164
+ /**
165
+ * Deterministic id from a group's durable keys, so two brokers observing the
166
+ * same machine mint the same id and a restart never renumbers the fleet.
167
+ * FNV-1a ×2, matching `stableChannelId`.
168
+ */
169
+ export declare function stableMachineId(identityKeys: readonly string[]): ScoutId;
170
+ /**
171
+ * The name to show. Scout's own name for the node wins because the operator
172
+ * chose it; then the tailnet hostname, then whatever the LAN advertised. A
173
+ * machine known only by MAC gets its MAC, never a fabricated label.
174
+ */
175
+ export declare function deriveMachineName(evidence: readonly MachineEvidence[]): string;
176
+ export declare function deriveMachinePlatform(evidence: readonly MachineEvidence[]): MachinePlatform;
177
+ export declare function deriveMachineCapabilities(evidence: readonly MachineEvidence[]): MachineCapability[];
178
+ /**
179
+ * Routes, best first: loopback (it's this machine), then LAN (no hop), then
180
+ * tailnet, then whatever the mesh advertised. This is the order a caller
181
+ * should dial, and it mirrors the LAN-first preference iOS already uses.
182
+ */
183
+ /**
184
+ * Every way we know to reach this machine, best first. `isSelf` decides whether
185
+ * loopback belongs at all: a peer advertising `http://127.0.0.1:43120` is
186
+ * describing its own loopback, which is not a route to it from here.
187
+ */
188
+ export declare function deriveMachineRoutes(evidence: readonly MachineEvidence[], isSelf?: boolean): MachineRoute[];
189
+ export type MachinePresence = "online" | "recent" | "offline";
190
+ /** Anything seen inside this window counts as present. */
191
+ export declare const MACHINE_ONLINE_WINDOW_MS: number;
192
+ export declare const MACHINE_RECENT_WINDOW_MS: number;
193
+ export interface MachineRecord {
194
+ id: ScoutId;
195
+ /** Operator-set name. Null means "use `name`". */
196
+ displayName: string | null;
197
+ /** Derived from the strongest evidence — recomputed every pass. */
198
+ name: string;
199
+ platform: MachinePlatform;
200
+ /** Durable keys, sorted. The join key set for the next pass. */
201
+ identityKeys: string[];
202
+ isSelf: boolean;
203
+ scoutNodeId?: ScoutId;
204
+ meshId?: ScoutId;
205
+ tailnetId?: string;
206
+ tailnetName?: string;
207
+ hostNames: string[];
208
+ addresses: string[];
209
+ macAddresses: string[];
210
+ capabilities: MachineCapability[];
211
+ routes: MachineRoute[];
212
+ evidence: MachineEvidence[];
213
+ /** Operator marked it worth keeping even when nothing has seen it in weeks. */
214
+ pinned: boolean;
215
+ notes?: string;
216
+ firstSeenAt: number;
217
+ lastSeenAt: number;
218
+ metadata?: MetadataMap;
219
+ }
220
+ export declare function machineLabel(machine: Pick<MachineRecord, "displayName" | "name">): string;
221
+ export declare function machinePresence(machine: Pick<MachineRecord, "lastSeenAt">, now?: number): MachinePresence;
222
+ /**
223
+ * Build (or rebuild) a record from a group. `previous` carries the parts an
224
+ * operator owns — id, chosen name, notes, pin, first-seen — which no amount of
225
+ * fresh evidence may overwrite.
226
+ */
227
+ export declare function buildMachineRecord(group: MachineEvidenceGroup, previous?: Partial<MachineRecord> | null): MachineRecord;
228
+ /** Sort for display: this machine, then present ones, then by name. */
229
+ export declare function compareMachines(a: MachineRecord, b: MachineRecord): number;
230
+ export {};
231
+ //# sourceMappingURL=machines.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"machines.d.ts","sourceRoot":"","sources":["../src/machines.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAExE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,eAAO,MAAM,iBAAiB,UAAU,CAAC;AAEzC,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,SAAS,GAAG,KAAK,GAAG,MAAM,CAAC;AAEvE,MAAM,MAAM,eAAe,GACvB,OAAO,GACP,OAAO,GACP,SAAS,GACT,KAAK,GACL,SAAS,GACT,SAAS,CAAC;AAEd;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GACzB,cAAc,GACd,WAAW,GACX,eAAe,GACf,OAAO,GACP,MAAM,GACN,QAAQ,GACR,KAAK,GACL,KAAK,GACL,KAAK,GACL,MAAM,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,KAAK,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;AAEjF,kFAAkF;AAClF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,gBAAgB,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE9D,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,KAAK,CAAC;AAEhD,UAAU,mBAAmB;IAC3B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,0EAA0E;AAC1E,MAAM,WAAW,oBAAqB,SAAQ,mBAAmB;IAC/D,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,iFAAiF;AACjF,MAAM,WAAW,sBAAuB,SAAQ,mBAAmB;IACjE,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,6EAA6E;AAC7E,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;IAC7D,IAAI,EAAE,KAAK,CAAC;IACZ,MAAM,EAAE,kBAAkB,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,WAAW,CAAC;CACnB;AAED,6DAA6D;AAC7D,MAAM,WAAW,mBAAoB,SAAQ,mBAAmB;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,mBAAmB,CAAC;IAC1B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,eAAe,GACvB,oBAAoB,GACpB,sBAAsB,GACtB,kBAAkB,GAClB,mBAAmB,CAAC;AAExB;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAID,kEAAkE;AAClE,eAAO,MAAM,4BAA4B,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAQpF,CAAC;AAEF,8EAA8E;AAC9E,eAAO,MAAM,yBAAyB,EAAE,SAAS,MAAM,EAOtD,CAAC;AAqBF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAWjF;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAmB5E;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAIhF;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGxD;AAED,uEAAuE;AACvE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAQvD;AAWD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,eAAe,GAAG,mBAAmB,CAkDlF;AAED,MAAM,WAAW,oBAAoB;IACnC,8EAA8E;IAC9E,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B;;;;OAIG;IACH,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,SAAS,eAAe,EAAE,EACpC,KAAK,GAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAO,GACzC,oBAAoB,EAAE,CAqExB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAWxE;AAWD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,GAAG,MAAM,CAoC9E;AAaD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,GAAG,eAAe,CAc3F;AAED,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,SAAS,eAAe,EAAE,GACnC,iBAAiB,EAAE,CAerB;AAED;;;;GAIG;AACH;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,SAAS,eAAe,EAAE,EACpC,MAAM,UAAQ,GACb,YAAY,EAAE,CAkEhB;AAID,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9D,0DAA0D;AAC1D,eAAO,MAAM,wBAAwB,QAAa,CAAC;AACnD,eAAO,MAAM,wBAAwB,QAAc,CAAC;AAEpD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,kDAAkD;IAClD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,eAAe,CAAC;IAC1B,gEAAgE;IAChE,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAClC,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,+EAA+E;IAC/E,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,WAAW,CAAC;CACxB;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,aAAa,EAAE,aAAa,GAAG,MAAM,CAAC,GAAG,MAAM,CAEzF;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,IAAI,CAAC,aAAa,EAAE,YAAY,CAAC,EAC1C,GAAG,GAAE,MAAmB,GACvB,eAAe,CAKjB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,oBAAoB,EAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,GACvC,aAAa,CAwDf;AAED,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,GAAG,MAAM,CAI1E"}