newt-core 0.8.0

Newt-Agent core types, errors, and the NeMoCode-style tier router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
/// **The policy (#1866)**, in the shape `progress_sink`'s
/// `protocol_mode_vetoes_rendering_from_every_capability` uses, and for the
/// same reason: `enter_protocol_mode` is documented as one-way, so a test
/// that set the real flag would veto every sibling test in this binary for
/// the rest of the run.
#[test]
fn protocol_mode_vetoes_every_prompt() {
    assert!(
        !super::prompts_permitted(true),
        "fd 1 may be a JSON-RPC wire; no prompt may reach it"
    );
}

/// The anti-vacuous twin: without it `prompts_permitted` could be `false`
/// always and the test above would still pass. Exactly one shape prompts,
/// and this names it.
#[test]
fn and_a_process_outside_protocol_mode_may_prompt() {
    assert!(
        super::prompts_permitted(false),
        "an ordinary terminal session is the ONE shape that prompts — if \
         this fails the veto test above is vacuous"
    );
}

/// The veto as the CALLER meets it: a vetoed window refuses, and refuses in
/// the two different ways the three methods deliberately chose.
///
/// Reaches `PromptWindow::vetoed(..)` because this module is the seal's
/// inside. Nothing here widens it — the constructor is private, and the
/// trybuild proofs under `tests/ui/` still pin that no public constructor
/// exists, the struct cannot be literaled, and `test_stub` is unreachable
/// from outside.
#[test]
fn a_vetoed_window_refuses_to_ask_or_read_and_drops_notices() {
    let window = super::PromptWindow::vetoed(super::PromptOutput::Stdout);

    assert!(
        window.ask("question > ").is_err(),
        "ask must refuse rather than report a question it never wrote"
    );

    let mut buf = String::new();
    let read = window.read_line_into(&mut buf);
    assert!(
        read.is_err(),
        "read must ERROR, not return Ok(0) — EOF is a deliberate empty \
         answer from a human, and forging one is not failing closed: {read:?}"
    );
    assert!(buf.is_empty(), "nothing may land in the caller's buffer");

    assert!(
        window.notice("fyi").is_ok(),
        "a notice is informational and is dropped silently, which is why \
         it differs from ask"
    );
}

/// …and the twin for THAT: an unvetoed window is not refused, so the
/// assertions above are about protocol mode rather than about every
/// window being inert.
#[test]
fn and_an_unvetoed_window_is_not_refused() {
    let window = super::PromptWindow::test_stub();
    assert!(window.ask("").is_ok(), "an ordinary window may ask");
    assert!(window.notice("").is_ok(), "an ordinary window may narrate");
}

/// The explicit-output seam writes both prompt byte families to the file
/// it owns. A regular file is deliberately non-terminal, which also pins
/// the capability probe modal input uses instead of process stdout.
#[serial_test::serial(tty_arbiter)]
#[test]
fn an_explicit_prompt_output_routes_ask_and_notice_to_that_file() {
    let output = tempfile::NamedTempFile::new().expect("prompt output file");
    let c = counter();
    let dynamic: Arc<dyn Ephemeral> = c.clone();
    Terminal::register(9_002, &dynamic);
    let window = Terminal::suspend_for_prompt_to(
        output.reopen().expect("independent prompt output handle"),
        TerminalTaker::PlainCliConfirm,
    );

    assert_eq!(
        c.erased.load(Ordering::SeqCst),
        1,
        "the alternate output must not bypass prompt arbitration"
    );
    assert!(suspended(), "the alternate output quiesces other writers");
    assert!(
        prompt_stdin_active(),
        "the alternate output still owns prompt stdin"
    );
    assert!(
        !window.output_is_terminal(),
        "a regular-file destination must select the non-TTY modal path"
    );
    window.ask("question > ").expect("write the question");
    window.notice("narration").expect("write the notice");
    drop(window);

    assert!(!suspended(), "dropping the window resumes other writers");
    assert!(!prompt_stdin_active(), "dropping the window releases stdin");
    assert_eq!(c.restored.load(Ordering::SeqCst), 1);
    assert_eq!(
        std::fs::read_to_string(output.path()).expect("read routed prompt bytes"),
        "question > narration\n"
    );
}

/// Real-resource grounding for the modal branch predicate: a duplicated
/// PTY slave is a `File` just like a presenter's saved terminal, and the
/// window both recognizes it as interactive and writes to that device.
#[cfg(unix)]
#[serial_test::serial(tty_arbiter)]
#[test]
fn an_explicit_pty_output_is_detected_and_written_as_a_terminal() {
    use std::io::Read as _;
    use std::os::fd::FromRawFd as _;

    let mut master_fd = -1;
    let mut slave_fd = -1;
    // SAFETY: `openpty` initializes both owned descriptors on success. Each
    // is immediately transferred into exactly one `File` below.
    let opened = unsafe {
        libc::openpty(
            &mut master_fd,
            &mut slave_fd,
            std::ptr::null_mut(),
            std::ptr::null_mut(),
            std::ptr::null_mut(),
        )
    };
    assert_eq!(opened, 0, "open the grounding PTY");
    // SAFETY: successful `openpty` returned fresh descriptors, and these
    // `File`s become their sole owners.
    let (mut master, output) =
        unsafe { (File::from_raw_fd(master_fd), File::from_raw_fd(slave_fd)) };
    let window = Terminal::suspend_for_prompt_to(output, TerminalTaker::PlainCliConfirm);

    assert!(
        window.output_is_terminal(),
        "the saved terminal file must select the interactive modal path"
    );
    window.ask("direct prompt").expect("write to the PTY slave");

    let mut painted = [0_u8; "direct prompt".len()];
    master
        .read_exact(&mut painted)
        .expect("read the explicitly routed terminal bytes");
    assert_eq!(&painted, b"direct prompt");
    drop(window);
}

