# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.3] - 2026-03-19

### Fixed
- **[CRITICAL] Streaming Calls Not Tracked Under instrument()** (Streaming Auto-Context Bug)
  - Problem: `instrument()` correctly tracked non-streaming calls (fixed in v0.2.2) but streaming calls (`stream=True`) still silently dropped events. `StreamWrapper._capture_to_context()` checked `if ctx is None: return`, finding no active context and doing nothing.
  - Root Cause: The v0.2.2 fix used `auto_context_for_instrumented_call` at call-time for non-streaming responses. For streams, context creation needed to happen at stream-completion time (inside `StreamWrapper`), not at API-call time.
  - Solution: Updated `StreamWrapper._capture_to_context()` in OpenAI, Anthropic, and Vertex AI providers to use `auto_context_for_instrumented_call` when no active context is found at stream exhaustion.
  - Affected providers: OpenAI (sync + async), Anthropic (sync + async), Vertex AI (sync + async). Google GenAI was already correct.
  - No double-emission: `auto_context_for_instrumented_call` is a no-op when a manual `wrap()` context is active, preventing duplicate events.
  - Test coverage: Added `tests/test_streaming_instrument.py` with 11 cases covering all providers, error paths, and the no-double-emit guarantee.

- **Failing test: `test_instrument_genai_module_returns_false_when_not_installed`**
  - `google-genai` is installed in the dev/CI environment, so the test needed to mock the import rather than assume it was absent.
  - Fixed with `patch.dict(sys.modules, {"google.genai": None})` + reset of `_module_instrumented` state.

### Added
- **VETCH_ENDPOINT: First-Class HTTP Output**
  - `VETCH_ENDPOINT=https://your-endpoint.example.com/ingest` now wires up the HTTP emitter unconditionally — no `VETCH_ENABLE_REMOTE=true` flag required.
  - `VETCH_API_KEY=...` sends `Authorization: Bearer {key}` on every POST. Leave unset for internal/firewall-protected endpoints that don't require auth.
  - Legacy `VETCH_OUTPUT=https://...` still works but now prints a hint to use `VETCH_ENDPOINT` instead.
  - Rate-limited error logging in `HttpHandler`: connection failures log at most once per minute to stderr (previously silent).

- **`vetch.configure_http_endpoint(url, api_key=None)`** — programmatic alternative to `VETCH_ENDPOINT` env var. Useful for multi-destination routing or dynamic configuration.

- **QUICKSTART-SEND.md** — new guide covering all output destinations: local stderr, internal HTTP endpoints, OTLP stacks, file output, multi-destination routing, and green routing with `get_cleanest_region()`.

- **METHODOLOGY.md: SDK Instrumentation Model section**
  - Documents that `instrument()` is production-ready as of v0.2.2.
  - Explains the auto-context lifecycle for both non-streaming and streaming calls.
  - Clarifies the `wrap()` vs `instrument()` relationship and priority.

### Changed
- `HttpHandler` now accepts `api_key: str | None = None` parameter (backward compatible).
- `_configure_logging()` checks `VETCH_ENDPOINT` before `VETCH_OUTPUT` on module import.

## [0.2.2] - 2026-03-16

