# Issue #3830 Batch 1 (A.2 + A.3 + C) Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Complete three Issue #3830 sub-projects in one PR: declarations manifest (A.3) closes the registration-vs-mount gap, pip extras (C) make A-full's install hints resolve, cleanup (A.2) deletes the now-unneeded legacy `info.requires` property.

**Architecture:** New `src/nexus/backends/_manifest.py` holds every built-in connector as a static `ConnectorManifestEntry`. Phase 1 of `_register_optional_backends()` reads the manifest and registers placeholder `ConnectorInfo` entries (with `connector_class=None` and manifest-sourced `runtime_deps`). Phase 2 imports each connector module; on success the module's `@register_connector("name")` call binds the real class into the placeholder preserving metadata; on `ImportError` the placeholder stays. `BackendFactory.create()` already calls `check_runtime_deps` — it now also checks for placeholder (`connector_class is None`) as a safety net. Built-in `@register_connector` calls shrink to `@register_connector("name")` — all metadata lives in the manifest. `packages/nexus-fs/pyproject.toml` gains 6 new extras so `install_hint` strings resolve. `ConnectorInfo.requires` property deletion is last.

**Tech Stack:** Python 3.14, dataclasses, existing `BaseRegistry`, pytest, hatchling.

**Spec:** [`docs/superpowers/specs/2026-04-21-issue-3830-batch1-design.md`](../specs/2026-04-21-issue-3830-batch1-design.md)

---

## File structure

### New files
- `src/nexus/backends/_manifest.py` — `ConnectorManifestEntry` dataclass + `CONNECTOR_MANIFEST` tuple with all 22 entries.
- `tests/unit/backends/test_manifest.py` — structural validation of `CONNECTOR_MANIFEST`.
- `tests/integration/backends/test_factory_placeholder.py` — end-to-end factory behavior for placeholder + import-failure cases.

### Modified files
- `src/nexus/backends/base/registry.py` — add `register_placeholder()` classmethod; extend `register()` for placeholder-binding path; `ConnectorInfo.connector_class` type becomes `type[Backend] | None`; delete the `@property requires` (last task).
- `src/nexus/backends/base/factory.py` — add placeholder-check after the dep-check block.
- `src/nexus/backends/__init__.py` — rewrite `_register_optional_backends()` to use the manifest; drop `_OPTIONAL_BACKENDS` dict (or reduce to a thin `__getattr__` helper derived from the manifest).
- Built-in connector files (22 `@register_connector` call sites): collapse to name-only. Files:
  - `src/nexus/backends/storage/path_gcs.py`
  - `src/nexus/backends/storage/cas_gcs.py`
  - `src/nexus/backends/storage/path_s3.py`
  - `src/nexus/backends/storage/path_local.py`
  - `src/nexus/backends/storage/cas_local.py`
  - `src/nexus/backends/storage/local_connector.py`
  - `src/nexus/backends/connectors/gdrive/connector.py`
  - `src/nexus/backends/connectors/gmail/connector.py`
  - `src/nexus/backends/connectors/calendar/connector.py` (2 sites)
  - `src/nexus/backends/connectors/x/connector.py`
  - `src/nexus/backends/connectors/slack/connector.py`
  - `src/nexus/backends/connectors/hn/connector.py`
  - `src/nexus/backends/compute/anthropic_native.py`
  - `src/nexus/backends/compute/openai_compatible.py`
  - `src/nexus/backends/connectors/gws/connector.py` (6 sites; also drop `_GWS_RUNTIME_DEPS` constant)
  - `src/nexus/backends/connectors/github/connector.py` (2 stacked decorators; also drop `_GH_RUNTIME_DEPS` constant)
- `src/nexus/bricks/mount/mount_service.py` — update line 842 to read `runtime_deps` directly.
- `tests/unit/backends/test_registry.py` — remove the two `requires_property_*` tests (last task).
- `packages/nexus-fs/pyproject.toml` — add 6 new extras.

---

## Task 1: Create the manifest module with all 22 entries

**Files:**
- Create: `src/nexus/backends/_manifest.py`
- Test: `tests/unit/backends/test_manifest.py`

- [ ] **Step 1: Write failing structural tests**

Create `tests/unit/backends/test_manifest.py`:

```python
"""Structural tests for CONNECTOR_MANIFEST (Issue #3830 sub-project A.3)."""

from __future__ import annotations

import pytest

from nexus.backends._manifest import CONNECTOR_MANIFEST, ConnectorManifestEntry
from nexus.backends.base.runtime_deps import (
    BinaryDep,
    PythonDep,
    RuntimeDep,
    ServiceDep,
)


class TestManifestStructure:
    def test_manifest_is_non_empty(self) -> None:
        assert len(CONNECTOR_MANIFEST) > 0

    def test_every_entry_has_required_fields(self) -> None:
        for entry in CONNECTOR_MANIFEST:
            assert isinstance(entry, ConnectorManifestEntry)
            assert entry.name, f"empty name in manifest entry: {entry!r}"
            assert entry.module_path, f"empty module_path: {entry.name}"
            assert entry.class_name, f"empty class_name: {entry.name}"
            assert entry.description, f"empty description: {entry.name}"
            assert entry.category, f"empty category: {entry.name}"

    def test_names_are_unique(self) -> None:
        names = [e.name for e in CONNECTOR_MANIFEST]
        dupes = {n for n in names if names.count(n) > 1}
        assert not dupes, f"duplicate manifest names: {sorted(dupes)}"

    def test_runtime_deps_are_typed(self) -> None:
        for entry in CONNECTOR_MANIFEST:
            for dep in entry.runtime_deps:
                assert isinstance(dep, (PythonDep, BinaryDep, ServiceDep)), (
                    f"{entry.name}: unexpected dep type {type(dep).__name__}"
                )

    def test_expected_connectors_present(self) -> None:
        """All 22 A-full connectors must appear in the manifest."""
        names = {e.name for e in CONNECTOR_MANIFEST}
        expected = {
            "path_gcs", "cas_gcs", "path_s3", "path_local", "cas_local",
            "local_connector",
            "gdrive_connector", "gmail_connector",
            "calendar_connector", "gcalendar_connector",
            "x_connector", "slack_connector", "hn_connector",
            "anthropic_native", "openai_compatible",
            "gws_gmail", "gws_calendar", "gws_sheets", "gws_docs", "gws_chat", "gws_drive",
            "github_connector", "gws_github",
        }
        missing = expected - names
        assert not missing, f"missing manifest entries: {sorted(missing)}"
```

- [ ] **Step 2: Run tests to verify failure**

```
PYTHONPATH=/Users/tafeng/nexus/.claude/worktrees/giggly-juggling-abelson/src pytest tests/unit/backends/test_manifest.py -v --noconftest --override-ini="addopts="
```