/// Protocol veto semantics belong to the window, not to stdout. Supplying
/// another file must not create a side door that can emit a question.
#[test]
fn a_vetoed_explicit_output_still_emits_zero_bytes() {
    let output = tempfile::NamedTempFile::new().expect("vetoed prompt output file");
    let window = PromptWindow::vetoed(PromptOutput::File(
        output.reopen().expect("independent prompt output handle"),
    ));

    assert!(window.ask("question").is_err());
    assert!(window.notice("narration").is_ok());
    drop(window);

    assert_eq!(
        std::fs::read_to_string(output.path()).expect("read vetoed prompt output"),
        ""
    );
}

/// The original entry point remains process-stdout-backed. Keeping this
/// as a distinct assertion prevents a future refactor from silently
/// making every prompt require an explicit file.
#[test]
fn the_default_prompt_window_keeps_the_process_stdout_route() {
    let window = PromptWindow::test_stub();
    assert!(matches!(&window.output, PromptOutput::Stdout));
    assert_eq!(
        window.output_is_terminal(),
        io::stdout().is_terminal(),
        "the legacy window must keep probing the stream it writes"
    );
}

use super::*;
use std::sync::atomic::AtomicUsize;

struct Counter {
    erased: AtomicUsize,
    restored: AtomicUsize,
}

impl Ephemeral for Counter {
    fn erase(&self) {
        self.erased.fetch_add(1, Ordering::SeqCst);
    }
    fn restore(&self) {
        self.restored.fetch_add(1, Ordering::SeqCst);
    }
}

fn counter() -> Arc<Counter> {
    Arc::new(Counter {
        erased: AtomicUsize::new(0),
        restored: AtomicUsize::new(0),
    })
}

/// §6.4(a): at most ONE ephemeral lease exists at a time. The second
/// acquirer is refused rather than becoming a second writer on the same row.
#[serial_test::serial(tty_arbiter)]
#[test]
fn the_line_admits_exactly_one_writer() {
    let first = Terminal::lease_with_caps(LineCaps::Own, Sink::Stdout)
        .expect("an Own capability yields the line");
    assert!(
        Terminal::lease_with_caps(LineCaps::Own, Sink::Stdout).is_none(),
        "a second writer must NOT get the line while the first holds it"
    );
    drop(first);
    let third = Terminal::lease_with_caps(LineCaps::Own, Sink::Stdout);
    assert!(third.is_some(), "the line is reusable once released");
}

/// The gate is honored: `LineCaps::None` yields no lease, so a caller emits
/// zero bytes rather than painting into a pipe.
#[serial_test::serial(tty_arbiter)]
#[test]
fn no_capability_means_no_lease_and_no_bytes() {
    assert!(Terminal::lease_with_caps(LineCaps::None, Sink::Stdout).is_none());
}

/// §6.5's mechanism, at the unit tier: suspending for a prompt erases every
/// registered ephemeral BEFORE the window exists, and restores on drop.
#[serial_test::serial(tty_arbiter)]
#[test]
fn suspending_erases_every_ephemeral_then_restores() {
    let c = counter();
    let dynamic: Arc<dyn Ephemeral> = c.clone();
    Terminal::register(9_001, &dynamic);

    assert_eq!(c.erased.load(Ordering::SeqCst), 0);
    {
        let w = Terminal::suspend_for_prompt(TerminalTaker::PlainCliConfirm);
        assert_eq!(
            c.erased.load(Ordering::SeqCst),
            1,
            "the ephemeral must be erased before the window is handed out"
        );
        assert!(suspended(), "the ticker must see the suspend flag");
        // Painting is inert while a question is on screen — this is the
        // property that stops a 100ms ticker overwriting the prompt.
        assert_eq!(c.restored.load(Ordering::SeqCst), 0);
        drop(w);
    }
    assert!(!suspended(), "the flag clears when the window drops");
    assert_eq!(c.restored.load(Ordering::SeqCst), 1);
}

/// A lease held while a window is alive paints nothing, so the question
/// stays the most recent thing on the terminal.
#[serial_test::serial(tty_arbiter)]
#[test]
fn a_live_prompt_window_makes_painting_a_no_op() {
    let lease = Terminal::lease_with_caps(LineCaps::Own, Sink::Stdout).expect("lease");
    let w = Terminal::suspend_for_prompt(TerminalTaker::PlainCliConfirm);
    lease.paint(|_w| Ok(()));
    assert!(
        !lease.painted.load(Ordering::SeqCst),
        "a paint during a prompt must be dropped, not deferred onto the question"
    );
    drop(w);
    lease.paint(|_w| Ok(()));
    assert!(
        lease.painted.load(Ordering::SeqCst),
        "painting resumes once the window is gone"
    );
}

/// Erase is idempotent and flag-guarded, so a `Drop` after an explicit
/// teardown cannot clear a row someone else has since taken.
#[serial_test::serial(tty_arbiter)]
#[test]
fn erase_is_idempotent() {
    let lease = Terminal::lease_with_caps(LineCaps::Own, Sink::Stdout).expect("lease");
    lease.paint(|_w| Ok(()));
    assert!(lease.painted.load(Ordering::SeqCst));
    lease.erase();
    assert!(!lease.painted.load(Ordering::SeqCst));
    lease.erase(); // no-op, no panic, no stray escape
    assert!(!lease.painted.load(Ordering::SeqCst));
}

