Skip to main content
Documentation menu

Standard compatibility: formal specification

1. Status and scope

This document is the normative specification of resolver-level language-standard compatibility filtering. Where implementation code and this document disagree, this document wins. Existing type names in the code (for example those in crates/cabin-core/src/language_standard.rs) are non-authoritative implementation detail; the implementation must be brought into agreement with this document, not the other way around.

The document is self-contained: an implementer must be able to build the compatibility module from this document alone, and a reviewer must be able to check every proof here without external context. It contains no implementation code.

In scope:

  • The per-language requirement domain, its order, and its join.
  • How a dependency target’s declarations map to a requirement on consumers, including header-only inference and the cross-language defaults.
  • How requirements propagate along public dependency edges.
  • Edge compatibility and package-version viability, as used by the resolver to filter candidate versions.
  • Proofs of the algebraic and computational properties the implementation and its tests rely on.

Out of scope (specified elsewhere, consumed here as resolved inputs):

  • The manifest surface: field names, parsing, target-over-package precedence, workspace inheritance, diagnostics, and the interface/implementation contradiction lint. See docs/language-standards.md. This document consumes only the resolved, typed per-target values (D6, D7).
  • Compiler flag lowering and toolchain support validation.
  • How the resolver enumerates candidate versions. This document defines only the viability predicate the resolver applies to them (D14).
  • The post-resolution, build-time interface enforcement documented in docs/language-standards.md. That check runs after a resolution is fixed and keeps its own documented contract; this specification governs which candidate versions the resolver may pick in the first place. The two layers deliberately differ in two defaults. At the resolver, a compiled target with no interface declaration imposes no constraint (D9 row 4) - filtering versions by the implementation-standard fallback would reject resolutions the build-time check is already positioned to diagnose precisely, so the fallback stays a build-time concern. And an explicit "none" is unsatisfiable here (D9 row 1) - the resolver ranks such a version last and selects it only when nothing better is in range, where the post-resolution enforcement then refuses it (preference-mode.md). The build-time check deliberately leaves "none" to that post-resolution layer, whose per-edge ignore-interface-standard override must be able to unblock exactly that class; the range bounds themselves (minimum and maximum) are enforced at both layers. Where the two documents appear to disagree, each governs its own layer; for resolver behavior, this document wins.

2. The model at a glance (informative)

Every dependency target induces, per consumer language, a requirement: the set of consumer levels it accepts - everything (unconstrained), everything from a minimum up, an inclusive bounded range, or nothing (forbidden). Requirements accumulate along public dependency edges by intersecting their accepted sets (the join); an empty intersection accepts nothing. A dependency edge is compatible when the consumer’s compile level, in every language the consumer compiles, lies inside the dependency’s accumulated set. A candidate package version is viable when every edge resolving to it is compatible. Two consequences shape everything downstream: requirements are only partially ordered by strictness (two ranges can be incomparable), and a composed requirement’s two bounds may come from different sources - no algorithm or diagnostic may assume one declaration explains a composed value. Everything below makes this precise and proves it well-behaved.

3. Definitions

Notation: ⊥\bot denotes “absent” for partial attribute values; ∅\emptyset is the empty set; ⊆\subseteq is set inclusion; ≤\le is the level order of D2; ⊑\sqsubseteq is the requirement order of D3. Definitions are numbered D1, D2, …; lemmas L1, …; theorems T1, …; corollaries C1, … Proof ends are marked ■\blacksquare.

D1 (languages). The set of languages is Lang={C,C++}\mathrm{Lang} = \{\mathsf{C}, \mathsf{C{+}{+}}\}.

D2 (levels). Each language has a finite, totally ordered set of ISO standard levels:

CLevel={89,99,11,17,23},ordered 89<99<11<17<23;CxxLevel={98,11,14,17,20,23,26},ordered 98<11<14<17<20<23<26.\begin{aligned} \mathrm{CLevel} &= \{89, 99, 11, 17, 23\}, &\text{ordered } 89 < 99 < 11 < 17 < 23; \\ \mathrm{CxxLevel} &= \{98, 11, 14, 17, 20, 23, 26\}, &\text{ordered } 98 < 11 < 14 < 17 < 20 < 23 < 26. \end{aligned}

The order is chronological enumeration order, not numeric order (98<1198 < 11 in CxxLevel\mathrm{CxxLevel}; 89<1189 < 11 in CLevel\mathrm{CLevel}). There is no equivalence special case anywhere in either chain; in particular c11<c17\texttt{c11} < \texttt{c17} strictly. We write levels as c89, …, c23 and c++98, …, c++26, and write LevelL\mathrm{Level}_L for the level set of language LL (LevelC=CLevel\mathrm{Level}_{\mathsf{C}} = \mathrm{CLevel}, LevelC++=CxxLevel\mathrm{Level}_{\mathsf{C{+}{+}}} = \mathrm{CxxLevel}). We write ⊥L\bot_L for the least element of LevelL\mathrm{Level}_L (c89, c++98).

Remark (aliases are outside the model). c90 is a parser-level alias of c89, and c++03 of c++98. Aliases are normalized by the manifest parser before any value reaches this model; no alias is an element of CLevel\mathrm{CLevel} or CxxLevel\mathrm{CxxLevel}, and nothing in this document mentions them again.

D3 (requirement domain). For each language LL, the per-language requirement domain is the interval domain

ReqL={unconstrained}∪{ [m,↑]:m∈LevelL }∪{ [a,b]:a,b∈LevelL, a≤b }∪{forbidden}\mathrm{Req}_L = \{\textsf{unconstrained}\} \cup \{\, [m, {\uparrow}] : m \in \mathrm{Level}_L \,\} \cup \{\, [a, b] : a, b \in \mathrm{Level}_L,\ a \le b \,\} \cup \{\textsf{forbidden}\}

with the denotation ⟦⋅⟧:ReqL→P(LevelL)\llbracket \cdot \rrbracket : \mathrm{Req}_L \to \mathcal{P}(\mathrm{Level}_L) - the set of consumer levels a requirement accepts:

⟦unconstrained⟧=LevelL⟦[m,↑]⟧={ ℓ:ℓ≥m }⟦[a,b]⟧={ ℓ:a≤ℓ≤b }⟦forbidden⟧=∅\begin{aligned} \llbracket \textsf{unconstrained} \rrbracket &= \mathrm{Level}_L \\ \llbracket [m, {\uparrow}] \rrbracket &= \{\, \ell : \ell \ge m \,\} \\ \llbracket [a, b] \rrbracket &= \{\, \ell : a \le \ell \le b \,\} \\ \llbracket \textsf{forbidden} \rrbracket &= \emptyset \end{aligned}

[m,↑][m, {\uparrow}] is the minimum-only shape (declared min with no max); [a,b][a, b] the bounded shape (declared min and max, inclusive on both ends; a≤ba \le b is a manifest validation invariant - an empty declared range is rejected at parse). The strictness preorder is reverse inclusion of denotations:

r1⊑r2  ⟺  ⟦r2⟧⊆⟦r1⟧r_1 \sqsubseteq r_2 \iff \llbracket r_2 \rrbracket \subseteq \llbracket r_1 \rrbracket

Write r1≈r2r_1 \approx r_2 when both directions hold, i.e. ⟦r1⟧=⟦r2⟧\llbracket r_1 \rrbracket = \llbracket r_2 \rrbracket. ⊑\sqsubseteq is reflexive and transitive; it is antisymmetric only on the quotient by ≈\approx (L1 lists the ≈\approx classes) and it is not total: two ranges can be incomparable - e.g. ⟦[c++11,c++14]⟧\llbracket [\texttt{c++11}, \texttt{c++14}] \rrbracket and ⟦[c++20,c++23]⟧\llbracket [\texttt{c++20}, \texttt{c++23}] \rrbracket contain neither one the other. No definition, algorithm, or diagnostic may assume two requirements are comparable.

D4 (join). For r1,r2∈ReqLr_1, r_2 \in \mathrm{Req}_L, the join r1⊔r2r_1 \sqcup r_2 is the requirement denoting the intersection of the accepted sets: ⟦r1⊔r2⟧=⟦r1⟧∩⟦r2⟧\llbracket r_1 \sqcup r_2 \rrbracket = \llbracket r_1 \rrbracket \cap \llbracket r_2 \rrbracket. The denoted sets are intervals of a finite chain and intervals are closed under intersection, so such a requirement always exists; it is unique up to ≈\approx, and the normative structural rule picks one shape deterministically:

  • if either operand is forbidden\textsf{forbidden}, the join is forbidden\textsf{forbidden};
  • otherwise take the lower bound as the maximum of the operands’ lower bounds (absent when neither has one) and the upper bound as the minimum of the operands’ upper bounds (absent when neither has one);
  • no bounds →\to unconstrained\textsf{unconstrained}; lower bound mm only →\to [m,↑][m, {\uparrow}]; both bounds with a≤ba \le b →\to [a,b][a, b]; both bounds with a>ba > b - the empty intersection - →\to forbidden\textsf{forbidden}.

