Skip to content

I3775 — A page written by hand is never resolved as derived: the merge=derived globs name only what a generator writes

Make the merge=derived attribute true of every path it covers: each path .gitattributes resolves with scripts/merge-derived is one that scripts/derive or majordomus generate writes, and a page a person writes is merged as text, so a merge never drops one side of a hand edit.

READY wave 0 · p1 · implementation profile · parallel safe

Part of derived-data-integrity — A committed derivation describes something it can be a projection of.

Ready. Every dependency is done, so majordomus plan start I3775 will be accepted.

Objective

Make the merge=derived attribute true of every path it covers: each path .gitattributes resolves with scripts/merge-derived is one that scripts/derive or majordomus generate writes, and a page a person writes is merged as text, so a merge never drops one side of a hand edit.

Why

Found on 2026-10-21 while splitting the campaign records into source and derived paths. site/content/roadmap.md is written by hand (no generator writes it and site/content-src holds no copy), yet the glob site/content/*.md marks it merge=derived. scripts/merge-derived resolves such a path to ours and only warns that it is stale, so a merge of two branches that both edit the roadmap prose keeps one side and drops the other without a conflict, and the next derive does not restore it because nothing derives it.

Current state

The .gitattributes line site/content/*.md merge=derived covers every top-level page. Most come from site/content-src through scripts/generate-site-data and _index.md is written by the generator, but site/content/roadmap.md has no source anywhere, and changelog.md, getting-started.md, limitations.md and policy.md have no site/content-src counterpart and are not yet traced. No gate compares the merge=derived set with the set a derive writes.

Desired state

Each merge=derived pattern matches only paths a derive writes, and a gate refuses a tracked path the attribute resolves as derived when a derive does not write it, naming the path. Hand-written pages either move to site/content-src, so the derive projects them, or are excluded from the glob.

Scope

  • .gitattributes
  • scripts/merge-derived
  • scripts/ci/derived-attributes-check
  • site/content/roadmap.md
  • site/content-src
  • test/cases/3725_a_hand_written_page_is_never_resolved_as_derived.sh

Out of scope

  • Changing how scripts/merge-derived resolves a path that is derived
  • Moving the generated pages out of site/content

Dependencies

None. This issue is a root of the graph.

Acceptance criteria

  • Every tracked path that git check-attr resolves as merge=derived is written by a derive run from a clean checkout
  • site/content/roadmap.md is either projected from site/content-src or merged as text, and a merge of two branches that edit its prose differently conflicts instead of keeping one side
  • Negative: a fixture checkout with a hand-written file under a merge=derived glob fails the gate, which names the file

Validation

  • bash test/run.sh 3725_a_hand_written_page_is_never_resolved_as_derived
  • majordomus plan validate

Evidence required

  • test_a_hand_written_page_is_never_resolved_as_derived

Evidence

None recorded. Every token above needs a command or an artifact behind it before this issue can be completed; narrative is refused.

Timeline

started
—
verified
—
completed
—

Those three fields, the evidence above and the state of the dependencies are all the status is made of. There is no status field to disagree with them.

Canonical record: .ai/repo/project/issues/I3775.yaml. Read it back with majordomus plan show I3775.

Searches the titles and descriptions of every page this site publishes.