Expected: `ImportError: No module named 'nexus.backends._manifest'`.

- [ ] **Step 3: Create the manifest module**

Create `src/nexus/backends/_manifest.py`:

```python
"""Static declaration of every built-in connector (Issue #3830 sub-project A.3).

This manifest is read BEFORE any connector module is imported. Each entry
becomes a placeholder ``ConnectorInfo`` in the registry. If the connector
module then imports successfully, its ``@register_connector`` call binds
the real class into the placeholder, preserving manifest metadata. If the
module fails to import, the placeholder stays — and
``BackendFactory.create()`` raises ``MissingDependencyError`` with the
manifest's install hints instead of the pre-A.3 ``RuntimeError("Unsupported
backend type")``.

Adding a new built-in connector:
    1. Add an entry to ``CONNECTOR_MANIFEST`` here.
    2. Write the connector class; decorate with ``@register_connector("name")``
       — name only, no metadata kwargs (those come from this manifest).

External plugins (entry-point based) are NOT listed here; they register
via the regular decorator path and go through the no-existing-entry branch
of ``ConnectorRegistry.register()``.
"""

from __future__ import annotations

from dataclasses import dataclass, field

from nexus.backends.base.runtime_deps import (
    BinaryDep,
    PythonDep,
    RuntimeDep,
    ServiceDep,
)


@dataclass(frozen=True, slots=True)
class ConnectorManifestEntry:
    """Static declaration of one built-in connector."""

    name: str
    """Registry key (what users mount with, e.g. ``"path_gcs"``)."""

    module_path: str
    """Dotted import path for the connector module."""

    class_name: str
    """Class name exposed from the module (for legacy ``__getattr__`` lookup)."""

    description: str
    """One-line description shown in ``nexus connectors list``."""

    category: str
    """Grouping key (``"storage"``, ``"oauth"``, ``"cli"``, ``"compute"``, ...)."""

    runtime_deps: tuple[RuntimeDep, ...] = ()
    """Typed deps checked at mount time."""

    service_name: str | None = None
    """Unified service name for ``service_map.py`` integration."""


_GWS_RUNTIME_DEPS: tuple[RuntimeDep, ...] = (
    BinaryDep("gws", "brew install nexi-lab/tap/gws"),
    ServiceDep("token_manager"),
)

_GH_RUNTIME_DEPS: tuple[RuntimeDep, ...] = (
    BinaryDep("gh", "brew install gh"),
    ServiceDep("token_manager"),
)

_GOOGLE_OAUTH_DEPS = (
    PythonDep("googleapiclient", extras=("gdrive",)),
    PythonDep("google_auth_oauthlib", extras=("gdrive",)),
)

CONNECTOR_MANIFEST: tuple[ConnectorManifestEntry, ...] = (
    # --- Storage ---
    ConnectorManifestEntry(
        name="path_gcs",
        module_path="nexus.backends.storage.path_gcs",
        class_name="PathGCSBackend",
        description="Google Cloud Storage with direct path mapping",
        category="storage",
        runtime_deps=(PythonDep("google.cloud.storage", extras=("gcs",)),),
        service_name="gcs",
    ),
    ConnectorManifestEntry(
        name="cas_gcs",
        module_path="nexus.backends.storage.cas_gcs",
        class_name="CASGCSBackend",
        description="Google Cloud Storage with CAS deduplication",
        category="storage",
        runtime_deps=(PythonDep("google.cloud.storage", extras=("gcs",)),),
    ),
    ConnectorManifestEntry(
        name="path_s3",
        module_path="nexus.backends.storage.path_s3",
        class_name="PathS3Backend",
        description="AWS S3 with direct path mapping",
        category="storage",
        runtime_deps=(PythonDep("boto3", extras=("s3",)),),
        service_name="s3",
    ),
    ConnectorManifestEntry(
        name="path_local",
        module_path="nexus.backends.storage.path_local",
        class_name="PathLocalBackend",
        description="Local filesystem with direct path mapping (no CAS)",
        category="storage",
    ),
    ConnectorManifestEntry(
        name="cas_local",
        module_path="nexus.backends.storage.cas_local",
        class_name="CASLocalBackend",
        description="Local filesystem with CAS deduplication",
        category="storage",
    ),
    ConnectorManifestEntry(
        name="local_connector",
        module_path="nexus.backends.storage.local_connector",
        class_name="LocalConnectorBackend",
        description="Mount local folder into Nexus (reference mode, no copy)",
        category="storage",
    ),
    # --- OAuth / API connectors ---
    ConnectorManifestEntry(
        name="gdrive_connector",
        module_path="nexus.backends.connectors.gdrive.connector",
        class_name="PathGDriveBackend",
        description="Google Drive with OAuth 2.0 authentication",
        category="oauth",
        runtime_deps=(
            PythonDep("googleapiclient", extras=("gdrive",)),
            PythonDep("google_auth_oauthlib", extras=("gdrive",)),
        ),
        service_name="google-drive",
    ),
    ConnectorManifestEntry(
        name="gmail_connector",
        module_path="nexus.backends.connectors.gmail.connector",
        class_name="PathGmailBackend",
        description="Gmail with OAuth 2.0 authentication",
        category="oauth",
        runtime_deps=(
            PythonDep("googleapiclient", extras=("gmail",)),
            PythonDep("google_auth_oauthlib", extras=("gmail",)),
        ),
        service_name="gmail",
    ),
    ConnectorManifestEntry(
        name="calendar_connector",
        module_path="nexus.backends.connectors.calendar.connector",
        class_name="PathCalendarBackend",
        description="Google Calendar with OAuth 2.0 authentication",
        category="oauth",
        runtime_deps=(
            PythonDep("googleapiclient", extras=("gcalendar",)),
            PythonDep("google_auth_oauthlib", extras=("gcalendar",)),
        ),
        service_name="google-calendar",
    ),
    ConnectorManifestEntry(
        name="gcalendar_connector",
        module_path="nexus.backends.connectors.calendar.connector",
        class_name="PathCalendarBackend",
        description="Google Calendar (alias)",
        category="oauth",
        runtime_deps=(
            PythonDep("googleapiclient", extras=("gcalendar",)),
            PythonDep("google_auth_oauthlib", extras=("gcalendar",)),
        ),
    ),
    ConnectorManifestEntry(
        name="x_connector",
        module_path="nexus.backends.connectors.x.connector",
        class_name="PathXBackend",
        description="X (Twitter) via OAuth 1.0a",
        category="oauth",
        runtime_deps=(PythonDep("requests_oauthlib", extras=("x",)),),
    ),
    ConnectorManifestEntry(
        name="slack_connector",
        module_path="nexus.backends.connectors.slack.connector",
        class_name="PathSlackBackend",
        description="Slack messaging via Web API",
        category="oauth",
        runtime_deps=(PythonDep("slack_sdk", extras=("slack",)),),
    ),
    ConnectorManifestEntry(
        name="hn_connector",
        module_path="nexus.backends.connectors.hn.connector",
        class_name="PathHNBackend",
        description="Hacker News read-only JSON API",
        category="api",
    ),
    # --- Compute ---
    ConnectorManifestEntry(
        name="anthropic_native",
        module_path="nexus.backends.compute.anthropic_native",
        class_name="CASAnthropicBackend",
        description="Native Anthropic Claude API (direct SDK)",
        category="compute",
        runtime_deps=(PythonDep("anthropic", extras=("anthropic",)),),
    ),
    ConnectorManifestEntry(
        name="openai_compatible",
        module_path="nexus.backends.compute.openai_compatible",
        class_name="CASOpenAIBackend",
        description="OpenAI-compatible LLM API",
        category="compute",
        runtime_deps=(PythonDep("openai", extras=("openai",)),),
    ),
    # --- CLI-backed connectors (gws family) ---
    ConnectorManifestEntry(
        name="gws_gmail",
        module_path="nexus.backends.connectors.gws.connector",
        class_name="GmailConnector",
        description="Gmail via gws CLI",
        category="cli",
        runtime_deps=_GWS_RUNTIME_DEPS,
        service_name="gws",
    ),
    ConnectorManifestEntry(
        name="gws_calendar",
        module_path="nexus.backends.connectors.gws.connector",
        class_name="CalendarConnector",
        description="Google Calendar via gws CLI",
        category="cli",
        runtime_deps=_GWS_RUNTIME_DEPS,
        service_name="gws",
    ),
    ConnectorManifestEntry(
        name="gws_sheets",
        module_path="nexus.backends.connectors.gws.connector",
        class_name="SheetsConnector",
        description="Google Sheets via gws CLI",
        category="cli",
        runtime_deps=_GWS_RUNTIME_DEPS,
        service_name="gws",
    ),
    ConnectorManifestEntry(
        name="gws_docs",
        module_path="nexus.backends.connectors.gws.connector",
        class_name="DocsConnector",
        description="Google Docs via gws CLI",
        category="cli",
        runtime_deps=_GWS_RUNTIME_DEPS,
        service_name="gws",
    ),
    ConnectorManifestEntry(
        name="gws_chat",
        module_path="nexus.backends.connectors.gws.connector",
        class_name="ChatConnector",
        description="Google Chat via gws CLI",
        category="cli",
        runtime_deps=_GWS_RUNTIME_DEPS,
        service_name="gws",
    ),
    ConnectorManifestEntry(
        name="gws_drive",
        module_path="nexus.backends.connectors.gws.connector",
        class_name="DriveConnector",
        description="Google Drive via gws CLI",
        category="cli",
        runtime_deps=_GWS_RUNTIME_DEPS,
        service_name="gws",
    ),
    # --- GitHub CLI ---
    ConnectorManifestEntry(
        name="github_connector",
        module_path="nexus.backends.connectors.github.connector",
        class_name="GitHubConnector",
        description="GitHub via gh CLI",
        category="cli",
        runtime_deps=_GH_RUNTIME_DEPS,
    ),
    ConnectorManifestEntry(
        name="gws_github",
        module_path="nexus.backends.connectors.github.connector",
        class_name="GitHubConnector",
        description="GitHub via gh CLI (deprecated alias, use github_connector)",
        category="cli",
        runtime_deps=_GH_RUNTIME_DEPS,
    ),
)


__all__ = ["CONNECTOR_MANIFEST", "ConnectorManifestEntry"]
```