For a finite set or multiset S⊆ReqLS \subseteq \mathrm{Req}_L, ⨆S\bigsqcup S is the iterated join, with ⨆∅=unconstrained\bigsqcup \emptyset = \textsf{unconstrained} (L2 shows the result is independent of iteration order and multiplicity). An empty intersection arising anywhere in a composition collapses the whole join to forbidden\textsf{forbidden}: no consumer level satisfies the combined requirements, and diagnostics must be able to name both contributing bounds.

Remark (why ≈\approx-equal shapes stay distinct). [m,↑][m, {\uparrow}] and [m,max⁡LevelL][m, \max \mathrm{Level}_L] denote the same set today, and [⊥L,↑][\bot_L, {\uparrow}] denotes the same set as unconstrained\textsf{unconstrained}. The shapes are kept distinct anyway, for two normative reasons. First, provenance: diagnostics report exactly the declared bounds (“c++17 or newer” versus “c++17..c++26”), and “nothing declared” versus “a declared minimum at the lowest level” are different facts about the manifest. Second, chain extension: when a future revision appends a new level to LevelL\mathrm{Level}_L, a minimum-only requirement accepts it and a bounded one does not - the two shapes diverge, so serialized metadata must preserve which one was declared. The structural join of D4 preserves shapes accordingly: it emits a bounded result only when some operand contributed an upper bound.

D5 (targets, dependency graph, public reachability). Fix a finite set TT of targets and a set of directed dependency edges E⊆T×TE \subseteq T \times T, where (c,d)∈E(c, d) \in E means target cc depends on target dd. Each edge is classified public or private; Epub⊆EE_{\mathrm{pub}} \subseteq E is the set of public edges. The graph (T,E)(T, E) is acyclic; acyclicity is guaranteed by resolution before this model applies (a dependency cycle is an error upstream of compatibility filtering).

The intended semantics of the classification, which T4 makes precise as a premise: across any edge (c,d)∈E(c, d) \in E, the consumer cc‘s translation units may include dd‘s public headers; the edge is public exactly when dd‘s public headers are themselves part of cc‘s public interface (re-exported), so that headers reachable through dd‘s public edges are in turn reachable from cc‘s consumers. A private edge exposes dd‘s public headers to cc‘s translation units but not to cc‘s own public headers. How an edge’s classification is declared in manifests is outside this document’s scope.

Define the public reachability set of t∈Tt \in T:

PubReach(t)={t}∪{ u∈T:there is a nonempty path from t to u using only edges in Epub }\mathrm{PubReach}(t) = \{t\} \cup \{\, u \in T : \text{there is a nonempty path from } t \text{ to } u \text{ using only edges in } E_{\mathrm{pub}} \,\}

PubReach(t)\mathrm{PubReach}(t) is finite (it is a subset of TT).

D6 (dependency target attributes). Each target t∈Tt \in T carries the following resolved attributes, produced by the manifest layer (precedence and inheritance already applied; see docs/language-standards.md):

  • kind(t)∈{compiled,header-only}\mathrm{kind}(t) \in \{\textsf{compiled}, \textsf{header-only}\} - whether the target has translation units of its own.
  • For each L∈LangL \in \mathrm{Lang}, an effective implementation standard implL(t)∈LevelL∪{⊥}\mathrm{impl}_L(t) \in \mathrm{Level}_L \cup \{\bot\}, with ⊥\bot meaning the target does not implement LL. Population contract: implL(t)\mathrm{impl}_L(t) is non-⊥\bot exactly when the target itself implements LL - a compiled target implements LL when it has sources of LL (the level then resolves through the usual target-over-package precedence), and a header-only target implements LL only through a target-level implementation declaration. A package-level implementation default alone never populates implL(t)\mathrm{impl}_L(t), mirroring the relevance rule of docs/language-standards.md: an inherited implementation default says how sibling targets compile, not that this target’s headers involve LL.
  • For each L∈LangL \in \mathrm{Lang}, an explicit interface declaration declL(t)\mathrm{decl}_L(t), one of: a declared range (m,M)(m, M) with m∈LevelLm \in \mathrm{Level}_L and M∈LevelL∪{↑}M \in \mathrm{Level}_L \cup \{{\uparrow}\}, m≤Mm \le M (interface-c-standard / interface-cxx-standard; the string form "c++17" is the minimum-only (m,↑)(m, {\uparrow}), the table form { min, max } a bounded (m,M)(m, M)); none\textsf{none}, the declared value "none" (headers not consumable from LL); or ⊥\bot, no explicit interface declaration for LL.

Remark (why D6’s population contract matters). D9 routes on whether implL(d)\mathrm{impl}_L(d) is present. If a package-level implementation default could populate it, a pure-C++ compiled target inheriting a package c-standard would take D9 row 4 (unconstrained\textsf{unconstrained}) instead of row 6 (forbidden\textsf{forbidden}) for C consumers, silently defeating the strict C++-to-C default; and a header-only target inheriting a package cxx-standard while declaring only interface-c-standard would manufacture a C++ interface minimum (row 3) for a language its package never exposed. The population contract rules both out.

D7 (consumer effective standards). A consumer target cc compiles a (possibly empty) set of languages langs(c)⊆Lang\mathrm{langs}(c) \subseteq \mathrm{Lang}, and for each L∈langs(c)L \in \mathrm{langs}(c) has an effective compile level lvl(c,L)∈LevelL\mathrm{lvl}(c, L) \in \mathrm{Level}_L. (A target that compiles a language without an effective standard is a manifest error upstream of this model; lvl\mathrm{lvl} is total on langs(c)\mathrm{langs}(c).) A header-only target has no translation units, so as a consumer it has langs(c)=∅\mathrm{langs}(c) = \emptyset; D13 spells out what its edges mean.

D8 / Invariant I1 (gnu-extensions is excluded). Each target has a boolean gnu-extensions attribute, default false, which selects the GNU spelling of the same ISO level at compiler-flag lowering time. Invariant I1: no definition, function, or predicate in this specification takes gnu-extensions as an input; it never participates in compatibility. In particular ReqOf\mathrm{ReqOf}, RLR_L, satisfies\mathrm{satisfies}, edge compatibility, and viability are independent of every target’s gnu-extensions value. This is a design invariant: future revisions of this specification must preserve it. (GNU dialect strings such as gnu++20 do not exist in manifests and are not levels; see D2.)

D9 (declaration-to-requirement function ReqOf\mathrm{ReqOf}). For a dependency target d∈Td \in T and a consumer language L∈LangL \in \mathrm{Lang}, define ReqOf(d,L)∈ReqL\mathrm{ReqOf}(d, L) \in \mathrm{Req}_L by the first matching row:

#ConditionReqOf(d,L)\mathrm{ReqOf}(d, L)
1declL(d)=none\mathrm{decl}_L(d) = \textsf{none}forbidden\textsf{forbidden}
2declL(d)=(m,M)\mathrm{decl}_L(d) = (m, M)[m,↑][m, {\uparrow}] when M=↑M = {\uparrow}, else [m,M][m, M]
3declL(d)=⊥\mathrm{decl}_L(d) = \bot, implL(d)=m∈LevelL\mathrm{impl}_L(d) = m \in \mathrm{Level}_L, kind(d)=header-only\mathrm{kind}(d) = \textsf{header-only}[m,↑][m, {\uparrow}]
4declL(d)=⊥\mathrm{decl}_L(d) = \bot, implL(d)=m∈LevelL\mathrm{impl}_L(d) = m \in \mathrm{Level}_L, kind(d)=compiled\mathrm{kind}(d) = \textsf{compiled}unconstrained\textsf{unconstrained}
5declL(d)=⊥\mathrm{decl}_L(d) = \bot, implL(d)=⊥\mathrm{impl}_L(d) = \bot, L=C++L = \mathsf{C{+}{+}}unconstrained\textsf{unconstrained}
6declL(d)=⊥\mathrm{decl}_L(d) = \bot, implL(d)=⊥\mathrm{impl}_L(d) = \bot, L=CL = \mathsf{C}forbidden\textsf{forbidden}

