06 — Publish Pipeline
The automation that takes a creator from "click Publish" to "live on my site" without their further involvement. Wired once during onboarding (doc 04), runs identically every publish thereafter.
Pipeline shape
[BKA app, browser]
│
│ 1. compose + upload media
│ 2. atomic commit to public repo
↓
[GitHub Contents API / Releases API]
│
│ 3. push triggers
↓
[Cloudflare Pages git integration]
│
│ 4. build viewer-template
│ 5. deploy to creator's CF Pages
↓
[Live site at creator's URL]
│
│ 6. (optional) ping indexer for priority pull
↓
[BKA indexer worker — pulls /feed.json + updates discovery index]
The whole pipeline is ~30-90 seconds end-to-end after the push lands. The creator sees a status indicator in BKA throughout.
Step-by-step responsibilities
Step 1-2: Browser-side (BKA app)
Handled by the publish action (see 05_TWO_REPO_MODEL.md). Uses:
- GitHub Contents API for the metadata commit (
PUT /repos/<owner>/<repo>/contents/posts/<slug>.meta.jsonetc.) - GitHub Releases API for binary assets (
POST /repos/<owner>/<repo>/releases+POST /repos/<owner>/<repo>/releases/<id>/assets) - GitHub Git Data API if we batch multiple file commits into one (avoids triggering N CF builds in a row)
The BKA app holds the creator's GitHub OAuth token in localStorage. Calls are direct from browser to GitHub. No server intermediary.
Step 3: Cloudflare Pages git integration
Configured once during onboarding (step 6 in 04_CREATOR_ONBOARDING.md). CF Pages watches the public repo's main branch; every push triggers a build.
We use CF Pages's native git integration, not GitHub Actions, because:
- CF Pages's build is already provisioned + paid for by the creator's CF account
- Free tier covers 500 builds/month per project (plenty for a typical creator)
- Build env, deploy URL, custom domain binding are all already configured
- One fewer system in the loop = one fewer thing that can break
GitHub Actions is reserved for things CF Pages can't do (e.g., uploading release assets — but those are done browser-side anyway).
Step 4: Build viewer-template
CF Pages runs the build configured during onboarding:
- Build command:
cd viewer && npm install && npm run build - Output directory:
viewer/dist/ - Environment: Node 22 (matching the rest of the binary-blender stack)
The viewer-template reads ../feed.json, ../posts/, ../assets/, ../theme.json at build time and generates a static SPA + per-post pages. See 09_VIEWER_TEMPLATE.md.
Build time: typically <60 seconds. Cached node_modules makes repeat builds fast.
Step 5: Deploy
CF Pages copies viewer/dist/ to the creator's Pages project. Active deployment swaps. Edge cache invalidates.
The creator's URL (<handle>-bka-public.pages.dev or their custom domain) serves the new build.
Step 6: (Optional) ping the indexer
BKA's publish action ends with a fetch to:
POST https://bka-indexer.binary-blender.com/api/refresh
Headers:
Content-Type: application/json
Body:
{
"googleIdToken": "<the user's Google JWT>",
"feedUrl": "https://<creator-site>/feed.json"
}
The indexer validates the JWT, checks that the feedUrl matches a registered creator, and adds the feed to a priority crawl queue (re-pulls within a minute instead of waiting for the next hourly cadence).
If the indexer is down or the creator isn't registered, this fails silently. Not blocking; CF deploy is already done.
The viewer-template build (what runs on CF Pages)
The viewer-template is a 4G app (or a simpler static site generator — see open question 6). What it does at build time:
- Read inputs from the public repo root:
feed.json,theme.json,posts/,assets/ - Generate routes:
/— home feed/posts/<slug>— per-post page/feed.xml— RSS mirror of feed.json/sitemap.xml— for search engines/about— frommeta/about.mdif it exists, or a default
- Render with the chosen theme (theme.json picks one of the bundled themes)
- Output static HTML + a tiny JS bundle for client-side enhancements (video player, search, etc.)
The viewer-template version ships as a versioned dependency of the public repo. Creators get viewer-template updates by bumping the dependency (or auto-update on push if they opt in).
Atomic publish
The publish action ideally commits ALL changed files in one push (the post body + meta + feed.json + feed.xml + any committed small media). This means:
- ONE CF Pages build, not N
- ONE indexer ping, not N
- A single point of failure (either the whole publish committed or none of it did)
We use the GitHub Git Data API for this:
- Compute the file changes locally
- Create a tree object with all the new file states
- Create a commit pointing at the new tree
- Move
refs/heads/mainto the new commit
This is more API calls than naive sequential PUTs but it's a single atomic state change on GitHub's side. See agicore-foundry's lib/github-sync.ts::pushSnapshot for the exact pattern.
Custom GitHub Actions (optional, post-v1)
For creators who want post-publish automation beyond the default pipeline (syndication push, email send, analytics ping), we ship a publish.yml template:
on:
push:
branches: [main]
paths: ['posts/**', 'feed.json']
jobs:
syndicate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: # extract the new post(s) from this commit
- uses: bka-actions/syndicate-x@v1
with: { token: ${{ secrets.X_TOKEN }} }
- uses: bka-actions/syndicate-mastodon@v1
with: { token: ${{ secrets.MASTODON_TOKEN }} }
- uses: bka-actions/send-newsletter@v1
with: { token: ${{ secrets.EMAIL_TOKEN }} }
The bka-actions/* org publishes maintained actions for the common targets. Creators opt in by uncommenting the relevant block + adding the secrets to their public repo's secrets.
Deferred until v1.2 (syndication sprint). Not blocking on v1.
Roll-back
If a creator publishes something they regret:
- Soft path — unpublish action removes the post from public + regenerates feeds + CF rebuilds. Done.
- Hard path —
git reverton the public repo's main branch via BKA's "rollback last publish" button. Recovers from accidental publish, schema drift, etc.
The CF Pages git integration also tracks deployment history — creator can roll back via CF dashboard if BKA's UI is unavailable.
Build-time vs runtime split
Build time (CF Pages, ~60s):
- Read feed.json + posts/* + theme.json
- Render static HTML for all routes
- Bundle theme CSS + minimal client JS
Runtime (visitor's browser, ~ms):
- Load the static page
- If video/audio, browser progressive-downloads from GitHub Releases URL
- If interactive bits (search, comments), client JS hydrates
Critically: no AI runs at runtime on a creator's site. All AI use is at authoring time (in BKA, before publish) or at discovery time (in the indexer, before the visitor lands). The site itself is static + deterministic.
This matches the QC-harness thesis from the broader memory: AI at build, determinism at runtime.
Failure modes + their fallbacks
| Failure | Cascade | Creator-visible |
|---|---|---|
| GitHub API rate-limited mid-publish | Atomic commit fails, no partial state | BKA shows "GitHub busy, retry in a minute" |
| CF Pages build fails (bad theme, missing dep) | Site stays on previous deployment; new commit is on GitHub but not deployed | BKA shows build error from CF; creator can fix + push again |
| CF account out of free tier on builds | Same as above (build fails) | BKA shows quota error + upgrade link |
| Indexer ping fails | Indexer eventually picks up the update on next cadence | None (silent) |
| Creator's CF token expired | Pages auto-deploy still works (it uses CF's git integration, not the creator's token); next BKA op that needs the token prompts re-auth | BKA prompts "your CF token expired, sign in to refresh" |
| Creator's GitHub token expired | Publish fails on first API call | BKA prompts "your GitHub OAuth expired, sign in to refresh" |
The creator's site stays live as long as the LAST successful deploy is the current one. Even if our app goes down entirely, their site keeps serving from CF until they make changes.
What we monitor (us, operator-side)
For the BKA app: standard CF Pages analytics on the app itself (bka.binary-blender.com).
For the indexer: per-creator pull success rate, queue depth, BabyAI call latency.
For each creator's deploy: nothing. We don't monitor creator sites. They're not ours.