Skip to main content

tj_core/
lib.rs

1//! tj-core: append-only event log + derived SQLite state for Task Journal.
2
3#![deny(rust_2018_idioms)]
4
5/// On-disk + on-wire schema version for events and packs. Bump when a
6/// breaking change is made to the JSONL event shape or the pack JSON
7/// envelope. Single source of truth across the workspace — never inline.
8pub const SCHEMA_VERSION: &str = "1.0";
9
10/// Build a fresh task identifier of the form `tj-<10 lowercase base32>`.
11///
12/// 50 bits of entropy from the ULID random suffix → birthday-collision
13/// threshold ≈ 33 million tasks per project. The previous 6-char form
14/// only gave ~4096; old IDs remain valid since storage keys are strings.
15pub fn new_task_id() -> String {
16    format!(
17        "tj-{}",
18        &ulid::Ulid::new().to_string()[10..20].to_lowercase()
19    )
20}
21
22#[cfg(test)]
23mod task_id_tests {
24    use super::new_task_id;
25    use std::collections::HashSet;
26
27    #[test]
28    fn new_task_id_has_expected_shape() {
29        let id = new_task_id();
30        assert!(id.starts_with("tj-"), "{id}");
31        assert_eq!(id.len(), 13, "{id}");
32        assert!(
33            id[3..]
34                .chars()
35                .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit()),
36            "{id}"
37        );
38    }
39
40    #[test]
41    fn new_task_id_unique_over_ten_thousand() {
42        let mut seen = HashSet::with_capacity(10_000);
43        for _ in 0..10_000 {
44            let id = new_task_id();
45            assert!(seen.insert(id.clone()), "collision: {id}");
46        }
47    }
48}
49
50pub mod artifacts;
51pub mod classifier;
52pub mod completeness;
53pub mod db;
54pub mod dream;
55pub mod event;
56pub mod frontmatter;
57pub mod fts;
58pub mod pack;
59pub mod paths;
60pub mod project_hash;
61pub mod recall;
62pub mod reminder;
63pub mod session;
64pub mod session_id;
65pub mod storage;
66
67#[cfg(test)]
68mod schema_version_tests {
69    /// Source-level guard: production sites must reference `SCHEMA_VERSION`
70    /// rather than inlining a literal. If you bump the version, do it in
71    /// the const — never in a struct literal.
72    #[test]
73    fn pack_assembler_does_not_inline_schema_version_literal() {
74        let pack_src = include_str!("pack.rs");
75        assert!(
76            !pack_src.contains("schema_version: \""),
77            "pack.rs has an inline schema_version string literal — use crate::SCHEMA_VERSION"
78        );
79    }
80
81    #[test]
82    fn schema_version_matches_event_default() {
83        let evt = crate::event::Event::new(
84            "tj-x",
85            crate::event::EventType::Open,
86            crate::event::Author::User,
87            crate::event::Source::Cli,
88            "x".into(),
89        );
90        assert_eq!(evt.schema_version, super::SCHEMA_VERSION);
91    }
92}