Draft -> publish workflow
Every config write produces a draft. The draft becomes a published
version only when you explicitly click Publish (or POST /publish).
Workers read the published version from a DynamoDB replica; they never read
your draft.
Note (2026-06): the three dashboard screenshots referenced below are pending capture in a follow-up issue. The image paths are wired correctly; the binaries will land later. The textual workflow below is the authoritative description in the meantime.
The model
+----------------+ +-----------------------+ +-----------------------+
| | PATCH | | POST | |
| Dashboard +--------> connector_config_ +--------> connector_config_ |
| AI agent |/draft | drafts |/publish| versions |
| MCP server | | (1 row, mutable) | | (append-only history,|
| Setup scripts | | | | one is_active=true) |
| | +-----------------------+ +----------+------------+
+----------------+ |
v
+-----------+-----------+
| DynamoDB replica |
| (worker hot read) |
| + event_flow_ |
| manifests |
| + SQS notify |
+-----------------------+Invariants (see ADR-0014):
- Single write path. Every writer (UI, agent, MCP, setup script) goes through
PATCH /api/connectors/{id}/draftor one of the granular helpers below. No endpoint mutates state by side effect. - Single read source for workers. Workers consume
DynamoConfigStore, written only at publish time by the outbox dispatcher. They never touch your draft. - Atomic publish. The transition `draft -> version + event_flow_manifests
- outbox row` happens in one PostgreSQL transaction. The DynamoDB replica and SQS notify are async via the outbox dispatcher (transactional outbox).
- Immutable history. Every published version is strictly immutable.
Endpoints
| Endpoint | Verb | Purpose |
|---|---|---|
/api/connectors/{id}/draft | PATCH | Full or partial draft update (any subset of the v3 schema). |
/api/connectors/{id}/draft/entities/{name} | PUT | Update a single entity in the draft. |
/api/connectors/{id}/draft/event-flows/{src}/{type} | PUT | Update a single flow in the draft (keyed by source/type). |
/api/connectors/{id}/draft/settings | PUT | Update settings only. |
/api/connectors/{id}/publish | POST | Publish the current draft as the next version. |
The publish endpoint requires a base_version field in the body; see
“Concurrency” below.
Dashboard walkthrough
After any draft write (UI edit, agent edit, script setup), the connector workspace shows an “unpublished changes” banner:
Unpublished changes banner (screenshot pending)
Dashboard UI as of 2026-06-01; controls may have moved by the time you read this. The underlying workflow is stable.
Click Review changes to see a diff between your draft and the currently active version:
Review changes modal (screenshot pending)
Click Publish to commit the new version. The banner disappears and the version label updates:
Published state (screenshot pending)
Concurrency
POST /publish requires a base_version field. If your draft was started
when version 3 was active but someone else published version 4 in the
meantime, your publish fails with HTTP 409 and the dashboard shows the
DraftConflictModal:
- Reset draft: drop your changes and start from version 4.
- Keep editing: keep your draft on disk; you’ll resolve manually.
Restore
The dashboard lets you restore an older version into the draft. Restore
does not auto-publish; it copies the old version’s config blob into
connector_config_drafts, leaving the active version unchanged. You then
review and publish (or further edit) explicitly.
Setup-script behavior
The standard setup scripts ((internal plugsync tooling - see the source repo)) default to
draft-only. Pass --publish to also publish atomically. This split
keeps CI from publishing by accident.
Related reference
Open follow-ups (not blocking this reference)
- The three dashboard screenshots above are pending capture in a follow-up issue. The capture script lives at (internal plugsync tooling - see the source repo) and needs working HubSpot OAuth on the target connector before the workspace page will load.
- A dedicated docs-screenshot staging account would avoid leaking implementer-account chrome (avatars, initials) into the captured PNGs.