/// Nested prompts keep ownership until the LAST one releases — dropping an
/// inner guard must not hand stdin back to the turn watcher mid-question.
/// (Moved here from `newt-tui/src/permissions.rs` with the mechanism.)
#[serial_test::serial(tty_arbiter)]
#[test]
fn nested_prompts_hold_stdin_until_the_outermost_releases() {
    assert!(
        !prompt_stdin_active(),
        "test starts with no active prompt stdin owner"
    );
    {
        let _outer = StdinToken::acquire();
        assert!(
            try_watch_stdin().is_none(),
            "the watcher cannot read while a prompt owns stdin"
        );
        assert!(prompt_stdin_active());
        {
            let _nested = StdinToken::acquire();
            assert!(prompt_stdin_active(), "nested prompts keep ownership");
        }
        assert!(
            prompt_stdin_active(),
            "dropping one nested guard must not release stdin early"
        );
    }
    assert!(
        !prompt_stdin_active(),
        "prompt stdin ownership must clear when the last guard drops"
    );
}

/// The watcher's protected read BLOCKS a prompt from entering, closing the
/// check-then-read race at permission transitions. (Also moved here.)
#[serial_test::serial(tty_arbiter)]
#[test]
fn watcher_read_token_blocks_prompt_entry_until_the_read_finishes() {
    let watcher = try_watch_stdin().expect("watcher acquires idle stdin");
    let (entered_tx, entered_rx) = std::sync::mpsc::channel();
    let prompt = std::thread::spawn(move || {
        let _prompt = StdinToken::acquire();
        let _ = entered_tx.send(());
    });

    // Wait for whichever happens first: the prompt thread PARKS in `acquire`
    // (correct; the event this proof needs) or it ENTERS (the bug, caught at
    // once). A 50 ms `recv_timeout(..).is_err()` only ever proved the thread
    // had not been scheduled yet.
    while super::STDIN_TOKEN_WAITERS.load(std::sync::atomic::Ordering::SeqCst) == 0 {
        assert!(
            entered_rx.try_recv().is_err(),
            "prompt entered during the watcher's protected read"
        );
        std::thread::yield_now();
    }
    drop(watcher);
    crate::test_guard::recv_guarded(
        &entered_rx,
        "the prompt entering once the watcher releases stdin",
    );
    prompt.join().unwrap();
}

/// The stdin half still interlocks: the watcher's read token blocks a
/// prompt, and a prompt blocks the watcher. (The behavior the TUI's
/// `PromptStdinGuard` / `try_watch_stdin` pair had, now under one arbiter.)
#[serial_test::serial(tty_arbiter)]
#[test]
fn watcher_and_prompt_exclude_each_other_on_stdin() {
    assert!(!prompt_stdin_active());
    let watcher = try_watch_stdin().expect("the watcher acquires idle stdin");
    assert!(
        try_watch_stdin().is_none(),
        "the watcher's read token is exclusive"
    );
    drop(watcher);

    let token = StdinToken::acquire();
    assert!(prompt_stdin_active());
    assert!(
        try_watch_stdin().is_none(),
        "the watcher must not read while a prompt owns stdin"
    );
    drop(token);
    assert!(!prompt_stdin_active());
    assert!(try_watch_stdin().is_some(), "released on drop");
}

/// **#1959: the seal has exactly two doors, and both gates sit BELOW the
/// fork.**
///
/// The seal's value is that its doors are enumerated and each is proven.
/// A second public constructor is fine; a second constructor that skipped
/// the protocol veto or the stdin token would be a hole, and the thing
/// that keeps both honest is that they delegate to ONE private builder
/// with the gates inside it.
///
/// Stated as a source scan rather than a behaviour, because the property
/// is structural: "no future door can be added above the gates". A
/// behavioural test can only cover the doors that exist today.
///
/// Production code only, and cut at the test module — the lesson
/// `config_panel::enter_panel_raw_mode_is_the_only_way_in` records the
/// hard way: this test lives IN the file it scans, so its own needles
/// would otherwise be counted.
#[test]
fn the_seal_has_exactly_two_doors_and_both_gates_sit_below_the_fork() {
    let src = include_str!("arbiter.rs");
    let production = src.split("\n#[cfg(test)]").next().unwrap_or("");
    assert!(
        production.len() > 1000,
        "the production cut read nothing; every count below would be vacuous"
    );

    assert_eq!(
        production.matches("pub fn suspend_for_prompt(").count(),
        1,
        "the stdout door"
    );
    assert_eq!(
        production.matches("pub fn suspend_for_prompt_to(").count(),
        1,
        "the File door (#1959)"
    );
    assert_eq!(
        production
            .matches("Self::suspend_for_prompt_with_output(")
            .count(),
        2,
        "BOTH public doors must delegate to the one private builder — a \
         third door, or a door that built a PromptWindow itself, would \
         bypass the gates below"
    );

    // Call forms, not names: `prompts_permitted` is also DEFINED here and
    // discussed in prose, and counting mentions would move whenever
    // someone edited a comment.
    let veto = "prompts_permitted(super::caps::protocol_mode())";
    let acquire = "StdinToken::acquire()";
    assert_eq!(production.matches(veto).count(), 1, "one veto, one place");
    assert_eq!(
        production.matches(acquire).count(),
        1,
        "one acquire, one place"
    );

    let fork = production
        .find("fn suspend_for_prompt_with_output")
        .expect("the private builder");
    assert!(
        production.find(veto).is_some_and(|at| at > fork),
        "the protocol veto was hoisted ABOVE the fork — it would then \
         cover only the door it sits in, and the other would prompt on a \
         JSON-RPC wire"
    );
    assert!(
        production.find(acquire).is_some_and(|at| at > fork),
        "the stdin token was hoisted ABOVE the fork — one door would then \
         ask without exclusive stdin"
    );
}

