<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
  <title id="root">PROP-034: Transitive inclusion links and the static boot-link graph</title>
  <status stage="impl" state="done" comment="C 2026-07-25: the transitive semantics shipped under the renamed terms; PROP-035 §12 / PROP-038 absorbed the graph as their emission layer; body predates the 2026-07-16 rename"/>
  <p p="1"><fact id="status-line" status="impl/done">**Status: IMPLEMENTED under renamed terms** (requirements authored 2026-07-14 at the owner's request; verified against the tree 2026-07-25 by the spec-actualization campaign). `static-transitive` is live on `LinkType`, and dedup + topological ordering + tie-break emission run in `bootgen` — this repository's own `STATIC.md` is the §3 redbook closure in the flesh. [PROP-035 §12](PROP-035-spec-compiler.xml) and [PROP-038](PROP-038-hybrid-boot-linking.xml) absorbed the graph as their emission layer; read the `TERMINOLOGY-RENAME` note below before the body. Extends [PROP-009](PROP-009-loading-model.xml) (the loading model).</fact></p>
  <p p="2"><fact id="VISIBILITY-SCOPES-THE-GRAPH" status="impl/done">**Scope note (2026-08-23, PROP-050 §3):** the boot-link graph this document defines is now built over the consumer root's **effective set E(R)** — the visibility layer (access / friendship / exclude / override, PROP-050) decides which nodes exist before any link mode is read; a `static-transitive` edge propagates strength across visible edges only.</fact></p>
  <p p="3"><fact id="TERMINOLOGY-RENAME" status="spec/done">**Terminology (2026-07-16):** the `inline` / `static` / `dynamic` link types this document describes were **renamed** — read `inline` as `static` (the verbatim `STATIC.md` lane), `static` as `dynamic` (the default, a by-reference read), and the old `dynamic` as a `dynamic` entry carrying a `when`; `inline-transitive` is now `static-transitive`. See PROP-009 §2.4. The body below predates the rename and keeps the old names.</fact></p>
  <p p="4"><fact id="related" status="spec/done">**Related:** [PROP-009 §2.4](PROP-009-loading-model.xml#inclusion-types) (the direct `inline` / `static` / `dynamic` link types this PROP makes transitive), [PROP-028](../../common/PROP-028-package-families.xml) (families / collections — the motivating consumer), [PROP-003](../vibe-resolver/PROP-003-dep-evolution.xml) / [PROP-017](../vibe-resolver/PROP-017-resolvo-resolver.xml) (version unification — one node per resolved package), [`vibevm/vibespecs/design/loading-and-boot-model.xml`](../../design/loading-and-boot-model.xml) (the static/dynamic-linking metaphor this PROP completes).</fact></p>
  <p p="5"><fact id="supersedes-line" status="spec/done">**Supersedes:** backlog `B1` (`transitive-inline` / `transitive-static`) — promoted to this PROP.</fact></p>
  <section id="motivation" title="1. Motivation — the boot closure must link like a static linker">
    <p p="6"><fact id="two-gaps-lead" status="spec/done">PROP-009 §2.4 gives each **direct** dependency edge an inclusion type — `inline` (verbatim into `INLINE.md`, the priority lane), `static` (a path in `INDEX.md`), or `dynamic` (a conditional INCLUDE). Two gaps make a growing dependency set fragile, and a collection like `redbook` un-loadable the way its consumer wants:</fact></p>
    <p p="7"><fact id="GAP-NO-PROPAGATION" status="spec/done">**Gap 1 — inclusion does not propagate transitively.** A consumer sets `link` only on its own direct edges.</fact></p>
    <list ordered="false" p="8">
      <item><fact id="GAP1-CLOSURE-DEFAULT" status="spec/done">A dependency's transitive closure takes its type from each member's own `[boot_snippet]` suggested link, or the `static` default — `bootgen` resolves `declared_link.or(suggested_link)`, and a transitive dependency's `declared_link` reads back as `None`.</fact></item>
      <item><fact id="GAP1-COLLECTION-BLOCKED" status="spec/done">So a **collection** (e.g. `redbook`, PROP-028) cannot say "load my whole closure inline."</fact></item>
      <item><fact id="GAP1-WORKAROUND-WRONG" status="spec/done">The only workaround — each member self-suggesting `inline` — is wrong: it forces inline on **every** consumer of that member, not just the one collection that wanted it.</fact></item>
      <item><fact id="GAP1-CONSUMER-PROPERTY" status="spec/done">Inclusion strength is a property of *how a consumer pulls a subtree*, and today that cannot be expressed.</fact></item>
    </list>
    <p p="9"><fact id="GAP-NO-LINK-GRAPH" status="spec/done">**Gap 2 — the boot closure is not resolved as a static link graph.** A dependency closure is a graph, and assembling a boot from it is exactly **static linking**.</fact></p>
    <p p="10"><fact id="INVARIANTS-INFORMAL" status="spec/done">Three linker invariants are currently only informally met by PROP-009's "ordered list" and are load-bearing:</fact></p>
    <list ordered="false" p="11">
      <item><fact id="INV-LINK-ONCE" status="spec/done">**Each package linked exactly once.** A package reachable through several dependency paths must appear **once** in `INLINE.md` / `INDEX.md`, never N times. Double-inclusion wastes context, and for `inline` it duplicates verbatim text and its anchors (a `duplicate-anchor` hazard).</fact></item>
      <item><fact id="INV-DEP-ORDER" status="spec/done">**Dependency order (topological).** A package must be emitted **before** every package that requires it, so that when a dependent's boot text is read, everything it builds on is already in context. A package needed by another (or several) comes **earlier** in the sequence.</fact></item>
      <item><fact id="INV-DETERMINISTIC" status="spec/done">**Deterministic and acyclic.** The walk must be deterministic (stable output across runs) and reject cycles loudly at generate time — the agent-side read stays recursion-free (PROP-009 §2), so all graph work, including cycle detection, happens once in `vibe`.</fact></item>
    </list>
    <p p="12"><fact id="RISK-WITHOUT-PINNING" status="spec/done">Without these pinned, adding the whole `redbook`, `delegation-first`, and future collections risks a boot that double-includes packages and orders a dependent before its dependency — the failure the owner names as "we might never load cleanly."</fact></p>
  </section>
  <section id="decisions" title="2. Decisions">
    <section id="transitive-links" title="2.1 Transitive inclusion links">
      <p p="13"><fact id="TRANSITIVE-VARIANTS" status="spec/done">Extend the §2.4 `link` value set with **transitive** variants:</fact></p>
      <fence lang="toml" p="14">[requires.packages]
"flow:org.vibevm.world/redbook"     = { version = "^0.2", link = "inline-transitive" }
"stack:org.vibevm.ai-native/rust"   = { version = "^0.7", link = "static-transitive" }
"flow:org.vibevm.world/wal"         = { version = "^0.2", link = "static" }   # direct (this edge only)</fence>
      <list ordered="false" p="15">
        <item><fact id="LINK-INLINE-TRANSITIVE" status="spec/done">`link = "inline-transitive"` — this package **and its entire transitive closure** are pulled `inline`.</fact></item>
        <item><fact id="LINK-STATIC-TRANSITIVE" status="spec/done">`link = "static-transitive"` — this package and its entire transitive closure are pulled `static`.</fact></item>
        <item><fact id="LINK-DYNAMIC-RESERVED" status="spec/done">`dynamic-transitive` — reserved (§5).</fact></item>
      </list>
      <list ordered="false" p="16">
        <item><fact id="DIRECT-UNCHANGED" status="spec/done">The existing `inline` / `static` / `dynamic` remain **direct**: they set the mode of *this edge's target only*; the target's own closure resolves by its own rules.</fact></item>
        <item><fact id="TRANSITIVE-SUBTREE-DECL" status="spec/done">A transitive link is the consumer's declaration that the mode applies to *the whole subtree reached through this edge* — the missing "how I pull this subtree" expressiveness of Gap 1.</fact></item>
        <item><fact id="CONSUMER-SIDE-PROPERTY" status="spec/done">This is a **consumer-side** property of the edge, not a property baked into the pulled package — the same package can be pulled `inline-transitive` by one consumer and `static` by another; nothing is written into the package.</fact></item>
      </list>
    </section>
    <section id="precedence" title="2.2 Effective inclusion mode — the precedence lattice">
      <p p="17"><fact id="multi-path-reality" status="spec/done">A package can be reached by several edges and several transitive closures carrying different modes.</fact></p>
      <p p="18"><fact id="EFFECTIVE-MODE-JOIN" status="spec/done">Its **effective inclusion mode** is the join of every mode that reaches it, under the lattice:</fact></p>
      <fence p="19">inline ⊐ static ⊐ dynamic</fence>
      <p p="20"><fact id="resolution-lead" status="spec/done">Resolution, for each package in the closure:</fact></p>
      <list ordered="true" p="21">
        <item><fact id="RES-COLLECT" status="spec/done">Collect every mode reaching it:</fact></item>
        <item><fact id="RES-SRC-DIRECT" status="spec/done">(a) a **direct** edge to it contributes that edge's mode;</fact></item>
        <item><fact id="RES-SRC-TRANSITIVE" status="spec/done">(b) a **transitive** link contributes its mode to *every* package in the closure reached through it;</fact></item>
        <item><fact id="RES-SRC-SUGGESTED" status="spec/done">(c) the package's own `[boot_snippet]` suggested link;</fact></item>
        <item><fact id="RES-SRC-DEFAULT" status="spec/done">(d) the `static` default when nothing else applies.</fact></item>
        <item><fact id="RES-STRONGEST" status="spec/done">The effective mode is the **strongest** (left-most) of those.</fact></item>
      </list>
      <p p="22"><fact id="consequences-lead" status="spec/done">Consequences, all deliberate:</fact></p>
      <list ordered="false" p="23">
        <item><fact id="INLINE-WINS" status="spec/done">**Inline wins, monotonically.** If any path pulls a package inline (a direct `inline`, or an `inline-transitive` ancestor, or a self-suggested `inline`), it is inline. Adding a stronger link never demotes a package — a package's priority can only rise, never silently fall, as the graph grows.</fact></item>
        <item><fact id="DYNAMIC-OVERRIDDEN" status="spec/done">**`dynamic` is overridden by any unconditional need.** If one path needs a package `dynamic` (context-gated) but another needs it `static`/`inline` unconditionally, it loads unconditionally — the `when` gate cannot hide a package something else requires outright.</fact></item>
        <item><fact id="NO-DIRECT-DEMOTION" status="spec/done">A **direct** link never demotes a package an ancestor pulled `inline-transitive` (inline is sticky, per the monotonicity above). A consumer that truly needs a subtree *not* inline must not sit under an `inline-transitive` edge to it.</fact></item>
      </list>
      <p p="24"><fact id="LATTICE-DETERMINISM" status="spec/done">The lattice makes the effective mode a deterministic function of the graph, independent of walk order.</fact></p>
    </section>
    <section id="link-graph" title="2.3 The static boot-link graph">
      <p p="25"><fact id="LINKER-ONCE" status="spec/done">`vibe` resolves the boot exactly once, at install / generate time, as a static linker:</fact></p>
      <list ordered="true" p="26">
        <item><fact id="STEP-BUILD-GRAPH" status="spec/done">**Build the graph.** Nodes = the resolved package versions in the root's dependency closure (one node per unified `(group, name, version)` — the resolver has already unified versions, PROP-003 / PROP-017). Edges = `requires`.</fact></item>
        <item><fact id="STEP-ASSIGN-MODES" status="spec/done">**Assign effective modes** to every node (§2.2).</fact></item>
        <item><fact id="STEP-DEDUP" status="spec/done">**Deduplicate.** Each node contributes its boot **exactly once**, in its effective mode — regardless of how many paths reach it.</fact></item>
        <item><fact id="STEP-TOPO-SORT" status="spec/done">**Topologically sort.** Order the nodes so that **every dependency precedes every dependent**. Independent nodes (no path between them) are ordered by a deterministic tie-break — category, then boot-snippet slot, then fully-qualified name — so the emitted sequence is byte-stable across runs.</fact></item>
        <item><fact id="STEP-REJECT-CYCLES" status="spec/done" action="continue" actionstage="doc" audience="user">**Reject cycles.** `requires` is expected acyclic; a cycle is a **hard error at generate time**, reported with the offending cycle path. The boot is never emitted half-linked. (This is the one place cycle detection lives; the agent-side read is a flat, recursion-free parse per PROP-009 §2.)</fact></item>
      </list>
    </section>
    <section id="emission" title="2.4 Emission — a dependency-ordered priority lane and index">
      <p p="27"><fact id="emission-lead" status="spec/done">From the sorted, deduplicated, mode-assigned node list:</fact></p>
      <list ordered="false" p="28">
        <item><fact id="EMIT-INLINE" status="spec/done">**`inline`** nodes → concatenated verbatim into `INLINE.md`, **in topological order** — a dependency's boot text precedes its dependents' within the priority lane. This is the **static-linked inline lane**: inline content, but resolved, deduplicated, and dependency-ordered by the linker rather than pasted in discovery order.</fact></item>
        <item><fact id="EMIT-STATIC" status="spec/done">**`static`** nodes → `[[entry]]` `kind = "static"` in `INDEX.md`, in topological order.</fact></item>
        <item><fact id="EMIT-DYNAMIC" status="spec/done">**`dynamic`** nodes → `[[entry]]` `kind = "dynamic"` with their `when`, in topological order.</fact></item>
      </list>
      <p p="29"><fact id="LANES-ORDERED" status="spec/done">Both lanes are dependency-ordered: a reader (human or agent) always meets a package **before** anything that builds on it, and never meets the same package twice.</fact></p>
    </section>
    <section id="budget" title="2.5 Boot budget note">
      <list ordered="false" p="30">
        <item><fact id="BUDGET-COST" status="spec/done">`inline-transitive` over a large collection puts that collection's **whole** boot closure into `INLINE.md`, read first and in full every session.</fact></item>
        <item><fact id="BUDGET-TRADEOFF" status="spec/done">That is the point when priority must be *guaranteed by position* (critical practices that must never be missed) — and a real cost when the closure is large.</fact></item>
        <item><fact id="BUDGET-GUIDANCE" status="spec/done">Choose `inline-transitive` for closures whose every member is boot-critical; keep `static-transitive` (still deduplicated and dependency-ordered, but read on demand from `INDEX.md`) as the default for large practice sets where INDEX-lane resolution is acceptable.</fact></item>
      </list>
    </section>
  </section>
  <section id="redbook" title="3. `redbook` as `inline-transitive` — the motivating case">
    <p p="31"><fact id="REDBOOK-CONSUMER" status="spec/done">vibevm pulls `flow:org.vibevm.world/redbook` as `inline-transitive`. The effect, under §2:</fact></p>
    <list ordered="false" p="32">
      <item><fact id="redbook-effect-inline" status="spec/done">Every practice `redbook` collects, plus their transitive deps, resolves to effective mode `inline`.</fact></item>
      <item><fact id="redbook-effect-dedup" status="spec/done">The graph is deduplicated: a practice several members share (e.g. `git-practices`, or a common dependency) appears **once**.</fact></item>
      <item><fact id="redbook-effect-order" status="spec/done">The graph is topologically ordered: a practice needed by another (or by `redbook` itself) is emitted **earlier** in `INLINE.md`.</fact></item>
      <item><fact id="redbook-effect-guarantee" status="spec/done">The whole tested edition is thus in the priority lane, verbatim, dependency-ordered, once each — the practices are guaranteed to load, which is the safety the owner is buying (over `static`, which trusts agent-side INDEX resolution).</fact></item>
    </list>
    <p p="33"><fact id="B1-INTERIM-DROPPED" status="spec/done">The per-member `[boot_snippet].link = "inline"` self-suggestions used as the B1 interim (e.g. on the `git-practices` members) become unnecessary once the consumer declares `inline-transitive`, and can be dropped — the inclusion strength moves back to the consumer where §2.1 puts it.</fact></p>
  </section>
  <section id="compat" title="4. Compatibility and migration">
    <list ordered="false" p="34">
      <item><fact id="COMPAT-ADDITIVE" status="spec/done">The direct `inline` / `static` / `dynamic` types and their semantics are unchanged; the transitive variants are purely additive to the `link` enum.</fact></item>
      <item><fact id="COMPAT-PINS-ORDER" status="spec/done">§2.3's dedup + topological order **pins** what PROP-009 §2 left as an "ordered list": existing manifests keep working and simply gain guaranteed single-inclusion and dependency order. Any boot that silently relied on a *non*-topological order is a latent bug this surfaces.</fact></item>
      <item><fact id="COMPAT-SCHEMA" status="spec/done">Manifest schema (`vibe-core`): the `link` field accepts the new variants; unknown `link` values remain a manifest error.</fact></item>
      <item><fact id="COMPAT-IMPL-SITE" status="spec/done">Implementation lands in `vibe-workspace` boot resolution (`bootgen`): mode propagation (§2.2), dedup, topological sort with the deterministic tie-break, and cycle rejection.</fact></item>
    </list>
  </section>
  <section id="open" title="5. Open questions">
    <list ordered="true" p="35">
      <item><fact id="OPEN-DYNAMIC-TRANSITIVE" status="spec/work">**`dynamic-transitive`.** Semantics of gating a whole closure behind one `when` (does the gate distribute to every member, or gate the subtree as a unit?) — reserved until a use case appears.</fact></item>
      <item><fact id="OPEN-WHEN-INSIDE-INLINE" status="spec/work">**`when` inside a transitive-inline closure.** A member carrying its own `when` OS-gate that is pulled `inline-transitive`: does inline (unconditional, verbatim) or the gate win? Provisional: an explicit member `when` keeps the member `dynamic` (the gate is more specific than the ancestor's blanket inline); confirm against a real case.</fact></item>
      <item><fact id="OPEN-EXPLICIT-DEMOTION" status="spec/work">**Explicit demotion.** Whether to add a `link = "static-here"` escape that lets a consumer pull a package `static` *even under* an ancestor's `inline-transitive` (breaking the monotonicity of §2.2). Deferred — no demotion use case today, and monotonic priority is the safer default.</fact></item>
    </list>
  </section>
  <section id="history" title="6. Version history">
    <list ordered="false" p="36">
      <item><fact id="HISTORY-DRAFTED" status="spec/done">**2026-07-14 — drafted (owner-requested).** Promotes backlog B1. Defines the transitive `inline-transitive` / `static-transitive` links (§2.1), the effective-mode precedence lattice (§2.2), and the static boot-link graph — dedup + topological order + cycle rejection (§2.3) — with dependency-ordered emission into the `INLINE.md` priority lane and `INDEX.md` (§2.4). `redbook` is the motivating `inline-transitive` consumer (§3). Implementation (manifest schema + `bootgen` resolution) is the next milestone.</fact></item>
    </list>
  </section>
</spec>
