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

# Connectors

> How canonic reads a warehouse, a dbt project, a BI tool, or a doc: through one capability contract.

A connector declares **capabilities**, not identity. canonic's core dispatches on capability (`introspect_schema`, `run_read_only_sql`, `extract_definitions`, `extract_evidence`), never on vendor name, so adding a new source is a registration, not a core-code change.

## The connector factory

`canonic.yaml` stores a connection as a descriptor: `id` + `type` + `params` (+ optional `credentials_ref`), not a live instance. At startup, the `ConnectorFactory` looks up `type` in its registry and builds the connector:

```python theme={null}
default_factory.register("postgres", PostgresConnector)
default_factory.register("dbt", _make_dbt)
default_factory.register("metabase", MetabaseConnector)
# … one register() call per type, and no vendor branches in core logic
```

An unregistered `type` raises `UnknownConnectorType` (exit `13`) listing what *is* registered, never a silent fallback. Manage connections with [`canonic connection`](/cli-reference/connection).

## The credential provider registry

Credentials resolve through a second registry with the same shape. A `credentials_ref` of `env:`, `keyring:` or `file:` resolves once to a fixed string. A `provider:<name>` ref instead selects a registered credential provider, which fetches a short-lived credential from a cloud issuer and reports its expiry:

```python theme={null}
registry.register("aws-iam-redshift", make_aws_iam_redshift_provider)
```

Caching and refresh live once, in the layer that wraps every provider, so no provider reimplements expiry arithmetic and no connector reimplements caching. An unregistered name raises `UnknownCredentialProvider` listing what is registered, mirroring `UnknownConnectorType`.