- [ ] **Step 4: Run tests to verify they pass**

```
PYTHONPATH=/Users/tafeng/nexus/.claude/worktrees/giggly-juggling-abelson/src pytest tests/unit/backends/test_manifest.py -v --noconftest --override-ini="addopts="
```

Expected: 5 passed.

- [ ] **Step 5: Commit**

```bash
git add src/nexus/backends/_manifest.py tests/unit/backends/test_manifest.py
git commit -m "feat(backends): add CONNECTOR_MANIFEST for placeholder registration (issue #3830 A.3)"
```

---

## Task 2: Registry support — placeholder register + class binding

**Files:**
- Modify: `src/nexus/backends/base/registry.py` (ConnectorInfo type, register() binding path, new register_placeholder)
- Test: `tests/unit/backends/test_registry.py` (extend)

- [ ] **Step 1: Write failing tests**

Append to `tests/unit/backends/test_registry.py`:

```python
class TestPlaceholderRegistration:
    def setup_method(self) -> None:
        from nexus.backends.base.registry import ConnectorRegistry
        ConnectorRegistry.clear()

    def teardown_method(self) -> None:
        from nexus.backends.base.registry import ConnectorRegistry
        ConnectorRegistry.clear()

    def test_register_placeholder_stores_entry(self) -> None:
        from nexus.backends._manifest import ConnectorManifestEntry
        from nexus.backends.base.registry import ConnectorRegistry
        from nexus.backends.base.runtime_deps import PythonDep

        entry = ConnectorManifestEntry(
            name="placeholder_test",
            module_path="nowhere.real",
            class_name="Nope",
            description="Placeholder for test",
            category="storage",
            runtime_deps=(PythonDep("boto3", extras=("s3",)),),
        )
        ConnectorRegistry.register_placeholder(entry)

        info = ConnectorRegistry.get_info("placeholder_test")
        assert info.connector_class is None
        assert info.runtime_deps == (PythonDep("boto3", extras=("s3",)),)
        assert info.description == "Placeholder for test"
        assert info.category == "storage"

    def test_register_binds_class_into_placeholder(self) -> None:
        from nexus.backends._manifest import ConnectorManifestEntry
        from nexus.backends.base.registry import (
            ConnectorRegistry,
            register_connector,
        )
        from nexus.backends.base.runtime_deps import PythonDep

        entry = ConnectorManifestEntry(
            name="bind_test",
            module_path="nowhere.real",
            class_name="DummyBackend",
            description="Bind test",
            category="storage",
            runtime_deps=(PythonDep("json"),),
            service_name="bind-svc",
        )
        ConnectorRegistry.register_placeholder(entry)

        @register_connector("bind_test")
        class T(DummyBackend):
            pass

        info = ConnectorRegistry.get_info("bind_test")
        assert info.connector_class is T
        # manifest metadata preserved
        assert info.runtime_deps == (PythonDep("json"),)
        assert info.description == "Bind test"
        assert info.category == "storage"
        assert info.service_name == "bind-svc"

    def test_register_emits_warning_when_builtin_passes_metadata(self) -> None:
        import warnings

        from nexus.backends._manifest import ConnectorManifestEntry
        from nexus.backends.base.registry import (
            ConnectorRegistry,
            register_connector,
        )
        from nexus.backends.base.runtime_deps import PythonDep

        entry = ConnectorManifestEntry(
            name="warn_test",
            module_path="nowhere.real",
            class_name="DummyBackend",
            description="Manifest description",
            category="storage",
        )
        ConnectorRegistry.register_placeholder(entry)

        with warnings.catch_warnings(record=True) as caught:
            warnings.simplefilter("always")

            @register_connector(
                "warn_test",
                description="Decorator description",  # should trigger warning
            )
            class T(DummyBackend):
                pass

            assert any(
                issubclass(w.category, UserWarning) and "manifest" in str(w.message).lower()
                for w in caught
            ), "expected UserWarning about manifest overriding decorator kwargs"

        # Manifest values preserved despite decorator kwargs
        info = ConnectorRegistry.get_info("warn_test")
        assert info.description == "Manifest description"

    def test_external_plugin_still_registers_without_placeholder(self) -> None:
        from nexus.backends.base.registry import (
            ConnectorRegistry,
            register_connector,
        )
        from nexus.backends.base.runtime_deps import PythonDep

        @register_connector(
            "external_plugin",
            description="External plugin",
            category="external",
            runtime_deps=(PythonDep("json"),),
        )
        class T(DummyBackend):
            pass

        info = ConnectorRegistry.get_info("external_plugin")
        assert info.connector_class is T
        assert info.description == "External plugin"
        assert info.runtime_deps == (PythonDep("json"),)
```