/// **#1959: the File door takes the same exclusive stdin token.**
///
/// The scan above proves the acquire is shared; this proves what sharing
/// it buys, through the same `prompt_stdin_active` observable
/// `watcher_and_prompt_exclude_each_other_on_stdin` uses for door one.
///
/// `/dev/null` is a real fd, which the unit tier otherwise avoids —
/// `PromptOutput::File` takes a `std::fs::File` and offers no seam. It is
/// never written to here: the window is constructed and dropped, and the
/// assertions are all about stdin.
#[cfg(unix)]
#[serial_test::serial(tty_arbiter)]
#[test]
fn the_file_door_takes_the_same_exclusive_stdin_token() {
    assert!(!prompt_stdin_active(), "stdin must start idle");
    let sink = std::fs::OpenOptions::new()
        .write(true)
        .open("/dev/null")
        .expect("/dev/null");

    let window = Terminal::suspend_for_prompt_to(sink, TerminalTaker::PlainCliConfirm);
    assert!(
        prompt_stdin_active(),
        "the File door must take the prompt's stdin token, like the stdout door"
    );
    assert!(
        try_watch_stdin().is_none(),
        "and hold it EXCLUSIVELY — the turn watcher must not read underneath it"
    );

    drop(window);
    assert!(!prompt_stdin_active(), "released on drop");
    assert!(try_watch_stdin().is_some(), "the watcher may read again");
}

/// The test stub is inert: it arbitrates nothing, so it cannot leave the
/// process suspended for every later test.
#[serial_test::serial(tty_arbiter)]
#[test]
fn the_test_stub_arbitrates_nothing() {
    let w = PromptWindow::test_stub();
    assert!(!suspended());
    drop(w);
    assert!(!suspended());
}

/// Blocked/Unblocked describe reality, not intent: `Blocked` is emitted
/// only once stdin ownership and suspension have succeeded (observable
/// here as stdin already being prompt-owned when the observer runs),
/// `Unblocked` on drop, and the inert test stub emits neither.
#[serial_test::serial(tty_arbiter)]
#[test]
fn blocked_is_emitted_after_stdin_acquisition_and_unblocked_on_drop() {
    use crate::lifecycle::LifecycleEvent;

    // (event, stdin_owned_at_callback), recorded only for this thread —
    // the lifecycle registry is process-global and sibling tests emit
    // concurrently.
    let log: Arc<Mutex<Vec<(LifecycleEvent, bool)>>> = Arc::new(Mutex::new(Vec::new()));
    let sink = Arc::clone(&log);
    let me = std::thread::current().id();
    let sub = crate::lifecycle::subscribe(move |event| {
        if std::thread::current().id() == me {
            sink.lock()
                .unwrap()
                .push((event.event.clone(), prompt_stdin_active()));
        }
    });

    {
        let _stub = PromptWindow::test_stub();
    }
    assert!(
        log.lock().unwrap().is_empty(),
        "the stub must not emit lifecycle events"
    );

    let w = Terminal::suspend_for_prompt(TerminalTaker::PlainCliConfirm);
    drop(w);
    drop(sub);
    assert_eq!(
        *log.lock().unwrap(),
        vec![
            (LifecycleEvent::Blocked, true),
            (LifecycleEvent::Unblocked, false)
        ],
        "Blocked is emitted exactly once, with stdin already owned \
         (post-acquire, not intent); Unblocked on drop, after stdin is released"
    );
}

// ---- #1979: region ownership --------------------------------------

fn rows(top: u16, height: u16) -> Region {
    Region::Rows { top, height }
}

#[test]
fn regions_intersect_only_when_they_share_a_row() {
    assert!(rows(10, 3).intersects(rows(12, 2)), "overlapping");
    assert!(rows(12, 2).intersects(rows(10, 3)), "and symmetrically");
    assert!(
        !rows(10, 3).intersects(rows(13, 2)),
        "adjacent is not overlapping"
    );
    assert!(!rows(13, 2).intersects(rows(10, 3)), "and symmetrically");
    // The alternate screen is every row, including against itself.
    assert!(Region::WholeScreen.intersects(rows(0, 1)));
    assert!(rows(40, 1).intersects(Region::WholeScreen));
    assert!(Region::WholeScreen.intersects(Region::WholeScreen));
    // A zero-height region owns nothing.
    assert!(!rows(10, 0).intersects(rows(10, 3)));
}

#[serial_test::serial(tty_arbiter)]
#[test]
fn the_mint_refuses_rows_another_writer_holds() {
    let held = Terminal::lease_region(rows(18, 6), OnCollision::Refuse).expect("first take");
    assert_eq!(held.region(), rows(18, 6));
    assert!(
        Terminal::lease_region(rows(20, 4), OnCollision::Refuse).is_none(),
        "two writers were granted the same rows — this is #1977"
    );
    // TWIN: the refusal is about the OVERLAP, not about refusing always.
    let elsewhere =
        Terminal::lease_region(rows(2, 4), OnCollision::Refuse).expect("clear rows are granted");
    assert_eq!(elsewhere.region(), rows(2, 4));
    // And dropping returns them.
    drop(held);
    drop(elsewhere);
    let reclaimed =
        Terminal::lease_region(rows(18, 6), OnCollision::Refuse).expect("released rows return");
    assert_eq!(reclaimed.region(), rows(18, 6));
}

