Skip to content

No gate catches a conflict marker; one reached /docs/changelog #1898

Description

@jhgaylor

A >>>>>>> theirs conflict marker reached main in CHANGELOG.md and was served to readers at /docs/changelog until #1881 removed it. Nothing in the repo would have caught it, and nothing would catch the next one.

What happened

9da64a10 (the #1839 stack tip) added the runtime_command changelog block from a botched resolution: the resolver kept theirs wholesale, dropped the <<<<<<< and ======= lines, and left the trailing >>>>>>> theirs. No content was lost, so nothing read as broken in the diff.

CHANGELOG.md is a published docs page — Fountain.Docs declares it on extra_resources: (apps/fountain/lib/fountain/docs.ex:48), docs/changelog.md snippet-includes it, and docs/nav.yml:150 mounts it. >>>>>>> is seven nested blockquotes in markdown, so it rendered in production as an indented box holding the word "theirs" in the middle of the unreleased notes:

$ curl -s https://managoat.com/docs/changelog | grep -B2 -A2 theirs
<blockquote><blockquote><p>theirs</p></blockquote></blockquote>

Full CI was green on that merge, and full CI is green on #1881. Neither run had anything to say about it.

Why no gate saw it

Two independent holes, and the marker fell through both.

There is no conflict-marker check anywhere. A grep for the three marker forms over .ex, .exs, .sh, .yml, .py and .mjs finds no test, no script and no CI step that looks for them. This is a class gap, not a changelog one: a marker in an Elixir source file fails to compile, but one in markdown, JSON fixtures, .env.example, or a docs page under apps/*/docs/ is invisible to every gate we have.

CHANGELOG.md sits in a seam of the docs gates. The changes job keeps it off the docs-only allowlist deliberately (ci.yml:115) so the release-bump flow cannot skip the suite. That is the right call. The side effect is that docs_touched stays false for a changelog-only diff, so Docs and Docs prose gates both skip — as they did on #1881. And the prose linters take docs/ and apps/*/docs/ as their path arguments, not the repo root. So the one file that is both a published docs page and prose-gate-exempt is exactly where this landed.

What to build

A tree-wide marker check, not a changelog one. Two candidate homes:

  1. A scripts/ gate beside coverage-gate.exs and hex-audit-gate.exs, run from the elixir-static job or CI policy tests, grepping the tracked tree. Catches every file type, including ones no Elixir test reads.
  2. docs_test.exs, which already walks the manual and the declared external_resources/0. Narrower — it would have caught this instance, since CHANGELOG.md is a declared resource — but it leaves fixtures and config files uncovered.

(1) is the one that closes the class. Worth considering whether it also belongs in mix precommit, so the marker dies before the push rather than in CI.

Two things for whoever picks it up:

  • The backlog is empty. The tree is clean today, so the guard can go in with no allowlist and no ratchet file — the shape the docs gates had to grow into over Site-wide style pass, plus a CI style check #911 and the destink work.
  • Watch the self-match. The check's own source, and any doc that explains git conflict resolution, will contain the patterns it searches for. Anchor to line start and exclude the gate's own path rather than adding a general allowlist, so the list cannot start growing.

Found while reviewing #1881.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Worth doing, not urgentarea:docsDocumentation accuracyarea:testingTests and CI gatesbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions