I3447 — No two published paths differ only in case, and a publication that holds two is refused before it is built
Make every path the site and the UI snapshot publish unique without regard to case: (a) the Work page's 'Open the whole list' link reaches the issue status page by the status's one spelling, so the snapshot no longer carries issues/_status/ready beside issues/_status/READY; and (b) a check over every publication (the site's site/public, the UI and Cockpit snapshots) refuses two paths that are equal when case is folded, naming both, before anything is deployed.
READY wave 0 · p1 · implementation profile · parallel safe
Ready. Every dependency is done, so majordomus plan start I3447 will be accepted.
Objective
Make every path the site and the UI snapshot publish unique without regard to case: (a) the Work page's 'Open the whole list' link reaches the issue status page by the status's one spelling, so the snapshot no longer carries issues/_status/ready beside issues/_status/READY; and (b) a check over every publication (the site's site/public, the UI and Cockpit snapshots) refuses two paths that are equal when case is folded, naming both, before anything is deployed.
Why
Found on 2026-10-20 during the hand deploy of master 82ac259d0ae, after the Pages run 39392245576 published nothing. The ui-snapshot artifact of that run could not be extracted on macOS: 'error extracting issues/_status/ready/index.html: file exists', because the archive also holds issues/_status/READY/index.html. A case-insensitive filesystem holds one of the two, so a person on a Mac cannot deploy the site without a case-sensitive volume, and git with core.ignorecase folds one of the paths away. GitHub Pages serves both, so the duplicate is invisible on the live site.
Current state
The UI snapshot at 82ac259d0ae holds seven status pages under issues/_status/: ACTIVE, BLOCKED, CANCELLED, DONE, READY, VERIFY and also ready. The only link to the lowercase one is on /ui/work/: 'The first 16 of 179; 163 more not shown here. Open the whole list', with href ../issues/_status/ready/index.html (apps/majordomus-cli/src/ui/view.rs:420 renders the link from the href its caller passes). The snapshot crawler follows that link and writes a page for it. No gate compares published paths with case folded.
Desired state
The Work page links the status page by the status as the plan declares it, so the snapshot has one page per status. scripts/site-check, or the snapshot producer, refuses any two published paths under one mount that are equal case-folded, naming both, and the deploy refuses the same way. A Mac can extract the snapshot and run scripts/site-deploy with no special volume.
Scope
- apps/majordomus-cli/src/ui/view.rs
- apps/majordomus-cli/src/ui/work.rs
- apps/majordomus-cli/src/web/report/snapshot.rs
- scripts/site-check
- test/cases/3720_no_two_published_paths_differ_only_in_case.sh
Out of scope
- Renaming plan statuses, which stay upper case as the plan declares them
- Changing how GitHub Pages serves paths
Dependencies
None. This issue is a root of the graph.
Acceptance criteria
- The UI snapshot of the change holds exactly one page per declared status under issues/_status/, and /ui/work/'s whole-list link resolves to one of them
- A fixture publication holding a/X/index.html and a/x/index.html is refused by the check, which names both paths
- Negative: a publication whose paths differ in more than case passes the check
- The snapshot archive of the change extracts on a case-insensitive filesystem with no 'file exists' error
Validation
- cargo test --manifest-path apps/majordomus-cli/Cargo.toml --lib ui::
- bash test/run.sh 3720_no_two_published_paths_differ_only_in_case 09_site_mobile_first
- majordomus plan validate
Evidence required
- test_no_two_published_paths_differ_only_in_case
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/I3447.yaml. Read it back with majordomus plan show I3447.