hungry-ghost-hive 0.44.0 → 0.46.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/agents/base-agent.d.ts +1 -0
- package/dist/agents/base-agent.d.ts.map +1 -1
- package/dist/agents/base-agent.js +4 -0
- package/dist/agents/base-agent.js.map +1 -1
- package/dist/agents/intermediate.js +2 -2
- package/dist/agents/intermediate.js.map +1 -1
- package/dist/agents/junior.js +2 -2
- package/dist/agents/junior.js.map +1 -1
- package/dist/agents/qa.d.ts.map +1 -1
- package/dist/agents/qa.js +5 -5
- package/dist/agents/qa.js.map +1 -1
- package/dist/agents/senior.d.ts.map +1 -1
- package/dist/agents/senior.js +5 -5
- package/dist/agents/senior.js.map +1 -1
- package/dist/agents/tech-lead.d.ts.map +1 -1
- package/dist/agents/tech-lead.js +4 -2
- package/dist/agents/tech-lead.js.map +1 -1
- package/dist/cli/commands/assign.d.ts.map +1 -1
- package/dist/cli/commands/assign.js +4 -2
- package/dist/cli/commands/assign.js.map +1 -1
- package/dist/cli/commands/assign.test.js +5 -0
- package/dist/cli/commands/assign.test.js.map +1 -1
- package/dist/cli/commands/cluster.d.ts.map +1 -1
- package/dist/cli/commands/cluster.js +348 -1
- package/dist/cli/commands/cluster.js.map +1 -1
- package/dist/cli/commands/cluster.test.js +313 -9
- package/dist/cli/commands/cluster.test.js.map +1 -1
- package/dist/cli/commands/manager/handoff-recovery.d.ts.map +1 -1
- package/dist/cli/commands/manager/handoff-recovery.js +4 -2
- package/dist/cli/commands/manager/handoff-recovery.js.map +1 -1
- package/dist/cli/commands/manager/index.d.ts.map +1 -1
- package/dist/cli/commands/manager/index.js +16 -12
- package/dist/cli/commands/manager/index.js.map +1 -1
- package/dist/cli/commands/manager/tech-lead-lifecycle.d.ts.map +1 -1
- package/dist/cli/commands/manager/tech-lead-lifecycle.js +4 -2
- package/dist/cli/commands/manager/tech-lead-lifecycle.js.map +1 -1
- package/dist/cli/commands/msg.d.ts.map +1 -1
- package/dist/cli/commands/msg.js +8 -7
- package/dist/cli/commands/msg.js.map +1 -1
- package/dist/cli/commands/my-stories.js +3 -3
- package/dist/cli/commands/my-stories.js.map +1 -1
- package/dist/cli/commands/nuke.d.ts.map +1 -1
- package/dist/cli/commands/nuke.js +18 -7
- package/dist/cli/commands/nuke.js.map +1 -1
- package/dist/cli/commands/nuke.test.js +24 -0
- package/dist/cli/commands/nuke.test.js.map +1 -1
- package/dist/cli/commands/req-spawn.test.d.ts +2 -0
- package/dist/cli/commands/req-spawn.test.d.ts.map +1 -0
- package/dist/cli/commands/req-spawn.test.js +116 -0
- package/dist/cli/commands/req-spawn.test.js.map +1 -0
- package/dist/cli/commands/req.d.ts +1 -1
- package/dist/cli/commands/req.d.ts.map +1 -1
- package/dist/cli/commands/req.js +28 -18
- package/dist/cli/commands/req.js.map +1 -1
- package/dist/cli/commands/stories.js +3 -3
- package/dist/cli/commands/stories.js.map +1 -1
- package/dist/cli/dashboard/panels/agents.d.ts.map +1 -1
- package/dist/cli/dashboard/panels/agents.js +7 -3
- package/dist/cli/dashboard/panels/agents.js.map +1 -1
- package/dist/cluster/cluster-http-server.d.ts +32 -0
- package/dist/cluster/cluster-http-server.d.ts.map +1 -1
- package/dist/cluster/cluster-http-server.js +42 -0
- package/dist/cluster/cluster-http-server.js.map +1 -1
- package/dist/cluster/distributed-runtime-coverage.test.js +9 -0
- package/dist/cluster/distributed-runtime-coverage.test.js.map +1 -1
- package/dist/cluster/distributed-system.test.js +135 -0
- package/dist/cluster/distributed-system.test.js.map +1 -1
- package/dist/cluster/events.d.ts +23 -0
- package/dist/cluster/events.d.ts.map +1 -1
- package/dist/cluster/events.js +74 -0
- package/dist/cluster/events.js.map +1 -1
- package/dist/cluster/heartbeat-manager.d.ts +2 -0
- package/dist/cluster/heartbeat-manager.d.ts.map +1 -1
- package/dist/cluster/heartbeat-manager.js +42 -6
- package/dist/cluster/heartbeat-manager.js.map +1 -1
- package/dist/cluster/membership.test.d.ts +2 -0
- package/dist/cluster/membership.test.d.ts.map +1 -0
- package/dist/cluster/membership.test.js +416 -0
- package/dist/cluster/membership.test.js.map +1 -0
- package/dist/cluster/partition-safety.test.d.ts +2 -0
- package/dist/cluster/partition-safety.test.d.ts.map +1 -0
- package/dist/cluster/partition-safety.test.js +440 -0
- package/dist/cluster/partition-safety.test.js.map +1 -0
- package/dist/cluster/raft-state-machine.d.ts +33 -1
- package/dist/cluster/raft-state-machine.d.ts.map +1 -1
- package/dist/cluster/raft-state-machine.js +65 -3
- package/dist/cluster/raft-state-machine.js.map +1 -1
- package/dist/cluster/raft-store.d.ts +26 -1
- package/dist/cluster/raft-store.d.ts.map +1 -1
- package/dist/cluster/raft-store.js +137 -0
- package/dist/cluster/raft-store.js.map +1 -1
- package/dist/cluster/replication-lag.test.d.ts +2 -0
- package/dist/cluster/replication-lag.test.d.ts.map +1 -0
- package/dist/cluster/replication-lag.test.js +239 -0
- package/dist/cluster/replication-lag.test.js.map +1 -0
- package/dist/cluster/replication.d.ts +2 -2
- package/dist/cluster/replication.d.ts.map +1 -1
- package/dist/cluster/replication.js +1 -1
- package/dist/cluster/replication.js.map +1 -1
- package/dist/cluster/runtime.d.ts +78 -0
- package/dist/cluster/runtime.d.ts.map +1 -1
- package/dist/cluster/runtime.js +400 -13
- package/dist/cluster/runtime.js.map +1 -1
- package/dist/cluster/state-recovery.test.d.ts +2 -0
- package/dist/cluster/state-recovery.test.d.ts.map +1 -0
- package/dist/cluster/state-recovery.test.js +310 -0
- package/dist/cluster/state-recovery.test.js.map +1 -0
- package/dist/cluster/types.d.ts +30 -0
- package/dist/cluster/types.d.ts.map +1 -1
- package/dist/config/schema.d.ts +48 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +11 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/context-files/generator.d.ts +1 -1
- package/dist/context-files/generator.d.ts.map +1 -1
- package/dist/context-files/generator.js +4 -3
- package/dist/context-files/generator.js.map +1 -1
- package/dist/context-files/generator.test.js +51 -0
- package/dist/context-files/generator.test.js.map +1 -1
- package/dist/context-files/index.test.js +1 -0
- package/dist/context-files/index.test.js.map +1 -1
- package/dist/db/client.d.ts +1 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +6 -0
- package/dist/db/client.js.map +1 -1
- package/dist/db/migrations/015-add-story-markdown-path.sql +5 -0
- package/dist/db/queries/stories.d.ts +3 -3
- package/dist/db/queries/stories.d.ts.map +1 -1
- package/dist/db/queries/stories.js +23 -5
- package/dist/db/queries/stories.js.map +1 -1
- package/dist/db/queries/test-helpers.d.ts.map +1 -1
- package/dist/db/queries/test-helpers.js +1 -0
- package/dist/db/queries/test-helpers.js.map +1 -1
- package/dist/git/worktree.d.ts.map +1 -1
- package/dist/git/worktree.js +7 -0
- package/dist/git/worktree.js.map +1 -1
- package/dist/git/worktree.test.js +30 -0
- package/dist/git/worktree.test.js.map +1 -1
- package/dist/orchestrator/orphan-recovery.d.ts +1 -1
- package/dist/orchestrator/orphan-recovery.d.ts.map +1 -1
- package/dist/orchestrator/orphan-recovery.js +4 -4
- package/dist/orchestrator/orphan-recovery.js.map +1 -1
- package/dist/orchestrator/prompt-templates.d.ts +6 -2
- package/dist/orchestrator/prompt-templates.d.ts.map +1 -1
- package/dist/orchestrator/prompt-templates.js +61 -16
- package/dist/orchestrator/prompt-templates.js.map +1 -1
- package/dist/orchestrator/prompt-templates.test.js +214 -0
- package/dist/orchestrator/prompt-templates.test.js.map +1 -1
- package/dist/orchestrator/scheduler.d.ts +1 -0
- package/dist/orchestrator/scheduler.d.ts.map +1 -1
- package/dist/orchestrator/scheduler.js +30 -17
- package/dist/orchestrator/scheduler.js.map +1 -1
- package/dist/orchestrator/scheduler.test.js +98 -6
- package/dist/orchestrator/scheduler.test.js.map +1 -1
- package/dist/tmux/manager.d.ts +7 -6
- package/dist/tmux/manager.d.ts.map +1 -1
- package/dist/tmux/manager.js +29 -13
- package/dist/tmux/manager.js.map +1 -1
- package/dist/utils/instance.d.ts +32 -0
- package/dist/utils/instance.d.ts.map +1 -0
- package/dist/utils/instance.js +82 -0
- package/dist/utils/instance.js.map +1 -0
- package/dist/utils/instance.test.d.ts +2 -0
- package/dist/utils/instance.test.d.ts.map +1 -0
- package/dist/utils/instance.test.js +103 -0
- package/dist/utils/instance.test.js.map +1 -0
- package/dist/utils/paths.d.ts +2 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +2 -0
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/paths.test.js +6 -0
- package/dist/utils/paths.test.js.map +1 -1
- package/dist/utils/story-markdown.d.ts +16 -0
- package/dist/utils/story-markdown.d.ts.map +1 -0
- package/dist/utils/story-markdown.js +82 -0
- package/dist/utils/story-markdown.js.map +1 -0
- package/dist/utils/story-markdown.test.d.ts +2 -0
- package/dist/utils/story-markdown.test.d.ts.map +1 -0
- package/dist/utils/story-markdown.test.js +143 -0
- package/dist/utils/story-markdown.test.js.map +1 -0
- package/package.json +1 -1
- package/src/agents/base-agent.ts +5 -0
- package/src/agents/intermediate.ts +2 -2
- package/src/agents/junior.ts +2 -2
- package/src/agents/qa.ts +13 -8
- package/src/agents/senior.ts +21 -11
- package/src/agents/tech-lead.ts +24 -12
- package/src/cli/commands/assign.test.ts +5 -0
- package/src/cli/commands/assign.ts +4 -2
- package/src/cli/commands/cluster.test.ts +387 -9
- package/src/cli/commands/cluster.ts +486 -1
- package/src/cli/commands/manager/handoff-recovery.ts +4 -2
- package/src/cli/commands/manager/index.ts +16 -11
- package/src/cli/commands/manager/tech-lead-lifecycle.ts +5 -2
- package/src/cli/commands/msg.ts +8 -7
- package/src/cli/commands/my-stories.ts +22 -13
- package/src/cli/commands/nuke.test.ts +31 -0
- package/src/cli/commands/nuke.ts +18 -7
- package/src/cli/commands/req-spawn.test.ts +153 -0
- package/src/cli/commands/req.ts +40 -23
- package/src/cli/commands/stories.ts +22 -13
- package/src/cli/dashboard/panels/agents.ts +7 -3
- package/src/cluster/cluster-http-server.ts +80 -0
- package/src/cluster/distributed-runtime-coverage.test.ts +9 -0
- package/src/cluster/distributed-system.test.ts +168 -0
- package/src/cluster/events.ts +90 -0
- package/src/cluster/heartbeat-manager.ts +48 -6
- package/src/cluster/membership.test.ts +498 -0
- package/src/cluster/partition-safety.test.ts +523 -0
- package/src/cluster/raft-state-machine.ts +76 -4
- package/src/cluster/raft-store.ts +167 -1
- package/src/cluster/replication-lag.test.ts +284 -0
- package/src/cluster/replication.ts +6 -0
- package/src/cluster/runtime.ts +551 -12
- package/src/cluster/state-recovery.test.ts +420 -0
- package/src/cluster/types.ts +32 -0
- package/src/config/schema.ts +11 -0
- package/src/context-files/generator.test.ts +55 -0
- package/src/context-files/generator.ts +8 -7
- package/src/context-files/index.test.ts +1 -0
- package/src/db/client.ts +7 -0
- package/src/db/migrations/015-add-story-markdown-path.sql +5 -0
- package/src/db/queries/stories.ts +29 -5
- package/src/db/queries/test-helpers.ts +1 -0
- package/src/git/worktree.test.ts +43 -0
- package/src/git/worktree.ts +10 -0
- package/src/orchestrator/orphan-recovery.ts +32 -13
- package/src/orchestrator/prompt-templates.test.ts +267 -0
- package/src/orchestrator/prompt-templates.ts +69 -16
- package/src/orchestrator/scheduler.test.ts +130 -6
- package/src/orchestrator/scheduler.ts +66 -27
- package/src/tmux/manager.ts +42 -13
- package/src/utils/instance.test.ts +129 -0
- package/src/utils/instance.ts +95 -0
- package/src/utils/paths.test.ts +8 -0
- package/src/utils/paths.ts +3 -0
- package/src/utils/story-markdown.test.ts +176 -0
- package/src/utils/story-markdown.ts +94 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
// Licensed under the Hungry Ghost Hive License. See LICENSE.
|
|
2
|
+
import { mkdirSync, mkdtempSync, rmSync } from 'fs';
|
|
3
|
+
import { createServer as createNetServer } from 'net';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
7
|
+
import { ClusterRuntime } from './runtime.js';
|
|
8
|
+
const tempRoots = [];
|
|
9
|
+
const activeRuntimes = [];
|
|
10
|
+
afterEach(async () => {
|
|
11
|
+
for (const runtime of activeRuntimes.splice(0)) {
|
|
12
|
+
try {
|
|
13
|
+
await runtime.stop();
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// Best effort shutdown for test cleanup.
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
for (const root of tempRoots.splice(0)) {
|
|
20
|
+
rmSync(root, { recursive: true, force: true });
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
describe('fencing token validation', () => {
|
|
24
|
+
it('rejects heartbeats with fencing_token lower than term', async () => {
|
|
25
|
+
if (!(await canListenOnLocalhost()))
|
|
26
|
+
return;
|
|
27
|
+
const fixture = await startRuntimeFixture({
|
|
28
|
+
node_id: 'node-fence-reject',
|
|
29
|
+
election_timeout_min_ms: 2000,
|
|
30
|
+
election_timeout_max_ms: 2000,
|
|
31
|
+
});
|
|
32
|
+
// First, advance the term by accepting a vote request
|
|
33
|
+
await postJson(fixture.config.public_url, '/cluster/v1/election/request-vote', {
|
|
34
|
+
term: 5,
|
|
35
|
+
candidate_id: 'candidate-5',
|
|
36
|
+
});
|
|
37
|
+
// Send heartbeat with valid term but stale fencing token
|
|
38
|
+
const res = await postJson(fixture.config.public_url, '/cluster/v1/election/heartbeat', {
|
|
39
|
+
term: 5,
|
|
40
|
+
leader_id: 'candidate-5',
|
|
41
|
+
fencing_token: 3,
|
|
42
|
+
});
|
|
43
|
+
expect(res.success).toBe(false);
|
|
44
|
+
expect(res.fencing_token).toBe(5);
|
|
45
|
+
});
|
|
46
|
+
it('accepts heartbeats with valid fencing_token', async () => {
|
|
47
|
+
if (!(await canListenOnLocalhost()))
|
|
48
|
+
return;
|
|
49
|
+
const fixture = await startRuntimeFixture({
|
|
50
|
+
node_id: 'node-fence-accept',
|
|
51
|
+
election_timeout_min_ms: 2000,
|
|
52
|
+
election_timeout_max_ms: 2000,
|
|
53
|
+
});
|
|
54
|
+
const res = await postJson(fixture.config.public_url, '/cluster/v1/election/heartbeat', {
|
|
55
|
+
term: 3,
|
|
56
|
+
leader_id: 'leader-3',
|
|
57
|
+
fencing_token: 3,
|
|
58
|
+
});
|
|
59
|
+
expect(res.success).toBe(true);
|
|
60
|
+
expect(res.fencing_token).toBe(3);
|
|
61
|
+
});
|
|
62
|
+
it('rejects delta requests with stale fencing_token', async () => {
|
|
63
|
+
if (!(await canListenOnLocalhost()))
|
|
64
|
+
return;
|
|
65
|
+
const fixture = await startRuntimeFixture({
|
|
66
|
+
node_id: 'node-delta-fence',
|
|
67
|
+
election_timeout_min_ms: 2000,
|
|
68
|
+
election_timeout_max_ms: 2000,
|
|
69
|
+
});
|
|
70
|
+
// Advance term
|
|
71
|
+
await postJson(fixture.config.public_url, '/cluster/v1/election/heartbeat', {
|
|
72
|
+
term: 10,
|
|
73
|
+
leader_id: 'leader-10',
|
|
74
|
+
fencing_token: 10,
|
|
75
|
+
});
|
|
76
|
+
// Request delta with stale fencing token
|
|
77
|
+
const res = await fetch(`${fixture.config.public_url}/cluster/v1/events/delta`, {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
headers: { 'Content-Type': 'application/json' },
|
|
80
|
+
body: JSON.stringify({
|
|
81
|
+
version_vector: {},
|
|
82
|
+
fencing_token: 5,
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
85
|
+
expect(res.status).toBe(409);
|
|
86
|
+
const body = (await res.json());
|
|
87
|
+
expect(body.error).toContain('stale leader epoch');
|
|
88
|
+
expect(body.fencing_token).toBe(10);
|
|
89
|
+
});
|
|
90
|
+
it('accepts delta requests with current fencing_token', async () => {
|
|
91
|
+
if (!(await canListenOnLocalhost()))
|
|
92
|
+
return;
|
|
93
|
+
const fixture = await startRuntimeFixture({
|
|
94
|
+
node_id: 'node-delta-fence-ok',
|
|
95
|
+
election_timeout_min_ms: 2000,
|
|
96
|
+
election_timeout_max_ms: 2000,
|
|
97
|
+
});
|
|
98
|
+
// Set term to 4
|
|
99
|
+
await postJson(fixture.config.public_url, '/cluster/v1/election/heartbeat', {
|
|
100
|
+
term: 4,
|
|
101
|
+
leader_id: 'leader-4',
|
|
102
|
+
fencing_token: 4,
|
|
103
|
+
});
|
|
104
|
+
// Request delta with matching fencing token
|
|
105
|
+
const res = await fetch(`${fixture.config.public_url}/cluster/v1/events/delta`, {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers: { 'Content-Type': 'application/json' },
|
|
108
|
+
body: JSON.stringify({
|
|
109
|
+
version_vector: {},
|
|
110
|
+
fencing_token: 4,
|
|
111
|
+
}),
|
|
112
|
+
});
|
|
113
|
+
expect(res.status).toBe(200);
|
|
114
|
+
const body = (await res.json());
|
|
115
|
+
expect(body.fencing_token).toBe(4);
|
|
116
|
+
});
|
|
117
|
+
it('accepts delta requests without fencing_token for backward compatibility', async () => {
|
|
118
|
+
if (!(await canListenOnLocalhost()))
|
|
119
|
+
return;
|
|
120
|
+
const fixture = await startRuntimeFixture({
|
|
121
|
+
node_id: 'node-delta-no-fence',
|
|
122
|
+
election_timeout_min_ms: 2000,
|
|
123
|
+
election_timeout_max_ms: 2000,
|
|
124
|
+
});
|
|
125
|
+
const res = await fetch(`${fixture.config.public_url}/cluster/v1/events/delta`, {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
headers: { 'Content-Type': 'application/json' },
|
|
128
|
+
body: JSON.stringify({
|
|
129
|
+
version_vector: {},
|
|
130
|
+
}),
|
|
131
|
+
});
|
|
132
|
+
expect(res.status).toBe(200);
|
|
133
|
+
});
|
|
134
|
+
it('returns fencing_token in status endpoint', async () => {
|
|
135
|
+
if (!(await canListenOnLocalhost()))
|
|
136
|
+
return;
|
|
137
|
+
const fixture = await startRuntimeFixture({
|
|
138
|
+
node_id: 'node-status-fence',
|
|
139
|
+
election_timeout_min_ms: 2000,
|
|
140
|
+
election_timeout_max_ms: 2000,
|
|
141
|
+
});
|
|
142
|
+
// Advance term
|
|
143
|
+
await postJson(fixture.config.public_url, '/cluster/v1/election/heartbeat', {
|
|
144
|
+
term: 7,
|
|
145
|
+
leader_id: 'leader-7',
|
|
146
|
+
fencing_token: 7,
|
|
147
|
+
});
|
|
148
|
+
const status = fixture.runtime.getStatus();
|
|
149
|
+
expect(status.fencing_token).toBe(7);
|
|
150
|
+
expect(status.term).toBe(7);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
describe('leader lease validation', () => {
|
|
154
|
+
it('reports lease invalid when no heartbeat has been received', async () => {
|
|
155
|
+
if (!(await canListenOnLocalhost()))
|
|
156
|
+
return;
|
|
157
|
+
const fixture = await startRuntimeFixture({
|
|
158
|
+
node_id: 'node-lease-none',
|
|
159
|
+
election_timeout_min_ms: 2000,
|
|
160
|
+
election_timeout_max_ms: 2000,
|
|
161
|
+
});
|
|
162
|
+
const status = fixture.runtime.getStatus();
|
|
163
|
+
expect(status.leader_lease_valid).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
it('reports lease valid immediately after receiving heartbeat', async () => {
|
|
166
|
+
if (!(await canListenOnLocalhost()))
|
|
167
|
+
return;
|
|
168
|
+
const fixture = await startRuntimeFixture({
|
|
169
|
+
node_id: 'node-lease-fresh',
|
|
170
|
+
election_timeout_min_ms: 2000,
|
|
171
|
+
election_timeout_max_ms: 2000,
|
|
172
|
+
heartbeat_interval_ms: 100,
|
|
173
|
+
});
|
|
174
|
+
// Send a heartbeat
|
|
175
|
+
await postJson(fixture.config.public_url, '/cluster/v1/election/heartbeat', {
|
|
176
|
+
term: 2,
|
|
177
|
+
leader_id: 'leader-2',
|
|
178
|
+
fencing_token: 2,
|
|
179
|
+
});
|
|
180
|
+
const status = fixture.runtime.getStatus();
|
|
181
|
+
expect(status.leader_lease_valid).toBe(true);
|
|
182
|
+
});
|
|
183
|
+
it('leader always reports lease valid', async () => {
|
|
184
|
+
if (!(await canListenOnLocalhost()))
|
|
185
|
+
return;
|
|
186
|
+
const fixture = await startRuntimeFixture({
|
|
187
|
+
node_id: 'node-lease-leader',
|
|
188
|
+
election_timeout_min_ms: 80,
|
|
189
|
+
election_timeout_max_ms: 120,
|
|
190
|
+
heartbeat_interval_ms: 60,
|
|
191
|
+
});
|
|
192
|
+
await waitFor(() => fixture.runtime.getStatus().is_leader, 4000);
|
|
193
|
+
const status = fixture.runtime.getStatus();
|
|
194
|
+
expect(status.leader_lease_valid).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
it('reports lease expired after timeout elapses without heartbeat', async () => {
|
|
197
|
+
if (!(await canListenOnLocalhost()))
|
|
198
|
+
return;
|
|
199
|
+
const leaseMs = 150;
|
|
200
|
+
const fixture = await startRuntimeFixture({
|
|
201
|
+
node_id: 'node-lease-expire',
|
|
202
|
+
election_timeout_min_ms: 5000,
|
|
203
|
+
election_timeout_max_ms: 5000,
|
|
204
|
+
heartbeat_interval_ms: 50,
|
|
205
|
+
leader_lease_ms: leaseMs,
|
|
206
|
+
});
|
|
207
|
+
// Send heartbeat to establish lease
|
|
208
|
+
await postJson(fixture.config.public_url, '/cluster/v1/election/heartbeat', {
|
|
209
|
+
term: 1,
|
|
210
|
+
leader_id: 'leader-1',
|
|
211
|
+
fencing_token: 1,
|
|
212
|
+
});
|
|
213
|
+
expect(fixture.runtime.getStatus().leader_lease_valid).toBe(true);
|
|
214
|
+
// Wait for lease to expire
|
|
215
|
+
await new Promise(resolve => setTimeout(resolve, leaseMs + 50));
|
|
216
|
+
expect(fixture.runtime.getStatus().leader_lease_valid).toBe(false);
|
|
217
|
+
});
|
|
218
|
+
it('reports correct leader_lease_duration_ms from config', async () => {
|
|
219
|
+
if (!(await canListenOnLocalhost()))
|
|
220
|
+
return;
|
|
221
|
+
const fixture = await startRuntimeFixture({
|
|
222
|
+
node_id: 'node-lease-config',
|
|
223
|
+
heartbeat_interval_ms: 200,
|
|
224
|
+
leader_lease_ms: 1000,
|
|
225
|
+
});
|
|
226
|
+
expect(fixture.runtime.getStatus().leader_lease_duration_ms).toBe(1000);
|
|
227
|
+
});
|
|
228
|
+
it('defaults leader_lease_duration_ms to 3x heartbeat_interval_ms', async () => {
|
|
229
|
+
if (!(await canListenOnLocalhost()))
|
|
230
|
+
return;
|
|
231
|
+
const fixture = await startRuntimeFixture({
|
|
232
|
+
node_id: 'node-lease-default',
|
|
233
|
+
heartbeat_interval_ms: 200,
|
|
234
|
+
});
|
|
235
|
+
expect(fixture.runtime.getStatus().leader_lease_duration_ms).toBe(600);
|
|
236
|
+
});
|
|
237
|
+
it('resets lease on step-down from higher term', async () => {
|
|
238
|
+
if (!(await canListenOnLocalhost()))
|
|
239
|
+
return;
|
|
240
|
+
const fixture = await startRuntimeFixture({
|
|
241
|
+
node_id: 'node-lease-stepdown',
|
|
242
|
+
election_timeout_min_ms: 5000,
|
|
243
|
+
election_timeout_max_ms: 5000,
|
|
244
|
+
heartbeat_interval_ms: 100,
|
|
245
|
+
});
|
|
246
|
+
// Establish lease at term 2
|
|
247
|
+
await postJson(fixture.config.public_url, '/cluster/v1/election/heartbeat', {
|
|
248
|
+
term: 2,
|
|
249
|
+
leader_id: 'leader-2',
|
|
250
|
+
fencing_token: 2,
|
|
251
|
+
});
|
|
252
|
+
expect(fixture.runtime.getStatus().leader_lease_valid).toBe(true);
|
|
253
|
+
// Higher term vote request causes step-down, which should reset lease
|
|
254
|
+
await postJson(fixture.config.public_url, '/cluster/v1/election/request-vote', {
|
|
255
|
+
term: 5,
|
|
256
|
+
candidate_id: 'candidate-5',
|
|
257
|
+
});
|
|
258
|
+
// Lease should be invalid after step-down (no heartbeat from new leader yet)
|
|
259
|
+
expect(fixture.runtime.getStatus().leader_lease_valid).toBe(false);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
describe('partition healing scenarios', () => {
|
|
263
|
+
it('stale leader is fenced after partition heals', async () => {
|
|
264
|
+
if (!(await canListenOnLocalhost()))
|
|
265
|
+
return;
|
|
266
|
+
const portA = await getFreePort();
|
|
267
|
+
const portB = await getFreePort();
|
|
268
|
+
// Node A and B are peers
|
|
269
|
+
const configA = await buildConfig({
|
|
270
|
+
node_id: 'node-a-heal',
|
|
271
|
+
listen_port: portA,
|
|
272
|
+
public_url: `http://127.0.0.1:${portA}`,
|
|
273
|
+
peers: [{ id: 'node-b-heal', url: `http://127.0.0.1:${portB}` }],
|
|
274
|
+
election_timeout_min_ms: 80,
|
|
275
|
+
election_timeout_max_ms: 120,
|
|
276
|
+
heartbeat_interval_ms: 60,
|
|
277
|
+
});
|
|
278
|
+
const configB = await buildConfig({
|
|
279
|
+
node_id: 'node-b-heal',
|
|
280
|
+
listen_port: portB,
|
|
281
|
+
public_url: `http://127.0.0.1:${portB}`,
|
|
282
|
+
peers: [{ id: 'node-a-heal', url: `http://127.0.0.1:${portA}` }],
|
|
283
|
+
election_timeout_min_ms: 80,
|
|
284
|
+
election_timeout_max_ms: 120,
|
|
285
|
+
heartbeat_interval_ms: 60,
|
|
286
|
+
});
|
|
287
|
+
const fixtureA = await startRuntimeWithConfig(configA);
|
|
288
|
+
const fixtureB = await startRuntimeWithConfig(configB);
|
|
289
|
+
// Wait until at least one becomes leader
|
|
290
|
+
await waitFor(() => fixtureA.runtime.getStatus().is_leader || fixtureB.runtime.getStatus().is_leader, 4000);
|
|
291
|
+
const statusA = fixtureA.runtime.getStatus();
|
|
292
|
+
const statusB = fixtureB.runtime.getStatus();
|
|
293
|
+
// Exactly one should be leader (same term wins in a 2-node cluster)
|
|
294
|
+
const leaderCount = [statusA, statusB].filter(s => s.is_leader).length;
|
|
295
|
+
expect(leaderCount).toBeLessThanOrEqual(1);
|
|
296
|
+
// Both should have fencing tokens
|
|
297
|
+
expect(statusA.fencing_token).toBeGreaterThanOrEqual(0);
|
|
298
|
+
expect(statusB.fencing_token).toBeGreaterThanOrEqual(0);
|
|
299
|
+
});
|
|
300
|
+
it('follower rejects stale leader heartbeat after seeing higher term', async () => {
|
|
301
|
+
if (!(await canListenOnLocalhost()))
|
|
302
|
+
return;
|
|
303
|
+
const fixture = await startRuntimeFixture({
|
|
304
|
+
node_id: 'node-heal-reject',
|
|
305
|
+
election_timeout_min_ms: 5000,
|
|
306
|
+
election_timeout_max_ms: 5000,
|
|
307
|
+
});
|
|
308
|
+
// Node sees term 10 from new leader
|
|
309
|
+
await postJson(fixture.config.public_url, '/cluster/v1/election/heartbeat', {
|
|
310
|
+
term: 10,
|
|
311
|
+
leader_id: 'new-leader',
|
|
312
|
+
fencing_token: 10,
|
|
313
|
+
});
|
|
314
|
+
// Old leader (term 5) tries to send heartbeat after partition heals
|
|
315
|
+
const staleRes = await postJson(fixture.config.public_url, '/cluster/v1/election/heartbeat', {
|
|
316
|
+
term: 5,
|
|
317
|
+
leader_id: 'old-leader',
|
|
318
|
+
fencing_token: 5,
|
|
319
|
+
});
|
|
320
|
+
expect(staleRes.success).toBe(false);
|
|
321
|
+
expect(staleRes.fencing_token).toBe(10);
|
|
322
|
+
// Verify node still follows new leader
|
|
323
|
+
const status = fixture.runtime.getStatus();
|
|
324
|
+
expect(status.leader_id).toBe('new-leader');
|
|
325
|
+
expect(status.term).toBe(10);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
// --- Test helpers ---
|
|
329
|
+
async function startRuntimeFixture(overrides = {}) {
|
|
330
|
+
const attempts = overrides.listen_port ? 1 : 5;
|
|
331
|
+
let lastError;
|
|
332
|
+
for (let i = 0; i < attempts; i++) {
|
|
333
|
+
const config = await buildConfig(overrides);
|
|
334
|
+
try {
|
|
335
|
+
return await startRuntimeWithConfig(config);
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
lastError = error;
|
|
339
|
+
const err = error;
|
|
340
|
+
if (!overrides.listen_port && err.code === 'EADDRINUSE') {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
throw lastError instanceof Error ? lastError : new Error('Failed to start runtime fixture');
|
|
347
|
+
}
|
|
348
|
+
async function startRuntimeWithConfig(config) {
|
|
349
|
+
const root = mkdtempSync(join(tmpdir(), `hive-partition-safety-${config.node_id}-`));
|
|
350
|
+
const hiveDir = join(root, '.hive');
|
|
351
|
+
mkdirSync(hiveDir, { recursive: true });
|
|
352
|
+
const runtime = new ClusterRuntime(config, { hiveDir });
|
|
353
|
+
try {
|
|
354
|
+
await runtime.start();
|
|
355
|
+
activeRuntimes.push(runtime);
|
|
356
|
+
tempRoots.push(root);
|
|
357
|
+
return { root, hiveDir, config, runtime };
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
try {
|
|
361
|
+
await runtime.stop();
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
// Best effort cleanup for partial starts.
|
|
365
|
+
}
|
|
366
|
+
rmSync(root, { recursive: true, force: true });
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
async function buildConfig(overrides = {}) {
|
|
371
|
+
const port = overrides.listen_port ?? (await getFreePort());
|
|
372
|
+
const base = {
|
|
373
|
+
enabled: true,
|
|
374
|
+
node_id: 'node-test',
|
|
375
|
+
listen_host: '127.0.0.1',
|
|
376
|
+
listen_port: port,
|
|
377
|
+
public_url: `http://127.0.0.1:${port}`,
|
|
378
|
+
peers: [],
|
|
379
|
+
heartbeat_interval_ms: 100,
|
|
380
|
+
election_timeout_min_ms: 150,
|
|
381
|
+
election_timeout_max_ms: 250,
|
|
382
|
+
sync_interval_ms: 200,
|
|
383
|
+
request_timeout_ms: 600,
|
|
384
|
+
story_similarity_threshold: 0.8,
|
|
385
|
+
};
|
|
386
|
+
return {
|
|
387
|
+
...base,
|
|
388
|
+
...overrides,
|
|
389
|
+
public_url: overrides.public_url || base.public_url,
|
|
390
|
+
peers: overrides.peers || base.peers,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
async function postJson(baseUrl, path, body) {
|
|
394
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
395
|
+
method: 'POST',
|
|
396
|
+
headers: { 'Content-Type': 'application/json' },
|
|
397
|
+
body: JSON.stringify(body),
|
|
398
|
+
});
|
|
399
|
+
return (await res.json());
|
|
400
|
+
}
|
|
401
|
+
async function waitFor(predicate, timeoutMs) {
|
|
402
|
+
const start = Date.now();
|
|
403
|
+
while (Date.now() - start < timeoutMs) {
|
|
404
|
+
if (predicate())
|
|
405
|
+
return;
|
|
406
|
+
await new Promise(resolve => setTimeout(resolve, 25));
|
|
407
|
+
}
|
|
408
|
+
throw new Error('Timed out waiting for condition');
|
|
409
|
+
}
|
|
410
|
+
async function getFreePort() {
|
|
411
|
+
return new Promise((resolve, reject) => {
|
|
412
|
+
const server = createNetServer();
|
|
413
|
+
server.once('error', reject);
|
|
414
|
+
server.listen(0, '127.0.0.1', () => {
|
|
415
|
+
const address = server.address();
|
|
416
|
+
if (!address || typeof address === 'string') {
|
|
417
|
+
server.close(() => reject(new Error('Failed to allocate free port')));
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
const port = address.port;
|
|
421
|
+
server.close(err => {
|
|
422
|
+
if (err) {
|
|
423
|
+
reject(err);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
resolve(port);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
async function canListenOnLocalhost() {
|
|
432
|
+
try {
|
|
433
|
+
await getFreePort();
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
//# sourceMappingURL=partition-safety.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partition-safety.test.js","sourceRoot":"","sources":["../../src/cluster/partition-safety.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ClusterConfig, ClusterPeerConfig } from '../config/schema.js';
|
|
2
|
-
import type { DurableLogEntryType } from './raft-store.js';
|
|
2
|
+
import type { CompactionResult, DurableLogEntryType } from './raft-store.js';
|
|
3
3
|
import { RaftMetadataStore } from './raft-store.js';
|
|
4
|
+
import type { VersionVector } from './types.js';
|
|
4
5
|
type NodeRole = 'leader' | 'follower' | 'candidate';
|
|
5
6
|
interface VoteResponse {
|
|
6
7
|
term: number;
|
|
@@ -19,11 +20,40 @@ export declare class RaftStateMachine {
|
|
|
19
20
|
currentTerm: number;
|
|
20
21
|
votedFor: string | null;
|
|
21
22
|
leaderId: string | null;
|
|
23
|
+
lastHeartbeatReceivedAt: number;
|
|
24
|
+
/**
|
|
25
|
+
* When true, this node is catching up from a snapshot and must not
|
|
26
|
+
* participate in leader elections until fully recovered.
|
|
27
|
+
*/
|
|
28
|
+
isCatchingUp: boolean;
|
|
29
|
+
/** Dynamic peer list that can be updated at runtime via membership changes. */
|
|
30
|
+
private dynamicPeers;
|
|
22
31
|
private electionDeadline;
|
|
23
32
|
private electionInFlight;
|
|
24
33
|
private electionTimer;
|
|
25
34
|
private raftStore;
|
|
26
35
|
constructor(config: ClusterConfig, deps: RaftStateMachineDeps);
|
|
36
|
+
/** Returns the active peer list (dynamic if set, otherwise static config). */
|
|
37
|
+
getPeers(): ClusterPeerConfig[];
|
|
38
|
+
/** Replaces the dynamic peer list. */
|
|
39
|
+
setPeers(peers: ClusterPeerConfig[]): void;
|
|
40
|
+
/** Returns the leader lease window in milliseconds. */
|
|
41
|
+
get leaderLeaseDurationMs(): number;
|
|
42
|
+
/**
|
|
43
|
+
* Returns true when this follower has received a valid heartbeat
|
|
44
|
+
* from the current leader within the lease window.
|
|
45
|
+
*/
|
|
46
|
+
isLeaderLeaseValid(): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* The fencing token is the current Raft term. Operations tagged with a
|
|
49
|
+
* lower term than ours must be rejected to prevent stale-leader writes.
|
|
50
|
+
*/
|
|
51
|
+
getFencingToken(): number;
|
|
52
|
+
/**
|
|
53
|
+
* Validates a fencing token from a remote node. Returns true when the
|
|
54
|
+
* token is at least as recent as our current term.
|
|
55
|
+
*/
|
|
56
|
+
validateFencingToken(token: number): boolean;
|
|
27
57
|
initializeRaftStore(hiveDir: string): void;
|
|
28
58
|
getRaftStore(): RaftMetadataStore | null;
|
|
29
59
|
clearRaftStore(): void;
|
|
@@ -42,6 +72,8 @@ export declare class RaftStateMachine {
|
|
|
42
72
|
last_applied: number;
|
|
43
73
|
last_log_index: number;
|
|
44
74
|
} | null;
|
|
75
|
+
getLogEntryCount(): number;
|
|
76
|
+
createSnapshotAndCompact(versionVector: VersionVector): CompactionResult;
|
|
45
77
|
getLeaderUrl(): string | null;
|
|
46
78
|
}
|
|
47
79
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"raft-state-machine.d.ts","sourceRoot":"","sources":["../../src/cluster/raft-state-machine.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"raft-state-machine.d.ts","sourceRoot":"","sources":["../../src/cluster/raft-state-machine.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,14 @@ export class RaftStateMachine {
|
|
|
8
8
|
currentTerm = 0;
|
|
9
9
|
votedFor = null;
|
|
10
10
|
leaderId = null;
|
|
11
|
+
lastHeartbeatReceivedAt = 0;
|
|
12
|
+
/**
|
|
13
|
+
* When true, this node is catching up from a snapshot and must not
|
|
14
|
+
* participate in leader elections until fully recovered.
|
|
15
|
+
*/
|
|
16
|
+
isCatchingUp = false;
|
|
17
|
+
/** Dynamic peer list that can be updated at runtime via membership changes. */
|
|
18
|
+
dynamicPeers = null;
|
|
11
19
|
electionDeadline = 0;
|
|
12
20
|
electionInFlight = false;
|
|
13
21
|
electionTimer = null;
|
|
@@ -16,6 +24,43 @@ export class RaftStateMachine {
|
|
|
16
24
|
this.config = config;
|
|
17
25
|
this.deps = deps;
|
|
18
26
|
}
|
|
27
|
+
/** Returns the active peer list (dynamic if set, otherwise static config). */
|
|
28
|
+
getPeers() {
|
|
29
|
+
return this.dynamicPeers ?? this.config.peers;
|
|
30
|
+
}
|
|
31
|
+
/** Replaces the dynamic peer list. */
|
|
32
|
+
setPeers(peers) {
|
|
33
|
+
this.dynamicPeers = peers;
|
|
34
|
+
}
|
|
35
|
+
/** Returns the leader lease window in milliseconds. */
|
|
36
|
+
get leaderLeaseDurationMs() {
|
|
37
|
+
return this.config.leader_lease_ms ?? this.config.heartbeat_interval_ms * 3;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Returns true when this follower has received a valid heartbeat
|
|
41
|
+
* from the current leader within the lease window.
|
|
42
|
+
*/
|
|
43
|
+
isLeaderLeaseValid() {
|
|
44
|
+
if (this.role === 'leader')
|
|
45
|
+
return true;
|
|
46
|
+
if (this.lastHeartbeatReceivedAt === 0)
|
|
47
|
+
return false;
|
|
48
|
+
return Date.now() - this.lastHeartbeatReceivedAt < this.leaderLeaseDurationMs;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The fencing token is the current Raft term. Operations tagged with a
|
|
52
|
+
* lower term than ours must be rejected to prevent stale-leader writes.
|
|
53
|
+
*/
|
|
54
|
+
getFencingToken() {
|
|
55
|
+
return this.currentTerm;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Validates a fencing token from a remote node. Returns true when the
|
|
59
|
+
* token is at least as recent as our current term.
|
|
60
|
+
*/
|
|
61
|
+
validateFencingToken(token) {
|
|
62
|
+
return token >= this.currentTerm;
|
|
63
|
+
}
|
|
19
64
|
initializeRaftStore(hiveDir) {
|
|
20
65
|
if (this.raftStore)
|
|
21
66
|
return;
|
|
@@ -43,6 +88,12 @@ export class RaftStateMachine {
|
|
|
43
88
|
return;
|
|
44
89
|
if (this.role === 'leader')
|
|
45
90
|
return;
|
|
91
|
+
// Do not start elections while catching up from a snapshot — the node
|
|
92
|
+
// must not become leader until it has a complete, current state.
|
|
93
|
+
if (this.isCatchingUp) {
|
|
94
|
+
this.resetElectionDeadline();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
46
97
|
if (Date.now() >= this.electionDeadline) {
|
|
47
98
|
void this.startElection().catch(error => this.deps.handleBackgroundError(error));
|
|
48
99
|
}
|
|
@@ -72,7 +123,7 @@ export class RaftStateMachine {
|
|
|
72
123
|
});
|
|
73
124
|
let votes = 1;
|
|
74
125
|
try {
|
|
75
|
-
await Promise.all(this.
|
|
126
|
+
await Promise.all(this.getPeers()
|
|
76
127
|
.filter(peer => peer.id !== this.config.node_id)
|
|
77
128
|
.map(async (peer) => {
|
|
78
129
|
const response = await this.deps.postJson(peer, '/cluster/v1/election/request-vote', {
|
|
@@ -143,6 +194,7 @@ export class RaftStateMachine {
|
|
|
143
194
|
this.role = 'follower';
|
|
144
195
|
this.votedFor = null;
|
|
145
196
|
this.leaderId = leaderId;
|
|
197
|
+
this.lastHeartbeatReceivedAt = 0;
|
|
146
198
|
this.resetElectionDeadline();
|
|
147
199
|
this.persistRaftState();
|
|
148
200
|
this.appendDurableEntry('state_transition', {
|
|
@@ -153,7 +205,7 @@ export class RaftStateMachine {
|
|
|
153
205
|
});
|
|
154
206
|
}
|
|
155
207
|
quorum() {
|
|
156
|
-
const nodes = this.
|
|
208
|
+
const nodes = this.getPeers().length + 1;
|
|
157
209
|
return Math.floor(nodes / 2) + 1;
|
|
158
210
|
}
|
|
159
211
|
resetElectionDeadline() {
|
|
@@ -195,12 +247,22 @@ export class RaftStateMachine {
|
|
|
195
247
|
getRaftStoreState() {
|
|
196
248
|
return this.raftStore?.getState() ?? null;
|
|
197
249
|
}
|
|
250
|
+
getLogEntryCount() {
|
|
251
|
+
return this.raftStore?.getLogEntryCount() ?? 0;
|
|
252
|
+
}
|
|
253
|
+
createSnapshotAndCompact(versionVector) {
|
|
254
|
+
if (!this.raftStore) {
|
|
255
|
+
return { entries_removed: 0, entries_retained: 0, snapshot_index: 0 };
|
|
256
|
+
}
|
|
257
|
+
this.raftStore.createSnapshot(versionVector);
|
|
258
|
+
return this.raftStore.compactLog();
|
|
259
|
+
}
|
|
198
260
|
getLeaderUrl() {
|
|
199
261
|
if (!this.leaderId)
|
|
200
262
|
return null;
|
|
201
263
|
if (this.leaderId === this.config.node_id)
|
|
202
264
|
return this.config.public_url;
|
|
203
|
-
const peer = this.
|
|
265
|
+
const peer = this.getPeers().find(item => item.id === this.leaderId);
|
|
204
266
|
return peer?.url || null;
|
|
205
267
|
}
|
|
206
268
|
}
|