Oregami
Repositories/oxedyne/ore

oxedyne/ore/cli/tests/provenance.rs

22.0 KiB, 3 runs

created by r2848102244:35, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Signed provenance, driven through the compiled binary.
2//!
3//! What is being established here is not that Ed25519 works -- that is tested
4//! where it is implemented -- but that this tool actually puts the signature in
5//! the way of anything that would falsify a history. So the central test is the
6//! forgery: a segment rewritten by somebody with write access to the
7//! repository, digests repaired and everything consistent except the one thing
8//! a forger cannot produce. If that is refused by name, the verification is
9//! real. If it is not, everything else in this file is decoration.
10
11mod support;
12
13use support::{
14 forge_payload,
15 forget_key,
16 key_in,
17 ore,
18 read_segment,
19 runs_in,
20 segment_path,
21 set_require_signed,
22 write,
23 write_segment,
24 Scratch,
25};
26
27use oxedyne_fe2o3_core::prelude::*;
28use oxedyne_fe2o3_jdat::prelude::*;
29use oxedyne_fe2o3_ore::envelope::Envelope;
30use oxedyne_fe2o3_ore::op::{
31 Header,
32 Record,
33};
34use oxedyne_fe2o3_ore::segment::Entry;
35
36use std::fs;
37
38
39/// The bytes the tests write first.
40const FIRST: &[u8] = b"hello\nworld\n";
41/// What they write second.
42const SECOND: &[u8] = b"hello\nbrave\nworld\n";
43
44
45/// Returns how many operations a `log` listing put in each state.
46fn counted(text: &str)
47 -> Outcome<(usize, usize, usize)>
48{
49 for line in text.lines() {
50 if !line.contains("signed and verified,") || !line.contains("unsigned") {
51 continue;
52 }
53 if line.contains("provenance") {
54 // The legend, which names the states rather than counting them.
55 continue;
56 }
57 let fields: Vec<&str> = line.split_whitespace().collect();
58 let mut numbers: Vec<usize> = Vec::new();
59 for field in &fields {
60 if let Ok(n) = field.parse::<usize>() {
61 numbers.push(n);
62 }
63 }
64 if numbers.len() == 3 {
65 return Ok((numbers[0], numbers[1], numbers[2]));
66 }
67 }
68 Err(err!("No provenance count is in: {}", text; Test, Missing))
69}
70
71
72/// A fresh repository signs by default, and says so at every point a person
73/// looks.
74#[test]
75fn a_fresh_repository_signs_what_it_writes() -> Outcome<()> {
76 let scratch = res!(Scratch::new("prov_fresh"));
77 let root = res!(scratch.sub("work"));
78
79 let out = res!(ore(&root, &["init"]));
80 let text = fmt!("{}", res!(out.good("init")));
81 assert!(text.contains("signing key Ed25519"),
82 "init mints a key and names the scheme: {}", text);
83 assert!(text.contains("not encrypted"),
84 "and does not pretend the secret key is protected: {}", text);
85 let minted = res!(key_in(&text));
86
87 res!(write(&root, "a.txt", FIRST));
88 res!(res!(ore(&root, &["mark", "first"])).good("mark first"));
89
90 let out = res!(ore(&root, &["who", "a.txt"]));
91 let text = fmt!("{}", res!(out.good("who a.txt")));
92 let runs = runs_in(&text);
93 assert!(!runs.is_empty(), "the file has runs: {}", text);
94 for run in &runs {
95 assert_eq!(run.mark, '+',
96 "every run is signed by a key this repository knows: {}", text);
97 }
98 assert!(text.contains("provenance +"), "and the marks carry a legend: {}", text);
99
100 let out = res!(ore(&root, &["log"]));
101 let text = fmt!("{}", res!(out.good("log")));
102 let (signed, unknown, bare) = res!(counted(&text));
103 assert!(signed >= 3, "every operation is verified: {}", text);
104 assert_eq!((unknown, bare), (0, 0), "and none of them is anything else: {}", text);
105
106 // The key the configuration knows is the key the tool announced.
107 let cfg = res!(fs::read_to_string(root.join(".ore").join("config")));
108 assert!(cfg.contains(&minted), "the configuration holds the minted key: {}", cfg);
109
110 // Every entry on disk is sealed, which is what the marks were reporting.
111 let (_, entries) = res!(read_segment(&segment_path(&root)));
112 for entry in &entries {
113 match entry {
114 Entry::Sealed(_) => (),
115 Entry::Bare(rec) => return Err(err!(
116 "The operation {} was written bare by a repository that holds a key.",
117 rec.head.id(); Test, Invalid)),
118 Entry::Veiled(v) => return Err(err!(
119 "The operation {} was written veiled into a working copy, which veils \
120 what it hands to a carrier and nothing else.", v.head.id(); Test, Invalid)),
121 }
122 }
123 Ok(())
124}
125
126/// The key file is readable by its owner and nobody else.
127#[test]
128#[cfg(unix)]
129fn the_key_file_is_private() -> Outcome<()> {
130 use std::os::unix::fs::PermissionsExt;
131 let scratch = res!(Scratch::new("prov_mode"));
132 let root = res!(scratch.sub("work"));
133 res!(res!(ore(&root, &["init"])).good("init"));
134 let path = root.join(".ore").join("key");
135 let mode = res!(fs::metadata(&path)).permissions().mode() & 0o777;
136 assert_eq!(mode, 0o600, "the key file is private: {:o}", mode);
137 // And it says what it is, in its own words, to whoever opens it.
138 let text = res!(fs::read_to_string(&path));
139 assert!(text.contains("NOT encrypted"),
140 "the key file says it is unencrypted: {}", text);
141 Ok(())
142}
143
144/// A repository that began unsigned gains a key, and the mixed log replays.
145#[test]
146fn a_key_can_be_minted_later_and_the_mixed_log_replays() -> Outcome<()> {
147 let scratch = res!(Scratch::new("prov_later"));
148 let root = res!(scratch.sub("work"));
149
150 let out = res!(ore(&root, &["init", "--unsigned"]));
151 let text = fmt!("{}", res!(out.good("init --unsigned")));
152 assert!(text.contains("no signing key"), "it says what it made: {}", text);
153
154 res!(write(&root, "a.txt", FIRST));
155 res!(res!(ore(&root, &["mark", "before"])).good("mark before"));
156
157 let out = res!(ore(&root, &["key"]));
158 let text = fmt!("{}", res!(out.good("key")));
159 assert!(text.contains("minted a signing key"), "the key verb mints one: {}", text);
160 let first_key = res!(key_in(&text));
161
162 res!(write(&root, "a.txt", SECOND));
163 res!(res!(ore(&root, &["mark", "after"])).good("mark after"));
164
165 // One segment, holding both forms, and it replays.
166 let (_, entries) = res!(read_segment(&segment_path(&root)));
167 let bare = entries.iter().filter(|e| matches!(e, Entry::Bare(_))).count();
168 let sealed = entries.iter().filter(|e| matches!(e, Entry::Sealed(_))).count();
169 assert!(bare > 0 && sealed > 0,
170 "the one segment holds both forms: {} bare, {} sealed", bare, sealed);
171
172 let out = res!(ore(&root, &["log"]));
173 let text = fmt!("{}", res!(out.good("log")));
174 let (signed, unknown, was_bare) = res!(counted(&text));
175 assert_eq!(signed, sealed, "the sealed operations are verified: {}", text);
176 assert_eq!(was_bare, bare, "and the bare ones are reported as bare: {}", text);
177 assert_eq!(unknown, 0, "with nothing signed by a stranger: {}", text);
178
179 let out = res!(ore(&root, &["who", "a.txt"]));
180 let text = fmt!("{}", res!(out.good("who a.txt")));
181 let marks: Vec<char> = runs_in(&text).iter().map(|r| r.mark).collect();
182 assert!(marks.contains(&'-'), "the old bytes are unsigned: {}", text);
183 assert!(marks.contains(&'+'), "and the new ones are verified: {}", text);
184
185 // Rotating keeps the old key, so what it signed still verifies.
186 let out = res!(ore(&root, &["key"]));
187 let text = fmt!("{}", res!(out.good("key rotate")));
188 assert!(text.contains("rotated"), "a second key verb rotates: {}", text);
189 assert!(text.contains(&first_key), "and says what the old key was: {}", text);
190 let cfg = res!(fs::read_to_string(root.join(".ore").join("config")));
191 assert!(cfg.contains(&first_key),
192 "the old public key stays in the configuration: {}", cfg);
193
194 let out = res!(ore(&root, &["log"]));
195 let text = fmt!("{}", res!(out.good("log after rotation")));
196 let (still, unknown, _) = res!(counted(&text));
197 assert_eq!(still, signed,
198 "what the old key signed still verifies after a rotation: {}", text);
199 assert_eq!(unknown, 0, "and nothing became a stranger's: {}", text);
200 Ok(())
201}
202
203/// An unsigned repository says so once for the command, not once per operation.
204#[test]
205fn an_unsigned_repository_says_so_once() -> Outcome<()> {
206 let scratch = res!(Scratch::new("prov_notice"));
207 let root = res!(scratch.sub("work"));
208 res!(res!(ore(&root, &["init", "--unsigned"])).good("init --unsigned"));
209 res!(write(&root, "a.txt", FIRST));
210 res!(write(&root, "b.txt", b"another\n"));
211 res!(write(&root, "c.txt", b"and another\n"));
212
213 let out = res!(ore(&root, &["mark", "first"]));
214 let text = fmt!("{}", res!(out.good("mark first")));
215 let said = text.lines().filter(|l| l.contains("this repository is unsigned")).count();
216 assert_eq!(said, 1,
217 "the notice is once for the command, whatever it wrote: {}", text);
218 assert!(text.contains("`ore key`"), "and it says what to do about it: {}", text);
219 Ok(())
220}
221
222/// A forged operation is refused, by name, and the repository works again once
223/// it is put back.
224///
225/// This is the test that proves the verification is real. The segment is
226/// rewritten as a well formed one -- every digest recomputed, the framing
227/// intact -- so nothing but the signature stands between the forgery and the
228/// history.
229#[test]
230fn a_forged_operation_is_refused_by_name() -> Outcome<()> {
231 let scratch = res!(Scratch::new("prov_forge"));
232 let root = res!(scratch.sub("work"));
233 res!(res!(ore(&root, &["init"])).good("init"));
234 res!(write(&root, "a.txt", FIRST));
235 res!(res!(ore(&root, &["mark", "first"])).good("mark first"));
236
237 let path = segment_path(&root);
238 let backup = res!(fs::read(&path));
239 // The last sealed entry is the mark; the one before it holds the bytes.
240 let victim = res!(forge_payload(&path, 1));
241
242 let out = res!(ore(&root, &["log"]));
243 assert!(!out.ok, "a forged history is refused: {}{}", out.out, out.err);
244 assert!(out.err.contains(&victim),
245 "and the refusal names the operation {}: {}", victim, out.err);
246 assert!(out.err.contains("does not verify"),
247 "and says what is wrong with it: {}", out.err);
248
249 // Every verb, not merely the one that reads the history out.
250 for verb in [
251 vec!["flags"],
252 vec!["who", "a.txt"],
253 vec!["mark", "second"],
254 vec!["undo"],
255 ] {
256 let out = res!(ore(&root, &verb));
257 assert!(!out.ok, "`ore {}` is refused too: {}{}", verb.join(" "), out.out, out.err);
258 assert!(out.err.contains(&victim),
259 "and names the operation: {}", out.err);
260 }
261
262 // Put it back, and the repository is a repository again.
263 res!(fs::write(&path, &backup));
264 let out = res!(ore(&root, &["log"]));
265 let text = fmt!("{}", res!(out.good("log after restoring")));
266 let (signed, unknown, bare) = res!(counted(&text));
267 assert!(signed >= 3 && unknown == 0 && bare == 0,
268 "the restored history verifies throughout: {}", text);
269 Ok(())
270}
271
272/// A byte flipped and left alone is caught by the digest, before the signature
273/// is reached.
274///
275/// The two checks answer different questions and are both worth having: the
276/// digest catches a damaged file, the signature catches a forged one. This test
277/// exists so that a reader knows which is which, and so that the forgery test
278/// above cannot pass by accident of the digest.
279#[test]
280fn a_damaged_segment_is_caught_by_the_digest() -> Outcome<()> {
281 let scratch = res!(Scratch::new("prov_damage"));
282 let root = res!(scratch.sub("work"));
283 res!(res!(ore(&root, &["init"])).good("init"));
284 res!(write(&root, "a.txt", FIRST));
285 res!(res!(ore(&root, &["mark", "first"])).good("mark first"));
286
287 let path = segment_path(&root);
288 let mut bytes = res!(fs::read(&path));
289 let at = bytes.len() / 2;
290 bytes[at] ^= 0x01;
291 res!(fs::write(&path, &bytes));
292
293 let out = res!(ore(&root, &["log"]));
294 assert!(!out.ok, "a damaged segment is refused: {}{}", out.out, out.err);
295 assert!(out.err.contains("could not be decoded") || out.err.contains("digest"),
296 "and the reason is the file rather than the signature: {}", out.err);
297 Ok(())
298}
299
300/// Two signing repositories teach each other their keys, and each verifies the
301/// other's operations afterwards.
302#[test]
303fn a_sync_exchanges_keys_and_both_ends_verify() -> Outcome<()> {
304 let scratch = res!(Scratch::new("prov_sync"));
305 let here = res!(scratch.sub("here"));
306 let there = res!(scratch.sub("there"));
307
308 let out = res!(ore(&here, &["init"]));
309 let here_key = res!(key_in(res!(out.good("init here"))));
310 let out = res!(ore(&there, &["init"]));
311 let there_key = res!(key_in(res!(out.good("init there"))));
312
313 res!(write(&here, "a.txt", FIRST));
314 res!(res!(ore(&here, &["mark", "mine"])).good("mark mine"));
315 res!(write(&there, "b.txt", b"theirs\n"));
316 res!(res!(ore(&there, &["mark", "theirs"])).good("mark theirs"));
317
318 let out = res!(ore(&here, &["sync", &fmt!("{}", there.display())]));
319 let text = fmt!("{}", res!(out.good("sync")));
320 assert!(text.contains("2 known here (1 learned now)"),
321 "each end learned the other's key: {}", text);
322 assert!(text.contains("2 known there (1 learned now)"),
323 "in both directions: {}", text);
324
325 let here_cfg = res!(fs::read_to_string(here.join(".ore").join("config")));
326 let there_cfg = res!(fs::read_to_string(there.join(".ore").join("config")));
327 assert!(here_cfg.contains(&there_key), "and wrote it down here: {}", here_cfg);
328 assert!(there_cfg.contains(&here_key), "and there: {}", there_cfg);
329
330 // What crossed is sealed at the far end, not merely checked and forgotten.
331 let (_, entries) = res!(read_segment(&segment_path(&there)));
332 for entry in &entries {
333 if let Entry::Bare(rec) = entry {
334 return Err(err!(
335 "The operation {} crossed and was written down bare.",
336 rec.head.id(); Test, Invalid));
337 }
338 }
339
340 // And each end verifies the other's work, having been taught the key.
341 for (root, file) in [(&here, "b.txt"), (&there, "a.txt")] {
342 let out = res!(ore(root, &["who", file]));
343 let text = fmt!("{}", res!(out.good("who after sync")));
344 for run in runs_in(&text) {
345 assert_eq!(run.mark, '+',
346 "the other end's bytes verify in {}: {}", root.display(), text);
347 }
348 let out = res!(ore(root, &["log"]));
349 let text = fmt!("{}", res!(out.good("log after sync")));
350 let (_, unknown, bare) = res!(counted(&text));
351 assert_eq!((unknown, bare), (0, 0),
352 "and the whole merged history is accounted for: {}", text);
353 }
354 Ok(())
355}
356
357/// An operation whose key this repository has never seen is shown as such, and
358/// is not refused.
359///
360/// A signature that checks out under an unknown key is a real fact about the
361/// operation: somebody holding that key wrote it, and this repository cannot
362/// say who. Refusing it would be refusing a history because it came the long
363/// way round; accepting it silently would be claiming to know something.
364#[test]
365fn an_unknown_key_is_marked_rather_than_refused() -> Outcome<()> {
366 let scratch = res!(Scratch::new("prov_unknown"));
367 let here = res!(scratch.sub("here"));
368 let there = res!(scratch.sub("there"));
369
370 let out = res!(ore(&here, &["init"]));
371 let here_key = res!(key_in(res!(out.good("init here"))));
372 res!(res!(ore(&there, &["init"])).good("init there"));
373 res!(write(&here, "a.txt", FIRST));
374 res!(res!(ore(&here, &["mark", "mine"])).good("mark mine"));
375 res!(res!(ore(&here, &["sync", &fmt!("{}", there.display())])).good("sync"));
376
377 // The far end forgets where the history came from, which is the state a
378 // repository is in when operations reach it by some route that carried no
379 // keys.
380 res!(forget_key(&there, &here_key));
381
382 let out = res!(ore(&there, &["who", "a.txt"]));
383 let text = fmt!("{}", res!(out.good("who with a forgotten key")));
384 let marks: Vec<char> = runs_in(&text).iter().map(|r| r.mark).collect();
385 assert!(marks.contains(&'?'),
386 "the bytes are signed by a key this end no longer knows: {}", text);
387
388 let out = res!(ore(&there, &["log"]));
389 let text = fmt!("{}", res!(out.good("log with a forgotten key")));
390 let (_, unknown, bare) = res!(counted(&text));
391 assert!(unknown > 0, "and the log counts them: {}", text);
392 assert_eq!(bare, 0, "without calling them unsigned: {}", text);
393 Ok(())
394}
395
396/// A repository that requires signatures refuses a peer's unsigned operations,
397/// and says how many and whose.
398#[test]
399fn require_signed_refuses_a_bare_peer() -> Outcome<()> {
400 let scratch = res!(Scratch::new("prov_policy"));
401 let here = res!(scratch.sub("here"));
402 let there = res!(scratch.sub("there"));
403
404 res!(res!(ore(&here, &["init"])).good("init here"));
405 res!(res!(ore(&there, &["init", "--unsigned"])).good("init there"));
406 res!(write(&here, "a.txt", FIRST));
407 res!(res!(ore(&here, &["mark", "mine"])).good("mark mine"));
408 res!(write(&there, "b.txt", b"unsigned work\n"));
409 res!(res!(ore(&there, &["mark", "theirs"])).good("mark theirs"));
410
411 // Without the policy the two simply merge, unsigned operations and all.
412 let out = res!(ore(&here, &["sync", &fmt!("{}", there.display())]));
413 res!(out.good("sync without the policy"));
414 let out = res!(ore(&here, &["who", "b.txt"]));
415 let text = fmt!("{}", res!(out.good("who b.txt")));
416 assert!(runs_in(&text).iter().any(|r| r.mark == '-'),
417 "the peer's unsigned bytes arrived and are marked unsigned: {}", text);
418
419 // With it, a peer that has unsigned work to hand over is refused, before
420 // anything of theirs is absorbed.
421 let other = res!(scratch.sub("other"));
422 res!(res!(ore(&other, &["init", "--unsigned"])).good("init other"));
423 res!(write(&other, "c.txt", b"more unsigned work\n"));
424 res!(res!(ore(&other, &["mark", "more"])).good("mark more"));
425 res!(set_require_signed(&here, true));
426
427 let out = res!(ore(&here, &["sync", &fmt!("{}", other.display())]));
428 assert!(!out.ok, "the sync is refused: {}{}", out.out, out.err);
429 assert!(out.err.contains("requires signed operations"),
430 "and says why: {}", out.err);
431 assert!(out.err.contains("are unsigned"),
432 "and says how many of what: {}", out.err);
433 assert!(out.err.contains("`ore key`"),
434 "and says what would fix it: {}", out.err);
435 // c.txt never reached this working copy.
436 assert!(!here.join("c.txt").exists(),
437 "nothing of the refused peer's was absorbed");
438 Ok(())
439}
440
441/// A repository that requires signatures and holds no key says so, and says how
442/// to fix it, rather than writing operations it has undertaken not to write.
443#[test]
444fn require_signed_without_a_key_says_how_to_fix_it() -> Outcome<()> {
445 let scratch = res!(Scratch::new("prov_keyless"));
446 let root = res!(scratch.sub("work"));
447 res!(res!(ore(&root, &["init", "--unsigned"])).good("init --unsigned"));
448 res!(write(&root, "a.txt", FIRST));
449 res!(res!(ore(&root, &["mark", "before"])).good("mark before"));
450 res!(set_require_signed(&root, true));
451
452 for verb in [vec!["log"], vec!["flags"], vec!["mark", "second"]] {
453 let out = res!(ore(&root, &verb));
454 assert!(!out.ok, "`ore {}` is refused: {}{}", verb.join(" "), out.out, out.err);
455 assert!(out.err.contains("requires every operation to be signed"),
456 "and says what the repository asked for: {}", out.err);
457 assert!(out.err.contains("`ore key`"),
458 "and how to give it: {}", out.err);
459 }
460
461 // The fix works, and is the one the message named.
462 res!(res!(ore(&root, &["key"])).good("key"));
463 res!(write(&root, "a.txt", SECOND));
464 let out = res!(ore(&root, &["mark", "after"]));
465 res!(out.good("mark after the fix"));
466 Ok(())
467}
468
469/// An operation re-parented into another history does not verify there,
470/// because the seal covers the identifier and the parents as well as the edit.
471///
472/// The transplant is made deliberately plausible: the operation is given
473/// parents the receiving history actually holds, so the causal check that
474/// refuses an incomplete batch has nothing to object to and the signature is
475/// the only thing left standing between the forgery and the log. That is the
476/// property the whole feature rests on -- what is signed is the operation in
477/// its place in the history, not merely its bytes.
478#[test]
479fn a_reparented_operation_does_not_verify() -> Outcome<()> {
480 let scratch = res!(Scratch::new("prov_lift"));
481 let here = res!(scratch.sub("here"));
482 let there = res!(scratch.sub("there"));
483 res!(res!(ore(&here, &["init"])).good("init here"));
484 res!(res!(ore(&there, &["init"])).good("init there"));
485 res!(write(&here, "a.txt", FIRST));
486 res!(res!(ore(&here, &["mark", "mine"])).good("mark mine"));
487 res!(write(&there, "b.txt", b"theirs\n"));
488 res!(res!(ore(&there, &["mark", "theirs"])).good("mark theirs"));
489
490 // One repository's sealed operation, and the identifier of an operation the
491 // other one holds to hang it from.
492 let (_, mine) = res!(read_segment(&segment_path(&here)));
493 let taken = match mine.iter().rev().find(|e| matches!(e, Entry::Sealed(_))) {
494 Some(Entry::Sealed(env)) => env.clone(),
495 _ => return Err(err!("The signing repository wrote nothing sealed.";
496 Test, Missing)),
497 };
498 let (head, mut theirs) = res!(read_segment(&segment_path(&there)));
499 let anchor = match theirs.last() {
500 Some(entry) => res!(entry.id()),
501 None => return Err(err!("The other repository wrote nothing."; Test, Missing)),
502 };
503
504 // The same operation, the same identifier, the same signature, and parents
505 // the receiving history holds. Everything about it is consistent except who
506 // signed what.
507 let lifted = res!(taken.peek_record());
508 let reparented = Record::new(
509 res!(Header::new(lifted.head.id(), vec![anchor])),
510 lifted.op.clone(),
511 );
512 theirs.push(Entry::Sealed(Envelope::new(
513 res!(reparented.to_dat().to_bytes(Vec::new())),
514 taken.signer().to_vec(),
515 taken.signature().to_vec(),
516 )));
517 res!(write_segment(&segment_path(&there), &head, &theirs));
518
519 let out = res!(ore(&there, &["log"]));
520 assert!(!out.ok, "the transplant is refused: {}{}", out.out, out.err);
521 assert!(out.err.contains("does not verify"),
522 "and it is the signature that refuses it, the parents being ones this \
523 history holds: {}", out.err);
524 assert!(out.err.contains(&fmt!("{}", lifted.head.id())),
525 "and the refusal names the operation: {}", out.err);
526 Ok(())
527}
528
529/// A sync with a repository whose history has been forged is refused, and
530/// nothing of it crosses.
531///
532/// The forgery is caught when that repository is opened, which is the first
533/// moment anybody reads it. What matters here is that the refusal reaches the
534/// sync rather than being confined to the working copy a person is standing in:
535/// a peer's history is verified before a single operation of it is absorbed.
536#[test]
537fn a_sync_with_a_forged_peer_is_refused() -> Outcome<()> {
538 let scratch = res!(Scratch::new("prov_peer"));
539 let here = res!(scratch.sub("here"));
540 let there = res!(scratch.sub("there"));
541 res!(res!(ore(&here, &["init"])).good("init here"));
542 res!(res!(ore(&there, &["init"])).good("init there"));
543 res!(write(&here, "a.txt", FIRST));
544 res!(res!(ore(&here, &["mark", "mine"])).good("mark mine"));
545 res!(write(&there, "b.txt", b"theirs\n"));
546 res!(res!(ore(&there, &["mark", "theirs"])).good("mark theirs"));
547
548 let victim = res!(forge_payload(&segment_path(&there), 1));
549
550 let out = res!(ore(&here, &["sync", &fmt!("{}", there.display())]));
551 assert!(!out.ok, "the sync is refused: {}{}", out.out, out.err);
552 assert!(out.err.contains(&victim), "and names the operation: {}", out.err);
553 assert!(!here.join("b.txt").exists(),
554 "and nothing of that history reached this working copy");
555 Ok(())
556}