The rows are mutually exclusive and exhaustive, so ReqOf\mathrm{ReqOf} is a total function. Row by row:

  • Rows 1-2: an explicit declaration always wins, over inference and over both cross-language defaults. Declaring interface-c-standard on a C++ target is exactly how its headers become consumable from C (overriding row 6), and "none" is how a C target opts out of C++ consumption (overriding row 5).
  • Row 3: header-only inference - a header-only target without an explicit interface declaration for LL infers its interface minimum from its implementation standard for LL.
  • Row 4: a compiled target without an interface declaration imposes no constraint.
  • Row 5: the permissive C-to-C++ default - a target that implements no C++ (in practice, a C target) is consumable from C++ at any C++ level by default.
  • Row 6: the strict C++-to-C default - a target that implements no C (in practice, a C++ target) is not consumable from C unless interface-c-standard is explicitly declared.

Remark (why the defaults are asymmetric). C headers are conventionally consumable from C++ (possibly via extern "C" guards, which are the author’s obligation under Assumption A in T4); C++ headers are in general not valid C. The defaults encode that convention; both are overridable per rows 1-2.

D10 (effective requirement RLR_L). For each language L∈LangL \in \mathrm{Lang}, the effective requirement RL:T→ReqLR_L : T \to \mathrm{Req}_L is defined by the recursion

RL(t)=ReqOf(t,L)⊔⨆{ RL(d):(t,d)∈Epub }R_L(t) = \mathrm{ReqOf}(t, L) \sqcup \bigsqcup \{\, R_L(d) : (t, d) \in E_{\mathrm{pub}} \,\}

where the inner join is over the public dependencies of tt (and is unconstrained\textsf{unconstrained} when tt has none, by the empty-join convention of D4). Requirements propagate along public edges only; private edges of tt do not contribute to RL(t)R_L(t). T1 proves this recursion has exactly one solution on the finite DAG (T,Epub)(T, E_{\mathrm{pub}}) and gives its closed form.

Remark (per-bound provenance). Because the join intersects ranges, the lower and upper bound of RL(t)R_L(t) may be attained by different elements of PubReach(t)\mathrm{PubReach}(t), and a forbidden\textsf{forbidden} may arise either from a single forbidden\textsf{forbidden} contribution (rows 1 and 6 of D9) or from an empty intersection of two bounds. An implementation that explains RL(t)R_L(t) to users must therefore track provenance per bound - one origin chain for the lower bound, one for the upper - and, for an empty intersection, report both clashing chains; a single “origin of the requirement” does not exist in general.

D11 (satisfies\mathrm{satisfies}). For a consumer cc, a language L∈langs(c)L \in \mathrm{langs}(c), and a requirement r∈ReqLr \in \mathrm{Req}_L:

satisfies(c,L,r)=(lvl(c,L)∈⟦r⟧)\mathrm{satisfies}(c, L, r) = \bigl(\mathrm{lvl}(c, L) \in \llbracket r \rrbracket\bigr)

unfolded per shape: true for unconstrained\textsf{unconstrained}; lvl(c,L)≥m\mathrm{lvl}(c, L) \ge m for [m,↑][m, {\uparrow}]; a≤lvl(c,L)≤ba \le \mathrm{lvl}(c, L) \le b for [a,b][a, b]; false for forbidden\textsf{forbidden}.

D12 (satisfaction sets). For r∈ReqLr \in \mathrm{Req}_L, the satisfaction set is the denotation: SatL(r)=⟦r⟧\mathrm{Sat}_L(r) = \llbracket r \rrbracket. By construction, satisfies(c,L,r)\mathrm{satisfies}(c, L, r) iff lvl(c,L)∈SatL(r)\mathrm{lvl}(c, L) \in \mathrm{Sat}_L(r). We drop the subscript and write Sat(r)\mathrm{Sat}(r) when LL is clear. (D11/D12 keep both names so the edge-compatibility prose reads the same as before; they are one function.)

D13 (edge compatibility). A dependency edge (c,d)∈E(c, d) \in E is compatible iff

∀ L∈langs(c): satisfies(c,L,RL(d))\forall\, L \in \mathrm{langs}(c) :\ \mathrm{satisfies}(c, L, R_L(d))

The conjunction ranges over every language the consumer compiles: a mixed-language consumer must satisfy the dependency’s effective requirement for each of its languages. Languages the consumer does not compile impose nothing (in particular, RL(d)=forbiddenR_L(d) = \textsf{forbidden} for a language L∉langs(c)L \notin \mathrm{langs}(c) does not affect the edge). Compatibility is defined per edge; the edge’s own public/private classification does not appear in the condition (both kinds expose dd‘s public headers to cc‘s translation units, per D5).

A header-only consumer compiles no language (langs(c)=∅\mathrm{langs}(c) = \emptyset, D7), so every edge out of it is compatible vacuously - the empty conjunction is true. This is deliberate, not a hole: the target has no translation units for a requirement to constrain, and its dependencies reach the targets that do compile through propagation instead - when the header-only target’s edge to the dependency is public, D10 folds RL(d)R_L(d) into the header-only target’s own effective requirement, and every downstream compiling consumer picks it up across its edge onto the header-only target (Example 3’s chain shows the same mechanism).

D14 (package-version viability). In a candidate resolution, a package version vv is viable iff every dependency edge (c,d)∈E(c, d) \in E whose dependency target dd belongs to vv is compatible. Equivalently: vv is excluded as soon as at least one edge resolving to it is incompatible. Viability is the predicate that governs candidate preference: the resolver applies it as a version-selection ordering (preference-mode.md), never as a hard in-solver filter, and the post-resolution build-time enforcement of docs/language-standards.md is what actually refuses an unviable resolution. How candidates are enumerated is outside this document’s scope; what happens when no candidate is viable is answered by preference mode with select-latest-and-report.

4. Lemmas

L1 (structure of the domain). (ReqL,⊑)(\mathrm{Req}_L, \sqsubseteq) is a finite preorder with least element unconstrained\textsf{unconstrained} and greatest element forbidden\textsf{forbidden}. Its quotient by ≈\approx is a finite partial order in bijection with the set of interval-shaped subsets of LevelL\mathrm{Level}_L (including LevelL\mathrm{Level}_L itself and ∅\emptyset), ordered by reverse inclusion. The ≈\approx classes are exactly:

  • {unconstrained, [⊥L,↑], [⊥L,max⁡LevelL]}\{\textsf{unconstrained},\ [\bot_L, {\uparrow}],\ [\bot_L, \max \mathrm{Level}_L]\} (all denoting LevelL\mathrm{Level}_L);
  • {[m,↑], [m,max⁡LevelL]}\{[m, {\uparrow}],\ [m, \max \mathrm{Level}_L]\} for each m>⊥Lm > \bot_L;
  • the singleton {[a,b]}\{[a, b]\} for each a≤b<max⁡LevelLa \le b < \max \mathrm{Level}_L;
  • the singleton {forbidden}\{\textsf{forbidden}\}.

⊑\sqsubseteq is not total: for disjoint or partially overlapping ranges - e.g. [c++11,c++14][\texttt{c++11}, \texttt{c++14}] and [c++20,c++23][\texttt{c++20}, \texttt{c++23}] - neither denotation contains the other, so neither r1⊑r2r_1 \sqsubseteq r_2 nor r2⊑r1r_2 \sqsubseteq r_1.

Proof. Reflexivity and transitivity of ⊑\sqsubseteq are those of ⊆\subseteq; the bounds follow from ⟦unconstrained⟧=LevelL⊇⟦r⟧⊇∅=⟦forbidden⟧\llbracket \textsf{unconstrained} \rrbracket = \mathrm{Level}_L \supseteq \llbracket r \rrbracket \supseteq \emptyset = \llbracket \textsf{forbidden} \rrbracket. The map r↦⟦r⟧r \mapsto \llbracket r \rrbracket is surjective onto the nonempty intervals, LevelL\mathrm{Level}_L, and ∅\emptyset by construction of D3, and it identifies exactly the listed classes (two shapes denote the same set iff they have the same lower endpoint and both reach the top, or are both empty). Non-totality is the displayed counterexample. ■\blacksquare