#[serial_test::serial(tty_arbiter)]
#[test]
fn shift_opens_above_the_holder_rather_than_through_it() {
    let prompt = Terminal::lease_region(rows(18, 6), OnCollision::Refuse).expect("prompt");
    let panel = Terminal::lease_region(rows(18, 6), OnCollision::Shift).expect("panel shifts");
    assert_eq!(
        panel.region(),
        rows(12, 6),
        "#1977: the panel must open ABOVE the prompt's rows, not over them"
    );
    assert!(
        !panel.region().intersects(prompt.region()),
        "the shifted region still overlaps"
    );
}

#[serial_test::serial(tty_arbiter)]
#[test]
fn shift_refuses_when_there_is_no_room_above() {
    let _floor = Terminal::lease_region(rows(0, 4), OnCollision::Refuse).expect("floor");
    assert!(
        Terminal::lease_region(rows(2, 6), OnCollision::Shift).is_none(),
        "shifting off the top of the screen must refuse, not wrap"
    );
}

#[serial_test::serial(tty_arbiter)]
#[test]
fn the_whole_screen_collides_with_everything_and_shifts_nowhere() {
    let _rowsy = Terminal::lease_region(rows(10, 2), OnCollision::Refuse).expect("some rows");
    assert!(
        Terminal::lease_region(Region::WholeScreen, OnCollision::Refuse).is_none(),
        "the alternate screen takes every row and cannot share"
    );
    assert!(
        Terminal::lease_region(Region::WholeScreen, OnCollision::Shift).is_none(),
        "whole-screen has nowhere to shift to"
    );
}

#[serial_test::serial(tty_arbiter)]
#[test]
fn suspend_holder_takes_the_rows_the_caller_already_quiesced() {
    let _held = Terminal::lease_region(rows(18, 6), OnCollision::Refuse).expect("holder");
    let taken = Terminal::lease_region(rows(18, 6), OnCollision::SuspendHolder)
        .expect("a caller that quiesced the holder takes the rows");
    assert_eq!(taken.region(), rows(18, 6));
}

/// The cockpit presenter's block moves and is clamped on resize, so the
/// lease has to move WITHOUT a release-and-retake window.
#[serial_test::serial(tty_arbiter)]
#[test]
fn a_lease_relocates_in_place_and_still_respects_other_holders() {
    let _other = Terminal::lease_region(rows(0, 4), OnCollision::Refuse).expect("other");
    let mut moving = Terminal::lease_region(rows(18, 6), OnCollision::Refuse).expect("moving");
    assert!(
        moving.relocate(rows(10, 6), OnCollision::Refuse),
        "a clear move must succeed"
    );
    assert_eq!(moving.region(), rows(10, 6));
    // TWIN: relocation is checked, not merely recorded.
    assert!(
        !moving.relocate(rows(2, 4), OnCollision::Refuse),
        "relocating onto another holder was allowed"
    );
    assert_eq!(
        moving.region(),
        rows(10, 6),
        "a refused move must leave the lease holding what it held"
    );
    // Moving onto its OWN rows is not a self-collision.
    assert!(
        moving.relocate(rows(10, 8), OnCollision::Refuse),
        "a lease may resize in place"
    );
}

/// **Compose proof.** Panel over prompt, close, both restored in order —
/// the nested-modal property (`a_nested_frame_does_not_restore_the_
/// terminal_early`) at region scale. The inner holder returning its rows
/// must not disturb the outer one, which is what makes nesting safe.
#[serial_test::serial(tty_arbiter)]
#[test]
fn an_inner_region_returns_without_disturbing_the_outer_one() {
    let prompt = Terminal::lease_region(rows(18, 6), OnCollision::Refuse).expect("prompt");
    {
        let panel = Terminal::lease_region(rows(18, 6), OnCollision::Shift).expect("panel");
        assert_eq!(panel.region(), rows(12, 6));
        // While both are up, the rows they hold are BOTH unavailable.
        assert!(
            Terminal::lease_region(rows(12, 6), OnCollision::Refuse).is_none(),
            "the panel's rows were re-let while it held them"
        );
        assert!(
            Terminal::lease_region(rows(18, 6), OnCollision::Refuse).is_none(),
            "the prompt's rows were re-let while it held them"
        );
    }
    // The panel closed. ITS rows are free; the prompt's are NOT — the
    // inner drop must not have released the outer holder's claim.
    let reclaimed =
        Terminal::lease_region(rows(12, 6), OnCollision::Refuse).expect("the panel's rows return");
    assert_eq!(reclaimed.region(), rows(12, 6));
    assert!(
        Terminal::lease_region(rows(18, 6), OnCollision::Refuse).is_none(),
        "closing the panel released the PROMPT's rows — restoring more than \
         it took is the nested-modal defect"
    );
    drop(prompt);
    drop(reclaimed);
}

// ---- rows under the transcript: the placement rule, pure --------------

fn place(
    cursor: (u16, u16),
    height: u16,
    held: &[Region],
    policy: OnCollision,
) -> Option<Placement> {
    let held: Vec<(u64, Region)> = held
        .iter()
        .enumerate()
        .map(|(i, r)| (i as u64 + 1, *r))
        .collect();
    place_below_cursor(cursor, 24, height, &held, policy)
}

