Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Enforce the /v1 api_base invariant with types + a drift-checker assertion

Open
#746 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
55/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Quiet
Tech stack
go, kubernetes
Domain
backend, devops

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.

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)

  1. 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).

  1. 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: every openai/ entry's api_base ends in /v1. This alone would have caught the incident in production.

  2. Rejection over rewriting: constructors error on ambiguous input. Silent normalization is how the WarnAndStripV1Suffix disaster 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.

Dominant language
Go
Stars
11
Forks
1
Avg merge
1h 20m
Merged PRs (30d)
39

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from ObolNetwork/obol-stack

All issues in ObolNetwork/obol-stack

Similar issues

More Go issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.