L2 (bounded join-semilattice). The structural join of D4 is associative, commutative, and idempotent on shapes (not merely up to ≈\approx), with unconstrained\textsf{unconstrained} as identity and forbidden\textsf{forbidden} as absorbing element. Consequently the set join ⨆S\bigsqcup S of D4 is well-defined for every finite multiset SS - independent of iteration order and multiplicity - with ⨆∅=unconstrained\bigsqcup \emptyset = \textsf{unconstrained} and ⨆(S∪S′)=⨆S⊔⨆S′\bigsqcup (S \cup S') = \bigsqcup S \sqcup \bigsqcup S'.

Proof. Represent every non-forbidden\textsf{forbidden} shape by its bound pair (m−,m+)∈(LevelL∪{⊥})×(LevelL∪{⊥})(m^{-}, m^{+}) \in (\mathrm{Level}_L \cup \{\bot\}) \times (\mathrm{Level}_L \cup \{\bot\}), reading ⊥\bot as “no bound”: unconstrained=(⊥,⊥)\textsf{unconstrained} = (\bot, \bot), [m,↑]=(m,⊥)[m, {\uparrow}] = (m, \bot), [a,b]=(a,b)[a, b] = (a, b). The structural rule combines pairs componentwise - max⁡\max on lower bounds and min⁡\min on upper bounds, each with ⊥\bot as identity - and both components are commutative idempotent monoids, so the pair combination is associative, commutative, and idempotent, with (⊥,⊥)(\bot, \bot) as identity. The final rendering (collapsing an inverted pair to forbidden\textsf{forbidden}) does not disturb this: an inverted pair arises in some grouping iff the overall intersection is empty (the componentwise bounds are grouping-independent), and forbidden\textsf{forbidden} absorbs every further join, so all groupings agree on the shape. Well-definedness of ⨆\bigsqcup and the flattening law follow as usual from associativity, commutativity, idempotence, and the identity. ■\blacksquare

L3 (strictness is denotational). r1⊑r2r_1 \sqsubseteq r_2 iff Sat(r2)⊆Sat(r1)\mathrm{Sat}(r_2) \subseteq \mathrm{Sat}(r_1) - definitional after D3/D12, recorded as a lemma because downstream proofs cite it. The induced equivalence is the ≈\approx of D3, whose classes L1 lists; ≈\approx-equal shapes are behaviorally identical for satisfies\mathrm{satisfies} and differ only in provenance and under future chain extension (the remark after D4).

L4 (join is intersection of satisfaction sets). For all r1,r2∈ReqLr_1, r_2 \in \mathrm{Req}_L: Sat(r1⊔r2)=Sat(r1)∩Sat(r2)\mathrm{Sat}(r_1 \sqcup r_2) = \mathrm{Sat}(r_1) \cap \mathrm{Sat}(r_2), and for finite SS: Sat(⨆S)=⋂r∈SSat(r)\mathrm{Sat}(\bigsqcup S) = \bigcap_{r \in S} \mathrm{Sat}(r), with the empty intersection denoting LevelL\mathrm{Level}_L.

Proof. The binary claim is D4’s defining property; what needs proof is that the structural rule realizes it. Membership in ⟦r1⟧∩⟦r2⟧\llbracket r_1 \rrbracket \cap \llbracket r_2 \rrbracket means satisfying every lower bound and every upper bound present among the operands, i.e. ℓ≥\ell \ge the maximum of the lower bounds and ℓ≤\ell \le the minimum of the upper bounds (each vacuous when absent) - exactly the set the structural result denotes, including the empty case rendered forbidden\textsf{forbidden} and the no-bound cases rendered unconstrained\textsf{unconstrained} / [m,↑][m, {\uparrow}]. The finite generalization follows by induction on ∣S∣|S| using L2. ■\blacksquare

L5 (antitonicity of satisfies\mathrm{satisfies}). If r1⊑r2r_1 \sqsubseteq r_2 and satisfies(c,L,r2)\mathrm{satisfies}(c, L, r_2), then satisfies(c,L,r1)\mathrm{satisfies}(c, L, r_1).

Proof. lvl(c,L)∈Sat(r2)⊆Sat(r1)\mathrm{lvl}(c, L) \in \mathrm{Sat}(r_2) \subseteq \mathrm{Sat}(r_1) by L3. ■\blacksquare

L6 (satisfaction sets are convex, not upward closed in general). Every Sat(r)\mathrm{Sat}(r) is an order-convex subset of LevelL\mathrm{Level}_L: if ℓ1≤x≤ℓ2\ell_1 \le x \le \ell_2 with ℓ1,ℓ2∈Sat(r)\ell_1, \ell_2 \in \mathrm{Sat}(r), then x∈Sat(r)x \in \mathrm{Sat}(r). Upward closure - “raising a consumer’s level never breaks satisfaction” - fails exactly for the bounded shapes [a,b][a, b] with b<max⁡LevelLb < \max \mathrm{Level}_L: raising past bb breaks satisfaction. Every other shape’s denotation is upward closed: LevelL\mathrm{Level}_L itself, the up-sets of the minimum-only shapes, the empty set (vacuously), and a bounded range whose b=max⁡LevelLb = \max \mathrm{Level}_L - though the last stays upward closed only for today’s chain, since a future appended level falls outside it (the remark after D4).

Proof. Each denotation of D3 is LevelL\mathrm{Level}_L, an up-set, an interval, or empty; all are convex. For the failure claim: b∈Sat([a,b])b \in \mathrm{Sat}([a, b]) and any ℓ>b\ell > b is not, and such ℓ\ell exists exactly when b<max⁡LevelLb < \max \mathrm{Level}_L; the remaining shapes’ denotations are upward closed by inspection. ■\blacksquare

Remark (normative consequence for remedies). Diagnostics and documentation must not unconditionally advise raising a consumer’s standard. Below a minimum, raising (up to any cap) helps; above a maximum, only lowering the consumer, or changing the dependency, can - and against forbidden\textsf{forbidden} nothing at the standard level helps.

L7 (set joins are monotone). For finite multisets S⊆S′S \subseteq S' over ReqL\mathrm{Req}_L: ⨆S⊑⨆S′\bigsqcup S \sqsubseteq \bigsqcup S'. Moreover, if S={r1,…,rk}S = \{r_1, \ldots, r_k\} and S′={r1′,…,rk′}S' = \{r'_1, \ldots, r'_k\} with ri⊑ri′r_i \sqsubseteq r'_i pointwise, then ⨆S⊑⨆S′\bigsqcup S \sqsubseteq \bigsqcup S'.

Proof. By L4, Sat(⨆S′)=⋂r∈S′Sat(r)⊆⋂r∈SSat(r)=Sat(⨆S)\mathrm{Sat}(\bigsqcup S') = \bigcap_{r \in S'} \mathrm{Sat}(r) \subseteq \bigcap_{r \in S} \mathrm{Sat}(r) = \mathrm{Sat}(\bigsqcup S) for the subset claim (intersecting more sets can only shrink the result), and ⋂iSat(ri′)⊆⋂iSat(ri)\bigcap_i \mathrm{Sat}(r'_i) \subseteq \bigcap_i \mathrm{Sat}(r_i) for the pointwise claim (Sat(ri′)⊆Sat(ri)\mathrm{Sat}(r'_i) \subseteq \mathrm{Sat}(r_i) componentwise by L3). Both are ⊑\sqsubseteq by L3. ■\blacksquare

5. Theorems

T1 (RLR_L is well-defined on finite DAGs). On the finite DAG (T,Epub)(T, E_{\mathrm{pub}}), the recursion of D10 has exactly one solution, namely the closed form

RL(t)=⨆{ ReqOf(u,L):u∈PubReach(t) }R_L(t) = \bigsqcup \{\, \mathrm{ReqOf}(u, L) : u \in \mathrm{PubReach}(t) \,\}

and it is computable by processing targets in any topological order of (T,Epub)(T, E_{\mathrm{pub}}) (dependencies before dependents), with the same result for every such order. Computation terminates.

Proof.

Well-foundedness. Since (T,Epub)(T, E_{\mathrm{pub}}) is finite and acyclic (D5), define h(t)h(t) as the length of the longest path from tt using edges in EpubE_{\mathrm{pub}} (finite: paths in a finite DAG cannot repeat vertices, so their length is bounded by ∣T∣−1\lvert T \rvert - 1). If (t,d)∈Epub(t, d) \in E_{\mathrm{pub}} then h(d)<h(t)h(d) < h(t) (any path from dd extends to a longer one from tt).