fn placed(top: u16, height: u16, scroll: u16, return_to: (u16, u16)) -> Option<Placement> {
    Some(Placement {
        top,
        height,
        scroll,
        return_to,
        rows: 24,
    })
}

/// At column zero the frame opens on the cursor's row and returns there, so
/// the next committed line lands directly under the transcript. The partial
/// row case is tested separately below. The real-terminal half is `newt-tui`'s
/// `interaction_view_pty_test` (`after_a_frame_…`).
#[test]
fn a_frame_opens_on_the_cursor_row_and_returns_the_cursor_there() {
    assert_eq!(
        place((0, 5), 6, &[], OnCollision::Shift),
        placed(5, 6, 0, (0, 5))
    );
}

/// #2800: a non-newline-terminated transcript must never be in the erase band.
/// Byte assertions ground the geometry in the actual per-row erase writer.
#[test]
fn a_partial_transcript_row_survives_frame_erasure() {
    for (cursor, expected) in [
        ((11, 5), placed(6, 6, 0, (11, 5))),
        ((11, 20), placed(18, 6, 3, (11, 17))),
        ((11, 23), placed(18, 6, 6, (11, 17))),
    ] {
        let actual = place(cursor, 6, &[], OnCollision::Refuse).expect("placement");
        let expected = expected.unwrap();
        // Windows may execute these commands through the console API instead
        // of writing ANSI to a byte sink. Geometry is checked on every target.
        #[cfg(not(windows))]
        {
            let mut bytes = Vec::new();
            blank_rows(
                &mut super::LineWriter(&mut bytes),
                actual.top,
                actual.height,
            )
            .unwrap();
            let expected_bytes: String = (expected.top..expected.top + expected.height)
                .map(|row| format!("\x1b[{};1H\x1b[2K", row + 1))
                .collect();
            assert_eq!(
                bytes,
                expected_bytes.as_bytes(),
                "cursor {cursor:?}: erase must start below committed text"
            );
            assert!(
                !String::from_utf8(bytes)
                    .unwrap()
                    .contains(&format!("\x1b[{};1H\x1b[2K", actual.return_to.1 + 1)),
                "the return row must not be erased on open or close"
            );
        }
        assert_eq!(actual, expected);
    }
}

/// #2800: reserve the partial transcript row even for oversized frame requests.
#[test]
fn a_partial_row_limits_the_frame_to_the_remaining_screen() {
    assert_eq!(
        place((11, 5), 40, &[], OnCollision::Shift),
        placed(1, 23, 5, (11, 0))
    );
    assert_eq!(
        place_below_cursor((11, 0), 1, 1, &[], OnCollision::Refuse),
        None
    );
}

#[test]
fn a_frame_that_runs_off_the_bottom_scrolls_the_transcript_by_the_deficit() {
    // Cursor on row 20 of 24, six rows wanted: two short.
    assert_eq!(
        place((0, 20), 6, &[], OnCollision::Shift),
        placed(18, 6, 2, (0, 18))
    );
    // Exactly fits: no scroll.
    assert_eq!(
        place((0, 18), 6, &[], OnCollision::Shift),
        placed(18, 6, 0, (0, 18))
    );
}

#[test]
fn a_frame_taller_than_the_screen_is_clamped_to_it() {
    assert_eq!(
        place((0, 5), 40, &[], OnCollision::Shift),
        placed(0, 24, 5, (0, 0))
    );
    assert_eq!(
        place((0, 5), 0, &[], OnCollision::Shift),
        placed(5, 1, 0, (0, 5))
    );
}

/// A holder below the frame — the prompt editor on the bottom rows — is left
/// alone when the frame fits above it, whatever the policy.
#[test]
fn a_holder_below_a_frame_that_fits_is_not_a_collision() {
    let prompt = [rows(21, 3)];
    for policy in [
        OnCollision::Refuse,
        OnCollision::Shift,
        OnCollision::SuspendHolder,
    ] {
        assert_eq!(
            place((0, 5), 6, &prompt, policy),
            placed(5, 6, 0, (0, 5)),
            "{policy:?}"
        );
    }
}

/// Contested rows — the frame would land on a holder, or would have to
/// scroll one — follow the declared policy, and a shift keeps the cursor's
/// return where it was: the shift is about where the FRAME goes.
#[test]
fn contested_rows_follow_the_policy_and_a_shift_still_returns_the_cursor() {
    let prompt = [rows(21, 3)];
    // Lands on the holder.
    assert_eq!(place((0, 18), 6, &prompt, OnCollision::Refuse), None);
    assert_eq!(
        place((0, 18), 6, &prompt, OnCollision::SuspendHolder),
        placed(18, 6, 0, (0, 18))
    );
    assert_eq!(
        place((0, 18), 6, &prompt, OnCollision::Shift),
        placed(15, 6, 0, (0, 18))
    );
    // Would scroll the holder: contested even though no row overlaps.
    let above = [rows(0, 4)];
    assert_eq!(place((0, 20), 6, &above, OnCollision::Refuse), None);
    assert_eq!(
        place((0, 20), 6, &above, OnCollision::Shift),
        placed(18, 6, 0, (0, 20))
    );
    // Nowhere to shift to.
    assert_eq!(place((0, 18), 6, &[rows(0, 24)], OnCollision::Shift), None);
}