### Fixed
- **[CRITICAL] instrument() Not Tracking Calls Without wrap()** (Auto-Context Creation Bug)
  - Problem: `instrument()` patched LLM SDK clients but didn't automatically track calls. Users still needed explicit `wrap()` context managers, contradicting the docstring promise of "All calls are now automatically tracked!"
  - Root Cause: All provider wrappers checked `if ctx is None: return` and skipped tracking when no context was active. Since `instrument()` didn't create automatic contexts, tracking never happened.
  - Solution: Implemented automatic context creation in all provider wrappers. When no active context exists, wrappers now auto-create an implicit `VetchContext` using defaults from `instrument(region=..., tags=...)`
  - Changes:
    - Added `_default_tags` storage in `vetch/__init__.py` to store tags from `instrument()`
    - Added `get_default_tags()` function to retrieve stored default tags
    - Updated all provider wrappers to auto-create contexts when `ctx is None`:
      - Google GenAI: `_WeakMethodWrapper`, `_WeakAsyncMethodWrapper`, `_WeakEmbedWrapper`
      - OpenAI: `_after_create()`, `_on_create_error()`, `_after_embeddings_create()`, `_on_embeddings_error()`
      - Anthropic: `_after_create()`, `_on_create_error()`
      - Vertex AI: `_after_generate()`, `_on_generate_error()`
      - Azure OpenAI: Automatically covered (uses OpenAI's patching logic)
  - Impact: `instrument()` now works as documented - truly automatic tracking without needing `wrap()`
  - Backward Compatible: Manual `wrap()` usage continues to work perfectly, with tags merging correctly
  - Test Coverage: Added comprehensive integration tests in `tests/test_auto_context.py`

## [0.2.1] - 2026-03-09

### Added
- **VETCH_ENABLED Environment Variable**: Complement to VETCH_DISABLED for more intuitive control
  - Set `VETCH_ENABLED=false` to disable tracking (equivalent to `VETCH_DISABLED=true`)
  - Default: `true` (tracking enabled)
  - Priority: `VETCH_DISABLED` takes precedence over `VETCH_ENABLED` for backward compatibility
  - Example: `export VETCH_ENABLED=false && python app.py`

- **Thread-Safe Instrumentation**: Added `threading.Lock` to `instrument()` function
  - Prevents race conditions during concurrent initialization
  - Safe for multi-threaded applications calling `instrument()` at startup
  - No performance impact on single-threaded usage

- **Examples Directory**: Added comprehensive auto-instrumentation examples
  - `examples/auto_instrument_example.py`: Demonstrates one-line setup for all providers
  - `examples/opentelemetry_example.py`: Full OTel integration guide
  - `examples/README.md`: Documentation for examples directory
  - Shows Google GenAI, OpenAI, and Anthropic usage patterns

### Changed
- **Instrumentation Documentation**: Updated docstrings to clarify thread-safety and env var behavior
- **Kill Switch Feedback**: Made disabled tracking message opt-in via `VETCH_VERBOSE=true`
  - Previously: Message printed to stderr on every import when `VETCH_DISABLED=true` or `VETCH_ENABLED=false`
  - Now: Silent by default, only prints when `VETCH_VERBOSE=true` (opt-in for debugging)
  - Rationale: Reduces noise for "quiet" CLI tools and production environments

### Performance
- **Config Module Hotpath Optimization**: Reduced overhead in `get_config()` and `_normalize_region()`
  - Lazy imports for optional dependencies to reduce import-time cost
  - Cached config values to avoid repeated environment variable lookups
  - ~15-20% reduction in overhead for high-throughput scenarios

- **Google GenAI Provider Optimization**: Reduced method call overhead using weak references
  - Eliminated circular reference patterns that prevented garbage collection
  - Used `__slots__` in wrapper classes to reduce memory footprint
  - Faster patching/unpatching with fewer dict operations

### Fixed
- **[CRITICAL] Memory Leak in Google GenAI Provider** (Issue #1): Fixed circular references in client patching
  - Problem: Closures capturing client references created GC cycles (`client → method → closure → client`)
  - Solution: Implemented `_WeakMethodWrapper` classes using `weakref.ref(client)` to break cycles
  - Impact: Long-running services with frequent client instantiation no longer leak memory

- **[CRITICAL] Reasoning Tokens Not Tracked** (Issue #2): Added support for extended thinking models
  - Problem: Gemini 2.0 Flash Thinking and similar models return `thought_token_count` in usage metadata
  - Solution: Extract reasoning tokens as separate modality in usage dict (`usage["reasoning"]`)
  - Impact: Reasoning tokens can be 10x+ the visible output and were previously uncounted

- **[CRITICAL] Azure OpenAI Region Inference Failing** (Issue #3): Fixed URL parsing for custom domains
  - Problem: `extract_region_from_azure_endpoint()` assumed standard `*.openai.azure.com` format
  - Solution: Added fallback to deployment location header and better error handling
  - Impact: Azure deployments with custom domains now correctly infer region for carbon calculation

- **[CRITICAL] Session Aggregation Race Condition** (Issue #4): Thread-safe session metric accumulation
  - Problem: Multiple threads calling `session.add_event()` simultaneously could corrupt totals
  - Solution: Added `threading.Lock` around all session metric updates
  - Impact: Multi-threaded applications with sessions no longer risk incorrect cost/energy totals

- **[BUG] OpenAI Streaming Chunks Missing Token Counts** (Issue #5): Defensive access for partial chunks
  - Problem: Some streaming chunks lack `usage` field entirely, causing AttributeError
  - Solution: Use `getattr(chunk, "usage", None)` with None fallback
  - Impact: Streaming no longer crashes on partial chunks from OpenAI's API

- **[BUG] Anthropic Cache Tokens Miscounted** (Issue #6): Correct field names for prompt caching
  - Problem: Used `cache_read_input_tokens` instead of correct `cache_read_tokens` field name
  - Solution: Updated field extraction to match Anthropic API v2025-01 specification
  - Impact: Cache hit tracking now accurate for Anthropic Claude models

- **Type Checking**: Resolved all mypy strict mode errors and ruff linting issues
  - Added missing type annotations for Python 3.9 compatibility
  - Fixed Union type syntax (use `Union[X, Y]` instead of `X | Y` for Python 3.9)
  - Removed unused imports and variables flagged by ruff

- **Provider Wrapper __slots__**: Fixed dynamic attribute assignment in wrapper classes
  - Problem: Wrapper classes used `__slots__` but attempted to set `vetch_patched` attribute dynamically
  - Solution: Added `"vetch_patched"` and `"_vetch_original"` to `__slots__` tuples
  - Affected providers: Anthropic, OpenAI, VertexAI
  - Impact: Provider patching tests now pass consistently (fixed 5 failing integration tests)

- **Version Consistency**: Synchronized `__init__.py` version with `pyproject.toml`

## [0.2.0] - 2026-03-08

### Added - Google GenAI Provider
- **Google GenAI SDK Support**: Native integration with `google-genai` Python SDK
  - Automatic tracking for `client.models.generate_content()` (sync and async)
  - Embedding support via `client.models.embed_content()`
  - Streaming response tracking
  - Model name normalization (strips "models/" prefix and version suffixes like "-001")
  - Thread-safe per-client patching using WeakKeyDictionary
  - Privacy guarantee: only reads usage metadata, not prompt/completion content
  - Install with: `pip install vetch[genai]`
  - Example:
    ```python
    import google.genai as genai
    import vetch

    vetch.instrument()  # Auto-instruments all GenAI clients
    client = genai.Client(api_key="...")
    response = client.models.generate_content(model="gemini-2.0-flash", contents="Hello!")
    # Events automatically emitted with energy/carbon/cost
    ```

### Added - OpenTelemetry Semantic Conventions Exporter
- **GenAI Semantic Conventions**: Export vetch events as OpenTelemetry spans
  - Follows OpenTelemetry GenAI semantic conventions (v1.28+)
  - Required attributes: `gen_ai.system`, `gen_ai.request.model`, `gen_ai.usage.*`
  - Custom vetch attributes: `vetch.cost.*`, `vetch.energy.*`, `vetch.carbon.*`, `vetch.water.*`
  - Auto-export mode: `vetch.configure_otel_export(enabled=True)` automatically exports on context exit
  - Manual export: `vetch.export_event_as_span(event, tracer=tracer, parent_span=parent)`
  - Graceful degradation if `opentelemetry-api` not installed
  - Install with: `pip install vetch[opentelemetry]`
  - Example:
    ```python
    import vetch
    from opentelemetry import trace

    vetch.configure_otel_export(enabled=True)  # Enable auto-export

    with vetch.wrap() as ctx:
        response = client.chat.completions.create(...)
    # Span automatically created with GenAI semantic conventions + vetch attributes
    ```

### Added - Energy Calibration Tools
- **Calibration Methodology Framework**: Reverse-engineer per-token energy from aggregate measurements
  - `calibrate_from_measurement()`: Derive Wh/1k values from published datacenter measurements
  - `validate_calibration()`: Verify calibrated values reproduce original measurements
  - `explore_scenarios()`: Generate multiple calibration scenarios for uncertainty analysis
  - `propagate_to_family()`: Apply efficiency ratios to related models
  - `format_calibration_table()`: Generate markdown tables for documentation
  - Comprehensive documentation with formula derivations, assumptions, and validation
  - Example:
    ```python
    from vetch.tools.calibration import calibrate_from_measurement

    result = calibrate_from_measurement(
        measurement_wh=0.24,  # Google's published measurement
        pue=1.10,             # Google's PUE
        median_tokens=800,    # Estimated median prompt size
    )
    print(f"Input: {result.energy_per_1k_input:.3f} Wh/1k")
    print(f"Output: {result.energy_per_1k_output:.3f} Wh/1k")
    ```
- **Gemini Calibration Document**: `GEMINI_CALIBRATION.md` with full methodology
  - Anchored to Google's 0.24 Wh per median Gemini Apps prompt (August 2025)
  - 5 scenarios: Very Conservative (40 tokens), Conservative (400), Moderate (800), Optimistic (1600), Maximum (4000)
  - Transparent assumptions: PUE 1.10, output/input ratio 3:1, 50/50 input/output split
  - Validation script reproduces Google's measurement with <1% error
  - Critical assumption: Median prompt = 1,600 tokens (if actually 400 tokens, values would be 4x higher)

### Added - Tiered Pricing
- **Tiered Pricing Support**: Accurate cost calculation for long-context models
  - Gemini Pro models charge 2x per token for >128k context window
  - Schema: `tier_threshold` (128000) and `tier_multiplier` (2.0) in `pricing.json`
  - Implementation uses multiplier approach (single source of truth, no duplicate values)
  - Example: Gemini 2.5 Pro charges $1.25/M for ≤128k tokens, $2.50/M for >128k tokens
  - Calculation:
    ```python
    # For 200k input tokens:
    # First 128k: 128 × $1.25/M = $0.16
    # Remaining 72k: 72 × $2.50/M = $0.18
    # Total: $0.34
    ```

### Changed
- **Test Coverage**: Maintained at 69%
  - Added tests for calibration tool (19 tests)
  - Added tests for tiered pricing (5 tests)
  - Added tests for Google GenAI provider (9 tests)
  - Added tests for OpenTelemetry exporter (5 tests)
  - 764 tests passing total

### Fixed
- Google GenAI provider now correctly extracts model names with version suffixes (e.g., "models/gemini-1.5-pro-001" → "gemini-1.5-pro")

## [0.1.8] - 2026-03-03

### Added - Schema v2 & Multimodal Support
- **Schema Version 2**: Upgraded event schema to support multimodal inference (images, audio, video)
  - New `ImageUsage` TypedDict with `input_tokens`, `output_tokens`, `image_count`, `total_pixels`
  - New `AudioUsage` TypedDict with `input_tokens`, `output_tokens`, `input_seconds`, `output_seconds`
  - Updated `Usage` container to support `text`, `image`, and `audio` modalities
  - New `multimodal` flag on InferenceEvent (True if request includes non-text modalities)
  - Image token extraction from OpenAI GPT-4 Vision API responses (`prompt_tokens_details.image_tokens`)
- **Distributed Tracing Support**: W3C trace propagation for APM integration
  - New fields: `trace_id`, `span_id`, `parent_span_id` for correlation with Datadog, New Relic, etc.
  - Ready for OpenTelemetry context extraction (implementation in future release)
- **Water Usage Tracking**: Datacenter cooling water consumption
  - New `estimated_water_l` field (liters per inference)
  - Provider-specific WUE (Water Usage Effectiveness) values
  - Google: 1.1 L/kWh (efficient water-free cooling), Azure: 1.7 L/kWh, AWS: 2.2 L/kWh (evaporative cooling)
  - Formula: `water_l = (energy_wh / 1000) * WUE`
- **Embodied Carbon Tracking**: Hardware manufacturing emissions
  - New `embodied_carbon_g` field (gCO2e from GPU/TPU manufacturing amortized over lifetime)
  - Factor: ~0.075 gCO2e per 1k tokens (based on Patterson et al. 2021)
  - Adds 5-15% to operational carbon footprint
- **Degraded Mode Indicator**: Transparency on tracking accuracy
  - New `tracking_degraded` flag (True if tracking is active but with reduced accuracy)
  - Triggers when: unknown model, token estimation used, Tier 3 energy data, or Tier 3 PUE
  - Helps users understand confidence level of metrics
- **Batch API Support Placeholders**: Schema fields for OpenAI Batch API (50% pricing discount)
  - New `is_batch` flag (ready for implementation)
  - New `is_embedding` flag for embedding generation (different energy profile than text generation)
- **Time-of-Day Carbon Tracking**: Hourly grid intensity awareness
  - New `grid_intensity_time_of_day` flag (False for now, True when hourly data implemented)
  - Schema ready for future hourly Electricity Maps integration

### Added - Security & Compliance
- **Tag Cardinality Limits**: DoS protection for unbounded tag values
  - Default limit: 1000 unique values per tag key
  - Configurable via `vetch.set_tag_cardinality_limit(limit)`
  - Automatic warnings when limit exceeded, values filtered
- **Tag Allowlist Mode**: Strict security filtering for sensitive environments
  - `vetch.set_tag_allowlist(['team', 'env', 'service'])` for whitelisting
  - Non-allowlisted tags filtered with warnings
  - Prevents accidental leakage of PII or sensitive data via tags
- **Sensitive Tag Redaction**: PII protection via SHA256 hashing
  - `vetch.set_redacted_tags(['user_email', 'customer_id'])` to hash sensitive values
  - Redacted values shown as `redacted-{hash8}` in logs and exports
  - Prevents accidental PII leakage (GDPR/CCPA compliance)
- **Circuit Breaker for Remote Registry**: Prevents hammering GitHub on repeated failures
  - Opens after 3 consecutive fetch failures
  - 5-minute timeout before retry
  - Exponential backoff with jitter
  - New properties: `circuit_breaker_open`, `failure_count` for diagnostics
- **Registry Signature Verification**: Supply chain attack prevention
  - SHA256 checksum validation for registry files
  - Opt-in via `VETCH_REGISTRY_VERIFY_SIGNATURES=true`
  - Loads `checksums.json` from remote, verifies all downloads
  - Logs errors on mismatch, rejects updates (possible supply chain attack)
- **SSRF Protection**: Validates registry URLs to prevent internal network access
  - Blocks private/internal IPs (10.x, 192.168.x, 127.x, link-local, etc.)
  - Only allows http/https schemes (blocks file://, ftp://, etc.)
  - DNS resolution validation before fetch

### Added - Observability
- **Configurable OTLP Queue Size**: Tunable export buffer for high-throughput environments
  - Environment variable: `VETCH_EXPORT_QUEUE_SIZE` (default: 1000)
  - Prevents memory growth in high-traffic services
  - Events dropped if queue full (backpressure documented)

### Added - Framework Integrations
- **Native LangChain Callback Handler**: First-class LangChain integration
  - `from vetch.integrations.langchain import VetchCallbackHandler`
  - Automatic tracking for all LLM calls in chains, agents, and LCEL pipelines
  - Aggregates metrics across multiple calls: `handler.total_cost`, `handler.total_energy_wh`
  - Session support for distributed tracing across LangChain chains

### Fixed
- Tag validation now happens after allowlist filtering (correct order)
- SSRF validation prevents registry from resolving private hostnames

## [0.1.7] - 2026-03-03

### Added
- **Provider-Specific PUE**: Auto-detection of datacenter efficiency from cloud provider sustainability reports
  - Google Cloud (Vertex AI): 1.10 PUE (2023 average)
  - Microsoft Azure (OpenAI): 1.12 PUE (2024 newest generation)
  - AWS (Anthropic, Bedrock): 1.15 PUE (2024 global average)
  - Fallback to 1.20 for unknown providers (industry average)
  - Model-based provider inference (gpt-4 → Azure, claude → AWS, gemini → Google)
  - New event fields: `pue`, `pue_tier`, `pue_source` for transparency
  - PUE tier system: Tier 1 (known value from vendor or user config), Tier 3 (default fallback)
- **Tier 1 Energy Data from Jegham et al. (2025)**: First large-scale hardware measurements in commercial datacenters
  - **GPT-4o upgraded to Tier 1** with non-linear model (short/medium/long prompt awareness)
  - **New reasoning models**: o1 (12.1 Wh), o3 (21.4 Wh), DeepSeek-R1 (29.0 Wh) - 40-100x more energy than efficient models
  - **GPT-4.1 nano**: Most efficient model (0.271 Wh per medium prompt)
  - **Claude-3.7 Sonnet upgraded to Tier 1** (2.781 Wh, highest eco-efficiency among large models)
  - Source: arXiv:2505.09598 "How Hungry is AI? Benchmarking Energy, Water, and Carbon Footprint of LLM Inference"
- **Non-Linear Energy Model**: Prompt-length-aware coefficients capture efficiency gains for longer prompts
  - Short prompts (<1k tokens): Higher per-token cost due to fixed overhead
  - Medium prompts (1k-5k tokens): Baseline for typical usage
  - Long prompts (>5k tokens): ~6x more efficient per-token due to amortization
  - Automatic category selection based on total token count
- **Updated METHODOLOGY.md**: Comprehensive 2025 research citations, non-linearity explanation, data provenance
- **Backward Compatibility**: `calculate_carbon()` supports legacy `pue` parameter (alias for `pue_override`)

### Changed
- **Carbon calculation now returns tuple**: `(carbon_g, pue, pue_tier, pue_source)` instead of just `carbon_g`
  - Provides full transparency on PUE assumptions used in carbon calculations
  - Breaking change for direct callers of `calculate_carbon()` (use tuple unpacking)
- **Language clarity**: "±10x uncertainty" changed to "order of magnitude uncertainty" for Tier 3 estimates
- **Remote registry is now opt-in**: Set `VETCH_REGISTRY_REMOTE=true` to enable (disabled by default)
  - Prevents silent accuracy regressions when bundled registry is newer than remote
  - Will be re-enabled by default in 0.1.8 once remote registry is synced with Tier 1 data

### Fixed
- User-configured PUE (`VETCH_DEFAULT_PUE`) now correctly classified as Tier 1 (known value) instead of Tier 0

## [0.1.6] - 2026-02-25

### Added
- **Session Aggregation**: `vetch.Session()` for grouping multi-call agentic workflows
  - Hierarchical sessions with parent/child nesting
  - Distributed propagation via HTTP headers (`X-Vetch-Session-Id`)
  - Thread-safe metric accumulation (energy, cost, carbon, tokens)
  - Memory safety: `max_calls` limit (default 10,000) prevents OOM in runaway loops
  - Metadata set caps (100 unique models/providers tracked)
  - Cache metrics: `total_cache_read_tokens`, `total_cache_creation_tokens`
  - `session_id` field in inference events links calls to sessions
- **Azure OpenAI Provider**: Auto-detected via `vetch.instrument()`
  - Region inference from Azure endpoint URLs
  - Full sync/async/streaming support
- **Dynamic Registry**: Remote registry updates from GitHub without SDK upgrade
  - Local `.vetch/` overrides for custom energy/pricing values
  - Offline mode via `VETCH_REGISTRY_PATH` for air-gapped environments
  - `vetch registry freeze` CLI command for CI/CD
- **`vetch status` CLI**: Check configuration, environment, and provider detection
- **`vetch dashboard` CLI**: Export pre-built Grafana dashboard template
- **Cache-Aware Pricing**: Cost calculation now applies cache read discounts and creation premiums
- **Alert Cooldown**: `alert_cooldown_seconds` parameter on `set_budget()` prevents alert flooding
- **Price Multiplier**: `price_multiplier` parameter on `wrap()` and `awrap()` for discount pricing
- **Quiet Mode**: `emit=False` parameter on `wrap()` and `awrap()` (metrics available in `ctx.event`)
- **Python 3.13**: Added to CI test matrix
- **Bandit Security Scanning**: Added to CI pipeline

### Changed
- **PUE default changed from 1.1 to 1.2** (aligned with cloud provider averages: Google 1.09, AWS 1.14, Azure 1.12)
- **Energy registry values no longer include PUE** — PUE is applied once in `calculate_carbon()` only. Previous versions double-counted PUE (baked into registry via 1.3x multiplier AND applied in carbon calculation).
- Registry system multiplier reduced from 1.3x to 1.2x (hardware overhead only)
- `vetch.wrap()` and `vetch.awrap()` now expose `price_multiplier` and `emit` parameters
- OTLP service version now uses `__version__` instead of hardcoded string

### Fixed
- **Cache-aware pricing was not connected**: `calculate_cost()` now receives cache token counts from the wrapper (was silently ignoring them)
- **Atomic uninstrumentation**: Provider teardown now restores per-client methods under lock before restoring `__init__` (prevents race condition)
- URL parameter injection: region parameter in Electricity Maps API calls now URL-encoded
- Path traversal prevention in `VETCH_OUTPUT` file paths
- Restrictive file permissions on SQLite database (`0o600`) and directory (`0o700`)
- MagicMock guard on cache token values from captured calls

### Removed
- `pue_overrides.json` (was never loaded by any code — dead since v0.1.3)

## [0.1.5] - 2026-02-23

### Added
- **Budget Alerts** (warn-only): `set_budget()`, `on_budget_alert()`, `get_budget_status()`
  - Configure thresholds for cost, energy, or carbon
  - Alerts logged to stderr and fire callbacks
  - **Never blocks inference** - fail-open by design
  - Thread-safe: accumulation protected by `threading.Lock`
  - Bounded memory: warning deduplication uses LRU with max 1000 keys
  - Environment variables: `VETCH_BUDGET_COST_USD`, `VETCH_BUDGET_ENERGY_WH`, `VETCH_BUDGET_CARBON_G`
- **OTLP Export**: `configure_otlp_export()` for Datadog, Honeycomb, Grafana, Jaeger
  - Export spans and metrics to any OTLP-compatible backend
  - **Non-blocking**: uses background thread queue (max 1000 events)
  - Auto-configure via `VETCH_OTEL_EXPORT=true` and `OTEL_EXPORTER_OTLP_ENDPOINT`
  - Metrics: `vetch.energy_wh`, `vetch.carbon_g`, `vetch.cost_usd`, `vetch.requests_total`
- **Global Instrumentation**: `vetch.instrument()` for zero-code integration
  - Auto-patches OpenAI, Anthropic, and Vertex AI clients at import time
  - Works with LangChain, LlamaIndex, and other frameworks
- **Prompt Cache Detection**: Track cache hits for Anthropic and OpenAI
  - New event fields: `cache_read_tokens`, `cache_creation_tokens`, `cache_hit`
  - Attached to OTel spans when available
- **Grafana Dashboard Template**: `vetch/dashboards/grafana_vetch.json`
  - Pre-built panels for cost, energy, carbon, and request rate
  - Export via `vetch dashboard --export grafana` (CLI coming in v0.1.6)

### Changed
- OTLP integration now supports both span decoration (existing) and full export (new)
- Budget fields in events (`budget_exceeded`, `budget_cost_usd`, etc.) now populated
- Budget `window` parameter now only accepts `"request"` or `"session"` (removed misleading `"hour"`/`"day"` options that had no implementation)

### Environment Variables (New)
| Variable | Purpose | Default |
|----------|---------|---------|
| `VETCH_BUDGET_COST_USD` | Per-request cost alert threshold | (none) |
| `VETCH_BUDGET_ENERGY_WH` | Per-request energy alert threshold | (none) |
| `VETCH_BUDGET_CARBON_G` | Per-request carbon alert threshold | (none) |
| `VETCH_BUDGET_SESSION_COST_USD` | Session-wide cost alert threshold | (none) |
| `VETCH_OTEL_EXPORT` | Enable automatic OTLP export | `false` |
| `VETCH_OTEL_SERVICE_NAME` | Service name for OTLP export | `vetch` |

## [0.1.4] - 2026-02-23

### Added
- `emit=False` parameter for quiet mode (no JSON output to stderr)
- `vetch quickstart` CLI command with usage examples
- Package metadata with GitHub URLs (homepage, issues, repository)
- Expanded model aliases (claude-3-5-sonnet, gemini-flash-2.0, llama3.1-405b, etc.)

### Changed
- Default output changed to `none` (quiet by default). Set `VETCH_OUTPUT=stderr` for JSON.
- FutureWarning for experimental modules now only triggers on first use
- Improved CLI messaging for `vetch report` when storage is disabled

### Fixed
- Model alias mismatch: `claude-3-5-sonnet` now correctly maps to registry (was 12x overestimate)
- Reduced warning spam from experimental modules (storage, ci, calibrate)
- Multi-client patching: each client's original method now stored separately (was losing first client's original)
- Thread-safe patching: added locks to prevent race conditions in multi-threaded environments
- Context isolation: `_cleanup_patches` now only unpatches clients from its own context (was breaking other threads)
- Streaming fragility: defensive access for `chunk.choices` in OpenAI provider

## [0.1.3] - 2026-02-19

### Added
- `energy_uncertainty_pct` field in events (20/50/100/1000 for tiers 0-3)
- MoE active parameter accounting in energy registry (fixes ~300% overestimation)
- Architecture metadata (`architecture`, `total_params_b`, `active_params_b`, `quantization`)
- Provider-specific PUE table (removed in v0.1.6 — was never wired up)
- Expanded model aliases for Claude 3.5, Gemini 2.0, Llama 3.1

### Changed
- Energy estimates now based on active parameters for MoE models
- CLI output shows uncertainty as percentage or "order of magnitude" for Tier 3

## [0.1.2] - 2026-02-18

### Added
- Live API examples in demo.ipynb for OpenAI, Anthropic, and Vertex AI providers

### Changed
- Thread-safe file locking using non-blocking fcntl with polling
- Improved retry logic with capped exponential backoff

## [0.1.1] - 2026-02-18

### Added
- Quick Start Colab notebook (`demo.ipynb`) for interactive exploration

## [0.1.0] - 2026-02-18

### Added
- Initial **alpha** release of Vetch SDK
- Core context manager: `wrap()` for energy/carbon/cost tracking
- OpenAI provider wrapper (sync & streaming)
- Vertex AI provider wrapper (sync & streaming)
- Anthropic provider wrapper (sync & streaming)
- Multi-tier grid intensity cache (Memory + File with locking)
- Serverless mode (`VETCH_CACHE_MODE=memory-only`)
- Energy calculation engine with model registry
- Pricing registry for list cost estimation with `price_multiplier` support
- CLI tool: `estimate`, `compare`, `methodology`, `check`, `audit`, `report`
- Token estimation with tiktoken integration and language-aware fallback
- Session statistics: `get_session_stats()` for pattern detection
- Advisory engine: `generate_advisories()` for optimization recommendations
- OpenTelemetry span decoration (attach metrics to existing spans)
- SQLite local storage for historical analysis (experimental)
- GPU calibration module for Tier 0 measurements (experimental)
- CI summary mode for GitHub Actions

### Safety Features
- **Fail-open behavior**: LLM calls always proceed even when Vetch fails
- **Fail-loud diagnostics**: `vetch_warnings` field captures all issues
- **Kill switch**: `VETCH_DISABLED=true` completely disables Vetch
- **Privacy-first**: Zero access to prompt/completion content

### Alpha Limitations
- Timezone-based region inference has ~30% accuracy - use `VETCH_REGION` for production
- Experimental modules (`calibrate`, `storage`, `ci`) marked with `FutureWarning`
- Integration tests require live API credentials
- Energy estimates are Tier 3 (order of magnitude uncertainty) for most models

### Environment Variables
| Variable | Purpose | Default |
|----------|---------|---------|
| `VETCH_REGION` | Grid region for carbon calculation | (inferred) |
| `VETCH_OUTPUT` | Output target: `stderr`, `none`, or file path | `none` |
| `VETCH_DEFAULT_PUE` | Power Usage Effectiveness multiplier | `1.1` |
| `VETCH_CACHE_MODE` | Set to `memory-only` for serverless | (file-based) |
| `VETCH_DISABLED` | Set to `true` to disable all tracking | `false` |
| `ELECTRICITY_MAPS_API_KEY` | API key for live grid data | (optional) |

### Dependencies
- **Runtime**: Zero dependencies (stdlib only)
- **Optional**: `openai>=1.0,<2.0`, `google-cloud-aiplatform>=1.0`, `tiktoken>=0.5.0`
- **Test**: `pytest>=7.0`, `hypothesis>=6.0`
