fix(fb-32): address PR #122 round 1 review

- config: rename env-silent-ignore test + add file-load negative test
  asserting ConfigInvalid for negative TOML stale_threshold_days
- rag: add 5 boundary unit tests pinning compute_stale mirror equivalence
- search: rewrite "Task 6" plan refs in lexical/vector to point at
  actual function names (mark_stale_in_place / RagPipeline::ask)
- cli: dedupe write_config / ingest / backdate_updated_at helpers
  from wire_search_stale + wire_ask_stale into tests/common/mod.rs
- tui: clarify inspect.rs uses same source-of-truth as SearchHit
- rag: PackedCitation.stale invariant doc comment
- HOTFIXES: log conscious decision on wire-schema required-field
  expansion (strict-validator concern)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
th-kim0823
2026-05-09 12:04:28 +09:00
parent 1008bca342
commit efc6b7ebb0
9 changed files with 292 additions and 258 deletions

View File

@@ -54,6 +54,10 @@ struct PackedCitation {
marker: u32,
citation: Citation,
indexed_at: OffsetDateTime,
/// Pre-stamped by `RagPipeline::ask` against the configured
/// `search.stale_threshold_days` before `pack_context` runs;
/// this struct just forwards the value into the eventual
/// `AnswerCitation` and never recomputes.
stale: bool,
}
@@ -942,3 +946,54 @@ mod tests {
assert_eq!(left, 0);
}
}
/// p9-fb-32: boundary tests pinning the local `compute_stale` mirror's
/// semantic equivalence to `kebab_app::staleness::compute_stale`. The
/// two implementations are intentionally duplicated (dep-boundary rule
/// blocks `kebab-rag → kebab-app`); these tests are the contract that
/// guards both copies from drifting. Mirrors the test set in
/// `crates/kebab-app/src/staleness.rs`.
#[cfg(test)]
mod compute_stale_mirror_tests {
use super::compute_stale;
use time::Duration;
use time::OffsetDateTime;
use time::macros::datetime;
fn now() -> OffsetDateTime {
datetime!(2026-05-09 12:00:00 UTC)
}
#[test]
fn threshold_zero_always_fresh() {
let very_old = datetime!(2020-01-01 00:00:00 UTC);
assert!(!compute_stale(very_old, now(), 0));
}
#[test]
fn just_under_threshold_is_fresh() {
// 29 days, 23h, 59m old — under 30d.
let indexed = now() - Duration::days(29) - Duration::hours(23) - Duration::minutes(59);
assert!(!compute_stale(indexed, now(), 30));
}
#[test]
fn exactly_threshold_is_fresh() {
// strict `>` boundary: exactly 30d old is still fresh.
let indexed = now() - Duration::days(30);
assert!(!compute_stale(indexed, now(), 30));
}
#[test]
fn one_minute_past_threshold_is_stale() {
let indexed = now() - Duration::days(30) - Duration::minutes(1);
assert!(compute_stale(indexed, now(), 30));
}
#[test]
fn future_indexed_at_is_fresh() {
// clock skew safety: future timestamps must not be stale.
let future = now() + Duration::hours(1);
assert!(!compute_stale(future, now(), 30));
}
}