forjar/api.rs
1//! The supported library API.
2//!
3//! # Why this module exists
4//!
5//! GH-240: `forjar` exports seven top-level modules and roughly 1,844 `pub`
6//! items across 195 `pub` modules, with nothing distinguishing the supported
7//! API from an implementation detail that happens to be reachable. Not one item
8//! was marked `doc(hidden)`. The CLI implementation is public. In that state
9//! forjar cannot make a semver promise it is able to keep: any internal rename
10//! is a breaking change for somebody, and nobody — including us — can say which
11//! ones.
12//!
13//! GH-245 is the constructive half: a real consumer (rmedia, building course
14//! assets against paiml/catalogue) wants forjar's build-staleness logic inline
15//! in a Rust pipeline rather than shelled out to the CLI and re-parsed. They
16//! named the exact surface they need, compiled it against this tree, and asked
17//! only that we say it is supported so they need not pin `=1.13.2` and diff
18//! every bump by hand.
19//!
20//! This module is that promise, and it is deliberately small.
21//!
22//! # The promise
23//!
24//! **Everything re-exported here follows semver.** A breaking change to any of
25//! these items requires a major version bump and a changelog entry.
26//!
27//! **Nothing else does.** The rest of the crate is reachable, documented, and
28//! useful — and it may be renamed, moved or removed in a patch release. If you
29//! depend on an item that is not re-exported here, you are depending on an
30//! internal, and that is fine as long as you know it. Pin an exact version.
31//!
32//! This is a narrow promise on purpose. A promise over 1,844 items would be one
33//! we break by accident within a release, which is worse than no promise at all
34//! because it reads as a guarantee.
35//!
36//! # Deliberately absent
37//!
38//! [`crate::tripwire::hasher::composite_hash`] is **not** here. It is now
39//! injective (GH-235), but the fix changed every digest it produces, so it has
40//! no stability history yet. `hash_inputs` and `hash_outputs_in` funnel through
41//! it, which is why they are documented below as content-identity signals whose
42//! *values* are not stable across major versions — their comparison semantics
43//! are.
44//!
45//! # Example
46//!
47//! Deciding whether a build artifact needs regenerating:
48//!
49//! ```no_run
50//! use forjar::api::{hash_file, probe_resource, staleness_reason, Resource};
51//! use std::path::Path;
52//!
53//! let content_id = hash_file(Path::new("lesson.srt"))?;
54//! println!("srt identity: {content_id}");
55//!
56//! let resource = Resource::default();
57//! if let Some(probe) = probe_resource(&resource) {
58//! // `None` recorded hash deliberately means "re-run once to establish a
59//! // baseline", not "nothing to compare, therefore fresh".
60//! match staleness_reason(&probe, None, None) {
61//! Some(why) => println!("rebuild: {why}"),
62//! None => println!("fresh"),
63//! }
64//! }
65//! # Ok::<(), String>(())
66//! ```
67
68// ── Content identity ────────────────────────────────────────────────────
69//
70// Raw-byte content identity. Deterministic, `blake3:`-prefixed, and sensitive
71// to any byte change. Does not go through `composite_hash`, so it is unaffected
72// by the GH-235 framing change.
73pub use crate::tripwire::hasher::hash_file;
74
75// ── Build staleness ─────────────────────────────────────────────────────
76//
77// The decision "does this artifact need regenerating?", with the branch
78// ordering that was earned from real bugs:
79//
80// * `outputs_missing` is a flag distinct from `output_hash`, and is checked
81// FIRST — "absent" and "present but different" are different facts, and
82// letting the second alias the first is how a missing artifact gets reported
83// as an unchanged one.
84// * A missing recorded baseline means "re-run once to establish one", NOT
85// "fresh". Getting that backwards is the classic cache bug: a corrected
86// source file silently fails to trigger a rebuild.
87pub use crate::core::task::probe::{probe_all, probe_resource, staleness_reason, IoDigest};
88
89// Glob expansion and base-directory resolution over a declared I/O spec.
90//
91// STABILITY NOTE: the comparison semantics are covered by this promise — equal
92// hashes mean unchanged inputs, and that will keep holding. The literal hash
93// VALUES are not, because these funnel through `composite_hash`, whose framing
94// changed in GH-235 and could change again. Do not persist these values across
95// a major version and expect them to compare equal; re-probe instead.
96pub use crate::core::task::{hash_inputs, hash_outputs_in};
97
98// ── Change propagation ──────────────────────────────────────────────────
99//
100// The no-early-cutoff rule: a dirty upstream resource promotes its NoOp
101// dependents in a single topological sweep, so a downstream artifact is never
102// left stale because the traversal stopped at the first unchanged node.
103pub use crate::core::planner::propagation::propagate_changes;
104
105// ── Argument types ──────────────────────────────────────────────────────
106pub use crate::core::types::{PlanAction, PlannedChange, Resource};
107
108#[cfg(test)]
109#[path = "tests_api.rs"]
110mod tests;