ADR025 RelationshipSideBlocks
Title: Relationship side blocks
- Status: Accepted
- Decided: D2026-09-20
- Followed by: ADR029
EdgeBlocks - Related: ADR018
TemporalDimensions, ADR026ProjectionsStateTheirRules - Introduces: SP027
ReservedRelationshipNameError, SP028SideNotInRelationshipError - Affects:
Parser,Relationship,Ui - Pairings:
- ADR018
TemporalDimensions— parallel thinking over the same ground
- ADR018
Context
A two-word relationship has two sides, and prose can currently say something about only one thing: the pair.
relationship isLeftOf and isRightOf {
describe {
Places sit beside one another.
}
}
That describe covers both directions at once. isLeftOf and isRightOf do not
mean the same thing, and there is nowhere to say so. The same is true of
isHeldBy and hasRole, of isProducedBy and produces — every pair whose two
words carry distinct meaning gets one sentence between them.
A relationship is, in effect, half an entity. A describe inside one lands in
the manifest. A relationships block inside one parses and is silently
discarded — no diagnostic, and nothing appears on either side of the intended
edge. So a relationship can be described but cannot be connected.
What made this visible
Drawing a diagram. An arrow has to point somewhere, so anything rendering a relationship as an arrow has to decide a direction, and nothing in the manifest tells it one. The case that looks like a counter-argument is really the same argument: a diagram may legitimately want to reverse an arrow for emphasis, and "reverse this" is only sayable if there is a canonical direction to deviate from. A declared direction is what makes a rhetorical one expressible, not its competitor.
The same gap shows up in sequences, which is why this record sits alongside 018 rather than under it. That record surveyed three temporal axes and said of the first, ordering, that "prose already does this… this axis needs nothing." The narrower truth is that nothing declares which way a sequence advances.
Both of the universes that model time do the same thing:
dimension episode {
relationship follows and isFollowedBy { }
}
episode S1E2 { relationships { follows { S1E1 } } }
follows here points backward in time. isFollowedBy advances. To the
manifest, that pair is structurally identical to isParentOf and isChildOf —
oriented, but with no statement of which word moves forward. follows is a
sequence and isParentOf is not, and nothing says so.
The field guide compensates by guessing, and the guess is anchored on two
unrelated accidents: it canonicalizes edge direction on the manifest's forward
field, which holds the alphabetically first of the two words, and then picks
which way to read a run by preferring whichever direction agrees with the order
the author happened to declare the concepts in. Reorder the declarations and a
timeline can invert.
This record does not make a timeline renderable, and should not be read as claiming to. It provides somewhere to put the fact. What a universe puts there is its author's own vocabulary, so nothing can draw a timeline from it until some consumer publishes which words it honours and an author opts in. The guessing described above continues for every universe that does not. 018's axis 1 stays open; this record only removes the reason it could not be closed.
Decision
Inside a relationship declaration, a block named after one of the two declared words holds facts about that side.
relationship isLeftOf and isRightOf {
describe {
Places sit beside one another.
}
isRightOf {
describe {
The thing on the right, read as a reader would scan the row.
}
relationships {
indicates { Advancing }
}
}
}
A side block accepts describe and relationships. Pair-level describe,
title and relationships keep their existing meanings, and pair-level
relationships stops being silently discarded — symmetry is a fact about the
pair, advance is a fact about a side, and both levels are wanted.
The name must be one of the two words the relationship declared. A block named anything else is an error rather than a new declaration; a relationship body holds facts about that relationship and its two sides, and nothing else lives there. That is SP028.
A relationship word may not shadow a block valid inside its own body.
describe, title and relationships become unavailable as relationship
names. This is SP027, the same rule as SP021 and SP025 in a smaller scope:
a name may not be chosen that would make an existing name unreachable.
No keyword is added for direction. Advancing above is an ordinary concept
in the author's own universe, related by a word the author declared. Prose gains
somewhere to put the fact and stays silent about what the fact means. Which
names a consumer honours is the consumer's published convention — the same
arrangement as 011, where the language records and never
enforces.
Consequences
The grammar change is purely additive. A block with an unrecognised name inside a relationship body is SP016 today, so no universe that currently compiles uses this shape, and none can change meaning.
The name restriction is technically breaking. All three words are legal
relationship names right now; there is no reserved-word check on them at all.
The odds anyone named a relationship describe are negligible, but by
021 it is a minor with a note rather than a patch.
The field guide has to publish the names it honours. That is the cost of keeping the ontology out of the grammar: a documented convention rather than a keyword. It is versionable and replaceable, which a keyword is not.
forward and inverse in the manifest want renaming. They hold the
alphabetically first and second word, which is not what either name suggests,
and the confusion gets worse once a field exists that means actual direction.
023 already wrote the rule for this: a name may
be corrected when it no longer describes what it holds, while identity may not
be reallocated. Something lexical — first and second — frees the word.
Per-side description is a gain on its own terms. Orientation is one use of this facility, not its justification. The narrower reading of this record is that a relationship has two sides and prose could only describe one of them.
The inverse of a pair-level or side-level edge is not mirrored onto the
target concept. indicates { Advancing } lands on the relationship node and
Advancing gains nothing. Mirroring would put relationship ids into a slot
that currently holds only concept and dimension ids. Adding the mirror later
is additive; removing it later would be breaking.
What to watch. If several projections invent several orientation vocabularies, the one that wins will be whichever shipped first rather than whichever was right. The answer is not to stake a vocabulary quickly, which is a land grab wearing a design's clothes. It is for a projection to say plainly what words it looks for, so an author can opt in on purpose rather than by coincidence. 026 carries that obligation.
Alternatives considered
A keyword for direction, such as ordering follows and isFollowedBy { }.
Cheapest for consumers, because no convention has to be agreed. Rejected: it
puts an ontology back into the grammar, which is what the removed arc layer was.
Every further property anyone wanted — transitivity, acyclicity — would cost
another keyword, and the survey below found only one property clearly needed.
Treat the first-declared word as primary. Rejected twice over. It makes the
reverse unspeakable, because declaring relationship isRightOf and isLeftOf { }
to say something about the other side collides with the original as SP012. And
the manifest does not preserve declaration order at all — the pair is sorted
alphabetically — so the information the rule depends on is already gone.
or instead of and for a bidirectional pair, so that
relationship isLeftOf or isRightOf { } marks two equal sides while and marks
a primary and an opposite. Rejected. It is the keyword alternative wearing a
smaller costume: the distinction lives in the grammar rather than in the
universe. or also reads as a choice between two things when both are true
simultaneously from different ends, and nothing about the pair is disjunctive.
And it smuggles first-is-primary back in through and, which the paragraph above
rejects on independent grounds.
Make relationship words globally addressable, so a fact about a side could be
authored from either end, as it can for concepts. Rejected on namespace grounds.
Relationship words and dimensions do not collide today — a universe may hold both
dimension neighbors and relationship neighbors — and putting words into the
shared namespace creates a third kind of collision needing its own code. It also
gives one relationship three names: a pair identity and two side names.
Author the fact from the concept side, as
orientation Advancing { relationships { isIndicatedBy { isRightOf } } }.
Rejected even setting the namespace question aside. It scatters facts about a
relationship across whichever concepts happen to be their values, so a reader
looking at isRightOf would have to search the universe to learn what was said
about it.
Declare the head of a sequence instead of the direction. Attractive because
it needs no grammar at all: the head is the member with no incoming edge, and
from there the single edge present is the advancing one. Rejected as
insufficient. It fails on a closed run, where every member has edges both ways
and the head says nothing about which neighbour is next. timeOfDay closes, so
the first universe to need this is also the counterexample.
Evidence
The guess in sequences.js is explicit in the source, and honest about itself —
its header notes that nothing in the manifest says which relationships order the
things they touch, so it reads the shape of the edges instead. The direction
choice is this:
// Which way round? Compile order is very often the order the author
// wrote the run in, so prefer whichever direction agrees with it.
const forwardIsReading = rising(fwd) >= rising(rev);
Compiled against the parser at 0.9.0:
| input | result |
|---|---|
relationship isDating and dates { } |
id=V.dates::isDating forward=dates inverse=isDating |
relationship isLeftOf and isRightOf { } then the reverse |
[SP012] Relationship edge "isLeftOf" already exists |
dimension neighbors { } and relationship neighbors { } |
0 errors, both in the manifest |
The first row is the one that settles the primary-declared-first question:
isDating was written first and forward came back as dates.
Inside a relationship body today, describe, title and relationships are all
accepted, and any other block name is SP016. Of those three, relationships
parses and then vanishes, which is the silent case this record also closes.
A survey of what else a relationship might declare found one gap. Bidirectional
versus oriented is already encoded by whether the author gave one word or
two — a one-word relationship mirrors the same word onto both ends, a two-word
one gives each end its own. Cardinality was declined by 011. Transitivity is
derivable by walking, and nesting already covers containment. Acyclicity is a
validation nicety and timeOfDay cycles deliberately. Only advance direction is
both a fact about the world and load-bearing with no renderer present.