feat: resolve third-party backends via entry points (makes ADR 0005 real)#20
Conversation
…eal) ADR 0005 promises storage is an open extension point — "register an entry point, no core change" — but no code read the model_ledger.backends group; a user following that contract got a backend that never loaded. - backends/registry.py: load_backend_class(name) discovers a backend target in the model_ledger.backends entry-point group (mirrors the working introspector pattern). - cli/_resolve_backend: falls back to entry-point discovery for any --backend name that isn't built in; constructs Backend(path) or Backend(). - tests: entry-point resolution (monkeypatched), graceful None for unknown, and a protocol-conformance check (a real backend satisfies LedgerBackend). - docs/guides/backends: document the registration contract, now that it works. mypy clean (76 files), ruff clean, 729 passed / 24 skipped, coverage 74.76%. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 060ca627a8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if ep.name == name: | ||
| return ep.load() | ||
| except Exception: | ||
| return None |
There was a problem hiding this comment.
Fail instead of falling back when backend plugin won't load
When an installed third-party backend entry point matches the requested name but ep.load() raises (for example because the plugin has a missing dependency or an invalid target), this broad except returns None. In the inspected CLI path, both model-ledger mcp --backend <name> and model-ledger serve --backend <name> pass that None into create_server/create_app, which default to an in-memory backend, so a production service can silently start with transient empty storage instead of reporting the broken configured backend.
Useful? React with 👍 / 👎.
Turns a documented-but-fake extension point into a working one.
ADR 0005 (storage-agnostic) promises you can register a backend via an entry point with no core change — but nothing read the
model_ledger.backendsgroup, so following that contract gave you a backend that never loaded.load_backend_class(name)discovers a backend in themodel_ledger.backendsentry-point group (mirrors the working introspector pattern).--backend <name>falls back to entry-point discovery for any non-built-in name; constructsBackend(path)/Backend().Nonefor unknown, and a runtime-checkable protocol-conformance check.mypy clean (76 files) · 729 passed / 24 skipped · coverage 74.76%.
🤖 Generated with Claude Code