FC

Designer vs code-managed publish modes and audit history.

Workspace governance

FetchCatch lets a tenant edit flows from two places: the web console and the fcc CLI. To keep the question "who is the source of truth?" answered before it's ever asked, every workspace declares a governance mode that gates the publish event.

Editing drafts is unrestricted in all modes. The governance mode only controls who is allowed to mint a new published version — i.e. who can change what /v1/evaluate/{slug} returns.

The three modes

Mode Designer publish CLI publish Best for
DesignerOnly ✅ allowed ❌ refused (HTTP 400) Solo founders, no-code teams, demos
CodeManaged ❌ refused (HTTP 400) ✅ allowed Teams with PR review + CI/CD
Hybrid ✅ allowed ✅ allowed Default. Power-user opt-in; three-way merge + conflict detection apply

The mode is stored as a column on Workspaces and enforced on every publish attempt by the GovernanceEnforcer service. An Owner/Admin can change it at any time from Settings → Workspaces → Settings without restarting anything.

What "publish" means

Publishing snapshots the current draft graph as an immutable, numbered version and sets Flows.PublishedVersionId to point at it. From that moment on, every call to POST /v1/evaluate/{slug} returns the new version's decisions.

The publish event has provenance: every FlowVersions row carries

  • Source — one of designer-publish, cli-publish, cli-apply, designer-draft, rollback
  • CreatedByEmail — actor email at the time of the write
  • GitCommitSha — commit SHA the CLI advertised via X-FCC-Git-Commit

This shows up in three places:

  • Console flow history panel (sidebar opened from the designer) — two tabs, Published (only events that changed /v1/evaluate output) and All snapshots (every save/apply). Each row renders the actor as a colored initials chip, the source as a semantic badge, and the git commit short-SHA as a link to the workspace's configured repo URL (github.com, gitlab.com, bitbucket.org supported).
  • Console Runs dashboard — every row now shows the targeted version's number, source badge (cli / console / cli·draft / console·draft), and short commit SHA. A Source: cli / console / all filter chip sits next to the status chips.
  • CLI:
    • fcc history flows/<slug>.json — full save/apply log with commit column.
    • fcc history flows/<slug>.json --publishes — only publishes; marks the live version with *.

Querying provenance from your tools

The publish timeline is exposed as a stable JSON endpoint so you can plug it into release notes, Slack notifications, or dashboards without screen-scraping:

curl -H "Authorization: Bearer $FETCHCATCH_TOKEN" \
     https://api.fetchcatch.com/v1/sync/history/flow/<flowId>/publishes

Response (each item):

{
  "version": 17,
  "publishedAtUtc": "2026-05-26T14:31:02Z",
  "source": "cli-publish",
  "createdByEmail": "ci-bot@acme.com",
  "gitCommitSha": "a1b2c3d4e5f6789",
  "isCurrent": true
}

Choosing a mode

DesignerOnly

  • Console publishes only.
  • fcc apply and fcc publish are refused with: "This workspace is set to DesignerOnly. Publishing from the CLI is disabled. …"
  • Use when you want a clean "edit in the console, no scripting required" experience.
  • You can still run fcc pull to back up the workspace into git as read-only.

CodeManaged

  • CLI publishes only. The console shows the draft and lets you edit it (useful for inspection and last-mile tweaks before committing), but the Publish button is disabled with a banner pointing at your repo URL.
  • Use when every change should go through a pull request — the same workflow you have for code.
  • CI runs fcc publish after merge to ship the new version. The commit SHA is stamped onto the resulting FlowVersions row for audit.

Hybrid

  • Both surfaces are allowed.
  • Use when you want the speed of in-console fixes during incidents but also git history for the long-run audit trail.
  • Risk: changes from one surface can shadow the other. Mitigate with:
    • The recently-pulled banner in the designer (already wired in).
    • The "diverged from main" badge + fcc drift exit-code 2 (see below).

Drift detection

GET /v1/sync/drift (also fcc drift from CI) returns a small JSON payload:

{
  "workspace": { "slug": "billing-prod", "governanceMode": "Hybrid", "environmentKind": "Production" },
  "git":       { "branch": "main", "lastCommitSha": "a1b2c3d4…", "lastCommitSyncedAtUtc": "…" },
  "pending":   { "push": 0, "pull": 0, "conflicts": 0 },
  "designerPublishesSinceLastGitCommit": 2,
  "inSync":    false,
  "checkedAtUtc": "…"
}

designerPublishesSinceLastGitCommit > 0 means the console minted a published version that has no matching git commit. That's the canonical "someone hot-fixed in production" signal.

Wire fcc drift into a nightly job:

- name: Check for designer drift
  run: fcc drift
  env:
    FETCHCATCH_TOKEN: ${{ secrets.FETCHCATCH_TOKEN }}
    CI: "true"

Exit codes:

  • 0 — in sync, no drift
  • 2 — drift detected or local/remote pending changes
  • 3 — conflicts (local + remote both changed since last pull)
  • 1 — error

API reference

Verb Route Notes
PATCH /v1/workspaces/{id} { governanceMode?, environmentKind?, gitRepoUrl?, gitBranch?, name? }. Owner/Admin only.
POST /v1/sync/publish Body { flowIds: [] } publishes every flow with a draft; non-empty list publishes only those.
GET /v1/sync/drift CI-friendly drift summary.
GET /v1/sync/history/{type}/{id} All save/apply snapshots for a resource. Returns source, createdBy, gitCommitSha.
GET /v1/sync/history/flow/{flowId}/publishes New. Just the publish events — isCurrent=true marks the live version.
GET /v1/runs?source=cli|designer Runs list filtered by the targeted version's provenance family. Each row carries flowVersion, versionSource, versionGitCommitSha.

Migration safety

Two migrations ship together:

  • 1748300000_GovernanceAndProvenance.sql adds the columns. Every existing workspace defaults to GovernanceMode = 'Hybrid' (the pre-feature behavior). Existing FlowVersions get the catch-all Source = 'designer' so the audit trail still starts now and not retroactively.
  • 1748400000_ProvenanceBackfillAndIndexes.sql runs immediately after and:
    • Splits the catch-all 'designer' into the precise 'designer-publish' / 'designer-draft' labels based on PublishedAtUtc so old rows render sensibly in the new history panel.
    • Joins FlowVersions.CreatedBy → Users.Email to backfill CreatedByEmail where the new column is still null.
    • Adds three indexes that make the new query shapes O(log n) instead of full scans:
      • IX_FlowVersions_Workspace_Source_Commit — powers /v1/sync/drift.
      • IX_FlowVersions_Flow_PublishedAt — powers the new /v1/sync/history/flow/{id}/publishes endpoint.
      • IX_ResourceVersions_Resource_Version — covers fcc history and the designer snapshots tab.

Both migrations are idempotent. No behavior changes until an Owner explicitly switches a workspace to DesignerOnly or CodeManaged.