spec_driven_docs/self_depend/
stamp.rs1use camino::{Utf8Path, Utf8PathBuf};
10use jiff::civil::Date;
11
12use crate::domain::ownership::Sha256;
13use crate::domain::paths::SELF_DEPEND_STAMP_DIR;
14
15#[must_use]
20pub fn path(state_root: &Utf8Path, target: &Utf8Path) -> Utf8PathBuf {
21 let digest = Sha256::of(target.as_str().as_bytes());
22 state_root
23 .join(SELF_DEPEND_STAMP_DIR)
24 .join(format!("{}.stamp", &digest.as_str()[..16]))
25}
26
27#[must_use]
29pub fn read(path: &Utf8Path) -> Option<Date> {
30 std::fs::read_to_string(path).ok()?.trim().parse().ok()
31}
32
33#[must_use]
35pub fn attempted(path: &Utf8Path, today: Date) -> bool {
36 read(path) == Some(today)
37}
38
39pub fn mark(path: &Utf8Path, today: Date) -> std::io::Result<()> {
45 if let Some(parent) = path.parent() {
46 std::fs::create_dir_all(parent)?;
47 }
48 crate::adapters::fs::write_atomic(path, format!("{today}\n").as_bytes())
49}
50
51#[must_use]
53pub fn today() -> Date {
54 jiff::Zoned::now().date()
55}
56
57#[cfg(test)]
58mod tests {
59 #![allow(clippy::unwrap_used, reason = "a test panics as its failure signal")]
60
61 use super::*;
62
63 #[test]
65 fn a_stamp_holds_one_attempt_per_day_per_checkout() {
66 let dir = tempfile::tempdir().unwrap();
67 let root = Utf8Path::from_path(dir.path()).unwrap();
68 let one = path(root, Utf8Path::new("/work/a"));
69 let two = path(root, Utf8Path::new("/work/b"));
70 assert_ne!(one, two);
71 let day = Date::constant(2026, 9, 15);
72 assert!(!attempted(&one, day));
73 mark(&one, day).unwrap();
74 assert!(attempted(&one, day));
75 assert!(!attempted(&one, Date::constant(2026, 9, 16)));
76 assert!(!attempted(&two, day));
77 }
78}