Skip to content

fix: validate environment names, and read the owner record one way - #73

Merged
vishr merged 1 commit into
mainfrom
fix/validate-environment-names
Aug 18, 2026
Merged

fix: validate environment names, and read the owner record one way#73
vishr merged 1 commit into
mainfrom
fix/validate-environment-names

Conversation

@vishr

@vishr vishr commented Aug 18, 2026

Copy link
Copy Markdown
Member

Two defects found by an independent review of the merged #71 — both introduced by it. Closes #54.

1. A legal config could brick the host

#71 made the environment name load-bearing: bootstrap writes it into the host owner record, and every later command parses it back through a strict grammar. Nothing validated it — environments: was the one name-bearing map in validate.go whose key was never checked against gIdent.

Measured:

"Staging"          ACCEPTED by ob validate
"prod_east"        ACCEPTED by ob validate
"staging replica"  ACCEPTED by ob validate

parseHostOwner("sample Staging")         ok=false
parseHostOwner("sample prod_east")       ok=false
parseHostOwner("sample staging replica") ok=false

ob bootstrap writes the record and succeeds. Every mutation afterwards refuses a record it cannot parse — permanently, and the message named no remedy.

Worth stating plainly: this is #54, which I filed this morning and then downgraded to low after #51, on the grounds the name no longer reached a shell or filename. That was true when written and stopped being true a few hours later, in this repository, without the issue being revisited.

2. Preflight was more permissive than the engine

hostOwnerCheck read fields[0] and fields[1] and ignored the rest; the engine rejects anything that is not exactly 1 or 2 fields. A three-field record got a green preflight and then failed every mutation — the inversion preflight exists to prevent.

There is now one parser, app.ParseHostOwnerRecord, used by both.

3. The unparseable-record refusal names a remedy

Matching the empty-record case beside it. No ob command rewrites a record it cannot read, so the operator has to be told to remove it.

Tests

Both regressions covered, both fail without the fix:

--- FAIL: TestEnvironmentNamesMustSurviveTheOwnerRecord
    environment name "Staging" was accepted by the loader but cannot
    round-trip the owner record

The existing TestHostOwnerInvalidRecordFailsClosed asserted on the word "invalid"; it now asserts the refusal names the record and its remedy, which is the property that matters.

Verified

just check, golangci-lint run ./..., go test -race ./....

Two defects found by review of the merged #71, both created by it.

That change made the environment name load-bearing: bootstrap writes it into
the host owner record and every later command parses it back through a strict
grammar. Nothing validated it. `environments:` was the one name-bearing map in
validate.go whose key was never checked against gIdent, which was harmless while
the name was a lookup key and nothing else.

So `Staging`, `prod_east` and `staging replica` all passed `ob validate`, and
all three are rejected by the parser that reads the record back. Bootstrap
writes the record and succeeds; every mutation afterwards refuses a record it
cannot parse, permanently, with no remedy in the message. A legal config
bricked the host.

This is the item filed as #54 and downgraded to low earlier the same day, on
the grounds that the name no longer reached a shell or a filename. That was
true when it was written and stopped being true a few hours later, in this
repository, without the issue being revisited.

Preflight also disagreed with the engine. It read the first two fields of the
record and ignored anything after them, so a three-field record passed preflight
and then failed every mutation on the engine's parser. A check that is more
permissive than the thing it predicts is worse than no check, so there is now
one parser — app.ParseHostOwnerRecord — used by both.

The refusal for an unparseable record now names its remedy, matching the empty
record beside it: no ob command rewrites a record it cannot read, so the
operator has to be told to remove it.

Tests cover both, and both fail without the fix. Removing the validator gate:

    --- FAIL: TestEnvironmentNamesMustSurviveTheOwnerRecord
        environment name "Staging" was accepted by the loader but cannot
        round-trip the owner record

`just check`, `golangci-lint run ./...` and `go test -race ./...` pass.

Closes #54.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vishr
vishr merged commit 7a784b8 into main Aug 18, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Environment names are never validated

1 participant