What is wrong
A document under docs/ that links a file at the repository root writes the
target as ../NAME, and that link is read by neither of the two readings the
paths leg joins. So the case #162 raised and #165 closed for a root document is
still open one directory down: a document under docs/ can link a root file that
is not in the tree and nothing here says so.
PathsNamedInProse requires a leading segment naming a directory of this tree,
and .. is not one, which is the deliberate narrowness its own comment argues
for. LinkTargetsWithoutADirectory skips any target containing a slash, and
../NAME contains one. The two readings sit either side of this shape rather
than one of them reaching it.
Three links on the default branch are in that state today. The two carrying
LICENSE arrived with #163; NOTICE.md has been there longer.
git grep -n -F '](../' origin/main -- docs/
origin/main:docs/operator-guide.md:191:[NOTICE.md](../NOTICE.md) at the root of the checkout is the intended-use
origin/main:docs/operator-guide.md:204:[LICENSE](../LICENSE) at the root of the checkout is the GNU Affero General
origin/main:docs/promotion.md:35:The licence the code carries out. [LICENSE](../LICENSE) at the root of this
All three resolve to files that are there, so nothing is broken today. What is
absent is the refusal that would say so on the day one of them is not, and that
is exactly the day the removal in #155 produced: LICENSE left the default
branch for a day and the operator guide would have pointed at nothing.
What the evidence is
The two readings, at 19ca148a449109074ce2094915844497f96a258b:
git grep -n 'strings.ContainsAny(target' -- internal/check/paths.go
internal/check/paths.go:58: if target == "" || strings.ContainsAny(target, "/:\\") || seen[target] {
The gap is one character wide, which is what makes it worth a guard rather than
a habit. Appending a link to a file that does not exist to docs/privacy.md,
first as ../NO-SUCH-FILE.md and then as NO-SUCH-FILE.md, and running the
same leg over each:
printf '\n[a file that is not there](../NO-SUCH-FILE.md)\n' >> docs/privacy.md
go test ./internal/invariants -run TestThisRepositorySatisfiesTheInvariants -count=1 -v
paths this repository's own documents name: 34 examined
--- PASS: TestThisRepositorySatisfiesTheInvariants (0.20s)
printf '\n[a file that is not there](NO-SUCH-FILE.md)\n' >> docs/privacy.md
go test ./internal/invariants -run TestThisRepositorySatisfiesTheInvariants -count=1 -v
paths this repository's own documents name: 34 examined
..\..\docs\privacy.md: it names docs/NO-SUCH-FILE.md, which is not in this tree (document-names-a-path-that-does-not-resolve)
--- FAIL: TestThisRepositorySatisfiesTheInvariants (0.18s)
The examined count is 34 in both runs, so the second refusal is a subject the
leg reached and the first is a subject it never had. The subject in the refusal
line is the path the harness handed the scan, pasted as it printed on the machine
that ran it.
The passing run is the defect. The link nobody can follow is the one that passes.
What this does not decide
Which of the two readings grows is a design question and this issue does not
take it. LinkTargetsWithoutADirectory argues in its own comment that the
parentheses carry the intent, which is the argument for resolving ../NAME there
rather than widening the sentence pattern; against that, its name says what it
returns and a target with a directory in it is not that. A third reading beside
the two is also on the table. Whichever is chosen, the resolution has to stay
relative to the document rather than to the root, which pathsNamedIn already
does for the targets it does reach.
It does not widen PathsNamedInProse to read .. in a sentence. That pattern is
narrow on purpose, and the reason is written where it is narrow.
What done looks like
A document under docs/ linking a root file that is not in the tree is refused
under document-names-a-path-that-does-not-resolve, and the same document
linking one that is there is not.
The guard is proved by a fixture that bites, in the register this repository
already uses for the two neighbouring cases:
git ls-tree --name-only origin/main testdata/invariants/ | grep root-file
testdata/invariants/a-document-linking-a-root-file-that-is-not-there
testdata/invariants/a-document-linking-a-root-file-that-is-there
testdata/invariants/a-root-file-name-in-a-sentence-rather-than-a-link
Done when the near-miss above reddens the leg in the ../ spelling as well as
without it, a fixture holds both directions, and the three links listed above are
subjects the leg examines rather than text it walks past.
What is wrong
A document under
docs/that links a file at the repository root writes thetarget as
../NAME, and that link is read by neither of the two readings thepaths leg joins. So the case #162 raised and #165 closed for a root document is
still open one directory down: a document under
docs/can link a root file thatis not in the tree and nothing here says so.
PathsNamedInProserequires a leading segment naming a directory of this tree,and
..is not one, which is the deliberate narrowness its own comment arguesfor.
LinkTargetsWithoutADirectoryskips any target containing a slash, and../NAMEcontains one. The two readings sit either side of this shape ratherthan one of them reaching it.
Three links on the default branch are in that state today. The two carrying
LICENSEarrived with #163;NOTICE.mdhas been there longer.All three resolve to files that are there, so nothing is broken today. What is
absent is the refusal that would say so on the day one of them is not, and that
is exactly the day the removal in #155 produced:
LICENSEleft the defaultbranch for a day and the operator guide would have pointed at nothing.
What the evidence is
The two readings, at
19ca148a449109074ce2094915844497f96a258b:The gap is one character wide, which is what makes it worth a guard rather than
a habit. Appending a link to a file that does not exist to
docs/privacy.md,first as
../NO-SUCH-FILE.mdand then asNO-SUCH-FILE.md, and running thesame leg over each:
The examined count is 34 in both runs, so the second refusal is a subject the
leg reached and the first is a subject it never had. The subject in the refusal
line is the path the harness handed the scan, pasted as it printed on the machine
that ran it.
The passing run is the defect. The link nobody can follow is the one that passes.
What this does not decide
Which of the two readings grows is a design question and this issue does not
take it.
LinkTargetsWithoutADirectoryargues in its own comment that theparentheses carry the intent, which is the argument for resolving
../NAMEthererather than widening the sentence pattern; against that, its name says what it
returns and a target with a directory in it is not that. A third reading beside
the two is also on the table. Whichever is chosen, the resolution has to stay
relative to the document rather than to the root, which
pathsNamedInalreadydoes for the targets it does reach.
It does not widen
PathsNamedInProseto read..in a sentence. That pattern isnarrow on purpose, and the reason is written where it is narrow.
What done looks like
A document under
docs/linking a root file that is not in the tree is refusedunder
document-names-a-path-that-does-not-resolve, and the same documentlinking one that is there is not.
The guard is proved by a fixture that bites, in the register this repository
already uses for the two neighbouring cases:
Done when the near-miss above reddens the leg in the
../spelling as well aswithout it, a fixture holds both directions, and the three links listed above are
subjects the leg examines rather than text it walks past.