---
title: "BrandForge — Flagship | Harshith Nayaka L"
description: "Multi-model campaign pipeline that observes a brand from its own site, pins the product's invariants, then grades and repairs its own generated images — or blocks them."
canonical: "https://harshith-nayaka-l-portfolio.vercel.app/work/brandforge"
last-updated: "2026-09-10T00:51:52Z"
author: "Harshith Nayaka L"
content-type: "text/markdown"
html-version: "https://harshith-nayaka-l-portfolio.vercel.app/work/brandforge"
---
# BrandForge — Flagship

> Give it a brand's website and one product photo. It researches the brand from that site, pins what cannot change about the product, plans its own campaign, generates six images, grades its own work, repairs what it can and refuses what it cannot safely fix.

Case study by Harshith Nayaka L, AI Engineer (Full-Stack), Bengaluru, India.
Canonical page: https://harshith-nayaka-l-portfolio.vercel.app/work/brandforge

- **Type:** Multi-model generation pipeline
- **Focus:** Brand fidelity & product identity
- **Pattern:** Generate → verify → repair → block
- **Status:** Open source, orchestration runs in-process or on n8n

## The problem

Ask a generative model for a brand's campaign imagery and you get two failures that look like success. The brand gets imagined: a model asked about a company recalls a stereotype of its category rather than the company itself, and produces something plausible for a coffee brand instead of something true about this one. And the product drifts: image models produce a convincing member of a category, not your item, so the handle changes shape, the label moves, and the result is unusable for the one job it had.

Both failures pass a casual glance. That is what makes them expensive — nobody catches them until a customer does.

## What I built

The brand is observed, never recalled. A bounded Playwright crawl reads the brand's own site, and every conclusion the model draws is tagged observed — backed by a specific piece of evidence on a specific page — or inferred, meaning the model generalised. The distinction is surfaced in the interface rather than flattened, so a person reviewing the output can see which claims are grounded.

The product is pinned before anything is generated. One pass analyses the supplied photo into a canonical identity plus a set of invariants — the attributes that must survive every shot. Generation then runs through the image edits endpoint with that photo as the anchor rather than text-to-image, because text-to-image has nothing to be faithful to.

Then it grades itself, and the grading is not advisory. A multimodal QA pass compares each generated shot against the original attribute by attribute. The model scores; deterministic code decides. A failed invariant forces a repair no matter how good the aesthetic score, an invariant the model failed to report on counts as a failure rather than a pass, and running out of repair budget produces a BLOCK — shipped visibly as blocked, never quietly dropped or silently passed.

The whole thing is brand-agnostic by construction: point it at a different brand with no code change and the crawl, the kit and the plan all regenerate.

## Pipeline

- **Intake:** Brand URL (SSRF-guarded) → Product photo (Magic-byte checked)
- **Observe:** Bounded crawl (Playwright, page ceiling) → Brand kit (Observed vs inferred)
- **Pin:** Product identity (Invariants extracted once)
- **Plan:** Six shot contracts (The campaign, planned)
- **Generate:** Image edits (Your photo as anchor, 3 at a time)
- **Verify:** Multimodal QA (Attribute by attribute) → Pass · Repair · Block (Code decides, not the model)

## The judgment calls

**The model scores, the code decides**

QA returns numbers; it does not return a verdict. Deterministic code reads those numbers against the product invariants, and any failed invariant forces a repair however high the aesthetic score came back. An invariant the model simply did not mention is treated as a failure, not an absence of evidence — otherwise silence becomes a pass, which is the easiest way for a grader to be useless.

**A refusal is a result**

When repairs run out, the shot is marked BLOCKED and shipped as blocked: it keeps full layout weight in the gallery and carries the reason it failed and what QA saw. A pipeline that hides its failures is reporting a higher success rate than it earned, and the person reviewing it has no way to know.

**Nothing is discarded**

A repaired shot keeps every earlier attempt, the exact prompt that produced it, and each QA verdict on disk. The run also writes a full event log. That is what makes a bad output diagnosable after the fact instead of a mystery.

**Reviewable with no keys and no network**

Every screen can be exercised from local fixtures, and the fixtures refuse to impersonate real output: run ids are prefixed, the manifest carries a fixture flag, and the model fields say so in words. A demo that quietly looks like a real result is a lie waiting to be quoted.

**One implementation, two orchestrators**

The pipeline runs in-process by default and can hand orchestration to n8n by switching one environment variable. Both paths call the same stage functions — there is one implementation, not a code path and a workflow that drift apart. The exported workflow's node types are read from the installed n8n and re-checked by a script.

**Keys never reach the browser**

Both model credentials live only in the API process; the frontend has none and the dev server proxies to it. A preflight command reports which keys the API can actually see, so a missing key surfaces before a run starts spending.

## What it changed

**What it proves:** That a generative pipeline can be held to a standard rather than admired for its best output — brand claims traced to evidence, product attributes verified rather than assumed, and failures surfaced instead of hidden.

**Verification:** One command runs eight groups with no keys and no external network: palette contrast at WCAG AA across 15 pairs, 52 unit tests over the deterministic parts, a production build, n8n workflow build and integrity (every node reachable, every money-spending HTTP node carrying an error path), 16 SSRF vectors rejected, API security and error paths, and a UI sweep across three viewports.

**Honest status:** The pipeline up to brand analysis has been exercised against real sites. Everything past it — brand kit, product identity, planning, generation, QA and repair — is implemented and schema-validated but needs live API keys to run end to end.

## Built with

Node.js, TypeScript, React, Vite, Playwright, Google Gemini (multimodal), OpenAI image edits, n8n, Zod

## Links

- [View on GitHub](https://github.com/HarshithNayakaL/BrandForge)
