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:

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:

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:

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:

  1. Read inputs from the public repo root: feed.json, theme.json, posts/, assets/
  2. Generate routes:
    • / — home feed
    • /posts/<slug> — per-post page
    • /feed.xml — RSS mirror of feed.json
    • /sitemap.xml — for search engines
    • /about — from meta/about.md if it exists, or a default
  3. Render with the chosen theme (theme.json picks one of the bundled themes)
  4. 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:

We use the GitHub Git Data API for this:

  1. Compute the file changes locally
  2. Create a tree object with all the new file states
  3. Create a commit pointing at the new tree
  4. Move refs/heads/main to 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:

  1. Soft path — unpublish action removes the post from public + regenerates feeds + CF rebuilds. Done.
  2. Hard pathgit revert on 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):

Runtime (visitor's browser, ~ms):

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.