suite check: направление ссылок внутри темы проверяется во всём документе

- ссылка на слой своей темы с объявленной осью — ошибка где угодно, а не только
  в норме: гарантированно присутствует в копии один базовый слой
- ссылки на чужую тему вне нормы остаются разрешёнными по META-20
This commit is contained in:
av
2026-07-27 10:27:30 +03:00
parent 51d2050200
commit 709157237d
2 changed files with 82 additions and 20 deletions
+45 -18
View File
@@ -19,7 +19,8 @@ func checkSpread(s *suite.Suite, d *doc.Document, rep *Report) {
checkExtends(s, d, rep)
checkMechanized(s, d, rep)
checkCanonPaths(s, d, rep)
checkForeignTopicPrefix(s, d, rep)
checkForeignTopicInNorm(s, d, rep)
checkOwnTopicLayerRefs(s, d, rep)
}
// checkTopic reconciles the topic from the front matter with the manifest
@@ -151,13 +152,16 @@ func checkCanonPaths(s *suite.Suite, d *doc.Document, rep *Report) {
}
}
// checkForeignTopicPrefix looks for the prefix of a foreign topic inside a norm
// checkForeignTopicInNorm looks for the prefix of a foreign topic inside a norm
// block (META-20). The norm of a rule must be executable holding this one file:
// a repository subscribes to an arbitrary subset of the conventions, and it has
// no dependency graph by construction. The prefix of the base layer of its own
// topic is allowed there (META-24) — an assembled file starts with that layer
// whatever language and stack were chosen.
func checkForeignTopicPrefix(s *suite.Suite, d *doc.Document, rep *Report) {
// no dependency graph by construction.
//
// Outside a norm such a reference is lawful and stays unchecked. A rationale
// that loses its addressee degrades honestly — the cross-check goes, the
// meaning stays — while a norm missing a neighbouring file becomes unenforceable
// in silence.
func checkForeignTopicInNorm(s *suite.Suite, d *doc.Document, rep *Report) {
own := s.Prefix(d)
for _, r := range d.Rules {
for _, norm := range r.Norms() {
@@ -166,21 +170,44 @@ func checkForeignTopicPrefix(s *suite.Suite, d *doc.Document, rep *Report) {
continue
}
target, ok := s.ByPrefix[ref.Prefix]
if !ok {
if !ok || target.Front.Topic == d.Front.Topic {
continue
}
if target.Front.Topic != d.Front.Topic {
rep.Errorf(Spread, d.Path, ref.Line,
"the norm of %s refers to %s from the foreign topic %q: only the rationale looks outward",
r.ID(), ref.Text, target.Front.Topic)
continue
}
if target.Front.Axis() {
rep.Errorf(Spread, d.Path, ref.Line,
"the norm of %s refers to %s, a layer of its own topic but not the base one: that layer reaches the copy through the manifest, so there is no guarantee it stands nearby",
r.ID(), ref.Text)
}
rep.Errorf(Spread, d.Path, ref.Line,
"the norm of %s refers to %s from the foreign topic %q: only the rationale looks outward",
r.ID(), ref.Text, target.Front.Topic)
}
}
}
}
// checkOwnTopicLayerRefs holds the direction of references inside one topic:
// only the base layer may be pointed at, and from anywhere in the document
// rather than from the norm alone.
//
// Guaranteed to stand in the copy is exactly one layer of a topic — the base
// one; it goes in whatever language and stack were chosen (META-24). The order
// of assembly, base then language then stack, is the order of concatenation and
// not a chain of dependency: a consumer is free to take stack=htmx with
// lang=python, so a stack layer has no claim on a language layer either.
//
// The reason reaches past dangling links. The base layer is the text that has
// to hold for every language and every stack; a rationale of its that needs a
// rule of the Go layer to explain itself is the specific leaking into the
// shared, and the reasoning belongs in that layer instead. The base layer is
// therefore left no way to point at a layer above it at all — by design.
func checkOwnTopicLayerRefs(s *suite.Suite, d *doc.Document, rep *Report) {
own := s.Prefix(d)
for _, ref := range refsIn(d, d.Body, d.Len()) {
if ref.Prefix == own || strings.HasPrefix(ref.Prefix, "X") {
continue
}
target, ok := s.ByPrefix[ref.Prefix]
if !ok || target.Front.Topic != d.Front.Topic || !target.Front.Axis() {
continue
}
rep.Errorf(Spread, d.Path, ref.Line,
"the reference %s points at a layer of this topic that is not the base one: only the base layer is guaranteed to stand in a copy, and a rule that needs this one belongs in that layer",
ref.Text)
}
}