- [ ] **Step 2: Run to verify failure**

```
PYTHONPATH=/Users/tafeng/nexus/.claude/worktrees/giggly-juggling-abelson/src pytest tests/unit/backends/test_registry.py::TestPlaceholderRegistration -v --noconftest --override-ini="addopts="
```

Expected: `AttributeError: type object 'ConnectorRegistry' has no attribute 'register_placeholder'`.

- [ ] **Step 3: Update `ConnectorInfo.connector_class` type**

In `src/nexus/backends/base/registry.py`, find the `ConnectorInfo` dataclass definition (around lines 197-220). Change the `connector_class` field type annotation from `"type[Backend]"` to `"type[Backend] | None"` and update its docstring:

```python
    connector_class: "type[Backend] | None"
    """The connector class, or None if this is a manifest-registered
    placeholder whose module has not yet been imported (or whose import
    failed). ``BackendFactory.create()`` raises ``MissingDependencyError``
    or a clear ``RuntimeError`` when attempting to mount a placeholder
    whose deps are satisfied."""
```

- [ ] **Step 4: Add `register_placeholder()` classmethod**

Inside the `ConnectorRegistry` class, place this method right before the existing `register()` classmethod:

```python
    @classmethod
    def register_placeholder(cls, entry: "ConnectorManifestEntry") -> None:
        """Register a manifest-sourced placeholder ConnectorInfo.

        Called during Phase 1 of ``_register_optional_backends()``, before
        any connector module is imported. The placeholder carries the
        manifest's metadata and runtime_deps but has ``connector_class=None``.

        If the connector module imports successfully later, its
        ``@register_connector("name")`` call hits the placeholder-binding
        path in :meth:`register` and attaches the real class (preserving
        manifest metadata). If the import fails, the placeholder remains
        — ``BackendFactory.create()`` will raise ``MissingDependencyError``
        with the manifest's install hints.
        """
        info = ConnectorInfo(
            name=entry.name,
            connector_class=None,
            description=entry.description,
            category=entry.category,
            runtime_deps=entry.runtime_deps,
            service_name=entry.service_name,
        )
        cls._base.register(entry.name, info, allow_overwrite=True)
```

Add the forward-reference import at the top of `registry.py` inside the existing `TYPE_CHECKING` block:

```python
if TYPE_CHECKING:
    from nexus.backends.base.backend import Backend
    from nexus.backends._manifest import ConnectorManifestEntry
```

- [ ] **Step 5: Extend `ConnectorRegistry.register()` with placeholder-binding path**

Find the existing-entry check in `register()` (around lines 394-410 — the `existing = cls._base.get(name)` block). Replace with:

```python
        existing = cls._base.get(name)
        if existing is not None:
            if existing.connector_class is None:
                # Placeholder-binding path. The manifest (via
                # register_placeholder) already set runtime_deps,
                # description, category, service_name. We attach
                # the real class and derive class-scoped fields
                # (user_scoped, config_mapping, backend_features).
                # Decorator kwargs other than `name` are IGNORED
                # here — manifest is the single source of truth for
                # built-in connectors. If the caller passed any such
                # kwargs, emit a UserWarning pointing them at the
                # manifest.
                decorator_provided_metadata = (
                    (description and description != existing.description)
                    or (category and category != "storage" and category != existing.category)
                    or requires is not None
                    or service_name is not None
                    or runtime_deps is not None
                )
                if decorator_provided_metadata:
                    warnings.warn(
                        f"@register_connector('{name}', ...): metadata kwargs "
                        f"ignored because '{name}' is declared in the manifest "
                        f"(src/nexus/backends/_manifest.py). Edit the manifest "
                        f"to change metadata for built-in connectors.",
                        UserWarning,
                        stacklevel=3,
                    )

                user_scoped = getattr(connector_class, "user_scoped", False)
                if isinstance(user_scoped, property):
                    user_scoped = False

                bound = ConnectorInfo(
                    name=name,
                    connector_class=connector_class,
                    description=existing.description,
                    category=existing.category,
                    user_scoped=user_scoped,
                    config_mapping=derive_config_mapping(connector_class),
                    service_name=existing.service_name,
                    backend_features=getattr(
                        connector_class, "_BACKEND_FEATURES", frozenset()
                    ),
                    runtime_deps=existing.runtime_deps,
                )
                cls._base.register(name, bound, allow_overwrite=True)
                return

            if existing.connector_class is not connector_class:
                raise ValueError(
                    f"Connector '{name}' is already registered to "
                    f"{existing.connector_class.__name__}. "
                    f"Cannot register {connector_class.__name__}."
                )
            # Same class; idempotent re-registration.
            return
```

- [ ] **Step 6: Import `warnings` at module top if not already there**

Check the top of `registry.py` for `import warnings`. If absent, add it to the stdlib import stanza.

- [ ] **Step 7: Verify tests pass**

```
PYTHONPATH=/Users/tafeng/nexus/.claude/worktrees/giggly-juggling-abelson/src pytest tests/unit/backends/test_registry.py -v --noconftest --override-ini="addopts="
```

