# Discovery Connectors **Status:** draft **Updated:** 2026-06-27 **Repo:** config-atlas **Workplan:** ATLAS-WP-0003 (Phase 2) **Related:** [`../specs/ArchitectureBlueprint.md`](../specs/ArchitectureBlueprint.md) §4/§6, [`ecosystem-boundaries.md`](ecosystem-boundaries.md) §2.4, [`configuration-surface-schema.md`](configuration-surface-schema.md) Connectors grow the surface registry from **automated discovery** instead of only hand authoring — reusing `repo-scoping`'s scanner → candidate → approval model rather than building bespoke approval machinery. ## Contract A connector **MUST**: - be **read-only and stateless** — never write a source system, never auto-merge; - **never read or store configuration values or secret values** — record file *locations* and key/flag *identities* only; - emit **candidate** surface entries (`status: candidate`) with provenance in `evidence.discovery_method = connector:`; - produce **schema-valid** entries (validated by `connector_base.emit_candidate`); - **never overwrite a promoted entry** — if an id already exists under `registry/surfaces/*.md`, the candidate is skipped (the registry is truth). Connectors **propose**; humans/agents **dispose**. ## Candidate lifecycle ```text connector -> registry/surfaces/candidates/.md (status: candidate) -> PR review (human or trusted agent) -> promote: move to registry/surfaces/.md (status: draft|active), add row to registry/indexes/surfaces.yaml, set a real owner -> or reject: delete the candidate ``` Candidates live in `registry/surfaces/candidates/` and are **excluded** from the promoted-entry validation/index gate (`tools/validate_registry.py` globs `registry/surfaces/*.md` non-recursively), so unreviewed candidates never become registry truth or break the index. The candidates directory is **gitignored** — candidates are ephemeral connector output; **promotion** moves an entry into the tracked `registry/surfaces/` and `surfaces.yaml`. ## Connectors | Connector | Source | Emits | Command | |-----------|--------|-------|---------| | `git-config` | repo config files (`*.env.example`, `values*.yaml`, `config*.yaml`); real `.env` → secret-ref | app/deploy/secret-ref candidates | `make connect-gitconfig REPO=` | | `repo-scoping` | repo-scoping observed facts (`--facts` file or `REPO_SCOPING_URL`) | app-config candidates | `make connect-reposcoping REPO=` | | `feature-control` | feature-control key registry (`--keys` or its index) | feature-flag candidates (link only) | `make connect-featurecontrol` | All degrade gracefully (emit nothing) when their source is unavailable. ## Health `make registry-health` (`tools/registry_health.py`) reports **unowned** surfaces (missing owner, or owner not resolvable to a known identity) and **stale** surfaces (`evidence.last_seen` older than the threshold). Ownership resolution currently uses the reuse-surface roster as a stand-in until `domain-tree` binding is wired. ## Boundaries Connectors reuse `repo-scoping`'s discovery model and never duplicate it; the `feature-control` connector **links** to feature-control keys and never re-derives evaluation logic (PRD FR-12). See [`ecosystem-boundaries.md`](ecosystem-boundaries.md).