Skip to content

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.

Terminal window
cd rust/rust-ai-engineering/learning/brandforge
cargo test

The Day 4 evidence tests must pass before you continue.

ActionWorking-copy fileSource of the complete checkpoint
CREATEsrc/lib.rs../../crates/brandforge/src/lib.rs
CREATEsrc/error.rs../../crates/brandforge/src/error.rs
CREATEsrc/pipeline.rs../../crates/brandforge/src/pipeline.rs
CREATEsrc/render.rs../../crates/brandforge/src/render.rs
REPLACEsrc/main.rs../../crates/brandforge/src/main.rs
REPLACEsrc/model.rs../../crates/brandforge/src/model.rs
REPLACEsrc/profile.rs../../crates/brandforge/src/profile.rs
CREATEtests/cli.rs../../crates/brandforge/tests/cli.rs
Do not editCargo.tomlThe 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.

Run these commands exactly:

Terminal window
mkdir -p tests
cp ../../crates/brandforge/src/lib.rs src/lib.rs
cp ../../crates/brandforge/src/error.rs src/error.rs
cp ../../crates/brandforge/src/pipeline.rs src/pipeline.rs
cp ../../crates/brandforge/src/render.rs src/render.rs
cp ../../crates/brandforge/src/main.rs src/main.rs
cp ../../crates/brandforge/src/model.rs src/model.rs
cp ../../crates/brandforge/src/profile.rs src/profile.rs
cp ../../crates/brandforge/tests/cli.rs tests/cli.rs

This 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 binary

Run the completion gate immediately:

Terminal window
cargo fmt
cargo check
cargo test

If a command fails, use the filename in the first compiler error and confirm that it appears exactly once in the tree above.

Do not start with main.rs. Read the smaller dependencies first:

1. error.rs
2. profile.rs
3. model.rs
4. render.rs
5. pipeline.rs
6. lib.rs
7. main.rs
8. tests/cli.rs

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.

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.json

The audit file is written before the unsupported-claim check. A failed run keeps diagnostic evidence but does not create a publishable preview.

Terminal window
cargo run -- build \
--source fixtures/site.md \
--profile fixtures/business-profile.json \
--output target/brandforge

Then inspect the exact files:

Terminal window
ls target/brandforge

Expected files:

ad-preview.svg
business-profile.json
claim-audit.json
image-prompt.txt
run-report.json

Open 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.

Use a different output directory so the successful preview cannot be mistaken for a new result:

Terminal window
cargo run -- build \
--source fixtures/site.md \
--profile fixtures/business-profile-unsupported.json \
--output target/brandforge-bad

Now verify the behavior:

Terminal window
test -f target/brandforge-bad/claim-audit.json
test ! -f target/brandforge-bad/ad-preview.svg

Both commands should succeed. The first proves diagnostic evidence remains. The second proves the unsupported profile did not produce a publishable artifact.

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.json exists;
  • ad-preview.svg does not exist.

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:

Terminal window
cargo test
cargo run -- build \
--source fixtures/site.md \
--profile fixtures/business-profile.json \
--output target/brandforge-edited

Open 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.

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 test pass.

Finish with the Project 1 Revision →.