# VLM Provider Trait + Disabled Default Impl + Feature Flag

**Task**: AZ-672_vlm_client_provider_trait
**Name**: VlmAssessmentProvider trait + default disabled impl + build-time feature gating
**Description**: Define `VlmAssessmentProvider` trait (in `shared::contracts`) and a default impl that always returns `status: disabled`. The `vlm_client` crate is behind a build-time feature flag; with the feature off the default impl is used and the binary builds + runs identically without `vlm_client`.
**Complexity**: 2 points
**Dependencies**: AZ-640_initial_structure
**Component**: vlm_client
**Tracker**: AZ-672
**Epic**: AZ-631

## Problem

VLM is optional in two ways: at runtime (`vlm_enabled` flag) and at build time (`vlm_client` Cargo feature). `scan_controller` depends only on the trait — never on the `vlm_client` crate directly — so the binary builds and runs with VLM absent. The default trait impl returns `status: disabled` so the call-site code path is identical whether VLM is enabled or absent.

## Outcome

- `VlmAssessmentProvider` trait in `shared::contracts::vlm`:
  ```text
  trait VlmAssessmentProvider {
      async fn assess(&self, roi_crop: &RoiCrop, prompt: &str) -> VlmAssessment;
  }
  ```
- Default impl `DisabledVlmProvider` returns `VlmAssessment { status: Disabled, .. }` for every call.
- `vlm_client` Cargo feature gates inclusion of the real `vlm_client` crate; with feature off, only `DisabledVlmProvider` is registered.
- Runtime flag `vlm_enabled = false` causes the composition root to install `DisabledVlmProvider` even when the feature is compiled in.

## Scope

### Included
- Trait definition in `shared::contracts::vlm`.
- `DisabledVlmProvider` default impl (also in `shared` so it's available regardless of feature).
- Cargo feature flag wiring in `Cargo.toml` (workspace + binary).
- Runtime flag plumb from config.

### Excluded
- The real NanoLLM IPC client (task 34).
- Schema validation (task 35).

## Acceptance Criteria

**AC-1: Disabled default returns disabled status**
Given a `DisabledVlmProvider`
When `assess(roi, "...")` is called
Then it returns `VlmAssessment { status: Status::Disabled, .. }` immediately (≤1 ms).

**AC-2: Binary builds without vlm_client feature**
Given the binary is built with `--no-default-features` (or whatever toggles the `vlm_client` feature off)
When the build runs
Then it succeeds; the `vlm_client` crate is NOT a build dependency.

**AC-3: Runtime vlm_enabled = false uses disabled impl**
Given the binary is built WITH the `vlm_client` feature but config sets `vlm_enabled = false`
When the composition root constructs the provider
Then `DisabledVlmProvider` is installed; the real NanoLLM client is NOT constructed.

## Non-Functional Requirements

**Performance**
- `DisabledVlmProvider::assess` ≤1 ms.

## Contract

- Canonical typed model: `data_model.md §VlmAssessment`.

## Runtime Completeness

- **Named capability**: optional-VLM trait + disabled default.
- **Production code that must exist**: real trait; real disabled impl; real feature-flag wiring.
- **Unacceptable substitutes**: hardcoding `vlm_client` as a non-optional dependency is unacceptable per `description.md §9 Optionality Model`.
