## Description
A stray or typo'd `HEADROOM_QDRANT_PORT` can crash proxy startup during
`ProxyConfig()` construction, even when memory is disabled.
`memory_qdrant_port` is resolved through a dataclass field
`default_factory`. That factory runs on every `ProxyConfig()`
construction, regardless of whether memory or the `qdrant-neo4j` backend
is enabled. The strict qdrant env parser raises on non-integer or
out-of-range ports, so inherited values such as
`HEADROOM_QDRANT_PORT=0`, `70000`, or `not-a-port` could prevent the
proxy from starting for an off-by-default subsystem.
## Fix
Use a fail-soft wrapper for the `ProxyConfig.memory_qdrant_port` default
factory. Bad env values log a warning and fall back to the default port
`6333`; valid env values are still honored. The strict
`qdrant_env.qdrant_env_port()` parser remains unchanged for direct
memory APIs and explicit tests, and the CLI `--memory-qdrant-port`
option still validates `1..65535` before constructing `ProxyConfig`.
## Type of Change
- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)
## Changes Made
- `headroom/proxy/models.py`: add `_qdrant_env_port_or_default()` and
use it for the proxy config qdrant port default.
- `tests/test_proxy_config_qdrant_port.py`: cover bad env values falling
back, valid env values passing through, and `ProxyConfig()` construction
surviving bad env.
- `CHANGELOG.md`: add a bug-fix entry.
- Merged current `main` to pick up the repository-wide memory factory
type annotation fix that was breaking the PR lint job.
## Testing
- [x] Unit tests pass (`pytest` focused locally; broader CI passed on
the pre-merge head and fresh CI is running on the main-merged head)
- [x] Linting passes (`ruff check` focused locally)
- [x] Type checking passes for the prior mypy blocker after merging
current `main`
- [x] New tests added for new functionality
- [ ] Manual testing performed
### Test Output
```text
uvx ruff@0.15.17 check headroom/proxy/models.py tests/test_proxy_config_qdrant_port.py headroom/memory/factory.py
All checks passed!
uvx ruff@0.15.17 format --check headroom/proxy/models.py tests/test_proxy_config_qdrant_port.py headroom/memory/factory.py
3 files already formatted
git diff --check headroomlabs/main...HEAD
# no output
uv run --extra dev python -m pytest tests/test_proxy_config_qdrant_port.py -q
5 passed
```
## Real Behavior Proof
- Environment: Windows 11 review worktree, Python 3.13.3.
- Exact command / steps: ran focused qdrant proxy config tests and
targeted lint/format checks.
- Observed result: invalid `HEADROOM_QDRANT_PORT` values fall back to
`6333`; valid `6444` is honored; `ProxyConfig()` no longer raises during
construction when env is bad.
- Not tested: full suite; fresh CI is queued after the main merge.
## Review Readiness
- [x] I have performed a self-review
- [x] This PR is ready for human review
## Checklist
- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [x] I have updated the CHANGELOG.md if applicable
## Additional Notes
This intentionally changes only the unconditional proxy config default
path. Direct qdrant env parsing remains strict, and explicit CLI qdrant
port input is still range-validated by Click.
Co-authored-by: JerrettDavis <mxjerrett@gmail.com>