Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions crates/eqoxide-command/src/slot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,17 @@ mod tests {
/// **Why this one stayed line-based when `eqoxide-http`'s guard did not (round 2, N2 / round
/// 3).** That guard was rewritten to normalise the source because a call site reflowed across
/// lines silently left its coverage — the guard was the ONLY thing standing behind those call
/// sites, so a formatting change could delete a check. That argument does not transfer here,
/// and the reason is stronger than "the risk is lower": this crate's real universal is
/// sites, so a formatting change could delete a check. This crate's real universal is
/// [`every_no_overwrite_request_refuses_a_second_write`], which is BEHAVIOURAL — it issues a
/// second write to a live slot and asserts the first message is the one that drains — and its
/// case table is completeness-checked against [`declared_no_overwrite_requests`], itself read
/// out of the sources. **No reformatting evades a behavioural test.** Wrap a write across five
/// lines and the second write still has to be refused, or that test fails. This static rule is
/// a redundant early-warning on top of it, catching a *newly added* blind write before anyone
/// writes a behaviour test for it; a reflow can cost it a line, not the guarantee.
/// out of the sources. That coverage set is exactly the `-> bool` refusing methods; this
/// crate's blind writes are not among them, so for a blind write this static rule is the only
/// defence *and a reflow does cost it* — measured, not assumed: an un-annotated blind write
/// added to a domain module and wrapped across lines the way rustfmt wraps a long receiver
/// chain passes this crate's tests fully green, because the wrap breaks this rule's `*self.`
/// anchor and a blind writer was never in the behavioural universal's coverage set to begin
/// with. That is an accepted limit, not a backstopped one.
#[test]
fn no_domain_module_blind_writes_a_command_slot() {
let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
Expand Down
68 changes: 64 additions & 4 deletions crates/eqoxide-http/src/guild.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,17 @@ mod no_silent_overwrite_guard {
/// The statement bounds are the previous `;`/`{`/`}` and — because the canonical shape carries
/// its `return busy;` inside a block — the first `;` after the statement's opening brace, when
/// a brace comes before the next `;`.
///
/// **Do not add a "which terminator did this site use" field here (#770 B1, round 2 attempted
/// exactly this and it was reverted).** The span above ends at the first `;` *after* the
/// opening brace, so a malformed site missing its `;` inside `{ return busy` — the very thing
/// conjunct 3 exists to flag — lets the span run on into the FOLLOWING statement and pick up
/// *its* terminator instead. A message that echoes "the terminator this span contains" is then
/// not "the terminator this call site uses"; it can name the wrong one, on exactly the
/// malformed input the guard is supposed to describe accurately. Fixing the span to stop at
/// the offending statement's own closing brace would be a behaviour change, out of place in a
/// docs-only fix; until that happens, [`offender_message`] states both accepted shapes rather
/// than guessing which one applies.
fn refusal_sites(src: &str) -> Vec<Site> {
const NEEDLE: &str = "s.command.request_";
let (text, map) = normalize(src);
Expand Down Expand Up @@ -367,6 +378,28 @@ mod no_silent_overwrite_guard {
out
}

/// The offender message for a non-canonical [`Site`]. Kept as its own function, rather than
/// inlined at the one call site, so a test can pin its exact text without needing the scanner
/// to find a real non-canonical site on this (canonical) tree — see
/// `offender_message_names_both_accepted_shapes` below.
///
/// #770 B1, round 2: this used to echo back "the" terminator found in the site's scanned
/// statement, labelled as the one accepted spelling / guaranteed to compile. Both claims were
/// false — conjunct 2 accepts two non-interchangeable terminators, and the span that finds one
/// of them is not reliably bounded to the flagged call (see the note on [`refusal_sites`]). So
/// this states both accepted shapes plainly instead of asserting which one is correct here: a
/// message that says less but is never wrong beats one that is usually right.
fn offender_message(file: &str, line: usize, name: &str, faults: &[u8]) -> String {
format!(
"{file}:{line}: {name} — not the canonical refusal (violates conjunct(s) {faults:?} of \
refusal_shape_faults). The accepted shape is one of:\n \
if let Some(busy) = s.command.{name}(..).refused(MSG) {{ return busy; }} \
// handler returns (StatusCode, String)\n \
if let Some(busy) = s.command.{name}(..).refused_json(json!({{ .. }})) {{ return busy; }} \
// handler returns Response"
)
}

/// R2-1 + R2-3 + R3-1, pinned. Every row is a literal source snippet; the expectation is the
/// EXACT set of [`refusal_shape_faults`] conjuncts it violates (`None` = no site enumerated at
/// all, `Some([])` = canonical). A return to line-oriented detection flips the wrapped rows to
Expand Down Expand Up @@ -524,6 +557,36 @@ mod no_silent_overwrite_guard {
);
}

/// #770 B1, round 2: [`offender_message`] is built inside `!canonical`, which no site on this
/// (canonical) tree ever reaches — a green run of
/// [`every_refusable_command_request_is_checked_by_its_http_caller`] exercises it zero times, so
/// it was possible to publish a false claim ("guaranteed to compile") about a code path no test
/// ever executed. Calling the function directly, on a fabricated non-canonical case, is what
/// makes it not-dead: this test fails if the message text drifts from either accepted shape, or
/// if it starts claiming to know which one applies to a given site (the thing that was false).
#[test]
fn offender_message_names_both_accepted_shapes() {
let msg = offender_message("combat.rs", 152, "request_attack", &[1, 3]);
assert_eq!(
msg,
"combat.rs:152: request_attack — not the canonical refusal (violates conjunct(s) [1, 3] \
of refusal_shape_faults). The accepted shape is one of:\n \
if let Some(busy) = s.command.request_attack(..).refused(MSG) { return busy; } \
// handler returns (StatusCode, String)\n \
if let Some(busy) = s.command.request_attack(..).refused_json(json!({ .. })) { return busy; } \
// handler returns Response"
);
// Anti-vacuity against the exact failure mode this replaces: the message must not single
// out one shape as "the" accepted one, or name a terminator without the other beside it —
// both `.refused(` and `.refused_json(` must be present, uncoupled from which one (if
// either) actually appears at the flagged site, because the scanner cannot reliably tell
// (see the note on `refusal_sites`).
assert!(msg.contains(".refused(MSG)") && msg.contains(".refused_json(json!({ .. }))"),
"offender_message must always name BOTH accepted shapes, not the one it guesses: {msg:?}");
assert!(!msg.to_lowercase().contains("guarantee"),
"offender_message must not claim a compile guarantee it cannot back: {msg:?}");
}

/// The universal at the HTTP boundary: a handler that calls a refusable `request_*` and IGNORES
/// the returned `bool` has re-created exactly the #347 defect — the command was dropped on the
/// floor and the caller was still told `200`. Every such call site must consume the result.
Expand Down Expand Up @@ -567,10 +630,7 @@ mod no_silent_overwrite_guard {
} else if canonical {
checked.push(format!("{file}:{line}: {name}"));
} else {
offenders.push(format!(
"{file}:{line}: {name} — not the canonical refusal (violates conjunct(s) \
{faults:?} of refusal_shape_faults)"
));
offenders.push(offender_message(&file, line, &name, &faults));
}
}
}
Expand Down
Loading