@feltdb/core 0.7.3 → 0.7.4
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/application-development.d.ts +76 -0
- package/dist/application-development.d.ts.map +1 -0
- package/dist/application-development.js +137 -0
- package/dist/application-manifest.d.ts +5 -1
- package/dist/application-manifest.d.ts.map +1 -1
- package/dist/application-manifest.js +10 -3
- package/dist/canonical-application.d.ts +26 -0
- package/dist/canonical-application.d.ts.map +1 -0
- package/dist/canonical-application.js +45 -0
- package/dist/cli/ai-model-config.js +97 -0
- package/dist/cli/commands.js +466 -105
- package/dist/cli/index.js +1 -1
- package/dist/cli/proposal-client.js +54 -0
- package/dist/cli/publish-engine.js +162 -0
- package/dist/cli/source-sync.js +139 -0
- package/dist/create/cli.js +1 -1
- package/dist/create/create.js +30 -6
- package/dist/create/managed-account.js +14 -7
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/crates/feltdb/src/application.rs +31 -1
- package/dist/create/server-source/crates/feltdb/src/authority_failover.rs +84 -6
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +126 -38
- package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +72 -34
- package/dist/create/server-source/crates/feltdb-server/src/identity.rs +46 -3
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +315 -16
- package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +20 -0
- package/dist/create/template/default-project/README.md +57 -0
- package/dist/create/template/default-project/agents/activity-assistant.ts +18 -0
- package/dist/create/template/default-project/agents/project-assistant.ts +19 -0
- package/dist/create/template/default-project/capabilities/activity-summary.ts +14 -0
- package/dist/create/template/default-project/capabilities/project-search.ts +16 -0
- package/dist/create/template/default-project/capabilities/project-summary.ts +15 -0
- package/dist/create/template/default-project/feltdb.flow +169 -0
- package/dist/create/template/default-project/src/App.tsx +20 -0
- package/dist/create/template/default-project/src/context/AuthContext.tsx +27 -0
- package/dist/create/template/default-project/src/feltdb.ts +87 -0
- package/dist/create/template/default-project/src/index.tsx +14 -0
- package/dist/create/template/default-project/src/pages/Activity.tsx +7 -0
- package/dist/create/template/default-project/src/pages/Agents.tsx +15 -0
- package/dist/create/template/default-project/src/pages/Dashboard.tsx +17 -0
- package/dist/create/template/default-project/src/pages/Invitations.tsx +11 -0
- package/dist/create/template/default-project/src/pages/Projects.tsx +11 -0
- package/dist/create/template/default-project/src/pages/SignIn.tsx +8 -0
- package/dist/create/template/default-project/src/pages/SignUp.tsx +8 -0
- package/dist/create/template/default-project/src/styles-application.css +21 -0
- package/dist/create/template/default-project/src/styles.css +1 -0
- package/dist/create/template/default-project/workflows/agent-assisted-summary.ts +1 -0
- package/dist/create/template/default-project/workflows/invitation.ts +30 -0
- package/dist/create/template/default-project/workflows/project-created.ts +2 -0
- package/dist/db.d.ts +49 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +77 -0
- package/dist/error-codes.d.ts +20 -1
- package/dist/error-codes.d.ts.map +1 -1
- package/dist/error-codes.js +25 -0
- package/dist/flowspec.d.ts.map +1 -1
- package/dist/flowspec.js +27 -6
- package/dist/http-db.d.ts +15 -2
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +113 -5
- package/dist/index-core.d.ts +3 -0
- package/dist/index-core.d.ts.map +1 -1
- package/dist/index-core.js +3 -0
- package/dist/proposal.d.ts +85 -0
- package/dist/proposal.d.ts.map +1 -0
- package/dist/proposal.js +105 -0
- package/dist/studio/app.d.ts +3 -1
- package/dist/studio/app.d.ts.map +1 -1
- package/dist/studio/components/ApplicationDesigner.d.ts +2 -1
- package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -1
- package/dist/studio/components/AskFeltDB.d.ts +20 -0
- package/dist/studio/components/AskFeltDB.d.ts.map +1 -0
- package/dist/studio/components/ProposalBrowser.d.ts +3 -0
- package/dist/studio/components/ProposalBrowser.d.ts.map +1 -0
- package/dist/studio/components/index.d.ts +2 -0
- package/dist/studio/components/index.d.ts.map +1 -1
- package/dist/studio/components/index.js +4 -4
- package/dist/studio/{components-Dxuhrv8_.js → components-Bm4sjhcX.js} +335 -5
- package/dist/studio/index.js +168 -157
- package/dist/studio/studio.css +1 -1
- package/dist/studio-app/assets/dist-DA1MYSzp.js +1 -0
- package/dist/studio-app/assets/{feltdb_wasm-DVKsw75S.js → feltdb_wasm-B-Ccgccb.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-wH3enJIA.wasm +0 -0
- package/dist/studio-app/assets/{index-C1GWyazR.css → index-BRrmvaKw.css} +1 -1
- package/dist/studio-app/assets/index-CEIqowCh.js +42 -0
- package/dist/studio-app/assets/lib-B2_7fcn7.js +69 -0
- package/dist/studio-app/assets/worker-CARZ5g7K.js +69 -0
- package/dist/studio-app/index.html +2 -2
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/workspace/local-development-authority.d.ts +3 -0
- package/dist/workspace/local-development-authority.d.ts.map +1 -1
- package/dist/workspace/local-development-authority.js +62 -1
- package/package.json +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-DNyNf0yy.wasm +0 -0
- package/dist/studio-app/assets/index-B5tnmvSD.js +0 -28
|
@@ -283,6 +283,10 @@ pub enum AuthorityError {
|
|
|
283
283
|
provided: FencingToken,
|
|
284
284
|
current: FencingToken,
|
|
285
285
|
},
|
|
286
|
+
QuorumUnavailable {
|
|
287
|
+
live: usize,
|
|
288
|
+
required: usize,
|
|
289
|
+
},
|
|
286
290
|
Io(String),
|
|
287
291
|
}
|
|
288
292
|
|
|
@@ -296,6 +300,12 @@ impl std::fmt::Display for AuthorityError {
|
|
|
296
300
|
Self::StaleToken { provided, current } => {
|
|
297
301
|
write!(f, "stale token {provided}, current is {current}")
|
|
298
302
|
}
|
|
303
|
+
Self::QuorumUnavailable { live, required } => {
|
|
304
|
+
write!(
|
|
305
|
+
f,
|
|
306
|
+
"authority quorum unavailable: {live} live, {required} required"
|
|
307
|
+
)
|
|
308
|
+
}
|
|
299
309
|
Self::Io(msg) => write!(f, "authority store: {msg}"),
|
|
300
310
|
}
|
|
301
311
|
}
|
|
@@ -308,6 +318,20 @@ impl AuthorityStore {
|
|
|
308
318
|
cluster_id: &str,
|
|
309
319
|
local_node_id: &str,
|
|
310
320
|
local_priority: u32,
|
|
321
|
+
) -> Result<Self, AuthorityError> {
|
|
322
|
+
Self::open_with_failure_detector(path, cluster_id, local_node_id, local_priority, 1_000, 5)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/// Open with an explicitly configured failure detector. Production keeps
|
|
326
|
+
/// the conservative default above; process-level acceptance tests use a
|
|
327
|
+
/// shorter interval so failure is event-driven without multi-second sleeps.
|
|
328
|
+
pub fn open_with_failure_detector<P: AsRef<Path>>(
|
|
329
|
+
path: P,
|
|
330
|
+
cluster_id: &str,
|
|
331
|
+
local_node_id: &str,
|
|
332
|
+
local_priority: u32,
|
|
333
|
+
heartbeat_interval_ms: u64,
|
|
334
|
+
failure_threshold: u32,
|
|
311
335
|
) -> Result<Self, AuthorityError> {
|
|
312
336
|
let path = path.as_ref().to_path_buf();
|
|
313
337
|
let state = if path.exists() {
|
|
@@ -323,7 +347,7 @@ impl AuthorityStore {
|
|
|
323
347
|
Ok(Self {
|
|
324
348
|
path,
|
|
325
349
|
state,
|
|
326
|
-
heartbeats: HeartbeatState::new(
|
|
350
|
+
heartbeats: HeartbeatState::new(heartbeat_interval_ms, failure_threshold),
|
|
327
351
|
})
|
|
328
352
|
}
|
|
329
353
|
|
|
@@ -333,8 +357,8 @@ impl AuthorityStore {
|
|
|
333
357
|
std::fs::create_dir_all(parent).map_err(|e| AuthorityError::Io(e.to_string()))?;
|
|
334
358
|
}
|
|
335
359
|
let temp = self.path.with_extension("tmp");
|
|
336
|
-
let bytes =
|
|
337
|
-
|
|
360
|
+
let bytes = serde_json::to_vec_pretty(&self.state)
|
|
361
|
+
.map_err(|e| AuthorityError::Io(e.to_string()))?;
|
|
338
362
|
{
|
|
339
363
|
use std::io::Write;
|
|
340
364
|
let mut file =
|
|
@@ -416,7 +440,9 @@ impl AuthorityStore {
|
|
|
416
440
|
|
|
417
441
|
let mut record =
|
|
418
442
|
AuthorityRecord::new(self.state.local_node_id.clone(), new_token, previous);
|
|
419
|
-
record
|
|
443
|
+
record
|
|
444
|
+
.acknowledgements
|
|
445
|
+
.insert(self.state.local_node_id.clone());
|
|
420
446
|
|
|
421
447
|
self.state.current_authority = Some(record);
|
|
422
448
|
self.state.local_role = AuthorityRole::Authority;
|
|
@@ -512,6 +538,31 @@ impl AuthorityStore {
|
|
|
512
538
|
Ok(())
|
|
513
539
|
}
|
|
514
540
|
|
|
541
|
+
/// Validate an authoritative write against both its fencing term and a
|
|
542
|
+
/// live quorum. A partitioned former authority may not have observed the
|
|
543
|
+
/// replacement's newer token yet, so fencing alone is insufficient until
|
|
544
|
+
/// healing; expiry of its quorum evidence closes that split-brain window.
|
|
545
|
+
pub fn validate_quorum_write(
|
|
546
|
+
&self,
|
|
547
|
+
token: FencingToken,
|
|
548
|
+
required_quorum: usize,
|
|
549
|
+
) -> Result<(), AuthorityError> {
|
|
550
|
+
self.validate_write(Some(token))?;
|
|
551
|
+
let live = 1 + self
|
|
552
|
+
.heartbeats
|
|
553
|
+
.last_seen
|
|
554
|
+
.keys()
|
|
555
|
+
.filter(|node_id| !self.heartbeats.is_failed(node_id))
|
|
556
|
+
.count();
|
|
557
|
+
if live < required_quorum {
|
|
558
|
+
return Err(AuthorityError::QuorumUnavailable {
|
|
559
|
+
live,
|
|
560
|
+
required: required_quorum,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
Ok(())
|
|
564
|
+
}
|
|
565
|
+
|
|
515
566
|
/// Step down from authority role (voluntary).
|
|
516
567
|
pub fn step_down(&mut self) -> Result<(), AuthorityError> {
|
|
517
568
|
if self.state.local_role == AuthorityRole::Authority {
|
|
@@ -746,13 +797,40 @@ mod tests {
|
|
|
746
797
|
assert!(heartbeats.is_failed("A"));
|
|
747
798
|
}
|
|
748
799
|
|
|
800
|
+
#[test]
|
|
801
|
+
fn partitioned_authority_loses_write_quorum() {
|
|
802
|
+
let dir = tempfile::tempdir().unwrap();
|
|
803
|
+
let mut store = AuthorityStore::open_with_failure_detector(
|
|
804
|
+
dir.path().join("authority.json"),
|
|
805
|
+
"test-cluster",
|
|
806
|
+
"A",
|
|
807
|
+
10,
|
|
808
|
+
1,
|
|
809
|
+
1,
|
|
810
|
+
)
|
|
811
|
+
.expect("open authority store");
|
|
812
|
+
let token = match store.claim_authority().expect("claim") {
|
|
813
|
+
ClaimResult::Granted { token } => token,
|
|
814
|
+
_ => panic!("authority claim was not granted"),
|
|
815
|
+
};
|
|
816
|
+
store.heartbeat("B");
|
|
817
|
+
assert!(store.validate_quorum_write(token, 2).is_ok());
|
|
818
|
+
store.heartbeats.last_seen.insert("B".to_string(), 0);
|
|
819
|
+
assert!(matches!(
|
|
820
|
+
store.validate_quorum_write(token, 2),
|
|
821
|
+
Err(AuthorityError::QuorumUnavailable {
|
|
822
|
+
live: 1,
|
|
823
|
+
required: 2
|
|
824
|
+
})
|
|
825
|
+
));
|
|
826
|
+
}
|
|
827
|
+
|
|
749
828
|
#[test]
|
|
750
829
|
fn recovery_from_fenced() {
|
|
751
830
|
let dir = tempfile::tempdir().unwrap();
|
|
752
831
|
let path = dir.path().join("authority.json");
|
|
753
832
|
|
|
754
|
-
let mut store =
|
|
755
|
-
AuthorityStore::open(&path, "test-cluster", "A", 10).expect("open store");
|
|
833
|
+
let mut store = AuthorityStore::open(&path, "test-cluster", "A", 10).expect("open store");
|
|
756
834
|
store.claim_authority().expect("claim");
|
|
757
835
|
|
|
758
836
|
// Get fenced
|
|
@@ -51,9 +51,11 @@ use feltdb::distributed_transactions::{
|
|
|
51
51
|
CausalCapacity, DistributedTransactionExecutor, ReceiveOutcome, ReplicationMessage,
|
|
52
52
|
SubmitOutcome,
|
|
53
53
|
};
|
|
54
|
-
use feltdb::
|
|
55
|
-
|
|
54
|
+
use feltdb::replica_acknowledgements::{
|
|
55
|
+
AcknowledgementStore, ReclamationEvidence, ReplicaAcknowledgement,
|
|
56
|
+
};
|
|
56
57
|
use feltdb::replica_membership::MembershipStore;
|
|
58
|
+
use feltdb::replication_protocol::ProtocolTransport;
|
|
57
59
|
use feltdb::state_hash::StateHash;
|
|
58
60
|
use feltdb::tcp_transport::TcpTransport;
|
|
59
61
|
use feltdb::transactions::{
|
|
@@ -127,7 +129,11 @@ fn deliver(
|
|
|
127
129
|
Ok(ReceiveOutcome::AlreadyKnown) => {
|
|
128
130
|
json!({ "envelope": key, "outcome": "already_known" })
|
|
129
131
|
}
|
|
130
|
-
Ok(ReceiveOutcome::CapacityDeferred {
|
|
132
|
+
Ok(ReceiveOutcome::CapacityDeferred {
|
|
133
|
+
missing,
|
|
134
|
+
pending_entries,
|
|
135
|
+
pending_bytes,
|
|
136
|
+
}) => {
|
|
131
137
|
// Durable, but not buffered. The caller's obligation is to stop
|
|
132
138
|
// reading from this peer; the envelope is admitted from the log
|
|
133
139
|
// once the backlog drains.
|
|
@@ -178,16 +184,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
178
184
|
let mut max_pending_entries: Option<usize> = None;
|
|
179
185
|
let mut max_pending_bytes: Option<usize> = None;
|
|
180
186
|
let mut authority_priority: u32 = 0;
|
|
187
|
+
let mut heartbeat_interval_ms: u64 = 1_000;
|
|
188
|
+
let mut heartbeat_failure_threshold: u32 = 5;
|
|
181
189
|
|
|
182
190
|
let mut index = 1;
|
|
183
191
|
while index < args.len() {
|
|
184
192
|
match args[index].as_str() {
|
|
185
193
|
"--node-id" => {
|
|
186
194
|
index += 1;
|
|
187
|
-
node_id = args
|
|
188
|
-
.get(index)
|
|
189
|
-
.ok_or("--node-id requires a value")?
|
|
190
|
-
.clone();
|
|
195
|
+
node_id = args.get(index).ok_or("--node-id requires a value")?.clone();
|
|
191
196
|
}
|
|
192
197
|
"--data" => {
|
|
193
198
|
index += 1;
|
|
@@ -197,11 +202,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
197
202
|
}
|
|
198
203
|
"--send" => {
|
|
199
204
|
index += 1;
|
|
200
|
-
send_specs.push(parse_link(
|
|
205
|
+
send_specs.push(parse_link(
|
|
206
|
+
args.get(index).ok_or("--send requires PEER=ADDR")?,
|
|
207
|
+
)?);
|
|
201
208
|
}
|
|
202
209
|
"--recv" => {
|
|
203
210
|
index += 1;
|
|
204
|
-
recv_specs.push(parse_link(
|
|
211
|
+
recv_specs.push(parse_link(
|
|
212
|
+
args.get(index).ok_or("--recv requires PEER=ADDR")?,
|
|
213
|
+
)?);
|
|
205
214
|
}
|
|
206
215
|
// The bound is settable so an acceptance test can reach it through
|
|
207
216
|
// the production path. Filling the shipping default would mean
|
|
@@ -237,6 +246,22 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
237
246
|
.parse::<u32>()
|
|
238
247
|
.map_err(|e| format!("--authority-priority: {e}"))?;
|
|
239
248
|
}
|
|
249
|
+
"--heartbeat-interval-ms" => {
|
|
250
|
+
index += 1;
|
|
251
|
+
heartbeat_interval_ms = args
|
|
252
|
+
.get(index)
|
|
253
|
+
.ok_or("--heartbeat-interval-ms requires a value")?
|
|
254
|
+
.parse::<u64>()
|
|
255
|
+
.map_err(|e| format!("--heartbeat-interval-ms: {e}"))?;
|
|
256
|
+
}
|
|
257
|
+
"--heartbeat-failure-threshold" => {
|
|
258
|
+
index += 1;
|
|
259
|
+
heartbeat_failure_threshold = args
|
|
260
|
+
.get(index)
|
|
261
|
+
.ok_or("--heartbeat-failure-threshold requires a value")?
|
|
262
|
+
.parse::<u32>()
|
|
263
|
+
.map_err(|e| format!("--heartbeat-failure-threshold: {e}"))?;
|
|
264
|
+
}
|
|
240
265
|
other => return Err(format!("unknown argument {other}").into()),
|
|
241
266
|
}
|
|
242
267
|
index += 1;
|
|
@@ -282,7 +307,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
282
307
|
// recover its previous role. The cluster ID is set by the first `authority-claim`
|
|
283
308
|
// command, so it starts as the node's own ID.
|
|
284
309
|
let authority = Arc::new(Mutex::new(
|
|
285
|
-
AuthorityStore::
|
|
310
|
+
AuthorityStore::open_with_failure_detector(
|
|
286
311
|
data_dir
|
|
287
312
|
.as_ref()
|
|
288
313
|
.map(|dir| dir.join("authority.json"))
|
|
@@ -290,13 +315,18 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
290
315
|
&node_id, // cluster_id defaults to node_id until set
|
|
291
316
|
&node_id,
|
|
292
317
|
authority_priority,
|
|
318
|
+
heartbeat_interval_ms,
|
|
319
|
+
heartbeat_failure_threshold,
|
|
293
320
|
)
|
|
294
321
|
.map_err(|e| e.to_string())?,
|
|
295
322
|
));
|
|
296
323
|
|
|
297
324
|
let initial_state = StateHash::from_hex("0".repeat(64));
|
|
298
|
-
let mut executor =
|
|
299
|
-
|
|
325
|
+
let mut executor = DistributedTransactionExecutor::with_log(
|
|
326
|
+
node_id.clone(),
|
|
327
|
+
initial_state.clone(),
|
|
328
|
+
log_path.clone(),
|
|
329
|
+
)?;
|
|
300
330
|
|
|
301
331
|
// The bound is set before recovery, not after, because recovery restores
|
|
302
332
|
// the backlog and has to respect the same limit the live path does.
|
|
@@ -338,7 +368,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
338
368
|
// missed an operation observably divergent. It lives beside the executor
|
|
339
369
|
// under one lock so a local commit and an arriving replication cannot
|
|
340
370
|
// interleave and chain onto a parent that is already stale.
|
|
341
|
-
let
|
|
371
|
+
let recovered_version = executor
|
|
372
|
+
.get_replica_state(&node_id)
|
|
373
|
+
.map(|replica| StateVersion::new(replica.vector_clock, replica.state_hash.to_string()))
|
|
374
|
+
.unwrap_or_else(|| StateVersion::new(VectorClock::new(), "0".repeat(64)));
|
|
375
|
+
let core = Arc::new(Mutex::new((executor, recovered_version)));
|
|
342
376
|
let should_exit = Arc::new(AtomicBool::new(false));
|
|
343
377
|
// Per-peer delivery hold.
|
|
344
378
|
//
|
|
@@ -367,7 +401,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
367
401
|
.into_iter()
|
|
368
402
|
.map(|(peer, addr)| {
|
|
369
403
|
let transport = TcpTransport::new("127.0.0.1:0".to_string());
|
|
370
|
-
SendLink {
|
|
404
|
+
SendLink {
|
|
405
|
+
peer,
|
|
406
|
+
addr,
|
|
407
|
+
control: Arc::new(Mutex::new(transport.clone())),
|
|
408
|
+
io: transport,
|
|
409
|
+
}
|
|
371
410
|
})
|
|
372
411
|
.collect();
|
|
373
412
|
|
|
@@ -375,7 +414,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
375
414
|
.into_iter()
|
|
376
415
|
.map(|(peer, addr)| {
|
|
377
416
|
let transport = TcpTransport::new(addr.clone());
|
|
378
|
-
RecvLink {
|
|
417
|
+
RecvLink {
|
|
418
|
+
peer,
|
|
419
|
+
addr,
|
|
420
|
+
control: Arc::new(Mutex::new(transport.clone())),
|
|
421
|
+
io: transport,
|
|
422
|
+
}
|
|
379
423
|
})
|
|
380
424
|
.collect();
|
|
381
425
|
|
|
@@ -509,8 +553,35 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
509
553
|
};
|
|
510
554
|
|
|
511
555
|
match verb {
|
|
512
|
-
"execute-tx" => {
|
|
513
|
-
let record = if
|
|
556
|
+
"execute-tx" | "authority-write" => {
|
|
557
|
+
let record = if verb == "authority-write" {
|
|
558
|
+
let mut parts = argument.splitn(2, ' ');
|
|
559
|
+
let token = parts.next().and_then(|value| value.parse::<u64>().ok());
|
|
560
|
+
let record = parts.next().unwrap_or("item").to_string();
|
|
561
|
+
let validation = match token {
|
|
562
|
+
Some(token) => authority
|
|
563
|
+
.lock()
|
|
564
|
+
.await
|
|
565
|
+
.validate_quorum_write(FencingToken(token), 2),
|
|
566
|
+
None => Err(feltdb::authority_failover::AuthorityError::StaleToken {
|
|
567
|
+
provided: FencingToken(0),
|
|
568
|
+
current: authority.lock().await.state().current_token(),
|
|
569
|
+
}),
|
|
570
|
+
};
|
|
571
|
+
if let Err(error) = validation {
|
|
572
|
+
println!(
|
|
573
|
+
"AUTHORITY_WRITE_REJECTED {}",
|
|
574
|
+
json!({ "record": record, "reason": error.to_string() })
|
|
575
|
+
);
|
|
576
|
+
io::stdout().flush().ok();
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
record
|
|
580
|
+
} else if argument.is_empty() {
|
|
581
|
+
"item".to_string()
|
|
582
|
+
} else {
|
|
583
|
+
argument
|
|
584
|
+
};
|
|
514
585
|
|
|
515
586
|
let mut fields = HashMap::new();
|
|
516
587
|
fields.insert("record".to_string(), json!(record.clone()));
|
|
@@ -854,7 +925,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
854
925
|
let (frontier, marks) = {
|
|
855
926
|
let guard = core.lock().await;
|
|
856
927
|
let (executor, _) = &*guard;
|
|
857
|
-
(
|
|
928
|
+
(
|
|
929
|
+
executor.causal_frontier().clone(),
|
|
930
|
+
executor.origin_sequence_high_water(),
|
|
931
|
+
)
|
|
858
932
|
};
|
|
859
933
|
let view: Vec<String> = membership
|
|
860
934
|
.get_members()
|
|
@@ -1083,27 +1157,30 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
1083
1157
|
"list-operations" => {
|
|
1084
1158
|
let guard = core.lock().await;
|
|
1085
1159
|
let (executor, _) = &*guard;
|
|
1086
|
-
|
|
1160
|
+
|
|
1087
1161
|
// Get all operations from the log
|
|
1088
1162
|
let mut all_ops = Vec::new();
|
|
1089
1163
|
let mut applied_ops = Vec::new();
|
|
1090
1164
|
let pending_ops = executor.pending_causal_keys();
|
|
1091
1165
|
let deferred_ops = executor.deferred_causal_keys();
|
|
1092
|
-
|
|
1166
|
+
|
|
1093
1167
|
if let Some(ref log) = executor.operation_log {
|
|
1094
1168
|
if let Ok(envelopes) = log.load_all() {
|
|
1095
1169
|
for env in &envelopes {
|
|
1096
|
-
let key = format!(
|
|
1170
|
+
let key = format!(
|
|
1171
|
+
"{}:{}",
|
|
1172
|
+
env.envelope_id.originating_node, env.envelope_id.sequence
|
|
1173
|
+
);
|
|
1097
1174
|
all_ops.push(key.clone());
|
|
1098
1175
|
}
|
|
1099
1176
|
}
|
|
1100
1177
|
}
|
|
1101
|
-
|
|
1178
|
+
|
|
1102
1179
|
// Get applied operations from the barrier
|
|
1103
1180
|
for key in executor.causal_barrier.applied_keys() {
|
|
1104
1181
|
applied_ops.push(key.to_string());
|
|
1105
1182
|
}
|
|
1106
|
-
|
|
1183
|
+
|
|
1107
1184
|
println!(
|
|
1108
1185
|
"OPERATIONS {}",
|
|
1109
1186
|
json!({
|
|
@@ -1120,23 +1197,26 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
1120
1197
|
"operation-inventory" => {
|
|
1121
1198
|
let guard = core.lock().await;
|
|
1122
1199
|
let (executor, _) = &*guard;
|
|
1123
|
-
|
|
1200
|
+
|
|
1124
1201
|
// Get all operations from the log
|
|
1125
1202
|
let mut operations = json!({
|
|
1126
1203
|
"applied": [],
|
|
1127
1204
|
"pending": [],
|
|
1128
1205
|
"deferred": [],
|
|
1129
1206
|
});
|
|
1130
|
-
|
|
1207
|
+
|
|
1131
1208
|
if let Some(ref log) = executor.operation_log {
|
|
1132
1209
|
if let Ok(envelopes) = log.load_all() {
|
|
1133
1210
|
let applied_keys = executor.causal_barrier.applied_keys();
|
|
1134
1211
|
let pending_keys = executor.pending_causal_keys();
|
|
1135
1212
|
let deferred_keys = executor.deferred_causal_keys();
|
|
1136
|
-
|
|
1213
|
+
|
|
1137
1214
|
for env in &envelopes {
|
|
1138
|
-
let key = format!(
|
|
1139
|
-
|
|
1215
|
+
let key = format!(
|
|
1216
|
+
"{}:{}",
|
|
1217
|
+
env.envelope_id.originating_node, env.envelope_id.sequence
|
|
1218
|
+
);
|
|
1219
|
+
|
|
1140
1220
|
if applied_keys.contains(&key) {
|
|
1141
1221
|
operations["applied"].as_array_mut().unwrap().push(json!({
|
|
1142
1222
|
"id": key,
|
|
@@ -1159,7 +1239,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
1159
1239
|
}
|
|
1160
1240
|
}
|
|
1161
1241
|
}
|
|
1162
|
-
|
|
1242
|
+
|
|
1163
1243
|
println!("INVENTORY {}", operations);
|
|
1164
1244
|
io::stdout().flush().ok();
|
|
1165
1245
|
}
|
|
@@ -1198,7 +1278,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
1198
1278
|
})
|
|
1199
1279
|
);
|
|
1200
1280
|
}
|
|
1201
|
-
Ok(ClaimResult::Rejected {
|
|
1281
|
+
Ok(ClaimResult::Rejected {
|
|
1282
|
+
current_authority,
|
|
1283
|
+
current_token,
|
|
1284
|
+
}) => {
|
|
1202
1285
|
println!(
|
|
1203
1286
|
"AUTHORITY_REJECTED {}",
|
|
1204
1287
|
json!({
|
|
@@ -1230,11 +1313,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
1230
1313
|
"authority-accept" => {
|
|
1231
1314
|
let mut parts = argument.split_whitespace();
|
|
1232
1315
|
let authority_node = parts.next().unwrap_or_default().to_string();
|
|
1233
|
-
let token_value: u64 = parts
|
|
1234
|
-
.next()
|
|
1235
|
-
.unwrap_or("0")
|
|
1236
|
-
.parse()
|
|
1237
|
-
.unwrap_or(0);
|
|
1316
|
+
let token_value: u64 = parts.next().unwrap_or("0").parse().unwrap_or(0);
|
|
1238
1317
|
let token = FencingToken(token_value);
|
|
1239
1318
|
|
|
1240
1319
|
let mut guard = authority.lock().await;
|
|
@@ -1265,7 +1344,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
1265
1344
|
let operations_applied = {
|
|
1266
1345
|
let core_guard = core.lock().await;
|
|
1267
1346
|
let (executor, _) = &*core_guard;
|
|
1268
|
-
executor
|
|
1347
|
+
executor
|
|
1348
|
+
.get_replica_state(&node_id)
|
|
1349
|
+
.map(|r| r.operations_applied)
|
|
1350
|
+
.unwrap_or(0)
|
|
1269
1351
|
};
|
|
1270
1352
|
println!(
|
|
1271
1353
|
"AUTHORITY {}",
|
|
@@ -1291,7 +1373,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
1291
1373
|
let from_node = parts.next().unwrap_or_default().to_string();
|
|
1292
1374
|
|
|
1293
1375
|
if from_node.is_empty() {
|
|
1294
|
-
println!(
|
|
1376
|
+
println!(
|
|
1377
|
+
"ERROR {}",
|
|
1378
|
+
json!({ "reason": "heartbeat requires NODE_ID" })
|
|
1379
|
+
);
|
|
1295
1380
|
} else {
|
|
1296
1381
|
let mut guard = authority.lock().await;
|
|
1297
1382
|
guard.heartbeat(&from_node);
|
|
@@ -1377,7 +1462,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
1377
1462
|
}
|
|
1378
1463
|
|
|
1379
1464
|
_ => {
|
|
1380
|
-
println!(
|
|
1465
|
+
println!(
|
|
1466
|
+
"ERROR {}",
|
|
1467
|
+
json!({ "reason": format!("unknown command {verb}") })
|
|
1468
|
+
);
|
|
1381
1469
|
io::stdout().flush().ok();
|
|
1382
1470
|
}
|
|
1383
1471
|
}
|