@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.
Files changed (95) hide show
  1. package/dist/application-development.d.ts +76 -0
  2. package/dist/application-development.d.ts.map +1 -0
  3. package/dist/application-development.js +137 -0
  4. package/dist/application-manifest.d.ts +5 -1
  5. package/dist/application-manifest.d.ts.map +1 -1
  6. package/dist/application-manifest.js +10 -3
  7. package/dist/canonical-application.d.ts +26 -0
  8. package/dist/canonical-application.d.ts.map +1 -0
  9. package/dist/canonical-application.js +45 -0
  10. package/dist/cli/ai-model-config.js +97 -0
  11. package/dist/cli/commands.js +466 -105
  12. package/dist/cli/index.js +1 -1
  13. package/dist/cli/proposal-client.js +54 -0
  14. package/dist/cli/publish-engine.js +162 -0
  15. package/dist/cli/source-sync.js +139 -0
  16. package/dist/create/cli.js +1 -1
  17. package/dist/create/create.js +30 -6
  18. package/dist/create/managed-account.js +14 -7
  19. package/dist/create/package-versions.js +1 -1
  20. package/dist/create/server-source/crates/feltdb/src/application.rs +31 -1
  21. package/dist/create/server-source/crates/feltdb/src/authority_failover.rs +84 -6
  22. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +126 -38
  23. package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +72 -34
  24. package/dist/create/server-source/crates/feltdb-server/src/identity.rs +46 -3
  25. package/dist/create/server-source/crates/feltdb-server/src/main.rs +315 -16
  26. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +20 -0
  27. package/dist/create/template/default-project/README.md +57 -0
  28. package/dist/create/template/default-project/agents/activity-assistant.ts +18 -0
  29. package/dist/create/template/default-project/agents/project-assistant.ts +19 -0
  30. package/dist/create/template/default-project/capabilities/activity-summary.ts +14 -0
  31. package/dist/create/template/default-project/capabilities/project-search.ts +16 -0
  32. package/dist/create/template/default-project/capabilities/project-summary.ts +15 -0
  33. package/dist/create/template/default-project/feltdb.flow +169 -0
  34. package/dist/create/template/default-project/src/App.tsx +20 -0
  35. package/dist/create/template/default-project/src/context/AuthContext.tsx +27 -0
  36. package/dist/create/template/default-project/src/feltdb.ts +87 -0
  37. package/dist/create/template/default-project/src/index.tsx +14 -0
  38. package/dist/create/template/default-project/src/pages/Activity.tsx +7 -0
  39. package/dist/create/template/default-project/src/pages/Agents.tsx +15 -0
  40. package/dist/create/template/default-project/src/pages/Dashboard.tsx +17 -0
  41. package/dist/create/template/default-project/src/pages/Invitations.tsx +11 -0
  42. package/dist/create/template/default-project/src/pages/Projects.tsx +11 -0
  43. package/dist/create/template/default-project/src/pages/SignIn.tsx +8 -0
  44. package/dist/create/template/default-project/src/pages/SignUp.tsx +8 -0
  45. package/dist/create/template/default-project/src/styles-application.css +21 -0
  46. package/dist/create/template/default-project/src/styles.css +1 -0
  47. package/dist/create/template/default-project/workflows/agent-assisted-summary.ts +1 -0
  48. package/dist/create/template/default-project/workflows/invitation.ts +30 -0
  49. package/dist/create/template/default-project/workflows/project-created.ts +2 -0
  50. package/dist/db.d.ts +49 -1
  51. package/dist/db.d.ts.map +1 -1
  52. package/dist/db.js +77 -0
  53. package/dist/error-codes.d.ts +20 -1
  54. package/dist/error-codes.d.ts.map +1 -1
  55. package/dist/error-codes.js +25 -0
  56. package/dist/flowspec.d.ts.map +1 -1
  57. package/dist/flowspec.js +27 -6
  58. package/dist/http-db.d.ts +15 -2
  59. package/dist/http-db.d.ts.map +1 -1
  60. package/dist/http-db.js +113 -5
  61. package/dist/index-core.d.ts +3 -0
  62. package/dist/index-core.d.ts.map +1 -1
  63. package/dist/index-core.js +3 -0
  64. package/dist/proposal.d.ts +85 -0
  65. package/dist/proposal.d.ts.map +1 -0
  66. package/dist/proposal.js +105 -0
  67. package/dist/studio/app.d.ts +3 -1
  68. package/dist/studio/app.d.ts.map +1 -1
  69. package/dist/studio/components/ApplicationDesigner.d.ts +2 -1
  70. package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -1
  71. package/dist/studio/components/AskFeltDB.d.ts +20 -0
  72. package/dist/studio/components/AskFeltDB.d.ts.map +1 -0
  73. package/dist/studio/components/ProposalBrowser.d.ts +3 -0
  74. package/dist/studio/components/ProposalBrowser.d.ts.map +1 -0
  75. package/dist/studio/components/index.d.ts +2 -0
  76. package/dist/studio/components/index.d.ts.map +1 -1
  77. package/dist/studio/components/index.js +4 -4
  78. package/dist/studio/{components-Dxuhrv8_.js → components-Bm4sjhcX.js} +335 -5
  79. package/dist/studio/index.js +168 -157
  80. package/dist/studio/studio.css +1 -1
  81. package/dist/studio-app/assets/dist-DA1MYSzp.js +1 -0
  82. package/dist/studio-app/assets/{feltdb_wasm-DVKsw75S.js → feltdb_wasm-B-Ccgccb.js} +1 -1
  83. package/dist/studio-app/assets/feltdb_wasm_bg-wH3enJIA.wasm +0 -0
  84. package/dist/studio-app/assets/{index-C1GWyazR.css → index-BRrmvaKw.css} +1 -1
  85. package/dist/studio-app/assets/index-CEIqowCh.js +42 -0
  86. package/dist/studio-app/assets/lib-B2_7fcn7.js +69 -0
  87. package/dist/studio-app/assets/worker-CARZ5g7K.js +69 -0
  88. package/dist/studio-app/index.html +2 -2
  89. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  90. package/dist/workspace/local-development-authority.d.ts +3 -0
  91. package/dist/workspace/local-development-authority.d.ts.map +1 -1
  92. package/dist/workspace/local-development-authority.js +62 -1
  93. package/package.json +1 -1
  94. package/dist/studio-app/assets/feltdb_wasm_bg-DNyNf0yy.wasm +0 -0
  95. 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(1000, 5), // 1s heartbeat, 5 missed = failure
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
- serde_json::to_vec_pretty(&self.state).map_err(|e| AuthorityError::Io(e.to_string()))?;
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.acknowledgements.insert(self.state.local_node_id.clone());
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::replication_protocol::ProtocolTransport;
55
- use feltdb::replica_acknowledgements::{AcknowledgementStore, ReclamationEvidence, ReplicaAcknowledgement};
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 { missing, pending_entries, pending_bytes }) => {
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(args.get(index).ok_or("--send requires PEER=ADDR")?)?);
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(args.get(index).ok_or("--recv requires PEER=ADDR")?)?);
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::open(
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
- DistributedTransactionExecutor::with_log(node_id.clone(), initial_state.clone(), log_path.clone())?;
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 core = Arc::new(Mutex::new((executor, StateVersion::new(VectorClock::new(), "0".repeat(64)))));
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 { peer, addr, control: Arc::new(Mutex::new(transport.clone())), io: transport }
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 { peer, addr, control: Arc::new(Mutex::new(transport.clone())), io: transport }
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 argument.is_empty() { "item".to_string() } else { argument };
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
- (executor.causal_frontier().clone(), executor.origin_sequence_high_water())
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!("{}:{}", env.envelope_id.originating_node, env.envelope_id.sequence);
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!("{}:{}", env.envelope_id.originating_node, env.envelope_id.sequence);
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 { current_authority, current_token }) => {
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.get_replica_state(&node_id).map(|r| r.operations_applied).unwrap_or(0)
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!("ERROR {}", json!({ "reason": "heartbeat requires NODE_ID" }));
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!("ERROR {}", json!({ "reason": format!("unknown command {verb}") }));
1465
+ println!(
1466
+ "ERROR {}",
1467
+ json!({ "reason": format!("unknown command {verb}") })
1468
+ );
1381
1469
  io::stdout().flush().ok();
1382
1470
  }
1383
1471
  }