ObolNetwork / ObolNetwork/obol-stack
Enforce the /v1 api_base invariant with types + a drift-checker assertion
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 11
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
Problem
The /v1-suffix bug family (#745, ffa7cf49, the deleted WarnAndStripV1Suffix fossil) is a recurring type error: "service root without /v1" and "OpenAI base with /v1" are two different types carried in one string. The invariant is currently documentation-enforced (CLAUDE.md pitfall 6, a doc comment on buildCustomEndpointEntryWithOptions) — and documentation-enforced invariants are exactly what decayed into this incident: the codebase carried two contradictory theories of who appends /v1 for months, with no test or build failure.
Full audit of every producer/consumer: https://github.com/ObolNetwork/obol-stack/pull/745#issuecomment-4966292476
Proposal (~40 lines, one package)
- Newtypes in
internal/model:
type ServiceRoot string // scheme://host:port — never a path
type OpenAIBase string // ServiceRoot + "/v1" — what a LiteLLM openai/ api_base must be
func NewServiceRoot(raw string) (ServiceRoot, error) // trims trailing "/"; REJECTS a trailing /v1 with an error — never silently strips
func (r ServiceRoot) OpenAI() OpenAIBase
func (r ServiceRoot) ChatCompletions() string
buildCustomEndpointEntryWithOptions takes OpenAIBase; the discovery bug class becomes a compile error. Buyer-side code keeps ServiceRoot and derives full URLs at the edge (it already follows this convention — buyprompts.ChatCompletionsURL).
-
Runtime tripwire for what types can't reach (the live ConfigMap): the config-drift checker compares model names only, so a same-name entry with a divergent
api_base— exactly the poisoned model group in #745's incident — is invisible to it. Add an assertion at reconcile: everyopenai/entry'sapi_baseends in/v1. This alone would have caught the incident in production. -
Rejection over rewriting: constructors error on ambiguous input. Silent normalization is how the
WarnAndStripV1Suffixdisaster happened — a helpful auto-fix encoding a wrong theory.
Non-goals
No behavior change; pure enforcement of the invariant #745 documented. Not release-blocking for v0.13.0.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by auditing internal/model and the buildCustomEndpointEntryWithOptions call path, using buyprompts.ChatCompletionsURL as the existing ServiceRoot convention. Then trace the config-drift checker through reconcile and its live ConfigMap entries. Done means the proposed types reject ambiguous inputs, consumers use the appropriate type, and reconciliation asserts that every openai/ api_base ends in /v1.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, kubernetes
- Domain
- backend, devops
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100