Expected: all tests pass including the 4 new `TestPlaceholderRegistration` tests. Existing tests (particularly `TestRegisterRuntimeDeps`) should still pass — they use decorator arg without pre-existing placeholder, which hits the external-plugin branch.

- [ ] **Step 8: Commit**

```bash
git add src/nexus/backends/base/registry.py tests/unit/backends/test_registry.py
git commit -m "feat(backends): add register_placeholder + class-binding path (issue #3830 A.3)"
```

---

## Task 3: Factory — handle placeholder-still-unbound case

**Files:**
- Modify: `src/nexus/backends/base/factory.py`
- Test: `tests/integration/backends/test_factory_placeholder.py` (new)

- [ ] **Step 1: Write failing integration test**

Create `tests/integration/backends/test_factory_placeholder.py`:

```python
"""Factory behavior for placeholder ConnectorInfo entries (Issue #3830 A.3)."""

from __future__ import annotations

import pytest

from nexus.backends._manifest import ConnectorManifestEntry
from nexus.backends.base.factory import BackendFactory
from nexus.backends.base.registry import ConnectorRegistry
from nexus.backends.base.runtime_deps import BinaryDep, PythonDep
from nexus.contracts.exceptions import MissingDependencyError


@pytest.fixture(autouse=True)
def _clean_registry():
    names_before = set(ConnectorRegistry.list_available())
    yield
    for nm in set(ConnectorRegistry.list_available()) - names_before:
        ConnectorRegistry._base.unregister(nm)


class TestFactoryPlaceholder:
    def test_placeholder_with_missing_python_dep_raises_missing_dep(self) -> None:
        entry = ConnectorManifestEntry(
            name="ph_py_missing",
            module_path="nowhere.real",
            class_name="X",
            description="Placeholder",
            category="storage",
            runtime_deps=(
                PythonDep("definitely_not_a_real_module_xyz", extras=("gcs",)),
            ),
        )
        ConnectorRegistry.register_placeholder(entry)

        with pytest.raises(MissingDependencyError) as exc_info:
            BackendFactory.create("ph_py_missing", {})
        assert "pip install nexus-fs[gcs]" in str(exc_info.value)

    def test_placeholder_with_missing_binary_raises_missing_dep(self) -> None:
        entry = ConnectorManifestEntry(
            name="ph_bin_missing",
            module_path="nowhere.real",
            class_name="X",
            description="Placeholder",
            category="cli",
            runtime_deps=(
                BinaryDep("definitely_not_a_real_binary_xyz", "brew install xyz"),
            ),
        )
        ConnectorRegistry.register_placeholder(entry)

        with pytest.raises(MissingDependencyError) as exc_info:
            BackendFactory.create("ph_bin_missing", {})
        assert "brew install xyz" in str(exc_info.value)

    def test_placeholder_with_satisfied_deps_raises_module_failed(self) -> None:
        """Deps met but class_class still None means import failed for another
        reason (syntax error, etc.) — surface a clear RuntimeError."""
        entry = ConnectorManifestEntry(
            name="ph_deps_ok_import_bad",
            module_path="nowhere.real",
            class_name="X",
            description="Placeholder",
            category="storage",
            runtime_deps=(PythonDep("json"),),  # always present
        )
        ConnectorRegistry.register_placeholder(entry)

        with pytest.raises(RuntimeError) as exc_info:
            BackendFactory.create("ph_deps_ok_import_bad", {})
        msg = str(exc_info.value)
        assert "ph_deps_ok_import_bad" in msg
        assert "failed to import" in msg
```

- [ ] **Step 2: Run to verify failure**

```
PYTHONPATH=/Users/tafeng/nexus/.claude/worktrees/giggly-juggling-abelson/src pytest tests/integration/backends/test_factory_placeholder.py -v --noconftest --override-ini="addopts="
```

Expected: `test_placeholder_with_satisfied_deps_raises_module_failed` fails — today the factory proceeds to `connector_cls = info.connector_class` with `None`, then tries to instantiate `None(**kwargs)` → `TypeError`, not a clear `RuntimeError`.

- [ ] **Step 3: Add placeholder safety-net to `BackendFactory.create()`**

In `src/nexus/backends/base/factory.py`, find the existing dep-check block (the one added in A-full Task 6). After the `if missing: raise MissingDependencyError(...)` block and before `connector_cls = info.connector_class`, insert:

```python
        if info.connector_class is None:
            # Deps are satisfied (or manifest declared none) but the
            # placeholder was never bound — the connector's module
            # failed to import for a reason other than a missing Python
            # dep (syntax error, circular import, etc.). Surface a
            # clear error — check logs for the original ImportError.
            raise RuntimeError(
                f"Connector '{backend_type}' is declared in the "
                f"manifest but its module failed to import. Check "
                f"logs for the original ImportError."
            )
```

The full updated block is:

```python
        from nexus.backends.base.runtime_deps import check_runtime_deps
        from nexus.contracts.exceptions import MissingDependencyError

        missing = check_runtime_deps(info.runtime_deps)
        if missing:
            raise MissingDependencyError(backend=backend_type, missing=missing)

        if info.connector_class is None:
            raise RuntimeError(
                f"Connector '{backend_type}' is declared in the "
                f"manifest but its module failed to import. Check "
                f"logs for the original ImportError."
            )

        connector_cls = info.connector_class
```

- [ ] **Step 4: Verify tests pass**

```
PYTHONPATH=/Users/tafeng/nexus/.claude/worktrees/giggly-juggling-abelson/src pytest tests/integration/backends/test_factory_placeholder.py -v --noconftest --override-ini="addopts="
```

Expected: 3 passed. Also re-run the existing integration tests:

```
PYTHONPATH=/Users/tafeng/nexus/.claude/worktrees/giggly-juggling-abelson/src pytest tests/integration/backends/test_factory_dep_check.py -v --noconftest --override-ini="addopts="
```

Expected: 4 passed (unchanged — those tests register with real classes, not placeholders).

- [ ] **Step 5: Commit**

```bash
git add src/nexus/backends/base/factory.py tests/integration/backends/test_factory_placeholder.py
git commit -m "feat(backends): surface RuntimeError when placeholder stays unbound (issue #3830 A.3)"
```

---

## Task 4: Rewrite `_register_optional_backends()` to use the manifest

**Files:**
- Modify: `src/nexus/backends/__init__.py`

- [ ] **Step 1: Read current `_register_optional_backends()` behavior**

Before editing, read the current `src/nexus/backends/__init__.py` lines 101-191 to understand the existing flow (manifest-free import loop + entry-points scan + YAML-config loop). Preserve Phase 3 (entry points + YAML) untouched.

- [ ] **Step 2: Rewrite the function**

Replace the body of `_register_optional_backends()` (inside the lock) with:

