Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Tenancy Model

A team means there is no single "the AST." Each developer sits at some branch/commit plus an uncommitted working delta, and those deltas diverge. Yupana resolves this with shared base + per-tenant overlay (copy-on-write).

  • Shared base. The full structural graph is computed once at a baseline commit and held read-only in memory.
  • Per-tenant overlay. Each session gets a lightweight overlay: only touched files are re-parsed, only affected edges recomputed, layered over the base. Queries resolve against base + overlay. An overlay is invisible to other tenants — isolation is automatic.
  • Content-hash sharing. With structural sharing, N developers cost one base plus N small deltas, not N full graphs.

Blast radius is the incremental-update primitive

The overlay is not just the edited file — it is the edited file plus its frontier. When a signature changes, every reference to it (possibly in files the developer never opened) now has different facts. So the updater queries the base graph for the references and dependents of the changed symbols — the same reachability query that answers "what does this change affect?" for a consumer.

One primitive, two uses — build it once.

See the Specification §5.5 for the frontier algorithm, eviction policy, and high-fan-in handling.

Implementation state

The engine above exists as a library (yupana #2): graph::Base (shared, read-only, Arc-shared, built at a resolved commit with per-file content hashes), graph::Overlay (owned re-parses of touched files only — O(touched), never O(repo)), and graph::TenantRegistry / graph::TenantView (the per-query base + overlay composition, walked by the same FR-12 BFS as every other graph). Isolation is structural: a view composes exactly one tenant's overlay, the base is immutable, and interned parses are shared by content hash (FR-15) without sharing any view state. The tests/overlay_isolation_tests.rs suite pins §6.3 absolute isolation, masking/revert/deletion, and the cost shape.

The resident daemon wires this live: it holds the registry (base at the startup HEAD), POST /edit is the FR-30 feed (the post-edit hook calls it per save), query endpoints take tenant=, and /status reports the base commit and active overlays.

The FR-16 frontier is graph::update_frontier (yupana #3): editing a symbol perturbs its transitive callers AND callees, so the recompute is bounded to that reachable frontier — the second caller of the one reachable() BFS (FR-12), never a second traversal. The base also keeps a call-site index (callers_of_name) keyed by callee name, which is what lets an overlay-NEW symbol (one with zero base definitions) find its base callers — the case a naive per-file update misses.

On-disk edits drive this automatically (FR-17, yupana #5): a notify watcher (watch::OverlayRefresh), .gitignore-filtered and debounced, touches the tenant's overlay on the fast tier and runs update_frontier on the deferred heavy tier, tracking per-file freshness (recomputing while the frontier is pending, fresh after).

Overlay lifecycle (FR-18/§14.2, yupana #6) is the registry's job: sessions open on first touch and close explicitly (close_session), reset clears a tenant to base, and live overlays are capped at [yupana.tenancy].max_overlays — a new overlay past the cap evicts one per overlay_eviction (lru, or oldest-created as the on_session_close backstop), always logged. A symbol whose direct fan-in exceeds high_fanin_threshold has its frontier cascade clipped to one hop, so a widely-referenced signature edit cannot blow the recompute budget — also logged, never a silent truncation. This completes Phase 3.