oh-my-codex 0.21.16 → 0.21.18
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/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/crates/omx-runtime/src/main.rs +14 -2
- package/crates/omx-runtime/tests/execution.rs +63 -1
- package/crates/omx-runtime-core/src/dispatch.rs +53 -0
- package/crates/omx-runtime-core/src/engine.rs +637 -35
- package/crates/omx-runtime-core/src/lib.rs +14 -0
- package/crates/omx-runtime-core/src/mailbox.rs +168 -38
- package/dist/cli/__tests__/doctor-invalid-config.test.js +57 -0
- package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
- package/dist/cli/__tests__/plugin-cache-live-pin-3790.test.js +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +54 -0
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/compat/__tests__/rust-runtime-compat.test.js +3 -3
- package/dist/config/__tests__/generator-idempotent.test.js +64 -1
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +66 -21
- package/dist/config/generator.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +1006 -10
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/mcp/state-server.d.ts +1 -1
- package/dist/runtime/__tests__/bridge.test.js +91 -8
- package/dist/runtime/__tests__/bridge.test.js.map +1 -1
- package/dist/runtime/bridge.d.ts +13 -2
- package/dist/runtime/bridge.d.ts.map +1 -1
- package/dist/runtime/bridge.js +94 -41
- package/dist/runtime/bridge.js.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.d.ts +12 -0
- package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.js +127 -27
- package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
- package/dist/team/__tests__/api-interop.test.js +2 -2
- package/dist/team/__tests__/delivery-e2e-smoke.test.js +19 -5
- package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +21 -1
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/state.test.js +1532 -11
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/mcp-comm.js +2 -2
- package/dist/team/mcp-comm.js.map +1 -1
- package/dist/team/state/dispatch-recovery.d.ts +8 -0
- package/dist/team/state/dispatch-recovery.d.ts.map +1 -0
- package/dist/team/state/dispatch-recovery.js +88 -0
- package/dist/team/state/dispatch-recovery.js.map +1 -0
- package/dist/team/state/dispatch.d.ts.map +1 -1
- package/dist/team/state/dispatch.js +94 -69
- package/dist/team/state/dispatch.js.map +1 -1
- package/dist/team/state/mailbox.d.ts +9 -2
- package/dist/team/state/mailbox.d.ts.map +1 -1
- package/dist/team/state/mailbox.js +186 -68
- package/dist/team/state/mailbox.js.map +1 -1
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +116 -30
- package/dist/team/state.js.map +1 -1
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/src/scripts/notify-hook/team-dispatch.ts +117 -26
|
@@ -218,36 +218,67 @@ impl RuntimeEngine {
|
|
|
218
218
|
}
|
|
219
219
|
RuntimeCommand::CaptureSnapshot => RuntimeEvent::SnapshotCaptured,
|
|
220
220
|
RuntimeCommand::CreateMailboxMessage {
|
|
221
|
+
team_name,
|
|
221
222
|
message_id,
|
|
222
223
|
from_worker,
|
|
223
224
|
to_worker,
|
|
224
225
|
body,
|
|
225
226
|
} => {
|
|
226
|
-
self.mailbox
|
|
227
|
-
.
|
|
227
|
+
self.mailbox.create(
|
|
228
|
+
team_name.clone(),
|
|
229
|
+
&message_id,
|
|
230
|
+
&from_worker,
|
|
231
|
+
&to_worker,
|
|
232
|
+
&body,
|
|
233
|
+
)?;
|
|
228
234
|
RuntimeEvent::MailboxMessageCreated {
|
|
235
|
+
team_name,
|
|
229
236
|
message_id,
|
|
230
237
|
from_worker,
|
|
231
238
|
to_worker,
|
|
232
239
|
body: Some(body),
|
|
233
240
|
}
|
|
234
241
|
}
|
|
235
|
-
RuntimeCommand::MarkMailboxNotified {
|
|
236
|
-
|
|
237
|
-
|
|
242
|
+
RuntimeCommand::MarkMailboxNotified {
|
|
243
|
+
message_id,
|
|
244
|
+
team_name,
|
|
245
|
+
} => {
|
|
246
|
+
let team_name = self
|
|
247
|
+
.mailbox
|
|
248
|
+
.mark_notified(&message_id, team_name.as_deref())?;
|
|
249
|
+
RuntimeEvent::MailboxNotified {
|
|
250
|
+
team_name,
|
|
251
|
+
message_id,
|
|
252
|
+
}
|
|
238
253
|
}
|
|
239
|
-
RuntimeCommand::MarkMailboxDelivered {
|
|
240
|
-
|
|
241
|
-
|
|
254
|
+
RuntimeCommand::MarkMailboxDelivered {
|
|
255
|
+
message_id,
|
|
256
|
+
team_name,
|
|
257
|
+
} => {
|
|
258
|
+
let team_name = self
|
|
259
|
+
.mailbox
|
|
260
|
+
.mark_delivered(&message_id, team_name.as_deref())?;
|
|
261
|
+
RuntimeEvent::MailboxDelivered {
|
|
262
|
+
team_name,
|
|
263
|
+
message_id,
|
|
264
|
+
}
|
|
242
265
|
}
|
|
243
266
|
};
|
|
244
267
|
|
|
245
|
-
|
|
268
|
+
// Projection refreshes do not change domain state or need replay history.
|
|
269
|
+
if !matches!(
|
|
270
|
+
event,
|
|
271
|
+
RuntimeEvent::DispatchRecordsRemoved { .. } | RuntimeEvent::SnapshotCaptured
|
|
272
|
+
) {
|
|
246
273
|
self.event_log.push(event.clone());
|
|
247
274
|
}
|
|
248
275
|
Ok(event)
|
|
249
276
|
}
|
|
250
277
|
|
|
278
|
+
pub fn dispatch_records(&self) -> &[crate::DispatchRecord] {
|
|
279
|
+
self.dispatch.records()
|
|
280
|
+
}
|
|
281
|
+
|
|
251
282
|
pub fn snapshot(&self) -> RuntimeSnapshot {
|
|
252
283
|
RuntimeSnapshot {
|
|
253
284
|
schema_version: RUNTIME_SCHEMA_VERSION,
|
|
@@ -362,9 +393,11 @@ impl RuntimeEngine {
|
|
|
362
393
|
let events_path = dir.join("events.json");
|
|
363
394
|
let events_json = std::fs::read_to_string(&events_path)?;
|
|
364
395
|
let mut events: Vec<RuntimeEvent> = serde_json::from_str(&events_json)?;
|
|
365
|
-
let mailbox = std::fs::read_to_string(dir.join("mailbox.json"))
|
|
366
|
-
|
|
367
|
-
|
|
396
|
+
let mailbox = match std::fs::read_to_string(dir.join("mailbox.json")) {
|
|
397
|
+
Ok(mailbox_json) => Some(serde_json::from_str::<MailboxLog>(&mailbox_json)?),
|
|
398
|
+
Err(error) if error.kind() == std::io::ErrorKind::NotFound => None,
|
|
399
|
+
Err(error) => return Err(error.into()),
|
|
400
|
+
};
|
|
368
401
|
let ledger = match std::fs::read_to_string(dir.join(DISPATCH_SEEN_LEDGER_FILE)) {
|
|
369
402
|
Ok(json) => parse_dispatch_seen_ledger(&json)?,
|
|
370
403
|
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
|
|
@@ -376,14 +409,13 @@ impl RuntimeEngine {
|
|
|
376
409
|
}
|
|
377
410
|
Err(error) => return Err(error.into()),
|
|
378
411
|
};
|
|
379
|
-
let
|
|
380
|
-
Ok(json) =>
|
|
381
|
-
|
|
382
|
-
collect_unique_dispatch_ids(dispatch.request_ids())?
|
|
383
|
-
}
|
|
384
|
-
Err(error) if error.kind() == std::io::ErrorKind::NotFound => BTreeSet::new(),
|
|
412
|
+
let dispatch_projection = match std::fs::read_to_string(dir.join("dispatch.json")) {
|
|
413
|
+
Ok(json) => serde_json::from_str::<DispatchLog>(&json)?,
|
|
414
|
+
Err(error) if error.kind() == std::io::ErrorKind::NotFound => DispatchLog::new(),
|
|
385
415
|
Err(error) => return Err(error.into()),
|
|
386
416
|
};
|
|
417
|
+
let persisted_dispatch_ids =
|
|
418
|
+
collect_unique_dispatch_ids(dispatch_projection.request_ids())?;
|
|
387
419
|
|
|
388
420
|
drop(lock_file);
|
|
389
421
|
|
|
@@ -403,29 +435,64 @@ impl RuntimeEngine {
|
|
|
403
435
|
.into());
|
|
404
436
|
}
|
|
405
437
|
engine.seen_dispatch_ids = ledger;
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
438
|
+
engine
|
|
439
|
+
.dispatch
|
|
440
|
+
.recover_pending_hook_attempts(&dispatch_projection);
|
|
441
|
+
|
|
442
|
+
if let Some(mut mailbox_state) = mailbox {
|
|
443
|
+
let mut body_by_message = std::collections::HashMap::new();
|
|
444
|
+
for record in mailbox_state.records() {
|
|
445
|
+
body_by_message
|
|
446
|
+
.entry((record.team_name.clone(), record.message_id.clone()))
|
|
447
|
+
.and_modify(|body| *body = None)
|
|
448
|
+
.or_insert(Some(record.body.clone()));
|
|
449
|
+
}
|
|
413
450
|
|
|
414
451
|
for event in &mut events {
|
|
415
452
|
if let RuntimeEvent::MailboxMessageCreated {
|
|
416
|
-
|
|
453
|
+
team_name,
|
|
454
|
+
message_id,
|
|
455
|
+
body,
|
|
456
|
+
..
|
|
417
457
|
} = event
|
|
418
458
|
{
|
|
419
459
|
if body.is_none() {
|
|
420
|
-
if let Some(record_body) =
|
|
460
|
+
if let Some(Some(record_body)) =
|
|
461
|
+
body_by_message.get(&(team_name.clone(), message_id.clone()))
|
|
462
|
+
{
|
|
421
463
|
if !record_body.is_empty() {
|
|
422
|
-
*body = Some(
|
|
464
|
+
*body = Some(record_body.clone());
|
|
423
465
|
}
|
|
424
466
|
}
|
|
425
467
|
}
|
|
468
|
+
if let Some(body) = body.as_deref() {
|
|
469
|
+
mailbox_state.recover_body(team_name.as_deref(), message_id, body);
|
|
470
|
+
}
|
|
426
471
|
}
|
|
427
472
|
}
|
|
428
473
|
engine.mailbox = mailbox_state;
|
|
474
|
+
for event in &events {
|
|
475
|
+
match event {
|
|
476
|
+
RuntimeEvent::MailboxMessageCreated {
|
|
477
|
+
team_name,
|
|
478
|
+
message_id,
|
|
479
|
+
..
|
|
480
|
+
} if !engine.mailbox.records().iter().any(|record| {
|
|
481
|
+
record.message_id == *message_id
|
|
482
|
+
&& record.team_name.as_deref() == team_name.as_deref()
|
|
483
|
+
}) =>
|
|
484
|
+
{
|
|
485
|
+
replay_event(&mut engine, event)?
|
|
486
|
+
}
|
|
487
|
+
RuntimeEvent::MailboxNotified { .. }
|
|
488
|
+
| RuntimeEvent::MailboxDelivered { .. }
|
|
489
|
+
if !mailbox_acknowledgement_is_reflected(&engine.mailbox, event) =>
|
|
490
|
+
{
|
|
491
|
+
replay_event(&mut engine, event)?;
|
|
492
|
+
}
|
|
493
|
+
_ => {}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
429
496
|
}
|
|
430
497
|
|
|
431
498
|
engine.event_log = events;
|
|
@@ -611,23 +678,51 @@ fn replay_event(engine: &mut RuntimeEngine, event: &RuntimeEvent) -> Result<(),
|
|
|
611
678
|
engine.replay.request_replay(cursor.clone());
|
|
612
679
|
}
|
|
613
680
|
RuntimeEvent::MailboxMessageCreated {
|
|
681
|
+
team_name,
|
|
614
682
|
message_id,
|
|
615
683
|
from_worker,
|
|
616
684
|
to_worker,
|
|
617
685
|
body,
|
|
618
686
|
} => {
|
|
619
|
-
engine.mailbox.
|
|
687
|
+
engine.mailbox.replay_create(
|
|
688
|
+
team_name.clone(),
|
|
620
689
|
message_id,
|
|
621
690
|
from_worker,
|
|
622
691
|
to_worker,
|
|
623
692
|
body.as_deref().unwrap_or(""),
|
|
624
693
|
);
|
|
625
694
|
}
|
|
626
|
-
RuntimeEvent::MailboxNotified {
|
|
627
|
-
|
|
695
|
+
RuntimeEvent::MailboxNotified {
|
|
696
|
+
message_id,
|
|
697
|
+
team_name,
|
|
628
698
|
}
|
|
629
|
-
RuntimeEvent::MailboxDelivered {
|
|
630
|
-
|
|
699
|
+
| RuntimeEvent::MailboxDelivered {
|
|
700
|
+
message_id,
|
|
701
|
+
team_name,
|
|
702
|
+
} => {
|
|
703
|
+
// Older acknowledgement events have no scope and are safe only for a unique ID.
|
|
704
|
+
let replay_team = team_name.clone().or_else(|| {
|
|
705
|
+
let mut records = engine
|
|
706
|
+
.mailbox
|
|
707
|
+
.records()
|
|
708
|
+
.iter()
|
|
709
|
+
.filter(|record| record.message_id == *message_id);
|
|
710
|
+
let first = records.next()?;
|
|
711
|
+
if records.next().is_none() {
|
|
712
|
+
first.team_name.clone()
|
|
713
|
+
} else {
|
|
714
|
+
None
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
if matches!(event, RuntimeEvent::MailboxNotified { .. }) {
|
|
718
|
+
let _ = engine
|
|
719
|
+
.mailbox
|
|
720
|
+
.mark_notified(message_id, replay_team.as_deref());
|
|
721
|
+
} else {
|
|
722
|
+
let _ = engine
|
|
723
|
+
.mailbox
|
|
724
|
+
.mark_delivered(message_id, replay_team.as_deref());
|
|
725
|
+
}
|
|
631
726
|
}
|
|
632
727
|
RuntimeEvent::DispatchRecordsRemoved { .. } | RuntimeEvent::SnapshotCaptured => {}
|
|
633
728
|
event => replay_dispatch_event(&mut engine.dispatch, event)?,
|
|
@@ -635,6 +730,66 @@ fn replay_event(engine: &mut RuntimeEngine, event: &RuntimeEvent) -> Result<(),
|
|
|
635
730
|
Ok(())
|
|
636
731
|
}
|
|
637
732
|
|
|
733
|
+
fn mailbox_acknowledgement_is_reflected(mailbox: &MailboxLog, event: &RuntimeEvent) -> bool {
|
|
734
|
+
let (message_id, team_name, delivered) = match event {
|
|
735
|
+
RuntimeEvent::MailboxNotified {
|
|
736
|
+
message_id,
|
|
737
|
+
team_name,
|
|
738
|
+
} => (message_id.as_str(), team_name.as_deref(), false),
|
|
739
|
+
RuntimeEvent::MailboxDelivered {
|
|
740
|
+
message_id,
|
|
741
|
+
team_name,
|
|
742
|
+
} => (message_id.as_str(), team_name.as_deref(), true),
|
|
743
|
+
_ => return false,
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
let record = if let Some(team_name) = team_name {
|
|
747
|
+
let mut scoped = mailbox.records().iter().filter(|record| {
|
|
748
|
+
record.message_id == message_id && record.team_name.as_deref() == Some(team_name)
|
|
749
|
+
});
|
|
750
|
+
match scoped.next() {
|
|
751
|
+
Some(record) => {
|
|
752
|
+
if scoped.next().is_none() {
|
|
753
|
+
Some(record)
|
|
754
|
+
} else {
|
|
755
|
+
None
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
None => {
|
|
759
|
+
let mut matches = mailbox
|
|
760
|
+
.records()
|
|
761
|
+
.iter()
|
|
762
|
+
.filter(|record| record.message_id == message_id);
|
|
763
|
+
let first = matches.next();
|
|
764
|
+
if matches.next().is_none() {
|
|
765
|
+
first.filter(|record| record.team_name.is_none())
|
|
766
|
+
} else {
|
|
767
|
+
None
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
} else {
|
|
772
|
+
let mut matches = mailbox
|
|
773
|
+
.records()
|
|
774
|
+
.iter()
|
|
775
|
+
.filter(|record| record.message_id == message_id);
|
|
776
|
+
let first = matches.next();
|
|
777
|
+
if matches.next().is_none() {
|
|
778
|
+
first
|
|
779
|
+
} else {
|
|
780
|
+
None
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
record.is_some_and(|record| {
|
|
785
|
+
if delivered {
|
|
786
|
+
record.delivered_at.is_some()
|
|
787
|
+
} else {
|
|
788
|
+
record.notified_at.is_some() || record.delivered_at.is_some()
|
|
789
|
+
}
|
|
790
|
+
})
|
|
791
|
+
}
|
|
792
|
+
|
|
638
793
|
pub fn derive_readiness(
|
|
639
794
|
authority: &AuthorityLease,
|
|
640
795
|
_dispatch: &DispatchLog,
|
|
@@ -668,6 +823,80 @@ pub fn derive_readiness(
|
|
|
668
823
|
#[cfg(test)]
|
|
669
824
|
mod tests {
|
|
670
825
|
use super::*;
|
|
826
|
+
use crate::DispatchStatus;
|
|
827
|
+
|
|
828
|
+
#[test]
|
|
829
|
+
fn load_preserves_pending_hook_attempts_across_teams_without_overriding_events() {
|
|
830
|
+
let dir = std::env::temp_dir().join(format!("omx-hook-recovery-{}", std::process::id()));
|
|
831
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
832
|
+
let mut engine = RuntimeEngine::new().with_state_dir(&dir);
|
|
833
|
+
for (id, team) in [
|
|
834
|
+
("alpha", "alpha"),
|
|
835
|
+
("beta", "beta"),
|
|
836
|
+
("notified", "alpha"),
|
|
837
|
+
("wrong-team", "alpha"),
|
|
838
|
+
("wrong-target", "alpha"),
|
|
839
|
+
("terminal-projection", "alpha"),
|
|
840
|
+
] {
|
|
841
|
+
engine.process(RuntimeCommand::QueueDispatch {
|
|
842
|
+
request_id: id.into(), target: "worker-1".into(),
|
|
843
|
+
metadata: Some(serde_json::json!({"team_name": team, "attempt_count": 0, "trigger_message": "original"})),
|
|
844
|
+
}).unwrap();
|
|
845
|
+
}
|
|
846
|
+
engine
|
|
847
|
+
.process(RuntimeCommand::MarkNotified {
|
|
848
|
+
request_id: "notified".into(),
|
|
849
|
+
channel: "runtime".into(),
|
|
850
|
+
})
|
|
851
|
+
.unwrap();
|
|
852
|
+
engine.persist().unwrap();
|
|
853
|
+
let path = dir.join("dispatch.json");
|
|
854
|
+
let mut projection: serde_json::Value =
|
|
855
|
+
serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
|
|
856
|
+
for record in projection["records"].as_array_mut().unwrap() {
|
|
857
|
+
record["status"] = serde_json::json!("pending");
|
|
858
|
+
record["reason"] = serde_json::json!("tmux_send_keys_unconfirmed");
|
|
859
|
+
record["metadata"]["attempt_count"] = serde_json::json!(2);
|
|
860
|
+
record["metadata"]["last_attempt_at"] = serde_json::json!("2026-10-16T00:00:00Z");
|
|
861
|
+
record["metadata"]["trigger_message"] = serde_json::json!("corrupted");
|
|
862
|
+
match record["request_id"].as_str().unwrap() {
|
|
863
|
+
"wrong-team" => record["metadata"]["team_name"] = serde_json::json!("beta"),
|
|
864
|
+
"wrong-target" => record["target"] = serde_json::json!("worker-2"),
|
|
865
|
+
"terminal-projection" => record["status"] = serde_json::json!("delivered"),
|
|
866
|
+
_ => {}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
std::fs::write(&path, serde_json::to_string(&projection).unwrap()).unwrap();
|
|
870
|
+
let mut loaded = RuntimeEngine::load(&dir).unwrap();
|
|
871
|
+
loaded.process(RuntimeCommand::CaptureSnapshot).unwrap();
|
|
872
|
+
loaded.persist().unwrap();
|
|
873
|
+
let reloaded = RuntimeEngine::load(&dir).unwrap();
|
|
874
|
+
for record in reloaded.dispatch.records() {
|
|
875
|
+
let metadata = record.metadata.as_ref().unwrap();
|
|
876
|
+
assert_eq!(metadata["trigger_message"], "original");
|
|
877
|
+
assert_eq!(record.target, "worker-1");
|
|
878
|
+
if record.request_id == "alpha" || record.request_id == "beta" {
|
|
879
|
+
assert_eq!(metadata["attempt_count"], 2);
|
|
880
|
+
assert_eq!(metadata["last_attempt_at"], "2026-10-16T00:00:00Z");
|
|
881
|
+
assert_eq!(record.reason.as_deref(), Some("tmux_send_keys_unconfirmed"));
|
|
882
|
+
assert_eq!(record.status, DispatchStatus::Pending);
|
|
883
|
+
} else {
|
|
884
|
+
assert_eq!(metadata["attempt_count"], 0);
|
|
885
|
+
assert!(metadata.get("last_attempt_at").is_none());
|
|
886
|
+
assert_eq!(metadata["team_name"], "alpha");
|
|
887
|
+
assert_eq!(
|
|
888
|
+
record.status,
|
|
889
|
+
if record.request_id == "notified" {
|
|
890
|
+
DispatchStatus::Notified
|
|
891
|
+
} else {
|
|
892
|
+
DispatchStatus::Pending
|
|
893
|
+
}
|
|
894
|
+
);
|
|
895
|
+
assert_ne!(record.reason.as_deref(), Some("tmux_send_keys_unconfirmed"));
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
899
|
+
}
|
|
671
900
|
|
|
672
901
|
#[test]
|
|
673
902
|
fn process_acquire_authority() {
|
|
@@ -740,11 +969,11 @@ mod tests {
|
|
|
740
969
|
}
|
|
741
970
|
|
|
742
971
|
#[test]
|
|
743
|
-
fn
|
|
972
|
+
fn snapshot_refresh_does_not_accumulate_events() {
|
|
744
973
|
let mut engine = RuntimeEngine::new();
|
|
745
974
|
engine.process(RuntimeCommand::CaptureSnapshot).unwrap();
|
|
746
975
|
engine.process(RuntimeCommand::CaptureSnapshot).unwrap();
|
|
747
|
-
assert_eq!(engine.event_log().len(),
|
|
976
|
+
assert_eq!(engine.event_log().len(), 0);
|
|
748
977
|
}
|
|
749
978
|
|
|
750
979
|
#[test]
|
|
@@ -779,6 +1008,223 @@ mod tests {
|
|
|
779
1008
|
let _ = std::fs::remove_dir_all(&dir);
|
|
780
1009
|
}
|
|
781
1010
|
|
|
1011
|
+
#[test]
|
|
1012
|
+
fn mailbox_mutation_selects_scoped_record_after_legacy_id_collision() {
|
|
1013
|
+
let dir =
|
|
1014
|
+
std::env::temp_dir().join(format!("omx-mailbox-collision-{}", std::process::id()));
|
|
1015
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
1016
|
+
let mut engine = RuntimeEngine::new().with_state_dir(&dir);
|
|
1017
|
+
for team_name in [None, Some("beta".to_string())] {
|
|
1018
|
+
let event = RuntimeEvent::MailboxMessageCreated {
|
|
1019
|
+
team_name,
|
|
1020
|
+
message_id: "collision".into(),
|
|
1021
|
+
from_worker: "leader-fixed".into(),
|
|
1022
|
+
to_worker: "worker-1".into(),
|
|
1023
|
+
body: Some("payload".into()),
|
|
1024
|
+
};
|
|
1025
|
+
replay_event(&mut engine, &event).unwrap();
|
|
1026
|
+
engine.event_log.push(event);
|
|
1027
|
+
}
|
|
1028
|
+
engine
|
|
1029
|
+
.process(RuntimeCommand::MarkMailboxNotified {
|
|
1030
|
+
team_name: Some("beta".into()),
|
|
1031
|
+
message_id: "collision".into(),
|
|
1032
|
+
})
|
|
1033
|
+
.unwrap();
|
|
1034
|
+
engine
|
|
1035
|
+
.process(RuntimeCommand::MarkMailboxDelivered {
|
|
1036
|
+
team_name: Some("beta".into()),
|
|
1037
|
+
message_id: "collision".into(),
|
|
1038
|
+
})
|
|
1039
|
+
.unwrap();
|
|
1040
|
+
engine.persist().unwrap();
|
|
1041
|
+
std::fs::remove_file(dir.join("mailbox.json")).unwrap();
|
|
1042
|
+
let engine = RuntimeEngine::load(&dir).unwrap();
|
|
1043
|
+
assert!(engine.mailbox.records()[0].notified_at.is_none());
|
|
1044
|
+
assert!(engine.mailbox.records()[0].delivered_at.is_none());
|
|
1045
|
+
assert!(engine.mailbox.records()[1].notified_at.is_some());
|
|
1046
|
+
assert!(engine.mailbox.records()[1].delivered_at.is_some());
|
|
1047
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
#[test]
|
|
1051
|
+
fn old_mailbox_acknowledgements_replay_only_for_unique_ids() {
|
|
1052
|
+
for scopes in [
|
|
1053
|
+
vec![None],
|
|
1054
|
+
vec![Some("alpha".to_string())],
|
|
1055
|
+
vec![None, None],
|
|
1056
|
+
vec![None, Some("beta".to_string())],
|
|
1057
|
+
] {
|
|
1058
|
+
let mut engine = RuntimeEngine::new();
|
|
1059
|
+
for scope in &scopes {
|
|
1060
|
+
engine.mailbox.replay_create(
|
|
1061
|
+
scope.clone(),
|
|
1062
|
+
"legacy-event",
|
|
1063
|
+
"leader-fixed",
|
|
1064
|
+
"worker-1",
|
|
1065
|
+
"body",
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
for event in ["MailboxNotified", "MailboxDelivered"] {
|
|
1069
|
+
let event: RuntimeEvent = serde_json::from_value(serde_json::json!({
|
|
1070
|
+
"event": event, "message_id": "legacy-event"
|
|
1071
|
+
}))
|
|
1072
|
+
.unwrap();
|
|
1073
|
+
replay_event(&mut engine, &event).unwrap();
|
|
1074
|
+
}
|
|
1075
|
+
for record in engine.mailbox.records() {
|
|
1076
|
+
assert_eq!(record.notified_at.is_some(), scopes.len() == 1);
|
|
1077
|
+
assert_eq!(record.delivered_at.is_some(), scopes.len() == 1);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
#[test]
|
|
1083
|
+
fn mailbox_team_check_guards_the_record_mutated_when_ids_repeat() {
|
|
1084
|
+
let mut engine = RuntimeEngine::new();
|
|
1085
|
+
for team in ["alpha", "beta"] {
|
|
1086
|
+
engine
|
|
1087
|
+
.process(RuntimeCommand::CreateMailboxMessage {
|
|
1088
|
+
team_name: Some(team.into()),
|
|
1089
|
+
message_id: "repeated".into(),
|
|
1090
|
+
from_worker: "leader-fixed".into(),
|
|
1091
|
+
to_worker: "worker-1".into(),
|
|
1092
|
+
body: team.into(),
|
|
1093
|
+
})
|
|
1094
|
+
.unwrap();
|
|
1095
|
+
}
|
|
1096
|
+
assert!(engine
|
|
1097
|
+
.process(RuntimeCommand::MarkMailboxNotified {
|
|
1098
|
+
team_name: Some("beta".into()),
|
|
1099
|
+
message_id: "repeated".into(),
|
|
1100
|
+
})
|
|
1101
|
+
.is_ok());
|
|
1102
|
+
assert!(engine
|
|
1103
|
+
.process(RuntimeCommand::MarkMailboxDelivered {
|
|
1104
|
+
team_name: Some("beta".into()),
|
|
1105
|
+
message_id: "repeated".into(),
|
|
1106
|
+
})
|
|
1107
|
+
.is_ok());
|
|
1108
|
+
assert!(engine.mailbox.records()[0].notified_at.is_none());
|
|
1109
|
+
assert!(engine.mailbox.records()[0].delivered_at.is_none());
|
|
1110
|
+
assert!(engine.mailbox.records()[1].notified_at.is_some());
|
|
1111
|
+
assert!(engine.mailbox.records()[1].delivered_at.is_some());
|
|
1112
|
+
engine
|
|
1113
|
+
.process(RuntimeCommand::MarkMailboxDelivered {
|
|
1114
|
+
team_name: Some("alpha".into()),
|
|
1115
|
+
message_id: "repeated".into(),
|
|
1116
|
+
})
|
|
1117
|
+
.unwrap();
|
|
1118
|
+
assert!(engine.mailbox.records()[0].delivered_at.is_some());
|
|
1119
|
+
assert!(engine.mailbox.records()[1].delivered_at.is_some());
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
#[test]
|
|
1123
|
+
fn mailbox_team_scope_survives_reload_and_replay_and_guards_mutations() {
|
|
1124
|
+
let dir =
|
|
1125
|
+
std::env::temp_dir().join(format!("omx-mailbox-team-scope-{}", std::process::id()));
|
|
1126
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
1127
|
+
let mut engine = RuntimeEngine::new().with_state_dir(&dir);
|
|
1128
|
+
engine
|
|
1129
|
+
.process(RuntimeCommand::CreateMailboxMessage {
|
|
1130
|
+
team_name: Some("alpha".into()),
|
|
1131
|
+
message_id: "scoped".into(),
|
|
1132
|
+
from_worker: "leader-fixed".into(),
|
|
1133
|
+
to_worker: "worker-1".into(),
|
|
1134
|
+
body: "alpha only".into(),
|
|
1135
|
+
})
|
|
1136
|
+
.unwrap();
|
|
1137
|
+
engine.persist().unwrap();
|
|
1138
|
+
let mut loaded = RuntimeEngine::load(&dir).unwrap();
|
|
1139
|
+
assert_eq!(
|
|
1140
|
+
loaded.mailbox.records()[0].team_name.as_deref(),
|
|
1141
|
+
Some("alpha")
|
|
1142
|
+
);
|
|
1143
|
+
for team_name in [None, Some("beta".to_string())] {
|
|
1144
|
+
assert!(loaded
|
|
1145
|
+
.process(RuntimeCommand::MarkMailboxNotified {
|
|
1146
|
+
team_name: team_name.clone(),
|
|
1147
|
+
message_id: "scoped".into(),
|
|
1148
|
+
})
|
|
1149
|
+
.is_err());
|
|
1150
|
+
assert!(loaded
|
|
1151
|
+
.process(RuntimeCommand::MarkMailboxDelivered {
|
|
1152
|
+
team_name,
|
|
1153
|
+
message_id: "scoped".into(),
|
|
1154
|
+
})
|
|
1155
|
+
.is_err());
|
|
1156
|
+
}
|
|
1157
|
+
assert!(loaded.mailbox.records()[0].notified_at.is_none());
|
|
1158
|
+
assert!(loaded.mailbox.records()[0].delivered_at.is_none());
|
|
1159
|
+
std::fs::remove_file(dir.join("mailbox.json")).unwrap();
|
|
1160
|
+
let mut replayed = RuntimeEngine::load(&dir).unwrap();
|
|
1161
|
+
assert_eq!(
|
|
1162
|
+
replayed.mailbox.records()[0].team_name.as_deref(),
|
|
1163
|
+
Some("alpha")
|
|
1164
|
+
);
|
|
1165
|
+
replayed
|
|
1166
|
+
.process(RuntimeCommand::MarkMailboxNotified {
|
|
1167
|
+
team_name: Some("alpha".into()),
|
|
1168
|
+
message_id: "scoped".into(),
|
|
1169
|
+
})
|
|
1170
|
+
.unwrap();
|
|
1171
|
+
replayed
|
|
1172
|
+
.process(RuntimeCommand::MarkMailboxDelivered {
|
|
1173
|
+
team_name: Some("alpha".into()),
|
|
1174
|
+
message_id: "scoped".into(),
|
|
1175
|
+
})
|
|
1176
|
+
.unwrap();
|
|
1177
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
#[test]
|
|
1181
|
+
fn mailbox_event_log_preserves_acknowledgement_over_a_stale_snapshot() {
|
|
1182
|
+
let dir =
|
|
1183
|
+
std::env::temp_dir().join(format!("omx-mailbox-stale-snapshot-{}", std::process::id()));
|
|
1184
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
1185
|
+
let mut engine = RuntimeEngine::new().with_state_dir(&dir);
|
|
1186
|
+
engine
|
|
1187
|
+
.process(RuntimeCommand::CreateMailboxMessage {
|
|
1188
|
+
team_name: Some("alpha".into()),
|
|
1189
|
+
message_id: "stale-snapshot".into(),
|
|
1190
|
+
from_worker: "leader-fixed".into(),
|
|
1191
|
+
to_worker: "worker-1".into(),
|
|
1192
|
+
body: "delivery survives a stale projection".into(),
|
|
1193
|
+
})
|
|
1194
|
+
.unwrap();
|
|
1195
|
+
engine.persist().unwrap();
|
|
1196
|
+
let pending_snapshot = std::fs::read_to_string(dir.join("mailbox.json")).unwrap();
|
|
1197
|
+
|
|
1198
|
+
engine
|
|
1199
|
+
.process(RuntimeCommand::MarkMailboxDelivered {
|
|
1200
|
+
team_name: Some("alpha".into()),
|
|
1201
|
+
message_id: "stale-snapshot".into(),
|
|
1202
|
+
})
|
|
1203
|
+
.unwrap();
|
|
1204
|
+
engine.persist().unwrap();
|
|
1205
|
+
std::fs::write(dir.join("mailbox.json"), pending_snapshot).unwrap();
|
|
1206
|
+
|
|
1207
|
+
let mut loaded = RuntimeEngine::load(&dir).unwrap();
|
|
1208
|
+
assert!(loaded.mailbox.records()[0].delivered_at.is_some());
|
|
1209
|
+
let recovered_timestamp = loaded.mailbox.records()[0].delivered_at.clone();
|
|
1210
|
+
loaded
|
|
1211
|
+
.process(RuntimeCommand::MarkMailboxDelivered {
|
|
1212
|
+
team_name: Some("alpha".into()),
|
|
1213
|
+
message_id: "stale-snapshot".into(),
|
|
1214
|
+
})
|
|
1215
|
+
.unwrap();
|
|
1216
|
+
loaded.persist().unwrap();
|
|
1217
|
+
let persisted: MailboxLog =
|
|
1218
|
+
serde_json::from_str(&std::fs::read_to_string(dir.join("mailbox.json")).unwrap())
|
|
1219
|
+
.unwrap();
|
|
1220
|
+
assert_eq!(persisted.records()[0].delivered_at, recovered_timestamp);
|
|
1221
|
+
assert_eq!(
|
|
1222
|
+
RuntimeEngine::load(&dir).unwrap().mailbox.records()[0].delivered_at,
|
|
1223
|
+
recovered_timestamp
|
|
1224
|
+
);
|
|
1225
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
782
1228
|
#[test]
|
|
783
1229
|
fn persist_and_load_round_trip_preserves_mailbox_body() {
|
|
784
1230
|
let dir = std::env::temp_dir().join("omx-runtime-test-mailbox-body");
|
|
@@ -787,6 +1233,7 @@ mod tests {
|
|
|
787
1233
|
let mut engine = RuntimeEngine::new().with_state_dir(&dir);
|
|
788
1234
|
engine
|
|
789
1235
|
.process(RuntimeCommand::CreateMailboxMessage {
|
|
1236
|
+
team_name: Some("alpha".into()),
|
|
790
1237
|
message_id: "msg-1".into(),
|
|
791
1238
|
from_worker: "worker-1".into(),
|
|
792
1239
|
to_worker: "leader-fixed".into(),
|
|
@@ -817,6 +1264,7 @@ mod tests {
|
|
|
817
1264
|
std::fs::write(
|
|
818
1265
|
dir.join("events.json"),
|
|
819
1266
|
serde_json::to_string_pretty(&vec![RuntimeEvent::MailboxMessageCreated {
|
|
1267
|
+
team_name: None,
|
|
820
1268
|
message_id: "msg-legacy".into(),
|
|
821
1269
|
from_worker: "worker-1".into(),
|
|
822
1270
|
to_worker: "leader-fixed".into(),
|
|
@@ -868,6 +1316,160 @@ mod tests {
|
|
|
868
1316
|
let _ = std::fs::remove_dir_all(&dir);
|
|
869
1317
|
}
|
|
870
1318
|
|
|
1319
|
+
#[test]
|
|
1320
|
+
fn load_backfills_empty_snapshot_body_from_creation_event() {
|
|
1321
|
+
let dir = std::env::temp_dir().join(format!(
|
|
1322
|
+
"omx-runtime-test-mailbox-event-body-{}",
|
|
1323
|
+
std::process::id()
|
|
1324
|
+
));
|
|
1325
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
1326
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
1327
|
+
std::fs::write(
|
|
1328
|
+
dir.join("events.json"),
|
|
1329
|
+
serde_json::to_string_pretty(&vec![RuntimeEvent::MailboxMessageCreated {
|
|
1330
|
+
team_name: Some("alpha".into()),
|
|
1331
|
+
message_id: "msg-scoped".into(),
|
|
1332
|
+
from_worker: "worker-1".into(),
|
|
1333
|
+
to_worker: "leader-fixed".into(),
|
|
1334
|
+
body: Some("event body".into()),
|
|
1335
|
+
}])
|
|
1336
|
+
.unwrap(),
|
|
1337
|
+
)
|
|
1338
|
+
.unwrap();
|
|
1339
|
+
std::fs::write(
|
|
1340
|
+
dir.join("mailbox.json"),
|
|
1341
|
+
serde_json::to_string_pretty(&serde_json::json!({
|
|
1342
|
+
"records": [{
|
|
1343
|
+
"team_name": "alpha",
|
|
1344
|
+
"message_id": "msg-scoped",
|
|
1345
|
+
"from_worker": "worker-1",
|
|
1346
|
+
"to_worker": "leader-fixed",
|
|
1347
|
+
"body": "",
|
|
1348
|
+
"created_at": "2026-04-04T00:00:00.000Z",
|
|
1349
|
+
"notified_at": null,
|
|
1350
|
+
"delivered_at": null
|
|
1351
|
+
}]
|
|
1352
|
+
}))
|
|
1353
|
+
.unwrap(),
|
|
1354
|
+
)
|
|
1355
|
+
.unwrap();
|
|
1356
|
+
std::fs::write(
|
|
1357
|
+
dir.join(DISPATCH_SEEN_LEDGER_FILE),
|
|
1358
|
+
r#"{"schema_version":2,"ledger_epoch":1,"request_ids":[]}"#,
|
|
1359
|
+
)
|
|
1360
|
+
.unwrap();
|
|
1361
|
+
std::fs::write(dir.join("engine.lock"), "").unwrap();
|
|
1362
|
+
|
|
1363
|
+
let loaded = RuntimeEngine::load(&dir).unwrap();
|
|
1364
|
+
loaded.persist().unwrap();
|
|
1365
|
+
let mailbox_json = std::fs::read_to_string(dir.join("mailbox.json")).unwrap();
|
|
1366
|
+
let mailbox: serde_json::Value = serde_json::from_str(&mailbox_json).unwrap();
|
|
1367
|
+
assert_eq!(mailbox["records"][0]["body"], "event body");
|
|
1368
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
#[test]
|
|
1372
|
+
fn load_rejects_an_unreadable_mailbox_snapshot() {
|
|
1373
|
+
let dir = std::env::temp_dir().join(format!(
|
|
1374
|
+
"omx-runtime-test-mailbox-corrupt-{}",
|
|
1375
|
+
std::process::id()
|
|
1376
|
+
));
|
|
1377
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
1378
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
1379
|
+
std::fs::write(
|
|
1380
|
+
dir.join("events.json"),
|
|
1381
|
+
serde_json::to_string_pretty(&vec![RuntimeEvent::MailboxMessageCreated {
|
|
1382
|
+
team_name: None,
|
|
1383
|
+
message_id: "msg-legacy".into(),
|
|
1384
|
+
from_worker: "worker-1".into(),
|
|
1385
|
+
to_worker: "leader-fixed".into(),
|
|
1386
|
+
body: None,
|
|
1387
|
+
}])
|
|
1388
|
+
.unwrap(),
|
|
1389
|
+
)
|
|
1390
|
+
.unwrap();
|
|
1391
|
+
std::fs::write(dir.join("mailbox.json"), "{").unwrap();
|
|
1392
|
+
std::fs::write(
|
|
1393
|
+
dir.join(DISPATCH_SEEN_LEDGER_FILE),
|
|
1394
|
+
r#"{"schema_version":2,"ledger_epoch":1,"request_ids":[]}"#,
|
|
1395
|
+
)
|
|
1396
|
+
.unwrap();
|
|
1397
|
+
std::fs::write(dir.join("engine.lock"), "").unwrap();
|
|
1398
|
+
|
|
1399
|
+
assert!(RuntimeEngine::load(&dir).is_err());
|
|
1400
|
+
assert_eq!(
|
|
1401
|
+
std::fs::read_to_string(dir.join("mailbox.json")).unwrap(),
|
|
1402
|
+
"{"
|
|
1403
|
+
);
|
|
1404
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
#[test]
|
|
1408
|
+
fn mailbox_body_backfill_respects_team_and_rejects_duplicate_keys() {
|
|
1409
|
+
let dir =
|
|
1410
|
+
std::env::temp_dir().join(format!("omx-mailbox-backfill-scope-{}", std::process::id()));
|
|
1411
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
1412
|
+
let mut engine = RuntimeEngine::new().with_state_dir(&dir);
|
|
1413
|
+
for (team_name, message_id, body) in [
|
|
1414
|
+
(None, "collision", "legacy"),
|
|
1415
|
+
(Some("beta"), "collision", "beta secret"),
|
|
1416
|
+
(Some("alpha"), "collision", "alpha secret"),
|
|
1417
|
+
(Some("alpha"), "collision", "ambiguous alpha"),
|
|
1418
|
+
(None, "duplicate-legacy", "first legacy"),
|
|
1419
|
+
(None, "duplicate-legacy", "second legacy"),
|
|
1420
|
+
(Some("gamma"), "scoped-only", "gamma secret"),
|
|
1421
|
+
] {
|
|
1422
|
+
let event = RuntimeEvent::MailboxMessageCreated {
|
|
1423
|
+
team_name: team_name.map(String::from),
|
|
1424
|
+
message_id: message_id.into(),
|
|
1425
|
+
from_worker: "leader-fixed".into(),
|
|
1426
|
+
to_worker: "worker-1".into(),
|
|
1427
|
+
body: Some(body.into()),
|
|
1428
|
+
};
|
|
1429
|
+
replay_event(&mut engine, &event).unwrap();
|
|
1430
|
+
engine.event_log.push(event);
|
|
1431
|
+
}
|
|
1432
|
+
engine.persist().unwrap();
|
|
1433
|
+
for event in &mut engine.event_log {
|
|
1434
|
+
if let RuntimeEvent::MailboxMessageCreated {
|
|
1435
|
+
team_name,
|
|
1436
|
+
message_id,
|
|
1437
|
+
body,
|
|
1438
|
+
..
|
|
1439
|
+
} = event
|
|
1440
|
+
{
|
|
1441
|
+
*body = None;
|
|
1442
|
+
if message_id == "scoped-only" {
|
|
1443
|
+
*team_name = None;
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
engine.persist().unwrap();
|
|
1448
|
+
let loaded = RuntimeEngine::load(&dir).unwrap();
|
|
1449
|
+
loaded.persist().unwrap();
|
|
1450
|
+
let expected = [
|
|
1451
|
+
Some("legacy"),
|
|
1452
|
+
Some("beta secret"),
|
|
1453
|
+
None,
|
|
1454
|
+
None,
|
|
1455
|
+
None,
|
|
1456
|
+
None,
|
|
1457
|
+
None,
|
|
1458
|
+
];
|
|
1459
|
+
assert_eq!(loaded.event_log.len(), expected.len());
|
|
1460
|
+
for (event, expected_body) in loaded.event_log.iter().zip(expected) {
|
|
1461
|
+
assert!(
|
|
1462
|
+
matches!(event, RuntimeEvent::MailboxMessageCreated {body, ..} if body.as_deref() == expected_body)
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
std::fs::remove_file(dir.join("mailbox.json")).unwrap();
|
|
1466
|
+
let replayed = RuntimeEngine::load(&dir).unwrap();
|
|
1467
|
+
for (record, expected_body) in replayed.mailbox.records().iter().zip(expected) {
|
|
1468
|
+
assert_eq!(record.body, expected_body.unwrap_or(""));
|
|
1469
|
+
}
|
|
1470
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
871
1473
|
#[test]
|
|
872
1474
|
fn derive_readiness_stale_authority() {
|
|
873
1475
|
let mut authority = AuthorityLease::new();
|