Skip to content

Day 5 — Artifacts, Images, and Tests

Today Brandforge becomes a product. A successful run produces a visible preview, a provider-ready image prompt, and a report that identifies its inputs. Then tests drive the real executable through success and failure.

compile_image_prompt accepts a validated BusinessProfile. It never receives the raw model response:

pub fn compile_image_prompt(profile: &BusinessProfile) -> String {
format!(
"Create one calm, minimal advertisement for {}.\n\
Approved factual claims:\n{}\n\
Use only approved claims. Do not invent metrics, awards, prices, \
addresses, phone numbers, or testimonials.\n\
Leave a logo-safe area; composite the real logo later.",
profile.name,
approved_claims(profile),
)
}

The real implementation also includes services, service area, and palette. Prompt construction is now a pure function: same profile, same prompt, easy snapshot test.

The required path writes ad-preview.svg. SVG keeps the first project dependency-light while making layout, copy, palette, XML escaping, and logo-safe composition visible. Open it in a browser.

The optional live extension sends image-prompt.txt to an image-generation API and decodes returned base64 into Vec<u8> before writing ad.png. Keep this adapter outside the renderer. A network model may generate pixels; it may not bypass the claim audit.

For one-shot generation, OpenAI’s current image guide recommends the Image API; conversational editing uses the Responses API image tool. Read the linked guide before implementing the optional adapter because models and response fields are versioned external contracts.

pub struct BuildReport {
pub business: String,
pub source_sha256: String,
pub profile_sha256: String,
pub supported_claims: usize,
pub unsupported_claims: usize,
pub artifacts: Vec<String>,
}

A digest is not proof that the input is trustworthy. It is an identity: if any byte changes, the run report changes. Store acquisition time and origin when you add live scraping.

Unit tests cover validation and rendering. The integration test launches the same binary a learner uses:

let result = Command::new(env!("CARGO_BIN_EXE_brandforge"))
.args(["build", "--source", source, "--profile", profile, "--output", output])
.output()?;
assert!(result.status.success());
assert!(output.join("ad-preview.svg").is_file());
assert!(output.join("run-report.json").is_file());

The failure test asserts three things together:

  • exit status is nonzero;
  • audit evidence exists;
  • publishable preview does not exist.

That is an executable product requirement, not merely a function test.

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

Open target/brandforge/ad-preview.svg, then inspect every other artifact. You should be able to explain which stage created it and which earlier validation it depends on.

Choose one:

  • add --format json for machine-readable CLI errors;
  • add a HumanReview claim status;
  • add a real-logo path and composite it into the SVG;
  • add an optional live image adapter with a strict timeout;
  • add a golden test for image-prompt.txt;
  • record the provider/model/prompt release in run-report.json.

You shipped a real Rust + AI product slice. It turns model-produced data into a visible campaign only after typed validation and evidence grounding. You used ownership, Serde, enums, traits, iterators, files, hashes, bytes, custom errors, pure functions, and end-to-end tests because the product demanded them.

Official provider reference: OpenAI image generation guide.

Finish with the Project 1 Revision →.