Skip to content

fix: model Docker's identifier grammar and split it from the volume discriminator - #146

Merged
lesnik512 merged 1 commit into
mainfrom
fix/docker-name-grammar
Sep 27, 2026
Merged

lesnik512 merged 1 commit into
mainfrom
fix/docker-name-grammar

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Closes #143.

Docker enforces two unrelated rules that compose2pod had collapsed into one pattern,
stores.NAME_PATTERN, which was wrong for each in a different direction. They are two
rules now (three, counting the long volume form), each measured against
docker compose config v5.1.2.

The name grammar

values.NAME_GRAMMAR is [a-zA-Z0-9._-]+. Applied at the gate to the keys of top-level
services, volumes, secrets and configs, and to a service's long-form networks
mapping key.

That list is measured, not derived. A top-level networks: key is not held to it --
networks: {"a b": {}} on its own is a document Docker accepts -- and neither is a
service's short-form list entry. Only the long-form mapping key is. The issue text
asserted the top-level networks key was checked; it is not, and
tests/conformance/corpus/networks_top_level_name_unchecked.yaml pins the exception so
the grammar cannot later be tidied into applying everywhere.

Closed by this: services, volumes, secrets and configs keys such as a b,
a/b, a:b, a#b, ab!, a+b, a~b, a@b, a$b, "", a\nb and ä were all
accepted here and all rejected by Docker. Rule one, not a narrowing.

Lifted by this: .a, -a and _a as secret/config names. stores.NAME_PATTERN
required an alphanumeric first character Docker does not.

The volume discriminator

Not a grammar question at all, which is where my first pass got it wrong. Docker reads a
short-form source beginning with ., / or ~ as a host path and every other
spelling as a volume name
-- a/b, d/e/f, a b, a@b, a+b, a\b included, each
refers to undefined volume with no declaration. parsing._is_named_volume_source is
that rule now.

An earlier version of this file used the same leading-character rule without ~ and
swept ~/data into "named"; ~ was the missing prefix, not the grammar. Widening the
name pattern instead would have read .env:/app/.env as a named volume, which is the
live dotfile-bind regression the split exists to avoid.

The long form is a third rule: type: volume has already said which kind the entry is,
so any source names a volume -- source: /abs and source: ./r are both undefined-volume
errors to Docker where the short form reads the same strings as paths.

A ${VAR}-carrying source stays unnamed in both syntaxes. Docker rejects ${VAR}:/x
only because it interpolates an unset variable to empty first: with VAR=data or
VAR=/host it accepts either reading (measured). That is ADR-0006's host-state
boundary, not a hole.

Evidence

  • A 26-source differential sweep over both syntaxes: every rule-one hole closed, zero
    over-rejections introduced. The only documents where the two oracles still differ are
    the two ${VAR} sources above, which are the documented carve-out.
  • just test-ci 1566 passed, 100% coverage. just test-conformance 952 passed,
    over-rejections unchanged at 19.
  • New generated probe test_identifier_grammar_matches_docker: 11 refused and 4 accepted
    names across the two enforcing positions, plus a hostile pair on the remaining blocks,
    driven off NAME_CHECKED_TOP_LEVEL_BLOCKS so a block added there is probed at once. It
    asserts the verdict in both directions rather than calling assert_rule for its
    side effect, because assert_rule tolerates over-rejection and an over-rejection on the
    accepted names is exactly the defect this lifts.
  • Emission is unchanged for valid documents: .env still binds to /proj/.env, -a
    still emits -v "-a:/x", ${V} still expands at run time. No new podman claim, so no
    integration row is owed.

ADR-0006

"The hard rule has no exceptions left" was false when I wrote it three days ago, and the
paragraph now says so and says why the harness could not have caught it: the generated
matrix varies a key's value over hostile shapes and never touches a map key, so no
probe could reach a name. The new axis closes that blind spot.

Review

Two review passes found real defects, both fixed here. The first version implemented the
discriminator as "matches the grammar and no leading dot", which left ten measured
rule-one holes open and carried a docstring claiming a/b was a bind -- it is a named
volume. The generated probe also discarded its verdict, so its accept-side cases asserted
nothing.

@lesnik512
lesnik512 merged commit c948770 into main Sep 27, 2026
15 checks passed
@lesnik512
lesnik512 deleted the fix/docker-name-grammar branch September 27, 2026 19:05
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.

Docker's name grammar is unmodelled: three top-level maps unchecked, one pattern doing two jobs

1 participant