#[test]
fn the_whole_screen_refuses_every_placement_but_a_declared_take() {
    let screen = [Region::WholeScreen];
    assert_eq!(place((0, 5), 6, &screen, OnCollision::Refuse), None);
    assert_eq!(place((0, 5), 6, &screen, OnCollision::Shift), None);
    assert_eq!(
        place((0, 5), 6, &screen, OnCollision::SuspendHolder),
        placed(5, 6, 0, (0, 5))
    );
}

/// **#2027 (red-first): #2019's shape, at the arbiter.**
///
/// `/settings` acquired its own prompt window while the cockpit had an
/// editor mounted below it — two live chevrons, a modal with no rows
/// reserved, and a header repainting through the question every 250 ms.
/// Every one of those follows from the same fact: this file decides who
/// owns ROWS and, separately, hands out the TERMINAL, and the second half
/// never asked the first.
///
/// So: a surface holds rows, and somebody who did not declare that it
/// would take them asks for a prompt window. Nothing may have been taken.
/// Asserted on stdin ownership and the suspend flag rather than on bytes,
/// so the failing run emits nothing onto a sibling test's terminal.
#[serial_test::serial(tty_arbiter)]
#[test]
fn a_bare_acquisition_is_refused_while_a_surface_holds_rows() {
    let _held = Terminal::lease_region(rows(18, 6), OnCollision::Refuse).expect("holder");
    let window = Terminal::suspend_for_prompt(TerminalTaker::SlashForm);
    assert!(
        !prompt_stdin_active(),
        "a refused acquisition must not have taken stdin"
    );
    assert!(
        !suspended(),
        "a refused acquisition must not have quiesced the holder"
    );
    assert!(
        window.ask("question > ").is_err(),
        "a refused window must not report a question it never wrote"
    );
    assert!(
        window.read_line_into(&mut String::new()).is_err(),
        "and must ERROR rather than synthesise an EOF nobody typed"
    );
    assert!(
        window.notice("fyi").is_ok(),
        "a notice is informational and is dropped silently, as under the veto"
    );
    drop(window);
    assert!(
        !suspended(),
        "dropping a refused window must not clear a flag it never set"
    );
}

/// **ANTI-VACUOUS TWIN (rows).** The refusal above is about the OVERLAP,
/// not about `SlashForm` never getting a window. With no holder the same
/// taker acquires for real — stdin taken, ephemerals erased, the window
/// able to speak. Without this, `on_held_rows` could return `Refuse`
/// unconditionally and every prompt in the process would be silently dead.
#[serial_test::serial(tty_arbiter)]
#[test]
fn and_the_same_taker_acquires_when_nobody_holds_rows() {
    let counter = counter();
    let dynamic: Arc<dyn Ephemeral> = counter.clone();
    Terminal::register(9_027, &dynamic);

    let window = Terminal::suspend_for_prompt(TerminalTaker::SlashForm);
    assert!(prompt_stdin_active(), "an uncontested taker owns stdin");
    assert!(suspended(), "and quiesces every registered ephemeral");
    assert_eq!(counter.erased.load(Ordering::SeqCst), 1);
    assert!(window.ask("").is_ok(), "and may speak");
    drop(window);
    assert!(!prompt_stdin_active());
}

/// **ANTI-VACUOUS TWIN (declaration).** `SuspendHolder` is the OTHER legal
/// move: a caller that has already quiesced the holder — the cockpit
/// presenter, which holds the rows itself — takes them deliberately. If
/// the region check ignored the declaration, this would refuse too and the
/// cockpit's modal (and every mid-turn permission prompt under it) would
/// stop reaching the operator.
#[serial_test::serial(tty_arbiter)]
#[test]
fn a_declared_row_taker_acquires_while_a_surface_holds_rows() {
    let _held = Terminal::lease_region(rows(18, 6), OnCollision::Refuse).expect("holder");
    let window = Terminal::suspend_for_prompt(TerminalTaker::CockpitModal);
    assert!(
        prompt_stdin_active(),
        "a declared SuspendHolder taker must still get the terminal"
    );
    assert!(suspended(), "and must still quiesce the holder");
    assert!(window.ask("").is_ok(), "and must be able to speak");
    drop(window);
}

/// A refused acquisition is still COUNTED. §6.10's default-deny witness is
/// "no prompt was ever constructed"; hiding the attempts of a caller that
/// is reaching past a surface would hide exactly the caller this guard
/// exists to name — the same reasoning the protocol veto records.
#[serial_test::serial(tty_arbiter)]
#[test]
fn a_refused_acquisition_is_still_counted() {
    let _held = Terminal::lease_region(rows(18, 6), OnCollision::Refuse).expect("holder");
    let before = prompt_windows_constructed();
    drop(Terminal::suspend_for_prompt(TerminalTaker::SlashForm));
    assert_eq!(
        prompt_windows_constructed(),
        before + 1,
        "a refused attempt must remain visible in the counter"
    );
}

/// The registry is complete, distinct, and each row says what it does.
///
/// `name` is an exhaustive match, so a new variant cannot be added without
/// visiting it — and the count below is what makes the author visit
/// [`TerminalTaker::ALL`] in the same edit.
#[test]
fn the_taker_table_is_complete_and_distinct() {
    assert_eq!(
        TerminalTaker::ALL.len(),
        9,
        "a variant was added without reaching ALL, which the registry test walks"
    );
    let mut names: Vec<&str> = TerminalTaker::ALL.iter().map(|t| t.name()).collect();
    names.sort_unstable();
    let listed = names.len();
    names.dedup();
    assert_eq!(names.len(), listed, "two takers share a name: {names:?}");
}

