<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
  <title id="root">PROP-050: Dependency visibility — access, friendship, and the friend closure</title>
  <status stage="impl" state="done" comment="2026-08-23: BUILT — waves W1-W8 landed the same day the design closed; engine, resolver/lock on E(R), lanes, gate softening, why/friends/closure-diff, deliberate narrowing all live; leftovers filed as B-102..B-105"/>
  <p p="1"><fact id="status-line" status="impl/done">**Status: BUILT — 2026-08-23.** The full build landed the same day the design closed: W1 engine + manifest vocabulary, W2 resolution/lock on `E(R)` (schema v6, provenance), W3 lane goldens, W4 gate softening, W5 observability (`vibe why`/`vibe friends`/closure diff/tree annotations/hygiene cell), W6 power-instrument proofs, W7 deliberate narrowing (fractality group; host lanes byte-identical), W8 close-out. Build record: [`vibevm/vibespecs/terraforms/VISIBILITY-BUILD-PLAN-v0.1.xml`](../terraforms/VISIBILITY-BUILD-PLAN-v0.1.xml); measurements: [`vibevm/vibespecs/research/dependency-visibility-2026-08/04-measurements.xml`](../research/dependency-visibility-2026-08/04-measurements.xml); leftovers: BACKLOG B-102..B-105. All ten forks stand ruled (F1–F4, F6, F10 as drafted; F5 → no-legacy; F7 → warnings; F8 → designed+ratified; F9 → official, quiet, any-node); the only unruled sketch is ##CONCEPT-GATE-DIRECTION (B-105, non-blocking). Ratification history in §9/§10 stands as authored.</fact></p>
  <p p="2"><fact id="related" status="spec/work">**Related:** [PROP-009](../modules/vibe-workspace/PROP-009-loading-model.xml) (the loading model and the `link` axis), [PROP-034](../modules/vibe-workspace/PROP-034-transitive-links-boot-graph.xml) / [PROP-035 §12](../modules/vibe-workspace/PROP-035-spec-compiler.xml#transitive-inline) / [PROP-038](../modules/vibe-workspace/PROP-038-hybrid-boot-linking.xml) (the linker this proposal re-layers), [PROP-048](PROP-048-tokenomics.xml) (tokenomics — the law this system serves), [PROP-049](PROP-049-snippet-genre.xml) (the snippet genre — §4.3 settles its fate under visibility), [PROP-046](PROP-046-adoption-facts-registry.xml) (consumer sovereignty — the same philosophy), [PROP-024](PROP-024-code-bearing-packages.xml) (role equipotence — visibility is role-blind), [PROP-028](PROP-028-package-families.xml) (collections — the migration's largest client).</fact></p>
  <section id="motivation" title="1. Motivation — seepage is a budget defect, not a secrecy defect">
    <p p="3"><fact id="seepage-today" status="impl/done">Today the requires closure IS the effective set: every transitive dependency of every dependency reaches the consumer root, lands in `INDEX.md` (or, forced, in the static lane), and spends the root's context budget. The only controls are materialisation-side (`link` modes decide *how* a package loads, never *whether* it arrives) and the PROP-049 genre machinery (which polices *textual presupposition*, not structural arrival). The WAL leak — every new project sprouting `vibevm/vibespecs/WAL.xml` because a package chain delivered the wal flow — is the type specimen of the defect class.</fact></p>
    <p p="4"><fact id="VISIBILITY-IS-BUDGET-CONTROL" status="spec/done">**The inversion thesis.** In every prior-art system (C++ `friend`, Java module exports, OSGi, Bazel `visibility`) visibility is *provider-declared secrecy*: the provider protects its invariants by naming who may see in. In vibevm the scarce good is not the provider's encapsulation but the **consumer's context budget** (PROP-048): a package that seeps uninvited costs tokens in every session of every downstream project. Visibility here is therefore **budget control**: the provider *marks* how far each edge seeps (`access` — presence flows along declared edges by default, §2.2, so loaders never silently lose what was always meant to be available), while the **gated substance never arrives uninvited** — friendship is an explicit act on both sides (`friends-only` mark ∧ explicit grant, §2.3–2.4) — and every arrival is measurable and refusable (`vibe why`, lock-diff, `unfriend`/`exclude`/`override`). This inverts the direction of every friendship mechanism in prior art — ours is consumer-declared opt-in, not provider-declared allowlisting — and the consequences of that inversion are traced through §5.</fact></p>
    <p p="5"><fact id="owner-mandate" status="spec/work">**Owner mandate (2026-08-23, verbatim core):** «Каждая зависимость в vibe.toml должна обладать несколькими новыми опциональными свойствами… Пакеты с доступом public транзитивно просачиваются вдоль всей иерархии… потребительский корень проекта — это причина, почему дальше начинают происходить вычисления всей остальной иерархии пакетов… должна работать транзитивность внутренних друзей… Таким образом у нас получается некое общее "замыкание друзей"… Чтобы это замыкание не росло бесконтрольно, нужно предусмотреть параметры с исключениями».</fact></p>
  </section>
  <section id="model" title="2. The model">
    <section id="perspectives" title="2.1 Perspectives — relations first, materialisation at the root">
      <p p="6"><fact id="PER-NODE-PERSPECTIVE" status="spec/work">Every visibility notion in this document is **per-node**: each manifest's declarations define how the graph looks *from that node*. An interior node's friendships, unfriendings and exclusions are «просто отношение между пакетами» — pure relations, materialising nothing.</fact></p>
      <p p="7"><fact id="ROOT-DRIVES-MATERIALISATION" status="spec/work">**The consumer root is the cause of computation** (owner law). Only when a node is the session's consumer root (PROP-024: `[project]` or `[package]`, equipotently) do its perspective's relations resolve into an **effective set**, get version-resolved, materialised into `vibedeps/`, and compiled into lanes. «Потребительский корень проекта — это причина, почему дальше начинают происходить вычисления всей остальной иерархии пакетов.»</fact></p>
    </section>
    <section id="access" title="2.2 Edge access — how far this edge may seep">
      <p p="8"><fact id="ACCESS-LEVELS" status="spec/done" action="continue" actionstage="doc" audience="user,author">Each `[requires.packages]` edge gains an optional `access` property with three values — the provider-side seepage mark on the edge `P → Q`, declared by `P` about its own dependency `Q`:</fact></p>
      <fence lang="toml" p="9">[requires.packages]
"flow:org.x/api-conventions" = { version = "^1.0" }                          # access = "public" — the default
"flow:org.x/wal"             = { version = "^1.0", access = "private" }      # dev-world only, seeps nowhere
"flow:org.x/internal-style"  = { version = "^1.0", access = "friends-only" } # seeps to those who befriended P</fence>
      <list ordered="false" p="10">
        <item><fact id="ACCESS-PUBLIC" status="spec/done" action="continue" actionstage="doc" audience="user,author">`access = "public"` — **the default.** `Q` seeps through `P` to *every* consumer above, transitively along the whole hierarchy, with no opt-in. For edges whose target is part of the declarant's substance for *all* consumers — a collection's members, a stack's core, and every ordinary «my text builds on this» edge.</fact></item>
        <item><fact id="ACCESS-PRIVATE" status="spec/done" action="continue" actionstage="doc" audience="user,author">`access = "private"` — `Q` does not seep through `P` at all: the edge is traversed only when `P` itself is the consumer root (§4.4 — the dev world). An implementation detail in the strictest sense — the explicit mark for WAL-class disciplines and dev tooling.</fact></item>
        <item><fact id="ACCESS-FRIENDS" status="spec/done" action="continue" actionstage="doc" audience="user,author">`access = "friends-only"` — `Q` seeps through `P` only into consumers whose friend closure contains `P` (§2.4): those who deliberately named `P` (directly or transitively) a friend. The curated middle.</fact></item>
      </list>
      <p p="11"><fact id="DEFAULT-PUBLIC-RATIONALE" status="spec/done">**Default public — owner-ruled (2026-08-23), and the asymmetry of failures decides it.** The owner: «public по умолчанию точно должен быть true, потому что бессмысленное ограничение доступа к скриптам может привести к тому, что у нас сломаются динамические и статические лоадеры (без разбора ограничили то, что подразумевалось всегда доступным)». The deeper argument this survives on: the two failure modes are **not symmetric in observability**. Over-delivery (a forgotten `private`) costs tokens — visible in `vibe why`, lane sizes, the lock-diff, and fixable by any consumer (`exclude`, `unfriend`, `override`). Under-delivery (a forgotten `public` under a private default) makes the agent *silently dumber* — a hole in loaded knowledge that no measurement surfaces and only the package's author can fix. A budget system must push errors toward the observable side; presence therefore defaults open, and the budget discipline lives in explicit narrowing marks, the §7 observability, and the §5 authoring norm — not in the default. This replaces the draft's default-private rationale (fail-closed presence), which the ruling retires.</fact></p>
    </section>
    <section id="friendship" title="2.3 Friendship — consumer-declared, two grant forms">
      <p p="12"><fact id="FRIENDSHIP-IS-CONSUMER-DECLARED" status="spec/work">Friendship is declared by the **consumer of content**, never by its provider: naming a package your friend means *you receive* what it shares friends-only. There is no provider-side allowlist (§9 F8 keeps the door open). Two grant forms:</fact></p>
      <list ordered="false" p="13">
        <item><fact id="FRIEND-EDGE-FLAG" status="spec/done" action="continue" actionstage="doc" audience="user,author">**Per-edge `friend = true|false` (default `false` — owner re-ruled 2026-08-23).** Friendship is a raised privilege — receiving a target's gated substance — and raised privileges are not granted where they may never be used: an ordinary edge takes the package, not its inner circle. `friend = true` is the explicit opt-in; the original default-`true` belonged to the earlier one-field paradigm and is retired with it. With the public presence default (§2.2) the two defaults form the deliberate asymmetry: **presence is generous, friendship is stingy** — the ordinary world works with zero ceremony, the curated world opens only by explicit word.</fact></item>
        <item><fact id="FRIENDS-ONLY-IMPLIES-FRIEND" status="spec/done" action="continue" actionstage="doc" audience="author">**The implication — owner-ratified (F10, 2026-08-23): a `friends-only` mark implies `friend = true` on its own edge.** Vouching for a package as part of your substance while standing in no relation to it is incoherent — and under the strict `friend = false` default, a `friends-only` chain would otherwise need *two* marks per hop, where forgetting the second silently kills the chain. With the implication, the owner's `A → B → C → D` chain works from one mark per hop, exactly as originally intended; an explicit `friend = false` beside a `friends-only` mark overrides the implication and yields the lawful **no-vouch** cell of ##ACCESS-FRIEND-MATRIX (terminal delivery to the circle) — no lint, it is a real intent.</fact></item>
        <item><fact id="FRIENDS-LIST" status="spec/work">**Node-level `friends = ["group/name", …]`.** Explicit befriending of *any* package — «можно указать и свои прямые зависимости, и вообще любые зависимости» — including ones the node does not require directly (e.g. a practice known to arrive deep in the closure whose friends-only companions the root wants).</fact></item>
      </list>
      <p p="14"><fact id="VISIBILITY-SECTION" status="spec/done" action="continue" actionstage="doc" audience="user,author">**The node-level vocabulary lives in one role-blind `[visibility]` section** — `friends`, `unfriend`, `allow-friends` (§2.8), `ignore-concept-warnings` (##CONCEPTS-GATE-SOFTENED) — plus the sibling top-level `[override]` table (§2.9). One section serves both manifest roles by construction (PROP-024 equipotence): no field is duplicated between `[project]` and `[package]`.</fact></p>
      <p p="15"><fact id="PUBLIC-PLUS-FRIEND" status="spec/done" action="continue" actionstage="doc" audience="author">**`access` and `friend` compose independently on one edge** (owner-confirmed 2026-08-23: «access и friend разные понятия»). The two marks point in opposite directions, so their product is well-defined. The default shape — `public` presence, no friendship — is plain delivery: «Q reaches everyone above me; I take none of its gated substance». Adding `friend = true` to a public edge reads: «and *for myself* I enter friendship with Q, so Q's friends-only doors are open in my own perspective». That friendship does **not** travel onward through a public edge — re-exporting friendship is exactly what the `friends-only` mark does (##PUBLIC-GIVES-PRESENCE-NOT-FRIENDSHIP) — so in another root's closure a public edge contributes presence and nothing more. The remaining corner — «deliver publicly *and* vouch friendship onward» in a single edge — is deliberately inexpressible: a consumer that wants Q's gated substance opts in with its own `friends` line, which is the payer-decides invariant.</fact></p>
      <p p="16"><fact id="ACCESS-FRIEND-MATRIX" status="spec/done">**The full 3 × 2 matrix (owner-requested completeness check, 2026-08-23).** For an edge `P → Q`, read across three observers — an arbitrary consumer above `P`; a friend of `P`'s (a root `R` with `P ∈ C(R)`); and `P` itself as root (its dev world). `friend` answers one question everywhere — «does the observer get Q's *own gated substance*» — while `access` picks the audience that gets `Q` at all:</fact></p>
      <table p="17">
        <tr>
          <td>`access`</td>
          <td>`friend`</td>
          <td>consumer above P</td>
          <td>friend of P</td>
          <td>P as root</td>
          <td>practical name</td>
        </tr>
        <tr>
          <td><fact id="MX-PUBLIC-BARE" status="spec/done">`public`</fact></td>
          <td><fact id="MX-PUBLIC-BARE-FRIEND" status="spec/done">`false` *(defaults)*</fact></td>
          <td><fact id="MX-PUBLIC-BARE-CONSUMER-ABOVE-P" status="spec/done">gets Q</fact></td>
          <td><fact id="MX-PUBLIC-BARE-FRIEND-OF-P" status="spec/done">gets Q</fact></td>
          <td><fact id="MX-PUBLIC-BARE-P-AS-ROOT" status="spec/done">gets Q</fact></td>
          <td><fact id="MX-PUBLIC-BARE-PRACTICAL-NAME" status="spec/done">plain delivery — the ordinary edge</fact></td>
        </tr>
        <tr>
          <td><fact id="MX-PUBLIC-FRIEND" status="spec/done">`public`</fact></td>
          <td><fact id="MX-PUBLIC-FRIEND-FRIEND" status="spec/done">`true`</fact></td>
          <td><fact id="MX-PUBLIC-FRIEND-CONSUMER-ABOVE-P" status="spec/done">gets Q</fact></td>
          <td><fact id="MX-PUBLIC-FRIEND-FRIEND-OF-P" status="spec/done">gets Q</fact></td>
          <td><fact id="MX-PUBLIC-FRIEND-P-AS-ROOT" status="spec/done">gets Q **+ Q's circle**</fact></td>
          <td><fact id="MX-PUBLIC-FRIEND-PRACTICAL-NAME" status="spec/done">delivery + personal friendship</fact></td>
        </tr>
        <tr>
          <td><fact id="MX-FO-VOUCHED" status="spec/done">`friends-only`</fact></td>
          <td><fact id="MX-FO-VOUCHED-FRIEND" status="spec/done">`true`</fact></td>
          <td><fact id="MX-FO-VOUCHED-CONSUMER-ABOVE-P" status="spec/done">—</fact></td>
          <td><fact id="MX-FO-VOUCHED-FRIEND-OF-P" status="spec/done">gets Q **+ Q ∈ C(R)**: chain continues, Q's circle opens</fact></td>
          <td><fact id="MX-FO-VOUCHED-P-AS-ROOT" status="spec/done">gets Q + Q's circle</fact></td>
          <td><fact id="MX-FO-VOUCHED-PRACTICAL-NAME" status="spec/done">the chain link — transit with vouching (the F10 shape)</fact></td>
        </tr>
        <tr>
          <td><fact id="MX-FO-NOVOUCH" status="spec/done">`friends-only`</fact></td>
          <td><fact id="MX-FO-NOVOUCH-FRIEND" status="spec/done">`false`</fact></td>
          <td><fact id="MX-FO-NOVOUCH-CONSUMER-ABOVE-P" status="spec/done">—</fact></td>
          <td><fact id="MX-FO-NOVOUCH-FRIEND-OF-P" status="spec/done">gets Q, chain **stops**: Q's circle stays shut</fact></td>
          <td><fact id="MX-FO-NOVOUCH-P-AS-ROOT" status="spec/done">gets Q only</fact></td>
          <td><fact id="MX-FO-NOVOUCH-PRACTICAL-NAME" status="spec/done">terminal delivery to the circle — share without vouching</fact></td>
        </tr>
        <tr>
          <td><fact id="MX-PRIVATE-BARE" status="spec/done">`private`</fact></td>
          <td><fact id="MX-PRIVATE-BARE-FRIEND" status="spec/done">`false`</fact></td>
          <td><fact id="MX-PRIVATE-BARE-CONSUMER-ABOVE-P" status="spec/done">—</fact></td>
          <td><fact id="MX-PRIVATE-BARE-FRIEND-OF-P" status="spec/done">—</fact></td>
          <td><fact id="MX-PRIVATE-BARE-P-AS-ROOT" status="spec/done">gets Q</fact></td>
          <td><fact id="MX-PRIVATE-BARE-PRACTICAL-NAME" status="spec/done">dev tool</fact></td>
        </tr>
        <tr>
          <td><fact id="MX-PRIVATE-FRIEND" status="spec/done">`private`</fact></td>
          <td><fact id="MX-PRIVATE-FRIEND-FRIEND" status="spec/done">`true`</fact></td>
          <td><fact id="MX-PRIVATE-FRIEND-CONSUMER-ABOVE-P" status="spec/done">—</fact></td>
          <td><fact id="MX-PRIVATE-FRIEND-FRIEND-OF-P" status="spec/done">—</fact></td>
          <td><fact id="MX-PRIVATE-FRIEND-P-AS-ROOT" status="spec/done">gets Q + Q's circle</fact></td>
          <td><fact id="MX-PRIVATE-FRIEND-PRACTICAL-NAME" status="spec/done">dev tool with deep integration</fact></td>
        </tr>
      </table>
      <list ordered="false" p="18">
        <item><fact id="MATRIX-COMPLETENESS" status="spec/done">All six cells are distinct and each names a real intent — no redundancy, no gap. The one that *looked* dead — `friends-only` + `friend = false` — turns out load-bearing: it separates **delivery to the circle** from **vouching onward**, which is exactly the distinction the two-parameter split exists to express (rule (3) grants presence from the mark alone; the `grow` rule demands mark ∧ grant for the chain to continue). The parameter set is confirmed correct by exhaustion.</fact></item>
      </list>
      <p p="19"><fact id="TRANSIT-WITHOUT-DECLARATION" status="spec/done" action="continue" actionstage="doc" audience="user,author">**A friend declares nothing about the vouched content — by design.** When `R` befriends `P` (one explicit line: `friend = true` on its `P` edge, or a `friends` entry), everything `P` vouches for arrives with no mention in `R`'s manifest: `P`'s friends-only edge admits `Q` (rule (3)), the `grow` rule puts `Q` into `C(R)`, and `Q`'s own friends-only edges then open too — recursively, the owner's original transitivity requirement. Version choice for `Q` stays with the *voucher* (`P`'s constraint on its own edge) — the vouched set is a bundle `P` tested, not a menu `R` assembles. This is the standard re-export semantics of JPMS `requires transitive` (implied readability chains), Bazel `deps` + `exports*`, and Gradle `api` — a dependency on `P` is a dependency on «`P` with everything `P` stands on». Control never leaves the payer: the grant is explicit (the `false` default), `unfriend`/per-edge `exclude` prune point-wise, and the lock-diff + `vibe why` make every transit arrival legible. The hygiene norm transposes from Bazel strict-deps: transit covers «`Q` is here because `P` stands on it»; the moment `R`'s *own* text starts building on `Q`, `R` declares its own direct edge.</fact></p>
      <p p="20"><fact id="FRIENDS-ONLY-USE-CASES" status="spec/done">**What `friends-only` is for — the practical genres:** (a) **usage surface vs extension surface** — a family (e.g. git-practices) delivers its practices `public`, while the author-side toolkit for *writing a new member* (templates, style, test matrices) rides `friends-only`: consumers consume, extenders befriend the family and receive the workshop; (b) **platform inner layer** — a platform ships its API text to all, its implementation-notes layer to befriended plugins (the Eclipse x-friends case, inverted to consumer opt-in, and sealable with §2.8 when curation is wanted); (c) **incubation** — an unstable companion practice circulates among befriended dogfood roots before its mark widens to `public` (a feature release per ##ACCESS-IS-SEMVER-SURFACE); (d) **heavy opt-in tier** — an advanced companion whose token weight should not tax every consumer, delivered only to those who ask; (e) **the WAL shape** — a redbook-like collection could carry a wal-like discipline on a `friends-only` edge: ordinary consumers never see it, while a root that wants the full discipline writes `friends = ["…/redbook"]` and receives wal *at the version the collection tested* — the alternative to today's direct host edge.</fact></p>
    </section>
    <section id="closure" title="2.4 The friend closure — transitivity through friends-only edges">
      <p p="21"><fact id="GRANTS-DEFINITION" status="spec/done">For a node `N`, define `grants(N)` = the targets of `N`'s edges carrying `friend = true` (explicit under the `false` default, or implied by a `friends-only` mark per F10 — an explicit `friend = false` beside the mark wins), plus `N`'s `friends` list, minus `N`'s `unfriend` list (§2.6). These are the packages `N` *directly* befriends.</fact></p>
      <p p="22"><fact id="FRIEND-CLOSURE" status="spec/work">**The friend closure** `C(R)` of a root `R` is the least fixpoint of:</fact></p>
      <fence p="23">seed:  every G ∈ grants(R) is in C(R)
grow:  if F ∈ C(R), and G ∈ grants(F), and F's edge F → G has access = "friends-only",
       then G ∈ C(R)</fence>
      <p p="24"><fact id="closure-reading" status="spec/done">Reading: my friends are those I named; and when a friend marks one of its own befriended dependencies `friends-only` — «this one is part of my substance, shared with my friends» — that dependency becomes my friend too, recursively. The `friends-only` mark on `F → G` is thus simultaneously (a) the seepage gate of §2.2 and (b) the **re-export of friendship** that makes the closure transitive. This identification — the ordering message's per-edge «internal» mark IS `access = "friends-only"` — is **owner-ratified** (F1, 2026-08-23: «правильный термин — friends-only»).</fact></p>
      <p p="25"><fact id="PUBLIC-GIVES-PRESENCE-NOT-FRIENDSHIP" status="spec/done">**Public gives presence, not friendship** (owner-ratified, F6). A package that seeps to `R` through public edges does *not* thereby join `C(R)`, and its own friends-only edges stay closed to `R` unless `R` (or a chain of friends-only re-exports reaching `R`) befriends it explicitly. Friendship never grows through public or private edges — only through `friends-only` marks, which are explicit and rare (never a default), compounded by the `friend = false` default on the grant side. This is the built-in answer to «чтобы это замыкание не росло бесконтрольно»: the closure grows exactly along deliberate marks, backed by deliberate grants, and nowhere else.</fact></p>
      <p p="26"><fact id="closure-determinism" status="impl/done">`C(R)` is a monotone least fixpoint over per-edge static predicates — deterministic and order-independent. With §2.9's path-stack masks in the picture, the closure and the reachable mask-states form **one joint monotone fixpoint** (alternating expansion until stable; masks are static declarations and both sets only grow, so termination and determinism hold; implementations dedup normalized mask-states) — the earlier draft's «computed strictly before the effective set» reading was corrected at the W1 landing, where the implementation surfaced the contradiction. Materialisation (§3) stays outside either computation. Cycles in `requires` remain a hard generate-time error exactly as in the linker today (PROP-034 §2.3).</fact></p>
    </section>
    <section id="effective-set" title="2.5 Traversability and the effective set">
      <p p="27"><fact id="EFFECTIVE-SET" status="spec/work">**The effective set** `E(R)` — the packages that exist from `R`'s perspective — is everything reachable from `R` over *traversable* edges, along chains not killed by an `exclude` (§2.7). An edge `P → Q` is traversable for `R` iff any of the three rules below holds; private edges fail all three for `P ≠ R`, so a private dependency of a non-root package does not exist for the root — not resolved, not fetched, not materialised, not in any lane (§4.1–4.2).</fact></p>
      <fence p="28">(1) P = R                                  — the root's own edges always count
(2) access(P → Q) = "public"               — unconditional seepage
(3) access(P → Q) = "friends-only" ∧ P ∈ C(R)   — seepage to a friend of P's</fence>
      <p p="29"><fact id="effective-set-is-the-universe" status="impl/done">Everything downstream — version resolution, `vibedeps/` materialisation, link-mode assignment, lane compilation, `installed:` predicates, the facts registry — operates on `E(R)` and nothing else. Visibility is computed once, first, and every other system consumes its output (§3).</fact></p>
    </section>
    <section id="unfriend" title="2.6 `unfriend` — node-scoped pruning of the closure">
      <p p="30"><fact id="UNFRIEND-IS-NODE-SCOPED" status="spec/done" action="continue" actionstage="doc" audience="user,author">`unfriend = ["group/name", …]` (node-level) removes the named packages from the declaring node's `grants(…)` — and therefore from every friend closure *as seen through that node*. The unfriended package «притянется, но будет явно исключён из цепочки транзитивности внутренних друзей»: still usable at the declaring level (its edge, if any, still traversable by its own access), just never re-exported as a friend through the declarant. Node-scoped by owner law: «они выбрасываются из замыкания ТОЛЬКО с точки зрения той ноды, которая объявила их unfriend — а какой-нибудь другой пакет в иерархии может нормально включить их в замыкание» — another node's friends-only chain delivers the same package untouched. The name **`unfriend`** is owner-ratified (F2, 2026-08-23); the property list's `enemy` is retired.</fact></p>
    </section>
    <section id="exclude" title="2.7 `exclude` — hard subtree exclusion">
      <p p="31"><fact id="EXCLUDE-IS-EDGE-SCOPED" status="spec/done" action="continue" actionstage="doc" audience="user,author">`exclude = ["group/name", …]` (per-edge) kills the named packages in every chain passing through the declaring edge — «исключены из цепочки транзитивных подключений вообще, даже если внутри они объявлены как public». Maven-exclusions semantics, **owner-ratified** (F4, 2026-08-23: «exclude per-ребро Maven-style. Можно per-ребро делать глубокие эксклюды по иерархии — это не глобальный deny-list, это сужение в рамках поддерева»): the pruning reaches arbitrarily deep, but only within *this edge's subtree*; a different path still delivers the package, and then it simply exists in `E(R)` via that path — classic diamond behaviour, no global veto. Exclusion is pure **subtraction** — it can only narrow what providers granted, never widen — which is why any node may declare it as part of shaping its own delivery; the expansive counterpart is F9's root-only `override`, which may also re-house this syntax (the semantics here stand either way).</fact></p>
    </section>
    <section id="allow-friends" title="2.8 `allow-friends` — the provider&apos;s sealed circle">
      <p p="32"><fact id="ALLOW-FRIENDS-DESIGN" status="spec/done" action="continue" actionstage="doc" audience="author">**Owner-ordered design (F8, 2026-08-23: «спроектируй механизм allow-friends… похоже на возможность построить exhaustive замыкание sealed classes»).** A provider `G` may declare, node-level, who is permitted to *enter friendship with it* — the Java-`sealed`/`permits` shape transplanted onto the friendship relation (design ratified — «годится»):</fact></p>
      <fence lang="toml" p="33">[visibility]
allow-friends = ["org.a/x", "org.b/*"]   # the permits list; "*" after a group = any member</fence>
      <list ordered="false" p="34">
        <item><fact id="ALLOW-FRIENDS-STATES" status="spec/done" action="continue" actionstage="doc" audience="author">Three states: field **absent** — friendship is open, anyone's grant works (the default, today's semantics); **empty list** — sealed: nobody enters, the gated substance exists only in `G`'s own dev world; **a list** — exactly the named circle.</fact></item>
        <item><fact id="ALLOW-FRIENDS-CHECKPOINT" status="spec/done" action="continue" actionstage="doc" audience="author">The check sits on the **grant giver**: a grant `N → G` (an explicit `friend = true` edge, a `friends` entry, or an F10-implied one) participates in any closure only if `allow-friends(G)` is absent or covers `N`. A friends-only re-export hop `F → G` in `C(R)` likewise requires `F` permitted by `G`. A rejected grant is a **warning, never an error** (the §5 unknown-target precedent) — the closure simply does not grow there.</fact></item>
        <item><fact id="ALLOW-FRIENDS-EXHAUSTIVE" status="spec/done">The sealed payoff is **enumerability**: `G`'s inner world becomes computable from `G`'s manifest plus its permitted members' — `vibe friends org.x/g` lists who *may* befriend `G`, who actually does, and through which chains. The exhaustive closure the owner names.</fact></item>
        <item><fact id="ALLOW-FRIENDS-TRANSIT" status="spec/done">Trusted friends are **non-sealed points by design**: `G` curates its immediate circle; a permitted friend `F` that friends-only-marks its own `F → G` edge extends `G`'s substance to `F`'s friends — vouching onward is exactly what admission to the circle licenses (as a `permits`-listed subtype may itself be `non-sealed`). A provider wanting no transit keeps its circle to leaves it trusts not to re-export.</fact></item>
        <item><fact id="SEAL-GATES-FRIENDSHIP-NOT-DELIVERY" status="impl/done" action="continue" actionstage="doc" audience="author">**The seal gates friendship, never delivery (pinned at the W6 landing).** A sealed `G` still *arrives* wherever a declarant's `friends-only` edge delivers it — the declarant owns its own edge and could as well have marked it `public`; what the seal closes is **entry into `G`'s circle**: a rejected grant keeps `G` out of every closure, so `G`'s own friends-only inner content stays shut and the grant surfaces as a `RejectedGrant` warning. The observable difference between sealed and unsealed is always the inner content and the diagnostics, never the presence of `G` itself. Proven end-to-end by `cli_visibility_power.rs` (unseal and exact-circle scenarios).</fact></item>
        <item><fact id="ALLOW-FRIENDS-OVERRIDABLE" status="spec/done">**`override` breaks the seal — owner-ruled:** «механизм override должен всё равно иметь возможность взломать эти рёбра». A node-targeted override entry — `"org.x/g" = { allow-friends = "*" }` (or a replacement list) — rewrites the permits list in the root's perspective, quietly, like every override (F9, §2.9).</fact></item>
      </list>
    </section>
    <section id="override" title="2.9 `override` — the sanctioned break-in, anywhere in the graph">
      <p p="35"><fact id="OVERRIDE-ANYWHERE" status="spec/done" action="continue" actionstage="doc" audience="user,author">**Owner-ruled (2026-08-23): `override` is lawful in any manifest, not only the root** («разрешён не только в корневом манифесте, а где угодно»). Any node `N` may carry an `[override]` table whose entries rewrite *foreign* edges — their `access`, `friend`, presence (`exclude = true`), or a target's `allow-friends` — and the rewrite acts wherever `N` stands on the chain: an aggregator repairs or reshapes a member's edge for **all of its own consumers**, exactly as it curates its delivery with `exclude`. The threat model follows the owner's earlier ruling: a deliberate break-in is not an attack (the developer can edit any file on disk anyway); this is the official verb that replaces reflection-style hacks — and it stays **quiet** (pull-based provenance only).</fact></p>
      <p p="36"><fact id="OVERRIDE-PATH-SEMANTICS" status="spec/done" action="continue" actionstage="doc" audience="author">**Path-stack semantics.** An override applies to chains that pass through its declarant: walking a chain `R → … → N → … → P → Q`, the effective attributes of the edge `P → Q` are its declared attributes masked by the `[override]` tables of the chain's nodes in order, **nearer-to-root applied later and winning** — the root can re-override any intermediary, the payer always has the final word; between intermediaries, the outer (closer to `R`) wins on the chains it participates in. Effective attributes are therefore per-chain; `E(R)` and the `grow` rule quantify **existentially over chains** (a package is present / a hop extends the closure if *some* chain admits it), which is the diamond behaviour `exclude` already has. Determinism is preserved — masks are static declarations, the graph is acyclic, and the implementation dedups identical mask-states while walking the DAG (override tables are rare, so the practical state count stays small).</fact></p>
      <p p="37"><fact id="OVERRIDE-KEY-COEXISTENCE" status="impl/done" action="continue" actionstage="doc" audience="author">**Syntax note (W1 landing):** the manifest already carried `[[override]]` — the array-of-tables registry-pin form (`OverrideSection`). The visibility table lives under the same `override` key as an ordinary table; the wire layer distinguishes the two shapes structurally (array vs table), either form alone is lawful, and one manifest carrying both is a loud validation/serialisation error rather than a silent merge. A future wave may retire or rename the legacy form; until then the coexistence is deliberate.</fact></p>
      <p p="38"><fact id="OVERRIDE-IS-CONTRACT-MIDGRAPH" status="spec/done">**A mid-graph override is contract; the root's is private.** An `[override]` in a package's manifest changes what that package delivers — it is part of the package's versioned surface, subject to ##ACCESS-IS-SEMVER-SURFACE like any access change (expansive entries additionally ride at the breaker's own risk: the broken-into provider promised nothing). The consumer root's `[override]` binds nobody downstream and is versioned by nothing but its own repository. Observability is pull-based only, per the quiet ruling: lock provenance marks and `vibe why … — via override of &lt;node&gt;` answer whoever asks; nothing shouts at install time.</fact></p>
    </section>
    <section id="example" title="2.10 Worked example — the owner&apos;s chain">
      <p p="39"><fact id="worked-example" status="spec/done">The ordering message's chain `A → B → C → D` (D a practice), with each intermediate marking its dependency edge `friends-only` (each mark implying its own grant per F10): `grants(A) ⊇ {B}` (A's explicit grant on its own edge), so `B ∈ C(A)`; `B`'s edge `B → C` is friends-only and `C ∈ grants(B)`, so `C ∈ C(A)`; `C → D` likewise, so `D ∈ C(A)` — «загрузившись в D мы получим, что по цепочке транзитивности, D является другом для A». Traversal: `A → B` by rule (1); `B → C` by rule (3) with `B ∈ C(A)`; `C → D` by rule (3) with `C ∈ C(A)` — so `D ∈ E(A)` and D materialises for A. If `B` had declared `unfriend = ["…/C"]`, the chain would break at `B` for every root above `B` — while a sibling path `A → B′ → C` (B′ friends-only-marking C) would deliver C and D intact.</fact></p>
    </section>
  </section>
  <section id="layering" title="3. Layering — visibility above materialisation">
    <p p="40"><fact id="VISIBILITY-ABOVE-MATERIALISATION" status="impl/done">**Owner ruling, adopted as the layer boundary:** «признак типа static-transitive должен применяться уже ПОСЛЕ вычисления замыкания друзей, потому что замыкание друзей — это свойство логики связей в приложении, а транзитивная статичность — это свойство материализации, более логически низкая структура». The pipeline becomes:</fact></p>
    <fence p="41">1. visibility    — compute C(R), then E(R)                    (this PROP; pure relations)
2. resolution    — version-resolve E(R) only                  (PROP-003/017, unchanged in kind)
3. link modes    — declared / suggested / default, per edge   (PROP-009 §2.4, unchanged)
4. linker        — zones, static-transitive forcing, dedup,   (PROP-034/035 §12/038,
                   topo order, hoisting                        re-scoped to E(R))
5. emission      — STATIC / INDEX lanes                       (unchanged)</fence>
    <list ordered="false" p="42">
      <item><fact id="LINK-AXIS-ORTHOGONAL" status="impl/done">The `link` axis (`static` / `dynamic` / `static-transitive` / `static-hard`) survives intact as **materialisation strength within the visible graph**: «возможность спускать вниз по иерархии признак статичности всё так же очень нужна… как отдельная ось, ортогональная нашему замыканию друзей».</fact></item>
      <item><fact id="FORCING-NEVER-WIDENS" status="impl/done">**Forcing never widens visibility.** A `static-transitive` edge propagates staticness across *traversable* edges only: it can make a visible package static, never make an invisible package visible. The hybrid linker's forced-descent (`resolve_zone`, PROP-038 §2.2) simply walks `E(R)`'s subgraph instead of the raw requires graph; hoisting counts (PROP-038 §5.2) count within `E(R)`.</fact></item>
      <item><fact id="layer-law-fit" status="impl/done">This ordering is THE-LAYER-LAW (PROP-048) applied to the pipeline itself: the logical stratum (who exists) is more stable and more upstream than the materialisation stratum (how it loads); a change in link mode never invalidates the visibility computation above it.</fact></item>
    </list>
  </section>
  <section id="interactions" title="4. System interactions">
    <section id="resolver" title="4.1 Resolver and lock">
      <p p="43"><fact id="RESOLVE-EFFECTIVE-ONLY" status="impl/done" action="continue" actionstage="doc" audience="user">Version resolution operates on `E(R)` only: private edges of non-root packages contribute no constraints, fetch nothing, and cannot conflict. `vibe.lock` records `E(R)` — the lock of a consumer no longer contains other packages' dev-world entries. Version unification (one node per `(group, name)`, PROP-003/017) is unchanged *within* the effective set. A welcome simplification vs code ecosystems: the Cargo-RFC-1977 problem («may private deps duplicate at different versions?») does not arise — an invisible package has no copies at all.</fact></p>
      <p p="44"><fact id="RESOLVE-PRUNE-INTERLEAVING" status="impl/done">**The interleaving, named honestly.** Computing `E(R)` needs edges; edges live in manifests of *resolved* versions; constraints come only from `E(R)` — so the walk is joint: the resolver expands the graph following only traversable edges, with `C(R)` recomputed monotonically as newly-resolved manifests contribute grants and marks (all predicates are static edge attributes of already-chosen nodes, so the joint fixpoint stays deterministic). Reading a manifest to learn that its edge is private is a **metadata read, not materialisation** — package *content* is fetched for `E(R)` members only. The Cargo lesson (RFC 1977 lost six years to resolver entanglement; RFC 3516 lives by decoupling) is honoured structurally: visibility stays a pure edge predicate the walk *consults* — never a quantity the version solver optimises over.</fact></p>
    </section>
    <section id="materialisation" title="4.2 Materialisation and `vibedeps/`">
      <p p="45"><fact id="MATERIALISE-EFFECTIVE-ONLY" status="impl/done" action="continue" actionstage="doc" audience="user">`vibedeps/` holds exactly `E(R)`: an excluded or invisible package leaves no slot, no cache entry for the root's world, no lane text. This is the structural fix for the WAL specimen: a wal flow declared `private` (or `friends-only`) by whatever requires it simply never arrives in a consumer's tree — no snippet, no `vibevm/vibespecs/WAL.xml` scaffold, no INDEX row.</fact></p>
    </section>
    <section id="prop-049-fate" title="4.3 PROP-049 under visibility — what the genre machinery still does">
      <p p="46"><fact id="flows-control-identification" status="impl/done">The ordering message asks whether «наша система с контролем flows» is still needed. No standalone "flows control" system exists in the spec tree (verified 2026-08-23: PROP-009's `flow` is an ordering *category*; PROP-028's flows are package *kinds*); the referent is read as the PROP-049 genre machinery — `installed:` predicates, snippet fragments, `concepts`, and the presupposition gate — built precisely against the WAL seepage this PROP now fixes structurally. Fork **F7** confirms the referent.</fact></p>
      <p p="47"><fact id="PROP-049-DIVISION-OF-LABOUR" status="impl/done">**Recommendation: keep PROP-049, re-scoped — the two systems solve different halves.** Visibility controls *structural arrival* (whether a package exists in `E(R)`); the genre machinery controls *textual behaviour* (what a snippet may presuppose, and how text adapts to a neighbour's presence). With visibility live: (a) `installed:` predicates and fragments remain THE adaptivity mechanism — an `installed:` predicate now queries `E(R)`, and a fragment binding to an absent friend stays physically omitted exactly as today; (b) the presupposition gate remains the authoring lint that catches a snippet textually assuming what its package did not lawfully receive; (c) what *dissolves* is the unbundling pressure — a future redbook-like collection may keep a wal-like member on a `friends-only`/`private` edge instead of expelling it.</fact></p>
      <p p="48"><fact id="CONCEPT-GATE-DIRECTION" status="spec/work">**Direction (F7 follow-up): concept-gated fragments.** Today the `concepts` array feeds exactly one consumer — the presupposition gate's dictionary (`crates/vibe-check/src/checks/snippet_presupposition.rs`): lexeme → owning package, matched against unconditional snippet text. A natural completion is `when = "concept:WAL"` — a fragment gated on «some package in `E(R)` declares this concept», resolving a discipline as an **interface** rather than a named implementation (`installed:` stays the exact-package form). Several packages could then carry one discipline interchangeably. Deferred to the owner's F7 ruling.</fact></p>
      <p p="49"><fact id="DEPS-EXEMPTION-NARROWS" status="spec/done">**One PROP-049 rule must tighten.** The presupposition lint currently exempts mentions of the package's own declared dependencies. Under visibility that exemption is too wide: a mention of an own **private** dependency in *unconditional* snippet text presupposes a package that never reaches any consumer — lawful only inside an `installed:`-gated fragment (which, for a private dependency, fires only in the declarant's own dev world, §4.4 — exactly right). The exemption narrows to *edges that seep* (public, or friends-only). This is a concrete PROP-049 amendment shipped with the implementation wave, in the warning genre of ##CONCEPTS-GATE-SOFTENED.</fact></p>
      <p p="50"><fact id="CONCEPTS-GATE-SOFTENED" status="spec/done">**Owner-ruled (F7, 2026-08-23): the gate speaks in warnings, with lawful homonymy, owner-dedup, and a subtree mute.** The owner's fear is exact — «термин WAL вообще супер ходовой, его многие используют в разных смыслах» — so: (a) a foreign-concept mention is a **warning** («пакет X использует концепт из пакета Y»), never an error; (b) **homonymy is lawful** — a package that declares the lexeme in its own `concepts` owns the word in its own world and triggers nothing; (c) **dedup across owners** — when several packages declare one lexeme, a legitimate relation to *any one* of them (own declaration, or a seeping edge to one owner) silences the warning — «достаточно лишь одного» — and an unexplained use warns once, naming all owners, not once per owner; (d) node-level **`ignore-concept-warnings = ["WAL", …]`** mutes the named concepts for the entire subtree hanging off that manifest. The machinery stays under this softening — «фича полезная, но очень нишевая».</fact></p>
    </section>
    <section id="dev-world" title="4.4 Equipotence and the dev world">
      <p p="51"><fact id="PRIVATE-IS-THE-DEV-WORLD" status="impl/done" action="continue" actionstage="doc" audience="user,author">Rule (1) of §2.5 — the root's own edges always traverse — combined with explicit `private` marks resolves the open tail of the equipotence wave (PROP-024): a package's `[requires]` is simultaneously its dev-set and its contract, **split per-edge by `access`** rather than by section. When the package is the consumer root (a dev checkout — `[project]` or `[package]`, equipotently), *all* its edges traverse and its private tooling materialises; when it is consumed as a dependency, only its seeping edges do. No separate dev-dependencies section needed.</fact></p>
    </section>
    <section id="vibefacts" title="4.5 vibefacts">
      <p p="52"><fact id="facts-scope-follows-visibility" status="impl/done">The adoption-facts registry (PROP-046) keys per-source files by installed packages; its universe follows `E(R)` mechanically. Registry entries for packages that leave the effective set surface through the existing `facts_sync` / lifecycle machinery (PROP-046 L5) — no new mechanism, one new reason entries become stale.</fact></p>
    </section>
    <section id="collections" title="4.6 Collections and families">
      <p p="53"><fact id="COLLECTIONS-DECLARE-PUBLIC" status="impl/done">A PROP-028 collection's whole point is aggregation: its member edges are the type case for `access = "public"` (redbook's ~21 exact-pinned members must seep to redbook's consumers — that is what depending on the collection *means*). A family's internal shared core is the type case for `friends-only` (members befriend each other through the family's marks; outsiders opt in or see nothing). Migration inventory in §6.</fact></p>
    </section>
  </section>
  <section id="prior-art" title="5. Prior art — what the neighbours teach">
    <p p="54"><fact id="prior-art-method" status="spec/work">Three research sweeps (2026-08-23; JVM lineage — JPMS / sealed / Kotlin / Swift; module-graph lineage — OSGi / Eclipse PDE / Bazel / Buck / Pants; dependency-manager lineage — Gradle / Maven / Cargo / npm / C++ `friend` and C++20 modules) ground this section; the full worker reports are archived in [`vibevm/vibespecs/research/dependency-visibility-2026-08/`](../research/dependency-visibility-2026-08/01-jvm-lineage.xml). The map, on the two axes that matter — who declares, and what a denial costs:</fact></p>
    <table p="55">
      <tr>
        <td>System</td>
        <td>Direction</td>
        <td>Closest primitive to ours</td>
        <td>Verdict used here</td>
      </tr>
      <tr>
        <td><fact id="PA-JPMS-RT" status="spec/done">JPMS `requires transitive`</fact></td>
        <td><fact id="PA-JPMS-RT-DIRECTION" status="spec/done">provider re-export</fact></td>
        <td><fact id="PA-JPMS-RT-CLOSEST-PRIMITIVE-TO-OUR" status="spec/done">the closure `grow` rule — same recursive shape</fact></td>
        <td><fact id="PA-JPMS-RT-VERDICT-USED-HERE" status="spec/done">adopt shape; adopt its usage norm</fact></td>
      </tr>
      <tr>
        <td><fact id="PA-JPMS-QE" status="spec/done">JPMS `exports … to`</fact></td>
        <td><fact id="PA-JPMS-QE-DIRECTION" status="spec/done">provider allowlist</fact></td>
        <td><fact id="PA-JPMS-QE-CLOSEST-PRIMITIVE-TO-OUR" status="spec/done">`friends-only` (opposite direction)</fact></td>
        <td><fact id="PA-JPMS-QE-VERDICT-USED-HERE" status="spec/done">unknown target = warning, not error</fact></td>
      </tr>
      <tr>
        <td><fact id="PA-SEALED" status="spec/done">Java `sealed … permits`</fact></td>
        <td><fact id="PA-SEALED-DIRECTION" status="spec/done">provider allowlist</fact></td>
        <td><fact id="PA-SEALED-CLOSEST-PRIMITIVE-TO-OUR" status="spec/done">friends list</fact></td>
        <td><fact id="PA-SEALED-VERDICT-USED-HERE" status="spec/done">allowlist needs one maintenance domain — ours has it (the consumer's own file)</fact></td>
      </tr>
      <tr>
        <td><fact id="PA-SWIFT-0409" status="spec/done">Swift SE-0409 access-level-on-imports</fact></td>
        <td><fact id="PA-SWIFT-0409-DIRECTION" status="spec/done">**consumer, per-edge**</fact></td>
        <td><fact id="PA-SWIFT-0409-CLOSEST-PRIMITIVE-TO-OUR" status="spec/done">`access` — the closest precedent alive</fact></td>
        <td><fact id="PA-SWIFT-0409-VERDICT-USED-HERE" status="spec/done">validates the core; they deferred the default flip twice</fact></td>
      </tr>
      <tr>
        <td><fact id="PA-SWIFT-SPI" status="spec/done">Swift `@_spi`</fact></td>
        <td><fact id="PA-SWIFT-SPI-DIRECTION" status="spec/done">bilateral handshake</fact></td>
        <td><fact id="PA-SWIFT-SPI-CLOSEST-PRIMITIVE-TO-OUR" status="spec/done">`friends-only` ∧ friendship</fact></td>
        <td><fact id="PA-SWIFT-SPI-VERDICT-USED-HERE" status="spec/done">conjunction, adopted (§2.5)</fact></td>
      </tr>
      <tr>
        <td><fact id="PA-OSGI-REEXPORT" status="spec/done">OSGi `Require-Bundle; visibility:=reexport`</fact></td>
        <td><fact id="PA-OSGI-REEXPORT-DIRECTION" status="spec/done">provider re-export</fact></td>
        <td><fact id="PA-OSGI-REEXPORT-CLOSEST-PRIMITIVE-TO-OUR" status="spec/done">`friends-only` as re-export</fact></td>
        <td><fact id="PA-OSGI-REEXPORT-VERDICT-USED-HERE" status="spec/done">its irreversibility lesson → ##ACCESS-IS-SEMVER-SURFACE</fact></td>
      </tr>
      <tr>
        <td><fact id="PA-ECLIPSE-XFRIENDS" status="spec/done">Eclipse `x-friends`</fact></td>
        <td><fact id="PA-ECLIPSE-XFRIENDS-DIRECTION" status="spec/done">provider allowlist</fact></td>
        <td><fact id="PA-ECLIPSE-XFRIENDS-CLOSEST-PRIMITIVE-TO-OUR" status="spec/done">friends-only</fact></td>
        <td><fact id="PA-ECLIPSE-XFRIENDS-VERDICT-USED-HERE" status="spec/done">provider lists rot because the payer can't edit them — the inversion argument</fact></td>
      </tr>
      <tr>
        <td><fact id="PA-BAZEL-EXPORTS" status="spec/done">Bazel `deps` + `exports*` chains</fact></td>
        <td><fact id="PA-BAZEL-EXPORTS-DIRECTION" status="spec/done">provider re-export</fact></td>
        <td><fact id="PA-BAZEL-EXPORTS-CLOSEST-PRIMITIVE-TO-OUR" status="spec/done">the closure formula, verbatim</fact></td>
        <td><fact id="PA-BAZEL-EXPORTS-VERDICT-USED-HERE" status="spec/done">battle-tested shape of §2.4</fact></td>
      </tr>
      <tr>
        <td><fact id="PA-BUCK-WITHINVIEW" status="spec/done">Buck `within_view`</fact></td>
        <td><fact id="PA-BUCK-WITHINVIEW-DIRECTION" status="spec/done">**consumer cap, wins conflicts**</fact></td>
        <td><fact id="PA-BUCK-WITHINVIEW-CLOSEST-PRIMITIVE-TO-OUR" status="spec/done">`exclude`</fact></td>
        <td><fact id="PA-BUCK-WITHINVIEW-VERDICT-USED-HERE" status="spec/done">exclusion beats any grant, adopted</fact></td>
      </tr>
      <tr>
        <td><fact id="PA-PANTS-CONJ" status="spec/done">Pants dependency/dependents rules</fact></td>
        <td><fact id="PA-PANTS-CONJ-DIRECTION" status="spec/done">both ends must agree</fact></td>
        <td><fact id="PA-PANTS-CONJ-CLOSEST-PRIMITIVE-TO-OUR" status="spec/done">rule (3) of §2.5</fact></td>
        <td><fact id="PA-PANTS-CONJ-VERDICT-USED-HERE" status="spec/done">conjunction as the core evaluation rule</fact></td>
      </tr>
      <tr>
        <td><fact id="PA-GRADLE-API" status="spec/done">Gradle `api`/`implementation`</fact></td>
        <td><fact id="PA-GRADLE-API-DIRECTION" status="spec/done">provider</fact></td>
        <td><fact id="PA-GRADLE-API-CLOSEST-PRIMITIVE-TO-OUR" status="spec/done">`public`/`private`</fact></td>
        <td><fact id="PA-GRADLE-API-VERDICT-USED-HERE" status="spec/done">the `compile`-removal precedent: leakage-by-default is unpayable</fact></td>
      </tr>
      <tr>
        <td><fact id="PA-MAVEN-EXCL" status="spec/done">Maven `&lt;exclusions&gt;` / enforcer</fact></td>
        <td><fact id="PA-MAVEN-EXCL-DIRECTION" status="spec/done">consumer edge / assertion pass</fact></td>
        <td><fact id="PA-MAVEN-EXCL-CLOSEST-PRIMITIVE-TO-OUR" status="spec/done">`exclude` / deferred deny-list</fact></td>
        <td><fact id="PA-MAVEN-EXCL-VERDICT-USED-HERE" status="spec/done">two-layer split, adopted in F4</fact></td>
      </tr>
      <tr>
        <td><fact id="PA-CARGO-1977" status="spec/done">Cargo RFC 1977 → 3516</fact></td>
        <td><fact id="PA-CARGO-1977-DIRECTION" status="spec/done">provider mark</fact></td>
        <td><fact id="PA-CARGO-1977-CLOSEST-PRIMITIVE-TO-OUR" status="spec/done">`public`</fact></td>
        <td><fact id="PA-CARGO-1977-VERDICT-USED-HERE" status="spec/done">six years lost to resolver entanglement — keep visibility a pre-filter</fact></td>
      </tr>
      <tr>
        <td><fact id="PA-NPM-EXPORTS" status="spec/done">npm `exports` map</fact></td>
        <td><fact id="PA-NPM-EXPORTS-DIRECTION" status="spec/done">provider surface seal</fact></td>
        <td><fact id="PA-NPM-EXPORTS-CLOSEST-PRIMITIVE-TO-OUR" status="spec/done">(none yet)</fact></td>
        <td><fact id="PA-NPM-EXPORTS-VERDICT-USED-HERE" status="spec/done">sub-package surfaces — deferred direction</fact></td>
      </tr>
      <tr>
        <td><fact id="PA-CPP-FRIEND" status="spec/done">C++ `friend`</fact></td>
        <td><fact id="PA-CPP-FRIEND-DIRECTION" status="spec/done">provider, deliberately non-transitive</fact></td>
        <td><fact id="PA-CPP-FRIEND-CLOSEST-PRIMITIVE-TO-OUR" status="spec/done">the anti-model</fact></td>
        <td><fact id="PA-CPP-FRIEND-VERDICT-USED-HERE" status="spec/done">what non-transitivity protects → ##CLOSURE-DRIFT-CONTROL</fact></td>
      </tr>
    </table>
    <p p="56"><fact id="PRIOR-ART-ADOPTIONS" status="spec/work">**Adopted into the model on prior-art strength:** (1) the **conjunction rule** — an edge materialises iff provider access permits ∧ consumer grant permits (Pants' "both ends must agree", Swift `@_spi`'s handshake) — §2.5 rule (3) is exactly this; (2) the **closure shape** — first hop a direct grant, then zero-or-more re-export marks — is Bazel's `deps`-then-`exports*` and JPMS implied readability, both battle-tested recursive; (3) **friend groups need no new primitive** — befriending a collection whose member edges are `friends-only` already delivers the members through the ordinary closure, giving Bazel-`package_group`-style composition for free and dodging Swift's rejected "large, complex manifests mingling all the layers"; (4) **unknown allowlist targets warn, never fail** (JPMS qualified-export precedent) — a `friends`/`unfriend` entry naming a package absent from every chain is a lint, §7; (5) **denial is legibility, not failure** — every surveyed system hard-fails because a missing symbol breaks a build; a missing prompt lane merely changes what loads, so our enforcement output is the pruning report (`vibe why`, §7), and only graph cycles remain hard errors.</fact></p>
    <p p="57"><fact id="ACCESS-IS-SEMVER-SURFACE" status="spec/work">**An access mark is versioned surface.** OSGi's verdict on `reexport` — «once you have added re-export you cannot remove it without considering the corresponding API change» — and Cargo's `public = true` semver reasoning transfer whole: widening an edge (`private → friends-only → public`) is a feature; **narrowing one is a breaking change** of the declaring package, versioned like any contract change. The publish gate (C6 family) checks access narrowing against the previous published version.</fact></p>
    <p p="58"><fact id="CLOSURE-DRIFT-CONTROL" status="spec/work">**What C++'s non-transitivity protected, restored by other means.** C++ keeps `friend` non-transitive so the set that can touch you stays enumerable *by reading one file*; our marks keep the closure enumerable but **not local** — a mid-graph package can widen `C(R)` in a patch release the root never reads. Three controls restore the audit: (a) the lock records `E(R)` with per-package lane cost, so `vibe update` surfaces closure drift as a reviewable diff («+2 packages, +3.1k static-lane tokens»); (b) ##ACCESS-IS-SEMVER-SURFACE makes silent widening a versioning violation; (c) an optional per-lane token budget cap (a PROP-048 direction) turns runaway growth into a loud stop.</fact></p>
    <p p="59"><fact id="REEXPORT-USAGE-NORM" status="spec/done" action="continue" actionstage="doc" audience="author">**Authoring norm for narrowing marks** (the JPMS community rule, transposed and then inverted by the public default): since presence flows by default, the author's duty is to **narrow deliberately** — mark `private` every edge that is not part of your consumable substance (WAL-class disciplines, dev tooling, heavy optional companions), and `friends-only` what only your inner circle should build on. Aggregator-style «one edge pulls the world» is lawful for collections, whose members are their declared substance (§4.6); everyone else answers for every public edge with its lane cost (§7 measurements make the bill legible). Advisory, policed by the §7 lints, enforced by nobody — strict-deps culture with the autofix command in place of ceremony.</fact></p>
    <p p="60"><fact id="prior-art-rejections" status="spec/work">**Deliberately not taken:** OSGi `uses:`-style implicit constraint propagation (prompt text has no class space — a budget concern must never become a global satisfiability problem); Kotlin's compilation-unit boundary (visibility binds to declared edges and coordinates, never to «whatever landed in one lane»); graph-global unification of any visibility knob (Cargo's feature-unification scar — closures are per-root by construction, §2.1, and never merge); provider-side allowlists (F8); npm-`exports`-style named sub-package surfaces — a real direction (a grant admitting one declared fragment rather than a package's whole contribution) that composes naturally with PROP-049 fragments, deferred until the package-level system has run in anger.</fact></p>
  </section>
  <section id="migration" title="6. Migration">
    <p p="61"><fact id="MIGRATION-NO-LEGACY" status="spec/done">**Owner ruling (2026-08-23) — no legacy read, no backward compatibility:** «нужно переделать существующие пакеты, никакого legacy-чтения и обратной совместимости; система, которую мы сейчас сделаем, становится основной и не откатываемой никуда; пакеты предыдущих форматов не имеют смысла (поэтому их нужно улучшить)». The strict reading — with the ruled defaults, `access = "public"` / `friend = false` (##DEFAULT-PUBLIC-RATIONALE, F3) — is the **only** reading; the implementation wave marks the tree's manifests in the same wave the machinery lands. This overrules the draft's legacy-read/ratchet proposal.</fact></p>
    <p p="62"><fact id="migration-flag-day-scope" status="spec/done">Why the §5 flag-day graveyard does not forbid this: those lessons price a default flip across an **open** ecosystem of foreign artefacts (JPMS: the world's jars; Swift: every existing package). This tree is a closed world — every manifest that exists lives in this repository, C6 publication is frozen, and there are no external consumers yet — so the «flag day» is an ordinary all-packages refactoring wave over owned files. The public presence default (##DEFAULT-PUBLIC-RATIONALE) softens it further: an unmarked manifest behaves exactly as today, so a partially-marked tree merely *keeps seeping where not yet narrowed* — degraded budget, never a broken world. The atomic wave + §7 golden snapshot remain the standard for landing cleanly, as review hygiene rather than a survival condition.</fact></p>
    <p p="63"><fact id="migration-inventory" status="impl/done">Re-marking inventory (in-wave per ##MIGRATION-NO-LEGACY; under the public default the work is **deliberate narrowing**, not blanket re-annotation): redbook's 21 member edges, stack aggregators' cores, `delegation-first → delegation-rules`, `wal-specspaces → wal`, host root edges — all correct under the default, **no marks needed**; dev-/tooling edges across the tree → explicit `private`; `git-practices` family internals → first candidate for `friends-only` dogfooding; and the door PROP-049's unbundling closed re-opens as an *option* — a redbook-like collection may again carry a wal-like member on an explicit `private`/`friends-only` edge, a separate decision for its author. The 8 world packages' `[boot_snippet] link = "static"` self-suggestions are materialisation-axis and untouched.</fact></p>
    <p p="64"><fact id="migration-shape" status="spec/done">Implementation shape (post-approval, separate wave, ask-first per Rule 4 only where its list applies): (1) `vibe-core` manifest schema — the new edge/node fields, strict semantics only; (2) `C(R)`/`E(R)` computation as a new pure module consumed by resolver + bootgen; (3) linker re-scoped to `E(R)`; (4) PROP-049 exemption narrowing; (5) package-tree/`vibe why` observability (§7); (6) re-marking of **every** package manifest in the tree (the inventory above); (7) the golden landing snapshot + measurement pass (§7). Steps 1–5 are machinery, step 6 is the flip, and the wave lands **atomically** — no commit leaves the tree half-marked (##migration-flag-day-scope). The wave-by-wave build plan elaborating this shape is [`vibevm/vibespecs/terraforms/VISIBILITY-BUILD-PLAN-v0.1.xml`](../terraforms/VISIBILITY-BUILD-PLAN-v0.1.xml).</fact></p>
  </section>
  <section id="verification" title="7. Verification and measurement — «РЕАЛЬНО ХОРОШО»">
    <p p="65"><fact id="verification-plan" status="impl/done">The owner's bar is explicit: «перестроить статические и динамические лоадеры и проверить, что всё работает РЕАЛЬНО ХОРОШО. Измерить, проверить, посмотреть на тестовых примерах». The implementation wave carries, as first-class deliverables:</fact></p>
    <list ordered="false" p="66">
      <item><fact id="VERIFY-GOLDEN-CHAIN" status="impl/done">**Golden chains.** Fixture worlds for every §2 rule — the owner's `A → B → C → D` chain verbatim (test №1), the unfriend break, the diamond-with-exclude, public-presence-without-friendship, the dev-world flip (same package as root vs as dependency) — each asserting the exact `E(R)`, lock content, and lane bytes.</fact></item>
      <item><fact id="VERIFY-BYTE-STABILITY" status="spec/done">**Determinism and the landing snapshot.** Re-runs over identical inputs are byte-identical (the PROP-038 §5 stability bar, extended to visibility inputs). Since there is no legacy mode (##MIGRATION-NO-LEGACY), the correctness artefact is the **golden snapshot of the re-marked world**: the wave captures the intended `E(R)` and lane composition per root, the one-time diff against the pre-visibility lanes is read by hand at landing (expected: WAL-class removals, zero accidental losses), and a panel cell pins the snapshot thereafter.</fact></item>
      <item><fact id="VERIFY-MEASURE" status="impl/done">**Measurement.** Before/after per-root metrics, recorded in the wave's harvest: effective-set package count, lane byte sizes, fetch/materialise counts and install wall-time. The tokenomics claim (§1) must show up as numbers, not prose.</fact></item>
      <item><fact id="VIBE-WHY" status="impl/done" action="continue" actionstage="doc" audience="user">**Observability: `vibe why &lt;group&gt;/&lt;name&gt;`.** For any package, print the chains that admit it into `E(R)` — each hop annotated with its rule ((1)/(2)/(3)), access mark, and friendship provenance — and for an absent package, the nearest blocked chains and *what* blocked them (private edge / missing friendship / unfriend / exclude). The debugging surface without which a visibility system rots into folklore; `vibe tree` gains the same annotations.</fact></item>
      <item><fact id="VERIFY-LOCK-DIFF" status="impl/done" action="continue" actionstage="doc" audience="user">**Closure-drift visibility.** The lock carries `E(R)` with each member's lane cost (bytes/tokens of its contribution); `vibe update` prints the closure diff — packages entering/leaving and the lane-cost delta — so a mid-graph re-export widening (##CLOSURE-DRIFT-CONTROL) is a reviewed event, not a silent seep.</fact></item>
      <item><fact id="VERIFY-LINTS" status="impl/done">**Hygiene lints (built as the `visibility_hygiene` cell; the per-grant lane-cost figure is deferred — BACKLOG B-103).** Dead `friends`/`unfriend`/`exclude` entries naming packages that never appear in any chain (warning, per the JPMS qualified-export precedent); friends-only edges whose declarant nobody befriends (unreachable sharing); a `friend = false` edge to a package with no friends-only edges (a no-op mark); grants whose admitted chains contribute no lane text this root ever loads — each reported with the lane cost the grant admits, so an unused or cost-heavy friendship is visible (the consumer-side mirror of Eclipse's rotting x-friends lists). Advisory, in `vibe check`.</fact></item>
    </list>
  </section>
  <section id="rollback" title="8. Rollback">
    <p p="67"><fact id="rollback-shape" status="spec/done">**By owner ruling — none.** Until implementation this document changes nothing. Once the wave lands, visibility is the primary and irreversible model — «становится основной и не откатываемой никуда» — and pre-visibility manifest reading retires with it; there is no legacy mode to fall back to, and the old formats are not preserved («пакеты предыдущих форматов не имеют смысла»). `formats/EPOCHS.toml` is untouched; the safety that replaces rollback is the atomic wave + golden snapshot of §6/§7.</fact></p>
  </section>
  <section id="forks" title="9. Open forks for the owner">
    <p p="68"><fact id="fork-list" status="spec/work">The reconstruction forks, with the owner's 2026-08-23 rulings recorded; F7–F9 remain live:</fact></p>
    <list ordered="true" p="69">
      <item><fact id="FORK-F1-INTERNAL" status="spec/done">**F1 — «internal» ≡ `access = "friends-only"`. RATIFIED** («правильный термин — friends-only»): one mark is both the seepage gate and the friendship re-export (§2.4); no separate `internal` field exists.</fact></item>
      <item><fact id="FORK-F2-NAMING" status="spec/done">**F2 — the anti-list is `unfriend`. RATIFIED**; `enemy` is retired.</fact></item>
      <item><fact id="FORK-F3-DEFAULT-FRIEND" status="spec/done">**F3 — `friend` default. RE-RULED 2026-08-23 to `false`.** The original `true` belonged to the one-field paradigm; with access and friendship separated, friendship is a raised privilege granted only explicitly («Не давать повышенных привилегий там, где они могут и не пригодиться»). Paired with the same ruling's **access default flip to `public`** (##DEFAULT-PUBLIC-RATIONALE): presence generous, friendship stingy. See F10 for the one ergonomic consequence.</fact></item>
      <item><fact id="FORK-F4-EXCLUDE-SCOPE" status="spec/done">**F4 — `exclude` is per-edge, Maven-style, arbitrarily deep within the edge's subtree. RATIFIED** («можно per-ребро делать глубокие эксклюды по иерархии — это не глобальный deny-list, это сужение в рамках поддерева»). No global deny-list. F9 may re-house the syntax inside `override`; the semantics stand.</fact></item>
      <item><fact id="FORK-F5-MIGRATION" status="spec/done">**F5 — migration. OVERRULED by the owner:** no legacy read, no backward compatibility, the system is primary and irreversible; every existing package is re-marked in the landing wave (##MIGRATION-NO-LEGACY, ##migration-flag-day-scope). The draft's autodetect+ratchet proposal is retired.</fact></item>
      <item><fact id="FORK-F6-PUBLIC-NO-FRIENDSHIP" status="spec/done">**F6 — public gives presence, not friendship. RATIFIED** («access и friend разные понятия, public даёт присутствие но не дружбу»); the composition of the two marks on one edge is pinned as ##PUBLIC-PLUS-FRIEND.</fact></item>
      <item><fact id="FORK-F7-PROP-049" status="spec/done">**F7 — PROP-049's fate. RULED 2026-08-23: keep, softened.** The gate becomes a warning system with lawful homonymy, owner-dedup and the `ignore-concept-warnings` subtree mute — ##CONCEPTS-GATE-SOFTENED carries the four rulings; ##DEPS-EXEMPTION-NARROWS joins the same genre. ##CONCEPT-GATE-DIRECTION (`when = "concept:X"`) stays a sketch, unruled.</fact></item>
      <item><fact id="FORK-F8-PROVIDER-ALLOWLIST" status="spec/done">**F8 — provider-side allowlists. RULED and design RATIFIED 2026-08-23** («спроектируй механизм allow-friends…» → design delivered as §2.8 → «годится»): node-level permits list; absent = open, `[]` = sealed, list = the circle; checked on the grant giver; rejected grant = warning; enumerable via `vibe friends`; trusted friends are non-sealed transit points; `override` breaks the seal by node-targeted entry.</fact></item>
      <item><fact id="FORK-F9-OVERRIDE" status="spec/done">**F9 — `override`: the official, QUIET break-in, lawful ANYWHERE. RULED 2026-08-23 (two passes).** First pass: deliberate developer break-in is not a threat model — «он всё равно может пойти и руками поправить любые свойства пакетов на диске», as Java's reflection era proved; languages with no sanctioned privacy story (Python) breed confusion about the intended surface; so vibevm ships an **official break-in verb instead of hacks**, and ships it **quiet** — no install-time notices («постоянный мусор… иллюзия безопасности»), pull-based provenance only. `exclude` stays a separate directive («список exclude убирать не надо»). Second pass: the draft's root-only recommendation is **overruled** — «разрешён не только в корневом манифесте, а где угодно»; the any-node semantics (path-stack masks, nearer-to-root wins, existential-over-chains, mid-graph override as versioned contract) is the body's §2.9. The subtraction-vs-expansion analysis stands: expansive entries breach ##ACCESS-IS-SEMVER-SURFACE at the breaker's own risk.</fact></item>
      <item><fact id="FORK-F10-IMPLICATION" status="spec/done">**F10 — `friends-only` implies `friend = true` on its own edge. RATIFIED 2026-08-23** (##FRIENDS-ONLY-IMPLIES-FRIEND): one mark per hop makes the chain; explicit `friend = false` beside the mark wins, producing the lawful no-vouch cell of ##ACCESS-FRIEND-MATRIX.</fact></item>
    </list>
  </section>
  <section id="history" title="10. Version history">
    <list ordered="false" p="70">
      <item><fact id="HISTORY-FINAL-RATIFICATION" status="spec/done">**2026-08-23 (fourth pass) — design closed, build planned.** F10 ratified (the implication); §2.8 `allow-friends` ratified as designed; F9 re-ruled to **any-node** override — root-only overruled, the path-stack semantics (nearer-to-root wins, existential over chains, mid-graph override is versioned contract) lands as §2.9; the worked example moves to §2.10. The access×friend matrix (##ACCESS-FRIEND-MATRIX) and the transit/use-case facts (##TRANSIT-WITHOUT-DECLARATION, ##FRIENDS-ONLY-USE-CASES) had landed between passes. Build plan authored: `vibevm/vibespecs/terraforms/VISIBILITY-BUILD-PLAN-v0.1.xml`; implementation awaits the owner's start word.</fact></item>
      <item><fact id="HISTORY-DEFAULTS-RECAST" status="spec/done">**2026-08-23 (third pass) — defaults recast, remaining forks ruled.** Owner re-rulings: `access` defaults **public** (##DEFAULT-PUBLIC-RATIONALE — loaders must not silently lose what was always available; the observability asymmetry seals it), `friend` defaults **false** (F3 re-ruled — friendship is a raised privilege; presence generous, friendship stingy). F7 ruled: the concepts gate softens to warnings with lawful homonymy, owner-dedup and `ignore-concept-warnings` (##CONCEPTS-GATE-SOFTENED). F8 ruled: `allow-friends` designed (§2.8) — sealed-`permits` shape, override-breakable. F9 ruled in substance: the override is official and **quiet** (no install notices; pull-based provenance only); `exclude` stays separate; root-only stands as recommendation. F10 filed (friends-only implies its own grant). Migration softened accordingly: an unmarked tree seeps as today instead of breaking (##migration-flag-day-scope); the wave's work is deliberate narrowing.</fact></item>
      <item><fact id="HISTORY-RATIFIED-CORE" status="spec/done">**2026-08-23 (later the same day) — core ratified by the owner.** F1 (friends-only ≡ the re-export mark), F2 (`unfriend`), F3 (`friend = true` default on own edges), F4 (per-edge deep-subtree `exclude`) and F6 (access ⊥ friendship; ##PUBLIC-PLUS-FRIEND added) ruled as drafted. F5 **overruled**: no legacy read, no backward compatibility, irreversible — ##MIGRATION-NO-LEGACY replaces the autodetect+ratchet proposal; §7/§8 re-anchored to the atomic wave + golden snapshot. F7 (concepts mechanics + ##CONCEPT-GATE-DIRECTION) and F8 (allow-list mechanics) explained, rulings pending; F9 (`override`, the root-side sanctioned break-in) filed with the subtraction-vs-expansion analysis.</fact></item>
      <item><fact id="HISTORY-DRAFTED" status="spec/work">**2026-08-23 — drafted (owner-ordered).** The visibility model: per-edge `access` (public / private-default / friends-only), consumer-declared friendship (per-edge flag + node list), the friend closure as a least fixpoint over friends-only re-exports, `unfriend` (node-scoped) and `exclude` (edge-scoped) growth controls, root-driven materialisation. Layering: visibility above materialisation (owner ruling); the `link` axis survives re-scoped to the effective set. Interactions: resolver/lock/vibedeps on `E(R)` only; PROP-049 kept re-scoped with the deps-exemption narrowed; the dev-world reading resolves the equipotence wave's open tail. Migration by per-manifest legacy read; verification plan with golden chains, byte-stability, measurement, and `vibe why`. Prior-art synthesis and §9 forks await the owner.</fact></item>
    </list>
  </section>
</spec>
