# Видимость зависимостей: public, private и друзья {#root}

@status:doc/work @audience:user,author

[p01] У пакета, от которого вы зависите, есть собственные зависимости, и они тоже могут дотянуться до вашего проекта. Дотянутся ли, решается там, где объявлена каждая зависимость, построчно: получают все, не получает никто или только проекты, которые назвали объявляющий пакет другом. Эта страница объясняет три метки, дружбу, которая открывает среднюю из них, что в результате оказывается в вашем дереве и как спросить vibe, почему пакет тут есть или почему его нет.

[p02] Example `why-wal` is copied from the source page at projection time.

## Три метки на требовании {#three-marks}

[p03] Каждая строка под `[requires.packages]` — ребро от вашего [пакета](../glossary/index.xml#package) к тому, который она называет, и ребро несёт необязательную метку `access`. Метка — слово объявляющего пакета о его собственной зависимости: как далеко вверх ей можно просачиваться, к проектам, которые зависят от объявляющего пакета.

> [p04] 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`:
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#ACCESS-LEVELS>

[p05]
```toml
[requires.packages]
"org.example/style" = "^1.0"
"org.example/wal" = { version = "^2.0", access = "private" }
"org.example/inner" = { version = "^1.0", access = "friends-only" }
```

[p06] `public` — умолчание, и писать его не нужно: зависимость достигает каждого потребителя над вами, как бы высоко он ни стоял, и никому не нужно ничего включать. Это правильная метка для всего, на чём строится ваш собственный текст, и для участников коллекции.

> [p07] `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.
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#ACCESS-PUBLIC>

[p08] `private` останавливает зависимость у вашей двери. По ребру идут, только когда ваш пакет сам корень установки, как в вашем собственном чекауте. Это метка для инструментов и для дисциплин, которые определяют, как вы работаете, а не что вы поставляете.

> [p09] `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.
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#ACCESS-PRIVATE>

[p10] `friends-only` стоит между ними. Зависимость достигает только потребителей, чьё [замыкание друзей](../glossary/index.xml#friend-closure) содержит ваш пакет: тех, кто намеренно назвал вас другом, напрямую или через своего друга.

> [p11] `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.
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#ACCESS-FRIENDS>

[p12] Поскольку присутствие течёт по умолчанию, долг автора — сужать намеренно. Помечайте `private` каждое ребро, которое не входит в то, что вы предлагаете, и `friends-only` — то, на чём должен строиться только ваш ближний круг. Каждое публичное ребро стоит вашим потребителям бюджета чтения, и [лок-файл](../glossary/index.xml#lock-file) делает этот счёт видимым.

> [p13] **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.
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#REEXPORT-USAGE-NORM>

## Дружба: согласие потребителя {#friendship}

[p14] Дружбу объявляет потребитель, и никогда она не подразумевается. На ребре `friend = true` говорит: «я вступаю в дружбу с этим пакетом»; в секции `[visibility]` `friends = ["org.example/inner"]` говорит то же о пакете, который вы напрямую не требуете. По умолчанию `false`: обычное ребро берёт пакет, а не его ближний круг. Присутствие щедро, дружба скупа, и два умолчания различаются намеренно.

> [p15] **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.
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#FRIEND-EDGE-FLAG>

[p16] Метка `friends-only` на вашем собственном ребре подразумевает дружбу с пакетом, который оно называет, так что цепочка друзей работает от одной метки на переход. Напишите рядом `friend = false`, когда хотите доставить пакет своему кругу, не входя в его круг сами.

> [p17] **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.
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#FRIENDS-ONLY-IMPLIES-FRIEND>

[p18] Друг ничего не объявляет о том, что получает. Когда вы дружите с пакетом, всё, за что он ручается, приходит без строки в вашем [манифесте](../glossary/index.xml#manifest), в версиях, которые выбрал поручитель, и ребра friends-only этих пакетов открываются в свою очередь. В тот момент, когда ваш собственный текст начинает строиться на одном из них, объявите собственное прямое ребро: транзит говорит «здесь, потому что на этом стоит друг», а не «моё».

> [p19] **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.
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#TRANSIT-WITHOUT-DECLARATION>

[p20] Две метки независимы и указывают в противоположные стороны, поэтому сочетаются. Публичное ребро с `friend = true` доставляет пакет всем над вами и открывает его двери friends-only только для вас; эта дружба дальше не едет. Передавать дружбу дальше — ровно то, что делает метка `friends-only`.

> [p21] **`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.
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#PUBLIC-PLUS-FRIEND>

## Секция visibility, unfriend и exclude {#the-visibility-section}

[p22] Всё, что касается пакета в целом, а не одного ребра, живёт в одной секции, `[visibility]`, одинаковой для проекта и для пакета: `friends`, `unfriend`, `allow-friends` и `ignore-concept-warnings`. Рядом с ней стоит таблица верхнего уровня `[override]`, описанная ниже.

> [p23] **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]`.
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#VISIBILITY-SECTION>

[p24]
```toml
[visibility]
friends = ["org.example/inner"]
unfriend = ["org.example/noisy"]
allow-friends = ["org.example/partner"]
```

[p25] `unfriend` убирает названные пакеты из дружб, которые вы передаёте дальше. Они по-прежнему приходят на ваш уровень, когда их допускает ребро, но никто не видит их вашими друзьями через вас. Другой пакет в том же дереве всё ещё может с ними дружить; обрезка только ваша.

> [p26] `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.
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#UNFRIEND-IS-NODE-SCOPED>

[p27] `exclude`, записанный на ребре, — другая обрезка: названные пакеты вырезаются из каждой цепочки, проходящей через это ребро, как бы глубоко они ни были, даже там, где они публичны. Другой путь всё ещё может их доставить, и тогда они присутствуют через него. Исключение может только сузить то, что предоставили объявляющие пакеты, и никогда не расширить, поэтому объявить его может любой пакет.

> [p28] `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).
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#EXCLUDE-IS-EDGE-SCOPED>

[p29] Пакет может и сказать, кому разрешено с ним дружить. Отсутствующий `allow-friends` значит «кому угодно»; пустой список запечатывает пакет, так что его закрытое содержимое существует только в его собственном чекауте; список называет точный круг. Проверка ложится на того, кто объявляет дружбу: объявление, которого пакет не разрешает, — предупреждение, а не ошибка, и замыкание там не растёт.

> [p30] **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 — «годится»):
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#ALLOW-FRIENDS-DESIGN>

> [p31] 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.
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#ALLOW-FRIENDS-STATES>

> [p32] 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.
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#ALLOW-FRIENDS-CHECKPOINT>

[p33] Печать закрывает дружбу, а не доставку. Запечатанный пакет всё равно приходит туда, куда его доставляет ребро friends-only; закрытым остаётся его собственный ближний круг, а отвергнутое объявление попадает в отчёт. `vibe friends org.example/partner` печатает полную картину по одному пакету: открыт, запечатан или назван круг, кто с ним дружит, какие объявления он отвергает и входит ли он в ваше замыкание.

> [p34] **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).
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#SEAL-GATES-FRIENDSHIP-NOT-DELIVERY>