The consequence for a connector is *when* it resolves its credential. A static ref can be baked into a connection string at startup. A provider-backed one cannot: a 15 minute Redshift IAM credential is dead long before a daemon is, so the connector resolves on every connect instead. `redshift` is the first connector that does this, and any future provider-backed connector inherits the same rule. See [dynamic credentials](/reference/config-schema#dynamic-credentials) for the configuration surface.

## Three classes of connector

### Queryable (primary)

Implement `introspect_schema` + `run_read_only_sql`: these feed the [semantic layer](/concepts/semantics) and are executed against directly by [`canonic query`](/cli-reference/query-sql-assert) / `canonic sql`.

| Type       | Notes                                                        |
| ---------- | ------------------------------------------------------------ |
| `postgres` | Catalog introspection via `information_schema`/`pg_catalog`. |
| `sqlite`   | Local `.db` file, no server, no network.                     |
| `duckdb`   | Local `.duckdb` file, or reads CSV/Parquet/JSON directly.    |
| `redshift` | Postgres-compatible catalog surface.                         |

### Definition

Implement `extract_definitions`: feed the semantic layer and canonical-binding candidates from *modeling code*, never the query path (no `run_read_only_sql`).

| Type  | Notes                                                                                           |
| ----- | ----------------------------------------------------------------------------------------------- |
| `dbt` | Parses a compiled `manifest.json`: models → typed columns/grain, measures, joins, descriptions. |

A definition connector is its own connection entry (own `id`) but describes tables that live in a *different*, queryable connection. `params.target_connection` names that connection's id, so the evidence is attributed to it rather than to the definition connector's own id: this is what lets its `RelationSchema` proposals land on the same `semantics/<connection>/<name>.yaml` target as that connection's live introspection and reconcile against it (see the [worked example](/concepts/ingestion-and-reconciliation#example-adding-a-definition-connector-after-an-initial-apply)), instead of colliding on the project-wide unique source `name`. Omitting it falls back to the definition connector's own id, fine for a definitions-only project with no paired physical connection, but such sources can never actually be queried (no `run_read_only_sql`).

### Evidence

Implement `extract_evidence`: feed [knowledge pages](/concepts/knowledge-layer) and reconciliation signal from docs and BI usage. Also never queryable.

| Type       | Notes                                                                                                                                                                          |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `looker`   | Dashboards/explores/usage → usage evidence.                                                                                                                                    |
| `metabase` | Questions/dashboards → usage evidence: candidate metric definitions plus usage frequency.                                                                                      |
| `notion`   | Pages → doc evidence (policies, caveats, definitions).                                                                                                                         |
| `url`      | Arbitrary web pages/text → doc evidence, the generic, vendor-free path, also what [`canonic knowledge add`](/cli-reference/knowledge#knowledge-add) uses for one-shot fetches. |

<Note>
  A BI question's SQL is only ever evidence, never executed. If a Metabase/Looker-encoded metric is adopted, it's recompiled through the deterministic compiler like any other definition, never run as-is.
</Note>

This list isn't closed: any wiki or knowledge base (Confluence, Jira, internal docs tools, etc.) fits the same `extract_evidence` capability as `notion` and `url` today, and can be added as a new registered type without touching core logic.

## Normalized evidence, one shape per kind

Every connector translates its native output into one of a few normalized shapes, so the [ingestion engine](/concepts/ingestion-and-reconciliation) and compiler never see vendor-specific structures:

* **`RelationSchema`**, a table/view: columns (normalized types), primary key, foreign keys, row-count estimate. From queryable connectors' `introspect_schema`.
* **`DefinitionEvidence`**, a named measure/dimension/join from modeling code, with its expression and additivity. From `extract_definitions`.
* **`DocEvidence`**, a title + body + candidate topic references, with a `usage_hint` that maps to a knowledge page's `usage_mode`. From `extract_evidence` doc sources.
* **`UsageEvidence`**, a BI artifact (question/dashboard), the metric it appears to define, and how often it's used, always a *candidate*, never auto-promoted to canonical.

Unmappable native constructs are recorded with a warning, never silently dropped.

## Read-only enforcement

For queryable connectors, read-only is defense in depth, not a convention: a read-only role/credential where the engine supports it, a parse-level check on `run_read_only_sql` that rejects anything but a single `SELECT`/`WITH…SELECT`, and a hard row cap plus statement timeout on every execution. Any layer failing aborts with `READ_ONLY_VIOLATION` before the query runs.

## Schema acquisition ladder

When live introspection is unavailable or partial, canonic descends a priority order. Every tier still emits the same `RelationSchema`, tagged with which tier produced it:

1. **Live introspection**: catalog views.
2. **Modeling code as schema**: via a `dbt`/definition connector.
3. **Query-history inference** *(not yet implemented)*.
4. **Declarative import**: user supplies DDL / a schema export.
5. **Sample-based inference** *(not yet implemented)*.
6. **Hand-authored `semantics/*.yaml`**: validated against the live source before being trusted (below).

Acquisition tier and provenance are independent axes. A tier-2 `dbt` import still enters [reconciliation](/concepts/ingestion-and-reconciliation#reconciliation) at provenance `inferred`, exactly like raw introspection, so it never displaces a `human_curated` or `board_approved` fact. Its acquisition tier only breaks ties **within** the `inferred` band: when a modeling-code definition and a raw-introspection fact describe the same relation without disagreeing on type, the modeling-code evidence is preferred. Structural fields (`grain`, `joins`, `measures`) still always require review regardless of confidence. See a [worked example](/concepts/ingestion-and-reconciliation#example-adding-a-definition-connector-after-an-initial-apply). A fact only reaches `human_curated` through an explicit [`canonic review curate`](/cli-reference/review-apply), never automatically from a higher acquisition tier.

Partial capability is never silent: if only some relations are introspectable, the gap is reported rather than omitted.

## Schema validation probe

Whenever a schema is acquired declaratively or hand-authored (tiers 4–6), canonic issues a zero-data, read-only probe (`SELECT <declared columns> FROM <relation> WHERE false`) against the live source before trusting the evidence. A mismatch returns `SCHEMA_MISMATCH` with a diff of missing/extra columns and type conflicts, never a silent accept.