Existence and uniqueness. We show by strong induction on h(t)h(t) that any function f:T→ReqLf : T \to \mathrm{Req}_L satisfying the recursion of D10 must agree with the closed form at tt; since the closed form itself is a well-defined function (each PubReach(t)\mathrm{PubReach}(t) is a finite set and ⨆\bigsqcup of a finite set is well-defined by L2), and substituting it into the recursion succeeds (verified below), existence and uniqueness both follow.

Fix tt and assume the claim for all uu with h(u)<h(t)h(u) < h(t); in particular for every public dependency dd of tt. Then for any solution ff:

f(t)=ReqOf(t,L)⊔⨆{ f(d):(t,d)∈Epub }(recursion, D10)=ReqOf(t,L)⊔⨆{ ⨆{ ReqOf(u,L):u∈PubReach(d) }:(t,d)∈Epub }(IH)=⨆({ReqOf(t,L)}∪⋃{ { ReqOf(u,L):u∈PubReach(d) }:(t,d)∈Epub })=⨆{ ReqOf(u,L):u∈PubReach(t) }\begin{aligned} f(t) &= \mathrm{ReqOf}(t, L) \sqcup \bigsqcup \{\, f(d) : (t, d) \in E_{\mathrm{pub}} \,\} &&\text{(recursion, D10)} \\ &= \mathrm{ReqOf}(t, L) \sqcup \bigsqcup \Bigl\{\, \bigsqcup \{\, \mathrm{ReqOf}(u, L) : u \in \mathrm{PubReach}(d) \,\} : (t, d) \in E_{\mathrm{pub}} \,\Bigr\} &&\text{(IH)} \\ &= \bigsqcup \Bigl( \{\mathrm{ReqOf}(t, L)\} \cup \bigcup \{\, \{\, \mathrm{ReqOf}(u, L) : u \in \mathrm{PubReach}(d) \,\} : (t, d) \in E_{\mathrm{pub}} \,\} \Bigr) && \\ &= \bigsqcup \{\, \mathrm{ReqOf}(u, L) : u \in \mathrm{PubReach}(t) \,\} && \end{aligned}

