> ## Documentation Index
> Fetch the complete documentation index at: https://docs.synq.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Workspace suites

> From a local file to a scheduled production deployment — Development, Production, and what each step preserves

A suite that only runs on your machine is a useful tool. A suite in your workspace is a monitored one: versioned, runnable by anyone with access, schedulable, and wired into assets, checks, issues and alerts.

Two stages, in the app under **Health → Reconciliations**:

| Stage           | What lives there                                                    | How a run starts                         |
| --------------- | ------------------------------------------------------------------- | ---------------------------------------- |
| **Development** | Stored, versioned suites. The shared draft.                         | On demand only                           |
| **Production**  | Deployments — immutable snapshots with a schedule and run settings. | Schedule, API trigger, or the Run button |

## Save to Development

<Tabs>
  <Tab title="CLI">
    ```bash theme={null}
    synq-recon auth whoami        # confirm the workspace first
    synq-recon upload-config suite.yaml --change-summary "Add inventory reconciliation"
    ```

    Prints the suite id. Requires `SCOPE_RECON_EDIT`.
  </Tab>

  <Tab title="App">
    **Development suites → New suite** opens a four-step wizard: **Data Sources**, **Comparison**, **Suite Info**, **Review**. It searches your catalog, so you can pick a table you have already catalogued rather than typing warehouse coordinates.

    **Upload Suite Config** takes a whole suite as pasted or uploaded YAML — which is how you get anything the wizard does not cover. [What the wizard covers](/reconciliation/authoring-suites#what-the-wizard-covers-and-what-needs-yaml) is the map.
  </Tab>
</Tabs>

Every save creates a new **version**. The change summary is what makes the version history readable six months later.

```bash theme={null}
synq-recon suite list
synq-recon suite versions <suite-id>
synq-recon suite yaml <suite-id> > suite.yaml     # read it back as editable YAML
synq-recon suite delete <suite-id>                # soft delete
```

<Tip>
  `suite yaml` is the round-trip that makes a stored suite editable again — including one someone else created in the wizard. Fetch it, edit the file, `upload-config` it back. This is also the recipe for copying a suite between workspaces.
</Tip>

## Bind connections to integrations

A backend run does not see the credentials in your YAML. It uses **integrations** — the warehouse credentials your workspace already manages.

```bash theme={null}
synq-recon connections remote list                    # what is available, with ids
synq-recon connections remote list --recon-deployable  # only the ones recon can use
```

By default each connection name binds to the integration whose id matches it. Map them explicitly when the names differ:

```bash theme={null}
synq-recon run-remote <suite-id> --map gateway=<integration-id> --map warehouse=<integration-id>
```

In the app this is the **Bind suite connections to integrations** dialog.

<Note>
  `N connection(s) could not be bound to an integration` means a suite connection has no matching integration, or the integration is not enabled for reconciliation. Check the names with `connections remote list --recon-deployable`, or bind explicitly with `--map`.
</Note>

Read [connection vs integration](/reconciliation/concepts#connection-vs-integration) once if that distinction is new — the same YAML key means two different things in the two stages, and it is the most common source of confusion.

## Run it on the backend

<Tabs>
  <Tab title="CLI">
    ```bash theme={null}
    synq-recon run-remote <suite-id> --drill=false --wait
    ```

    The run is asynchronous. `--wait` blocks until it finishes and sets a meaningful exit code; without it, poll with `synq-recon audit-logs get <invocation-id>`.

    `--invocation-id <id>` makes submission idempotent — re-submitting with the same id will not start a duplicate run, which is what you want from a retrying pipeline.
  </Tab>

  <Tab title="App">
    **Development → Run** on the suite. The run options dialog carries the same drill and timeout settings.
  </Tab>
</Tabs>

<Warning>
  **Pass `--drill=false` explicitly if you do not want a drill.** Omitting the flag does not mean "off".

  Unlike `promote`, where an omitted flag preserves the stored setting, an ad-hoc run has no deployment to inherit from — so an omitted `--drill` falls through to each reconciliation's own `bisection.enabled` in the YAML, which defaults to on. On a suite with genuinely many differences that is the difference between a two-minute run and a fifteen-minute one with nothing usable at the end.
</Warning>

## Promote to production

Promoting creates a **deployment**: an immutable snapshot of the suite plus its schedule, bindings, run settings and annotations.

<Tabs>
  <Tab title="CLI">
    ```bash theme={null}
    synq-recon promote <suite-id> \
      --schedule "0 */4 * * *" \
      --timezone UTC \
      --triggerable-by-api \
      --drill=false \
      --execution-timeout 30m \
      --annotation team=data-platform \
      --change-summary "Every four hours, no drill"
    ```
  </Tab>

  <Tab title="App">
    **Promote to production** on the suite card. The dialog covers the schedule and the run settings, and shows a diff of what is about to change against what production is running today.
  </Tab>
</Tabs>

Once promoted, each reconciliation in the deployment becomes a platform asset that produces a check on every run — so it appears in search and lineage, participates in [check categorisation](/analytics/check-categories), and opens [issues](/incidents/incident-overview) that route through your existing [alerts](/alerts/alert-overview) and [ownership](/ownership/ownership-overview).

### Schedules

Two forms, mutually exclusive:

```bash theme={null}
--schedule "0 */4 * * *"                              # cron
--ical "FREQ=MONTHLY;BYDAY=TH;BYSETPOS=-1"            # RFC 5545 recurrence rule
```

Both take `--timezone` (for example `UTC`, `Europe/Warsaw`); `--ical` additionally accepts `--dtstart` as an anchor. Use iCal for the schedules cron cannot express — last Thursday of the month, the second Tuesday, business-day patterns.

### Two different timeouts

<Warning>
  `--timeout` and `--execution-timeout` are unrelated, and mixing them up is easy:

  * **`--timeout`** is a **per-query** budget for locally executed commands. Default `5m`.
  * **`--execution-timeout`** is the **whole-run** wall-clock budget for a backend run, set on a deployment or per trigger. The server clamps it to its supported range.
</Warning>

## Editing a deployment

Deployment edits **preserve what you omit**. `deployment update` always behaves this way, and so does a re-promote — so refreshing a suite snapshot never silently unschedules the deployment or turns off API triggers.

```bash theme={null}
synq-recon deployment update <suite-id> --execution-timeout 45m   # changes only that
synq-recon deployment update <suite-id> --clear                   # remove the schedule
synq-recon promote <suite-id> --clear-schedule                    # same, while re-promoting
```

Removing a schedule is therefore always explicit.

<Warning>
  **A *fresh* promote applies defaults, and one of them may surprise you.** With no existing deployment to preserve from, a promote defaults to: no schedule, not triggerable by API, the server-default execution timeout — and **drill-down on**.

  If you want a scheduled production run that never drills, pass `--drill=false` on the first promote. Do not rely on omission.
</Warning>

<Warning>
  **A deployment is a snapshot, not a pointer.** Editing the suite in Development changes nothing in production until you re-promote. The app says so on the suite card — *"Dev has unpromoted changes"* versus *"Up to date with prod"* — and it is worth checking that line before concluding that a fix did not work.
</Warning>

### Keep the deployment id stable

A deployment's id is part of every reconciliation's asset path. Rebuilding a deployment from scratch under a new id re-homes its checks and drops their history.

Re-promoting the same suite reuses its id automatically, so this is only a concern if you are rebuilding deliberately — in which case pin it:

```bash theme={null}
synq-recon promote <suite-id> --deployment-id <existing-id>
```

## Pause, resume, unpromote

```bash theme={null}
synq-recon deployment pause <suite-id> --until 2026-04-01T00:00:00Z
synq-recon deployment pause <suite-id>          # indefinitely
synq-recon deployment resume <suite-id>
synq-recon unpromote <suite-id> --reason "Migration complete"
```

Pause is for a planned maintenance window: the deployment stays configured and stops running. Unpromote deactivates it entirely; its history is kept, and re-promoting brings it back under the same id.

<Note>
  `deployment is not active; re-promote to modify` means the deployment was unpromoted. Re-promote before changing or tearing it down.
</Note>

## Inspecting what ran

```bash theme={null}
synq-recon runs list --status in_progress
synq-recon runs list --trigger scheduled --suite <suite-id> --limit 20
synq-recon runs cancel <suite-id> <run-id>

synq-recon audit-logs list --status mismatched
synq-recon audit-logs get <invocation-id> -o json

synq-recon deployment get <suite-id> -o yaml     # schedule, bindings, run settings
synq-recon deployment history <suite-id>         # every promote, pause, edit
```

A run's `--trigger` says how it started, which is often the first thing you want to know:

| Trigger     | Means                                                                                             |
| ----------- | ------------------------------------------------------------------------------------------------- |
| `adhoc`     | Submitted against a Development suite (`run-remote`, or **Run** in the app).                      |
| `scheduled` | A schedule fired.                                                                                 |
| `api`       | An explicit trigger — the Run button on a promoted suite, or an external caller such as a CI job. |

`deployment history` is append-only and records every promote, unpromote, schedule change, binding change, annotation edit, pause and resume. When production behaviour changed and nobody remembers why, it is the first place to look.

## Copy production back to Development

To iterate on what production is actually running, rather than on what Development happens to contain:

<Tabs>
  <Tab title="CLI">
    ```bash theme={null}
    synq-recon suite yaml <suite-id> > suite.yaml
    # edit
    synq-recon upload-config suite.yaml --change-summary "Iterating on the prod snapshot"
    ```
  </Tab>

  <Tab title="App">
    **Copy production snapshot to development** on the production deployment card.
  </Tab>
</Tabs>

## Permissions

| Action                                                                  | Scope                 |
| ----------------------------------------------------------------------- | --------------------- |
| View suites, deployments, runs, audit logs                              | `SCOPE_RECON_READ`    |
| `upload-config`, suite edits and deletes, ad-hoc runs, cancelling a run | `SCOPE_RECON_EDIT`    |
| Promote, unpromote, trigger, pause, resume, deployment edits            | `SCOPE_RECON_PROMOTE` |
| Report a locally executed run's results                                 | `SCOPE_RECON_EDIT`    |

The app hides controls whose call would be refused, so a missing scope shows up as a missing button rather than an error. [CI/CD and automation](/reconciliation/cicd-and-automation#scopes-and-credentials) maps these to roles and to the credentials an unattended caller uses.

## Next

<CardGroup cols={2}>
  <Card title="Investigating results" icon="magnifying-glass" href="/reconciliation/investigating-results">
    Reading a run, and following a finding to the rows.
  </Card>

  <Card title="CI/CD and automation" icon="gear" href="/reconciliation/cicd-and-automation">
    `trigger --wait` as a release gate, exit codes and credentials.
  </Card>
</CardGroup>
