Day 5 — Artifacts, Files, and Tests
Today you assemble the final product-shaped crate. This is the only Brandforge day where you copy the
reviewed reference files into your learning project. You already built the input, profile, provider,
and evidence boundaries by hand. Now you can inspect how those pieces are separated for a maintainable
CLI instead of guessing how to split a large main.rs.
Start in the correct directory
Section titled “Start in the correct directory”cd rust/rust-ai-engineering/learning/brandforgecargo testThe Day 4 evidence tests must pass before you continue.
Today’s edit map
Section titled “Today’s edit map”| Action | Working-copy file | Source of the complete checkpoint |
|---|---|---|
| CREATE | src/lib.rs | ../../crates/brandforge/src/lib.rs |
| CREATE | src/error.rs | ../../crates/brandforge/src/error.rs |
| CREATE | src/pipeline.rs | ../../crates/brandforge/src/pipeline.rs |
| CREATE | src/render.rs | ../../crates/brandforge/src/render.rs |
| REPLACE | src/main.rs | ../../crates/brandforge/src/main.rs |
| REPLACE | src/model.rs | ../../crates/brandforge/src/model.rs |
| REPLACE | src/profile.rs | ../../crates/brandforge/src/profile.rs |
| CREATE | tests/cli.rs | ../../crates/brandforge/tests/cli.rs |
| Do not edit | Cargo.toml | The learning manifest already has the required dependencies |
The path ../../crates/brandforge is correct only when your terminal is inside
rust/rust-ai-engineering/learning/brandforge.
Create the final checkpoint files
Section titled “Create the final checkpoint files”Run these commands exactly:
mkdir -p tests
cp ../../crates/brandforge/src/lib.rs src/lib.rscp ../../crates/brandforge/src/error.rs src/error.rscp ../../crates/brandforge/src/pipeline.rs src/pipeline.rscp ../../crates/brandforge/src/render.rs src/render.rscp ../../crates/brandforge/src/main.rs src/main.rscp ../../crates/brandforge/src/model.rs src/model.rscp ../../crates/brandforge/src/profile.rs src/profile.rscp ../../crates/brandforge/tests/cli.rs tests/cli.rsThis is the resulting tree:
learning/brandforge/├── Cargo.toml├── fixtures/│ ├── site.md│ ├── business-profile.json│ └── business-profile-unsupported.json├── src/│ ├── main.rs # CLI arguments, output, and exit status│ ├── lib.rs # declares and exports the library modules│ ├── error.rs # named failure types│ ├── model.rs # ProfileModel plus scripted response adapter│ ├── profile.rs # typed profile, validation, and claim audit│ ├── pipeline.rs # reads, validates, renders, and writes artifacts│ └── render.rs # pure prompt and SVG functions└── tests/ └── cli.rs # launches the real compiled binaryRun the completion gate immediately:
cargo fmtcargo checkcargo testIf a command fails, use the filename in the first compiler error and confirm that it appears exactly once in the tree above.
Read the files in dependency order
Section titled “Read the files in dependency order”Do not start with main.rs. Read the smaller dependencies first:
1. error.rs2. profile.rs3. model.rs4. render.rs5. pipeline.rs6. lib.rs7. main.rs8. tests/cli.rssrc/error.rs owns named failures
Section titled “src/error.rs owns named failures”READ ONLY — src/error.rs:
#[derive(Debug, Error)]pub enum BrandforgeError { // Read, Write, ProfileJson, Serialize, InvalidProfile, UnsupportedClaims}Low-level std::io::Error and serde_json::Error values remain available as sources, while the top
level tells the user which product boundary failed.
src/model.rs keeps Day 3’s provider boundary
Section titled “src/model.rs keeps Day 3’s provider boundary”READ ONLY — src/model.rs:
pub trait ProfileModel: Send + Sync { fn extract(&self, source: &str) -> Result<BusinessProfile, BrandforgeError>;}ScriptedProfileModel parses the saved response. A future live adapter must implement the same trait.
src/render.rs contains pure output functions
Section titled “src/render.rs contains pure output functions”READ ONLY — src/render.rs:
pub fn compile_image_prompt(profile: &BusinessProfile) -> String { // Same accepted profile always produces the same prompt.}
pub fn render_preview_svg(profile: &BusinessProfile) -> String { // Model text is XML-escaped before entering the SVG.}These functions do not read files or call a provider. That makes them quick to test.
src/pipeline.rs owns effect order
Section titled “src/pipeline.rs owns effect order”The important order inside build_campaign is:
read source and saved response → extract typed profile through ProfileModel → validate profile → audit every claim → write claim-audit.json → stop if any claim is unsupported → compile prompt and SVG → write publishable artifacts → write hash-bound run-report.jsonThe audit file is written before the unsupported-claim check. A failed run keeps diagnostic evidence but does not create a publishable preview.
Build the successful campaign pack
Section titled “Build the successful campaign pack”cargo run -- build \ --source fixtures/site.md \ --profile fixtures/business-profile.json \ --output target/brandforgeThen inspect the exact files:
ls target/brandforgeExpected files:
ad-preview.svgbusiness-profile.jsonclaim-audit.jsonimage-prompt.txtrun-report.jsonOpen target/brandforge/ad-preview.svg in a browser or image viewer. Then open
target/brandforge/run-report.json in your editor and find source_sha256 and profile_sha256.
Run the dangerous case
Section titled “Run the dangerous case”Use a different output directory so the successful preview cannot be mistaken for a new result:
cargo run -- build \ --source fixtures/site.md \ --profile fixtures/business-profile-unsupported.json \ --output target/brandforge-badNow verify the behavior:
test -f target/brandforge-bad/claim-audit.jsontest ! -f target/brandforge-bad/ad-preview.svgBoth commands should succeed. The first proves diagnostic evidence remains. The second proves the unsupported profile did not produce a publishable artifact.
Understand the CLI integration test
Section titled “Understand the CLI integration test”Open tests/cli.rs. It does not call build_campaign directly. It launches this executable:
Command::new(env!("CARGO_BIN_EXE_brandforge"))That checks the same Clap parsing, filesystem behavior, stderr, and exit code that a terminal user sees. The failure test asserts three facts together:
- the process status is unsuccessful;
claim-audit.jsonexists;ad-preview.svgdoes not exist.
Make one change yourself
Section titled “Make one change yourself”Open src/render.rs and locate this text inside compile_image_prompt:
Keep text short, legible, balanced, and conversion-focused.Change short to concise, save, and run:
cargo testcargo run -- build \ --source fixtures/site.md \ --profile fixtures/business-profile.json \ --output target/brandforge-editedOpen target/brandforge-edited/image-prompt.txt and confirm your word appears. This final small edit
closes the loop: edit one named file, run tests, build a new artifact, and inspect the result.
Completion proof
Section titled “Completion proof”You have completed Project 1 when you can:
- point to the file responsible for CLI parsing, model output, claim evidence, rendering, and writes;
- build the supported fixture;
- make the unsupported fixture fail without producing a preview;
- explain why valid JSON is not verified advertising content;
- locate both input hashes in the run report;
- make the focused edit above and see it in a newly generated artifact;
- make
cargo testpass.
Finish with the Project 1 Revision →.