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), even while the build-time check’s "none" handling remains deferred. 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: unconstrained, a minimum standard level, or forbidden. Requirements accumulate along public dependency edges by taking the strictest (the join). A dependency edge is compatible when the consumer’s compile level, in every language the consumer compiles, satisfies the dependency’s accumulated requirement. A candidate package version is viable when every edge resolving to it is compatible. 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

ReqL={unconstrained}{[m]:mLevelL}{forbidden}\mathrm{Req}_L = \{\textsf{unconstrained}\} \cup \{\, [m] : m \in \mathrm{Level}_L \,\} \cup \{\textsf{forbidden}\}

ordered by strictness \sqsubseteq, defined case by case:

  • unconstrainedr\textsf{unconstrained} \sqsubseteq r for every rReqLr \in \mathrm{Req}_L;
  • rforbiddenr \sqsubseteq \textsf{forbidden} for every rReqLr \in \mathrm{Req}_L;
  • [a][b][a] \sqsubseteq [b] iff aba \le b in LevelL\mathrm{Level}_L;
  • no other pairs are related, and \sqsubseteq is reflexive.

Informally: unconstrained\textsf{unconstrained} imposes nothing, [m][m] requires a consumer level of at least mm, forbidden\textsf{forbidden} is unsatisfiable. In v1, ReqL\mathrm{Req}_L is a finite chain (L1):

unconstrained[L][maxLevelL]forbidden\textsf{unconstrained} \sqsubseteq [\bot_L] \sqsubseteq \cdots \sqsubseteq [\max \mathrm{Level}_L] \sqsubseteq \textsf{forbidden}

D4 (join). For r1,r2ReqLr_1, r_2 \in \mathrm{Req}_L, the join r1r2r_1 \sqcup r_2 is the \sqsubseteq-maximum of r1r_1 and r2r_2 (well-defined because \sqsubseteq is total, L1). For a finite set or multiset SReqLS \subseteq \mathrm{Req}_L, S\bigsqcup S is the \sqsubseteq-maximum of SS, with =unconstrained\bigsqcup \emptyset = \textsf{unconstrained}.

Remark (reserved max and the interval generalization). Each interface requirement is serialized as a pair {min, max} whose max slot is reserved and always absent in v1. ReqL\mathrm{Req}_L is designed so that populating max later is a domain swap, not a signature change. Define the interval domain

IntL={[a,b]:a,bLevelL, ab}{full}{empty}\mathrm{Int}_L = \{\, [a, b] : a, b \in \mathrm{Level}_L,\ a \le b \,\} \cup \{\textsf{full}\} \cup \{\textsf{empty}\}

where [a,b][a, b] denotes {LevelL:ab}\{\, \ell \in \mathrm{Level}_L : a \le \ell \le b \,\}, full\textsf{full} denotes LevelL\mathrm{Level}_L, and empty\textsf{empty} denotes \emptyset. ReqL\mathrm{Req}_L embeds into IntL\mathrm{Int}_L by unconstrainedfull\textsf{unconstrained} \mapsto \textsf{full}, [m][m,maxLevelL][m] \mapsto [m, \max \mathrm{Level}_L], forbiddenempty\textsf{forbidden} \mapsto \textsf{empty}. On IntL\mathrm{Int}_L, the order is reverse set inclusion of the denoted sets, the join is set intersection of the denoted sets, and an empty intersection is forbidden\textsf{forbidden} (empty\textsf{empty}). Under the embedding, intersection of two up-sets [m1,maxLevelL][m_1, \max \mathrm{Level}_L] and [m2,maxLevelL][m_2, \max \mathrm{Level}_L] is [m1m2,maxLevelL][m_1 \sqcup m_2, \max \mathrm{Level}_L], which agrees with the v1 join, and full\textsf{full} and empty\textsf{empty} remain identity and absorbing element. Every downstream definition in this document (D10 through D14) depends on ReqL\mathrm{Req}_L only through the operations \sqcup, \bigsqcup, and the predicate satisfies\mathrm{satisfies} / the set Sat\mathrm{Sat} (D11, D12), all of which are defined on IntL\mathrm{Int}_L verbatim (satisfies\mathrm{satisfies} becomes membership of the consumer level in the denoted set). The extension therefore changes no downstream signatures. The rest of this document works in the v1 chain.