```python
def _register_optional_backends() -> None:
    """Pre-register manifest placeholders, then attempt module imports.

    Phase 1: read ``CONNECTOR_MANIFEST`` and register a placeholder
    ``ConnectorInfo`` for every entry. No imports happen.

    Phase 2: import each manifest entry's ``module_path``. On success the
    module's ``@register_connector("name")`` binds the real class into
    the placeholder (see ``ConnectorRegistry.register()``). On ImportError
    the placeholder remains — ``BackendFactory.create()`` will raise
    ``MissingDependencyError`` with the manifest's install hints.

    Phase 3: scan external entry points + YAML configs (unchanged).
    """
    global _optional_backends_registered

    if _optional_backends_registered:
        return
    with _registration_lock:
        if _optional_backends_registered:
            return
        _optional_backends_registered = True

        from nexus.backends._manifest import CONNECTOR_MANIFEST
        from nexus.backends.base.registry import ConnectorRegistry

        # Phase 1: placeholders from manifest
        for entry in CONNECTOR_MANIFEST:
            ConnectorRegistry.register_placeholder(entry)

        # Phase 2: attempt module imports; successful imports run
        # @register_connector which binds the class into the placeholder.
        seen_modules: set[str] = set()
        for entry in CONNECTOR_MANIFEST:
            if entry.module_path in seen_modules:
                continue
            seen_modules.add(entry.module_path)
            try:
                importlib.import_module(entry.module_path)
            except ImportError as e:
                _logger.debug(
                    "Connector module %s not available: %s "
                    "(placeholder stays; mount will raise MissingDependencyError)",
                    entry.module_path, e,
                )

        # Phase 3: external plugins via entry points
        try:
            from importlib.metadata import entry_points

            for ep in entry_points(group="nexus.connectors"):
                try:
                    ep.load()
                except (ImportError, ModuleNotFoundError):
                    _logger.debug(
                        "Connector plugin %s not installed, skipping", ep.name
                    )
                except Exception:
                    _logger.warning(
                        "Connector plugin %s failed to load", ep.name, exc_info=True
                    )
        except Exception:
            _logger.debug("Entry point scanning unavailable")

        # Phase 3 (cont.): CLI connector configs from config directory
        config_dir_env = os.getenv("NEXUS_CONNECTORS_DIR")
        config_dirs = []
        if config_dir_env:
            config_dirs.append(Path(config_dir_env))
        config_dirs.append(Path.home() / ".nexus" / "connectors")

        for config_dir in config_dirs:
            if not config_dir.is_dir():
                continue
            try:
                from nexus.backends.connectors.cli.loader import (
                    create_connector_class_from_yaml,
                    load_all_configs,
                )

                configs = load_all_configs(config_dir)
                for name, config in configs.items():
                    try:
                        connector_cls = create_connector_class_from_yaml(name, config)
                        ConnectorRegistry.register(
                            name=f"cli:{name}",
                            connector_class=connector_cls,
                            description=f"CLI connector: {config.cli} {config.service}",
                            category="cli",
                        )
                        _logger.info(
                            "Registered CLI connector from config: %s (%s %s)",
                            name, config.cli, config.service,
                        )
                    except Exception:
                        _logger.warning(
                            "Failed to register CLI connector %s from %s",
                            name, config_dir, exc_info=True,
                        )
            except (ImportError, ModuleNotFoundError):
                _logger.debug("CLI connector loader not available")
                break
```

Add the required top-of-file imports if missing: `import os`, `from pathlib import Path`.

- [ ] **Step 3: Update `_OPTIONAL_BACKENDS` usage**

The existing `_OPTIONAL_BACKENDS` dict at the top of the file is used by the legacy `__getattr__` for `from nexus.backends import PathGCSBackend` style access.

Replace the hardcoded dict with a manifest-derived view — add right after the `CONNECTOR_MANIFEST` gets introduced:

```python
# Derived from CONNECTOR_MANIFEST for backward-compatible attribute access:
#   `from nexus.backends import PathGCSBackend` → lazily imports
# the module and returns the named attribute.
_OPTIONAL_BACKENDS: dict[str, tuple[str, str]] = {}

def _populate_optional_backends_map() -> None:
    """Populate _OPTIONAL_BACKENDS from the manifest (one-time)."""
    from nexus.backends._manifest import CONNECTOR_MANIFEST
    for entry in CONNECTOR_MANIFEST:
        _OPTIONAL_BACKENDS[entry.class_name] = (entry.module_path, entry.class_name)

_populate_optional_backends_map()
```

This preserves the existing `__getattr__` behavior without duplicating the data.

Delete the large `_OPTIONAL_BACKENDS = {...}` literal block (the one that lists `CASLocalBackend`, `PathGCSBackend`, etc.).

- [ ] **Step 4: Run the full suite to verify**

```
PYTHONPATH=/Users/tafeng/nexus/.claude/worktrees/giggly-juggling-abelson/src pytest tests/unit/backends/ tests/integration/backends/ -v --noconftest --override-ini="addopts=" -x
```

Expected: all passing (except pre-existing unrelated `test_wrap_compressed`).

If a connector test fails because `@register_connector` on a built-in connector now emits the manifest-override `UserWarning`, remember that Task 5 hasn't stripped those kwargs yet. Temporarily suppress the warning in tests that construct the warning condition: wrap in `warnings.catch_warnings()` + `simplefilter("ignore", UserWarning)`.

Actually better approach: re-order and do Task 5 FIRST before this one lands cleanly. The xfail-ish pattern is ugly. Swap Task 4 and Task 5 order: strip metadata kwargs from built-in decorators first, THEN enable the manifest flow.

For this plan as written, proceed: run tests, note any warning-induced failures, and skip to Task 5 to fix them properly.

- [ ] **Step 5: Commit**

```bash
git add src/nexus/backends/__init__.py
git commit -m "feat(backends): wire _register_optional_backends through CONNECTOR_MANIFEST (issue #3830 A.3)"
```

---

## Task 5: Strip metadata from built-in `@register_connector` calls

**Files:** All 22 built-in connector files (see File Structure section).

Goal: every `@register_connector(...)` call on a built-in connector becomes `@register_connector("<name>")` — name only. Drop `description`, `category`, `runtime_deps`, `service_name`. Remove imports of `PythonDep`/`BinaryDep`/`ServiceDep` where they were only used for the decorator. Remove the `_GWS_RUNTIME_DEPS` and `_GH_RUNTIME_DEPS` module-level constants from gws/connector.py and github/connector.py.

- [ ] **Step 1: Storage — simplify 6 files**

For each of the 6 storage files, collapse the decorator to name-only.

`src/nexus/backends/storage/path_gcs.py` — find the existing `@register_connector(...)` block (lines ~39-45 per A-full) and replace with:

