@hotmeshio/long-tail 0.25.4 → 0.26.0
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/README.md +1 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +8 -1
- package/build/lib/machines/index.d.ts +5 -0
- package/build/lib/machines/index.js +21 -0
- package/build/lib/machines/line.d.ts +44 -0
- package/build/lib/machines/line.js +114 -0
- package/build/lib/machines/protocol/codec.d.ts +92 -0
- package/build/lib/machines/protocol/codec.js +79 -0
- package/build/lib/machines/protocol/constants.d.ts +120 -0
- package/build/lib/machines/protocol/constants.js +97 -0
- package/build/lib/machines/protocol/contract.d.ts +19 -0
- package/build/lib/machines/protocol/contract.js +132 -0
- package/build/lib/machines/protocol/envelope.d.ts +14 -0
- package/build/lib/machines/protocol/envelope.js +43 -0
- package/build/lib/machines/protocol/index.d.ts +5 -0
- package/build/lib/machines/protocol/index.js +21 -0
- package/build/lib/machines/protocol/topics.d.ts +33 -0
- package/build/lib/machines/protocol/topics.js +79 -0
- package/build/lib/machines/site/mosquitto-conf.d.ts +27 -0
- package/build/lib/machines/site/mosquitto-conf.js +81 -0
- package/build/lib/machines/site/site-conf.d.ts +20 -0
- package/build/lib/machines/site/site-conf.js +55 -0
- package/build/lib/machines/transport/callback-feed.d.ts +18 -0
- package/build/lib/machines/transport/callback-feed.js +31 -0
- package/build/lib/machines/transport/delivery.d.ts +21 -0
- package/build/lib/machines/transport/delivery.js +42 -0
- package/build/lib/machines/transport/index.d.ts +11 -0
- package/build/lib/machines/transport/index.js +27 -0
- package/build/lib/machines/transport/memory-broker.d.ts +35 -0
- package/build/lib/machines/transport/memory-broker.js +145 -0
- package/build/lib/machines/transport/memory.d.ts +40 -0
- package/build/lib/machines/transport/memory.js +79 -0
- package/build/lib/machines/transport/mqtt-bus.d.ts +24 -0
- package/build/lib/machines/transport/mqtt-bus.js +43 -0
- package/build/lib/machines/transport/mqtt-link.d.ts +16 -0
- package/build/lib/machines/transport/mqtt-link.js +37 -0
- package/build/lib/machines/transport/mqtt-session.d.ts +34 -0
- package/build/lib/machines/transport/mqtt-session.js +127 -0
- package/build/lib/machines/transport/nats-bus.d.ts +42 -0
- package/build/lib/machines/transport/nats-bus.js +67 -0
- package/build/lib/machines/transport/nats-durable.d.ts +11 -0
- package/build/lib/machines/transport/nats-durable.js +28 -0
- package/build/lib/machines/transport/nats-feed.d.ts +26 -0
- package/build/lib/machines/transport/nats-feed.js +49 -0
- package/build/lib/machines/transport/serial.d.ts +21 -0
- package/build/lib/machines/transport/serial.js +54 -0
- package/build/lib/machines/transport/streams.d.ts +28 -0
- package/build/lib/machines/transport/streams.js +92 -0
- package/build/scripts/machine-line.d.ts +33 -0
- package/build/scripts/machine-line.js +301 -0
- package/build/services/machines/commands.d.ts +29 -0
- package/build/services/machines/commands.js +37 -0
- package/build/services/machines/config.d.ts +43 -0
- package/build/services/machines/config.js +84 -0
- package/build/services/machines/contracts.d.ts +193 -0
- package/build/services/machines/contracts.js +115 -0
- package/build/services/machines/forms.d.ts +55 -0
- package/build/services/machines/forms.js +37 -0
- package/build/services/machines/gateway/actions.d.ts +111 -0
- package/build/services/machines/gateway/actions.js +8 -0
- package/build/services/machines/gateway/address-book.d.ts +16 -0
- package/build/services/machines/gateway/address-book.js +24 -0
- package/build/services/machines/gateway/admission.d.ts +35 -0
- package/build/services/machines/gateway/admission.js +66 -0
- package/build/services/machines/gateway/api-escalations.d.ts +23 -0
- package/build/services/machines/gateway/api-escalations.js +101 -0
- package/build/services/machines/gateway/dispatcher.d.ts +63 -0
- package/build/services/machines/gateway/dispatcher.js +157 -0
- package/build/services/machines/gateway/escalations.d.ts +51 -0
- package/build/services/machines/gateway/escalations.js +40 -0
- package/build/services/machines/gateway/executor.d.ts +55 -0
- package/build/services/machines/gateway/executor.js +195 -0
- package/build/services/machines/gateway/gateway.d.ts +82 -0
- package/build/services/machines/gateway/gateway.js +247 -0
- package/build/services/machines/gateway/index.d.ts +10 -0
- package/build/services/machines/gateway/index.js +26 -0
- package/build/services/machines/gateway/jobs.d.ts +16 -0
- package/build/services/machines/gateway/jobs.js +24 -0
- package/build/services/machines/gateway/log.d.ts +41 -0
- package/build/services/machines/gateway/log.js +42 -0
- package/build/services/machines/gateway/lt-http.d.ts +51 -0
- package/build/services/machines/gateway/lt-http.js +120 -0
- package/build/services/machines/gateway/pacing.d.ts +29 -0
- package/build/services/machines/gateway/pacing.js +134 -0
- package/build/services/machines/gateway/plan-escalation.d.ts +19 -0
- package/build/services/machines/gateway/plan-escalation.js +201 -0
- package/build/services/machines/gateway/plan-machine.d.ts +16 -0
- package/build/services/machines/gateway/plan-machine.js +242 -0
- package/build/services/machines/gateway/plan-shared.d.ts +18 -0
- package/build/services/machines/gateway/plan-shared.js +36 -0
- package/build/services/machines/gateway/provision.d.ts +22 -0
- package/build/services/machines/gateway/provision.js +79 -0
- package/build/services/machines/gateway/receipts.d.ts +33 -0
- package/build/services/machines/gateway/receipts.js +65 -0
- package/build/services/machines/gateway/reconcile.d.ts +33 -0
- package/build/services/machines/gateway/reconcile.js +86 -0
- package/build/services/machines/gateway/registration.d.ts +38 -0
- package/build/services/machines/gateway/registration.js +121 -0
- package/build/services/machines/gateway/reply-waiter.d.ts +24 -0
- package/build/services/machines/gateway/reply-waiter.js +65 -0
- package/build/services/machines/gateway/state.d.ts +125 -0
- package/build/services/machines/gateway/state.js +228 -0
- package/build/services/machines/gateway/trace.d.ts +16 -0
- package/build/services/machines/gateway/trace.js +43 -0
- package/build/services/machines/identities.d.ts +25 -0
- package/build/services/machines/identities.js +77 -0
- package/build/services/machines/index.d.ts +11 -0
- package/build/services/machines/index.js +27 -0
- package/build/services/machines/leadership.d.ts +22 -0
- package/build/services/machines/leadership.js +75 -0
- package/build/services/machines/roles.d.ts +13 -0
- package/build/services/machines/roles.js +62 -0
- package/build/services/machines/rows.d.ts +30 -0
- package/build/services/machines/rows.js +56 -0
- package/build/services/machines/select.d.ts +18 -0
- package/build/services/machines/select.js +63 -0
- package/build/services/machines/surface.d.ts +33 -0
- package/build/services/machines/surface.js +98 -0
- package/build/services/machines/testing/fake-long-tail.d.ts +78 -0
- package/build/services/machines/testing/fake-long-tail.js +155 -0
- package/build/services/machines/testing/index.d.ts +3 -0
- package/build/services/machines/testing/index.js +19 -0
- package/build/services/machines/testing/recording-log.d.ts +14 -0
- package/build/services/machines/testing/recording-log.js +22 -0
- package/build/services/machines/testing/wait.d.ts +3 -0
- package/build/services/machines/testing/wait.js +17 -0
- package/build/services/mcp/db-server/schemas.d.ts +2 -2
- package/build/start/index.js +13 -0
- package/build/system/mcp-servers/admin/schemas.d.ts +221 -221
- package/build/system/mcp-servers/db-query/schemas.d.ts +2 -2
- package/build/system/mcp-servers/human-queue-schemas.d.ts +6 -6
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/index.d.ts +2 -1
- package/build/types/machines.d.ts +166 -0
- package/build/types/machines.js +7 -0
- package/build/types/startup.d.ts +23 -0
- package/docs/machines.md +293 -0
- package/package.json +11 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Learned addresses. On a line that learns them, a station is bound to the
|
|
4
|
+
* address that first claims it. A claim that disagrees with a binding (a
|
|
5
|
+
* station already bound to another address, or an address bound to another
|
|
6
|
+
* station) is a person's decision: one registration row per claim, and the
|
|
7
|
+
* claimant's messages have no effect until it is approved. Lines that declare
|
|
8
|
+
* their addresses pass through unchanged.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.SENDER = void 0;
|
|
12
|
+
exports.resolveSender = resolveSender;
|
|
13
|
+
exports.identFor = identFor;
|
|
14
|
+
exports.reachable = reachable;
|
|
15
|
+
exports.registrationRow = registrationRow;
|
|
16
|
+
exports.planRegistrationRow = planRegistrationRow;
|
|
17
|
+
const contracts_1 = require("../contracts");
|
|
18
|
+
const line_1 = require("../../../lib/machines/line");
|
|
19
|
+
const protocol_1 = require("../../../lib/machines/protocol");
|
|
20
|
+
exports.SENDER = { KNOWN: 'known', BOUND: 'bound', CONTESTED: 'contested', UNKNOWN: 'unknown' };
|
|
21
|
+
const onLine = (line, machineId) => !!machineId && line.machines.some((m) => m.machineId === machineId);
|
|
22
|
+
function declaredAt(line, machineId, address) {
|
|
23
|
+
return onLine(line, machineId) && !(0, line_1.learnsAddress)(line, machineId) && (0, protocol_1.addressOf)((0, line_1.identOf)(line, machineId)) === address;
|
|
24
|
+
}
|
|
25
|
+
function asStation(seen, machineId, address) {
|
|
26
|
+
return { ...seen, parsed: { ...seen.parsed, ident: { ...seen.parsed.ident, machineId, address } } };
|
|
27
|
+
}
|
|
28
|
+
/** Who sent a message: a known station, one bound just now, a contested claim, or no station at all. */
|
|
29
|
+
function resolveSender(state, line, seen) {
|
|
30
|
+
const ident = seen.parsed.ident;
|
|
31
|
+
const address = ident.address ?? ident.machineId;
|
|
32
|
+
if (line.addressing !== line_1.ADDRESSING.LEARNED)
|
|
33
|
+
return { status: exports.SENDER.KNOWN, address, machineId: ident.machineId, seen };
|
|
34
|
+
// A station whose address the line declares is known only on that address.
|
|
35
|
+
if (declaredAt(line, ident.machineId, address))
|
|
36
|
+
return { status: exports.SENDER.KNOWN, address, machineId: ident.machineId, seen };
|
|
37
|
+
const claim = seen.claim ?? (ident.address ? ident.machineId : undefined);
|
|
38
|
+
const bound = state.stationAt(address);
|
|
39
|
+
if (bound) {
|
|
40
|
+
if (claim && claim !== bound)
|
|
41
|
+
return { status: exports.SENDER.CONTESTED, address, machineId: claim };
|
|
42
|
+
return { status: exports.SENDER.KNOWN, address, machineId: bound, seen: asStation(seen, bound, address) };
|
|
43
|
+
}
|
|
44
|
+
if (!onLine(line, claim) || !(0, line_1.learnsAddress)(line, claim))
|
|
45
|
+
return { status: exports.SENDER.UNKNOWN, address };
|
|
46
|
+
if (state.learnedAddress(claim))
|
|
47
|
+
return { status: exports.SENDER.CONTESTED, address, machineId: claim };
|
|
48
|
+
return { status: exports.SENDER.BOUND, address, machineId: claim, seen: asStation(seen, claim, address) };
|
|
49
|
+
}
|
|
50
|
+
/** A station's identity on the wire, with the address the line learned for it. */
|
|
51
|
+
function identFor(state, line, machineId) {
|
|
52
|
+
const learned = (0, line_1.learnsAddress)(line, machineId) ? state.learnedAddress(machineId) : undefined;
|
|
53
|
+
return { ...(0, line_1.identOf)(line, machineId), ...(learned ? { address: learned } : {}) };
|
|
54
|
+
}
|
|
55
|
+
/** Whether the gateway knows where to send to a station: a declared address, or one it learned. */
|
|
56
|
+
function reachable(state, line, machineId) {
|
|
57
|
+
return !(0, line_1.learnsAddress)(line, machineId) || !!state.learnedAddress(machineId);
|
|
58
|
+
}
|
|
59
|
+
/** The row asking a person whether `address` is `machineId`. */
|
|
60
|
+
function registrationRow(state, line, address, machineId) {
|
|
61
|
+
const boundAddress = state.learnedAddress(machineId) ?? null;
|
|
62
|
+
const boundStation = state.stationAt(address) ?? null;
|
|
63
|
+
const now = [
|
|
64
|
+
boundAddress ? `${machineId} is bound to ${boundAddress}` : null,
|
|
65
|
+
boundStation ? `${address} is bound to ${boundStation}` : null,
|
|
66
|
+
].filter(Boolean).join('; ');
|
|
67
|
+
return {
|
|
68
|
+
type: contracts_1.ROW_TYPE.MACHINE_REGISTRATION, role: (0, line_1.operatorRole)(line), priority: 2,
|
|
69
|
+
description: `${address} says it is ${machineId}. ${now}. Approve to bind ${machineId} to ${address}.`,
|
|
70
|
+
metadata: { siteId: line.siteId, lineId: line.lineId, machineId, address, boundAddress, boundStation },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/** A row's resolver payload, which Long Tail stores serialized. */
|
|
74
|
+
function resolutionOf(raw) {
|
|
75
|
+
if (raw && typeof raw === 'object')
|
|
76
|
+
return raw;
|
|
77
|
+
if (typeof raw !== 'string')
|
|
78
|
+
return null;
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(raw);
|
|
81
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Why an approved registration may not bind; null when it may. The row is only a request:
|
|
89
|
+
* the station must learn its address, the address must be one a machine could send from,
|
|
90
|
+
* and a machine at that address must have claimed the station on the wire.
|
|
91
|
+
*/
|
|
92
|
+
function bindingRefusal(state, line, machineId, address) {
|
|
93
|
+
if (!onLine(line, machineId) || !(0, line_1.learnsAddress)(line, machineId))
|
|
94
|
+
return `${machineId} is not a station on this line that learns its address`;
|
|
95
|
+
if (!(0, protocol_1.isTopicLevel)(address))
|
|
96
|
+
return `"${address}" is not an address a machine can send from`;
|
|
97
|
+
if (line.machines.some((m) => declaredAt(line, m.machineId, address)))
|
|
98
|
+
return `${address} is the declared address of another station`;
|
|
99
|
+
if (!state.claimHeard(address, machineId))
|
|
100
|
+
return `no machine at ${address} has claimed ${machineId} since this gateway took the line`;
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
/** A person decided a registration row: approval binds the station to the claimant; anything else leaves the binding. */
|
|
104
|
+
function planRegistrationRow(state, line, event) {
|
|
105
|
+
const md = event.data?.metadata ?? {};
|
|
106
|
+
if (typeof md.machineId !== 'string' || typeof md.address !== 'string')
|
|
107
|
+
return [];
|
|
108
|
+
if (event.verb === 'created') {
|
|
109
|
+
state.raiseClaim(md.address, md.machineId);
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
state.settleClaim(md.address, md.machineId);
|
|
113
|
+
const approved = event.verb === 'resolved' && resolutionOf(event.data?.resolver_payload)?.decision === contracts_1.DECISION.APPROVE;
|
|
114
|
+
const by = event.data?.resolved_by ?? event.data?.metadata?.resolved_by ?? undefined;
|
|
115
|
+
if (!approved)
|
|
116
|
+
return [];
|
|
117
|
+
const reason = bindingRefusal(state, line, md.machineId, md.address);
|
|
118
|
+
return reason
|
|
119
|
+
? [{ kind: 'refuseBinding', machineId: md.machineId, address: md.address, reason }]
|
|
120
|
+
: [{ kind: 'bindAddress', machineId: md.machineId, address: md.address, by }];
|
|
121
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command replies by cmdId, and machine snapshots by machine. A wait registers
|
|
3
|
+
* before the command is published and resolves with the reply, or null at the
|
|
4
|
+
* deadline.
|
|
5
|
+
*/
|
|
6
|
+
export interface CommandReply {
|
|
7
|
+
cmdId: string;
|
|
8
|
+
status: string;
|
|
9
|
+
reason: string;
|
|
10
|
+
}
|
|
11
|
+
/** The jobs a machine reports holding, queued or in work. */
|
|
12
|
+
export type HeldJobs = Set<string>;
|
|
13
|
+
export declare class ReplyWaiter {
|
|
14
|
+
private replies;
|
|
15
|
+
private snapshots;
|
|
16
|
+
wait(cmdId: string, timeoutMs: number): Promise<CommandReply | null>;
|
|
17
|
+
/** True when a waiter took the reply. */
|
|
18
|
+
settle(reply: CommandReply): boolean;
|
|
19
|
+
/** The next snapshot from `machineId`, or null at the deadline. */
|
|
20
|
+
waitSnapshot(machineId: string, timeoutMs: number): Promise<HeldJobs | null>;
|
|
21
|
+
/** Answer a status query in flight; null when there is nothing to wait for. */
|
|
22
|
+
settleSnapshot(machineId: string, jobs: HeldJobs | null): void;
|
|
23
|
+
cancelAll(): void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Command replies by cmdId, and machine snapshots by machine. A wait registers
|
|
4
|
+
* before the command is published and resolves with the reply, or null at the
|
|
5
|
+
* deadline.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.ReplyWaiter = void 0;
|
|
9
|
+
class Waits {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.waiting = new Map();
|
|
12
|
+
}
|
|
13
|
+
wait(key, timeoutMs) {
|
|
14
|
+
this.waiting.get(key)?.resolve(null);
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
const timer = setTimeout(() => {
|
|
17
|
+
this.waiting.delete(key);
|
|
18
|
+
resolve(null);
|
|
19
|
+
}, timeoutMs);
|
|
20
|
+
this.waiting.set(key, { resolve, timer });
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
settle(key, value) {
|
|
24
|
+
const w = this.waiting.get(key);
|
|
25
|
+
if (!w)
|
|
26
|
+
return false;
|
|
27
|
+
clearTimeout(w.timer);
|
|
28
|
+
this.waiting.delete(key);
|
|
29
|
+
w.resolve(value);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
cancelAll() {
|
|
33
|
+
for (const [, w] of this.waiting) {
|
|
34
|
+
clearTimeout(w.timer);
|
|
35
|
+
w.resolve(null);
|
|
36
|
+
}
|
|
37
|
+
this.waiting.clear();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
class ReplyWaiter {
|
|
41
|
+
constructor() {
|
|
42
|
+
this.replies = new Waits();
|
|
43
|
+
this.snapshots = new Waits();
|
|
44
|
+
}
|
|
45
|
+
wait(cmdId, timeoutMs) {
|
|
46
|
+
return this.replies.wait(cmdId, timeoutMs);
|
|
47
|
+
}
|
|
48
|
+
/** True when a waiter took the reply. */
|
|
49
|
+
settle(reply) {
|
|
50
|
+
return this.replies.settle(reply.cmdId, reply);
|
|
51
|
+
}
|
|
52
|
+
/** The next snapshot from `machineId`, or null at the deadline. */
|
|
53
|
+
waitSnapshot(machineId, timeoutMs) {
|
|
54
|
+
return this.snapshots.wait(machineId, timeoutMs);
|
|
55
|
+
}
|
|
56
|
+
/** Answer a status query in flight; null when there is nothing to wait for. */
|
|
57
|
+
settleSnapshot(machineId, jobs) {
|
|
58
|
+
this.snapshots.settle(machineId, jobs);
|
|
59
|
+
}
|
|
60
|
+
cancelAll() {
|
|
61
|
+
this.replies.cancelAll();
|
|
62
|
+
this.snapshots.cancelAll();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.ReplyWaiter = ReplyWaiter;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the gateway knows: the open handoff rows (from escalation events, so a
|
|
3
|
+
* replay rebuilds it), recently closed ones and how they ended (so replayed
|
|
4
|
+
* machine events are recognized as done), open alarm rows, and each
|
|
5
|
+
* machine's link: presence, last message, software and boot id.
|
|
6
|
+
*/
|
|
7
|
+
import type { HandoffFacets, TraceEntry } from '../contracts';
|
|
8
|
+
/**
|
|
9
|
+
* WAITING: the machine has no room yet (pacing.ts); the row waits in its role queue.
|
|
10
|
+
* ARRIVING: the job goes out once its units arrive at the receiver (codec `dispatchOn: arrival`).
|
|
11
|
+
*/
|
|
12
|
+
export declare const DISPATCH_STATE: {
|
|
13
|
+
readonly NONE: "none";
|
|
14
|
+
readonly WAITING: "waiting";
|
|
15
|
+
readonly ARRIVING: "arriving";
|
|
16
|
+
readonly PENDING: "pending";
|
|
17
|
+
readonly ACCEPTED: "accepted";
|
|
18
|
+
readonly REFUSED: "refused";
|
|
19
|
+
};
|
|
20
|
+
export type DispatchState = typeof DISPATCH_STATE[keyof typeof DISPATCH_STATE];
|
|
21
|
+
export declare const ROW_END: {
|
|
22
|
+
readonly RESOLVED: "resolved";
|
|
23
|
+
readonly CANCELLED: "cancelled";
|
|
24
|
+
readonly EXPIRED: "expired";
|
|
25
|
+
};
|
|
26
|
+
export type RowEnd = typeof ROW_END[keyof typeof ROW_END];
|
|
27
|
+
export interface HandoffRow extends HandoffFacets {
|
|
28
|
+
id: string;
|
|
29
|
+
role: string;
|
|
30
|
+
dispatch: DispatchState;
|
|
31
|
+
ended?: RowEnd;
|
|
32
|
+
/** Finished-unit records already on the row's metadata (trace.ts). */
|
|
33
|
+
trace?: TraceEntry[];
|
|
34
|
+
/** Each unit's arrival event, as its receiver reported it (dispatch on arrival). */
|
|
35
|
+
arrivals?: Record<string, Record<string, any>>;
|
|
36
|
+
}
|
|
37
|
+
export interface AlarmTrack {
|
|
38
|
+
machineId: string;
|
|
39
|
+
alarmId: string;
|
|
40
|
+
/** When the machine raised it; a reused alarm id with a new time is a new alarm. */
|
|
41
|
+
raisedAt?: string;
|
|
42
|
+
rowId?: string;
|
|
43
|
+
/** Created and not yet resolved by anyone. */
|
|
44
|
+
open: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface LinkTrack {
|
|
47
|
+
online: boolean;
|
|
48
|
+
since: number;
|
|
49
|
+
lastSeen: number;
|
|
50
|
+
rowId?: string;
|
|
51
|
+
quietRowId?: string;
|
|
52
|
+
watching?: boolean;
|
|
53
|
+
software?: string;
|
|
54
|
+
bootId?: string;
|
|
55
|
+
}
|
|
56
|
+
export declare class GatewayState {
|
|
57
|
+
/** Units one dispatch may carry, from the codec; null when any number may. */
|
|
58
|
+
unitsPerDispatch: number | null;
|
|
59
|
+
/** Jobs go to a receiver once its units arrive there, from the codec. */
|
|
60
|
+
dispatchOnArrival: boolean;
|
|
61
|
+
/** While reconciling, new handoffs wait so pacing sees the whole line before anything is sent. */
|
|
62
|
+
holdDispatch: boolean;
|
|
63
|
+
/** Units at each machine that arrived and have not left: machine → unit → job. */
|
|
64
|
+
private held;
|
|
65
|
+
private openRows;
|
|
66
|
+
private closedRows;
|
|
67
|
+
readonly alarms: Map<string, AlarmTrack>;
|
|
68
|
+
/** Finished records that came before their row: machine/unit → job body. */
|
|
69
|
+
private earlyFinishes;
|
|
70
|
+
private openCommands;
|
|
71
|
+
/** Accepted dispatch command ids → their row, so a later failure finds the row. */
|
|
72
|
+
private dispatches;
|
|
73
|
+
private endedCommands;
|
|
74
|
+
readonly links: Map<string, LinkTrack>;
|
|
75
|
+
/** Messages already shown to a person as contract violations. */
|
|
76
|
+
private violations;
|
|
77
|
+
/** Learned addresses, both ways: station → address and address → station. */
|
|
78
|
+
private addressOfStation;
|
|
79
|
+
private stationOfAddress;
|
|
80
|
+
/** Contested claims already raised as registration rows, `${address}>${station}`. */
|
|
81
|
+
private claims;
|
|
82
|
+
/** Contested claims heard from the machines themselves, `${address}>${station}`. */
|
|
83
|
+
private heard;
|
|
84
|
+
/** Addresses heard that claim no station, logged once each. */
|
|
85
|
+
private strangers;
|
|
86
|
+
index(row: HandoffRow): void;
|
|
87
|
+
get(id: string): HandoffRow | undefined;
|
|
88
|
+
close(id: string, ended?: RowEnd): HandoffRow | undefined;
|
|
89
|
+
isClosed(id: string): boolean;
|
|
90
|
+
open(): HandoffRow[];
|
|
91
|
+
/** The open handoff carrying this unit that `pred` picks (latest attempt first). */
|
|
92
|
+
openFor(unitId: string, pred: (r: HandoffRow) => boolean): HandoffRow | undefined;
|
|
93
|
+
closedFor(unitId: string, pred: (r: HandoffRow) => boolean): HandoffRow | undefined;
|
|
94
|
+
hold(machineId: string, unitId: string, jobId: string): void;
|
|
95
|
+
letGo(machineId: string, unitId: string): void;
|
|
96
|
+
/** The job a unit arrived under at `machineId`, while it is still there. */
|
|
97
|
+
heldJob(machineId: string, unitId: string): string | undefined;
|
|
98
|
+
heldAt(machineId: string): string[];
|
|
99
|
+
/** Keep only units whose job the machine still lists (its snapshot). */
|
|
100
|
+
keepHeld(machineId: string, jobIds: Set<string>): void;
|
|
101
|
+
/** Keep a sender's finished record until its row is known, so an arrival cannot close the row without it. */
|
|
102
|
+
rememberFinish(machineId: string, unitId: string, body: Record<string, any>): void;
|
|
103
|
+
takeFinish(machineId: string, unitId: string): Record<string, any> | undefined;
|
|
104
|
+
rememberDispatch(cmdId: string, rowId: string): void;
|
|
105
|
+
/** The open row an accepted dispatch command belongs to. */
|
|
106
|
+
rowForDispatch(cmdId: string): HandoffRow | undefined;
|
|
107
|
+
/** Track a machine-command row; false when it is already tracked or has ended. */
|
|
108
|
+
trackCommand(id: string): boolean;
|
|
109
|
+
endCommand(id: string): void;
|
|
110
|
+
/** Bind a station to an address, replacing whatever either was bound to before. */
|
|
111
|
+
bindAddress(machineId: string, address: string): void;
|
|
112
|
+
learnedAddress(machineId: string): string | undefined;
|
|
113
|
+
stationAt(address: string): string | undefined;
|
|
114
|
+
claimRaised(address: string, machineId: string): boolean;
|
|
115
|
+
raiseClaim(address: string, machineId: string): void;
|
|
116
|
+
settleClaim(address: string, machineId: string): void;
|
|
117
|
+
hearClaim(address: string, machineId: string): void;
|
|
118
|
+
claimHeard(address: string, machineId: string): boolean;
|
|
119
|
+
/** True the first time an address that claims no station is heard. */
|
|
120
|
+
firstHeardStranger(address: string): boolean;
|
|
121
|
+
violationRaised(key: string): boolean;
|
|
122
|
+
raiseViolation(key: string): void;
|
|
123
|
+
link(machineId: string, now?: number): LinkTrack;
|
|
124
|
+
alarmKey(machineId: string, alarmId: string): string;
|
|
125
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* What the gateway knows: the open handoff rows (from escalation events, so a
|
|
4
|
+
* replay rebuilds it), recently closed ones and how they ended (so replayed
|
|
5
|
+
* machine events are recognized as done), open alarm rows, and each
|
|
6
|
+
* machine's link: presence, last message, software and boot id.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.GatewayState = exports.ROW_END = exports.DISPATCH_STATE = void 0;
|
|
10
|
+
const trace_1 = require("./trace");
|
|
11
|
+
/**
|
|
12
|
+
* WAITING: the machine has no room yet (pacing.ts); the row waits in its role queue.
|
|
13
|
+
* ARRIVING: the job goes out once its units arrive at the receiver (codec `dispatchOn: arrival`).
|
|
14
|
+
*/
|
|
15
|
+
exports.DISPATCH_STATE = {
|
|
16
|
+
NONE: 'none', WAITING: 'waiting', ARRIVING: 'arriving', PENDING: 'pending', ACCEPTED: 'accepted', REFUSED: 'refused',
|
|
17
|
+
};
|
|
18
|
+
exports.ROW_END = { RESOLVED: 'resolved', CANCELLED: 'cancelled', EXPIRED: 'expired' };
|
|
19
|
+
const CLOSED_LIMIT = 2_000;
|
|
20
|
+
class GatewayState {
|
|
21
|
+
constructor() {
|
|
22
|
+
/** Units one dispatch may carry, from the codec; null when any number may. */
|
|
23
|
+
this.unitsPerDispatch = null;
|
|
24
|
+
/** Jobs go to a receiver once its units arrive there, from the codec. */
|
|
25
|
+
this.dispatchOnArrival = false;
|
|
26
|
+
/** While reconciling, new handoffs wait so pacing sees the whole line before anything is sent. */
|
|
27
|
+
this.holdDispatch = false;
|
|
28
|
+
/** Units at each machine that arrived and have not left: machine → unit → job. */
|
|
29
|
+
this.held = new Map();
|
|
30
|
+
this.openRows = new Map();
|
|
31
|
+
this.closedRows = new Map();
|
|
32
|
+
this.alarms = new Map();
|
|
33
|
+
/** Finished records that came before their row: machine/unit → job body. */
|
|
34
|
+
this.earlyFinishes = new Map();
|
|
35
|
+
this.openCommands = new Set();
|
|
36
|
+
/** Accepted dispatch command ids → their row, so a later failure finds the row. */
|
|
37
|
+
this.dispatches = new Map();
|
|
38
|
+
this.endedCommands = new Set();
|
|
39
|
+
this.links = new Map();
|
|
40
|
+
/** Messages already shown to a person as contract violations. */
|
|
41
|
+
this.violations = new Set();
|
|
42
|
+
/** Learned addresses, both ways: station → address and address → station. */
|
|
43
|
+
this.addressOfStation = new Map();
|
|
44
|
+
this.stationOfAddress = new Map();
|
|
45
|
+
/** Contested claims already raised as registration rows, `${address}>${station}`. */
|
|
46
|
+
this.claims = new Set();
|
|
47
|
+
/** Contested claims heard from the machines themselves, `${address}>${station}`. */
|
|
48
|
+
this.heard = new Set();
|
|
49
|
+
/** Addresses heard that claim no station, logged once each. */
|
|
50
|
+
this.strangers = new Set();
|
|
51
|
+
}
|
|
52
|
+
index(row) {
|
|
53
|
+
if (this.closedRows.has(row.id))
|
|
54
|
+
return;
|
|
55
|
+
const existing = this.openRows.get(row.id);
|
|
56
|
+
// An event may carry metadata older than what the gateway already wrote: keep both traces.
|
|
57
|
+
this.openRows.set(row.id, existing
|
|
58
|
+
? { ...row, dispatch: existing.dispatch, trace: (0, trace_1.mergeTrace)(row.trace, existing.trace), arrivals: existing.arrivals }
|
|
59
|
+
: row);
|
|
60
|
+
}
|
|
61
|
+
get(id) {
|
|
62
|
+
return this.openRows.get(id);
|
|
63
|
+
}
|
|
64
|
+
close(id, ended = exports.ROW_END.RESOLVED) {
|
|
65
|
+
const row = this.openRows.get(id);
|
|
66
|
+
if (!row)
|
|
67
|
+
return undefined;
|
|
68
|
+
this.openRows.delete(id);
|
|
69
|
+
row.ended = ended;
|
|
70
|
+
this.closedRows.set(id, row);
|
|
71
|
+
if (this.closedRows.size > CLOSED_LIMIT) {
|
|
72
|
+
const oldest = this.closedRows.keys().next().value;
|
|
73
|
+
if (oldest)
|
|
74
|
+
this.closedRows.delete(oldest);
|
|
75
|
+
}
|
|
76
|
+
return row;
|
|
77
|
+
}
|
|
78
|
+
isClosed(id) {
|
|
79
|
+
return this.closedRows.has(id);
|
|
80
|
+
}
|
|
81
|
+
open() {
|
|
82
|
+
return [...this.openRows.values()];
|
|
83
|
+
}
|
|
84
|
+
/** The open handoff carrying this unit that `pred` picks (latest attempt first). */
|
|
85
|
+
openFor(unitId, pred) {
|
|
86
|
+
return this.open().filter((r) => carries(r, unitId) && pred(r)).sort((a, b) => b.attempt - a.attempt)[0];
|
|
87
|
+
}
|
|
88
|
+
closedFor(unitId, pred) {
|
|
89
|
+
return [...this.closedRows.values()].find((r) => carries(r, unitId) && pred(r));
|
|
90
|
+
}
|
|
91
|
+
hold(machineId, unitId, jobId) {
|
|
92
|
+
if (!this.held.has(machineId))
|
|
93
|
+
this.held.set(machineId, new Map());
|
|
94
|
+
this.held.get(machineId).set(unitId, jobId);
|
|
95
|
+
}
|
|
96
|
+
letGo(machineId, unitId) {
|
|
97
|
+
this.held.get(machineId)?.delete(unitId);
|
|
98
|
+
}
|
|
99
|
+
/** The job a unit arrived under at `machineId`, while it is still there. */
|
|
100
|
+
heldJob(machineId, unitId) {
|
|
101
|
+
return this.held.get(machineId)?.get(unitId);
|
|
102
|
+
}
|
|
103
|
+
heldAt(machineId) {
|
|
104
|
+
return [...(this.held.get(machineId)?.values() ?? [])];
|
|
105
|
+
}
|
|
106
|
+
/** Keep only units whose job the machine still lists (its snapshot). */
|
|
107
|
+
keepHeld(machineId, jobIds) {
|
|
108
|
+
const units = this.held.get(machineId);
|
|
109
|
+
if (!units)
|
|
110
|
+
return;
|
|
111
|
+
for (const [unitId, jobId] of units)
|
|
112
|
+
if (!jobIds.has(jobId))
|
|
113
|
+
units.delete(unitId);
|
|
114
|
+
}
|
|
115
|
+
/** Keep a sender's finished record until its row is known, so an arrival cannot close the row without it. */
|
|
116
|
+
rememberFinish(machineId, unitId, body) {
|
|
117
|
+
this.earlyFinishes.delete(`${machineId}/${unitId}`);
|
|
118
|
+
this.earlyFinishes.set(`${machineId}/${unitId}`, body);
|
|
119
|
+
if (this.earlyFinishes.size > CLOSED_LIMIT) {
|
|
120
|
+
const oldest = this.earlyFinishes.keys().next().value;
|
|
121
|
+
if (oldest)
|
|
122
|
+
this.earlyFinishes.delete(oldest);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
takeFinish(machineId, unitId) {
|
|
126
|
+
const body = this.earlyFinishes.get(`${machineId}/${unitId}`);
|
|
127
|
+
this.earlyFinishes.delete(`${machineId}/${unitId}`);
|
|
128
|
+
return body;
|
|
129
|
+
}
|
|
130
|
+
rememberDispatch(cmdId, rowId) {
|
|
131
|
+
this.dispatches.set(cmdId, rowId);
|
|
132
|
+
if (this.dispatches.size > CLOSED_LIMIT) {
|
|
133
|
+
const oldest = this.dispatches.keys().next().value;
|
|
134
|
+
if (oldest)
|
|
135
|
+
this.dispatches.delete(oldest);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** The open row an accepted dispatch command belongs to. */
|
|
139
|
+
rowForDispatch(cmdId) {
|
|
140
|
+
const rowId = this.dispatches.get(cmdId);
|
|
141
|
+
return rowId ? this.openRows.get(rowId) : undefined;
|
|
142
|
+
}
|
|
143
|
+
/** Track a machine-command row; false when it is already tracked or has ended. */
|
|
144
|
+
trackCommand(id) {
|
|
145
|
+
if (this.openCommands.has(id) || this.endedCommands.has(id))
|
|
146
|
+
return false;
|
|
147
|
+
this.openCommands.add(id);
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
endCommand(id) {
|
|
151
|
+
this.openCommands.delete(id);
|
|
152
|
+
this.endedCommands.add(id);
|
|
153
|
+
if (this.endedCommands.size > CLOSED_LIMIT) {
|
|
154
|
+
const oldest = this.endedCommands.values().next().value;
|
|
155
|
+
if (oldest)
|
|
156
|
+
this.endedCommands.delete(oldest);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/** Bind a station to an address, replacing whatever either was bound to before. */
|
|
160
|
+
bindAddress(machineId, address) {
|
|
161
|
+
const before = this.addressOfStation.get(machineId);
|
|
162
|
+
if (before)
|
|
163
|
+
this.stationOfAddress.delete(before);
|
|
164
|
+
const other = this.stationOfAddress.get(address);
|
|
165
|
+
if (other)
|
|
166
|
+
this.addressOfStation.delete(other);
|
|
167
|
+
this.addressOfStation.set(machineId, address);
|
|
168
|
+
this.stationOfAddress.set(address, machineId);
|
|
169
|
+
}
|
|
170
|
+
learnedAddress(machineId) {
|
|
171
|
+
return this.addressOfStation.get(machineId);
|
|
172
|
+
}
|
|
173
|
+
stationAt(address) {
|
|
174
|
+
return this.stationOfAddress.get(address);
|
|
175
|
+
}
|
|
176
|
+
claimRaised(address, machineId) {
|
|
177
|
+
return this.claims.has(`${address}>${machineId}`);
|
|
178
|
+
}
|
|
179
|
+
raiseClaim(address, machineId) {
|
|
180
|
+
this.claims.add(`${address}>${machineId}`);
|
|
181
|
+
}
|
|
182
|
+
settleClaim(address, machineId) {
|
|
183
|
+
this.claims.delete(`${address}>${machineId}`);
|
|
184
|
+
}
|
|
185
|
+
hearClaim(address, machineId) {
|
|
186
|
+
this.heard.add(`${address}>${machineId}`);
|
|
187
|
+
if (this.heard.size > CLOSED_LIMIT)
|
|
188
|
+
this.heard.delete(this.heard.values().next().value);
|
|
189
|
+
}
|
|
190
|
+
claimHeard(address, machineId) {
|
|
191
|
+
return this.heard.has(`${address}>${machineId}`);
|
|
192
|
+
}
|
|
193
|
+
/** True the first time an address that claims no station is heard. */
|
|
194
|
+
firstHeardStranger(address) {
|
|
195
|
+
if (this.strangers.has(address))
|
|
196
|
+
return false;
|
|
197
|
+
this.strangers.add(address);
|
|
198
|
+
if (this.strangers.size > CLOSED_LIMIT)
|
|
199
|
+
this.strangers.delete(this.strangers.values().next().value);
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
violationRaised(key) {
|
|
203
|
+
return this.violations.has(key);
|
|
204
|
+
}
|
|
205
|
+
raiseViolation(key) {
|
|
206
|
+
this.violations.add(key);
|
|
207
|
+
if (this.violations.size > CLOSED_LIMIT) {
|
|
208
|
+
const oldest = this.violations.values().next().value;
|
|
209
|
+
if (oldest)
|
|
210
|
+
this.violations.delete(oldest);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
link(machineId, now = Date.now()) {
|
|
214
|
+
let link = this.links.get(machineId);
|
|
215
|
+
if (!link) {
|
|
216
|
+
link = { online: true, since: now, lastSeen: now };
|
|
217
|
+
this.links.set(machineId, link);
|
|
218
|
+
}
|
|
219
|
+
return link;
|
|
220
|
+
}
|
|
221
|
+
alarmKey(machineId, alarmId) {
|
|
222
|
+
return `${machineId}/${alarmId}`;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
exports.GatewayState = GatewayState;
|
|
226
|
+
function carries(row, unitId) {
|
|
227
|
+
return row.units.some((u) => u.unitId === unitId);
|
|
228
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Traceability on the row. When a sender finishes a unit, its record (result
|
|
3
|
+
* and the material lots it consumed) is added to the handoff row's metadata
|
|
4
|
+
* in the same statement that records the release, so a search by lot finds
|
|
5
|
+
* every handoff, and so every unit, that used it. The full record stays on
|
|
6
|
+
* the row as the release item.
|
|
7
|
+
*/
|
|
8
|
+
import { type TraceEntry } from '../contracts';
|
|
9
|
+
/** The trace entry for a finished job event from `machineId`. */
|
|
10
|
+
export declare function traceEntry(machineId: string, body: Record<string, any>): TraceEntry;
|
|
11
|
+
/** A row's trace as read back from its metadata; entries without a unit or machine are dropped. */
|
|
12
|
+
export declare function readTrace(md?: Record<string, any>): TraceEntry[];
|
|
13
|
+
/** Entries from both lists, one per unit and machine; a later entry replaces an earlier one. */
|
|
14
|
+
export declare function mergeTrace(a?: TraceEntry[], b?: TraceEntry[]): TraceEntry[];
|
|
15
|
+
/** The metadata patch for a trace: the entries, and every lot once. */
|
|
16
|
+
export declare function traceFacets(trace: TraceEntry[]): Record<string, unknown>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Traceability on the row. When a sender finishes a unit, its record (result
|
|
4
|
+
* and the material lots it consumed) is added to the handoff row's metadata
|
|
5
|
+
* in the same statement that records the release, so a search by lot finds
|
|
6
|
+
* every handoff, and so every unit, that used it. The full record stays on
|
|
7
|
+
* the row as the release item.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.traceEntry = traceEntry;
|
|
11
|
+
exports.readTrace = readTrace;
|
|
12
|
+
exports.mergeTrace = mergeTrace;
|
|
13
|
+
exports.traceFacets = traceFacets;
|
|
14
|
+
const contracts_1 = require("../contracts");
|
|
15
|
+
const str = (v) => (typeof v === 'string' && v ? v : null);
|
|
16
|
+
function materialsOf(v) {
|
|
17
|
+
if (!Array.isArray(v))
|
|
18
|
+
return [];
|
|
19
|
+
return v.filter((m) => str(m?.material) && str(m?.lot)).map((m) => ({ material: m.material, lot: m.lot }));
|
|
20
|
+
}
|
|
21
|
+
/** The trace entry for a finished job event from `machineId`. */
|
|
22
|
+
function traceEntry(machineId, body) {
|
|
23
|
+
return { unitId: String(body.unitId), machineId, jobId: str(body.jobId), result: str(body.result), materials: materialsOf(body.materials) };
|
|
24
|
+
}
|
|
25
|
+
/** A row's trace as read back from its metadata; entries without a unit or machine are dropped. */
|
|
26
|
+
function readTrace(md = {}) {
|
|
27
|
+
const raw = md[contracts_1.TRACE_FACET.TRACE];
|
|
28
|
+
if (!Array.isArray(raw))
|
|
29
|
+
return [];
|
|
30
|
+
return raw.filter((e) => str(e?.unitId) && str(e?.machineId)).map((e) => ({ ...e, materials: materialsOf(e.materials) }));
|
|
31
|
+
}
|
|
32
|
+
/** Entries from both lists, one per unit and machine; a later entry replaces an earlier one. */
|
|
33
|
+
function mergeTrace(a = [], b = []) {
|
|
34
|
+
const byKey = new Map();
|
|
35
|
+
for (const e of [...a, ...b])
|
|
36
|
+
byKey.set(`${e.unitId}@${e.machineId}`, e);
|
|
37
|
+
return [...byKey.values()];
|
|
38
|
+
}
|
|
39
|
+
/** The metadata patch for a trace: the entries, and every lot once. */
|
|
40
|
+
function traceFacets(trace) {
|
|
41
|
+
const lots = [...new Set(trace.flatMap((e) => e.materials.map((m) => m.lot)))];
|
|
42
|
+
return { [contracts_1.TRACE_FACET.TRACE]: trace, [contracts_1.TRACE_FACET.LOTS]: lots };
|
|
43
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identities inside Long Tail: each machine is a bot account holding its
|
|
3
|
+
* station roles, so rows it claims and resolves name the machine. Bots are
|
|
4
|
+
* found by name and given any role they are missing; nothing is removed. A
|
|
5
|
+
* person's account under a bot's name stops the line from starting.
|
|
6
|
+
*/
|
|
7
|
+
import type { LineDef } from '../../lib/machines/line';
|
|
8
|
+
import { type Actor } from './gateway/actions';
|
|
9
|
+
import type { AddressBook } from './gateway/address-book';
|
|
10
|
+
/** The user property on a station's bot account that holds the address its machine registered. */
|
|
11
|
+
export declare const ADDRESS_PROPERTY = "machineAddress";
|
|
12
|
+
/** Make sure every bot the line needs exists with its roles; returns actor → user id. */
|
|
13
|
+
export declare function ensureLineBots(line: LineDef): Promise<Map<Actor, string>>;
|
|
14
|
+
/**
|
|
15
|
+
* Learned addresses kept on each station's bot account, so a replica that
|
|
16
|
+
* takes the line over sends to the machines the line already knows. An address
|
|
17
|
+
* belongs to one station: binding it to a station clears it from any other.
|
|
18
|
+
*/
|
|
19
|
+
export declare class BotAddressBook implements AddressBook {
|
|
20
|
+
private readonly bots;
|
|
21
|
+
constructor(bots: Map<Actor, string>);
|
|
22
|
+
load(): Promise<Map<string, string>>;
|
|
23
|
+
bind(machineId: string, address: string): Promise<void>;
|
|
24
|
+
private read;
|
|
25
|
+
}
|