D5 (targets, dependency graph, public reachability). Fix a finite set TT of targets and a set of directed dependency edges ET×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; EpubEE_{\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 tTt \in T:

PubReach(t)={t}{uT: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 tTt \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 LLangL \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 LLangL \in \mathrm{Lang}, an explicit interface declaration declL(t)LevelL{none}{}\mathrm{decl}_L(t) \in \mathrm{Level}_L \cup \{\textsf{none}\} \cup \{\bot\}, where a level means a declared minimum (interface-c-standard / interface-cxx-standard), none\textsf{none} means the declared value "none" (headers not consumable from LL), and \bot means 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 Llangs(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 dTd \in T and a consumer language LLangL \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)=mLevelL\mathrm{decl}_L(d) = m \in \mathrm{Level}_L[m][m]
3declL(d)=\mathrm{decl}_L(d) = \bot, implL(d)=mLevelL\mathrm{impl}_L(d) = m \in \mathrm{Level}_L, kind(d)=header-only\mathrm{kind}(d) = \textsf{header-only}[m][m]
4declL(d)=\mathrm{decl}_L(d) = \bot, implL(d)=mLevelL\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 LLangL \in \mathrm{Lang}, the effective requirement RL:TReqLR_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.

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

satisfies(c,L,unconstrained)=truesatisfies(c,L,[m])=(lvl(c,L)m)satisfies(c,L,forbidden)=false\begin{aligned} \mathrm{satisfies}(c, L, \textsf{unconstrained}) &= \text{true} \\ \mathrm{satisfies}(c, L, [m]) &= \bigl(\mathrm{lvl}(c, L) \ge m\bigr) \\ \mathrm{satisfies}(c, L, \textsf{forbidden}) &= \text{false} \end{aligned}

D12 (satisfaction sets). For rReqLr \in \mathrm{Req}_L, define SatL(r)LevelL\mathrm{Sat}_L(r) \subseteq \mathrm{Level}_L:

SatL(unconstrained)=LevelLSatL([m])={LevelL:m}SatL(forbidden)=\begin{aligned} \mathrm{Sat}_L(\textsf{unconstrained}) &= \mathrm{Level}_L \\ \mathrm{Sat}_L([m]) &= \{\, \ell \in \mathrm{Level}_L : \ell \ge m \,\} \\ \mathrm{Sat}_L(\textsf{forbidden}) &= \emptyset \end{aligned}

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.

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

Llangs(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 Llangs(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 (ReqL\mathrm{Req}_L is a finite chain). (ReqL,)(\mathrm{Req}_L, \sqsubseteq) is a finite totally ordered set with least element unconstrained\textsf{unconstrained} and greatest element forbidden\textsf{forbidden}.

Proof. Let LevelL={m1<m2<<mn}\mathrm{Level}_L = \{m_1 < m_2 < \cdots < m_n\} (finite and totally ordered by D2; n=5n = 5 for C, n=7n = 7 for C++). Define φ:ReqL{0,1,,n+1}\varphi : \mathrm{Req}_L \to \{0, 1, \ldots, n+1\} by

φ(unconstrained)=0,φ([mi])=i,φ(forbidden)=n+1\varphi(\textsf{unconstrained}) = 0, \qquad \varphi([m_i]) = i, \qquad \varphi(\textsf{forbidden}) = n+1

φ\varphi is a bijection: the three shapes of D3 are disjoint, and miim_i \mapsto i is a bijection on the middle block. We check rs    φ(r)φ(s)r \sqsubseteq s \iff \varphi(r) \le \varphi(s) by cases on the definition of \sqsubseteq in D3:

  • r=unconstrainedr = \textsf{unconstrained}: rsr \sqsubseteq s holds for all ss, and φ(r)=0φ(s)\varphi(r) = 0 \le \varphi(s) holds for all ss.
  • s=forbiddens = \textsf{forbidden}: rsr \sqsubseteq s holds for all rr, and φ(r)n+1=φ(s)\varphi(r) \le n+1 = \varphi(s) holds for all rr.
  • r=[mi]r = [m_i], s=[mj]s = [m_j]: rs    mimj    ij    φ(r)φ(s)r \sqsubseteq s \iff m_i \le m_j \iff i \le j \iff \varphi(r) \le \varphi(s).
  • r=[mi]r = [m_i], s=unconstraineds = \textsf{unconstrained}: D3 relates this pair only via reflexivity, which does not apply ([mi]unconstrained[m_i] \ne \textsf{unconstrained}), so r⋢sr \not\sqsubseteq s; and φ(r)=i1>0=φ(s)\varphi(r) = i \ge 1 > 0 = \varphi(s).
  • r=forbiddenr = \textsf{forbidden}, sforbiddens \ne \textsf{forbidden}: similarly r⋢sr \not\sqsubseteq s and φ(r)=n+1>φ(s)\varphi(r) = n+1 > \varphi(s).

So φ\varphi is an order isomorphism onto the integer interval {0,,n+1}\{0, \ldots, n+1\} with its usual total order. Total orders, reflexivity, antisymmetry, and transitivity transport along order isomorphisms, so (ReqL,)(\mathrm{Req}_L, \sqsubseteq) is a finite chain; φ1(0)=unconstrained\varphi^{-1}(0) = \textsf{unconstrained} is its least and φ1(n+1)=forbidden\varphi^{-1}(n+1) = \textsf{forbidden} its greatest element. \blacksquare

L2 (bounded join-semilattice). (ReqL,,)(\mathrm{Req}_L, \sqsubseteq, \sqcup) is a bounded join-semilattice: \sqcup is the least upper bound, and it is associative, commutative, and idempotent, with unconstrained\textsf{unconstrained} as identity and forbidden\textsf{forbidden} as absorbing element.

Proof. By L1 the order is total, so for any r1,r2r_1, r_2 the \sqsubseteq-maximum max(r1,r2)\max(r_1, r_2) exists and is one of the two elements. It is an upper bound of both by definition of maximum, and any upper bound uu satisfies umax(r1,r2)u \sqsupseteq \max(r_1, r_2) because uu is above the larger of the two; so =max\sqcup = \max is the least upper bound. Through the isomorphism φ\varphi of L1, \sqcup corresponds to max\max on integers, which is associative, commutative, and idempotent; these equational properties transport along the bijection φ\varphi. For example φ(r1r2)=max(φ(r1),φ(r2))\varphi(r_1 \sqcup r_2) = \max(\varphi(r_1), \varphi(r_2)), so

φ((r1r2)r3)=max(max(φr1,φr2),φr3)=max(φr1,max(φr2,φr3))=φ(r1(r2r3))\varphi\bigl((r_1 \sqcup r_2) \sqcup r_3\bigr) = \max\bigl(\max(\varphi r_1, \varphi r_2), \varphi r_3\bigr) = \max\bigl(\varphi r_1, \max(\varphi r_2, \varphi r_3)\bigr) = \varphi\bigl(r_1 \sqcup (r_2 \sqcup r_3)\bigr)

and injectivity of φ\varphi gives associativity; commutativity and idempotence are the same argument. unconstrained\textsf{unconstrained} is the least element (L1), so unconstrainedr=max(unconstrained,r)=r\textsf{unconstrained} \sqcup r = \max(\textsf{unconstrained}, r) = r: identity. forbidden\textsf{forbidden} is the greatest element (L1), so forbiddenr=forbidden\textsf{forbidden} \sqcup r = \textsf{forbidden}: absorbing. Boundedness is L1’s least and greatest elements. Consequently the set join S\bigsqcup S of D4 is well-defined for every finite multiset SS: by associativity and commutativity the result is independent of the order of combination, by idempotence it is independent of multiplicity, and =unconstrained\bigsqcup \emptyset = \textsf{unconstrained} is the identity, so (SS)=SS\bigsqcup (S \cup S') = \bigsqcup S \sqcup \bigsqcup S' for all finite S,SS, S'. \blacksquare

L3 (semantic characterization). For all r1,r2ReqLr_1, r_2 \in \mathrm{Req}_L:

  1. (Soundness) r1r2    Sat(r2)Sat(r1)r_1 \sqsubseteq r_2 \implies \mathrm{Sat}(r_2) \subseteq \mathrm{Sat}(r_1).
  2. (Completeness, up to one degenerate pair) Sat(r2)Sat(r1)    r1r2\mathrm{Sat}(r_2) \subseteq \mathrm{Sat}(r_1) \implies r_1 \sqsubseteq r_2, except for the single pair r1=[L]r_1 = [\bot_L], r2=unconstrainedr_2 = \textsf{unconstrained} (where L\bot_L is the least level, D2).
  3. (Induced equivalence) Sat(r1)=Sat(r2)\mathrm{Sat}(r_1) = \mathrm{Sat}(r_2) iff r1=r2r_1 = r_2 or {r1,r2}={unconstrained,[L]}\{r_1, r_2\} = \{\textsf{unconstrained}, [\bot_L]\}.

Consequently \sqsubseteq coincides with reverse Sat\mathrm{Sat}-inclusion on the quotient of ReqL\mathrm{Req}_L by the equivalence unconstrained[L]\textsf{unconstrained} \approx [\bot_L], and Sat\mathrm{Sat} is an order isomorphism from that quotient (ordered by \sqsubseteq) onto its image ordered by \supseteq. The two identified elements are kept distinct in ReqL\mathrm{Req}_L anyway: they are behaviorally equal for satisfies\mathrm{satisfies} but differ in provenance (nothing declared versus a declared minimum), which diagnostics report.

Proof.

(1) Assume r1r2r_1 \sqsubseteq r_2; cases on D3. If r1=unconstrainedr_1 = \textsf{unconstrained} then Sat(r1)=LevelLSat(r2)\mathrm{Sat}(r_1) = \mathrm{Level}_L \supseteq \mathrm{Sat}(r_2) for any r2r_2. If r2=forbiddenr_2 = \textsf{forbidden} then Sat(r2)=Sat(r1)\mathrm{Sat}(r_2) = \emptyset \subseteq \mathrm{Sat}(r_1). If r1=[a]r_1 = [a], r2=[b]r_2 = [b] with aba \le b: Sat([b])\ell \in \mathrm{Sat}([b]) means ba\ell \ge b \ge a, so Sat([a])\ell \in \mathrm{Sat}([a]). These three cases cover every related pair (D3 relates no others, and the reflexive pairs are trivial).

(2) We prove the contrapositive: assume r1⋢r2r_1 \not\sqsubseteq r_2 and (r1,r2)([L],unconstrained)(r_1, r_2) \ne ([\bot_L], \textsf{unconstrained}); we show Sat(r2)⊈Sat(r1)\mathrm{Sat}(r_2) \not\subseteq \mathrm{Sat}(r_1), i.e. exhibit Sat(r2)Sat(r1)\ell \in \mathrm{Sat}(r_2) \setminus \mathrm{Sat}(r_1). Since \sqsubseteq is total (L1), r1⋢r2r_1 \not\sqsubseteq r_2 means r2r1r_2 \sqsubset r_1 strictly. Cases on the strict pairs, using the chain of L1:

  • r2=unconstrainedr_2 = \textsf{unconstrained}, r1=[a]r_1 = [a] with aLa \ne \bot_L (the excluded pair is exactly a=La = \bot_L): take =L\ell = \bot_L. Then Sat(unconstrained)=LevelL\ell \in \mathrm{Sat}(\textsf{unconstrained}) = \mathrm{Level}_L, and Sat([a])\ell \notin \mathrm{Sat}([a]) because L<a\bot_L < a.
  • r2=unconstrainedr_2 = \textsf{unconstrained}, r1=forbiddenr_1 = \textsf{forbidden}: any LevelL\ell \in \mathrm{Level}_L works (LevelL\mathrm{Level}_L is nonempty); LevelL=Sat(r2)\ell \in \mathrm{Level}_L = \mathrm{Sat}(r_2) and Sat(r1)=\mathrm{Sat}(r_1) = \emptyset.
  • r2=[b]r_2 = [b], r1=[a]r_1 = [a] with b<ab < a: take =b\ell = b. Then b\ell \ge b so Sat([b])\ell \in \mathrm{Sat}([b]), and =b<a\ell = b < a so Sat([a])\ell \notin \mathrm{Sat}([a]).
  • r2=[b]r_2 = [b], r1=forbiddenr_1 = \textsf{forbidden}: take =bSat([b])\ell = b \in \mathrm{Sat}([b]); Sat(forbidden)=\mathrm{Sat}(\textsf{forbidden}) = \emptyset.

(There is no case r2=forbiddenr_2 = \textsf{forbidden} with r2r1r_2 \sqsubset r_1, since forbidden\textsf{forbidden} is greatest.) In every case Sat(r2)⊈Sat(r1)\mathrm{Sat}(r_2) \not\subseteq \mathrm{Sat}(r_1), proving the contrapositive.

For the excluded pair itself: Sat(unconstrained)=LevelL={:L}=Sat([L])\mathrm{Sat}(\textsf{unconstrained}) = \mathrm{Level}_L = \{\, \ell : \ell \ge \bot_L \,\} = \mathrm{Sat}([\bot_L]), so Sat(r2)Sat(r1)\mathrm{Sat}(r_2) \subseteq \mathrm{Sat}(r_1) holds while [L]unconstrained[\bot_L] \sqsubseteq \textsf{unconstrained} fails (D3, as in L1’s fourth case) - the exception is genuine and is the only one.

(3) If r1=r2r_1 = r_2, equality of Sat\mathrm{Sat} is trivial; and Sat(unconstrained)=Sat([L])\mathrm{Sat}(\textsf{unconstrained}) = \mathrm{Sat}([\bot_L]) was just shown. Conversely assume Sat(r1)=Sat(r2)\mathrm{Sat}(r_1) = \mathrm{Sat}(r_2) with r1r2r_1 \ne r_2; by totality (L1) one is strictly below the other, say r2r1r_2 \sqsubset r_1. Mutual inclusion holds, so by the contrapositive argument of (2) the pair must be the excluded one: (r1,r2)=([L],unconstrained)(r_1, r_2) = ([\bot_L], \textsf{unconstrained}). (Every other strict pair produced a separating \ell.) \blacksquare

L4 (join is intersection of satisfaction sets). For all r1,r2ReqLr_1, r_2 \in \mathrm{Req}_L: Sat(r1r2)=Sat(r1)Sat(r2)\mathrm{Sat}(r_1 \sqcup r_2) = \mathrm{Sat}(r_1) \cap \mathrm{Sat}(r_2). More generally, for finite SReqLS \subseteq \mathrm{Req}_L: Sat(S)=rSSat(r)\mathrm{Sat}(\bigsqcup S) = \bigcap_{r \in S} \mathrm{Sat}(r), with the convention that the empty intersection is LevelL\mathrm{Level}_L.

Proof. By L1, \sqsubseteq is total, so without loss of generality r1r2r_1 \sqsubseteq r_2, hence r1r2=r2r_1 \sqcup r_2 = r_2 and by L3(1) Sat(r2)Sat(r1)\mathrm{Sat}(r_2) \subseteq \mathrm{Sat}(r_1). Then Sat(r1r2)=Sat(r2)=Sat(r1)Sat(r2)\mathrm{Sat}(r_1 \sqcup r_2) = \mathrm{Sat}(r_2) = \mathrm{Sat}(r_1) \cap \mathrm{Sat}(r_2), the last step because Sat(r2)\mathrm{Sat}(r_2) is the smaller of two nested sets. The finite generalization follows by induction on S|S|: the base case is Sat()=Sat(unconstrained)=LevelL\mathrm{Sat}(\bigsqcup \emptyset) = \mathrm{Sat}(\textsf{unconstrained}) = \mathrm{Level}_L (the empty intersection), and the step is the binary case just proved together with (S{r})=Sr\bigsqcup (S \cup \{r\}) = \bigsqcup S \sqcup r (L2). \blacksquare

Remark. L4 is the v1 shadow of the interval rule of D4’s remark: under the interval generalization, Sat\mathrm{Sat} becomes the denotation itself and L4 becomes the definition of join (intersection), with Sat(r)=    r=forbidden\mathrm{Sat}(r) = \emptyset \iff r = \textsf{forbidden}. Nothing downstream distinguishes the two readings.

L5 (antitonicity of satisfies\mathrm{satisfies}). If r1r2r_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. satisfies(c,L,r2)\mathrm{satisfies}(c, L, r_2) iff lvl(c,L)Sat(r2)\mathrm{lvl}(c, L) \in \mathrm{Sat}(r_2) (D12). By L3(1), Sat(r2)Sat(r1)\mathrm{Sat}(r_2) \subseteq \mathrm{Sat}(r_1), so lvl(c,L)Sat(r1)\mathrm{lvl}(c, L) \in \mathrm{Sat}(r_1), i.e. satisfies(c,L,r1)\mathrm{satisfies}(c, L, r_1). \blacksquare

L6 (satisfaction sets are upward closed). For every rReqLr \in \mathrm{Req}_L, if Sat(r)\ell \in \mathrm{Sat}(r) and \ell' \ge \ell then Sat(r)\ell' \in \mathrm{Sat}(r). Consequently, raising a consumer’s effective level in any language never breaks satisfaction of any requirement: if satisfies(c,L,r)\mathrm{satisfies}(c, L, r) and cc' agrees with cc except lvl(c,L)lvl(c,L)\mathrm{lvl}(c', L) \ge \mathrm{lvl}(c, L), then satisfies(c,L,r)\mathrm{satisfies}(c', L, r).

Proof. Cases on rr (D12). Sat(unconstrained)=LevelL\mathrm{Sat}(\textsf{unconstrained}) = \mathrm{Level}_L is upward closed trivially. Sat([m])={:m}\mathrm{Sat}([m]) = \{\, \ell : \ell \ge m \,\}: from m\ell \ge m and \ell' \ge \ell, transitivity of \le gives m\ell' \ge m. Sat(forbidden)=\mathrm{Sat}(\textsf{forbidden}) = \emptyset is upward closed vacuously. The consequence is immediate from D11/D12: satisfies(c,L,r)\mathrm{satisfies}(c, L, r) iff lvl(c,L)Sat(r)\mathrm{lvl}(c, L) \in \mathrm{Sat}(r), and lvl(c,L)lvl(c,L)\mathrm{lvl}(c', L) \ge \mathrm{lvl}(c, L) stays in the upward closed set. \blacksquare

L7 (set joins are monotone). For finite multisets SSS \subseteq S' over ReqL\mathrm{Req}_L: SS\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 ririr_i \sqsubseteq r'_i pointwise, then SS\bigsqcup S \sqsubseteq \bigsqcup S'.

Proof. For the first claim: S\bigsqcup S' is an upper bound of every element of SS', hence of every element of SSS \subseteq S'; since S\bigsqcup S is the least upper bound of SS (L2), SS\bigsqcup S \sqsubseteq \bigsqcup S'. For the second: each ririSr_i \sqsubseteq r'_i \sqsubseteq \bigsqcup S', so S\bigsqcup S' is an upper bound of {r1,,rk}\{r_1, \ldots, r_k\}, and again leastness of S\bigsqcup S gives SS\bigsqcup S \sqsubseteq \bigsqcup S'. \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):uPubReach(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 T1\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:TReqLf : 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):uPubReach(d)}:(t,d)Epub}(IH)=({ReqOf(t,L)}{{ReqOf(u,L):uPubReach(d)}:(t,d)Epub})={ReqOf(u,L):uPubReach(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 (SS)=SS\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 EpubEpubE_{\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 uTu \in T. Write RLR_L and RLR'_L for the respective effective requirements. Then for every tTt \in T:

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

Proof. EpubEpubE_{\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):uPubReach(t)}{ReqOf(u,L):uPubReach(t)}(pointwise, L7 second claim){ReqOf(u,L):uPubReach(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 rReqLr \in \mathrm{Req}_L, leaving everything else fixed, satisfies T2’s hypotheses: unconstrained\textsf{unconstrained} is the least element (L1), so unconstrainedr\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 Llangs(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)] by a smaller [m][m] - 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 [m][m] - again a relaxation moving down, and again the point of the declaration.

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 tTt \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 three tags, and the [m][m] case is a single comparison of two 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, by (1)‘s comparison bound and L2’s =max\sqcup = \max). 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 minimum mm, its public headers compile under every consumer level m\ge m in that language; 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 uPubReach(d)u \in \mathrm{PubReach}(d) and every language Llangs(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 Llangs(c)L \in \mathrm{langs}(c) and uPubReach(d)u \in \mathrm{PubReach}(d). By the closed form (T1), RL(d)={ReqOf(w,L):wPubReach(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(1), 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=7\lvert \mathrm{Req}_{\mathsf{C}} \rvert = 7, ReqC++=9\lvert \mathrm{Req}_{\mathsf{C{+}{+}}} \rvert = 9. 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 (or triples, for associativity) and assert the property, citing the lemma number it checks (L1 totality/antisymmetry/transitivity, L2 associativity, commutativity, idempotence, identity and absorption, L3 soundness and its single exception, L4 intersection, L5 antitonicity, L6 upward closure). 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}]\checkmark\checkmark\checkmark\checkmark
[c++20][\texttt{c++20}]\checkmark\checkmark\checkmark
forbidden\textsf{forbidden}

Each row is upward closed (L6), and rows shrink as the requirement climbs the chain of L1 (L3(1) in table form).

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}] 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}] 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}] \sqcup \textsf{unconstrained} = [\texttt{c++17}] (D10, D4, L2 identity).
  • Edge (X,Z)(X, Z): satisfies(X,C++,[c++17])\mathrm{satisfies}(X, \mathsf{C{+}{+}}, [\texttt{c++17}]) iff c++17c++17\texttt{c++17} \ge \texttt{c++17}: true - see the [c++17][\texttt{c++17}] 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}] as in Example 1.
  • Edge (Y,Z)(Y, Z): c++23c++20\texttt{c++23} \ge \texttt{c++20} - compatible.
  • Edge (X,Z)(X, Z): c++17c++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. (L6 view: Sat([c++20])\mathrm{Sat}([\texttt{c++20}]) is upward closed, YY sits inside it, XX sits below it.)

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