/// **`Shift` is not a terminal policy.** A prompt window is the whole
/// terminal; there is nowhere above it to move to. The type cannot say so
/// (it reuses `OnCollision`, which is right — a second two-variant enum
/// for the same two intents is the sprawl the reuse discipline forbids),
/// so the table says it here.
#[test]
fn no_taker_declares_a_shift() {
    for taker in TerminalTaker::ALL {
        assert_ne!(
            taker.on_held_rows(),
            OnCollision::Shift,
            "`{}` declared Shift, which a whole-terminal take cannot honour",
            taker.name()
        );
    }
}

/// **The ratchet, and it is not a call-site count.**
///
/// #2027 records why counting acquisitions is the wrong guard: it would
/// have passed #2019 unchanged, because the call site already existed.
/// What is worth counting is the ESCAPE HATCH — takers that take rows
/// another surface owns. Two are justified today, each documented on its
/// variant. **This number may only go DOWN.** A third is how the
/// declaration turns back into a formality.
#[test]
fn exactly_two_takers_take_rows_they_do_not_own() {
    let deliberate: Vec<&str> = TerminalTaker::ALL
        .iter()
        .filter(|t| t.on_held_rows() == OnCollision::SuspendHolder)
        .map(|t| t.name())
        .collect();
    assert_eq!(
        deliberate,
        vec!["CockpitModal", "PermissionAuthorization"],
        "the set of takers that take rows they do not own may only shrink"
    );
}

/// A relocation is sometimes a REPORT, not a request (#1980).
///
/// The cockpit presenter recomputes its top from the terminal's new size
/// on a resize. Refusing that move would not un-resize the terminal — it
/// would leave the lease naming rows the block has already left.
#[serial_test::serial(tty_arbiter)]
#[test]
fn a_forced_relocation_lands_where_a_checked_one_is_refused() {
    let _other = Terminal::lease_region(rows(0, 4), OnCollision::Refuse).expect("other");
    let mut block = Terminal::lease_region(rows(18, 6), OnCollision::Refuse).expect("block");

    // Checked: refused, and the lease is unchanged.
    assert!(!block.relocate(rows(2, 4), OnCollision::Refuse));
    assert_eq!(block.region(), rows(18, 6));

    // Forced: lands, because the move already happened on the terminal.
    assert!(
        block.relocate(rows(2, 4), OnCollision::SuspendHolder),
        "a forced relocation must land, or the lease describes rows the \
         writer has already left"
    );
    assert_eq!(block.region(), rows(2, 4));

    // TWIN: `Shift` is rejected outright rather than quietly landing
    // somewhere else, which would make the lease disagree with the
    // caller's own bookkeeping.
    assert!(
        !block.relocate(rows(0, 4), OnCollision::Shift),
        "a shifting relocation would move the lease somewhere the caller \
         did not ask for"
    );
    assert_eq!(block.region(), rows(2, 4));
}

/// #2809: the next watcher poll must yield to an already waiting cursor
/// query, rather than reacquiring the token until its 300ms budget expires.
#[test]
fn quiet_query_waiter_has_priority_over_the_next_watcher_poll() {
    let mut state = super::Inner::default();
    assert!(state.watcher_can_read());
    state.quiet_waiters = 1;
    assert!(
        !state.watcher_can_read(),
        "a waiting query gets the next read turn"
    );
    state.quiet_waiters = 0;
    assert!(state.watcher_can_read());
}

/// #2809: timeout must release priority; taking the token still excludes
/// watcher reads until the quiet query guard is dropped.
#[test]
#[serial_test::serial(tty_arbiter)]
fn quiet_query_timeout_releases_priority() {
    let watcher = try_watch_stdin().unwrap();
    assert!(super::StdinQuiet::take_within(std::time::Duration::ZERO).is_none());
    assert_eq!(super::lock().quiet_waiters, 0);
    drop(watcher);
    let quiet = super::StdinQuiet::take_within(std::time::Duration::ZERO).unwrap();
    assert_eq!(super::lock().quiet_waiters, 0);
    assert!(try_watch_stdin().is_none());
    drop(quiet);
    assert!(try_watch_stdin().is_some());
}

/// #2809: witness a real waiting query registering its priority and acquiring
/// the read token when the current watcher finishes; no terminal I/O needed.
#[test]
#[serial_test::serial(tty_arbiter)]
fn quiet_query_registers_priority_until_the_watcher_finishes() {
    let watcher = try_watch_stdin().unwrap();
    let (acquired_tx, acquired_rx) = std::sync::mpsc::channel();
    let (release_tx, release_rx) = std::sync::mpsc::channel();
    let query = std::thread::spawn(move || {
        let quiet = super::StdinQuiet::take_within(crate::test_guard::HANG_GUARD);
        acquired_tx.send(quiet.is_some()).unwrap();
        crate::test_guard::recv_guarded(&release_rx, "release quiet query");
        drop(quiet);
    });
    let deadline = std::time::Instant::now() + crate::test_guard::HANG_GUARD;
    while super::lock().quiet_waiters == 0 {
        assert!(std::time::Instant::now() < deadline, "query never queued");
        std::thread::yield_now();
    }
    drop(watcher);
    assert!(crate::test_guard::recv_guarded(
        &acquired_rx,
        "query takes the next turn"
    ));
    assert_eq!(super::lock().quiet_waiters, 0);
    assert!(try_watch_stdin().is_none());
    release_tx.send(()).unwrap();
    query.join().unwrap();
    assert!(try_watch_stdin().is_some());
}