feat(app): staleness module + post-process search hits (fb-32)

compute_stale: strict > boundary, threshold=0 disables, future
timestamps treated as fresh (clock skew safety). App::search
re-stamps on cache hit so config threshold changes take effect
without flushing the cache.

Also unblocks the workspace build by plugging placeholder
indexed_at/stale into the two AnswerCitation construction
sites in kebab-rag/pipeline.rs (the score-gate refusal path
forwards from SearchHit; the LLM-citation path uses
UNIX_EPOCH/false until Task 7 wires the real values through
pack_context).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
th-kim0823
2026-05-09 01:30:10 +09:00
parent 8faad2f407
commit dfef65f196
6 changed files with 224 additions and 6 deletions

View File

@@ -343,6 +343,10 @@ impl RagPipeline {
// `AnswerCitation.marker` strips the `#`.
marker: Some(format!("[{n}]")),
citation: c.clone(),
// p9-fb-32: placeholder — Task 7 owns wiring real
// indexed_at / stale here from the underlying SearchHit.
indexed_at: time::OffsetDateTime::UNIX_EPOCH,
stale: false,
})
.collect();
@@ -560,6 +564,11 @@ impl RagPipeline {
.map(|h| AnswerCitation {
marker: None,
citation: h.citation.clone(),
// p9-fb-32: forward staleness from the underlying
// `SearchHit` directly — this is the score-gate refusal
// path which doesn't go through `pack_context`.
indexed_at: h.indexed_at,
stale: h.stale,
})
.collect();
let chunks_returned = u32::try_from(hits.len()).unwrap_or(u32::MAX);