Chain RootAB\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)=unconstrainedRC++(B)=unconstrainedforbidden=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 ABA \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}] (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}]) iff c11c17\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 (L6: once inside Sat([c17])\mathrm{Sat}([\texttt{c17}]), raising further never breaks it), 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}] 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}].
  • Edge (X,H)(X, H): c++17c++20\texttt{c++17} \ge \texttt{c++20} is false - incompatible (the [c++20][\texttt{c++20}] 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}, and D9 row 2 preempts row 3 - the explicit declaration wins over inference. RC++(H)=[c++17]R_{\mathsf{C{+}{+}}}(H) = [\texttt{c++17}], and the edge is compatible. Note this move went down the chain ([c++17][c++20][\texttt{c++17}] \sqsubseteq [\texttt{c++20}]): 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).

Exhaustiveness note

Every check above is a lookup in a table like the reference table, and both tables and graphs here are tiny by construction of the model: ReqL\mathrm{Req}_L has at most 9 elements, satisfies\mathrm{satisfies} at most 9×7=639 \times 7 = 63 cells per language, \sqcup at most 9×9=819 \times 9 = 81 cells, and D9 is a six-row decision table over finitely many attribute combinations. The implementation’s test suite is expected to verify L1-L6 by full enumeration of those tables (citing the lemma numbers), T1/T2 on small DAGs including the diamond of Example 2 and the chain of Example 3, and each row of D9 by a dedicated fixture - covering C alongside C++ throughout.