> Move providers between workspaces with a versioned, checksummed archive that never carries secrets

# Resource Archives

A resource archive moves one family of workspace resources to another workspace or instance.
It is a `.tar.gz` file whose first entry, `manifest.json`, records:

* the format version;
* the resource family;
* the instance it came from;
* a SHA-256 checksum for every data file.

**Providers** is the first family. Other families follow in dependency order: skills and MCP
servers, schedules, hooks, workflows, channels, packages, and finally a whole-workspace bundle.

## Export

```bash
# What an export would contain: names, counts, providers left out, credentials to re-enter
dewee import-export preview --kind providers

# Download the archive
dewee import-export export --kind providers --output providers.tar.gz
```

Credentials are **omitted**. They are not masked and not encrypted; they are simply absent:

* API keys are dropped.
* `user:password@` is removed from the API base URL.
* Secret-looking settings are removed at any depth.

The manifest lists every omission under `needs_credentials`.

Providers bound to the source host (`claude_cli`, `acp`) are left out and listed under
`excluded`. An export from the Customer Control Plane also leaves out provider types the
dashboard does not offer. ChatGPT sign-in details (account, plan, expiry, scopes) are never
exported.

## Import

Always run a dry run first. It writes nothing and reports exactly what the apply would do.

```bash
dewee import-export preview --kind providers --file providers.tar.gz --conflict rename
dewee import-export import  --kind providers --file providers.tar.gz --conflict rename
```

`--conflict` decides what happens when a provider with the same name already exists:

\| Strategy | Effect |
\|---|---|
\| `skip` (default) | Keep the existing provider. |
\| `rename` | Import the archive copy as `<name>-imported`. |
\| `replace` | Overwrite the existing provider. Its API key is kept only when the type and endpoint are unchanged. A ChatGPT sign-in is never moved to another type or endpoint; that replace is rejected. |

A provider that arrives without its credentials is imported **disabled**. Re-enter the key and
enable it in one step:

```bash
printf '%s' "$OPENAI_API_KEY" | dewee providers update openai-main --api-key-stdin --enabled
```

When any item is rejected, the import exits non-zero with the error code `PARTIAL_FAILURE`, and
`--json` prints the full report.

In the Customer Control Plane, open **Import / export**, choose **Providers**, pick a conflict
strategy, and upload the archive. The dry run and the apply call the same runtime routes as the
CLI. Customer workspaces can only import the provider types they could set up from the dashboard.

## Errors

The runtime validates the manifest before it reads any provider. The most common rejections are:

\| Code | Meaning |
\|---|---|
\| `archive_version_unsupported` | The archive’s format version is newer or older than this runtime reads. Upgrade the runtime, or export the archive again. |
\| `archive_checksum_mismatch` | A file is damaged or incomplete. Checksums catch corruption, not deliberate edits. |
\| `archive_family_mismatch` | The archive holds another resource family. |
\| `archive_manifest_missing` | The file is not a resource archive. |

Every import is scoped to your own workspace. An archive can never read or overwrite another
workspace’s providers.

Exports are audited as `provider.exported` and imports as `provider.imported`.
