diff --git a/docs/implementer-lessons.md b/docs/implementer-lessons.md index b35818d..95b23fd 100644 --- a/docs/implementer-lessons.md +++ b/docs/implementer-lessons.md @@ -65,7 +65,7 @@ How it is used: | T27 | Every wait in a given test has a limit (`recv_timeout`, a deadline loop, `is_finished` before `join`). A test that waits forever on a broken implementation hangs the driver instead of failing, and the implementer cannot tell a hang from slowness. | M4a planning: breaking the reference's ping made `ws_conn` hang on `rx.recv()`; retrying a refused token made the serve test hang on `join()`. Both now fail within 5 s. | | T28 | Before hand-over, break the reference on purpose, one line at a time (drop a check, move a bound by one, skip a save), and run the given tests against each change. A change the tests still pass is a missing test, unless it cannot change behaviour. Rustfmt reflows lines: match on text that survives formatting. | M4a planning: 69 changes over six modules; the tests missed 8. Five were real gaps and got tests (a reconnect that re-sent "interrupted" for a running turn was one), two could not change behaviour, and one was left (an event frame with another id, which `loopd` never sends). | -| T29 | Check a skeleton's comments, not only its code. Print every `todo!()` beside its signature and read each pair before hand-over. Filling the bodies from your own code proves the split can pass; it cannot show that a comment sits on the wrong function. Edit comments by function, never by "the next comment after this name". And tell the implementer to stop and quote a comment that does not fit. | M4a task 14, second session: `post`'s comment was `now_ms`'s text, put there by a script; Ornith saw the contradiction and deliberated over it until the turn ran out. | +| T29 | Check a skeleton's comments, not only its code. Print every `todo!()` beside its signature and read each pair before hand-over. Filling the bodies from your own code proves the split can pass; it cannot show that a comment sits on the wrong function. Edit comments by function, never by "the next comment after this name". And tell the implementer to stop and quote a comment that does not fit. | M4a task 14, second session: `post`'s comment was `now_ms`'s text, put there by a script; Ornith saw the contradiction and deliberated over it until the turn ran out. M4a task 16: a comment naming the function it replaced made the task's own `grep` fail; and a given test copied into the plan before `cargo fmt` came back reformatted. Run the task's checks, and `cargo fmt`, on the end state before hand-over. | ## What worked and should be kept diff --git a/docs/implementer-log.md b/docs/implementer-log.md index 039818a..56d9c7e 100644 --- a/docs/implementer-log.md +++ b/docs/implementer-log.md @@ -570,3 +570,18 @@ T25); task 14 made the model read the whole crate to learn its calls (T25, a cal 14's second session found `now_ms`'s comment on `post`, put there by the design model's script (tip T29). It saw the contradiction and would not guess, which was right, but it deliberated instead of stopping. + +### M4a, task 16 — reviewed 2026-09-24 by the design model (Claude) + +Accepted; finding 2 is closed. `read_checked` follows the task step for step: the path is checked +without following links, the opened file must be the same one (device and inode), the owner and +mode checks run on the open file, and the bytes come from that handle into `Zeroizing`. The gate +passes; both of the plan's mutations (no inode check; the mode taken from the path) turn the +given test red against this code. Against the owner's server, a token in a 0600 file gave the +warning and connected, and the same file at 0644 was refused with its pointer; the token was in no +output. + +The one deviation, reported, was the plan's fault: step 6 of the comment said "from the old +`check_file`", which made step 5's `grep` print a line the task said it would not. The given test +also differed from the plan's copy, in rustfmt's layout only: the design model copied it into the +plan before formatting it. The plan's copy is now the formatted file. diff --git a/docs/plans/M4a/files/crates/gatewayd/tests/secrets_race.rs b/docs/plans/M4a/files/crates/gatewayd/tests/secrets_race.rs index 219864a..3faa5cf 100644 --- a/docs/plans/M4a/files/crates/gatewayd/tests/secrets_race.rs +++ b/docs/plans/M4a/files/crates/gatewayd/tests/secrets_race.rs @@ -24,7 +24,10 @@ fn refused(path: &Path, between: &dyn Fn(), word: &str) { let why = read_checked(path, between).expect_err(word); assert!(why.contains(word), "{word}: {why}"); assert!(why.contains(&path.display().to_string()), "{why}"); - assert!(!why.contains(TOKEN) && !why.contains(OTHER), "never a value: {why}"); + assert!( + !why.contains(TOKEN) && !why.contains(OTHER), + "never a value: {why}" + ); } #[test] @@ -72,8 +75,16 @@ fn the_path_checks_still_come_first() { let other = owner_file(&dir, "other", OTHER); let link = dir.path().join("link"); std::os::unix::fs::symlink(&other, &link).unwrap(); - refused(&link, &|| panic!("never reached for a link"), "is a symbolic link"); - refused(dir.path(), &|| panic!("never reached for a directory"), "is not a regular file"); + refused( + &link, + &|| panic!("never reached for a link"), + "is a symbolic link", + ); + refused( + dir.path(), + &|| panic!("never reached for a directory"), + "is not a regular file", + ); let relative = Path::new("relative/token"); let why = read_checked(relative, &|| panic!("never reached")).unwrap_err(); assert!(why.contains("is not an absolute path"), "{why}");