Tenancy and layers¶
DNA separates what a scope declares from who overrides it and how. Two
orthogonal mechanisms do that work: layers (overlays that override a
base) and tenants (a first-class dimension for multi-tenant deployments).
A LayerPolicy governs
which overrides are allowed.
This page is the conceptual overview. The mechanics of storing overlays live in the source adapters — see How to write a source adapter.
Scopes and the shared library¶
A scope is a directory of manifests — the unit you load with
Kernel.quick(scope). Scopes are not islands: every scope can inherit shared
documents from a sibling .dna/_lib/ library scope. Put an agent, skill
or theme in _lib and every scope sees it, unless the scope overrides it.
This is the base of the override model: _lib provides shared defaults; a
scope specialises them.
Layers — overlays over a base¶
A layer is an overlay: a set of documents that override the base for some dimension without editing the base. The base stays the shared product; a layer carries only the diffs.
The canonical use is a tenant overlay — a per-tenant set of overrides
composed on top of the shared base at read time. The adapter resolves a
load_layer(tenant) view that merges the tenant's overrides over the base;
the base document is never mutated. A tenant that overrides nothing sees the
base unchanged.
The merge is by (kind, name) — an overlay document shadows its base twin,
everything else passes through:
flowchart LR
subgraph base ["base scope"]
B1["Agent greeter"]
B2["Skill review"]
end
subgraph overlay ["tenant overlay"]
O1["Agent greeter (override)"]
end
B1 --> M{"merge by<br/>(kind, name)"}
B2 --> M
O1 -->|shadows base| M
M --> V["tenant view<br/>greeter (overlay) + review (base)"]
Tenants — a first-class dimension¶
Tenancy is orthogonal to layers, not a special case of them. DNA models
it with its own Kinds under the tenant/v1 namespace:
| Kind | apiVersion | What it is |
|---|---|---|
Tenant |
github.com/ruinosus/dna/tenant/v1 |
A first-class tenant identity |
TenantMembership |
github.com/ruinosus/dna/tenant/v1 |
Who belongs to which tenant |
Because tenant is a kernel dimension rather than a naming convention, a tenant overlay for one scope does not leak into another — the base for a scope belongs to that scope, and each tenant sees the base plus its own diffs.
LayerPolicy — which layers may override which Kinds¶
Not every Kind should be overridable by every layer. A LayerPolicy
(github.com/ruinosus/dna/policy/v1 · LayerPolicy) declares which layers may
override which Kinds — the guardrail on the override model. It is data, like
everything else: a policy document, validated and versioned.
The maxim: inheritable ⇒ never per-tenant-only¶
A design invariant worth stating plainly: a Kind that is an inheritable
default — one a scope inherits from _lib and may override — must be
writable at the shared base. Reading such a Kind promises a base default that
overlays can specialise; a storage mode that forbids writing that base would
contradict the read contract. So inheritable Kinds use a permissive or
global tenancy model, never a strictly per-tenant one. Per-tenant-only
storage is reserved for data that has no shared default (audit logs, per-user
profiles, and the like).
Where to go next¶
- The microkernel and its five ports — where source adapters (and their layer support) plug in.
- How to write a source adapter — the per-tenant overlay capability in the port contract.
- Kinds — the identity model these dimensions apply to.