The third equality is the flattening law ⨆(S∪S′)=⨆S⊔⨆S′\bigsqcup (S \cup S') = \bigsqcup S \sqcup \bigsqcup S' iterated over the finitely many public dependencies, valid by associativity, commutativity, and the identity element (L2). The fourth holds because

PubReach(t)={t}∪⋃{ PubReach(d):(t,d)∈Epub }\mathrm{PubReach}(t) = \{t\} \cup \bigcup \{\, \mathrm{PubReach}(d) : (t, d) \in E_{\mathrm{pub}} \,\}

by definition of PubReach\mathrm{PubReach} (D5): a nonempty public path from tt starts with some public edge (t,d)(t, d) and continues as a possibly-empty public path from dd. Note that a target uu reachable through several public dependencies of tt (a diamond) contributes ReqOf(u,L)\mathrm{ReqOf}(u, L) once on the right but possibly several times in the flattened multiset on the left; idempotence (L2) makes the multiset join equal to the set join, so the equality holds regardless of path multiplicity. Reading the chain of equalities backwards also shows the closed form is a solution of the recursion, completing existence.

Order-independence and termination. A topological order of the finite DAG exists and every prefix of the computation only reads values of targets already processed (each dd with (t,d)∈Epub(t, d) \in E_{\mathrm{pub}} precedes tt). Whatever topological order is chosen, the computed value at each tt satisfies the recursion, and by uniqueness it equals the closed form - so all orders agree (confluence). Termination is immediate: ∣T∣\lvert T \rvert steps, each a finite join. ■\blacksquare

T2 (growth). Let two attribute assignments over the same target set TT be given, with public edge sets Epub⊆Epub′E_{\mathrm{pub}} \subseteq E'_{\mathrm{pub}} and requirement functions satisfying ReqOf(u,L)⊑ReqOf′(u,L)\mathrm{ReqOf}(u, L) \sqsubseteq \mathrm{ReqOf}'(u, L) for every u∈Tu \in T. Write RLR_L and RL′R'_L for the respective effective requirements. Then for every t∈Tt \in T:

RL(t)⊑RL′(t)R_L(t) \sqsubseteq R'_L(t)

Proof. Epub⊆Epub′E_{\mathrm{pub}} \subseteq E'_{\mathrm{pub}} implies PubReach(t)⊆PubReach′(t)\mathrm{PubReach}(t) \subseteq \mathrm{PubReach}'(t) for every tt (every public path in the smaller graph is one in the larger). Using the closed form (T1) twice:

RL(t)=⨆{ ReqOf(u,L):u∈PubReach(t) }⊑⨆{ ReqOf′(u,L):u∈PubReach(t) }(pointwise, L7 second claim)⊑⨆{ ReqOf′(u,L):u∈PubReach′(t) }(superset, L7 first claim)=RL′(t)■\begin{aligned} R_L(t) &= \bigsqcup \{\, \mathrm{ReqOf}(u, L) : u \in \mathrm{PubReach}(t) \,\} && \\ &\sqsubseteq \bigsqcup \{\, \mathrm{ReqOf}'(u, L) : u \in \mathrm{PubReach}(t) \,\} &&\text{(pointwise, L7 second claim)} \\ &\sqsubseteq \bigsqcup \{\, \mathrm{ReqOf}'(u, L) : u \in \mathrm{PubReach}'(t) \,\} &&\text{(superset, L7 first claim)} \\ &= R'_L(t) && \blacksquare \end{aligned}

C1 (adding a public dependency never lowers RLR_L). Adding an edge to EpubE_{\mathrm{pub}} (leaving every ReqOf\mathrm{ReqOf} unchanged, and preserving acyclicity) satisfies T2’s hypotheses with equality on ReqOf\mathrm{ReqOf}, so RL(t)⊑RL′(t)R_L(t) \sqsubseteq R'_L(t) for every tt.

C2 (adding a declaration where nothing was imposed never lowers RLR_L). Suppose target uu has ReqOf(u,L)=unconstrained\mathrm{ReqOf}(u, L) = \textsf{unconstrained} - by D9 that is a compiled target with no interface declaration for a language it implements (row 4), or a target consumed from C++ under the permissive default (row 5). Changing uu‘s declarations so that ReqOf′(u,L)=r\mathrm{ReqOf}'(u, L) = r for any r∈ReqLr \in \mathrm{Req}_L, leaving everything else fixed, satisfies T2’s hypotheses: unconstrained\textsf{unconstrained} is the least element (L1), so unconstrained⊑r\textsf{unconstrained} \sqsubseteq r, and every other target’s ReqOf\mathrm{ReqOf} is unchanged. Hence RL(t)⊑RL′(t)R_L(t) \sqsubseteq R'_L(t) for every tt.

C3 (viable versions can only shrink). Under the hypotheses of T2 (in particular after any change covered by C1 or C2), every dependency edge compatible under the primed assignment is compatible under the unprimed one; consequently every package version viable under the primed assignment is viable under the unprimed one - the set of viable versions can only shrink as public edges are added or requirements grow.

Proof. Let edge (c,d)(c, d) be compatible under the primed assignment: for every L∈langs(c)L \in \mathrm{langs}(c), satisfies(c,L,RL′(d))\mathrm{satisfies}(c, L, R'_L(d)). By T2, RL(d)⊑RL′(d)R_L(d) \sqsubseteq R'_L(d), so by antitonicity (L5) satisfies(c,L,RL(d))\mathrm{satisfies}(c, L, R_L(d)) for every such LL: the edge is compatible unprimed. Viability of a version is a conjunction of edge compatibilities (D14); each conjunct transfers, so viability transfers. Contrapositively, growing the requirements can only remove versions from the viable set, never add any. ■\blacksquare

Remark (the two deliberate exceptions). C2’s hypothesis - that the prior requirement was unconstrained\textsf{unconstrained} - is essential, and two rows of D9 sit above the bottom by design:

  • A header-only target with no declaration already imposes its inferred implementation minimum (row 3). Declaring an explicit, older interface-*-standard replaces [implL(u),↑][\mathrm{impl}_L(u), {\uparrow}] by a wider range - a relaxation that can move RLR_L down. That is the declared purpose of the field: promising less than the implementation uses.
  • A target consumed from C under the strict default already imposes forbidden\textsf{forbidden} (row 6). Declaring interface-c-standard replaces forbidden\textsf{forbidden} by a range - again a relaxation moving down, and again the point of the declaration.
  • Symmetrically, a change that only tightens - raising a minimum, lowering or adding a maximum, shifting a range so that it excludes previously accepted levels - satisfies T2’s hypotheses in the tightening direction and can only shrink the viable set (C3). A sideways shift both relaxes and tightens; neither T2 direction applies to it as a whole, and the viable set can change arbitrarily.

These moves are relaxations by the author of the dependency, widening its consumer set; T2 and C3 are about changes that tighten requirements. Both directions are monotone: T2 applied with the roles of the two assignments swapped shows a pointwise relaxation can only move every RLR_L down and can only grow the viable set.

T3 (decidability and complexity). All predicates of this specification are decidable, with:

  1. satisfies(c,L,r)\mathrm{satisfies}(c, L, r) in O(1)O(1);
  2. RL(t)R_L(t) for all t∈Tt \in T in O(∣T∣+∣Epub∣)O(\lvert T \rvert + \lvert E_{\mathrm{pub}} \rvert) per language, hence O(∣T∣+∣E∣)O(\lvert T \rvert + \lvert E \rvert) total (with ∣Lang∣=2\lvert \mathrm{Lang} \rvert = 2 a constant);
  3. viability of all package versions in a candidate resolution in O(∣T∣+∣E∣)O(\lvert T \rvert + \lvert E \rvert) overall.

Proof.

(1) A requirement is one of four shapes, and the range cases are at most two comparisons of elements of a fixed finite chain (D2): constant work.

(2) Fix LL. Compute a topological order of (T,Epub)(T, E_{\mathrm{pub}}) in O(∣T∣+∣Epub∣)O(\lvert T \rvert + \lvert E_{\mathrm{pub}} \rvert) (standard for finite DAGs). Process targets in reverse dependency order; at target tt, fold ⊔\sqcup over ReqOf(t,L)\mathrm{ReqOf}(t, L) (constant work: D9 is a six-row decision table over already-resolved attributes) and the stored values RL(d)R_L(d) of its public dependencies (one O(1)O(1) join per outgoing public edge: D4’s structural rule is a constant number of comparisons). Each edge is touched once, each target once: O(∣T∣+∣Epub∣)O(\lvert T \rvert + \lvert E_{\mathrm{pub}} \rvert). Correctness: this is exactly the topological computation of T1, which proved it yields the unique solution regardless of the order chosen. Summing over the two languages gives O(∣T∣+∣E∣)O(\lvert T \rvert + \lvert E \rvert).

(3) With all RLR_L values stored, checking one edge (c,d)(c, d) is a conjunction over langs(c)⊆Lang\mathrm{langs}(c) \subseteq \mathrm{Lang}, so at most two O(1)O(1) satisfies\mathrm{satisfies} checks. Viability of every version is the conjunction, over each version’s incoming edges, of those edge checks (D14); every edge belongs to exactly one dependency target hence to one version’s conjunction, so all versions together cost O(∣E∣)O(\lvert E \rvert). Adding (2)‘s precomputation gives O(∣T∣+∣E∣)O(\lvert T \rvert + \lvert E \rvert). Decidability is immediate: every domain in sight is finite and every function is total (D9’s table is exhaustive; D11 is a three-case match). ■\blacksquare

Assumption A (author obligation). For every target uu and language LL: every consumer level ℓ∈SatL(ReqOf(u,L))\ell \in \mathrm{Sat}_L(\mathrm{ReqOf}(u, L)) can compile uu‘s public headers as language LL at level ℓ\ell. Unfolding D12, that means: if uu declares (or, per D9, infers) an interface range, its public headers compile under every consumer level inside that range - including, for a bounded range, no newer than its maximum, which is exactly how an author records headers that use features a later standard removed; if ReqOf(u,L)=unconstrained\mathrm{ReqOf}(u, L) = \textsf{unconstrained}, they compile under every level of LL (for the C-to-C++ default, row 5 of D9, this is the C author’s obligation that the headers are consumable from any C++ level, for example via extern "C" guards); if ReqOf(u,L)=forbidden\mathrm{ReqOf}(u, L) = \textsf{forbidden}, Sat=∅\mathrm{Sat} = \emptyset and the obligation is vacuous. Assumption A is the package author’s obligation, not something Cabin verifies (see Non-goals).

T4 (conditional semantic soundness). Let (c,d)∈E(c, d) \in E be a compatible edge (D13), and suppose Assumption A holds for every target u∈PubReach(d)u \in \mathrm{PubReach}(d) and every language L∈langs(c)L \in \mathrm{langs}(c). Then for every such uu and LL:

lvl(c,L)∈SatL(ReqOf(u,L))\mathrm{lvl}(c, L) \in \mathrm{Sat}_L(\mathrm{ReqOf}(u, L))

and therefore, under A, every public header of every target in PubReach(d)\mathrm{PubReach}(d) compiles as language LL at cc‘s level lvl(c,L)\mathrm{lvl}(c, L). Since by D5 the public headers reachable from cc‘s translation units through the edge (c,d)(c, d) are exactly the public headers of targets in PubReach(d)\mathrm{PubReach}(d), edge compatibility implies that cc‘s translation units can compile every public include they can reach through dd.

Proof. Fix L∈langs(c)L \in \mathrm{langs}(c) and u∈PubReach(d)u \in \mathrm{PubReach}(d). By the closed form (T1), RL(d)=⨆{ ReqOf(w,L):w∈PubReach(d) }R_L(d) = \bigsqcup \{\, \mathrm{ReqOf}(w, L) : w \in \mathrm{PubReach}(d) \,\}, and a join is an upper bound of each of its elements (L2), so

ReqOf(u,L)⊑RL(d)\mathrm{ReqOf}(u, L) \sqsubseteq R_L(d)

Compatibility of the edge gives satisfies(c,L,RL(d))\mathrm{satisfies}(c, L, R_L(d)), i.e. lvl(c,L)∈Sat(RL(d))\mathrm{lvl}(c, L) \in \mathrm{Sat}(R_L(d)) (D12). By L3, Sat(RL(d))⊆Sat(ReqOf(u,L))\mathrm{Sat}(R_L(d)) \subseteq \mathrm{Sat}(\mathrm{ReqOf}(u, L)), hence lvl(c,L)∈Sat(ReqOf(u,L))\mathrm{lvl}(c, L) \in \mathrm{Sat}(\mathrm{ReqOf}(u, L)). Assumption A for uu and LL states that every level in Sat(ReqOf(u,L))\mathrm{Sat}(\mathrm{ReqOf}(u, L)) compiles uu‘s public headers as LL; applying it at ℓ=lvl(c,L)\ell = \mathrm{lvl}(c, L) yields the conclusion for uu. As uu and LL were arbitrary, the claim holds for all of PubReach(d)\mathrm{PubReach}(d) and all of langs(c)\mathrm{langs}(c). ■\blacksquare

Remark (scope of the guarantee). T4 speaks only about headers reachable along public edges below dd. Headers of dd‘s private dependencies are, by the edge semantics of D5, not included from dd‘s public headers, so cc‘s translation units never see them through this edge and no constraint is needed; the private dependency’s own edge from dd is checked separately (D13 applies to every edge). T4 is exactly as strong as Assumption A: Cabin checks the arithmetic, the author promises the headers (see Non-goals).

6. Non-goals

This specification makes no claim about any of the following, and no lemma or theorem above should be read as implying one:

  • ODR consistency across #if __cplusplus (or __STDC_VERSION__) branches. Two translation units at different levels may see different definitions of the same entity through the same header; T4 guarantees each unit compiles, not that their definitions are link-compatible or ODR-consistent.
  • ABI and mangling. No guarantee that objects compiled at different levels link correctly or mean the same thing at the boundary - for example, C++17 made noexcept part of the function type, changing template results and mangling relative to C++14 for the same header.
  • C++20 module BMI compatibility. Built module interfaces are compiler-, version-, flag-, and level-sensitive; nothing here models them.
  • Verification of Assumption A itself. Cabin does not compile-check a dependency’s headers at each level of Sat(ReqOf(⋅))\mathrm{Sat}(\mathrm{ReqOf}(\cdot)); A is the package author’s obligation, and a violated A voids T4’s conclusion for the offending header without affecting any other result in this document (T1-T3 and L1-L7 are purely order-theoretic and hold regardless).

Appendix: worked examples

All domains in this specification are finite: ∣CLevel∣=5\lvert \mathrm{CLevel} \rvert = 5, ∣CxxLevel∣=7\lvert \mathrm{CxxLevel} \rvert = 7, ∣ReqC∣=2+5+15=22\lvert \mathrm{Req}_{\mathsf{C}} \rvert = 2 + 5 + 15 = 22, ∣ReqC++∣=2+7+28=37\lvert \mathrm{Req}_{\mathsf{C{+}{+}}} \rvert = 2 + 7 + 28 = 37 (two sentinels, the minimum-only shapes, and one bounded shape per pair a≤ba \le b). Every per-pair claim below - and every lemma about ⊑\sqsubseteq, ⊔\sqcup, Sat\mathrm{Sat}, and satisfies\mathrm{satisfies} - is therefore verifiable by exhaustive enumeration over the full domain, and the implementation’s test suite is expected to do exactly that: enumerate all pairs (and triples, for associativity, at least on the C chain) and assert the property, citing the lemma it checks (L2 associativity, commutativity, idempotence, identity and absorption; L4 intersection including the empty-intersection collapse; L5 antitonicity; L6 convexity and the failure of upward closure on bounded shapes; L1 non-totality by counterexample). The examples pick representative points of that space and work them end to end.

Reference table - satisfies\mathrm{satisfies} over all of CxxLevel\mathrm{CxxLevel} for the requirements used below (rows are requirements rr, columns consumer levels ℓ\ell; ✓\checkmark means ℓ∈Sat(r)\ell \in \mathrm{Sat}(r), i.e. satisfies\mathrm{satisfies} is true per D11/D12, and an empty cell means false):

Requirement / levelc++98c++11c++14c++17c++20c++23c++26
unconstrained\textsf{unconstrained}✓\checkmark✓\checkmark✓\checkmark✓\checkmark✓\checkmark✓\checkmark✓\checkmark
[c++17,↑][\texttt{c++17}, {\uparrow}]✓\checkmark✓\checkmark✓\checkmark✓\checkmark
[c++20,↑][\texttt{c++20}, {\uparrow}]✓\checkmark✓\checkmark✓\checkmark
[c++11,c++14][\texttt{c++11}, \texttt{c++14}]✓\checkmark✓\checkmark
forbidden\textsf{forbidden}

Each row is an order-convex block (L6); the bounded row is the one that is not upward closed - it ends at its cap, while the unconstrained\textsf{unconstrained} and minimum-only rows are up-sets and the forbidden\textsf{forbidden} row is upward closed vacuously. [c++20,↑]⊔[c++11,c++14]=forbidden[\texttt{c++20}, {\uparrow}] \sqcup [\texttt{c++11}, \texttt{c++14}] = \textsf{forbidden}: the two rows share no column (D4’s empty-intersection collapse).

Example 1: C++23 implementation, c++17 interface, consumed from c++17

Library ZZ: kind(Z)=compiled\mathrm{kind}(Z) = \textsf{compiled}, implC++(Z)=c++23\mathrm{impl}_{\mathsf{C{+}{+}}}(Z) = \texttt{c++23}, declC++(Z)=c++17\mathrm{decl}_{\mathsf{C{+}{+}}}(Z) = \texttt{c++17} (interface-cxx-standard = "c++17": the public headers only need C++17 even though the implementation compiles as C++23). ZZ has no public dependencies. Consumer XX: langs(X)={C++}\mathrm{langs}(X) = \{\mathsf{C{+}{+}}\}, lvl(X,C++)=c++17\mathrm{lvl}(X, \mathsf{C{+}{+}}) = \texttt{c++17}.

  • ReqOf(Z,C++)=[c++17,↑]\mathrm{ReqOf}(Z, \mathsf{C{+}{+}}) = [\texttt{c++17}, {\uparrow}] by D9 row 2 - the explicit declaration wins; the implementation standard never enters (contrast Example 5, where it would infer [c++23,↑][\texttt{c++23}, {\uparrow}] only for a header-only target; for this compiled target an absent declaration would give unconstrained\textsf{unconstrained} by row 4).
  • RC++(Z)=ReqOf(Z,C++)⊔⨆∅=[c++17,↑]⊔unconstrained=[c++17,↑]R_{\mathsf{C{+}{+}}}(Z) = \mathrm{ReqOf}(Z, \mathsf{C{+}{+}}) \sqcup \bigsqcup \emptyset = [\texttt{c++17}, {\uparrow}] \sqcup \textsf{unconstrained} = [\texttt{c++17}, {\uparrow}] (D10, D4, L2 identity).
  • Edge (X,Z)(X, Z): satisfies(X,C++,[c++17,↑])\mathrm{satisfies}(X, \mathsf{C{+}{+}}, [\texttt{c++17}, {\uparrow}]) iff c++17≥c++17\texttt{c++17} \ge \texttt{c++17}: true - see the [c++17,↑][\texttt{c++17}, {\uparrow}] row of the reference table. The edge is compatible (D13); if it is the only edge resolving to ZZ‘s version, that version is viable (D14).

Example 2: diamond - consumers at c++17 and c++23 sharing one dependency

Targets XX (lvl(X,C++)=c++17\mathrm{lvl}(X, \mathsf{C{+}{+}}) = \texttt{c++17}) and YY (lvl(Y,C++)=c++23\mathrm{lvl}(Y, \mathsf{C{+}{+}}) = \texttt{c++23}) both depend on library ZZ (kind(Z)=compiled\mathrm{kind}(Z) = \textsf{compiled}, declC++(Z)=c++20\mathrm{decl}_{\mathsf{C{+}{+}}}(Z) = \texttt{c++20}, no public dependencies), and some root depends on both XX and YY - a diamond with ZZ shared at the bottom, both edges resolving to the same candidate version vv of ZZ.

  • RC++(Z)=[c++20,↑]R_{\mathsf{C{+}{+}}}(Z) = [\texttt{c++20}, {\uparrow}] as in Example 1.
  • Edge (Y,Z)(Y, Z): c++23≥c++20\texttt{c++23} \ge \texttt{c++20} - compatible.
  • Edge (X,Z)(X, Z): c++17≥c++20\texttt{c++17} \ge \texttt{c++20} is false (c++17<c++20\texttt{c++17} < \texttt{c++20} in D2’s chain) - incompatible.
  • Viability (D14) is a conjunction over every edge resolving to vv: the (Y,Z)(Y, Z) edge cannot rescue vv; because (X,Z)(X, Z) is incompatible, vv is not viable, and the resolver must find a version of ZZ whose requirement XX satisfies (or fail). One incompatible consumer poisons the version for the whole graph - exactly the per-edge conjunction of D13/D14. (Sat\mathrm{Sat} view: YY sits inside Sat([c++20,↑])\mathrm{Sat}([\texttt{c++20}, {\uparrow}]), XX below it.)

Example 3: "none" on a transitive public dependency poisons the root

Chain Root→A→B\mathrm{Root} \to A \to B, both edges public; every target compiles only C++. Root\mathrm{Root} has lvl(Root,C++)=c++26\mathrm{lvl}(\mathrm{Root}, \mathsf{C{+}{+}}) = \texttt{c++26} - the newest level there is. AA is a compiled library with no interface declaration; BB declares interface-cxx-standard = "none".

  • ReqOf(B,C++)=forbidden\mathrm{ReqOf}(B, \mathsf{C{+}{+}}) = \textsf{forbidden} (D9 row 1). RC++(B)=forbiddenR_{\mathsf{C{+}{+}}}(B) = \textsf{forbidden}.
  • ReqOf(A,C++)=unconstrained\mathrm{ReqOf}(A, \mathsf{C{+}{+}}) = \textsf{unconstrained} (D9 row 4). By D10: RC++(A)=unconstrained⊔RC++(B)=unconstrained⊔forbidden=forbiddenR_{\mathsf{C{+}{+}}}(A) = \textsf{unconstrained} \sqcup R_{\mathsf{C{+}{+}}}(B) = \textsf{unconstrained} \sqcup \textsf{forbidden} = \textsf{forbidden} - the absorbing element of L2 in action: once forbidden\textsf{forbidden} enters a join, nothing recovers.
  • Edge (Root,A)(\mathrm{Root}, A): satisfies(Root,C++,forbidden)\mathrm{satisfies}(\mathrm{Root}, \mathsf{C{+}{+}}, \textsf{forbidden}) is false (D11) - incompatible at every consumer level, even c++26\texttt{c++26} (the forbidden\textsf{forbidden} row of the reference table is empty; Sat(forbidden)=∅\mathrm{Sat}(\textsf{forbidden}) = \emptyset). Any version of AA that publicly depends on this BB is unviable for any C++ consumer: BB‘s opt-out propagates up the public chain and poisons the root. Had the edge A→BA \to B been private, D10 would not have folded RC++(B)R_{\mathsf{C{+}{+}}}(B) into RC++(A)R_{\mathsf{C{+}{+}}}(A) at all, RC++(A)=unconstrainedR_{\mathsf{C{+}{+}}}(A) = \textsf{unconstrained}, and the root would be unaffected - propagation is along public edges only.

Example 4: mixed-language consumer

Consumer MM compiles both languages: langs(M)={C,C++}\mathrm{langs}(M) = \{\mathsf{C}, \mathsf{C{+}{+}}\}, lvl(M,C)=c11\mathrm{lvl}(M, \mathsf{C}) = \texttt{c11}, lvl(M,C++)=c++20\mathrm{lvl}(M, \mathsf{C{+}{+}}) = \texttt{c++20}. Dependency WW is a compiled C library: implC(W)=c17\mathrm{impl}_{\mathsf{C}}(W) = \texttt{c17}, implC++(W)=⊥\mathrm{impl}_{\mathsf{C{+}{+}}}(W) = \bot, declC(W)=c17\mathrm{decl}_{\mathsf{C}}(W) = \texttt{c17} (interface-c-standard = "c17"), declC++(W)=⊥\mathrm{decl}_{\mathsf{C{+}{+}}}(W) = \bot, no public dependencies.

  • RC(W)=ReqOf(W,C)=[c17,↑]R_{\mathsf{C}}(W) = \mathrm{ReqOf}(W, \mathsf{C}) = [\texttt{c17}, {\uparrow}] (D9 row 2).
  • RC++(W)=ReqOf(W,C++)=unconstrainedR_{\mathsf{C{+}{+}}}(W) = \mathrm{ReqOf}(W, \mathsf{C{+}{+}}) = \textsf{unconstrained} (D9 row 5: no C++ implementation, no declaration - the permissive C-to-C++ default).
  • Edge (M,W)(M, W) is a conjunction over langs(M)\mathrm{langs}(M) (D13):
    • L=C++L = \mathsf{C{+}{+}}: satisfies(M,C++,unconstrained)\mathrm{satisfies}(M, \mathsf{C{+}{+}}, \textsf{unconstrained}) is true.
    • L=CL = \mathsf{C}: satisfies(M,C,[c17,↑])\mathrm{satisfies}(M, \mathsf{C}, [\texttt{c17}, {\uparrow}]) iff c11≥c17\texttt{c11} \ge \texttt{c17}: false (c11<c17\texttt{c11} < \texttt{c17}, D2 - no equivalence special case).
  • One failed conjunct suffices: the edge is incompatible, even though the C++ side is satisfied. MM must raise its C level to c17 or c23 (a minimum-only requirement is upward closed, L6), or WW must relax its interface. Conversely, a C++-only consumer (langs={C++}\mathrm{langs} = \{\mathsf{C{+}{+}}\}) would take only the first conjunct and pass: languages the consumer does not compile impose nothing.

For the strict opposite direction: if MM instead depended on a compiled C++ library VV with declC(V)=⊥\mathrm{decl}_{\mathsf{C}}(V) = \bot and implC(V)=⊥\mathrm{impl}_{\mathsf{C}}(V) = \bot, then RC(V)=forbiddenR_{\mathsf{C}}(V) = \textsf{forbidden} (D9 row 6) and the L=CL = \mathsf{C} conjunct would fail at every C level - a C++ library is consumable from C only via an explicit interface-c-standard (D9 row 2 overriding row 6).

Example 5: header-only inference

Header-only library HH: kind(H)=header-only\mathrm{kind}(H) = \textsf{header-only}, implC++(H)=c++20\mathrm{impl}_{\mathsf{C{+}{+}}}(H) = \texttt{c++20} (declared on the target itself - per D6’s population contract, a package-level implementation default alone would leave implC++(H)=⊥\mathrm{impl}_{\mathsf{C{+}{+}}}(H) = \bot), declC++(H)=⊥\mathrm{decl}_{\mathsf{C{+}{+}}}(H) = \bot, no public dependencies. Consumer XX at lvl(X,C++)=c++17\mathrm{lvl}(X, \mathsf{C{+}{+}}) = \texttt{c++17}.

  • ReqOf(H,C++)=[c++20,↑]\mathrm{ReqOf}(H, \mathsf{C{+}{+}}) = [\texttt{c++20}, {\uparrow}] by D9 row 3: with no translation units of its own, HH‘s headers are the implementation, so the implementation standard is inferred as the interface minimum. RC++(H)=[c++20,↑]R_{\mathsf{C{+}{+}}}(H) = [\texttt{c++20}, {\uparrow}].
  • Edge (X,H)(X, H): c++17≥c++20\texttt{c++17} \ge \texttt{c++20} is false - incompatible (the [c++20,↑][\texttt{c++20}, {\uparrow}] row of the reference table).
  • Now the author audits the headers, finds they only use C++17, and declares interface-cxx-standard = "c++17": declC++(H)=(c++17,↑)\mathrm{decl}_{\mathsf{C{+}{+}}}(H) = (\texttt{c++17}, {\uparrow}), and D9 row 2 preempts row 3 - the explicit declaration wins over inference. RC++(H)=[c++17,↑]R_{\mathsf{C{+}{+}}}(H) = [\texttt{c++17}, {\uparrow}], and the edge is compatible. Note this move widened the accepted set ([c++17,↑]⊑[c++20,↑][\texttt{c++17}, {\uparrow}] \sqsubseteq [\texttt{c++20}, {\uparrow}]): it is the first deliberate exception in the remark after C3 - a relaxation by the dependency’s author, widening the consumer set (T2 with the assignments swapped: the viable set can only grow).

Example 6: a bounded interface and the empty intersection

Library GG ships headers that use a construct a later standard removed (say, dynamic exception specifications or register, both removed in C++17): its author declares interface-cxx-standard = { min = "c++11", max = "c++14" }, so declC++(G)=(c++11,c++14)\mathrm{decl}_{\mathsf{C{+}{+}}}(G) = (\texttt{c++11}, \texttt{c++14}) and ReqOf(G,C++)=[c++11,c++14]\mathrm{ReqOf}(G, \mathsf{C{+}{+}}) = [\texttt{c++11}, \texttt{c++14}] (D9 row 2).

  • Consumer XX at c++17\texttt{c++17}: satisfies(X,C++,[c++11,c++14])\mathrm{satisfies}(X, \mathsf{C{+}{+}}, [\texttt{c++11}, \texttt{c++14}]) is false - c++17>c++14\texttt{c++17} > \texttt{c++14}, the bounded row of the reference table. Raising XX cannot help (L6’s remark); only lowering to the range, or a newer GG, can.
  • Aggregator AA publicly depends on both GG and a modern library NN with ReqOf(N,C++)=[c++20,↑]\mathrm{ReqOf}(N, \mathsf{C{+}{+}}) = [\texttt{c++20}, {\uparrow}]. By D10, RC++(A)=[c++20,↑]⊔[c++11,c++14]=forbiddenR_{\mathsf{C{+}{+}}}(A) = [\texttt{c++20}, {\uparrow}] \sqcup [\texttt{c++11}, \texttt{c++14}] = \textsf{forbidden} - the empty intersection: no C++ level satisfies both. Every edge onto AA is incompatible at every consumer level, and a useful diagnostic must name both chains - the [c++20,↑][\texttt{c++20}, {\uparrow}] bound via NN and the [c++11,c++14][\texttt{c++11}, \texttt{c++14}] cap via GG - because neither source alone explains the composed forbidden\textsf{forbidden} (the remark after D10).

Exhaustiveness note

Every check above is a lookup in a table like the reference table, and both tables and graphs here are small by construction of the model: ReqL\mathrm{Req}_L has at most 37 elements, satisfies\mathrm{satisfies} at most 37×7=25937 \times 7 = 259 cells per language, ⊔\sqcup at most 37×37=136937 \times 37 = 1369 cells, and D9 is a six-row decision table over finitely many attribute combinations. The implementation’s test suite is expected to verify L1-L7 by full enumeration of those tables (citing the lemmas), T1/T2 on small DAGs including the diamond of Example 2 and the chain of Example 3, the empty intersection of Example 6 with both provenance chains, and each row of D9 by a dedicated fixture - covering C alongside C++ throughout.