```python
@register_connector("path_gcs")
```

Remove the line `from nexus.backends.base.runtime_deps import PythonDep` if it's only used in that decorator.

Same shape for:
- `cas_gcs.py` → `@register_connector("cas_gcs")`
- `path_s3.py` → `@register_connector("path_s3")`
- `path_local.py` → `@register_connector("path_local")`
- `cas_local.py` → `@register_connector("cas_local")`
- `local_connector.py` → `@register_connector("local_connector")`

- [ ] **Step 2: API connectors — simplify 6 files (7 sites including calendar ×2)**

Same pattern. Collapse each to name-only:
- `gdrive/connector.py` → `@register_connector("gdrive_connector")`
- `gmail/connector.py` → `@register_connector("gmail_connector")`
- `calendar/connector.py` — two decorators — `@register_connector("calendar_connector")` and `@register_connector("gcalendar_connector")`
- `x/connector.py` → `@register_connector("x_connector")`
- `slack/connector.py` → `@register_connector("slack_connector")`
- `hn/connector.py` → `@register_connector("hn_connector")`

Remove `from nexus.backends.base.runtime_deps import PythonDep` from each file where it's no longer used.

- [ ] **Step 3: Compute connectors — simplify 2 files**

- `anthropic_native.py` → `@register_connector("anthropic_native")`
- `openai_compatible.py` → `@register_connector("openai_compatible")`

Remove `PythonDep` imports.

- [ ] **Step 4: CLI connectors — simplify gws (6 sites) + github (2 sites)**

In `src/nexus/backends/connectors/gws/connector.py`:
- Collapse all 6 `@register_connector(...)` decorators to name-only:
  - `@register_connector("gws_gmail")`
  - `@register_connector("gws_calendar")`
  - `@register_connector("gws_sheets")`
  - `@register_connector("gws_docs")`
  - `@register_connector("gws_chat")`
  - `@register_connector("gws_drive")`
- Remove the `_GWS_RUNTIME_DEPS = (...)` module-level constant and its `BinaryDep, ServiceDep` import.

In `src/nexus/backends/connectors/github/connector.py`:
- Collapse both stacked decorators to:
  ```python
  @register_connector("github_connector")
  @register_connector("gws_github")
  class GitHubConnector(PathCLIBackend):
      ...
  ```
- Remove the `_GH_RUNTIME_DEPS = (...)` module-level constant and its `BinaryDep, ServiceDep` import.

- [ ] **Step 5: Run full suite, expect manifest-override warnings to be gone**

```
PYTHONPATH=/Users/tafeng/nexus/.claude/worktrees/giggly-juggling-abelson/src pytest tests/unit/backends/ tests/integration/backends/ -v --noconftest --override-ini="addopts=" -x
```

Expected: all pass. The `TestPlaceholderRegistration.test_register_emits_warning_when_builtin_passes_metadata` test explicitly constructs the warning condition so it keeps passing. Other connector-import paths should be silent.

- [ ] **Step 6: Smoke test every connector loads with correct manifest metadata**

```
PYTHONPATH=/Users/tafeng/nexus/.claude/worktrees/giggly-juggling-abelson/src python -c "
from nexus.backends import _register_optional_backends
_register_optional_backends()
from nexus.backends.base.registry import ConnectorRegistry
for info in ConnectorRegistry.list_all():
    bound = 'BOUND' if info.connector_class is not None else 'PLACEHOLDER'
    print(f'{info.name:<25} {bound:<12} runtime_deps={len(info.runtime_deps)}')
"
```

Expected: every manifest connector listed, either BOUND (module imported OK in this env) or PLACEHOLDER (import failed — typically because `nexus.server` or some module is slim-stripped in this environment). Runtime_deps count matches manifest.

- [ ] **Step 7: Commit**

```bash
git add src/nexus/backends/storage/ src/nexus/backends/connectors/ src/nexus/backends/compute/
git commit -m "refactor(backends): collapse built-in @register_connector calls to name-only (issue #3830 A.3)"
```

---

## Task 6: C — expand pip extras

**Files:**
- Modify: `packages/nexus-fs/pyproject.toml`

- [ ] **Step 1: Update the `[project.optional-dependencies]` table**

Open `packages/nexus-fs/pyproject.toml`. Find the `[project.optional-dependencies]` section (around lines 46-58). Replace the entire section with:

```toml
[project.optional-dependencies]
s3 = ["boto3>=1.28"]
gcs = ["google-cloud-storage>=2.0"]
gdrive = [
    "google-api-python-client>=2.0",
    "google-auth-oauthlib>=1.0",
]
gmail = [
    "google-api-python-client>=2.0",
    "google-auth-oauthlib>=1.0",
]
gcalendar = [
    "google-api-python-client>=2.0",
    "google-auth-oauthlib>=1.0",
]
slack = ["slack-sdk>=3.0"]
anthropic = ["anthropic>=0.30"]
openai = ["openai>=1.0"]
x = ["requests-oauthlib>=1.3"]
edit = ["rapidfuzz>=3.0"]
fsspec = ["fsspec>=2024.0"]
tui = ["textual>=1.0"]
all = [
    "nexus-fs[s3,gcs,gdrive,gmail,gcalendar,slack,anthropic,openai,x,fsspec,tui,edit]",
]
dev = [
    "nexus-fs[all]",
    "pytest>=8.0",
    "pytest-asyncio>=0.23",
    "pytest-cov>=5.0",
    "pytest-textual-snapshot>=1.0",
    "moto[s3]>=5.0",
    "ruff>=0.4",
    "mypy>=1.10",
]
```

- [ ] **Step 2: Verify the TOML parses**

```
python -c "import tomllib; tomllib.loads(open('packages/nexus-fs/pyproject.toml').read())"
```

Expected: no exceptions.

- [ ] **Step 3: Sanity check — each manifest-referenced extra exists**

```
python -c "
import tomllib, json
with open('packages/nexus-fs/pyproject.toml', 'rb') as f:
    data = tomllib.load(f)
extras = set(data['project']['optional-dependencies'].keys())
needed = {'s3', 'gcs', 'gdrive', 'gmail', 'gcalendar', 'slack', 'anthropic', 'openai', 'x'}
missing = needed - extras
print('missing extras:', sorted(missing) or 'none')
print('present:', sorted(needed & extras))
"
```

Expected: `missing extras: none`.

- [ ] **Step 4: Commit**

```bash
git add packages/nexus-fs/pyproject.toml
git commit -m "chore(nexus-fs): add extras for gmail/gcalendar/slack/anthropic/openai/x (issue #3830 C)"
```

---

## Task 7: A.2 — update `mount_service.py:842` to read runtime_deps directly

**Files:**
- Modify: `src/nexus/bricks/mount/mount_service.py`