## Починить ребро, которым вы не владеете {#overrides}

[p35] Иногда нужная вам метка стоит на ребре, которым вы не владеете: участник коллекции пометил зависимость как private, а она нужна вашим потребителям, или пакет запечатался от вас. Таблица `[override]` переписывает чужие ребра, и она законна в любом манифесте, в корне или ниже. Агрегатор пользуется ею, чтобы перекроить ребро участника для всех своих потребителей, так же как курирует свою доставку через `exclude`.

> [p36] **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).
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#OVERRIDE-ANYWHERE>

[p37]
```toml
[override]
"org.example/member -> org.example/wal" = { access = "public" }
"org.example/partner" = { allow-friends = "*" }
```

[p38] Ключ-ребро называет оба конца ребра стрелкой; ключ-пакет называет один пакет и переписывает его `allow-friends`. [Переопределения](../glossary/index.xml#override) применяются вдоль цепочек, которые проходят через объявивший их манифест. То, что ближе к корню, применяется позже и побеждает, так что за корнем всегда последнее слово, а посредник побеждает только на цепочках, в которых участвует.

> [p39] **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).
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#OVERRIDE-PATH-SEMANTICS>

[p40] Тот же ключ, `override`, обслуживает и старую форму-массив `[[override]]`, которая закрепляет замену источника для одной [координаты](../glossary/index.xml#coordinate). Две формы различаются по виду, каждая по отдельности законна, а манифест с обеими сразу — громкая ошибка, а не слияние.

> [p41] **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.
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#OVERRIDE-KEY-COEXISTENCE>

## Что приходит в ваше дерево {#what-arrives}

[p42] Пакеты, которые метки допускают от вашего корня, образуют [действующее множество](../glossary/index.xml#effective-set), и это единственное множество, с которым работает vibe. Разрешение версий идёт только по нему: приватное ребро пакета, который не ваш корень, не вносит ограничения, ничего не скачивает и не может конфликтовать. Лок-файл записывает действующее множество, так что ваш лок никогда не несёт чужих инструментов.

> [p43] 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.
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#RESOLVE-EFFECTIVE-ONLY>

[p44] Дерево зависимостей под `vibedeps/` держит ровно это множество. Исключённый или невидимый пакет не оставляет ни папки, ни записи в кэше для вашего мира, ни текста в [стартовой полосе](../glossary/index.xml#boot-lane).

> [p45] `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.
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#MATERIALISE-EFFECTIVE-ONLY>

[p46] По вашим собственным ребрам идут всегда, включая приватные. Так один `[requires]` служит и вашим набором для разработки, и вашим контрактом, разделённым по ребрам, а не по секциям: в вашем чекауте приватные инструменты материализуются; когда вас потребляют как зависимость, материализуются только просачивающиеся ребра.

> [p47] 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.
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#PRIVATE-IS-THE-DEV-WORLD>

[p48] Поскольку метка в середине графа может расширить то, что до вас доходит, `vibe update` печатает изменение действующего множества: какие пакеты входят или выходят и сколько бюджета чтения они добавляют или снимают. Расширение — событие для ревью, а не тихое просачивание.

> [p49] **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.
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#VERIFY-LOCK-DIFF>

## Спросить почему {#asking-why}

[p50] `vibe why` отвечает на вопрос, который система видимости иначе превращает в фольклор. Для присутствующего пакета она печатает цепочку, которая его допускает, каждый переход с его правилом, меткой доступа и источником дружбы. Для отсутствующего — ближайшие перекрытые цепочки и что перекрыло каждую: приватное ребро, отсутствие дружбы, unfriend или exclude. `vibe tree` несёт те же пометки на каждом узле.

> [p51] **Observability: `vibe why <group>/<name>`.** 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.
>
> <spec://org.vibevm.core/vibevm/common/PROP-050#VIBE-WHY>

## Особые случаи и правила {#edge-cases}

[p52] Пакет, достижимый двумя путями, присутствует, как только один путь его допускает; exclude на другом пути ничего не меняет. Исключение — вычитание, и объявлять его можно где угодно; расширение — переопределение, и корень всегда может переопределить посредника заново. Дружба, unfriend и печать меняют, какие цепочки открыты, но никогда не меняют, какие версии выбраны: версия пакета, за который поручились, остаётся за поручителем.

