@relaycast/engine 12.1.0 → 12.2.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/dist/engine/action.d.ts.map +1 -1
- package/dist/engine/action.js +7 -6
- package/dist/engine/action.js.map +1 -1
- package/dist/engine/agent.d.ts.map +1 -1
- package/dist/engine/agent.js +37 -9
- package/dist/engine/agent.js.map +1 -1
- package/dist/engine/delivery.d.ts.map +1 -1
- package/dist/engine/delivery.js +5 -0
- package/dist/engine/delivery.js.map +1 -1
- package/dist/engine/deliveryWrites.d.ts +23 -0
- package/dist/engine/deliveryWrites.d.ts.map +1 -1
- package/dist/engine/deliveryWrites.js +50 -8
- package/dist/engine/deliveryWrites.js.map +1 -1
- package/dist/engine/groupDm.d.ts.map +1 -1
- package/dist/engine/groupDm.js +4 -0
- package/dist/engine/groupDm.js.map +1 -1
- package/dist/engine/inboundWebhook.d.ts.map +1 -1
- package/dist/engine/inboundWebhook.js +8 -1
- package/dist/engine/inboundWebhook.js.map +1 -1
- package/dist/engine/message.d.ts.map +1 -1
- package/dist/engine/message.js +4 -0
- package/dist/engine/message.js.map +1 -1
- package/dist/engine/node.d.ts +47 -42
- package/dist/engine/node.d.ts.map +1 -1
- package/dist/engine/node.js +96 -22
- package/dist/engine/node.js.map +1 -1
- package/dist/engine/nodeLiveness.d.ts +34 -0
- package/dist/engine/nodeLiveness.d.ts.map +1 -0
- package/dist/engine/nodeLiveness.js +52 -0
- package/dist/engine/nodeLiveness.js.map +1 -0
- package/dist/engine/placement.d.ts.map +1 -1
- package/dist/engine/placement.js +15 -18
- package/dist/engine/placement.js.map +1 -1
- package/dist/engine/presence.d.ts.map +1 -1
- package/dist/engine/presence.js +33 -6
- package/dist/engine/presence.js.map +1 -1
- package/dist/engine/thread.d.ts.map +1 -1
- package/dist/engine/thread.js +8 -0
- package/dist/engine/thread.js.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +3 -0
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/ports/database.d.ts.map +1 -1
- package/dist/ports/database.js +6 -1
- package/dist/ports/database.js.map +1 -1
- package/dist/ports/index.d.ts +4 -0
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js.map +1 -1
- package/dist/ports/nodeLiveness.d.ts +23 -0
- package/dist/ports/nodeLiveness.d.ts.map +1 -0
- package/dist/ports/nodeLiveness.js +12 -0
- package/dist/ports/nodeLiveness.js.map +1 -0
- package/dist/ports/presence.d.ts +7 -0
- package/dist/ports/presence.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/engine/node.d.ts
CHANGED
|
@@ -42,7 +42,16 @@ type CapabilityLike = string | FleetCapability;
|
|
|
42
42
|
export declare const IMPLICIT_DIRECT_NODE_ID_PREFIX = "node_direct_";
|
|
43
43
|
export declare function directNodeIdForAgent(agentId: string): string;
|
|
44
44
|
export declare const SERVER_OWNED_NODE_TAG_PREFIX = "cloud:";
|
|
45
|
-
declare function publicNode(row: NodeRow, now?: number): {
|
|
45
|
+
declare function publicNode(row: NodeRow, now?: number, authoritativeLive?: boolean, authorityStatus?: 'online' | 'offline' | 'unknown'): {
|
|
46
|
+
live: boolean;
|
|
47
|
+
handlers_live: boolean;
|
|
48
|
+
load: number | null;
|
|
49
|
+
active_agents: number;
|
|
50
|
+
active_agents_stale: boolean;
|
|
51
|
+
max_agents: number;
|
|
52
|
+
last_heartbeat_at: string | null;
|
|
53
|
+
created_at: string;
|
|
54
|
+
liveness_status?: "unknown" | "offline" | "online" | undefined;
|
|
46
55
|
id: string;
|
|
47
56
|
name: string;
|
|
48
57
|
kind: string;
|
|
@@ -61,14 +70,6 @@ declare function publicNode(row: NodeRow, now?: number): {
|
|
|
61
70
|
tags: string[];
|
|
62
71
|
version: string;
|
|
63
72
|
status: string;
|
|
64
|
-
live: boolean;
|
|
65
|
-
handlers_live: boolean;
|
|
66
|
-
load: number | null;
|
|
67
|
-
active_agents: number;
|
|
68
|
-
active_agents_stale: boolean;
|
|
69
|
-
max_agents: number;
|
|
70
|
-
last_heartbeat_at: string | null;
|
|
71
|
-
created_at: string;
|
|
72
73
|
};
|
|
73
74
|
export declare function createNodeToken(db: Db, workspaceId: string, data: {
|
|
74
75
|
node_id?: string;
|
|
@@ -94,6 +95,15 @@ export declare function createNodeToken(db: Db, workspaceId: string, data: {
|
|
|
94
95
|
admitRegistration?: RegistrationAdmission;
|
|
95
96
|
}): Promise<{
|
|
96
97
|
token: string;
|
|
98
|
+
live: boolean;
|
|
99
|
+
handlers_live: boolean;
|
|
100
|
+
load: number | null;
|
|
101
|
+
active_agents: number;
|
|
102
|
+
active_agents_stale: boolean;
|
|
103
|
+
max_agents: number;
|
|
104
|
+
last_heartbeat_at: string | null;
|
|
105
|
+
created_at: string;
|
|
106
|
+
liveness_status?: "unknown" | "offline" | "online" | undefined;
|
|
97
107
|
id: string;
|
|
98
108
|
name: string;
|
|
99
109
|
kind: string;
|
|
@@ -112,14 +122,6 @@ export declare function createNodeToken(db: Db, workspaceId: string, data: {
|
|
|
112
122
|
tags: string[];
|
|
113
123
|
version: string;
|
|
114
124
|
status: string;
|
|
115
|
-
live: boolean;
|
|
116
|
-
handlers_live: boolean;
|
|
117
|
-
load: number | null;
|
|
118
|
-
active_agents: number;
|
|
119
|
-
active_agents_stale: boolean;
|
|
120
|
-
max_agents: number;
|
|
121
|
-
last_heartbeat_at: string | null;
|
|
122
|
-
created_at: string;
|
|
123
125
|
}>;
|
|
124
126
|
export declare function getNodeByTokenHash(db: Db, tokenHash: string): Promise<{
|
|
125
127
|
id: string;
|
|
@@ -286,7 +288,7 @@ export declare function getBrokerNodeByMachineId(db: Db, workspaceId: string, ma
|
|
|
286
288
|
provenLiveAt: Date | null;
|
|
287
289
|
livenessRevision: number;
|
|
288
290
|
createdAt: Date;
|
|
289
|
-
}
|
|
291
|
+
}>;
|
|
290
292
|
/**
|
|
291
293
|
* The role an enroll is asking for, derived from the request alone.
|
|
292
294
|
*
|
|
@@ -375,6 +377,15 @@ export declare function registerNode(db: Db, workspaceId: string, authenticatedN
|
|
|
375
377
|
export declare function heartbeatNode(db: Db, workspaceId: string, nodeId: string, providerName: string, message: FleetNodeHeartbeatMessage, priorNode?: {
|
|
376
378
|
role: string | null;
|
|
377
379
|
} | null): Promise<{
|
|
380
|
+
live: boolean;
|
|
381
|
+
handlers_live: boolean;
|
|
382
|
+
load: number | null;
|
|
383
|
+
active_agents: number;
|
|
384
|
+
active_agents_stale: boolean;
|
|
385
|
+
max_agents: number;
|
|
386
|
+
last_heartbeat_at: string | null;
|
|
387
|
+
created_at: string;
|
|
388
|
+
liveness_status?: "unknown" | "offline" | "online" | undefined;
|
|
378
389
|
id: string;
|
|
379
390
|
name: string;
|
|
380
391
|
kind: string;
|
|
@@ -393,14 +404,6 @@ export declare function heartbeatNode(db: Db, workspaceId: string, nodeId: strin
|
|
|
393
404
|
tags: string[];
|
|
394
405
|
version: string;
|
|
395
406
|
status: string;
|
|
396
|
-
live: boolean;
|
|
397
|
-
handlers_live: boolean;
|
|
398
|
-
load: number | null;
|
|
399
|
-
active_agents: number;
|
|
400
|
-
active_agents_stale: boolean;
|
|
401
|
-
max_agents: number;
|
|
402
|
-
last_heartbeat_at: string | null;
|
|
403
|
-
created_at: string;
|
|
404
407
|
} | null>;
|
|
405
408
|
/**
|
|
406
409
|
* Flip a node's row offline in ONE conditional UPDATE — the only part of an
|
|
@@ -461,6 +464,15 @@ export declare function ensureDirectNodeForAgent(db: Db, workspaceId: string, ag
|
|
|
461
464
|
online?: boolean;
|
|
462
465
|
sessionRef?: string | null;
|
|
463
466
|
}): Promise<{
|
|
467
|
+
live: boolean;
|
|
468
|
+
handlers_live: boolean;
|
|
469
|
+
load: number | null;
|
|
470
|
+
active_agents: number;
|
|
471
|
+
active_agents_stale: boolean;
|
|
472
|
+
max_agents: number;
|
|
473
|
+
last_heartbeat_at: string | null;
|
|
474
|
+
created_at: string;
|
|
475
|
+
liveness_status?: "unknown" | "offline" | "online" | undefined;
|
|
464
476
|
id: string;
|
|
465
477
|
name: string;
|
|
466
478
|
kind: string;
|
|
@@ -479,14 +491,6 @@ export declare function ensureDirectNodeForAgent(db: Db, workspaceId: string, ag
|
|
|
479
491
|
tags: string[];
|
|
480
492
|
version: string;
|
|
481
493
|
status: string;
|
|
482
|
-
live: boolean;
|
|
483
|
-
handlers_live: boolean;
|
|
484
|
-
load: number | null;
|
|
485
|
-
active_agents: number;
|
|
486
|
-
active_agents_stale: boolean;
|
|
487
|
-
max_agents: number;
|
|
488
|
-
last_heartbeat_at: string | null;
|
|
489
|
-
created_at: string;
|
|
490
494
|
} | null>;
|
|
491
495
|
export declare function markDirectNodeOfflineForAgent(db: Db, workspaceId: string, agentId: string): Promise<void>;
|
|
492
496
|
declare function serializeBinding(row: {
|
|
@@ -658,6 +662,15 @@ export interface ListNodesHistoryPage {
|
|
|
658
662
|
}
|
|
659
663
|
export declare function listNodes(db: Db, workspaceId: string, filters?: ListNodesFilters): Promise<ListNodesHistoryPage>;
|
|
660
664
|
export declare function getPublicNode(db: Db, workspaceId: string, name: string): Promise<{
|
|
665
|
+
live: boolean;
|
|
666
|
+
handlers_live: boolean;
|
|
667
|
+
load: number | null;
|
|
668
|
+
active_agents: number;
|
|
669
|
+
active_agents_stale: boolean;
|
|
670
|
+
max_agents: number;
|
|
671
|
+
last_heartbeat_at: string | null;
|
|
672
|
+
created_at: string;
|
|
673
|
+
liveness_status?: "unknown" | "offline" | "online" | undefined;
|
|
661
674
|
id: string;
|
|
662
675
|
name: string;
|
|
663
676
|
kind: string;
|
|
@@ -676,14 +689,6 @@ export declare function getPublicNode(db: Db, workspaceId: string, name: string)
|
|
|
676
689
|
tags: string[];
|
|
677
690
|
version: string;
|
|
678
691
|
status: string;
|
|
679
|
-
live: boolean;
|
|
680
|
-
handlers_live: boolean;
|
|
681
|
-
load: number | null;
|
|
682
|
-
active_agents: number;
|
|
683
|
-
active_agents_stale: boolean;
|
|
684
|
-
max_agents: number;
|
|
685
|
-
last_heartbeat_at: string | null;
|
|
686
|
-
created_at: string;
|
|
687
692
|
} | null>;
|
|
688
693
|
export declare function handleNodeControlMessage(args: HandleNodeControlMessageArgs): Promise<void>;
|
|
689
694
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/engine/node.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/engine/node.ts"],"names":[],"mappings":"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"}
|
package/dist/engine/node.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nodeMayOwn, providerLive, filterAsync, nodeLivenessAuthority, inspectNodeLiveness, snapshotOnline } from './nodeLiveness.js';
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
import { invalidateChannelCache } from './cache.js';
|
|
3
4
|
import { and, asc, eq, exists, gt, gte, inArray, isNotNull, isNull, lt, lte, ne, notExists, or, sql } from 'drizzle-orm';
|
|
@@ -219,8 +220,8 @@ async function connectionNegotiatedDeliveryCursor(db, registry, workspaceId, nod
|
|
|
219
220
|
function requestId(message) {
|
|
220
221
|
return message.id ?? generateId();
|
|
221
222
|
}
|
|
222
|
-
function publicNode(row, now) {
|
|
223
|
-
const live = isNodeLive(row, now);
|
|
223
|
+
function publicNode(row, now, authoritativeLive, authorityStatus) {
|
|
224
|
+
const live = authoritativeLive ?? isNodeLive(row, now);
|
|
224
225
|
return {
|
|
225
226
|
id: row.id,
|
|
226
227
|
name: row.name,
|
|
@@ -233,6 +234,7 @@ function publicNode(row, now) {
|
|
|
233
234
|
tags: row.tags,
|
|
234
235
|
version: row.version,
|
|
235
236
|
status: live ? 'online' : 'offline',
|
|
237
|
+
...(authorityStatus ? { liveness_status: authorityStatus } : {}),
|
|
236
238
|
live,
|
|
237
239
|
handlers_live: live && row.handlersLive,
|
|
238
240
|
load: row.loadReported ? row.load : null,
|
|
@@ -423,7 +425,9 @@ export async function createNodeToken(db, workspaceId, data, authorization = {})
|
|
|
423
425
|
// Token proof is a generation fence, not just a read-time check. If a
|
|
424
426
|
// concurrent rotation changes the credential after `existing` was read,
|
|
425
427
|
// this request must not overwrite the winner with stale authority.
|
|
426
|
-
.where(and(eq(nodes.workspaceId, workspaceId), eq(nodes.id, existing.id),
|
|
428
|
+
.where(and(eq(nodes.workspaceId, workspaceId), eq(nodes.id, existing.id),
|
|
429
|
+
// Accepted reconnect/register advances this revision before its lease.
|
|
430
|
+
...(nodeLivenessAuthority(db) ? [eq(nodes.livenessRevision, existing.livenessRevision)] : []), ...(hasPresentedTokenProof && !authorizedDirectNode
|
|
427
431
|
? [eq(nodes.tokenHash, authorization.expectedTokenHash)]
|
|
428
432
|
: [])))
|
|
429
433
|
.returning();
|
|
@@ -540,7 +544,7 @@ export async function getBrokerNodeByMachineId(db, workspaceId, machineId, expec
|
|
|
540
544
|
.from(nodes)
|
|
541
545
|
.where(and(reusableWhere, eq(nodes.tokenHash, expectedTokenHash)))
|
|
542
546
|
.limit(1);
|
|
543
|
-
if (provenCandidate &&
|
|
547
|
+
if (provenCandidate && await reusableMachineNode(db, provenCandidate)) {
|
|
544
548
|
return provenCandidate;
|
|
545
549
|
}
|
|
546
550
|
}
|
|
@@ -554,7 +558,7 @@ export async function getBrokerNodeByMachineId(db, workspaceId, machineId, expec
|
|
|
554
558
|
// single definition of reusability and mirrors it exactly — including
|
|
555
559
|
// treating an `online` row with a future heartbeat as live. Re-checking here
|
|
556
560
|
// keeps that definition authoritative rather than trusting the query alone.
|
|
557
|
-
return
|
|
561
|
+
return (await filterAsync(candidates, node => reusableMachineNode(db, node)))[0] ?? null;
|
|
558
562
|
}
|
|
559
563
|
/**
|
|
560
564
|
* The role an enroll is asking for, derived from the request alone.
|
|
@@ -1072,8 +1076,14 @@ export async function deregisterProvider(db, registry, workspaceId, nodeId, prov
|
|
|
1072
1076
|
}
|
|
1073
1077
|
}
|
|
1074
1078
|
export async function sweepOfflineNodes(db, registry, deps) {
|
|
1079
|
+
// Only the external socket owner can fence this transition with reconnect.
|
|
1080
|
+
if (nodeLivenessAuthority(db))
|
|
1081
|
+
return 0;
|
|
1075
1082
|
const rows = await db.select().from(nodes).where(eq(nodes.status, 'online'));
|
|
1076
|
-
const stale = rows
|
|
1083
|
+
const stale = await filterAsync(rows, async (node) => {
|
|
1084
|
+
const authority = await inspectNodeLiveness(db, node.workspaceId, node.id);
|
|
1085
|
+
return authority ? authority.status === 'offline' : !isNodeLive(node);
|
|
1086
|
+
});
|
|
1077
1087
|
let swept = 0;
|
|
1078
1088
|
for (const node of stale) {
|
|
1079
1089
|
const offlined = await serializeNodeOp(node.workspaceId, node.id, async () => {
|
|
@@ -1083,7 +1093,14 @@ export async function sweepOfflineNodes(db, registry, deps) {
|
|
|
1083
1093
|
.select()
|
|
1084
1094
|
.from(nodes)
|
|
1085
1095
|
.where(and(eq(nodes.workspaceId, node.workspaceId), eq(nodes.id, node.id)));
|
|
1086
|
-
if (!current || current.status !== 'online'
|
|
1096
|
+
if (!current || current.status !== 'online')
|
|
1097
|
+
return false;
|
|
1098
|
+
const authority = await inspectNodeLiveness(db, current.workspaceId, current.id);
|
|
1099
|
+
if (authority ? authority.status !== 'offline' : isNodeLive(current))
|
|
1100
|
+
return false;
|
|
1101
|
+
// External owners serialize offline transitions with reconnect; this
|
|
1102
|
+
// generic sweep cannot hold that fence across markNodeOffline.
|
|
1103
|
+
if (authority)
|
|
1087
1104
|
return false;
|
|
1088
1105
|
await markNodeOffline(db, registry, node.workspaceId, node.id, { deps, reason: 'liveness_timeout' });
|
|
1089
1106
|
return true;
|
|
@@ -1464,6 +1481,7 @@ export async function bindAgentToNode(db, workspaceId, nodeName, agentName, opts
|
|
|
1464
1481
|
// stranded on its implicit `direct-*` pseudo-node and never woken — the
|
|
1465
1482
|
// exact shape of the broker's create-only register falling back to this
|
|
1466
1483
|
// endpoint. `unbindAgentFromNode` already assumes this move happened.
|
|
1484
|
+
let incumbentNode;
|
|
1467
1485
|
if (agent.status === 'active'
|
|
1468
1486
|
&& agent.locationNodeId
|
|
1469
1487
|
&& agent.locationNodeId !== node.id
|
|
@@ -1474,16 +1492,17 @@ export async function bindAgentToNode(db, workspaceId, nodeName, agentName, opts
|
|
|
1474
1492
|
.where(and(eq(nodes.workspaceId, workspaceId), eq(nodes.id, agent.locationNodeId)));
|
|
1475
1493
|
// A dead or missing prior location is not a conflict — it is exactly what
|
|
1476
1494
|
// a bind is for. Only a live one still owns the identity.
|
|
1477
|
-
if (locatedNode &&
|
|
1495
|
+
if (locatedNode && await nodeMayOwn(db, locatedNode)) {
|
|
1478
1496
|
throw codedError(`Agent "${agentName}" is already active on another live location`, 'agent_location_conflict', 409);
|
|
1479
1497
|
}
|
|
1498
|
+
incumbentNode = locatedNode;
|
|
1480
1499
|
}
|
|
1481
1500
|
// Provider rows persist after a disconnect so the node keeps its capability
|
|
1482
1501
|
// manifest; they are history, not sockets. Adopt only among providers that
|
|
1483
1502
|
// can actually receive a push right now — keeping or choosing an offline
|
|
1484
1503
|
// row while a live sibling serves the node would point the agent at a
|
|
1485
1504
|
// provider connection that does not exist.
|
|
1486
|
-
const
|
|
1505
|
+
const providerCandidates = await tx
|
|
1487
1506
|
.select({
|
|
1488
1507
|
name: nodeProviders.name,
|
|
1489
1508
|
status: nodeProviders.status,
|
|
@@ -1491,9 +1510,8 @@ export async function bindAgentToNode(db, workspaceId, nodeName, agentName, opts
|
|
|
1491
1510
|
lastHeartbeatAt: nodeProviders.lastHeartbeatAt,
|
|
1492
1511
|
})
|
|
1493
1512
|
.from(nodeProviders)
|
|
1494
|
-
.where(and(eq(nodeProviders.workspaceId, workspaceId), eq(nodeProviders.nodeId, node.id)))
|
|
1495
|
-
|
|
1496
|
-
.map((row) => row.name);
|
|
1513
|
+
.where(and(eq(nodeProviders.workspaceId, workspaceId), eq(nodeProviders.nodeId, node.id)));
|
|
1514
|
+
const liveProviderNames = (await filterAsync(providerCandidates, provider => providerLive(db, workspaceId, node.id, provider))).map(row => row.name);
|
|
1497
1515
|
const adoptedProviderName = adoptNodeProviderName(agent.providerName, liveProviderNames);
|
|
1498
1516
|
const activeNodeIds = await activeBindingNodeIdsForAgent(tx, workspaceId, agent.id);
|
|
1499
1517
|
const targetWasActive = activeNodeIds.includes(node.id);
|
|
@@ -1511,6 +1529,9 @@ export async function bindAgentToNode(db, workspaceId, nodeName, agentName, opts
|
|
|
1511
1529
|
// charges the old node forever, because the retry that would fix either
|
|
1512
1530
|
// reads the half-applied state as already done.
|
|
1513
1531
|
const buildMoveWrites = (writeDb) => [
|
|
1532
|
+
...(incumbentNode && nodeLivenessAuthority(db) ? [writeDb.update(nodes).set({
|
|
1533
|
+
activeAgents: sql `CASE WHEN ${nodes.livenessRevision} = ${incumbentNode.livenessRevision} THEN ${nodes.activeAgents} ELSE NULL END`,
|
|
1534
|
+
}).where(and(eq(nodes.workspaceId, workspaceId), eq(nodes.id, incumbentNode.id)))] : []),
|
|
1514
1535
|
...agentNodeBindingWrites(writeDb, workspaceId, agent, node.id, {
|
|
1515
1536
|
sessionRef: opts.session_ref ?? null,
|
|
1516
1537
|
priority: opts.priority ?? 0,
|
|
@@ -2061,7 +2082,9 @@ export async function reconcileInventory(db, registry, workspaceId, nodeId, prov
|
|
|
2061
2082
|
const acceptedInventoryAgents = [];
|
|
2062
2083
|
const rejectedInventoryErrors = [];
|
|
2063
2084
|
const legacyDefaultAdoptions = new Set();
|
|
2085
|
+
const incumbentRevisions = new Map();
|
|
2064
2086
|
let canAdoptLegacyDefault;
|
|
2087
|
+
let authorityAdoption;
|
|
2065
2088
|
let acceptedExistingAgentCount = 0;
|
|
2066
2089
|
for (const item of inventoryAgents) {
|
|
2067
2090
|
const [existing] = await db
|
|
@@ -2092,8 +2115,23 @@ export async function reconcileInventory(db, registry, workspaceId, nodeId, prov
|
|
|
2092
2115
|
.where(and(eq(nodeProviders.workspaceId, workspaceId), eq(nodeProviders.nodeId, nodeId), inArray(nodeProviders.name, [DEFAULT_PROVIDER_NAME, 'broker'])));
|
|
2093
2116
|
const defaultProvider = providerRows.find((row) => row.name === DEFAULT_PROVIDER_NAME);
|
|
2094
2117
|
const brokerProvider = providerRows.find((row) => row.name === 'broker');
|
|
2095
|
-
|
|
2096
|
-
|
|
2118
|
+
const authority = await inspectNodeLiveness(db, workspaceId, nodeId);
|
|
2119
|
+
if (authority) {
|
|
2120
|
+
const now = Date.now();
|
|
2121
|
+
const brokerLease = authority.providers.find(p => p.name === 'broker' && p.live && p.handlers_live && p.expires_at > now && p.last_received_at <= now);
|
|
2122
|
+
const defaultLive = authority.providers.some(p => p.name === DEFAULT_PROVIDER_NAME && p.live && p.handlers_live && p.expires_at > now && p.last_received_at <= now);
|
|
2123
|
+
canAdoptLegacyDefault = authority.status === 'online' && !!brokerProvider && !!brokerLease && !defaultLive;
|
|
2124
|
+
if (canAdoptLegacyDefault && brokerProvider && brokerLease)
|
|
2125
|
+
authorityAdoption = {
|
|
2126
|
+
brokerId: brokerProvider.id, brokerRevision: brokerProvider.livenessRevision,
|
|
2127
|
+
defaultId: defaultProvider?.id, defaultRevision: defaultProvider?.livenessRevision,
|
|
2128
|
+
expiresAt: brokerLease.expires_at,
|
|
2129
|
+
};
|
|
2130
|
+
}
|
|
2131
|
+
else {
|
|
2132
|
+
canAdoptLegacyDefault = !!brokerProvider && isProviderLive(brokerProvider)
|
|
2133
|
+
&& (!defaultProvider || !isProviderLive(defaultProvider));
|
|
2134
|
+
}
|
|
2097
2135
|
}
|
|
2098
2136
|
if (legacyDefaultClaim && canAdoptLegacyDefault) {
|
|
2099
2137
|
legacyDefaultAdoptions.add(item.name);
|
|
@@ -2107,7 +2145,9 @@ export async function reconcileInventory(db, registry, workspaceId, nodeId, prov
|
|
|
2107
2145
|
.select()
|
|
2108
2146
|
.from(nodes)
|
|
2109
2147
|
.where(and(eq(nodes.workspaceId, workspaceId), eq(nodes.id, existing.locationNodeId ?? '')));
|
|
2110
|
-
const boundNodeLive = !!boundNode &&
|
|
2148
|
+
const boundNodeLive = !!boundNode && await nodeMayOwn(db, boundNode);
|
|
2149
|
+
if (boundNode && boundNode.id !== nodeId && !boundNodeLive && nodeLivenessAuthority(db))
|
|
2150
|
+
incumbentRevisions.set(item.name, { id: boundNode.id, revision: boundNode.livenessRevision });
|
|
2111
2151
|
const conflict = existing.locationType !== 'via_node'
|
|
2112
2152
|
|| !existing.locationNodeId
|
|
2113
2153
|
|| (existing.locationNodeId !== nodeId && boundNodeLive && !isImplicitDirectLocation(existing));
|
|
@@ -2208,14 +2248,24 @@ export async function reconcileInventory(db, registry, workspaceId, nodeId, prov
|
|
|
2208
2248
|
// compare-and-set fences broker liveness loss, a default provider
|
|
2209
2249
|
// reconnecting, and concurrent ownership changes during the apply pass.
|
|
2210
2250
|
const adoptionNow = Date.now();
|
|
2211
|
-
|
|
2251
|
+
let noLiveDefaultProvider = notExists(db
|
|
2212
2252
|
.select({ id: nodeProviders.id })
|
|
2213
2253
|
.from(nodeProviders)
|
|
2214
2254
|
.where(and(eq(nodeProviders.workspaceId, workspaceId), eq(nodeProviders.nodeId, nodeId), eq(nodeProviders.name, DEFAULT_PROVIDER_NAME), eq(nodeProviders.status, 'online'), eq(nodeProviders.handlersLive, true), gte(nodeProviders.lastHeartbeatAt, new Date(adoptionNow - NODE_LIVENESS_TTL_MS)), lte(nodeProviders.lastHeartbeatAt, new Date(adoptionNow)))));
|
|
2215
|
-
|
|
2255
|
+
let liveBrokerProvider = exists(db
|
|
2216
2256
|
.select({ id: nodeProviders.id })
|
|
2217
2257
|
.from(nodeProviders)
|
|
2218
2258
|
.where(and(eq(nodeProviders.workspaceId, workspaceId), eq(nodeProviders.nodeId, nodeId), eq(nodeProviders.name, 'broker'), eq(nodeProviders.status, 'online'), eq(nodeProviders.handlersLive, true), gte(nodeProviders.lastHeartbeatAt, new Date(adoptionNow - NODE_LIVENESS_TTL_MS)), lte(nodeProviders.lastHeartbeatAt, new Date(adoptionNow)))));
|
|
2259
|
+
if (authorityAdoption) {
|
|
2260
|
+
// Every accepted register/reconnect advances the provider revision
|
|
2261
|
+
// before renewing its owner lease. Fence those generations in the
|
|
2262
|
+
// same SQL update, and reject if the observed broker lease expires.
|
|
2263
|
+
const proof = authorityAdoption;
|
|
2264
|
+
noLiveDefaultProvider = proof.defaultId
|
|
2265
|
+
? exists(db.select({ id: nodeProviders.id }).from(nodeProviders).where(and(eq(nodeProviders.workspaceId, workspaceId), eq(nodeProviders.nodeId, nodeId), eq(nodeProviders.name, DEFAULT_PROVIDER_NAME), eq(nodeProviders.id, proof.defaultId), eq(nodeProviders.livenessRevision, proof.defaultRevision))))
|
|
2266
|
+
: notExists(db.select({ id: nodeProviders.id }).from(nodeProviders).where(and(eq(nodeProviders.workspaceId, workspaceId), eq(nodeProviders.nodeId, nodeId), eq(nodeProviders.name, DEFAULT_PROVIDER_NAME))));
|
|
2267
|
+
liveBrokerProvider = exists(db.select({ id: nodeProviders.id }).from(nodeProviders).where(and(eq(nodeProviders.workspaceId, workspaceId), eq(nodeProviders.nodeId, nodeId), eq(nodeProviders.name, 'broker'), eq(nodeProviders.id, proof.brokerId), eq(nodeProviders.livenessRevision, proof.brokerRevision), sql `unixepoch() * 1000 < ${proof.expiresAt}`)));
|
|
2268
|
+
}
|
|
2219
2269
|
const [updated] = await db
|
|
2220
2270
|
.update(agents)
|
|
2221
2271
|
.set({
|
|
@@ -2227,7 +2277,7 @@ export async function reconcileInventory(db, registry, workspaceId, nodeId, prov
|
|
|
2227
2277
|
originNodeId: existing.originNodeId ?? nodeId,
|
|
2228
2278
|
sessionRef: item.session_ref ?? existing.sessionRef,
|
|
2229
2279
|
})
|
|
2230
|
-
.where(and(eq(agents.workspaceId, workspaceId), eq(agents.id, existing.id), ...(adoptingLegacyDefault ? [
|
|
2280
|
+
.where(and(eq(agents.workspaceId, workspaceId), eq(agents.id, existing.id), ...(incumbentRevisions.has(item.name) ? [notExists(db.select({ id: nodes.id }).from(nodes).where(and(eq(nodes.workspaceId, workspaceId), eq(nodes.id, incumbentRevisions.get(item.name).id), ne(nodes.livenessRevision, incumbentRevisions.get(item.name).revision))))] : []), ...(adoptingLegacyDefault ? [
|
|
2231
2281
|
eq(agents.providerName, DEFAULT_PROVIDER_NAME),
|
|
2232
2282
|
eq(agents.locationType, 'via_node'),
|
|
2233
2283
|
eq(agents.locationNodeId, nodeId),
|
|
@@ -2434,7 +2484,7 @@ export async function listNodes(db, workspaceId, filters = {}) {
|
|
|
2434
2484
|
conditions.push(eq(nodes.name, filters.name));
|
|
2435
2485
|
if (filters.capability)
|
|
2436
2486
|
conditions.push(capabilityMatchCondition(filters.capability));
|
|
2437
|
-
if (filters.status)
|
|
2487
|
+
if (filters.status && !nodeLivenessAuthority(db))
|
|
2438
2488
|
conditions.push(livenessCondition(filters.status, now));
|
|
2439
2489
|
// Observer authorization is a WHERE condition, evaluated in the same query
|
|
2440
2490
|
// as every other filter and therefore before pagination/cursor computation
|
|
@@ -2448,7 +2498,7 @@ export async function listNodes(db, workspaceId, filters = {}) {
|
|
|
2448
2498
|
// that predates the history/status selector. `name`/`capability`/`status`
|
|
2449
2499
|
// are still evaluated in SQL instead of after a full-table fetch.
|
|
2450
2500
|
const rows = await db.select().from(nodes).where(and(...conditions));
|
|
2451
|
-
const page =
|
|
2501
|
+
const page = await publicNodes(db, rows, now.getTime(), filters.status);
|
|
2452
2502
|
if (!filters.observerAgentIds)
|
|
2453
2503
|
return { nodes: page, nextCursor: null };
|
|
2454
2504
|
const counts = await visibleActiveAgentCounts(db, workspaceId, rows.map((row) => row.id), filters.observerAgentIds);
|
|
@@ -2468,7 +2518,7 @@ export async function listNodes(db, workspaceId, filters = {}) {
|
|
|
2468
2518
|
.limit(limit + 1);
|
|
2469
2519
|
const page = rows.slice(0, limit);
|
|
2470
2520
|
const nextCursor = rows.length > limit ? page[page.length - 1].id : null;
|
|
2471
|
-
const publicPage =
|
|
2521
|
+
const publicPage = await publicNodes(db, page, now.getTime(), filters.status);
|
|
2472
2522
|
if (!filters.observerAgentIds)
|
|
2473
2523
|
return { nodes: publicPage, nextCursor };
|
|
2474
2524
|
const counts = await visibleActiveAgentCounts(db, workspaceId, page.map((row) => row.id), filters.observerAgentIds);
|
|
@@ -2479,7 +2529,7 @@ export async function listNodes(db, workspaceId, filters = {}) {
|
|
|
2479
2529
|
}
|
|
2480
2530
|
export async function getPublicNode(db, workspaceId, name) {
|
|
2481
2531
|
const node = await getNodeByName(db, workspaceId, name);
|
|
2482
|
-
return node ?
|
|
2532
|
+
return node ? (await publicNodes(db, [node], Date.now()))[0] : null;
|
|
2483
2533
|
}
|
|
2484
2534
|
/** The node's persisted `status` column, or undefined when the node is gone. */
|
|
2485
2535
|
async function readNodeStatus(db, workspaceId, nodeId) {
|
|
@@ -2940,4 +2990,28 @@ export async function handleNodeControlMessage(args) {
|
|
|
2940
2990
|
}
|
|
2941
2991
|
});
|
|
2942
2992
|
}
|
|
2993
|
+
async function publicNodes(db, rows, now, status) {
|
|
2994
|
+
if (!nodeLivenessAuthority(db))
|
|
2995
|
+
return rows.map(row => publicNode(row, now));
|
|
2996
|
+
const result = [];
|
|
2997
|
+
await filterAsync(rows, async (row) => {
|
|
2998
|
+
const snapshot = await inspectNodeLiveness(db, row.workspaceId, row.id);
|
|
2999
|
+
const live = !!snapshot && snapshotOnline(snapshot);
|
|
3000
|
+
const item = publicNode(row, now, live, snapshot?.status ?? 'unknown');
|
|
3001
|
+
// Readiness is owned by the fresh lease, independently of checkpointed
|
|
3002
|
+
// catalog metadata in nodes.handlers_live.
|
|
3003
|
+
const receivedAt = Date.now();
|
|
3004
|
+
item.handlers_live = live && !!snapshot?.providers.some(p => p.live && p.handlers_live && p.expires_at > receivedAt && p.last_received_at <= receivedAt);
|
|
3005
|
+
if (!status || item.status === status)
|
|
3006
|
+
result.push(item);
|
|
3007
|
+
return true;
|
|
3008
|
+
});
|
|
3009
|
+
// Concurrent probes must not scramble stable list ordering.
|
|
3010
|
+
const positions = new Map(rows.map((row, index) => [row.id, index]));
|
|
3011
|
+
return result.sort((a, b) => positions.get(a.id) - positions.get(b.id));
|
|
3012
|
+
}
|
|
3013
|
+
async function reusableMachineNode(db, node) {
|
|
3014
|
+
const authority = await inspectNodeLiveness(db, node.workspaceId, node.id);
|
|
3015
|
+
return authority ? authority.status === 'offline' : isReusableForMachineMatch(node);
|
|
3016
|
+
}
|
|
2943
3017
|
//# sourceMappingURL=node.js.map
|