- [ ] **Step 1: Read the current call site**

Open `src/nexus/bricks/mount/mount_service.py`. Around line 842 the code reads:

```python
                "requires": c.requires,
```

where `c` is a `ConnectorInfo`. The derived `requires` property returns `[d.module for d in c.runtime_deps if isinstance(d, PythonDep)]`.

- [ ] **Step 2: Replace with direct read**

Replace the line with:

```python
                "requires": [
                    d.module
                    for d in c.runtime_deps
                    if isinstance(d, PythonDep)
                ],
```

Add `from nexus.backends.base.runtime_deps import PythonDep` near the top of the file (grouped with other nexus.backends imports).

- [ ] **Step 3: Run existing mount-service tests**

```
PYTHONPATH=/Users/tafeng/nexus/.claude/worktrees/giggly-juggling-abelson/src pytest tests/unit/bricks/mount/ -v --noconftest --override-ini="addopts="
```

Expected: pass. If a test snapshot compares the full response dict, the `"requires"` value shape is unchanged (still a `list[str]` of module names).

- [ ] **Step 4: Commit**

```bash
git add src/nexus/bricks/mount/mount_service.py
git commit -m "refactor(mount): read runtime_deps directly (issue #3830 A.2)"
```

---

## Task 8: A.2 — delete the deprecated `requires` property

**Files:**
- Modify: `src/nexus/backends/base/registry.py`
- Modify: `tests/unit/backends/test_registry.py`

- [ ] **Step 1: Delete the property**

In `src/nexus/backends/base/registry.py`, find the `@property requires` on `ConnectorInfo` (added in A-full Task 4). Delete the entire property definition (the `@property` decorator, the method, and its docstring — about 10 lines).

- [ ] **Step 2: Delete the two scaffolding tests**

In `tests/unit/backends/test_registry.py`, find and delete:
- `test_requires_property_derives_from_python_deps`
- `test_requires_property_empty_when_no_python_deps`

Inside the `TestConnectorInfoRuntimeDeps` class (added in A-full Task 4). Keep the other two tests in that class (`test_default_empty_tuple`, `test_runtime_deps_stored`) — they cover the field itself.

- [ ] **Step 3: Confirm no other readers exist**

```
grep -rn "\.requires\b" src/nexus/backends/ src/nexus/bricks/ src/nexus/server/ src/nexus/cli/ tests/
```

Any remaining `.requires` hits should be on unrelated objects (`PluginMetadata.requires`, `MigrationStep.requires_backup`). Confirm each one.

- [ ] **Step 4: Run full suite**

```
PYTHONPATH=/Users/tafeng/nexus/.claude/worktrees/giggly-juggling-abelson/src pytest tests/unit/backends/ tests/integration/backends/ -v --noconftest --override-ini="addopts=" -x
```

Expected: all pass. If a test you didn't touch references `info.requires`, fix it to read `runtime_deps` inline.

- [ ] **Step 5: Commit**

```bash
git add src/nexus/backends/base/registry.py tests/unit/backends/test_registry.py
git commit -m "refactor(backends): delete deprecated ConnectorInfo.requires property (issue #3830 A.2)"
```

---

## Task 9: Final verification + PR prep

- [ ] **Step 1: Full test suite**

```
PYTHONPATH=/Users/tafeng/nexus/.claude/worktrees/giggly-juggling-abelson/src pytest tests/unit/ tests/integration/ --noconftest --override-ini="addopts=" -q
```

Expected: all pass. Note the pre-existing unrelated `test_wrap_compressed` failure remains.

- [ ] **Step 2: Grep sanity — no lingering `requires=[`, no lingering class-attr RUNTIME_DEPS**

```
grep -rn "requires=\[" src/nexus/backends/
grep -rn "RUNTIME_DEPS" src/nexus/backends/
```

Expected: zero matches for `requires=[`. `RUNTIME_DEPS` may appear in `registry.py` / `_manifest.py` / tests as legitimate references to the concept; only the class-attribute assignments inside connector modules should be gone.

- [ ] **Step 3: Count manifest entries matches registered connectors**

```
PYTHONPATH=/Users/tafeng/nexus/.claude/worktrees/giggly-juggling-abelson/src python -c "
from nexus.backends._manifest import CONNECTOR_MANIFEST
print(f'manifest entries: {len(CONNECTOR_MANIFEST)}')
from nexus.backends import _register_optional_backends
_register_optional_backends()
from nexus.backends.base.registry import ConnectorRegistry
all_names = {info.name for info in ConnectorRegistry.list_all()}
manifest_names = {e.name for e in CONNECTOR_MANIFEST}
missing = manifest_names - all_names
extra = all_names - manifest_names
print(f'registered but not in manifest: {sorted(extra)}')
print(f'manifest but not registered: {sorted(missing)}')
"
```

Expected: no missing/extra (modulo any CLI-YAML connectors or external-plugin registrations in Phase 3).

- [ ] **Step 4: Push + open PR**

```bash
git push -u origin <current-branch>
```

PR title: `feat(backends): connector manifest + pip extras + cleanup (Issue #3830 A.2 + A.3 + C)`.

PR body: "Stacked on #3834 — merges after A-full lands. See `docs/superpowers/specs/2026-04-21-issue-3830-batch1-design.md`. Closes the registration-vs-mount gap flagged in A-full's PR body: primary-Python-dep-missing connectors now raise `MissingDependencyError` instead of `RuntimeError('Unsupported backend type')`."

---

## Self-review notes

Spec coverage:
- A.3 §1 (manifest) → Task 1
- A.3 §2 (register_placeholder, ConnectorInfo type change) → Task 2
- A.3 §3 (register() binding path + warning) → Task 2
- A.3 §4 (factory placeholder safety net) → Task 3
- A.3 §5 (rewrite _register_optional_backends) → Task 4
- A.3 §6 (strip class-level RUNTIME_DEPS + decorator kwargs on built-ins) → Task 5
- A.3 §7 (tests) → Tasks 1, 2, 3 (new/extended test files)
- C (extras) → Task 6
- A.2 (update mount_service.py + delete property + tests) → Tasks 7 and 8
- Final verification → Task 9

No placeholders ("TBD"/"TODO"/"similar to Task N") in the steps.

Type consistency:
- `ConnectorManifestEntry` fields consistent across Tasks 1-5.
- `ConnectorInfo.connector_class: type[Backend] | None` consistent in Tasks 2 and 3.
- `register_placeholder`, `register()` binding path, factory safety net all reference the `connector_class is None` predicate consistently.

Known gap:
- Task 4's note about manifest UserWarning firing until Task 5 strips decorator kwargs is real; implementer may prefer to execute Task 5 before Task 4. That's fine — the final diff is identical. Document the swap if done.
