Asset sync
Brand imagery, synced from your repo
Your logo, social card, cover and screenshots live in your repo next to the copy. After a deploy only the files whose sha256 changed are uploaded, and the site renders every size it needs from the original.
Declare assets
In blueforge.product.yml, under the product's assets key. Paths are relative to the repo root. Declare what exists rather than inventing a file to fill a key — missing kinds fall back (below).
assets:
logo: public/brand/logo-512.webp # square mark, transparent preferred
og: public/brand/og-1200x630.webp # social card
cover: public/brand/cover.webp # wide hero for the product page
screenshots: # gallery, in this order
- path: public/screens/orders.webp
caption: Orders from intake to delivery
alt: The orders dashboardAny format the image pipeline decodes is accepted — png, jpeg, webp, gif, avif, svg, tiff — up to 10 MB per file. The site stores webp renditions.
Kinds and renditions
| Kind | Renditions (variant names) | Used for |
|---|---|---|
logo | 512, 128 — square, contained on transparent | Cards, product page, search |
og | 1200x630, 512 (512×320 card thumb) | og:image / twitter:image on the product page |
cover | 2048 (2048×1280), 1200x630, 1024 (1024×640), 512 (512×320) | Product page hero and listing cards; social card when there is no og |
hero | 1024 (1024×640), 512 (512×320) | Used as the cover only when no cover exists |
screenshot | 1024 (1024×640), 512 (512×320), per slot | Product page gallery and /apps thumbnails |
The sync protocol
On a platform deploy, forge-control runs this after the deploy succeeds. It is idempotent:
GET /api/products/:id/assets(public) returns the manifest, withsourceSha256per(kind, slot).- For each declared file, compute its sha256. If the manifest already holds that sha for the same kind and slot, skip it.
- Otherwise
PUT /api/products/:id/assets/:kind(add?slot=Nfor screenshots) with the raw file as the body. - Screenshots are positional — slot 0, 1, 2 … in declared order.
DELETE …/screenshot?slot=Nremoves one.
SHA=$(shasum -a 256 public/brand/logo-512.webp | cut -d' ' -f1)
curl -X PUT "https://blueforge.studio/api/products/acme-store/assets/logo" \
-H "X-Forge-Deploy-Token: $FORGE_DEPLOY_TOKEN" \
-H "Content-Type: image/webp" \
-H "X-Asset-Sha256: $SHA" \
-H "X-Asset-Source: sidecar" \
--data-binary @public/brand/logo-512.webp| Header | Meaning |
|---|---|
X-Forge-Deploy-Token | Required. |
Content-Type | Required, image/*. |
X-Asset-Sha256 | Optional; the upload is refused if it does not match the body. |
X-Asset-Source | sidecar, capture, manual (default) or discovered. |
X-Asset-Source-Url | Optional; where the original came from. |
| Status | Meaning |
|---|---|
201 | Renditions written. |
200 | The stored original already had this sha — nothing to do. |
400 | Empty body, sha mismatch, invalid product id or slot, or a slot on a non-screenshot kind. |
401 | Bad or missing token. |
404 | Unknown kind. |
413 | Larger than 10 MB. |
415 | Not an image, or not decodable. |
Uploading from your own CI
The upload and delete routes accept only the platform deploy token today, not org API keys. If you register from your own CI, leaveassets out of the sidecar you send and let discovery (below) pick up the mark and social card from your live site — or talk to us about uploads.Where the site reads from
For each kind the site takes the first that exists:
- an uploaded asset (from a sidecar sync, a screenshot capture, or a manual upload);
- an asset discovered from the product's live site;
- an image bundled with the BlueForge catalog, if the product has one.
The cover has its own chain: uploaded cover, then hero, then screenshot slot 0. So a hero only ever appears where a cover is missing.
Stored objects are content-addressed: a replaced file gets a new URL (it carries the source sha), which is why image responses can be cached for a year without going stale.
Discovery from your site
When a product's primary app registers and its sidecar declares no assets, the site reads that app's live page <head> for its icon and og:image after answering the registration, and stores what it finds. It only fills gaps: a kind already uploaded from a sidecar or capture is left alone, and a repo that later declares assets takes over on its next platform deploy.
The approval gate
The gate covers imagery, not the YAML (the YAML is validated by the schema). It answers one question per upload: may this rendition be shown?
- A vision model can review each upload and record an advisory verdict.
- A high-confidence approval can become a standing decision automatically. Rejection always needs a person.
- With the gate on, an image nobody has approved is answered
404to the public, as if it did not exist. - A replaced image is versioned, not deleted: the previously approved version keeps showing while the new one waits, and rolling back is restoring that version.
These stages are switched on independently and in that order, so whether your upload shows immediately depends on which stages are live.